京东-JMQ框架介绍
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
京东-JMQ框架
JMQ是京东自主研发的一款消息中间件系统,具有高可用、数据高可靠等特性。广泛应用于公司内部系统,包括订单、支付、库房等场景。
1.整体结构
系统包括服务端、客户端、管理端与其他支撑模块。
JFS( JOURNAL FILE SYSTEM):一种字节级日志文件系统,借鉴了数据库保护系统的技术,以日志的形式记录文件的变化。JFS通过记录文件结构而不是数据本身的变化来保证数据的完整性。这种方式可以确保在任何时刻都能维护数据的可访问性。
Redis:是一个key-value存储系统。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,redis支持各种不同方式的排序。与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。
HBase:Hadoop Database,是一个高可靠性、高性能、面向列、可伸缩的分布式存储系统,利用HBase 技术可在廉价PC Server上搭建起大规模结构化存储集群。
1.1.服务端
服务端提供了配置信息分发、重试消息管理和消息存储与分发这三大类功能。每个服务端实例都具备这三类功能的服务能力,但是在实际部署上这三类功能对应三个不同的集群,对应每一个实例功能不叠加。在测试环境和库房等资源有限的环境下,这三类功能由同一个服务端实例提供服务。
配置信息分发:负责客户端参数变更时与消息分配的服务端实例变更时通知客户端。
重试消息管理:主要用于对业务系统临时处理不了的消息进行存放,然后再按照一定的策略投递给客户端处理。可以提供错误原因、错误处理次数等查询。
消息存储与分发:接收生产者投递的消息,把消息存放在本地磁盘上,消费者从该服务上拉取消息进行消费。
1.2.客户端
目前只提供了JAVA语言的SDK和支持HTTP协议的proxy,非JAVA语言通过proxy接入。
1.3.管理端
主要功能有:接入申请、消息元数据管理、重试消息信息查询、消息发送和消费日志查询、服务端状态信息管理查看、客户端连接信息管理查看等。
1.4.支撑模块
主要有报警模块、任务模块、归档模块、信息采集模块等。
1.5.数据可靠性
针对公司的业务特点,消息服务主要应用于订单、支付、物流等环节。服务端采用MASTER-SLAVE结构,消息在正常情况下会同时存放两份,其中一份会强制持久化到磁盘,磁盘做RAID-5。默认情况下客户端采用
同步发送,每条消息到达服务端MASTER后会强制刷入磁盘同时并行推送一份到SLAVE上,SLAVE写入文件系统后不等待强制刷盘就反馈给MASTER。根据不同的场景为了提高服务的可用性,普通级别的消息SLAVE 断开后,该组服务可以正常使用,当SLAVE连接上后又会自动切换为保存两份。当然对数据可靠级别高的消息是强制要求数据必须写两份才算成功的。
1.6.服务高可用
每类消息一般都会分配3组及以上的服务组,每组服务包括一个MASTER和一个SLAVE,当然如果有需要也可以挂载多个SLAVE。
客户端发送消息时,如果其中一组出现故障会重试发送给其他的组。
虽然MASTER-SLAVE支持切换,提高服务的可用性,但是在实际生产中MASTER出现故障时会优先采用通过其他服务组自动接替生产服务的方式,本组服务只提供从SLAVE读取的方式,而不是让SLAVE接替MASTER 的写入,避免临界状态下丢失消息。
对要求严格顺序的消息,不能通过简单的切换服务组实现,具体实现方式参考《高可用保证消息绝对顺序消费的BROKER设计方案》(点击“阅读原文”查看这篇文章)。
1.7.消费模型
由于公司以前有使用基于ACTIVEMQ二次开发的服务,服务端会存放客户端的消费位置,因此在自主研发JMQ时也延续了这种方式(可以兼容ACTIVEMQ的客户端)。但是ACTIVEMQ生产和消费都会操作索引文件,影响性能,JMQ吸取了这个经验教训。消费者在消费时按照索引分区顺序的消费,消费确认时只需要变更最后确认位置的值,不需要操作索引文件,而且多个消费者共用一个索引文件,各自保存自己的消费偏移位置就可以了。
当然在实践过程中,由于一些特殊场景需要,会允许一定范围内不完全按照顺序消费,但是服务端会记录已经消费的索引区间。
与KAFKA的对比
JMQ在服务端存储设计上与KAFKA有一些相似的地方,借鉴了文件按照偏移位置管理、顺序追加等特点。
不过JMQ的存储和消费模型有自己的特点:
1.8.消息存放
JMQ每个存储系统只有一个分段存储的日志文件,不同类的消息按照服务端接收的顺序存放在日志文件中,通过索引程序按照不同的消息(主题)分类名异步创建各自的索引,方便消费端获取消息时快速定位该客户端所关心的(主题)分类消息。每个(主题)分类的索引划分了多个分区,同一(主题)分类的消息分配在多组服务器上的分区数是相同的。每个索引分区都是以链表按照时间序存放消息引用信息。
消费
JMQ也采用客户端主动拉取的方式,但是客户端不需要协调自己应该从哪个服务器上获取消息,服务端会控制好每个索引分区里对应的消息在同一时刻只会被一个客户端线程取走,直到客户端反馈消费成功或者消费异常,消费异常会被重试程序转移到重试服务中。如果客户端长时间没有反馈信息,达到了超时时间,那么锁定的消息可以被其他的线程拉取走。
由于服务端储存了每个消费者消费的位置,因此服务器可以随时把已经消费的消息移除走。
2.主要特性与场景
2.1.发布与订阅
目前公司接入的消息绝大部分都采用这种方式,不同类的消息通过主题名进行区分,多个消费者分组之间各自消费一份完整的消息内容,他们看到的消费视图一模一样,唯一的区别就是各自消费进度不同。
同一个消费分组内的消费实例只会消费到其中一部分消息,各自连接服务端,通过抢占的方式进行消费。
场景:
以订单消息为例,订单系统在订单的生命周期里的每一次变更都会发送消息,订单查询系统、结算系统、库房生产系统等都会订阅该类型的消息,每个系统拿到一份完整的消息,各自进行处理。
2.2.广播
由于发布订阅型的主题消息,如果要获取一份完整的消息就需要命名一个消费组,如果一类消息每个消费者实例都需要获取一份完整的消息,如果还按照主题消息管理那么就需要为每一个实例命名一个唯一的标识,使用时非常不方便,这时可以使用广播类型的消息,每个消费广播消息的实例都会拿到一份完整消息。
场景:
分布式数据库接入层对应的服务端拓扑信息需要调整,客户端可以订阅一个拓扑变更的广播消息,提前把需要变更的拓扑信息下发给每个客户端备用,当捕捉到拓扑变更的异常后就启用备用拓扑信息。