商城项目【尚品汇】07分布式锁-2 Redisson篇

打印 上一主题 下一主题

主题 570|帖子 570|积分 1712

1 Redisson功能先容

基于自界说setnx实现的分布式锁存在下面的问题:
重入问题:重入问题是指 得到锁的线程可以再次进入到相同的锁的代码块中,可重入锁的意义在于防止死锁,比如HashTable这样的代码中,他的方法都是利用synchronized修饰的,假如他在一个方法内,调用另一个方法,那么此时如果是不可重入的,不就死锁了吗?所以可重入锁他的主要意义是防止死锁,我们的synchronized和Lock锁都是可重入的。
不可重试:是指现在的分布式只能实行一次,我们以为合理的情况是:当线程在得到锁失败后,他应该能再次实行得到锁。
**超时释放:**我们在加锁时增长了逾期时间,这样的我们可以防止死锁,但是如果卡顿的时间超长,虽然我们采用了lua表达式防止删锁的时间,误删别人的锁,但是毕竟没有锁住,有安全隐患
主从一致性: 如果Redis提供了主从集群,当我们向集群写数据时,主机需要异步的将数据同步给从机,而万一在同步已往之前,主机宕机了,就会出现死锁问题。

那么什么是Redisson呢
Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid)。它不光提供了一系列的分布式的Java常用对象,还提供了许多分布式服务,此中就包含了各种分布式锁的实现。
Redission提供了分布式锁的多种多样的功能

2 Redisson在Springboot中快速入门(代码)

2.1 导入依赖

  1. <!-- redisson -->
  2. <dependency>
  3.    <groupId>org.redisson</groupId>
  4.    <artifactId>redisson</artifactId>
  5.    <version>3.15.3</version>
  6. </dependency>
复制代码
2.2 Redisson设置

  1. package com.atguigu.gmall.common.config;
  2. import lombok.Data;
  3. import org.redisson.Redisson;
  4. import org.redisson.api.RedissonClient;
  5. import org.redisson.config.Config;
  6. import org.redisson.config.SingleServerConfig;
  7. import org.springframework.boot.context.properties.ConfigurationProperties;
  8. import org.springframework.context.annotation.Bean;
  9. import org.springframework.context.annotation.Configuration;
  10. import org.springframework.util.StringUtils;
  11. /**
  12. * redisson配置信息
  13. */
  14. @Data
  15. @Configuration
  16. @ConfigurationProperties("spring.redis")
  17. public class RedissonConfig {
  18.     private String host;
  19.     private String addresses;
  20.     private String password;
  21.     private String port;
  22.     private int timeout = 3000;
  23.     private int connectionPoolSize = 64;
  24.     private int connectionMinimumIdleSize=10;
  25.     private int pingConnectionInterval = 60000;
  26.     private static String ADDRESS_PREFIX = "redis://";
  27.     /**
  28.      * 自动装配
  29.      *
  30.      */
  31.     @Bean
  32.     RedissonClient redissonSingle() {
  33.         Config config = new Config();
  34.         if(StringUtils.isEmpty(host)){
  35.             throw new RuntimeException("host is  empty");
  36.         }
  37.         SingleServerConfig serverConfig = config.useSingleServer()
  38.                 //redis://127.0.0.1:7181
  39.                 .setAddress(ADDRESS_PREFIX + this.host + ":" + port)
  40.                 .setTimeout(this.timeout)
  41.                 .setPingConnectionInterval(pingConnectionInterval)
  42.                 .setConnectionPoolSize(this.connectionPoolSize)
  43.                 .setConnectionMinimumIdleSize(this.connectionMinimumIdleSize)
  44.                 ;
  45.         if(!StringUtils.isEmpty(this.password)) {
  46.             serverConfig.setPassword(this.password);
  47.         }
  48.         // RedissonClient redisson = Redisson.create(config);
  49.         return Redisson.create(config);
  50.     }
  51. }
复制代码
2.3 将自界说锁setnx换成Redisson实现(可重入锁)

实现类具体看这个章节:https://blog.csdn.net/yu_fu_a_bu/article/details/139408497
  1.     @GetMapping("testRedisLock")
  2.     public Result testRedisLock(){
  3.         testLockService.testRedisLock();
  4.         return Result.ok();
  5.     }
复制代码
  1.     @Autowired
  2.     private RedissonClient redissonClient;
  3.     @Override
  4.     public void testRedisson() {
  5.         //创建可重入锁对象,并指定锁的名称
  6.         RLock lock = redissonClient.getLock("redisson:1");
  7.         //获取锁,循环重试的时间,逻辑过期时间,时间单位
  8.         boolean isLock = false;
  9.         try {
  10.             isLock = lock.tryLock(1, 10, TimeUnit.SECONDS);
  11.         } catch (InterruptedException e) {
  12.             e.printStackTrace();
  13.             System.out.println("获取锁出现异常");
  14.         }
  15.         //获取锁成功,执行业务逻辑
  16.         if (isLock){
  17.             try{
  18.                 //从缓存中获取值
  19.                 String num = stringRedisTemplate.opsForValue().get("redissonLock");
  20.                 //加1操作
  21.                 int i = Integer.parseInt(num)+1;
  22.                 String s = StringUtil.toString(i);
  23.                 //存储到redis中
  24.                 stringRedisTemplate.opsForValue().set("redissonLock",s);
  25.             }finally {
  26.                 //只有获取到锁的线程,才去释放锁,
  27.                 lock.unlock();
  28.             }
  29.         }
  30.     }
复制代码
测试

ab -n 5000 -c 100 http://192.168.142.1:81/admin/product/testLock/testRedisson


3 可重入锁原理

3.1 自界说分布式锁setnx为什么不可以重入

数据结构:key-value的情势。String类型

  1. public class DistributedLockExample {
  2.    
  3.     private static Jedis jedis = new Jedis("127.0.0.1", 6379);
  4.     public void acquireLock(String lockKey) {
  5.         Boolean locked = jedis.setnx(lockKey, "true");
  6.         if (locked) {
  7.             System.out.println("Lock acquired successfully.");
  8.         } else {
  9.             System.out.println("Lock already acquired by another process.");
  10.         }
  11.     }
  12.     public static void main(String[] args) {
  13.         DistributedLockExample example = new DistributedLockExample();
  14.         example.acquireLock("myLock");
  15.         example.acquireLock("myLock"); // 尝试重入
  16.     }
  17. }
复制代码
在上面的示例中,acquireLock方法通过setnx实行获取锁。第一次调用acquireLock时乐成获取锁,因为myLock这个key在Redis中不存在。第二次调用acquireLock实行重入时,会返回锁已被占用的提示,因为Redis的setnx指令无法识别重入情况,每次获取锁都需要先检查是否已被占用。
3.2 redisson为什么可以实现可重入

数据类型:key-value(field-value) Hash类型

   不光存入线程标识(保证不删除别人的锁),而且存入可重入的次数(保证可重入)。
  

3.2.1 获取锁的lua脚本

   lua脚本可以保证原子性
  1. local key = KEYS[1];--锁的key
  2. local threadId = ARGV[1]; --线程的唯一标识
  3. local releaseTime = ARGV[2];--锁的自动释放时间
  4. --判断锁是否存在
  5. if(redis.call('exists',key) == 0) then
  6.         --不存在,获取锁
  7.         redis.call('hset', key,threadId,'1');
  8.         --设置有效期
  9.         redis.call('expire',key,releaseTime);
  10.         return 1;--返回结果
  11. end;
  12. --如果锁已经存在,判断threadId是否是自己的
  13. if(redis.call('hexists',key,threadId) == 1) then
  14.         --存在,获取锁,重入次数加一
  15.         redis.call('hincrby',key,threadId,'1');
  16.         -- 设置有效期
  17.         redis.call('expire',key,releaseTime);
  18.         return 1;--返回结果
  19. end;
  20. return 0;--获取锁失败
复制代码
3.2.2 释放锁的lua脚本

  1. local key = KEYS[1];--锁的key
  2. local threadId = ARGV[1]; --锁的唯一标识
  3. local releaseTime = ARGV[2];--锁的自动释放时间
  4. -- 判断锁是还是被自己持有
  5. if(redis.call('HEXISTS',key,threadId) == 0) then
  6.         return nil;--如果已经不是自己,则直接返回
  7. end;
  8. -- 是自己的锁,则可重入的次数进行-1
  9. local count = redis.call('HINCRVY',key,threadId,-1);
  10. -- 判断可重入的次数是否为0
  11. if(count > 0){
  12.         -- 大于0说明不能释放锁,重置过期时间然后返回
  13.         redis.call('EXPIRE',key,releaseTime);
  14.         return nil;
  15. else --等于0说明可以释放锁,直接删除
  16.         redis.call('DEL',key);
  17.         return nil;
  18. end;
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

刘俊凯

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表