新闻详情

新闻详情

首页 / 资讯中心 / 详情

用了 Caffeine,消息为什么还是被处理了两次?

发布时间:2026/10/29 17:16:32来源:尧图网络
用了 Caffeine,消息为什么还是被处理了两次?
最近的工作都是在公司的旧项目框架上去开发新的直播小游戏过程虽然坎坷别人的旧项目你们都懂但总算是顺利上线。然而某一天运营反馈某玩家发了一条参与指令结果扣了两次钱效果也叠加了。这里先普及下直播小游戏的玩法可能很多人未玩过就是玩家在真播间像聊天一样发指定格式的消息指令直播的游戏就会有不同的效果。例如本次玩家发的指令是参与C300表示使用300金币参与C玩法。当平播这边在收到这些弹幕指令后会转到游戏服务器经过游戏服处理后再发给客户端体现。这里也说明一下很多直播平台或支付中心在消息推送时都有个特点同一条消息可能重发。原因很多失败重发、推送地址检查等都会导致同一个消息的IDmessageId被推送多次。针对这种情况一般后端都会对消息做去重处理。从运营反馈的情况看大概率问题也是出在这个去重上。排查开始先简单看一眼代码去重逻辑确实写了用的是 Caffeine。当时第一反应是应该没问题吧Caffeine 挺成熟的。再检查相关日志。同一个 messageId两次进入推送流程16:49:44 INFO pushChat - {comment:参与C300,msgId:7673064607611507746,createTime:1786524582560} 16:49:46 INFO pushChat - {comment:参与C300,msgId:7673064607611507746,createTime:1786524583645}两条日志间隔 1.6 秒。如果是正常的去重命中应该会打一条 WARN[参与C300] msgchat-7673064607611507746 has process, ignored但翻遍日志这条 WARN 根本没出现。也就是说第二条消息进来的时候去重检查返回的是没处理过。奇怪的是同一时间点其他玩家的消息去重是正常的16:49:44 WARN [参与A1200] msgchat-7673064610798359587 has process, ignored 16:49:46 WARN [参与A1200] msgchat-7673064610798359587 has process, ignored同样是重复消息为什么有的能过滤、有的过滤不了再重新检查代码来事了Caffeine 是用了但用法有问题相关的去重处理是这样的privatefinalCacheString,StringMSG_CACHECaffeine.newBuilder().maximumSize(2048).expireAfterAccess(30,TimeUnit.MINUTES).build();privatebooleanisDuplicate(Stringlabel,StringmessageId){try{StringexistedMSG_CACHE.getIfPresent(messageId);if(existed!null){log.warn([{}] msg{} has process, ignored,label,messageId);returntrue;}}catch(Exceptione){}returnfalse;}调用处StringcacheKeychat-chatMsg.getMessageId();if(isDuplicate(chatMsg.getComment(),cacheKey)){continue;}MSG_CACHE.put(cacheKey,1);// 写入标记// ... 后续处理发现问题没有查和写是分开两步getIfPresent是查询put是写入而且isDuplicate后就已经马上put了中间应该不会有问题吧。但调用处实际是一个处理平台推送消息的Controller内这意味着是并发环境下的调用。所以真实情况下的执行时序会是时刻 T1请求A 进入 isDuplicate → 查缓存 MISS → 返回 false 时刻 T2请求B 进入 isDuplicate → 查缓存 MISS → 返回 false ← 缓存里还没写入 时刻 T3请求A 执行 MSG_CACHE.put → 写入缓存 时刻 T4请求B 执行 MSG_CACHE.put → 写入缓存重复写入但已经晚了请求 A 和请求 B 都认为自己没处理过于是都走完了整个流程。参与扣了两次钱。解决方法其实挺简单的对于我们的这个情况Caffeine 已经提供了对应的处理方案使用asMap().putIfAbsent。通过asMap()视图返回的是一个 ConcurrentMap。putIfAbsent是原子操作——返回 null 表示之前没有首次写入成功返回非 null 表示已存在重复跳过。具体实现privatebooleanisDuplicate(Stringlabel,StringmessageId){try{StringprevMSG_CACHE.asMap().putIfAbsent(messageId,1);if(prev!null){log.warn([{}] msg{} has process, ignored,label,messageId);returntrue;}}catch(Exceptione){log.error(isDuplicate error msg{},messageId,e);}returnfalse;}调用处去掉多余的 putStringcacheKeychat-chatMsg.getMessageId();if(isDuplicate(chatMsg.getComment(),cacheKey)){continue;}// 不需要再 putisDuplicate 里已经写入了使用putIfAbsent代替原来getIfPresent put的原因原子操作一步完成查和写没有窗口返回值即判定一步到位性能更好asMap()不是数据拷贝就是内部 Cache 的 ConcurrentMap 视图putIfAbsent内部基于 key hash 分区加锁不同 key 完全无竞争同 key 串行但只覆盖 CAS 级别的临界区比两次操作查写还快。问题是解决了但我更想讲的是对于成熟组件如本次的 Caffeine很多人觉得我用了 Caffeine它内部是线程安全那肯定没问题的。没错Caffeine 单个操作是线程安全的。getIfPresent是线程安全的put也是线程安全的。但你的业务逻辑是两个操作组合组合起来就不安全了。注意我说的这句组件的线程安全不等于你业务逻辑的线程安全。组件自身是线程安全的但是保存这个组件的空间本身并不一定线程安全。这种代码 review 的时候很容易放过逻辑看起来是对的先查、有就跳过、没有就处理然后写入但如果把眼光拉出来放在上一层去看就能发现问题。最后想强调的点第一公用组件不等于自动正确。以这次的 Caffeine 为例“用了 Caffeine” 和 “用对了 Caffeine” 是两回事。这次问题最隐蔽的地方在于代码看起来是对的逻辑是完整的甚至 review 的时候都觉得没问题但直到日志打脸才意识到两步操作之间有窗口执行存在并发。第二多实例部署下本地缓存的天花板。putIfAbsent只能解决了单进程下的并发。但如果部署了多个实例本地缓存不共享同一个 messageId 打到不同实例上还是会重复处理。这种场景要么上 Redis SETNX要么在接入层做一致性 hash 让同 messageId 落到同一实例。最后说一句公用组件不是万能往往只能解决一小部分问题。当真正的问题产生时很多时候真正的原因就藏在你觉得应该没问题的地方。本文首发于【掘金】作者【码路漫漫】
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

【外设】之大彩串口显示屏 2026/10/28 16:06:12

【外设】之大彩串口显示屏

大彩串口屏初步使用 1 .官网下载 STM32 屏幕 GUI 设计资料 http://www.gz-dc.com/category/typeid/4112 找到 STM32 Keil 工程,移植相关代码因项目而异进行移植,由于项目简单,本人只对用到的指令接口进行修改。 比如:注意事项&…

阅读更多 →
无法下载Windows系统iso文件 2026/10/28 13:44:36

无法下载Windows系统iso文件

当我遇到这个问题的时候,我打开了一个网站: 登录 然后我打算下载的时候: 突然那个官方的连接就可以下载了:

阅读更多 →
【清华代码熊】DeepSeek V4.1 Flash 后训练详解 2026/10/29 13:36:24

【清华代码熊】DeepSeek V4.1 Flash 后训练详解

📌 上期解析了 DeepSeek V4.1 Flash 模型架构改进,本期解析 DeepSeek V4.1 Flash 预训练/后训练技术: 🌟 预训练:45T 文本 多模态混合语料、直接训练 sparse attention(取消 DeepSeek V4 的 dense 冷启动&…

阅读更多 →
Shuffle-R1: Efficient RL framework for Multimodal Large Language Models via Data-centric Dynamic ... 2026/10/28 16:06:11

Shuffle-R1: Efficient RL framework for Multimodal Large Language Models via Data-centric Dynamic ...

文章主要内容和创新点 主要内容 本文聚焦于多模态大语言模型(MLLM)强化学习(RL)训练中的效率问题,提出了一个名为Shuffle-R1的框架。研究发现,当前RL训练存在两个关键缺陷: 优势值坍缩(Advantage Collapsing):批次中大多数优势值集中在零附近,导致有效梯度信号被淹…

阅读更多 →
PRvL: Quantifying the Capabilities and Risks of Large Language Models for PII Redaction 2026/10/29 13:36:50

PRvL: Quantifying the Capabilities and Risks of Large Language Models for PII Redaction

一、文章主要内容总结 本文聚焦于利用大型语言模型(LLMs)实现个人身份信息(PII)脱敏的研究,旨在解决传统脱敏方法(如基于规则的系统、领域特定命名实体识别(NER)模型)泛化能力差、跨格式/跨语境适应性弱的问题。 研究通过全面评估多种LLM架构(包括密集型LLM(D-LLM…

阅读更多 →
LLaVA-RE: Binary Image-Text Relevancy Evaluation with Multimodal Large Language Model 2026/10/28 16:06:10

LLaVA-RE: Binary Image-Text Relevancy Evaluation with Multimodal Large Language Model

文章主要内容和创新点 主要内容 本文聚焦于二进制图像-文本相关性评估任务(判断图像与文本“相关”或“不相关”),针对该任务中文本格式多样、相关性定义随场景变化等挑战,提出了基于多模态大语言模型(MLLM)的解决方案LLaVA-RE。 模型设计:LLaVA-RE基于LLaVA 1.5架构,…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉