赞
踩
今天在小组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); }
这个地方就遇到了一个问题,因为我这个项目是集群环境的,不只是一台机器,由于我写的代码有点拉,存在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.有基础的小伙伴都知道,如果try里面抛异常,那么finally是会执行到的,对吧,那它怎么解锁,这个地方就报错了
2.即使加锁成功,但是我们是集群环境,会不会存在机器2的线程,把机器1的刚加的锁给释放了?答案是肯定的,有可能的
我画张图,你可以这样来理解
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; } }
//加锁的时候
String key = RedisKeyConstant.genAutoReplyKey(chatMsgDO.getBindId(),autoReplyRuleDO.getId(),随机数/线程ID);
//解锁的时候
if(唯一标识==随机数/线程ID)
redisService.unlock(key);
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。