码迷,mamicode.com
首页 > 其他好文 > 详细

订单同步工程标准化改造事记

时间:2019-09-28 14:17:32      阅读:103      评论:0      收藏:0      [点我收藏+]

标签:除了   fail   art   一加   方案   时间   左右   十一点   define   

说起来,也是一段比较有挑战有压力的经历。做完之后,有一种云淡风轻的感觉,故记之。

缘起

周二下午,忽报:QA 环境下单之后,订单搜索不出来了。

略排查,发现订单记录并未同步到 ES 索引里。进一步发现,订单同步工程 S 虽然进程还在,但已经不再处理消息了。昨天因为一个项目的需求才测试过 QA 环境订单同步无问题,上午也没动静,怎么下午就突然报问题了呢?

很快联想到,前两日,框架层发了通告:不再为使用了 3.2.x 以下 dubbo 版本的应用提供自动注册 dubbo 服务的能力。很可能是 S dubbo 版本过低,无法注册和访问 dubb 服务,无法进行订单同步,进而影响订单搜索和详情,严重阻塞了项目的测试。订单是核心嘛~~

于是我将 pom 里的 dubbo 版本改成了 3.2.x 以上。可还是不行。要找框架同学一起排查下了。

旧世界

ClassVisitor

框架同学东顺说,早上貌似改过 zan 版本,也许是这个导致的。于是,我请求降回到原来的版本,先解决问题再说。sadly,即使降回到原来的版本进行部署, S 的服务依然起不来。退路已断。

框架同学子杰的第一个想法,是在本地启动调试。因为这样方便且高效。不过 S 应用已经很久没有在本地启动。而且 S 依赖比较多,在本地恐怕很难启动。

报 ClassVisitor should be a interface , not a class 。此类错误通常是 jar 冲突导致。因此,我在工程里搜索了下 ClassVisitor ,果然发现有两个,一个是接口,一个是类。 看来要排掉那个有类 ClassVisitor 的 jar 包。

       <dependency>
            <groupId>com.xxx.platform</groupId>
            <artifactId>bootstrap</artifactId>
            <version>3.2.7.3-RELEASE</version>
            <exclusions>
                <exclusion>
                    <groupId>org.ow2.asm</groupId>
                    <artifactId>asm</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

排掉之后,就不再报这个错了。 然而, S 依然不处理消息。

一边是测试同学在催促,一边是毫无头绪。心里有些急,可一时也想不到如何解决。子杰期望本地调试,可本地启动总是报奇怪的 groovy 不兼容错误。调了一晚上,终于把这个问题解决了。 但 S 依然不处理消息。

补丁

周三早上,继续战斗。子杰发了一段配置,让放在 S 工程的 XML 配置里。说这段配置是用来注册 dubbo 服务的。 加入之后,报错: cause: Could not initialize class com.coreos.jetcd.api.KVGrpc, dubbo version: 1.0.1-SNAPSHOT, current host: x.x.x.x 。 ZJ 说,这个错误之前见过,可能 protobuf 版本冲突了。

     <dependency>
            <groupId>org.apache.hbase</groupId>
            <artifactId>hbase-server</artifactId>
            <version>1.2.6</version>
            <exclusions>
                <exclusion>
                    <groupId>com.google.protobuf</groupId>
                    <artifactId>protobuf-java</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

排除 protobuf-java 包之后,终于可以起来了。 验证了下,订单同步 OK 了。 松了一口气。

间歇性跪

才没轻松多久, 又跪了。又跪了。又跪了。又跪了。 测试同学又来催了。

沟通

在这种情形下,发生一点小“纠纷”总是可能的。有一次,在吃饭的时候,S 跪了。 测试同学群里喊:进度如何了,找到根本原因了么? 心里有点恼火:作为开发,我肯定要尽职尽责尽早排查和解决问题;可是你也不能只是催,在这样尴尬的时刻,可以支援一下嘛。 不过,大家都很通情达理,我提供了“临时启动服务”的方法,测试同学就帮忙先启动服务了。

反思一下,测试同学的做法也是合情合理的,因为他们要负责保障更大域范围的环境稳定,需要知道进度和原因。解决棘手问题时,同步进度和原因也是很重要的,心里应该装下更大的世界。此外,若我能及早同步“临时解决方案”,让测试同学知道怎么解决,也不会有这样的“纠纷”。有话好好说,总能找到更柔和的方式来解决。

于是我在群里回复:他们的担心和考虑是合理的,且已经快接近成功了。稍安。其实我也不知道距离成功还有多远。

蛛丝马迹

排查问题,只能从错误日志里寻找一切蛛丝马迹。子杰找了一段 JVMExitHook 的代码,加在启动的时候,这样方便在进程退出的时候,打印一些线索。 不过,这招没起效果,打印出的信息似乎没有派上用场。

同时,我也在仔细观察日志里出现的各种细微错误。不能放过一个嫌疑份子。确认了有四种可能的影响因素:1. 应用 M 跪; 2. Hbase 读写失败; 3. 脏数据; 4. 底层 jar 不兼容,导致某种隐藏的问题。

M跪

又跪了。发现 S 有个任务访问 M 服务报错,登录机器,发现 M 服务跪了。重启 M 。 过了一段时间,S 又跪了,M 服务也跪了。会不会受了这个服务的影响呢 ? 将 M 服务所涉及的任务暂时禁用了。将 M 禁用后,S 似乎就没有跪过了。

M 报错的原因,是因为零售同学将一个接口迁移到了新的工程,也就是说,原来的接口下线了。 S 去注册这个接口的服务的时候找不到。

HBase

发现有一些 HBase 写入失败。咨询 HBase 同学,是因为 QA 集群运行了一些离线任务,有时资源比较紧张。 虽然有疑点,但不太可能导致 S 跪。

脏数据

在排查过程中,也发现有些脏数据,导致 NSQ 消息处理失败。脏数据可能把一个任务搞跪,不过 S 里有很多任务,要把 S 跪掉,还需要一些“道行” 吧。

隐藏的坑

只是猜测,但确实是比较大的嫌疑。 因为前三者都是局部的影响,不太可能将 S 整个搞跪掉,但这个可能是全局的影响。比如说,某个 jar 版本的组件,与另一个组件交互,可能产生 bug , 吃内存,导致内存 OOM 。只是猜测。

暂时维稳

子杰有了新的发现。从 dmsg 日志里看到,S 独占内存过多,Linux 内核将 S 的 java 进程 kill 掉了。调整了堆内存从 6.5G 到 4G,然后运行了一整天都没有跪。

java invoked oom-killer: gfp_mask=0x201da, order=0, oom_adj=0, oom_score_adj=0
java cpuset=/ mems_allowed=0
Pid: 4288, comm: java Not tainted 2.6.32-754.9.1.el6.x86_64 #1
[ 4286] 602 4286 3321904 1898888 2 0 0 java
Out of memory: Kill process 4286 (java) score 929 or sacrifice child
Killed process 4286, UID 602, (java) total-vm:13287616kB, anon-rss:7595528kB, file-rss:24kB


新工程

QA 环境终于可以暂时安静一会了。 可是,生产环境如何解决呢?

S 承载着公司亿级订单的同步,显然不能容忍用打补丁的方式来解决(何况这个补丁很恶心,会埋很大的坑),而且也无法接入 aladdin ,自动升级 jar 版本。 因此, QA 的做法,只能暂时维稳,不能用作生产环境的解决方案。

咨询子杰 ,是否有办法可以以最小成本来改造 S 应用 ? 回答:使用 youzan-boot 标准化工程。可是要改造成 youzan-boot 工程,势必改动很大,整个工程结构都变了,部署方式也变了,上线风险是很大的。对于 S 这样的应用,稍有不慎,就是 P1 或 P0 故障。

进退两难。

因为自己不太熟悉应用打包和部署的方式,有点畏难。但想了想,本质上就是把应用里的任务想办法启动起来。绝大部分代码都是可以复用的,只需要在新的工程结构中,将原来的启动代码嵌入进去。想到这里,有了一些信心,决定采用新工程的方式来解决这个问题。

new出了null

根据子杰提供的文档及界面,很快建立了新的标准化脚手架工程。接着,将原工程的代码拷贝到新的工程里,并在启动的入口,将原来启动任务的类的方法添加进去。

我是一个不解决主要问题寝食难安的人。深夜,继续。部署后,初始化任务出错。只好加日志,看看哪里抛异常了。不加不要紧,一加he一跳。
`Task task = new Task(); logger.info("task:{}", task) , 竟然打出了个 null !

new 出了个 null ? 百思不得其解。想着,恐怕又没进展了, 打算睡觉了。正准备睡觉的时候,突然灵光一闪,去查看了下 toString 方法,是打印 task 实例中的 taskConfig 的,而此时 taskConfig 作为入参还没有设置到 task 中,因此打印为 null 。真是脑经急转弯啊。 遇到的每个问题,都是一道面试题。):

看来,在程序的世界里,一切奇怪的事情,总有一个合理的缘由。 联想到前不久的 “奇怪之事总有缘由:订单状态对比不一致问题排查” ,有所领会。

prototype

继续排查为什么 task 没有正确初始化。对比原工程,发现 task 是一个声明为 prototype 的类,所引用的组件,也是 prototype 的。

折腾到凌晨一点,终于能够看到,任务在消费消息了。奇怪的是,日志没有打出来 。先睡觉吧。

日志

第二天一大早,就去找 DS 排查日志为什么没找到。DS 在一个 undefined.home 的目录下找到了日志文件。 日志路径的配置有问题。 按照 DS 的指点,导入了标准的 XML 日志配置,在期望的目录打印了任务处理日志。略开怀。

可错误日志没打出来。这也是要命的事情。在发布线上的过程中,若没有错误日志的提醒,那是冒着枪林弹雨的冲锋,是容易中弹身亡的。

DS 指点:可以直接把原来的文件拷贝过来亦可。错误日志终于打出来了! 开心 !~~ 又前进了一步,more closed to success。

打印日志到本地磁盘之后,还要上报到公司内部的日志平台上,方便查看多台服务器的日志。照葫芦画瓢,折腾了下,搞定了。喜。

简化

至此,新工程的整体和主要部分没问题了。还需要做一些简化,比如去掉一些无用的模块和文件,优化 pom 里的 jar 引用等,保持工程的简洁与干净,没有赘余。

发布准备

接下来就要考虑发布了。

CR

工程改动太多了,有点失控的感觉。 在发布之前,找几位有经验的同学帮忙一起 check 下是明智的选择,也是必经的流程。一位是一直帮我排查 QA 同步问题的子杰同学, 一位是见识比较广能力不错的王立同学,因为他作为后端同学解决了react 容器部署的问题,让 S 的新界面又重新问世啦!靠谱,厉害!还有一位是我的有伴水王,也是很踏实的。


业务回归

CR 的同时,也要验证这个工程是否能保证原有的任务都能正常进行。

前面谈到, 订单同步使得搜索和详情的任务都是 OK 的,也就是最重要的任务没问题;

有些任务是与第三方交互的,在 QA 是无法验证的。 好在:这个工程里绝大部分都是访问 DB, ES, HBase 的组件,绝大多数的业务逻辑都在配置的任务里。可以说是“将基础技术设施与业务逻辑相分离而实现配置化“的典范做法。因此,从基本层面看,只要验证部分任务, 覆盖到所有的组件类别, 就可以看作是验证了所有任务。

既可以把这种做法看成一种巧妙的方式,也可以看作是某种取巧和偷懒。这种方式还是会有漏网之鱼。

虚惊一场

使用新工程部署之后,一直在观察中,没有跪过。 有点安心了。 在发布前晚,突然发现: 跪掉了。 天 ! 难道还有什么细坑 ?又得继续观察和排查细坑了 ! 恐怕要延期发布了。

幸好,当天早上, 有伴告诉我,他重启过一次,运维同学也重启过一次,但是没启动成功。 我立即想到,可能他们是用老的 zan 版本启动的, 因为不兼容导致失败。我用 zan 的新版本启动是 OK 的。

很快确认了这一点, 虚惊一场。

庙算几何

孙子曰:“夫未战而庙算胜者,得算多也,未战而庙算不胜者,得算少也;多算胜,少算不胜,而况于无算呼?”

距离计划发布时间只有几个小时了。 这个工程的发布胜算在 92% 以上, 有 8% 的不确定性。胜算如下:

  1. 最主要的订单同步任务是没有问题的;
  2. 十几个的任务及子路径,我验证了大部分是可以正确输出的;
  3. 组件代码无改动,只是工程结构变了;
  4. 在 QA 和 预发环境运行稳定。

不确定性如下:

  1. 有一个任务是外部交互的,无法验证; 四个任务是边缘业务,不太好构造测试用例; 但这些任务所使用的组件,都在其他被验证过的任务中使用过了,因此,理论上也应该是间接被验证了;其实是有点懒,结果是受了惩罚。
  2. 引入了 youzan-boot 的包,还有以前工程里的 jar 包,可能存在一些细微的不兼容的坑,未被发现。

不管怎样, 胜算还是很大的。在发布的时候,再细心一些, 应该不会出什么问题。

决战时刻

预发的启示

在预发部署的时候,发现新的进程并没有正常起来,必须先手动 kill 掉原来的进程,再部署新的工程,才能让新进程正常启动和运行服务。 这一点尤为重要, 否则很容易出问题。

如果能对预发出现的问题敏锐地感知,有时预发的问题真的是神明的指示。 前不久做过的一个项目中,需要做一些交易配置的变更。在预发验证拼团订单的时候,订单状态没有正常流转,经排查日志发现:预发的请求竟然发到线上去了! 联系营销同学得知,订单的后续通知,取决于接收最后一个参团订单的请求的机器所处的环境。比如说吧,拼团发起者的下单请求在 T1 机器, 拼团参团者的下单请求在 T2 机器上。 机器 T1 切换到了新的配置, T2 还没有。 此时, T2 将向营销发送消息,营销处理消息完成后,向交易机器 T3 发送回送消息。如果 T3 还没有切新配置,就可能导致走不下去。 发现这个问题后, 仔细思考了下,发现在 下午正常发布窗口发布是有风险的。因为如果正好在那个时间段有很多拼团活动,势必会导致很多拼团订单的状态流转有问题,不仅会对商家的交易有影响,还要应对后续复杂麻烦的数据修复。 想想,还是辛苦一点,凌晨发布好了, 此时拼团活动极少, 且有更多时间来验证项目改动和回归业务。

经过沟通,决定在凌晨发布。安全上线。

发布文档

由于要赶在新进程起来之前手动 kill 老进程,这个时机得把握好, 为了避免误操作,临阵手忙脚乱,因此,必要写个发布文档, 写清楚每一个步骤要执行的命令, 在真正发布的时候,只要简单的复制粘贴就可以了。

本来想把这些命令写成一个脚本,可是 kill 老进程的执行要切换用户,一时间没有太多时间调试这个,且要执行的批量命令也不多, 因此,还是直接复制粘贴了。好在发布的时候,机器的编号是逐渐递增的,可以预测每批次发哪几台机器, 这就容易多了:只要在每批次发布之前,先 kill 掉将要发布的机器的老进程即可。

会影响线上服务么? 由于还有其他服务器在处理消息,且凌晨的消息量极少,因此 kill 几台机器的进程不会对线上服务有影响。

单测

写好发布文档之后,已经 十一点半了。 该提发布申请了。

在提发布申请之前,得先运行并单测全部通过。到单测平台上运行了下,发现报错: groovy 与 spock-core 的版本不兼容。

郁闷了。在本地运行是 OK 的,为啥在这个节骨眼报错了。 看了下工程的配置,还是比较老的版本。想想可以升级到较新的版本,对任务运行应该没有影响。于是从核心工程拷贝了更新的版本号,并修复了一个单测文件。

为了简单,根据报错只把 groovy 的版本升到了 2.0.1 , —— 给自己买了个坑。在程序的世界里,因果命中注定。

小插曲

突然想到, 除了 S ,还有一个附属应用 s, 代码与 S 相同 ,但只运行对比任务。嗯, 这个应用也要发布, 也在预发验证下。

我在 QA 也部署了应用 s ,可启动日志显示:访问交易配置表报错了:用户名被拒绝。这是怎么回事呢? 在发布之前,有一些配置变更我提交了。我想到应该是运维同学更新过,不会有问题。 QA 没法验证, 只好上预发验证了。

于是,我在预发也部署了 s 应用。启动日志没报错,可也没消费日志。想了想,也许之前就没有吧。 应该是 OK 的,就做发布前的准备去了。

突然被拉到一个群里,是预发交易配置库访问出错排查。由于我集中精力在准备发布,因此对这个并未投以太多关注。但群里好像说是比较严重的事情,坚持要查明白,似乎说我故意配错了用户名和密码,以致于访问到错误的地方,险些造成安全事故,还连累了很多同学耗费时间来查问题。可我只是提交了被别人改动的配置(并且我相信是运维同学改动且应该是正确的),并没做出格的事情。难道,我一不留神就犯下了错误 ?

事情原委是:DBA 同学杨大侠监控到交易配置库有大量访问错误,而且还来自于不同的机房,认为这是很大的不合理。初步排查到,访问出错来自于预发的 s 应用。这有什么关联呢? 我一时也有点懵逼,仿佛处于云山雾绕。

####&amp;####

我提交了发布申请,开始发布 s 应用。未料,线上也报错了。这可奇怪了,我对比了下 S 与 s 的 交易配置表的用户名和密码,完全一样的啊 ! 在 跳板机上用 mysql 连接了下,也是通的; S 在预发部署也没报错,这可奇怪了。

杨大侠提醒: DB 连接的 jdbcURL中有 &amp; 这样的字符。 我突然想到了, 在 QA 遇到过这个问题,因为高版本的 mysql 不支持这个老的语法,需要将这个变成 & 本身。

哪里还藏有 &amp; 这个字符呢 ? 重新再check 了下 s 应用的配置。 发现,交易配置表的配置有两处,其中一处是完全一样的,但还有一处。 重复出现的 DB 配置,真坑啊!

修改之后,就没有报错了。DBA 监控也没有报错了。 排查这个错误耗费了将近三个小时。发布完 s 应用后,已经三点了。

Bean没有找到

S 应用的发布似乎顺利一些。发了几台,没有报 DB 访问错误,或者其他奇怪的错误。

四点半了, 刚发了几台机器,发现有错误: RefundBizNsqInput 的 bean 找不到,任务启动报错。这个任务是消费退款消息,写入退款数据,供订单导出来计算退款状态及金额的。如果这个数据有误,可能会影响订单状态的展示,误导商家发货。

内心有点崩溃。 这可怎么办呢? 继续发布完成 ? 这样会导致这个任务大量报错, 退款消息无法消费, 引起上述问题, 造成故障; 如果暂停发布,等待下午发布,一则现在新老混布有较大风险,二则下午发布风险更大; 重新发布,已经快到 5 点的发布截止窗口了,稍有延迟,就要走紧急发布了。

思前想后,还是趁这会清净,赶紧发完吧。于是,我紧急看了下代码,发现 RefundBizNsqInput 不在组件自动 scan 的包路径下。 于是将这个任务涉及的组件类都移到了可以被自动扫描组件的路径下,修复了下单测。此刻,如果要再验证 OK 再发布的话,恐怕会耗时太长。 于是,我做了个大胆的决定,不验证就直接发布。因为只是通过 IDE 的重构功能挪了下包路径,理论上是不会有问题的。 其实心里还是有点虚的。有时,直觉很重要,虽然它来自于大量经验的积累;当机立断也很重要。

想定,取消了之前的发布单,联系运维同学,帮忙重新开了个紧急发布的绿灯, 重新开始发布。幸运的是,这次没有再报错误日志。于是,我按照写好的发布文档,一个人在这静静的天亮时分,按部就班地开始发布, 一直发了两个小时,到七点半多才发完,一如 08 年踏雪归来的感觉。

反思: 如果在 QA 有验证那个退款消息的任务, 就不会出现这个尴尬的局面了。因为这个任务的消息处理,需要加载一个自定义的 消息接收器,而这个没有被覆盖到。 这是一条漏网之鱼。


决战尾声

电子发票同步

正要鸣金收兵,忽现新军情:电子发票索引同步的一队小骑兵,趁我困倦之时,偷袭过来,想来个以逸待劳。任务里的一段 groovy 脚本无法执行。有几条消息就出错几条。得赶紧解决,不然线上又要报问题了。

排查这个错误,有点尴尬。 错误日志只打印了: failed to execute 。。。 啥信息也没有。 我总不可能添加一行日志, 再发布一次吧。近乎崩溃。怎么办怎么办 ? 扫了一眼脚本,有个奇怪的格式化日期的变量似乎没定义。想用另一种先替换看看。没效果。试来试去,没效果。

此刻,有点难以承载思考了,有点扛不住了。 怎么办 ? 思来想去,只好打扰一下团队同学了。 叫醒了三位懂些 groovy 的同学,帮忙一起排查下。

KP 同学提醒说,在本地运行下这个脚本。 我马上想到了,可以把消息和脚本放到本地工程里运行下,看看哪里报错。那个被怀疑的变量也找到了定义的地方。在本地运行是通过的。 这可让人有点摸不着头脑了。 天已经亮了,八点多了。

有同学建议说,回滚吧。可是,回滚的代价太大了。 花了一整晚,终于将老的工程迁移到新的工程, 难道要因为一个小的问题,再滚回到原来的 ? 且新的工程回滚到老的,也是有比较大的风险的,发布也要经过一系列比较繁琐的操作,容易出错。 我宁可背一个 P4 故障,也要把这个问题解决掉。已经没有退路了。

突然灵光一闪,可以在预发起相同的任务,然后加调试日志,在预发看看是什么缘故 。 燃起了新的希望。

用最后的精力折腾了一下, 发现报 unable to solve groovy.json.JsonSlurper 。 我以为是没有 import ,因此加了一行 import 。可是还是报错。 KP 同学说 JsonSlurper 这个类没有找到啊,且最好把 groovy-all 也引用进去吧。关键的一击啊 ! 我马上去工程里看了下,groovy 2.0.8 才支持 JsonSlurper ,而我之前为了单测通过,只用了 groovy 2.0.1 。 细心的读者应该注意到,前面跑单测的时候,我把 groovy 的版本改成了 2.0.1 。 因果的命中注定 ,是说,在你写下这行代码或配置的时候,命运已经注定了,只待前来认领。要慎重啊!

立即修改,部署到预发, 解决了。 耶 !!

线上消费出错怎么办呢 ? 先用预发的任务消费线上的消息吧,确保后续的数据都写入 OK , 待明天做个优化后,再切回到线上的任务消费。原来预发可以作为线上的 “备胎” 啊!

反思: 不用说,这个任务也没回归到,因为很边缘。 然而,这个任务引用了一个类,依赖更高版本的 groovy ,是个局部细节问题,没有覆盖到。 看来, 凡有疏忽,必受惩罚。 老天爷的惩罚还是比较轻的: 照这样算下来,胜算大抵只有 80% 左右。

终于可以碎觉了。 emm.... 别打扰我。

监控统计

下午来到公司, 王爷 跟我说, S 的监控统计不对劲, 曲线值很低,跟业务量完全不匹配。我想,可能是日志平台又出问题了 ? 之前间歇性也会这样。但王爷坚持要查一下。于是,我开完一个会之后,就找日志平台的同学苏苏一起去查了。

苏苏显然更相信自己抓包看到的数据,说,这个业务量可能就是这样。但我深知,业务量肯定不是这样,更可能是改造工程的过程中,某个配置有问题,但暂时不知道是哪里有问题。

不过苏苏查底层问题,明显很有章法。在经过我认可后,在机器上安装工具,抓包,看监控图表,发现 在 S 发布点之后,写监控统计就一直报错了。查来查去,发现 S 引用了一个自研轻量级 R 的包,来写入监控数据, R 则会读取 S 的一个配置文件,而这个文件的 NSQ 配置是错误的。至此,真相大白。

想到之前还没仔细看过监控统计的代码,领悟: 每一个不清楚的想要暂时跳过的地方, 老天爷都会恩赐一个问题, 逼着你最终弄清楚。


小结

像是走过了一段较长的路。 面对大流量下的大改动的发布,曾经感觉到比较大的压力。发布完成之后,感觉似乎没那么可怕了。 一切都过去了。

流量汹似海,心内亦平川。 平常心就好。

订单同步工程标准化改造事记

标签:除了   fail   art   一加   方案   时间   左右   十一点   define   

原文地址:https://www.cnblogs.com/lovesqcc/p/11602540.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!