No title

Xiao Ming works in an e-commerce platform to develop orders. The company has only one server, one service deployed, and his code reads like this:

    synchronized (this) {// Business operation: operation database to reduce inventory
    }
Copy the code

In a multithreaded environment, this code works fine, with no problems. As the company grew rapidly, servers became clustered, with multiple services deployed. The previous code is problematic because synchronized is only guaranteed to be thread-safe in one JVM. In multiple JVMS, this deduction of inventory can be a security issue. Ming came up with the idea of using REIDS in a distributed environment. His code reads as follows:

    private void process(a){

        String prodKey = "prod-001";
        try(Jedis jedis = getJedis()){

            Long setnx = jedis.setnx(prodKey, "1111");
            if(setnx == 0) {// If the configuration is not successful, it is occupied
                return;
            }

            try {
                // Business operation: operation database to reduce inventory
            } finally{ jedis.del(prodKey); }}}Copy the code

At first glance, there is no problem. Use setnx to set a key. If it exists and fails to set, it means it has been occupied. The problem is that if a server hangs in the middle of a program execution, the lock still cannot be released, resulting in a deadlock. Xiao Ming knows, simple, and a version as follows:

    private void process(a){

        String prodKey = "prod-001";
        try(Jedis jedis = getJedis()){

            Long setnx = jedis.setnx(prodKey, "1111");
            // Add expiration time, automatically invalid
            jedis.expire(prodKey, 30);
            if(setnx == 0) {// If the configuration is not successful, it is occupied
                return;
            }

            try {
                // Business operation: operation database to reduce inventory
            } finally{ jedis.del(prodKey); }}}Copy the code

I just add the expiration date, there’s a problem; If thread A executes jedis. Del (prodKey), thread A releases the lock on thread B. If thread A executes jedis. Del (prodKey), thread B releases the lock on thread C. Xiao Ming knows, simple, and a version as follows:

    private void process(a){

        String prodKey = "prod-001";
        String clientId = getMachineNum() + UUID.randomUUID().toString();
        try(Jedis jedis = getJedis()){

            Long setnx = jedis.setnx(prodKey, clientId);

            if(setnx == 0) {// If the configuration is not successful, it is occupied
                return;
            }

            // Add expiration time, automatically invalid
            jedis.expire(prodKey, 30);

            try {
                // select * from database
            } finally {
                if(clientId.equals(jedis.get("prodKey"))) {// Delete only your own keyjedis.del(prodKey); }}}}Copy the code

I added a unique identifier clientId of this machine. When deleting, I will judge whether it is consistent and delete it if it is consistent, so as not to delete it by mistake. There is another problem with the previous one. What if thread A runs past its expiration date? The lock timeout is automatically released, and it is still the same as if no lock had been done, so much useless work. Xiao Ming knows, simple, and a version as follows:

    private void process(a){

        String prodKey = "prod-001";
        String clientId = getMachineNum() + UUID.randomUUID().toString();
        try(Jedis jedis = getJedis()){

            Long setnx = jedis.setnx(prodKey, clientId);

            if(setnx == 0) {// If the configuration is not successful, it is occupied
                return;
            }

            // Add expiration time, automatically invalid
            jedis.expire(prodKey, 30);
            // Watchdog, keep the lock alive
            new Thread(new RenewalThread(jedis, prodKey)).start();

            try {
                // select * from database
            } finally {
                if(clientId.equals(jedis.get("prodKey"))) {// Delete only your own keyjedis.del(prodKey); }}}}class RenewalThread implements Runnable{

        Jedis jedis;
        String prodKey;
        boolean exist;

        public RenewalThread(Jedis jedis, String prodKey) {
            this.jedis = jedis;
            this.prodKey = prodKey;
            this.exist = true;
        }

        @Override
        public void run(a) {
            
            while(exist){
                if(jedis.exists(prodKey)){
                    jedis.expire(prodKey, 30);
                }else{
                    exist = false;
                }
                try {
                    Thread.sleep(10000);
                } catch(InterruptedException e) { e.printStackTrace(); }}}}Copy the code

Most of these problems are now solved, but there is a fatal problem; Multiple Redis commands are non-atomic operations. In the code there is setnx first, then expire; Check before deleting and so on. These are all potential security issues. Lua script parsing was added in redis2.6. Lua supports multiple commands and transactions. We just switch to lua scripts on those operations. At this point, the REIDS distributed lock has been minimized. When you finish the above steps, you look at Redisson and immediately think redisson smells good. He helped us do all the above steps well and make them better. After using Redisson:

    RedissonClient redissonClient = Redisson.create(new Config());
    
    String prodKey = "prod-001";
    / / acquiring a lock
    RLock lock = redissonClient.getLock(prodKey);
    / / lock
    lock.lock();
    
    try {
        // select * from database
    } finally {
        / / unlock
        lock.unlock();
    }
Copy the code

The actual code only has 3 steps, so with that in mind, go ahead and use Redisson.