一、背景
在互联网分布式应用中,如果上线的新版本有bug又不能回滚止损,带来的后果将是灾难性的。因此做到上线可回滚以及上线前的checklist是保证服务稳定性的基本要求。
在简单的场景里直接回滚到上一版个版本即可,但是如果涉及多个上下游和组件、考虑多版本兼容,就需要有好好设计下如何构建可回滚的代码,充分验证后还需要仔细检查上线checklist,最大程度保证线上服务的稳定性。
二、构建向前兼容的代码
回滚指的是程序或数据处理错误,将程序或数据恢复到上一次正确状态的行为。在回滚之后,程序依然能够正常处理,称为可回滚。
不可回滚原因大多是旧程序不能处理新数据,同时新数据又不能丢弃,代码回滚导致旧业务逻辑出错。
保证向前兼容的手段:
1、数据库变更
新加字段:设置默认值,默认值要保证新旧代码逻辑的语义一致性。比如用户表添加了用户状态,默认值要设置为默认有效。
删除字段:新版本全量发布后,最后迭代2-3个版本后,再删除无用字段,同时要做好数据库备份。
新加唯一约束:首先要确保原有的数据值没有重复的,再添加唯一索引,可使用以下SQL验证:
select field,count(1) from table group by field having count(1) > 1
复杂数据库字段变更:
复杂数据库字段变更:
通常做法是:双写读旧 -> 新字段离线验证 -> 旧字段全量拷贝至新字段 -> 双读旧为主diff -> 双读新为主diff -> 写新读新 -> 移除旧字段和逻辑;
出问题之后,只回滚程序,不回滚数据。
2、对外提供服务API变动设计
(1)对外提供RPC服务
入参新加字段:设置为可选的,如果没设置值,要有老业务逻辑兼容代码
返回值字段:设置为可选的,如果是新版本肯定有值字段,需要在字段描述文档里做好备注
如果入参和返回值结果差异较大,建议新建一个RPC方法,逐渐把业务方调用迁移到新方法
(2)对外提供HTTP服务
HTTP接口和RPC接口的不同是没有强制的约束,数据交换大多采用Json形式,虽然灵活性强,但是约束力低给管理带来很大的成本。因此HTTP接口文档必须给出类似RPC一样的规范,比如Swagger等工具,比如给指定字段名、类型、是否必填、不填写的默认值等。
3、多版本发布
对于客户端API服务或者基础服务来说,用户升级会有很大的延迟,对于服务提供方来说则需要尽量保证多版本同时可用。最好在最开始设计接口的时候就添加version字段,保证以后的扩展。
4、静态资源发布
因为静态资源一般放在CDN上缓存时间设置的比较长,比如1个月。这样假设发布的版本有问题,需要清理CDN缓存,并且也需要清理浏览器缓存,而且因为存在版本覆盖的问题,即使覆盖了也不一定保证是操作正确了。
现在的工具默认都会把md5设置到js、css文件名中,可有效的避免以上问题。
三、上线checklist
尽管准备工作做的再好,难免也会出现疏漏,因此指定上线前checklist,组内同学交叉评审,保证上线前的最后一步,是很有必要的。
发布策略一般是从下游逐步向上部署。而回滚方案相反,一般是从上游往下回滚。
通用的checklist需要考虑以下几方面:
1、依赖
(1)存储(数据库、分布式缓存)
是否有数据库变更需要代码上线前先提交,并且验证执行成功。
(2)动态配置
首先需要确认代码中的动态配置有无默认值,没有默认值需要先在动态配置系统上添加相关的配置。
(3)下游
确认下游jar包版本是否正确,线上最好使用release包。
确认下游的业务方是否已经上线。
(4)鉴权
美团内部的服务调用都有鉴权,检查鉴权是否已经添加并且已生效
2、上游
(1)Nginx层
确认路由策略,在发布过程中会不会造成用户不断刷新页面命中不同代码逻辑,给用户带来用户体验上的问题和困扰。
(2)上游业务方
确保和上游业务方沟通好,等当前服务全量发布后且观察、验证没问题再通知上游发布。
3、业务内部
通过代码review,QA测试等方式保证上线对现有业务逻辑没有影响。
如有必要可建设业务的checklist规范
代码review:重点查看代码规范、业务逻辑的正确性、对相关功能的影响
测试用例:测试相关代码的正常逻辑、边界条件、其他功能不受影响,如果写好测试用例请参考其他专业的文档
自动化测试:有条件的团队建议添加自动化测试,来保证核心业务流程的正确性
四、可回滚发布
1、回滚方案
需考虑以下几方面:
是否有动态开关、流量控制可以一键恢复到旧版本逻辑
回滚代码会不会导致上游调用失败
回滚代码:根据docker镜像、Git commitId、Git tag回滚
2、可回滚验证
方案设计要考虑可回滚性;通常要在test或预上线环境验证(演练)可回滚性。同时QA同学要把可回滚作为质量验收的一部分。
3、平滑升级
(1)线上验收
通过staging环境或线上灰度链路, 进行线上验收。
(2)蓝绿发布
线上部署两个集群,每个集群都能抗住所有的流量,发布的时候一个集群接受用户请求,等当前集群全部发布成功后,再把流量全部迁移到新版本集群。
优点:可保证新版本特性同一时间对所有用户生效;保证系统高可用,一个集群出问题,可快速切换到备用集群;
缺点:日常使用的机器只有一半,造成浪费;
蓝绿发布的高配方案:部署两套集群,通过Nginx层动态切换集群。
蓝绿发布的低配方案:用动态开关控制新旧逻辑,等新版本全量上线后,再切换到新代码逻辑。
(3)灰度发布(金丝雀发布)
采用金丝雀部署,可以在生产环境的基础设施中小范围的部署新的应用代码。一旦应用新发布,只有少数用户被路由到它。最大限度的降低影响。检测新版本没问题,再逐步扩大发布范围直至全量。
实现灰度发布,需要部署系统设置分批发布,同时必须有监控、报警等相关的系统的配合。确定没问题后再扩量。
还有一点需要注意的就是灰度路由策略,一般服务中用的多是的随机策略,这样可能导致用户在发布过程中不断刷新页面,可能会来回命中新老版本逻辑,影响用户体验。避免方式有以下几种:
采用用户ID做路由因子,问题是新增、删除机器时,算法匹配的机器会变,就需要引入一致性Hash算法,复杂性较高
RPC路由策略一般不能执行路由规则,就需要程序自己判断。常用做法是采用动态配置设置百分比值,算法如下:
userId % 100 < $dynamicTrafficPercent ? oldVersion : newVersion
(4)AB测试
AB测试虽然也有流量控制的功能,但一般是用于验证不同版本之间的性能、用户体验、效果数据等的手段。是上线后由运营控制的。
AB测试和灰度发布最大的不同是AB测试是通过桶的方式分配流量。
4、监控报警
常用的发布相关的监控报警包括:
新、旧版本的流量及百分比
对外提供的接口的性能指标、下游的性能指标(TP50、TP90、TP99、TP999)
异常指标及报警
业务指标
5、发布完成周知
PM: 周知PM关注业务指标、客诉等
上游:如果上游依赖此发布,周知上游开始发布
下游:周知下周注意流量增量及耗时等情况
五、上线步骤及规范
1、不在高峰期上线,如必须上线,要发邮件申请,同时降低并发度
2、上线前周知
3、保证能回滚
4、发布过程采用分组发布(强制)
5、上线过程中观察系统指标、业务指标、异常指标等;如有异常立即禁用已发布机器
6、上线后周知PM、上下游等
7、有异常第一时间回滚
六、总结
从本文可以看出,保证线上稳定性是一个复杂且系统的工程,需要从技术规范、流程规范、周知、检查、工具支持等各个方面来保证。
作者简介:木小丰,美团Java高级工程师,专注分享软件研发过程中的实践、思考。欢迎关注公共号:Java研发
原文链接:构建可回滚的应用及上线checklist实践
更多精彩文章:
Maven依赖冲突问题排查经验
升级Java17问题记录
使用Groovy构建DSL
Gradle最佳实践