分析Redis分布式锁的原理与实现相关知识
在单体应用中,如果我们对共享数据不进行加锁操作,会出现数据一致性问题,我们的解决办法通常是加锁。在分布式架构中,我们同样会遇到数据共享操作问题,此时,我们就需要分布式锁来解决问题,下面我们一起聊聊使用redis来实现分布式锁。
库存超卖 比如 5个笔记本 A 看 准备买3个 B 买2个 C 4个 一下单 3+2+4 =9 防止用户重复下单 MQ消息去重 订单操作变更 为什么要使用分布式锁从业务场景来分析,有一个共性,共享资源的竞争,比如库存商品,用户,消息,订单等,这些资源在同一时间点只能有一个线程去操作,并且在操作期间,禁止其他线程操作。要达到这个效果,就要实现共享资源互斥,共享资源串行化。其实,就是对共享资源加锁的问题。在单应用(单进程多线程)中使用锁,我们可以使用synchronize、ReentrantLock等关键字,对共享资源进行加锁。在分布式应用(多进程多线程)中,分布式锁是控制分布式系统之间同步访问共享资源的一种方式。
如何使用分布式锁互斥性
在任意时刻,只有一个客户端可以持有锁(排他性)
高可用,具有容错性
只要锁服务集群中的大部分节点正常运行,客户端就可以进行加锁解锁操作
避免死锁
具备锁失效机制,锁在一段时间之后一定会释放。(正常释放或超时释放)
加锁和解锁为同一个客户端
一个客户端不能释放其他客户端加的锁了
分布式锁的实现方式(以redis分布式锁实现为例)简单版本
/*** 简单版本
* @author:liyajie
* @createTime:2022/6/22 15:42
* @version:1.0
*/
public class SimplyRedisLock {
// Redis分布式锁的key
public static final String REDIS_LOCK = redis_lock
@Autowired
StringRedisTemplate template;
public String index(){
// 每个人进来先要进行加锁,key值为 redis_lock ,value随机生成
String value = UUID.randomUUID().toString().replace( - , );
try{
// 加锁
Boolean flag = template.opsForValue().setIfAbsent(REDIS_LOCK, value);
// 加锁失败
if(!flag){
return 抢锁失败!
}
System.out.println( value+ 抢锁成功 );
// 业务逻辑
String result = template.opsForValue().get( 001 );
int total = result == null ? 0 : Integer.parseInt(result);
if (total 0) {
int realTotal = total 1;
template.opsForValue().set( 001 , String.valueOf(realTotal));
// 如果在抢到所之后,删除锁之前,发生了异常,锁就无法被释放,
// 释放锁操作不能在此操作,要在finally处理
// template.delete(REDIS_LOCK);
System.out.println( 购买商品成功,库存还剩: + realTotal + 件 );
return 购买商品成功,库存还剩: + realTotal + 件
} else {
System.out.println( 购买商品失败 );
}
return 购买商品失败
}finally {
// 释放锁
template.delete(REDIS_LOCK);
}
}
该种实现方案比较简单,但是有一些问题。假如服务运行期间挂掉了,代码完成了加锁的处理,但是没用走的finally部分,即锁没有释放,这样的情况下,锁是永远没法释放的。于是就有了改进版本。
进阶版本
/*** 进阶版本
* @author:liyajie
* @createTime:2022/6/22 15:42
* @version:1.0
*/
public class SimplyRedisLock2 {
// Redis分布式锁的key
public static final String REDIS_LOCK = redis_lock
@Autowired
StringRedisTemplate template;
public String index(){
// 每个人进来先要进行加锁,key值为 redis_lock ,value随机生成
String value = UUID.randomUUID().toString().replace( - , );
try{
// 加锁
Boolean flag = template.opsForValue().setIfAbsent(REDIS_LOCK, value,10L, TimeUnit.SECONDS);
// 加锁失败
if(!flag){
return 抢锁失败!
}
System.out.println( value+ 抢锁成功 );
// 业务逻辑
String result = template.opsForValue().get( 001 );
int total = result == null ? 0 : Integer.parseInt(result);
if (total 0) {
int realTotal = total 1;
template.opsForValue().set( 001 , String.valueOf(realTotal));
// 如果在抢到所之后,删除锁之前,发生了异常,锁就无法被释放,
// 释放锁操作不能在此操作,要在finally处理
// template.delete(REDIS_LOCK);
System.out.println( 购买商品成功,库存还剩: + realTotal + 件 );
return 购买商品成功,库存还剩: + realTotal + 件
} else {
System.out.println( 购买商品失败 );
}
return 购买商品失败
}finally {
// 释放锁
template.delete(REDIS_LOCK);
}
}
这种实现方案,对key增加了一个过期时间,这样即使服务挂掉,到了过期时间之后,锁会自动释放。但是仔细想想,还是有问题。比如key值的过期时间为10s,但是业务处理逻辑需要15s的时间,这样就会导致某一个线程处理完业务逻辑之后,在释放锁,即删除key的时候,删除的key不是自己set的,而是其他线程设置的,这样就会造成数据的不一致性,引起数据的错误,从而影响业务。还需要改进。
进阶版本2-谁设置的锁,谁释放
/*** 进阶版本2-谁设置的锁,谁释放
* @author:liyajie
* @createTime:2022/6/22 15:42
* @version:1.0
*/
public class SimplyRedisLock3 {
// Redis分布式锁的key
public static final String REDIS_LOCK = redis_lock
@Autowired
StringRedisTemplate template;
public String index(){
// 每个人进来先要进行加锁,key值为 redis_lock ,value随机生成
String value = UUID.randomUUID().toString().replace( - , );
try{
// 加锁
Boolean flag = template.opsForValue().setIfAbsent(REDIS_LOCK, value,10L, TimeUnit.SECONDS);
// 加锁失败
if(!flag){
return 抢锁失败!
}
System.out.println( value+ 抢锁成功 );
// 业务逻辑
String result = template.opsForValue().get( 001 );
int total = result == null ? 0 : Integer.parseInt(result);
if (total 0) {
int realTotal = total 1;
template.opsForValue().set( 001 , String.valueOf(realTotal));
// 如果在抢到所之后,删除锁之前,发生了异常,锁就无法被释放,
// 释放锁操作不能在此操作,要在finally处理
// template.delete(REDIS_LOCK);
System.out.println( 购买商品成功,库存还剩: + realTotal + 件 );
return 购买商品成功,库存还剩: + realTotal + 件
} else {
System.out.println( 购买商品失败 );
}
return 购买商品失败
}finally {
// 谁加的锁,谁才能删除!!!!
if(template.opsForValue().get(REDIS_LOCK).equals(value)){
template.delete(REDIS_LOCK);
}
}
}
这种方式解决了因业务复杂,处理时间太长,超过了过期时间,而释放了别人锁的问题。还会有其他问题吗?其实还是有的,finally块的判断和del删除操作不是原子操作,并发的时候也会出问题,并发就是要保证数据的一致性,保证数据的一致性,最好要保证对数据的操作具有原子性。于是还是要改进。
进阶版本3-Lua版本
/*** 进阶版本-Lua版本
* @author:liyajie
* @createTime:2022/6/22 15:42
* @version:1.0
*/
public class SimplyRedisLock3 {
// Redis分布式锁的key
public static final String REDIS_LOCK = redis_lock
@Autowired
StringRedisTemplate template;
public String index(){
// 每个人进来先要进行加锁,key值为 redis_lock ,value随机生成
String value = UUID.randomUUID().toString().replace( - , );
try{
// 加锁
Boolean flag = template.opsForValue().setIfAbsent(REDIS_LOCK, value,10L, TimeUnit.SECONDS);
// 加锁失败
if(!flag){
return 抢锁失败!
}
System.out.println( value+ 抢锁成功 );
// 业务逻辑
String result = template.opsForValue().get( 001 );
int total = result == null ? 0 : Integer.parseInt(result);
if (total 0) {
int realTotal = total 1;
template.opsForValue().set( 001 , String.valueOf(realTotal));
// 如果在抢到所之后,删除锁之前,发生了异常,锁就无法被释放,
// 释放锁操作不能在此操作,要在finally处理
// template.delete(REDIS_LOCK);
System.out.println( 购买商品成功,库存还剩: + realTotal + 件 );
return 购买商品成功,库存还剩: + realTotal + 件
} else {
System.out.println( 购买商品失败 );
}
return 购买商品失败
}finally {
// 谁加的锁,谁才能删除,使用Lua脚本,进行锁的删除
Jedis jedis = null;
try{
jedis = RedisUtils.getJedis();
String script = if redis.call( get ,KEYS[1]) == ARGV[1] +
then +
return redis.call( del ,KEYS[1]) +
else +
return 0 +
end
Object eval = jedis.eval(script, Collections.singletonList(REDIS_LOCK), Collections.singletonList(value));
if( 1 .equals(eval.toString())){
System.out.println( del redis lock ok . );
}else{
System.out.println( del redis lock error . );
}
}catch (Exception e){
}finally {
if(null != jedis){
jedis.close();
}
}
}
}
这种方式,规定了谁上的锁,谁才能删除,并且解决了删除操作没有原子性问题。但还没有考虑缓存,以及Redis集群部署下,异步复制造成的锁丢失:主节点没来得及把刚刚set进来这条数据给从节点,就挂了。所以还得改进。
终极进化版
/*** 终极进化版
* @author:liyajie
* @createTime:2022/6/22 15:42
* @version:1.0
*/
public class SimplyRedisLock5 {
// Redis分布式锁的key
public static final String REDIS_LOCK = redis_lock
@Autowired
StringRedisTemplate template;
@Autowired
Redisson redisson;
public String index(){
RLock lock = redisson.getLock(REDIS_LOCK);
lock.lock();
// 每个人进来先要进行加锁,key值为 redis_lock
String value = UUID.randomUUID().toString().replace( - , );
try {
String result = template.opsForValue().get( 001 );
int total = result == null ? 0 : Integer.parseInt(result);
if (total 0) {
// 如果在此处需要调用其他微服务,处理时间较长。。。
int realTotal = total 1;
template.opsForValue().set( 001 , String.valueOf(realTotal));
System.out.println( 购买商品成功,库存还剩: + realTotal + 件 );
return 购买商品成功,库存还剩: + realTotal + 件
} else {
System.out.println( 购买商品失败 );
}
return 购买商品失败
}finally {
if(lock.isLocked() lock.isHeldByCurrentThread()){
lock.unlock();
}
}
}
这种实现方案,底层封装了多节点redis实现的分布式锁算法,有效防止单点故障,感兴趣的可以去研究一下。
分析问题的过程,也是解决问题的过程,也能锻炼自己编写代码时思考问题的方式和角度。
到此这篇关于详解Redis分布式锁的原理与实现的文章就介绍到这了,更多相关Redis分布式锁内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!
我想要获取技术服务或软件
服务范围:MySQL、ORACLE、SQLSERVER、MongoDB、PostgreSQL 、程序问题
服务方式:远程服务、电话支持、现场服务,沟通指定方式服务
技术标签:数据恢复、安装配置、数据迁移、集群容灾、异常处理、其它问题
本站部分文章参考或来源于网络,如有侵权请联系站长。
数据库远程运维 分析Redis分布式锁的原理与实现相关知识
相关文章
- Redis中文官方网站:让专业技术变得更加普及(redis中文官方网站)
- Redis:存储分布式数据的可靠底层技术(redis是干嘛用的)
- 基于Redis的分布式中间件系统(redis中间件)
- 如何解决 Redis 内存不足的问题?(redis内存不够用)
- 用Redis安全存储用户信息(redis存储用户信息)
- 构建基于Redis分布式架构的服务器环境(redis分布式架构)
- 建立Redis集群,实现分布式数据库的稳定运作(redis集群db)
- 极速分布式Redis实现订阅发布模式(订阅发布模式redis)
- 美团Redis构建分布式安全锁(美团redis 分布式锁)
- 检查Redis是否已经安装(看redis有没有安装)
- 电商安全解决方案Redis分布式锁(电商redis分布式锁)
- 为何Redis极速提升你的网站性能(为什么redis比较快)
- 利用分布式Redis优化消息队列处理(分布式redis消息队列)
- 分布式环境下使用Redis共享数据(分布式redis共享数据)
- 如何利用Redis构建分布式系统(如何实现redis分布式)
- 动如何关闭Redis自启动一步一步操作指南(如何关闭redis自启)
- 确保Redis最大效率坚持竞争(保持redis竞争)
- 解读Redis默认数据库容量限制(redis默认数据库容量)
- 红色的分布式与集群Redis实现多机分布式存储(redis集群和分布式)
- 优雅重组 Redis 集群节点(redis 集群删减节点)
- 以Redis为技术核心的分布式集群管理(redis集群分布式管理)
- 如何使用Redis防止数据库意外清空(redis防止清空数据库)
- 查看Redis队列总数一种创新方式(redis 队列总数)
- Redis实现快速键值获取(redis 键值获取)