欢乐小插曲
时间定格在周四 -- 常规发布日下午三点, 研发任务早已完成, 测试也无大碍. 这时, 办公室外走廊里传来了一阵急促的报警声 --"此大楼发生紧急情况, 请各单位抓紧撤离". 像往常一样刷刷博客园, 坐等发布生产的我闻听此声, 虎躯一震正准备要跑路, 转头看向周围的同事. 大都很淡定的坐在工位上各自撸码. 于是收回我那一只已经迈向过道的腿, 佯装淡定的坐下, 悄悄的问了问隔壁的老大哥: 老哥, 怎么没人跑路?"兄弟, 习惯就好, 八成又是大楼搞消防演习" 老哥扶了扶眼镜, 继续撸码. 果不其然, 半个小时后收到了公司的马后炮邮件 --"近期大楼消防演习, 请大家听到警报声后无需惊讶, 各自工作即可......"
事件经过
下午四点, 发布生产 g 环境 (生产环境 m 为正式环境, g 为内测环境). 这时测试有人提出 "服务器忙". 听到这里我赶紧翻了翻内测日志, 发现了最熟悉的老朋友 -- 未将对象引用设置到对象的实例. 问题出现在如下代码上.(我下面附上了伪代码, 大家可以看看下面的代码有没有问题, 当时我看了很久才发现问题所在)
- // 获取账户信息
- var accountInfo=GetAccountInfo();
- if(accountInfo==null){
- return "xxx";
- }
- // 判断账户信息中是否包含 xx-- 问题点就出现在下面这行代码 (未将对象引用设置到对象的实例)
- // 其中 Test 字段为此次发布新增字段 -- 线上版本的 AccountInfo 中并不存在此字段
- bool isTest=accountInfo.Test.Contains("A");
- // 获取账户信息方法
- public AccountInfo GetAccountInfo(){
- // 先读取 Cache
- AccountInfo info=cache.GetCache();
- if(accountInfo!=null)
- {
- return info;
- }
- info=SOAService.GetInfo();
- if(info!=null){
- cache.SetCache(info);
- return info;
- }
- return null;
- }
- // 账户信息类
- public class SOAService()
- {
- // 从远程服务获取账户信息
- public static AccountInfo GetInfo()
- {
- // 获取一个服务 Client
- SOAClient client=SOAClient.GetClient();
- // 获取账户信息
- Account account= client.GetAccountInfo();
- if(account!=null){
- AccountInfo info=new AccountInfo();
- info.xx=account.xx;
- //Test 为 null 处理
- info.Test=account.Test??string.empty;
- return info;
- }
- return null;
- }
- }
- // 账户信息实体类
- public class AccountInfo{
- private string name;
- //... 此处省略 n 个字段
- // 此处新增字段 test
- private string test;
- // 属性 Name
- public string Name{
- get { return this.name; }
- set { this.name=value; }
- }
- //... 此处省略 n 个属性
- // 属性 Test
- public string Test{
- get { return this.test; }
- set { this.test=value; }
- }
- }
在代码中观察许久仍没有发现问题. 这时测试一句话提醒了我,"我看 m 环境没有问题". 灵光一闪, 原来测试先从 m 环境登录, 浏览了一圈页面后, 已经缓存了 AccountInfo, 但是 m 环境此时是没有新增字段 Test 的, 此时切换到 g 环境 (我们的 m 环境, g 环境对应缓存数据都是一样的, 区别仅仅是应用服务器不同), 获取账户信息时会直接从 Cache 中读出来, 然后 accountInfo.Test 在用之前并没有判空, 所以... 未将对象引用设置到对象的实例. 于是乎得意的跟测试说, 你登录后别再 m 环境操作, 直接切到 g 环境, 就可以了, 等发 m 不会有问题的. 果不其然, 测试按我说的做了不再报错.
如果你以为事情就这么结束, 那就错了. 请原谅我那猪油蒙了心的傻叉操作. 不久, g 环境验证无误, 开始往 m 环境发布. 起初未见异常, 当发了集群大概三分之一节点的时候, 大量异常突然袭来, 瞬间监控开始报警. 一看日志满屏的老朋友. 紧急关头得亏脑子反应快, 紧急回滚代码. 静下心来脑子一想, 生产用户本身处于登录状态, 有使用缓存. 刚刚出现问题没去处理, 真是悔之晚矣! 于是紧急修复. 增加使用前判空, 问题终于解决.
事件教训
对于程序中大量使用缓存的系统, 开发时一定要考虑好缓存.(这个系统一些不合理的缓存设计坑的我苦不堪言)
对于测试中的每一个问题都要认真对待
纸上得来终觉浅 -- 背的滚瓜烂熟的, 缓存穿透, 雪崩, 缓存更新, 程序非空验证. 实战起来还是不够用
敬畏每一次生产环境的发布
来源: https://www.cnblogs.com/hunternet/p/11007917.html