数据库自动部署工具Flyway使用

之前做过一个数据库的工具,可以从数据库中抽取所有应用关心的信息,包括表结构、分区、索引,存储过程,函数,序列,常量参数,技术配置表的内容等。有了这些信息可以检查确保环境配置的正确性,另外也便于开发人员检索使用。近期计划在组内搭建数据库变更脚本的自动部署及管理工具,进一步提高数据库变更的效率,保证变更的正确性。目前此类开源工具有很多,我们最终决定使用Flyway,它在Java项目中很常用,它可以批量对数据库脚本进行部署,并会对当前数据库环境的部署情况进行记录跟踪。

Flyway工具使用简单,它提供了Java API、Maven插件、命令行等多种使用方式,这些具体的使用方式在此就不详细介绍了,大家可以参阅“Flyway 官方文档”。下面结合我项目的特点来介绍下Flyway的使用,也欢迎大家批评指正。

我们项目本身比较复杂,主要体现在下面几个方面:

  1. 从数据库角度整体分为联机数据库、批量数据库,对于特殊产品还会有其他数据库服务器,例如信息报告数据库、第三方支付数据库(主库、多个子库)、海外业务数据库(多个)等;
  2. 从测试环境上分为单元测试环境、PL1-PL4共4个应用组装测试环境、VT环境、生产环境,这些不同环境之间的表空间,数据库个数也有差异;
  3. 多个时间点的版本会并行进行开发测试,不同版本的测试环境也不同,且同一个测试环境的版本可能会出现跳跃。

众所周知,不同环境的数据库要尽量保持一致,这样在维护方面会简单很多,但有些差异是很难避免的。为了更好的解决不同环境的差异我们设计了一套模板体系,只需要上传一套sql变更脚本,然后通过程序来生成不同环境的特定脚本,便于部署。另外此程序还会完成变更脚本的比对工作,并输出比对结果,从而保证模板和具体环境脚本的一致性。具体程序代码见FlywayMigration Git,大家可以自由使用,后续此程序的功能还会继续完善。

目前我们的sql变更脚本的目录结构如下:

+ dbscript
|- ljdb // 联机数据库
    |- baseline //存储当前数据库的基线信息
    |- common //所有环境通用的信息
        |- 20170819 //按版本建立不同的目录
        |- 20170922
    |- template //模板信息
        |- 20170819 //按版本建立不同目录
        |- 20170922
    |- unit //单元测试
        |- xxx  //目录下内容会自动生成
    |- vt
    |- template-config.yml
|
+- pldb //批量数据库
    |-xxx //同ljdb

其中template目录下是按照版本保存的模板文件,unit、vt等特定环境目录是程序自动生成的,且不允许手工维护。 通过上面的做法可以很好的做到对环境差异的屏蔽。对于第三个版本跳跃的问题目前没想到好的方法,目前想到的方法是通过不同版本使用不同的非交叉版本号,当版本跳跃时设置Flyway允许不校验执行顺序,另外通过最开始提到数据库信息抽取工具对变更结果进行比对,从而保证正确性。

这里有个小故事想和大家分享下:最开始在安排脚本目录结构时计划将template、unit、vt等特定环境的目录放在版本点如20170819的目录下,以便同一个版本的所有变更脚本都在一起,便于维护,目录结构类似ljdb/20170822/common、unit、vt、template这样。这样一来针对某个环境部署sql脚本时就需要根据环境名称来过滤需要的脚本,例如对于unit单测环境同时需要ljdb/*/common+unit多个版本点的目录。在网上找了很多地方,发现flyway不支持这种路径过滤方式,只能把多个版本点的路径通过程序一一列举,这样体验很差,而且容易出现sql脚本部署的遗漏。官方对此的说法是通过调整目录结构来解决。当时还觉得不爽,现在回头看调整目录结构后反而觉得更清爽了。站在这个角度官方的做法反而很值得推崇,不提供不必要或者容易误用的功能。

文章未经特殊标明皆为本人原创,未经许可不得用于任何商业用途,转载请保持完整性并注明来源链接 《ITechLib》

账务系统热点帐号问题

账务系统的热点帐号是一个很常见的问题,特别是随着互联网支付的发展,热点帐号的问题更加突显。很多支付机构由于业务限制往往在一个银行只有一个对公帐号,这一个帐号往往就是热点帐号。

对于不同帐号的并发处理相对比较简单,比如按客户或帐号拆表、拆库,这之间比较复杂的是分布式事务问题。目前处理分布式事务一般的解决方案包括:

  1. 可靠消息+补偿模式;
  2. TCC分布式事务模式;
  3. 两阶段提交;
  4. 拆分为多个局部事务。

这些方案往往通过自动任务来处理异常的场景从而保证最终一致性。

如果说去IOE最困难的问题应该就数热点帐号问题了(或者说分布式事务问题),因为单个帐号的扣减余额等肯定要通过事务来保护,防止出现余额和流水不符的情况。但事务必然会导致并发度下降,即使对于功能很强大的主机核心系统,并发度也会显得捉襟见肘。为了防止自己处理能力超限,主机往往会进行流控以保护自己,但这样的用户体验会很差。如果这件事在开放系统中做,由于每个服务器性能有限,并发度会更低。下面介绍下自己对热点帐号问题的理解,因我本人并不负责核心系统,有不当之处欢迎指正。大体解决方案分为如下几种:

1. 汇总记账方式

汇总记账分为两种,收单和付款。其中收单采用异步入账的方式比较好,可以将钱先放在内部过渡户中(过渡户可以是一个或多个),然后异步将钱整批汇总后入收方帐号。对于付款会复杂些,主要涉及到透支的问题,如果业务上允许透支也可以采用先银行垫付一段时间,然后整批一笔入付方帐号;如果业务不允许透支就比较复杂了,一种可行的方案是将每笔账务先预先记录流水然后返回“处理中”,后台采用批量的形式以批量为单位更新付方帐号的余额。但这样带来的一个问题是响应不够及时。

2. 异步缓冲记账

参考资料1中提到的削峰填谷方式,思路比较简单,对于不超过并发数的交易同步响应,对于超过并发的交易放在缓存队列慢慢处理,可以返回处理中或成功(返回成功有透支风险)。这种方式本质并没有提高系统的并发量,只能算是一种简单体验改进。

3. 同步批量缓冲记账

在参考资料2中提到了一种可行的解决方式,但并没有介绍如何实现,结合参考资料3,可行的解决方案如下。当接到客户的转账请求后,如果为热点帐号,则在执行完所有的校验后,将交易放在一个队列(或多个队列)中。针对每个队列会有单一的线程进行处理,它一次或获取多条待处理记录然后统一记录数据库流水,对于热点帐号的余额扣减只扣减一次。对于这种方法如何确定队列的大小以及同步等待的时间是一个需要分析的问题。这种方法可以成倍减少数据库的压力,类似与数据库事务管理器的提交机制,但它也带来一个问题就是增加了单笔交易的延时。为了使这种方法收益最大化,在框架设计层面需要把同一个热点帐号的交易转到特定的服务器来处理。

4. 建立多个影子帐号

上述“同步批量缓冲入账”方式的限制是单台应用服务器的处理上限,单台服务器的处理上限。为了解决此问题,一种可行的解决方案是将一个热点帐号拆分为多个影子帐号,每个影子帐号有自己的余额和流水信息。所有影子帐号的余额之和是整个帐号的余额。拆分后的处理逻辑和单个帐号的处理方式相同,可以采用不同的处理方式,如异步缓冲记账或是同步批量缓冲记账等。这个方案需要特殊考虑的点包括:

  1. 帐号余额增加时如何分配给其他的影子帐号:为避免分布式事务,建议采用异步方式处理。当然由于这个交易量很少,采用分布式事务也是可行的。
  2. 当单个影子帐号余额不足但整体帐号余额充足时的处理方式:可以采用“直接报错“”或是“返回处理中”改为异步处理。对于客户的特殊提取交易不应直接报错,因为报错则无法支持客户转出所有余额。对于异步处理的方案在返回处理中时可将此笔账务交易转移到其他影子帐号处理,如果所有影子帐号均余额不足但整体余额充足时,此时需要进行影子帐号间调账,将此笔交易放入特殊队列,将此影子帐号置为停用,在处理时将此影子帐号的钱转给其他影子帐号然后继续处理。直到所有的帐号均不支持则进行报错。可以看出当开始停用影子帐号时系统整体的并发度会显著降低,所以采用影子账号应让客户知晓,并提前做好客户的工作,例如帐号至少要保留特定余额、指定特殊的销户流程、与客户约定好各种异常的处理策略等等。
  3. 多个影子帐号之间需要保证负载均衡问题:另外需支持动态增加和减少影子帐号的个数。

另外提一下,内存数据库并不是一个好的解决方案,首先如何保证内存数据库和关系数据库的数据一致性是一个困难的问题;另一方面内存数据库本身也是一个单点,会存在单点问题;再一方面关系数据库本身对于热点数据也会做内存级别缓存,所以内存数据库意义并不是很大。

上面介绍了很多方案,可以看出并没有完美的方案,热点帐号方案更多的考虑是一种权衡,我们需要根据不同的情况来指定不同的策略。后续我会试着对于方案三、四进行实现,看是否有其他问题。后面如发现更好的解决方案也会更新此文章,也欢迎大家告知其他解决方案。

参考资料:

文章未经特殊标明皆为本人原创,未经许可不得用于任何商业用途,转载请保持完整性并注明来源链接 《ITechLib》