1 Setnx+Lua缺陷

在Redis主从+哨兵模式下,正常逻辑如下:

如果用户1获取锁成功,但是master还没把数据同步到slave,master宕机了。哨兵将slave升级到master。假设用户1还没有执行完,用户2是可以在新master里获取到锁的。

2 RedLock

Redis也提供了Redlock算法,用来实现基于多个实例的分布式锁。
锁变量由多个实例维护,即使有实例发生了故障,锁变量仍然是存在的,客户端还是可以完成锁操作。Redlock算法是实现高可靠分布式锁的一种有效解决方案,可以在实际开发中使用。

设计理念:
该方案也是基于(set 加锁、Lua 脚本解锁)进行改良的,所以redis之父antirez 只描述了差异的地方,大致方案如下。
假设我们有N个Redis主节点,例如 N = 5这些节点是完全独立的,我们不使用复制或任何其他隐式协调系统,为了取到锁客户端执行以下操作:

1 获取当前时间,以毫秒为单位;
2 依次尝试从5个实例,使用相同的 key 和随机值(例如 UUID)获取锁。当向Redis 请求获取锁时,客户端应该设置一个超时时间,这个超时时间应该小于锁的失效时间。例如你的锁自动失效时间为 10 秒,则超时时间应该在 5-50 毫秒之间。这样可以防止客户端在试图与一个宕机的 Redis 节点对话时长时间处于阻塞状态。如果一个实例不可用,客户端应该尽快尝试去另外一个 Redis 实例请求获取锁;
3 客户端通过当前时间减去步骤 1 记录的时间来计算获取锁使用的时间。当且仅当从大多数(N/2+1,这里是 3 个节点)的 Redis 节点都取到锁,并且获取锁使用的时间小于锁失效时间时,锁才算获取成功;
4 如果取到了锁,其真正有效时间等于初始有效时间减去获取锁所使用的时间(步骤 3 计算的结果)。
5 如果由于某些原因未能获得锁(无法在至少 N/2 + 1 个 Redis 实例获取锁、或获取锁的时间超过了有效时间),客户端应该在所有的 Redis 实例上进行解锁(即便某些Redis实例根本就没有加锁成功,防止某些节点获取到锁但是客户端没有得到响应而导致接下来的一段时间不能被重新获取锁)。

3 实现

3.1 Redis准备

 docker run -p 6379:6379 --name redis-6379 \
-v /usr/local/many-redis/redis-6379/data:/data \
-v /usr/local/many-redis/redis-6379/conf/redis.conf:/etc/redis/redis.conf \
-d redis:6.2.3 redis-server /etc/redis/redis.confdocker run -p 6380:6379 --name redis-6380 \
-v /usr/local/many-redis/redis-6380/data:/data \
-v /usr/local/many-redis/redis-6380/conf/redis.conf:/etc/redis/redis.conf \
-d redis:6.2.3 redis-server /etc/redis/redis.confdocker run -p 6381:6379 --name redis-6381 \
-v /usr/local/many-redis/redis-6381/data:/data \
-v /usr/local/many-redis/redis-6381/conf/redis.conf:/etc/redis/redis.conf \
-d redis:6.2.3 redis-server /etc/redis/redis.conf

3.2 SpringBoot

3.2.1 pom

 <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.3.0.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><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.projectlombok</groupId><artifactId>lombok</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-logging</artifactId></dependency><!-- 集成redis --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!--guava Google 开源的 Guava 中自带的布隆过滤器--><dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>23.0</version></dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.71</version></dependency><!-- https://mvnrepository.com/artifact/org.redisson/redisson --><dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.12.0</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-configuration-processor</artifactId><optional>true</optional></dependency></dependencies>

3.2.2 yml

server:port: 9000spring:redisson:redis-6379:host: 192.168.38.80port: 6379password:redis-6380:host: 192.168.38.80port: 6380password:redis-6381:host: 192.168.38.80port: 6381password:#超时时间为3stimeout: 3000

3.2.3 Redis配置文件

@ConfigurationProperties(prefix = "spring.redisson")
@Data
@Configuration
public class RedisProperties {private RedisNode redis6379;private RedisNode redis6380;private RedisNode redis6381;private Integer timeout;@Datastatic class RedisNode {private String host;private String port;private String password;}}@Configuration
public class RedisConfig {@Autowiredprivate RedisProperties redisProperties;@Beanpublic RedissonClient redissonClient6379() {return Redisson.create(getRedisSonConfig(redisProperties.getRedis6379()));}@Beanpublic RedissonClient redissonClient6380() {return Redisson.create(getRedisSonConfig(redisProperties.getRedis6380()));}@Beanpublic RedissonClient redissonClient6381() {return Redisson.create(getRedisSonConfig(redisProperties.getRedis6381()));}private Config getRedisSonConfig(RedisProperties.RedisNode redisNode) {String address = "redis://" + redisNode.getHost() + ":" + redisNode.getPort();Config config = new Config();SingleServerConfig singleServerConfig = config.useSingleServer().setAddress(address).setTimeout(redisProperties.getTimeout());if (StringUtils.isNotBlank(redisNode.getPassword())) {singleServerConfig.setPassword(redisNode.getPassword());}return config;}}

3.3 demo

案例: 设置redLock为抢锁等待时间3秒,锁有效期为5分钟,5个线程去抢锁,查看redis。

@RunWith(SpringRunner.class)
@SpringBootTest(classes = RedisApplication.class)
public class RedisLockTest {private static final Integer THREAD_COUNTS = 5;private static final CountDownLatch countDownLatch = new CountDownLatch(THREAD_COUNTS);private static final String REDIS_LOCK = "redis_lock";@Autowired@Qualifier("redissonClient6379")private RedissonClient redissonClient6379;@Autowired@Qualifier("redissonClient6380")private RedissonClient redissonClient6380;@Autowired@Qualifier("redissonClient6381")private RedissonClient redissonClient6381;@Testpublic void contextLoads() {for (int i = 1; i <= THREAD_COUNTS; i++) {new Thread(this::addCount, "thread" + i).start();}try {countDownLatch.await();} catch (InterruptedException e) {e.printStackTrace();}}public void addCount() {//CACHE_KEY_REDLOCK为redis 分布式锁的keyRLock lock1 = redissonClient6379.getLock(REDIS_LOCK);RLock lock2 = redissonClient6380.getLock(REDIS_LOCK);RLock lock3 = redissonClient6381.getLock(REDIS_LOCK);RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);try {//抢锁等待时间为3秒,锁的有效期为5分钟if (redLock.tryLock(3, 300, TimeUnit.SECONDS)) {System.out.println(Thread.currentThread().getName() + " 抢到了锁");Thread.sleep(60 * 1000);} else {System.out.println(Thread.currentThread().getName() + " 抢锁失败");}} catch (Exception e) {throw new RuntimeException("分布式锁抛出异常,异常原因:{}", e.getCause());} finally {countDownLatch.countDown();redLock.unlock();}}
}


redis-6379:

redis-6380:

redis-6381:

Redis 实战之多节点分布式锁【SpringBoot + RedLock】相关推荐

  1. 基于Redis的分布式锁和Redlock算法

    来自:后端技术指南针 1 前言 今天开始来和大家一起学习一下Redis实际应用篇,会写几个Redis的常见应用. 在我看来Redis最为典型的应用就是作为分布式缓存系统,其他的一些应用本质上并不是杀手 ...

  2. redis实现轮询算法_【07期】Redis中是如何实现分布式锁的?

    点击上方"Java面试题精选",关注公众号 面试刷图,查缺补漏 分布式锁常见的三种实现方式: 数据库乐观锁: 基于Redis的分布式锁: 基于ZooKeeper的分布式锁. 本地面 ...

  3. 场景应用:Redis使用setnx命令实现分布式锁

    文章目录 何时需要分布式锁? 如何实现分布式锁? 探究单节点锁:setnx命令 1. 加锁: 2. 解锁: 带来的问题 探究分布式锁:RedLock算法 补充:setnx命令学习 何时需要分布式锁? ...

  4. redis应用场景—— 缓存,分布式锁,去重

    Redis实际应用场景 https://www.cnblogs.com/mrhgw/p/6278619.html Redis在很多方面与其他数据库解决方案不同: 它使用内存提供主存储支持,而仅使用硬盘 ...

  5. Java基于redis实现分布式锁(SpringBoot)

    前言 分布式锁,其实原理是就是多台机器,去争抢一个资源,谁争抢成功,那么谁就持有了这把锁,然后去执行后续的业务逻辑,执行完毕后,把锁释放掉. 可以通过多种途径实现分布式锁,例如利用数据库(mysql等 ...

  6. 【面试题】Redis中是如何实现分布式锁的

    分布式锁常见的三种实现方式: 数据库乐观锁: 基于Redis的分布式锁: 基于ZooKeeper的分布式锁. Redis的分布式锁 Redis要实现分布式锁,以下条件应该得到满足 互斥性:在任意时刻, ...

  7. Redis系列教程(八):分布式锁的由来、及Redis分布式锁的实现详解

    在很多场景中,我们为了保证数据的最终一致性,需要很多的技术方案来支持,比如分布式事务.分布式锁等.那具体什么是分布式锁,分布式锁应用在哪些业务场景.如何来实现分布式锁呢?今天来探讨分布式锁这个话题. ...

  8. Redis 基础 - 优惠券秒杀《分布式锁(初级)》

    参考 Redis基础 - 基本类型及常用命令 Redis基础 - Java客户端 Redis 基础 - 短信验证码登录 Redis 基础 - 用Redis查询商户信息 Redis 基础 - 优惠券秒杀 ...

  9. 基于Redis(setnx)实现分布式锁

    什么是分布式锁? 分布式锁是控制分布式系统或不同系统之间共同访问共享资源的一种锁实现,如果不同的系统或同一个系统的不同主机之间共享了某个资源时,往往需要互斥来防止彼此干扰来保证一致性. 分布式锁需要具 ...

  10. java 通过redis实现倒计时_突破Java面试(42) - Redis amp; ZooKeeper两种分布式锁实现的优劣...

    0 Github 1 面试题 一般实现分布式锁都有哪些方式?使用redis如何设计分布式锁?使用zk来设计分布式锁可以吗?这两种分布式锁的实现方式哪种效率比较高? 2 考点分析 一般先问问你zk,然后 ...

最新文章

  1. 如何面试java后端_近期面试Java后端的一些感悟
  2. mysql中00933错误_java.sql.SQLException: ORA-00933: SQL 命令未正确结束错误解决
  3. Algorithms_基础数据结构(03)_线性表之链表_双向链表
  4. Mysql数据库(四)——mysql索引相关知识
  5. leetcode 202. Happy Number
  6. SAP CRM PPR调试截图,头都搞大了,希望这问题这辈子只遇到这次
  7. vue 字典_【开源】基于Vue的前端组件库HeyUI
  8. 低代码开发平台_低代码开发平台测评——伙伴云
  9. 开源软件 Apache Dubbo 牵手 IDE 插件,开发部署提速不止 8 倍
  10. python中类的定义和使用_Python中类的定义与使用
  11. wordpress插件列表
  12. 老李分享:5个衡量软件质量的标准
  13. markdown模板(个人使用)
  14. 两个栈实现一个队列以及两个队列实现一个栈(Java)
  15. MSG360虚拟服务器,H3C MSG360-10:简单设置+多场景结合应用
  16. 信息熵与两种编码基础
  17. Orcad Capture CIS
  18. FPGA niosII 视频笔记--小梅
  19. win11无法连接wifi怎么办?
  20. oracle安装配置

热门文章

  1. STM32F103+RTT从零开始(二)——RTT系统中点亮LED
  2. VIM技巧及使用vim开发android应用
  3. win7文件和文件夹可以重名吗_怎么取消WIN7复制同名文件自动重命名
  4. 市场、运营、销售什么区别?
  5. Python连接MySQL数据库
  6. matlab中ones函数的使用方法详细介绍(附matlab代码)
  7. 移动智能终端PIN码破解
  8. 【scratch音乐课】天空之城:音符与节拍、消息与链表
  9. POJ - 1179
  10. 怎么做好饮料代理?如何发展市场