Sike java synchronized series of evolutionary history redis Distributed Lock

problem

How to (1) redis achieve Distributed Lock?

What are the advantages (2) redis distributed locks?

(3) redis distributed lock what are the disadvantages?

(4) redis realize there is no existing distributed lock the wheels can be used?

Brief introduction

Redis (full name: Remote Dictionary Server remote dictionary service) is an open source use written in ANSI C, support network, based on the persistence of memory can log type, Key-Value database, and provides multi-lingual API.

In this chapter we will describe how to (test based redis implementation of distributed lock, and the evolutionary history of its implementation from start to finish clearly explained, so that we can speak during the interview clearly redis distributed lock to (suddenly) Long (Yau) ) pulse (official).

Achieve lock conditions

Based on the above study on lock (distributed lock), we know that there are three conditions to achieve lock:

(1) state (Shared) variable, which is the state, whether the value of the identification of this state has been locked, is achieved by controlling the value of the state in ReentrantLock in ZookeeperLock is achieved by controlling the child node ;

(2) queue, which queue is used to store the thread, is achieved by AQS ReentrantLock in the queue, it is achieved by ordering the child nodes in ZookeeperLock;

(3) wake, wake up the next waiting thread on a thread releases the lock after the automatic wake-up a thread incorporated in the AQS ReentrantLock release queue, in ZookeeperLock sensing mechanism through which is realized;

Then the above three conditions is not necessary for it?

In fact, a necessary condition is not true, only the first realization of a lock on the shared variable control, if the shared variable is null gave him a set value (java can be used within the CAS operation process shared variables), if there is a value shared variable then check whether there repeated values ​​(retry), a null value is set back to be locked within the logic then completes the shared variable.

To put it plainly, as long as there is a place to store the shared variable on the line, but also to ensure that the entire system (multiple processes) only this one can be.

This is the key to achieve a distributed lock redis [This article by the public number "Tong brother read the source" original].

redis distributed lock evolutionary history

The evolutionary history of a --set

Now it says the implementation of distributed shared variables only need to lock in place to control, then redis how we control the shared variables?

First, we know redis base command has get / set / del, distributed lock can be achieved by these three commands do? of course can.

repeat

Before acquiring the lock to get lock_user_1see the latch does not exist, if it does not exist then set lock_user_1 value, if there is to wait a while and try again, and finally completed the re-use delete this lock del lock_user_1can be.

repeat

However, this scheme has a problem, if you start this lock is not there, go get two threads at the same time, this time the return is null (nil), then these two threads to set, this time out problem, two threads can be set successfully, the equivalent of two threads to acquire the same lock.

Therefore, this solution is not feasible!

The evolutionary history of two --setnx

The main reason for the above program is not feasible multiple threads simultaneously set all be successful, so then with setnxthis command, which is set if not existthe abbreviation, that is, if there is no set.

repeat

It can be seen when a key is repeated for the same conduct setnx, only the first is successful.

Therefore, the second scheme is to use the setnx lock_user_1 valuecommand returns 1 if it means locking is successful, returns 0 if it means that other threads to execute successful, it would retry after waiting for some time, and finally use the same del lock_user_1release the lock.

repeat

However, this program also has a problem if the client to obtain the lock broken how to do? This is not always lock will not release it? Yes, it is this.

Therefore, this solution is not feasible!

The evolutionary history of three --setnx + setex

The main reason for the above is not an option to acquire the lock after client disconnection problem can not release the lock, then I can immediately re-execute setex after setnx it?

The answer is yes, the previous version 2.6.12 implement a distributed lock redis we are all so fun.

repeat

Therefore, Option Three is to first use the setnx lock_user_1 valuecommand to get the lock, and then immediately setex lock_user_1 30 valueset the expiration time, and finally the use of del lock_user_1the lock is released.

Then perform setex set the expiration time to get locked in after setnx, so that a high probability to solve the problem of client disconnection does not release the lock after acquiring the lock.

However, this program is still a problem, then if setnx before setex the client will break out? Ah ~ it seems no solution, but this probability is very small, so the previous version 2.6.12 we all so used, there have been hardly any problems.

Therefore, this basic scheme is available, just not very good!

The evolutionary history of four --set nx ex

Not good mainly due to the above program is setnx / setex are two separate commands, can not be solved after the success of the former client disconnection problem, then, the two commands together not on line yet?

Yes, redis official also aware of the problem, so the 2.6.12 version to set some parameters added to the command:

SET key value [EX seconds] [PX milliseconds] [NX|XX]

EX, the expiration time, in seconds

PX, the expiration time, in milliseconds

NX, not exist, if there is no success before setting

XX, exist exist? If there was set successfully

With this order we are no longer afraid of a client for no reason disconnected from the public No. [This article "Tong brother read the source" original].

repeat

Therefore, the use of Option IV is the first set lock_user_1 value nx ex 30to acquire the lock, use after acquiring the lock, use to complete the final del lock_user_1release of the lock.

However, this solution is no problem?

Of course there are, in fact, where the lock is released simply perform del lock_user_1to, and does not check the lock is not a current client acquired.

Therefore, this scheme is not perfect.

Evolutionary history of five --random value + lua script

The above scheme is not perfect here, mainly due to the release of the lock control is not in place, then there is no other way to control the release of the lock thread and the thread must be locked with a client it?

redis official program given is this:

 // 加锁
 SET resource_name my_random_value NX PX 30000
 
 // 释放锁
 if redis.call("get",KEYS[1]) == ARGV[1] then
     return redis.call("del",KEYS[1])
 else
     return 0
 end

When locked, set random values ​​to ensure that only current client random value know.

When the lock is released, the implementation of a lua script, put this lua script as a complete command, first check the lock corresponding value is a random value is not set above, if it is then executed del release the lock, or return directly to release the lock failure .

We know, redis is single-threaded, so this lua script and del will not get concurrency problems, but can not get del java in the first, as this will both commands, there will be complicated by the problem, rather lua script then a command is transmitted to the redis together.

This program is a relatively perfect, but there are little flaws, this is set to the expiration time how much better?

Setting too small, there are likely to lock a thread does not end execution within the logic, the lock is automatically released, resulting in another thread can acquire the lock, and there have concurrency problems;

Set too large, it is necessary to consider the client disconnected, the lock to wait a long time.

So, here we are derived with a new problem, the expiration of the time I set up a little small, but it can be renewed automatically expire soon enough.

The evolutionary history of six --redisson (redis2.8 +)

Defects in the above scenario is not good grasp of the expiration time, although you can also start your own thread to handle the renewal of a listener, but the code is not very good writing, good ready-made wheels redisson has helped us to achieve this logic Well, we to take over the direct use of it.

Moreover, redisson full account of the evolution of the various issues redis left, stand-alone mode, Sentinel mode, cluster model, which all are handled well, whether it evolved from stand-alone to a cluster or clusters to evolve from a sentry, only need simple configuration modifications on it, do not change any code, you can say a non (industry) often (community) party (good) will (heart).

Internal distributed lock redisson implemented using Redlock algorithm, which is an algorithm official recommendation.

Further, redisson also provides a number of distributed objects (distributed class atom), a set of distributed (distributed Map / List / Set / Queue, etc.), the synchronizer distributed (distributed CountDownLatch / Semaphore, etc.), the distribution of type lock (distributed lock fair / unfair lock / read-write locks, etc.), we are interested can go and see, posted the following links:

repeat

Redlock description: https: //redis.io/topics/distlock

redisson description: https: //github.com/redisson/redisson/wiki

Code

Because the previous 5 designs are outdated, so Tong brother here lazy, do not get them implemented, we see a direct implementation of the last redisson.

pom.xml file

Add spring redis and rely redisson, I used here is springboot 2.1.6 version, springboot 1.x version of their attention to the next, see above github can find a way.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-data-21</artifactId>
    <version>3.11.0</version>
</dependency>
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.11.0</version>
</dependency>

application.yml file

Redis connection configuration information, Tong brother given here three ways.

spring:
  redis:
    # 单机模式
    #host: 192.168.1.102
    #port: 6379
    # password: <your passowrd>
    timeout: 6000ms  # 连接超时时长(毫秒)
    # 哨兵模式 【本篇文章由公众号“彤哥读源码”原创】
#    sentinel:
#      master: <your master>
#      nodes: 192.168.1.101:6379,192.168.1.102:6379,192.168.1.103:6379
    # 集群模式(三主三从伪集群)
    cluster:
      nodes:
        - 192.168.1.102:30001
        - 192.168.1.102:30002
        - 192.168.1.102:30003
        - 192.168.1.102:30004
        - 192.168.1.102:30005
        - 192.168.1.102:30006

Locker Interface

Locker defined interfaces.

public interface Locker {
    void lock(String key, Runnable command);
}

RedisLocker implementation class

RedissonClient directly acquire the lock Note that this does not need to be individually configured RedissonClient bean, redisson framework instance RedissonClient automatically generated according to the configuration, behind us is how to achieve it.

@Component
public class RedisLocker implements Locker {

    @Autowired
    private RedissonClient redissonClient;

    @Override
    public void lock(String key, Runnable command) {
        RLock lock = redissonClient.getLock(key);
        try {
            // 【本篇文章由公众号“彤哥读源码”原创】
            lock.lock();
            command.run();
        } finally {
            lock.unlock();
        }
    }
}

Test category

Start 1000 threads, each internal print a word, then sleep one second.


@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
public class RedisLockerTest {

    @Autowired
    private Locker locker;

    @Test
    public void testRedisLocker() throws IOException {
        for (int i = 0; i < 1000; i++) {
            new Thread(()->{
                locker.lock("lock", ()-> {
                    // 可重入锁测试
                    locker.lock("lock", ()-> {
                        System.out.println(String.format("time: %d, threadName: %s", System.currentTimeMillis(), Thread.currentThread().getName()));
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    });
                });
            }, "Thread-"+i).start();
        }

        System.in.read();
    }
}

operation result:

Can be seen at around 1000ms stably print a word, the described lock is available and is reentrant.

time: 1570100167046, threadName: Thread-756
time: 1570100168067, threadName: Thread-670
time: 1570100169080, threadName: Thread-949
time: 1570100170093, threadName: Thread-721
time: 1570100171106, threadName: Thread-937
time: 1570100172124, threadName: Thread-796
time: 1570100173134, threadName: Thread-944
time: 1570100174142, threadName: Thread-974
time: 1570100175167, threadName: Thread-462
time: 1570100176180, threadName: Thread-407
time: 1570100177194, threadName: Thread-983
time: 1570100178206, threadName: Thread-982
...

RedissonAutoConfiguration

RedissonClient not need to configure just said, in fact, it is automatically configured RedissonAutoConfiguration in, we simply look at its source code, mainly to see redisson () this method:


@Configuration
@ConditionalOnClass({Redisson.class, RedisOperations.class})
@AutoConfigureBefore(RedisAutoConfiguration.class)
@EnableConfigurationProperties({RedissonProperties.class, RedisProperties.class})
public class RedissonAutoConfiguration {

    @Autowired
    private RedissonProperties redissonProperties;
    
    @Autowired
    private RedisProperties redisProperties;
    
    @Autowired
    private ApplicationContext ctx;
    
    @Bean
    @ConditionalOnMissingBean(name = "redisTemplate")
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate<Object, Object>();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

    @Bean
    @ConditionalOnMissingBean(StringRedisTemplate.class)
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

    @Bean
    @ConditionalOnMissingBean(RedisConnectionFactory.class)
    public RedissonConnectionFactory redissonConnectionFactory(RedissonClient redisson) {
        return new RedissonConnectionFactory(redisson);
    }
    
    @Bean(destroyMethod = "shutdown")
    @ConditionalOnMissingBean(RedissonClient.class)
    public RedissonClient redisson() throws IOException {
        Config config = null;
        Method clusterMethod = ReflectionUtils.findMethod(RedisProperties.class, "getCluster");
        Method timeoutMethod = ReflectionUtils.findMethod(RedisProperties.class, "getTimeout");
        Object timeoutValue = ReflectionUtils.invokeMethod(timeoutMethod, redisProperties);
        int timeout;
        if(null == timeoutValue){
            // 超时未设置则为0
            timeout = 0;
        }else if (!(timeoutValue instanceof Integer)) {
            // 转毫秒
            Method millisMethod = ReflectionUtils.findMethod(timeoutValue.getClass(), "toMillis");
            timeout = ((Long) ReflectionUtils.invokeMethod(millisMethod, timeoutValue)).intValue();
        } else {
            timeout = (Integer)timeoutValue;
        }
        
        // 看下是否给redisson单独写了一个配置文件
        if (redissonProperties.getConfig() != null) {
            try {
                InputStream is = getConfigStream();
                config = Config.fromJSON(is);
            } catch (IOException e) {
                // trying next format
                try {
                    InputStream is = getConfigStream();
                    config = Config.fromYAML(is);
                } catch (IOException e1) {
                    throw new IllegalArgumentException("Can't parse config", e1);
                }
            }
        } else if (redisProperties.getSentinel() != null) {
            // 如果是哨兵模式
            Method nodesMethod = ReflectionUtils.findMethod(Sentinel.class, "getNodes");
            Object nodesValue = ReflectionUtils.invokeMethod(nodesMethod, redisProperties.getSentinel());
            
            String[] nodes;
            // 看sentinel.nodes这个节点是列表配置还是逗号隔开的配置
            if (nodesValue instanceof String) {
                nodes = convert(Arrays.asList(((String)nodesValue).split(",")));
            } else {
                nodes = convert((List<String>)nodesValue);
            }
            
            // 生成哨兵模式的配置
            config = new Config();
            config.useSentinelServers()
                .setMasterName(redisProperties.getSentinel().getMaster())
                .addSentinelAddress(nodes)
                .setDatabase(redisProperties.getDatabase())
                .setConnectTimeout(timeout)
                .setPassword(redisProperties.getPassword());
        } else if (clusterMethod != null && ReflectionUtils.invokeMethod(clusterMethod, redisProperties) != null) {
            // 如果是集群模式
            Object clusterObject = ReflectionUtils.invokeMethod(clusterMethod, redisProperties);
            Method nodesMethod = ReflectionUtils.findMethod(clusterObject.getClass(), "getNodes");
            // 集群模式的cluster.nodes是列表配置
            List<String> nodesObject = (List) ReflectionUtils.invokeMethod(nodesMethod, clusterObject);
            
            String[] nodes = convert(nodesObject);
            
            // 生成集群模式的配置
            config = new Config();
            config.useClusterServers()
                .addNodeAddress(nodes)
                .setConnectTimeout(timeout)
                .setPassword(redisProperties.getPassword());
        } else {
            // 单机模式的配置
            config = new Config();
            String prefix = "redis://";
            Method method = ReflectionUtils.findMethod(RedisProperties.class, "isSsl");
            // 判断是否走ssl
            if (method != null && (Boolean)ReflectionUtils.invokeMethod(method, redisProperties)) {
                prefix = "rediss://";
            }
            
            // 生成单机模式的配置
            config.useSingleServer()
                .setAddress(prefix + redisProperties.getHost() + ":" + redisProperties.getPort())
                .setConnectTimeout(timeout)
                .setDatabase(redisProperties.getDatabase())
                .setPassword(redisProperties.getPassword());
        }
        
        return Redisson.create(config);
    }

    private String[] convert(List<String> nodesObject) {
        // 将哨兵或集群模式的nodes转换成标准配置
        List<String> nodes = new ArrayList<String>(nodesObject.size());
        for (String node : nodesObject) {
            if (!node.startsWith("redis://") && !node.startsWith("rediss://")) {
                nodes.add("redis://" + node);
            } else {
                nodes.add(node);
            }
        }
        return nodes.toArray(new String[nodes.size()]);
    }

    private InputStream getConfigStream() throws IOException {
        // 读取redisson配置文件
        Resource resource = ctx.getResource(redissonProperties.getConfig());
        InputStream is = resource.getInputStream();
        return is;
    }

    
}

Information found on the Internet, many of the configuration is redundant (version might be a problem), look at the source code is very clear, this is a look at the benefits of source.

to sum up

(1) redis Due to historical reasons, there are three modes: single, Sentinel, a cluster;

Evolutionary history (2) redis implementation of distributed locking: set -> setnx -> setnx + setex -> set nx ex (or px) -> set nx ex (or px) + lua script -> redisson;

(3) redis have an existing distributed lock wheel redisson may be used;

(4) redisson also provides many useful components, such as a collection of distributed, distributed synchronization, a distributed object;

Egg

What are the advantages redis distributed locks?

A: 1) most systems rely on redis as cache, no additional depend on other components (for relative zookeeper);

2) redis can cluster deployment, with respect to a single point mysql more reliable;

3) do not take up mysql connections will not increase the pressure of mysql;

4) redis community relatively active, redisson to achieve a more stable and reliable;

5) the use of outdated mechanism to solve the problem the client is broken, although not in a timely manner;

6) There are ready-made wheels redisson can be used, the type of lock is relatively complete;

redis distributed lock what are the disadvantages?

A: 1) clustered mode will be implemented in all master node lock command, most of the (2N + 1) to obtain the lock is successful, the more nodes, the slower the process of locking;

2) under high concurrency, thread will not acquire a lock sleep retried, if the same lock is highly competitive, it will take up a lot of system resources;

3) pit causes a lot of history, it difficult to achieve robust redis distributed lock out;

In short, the advantage of distributed lock redis outweigh the disadvantages, and community activists, which is most of us use the system as a reason redis distributed lock.

Recommended Reading

1, Sike java synchronization of the Opening Series

2, Sike Unsafe java class of analytic magic

3, Sike java synchronized series of JMM (Java Memory Model)

4, Sike java synchronized series of volatile resolve

5, Sike java synchronized series of synchronized resolve

6, Sike java series of synchronous write himself a lock Lock

7, Sike java synchronized series of articles from the AQS

8, Sike ReentrantLock resolve java source code synchronized series of (a) - fair locks, lock unfair

9, Sike ReentrantLock resolve java source code synchronized series of (two) - Conditions Lock

10, Sike java synchronized series of ReentrantLock VS synchronized

11, Sike ReentrantReadWriteLock parse java source synchronous series

12, Sike Semaphore synchronization java source parsing Series

13, Sike java source parsing synchronous series CountDownLatch

14, Sike java synchronized series of AQS final chapter

15, Sike StampedLock parse java source synchronous series

16, Sike CyclicBarrier parse java source synchronous series

17, Sike java source code synchronized series of analytical Phaser

18, Sike java series of synchronized distributed lock mysql

19, Sike java synchronization zookeeper series distributed lock


I welcome the attention of the public number "Tong brother read source" view source code more series, and brother Tong source of ocean swim together.

qrcode

Guess you like

Origin www.cnblogs.com/tong-yuan/p/11621361.html