记一次愚蠢的操作--线程安全问题

Netty数据粘包问题:解决Adjusted frame length exceeds大数据异常 Netty是一个强大的网络编程框架,但在使用过程中需要注意数据粘包问题的处理。本文介绍了Netty中的一种粘包问题:"Adjusted frame length exceeds大数据"异常,并提出了三种解决方案:定长解码器、行分隔符解码器和分隔符解码器。根据实际场景选择合适的解决方案,可以有效地解决Netty中的数据粘包问题,提高网络通信的可靠性和性能。本文将重点讨论Netty中遇到的一种粘包问题:"Adjusted frame length exceeds大数据"异常,并提出解决方案。 阅读详情

前言

只有光头才能变强。

文本已收录至我的GitHub仓库,欢迎Star:https://github.com/ZhongFuCheng3y/3y

记一次在工作中愚蠢的操作,本文关键字:线程安全

(我怎么天天在写Bug啊)

一、交代背景

我这边有一个系统,提供一个RPC接口去发送各种信息(比如短信、邮件、微信)等等渠道。我这边的系统架构是这样的:

系统架构

概括:service系统提供一个RPC接口,别人调用我提供的接口,我在service系统中对这个消息进行判断、拼接等等业务逻辑,最后会将这个消息放到消息队列里边。sender系统会消费消息队列里边的数据,然后发送消息

例子:小王调用我们的RPC接口,想要发送邮件。我对邮件的参数进行判断和拼装成一个我这边定义好的Task,将这个Task丢到消息队列里边。sender系统消费这个Task,调用java.mail的API完成发送邮件的功能。

小王调用我们这个RPC接口,只要service系统把这个task丢到消息队列里边去,我们就返回response给小王。

  • 只要这个task放到了消息队列里边,我们就返回success。所以有的时候,小王会问:“我这明明返回是success啊,怎么我的邮件没发出去呢” ------(异步)

每发送一封邮件,我们都会将这封邮件的信息入库(保存在MySQL中),在MySQL中我们可以得知这封邮件的发送时间,发送状态等等。

而小王的这些邮件又十分在意是否成功发送出去了,如果发送失败了他那边需要重发。于是,他监听我们DB的binlog,根据binlog的信息来判断是否需要重发。

由于种种的原因,小王希望调用我们RPC接口的时候就能拿到一个唯一的标识好让他去判断这封邮件是成功还是失败

  • 显然,入库的Email ID是不可能的(因为他调我们RPC接口,我们将Task放到消息队列就返回了。此时sender系统还没消费呢)

于是,我们这边打算在service系统生成一个messageId,然后返回给他,将这个messageId绑定到Task里边,一直到入库。

流程图

二、上钩

上面确定好需求和思路之后,我这边就去看返回给小王的response对象,一看,发现已经有msgId字段了

public class SendResponse {
    
    // 错误码
    private int errCode;

    // 错误信息
    private String errInfo;

    // messageId
    private long msgId;

}

我搜了一下这个字段的信息ctrl + shift + f,发现这msgId没有被用到啊。一想,这刚好,我来用了。我看了一下用法,发现这边不是直接使用SendResponse的,而是在外面包了一个枚举类,代码大概如下:

public enum Response {
	
	SUCCESS(1, "success"),
	PARAM_MISSING(2, "param is missing"),
	INVALID_xxxx(3, "xxxx is invalid"),
	INVALID_xxxx(4, "xxxx is invalid"),
	
	private SendResponse sendResponse;
	
	private Response(int errCode, String errInfo) {
		sendResponse = new SendResponse();
		sendResponse.setMsgId(0);
		sendResponse.setErrCode(errCode);
		sendResponse.setErrInfo(errInfo);
	}

	public SendResponse getSendResponse() {
		return sendResponse;
	}

}

有了枚举使用起来就很简单了,比如我发现小王某个参数传进来有问题,我反手就是:

Response.PARAM_ERROR

service系统主要做了两件事

  • 判断参数/类型,各种业务逻辑有没有问题,将小王带过来的参数封装成Task对象
  • 将Task对象放到消息队列里边

两个任务

要明确的是:等到整一个调用链结束(将Task对象放到消息队列中),才会将sendResponse对象返回出去。而又因为可能要判断的地方有点多,所以我们这边是这样设计了一个Map来存储数据,这个Map贯穿整条链路:

// 首先将sendResponse默认设置为success,也就是代码如下:
map.put("sendResponse",Response.SUCCESS);

// 如果中途某个地方可能有问题了,那我们将Map中sendResponse进行修改
map.put("sendResponse",Response.ERROR);

// 等整条链路完成,从Map拿出sendResponse返回
return map.get("sendResponse");

于是我要做的就是:在将SendResponse返回之前,我生成一个唯一的msgId,并插入到SendResponse对象里边就好了。

Response.getSendResponse().setMsgid(uuid);

在返回sendResponse之前插入msgId就好了

这个需求完成得非常快,简单测试了一下也没毛病,就果断上线了。小王用了一阵子也没说有什么问题,于是这个需求就交付了。

三、出现问题

昨天,小王告诉我:“我这边邮件发送失败啦,有msgId,看下是什么原因造成的“

出问题啦

于是我就去捞线上的日志,发现根据他给出的msgId,我这边打出的日志都不是发送邮件的(而是其他Task的日志)。我这就慌了,难道我们这个系统出问题了?

  • 心理活动:msgId能够唯一标识这条Task,而小王发给我的msgId,却是别的Task的内容。是不是出大问题啦(错乱消费?数据全乱了?),惊慌失措

然后,他那边继续补充:

继续补充信息

之后发现邮件是发送成功的,但是他拿到部分的msgId是别的Task的,不是邮件的。于是只能先比对剩下的邮件是否有问题,再看看MsgId是什么原因。

解决首要的问题

四、寻找问题

现有的条件是:

  • 那批邮箱发送是成功的
  • 小王拿到了别的Task的msgId

所以,判断系统是没问题的,只是msgId在并发的过程中出了问题(拿到其他Task的msgId了)

于是我就去找原因啦,在查代码的时候发现前同事还在Service系统中的某个类留了一个注解@NotThreadSafe。我就觉得肯定是中途哪个地方我没注意到,导致小王拿到了其他Task的msgId。

人肉Debug了一个午休的时间还是没找出来:每个线程都独有一份的操作对象,对象的属性都没有逸出(都在方法内部操作),跟着整块链路一直传递,直至链路结束。

后来,一想,我应该只看msgId生成的地方就好了呀。才发现,项目里边用的是枚举啊!

// 首先将sendResponse默认设置为success,也就是代码如下:
map.put("sendResponse",Response.SUCCESS);

// 如果中途某个地方可能有问题了,那我们将Map中sendResponse进行修改
map.put("sendResponse",Response.ERROR);

// 把response的msgId的值设置为当前Task绑定的值
map.get("sendResponse").setMsgid(uuid);

// 等整条链路完成,从Map拿出sendResponse返回
return map.get("sendResponse");

醒悟:

  • 现在我有50个线程,每个线程在处理数据的时候都会有一个默认的sendResponse对象,这个对象是用枚举来标识Response.SUCCESS。所以,这50个线程都共享着这个sendResponse对象
  • 50个线程共享着这个sendResponse对象,每个线程都可以修改sendResponse里边的msgId属性,这就自然是线程不安全的。
  • 所以小王能拿到其他Task的msgId(小王的线程设置完msgId之后,还没返回,三歪的线程又更改了一次msgId,导致小王拿到三歪的msgId了)

总结:

  • 终于知道为啥当初前同事在代码上保留了msgId属性,但是没有使用这个属性。
  • 使用枚举就不应该带 有状态的属性(能修改、可变的属性)

最后

乐于输出干货的Java技术公众号:Java3y。公众号内有200多篇原创技术文章、海量视频资源、精美脑图,关注即可获取!

转发到朋友圈是对我最大的支持!

觉得我的文章写得不错,点赞!

【集合】Vector 是线程安全的? 1.美图 2.概述 今天遇到一个问题,让我怀疑Vector是否是线程安全的 【Flink】报错 KryoException ConcurrentModificationException StackOverflowError 或许,大家经常也碰到类似的问题:Vector 与 ArrayList 的区别? 好多人一拍脑门就出:Vector 是线程安全的 (在任何情况下都是)。。。 原因可能是因为 ... 阅读详情

相关推荐

运动目标成像必学:5分钟搞懂keystone变换原理与避坑指南

本文深入浅出地解析了雷达信号处理中的Keystone变换原理,它是解决运动目标距离徙动问题的关键技术。文章通过直观比喻和简化公式,阐述了该变换如何通过变量代换实现距离与慢时间的解耦合,将倾斜轨迹“掰直”,从而提升成像与检测性能。同时,文中重点指出了实际应用中关于大速度场景、插值误差、多目标处理及高阶运动补偿等关键避坑点,为工程实践提供了宝贵指南。

ios99的博客 403

Adjusted frame length exceeds 4096: 5637 - discarded 服务端解决

解决服务端Adjusted frame length exceeds 4096: 5637 - discarded问题1. 具体错误2. 错误原因3. 解决方法 1. 具体错误 下面展示一些 错误详情。 io.netty.handler.codec.TooLongFrameException: Adjusted frame length exceeds 4096: 18247 - discarde...

weixin_44055686的博客 2万+

ISP调试工程师相关面试问题_001

Qualcomm、MTK等ISP pipeline的框架原理及算法原理,还有调试经验。

notgoodenouf的博客 3551

Adjusted frame length exceeds 1677216:16777318-discarded

消息队列的nameser和broker运行后,在console无法显示,进入`broker.log`,`namesrv.log` 显示如下报错`Adjusted frame length exceeds 1677216:16777318-discarded`

冰川的博客 1870

手把手教你用Ollama & AnythingLLM搭建AI知识库,无需编程,跟着做就行!

本地运行大模型耗资源,需要选择较小的模型作为基础模型。在终端中运行时可能会出现 CUDA 错误,表示显存不足,导致提供的端口失效。中文支持不够完善。\3. 文中提到的技术软件工具有:Ollama、Chatbox、Open WebUI、向量数据库、嵌入模型、本地模型 Gemma、AnythingLLM。

2401_86435672的博客 1万+

自动驾驶入门到进阶完整指南:感知、预测、规划、端到端、仿真测评与场景挖掘全解析

对小白来说,不建议一开始直接冲端到端大模型。先学数据和坐标系:否则所有结果都看不懂。再学 LiDAR 3D 检测:最容易建立空间感。再学 BEV/多相机感知:这是当前主流。再学轨迹预测:理解交互和多模态。再学规划控制:理解车为什么这样开。再学仿真和闭环评测:理解 open-loop 和 closed-loop 差异。最后学端到端/VLM/VLA:这时候你才知道它到底替代了哪些模块。贯穿始终学习场景挖掘和安全测评:这是工业界最核心的能力之一。

u013250861的博客 394

[Spring Cloud Alibaba] Adjusted frame length exceeds 8388608

2020-11-12 10:47:48.952 INFO [NettyServerNIOWorker_1_8]io.seata.core.rpc.netty.AbstractRpcRemotingServer.exceptionCaught:278 -channel exx:Adjusted frame length exceeds 8388608: 539976035 - discarded,channel:[id: 0xe40eb690, L:/192.168.2.47:9091 - R:/192.16

u012433915的专栏 5844

深入解析CycloneDDS跨域通信中的XML配置文件优化策略

本文深入解析了CycloneDDS跨域通信中XML配置文件的优化策略,重点介绍了网络接口配置、多播设置、性能调优参数及复杂场景下的配置案例。通过实战技巧和调试方法,帮助开发者在SoC环境中实现高效稳定的跨域通信,提升系统性能。

soda5的博客 688

消息队列 线程安全_记一次愚蠢的操作--线程安全问题

前言 只有光头才能变强。 文本已收录至我的GitHub仓库,欢迎Star:https://github.com/ZhongFuCheng3y/3y记一次在工作中愚蠢的操作,本文关键字:线程安全(我怎么天天在写Bug啊)一、交代背景我这边有一个系统,提供一个RPC接口去发送各种信息(比如短信、邮件、微信)等等渠道。我这边的系统架构是这样的:概括:service系统提供一个RPC接口,别人调用我提供的...

weixin_39988476的博客 483

STM32开发 -- GPS模块开发详解

一、了解硬件 我使用的GPS模块型号为UBX-M8030 参看:UBX-M8030 系列 查看一下它的特性: 多用途 GNSS 芯片,提供三种产品等级 最多可并发接收 3 个 GNSS(GPS、伽利略、GLONASS、北斗) 行业领先的 -167 dBm 导航灵敏度 业界最低电流消耗 在城市峡谷中具有绝佳的定位精度 安全性和完整性保护 支持所有的卫星增强系统 车载级芯片的工作...

不积跬步,无以至千里 3万+

基于Simulink的发动机管理系统中的自适应增压控制系统设计

通过上述详细设计步骤,我们成功开发了一个基于Simulink的自适应增压控制系统。该系统不仅能够实时监测和预测发动机的运行状态,动态调整增压压力目标值,还具备实时监控、故障诊断和保护功能,确保系统的安全性和可靠性。此外,通过仿真实验和硬件在环测试,我们验证了系统的性能,并对其进行了优化,使其能够更好地满足实际应用需求。这个项目展示了如何利用MATLAB/Simulink的强大功能,结合控制理论和工程实践,开发出高效、可靠的自适应增压控制系统。

MHD0815的博客 220

winform判断线程有没有完成_记一次愚蠢的操作线程安全问题

前言只有光头才能变强。文本已收录至我的GitHub仓库,欢迎Star:https://github.com/ZhongFuCheng3y/3y记一次在工作中愚蠢的操作,本文关键字:线程安全(我怎么天天在写Bug啊)--本文适合新手观看一、交代背景我这边有一个系统,提供一个RPC接口去发送各种信息(比如短信、邮件、微信)等等渠道。我这边的系统架构是这样的:系统架构概括:service系统提...

weixin_39616961的博客 101

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

winform判断线程有没有完成_记一次愚蠢的操作--线程安全问题

记一次在工作中愚蠢的操作,本文关键字:线程安全(我怎么天天在写Bug啊)--本文适合新手观看一、交代背景我这边有一个系统,提供一个RPC接口去发送各种信息(比如短信、邮件、微信)等等渠道。我这边的系统架构是这样的:系统架构概括:service系统提供一个RPC接口,别人调用我提供的接口,我在service系统中对这个消息进行判断、拼接等等业务逻辑,最后会将这个消息放到消息队列里边。sender系统...

weixin_39620279的博客 127

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

工作中遇到的线程安全问题,你可千万不要这样做!

记一次在工作中愚蠢的操作,本文关键字:线程安全一、交代背景我这边有一个系统,提供一个RPC接口去发送各种信息(比如短信、邮件、微信)等等渠道。我这边的系统架构是这样的:系...

WantFlyDaCheng的博客 404

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

Vector 是线程安全的?

或许,大家经常也碰到类似的问题:Vector 与 ArrayList 的区别? 好多人一拍脑门就出:Vector 是线程安全的 (在任何情况下都是)。。。 原因可能是因为 Vector 的所有方法加上了 synchronized 关键字,从而保证访问 vector 的任何方法都必须获得对象的 intrinsic lock (或叫 monitor lock),也即,在vector内部,其

编程路上的野花 1万+

Stream的collect方法是怎么保证线程安全的

写这篇文章是因为前段时间有个同事用parallelStream处理集合的时候发现数据丢了。因为是线上问题所以和他一起看了一下。 出问题的代码大致如下: List<Foobar> result = new ArrayList<>(); fooList.parallelStream().forEach(bar -> { Foobar foobar = new...

z69183787的专栏 4386

愚蠢的bug

<br />调试了3天的内存错误。最后发现是,多线程队列操作产生的。<br /> <br />while(true)<br />{<br />     if(queue.empty())<br />         continue;<br /> <br />     queue.pop(elem);<br /> <br />}<br />出队列与判断队列为空内部都加了锁,是线程安全的。但是判空和出队列之间没有加锁。这个之间队列有可能为空。导致elem没有值。<br />而这段程序没有判断pop()的返回值

273
上一篇: 在工作中常用到的SQL
下一篇: 最近学到的前后端分离知识
Java3y
Java3y 领域专家: 后端开发技术领域 领域专家: 后端开发技术领域
博客等级 码龄9年 1万+粉丝 509原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值