RocketMQ银行实践案例分享
作者简介:金融业老兵,十余年金融行业工作经验,爱思考和分享,维护公众号【聊聊金融】(dhjr_it),每周持续更新金融科技与互联网运营相关原创文章。
1 概述
作为金融企业对公众提供服务一定要保证其可用性,尽量做到更多个9(一般SLA中描述的高可用性99.99%,中的9越多代表全年服务可用时间越长服务更可靠,停机时间越短),随着软件系统的复杂度越来越高,故障是不可避免的。这就需要企业实现整体的弹性架构(Resilient Architecture),为应对故障而设计。
然而,常见的RPC、RMI企业集成技术,因为是同步请求,常因为执行方失败、超时等因素而影响最终用户体验,而且很多故障是无法彻底消除的。而且RPC和RMI调用需要服务的消费方和服务的提供方同时在线,并且双方需要通过某种机制确认彼此的调用关系,因为存在这些弊端,就导致了面向消息的中间件(MOM)的产生,通过在企业架构中引入消息中间件,确保在故障发生时,受此影响的系统部分在一个很小的范围内。
消息中间件就是在分布式系统中间引入一个透明的中间层,隔离服务的提供方和消费方。
2 为什么要引入MQ
2.1何为MQ
消息队列(MQ)是一种不同应用程序之间(跨进程)的通信方法,用于上下游应用程序之间传递消息。我们拆分来看:
l 消息:应用程序通过写入和检索出入列队的数据(消息)来通信。
l 队列:除去了接收和发送应用程序同时执行的要求。
这样就实现了上游与下游之间的解耦,上游向MQ发送消息,下游从MQ接收消息,上游下游互不依赖,它们只依赖MQ。因为有队列的存在,MQ可在上下游之间进行缓冲,把上游信息先缓存起来,下游根据自己的能力从MQ中拉去信息,起到削峰的作用。
2.2 使用MQ带来的好处
2.2.1 解耦
l 什么是耦合
高内聚低耦合,是软件工程中的概念,这里的低耦合是指各个组件之间,尽可能相互独立。通俗一点的理解就是,增加模块间调用透明化,最高的透明度就是不用知道彼此的存在,因此减少接口的复杂性、规范调用的方式及传递的信息,降低产品各模块的依赖,提高重用程度。
l 如何解耦
在企业整体架构中解耦,主要设计两个方面:一是简化减少交互,二是增加一个中间层实现两方的隔离,MQ就是其中的中间层(如下图所示)。
引入MQ后生产者和消费者不必知道彼此的存在也不必同时在线,主要交互流程如下:
1) 生产者负责:生产消息并通过SDK或API调用发送给MQ(同步发送或者异步发送);
2) MQ负责:接收消息,并持久化消息到消息存储(同步或异步存储消息);
3) 生产者接收来自MQ的响应(消息发送状态或异常);
4) 消费者订阅消息后,接收来自MQ的消息;
5) 消费者执行消息对应的后续业务操作;
6) 对消费结果进行确认(消费成功、失败、异常等)。
2.2.2 削峰填谷
由于系统闲忙分布不均,QPS常相差几十倍甚至更高,特别是在遇到营销活动时,瞬间流量很可能超过后端系统的承载能力,这就要考虑通过消息中间件来缓冲,MQ客户端实例根据自己的处理能力从MQ服务器拉取消息,以此来减轻或消除后端系统的瓶颈。
(图片来源:
https://github.com/alibaba/Sentinel/wiki/Sentinel-为-RocketMQ-保驾护航)
2.2.3 异构集成
由于各种原因,我们在企业信息化建设过程中,都会面临软件产品来自不同的厂家只解决某特定领域的问题,这些产品因为封闭的架构无法对外提供服务或缺少核心开发而无法做大的改造,这就造成了彼此之间很难集成。通过引入MQ可以部分解决该问题,只需要在某个环节生产一条消息,或者根据消息做出具体的响应,只需与MQ对接,不必与其他系统做一对一的对接。
2.2.4异步|隔离
为了提供金融服务的整体弹性,需要隔离内部、外部系统间的依赖。如支付通知分为两种,一种是同步通知,这时API调用会因为网络故障而超时,因为服务提供方处理能力限制而得不到及时响应……等多种因素影响,另一种是异步通知,在一定时效范围内最终通知到即可,从而提供提高最终用户的体验和交易成功率,提高业务的整体生产率。
3 MQ选型
但凡选择就会受到主观和客观两个因素的影响。我们如何尽量客观的进行架构和框架选型,而避免先有结果而后找理由的文字游戏,下面我分享下我们做MQ选型的过程(这里不是说主观就是不好的,但作为工程师凡事做结构化和量化还是有必要的)。
3.1 关键需求
1) 集群支持:为了保证消息中间件的可靠性,需要提供完备的生产者、消费者、消息中间件集群方案;
2) 持久化的支持:为了避免消息丢失,需要支持消息保存到磁盘文件或其它格式存储;
3) 消息重试的支持:消息处理失败后的支持失败转存或重试,并提供消息至少投第一次或消息最多投递一次的配置;
4) 分布式事务的支持:为了保证业务的完整性,选择的中间件需要支持分布式;
5) 消息的按序消费:在有些场景下,需要消息的消费能够按照发送的同样顺序进行处理从而保证顺序执行;
6) 消息的延时支持:在2C业务处理或三方数据源对接中,会遇到消息延时投递要求,需要支持延投递;
7) 消息堆积和回溯功能:在消息中间件持久化保存大量消息时不会对性能有大的影响,支持消息查询、重发,或者按照时间点来重新消费消息,以应对某一段时间消息的重新消费场景。
3.2 其它需要考虑的因素
1) 产品与当前技术栈是否匹配,团队人员熟悉源代码更便于对消息中间件的原理理解和后续功能扩展;
2) 产品的使用广度特别是金融同业客户:同业因为业务同质化校对,场景需求相近,使用的人越多,说明关键场景支持较好,问题在之前暴露的越充分,当我们在使用时碰问题的时候,就比较容易找到对应的解决方案或解决人员;
3) 产品的高可用性:作为一个金融企业,需要服务的持续可用,作为提高企业弹性的基础消息平台,集群和高可用是一个必不可少的要求;
4) 产品的稳定性:产品可以持续、稳定的提供服务,不需要经常因为资源泄露或性能衰减等问题而重新启动。
5) 产品的活跃度:通过github统计数据能看出来这个产品是否经常有人维护,经常有人开发一些新的功能,经常fix一些bug。
3.3 选型要点及原则
l 搜寻满足关键需求的框架到候选清单;
l 从功能和非功能性需求等几个方面对候选框架进行筛选;
l 在帅选过程中要做好量化记录,避免先有倾向性的结果,后有筛选,这样选型就变成了一场数字游戏;
l 有时要换个角度思考,常用来做比较的可能就是最好的,如很多MQ框架都与Kafka做比较,那么Kafka有可能就是最通用的框架,如果做选型就要对其是否满足自己的需求做重点分析;
l 遵循第三眼美女原则,让理性引导感性;
l 适合的就是最好的,不要但纯追求高性能和功能全面。
3.4 选型建议
l 为未来最少三年或五到十年来选择,因为TedNeward在JAVA 消息服务的序言中总结了技术熟悉的过程4个阶段(门外汉、探索者、熟手、大师)。选型到全范围推广结束一年左右的时间就过去了,到大家熟悉和精通又一年过去了,谁都不想在刚熟悉还没用好,当前的产品就不满足要求了,又要重新来过。
l 区分关注点,确保只针对核心关注点进行选择。
l 给出选择的deadline,并按时进入到项目实战准备,再多的理论分析,都不如真正使用过后的感受深入,产品需要成长、团队成员同样需要成长,既然路在远方就赶紧起程。
3.5 候选产品比较
3.5.1 产品特性比较
3.5.2 测试:
功能测试:建议搭建POC环境进行验证,除验证相关功能性指标有没有,还要验证好不好用。所以在测试时要基于MQ提供的功能构建使用场景进行业务功能实现的验证。
性能测试:其实性能测试涉及的各方面因素比较多,如:基于什么样的环境,做了哪些配置,采用什么样的压测脚本和报文来做压力测试?比较指标:除TPS(发送者TPS、消费者最终处理业务的TPS)、延时、支持多少同时在线链接(生产者数据量、消费者数据量)、Topic配置(Topic数量以及每个Topic队列数量与生产者、消费者数据量的关系)、服务器的性能指标(cpu、内存、磁盘io、网络io)如何等也是需要考量的。
疲劳测试:在一定压力下持续运行24小时、一周或更长时间,稳定性如何?服务器的各项指标如何?是否有缓慢增长的趋势等?
重启或故障演练:分别对注册中心NameServer、Broker、Producer、Consumer的实例进行部分重启(或直接kill)或全部重启(或直接kill)、磁盘故障、网络故障等,查看应用的影响,如:在RocketMQ服务是否可以恢复,生产者消费者是否可以恢复服务,消息是否有丢失,消息是否有重复等。
3.6 选择RocketMQ的原因
l ActiveMQ的优势在于支持协议多样、文档资料丰富,缺点是性能、顺序投递支持有限;
l Kafka的优势在于高吞吐率,缺点是分布式事务、消费失败重试、延时/定时消息支持有限;
l RabbitMQ的优势在于与SpringBoot集成好,缺点是分布式事务、延时/定时消息支持有限;
l RockeMQ的优势在于高吞吐率、顺序消息、延时消息、消息堆积、消息回溯等支持较好,缺点是支持协议有限,多语言客户端支持有限。
我们最终选择RocketMQ的主要原因如下:
l 金融服务有场景对顺序消息有严格要求;
l 金融服务有场景对延时消息需求;
l 为了保证最终一致性需要支持分布式事务;
l 为了保证一致性消息对账需要MQ中间件支持消息查询;
l RocketMQ在高一致性(持久化、消息重试)做的比较好;
l 行内使用的JAVA技术栈,暂不需要多协议和多语言支持;
l RocketMQ的用户有国内知名的互联网金融公司、有微众银行、民生银行这样的互联银行和直销银行代表、有企业软件服务商,从3.1选型需求匹配和主要用户可以看出RocketMQ对金融场景适配比较好行业认可度高,随着金融用户的使用未来更多的需求将会被满足。
4 二次封装建议
封装主要是对业务、技术和数据进行抽象和封装,封装具有如下好处:
· 透明——通过引入二次封装SDK胶水层,隐藏具体实现细节
· 重用——提高基础代码的重用性同时增加的代码的可维护性和可靠性
· 安全——通过规范操作提高安全性
将规范封装到基础代码中以便在企业内部统一交互标准,具体的规范包括:
· topic命名规范
· producter命名规范
· Consumer命名规范
· 基于MessageId、key或业务ID的幂等通用方案封装
通过赋义编码规范可以通过名称定位到所属项目、模块等业务场景,以便在出现未知问题时可以快速协调人员进行处理,当然对topic、生产者、消费者等原数据管理也是必要的,毕竟命名规范能保留的信息有限。通过命名规范还可以避免冲突,比如topic的冲突会或误会导致消息无法正常消费或者业务流转等问题,消费者GroupID冲突会导致消息丢失等问题。
通过封装和定制化增强管理功能如批量信息查询、批量信息重发、消息对账等。
5 典型场景
RocketMQ是提高整体服务弹性的重要基础中间件,在个类金融交易中承担着重要的角色。
4.1 支付场景
下面以活期账户转出服务场景为例来讲解。
1) 出于安全考虑我们每笔用户活期账户转出都要发送一条短信通知,这时通过RPC调用即可实现,如下图所示:
2) 随着业务发展及出于安全和反欺诈考虑,建设了反欺诈服务,需要在每笔转出时发送支出场景的埋点数据,这个时候有两种方案,一种是继续使用RPC调用,如下图所示,这时转出操作响应时间可能是处理耗时A+短信调用时间B+反欺诈服务调用时间C(也有可能是通过异步RPC调用:响应时间转出操作时间+Max(B,C)),除了耗时之外,系统的故障点也多了,如果反欺诈服务或者短信服务宕机了,那么很大可能活期账户转出服务会随之宕掉(如果使用了熔断和隔离,可以很大程度规避跨系统间的调用异常影响)。
3) 另第二种解决方案是未雨绸缪,既然现在有第二个服务需要了解事件,那么以后是不是还会有第三个、第N个呢?如下图所示。这时活期账户转出服务的稳定性会进一步降低,复杂度急剧上升,在处理登录逻辑的同时需要考虑N个系统的关联需求。
4) 如果从领域模型上看这些是活期账户转出服务应该做的吗?显然不是,转出应该只关注与本身的账户操作,其它服务应该依赖转出的事件来做后续的相关业务逻辑,在这样的场景中适合用消息中间件来解耦。如下图所示。
这样各服务回归到本来应该的样子,账户转出服务不用关注于其它系统对它的依赖,只需要产生账户转出事件的消息,有需要的服务各自去订阅这个消息就好了,相互不受影响。
4.1 三方延时调用
在与银行外部系统对接的时候,会有需要在发送服务请求N秒后,去获得响应结果的异步请求。在使用RocketMQ的延时消息前,都是通过将数据落地到数据库或Redis缓存中,然后通过定时轮询任务来操作。这样做有以下缺点:
l 非通用方案,每个系统都需要单独维护一个轮询任务;
l 数据量大了以后轮询会影响数据库性能;
l 轮询时间不宜设置过低,常以分钟为单位轮询,时效性差,如果时间过短对数据库压力也会增加;
l 针对轮询失败的重试机制需要各自实现。
使用RocketMQ后有以下优点:
l 通用延时方案,生产延时消息和消息消费隔离;
l 支持高并发,具备较好的横向扩展能力;
l 时效性大幅提升,可以精确到秒级。
不过,当前版本还不支设置时间精度,只能按照特定的messageDelayLevel来设置,这就要在搭建RocketMQ时提前最好延时级别的规划或者对RocketMQ延时源码进行扩展以支持指定时间精度。
6 哪些场景不适合MQ
既然MQ有这么多优点,那么所有的调用都使用MQ岂不是很好?无处不在的经济学告诉我们:凡有收益必有代价。
MQ的不足:
(1) 在支持灵活性的同时,增加了系统的整体复杂度。
(2)因为异步调用的延时大于RPC同步调用是,所以会出现短暂的不一致性
(3)无法做到事务的强一致,需要分布式事务方案来处理
(4)服务的消费方要做幂等设计,来规避重复调用的问题
所以在软件的正常功能开发中,并不需要去刻意的寻找消息队列的使用场景,而是当出现性能瓶颈时,去查看业务逻辑是否存在可以异步处理的耗时操作,如果存在的话便可以引入消息队列来解决。否则盲目的使用消息队列可能会增加维护和开发的成本却无法得到可观的性能提升,那就得不偿失了。
7 经验总结和建议
遇到的那些坑
l Topic队列数设置不合理导致负载不均衡,影响RocketMQ的Broker的稳定。
l 消息堆积导致投递延时时间变长。
l 因为消费者的集群ID设置不规范,导致A消费者消费了B的消息。
l 因为Topic设置的不合理导致一个Broker节点的宕机导致消息消投递和消费异常。
l Topic、消费者GroupID相关命名不规范,导致运行一段时间后RocketMQ的Topic混乱无法清理回收资源。
当故障是不可避免的,就需要在应用程序设计的时候考虑通过一系列机制具备自我管理、自我恢复、自我配置等自治功能,随着云原生架构的流行,面对负载的加重和不断发生的各类故障,MQ不会是弹性系统设计的唯一选择,但也许是一个不错的选择,值得一试。