BUG—Nuget包版本不一致导致程序行为与预期不符
注:本文收录于《Bug集锦》,请点击此处查看全文目录
BUG起因
先介绍一下背景:
数周前的一个极其平常的下午,完成了本次迭代的开发工作,发布到QA提测,然后开始摸鱼。没几分钟,测试就来找我“麻烦”了:生产者的消息没有发送到RocketMQ的队列中。
“简单,看下日志就能定位原因了”,心想着,随即打开日志,果然,报错了,可是,这个错误消息,啧啧啧,看不懂啊:
NewLife.RocketMQ.Protocol.ResponseException: 1: the custom field <c> is null
在 NewLife.RocketMQ.ClusterClient.Invoke(RequestCode request, Object body, Object extFields, Boolean ignoreError)
在 NewLife.RocketMQ.Producer.Publish(Message msg, Int32 timeout)
......
c
是个什么玩意?我不记得有这东西啊!
BUG排查
问题没有头绪,只能先在本地环境测试一下了。先使用本地的生产者生产一条消息,发送到Dev环境的RocketMQ队列中,结果消息顺利到达,无异常。然后,将本地RocketMQ配置修改为QA环境配置,并生产一条消息,意外的是,消息也顺利到达了,并未出现异常。
这就奇怪了,既然无法快速解决,先启用“重启大法”,重新发个版本试试吧。发版后,果不其然,测试那边说没问题了。
可是,还没摸鱼一分钟,测试又来“找麻烦”了,问题又出现了:生产者的消息时而能够发送成功,时而发送失败。哎,最担心的问题还是出现了,这种时而报错时而正常的问题最难处理了。
RocketMQ版本升级了
不得不说,.NET对RocketMQ的支持确实不太好,NewLife.RocketMQ
是为数不多的能用于生产环境的开源库,去github碰碰运气吧,万一其他人也遇到这个问题了呢。
运气不错!其中一条Issue引起了我的注意——调用出错,似乎版本不兼容 rocketmq v4.9.1:
哦?难不成是有人偷偷的把QA环境的RocketMQ升级了?通过RocketMQ控制台看看:
靠!有一台broker版本升级为v4.9.2了,QA环境的Topic是不久前创建的,其中部分队列就分配到这台机器上了。
找到问题原因了,而且也有人提交pr 修复了这个bug:
var smrh = new SendMessageRequestHeader
{
ProducerGroup = Group,
Topic = Topic,
QueueId = mq.QueueId,
SysFlag = 0,
BornTimestamp = (Int64)ts.TotalMilliseconds,
Flag = msg.Flag,
Properties = msg.GetProperties(),
ReconsumeTimes = 0,
UnitMode = UnitMode,
DefaultTopic = "TBW102" // 增加了该行
};
现在,我只需要通过Nuget升级一下包的版本就好了,当时的最新版本是1.5.2021.1204:
重新发版,准备继续摸鱼!可是,还没来得及高兴,测试说问题依然存在,错误仍旧是之前的错误。这不就见鬼了吗!
这一天,一直忙活到晚上十点多,问题终究还是没有解决...
程序集版本不一致
有时候,脑子长时间思考同一个问题,往往会陷入一个封闭的环境,导致思维会越来越狭隘。
第二天,准备换个思路解决问题——将QA的dll复制到本地来调试。不过,在准备复制dll的时候,发现了一个问题:NewLife.RocketMQ.dll
的修改日期竟然还是6月份,也就是说实际上它还是老版本,不出问题才怪。
QA环境的RocketMQ是以集群方式部署的,共有三台Broker,其中一台是高版本v4.9.2,其它两台是低版本v4.4.0。QA环境的Topic也是今天刚刚创建的,这三台Broker上都有属于该Topic的队列。当生产者生产消息时,可能会发送到高版本的Broker,也可能会发送到低版本的Broker上,所以,才导致了“时而成功,时而失败”情况的发生。
可是,我明明已经升级版本了啊!
突然惊醒!其他Service的NewLife.RocketMQ
版本并未同步升级, 导致出现了程序集版本不一致的问题!(PS:该项目是一个.net4.5的老项目,其他团队的许多Service也在该解决方案中,遇到该问题时未及时通知其他团队,这也让我充分意识到团队间沟通的重要性!)
查看程序集的生成顺序:右击“解决方案”,选择“项目生成顺序”即可查看。
知道问题原因了,现在有两种解决方式:
- 升级项目中所有程序集的
NewLife.RocketMQ
版本,代价是需要测试走一遍回归。 - 要求运维团队暂时不要升级RocketMQ版本,待研发、测试团队准备好后再升级。
因为迭代急着上线,其他团队也来不及回归测试,且RocketMQ的升级仅仅是为了适用一下新特性,并非强制要求,所以选择了稳妥的方案2。至此,问题终于解决完毕!
其他
1. 当项目中出现Nuget包版本不一致的情况时,最终生成的程序集版本是哪个呢?
参考nuget使用经验:复杂依赖关系下的包版本问题,并亲测无问题,总结如下:
- 引用层级不同时,NuGet 将选择最接近入口程序集的版本
- 引用层级相同时,NuGet 将选择满足所有版本要求的最低版本
建议一定要保证项目内引用包版本一致,尽量避免引用多版本的情况
2. 为什么我本地生产者生产消息到QA环境RocketMQ没出问题?
经过详细排查,我发现并非仅仅是版本不一致导致的问题。实际上,虽然项目中同时引用了多个版本的NewLife.RocketMQ
,不过当项目编译后,入口程序集的bin目录下的NewLife.RocketMQ.dll
实际上已经是最新的了,所以也就不会出问题。然而,由于QA的发布脚本有问题,导致QA服务器上的NewLife.RocketMQ.dll
并未更新到,仍在使用老版本。
3. the custom field <c> is null 到底是什么意思?
事实上,这个c
指的是生产者发送消息请求头中的Default Topic
,对于不了解RocketMQ通信协议的我,是从Newlife.RocketMQ
源码中得知的:
/// <summary>发送消息请求头</summary>
public class SendMessageRequestHeader
{
/// <summary>默认主题</summary>
[XmlElement("c")]
public String DefaultTopic { get; set; }
// ...
}
至于Default Topic
的空校验,是由于在RocketMQ v4.9.1 中增加了decodeSendMessageHeaderV2
方法,如果有兴趣,可以点击此pr查看完整提交记录。
static SendMessageRequestHeaderV2 decodeSendMessageHeaderV2(RemotingCommand request)
throws RemotingCommandException {
SendMessageRequestHeaderV2 r = new SendMessageRequestHeaderV2();
HashMap<String, String> fields = request.getExtFields();
if (fields == null) {
throw new RemotingCommandException("the ext fields is null");
}
// ...
s = fields.get("c");
checkNotNull(s, "the custom field <c> is null");
r.setC(s);
// ...
}
总结
经过这次bug排查,有以下几点心得:
- 中间件升级要慎重,一定要在所有使用到该中间件的系统的负责人得知的的情况下再升级。
- 第三方程序集的版本升级要慎重,一定要保证项目中的程序集版本一致,并进行回归测试。
- 对于系统中所使用到的中间件,一定要持续关注新版本的发布,了解其基本原理,这样才能快速定位问题所在。
- 加强不同团队之间的沟通,思考问题尽量全面。
BUG—Nuget包版本不一致导致程序行为与预期不符的更多相关文章
- 【Gradle】配置中引用的jar包版本后面自动加冒号导致引入jar包失败的问题/gradle中引用jar包版本不一致的问题/gradle中引用jar失败的问题 解决方法
idea中 gradle中 引用jar包,版本后面默认加:的问题 gradle中引用jar包版本不一致的问题 gradle中引用jar失败的问题 如上题目所示,三个问题其实都是同一样的简单又恶心,因为 ...
- 解决 .net core 中 nuget 包版本冲突问题
今天在一个 asp.net core 项目中遇到了 nuget 包版本冲突的问题,错误信息如下: Version conflict detected for Microsoft.AspNet.WebA ...
- 解决 .net core 中 nuget 包版本冲突问题[转载]
今天在一个 asp.net core 项目中遇到了 nuget 包版本冲突的问题,错误信息如下: Version conflict detected for Microsoft.AspNet.WebA ...
- thrift 版本不一致导致 @Override 报错
thrift 版本不一致导致 @Override 报错 学习了:http://blog.csdn.net/antony1776/article/details/78920888 版本不一致导致的: 在 ...
- zookeeper 版本不一致导致不断重连
在使用kafka 和zookeeper 实现实时分析程序时,由于zookeeper部署版本和分析程序导入jar包的版本不一致,导致了当实时分析程序从远程服务器连接kafka集群的zookeeper时报 ...
- linux显示git commit id,同时解决insmod模块时版本不一致导致无法加载问题
linux内核默认会包含git的commit ID. 而linux的内核在insmod模块时,会对模块和内核本身的版本做严格的校验.在开发产品时,改动内核后,由于commit ID变更,会导致linu ...
- jdk 版本不一致导致的错误
平时做项目时难免会从git,svn下载代码或者把别人的项目文件导入到自己的MyEclipse中进行操作,因此会遇到很多问题,常见的有一种是使用的jdk版本不一致造成的报错, 错误案例: 错误提 ...
- 解决redis集群版本不一致导致RDB同步失败的问题
某天,运维反馈某两个机房的出口流量和入口流量过大,并且持续了好一段时间. 再仔细排查后发现是 redis 集群的几台服流量问题,于是开始查日志. 在日志中发现出现大量的 Can't handle RD ...
- Newtonsoft.Json 版本不一致导致错误
可以在配置文件添加这部分,其他版本的不一致,也可使用这种方式解决. <runtime> <assemblyBinding xmlns="urn:schemas-micros ...
随机推荐
- 为什么要重写hashcode和equals方法
我在面试 Java初级开发的时候,经常会问:你有没有重写过hashcode方法?不少候选人直接说没写过.我就想,或许真的没写过,于是就再通过一个问题确认:你在用HashMap的时候,键(Key)部分, ...
- 【Linux】【Database】【MySQL】使用percona搭建高可用的MySQL数据库
1. 简介 1.1. 官方文档: 数据库架构:https://docs.openstack.org/ha-guide/shared-database.html 1.2. 本次使用的的是Percona ...
- ES在项目中的测试
1.application.yml server: port: ${port:40100}spring: application: name: xc-search-servicexuecheng: e ...
- HTML DOM 对象 - 方法和属性
一些常用的 HTML DOM 方法: getElementById(id) - 获取带有指定 id 的节点(元素) appendChild(node) - 插入新的子节点(元素) removeChil ...
- BigDecimal中要注意的一些事
一.关于public BigDecimal(double val) BigDecimal中三个主要的构造函数 1 public BigDecimal(double val) 将double表示形式转换 ...
- 避免警报疲劳:每个 K8s 工程团队的 8 个技巧
避免警报疲劳:每个 K8s 工程团队的 8 个技巧 监控 Kubernetes 集群并不容易,警报疲劳通常是一个问题.阅读这篇文章,了解减少警报疲劳的有用提示. 如果您是随叫随到团队的一员,您可能知道 ...
- Table.RenameColumns重命名…Rename…(Power Query 之 M 语言)
数据源: "姓名""基数"等列 目标: 修改"姓名"列标题为"员工姓名" 操作过程: [转换]>[重命名]> ...
- LuoguB2104 矩阵加法 题解
Content 给定两个 \(n\times m\) 的矩阵 \(A,B\),求 \(C=A+B\). 数据范围:\(1\leqslant n,m\leqslant 100\). Solution 我 ...
- lvm 扩容
总体思路: 逻辑卷要扩容,先扩容对应卷组, 扩容卷组的方式: 添加新的物理卷(磁盘已有分区,扩容后新建分区:或者新加了一块硬盘创建了新的物理卷),vgextend myvg /dev/vdb 扩容,/ ...
- Java面向对象~类和对象&方法,类方法
面向对象 概念: 1.同一类事物的抽象描述,不是具体的 2.类和对象的关系: 类 是抽象的. 对象 是具体的. 3.对象的体征,称为"属性&q ...