工行传统的业务系统一般都是基于 JEE 的单体架构,面对金融业务线上化及多样化的发展趋势,传统架构已经无法满足业务的需求。因此从 2014 年开始,工行选择了一个业务系统做服务化的尝试,并验证、评估、对比了当时的几个分布式服务框架,最终选择了相对完善、并且国内已经有较多公司落地使用的 Dubbo。与此同时,工行还对 Dubbo 做了企业定制,帮助这个业务系统完成了服务化的落地,上线之后也收到了非常好的效果。
2015 年,工行开始扩大服务架构的落地范围,一方面帮助传统业务系统进行架构转型,另一方面也逐渐沉淀了类似中台的超大规模服务群组,支撑业务系统快速服务的组合和复用。随着经验积累,工行也不断对 Dubbo 进行迭代优化和企业定制,同时围绕服务也逐步打造了完善的服务生态体系。
2019 年,工行的微服务体系也正式升级为工行开放平台核心银行系统的关键能力之一,助力工行 IT 架构实现真正的分布式转型。
工行的微服务体系组成如下图所示:
经过工行多年的落地实践,本文共总结了以下两方面的最大挑战:
本文将先从服务发现方面,来分享一下工行的应对策略及成效。
在 Dubbo 中,服务的注册订阅及调用是一个标准范式,服务的提供者初始化时注册服务,服务消费者初始化时订阅服务并获取全量提供者列表。而运行期间,服务提供者发生变化时,服务消费者可获取最新的提供者列表。消费者与提供者之间点对点 RPC 调用,调用过程不经注册中心。
在注册中心的选择上,工行在 2014 年就选择了 Zookeeper。Zookeeper 在业界的各类场景下有大规模的应用,并且支持集群化部署,节点间数据一致性通过 CP 模式保证。
在 Zookeeper 内部,Dubbo 会按服务建立不同的节点,每个服务节点下又有 providers、consumers、configurations 及 routers 四个字节点:
在线上生产环境,Zookeeper 分数据中心部署了多个集群,每个集群配置了 5 个选举节点,若干个 Observer 节点。Observer 节点是 Zookeeper3.3.3 版本引入的一个新的节点类型,它不参与选举,只听取表决结果,其他能力则和 Follower 节点相同。Observer 节点有以下几方面的好处:
工行根据这几年线上 Zookeeper 的使用心酸血泪史,总结了 Zookeeper 在作为服务注册中心时面临的问题:
综上,可以得出的结论是:总体上 Zookeeper 作为注册中心还是比较称职的,但在更大规模服务量场景下,需要进一步优化。
工行最主要的优化措施包括下面这几方面:订阅延迟更新、注册中心采取 multiple 模式、升级到按节点注册等。
工行对 Zookeeper 客户端组件 zkclient 做了优化,把消费者收到事件通知后获取提供者列表做了一个小的延时。
当 zkclient 收到 childchange 一次性的事件后,installWatch() 通过 EventThread 去恢复对节点的监听,同时又使用 getChildren() 去读取节点下的全部子节点获取提供者列表,并刷新本地服务提供者缓存。这就是前面说的“5050 条数据”问题的根源。
工行在 zkclient 收到 childchange() 的事件后,做了个等待延迟,再让 installWatch() 去做它原来该做的事情。这个等待过程中如果服务提供者发生变化,则不产生 childchange 事件。
有人会问,这是不是违背了 zookeeper 的 CP 模型呢,其实并不是,zookeeper 服务端的数据是强一致的,消费者也收到了事件通知,只是延后去读取提供者清单,后面执行 getChildren() 时,读取到的已经是 zookeeper 上的最新数据,所以是没有问题的。
内部压测结果显示,服务提供者大规模上线时,优化前,每个消费者收到了总计 422 万个提供者节点的数据量,而延迟 1 秒处理后,这个数据量则变成了 26 万,childchange 事件次数和网络流量都变成了原来的 5% 左右,做完这个优化,就能从容应对投产高峰期大量服务的上下线。
工行采纳并优化改造了 Dubbo 新版本中 registry-multiple 的 SPI 实现,用于优化多注册中心场景下的服务订阅。
Dubbo 中服务消费者原有处理逻辑是这样:当存在多个注册中心的时候,消费者按注册中心对应的 invoker 缓存去筛选提供方,第一个注册中心对应的缓存中如果没找到,则去找第二个注册中心对应的缓存。如果此时第一个注册中心出现可用性问题,推送给消费者的数据有缺失,甚至为空,就会影响消费者的这个筛选过程,如出现无提供方的异常、调用负载不均衡等。
而 multiple 注册中心是把多个注册中心推送的数据合并后再更新缓存,所以即使单个注册中心故障,推送了数据不完整或者为空,只要有其他任意一个注册中心的数据使完整的,就不会影响最后合并的数据。
并且,multiple 注册中心机制也用于异构的注册中心场景,出现问题可以随时把注册中心下线,这个过程对服务节点的服务调用则完全透明,比较适合灰度试点或者应急切换。
更进一步,还有额外的收益,消费者端 Reference 对象是比较占用 JVM 内存,通过 multiple 注册中心模式,可以帮消费者端节省一半的 invoker 对象开销,因此,非常推荐多个注册中心场景采用 multiple 模式。
本文系作者在时代Java发表,未经许可,不得转载。
如有侵权,请联系nowjava@qq.com删除。