06 Redis distributed lock

Common Interview Questions

  • In addition to using Redis as a cache, what usages have you seen based on Redis?
  • Are there any problems that need to be paid attention to when Redis is doing distributed locks?
  • If Redis is deployed at a single point, what problems will it cause? So how are you going to solve the single point problem?
  • In cluster mode, such as master-slave mode, is there any problem?
  • Do you know how Redis solves the problem that the cluster mode is not reliable?
  • So can you briefly introduce Redlock? You write redisson on your resume, talk about it
  • What do you think is wrong with Redlock?
  • How to renew the Redis distributed lock? Does the watchdog know?

Classification of locks

  • Stand-alone version in the same JVM virtual machine, synchronized or Lock interface
  • In different distributed JVM virtual machines, the single-machine thread lock mechanism no longer works, and resource classes are shared among different servers.

The conditions and requirements for a reliable distributed lock

  1. Exclusiveness: OnlyOne, only one thread can hold it at any time
  2. High availability: In the redis cluster environment, it is not possible to fail to acquire and release locks because a certain node is hung up
  3. Anti-deadlock: To prevent deadlock, there must be a timeout control mechanism or undo operation, and there is a bottom-up termination and exit plan
  4. No random snatching: To prevent Zhangguan Lidai, you can't unlock other people's locks in private, you can only lock and release by yourself.
  5. Reentrancy: If the same thread on the same node acquires the lock, it can also acquire the lock again.

distributed lock

insert image description here

setnx key value

insert image description here
Bad review, setnx+expire is not safe, the two commands are non-atomic

Base case (boot+redis)

  • Usage scenario: Multiple services guarantee that the same user can only have one request at the same time at the same time (to prevent concurrent attacks on key businesses)

  • Build Module

    • boot_redis01
    • boot_redis02
  • Change POM

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.3.10.RELEASE</version>
            <relativePath/>
        </parent>
    
        <groupId>com.atguigu.redis</groupId>
        <artifactId>boot_redis01</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    
        <properties>
            <java.version>1.8</java.version>
        </properties>
    
        <dependencies>
            <!--guava-->
            <dependency>
                <groupId>com.google.guava</groupId>
                <artifactId>guava</artifactId>
                <version>23.0</version>
            </dependency>
            <!--web+actuator-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-actuator</artifactId>
            </dependency>
            <!--SpringBoot与Redis整合依赖-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-redis</artifactId>
            </dependency>
            <dependency>
                <groupId>org.apache.commons</groupId>
                <artifactId>commons-pool2</artifactId>
            </dependency>
            <!-- jedis -->
            <dependency>
                <groupId>redis.clients</groupId>
                <artifactId>jedis</artifactId>
                <version>3.1.0</version>
            </dependency>
            <!-- springboot-aop 技术-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-aop</artifactId>
            </dependency>
            <!-- redisson -->
            <dependency>
                <groupId>org.redisson</groupId>
                <artifactId>redisson</artifactId>
                <version>3.13.4</version>
            </dependency>
            <!--一般通用基础配置-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-devtools</artifactId>
                <scope>runtime</scope>
                <optional>true</optional>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <optional>true</optional>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
                <exclusions>
                    <exclusion>
                        <groupId>org.junit.vintage</groupId>
                        <artifactId>junit-vintage-engine</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    
    </project>
    
  • write YML

    server.port=1111
    
    # ========================redis相关配置=====================
    # Redis数据库索引(默认为0)
    spring.redis.database=0  
    # Redis服务器地址
    spring.redis.host=127.0.0.1
    # Redis服务器连接端口
    spring.redis.port=6379  
    # Redis服务器连接密码(默认为空)
    spring.redis.password=
    # 连接池最大连接数(使用负值表示没有限制) 默认 8
    spring.redis.lettuce.pool.max-active=8
    # 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1
    spring.redis.lettuce.pool.max-wait=-1
    # 连接池中的最大空闲连接 默认 8
    spring.redis.lettuce.pool.max-idle=8
    # 连接池中的最小空闲连接 默认 0
    spring.redis.lettuce.pool.min-idle=0
    
  • business class

    • config
      package com.learn.config;
      
      import org.redisson.Redisson;
      import org.redisson.config.Config;
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;
      import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
      import org.springframework.data.redis.core.RedisTemplate;
      import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
      import org.springframework.data.redis.serializer.StringRedisSerializer;
      
      import java.io.Serializable;
      
      /**
       * @author YSK
       * @since 2023/6/2 15:15
       */
      @Configuration
      public class RedisConfig {
              
              
          @Bean
          public RedisTemplate<String, Serializable> redisTemplate(LettuceConnectionFactory connectionFactory) {
              
              
              RedisTemplate<String, Serializable> redisTemplate = new RedisTemplate<>();
              redisTemplate.setConnectionFactory(connectionFactory);
      
              redisTemplate.setKeySerializer(new StringRedisSerializer());
              redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
      
              return redisTemplate;
          }
      
          @Bean
          public Redisson redisson() {
              
              
              Config config = new Config();
      
              config.useSingleServer().setAddress("redis://127.0.0.1:6379").setDatabase(0);
      
              return (Redisson) Redisson.create(config);
          }
      }
      
    • controller
      package com.learn.controller;
      
      import org.redisson.Redisson;
      import org.redisson.api.RLock;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.beans.factory.annotation.Value;
      import org.springframework.data.redis.core.StringRedisTemplate;
      import org.springframework.web.bind.annotation.GetMapping;
      import org.springframework.web.bind.annotation.RestController;
      
      /**
       * @author YSK
       * @since 2023/6/2 15:16
       */
      @RestController
      public class GoodController {
              
              
          @Autowired
          private StringRedisTemplate stringRedisTemplate;
          public static final String KEY = "atguiguLock_0511";
      
          @Value("${server.port}")
          private String serverPort;
          @Autowired
          private Redisson redisson;
      
          @GetMapping("/buy_goods")
          public String buy_Goods()
          {
              
              
              String result = stringRedisTemplate.opsForValue().get("goods:001");
              int goodsNumber = result == null ? 0 : Integer.parseInt(result);
      
              if(goodsNumber > 0)
              {
              
              
                  int realNumber = goodsNumber - 1;
                  stringRedisTemplate.opsForValue().set("goods:001",realNumber + "");
                  System.out.println("你已经成功秒杀商品,此时还剩余:" + realNumber + "件"+"\t 服务器端口:"+serverPort);
                  return "你已经成功秒杀商品,此时还剩余:" + realNumber + "件"+"\t 服务器端口:"+serverPort;
              }else{
              
              
                  System.out.println("商品已经售罄/活动结束/调用超时,欢迎下次光临"+"\t 服务器端口:"+serverPort);
              }
      
              return "商品已经售罄/活动结束/调用超时,欢迎下次光临"+"\t 服务器端口:"+serverPort;
          }
      }
      
      

insert image description here

The problem with the above case code

The stand-alone version is not locked

  • There is no lock, the number of concurrent downloads is incorrect, and the phenomenon of oversold occurs
  • think
    • 加synchronized
    • 加ReentrantLock
    • It will be all right

solve

@RestController
public class GoodController
{
    
    
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Value("${server.port}")
    private String serverPort;

    @GetMapping("/buy_goods")
    public String buy_Goods()
    {
    
    
        synchronized (this) {
    
    
            String result = stringRedisTemplate.opsForValue().get("goods:001");
            int goodsNumber = result == null ? 0 : Integer.parseInt(result);

            if(goodsNumber > 0)
            {
    
    
                int realNumber = goodsNumber - 1;
                stringRedisTemplate.opsForValue().set("goods:001",realNumber + "");
                System.out.println("你已经成功秒杀商品,此时还剩余:" + realNumber + "件"+"\t 服务器端口:"+serverPort);
                return "你已经成功秒杀商品,此时还剩余:" + realNumber + "件"+"\t 服务器端口:"+serverPort;
            }else{
    
    
                System.out.println("商品已经售罄/活动结束/调用超时,欢迎下次光临"+"\t 服务器端口:"+serverPort);
            }

            return "商品已经售罄/活动结束/调用超时,欢迎下次光临"+"\t 服务器端口:"+serverPort;
        }
    }
}

explain

  • In a stand-alone environment, you can use synchronized or Lock to achieve.
  • But in a distributed system, because the competing threads may not be on the same node (in the same jvm), it needs a lock that can be accessed by all processes (such as redis or zookeeper to build)
  • Locks at the jvm level of different processes will not work, so you can use a third-party component to acquire the lock. If the lock is not acquired, the current thread that wants to run will be blocked.

nginx distributed microservice architecture

question

After distributed deployment, stand-alone locks are still oversold, requiring distributed locks

Nginx configuration load balancing

jmeter pressure test
insert image description here

solve

  • Distributed lock setnx on redis
  • insert image description here
    @GetMapping("/buy_goods")
    public String buy_Goods()
    {
    
    
        String key = "zzyyRedisLock";
        String value = UUID.randomUUID().toString()+Thread.currentThread().getName();

        Boolean flagLock = stringRedisTemplate.opsForValue().setIfAbsent(key, value);
        if(!flagLock)
        {
    
    
            return "抢夺锁失败,o(╥﹏╥)o";
        }

        String result = stringRedisTemplate.opsForValue().get("goods:001");
        int goodsNumber = result == null ? 0 : Integer.parseInt(result);

        if(goodsNumber > 0)
        {
    
    
            int realNumber = goodsNumber - 1;
            stringRedisTemplate.opsForValue().set("goods:001",realNumber + "");
            stringRedisTemplate.delete(key);
            System.out.println("你已经成功秒杀商品,此时还剩余:" + realNumber + "件"+"\t 服务器端口:"+serverPort);
            return "你已经成功秒杀商品,此时还剩余:" + realNumber + "件"+"\t 服务器端口:"+serverPort;
        }else{
    
    
            System.out.println("商品已经售罄/活动结束/调用超时,欢迎下次光临"+"\t 服务器端口:"+serverPort);
        }

        return "商品已经售罄/活动结束/调用超时,欢迎下次光临"+"\t 服务器端口:"+serverPort;


    }

finally must close the lock resource

  • If an exception occurs, the lock may not be released, and the lock must be finally released at the code level
  • Lock and unlock, lock/unlock must appear at the same time and guarantee to call
    insert image description here

downtime problem

  • The machine where the microservice jar package is deployed hangs up. The code level has not reached the finally section at all, and there is no way to guarantee unlocking. The key has not been deleted, and an expiration time-limited key needs to be added.
  • insert image description here

Set the key+expiration time separately, and must be combined into one row to be atomic

insert image description here

Deleted someone else's lock

insert image description here

  • You can only delete your own, don't touch other people's
  • insert image description here

The judgment of finally block + del delete operation is not atomic

  • with Lua script
  • Redis calls Lua scripts to ensure the atomicity of code execution through the eval command

RedisUtils

 
package com.zzyy.study.util;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

import javax.annotation.PostConstruct;

/**
 * @auther zzyy
 * @create 2020-09-20 16:44
 */
public class RedisUtils
{
    
    
    private static JedisPool jedisPool;

    static {
    
    
        JedisPoolConfig jedisPoolConfig=new JedisPoolConfig();
        jedisPoolConfig.setMaxTotal(20);
        jedisPoolConfig.setMaxIdle(10);
        jedisPool=new JedisPool(jedisPoolConfig,"192.168.111.147",6379);
    }

    public static Jedis getJedis() throws Exception {
    
    
        if(null!=jedisPool){
    
    
            return jedisPool.getResource();
        }
        throw new Exception("Jedispool was not init");
    }

}

insert image description here

Compared with Zookeeper, the focus

  • Redis stand-alone is CP cluster is AP
  • Redis cluster: lock loss caused by redis asynchronous replication, for example: if the master node does not come and the data just set is sent to the slave node, the master hangs up, and the slave machine is on the position but there is no such data on the slave machine

Summarize

RedisConfig

package com.learn.config;

import org.redisson.Redisson;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.io.Serializable;

/**
 * @author YSK
 * @since 2023/6/2 15:15
 */
@Configuration
public class RedisConfig {
    
    
    @Bean
    public RedisTemplate<String, Serializable> redisTemplate(LettuceConnectionFactory connectionFactory) {
    
    
        RedisTemplate<String, Serializable> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(connectionFactory);

        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());

        return redisTemplate;
    }

    @Bean
    public Redisson redisson() {
    
    
        Config config = new Config();

        config.useSingleServer().setAddress("redis://127.0.0.1:6379").setDatabase(0);

        return (Redisson) Redisson.create(config);
    }
}

insert image description here
insert image description here

  • The synchronized stand-alone version is OK, and the distributed version
  • nginx distributed microservices, stand-alone lock does not work/(ㄒoㄒ)/~~
  • Cancel stand-alone lock, use redis distributed lock setnx
  • Only the lock is added, but the lock is not released. If an exception occurs, the lock may not be released. The lock must be finally released at the code level
  • Downtime, deployed micro-service code level did not go to finally, there is no way to guarantee unlocking, this key has not been deleted, the expiration time setting of the lockKey is required
  • Increase the expiration time for the distributed lock key of redis. In addition, setnx+expiration time must be the same line
  • It must be stipulated that you can only delete your own locks, you cannot delete other people's locks, to prevent misleading, 1 delete 2, 2 delete 3
  • In the redis cluster environment, our own writing is not OK. Go directly to RedLock's Redisson implementation

Redis distributed lock-Redlock algorithm

  • Usage scenario: Multiple services guarantee that the same user can only have one request at the same time at the same time (to prevent concurrent attacks on key businesses)
  • The more correct posture of Redis distributed lock is to use redisson, a client tool

Stand-alone case

  • three important elements
    • Locking: Locking is actually in redis, setting a value for the Key key, in order to avoid deadlock, and giving an expiration time
    • Unlock: Delete the Key key. But it can’t be deleted randomly. It can’t be said that the request of client 1 deletes the lock of client 2. It can only delete its own lock.
    • insert image description here
    • Timeout: Pay attention to the expiration time of the lock key, and it cannot be occupied for a long time
    • Main points to answer in the interview
      • Lock key logic
      • insert image description here
      • Unlock key logic
      • insert image description here
  • In stand-alone mode, it is generally done with set/setnx+lua scripts. Think about its shortcomings?

Multi-machine case

What are the disadvantages of distributed locks based on setnx?

insert image description here

  • Thread 1 first acquires the lock successfully, and writes the key-value pair to the master node of redis; before redis synchronizes the key-value pair to the slave node, the master fails; redis triggers a failover, and one of the slaves is upgraded to a new master; At this time, the new master does not contain the key-value pair written by thread 1, so thread 2 can successfully obtain the lock when it tries to acquire the lock; at this time, it is equivalent to two threads acquiring the lock, which may cause various unexpected The situation occurs, such as the most common dirty data.
  • What we added is an exclusive exclusive lock. Only one redis lock can be successfully built and held at the same time. It is strictly forbidden for more than 2 request threads to obtain the lock. dangerous

The father of redis proposed the Redlock algorithm to solve this problem

  • Redis also provides the Redlock algorithm to implement distributed locks based on multiple instances. The lock variable is maintained by multiple instances. Even if an instance fails, the lock variable still exists, and the client can still complete the lock operation. The Redlock algorithm is an effective solution to realize highly reliable distributed locks, which can be used in actual development.

Redlock Algorithm Design Concept

design concept
  • This solution is also improved based on (set lock, Lua script unlock), so antirez, the father of redis, only describes the differences. The general solution is as follows.
  • Suppose we have N Redis master nodes, for example N = 5. These nodes are completely independent, we do not use replication or any other implicit coordination system, in order to get the lock client do the following:
  • insert image description here
  • In order to solve the problem of data inconsistency, this solution directly discards asynchronous replication and only uses the master node. At the same time, because slaves are discarded, N nodes are introduced to ensure availability. The official recommendation is 5. Brother Yang uses 3 examples for this teaching demonstration.
  • Only when the following two conditions are met, the client can consider the lock successful.
    • Condition 1: The client has successfully acquired locks from more than half (greater than or equal to N/2+1) of the Redis instances;
    • Condition 2: The total time spent by the client to acquire the lock does not exceed the effective time of the lock.
solution

insert image description here

Guess you like

Origin blog.csdn.net/m0_56709616/article/details/131003426
Recommended