用redis实现分布式锁时容易踩的5个坑

我的业务场景是这样的,我们服务有库存模块,而我的服务又是多节点部署,要高峰期会存在库存差异,后面分析问题之后,打算采用redis实现分布式锁(主要的原因是服务已经集成了redis,不需要做额外的配置)。

踩坑1、数据库事务超时

不要感觉奇怪,分布式锁怎么会导致数据库事务超时呢?

我的代码大概是这样的:

伪代码
@Transaction(readOnly=false)
void update(){
    do{
        redis=JedisUtil.getJedis();
        flag = getLock(key,redis);
        if(flag){
            update();
        }
    }while(true)
}复制代码

当你的key长时间获取不到锁,并且数据库事务都有超时时间的限制,那么就会出现数据库事务超时问题;

解决方案:

数据库事务改为手动提交事务。

踩坑2、redis key过期,而业务没有执行完

我的key的过期时间设置的是30s,如果30秒业务还没有执行完毕,锁就会自动释放,锁释放之后,其它线程又会去占用锁,同样会导致问题的发生。

解决方案:

最简单的解决方案就是使用redisson;

如果非要用redis来解决的话,只能使用定时器去检测key,如果说key还有2秒就快过期了,那么再为key重新设置30秒的过期时间。

踩坑3、redis连接池爆满

分布式锁刚加上之后,生产出现一个问题,就是:

redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool

解决办法:

开始查代码,发现是开发人员没有对连接进行释放;

修复bug之后,又在线上跑了一段时间,又出现了

redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool

解决办法

void update(){
    do{
        redis=JedisUtil.getJedis();
        flag = getLock(key,redis);
        if(flag){
            update();
        }else{
            // 释放当前redis连接
            // 由于我们的业务场景属于比较耗时的业务型,所以在这里休眠1000毫秒
            redis.close();
            sleep(1000);
        }
    }while(true)
}复制代码

当前请求获取锁,如果获取不到,则释放当前连接,并休眠一会; 合理配置redis连接池大小,主要参考具体业务场景的并发量来设置。

踩坑4、解铃还须系铃人

回顾一下加锁的参数:

set(key, vlue,"NX","PX", 30000);复制代码

其中:value,我使用它来表示加锁人,必须是一个唯一的标识

比如:

A线程 key=test value=01

B线程 key=test value=02

如果A线程执行业务耗时超过了锁的持有时间,锁会自动释放;锁自动释放之后,线程B又加锁成功,但是,此时A线程执行完业务逻辑之后,去释放锁,但A线程的锁已经自动释放了,如果没有value来标识的话,它可能就会去释放B线程的锁。

踩坑5、redis集群实现分布式锁

这种情况我没有遇到,因为公司的redis集群做了改进;

先说一下这种问题产生的原因:

如果master节点由于某原因发生了主从切换,那么就会出现锁丢失的情况;

在master节点上拿到了锁;

但是这个加锁的key还没有同步到slave节点;

master故障,发生故障转移,slave节点升级为master节点;

故导致锁丢失;

解决办法:

需要通过使用redlock算法;

或使用redisson,它有对redlock算法做封装。

原文链接: blog.51cto.com/13733462/24… 文源网络,仅供学习之用,如有侵权,联系删除。

我将优质的技术文章和经验总结都汇集在了我的公众号【Java圈子】里。

为方便大家学习,我还整理了一套学习资料,涵盖Java虚拟机、spring框架、Java线程、数据结构、设计模式等等,免费提供给热爱Java的同学~

file

猜你喜欢

转载自juejin.im/post/5e97d38de51d45470720d0aa