今天的博客主题
MQ消息中间件 --》RocketMQ --》RocketMQ入门(三)
本文主要讲解RocketMQ的架构设计、架构部署及组件在实战中的建议
架构设计
RocketMQ是一个分布式消息传递和流媒体平台,具有低延迟,高性能和可靠性,万亿级容量和灵活的可扩展性。
它由四部分组成:名称服务器(name Server),代理(brokers),生产者(producers)和消费者(consumers)。它们中的每一个都可以水平扩展而没有单一的故障点。
生产者集群(Producer Cluster)
消息发布的角色,支持分布式集群方式部署。Producer通过MQ的负载均衡模块选择相应的Broker集群队列进行消息投递,投递的过程支持快速失败并且低延迟。
消费者集群(Consumer Cluster)
消息消费的角色,支持分布式集群方式部署。支持以push推,pull拉两种模式对消息进行消费。同时也支持集群方式和广播方式的消费,它提供实时消息订阅机制,可以满足大多数用户的需求。
名称服务器集群(NameServer Cluster)
名称服务器提供轻量级的服务发现和路由。每个名称服务器记录完整的路由信息,提供相应的读写服务,支持快速存储扩展。
NameServer是一个非常简单的Topic路由注册中心,其角色类似Dubbo中的zookeeper,支持Broker的动态注册与发现。
主要包括两个功能:
Broker管理:NameServer接受Broker集群的注册信息并且保存下来作为路由信息的基本数据。然后提供心跳检测机制,检查Broker是否还存活;
路由信息管理:每个NameServer将保存关于Broker集群的整个路由信息和用于客户端查询的队列信息。
然后Producer和Conumser通过NameServer就可以知道整个Broker集群的路由信息,从而进行消息的投递和消费。
NameServer通常也是集群的方式部署,各实例间相互不进行信息通讯。Broker是向每一台NameServer注册自己的路由信息,所以每一个NameServer实例上面都保存一份完整的路由信息。
当某个NameServer因某种原因下线了,Broker仍然可以向其它NameServer同步其路由信息,Producer,Consumer仍然可以动态感知Broker的路由的信息。
我们知道,RocketMQ客户机(生产者/消费者)将从名称服务器查询队列路由信息,但是客户机如何查找名称服务器地址?
有四种方法向客户机提供名称服务器地址列表:
编程方式,如producer.setNamesrvAddr(“ip:port”)。
Java选项,使用rocketmq. namespace .addr。
环境变量,使用NAMESRV_ADDR。
HTTP端点。
代理集群(Broker Cluster)
代理通过提供轻量级的主题和队列机制来处理消息存储。它们支持推拉模型,包含容错机制(2个副本或3个副本),并提供按原始时间顺序累积数千亿条消息的峰值填充和容量。
此外,代理程序还提供灾难恢复、丰富的度量统计数据和警报机制,这些都是传统消息传递系统所缺少的。
Broker负责消息的存储、投递和查询以及服务高可用保证,为了实现这些功能,Broker包含了以下几个重要子模块
Remoting Module:整个Broker的实体,负责处理来自clients端的请求。
Client Manager:负责管理客户端(Producer/Consumer)和维护Consumer的Topic订阅信息
Store Service:提供方便简单的API接口处理消息存储到物理硬盘和查询功能。
HA Service:高可用服务,提供Master Broker 和 Slave Broker之间的数据同步功能。
Index Service:根据特定的Message key对投递到Broker的消息进行索引服务,以提供消息的快速查询。
架构部署
RocketMQ 网络部署特点:
NameServer是一个几乎无状态节点,可集群部署,节点之间无任何信息同步。
Broker部署相对复杂,Broker分为Master与Slave,一个Master可以对应多个Slave,但是一个Slave只能对应一个Master,Master与Slave 的对应关系通过指定相同的BrokerName,不同的BrokerId 来定义,BrokerId为0表示Master,非0表示Slave。Master也可以部署多个。每个Broker与NameServer集群中的所有节点建立长连接,定时注册Topic信息到所有NameServer。
注意:当前RocketMQ版本在部署架构上支持一Master多Slave,但只有BrokerId=1的从服务器才会参与消息的读负载。
Producer与NameServer集群中的其中一个节点(随机选择)建立长连接,定期从NameServer获取Topic路由信息,并向提供Topic 服务的Master建立长连接,且定时向Master发送心跳。
Producer完全无状态,可集群部署。
Consumer与NameServer集群中的其中一个节点(随机选择)建立长连接,定期从NameServer获取Topic路由信息,并向提供Topic服务的Master、Slave建立长连接,且定时向Master、Slave发送心跳。
Consumer既可以从Master订阅消息,也可以从Slave订阅消息,消费者在向Master拉取消息时,Master服务器会根据拉取偏移量与最大偏移量的距离(判断是否读老消息,产生读I/O),
以及从服务器是否可读等因素建议下一次是从Master还是Slave拉取。
结合部署架构图,描述集群工作流程:
启动NameServer,NameServer起来后监听端口,等待Broker、Producer、Consumer连上来,相当于一个路由控制中心。
Broker启动,跟所有的NameServer保持长连接,定时发送心跳包。心跳包中包含当前Broker信息(IP+端口等)以及存储所有Topic信息。注册成功后,NameServer集群中就有Topic跟Broker的映射关系。
收发消息前,先创建Topic,创建Topic时需要指定该Topic要存储在哪些Broker上,也可以在发送消息时自动创建Topic。
Producer发送消息,启动时先跟NameServer集群中的其中一台建立长连接,并从NameServer中获取当前发送的Topic存在哪些Broker上,轮询从队列列表中选择一个队列,然后与队列所在的Broker建立长连接从而向Broker发消息。
Consumer跟Producer类似,跟其中一台NameServer建立长连接,获取当前订阅Topic存在哪些Broker上,然后直接跟Broker建立连接通道,开始消费消息。
组件作用
这里只是对组件的一些实战当中的一些要用的提示及建议
Broker Role(代理角色)
代理角色是ASYNC_MASTER、SYNC_MASTER或SLAVE。
如果不能容忍消息丢失,建议部署SYNC_MASTER并将一个从服务器附加到它.
如果对缺少感到满意,但希望代理始终可用,则可以使用从属性部署ASYNC_MASTER。
如果只是想简化它,您可能只需要一个ASYNC_MASTER而不需要从。
冲洗盘型(FlushDiskType)
建议使用ASYNC_FLUSH,因为SYNC_FLUSH开销很大,会导致太多性能损失。
如果想要可靠性,建议使用SYNC_MASTER和SLAVE。
Producer(生产者)
发送消息时,会获得包含SendStatus的SendResult。
首先,我们假设消息 isWaitStoreMsgOK=true(缺省值为true)。如果没有抛出异常,我们将始终得到SEND_OK。
下面是每个状态的描述列表:
FLUSH_DISK_TIMEOUT
如果代理程序设置了消息存储配置 FlushDiskType=SYNC_FLUSH(默认情况下为ASYNC_FLUSH),并且代理程序没有在消息存储配置同步刷新超时(默认为5秒)内完成磁盘刷新,将获得此状态。
FLUSH_SLAVE_TIMEOUT
如果代理的角色是SYNC_MASTER(默认情况下是ASYNC_MASTER),但是没有配置从代理,那么将得到这个状态。
SEND_OK
SEND_OK并不意味着它是可靠的。为了确保不会丢失任何消息,还应该启用SYNC_MASTER或SYNC_FLUSH。
Duplication or Missing(重复或丢失)
如果获得FLUSH_DISK_TIMEOUT、FLUSH_SLAVE_TIMEOUT,并且代理恰好在此时关闭,那么你可能会发现你的消息丢失了。
此时,有两个选择,一个是放手,这可能会导致这条信息丢失;另一种方法是重新发送消息,这可能会导致消息重复。
经常建议重新发送,并在使用时找到处理重复删除的方法。除非你觉得有些信息丢失并不重要。
但是请记住,当获得SLAVE_NOT_AVAILABLE时,重新发送是无用的。如果发生这种情况,您应该保持场景并提醒集群管理器。
Timeout(超时)
客户端向代理发送请求,并等待响应,但是如果最大等待时间已经过去,并且没有返回响应,客户机将抛出RemotingTimeoutException。
默认等待时间为3秒。还可以使用send(msg, timeout)而不是send(msg)传递超时参数。
注意,不建议等待时间过短,因为代理需要一些时间来刷新磁盘或与从服务器同步。
此外,如果该值远远超过syncFlushTimeout,则可能没有什么影响,因为代理可能在超时之前返回一个带有FLUSH_SLAVE_TIMEOUT或FLUSH_SLAVE_TIMEOUT的响应。
Message Size(消息大小)
官方建议消息大小不超过512K。(这个可能不太现实)
Async Sending(异步发送)
默认发送(msg)将阻塞,直到返回响应。因此,如果关心性能,建议使用send(msg, callback),它将以异步的方式工作。
Producer Group(生产者群组)
正常情况下,生产者群组没有影响。但是如果参与了一项交易,应该注意它。默认情况下,您只能在相同的JVM中创建一个具有相同生产者组的生产者,这通常就足够了。
Thread Safety(线程安全性)
生产者是线程安全的,可以在业务解决方案中使用它。
Performance(性能)
如果在一个JVM中有多个生产者来处理大数据,建议这样做。
使用异步发送一些生产者(3~5是足够的)
为每个生产者设置实例名称。
Consumer(消费者)
Consumer Group and Subscriptions(消费者群组和订阅)
应该注意的第一件事是,不同的消费群体可以独立地消费相同的主题,并且每个消费群体都有自己的消费补偿。
请确保同一组中的每个消费者订阅相同的主题。
MessageListener(消息监听)
Orderly(有序的)
使用者将锁定每个MessageQueue,以确保按顺序逐个使用它。这将导致性能损失,但当您关心消息的顺序时,它是有用的。
不建议抛出异常,您可以返回ConsumeOrderlyStatus.SUSPEND_CURRENT_QUEUE_A_MOMENT代替
Concurrently(并发性的)
顾名思义,使用者将同时使用消息。建议使用它来获得更好的性能。
不建议抛出异常,可以返回ConsumereConcurrentlyStatus.RECONSUME_LATER代替。
Comsume Status(消费现状)
对于消息监听并发,可以返回RECONSUME_LATER ,告诉消费者您现在不能使用它,希望稍后再使用它。
然后可以继续使用其他消息。对于消息监听顺序,因为您关心顺序,所以不能跳过消息,但是可以返回SUSPEND_CURRENT_QUEUE_A_MOMENT来告诉消费者等待一会儿。
Blocking(阻塞)
不建议阻塞监听,因为它将阻塞线程池,并最终可能停止使用进程。
Thread Number(线程数)
消费者使用ThreadPoolExecutor在内部处理消费,因此可以通过设置setConsumeThreadMin或setConsumeThreadMax来更改它。
Consume From Where(从哪里消费)
当一个新的消费者群组建立时,它将需要决定是否需要使用代理中已经存在的历史消息。
CONSUME_FROM_LAST_OFFSET将忽略历史消息,并使用在此之后生成的任何内容。
CONSUME_FROM_FIRST_OFFSET将使用代理中存在的所有消息。
还可以使用CONSUME_FROM_TIMESTAMP来使用指定时间戳之后生成的消息。
Duplication(重复)
许多情况可能导致重复,例如:
生产者重新发送消息,在FLUSH_SLAVE_TIMEOUT情况下
消费者宕机,某些补偿未及时更新到代理。
如果你的应用程序不能容忍重复,可能需要做一些外部工作来处理这个问题。例如,检查数据库的主键。
Name Server(名称服务器)
在 RocketMQ 中,名称服务器用于协调分布式系统的各个组件,协调主要通过管理主题路由信息来实现。
管理包括两部分
1.代理定期更新保存在每个名称服务器中的元数据。
2.名称服务器为客户端提供服务,包括提供最新路由信息的生产者、消费者和命令行客户端。
因此,在启动代理和客户机之前,我们需要告诉它们如何通过提供名称服务器地址列表来访问名称服务器。
在RocketMQ中,这可以通过四种方式实现。
Programmatic Way(编程方式)
对于代理,我们可以在代理配置文件中指定 namesrvAddr=name-server-ip1:port;name-server-ip2:port
对于生产者和消费者,我们可以向他们提供名称服务器地址列表,如下所示
如果使用shell中的管理命令行,还可以这样指定
sh mqadmin command-name -n name-server-ip1:port;name-server-ip2:port -X OTHER-OPTION
一个简单的例子:假设要查询name server节点上的集群信息,sh mqadmin -n localhost:9876 clusterList
如果已经将管理工具集成到您自己的仪表板中,则可以
Java Options(Java选项)
在启动之前,还可以通过指定sequel java选项rocketmq. namespace .addr将名称服务器地址列表提供给应用程序。
Environment Variable(环境变量)
可以导出NAMESRV_ADDR环境变量。如果设置了它的值,代理和客户端将检查并使用它。
HTTP Endpoint(HTTP端点)
如果没有使用前面提到的方法指定名称服务器地址列表,RocketMQ将访问以下HTTP端点,以每两分钟获取和更新名称服务器地址列表,初始延迟为10秒。默认情况下,终点是:http://jmenv.tbsite.net:8080/rocketmq/nsaddr
您可以使用这个Java选项:rocketmq. namespace .domain覆盖jmenv.tbsite.net
也可以使用这个Java选项:rocketmq. namespace .domain.subgroup覆盖nsaddr part
如果在生产环境中运行 RocketMQ,建议使用此方法,因为它为您提供了最大的灵活性,您可以动态添加或删除名称服务器节点,而无需根据名称服务器系统负载重新启动代理和客户端机器。
Priority(优先顺序)
Programmatic Way > Java Options > Environment Variable > HTTP Endpoint
RocketMQ JVM/Linux Configuration(RocketMQ的JVM/Linux配置项)
RocketMQ的JVM/OS参数配置的介绍。它指出了在部署RocketMQ集群之前应该考虑的特定配置。
JVM Options(JVM选项)
建议使用JDK 1.8以上的版本,使用服务器编译器和8g堆。设置相同的Xms和Xmx值,以防止JVM为了获得更好的性能而调整堆的大小。
一个简单的JVM配置:-server -Xms8g -Xmx8g -Xmn4g
如果不关心RocketMQ Broker的启动时间,那么预先接触Java堆以确保在JVM初始化期间分配每个页面是更好的选择。
不关心启动时间可以启用它:-XX:+AlwaysPreTouch
禁用偏向锁定可能会减少JVM暂停:-XX:-UseBiasedLocking
对于垃圾收集,建议使用JDK 1.8的G1收集器:
-XX:+UseG1GC -XX:G1HeapRegionSize=16m -XX:G1ReservePercent=25 -XX:InitiatingHeapOccupancyPercent=30
这些GC选项看起来有点激进,但事实证明它在我们的生产环境中具有良好的性能。
不要为-XX:MaxGCPauseMillis设置太小的值,否则JVM将使用很小的年轻代来实现这个目标,这将导致非常频繁的GC。
建议使用滚动GC日志文件:-XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=30m
如果写GC文件会增加代理的延迟,请考虑将GC日志文件重定向到内存文件系统:-Xloggc:/dev/shm/mq_gc_%p.log
Linux Kernel Parameters(Linux内核参数)
有一个os.sh脚本,它列出了文件夹bin中的许多内核参数,这些参数可以用于生产中,只需要进行少量更改。下面的参数需要注意,更多细节请参考/proc/sys/vm/*[1]的文档。
vm.extra_free_kbytes:告诉VM在后台回收(kswapd)和直接回收(通过分配进程)的阈值之间保持额外的空闲内存。RocketMQ使用此参数来避免内存分配中的高延迟。
vm.min_free_kbytes:如果将此值设置为小于1024KB,则系统将微妙地崩溃,并且在高负载下容易死锁。
vm.max_map_count:限制进程可能拥有的内存映射区域的最大数量。RocketMQ将使用mmap加载CommitLog和ConsumeQueue,因此建议为该参数设置更大的值。
vm.swappiness:定义内核交换内存页的积极程度。较高的值将增加侵略性,较低的值将减少交换量。建议为这个值设置10,以避免交换延迟。
File descriptor limits:RocketMQ需要为文件(CommitLog和ConsumeQueue)和网络连接打开文件描述符。我们建议为文件描述符设置655350。
Disk scheduler:建议对RocketMQ使用deadline I/O调度程序,它尝试为请求[2]提供一个有保证的延迟。