我们在采用消息中间件(MQ)时通常是因为需要“解耦、异步、削峰”的业务架构或技术架构需求,目前开源的消息中间件有很多,比较主流的有RabbitMQ(Erlang实现AMQP协议)、Kafka(Scala,Java)、ActiveMQ(Java-JMS)、RocketMQ(Java-HTTP、MQTT)、ZeroMQ(C++,以库的形式存在,协议层是TCP、UDP)等,虽然都是消息中间件,但是因为采用的技术、实现的思路和面向的业务有所区别所以在我们实际开发的过程中,需要注意的规范也会存在一定的差别。
MQ选型参考
Kafka的雏形由LinkedIn开发,设计之初被LinkedIn用来处理活动流数据和运营数据。活动流数据是指浏览器访问记录、页面搜索记录、查看网页详细记录等站点内容。运营数据,是指服务器的基本指标,例如CPU、磁盘I/O、网络、内存等,其主要特点是基于Pull的模式来处理消息消费,追求高吞吐量,适合于产生大量数据的互联网服务的数据收集业务。在服务端处理同步发送的性能上,Kafka>RocketMQ>RabbitMQ。
Rabbitmq是采用Erlang语言实现AMQP(Advanced Message Queuing Protocol,高级消息队列协议)的消息中间件,在金融场景中经常使用,具有较高的严谨性,数据丢失的可能性更小,同时具备更高的实时性。
ActiveMq水平扩展能力差,而且内部存在很多BUG,会存在漏丢消息的情况,目前通常不在选型时不会选择他。
RocketMQ天生为金融互联网领域而生,对于可靠性要求很高的场景,尤其是电商里面的订单扣款,以及业务削峰,存在很大的优势。其在稳定性上可能更值得信赖,这些业务场景在阿里双11已经经历了多次考验,如果业务有上述并发场景,建议可以选择RocketMQ。
引入MQ是一项新的技术,在解决现有问题的同时,势必会带来新的问题。可从如下三个方面考虑:1、可用性降低,系统引入的外部依赖越多,越容易挂掉,MQ 挂掉之后会导致整个系统不可用。2.复杂度提高,重复消费、消息丢失、消息的顺序性等这些都是引入 MQ 之后需要考虑的事情。3.一致性问题,A 系统处理完了直接返回成功了,人都以为你这个请求就成功了;但是问题是,要是 BCD 三个系统那里,BD 两个系统写库成功了,结果 C 系统写库失败了,就会导致数据不一致。
消息内容管理
我们在通过MQ进行消息的传递时,消息体的大小会对处理的效率和性能存在一定的影响(主要是网络和磁盘IO),尤其是过大的消息体,过大的消息在发送和接收时会通过MQ进行拆分组装,不同的MQ对消息的限制也不一样。 例如:RocketMq 普通消息和顺序消息限制在4 MB,事务消息和定时/延时消息限制在64 KB,RabbitMQ服务端的最大帧为128K(消息最好在一个帧中处理完),Kafka中则没有明确的限制条件。为了系统的扩展以及处理效率,提出如下两点规范:
1、为防止超大消息产生,在使用MQ时需要限定消息体大小。(不同MQ的设置方式不同,大部分MQ可以直接配置)。
2、在通过MQ进行消息分发时,尽量只发送必要信息。例如:在异步数据更新的时候,如果数据表中涉及30个字段,而我们仅仅需要更新状态和时间,其他字段的传输就会对资源存在浪费。
消息Topic管理
Topic是主题(逻辑概念),很多MQ通过Topic来区分不同类型的消息记录,这里主要的代表是Kafka和RocketMQ,在RabbitMQ中消息都只能存储在队列中,但RabbitMq中存在主题交换机(Topic exchange)。
图一 RabbitMq
图二 KafkaMq
因为其实现方式不同所以Topic的数据对处理的性能有一定的影响。在单机情况下Kafka的性能在topic数量小于64的时候性能较好,到几百时系统性能有很大的影响,RocketMq在几百/几千时也有一定的影响,同时为了我们方便对MQ进行管理,防止后续维护困难,特提出如下两点关于Topic的规范要求。
1、Topic的创建需要有规划且按照一定的规律创建。建议如下:a、将Topic或则Queue按基础服务域(日志、系统)、业务处理域(支付、订单)、监控管理域(健康检查)进行分组。b、对于可以自动创建Topic的Mq建议生产上关闭,由我们自己进行维护创建。
2、Topic的创建不能无限制的增加(尤其是针对Kafka),对于无效的Topic要进行回收处理,Topic的命名需要按一定规则命名(可以根据业务领域模型),方便后续维护。
MQ授权认证及安全管理
在选择MQ后,我们通常是为多个用户以及业务场景提供服务,这就要求我们对接入的服务以及业务做好权限的控制,防止非约定应用或则消费一些权限之外的数据(Topic或则Queue)。这就需要根据MQ的认证授权机制,按照我们自己的规范进行配置和调整,对于没有提供权限控制的则需要进行封装重写,除了需要服务器级别(特定端口以及黑白名单)的控制外,同时需要软件级别的安全认证。
1、需要配生产者和消费者对于Topic或者Queue的操作权限,防止没有权限的用户发送消息或者订阅消息。
2、因为消息队列clientAPI权限太大,clientAPI信任级别太高,建议通过消息总线,降低拦截成本、隐藏通信细节、实施实时管控。
3、需要搭建对应的管理后台以及对应的异常监控,以方便我们实时监控MQ的运行(MQ出现异常时,会对接入的服务产生很大的影响),以及出现问题时的快速定位处理。
MQ开发规范指引
一、消息发送规范
1、异步先于同步问题是指在当前业务尚未处理完成就发送MQ消息,MQ消费者接到消息会和剩余未完成的同步任务一起执行,在任务存在交集的时候会造成数据错乱。
2、消息确认机制是指为了保证消息能够发送到服务端的一种确认机制,在我们使用MQ时,有些情况我们为了保证效率,允许存在一定概率的丢失,并不能保证消息一定发送到了服务端,所以在实际应用中对于比较重要的消息,需要根据MQ的功能特点选择不同的消息确认机制,保证消息能够发送到MQ,防止生产端消息丢失。
3、MQ全链路跟踪 在微服务技术体系下全链路的追踪很重要,我们有时需要知道消息的参数是由哪个Http请求参数的,这就需要我们在MQ中实现全链路的追踪,不同的MQ实现方式不同,有些甚至不支持(如:RocketMQ),这就需要我们自己封装实现。
4、消息延迟发送 在某些特定情况下,我们产生了消息,但不要求消费者立即执行,而是在达到某个时间点或则特定时间段后在进行处理,此时可以使用延迟队列实现。
5、消息持久化 是指在MQ服务出现故障或则重启时,消息不会丢失。一般MQ都支持消息的持久化处理(需要我们进行一定的配置),但是支持的程度不一样,如Kafka可以实现永久存储,RabbitMq则是临时保存。还有一些是不支持持久化,如:RedisMQ(基于内存,实现发布订阅的功能)或则ZeroMQ。持久化的使用能够更好的防止消息的丢失问题。
二、消息消费规范
1、消息重复问题 是指消费者在消费MQ的消息过程中,并不能够完全杜绝重复,我们在实际处理时,针对比较重要的消息一定要进行幂等性的校验,防止消息被重复消费。
2、订阅关系一致性保证 此问题多出现在集群消费处理的阶段,我们需要保证每一个订阅消费组中的订阅的Topic是一样的。如果不一样会出现订阅关系不一致,影响消费者处理数据的准确性。
3、消息的有序性 在集群消费时,对于需要严格按顺序处理的消息,需要针对不同的MQ进行特殊的处理,例如 Kafka需要定于对应的某一给分区,RabbitMQ则需要通过主题模式处理。
4、广播模式和集群模式 需要区分广播模式和集群模式的区别,不同的MQ在实现上是存在一定的差别,另外在定义消费者分组时需要按一定规则进行命名。
图一 集群模式
图二 广播模式
5、消息消费确认机制 在消费者消费完消息后,理论下MQ会有一些自动确认的机制,但因为了不涉及到业务逻辑和故障处理,所以在存在某些消息的确认是不符合预期或则如果在消费者服务出现异常时,会出现消息的丢失。这里就要求我们在开发时尽量不要使用自动确认,而是要自己手动确认。