高并发下Redis实现分布式锁的坑你是否踩过

目录

一、锁,你了解多少?

二、设计分布式锁应该考虑的东西

三、可重入锁你知道吗?

四、分布式锁的选型实现

五、Redis实现分布式锁的坑你发现了吗 


一、锁,你了解多少?

有本地锁:synchronize、lock等,锁在当前进程内,分布式集群部署下依旧存在锁失效问题
还有分布式锁:redis、zookeeper等实现,虽然还是锁,但是多个进程共用的锁标记,可以用Redis、Zookeeper、Mysql等都可以

二、设计分布式锁应该考虑的东西

排他性
    在分布式应用集群中,同一个方法在同一时间只能被一台机器上的一个线程执行
容错性
    分布式锁一定能得到释放,比如客户端奔溃或者网络中断
满足可重入、高性能、高可用
注意分布式锁的开销、锁粒度 

三、可重入锁你知道吗?

单节点可重入锁
    可重入锁: JDK指的是以线程为单位,当一个线程获取对象锁之后,这个线程可以再次获取本对象上的锁,而其他的线程是不可以的,synchronized 和 ReentrantLock 都是可重入锁
分布式下的可重入锁
    进程单位,当一个线程获取对象锁之后,其他节点的同个业务线程可以再次获取本对象上的锁

四、分布式锁的选型实现

 实现分布式锁 可以用 Redis、Zookeeper、Mysql数据库这几种 , 性能最好的是Redis且是最容易理解

分布式锁离不开 key - value 设置

key 是锁的唯一标识,一般按业务来决定命名,比如想要给一种商品的秒杀活动加锁,key 命名为 “seckill_商品ID” 。value就可以使用固定值,比如设置成1。
短链码可以:short_link:code:xxxx

基于redis实现分布式锁,文档:http://www.redis.cn/commands.html#string

加锁 SETNX key value
    setnx 的含义就是 SET if Not Exists,有两个参数 setnx(key, value),该方法是原子性操作
    如果 key 不存在,则设置当前 key 成功,返回 1;
    如果当前 key 已经存在,则设置当前 key 失败,返回 0
    
解锁 del (key)
得到锁的线程执行完任务,需要释放锁,以便其他线程可以进入,调用 del(key)

配置锁超时 expire (key,30s)
客户端奔溃或者网络中断,资源将会永远被锁住,即死锁,因此需要给key配置过期时间,以保证即使没有被显式释放,这把锁也要在一定时间后自动释放 

综合伪代码

 methodA(){
  String key = "short_link:code:abcdef"
  
  if(setnx(key,1) == 1){
      expire(key,30,TimeUnit.MILLISECONDS)
      try {
          //做对应的业务逻辑
      } finally {
          del(key)
      }
  }else{
    //睡眠100毫秒,然后自旋调用本方法
    methodA()
  }
}

五、Redis实现分布式锁的坑你发现了吗 

上述伪代码最大的问题:多个命令之间不是原子性操作,如setnx和expire之间,如果setnx成功,但是expire失败,且宕机了,则这个资源就是死锁

可以使用原子命令解决:设置和配置过期时间  setnx / setex
如: set key 1 ex 30 nx
java代码里面 

String key = "short_link:code:abcdef"
redisTemplate.opsForValue().setIfAbsent(key,1,30,TimeUnit.MILLISECONDS)

 但是实际业务应用情况还有更多问题:
业务超时,如何避免其他线程勿删
业务执行时间过长,如何实现锁的自动续期

分布式锁的核心:

是保证多个指令原子性,加锁使用setnx setex 可以保证原子性,那解锁使用判断和设置等怎么保证原子性

多个命令的原子性:采用 lua脚本+redis, 由于【判断和删除】是lua脚本执行,所以要么全成功,要么全失败

流程:
* 先判断是否有,如没这个key,则设置key-value,配置过期时间,加锁成功
* 如果有这个key,判断value是否是同个账号,是同个账号则返回加锁成功
* 如果不是同个账号则加锁失败

代码实现:

//key1是短链码,ARGV[1]是accountNo,ARGV[2]是过期时间
String script = "if redis.call('EXISTS',KEYS[1])==0 then redis.call('set',KEYS[1],ARGV[1]); redis.call('expire',KEYS[1],ARGV[2]); return 1;" +
                " elseif redis.call('get',KEYS[1]) == ARGV[1] then return 2;" +
                " else return 0; end;";


Long result = redisTemplate.execute(new
                DefaultRedisScript<>(script, Long.class), Arrays.asList(code), value,100);

演示效果:同一个code下,不同账号进行加锁。同一个账号则不加锁。

code=aabbcc进入方法,账号是123,加锁成功,返回1 

 code=aabbcc进入方法,账号是123,加锁成功,返回2

 这样就实现了同一个code,同一个账号的可重入锁

code=aabbcc进入方法,账号是567,加锁失败

 实际业务代码中,可result 是否大于0来判断加锁失败和成功,然后走对应的业务逻辑即可。

### 回答1: 在高并发下,可以使用 Redis 的 SETNX 命令来实现分布式锁。SETNX 指令是将 key 的值设为 value,当且仅当 key 不存在。 步骤如下: 1. 使用 SETNX 命令尝试获取,如果返回值为 1,则说明获取成功。 2. 使用 EXPIRE 命令为设置过期时间,防止死。 3. 在业务代码执行完成后,使用 DEL 命令释放。 为了防止网络延迟等问题,可以在获取时设置一个随机值,并在释放时判断该值是否与当前对应的值相同,以确保只有持有的客户端才能释放。 ### 回答2: 高并发Redis分布式锁实现是通过Redis的setnx命令和expire命令来实现的。 首先,通过setnx命令尝试设置一个带有过期时间的key,如果成功设置,则表示获取到了分布式锁。如果设置失败,说明已经被其他客户端占用,需要等待或进行重试。 为了防止因为某个客户端处理时间过长而导致过期的情况,可以为的过期时间设置一个合理的值。在获取到后,可以使用expire命令为的key设置过期时间,确保在一定时间内释放。 为了提高的安全性,可以为每个客户端设置一个唯一的ID作为的值,并将名称与该ID进行绑定。这样可以确保只有获取的客户端才能释放,防止其他客户端误释放。 另外,考虑到高并发情况下的竞争,可以在获取失败后进行等待一段时间再进行重试,避免频繁的竞争对系统性能造成负面影响。 需要注意的是,Redis分布式锁实现并不能解决所有并发问题,仅适用于单个业务场景下的加和释放操作。在设计和使用时需要考虑到具体业务需求和场景,并进行适当的优化和调整。 ### 回答3: 在高并发场景下,为了保证数据的一致性和并发执行的正确性,常常需要使用分布式锁来控制对共享资源的访问。Redis作为一个高性能的内存键值存储系统,也可以用来实现分布式锁Redis实现分布式锁的一种常见方式是使用SETNX命令。当多个客户端同时尝试获取时,只有一个客户端能成功执行SETNX操作,即将对应的key设置为1,表示获取了。其他客户端获取失败,需要等待被释放后重新尝试。 为了保证的正确性和防止死,还需要为设置一个合理的过期时间,以防止获取的客户端因为异常情况导致无法及时释放。 在高并发环境下,为了提高的性能,可以考虑使用红机制。红是将分布式锁Redis的复制功能相结合,确保在大部分节点上都获取到后,才认为已经获得。这样可以避免某个节点出现故障或网络异常导致丢失的情况。 另外,为了避免因为程序异常导致无法释放的情况,可以在获取到之后,使用Lua脚本来保证在一个原子操作中判断的状态并释放。 在实现分布式锁时,还需要考虑高并发的性能问题。可以通过优化Redis的部署结构、增加Redis的内存和CPU资源,以及使用连接池等方法来提高Redis的性能和并发能力,从而提高分布式锁的性能。 综上所述,高并发下的Redis分布式锁实现主要包括使用SETNX命令获取、设置合理的超时时间、使用红机制增强的可靠性、使用Lua脚本保证原子操作、优化Redis的性能和并发能力等方面。
评论 24
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值