基于Redis的分布式锁

问题分析

随着业务发展的需要,原单体单机部署的系统被演化成分布式集群系统后,由于分布式系统多线程、多进程并且分布在不同机器上,这将使原单机部署情况下的并发控制锁策略失效,单纯的Java API并不能提供分布式锁的能力。为了解决这个问题就需要一种跨JVM的互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题。

分布式锁主流的实现方案:

  1. 基于数据库实现分布式锁
  2. 基于缓存(redis)
  3. 基于Zookeeper

每一种分布式锁解决方案都有自己的优缺点:

  1. 性能:redis最高
  2. 可靠性:zookeeper最高

这里,我们基于redis实现分布式锁

基于Redis实现

Redis 分布式锁是基于 SETNX 来实现的。SETNX 是 “set if not exist” 的简称,当且仅当key不存在时,set一个key为val的字符串,返回1;若key存在,则什么都不做,返回0。同时,key 还可以通过 expire命令 设置超时时间,通过 del命令删除key。

例:

setnx users 10                上锁

expire users 10                设置过期时间

 但是如果上锁之后出现异常就无法设置过期时间了,所以我们可以采用下面的操作

set users 10 nx ex 12

nx代表上锁,ex代表过期时间为12秒,此时是让上锁与过期时间设置一同进行,此时将两个步骤变成了原子操作

查看过期时间:ttl users

手动释放锁:del users

UUID防止误删

问题:

假如我们现在有三个操作要对同一个数据进行操作,设置过期时间10s,若a先抢到了:

  1. 上锁
  2. 具体操作时,服务器卡顿(超过10s)
  3. 锁自动释放

此时a的操作并未结束,然后b和c抢这把锁,假设b抢到了:

  1. 上锁
  2. 具体操作(未完成)

此时a服务器反应过来,a继续进行操作,操作之后最终手动释放锁,但此时锁在b上,所以它会把b的锁释放,此时问题出现。释放锁的过程中将别人的锁释放。

 解决方案

  1. 利用UUID表示不同的操作   set lock uuid nx ex <过期时间>
  2. 释放锁的时候,首先判断当前UUID和要释放锁的UUID是否一样

LUA保证删除原子性

问题:

首先a先进行操作:

  1. 上锁
  2. 具体操作
  3. 释放锁(del)
    1. 比较uuid,发现一样
    2. 删除操作的时候,正要删除但还没有删除,此时锁到了过期时间,自动释放,此时手动释放锁还未结束

然后b获取到锁:

  1. 上锁
  2. 具体操作
  3. a的手动释放将b的锁释放

这个问题是由删除不是原子性操作造成的。

解决方案

lua脚本:

优势:

  • 将复杂的或者多步的redis操作,写成一个脚本,一次提交给redis执行,减少反复连接redis次数,提高性能
  • LUA脚本是类似redis事务,有一定的原子性,不会被其他命令插队,可以完成一些redis事务性的操作

为了确保分布式锁可用,我们至少要确保加锁的实现同时满足以下四个条件

  • 互斥性。在任意时刻,又有一个客户端能持有锁
  • 不会发生死锁,即一个客户端在持有锁的期间崩溃而没有主动解锁,又能保证后续其他客户端加锁
  • 加锁和解锁必须是同一个客户端
  • 枷锁和解锁必须具有原子性
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

过街的老鼠

感谢你对诗仙女的打赏

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值