如何优雅的进行回滚?
回滚是指当程序或数据出错时,将程序或数据恢复到最近的一 个正确版本的行为。最常见的如事务回滚、代码库回滚、部署版本回滚、数据版本回滚、静态资源版本回滚等。通过回滚机制可保证系统在某些场景下的高可用。
事务回滚
在执行数据库SQL时,如果我们检测到事务提交冲突,那么事务中所有已执行的SQL要进行回滚,目的是防止数据库出现数据不一 致。
对于单库事务回滚直接使用相关SQL即可。如果涉及分布式数据库,则要考虑使用分布式事务,最常见的如两阶段提交、三阶段提交协议,这种方式实现事务回滚难度较低,但是对性能影响比较大,因为我们在大多数场景中需要的是最终一 致性,而不是强一 致性。因此,可以考虑如事务表、消息队列、补偿机制(执行/回滚)、TCC模式(预占/确认/取消)、Sagas模式(拆分事务+补偿机制)等实现最终一 致性。比如,电商中的单场景,会进行扣减优惠券、预占库存等操作,这涉及非常多的子系统,因此,很难使用分布式事务保证强一 致性,我们只要能保证最终一 致性即可。
可以看下面的例子:
image.png
一 种清况是当订单出错后,要把之前扣减的优惠券和库存回滚。但是,当保存订单出错时,JVM实例挂掉了,那么之前扣减的优惠券和库存就没有回滚,这种情况可以考虑在本地记录事务日志,当JVM实例重启后,分析事务日志重新回滚.
还有一种情况就是下单后一直没有支付,这时候可以使用补偿机制或者定时扫描订单表,把取消的订单并会滚库存和优惠券,总是目标就是要保持最终一致性。
关于普通的事务我们可以使用Spring提供的事务管理器去控制,而如果是分布式事务的话,关于详情,可以看下我这篇文章:
代码库回滚
在开发项目时, 一 定要将代码维护到代码仓库,从而进行版本管理。常见的有SVN、 Git 等,SVN 是一 款集中版本控制系统,而 Git 是一 款分布式版本控制系统。有了版本控制系统后就可以记录代码的历史版本,在出问题后可以方便回滚。当某个代码文件部署出现问题时,可以通过历史版本查看是谁修改的、修改了什么,从而快速定位出 BUG。
同时不同开发版本和功能还可以在不同分支进行开发和测试,互不影响,开发完成后合并分支到主干即可。
部署版本回滚
代码测试完成后,接下来就要进行系统的部署,在部署系统时,要考虑当前代码逻辑出现错误后如何快速恢复,总结为部署版本化、小版本增量发布、大版本灰度发布、架构升级并发发布。
其实上面说这么多,主要的区别就是跟你部署时,项目代码改动的规模以及涉及的影响去进行不同的部署。核心就是尽量小的减少部署可能带来的影响的同时,去降低部署失败后,如何去快速恢复。
-
部署版本化,就是将上一版本的包记录到部署系统中,在发布时应该采用全量发布,避免增量发布,必要可以之前全量版本回滚,不会受到限制。
-
小版本增量发布
比如修付小bug,添加一些简单的逻辑,这些都属于小问题。增量发布就是比如有10台服务器,先发布一台,没有问题了,再发布10台,最后全量发布。 -
大版本灰度发布
页面改版,添加新功能,大的改动,都需要进行灰度发布,一般情况是两个版本并行跑一段时间,一些用户访问老版本,一些访问新版本,测试成功后再全量发布。
4.架构升级并发布
架构升级时,很多问题都不确定,因为这时候新旧集群都会同时,存在,并且流量会慢慢迁移到新版本集群,老版本的部分功能,也可能继续运行,如果涉及到业务功能的大变动,这时候需要旧版本调用新版本提供的接口,后面会慢慢下线。
数据版本回滚
有些特定行业业务数据中的商品/价格数据需要进行版本化处理, 一 方面为了审计需要,另一 方面为了出现问题时能及时回滚。版本化设计可以基于下图的架构。
设计版本化数据结构时,有两种思路:全量和增量。全量版本化是指即使只变更了其中一个字段也将整体记录进行历史版本化,保存的数据量比较多,但是回滚方便。而增量版本化是指只保存变化的字段,保存的数据量较少,但是回滚起来很麻烦,需要回溯。因此,为了简单化处理一般采用全量版本化机制。
另外,在设计消息队列时,重要业务会对消息进行副本处理,以便万一业务逻辑出现问题能进行历史数据回滚,从而修复问题。
静态资源版木回滚
在前端开发中,静态资源版本也是会经常变更的,如JS/CSS,而每次内容变更时我们都会生成一个全量新版本放到项目的deploy目录中,从而保证版本可追溯,出现问题时能及时回滚。
因为静态资源一 般放在 CDN 上,所以缓存时间设置得比较长,比如 1个月。这样若发布的版本有问题,则需要清理CDN缓存,也需要清理浏览器缓存,而且因为存在版本覆盖的问题,所以即使覆盖了也不一 定保证操作正确。
所以基本的操作步骤如下:
• 发布新的静态资源到源服务器。
• 清理CDN缓存,从而可以回源服务器获取最新的静态资源。
• 在新的URL上添加随机数并清理浏览器缓存,代码如下。
<script type= "text/javascript" src= "/js/index .js?time= 201610231111"></ script>
而全量版本机制是最可靠的方式,我们先部署全量版本,然后通过如下方式引用。 <script type= "text/javascript" src= "/1.0.16/js/index.js"></script>
在当前发布版本出现问题时,只需要将版本号更改为上一 个版本号即可,不需要清理CDN、不需要清理浏览器缓存。
当然,这里要设置合理的服务器端页面缓存时间,比如2分钟,用户看到错误的发布版本最多2分钟时间。