许文奇, 蚂蚁金服高级技术专家,
SOFAStack 商业化产品技术 Leader, 多年分布式架构及中间件研发经验, 负责过蚂蚁金服分布式架构在多家金融机构的咨询和落地.
本文根据他在 2019 蚂蚁金服 ATEC(Ant Technology Exploration Conference)科技大会上海站的分享整理.
本次分享主要会从单体架构和微服务架构的对比开始, 后面重点谈一下实施金融级分布式架构的常见三个问题.
常用架构: 单体式架构
目前很多金融机构的架构是典型的单体式架构, 一般由反向代理服务器, 数据库和应用组成, 所有业务模块都打包在一个应用里面运行, 一般为了高可用考虑, 应用至少会部署两个节点. 单体式架构在业务简单的时候有很多它自身的优点:
开发, 测试简单
部署简单
扩容简单, 只要给应用加机器就行
但同样, 单体式架构也有很多缺点, 尤其是业务规模变得复杂以后, 缺点会非常突出:
编译慢, 启动慢, 代码冲突等各种问题, 严重影响开发效率
性能扩展有局限性, 一定规模后, 单纯堆机器已经很难扩展性能了.
蚂蚁金服的架构: 分布式架构
微服务架构是目前大家最关注的一种分布式架构. 微服务架构除了在性能可扩展性上对比单体式架构有巨大优势, 还有一个重要优势体现在复杂业务下的生产效率优势.
只有在业务复杂度较低的时候, 单体式架构的生产效率才能超过微服务架构, 但随着业务复杂度上升, 尤其过了临界点, 单体架构下的生产效率是非常恐怖的急速下坠, 而微服务架构生产效率有所下降, 但下降趋势非常缓慢, 且不会偏离原有生产效率太多, 能很好的应对业务复杂性的增长.
所以实施微服务架构最重要的一个意义是在业务复杂度较高的情况下提升生产效率, 更快速的进行业务创新.
实施金融级分布式架构最常见的三个挑战
基于微服务架构的巨大优势, 很多金融机构, 企业开始逐渐转型微服务架构, 转型总是伴随着挑战, 这里选了三个最常见的, 最多用户关心的问题, 聊聊蚂蚁金服的一些实践心得:
如何进行微服务架构拆分及治理?
新的分布式架构如何兼容老系统?
如何一步一步构建金融级容灾架构?
1, 微服务架构拆分及治理
微服务拆分模式可以从微服务架构的扩展立方开始讲起, 分为 X 轴, Y 轴, Z 轴三类拆分.
X 轴代表横向扩展模式. 主要通过部署多份应用, 负载均衡的方式来扩展性能, 这个单体架构也可以做. 当然, 在微服务架构下, 横向扩展模式的实现方式有别于单体架构.
微服务架构做服务负载均衡不需要通过 F5 或者 LVS 集群这样的硬件设备, 只需要通过注册中心就可以实现, 这样带来的好处是降低了成本, 不需要购买大量的硬件设备了; 提高了性能, 业务干路上少了一个单点风险; 降低了维护成本.
当然支持横向扩展模式还需要应用是无状态的, 这个是微服务架构的基本要求, 任何涉及状态的数据都应该保存在数据库, 缓存或者其他一些存储介质中. 更高的要求的话, 应用应该要满足著名的 12 要素的要求.
Y 轴代表功能分解模式. 我们提倡业务系统在开发的时候就应该按照模块化进行开发, 满足高内聚, 低耦合的架构要求. 为了更好支持模块化开发, 以 SOFAStack 中间件的 SOFABoot 框架为例, 框架支持模块隔离能力, 不同的模块使用不同的 Spring 上下文, 这样不同的模块之间就不能直接引用了, 如果需要调用别的模块的接口, 那么需要走 SOFABoot 框架规定的特别声明才可以办到. 这样极大的规范了模块化开发, 使得这些系统将来在进行拆分的时候, 成本非常的低.
关于功能分解, 服务拆分, 虽然没有一个标准答案告诉我们就该怎么做, 但其实也有一些基本指导原则和实践:
按照业务领域进行拆解, 而不是组织. 所有拆分必须按照业务领域模型进行合理划分, 因为实体组织不一定能和业务领域完全对应, 且组织一般粒度较大, 不能作为拆分细粒度微服务的参考.
从数据核心模型拆分开始先拆, 而后再往上进行服务拆分. 这个主要是进行拆分的一个重要次序问题. 一般梳理数据核心模型比较容易, 比如很容易梳理出哪些表, 哪些数据属于交易, 哪些表, 哪些数据属于账务, 然后按照业务垂直进行拆分. 有些业务归属不那么明确的可以按照数据亲和性进行拆分. 然后数据核心模型拆分好以后, 再往上按照功能模块, 所对应的场景, 进行服务拆分.
单一职责. 这个是微服务架构的一条金科玉律, 要求微服务架构必须遵守. 一个很主要的原因是为了避免微服务架构重新掉进单体架构的老问题里面. 原来单体架构最大的问题就是任何的改动, 都要导致整个应用重新的编译打包部署, 还包括全量回归测试. 试想一个微服务里面有太多的功能, 职责, 如果任意一个业务模块有需求, 需要改动, 必然导致该微服务的应用重新编译, 打包部署和全量回归测试, 频繁变动成本很高, 研发效率也会降低.
注意拆分粒度, 避免一开始拆得过细, 循序渐进. 关于拆分粒度, 这个也是一个没有标准答案的问题. 总的一个原则就是适用最好. 任何一家公司的业务都是独一无二的, 不存在一模一样业务的两家公司, 所以别人的拆分, 可以借鉴, 但往往很难照抄. 所以推荐拆分的时候, 一开始不要拆得过细, 按照业务发展的规律, 循序渐进. 举个例子, 比如说红包业务, 原来可能只是营销系统的一个模块, 但如果公司的业务在红包这块发展得足够复杂, 是可以考虑拆为单独的微服务, 这一切取决于业务发展.
注意服务分级和分层, 避免循环依赖. 这个要求服务拆分的时候, 充分注意到哪些是核心服务, 哪些是非核心服务, 不能出现核心服务强依赖非核心服务的情况, 更不能出现循环依赖的情况.
考虑团队结构. 最后微服务拆出来, 肯定是要有对应团队去维护的, 所以整个拆分的粒度, 拆分的逻辑也要考虑团队结构的情况.
Z 轴代表数据分区模式. 一般在数据库遇到性能瓶颈的时候, 需要进行数据拆分, 一般分为垂直拆分和水平拆分. 垂直拆分一般就是按照业务划分来进行, 比如以前账务和交易放在一个数据库, 现在数据库有瓶颈了, 那么就可以拆成账务和交易两个数据库, 缓解数据库的性能瓶颈. 当然, 如果按照垂直拆分完毕以后, 还是不能满足性能要求, 这个时候就需要进行水平拆分了.
关于水平拆分, 也有一些重要的实践原则. 在做数据库水平拆分的时候, 最好一次性考虑未来十年, 甚至二十年的业务的要求, 要避免今天做了一个拆 5 张表的决定, 过了两年, 发现不行, 还要继续拆成 10 张表, 甚至 20 张表的情况.
重新分片是个非常复杂的工作, 尤其是线上还不允许停机的情况. 另外关于确定如何拆多少库, 多少表, 也有一些实践. 一般拆的数据库数量等于未来业务的峰值 (TPS) 除以单数据库的容量(TPS). 这里并不代表要一步到位, 拆那么多实际物理库, 一开始可借助 SOFAStack 分库分表中间件虚拟成逻辑库, 只需要少量物理库, 等到访问量上来后, 再扩容物理库. 拆的表的数量等于单表时间业务量乘以存储时长除以单标容量上限. 记住表的数量一定要一步拆到位, 避免过一两年还要折腾.
做了数据拆分后, 就会遇到分布式事务的问题, 需要解决分布式事务的问题, 这个也是金融行业区分与别的行业的最大不同. SOFAStack 分布式事务中间件目前支持 TCC 和 FMT 两种模式(这里主要讨论实践, 对于 TCC 和 FMT 两种模式的原理, 这里不再赘述, 有兴趣的可以参考之前的文章).TCC 模式虽然编码复杂, 业务有侵入, 难度较高, 但胜在性能好, 所以像核心系统里面的分布式事务都是用该方案. 当然, 因为复杂, 所以实现中有些地方需要注意.
业务模型要分两步设计. 因为 TCC 是个二阶段协议, 一阶段 try 的时候需要锁定二阶段提交的资源, 所以业务模型要按照这样的思路来进行设计, 当然这里还有一些技巧, 需要保证锁的粒度足够小, 否则性能就比传统 XA 模式没有太大优势了. 拿账户余额举例子, 一般可以在数据库表里增加一个冻结金额字段, 需要转钱走的时候, 在 try 阶段会增加冻结金额, 表示钱已经冻住, 不能挪做别的用途, 保证第二阶段有足够的资金进行转账. 因为对账户余额的锁定是一个业务上的逻辑锁, 而且逻辑锁粒度已经最小, 只需要锁定需要转账的金额, 全程几乎无数据库锁, 所以性能比较高.
业务数据并发控制. 这个很好理解, 对所有涉及金额的操作, 肯定是要加锁的, 控制并发的, 否则肯定会出错的.
幂等控制. 因为分布式环境下, 非常容易出现多次调用的情况, 为了保证业务不出错, 所有 try,confirm,cancel 方法都需要保证幂等.
允许空回滚: 这个其实出现概率不是特别高, 但为了保证分布式事务的一致性, 必须要做这样的控制. 举个例子来说明为什么会出现这样的情况. 事务发起方调用参与者的 try 方法, 因为网络拥塞, try 请求可能没有到达参与者. 然后 try 方法超时, 分布式事务框架判定一阶段失败, 所以开始调用参与者的 cancel 方法. 这个时候网络好了, 而且, cancel 方法的调用先到参与者, 这个时候, 参与者会进行一次空回滚(因为没有执行过 try 方法, 没有锁定任何资源), 所以必须要保证这种情况下空回滚没有问题.
防悬挂控制: 空回滚之后, cancel 方法执行成功, 分布式事务框架判定这次分布式事务结束. 这个时候如果之前被拥塞的 try 请求到达参与者, 参与者执行了 try 方法, 但这个时候分布式事务已经判定这次事务结束了, 所以不会继续推进二阶段事务, 这就出现二阶段 cancel 比一阶段 Try 先执行的情况, 就造成了事务的悬挂, 所以这里要也需要业务代码做个控制, 防止事务的悬挂.
FMT 模式的特点就是使用简单, 基本上像使用单机事务一样, 当然性能自然没有 TCC 高, 所以一般使用在外围一点的系统, 对性能要求不那么高的系统上去保证分布式事务的一致性.
最后关于服务治理, 这块有很多方法可以推荐, 比如说 API 文档化, API 文档可以借助开源的 swagger 来生成. 还有对核心的服务一定要做限流保护, 避免雪崩效应的发生. 另外可以借助全链路压测工具识别整个架构的性能短板, 做好相应的保护.
当然最重要的还是组织的问题, 要保证微服务架构的有效运转, 一定要在组织层面有相应的保障.
有些金融机构在落地微服务的时候, 整个组织架构还是完全的老的一套组织架构体系. 按照康威定律, 组织沟通方式会通过系统设计表达出来, 如果组织还是原来维护单体架构的一套组织体系, 那么必然在维护发展微服务架构的时候有很多的别扭和不适, 久而久之可能整个微服务架构又会慢慢腐坏, 退化掉.
再比如很多企业提出要走中台战略, 如果都没有一个实体的组织去负责这个中台, 去背 KPI 去维护发展这个中台, 把相应的通用能力沉淀到中台, 那么这个中台战略其实是不能得到很好的贯彻及执行的, 最终也达不到中台的效果.
组织的问题是实施微服务架构一个亟需解决的问题, 很多企业可以考虑慢慢过渡, 循序渐进的方式去调整组织架构, 去适应未来的发展.
2, 新的分布式架构兼容老系统
金融行业其实整体的信息化水平是领先其他行业的, 这个也造成了分布式架构转型中需要处理好大量老系统, 很多老系统用的是 C 语言, COBOL 语言开发, 甚至也有一些 Python 等其他语言, 如何保证新分布式架构对这块的兼容性, 保证整体架构升级慢慢平滑过渡, 这是一个经常遇到的问题.
我们主要推荐 SOFAMesh/Gateway 的方案去解决.
SOFAMosn 就是一个 Sidecar, 它包括了中间件 SDK 的能力, 包括服务注册, 熔断限流, 动态配置等. SOFAMosn 可以拦截应用的调用, 并对相应的协议进行解析后进行代理转发, 这样对于一些老系统, 就可以使用 Mesh 的方案去通信, 从而实现异构语言系统之间的相互调用.
对于一些不方便部署 Sidecar 的老系统应用, 还有 Gateway 的方案, SOFAGateway 部署方案有点类似 ESB, 是个独立的集群, 当然不会像 ESB 那样有那么复杂的路由能力. 所有协议的兼容, 转换都在 Gateway 完成, 从而实现异构语言系统之间的通信, 当然 Gateway 还可以附加一些额外的能力, 比如对调用进行 QPS 控制, 做一些安全校验等.
3, 构建金融级容灾架构
我们知道, 金融行业监管对系统的容灾是有一定要求的, 所以容灾架构成了金融企业的标配. 从架构的复杂性和解决的问题看, 我们推荐按照同城双活架构, 两地三中心架构, 异地多活架构和三地五中心架构的次序来慢慢推进.
对于一般金融企业, 做到同城双活或两地三中心一般足够了, 有条件的企业可以考虑异地多活方案.
在做同城双活方案的时候, 还是有个点需要特别注意, 因为同城两个机房一般有 1~3ms 的延时, 而一个用户请求一般会放大为十几个甚至几十个 SQL 的数据库访问, 那么这些增加的延时有可能会造成一些性能问题甚至用户根本无法使用. 所以推荐在实施同城双活架构的时候, 一般最好能模拟这块的延时, 提早发现应用的一些问题, 尽早优化, 避免架构上线的时候才发现, 造成一些线上问题.
要实现异地多活, 一般就得考虑单元化架构了, 因为异地之间 200 公里的延时一般至少有 7~10ms, 上千公里的话有 30ms 以上的延时, 同城双活的方案已经不能行得通了. 单元化的思想主要是把业务划分为一个个独立的单元, 让业务调用尽量在单元里面封闭, 减少跨单元调用, 这样才能保证将一些单元部署在异地的时候不会影响线上正常运行.
当然单元化架构对业务有一定侵入, 会增加开发运维的复杂性, 有必要上异地多活的架构的企业可以考虑是否应用.
总结
当前, 越来越多的企业已经认识到分布式架构在实现业务灵活扩展以及敏捷开发等方面的巨大价值, 实施微服务架构的时候选择完善, 经过验证的架构方案, 中间件产品的选择又是至关重要.
SOFAStack 金融级分布式架构是蚂蚁金服分布式架构实践的结晶, 而且还在不断革新中, 持续助力金融机构, 企业分布式架构转型, 可以在 https://tech.antfin.com/sofa, 了解更多.
长按关注, 获取分布式架构干货
欢迎大家共同打造 SOFAStack https://github.com/alipay
来源: https://juejin.im/post/5c3e9413f265da616a47e407