Redis implementation, redisson and handwriting of distributed locks

Redis of distributed lock

Redisson

Introduce redission:

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.12.4</version>
</dependency>

The lock tool class RLock has been encapsulated in RedissonClient, and I use it directly here:

package com.morris.distribute.lock.redis.redisson;

import com.morris.distribute.entity.Order;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class OrderService {
    
    

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Autowired
    private RedissonClient redissonClient;

    /**
     * 分布式锁之redis(redisson实现)
     *
     * @param id
     */
    public void updateStatus(int id) {
    
    
        log.info("updateStatus begin, {}", id);

        String key =  "updateStatus" + id;

        RLock lock = redissonClient.getLock(key);
        lock.lock(); // 加锁
        try {
    
    
            Integer status = jdbcTemplate.queryForObject("select status from t_order where id=?", new Object[]{
    
    id}, Integer.class);

            if (Order.ORDER_STATUS_NOT_PAY == status) {
    
    

                try {
    
    
                    // 模拟耗时操作
                    TimeUnit.SECONDS.sleep(3);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }

                int update = jdbcTemplate.update("update t_order set status=? where id=? and status=?", new Object[]{
    
    2, id, Order.ORDER_STATUS_NOT_PAY});

                if (update > 0) {
    
    
                    log.info("updateStatus success, {}", id);
                } else {
    
    
                    log.info("updateStatus failed, {}", id);
                }
            } else {
    
    
                log.info("updateStatus status already updated, ignore this request, {}", id);
            }
            log.info("updateStatus end, {}", id);
        } finally {
    
    
            lock.unlock(); // 释放锁
        }
    }

}

The results are as follows:

2020-09-16 14:43:20,778  INFO [main] (Version.java:41) - Redisson 3.12.4
2020-09-16 14:43:21,298  INFO [redisson-netty-2-16] (ConnectionPool.java:167) - 1 connections initialized for 10.0.4.211/10.0.4.211:6379
2020-09-16 14:43:21,300  INFO [redisson-netty-2-19] (ConnectionPool.java:167) - 24 connections initialized for 10.0.4.211/10.0.4.211:6379
2020-09-16 14:43:21,371  INFO [t2] (OrderService.java:29) - updateStatus begin, 1
2020-09-16 14:43:21,371  INFO [t1] (OrderService.java:29) - updateStatus begin, 1
2020-09-16 14:43:21,371  INFO [t3] (OrderService.java:29) - updateStatus begin, 1
2020-09-16 14:43:24,610  INFO [t3] (OrderService.java:51) - updateStatus success, 1
2020-09-16 14:43:24,610  INFO [t3] (OrderService.java:58) - updateStatus end, 1
2020-09-16 14:43:24,620  INFO [t1] (OrderService.java:56) - updateStatus status already updated, ignore this request, 1
2020-09-16 14:43:24,620  INFO [t1] (OrderService.java:58) - updateStatus end, 1
2020-09-16 14:43:24,630  INFO [t2] (OrderService.java:56) - updateStatus status already updated, ignore this request, 1
2020-09-16 14:43:24,630  INFO [t2] (OrderService.java:58) - updateStatus end, 1

Realization of jedis

Let's manually implement redis distributed locks through jedis, and have a deeper understanding of the principle of redis implementing distributed locks.

Introduce jedis:

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.2.0</version>
</dependency>

The key SET key value NX PX milisecondscommand: .

What is a successful lock? Whoever calls the set command with the nx option, the key does not exist, the setting succeeds, otherwise it fails, and whoever sets the key successfully will get the lock.

What should I do if the client hangs up? You can set a timeout period for the key. If the client hangs up after it is locked, the key will be deleted at the end of the time without causing a deadlock.

What if the key is about to expire and the business has not been processed within the timeout period? Start a thread as the key to delay.

The specific implementation is as follows:

package com.morris.distribute.lock.redis.my;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.params.SetParams;

import java.util.Collections;
import java.util.concurrent.TimeUnit;

@Slf4j
public class RedisLock {
    
    

    @Autowired
    private JedisPool jedisPool;

    public void lock(String key, String value) {
    
    
        for (; ;) {
    
     // 自旋获取锁
            if (tryLock(key, value)) {
    
    
                return;
            }
            try {
    
    
                TimeUnit.MILLISECONDS.sleep(100); // 这里暂时休眠100ms后再次获取锁,后续可以向AQS一样使用等待队列实现
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }
    }

    /**
     * 尝试加锁
     *
     * @param key
     * @param value
     * @return
     */
    private boolean tryLock(String key, String value) {
    
    
        SetParams setParams = SetParams.setParams().nx().px(4_000); // 默认超时时间为4s
        Jedis jedis = jedisPool.getResource();
        String result = jedis.set(key, value, setParams);
        if ("OK".equals(result)) {
    
    
            Thread thread = new Thread(() -> {
    
    
                while (true) {
    
    
                    try {
    
    
                        TimeUnit.SECONDS.sleep(1); // 守护线程1s检测一下超时时间
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }
                    Long ttl = jedis.pttl(key);
                    if (ttl < 2_000) {
    
     // 当超时时间小于1/2时,增加超时时间到原来的4s
                        jedis.expire(key, 4_000);
                        log.info("add expire time for key : {}", key);
                    }
                }
            }, "expire1");
            thread.setDaemon(true);
            thread.start();
            return true;
        }
        return false;
    }

    public void unlock(String key, String value) {
    
    
        Jedis jedis = jedisPool.getResource();
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        jedis.eval(script, Collections.singletonList(key), Collections.singletonList(value));
    }

}

The usage is as follows:

package com.morris.distribute.lock.redis.my;

import com.morris.distribute.entity.Order;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;

import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class OrderService {
    
    

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Autowired
    private RedisLock redisLock;

    /**
     * 分布式锁之redis(jedis实现)
     *
     * @param id
     */
    public void updateStatus(int id) {
    
    
        log.info("updateStatus begin, {}", id);

        String key =  "updateStatus" + id;
        String value = UUID.randomUUID().toString();

        redisLock.lock(key, value);
        try {
    
    
            Integer status = jdbcTemplate.queryForObject("select status from t_order where id=?", new Object[]{
    
    id}, Integer.class);

            if (Order.ORDER_STATUS_NOT_PAY == status) {
    
    

                try {
    
    
                    // 模拟耗时操作
                    TimeUnit.SECONDS.sleep(3);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }

                int update = jdbcTemplate.update("update t_order set status=? where id=? and status=?", new Object[]{
    
    2, id, Order.ORDER_STATUS_NOT_PAY});

                if (update > 0) {
    
    
                    log.info("updateStatus success, {}", id);
                } else {
    
    
                    log.info("updateStatus failed, {}", id);
                }
            } else {
    
    
                log.info("updateStatus status already updated, ignore this request, {}", id);
            }
            log.info("updateStatus end, {}", id);
        } finally {
    
    
            redisLock.unlock(key, value); // 释放锁
        }
    }
}

The results are as follows:

2020-09-16 16:19:01,453  INFO [t2] (OrderService.java:28) - updateStatus begin, 1
2020-09-16 16:19:01,453  INFO [t1] (OrderService.java:28) - updateStatus begin, 1
2020-09-16 16:19:01,453  INFO [t3] (OrderService.java:28) - updateStatus begin, 1
2020-09-16 16:19:03,565  INFO [expire1] (RedisLock.java:53) - add expire time for key : updateStatus1
2020-09-16 16:19:04,748  INFO [t1] (OrderService.java:49) - updateStatus success, 1
2020-09-16 16:19:04,749  INFO [t1] (OrderService.java:56) - updateStatus end, 1
2020-09-16 16:19:04,801  INFO [t2] (OrderService.java:54) - updateStatus status already updated, ignore this request, 1
2020-09-16 16:19:04,801  INFO [t2] (OrderService.java:56) - updateStatus end, 1
2020-09-16 16:19:04,902  INFO [t3] (OrderService.java:54) - updateStatus status already updated, ignore this request, 1
2020-09-16 16:19:04,902  INFO [t3] (OrderService.java:56) - updateStatus end, 1

Why can not two steps, first set key valueagain expire key millseconds? Because these two operations are not atomic operations, if a client hangs up after it is locked, the key will never be deleted, causing a deadlock.

Why start a daemon thread to delay the key? The daemon thread will be automatically destroyed when the thread that created it is closed, without manual shutdown. The delay is to allow the execution of the business logic to complete, and to prevent the key from expiring and allowing other threads to grab the lock.

Why not send the del keycommand directly when releasing the lock ? When releasing the lock, you need to verify the value to prevent the lock added by process P1 from being released by other processes. Therefore, the setting of the value value is also exquisite. Only process P1 knows this value, so that only he can delete the key when it is released.

to sum up

Advantages: Distributed locks based on redis will have the characteristics of redis, that is, fast.

Disadvantages: The implementation logic is complex. Redis itself is an AP model, which can only guarantee network partition and availability, but cannot guarantee strong consistency. The distributed lock logic is a CP model and consistency must be guaranteed, so redis is an implementation method In a certain probability, multiple clients will acquire the lock. For example, the master node in redis successfully sets the key and returns it to the client. At this time, it hangs before it can synchronize to the slave, and then the slave is elected as the new master node. , Other clients will succeed in acquiring the lock, so that multiple clients can acquire the lock at the same time.

Guess you like

Origin blog.csdn.net/u022812849/article/details/108645002