当前位置:   article > 正文

实习踩坑之路:集群场景下,我的redis锁为什么不生效啊?别的机器上的线程给解锁了?_@redislock不生效原因

@redislock不生效原因

今天在小组codereview的时候,被大佬们揪出来我的程序写的拉的一点,分享出来,部分代码涉及内部机密,我用注释尽量描述清楚

场景

我是一个对话场景,需要对群会话加锁,然后去自动回复的,也就是说,我需要在消息进来的时候,不能让同一个群的所有人触发我这个自动回复的规则,只有在拿到锁的执行完自动回复解锁之后,在进行加锁/解锁

代码

  			//判断需要加锁
            boolean lockFlag = judgeLock(chatMsgDO.getChatType(),autoReplyRuleDO);
            if (lockFlag){
                //群聊消息处理
                //会话id + 规则主键ID
                String key = RedisKeyConstant.genAutoReplyKey(chatMsgDO.getBindId(),autoReplyRuleDO.getId());
                try{
                    redisService.lockWithExpireTimeAndRetryTimes(key, 60, 5);
                }catch (Exception e){
                    log.info("[关键词自动回复(群聊)]加锁失败,{}微信不处理",chatMsgDO.getReceiverId());
                }
                //直接保证只有一个托管微信进行同一个消息内容的自动回复
                //判断群聊回复时是否需要@
                 boolean mentionSwitch = judgeNeedMention(autoReplyRuleDO);
                 if (mentionSwitch == true) {
                      //需要 @,拼接@人    目前只能带着文本消息@
                      conversationSendMsgCommand.setMentionIdList(Arrays.asList(chatMsgDO.getSenderId()));
                 }
                 //处理自动回复公共逻辑
                 handlerAutoReplyEvent(sendMsgBO, chatMsgDO, autoReplyRuleDO);
   				 redisService.unlock(key);
            }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

问题

这个地方就遇到了一个问题,因为我这个项目是集群环境的,不只是一台机器,由于我写的代码有点拉,存在bug,我这边大概梳理一下
1.加锁解锁没有在try,catch,finally中(可以思考下,即使加了finally正确么)
2.没有考虑到加锁失败的场景,加锁失败程序会怎么样?

如果说我简单的加一下try/catch/finally,你看这样正确么?

  			boolean lockFlag = judgeLock(chatMsgDO.getChatType(),autoReplyRuleDO);
            if (lockFlag){
                //群聊消息处理
                //会话id + 规则主键ID
                String key = RedisKeyConstant.genAutoReplyKey(chatMsgDO.getBindId(),autoReplyRuleDO.getId());
                try{
                    redisService.lockWithExpireTimeAndRetryTimes(key, 60, 5);
                    //直接保证只有一个托管微信进行同一个消息内容的自动回复
                    //判断群聊回复时是否需要@
                    boolean mentionSwitch = judgeNeedMention(autoReplyRuleDO);
                    if (mentionSwitch == true) {
                         //需要 @,拼接@人    目前只能带着文本消息@
                         conversationSendMsgCommand.setMentionIdList(Arrays.asList(chatMsgDO.getSenderId()));
                    }
                    //处理自动回复公共逻辑
                    handlerAutoReplyEvent(sendMsgBO, chatMsgDO, autoReplyRuleDO);
                }catch (Exception e){
                    log.info("[关键词自动回复(群聊)]加锁失败,{}微信不处理",chatMsgDO.getReceiverId());
                }finally {
                    //最后会话消息解锁
                    redisService.unlock(key);
                }          
            }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

这个版本的代码安全么?
答案是不安全的
1.有基础的小伙伴都知道,如果try里面抛异常,那么finally是会执行到的,对吧,那它怎么解锁,这个地方就报错了
2.即使加锁成功,但是我们是集群环境,会不会存在机器2的线程,把机器1的刚加的锁给释放了?答案是肯定的,有可能的

我画张图,你可以这样来理解
在这里插入图片描述

解决办法

  1. 代码写法/流程,让他不会出现这种情况,也就是说我只有加锁成功的,才能解锁,不让你失败的走finally,代码如下
  boolean lockFlag = judgeLock(chatMsgDO.getChatType(),autoReplyRuleDO);
            if (lockFlag){
                //群聊消息处理
                //会话id + 规则主键ID
                String key = RedisKeyConstant.genAutoReplyKey(chatMsgDO.getBindId(),autoReplyRuleDO.getId());
                boolean lock = redisService.lockWithExpireTimeAndRetryTimes(key, 60, 5);
                if(lock) {
                     try{
                         //直接保证只有一个托管微信进行同一个消息内容的自动回复
                         //判断群聊回复时是否需要@
                         boolean mentionSwitch = judgeNeedMention(autoReplyRuleDO);
                         if (mentionSwitch == true) {
                             //需要 @,拼接@人    目前只能带着文本消息@
                             conversationSendMsgCommand.setMentionIdList(Arrays.asList(chatMsgDO.getSenderId()));
                         }
                         //处理自动回复公共逻辑
                         handlerAutoReplyEvent(sendMsgBO, chatMsgDO, autoReplyRuleDO);
                     } catch (Exception e){
                         log.info("[关键词自动回复(群聊)]加锁失败,{}微信不处理",chatMsgDO.getReceiverId());
                     }finally {
                         //最后会话消息解锁
                         redisService.unlock(key);
                     }
                } else {
                    //加锁失败
                    log.error("[加锁失败],消息内容={}",chatMsgDO);
                    return;
                }
            }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  1. 方法二就是携带唯一标识,解锁的时候判断一下这个唯一标识是不是一样的,一样的才能解锁
    常用的方法就有,线程号、UUID这种散列性更强的
    加锁的时候携带这个随机数
//加锁的时候
 String key = RedisKeyConstant.genAutoReplyKey(chatMsgDO.getBindId(),autoReplyRuleDO.getId(),随机数/线程ID);

//解锁的时候
if(唯一标识==随机数/线程ID)
	 redisService.unlock(key);

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/羊村懒王/article/detail/200981
推荐阅读
相关标签
  

闽ICP备14008679号