REDIS10_Redission的入门案例多主案例搭建分布式锁进行加锁解锁底层源码解析

Posted 所得皆惊喜

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了REDIS10_Redission的入门案例多主案例搭建分布式锁进行加锁解锁底层源码解析相关的知识,希望对你有一定的参考价值。

文章目录

①. 如何基于官网进行开发

  • ①. 进入redis中文官网,点击文档
  • ②.选择分布式锁,打开页面
  • ③. 点击Wiki

  • ④. 后续关于分布式锁,需要什么内容,进行文档的查阅
  • ⑤. 天上飞的理念(RedLock)必然有落地的实现(Redisson)
  • ⑥. redission解决了两个问题
  1. 锁的自动续期,如果业务超长,运行期间自动给锁续上新的30s,不用担心业务时间长
  2. 如果业务宕机了,这个默认的过期时间是30s,避免了死锁
  3. 加锁的业务只要运行完成,就不会给当前锁续期,即使不手动解锁,锁默认在30s以后自动删除

②. 基于Redisson的入门案例

  • ①. 导入pom
	<!--引入redisson分布式锁-->
	<dependency>
		<groupId>org.redisson</groupId>
		<artifactId>redisson</artifactId>
		<version>3.13.4</version>
	</dependency>
  • ②. 建立配置类,参考文档
//2.建立配置类
@Configuration
public class MyRedisConfig 
    /**
     * 所有对Redisson的使用
     * @return
     * @throws IOException
     */
    //https://github.com/redisson/redisson/wiki/14.-%E7%AC%AC%E4%B8%89%E6%96%B9%E6%A1%86%E6%9E%B6%E6%95%B4%E5%90%88
    @Bean(destroyMethod="shutdown")
    public RedissonClient redisson() throws IOException 
        Config config = new Config();
        // 创建单例模式的配置
        //config.useSingleServer().setAddress("redis://" + ipAddr + ":6379");
        config.useSingleServer().setAddress("redis://192.168.56.10:6379");
        return Redisson.create(config);
    

  • ③. 进行单元测试
@Autowired
//RedissonClient redissonClient;
RedissonClient redisson;
@Test
public void redission()
	System.out.println(redissonClient);
	RLock lock = redisson.getLock("lock");

③. setnx的分布式锁有哪些不足

  • ①. 基于setnx的分布式锁有什么缺点?
  1. 线程1首先获取锁成功,将键值对写入 redis 的 master 节点
  2. 在redis将该键值对同步到slave节点之前,master 发生了故障
  3. redis 触发故障转移,其中一个 slave 升级为新的 master
  4. 此时新的master并不包含线程1写入的键值对,因此线程2尝试获取锁也可以成功拿到锁
  5. 此时相当于有两个线程获取到了锁,可能会导致各种预期之外的情况发生,例如最常见的脏数据。
    我们加的是排它独占锁,同一时间只能有一个建redis锁成功并持有锁,严禁出现2个以上的请求线程拿到锁。
  • ②. redis之父提出了Redlock算法解决这个问题
    (Redis也提供了Redlock算法,用来实现基于多个实例的分布式锁。锁变量由多个实例维护,即使有实例发生了故障,锁变量仍然是存在的,客户端还是可以完成锁操作。Redlock算法是实现高可靠分布式锁的一种有效解决方案,可以在实际开发中使用)

  • ③. Redis集群的AP(redis异步复制造成的锁丢失,比如:主节点没来的及把刚刚set进来这条数据给从节点,就挂了)

  • ④. redis分布式锁,多主集群模式,需要计算容错率(N=2X+1)
    比如,网络中死了1台机器,我要求还是OK的,可以用,请问,最多主集群部署几台?
    N=2*1+1=3

  • ⑤. 为什么是奇数? N = 2X + 1 (N是最终部署机器数,X是容错机器数)
    最少的机器,最多的产出效果
    加入在集群环境中,redis失败1台,可接受。2N+2= 2 * 1+2 =4,部署4台
    加入在集群环境中,redis失败2台,可接受。2N+2 = 2 * 2+2 =6,部署6台

  • ⑥. 那么什么是容错呢?

  1. 失败了多少个机器实例后我还是可以容忍的,所谓的容忍就是数据一致性还是可以Ok的,CP数据一致性还是可以满足
  2. 加入在集群环境中,redis失败1台,可接受。2X+1 = 2 * 1+1 =3,部署3台,死了1个剩下2个可以正常工作,那就部署3台
  3. 加入在集群环境中,redis失败2台,可接受。2X+1 = 2 * 2+1 =5,部署5台,死了2个剩下3个可以正常工作,那就部署5台

④. 三台主机案例搭建

  • ①. docker上安装三台机器
 docker run -p 6381:6379 --name redis-master-1 -d redis:6.08
docker run -p 6382:6379 --name redis-master-2 -d redis:6.0.8
docker run -p 6383:6379 --name redis-master-3 -d redis:6.0.8

  • ②. 建Module(redis_redlock)、改pom.xml
<?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/> <!-- lookup parent from repository -->
    </parent>

    <groupId>com.xiaozhi.redis.redlock</groupId>
    <artifactId>redis_redlock</artifactId>
    <version>0.0.1-SNAPSHOT</version>


    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <!--<version>3.12.0</version>-->
            <version>3.13.4</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.8</version>
        </dependency>
        <!--swagger-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>
        <!--swagger-ui-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.4</version>
            <scope>compile</scope>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
  • ③. 各个配置文件如下
spring.application.name=spring-boot-redis
server.port=9090

spring.swagger2.enabled=true

spring.redis.database=0
spring.redis.password=
spring.redis.timeout=3000
#sentinel/cluster/single
spring.redis.mode=single

spring.redis.pool.conn-timeout=3000
spring.redis.pool.so-timeout=3000
spring.redis.pool.size=10

spring.redis.single.address1=192.168.111.147:6381
spring.redis.single.address2=192.168.111.147:6382
spring.redis.single.address3=192.168.111.147:6383
//CacheConfiguration
@Configuration
@EnableConfigurationProperties(RedisProperties.class)
public class CacheConfiguration 

    @Autowired
    RedisProperties redisProperties;

    @Bean
    RedissonClient redissonClient1() 
        Config config = new Config();
        String node = redisProperties.getSingle().getAddress1();
        node = node.startsWith("redis://") ? node : "redis://" + node;
        SingleServerConfig serverConfig = config.useSingleServer()
                .setAddress(node)
                .setTimeout(redisProperties.getPool().getConnTimeout())
                .setConnectionPoolSize(redisProperties.getPool().getSize())
                .setConnectionMinimumIdleSize(redisProperties.getPool().getMinIdle());
        if (StringUtils.isNotBlank(redisProperties.getPassword())) 
            serverConfig.setPassword(redisProperties.getPassword());
        
        return Redisson.create(config);
    

    @Bean
    RedissonClient redissonClient2() 
        Config config = new Config();
        String node = redisProperties.getSingle().getAddress2();
        node = node.startsWith("redis://") ? node : "redis://" + node;
        SingleServerConfig serverConfig = config.useSingleServer()
                .setAddress(node)
                .setTimeout(redisProperties.getPool().getConnTimeout())
                .setConnectionPoolSize(redisProperties.getPool().getSize())
                .setConnectionMinimumIdleSize(redisProperties.getPool().getMinIdle());
        if (StringUtils.isNotBlank(redisProperties.getPassword())) 
            serverConfig.setPassword(redisProperties.getPassword());
        
        return Redisson.create(config);
    

    @Bean
    RedissonClient redissonClient3() 
        Config config = new Config();
        String node = redisProperties.getSingle().getAddress3();
        node = node.startsWith("redis://") ? node : "redis://" + node;
        SingleServerConfig serverConfig = config.useSingleServer()
                .setAddress(node)
                .setTimeout(redisProperties.getPool().getConnTimeout())
                .setConnectionPoolSize(redisProperties.getPool().getSize())
                .setConnectionMinimumIdleSize(redisProperties.getPool().getMinIdle());
        if (StringUtils.isNotBlank(redisProperties.getPassword())) 
            serverConfig.setPassword(redisProperties.getPassword());
        
        return Redisson.create(config);
    

    /**
     * 单机
     * @return
     */
    /*@Bean
    public Redisson redisson()
    
        Config config = new Config();

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

        return (Redisson) Redisson.create(config);
    */

//RedisPoolProperties
@Data
public class RedisPoolProperties 
    private int maxIdle;
    private int minIdle;
    private int maxActive;
    private int maxWait;
    private int connTimeout;
    private int soTimeout;
    /**
     * 池大小
     */
    private  int size;

//RedisProperties
@ConfigurationProperties(prefix = "spring.redis", ignoreUnknownFields = false)
@Data
public class RedisProperties 
    private int database;
    /**
     * 等待节点回复命令的时间。该时间从命令发送成功时开始计时
     */
    private int timeout;
    private String password
    private String mode;
    /**
     * 池配置
     */
    private RedisPoolProperties pool;
    /**
     * 单机信息配置
     */
    private RedisSingleProperties single;

//RedisSingleProperties
@Data
public class RedisSingleProperties 
    private  String address1;
    private  String address2;
    private  String address3;

  • ④. controller代码展示
@RestController
@Slf4j
public class RedLockController 
    public static final String CACHE_KEY_REDLOCK = "TANGZHI_REDLOCK";
    @Autowired
    RedissonClient redissonClient1;
    @Autowired
    RedissonClient redissonClient2;
    @Autowired
    RedissonClient redissonClient3;
    @GetMapping(value = "/redlock")
    public void getlock() 
        //CACHE_KEY_REDLOCK为redis 分布式锁的key
        RLock lock1 = redissonClient1.getLock(CACHE_KEY_REDLOCK);
        RLock lock2 = redissonClient2.getLock(CACHE_KEY_REDLOCK);
        RLock lock3 = redissonClient3.getLock(CACHE_KEY_REDLOCK);
        RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);
        boolean isLockBoolean;
        try 
            //waitTime 抢锁的等待时间,正常情况下等3秒
            //leaseTime就是redis key的过期时间,正常情况下等5分钟300秒。
            isLockBoolean = redLock.tryLock(3, 300, TimeUnit.SECONDS);
            log.info("线程,是否拿到锁: ",Thread.currentThread().getName(),isLockBoolean);
            if (isLockBoolean) 
                System.out.println(Thread.currentThread().getName()+"\\t"+"---come in biz");
                //业务逻辑,忙10分钟
                try  TimeUnit.MINUTES.sleep(10);  catch (InterruptedException e)  e.printStackTrace(); 
            
         catch (Exception e) 
            log.error("redlock exception ",e);
         finally 
            // 无论如何, 最后都要解锁
            redLock.unlock();
        
    

  • ⑤. 测试:http://localhost:9090/redlock
[root@TANG2021 ~]# docker exec -it redis-master-1 redis-cli
127.0.0.1:6379> keys *
1) "TANGZHI_REDLOCK"
127.0.0.1:6379> type TANGZHI_REDLOCK
hash
127.0.0.1:6379> hgetall TANGZHI_REDLOCK
1) "ca512c4b-f05f-4578-9b8f-45e2e35aa0d7:50"
2) "1"

⑤. Redisson源码解析