记一次生产事故的思考
二 事故描述某天晚上突发了一批预警当时的场景AB帮忙看下你们的服务我这里预警了B我刚发布了一个补丁跟我有关A我这里没有发布当然有关系了赶紧回退B我这里又没改你们用到的接口为啥是我们回退A那怪我喽我这里又没发布过东西赶紧回退B这个接口很长时间没有改过肯定是你们自己的问题。A不管谁的问题咱们先回退看看。B行吧稍等下发布助手回退中……回退后预警消失A……B……三 事故问题分析虽然事故发生后通过回退补丁解决了当时的问题但是事后对于问题的分析一直进行到了深夜。因为这次事故虽然解决起来简单但是直接挑战了我们对于服务的认识如果不查找到根本原因后续的工作难以放心的开展。以前我们对于服务的认识简单归纳为增加属性不会导致客户端反序列化的失败。但是这个并非是官方的说法只是开发人员在使用过程中通过实际使用总结出来的规律。经验的总结往往缺乏理论的支持在遇到问题的时候便一筹莫展。发生问题时客户端捕获到的异常堆栈是这样的System.Runtime.Serialization.SerializationExceptionHResult0x8013150CMessageObjectManager 发现链接地址信息的数目无效。这通常表示格式化程序中有问题。SourcemscorlibStackTrace:在 System.Runtime.Serialization.ObjectManager.DoFixups()在 System.Runtime.Serialization.Formatters.Binary.ObjectReader.Deserialize(HeaderHandler handler, __BinaryParser serParser, Boolean fCheck, Boolean isCrossAppDomain, IMethodCallMessage methodCallMessage)在 System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize(Stream serializationStream, HeaderHandler handler, Boolean fCheck, Boolean isCrossAppDomain, IMethodCallMessage methodCallMessage)在 System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize(Stream serializationStream)通过异常堆栈能够看出是在进行二进制反序列化时发生了异常。通过多方查阅资料针对此问题的观点基本可以总结为两点反序列化使用的客户端过旧将反序列化使用的类替换为最新的类。出现该问题跟泛型集合有关如果新增了泛型集合容易出现此类问题。观点一对于解决当前问题毫无帮助观点二倒是有些用处经过了解当日发布的补丁中涉及的微服务接口并未新增泛型集合属性而是对于以前增加而未使用的一个泛型集合增加了赋值的逻辑。后来经过测试确实是由此处改动造成的问题。由此也可以看出开发人员在日常开发过程中所总结出来的经验有一些局限性有必要深入的分析下二进制序列化在何种情况下会导致反序列化失败。四 二进制序列化与反序列化测试为了测试不同的数据类型对于反序列化的影响针对常用数据类型编写测试方案。本次测试涉及到两个代码解决方案序列化的程序简称V1和反序列化的程序简称V2。测试步骤V1中声明类及属性V1中将类对象进行二进制序列化并保存到文件中修改V1中类的属性去掉相关的属性的声明后重新编译DLLV2中引用步骤3中生成的DLL并读取步骤2中生成的数据进行反序列化////// V1测试过程用到的类///[Serializable]public class ObjectItem{public string TestStr { get; set; }}////// V1测试过程用到的结构体///[Serializable]public struct StructItem{public string TestStr;}测试常用数据类型的结果新增数据类型 测试用的数值 反序列化是否成功int 100 成功int[] 成功string “test” 成功string[] 成功double 1d 成功double[] 成功bool true 成功bool[] 成功List null 成功List {} 成功List 成功List null 成功List {} 成功List 成功List null 成功List {} 成功List 成功List null 成功List {} 成功List 成功ObjectItem null 成功ObjectItem new ObjectItem() 成功ObjectItem[] {} 成功ObjectItem{} 失败当反序列化时客户端没有ObjectItem这个类ObjectItem{} 成功当反序列化时客户端有ObjectItem这个类List null 成功List {} 成功List 失败当反序列化时客户端没有ObjectItem这个类List 成功当反序列化时客户端有ObjectItem这个类StructItem null 成功StructItem new StructItem() 成功List null 成功List {} 成功List 成功当反序列化时客户端没有ObjectItem这个类List 成功当反序列化时客户端有ObjectItem这个类测试结果总结二进制反序列化的时候会自动兼容处理序列化一方新增的数据。但是在个别情况下会出现反序列化的过程中遇到异常的情况。出现反序列化异常的数据类型泛型集合数组这两种数据结构并非是一定会导致二进制反序列化报错而是有一定的条件。泛型集合出现反序列化异常的条件有三个序列化的对象新增了泛型集合泛型使用的是新增的类新增的类在反序列化的时候不存在数组也是类似的只有满足上述三个条件的时候才会导致二进制反序列化失败。这也是为什么之前发布后一直没有问题而对于其中的泛型集合进行赋值后出现微服务客户端报错的原因。既然通过测试了解到了二进制反序列化确实会有自动的兼容处理机制那么有必要深入了解下MSDN上对于二进制反序列化的容错机制的理论知识。五 二进制反序列化的容错机制二进制反序列化过程中不可避免会遇到序列化与反序列化使用的程序集版本不同的情况如果强行要求反序列化的一方比如微服务的客户端一定要跟序列化的一方比如微服务的服务端时时刻刻保持一致在实际应用过程是不现实的。从.NET2.0版本开始.NET中针对二进制反序列化引入了版本容错机制(Version Tolerant Serialization简称VTS)。当使用 BinaryFormatter 时将启用 VTS 功能。VTS 功能尤其是为应用了 SerializableAttribute 特性的类包括泛型类型而启用的。 VTS 允许向这些类添加新字段而不破坏与该类型其他版本的兼容性。序列化与反序列化过程中如果遇到客户端与服务端程序集不同的情况下.NET会尽量的进行兼容所以平时使用过程中对此基本没有太大的感触甚至有习以为常的感觉。要确保版本管理行为正确修改类型版本时请遵循以下规则切勿移除已序列化的字段。如果未在以前版本中将 NonSerializedAttribute 特性应用于某个字段则切勿将该特性应用于该字段。切勿更改已序列化字段的名称或类型。添加新的已序列化字段时请应用 OptionalFieldAttribute 特性。从字段在以前版本中不可序列化中移除 NonSerializedAttribute 特性时请应用 OptionalFieldAttribute 特性。对于所有可选字段除非可接受 0 或 null 作为默认值否则请使用序列化回调设置有意义的默认值。要确保类型与将来的序列化引擎兼容请遵循以下准则始终正确设置 OptionalFieldAttribute 特性上的 VersionAdded 属性。避免版本管理分支。六 二进制序列化数据的结构通过前文已经了解了二进制序列化以及版本兼容性的理论知识。接下来有必要对于平时所用的二进制序列化结果进行直观的学习消除对于二进制序列化结果的陌生感。6.1 远程调用过程中发送的数据目前我们所使用的.NET微服务框架所使用的正是二进制的数据序列化方式。当进行远程调用的过程中客户端发给服务端的数据到底是什么样子的呢引用文档中一个现成的例子(参考资料4)远程调用的例子上图表示的是客户端远程调用服务端的SendAddress方法并且发送的是名为Address的类对象该类有四个属性(Street “One Microsoft Way”, City “Redmond”, State “WA” and Zip “98054”) 。服务端回复的是一个字符串“Address Received”。客户端实际发送的数据如下0000 00 01 00 00 00 FF FF FF FF 01 00 00 00 00 00 00 …ÿÿÿÿ…0010 00 15 14 00 00 00 12 0B 53 65 6E 64 41 64 64 72 …SendAddr0020 65 73 73 12 6F 44 4F 4A 52 65 6D 6F 74 69 6E 67 ess.oDOJRemoting0030 4D 65 74 61 64 61 74 61 2E 4D 79 53 65 72 76 65 Metadata.MyServe0040 72 2C 20 44 4F 4A 52 65 6D 6F 74 69 6E 67 4D 65 r, DOJRemotingMe0050 74 61 64 61 74 61 2C 20 56 65 72 73 69 6F 6E 3D tadata, Version0060 31 2E 30 2E 32 36 32 32 2E 33 31 33 32 36 2C 20 1.0.2622.31326,0070 43 75 6C 74 75 72 65 3D 6E 65 75 74 72 61 6C 2C Cultureneutral,0080 20 50 75 62 6C 69 63 4B 65 79 54 6F 6B 65 6E 3D PublicKeyToken0090 6E 75 6C 6C 10 01 00 00 00 01 00 00 00 09 02 00 null…00A0 00 00 0C 03 00 00 00 51 44 4F 4A 52 65 6D 6F 74 …QDOJRemot00B0 69 6E 67 4D 65 74 61 64 61 74 61 2C 20 56 65 72 ingMetadata, Ver00C0 73 69 6F 6E 3D 31 2E 30 2E 32 36 32 32 2E 33 31 sion1.0.2622.3100D0 33 32 36 2C 20 43 75 6C 74 75 72 65 3D 6E 65 75 326, Cultureneu00E0 74 72 61 6C 2C 20 50 75 62 6C 69 63 4B 65 79 54 tral, PublicKeyT00F0 6F 6B 65 6E 3D 6E 75 6C 6C 05 02 00 00 00 1B 44 okennull…D0100 4F 4A 52 65 6D 6F 74 69 6E 67 4D 65 74 61 64 61 OJRemotingMetada0110 74 61 2E 41 64 64 72 65 73 73 04 00 00 00 06 53 ta.Address…S0120 74 72 65 65 74 04 43 69 74 79 05 53 74 61 74 65 treet.City.State0130 03 5A 69 70 01 01 01 01 03 00 00 00 06 04 00 00 .Zip…0140 00 11 4F 6E 65 20 4D 69 63 72 6F 73 6F 66 74 20 …One Microsoft0150 57 61 79 06 05 00 00 00 07 52 65 64 6D 6F 6E 64 Way…Redmond0160 06 06 00 00 00 02 57 41 06 07 00 00 00 05 39 38 …WA…980170 30 35 34 0B 054.上文的数据是二进制的能看出来序列化后的结果中包含程序集信息被调用的方法、使用的参数类、属性及各个属性的值等信息。对于上述的序列化后数据进行详细解读的分析可以参考资料4。6.2 类对象二进制序列化结果对于类对象进行序列化后的结果没有现成的例子针对此专门设计了一个简单的场景将序列化后的数据保存到本地文件中。////// 自定义序列化对象///[Serializable]public class MyObject{public bool BoolMember { get; set; }public int IntMember { get; set; }}////// 程序入口///class Program{static void Main(string[] args){var obj new MyObject();obj.BoolMember true;obj.IntMember 10000;IFormatter formatter new BinaryFormatter(); Stream stream new FileStream(data.dat, FileMode.Create, FileAccess.Write, FileShare.None); formatter.Serialize(stream, obj); stream.Close(); }}data.dat中的内容0000: 00 01 00 00 00 ff ff ff ff 01 00 00 00 00 00 00 …0010: 00 0c 02 00 00 00 4e 42 69 6e 61 72 79 53 65 72 …NBinarySer0020: 69 61 6c 69 7a 65 50 72 61 63 74 69 73 65 2c 20 ializePractise,0030: 56 65 72 73 69 6f 6e 3d 31 2e 30 2e 30 2e 30 2c Version1.0.0.0,0040: 20 43 75 6c 74 75 72 65 3d 6e 65 75 74 72 61 6c Cultureneutral0050: 2c 20 50 75 62 6c 69 63 4b 65 79 54 6f 6b 65 6e , PublicKeyToken0060: 3d 6e 75 6c 6c 05 01 00 00 00 20 42 69 6e 61 72 null… Binar0070: 79 53 65 72 69 61 6c 69 7a 65 50 72 61 63 74 69 ySerializePracti0080: 73 65 2e 4d 79 4f 62 6a 65 63 74 02 00 00 00 1b se.MyObject…0090: 3c 42 6f 6f 6c 4d 65 6d 62 65 72 3e 6b 5f 5f 42 k__B00a0: 61 63 6b 69 6e 67 46 69 65 6c 64 1a 3c 49 6e 74 ackingField.Int00b0: 4d 65 6d 62 65 72 3e 6b 5f 5f 42 61 63 6b 69 6e Memberk__Backin00c0: 67 46 69 65 6c 64 00 00 01 08 02 00 00 00 01 10 gField…00d0: 27 00 00 0b …对于类对象直接进行二进制序列化后的结果与远程调用场景二进制序列化的结构有所不同。按照[MS-NRBF]所言序列化后的结果首先是序列化数据头其中包含RecordTypeEnum、TopId、HeaderId、MajorVersion和MajorVersion。这之后就是被序列化的类的一些信息包括程序集、类名、属性和属性对应的值。Binary Serialization FormatSerializationHeaderRecord:RecordTypeEnum: SerializedStreamHeader (0x00)TopId: 1 (0x1)HeaderId: -1 (0xFFFFFFFF)MajorVersion: 1 (0x1)MinorVersion: 0 (0x0)Record Definition:RecordTypeEnum: SystemClassWithMembers (0x02)ClassInfo:ObjectId: (0x4e000000)LengthPrefixedString:Length: 78 (0x4e)String: BinarySerializePractise, Version1.0.0.0, Cultureneutral, PublicKeyTokennullObjectId: (0x00000001)LengthPrefixedString:Length: 32 (0x20)String: BinarySerializePractise.MyObjectMemberCount: 2(0x00000002)LengthPrefixedString:Length: 27(0x1b)String: k__BackingFieldLengthPrefixedString:Length: 26(0x1a)String: k__BackingFieldObjectId0x08010000Length0x00000002Value:1(0x01)Value:10000(0x00002710)MessageEnd:RecordTypeEnum: MessageEnd (0x0b)七 总结

相关新闻

大型展会纪念品:工艺品摆件定制的包装与运输建议

大型展会纪念品:工艺品摆件定制的包装与运输建议

展会高峰期,工艺品摆件定制的包装与运输避坑指南 大型展会期间人流密集且布展时间紧迫,作为企业形象展示重要载体的工艺品摆件定制产品,若在运输途中出现破损,或因包装不当导致展示效果打折,将直接影响品牌的现场呈现…

2026/10/17 5:15:03 阅读更多 →
Vue.js报错-Maximum-recursive-updates-exceeded

Vue.js报错-Maximum-recursive-updates-exceeded

Vue.js 报错:Maximum recursive updates exceeded in component 一句话总结:用「watch computed nextTick」三件套,让响应式更新不再无限递归,警告瞬间消失! 正文目录 报错含义:Vue 在警告什么「递归更新…

2026/10/14 10:25:08 阅读更多 →
From Pixar-Themed Sets to a Myst

From Pixar-Themed Sets to a Myst

迪士尼Lorcana背靠整个迪士尼角色库,在集换式卡牌游戏中拥有独特优势。外媒IGN近日整理了Ravensburger已公布的2026年产品计划,从年初的冬季系列到10月的《寻梦环游记》扩展,路线图逐渐清晰,但下半年仍有待填补的空白。 【Winter…

2026/10/14 23:10:49 阅读更多 →

最新新闻

IDA逆向工程实战:从静态分析到二进制补丁的完整流程

IDA逆向工程实战:从静态分析到二进制补丁的完整流程

干逆向这一行的,绕不开IDA。不管是分析一个陌生二进制文件到底干了什么、给某个老程序打补丁,还是解CTF里的Reverse题,IDA几乎都是所有人默认打开的第一件工具。它最核心的价值就是把机器码翻译成人类能读的伪代码和汇编,同时把整…

2026/10/17 7:18:06 阅读更多 →
MySQL数据库与表管理实战:从建库建表到字符集与DDL避坑指南

MySQL数据库与表管理实战:从建库建表到字符集与DDL避坑指南

数据库管理这事,入门容易,想管得明白、少在晚上被电话叫醒,靠的全是细节。MySQL作为使用面最广的开源关系型数据库之一,大家对它的印象往往是“装完就能用”,可真到了建库建表、调整表结构、处理字符集乱码、删错数据的…

2026/10/17 7:18:06 阅读更多 →
数据加密如何重塑仓储管理系统:字段级加密、密钥与查询设计

数据加密如何重塑仓储管理系统:字段级加密、密钥与查询设计

做仓储管理系统,大家第一反应就是把入库、出库、库存台账、盘点这些业务功能铺开,最后再在功能树上挂一个“数据加密”就算交差。但这份“基于数据加密的仓库货物管理系统设计与实现任务书”的落脚点,恰恰不在“仓库货物管理”这六个字&#…

2026/10/17 7:18:06 阅读更多 →
5款AI编程神器横评:从补全到自动Debug

5款AI编程神器横评:从补全到自动Debug

2026年还没过半,AI编程工具的洗牌速度已经让不少人看花了眼。前两年大家还在为一款编辑器疯狂买单,那场面我至今记得——好像装上它,代码就能自己写完。如今再回头看,那句话不算全错,只是说得太早了。因为它擅长的本来…

2026/10/17 7:18:06 阅读更多 →
MySQL 8.0 CTE实战:从嵌套子查询到清晰报表的SQL重构指南

MySQL 8.0 CTE实战:从嵌套子查询到清晰报表的SQL重构指南

最近在做一批历史报表SQL的梳理,遇到一个印象很深的查询:统计各区域销售Top 10产品,源数据就三张表,写出来的SQL却有将近三百行,光子查询嵌套就有七层。不是说跑不动,而是后来同事想在这个基础上加一个过滤…

2026/10/17 7:18:06 阅读更多 →
鸿蒙应用运行机制详解:从点击图标到第一帧画面

鸿蒙应用运行机制详解:从点击图标到第一帧画面

上篇说完环境搭建、建完工程、能跑出“你好,鸿蒙”的页面之后,很多初学的朋友都会冒出一个问题:我就按了一下图标,屏幕上就从无到有出现了一个应用界面,这中间到底发生了什么事?这篇就专门把“应用是怎样运…

2026/10/17 7:17:05 阅读更多 →

日新闻

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

简介:《DeepSeek使用方法.pdf》是一份面向DeepSeek入门及进阶用户的完整使用指南,重点围绕V3与R1两款模型的能力差异与适用场景展开,帮助读者根据任务类型与成本选择合适的模型。资源共1个PDF文件,压缩包大小约311KB,篇…

2026/10/17 0:00:25 阅读更多 →
200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

简介:这是一份聚焦DeepSeek提示词优化的《200Deepseek润色指令》PDF文档,共收录两百余条可直接套用的润色指令,涵盖语法修正、风格统一、逻辑梳理、表达精炼等维度,并针对商务写作、学术论文、广告文案等场景给出差异化建议。文档…

2026/10/17 0:00:25 阅读更多 →
OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

简介:本资源是一份基于OpenCV的本科课程设计完整报告,面向计算机视觉初学者、图像处理学习者及高校通信/信息类专业学生,解决物体智能计数与检测结果结构化记录的实际问题。项目覆盖图像预处理、轮廓检测、边界框绘制、轨迹栏交互式参数调节、…

2026/10/17 0:00:25 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/16 21:35:51 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/17 1:44:59 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/16 15:04:24 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 21:54:38 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 3:49:43 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 14:23:36 阅读更多 →