文章首发于公众号 松花皮蛋的黑板报
作者就职于京东,在稳定性保障、敏捷开发、高级JAVA、微服务架构有深入的理解
为什么今天要讨论这个话题呢?因为我最近上线时就犯了一个错误,想把这事和后来的复盘分享给大家,事故的过程如果没看懂可以直接往下拉看复盘。
过程是这样的:我的需求是在方法参数POJO类中新增一个可选参数,我将这个参数定义在POJO类的父类的最后一个。其中提供的是远程调用RPC服务,所以需要打一个描述类的JAR包供调用方使用,这个JAR包包含服务接口和入参、出参实体。
联调测试时使用的是1.1-SHAPSHOT版本,上线前我将版本号修改成1.0-SHAPSHOT版本,并且将私服中1.0-SHAPSHOT快照包更新了,业务代码没做任何改动。快照包的意思是同一个版本每次更新时都重新生成一个附有时间戳的包,当你构建下载包时都会下载指定版本最新更新的包,比如可以同时存在1.0-SHAPSHOT-2019101309和1.0-SHAPSHOT-2019080808版本。我当时上线后验证是通过的,说明版本是向下兼容的,就是调用方使用的1.0-SHAPSHOT-201908080也能正常调用我的1.0-SHAPSHOT-2019101309版本的服务。过几个小时后调用方使用了1.1-SHAPSHOT版本的包上线了,全量上线后,发现请求根本达到不了我这,因为在RPC框架中序列化异常了,此时调用方开始回滚,使用1.0最新的1.0-SHAPSHOT-2019101309开始构建上线,还是出错了。于是我比较着急也开始回滚,不过呢,我是将历史构建的包发布上线,也就是说使用的是1.0旧的1.0-SHAPSHOT-201908080包,当然毫无疑问还是会调用出错,此时才发现调用方回退时重新构建了,于是联系私服的同事将1.0最新的1.0-SHAPSHOT-2019101309删除,随后通知调用方重新构建上线,此时服务才恢复。
整个过程双方都有很多操作缺陷,比如我上线的并不是严格测试的包,甚至没有经过双方回归验证。比如我处理线上问题的方法方式,我其实是不需要回滚的。比如我告知了下游使用的版本号,但是下游还是使用了测试的版本。再比如下游没有进行灰度发布验证、异常后回退时重新构建了,等等。一个看似很简单的上线却失败了,说明上线发版规范没有完全把握好。
读者可能觉得上线变更没有值得深入探讨的地方,上线无非就是将要发布的包通过一定的技术手段替换现在线上运行的包,或者将配置信息覆盖更新,然后重启服务,并且现在都是通过鼠标点点按钮就能完成的事。但是呢,这其中有很多细节,稍微不注意就会像我一样犯错。接下来我将说说上线前、上线中、上线后、上线失败需要注意的地方。
先来说说上线前,这个上线包含新增实例分组发布、新增机器发布、在原有机器上发布。新增实例分组意味着你需要和旧分组仔细对比配置,包括日记级别配置。新增机器发布意味着你的机器网段可能是新的、你的调用外网服务权限可能是没有的、你的依赖系统库可能是没有安装的、你的IP可能不在白名单内,这些都是我在实际工作中碰到过的问题。
当上线条件和环境具备,包括前面说的机器配置,还包括上线时间,我们就可以提出上线申请了。原则上节假日(包括周末)前一天、重大促销活动(比如产品发布会)当天、流量高峰时间段都是不允许上线的。上线申请内容一般包括背景描述、操作对象、操作步骤、CHECKLIST、预期结果、回滚方案,还会包括自测情况。任何操作都应该有明确的文字说明,拒绝模糊或仅在大脑中认为可行的方案。同时你的操作变更还得周知产品、测试人员和其他同事,不能只有你、代码评审者、领导知道本次变更操作。避免当其他服务受到牵连时,其他人只能通过查看上线记录或者翻查代码提交记录才知道应该找谁。如果你修改的是公共代码或者协议,那更要提前周知了。
上线前检查还有一个需要注意的地方,就是确保你构建出来的上线包包括了你合并的代码块,现在大部分的平台构建出来的上线包,包名会附有最新提交到GIT代码仓库的COMMIT_ID,非常直观明了。
当上线前检查完成后,就可以发布部署了,一般将操作的服务实例按分组、机房分别分批部署,我这里强调的是分组、分机房,强调的是并不仅仅是以全部实例按多少比例部署,当然我们通常按30%的比例进行分批部署。不过,如果你的比例刚好命中某个分组或者某个机房全部实例时,那就意味着这个分组、机房的服务在全量上线,就很有可能出现无服务提供者的情况,如果上线失败,情况就不是短暂性的了。
分批部署第一步一般是每个机房选择一台进行发布验证,这样有助于我们及时地发现问题,避免影响扩散,甚至有些功能需要数据的积累才能验证,所以有时也会分时间段部署,每间隔一个小时部署一小比例的服务实例。当然分批部署只是我们规避线上风险的手段,不具备测试的目的,不能取代测试,也就是禁止将没有进行测试的包部署到生产环境,那怕只是修改了RPC服务的JAR包版本号。
分批部署一般需要验证原有功能是否有受到影响、业务监控是否有异常、服务实例是否正常启动、流量是否正常到达、功能是否生效是否有缺陷、程序资源消耗是否正常、程序性能是否正常。我碰到过发布平台显示上线成功,但是服务实例OOM崩溃了,如果此时贸然将此实例挂载到服务下,等待我的就是异常告警了。我还碰到过同事将新扩的机器部署后,却忘记挂载流量的情况,浪费了时间还浪费了机器资源,所幸原有机器成功抗住了流量。
说到这啊,我再补充一种情况,部署时发现有台机器连通性异常了,处在运维状态,可能只是发包使用的端口受到影响而已,服务监听的端口是没有受到影响的,此时你需要将这台机器流量摘掉,避免状态正常后流量打到了错误的服务版本了。为什么要将这个事单独拿出来说呢,其实是想强调我们要确认全部实例都更新为新功能版本了,包括避免漏发实例。
上线变更是事故的高发场景,当真的发生问题时,我们也不要慌张,先报备领导,然后第一时间止损和恢复服务。如果在发第一台验证的时候就出现异常了,最快的方式是修改Nginx配置将流量打到其他正常机器上,如果你摘取流量或者停止实例,其实都是有非同步状态的,因为用户接入层的负载均衡心跳检测可能是有延迟的。如果全量发布后发现异常,按照应急预案也无法及时止损的话,只能选择回滚服务,要避免造成二次影响。
实际上,当用户碰到问题时极少会选择反馈,沉默的是大多数,所以上线务必进行充分验证和全方面的监控,不要干等着用户来反馈,当用户来反馈时影响范围可能很大了。那么我们就需要规范化工作过程和输出,提高稳定性和质量。
好,本次的分享就到这,如果有帮助到你,欢迎点个在看或者分享给你的朋友们。
文章来源: www.liangsonghua.me
作者介绍:京东资深工程师-梁松华,在稳定性保障、敏捷开发、JAVA高级、微服务架构方面有深入的理解