https://pcnb9s6ju12q.feishu.cn/wiki/AdphwUz0biz8wgkYDhFcSConnle

 

改造一、集群模式改造

需求:

针对B新机房上线,在A机房架构上针对原有的rabbitmq,进行健壮性改造、部署

确保mq的高可用和分布式

架构改造:

荷丹原有架构模式--->普通集群:

普通集群模式,也就是在多台机器上启动多个 RabbitMQ 实例,每个机器启动一个。我们创建的 queue,只会放在一个 RabbitMQ 实例上,但是每个实例都同步 queue 的元数据(元数据可以认为是 queue 的一些配置信息,通过元数据,可以找到 queue 所在实例)。你消费的时候,实际上如果连接到了另外一个实例,那么那个实例会从 queue 所在实例上拉取数据过来。

 

架构图:

 
 
 
 
 
 
 
 

易故障问题点引入:

针对普通集群导致我们要么消费者每次随机连接一个实例然后拉取数据,要么固定连接那个 queue 所在实例消费数据,前者有数据拉取的开销,后者导致单实例性能瓶颈。

另外:那个放 queue 的实例宕机了,会导致接下来其他实例就无法从那个实例拉取。

如果我们开启了消息持久化,让 RabbitMQ 落地存储消息的话,消息不一定会丢,得等这个实例恢复了,然后才可以继续从这个 queue 拉取数据。

所以这个事儿就比较尴尬了,这就没有什么所谓的高可用性,这方案主要是提高吞吐量的,就是说让集群中多个节点来服务某个 queue 的读写操作。

 
 
 

架构改造:

B机房现有架构模式--->:镜像集群模式

 
 
 
 
 
 
 

如何开启这个镜像集群模式呢?

方式1:

RabbitMQ 有很好的管理控制台,就是在后台新增一个策略,这个策略是镜像集群模式的策略,指定的时候是可以要求数据同步到所有节点的,也可以要求同步到指定数量的节点,再次创建 queue 的时候,应用这个策略,就会自动将数据同步到其他的节点上去了。

方式2:

变镜像集群很简单,在上面普通集群的基础上,在任意一个节点下执行

#把集群变成镜像集群 rabbitmqctl set_policy ha-all "^" '{"ha-mode":"all"}'

收益:

我们任何一个机器宕机了,没事儿,其它机器(节点)还包含了这个 queue 的完整数据,别的 consumer 都可以到其它节点上去消费数据。

 
 

!!!注意说明:

在对集群默认对vhost做全镜像后,即整个vhost下面的Q和Message都会进行镜像克隆

此时我们不支持对临时队列和排他队列的保障

 
 
 
 
 

改造二、计划rabbitmq消息改造

 

易故障问题点引入:

本来rabbitmq默认的最大消息体位512M,我们rabbitmq集群实际消息体控制在4MB以内。

但是酒店预定系统,突发性的网络和带宽增大,消息堆积200W,服务器cpu暴涨,尤其是内存

 并且部分客户端的tcp连接中断

针对公司酒店集团的业务场景,针对大消息处理的案例,其中消息正文超过4MB,需要将正文存储到Redis中:

案例背景华住酒店集团旗下的酒店系统需要处理大量的预订和入住数据,这些数据包括详细的客户信息、预订记录、房间分配、服务需求等。单条消息的正文非常大,例如在处理批量预订或复杂的预订需求时。

华住酒店集团的预订系统需要处理以下类型的业务消息:

1. 批量预订请求:一个旅行社为一个大型团队预订多个房间,每个房间的预订信息包含客户姓名、联系方式、入住时间、退房时间、特殊需求等详细信息。

2. 酒店入住记录同步:为了满足数据安全和隐私保护的要求,系统需要将入住记录同步到多个系统中,这些记录可能包含大量的客户个人信息和入住细节。

在这些场景中消息正文过大,单条消息的正文超过4MB,导致RabbitMQ的传输效率下降,甚至可能触发消息队列的大小限制。

解决方案为了优化性能和可靠性,我们的实现思路如下:

1. 消息拆分:

将消息正文拆分为头部(Header)和正文(Body)。头部包含消息的关键信息(如消息ID、消息类型、分片信息等),正文包含实际的业务数据。

将正文存储到Redis中,仅将头部信息通过RabbitMQ传递。

2. 存储策略:

使用Redis存储正文内容,为每个消息正文生成一个唯一的键(如 message_<message_id> )

在RabbitMQ的消息头部中,包含Redis键名和存储位置信息,消费者可以根据这些信息从Redis中读取完整的正文内容。

3. 消费者处理:

消费者从RabbitMQ中接收消息头部,根据头部信息从Redis中读取正文内容。处理完成后,消费者可以选择从Redis中删除对应的正文,以释放存储空间。

解决问题方案:

减轻RabbitMQ负载:

通过将大消息正文存储到Redis中,避免了RabbitMQ在处理大消息时的性能瓶颈

高性能读写:Redis的内存存储机制可以快速读取和写入大消息,适合高并发场景。

数据一致性:通过消息头部和正文的分离,确保了消息的完整性和一致性。

另外redis提供了丰富的数据结构和操作接口,便于对消息正文进行管理。

通过这种设计,华住酒店集团可以在处理大消息时保持系统的高性能和可靠性,同时利用Redis的高性能优势来优化消息传输。

 
 

改造三、rabbitmq启用proxy协议

易故障问题点引入::

现网服务80%都容器化,但由于历史坑的原因,原有12套rabbitmq集群没有设置代理,导致每次容器连接rabbitmq服务在抓包时,获取调用只能通过k8s的node的contrack去找到服务的调用,导致线上业务异常时,排查调用非常的耗时。

问题实施解决:

在 RabbitMQ 的配置中,可以通过启用 proxy-protocol 来获取客户端的真实 IP 地址。

具体配置如下:

1. 启用 proxy-protocol 在 RabbitMQ 的配置文件 rabbitmq.conf 中,需要启用 proxy-protocol ,并指定监听的端口

listeners.tcp.default = 5672

listeners.tcp.proxy = 15672 #代理监听的端口

proxy-protocol = on #表示启用代理协议

2. 配置代理服务器 RabbitMQ 通过代理服务器 HAProxy 转发请求,在代理服务器上配置 send-proxy 参数

在 HAProxy 的配置中:

backend rabbitmq_backend

mode tcp

server rabbitmq_server <RabbitMQ_server_ip>:5672 send-proxy

server rabbitmq_server <RabbitMQ_server_ip>:5672 send-prox 

server rabbitmq_server <RabbitMQ_server_ip>:5672 send-proxy

这样,HAProxy 会将客户端的真实 IP 通过 proxy-protocol 发送到 RabbitMQ

验证真实 IP 配置完成后,RabbitMQ 的管理界面或日志中会显示客户端的真实 IP 地址,而不是代理服务器的 IP。

通过以上配置,RabbitMQ 可以正确解析并显示客户端的真实 IP 地址。

 
 
 

改造四、rabbitmq内存水位优化

易故障问题点引入:

由于我们线上mq集群基本都使用虚拟机,鉴于之前虹漕房遇到mq水位问题,江桥新上B端、C端、base集群配置合适水位,并且修正虹漕机房12套rabbitmq的水位问题

目前设置默认的高水位内存为40%,阻塞刷盘为高水位的75%开始进行磁盘sync,水位低的原因主要是磁盘为HDD,较多数据量sync会导致rmq夯住,导致连接block,无法正常使用。

 

问题解决:

B机房变更:ansbile使用superman

A机房变更: ansible使用root账号

1、扫描

 ansible -i /etc/ansible/hosts nodemq   -m shell -a ' cat /etc/rabbitmq/rabbitmq.conf | grep -v "^#"  | grep -v "^$" | grep high'  

2、备份

 ansible -i /etc/ansible/hosts  hc-3   -m shell -a ' cp /etc/rabbitmq/rabbitmq.conf  /etc/rabbitmq/rabbitmq.conf.bak20230629'  

3、检查备份

 ansible -i /etc/ansible/hosts  hc-3   -m shell -a ' 11 /etc/rabbitmq/rabbitmq.conf.bak20230629'  

4、内存升级2倍,并设置水位

针对线上已经有业务集群,采用临时生 

ansible -i /etc/ansible/hosts  hc-3   -m shell -a  'rabbitmqctl set_vm_memory_high_watermark 0.4 '  

rabbitmqctl set_vm_memory_high_watermark 0.4

rabbitmqctl vm_memory_high_watermark_paging_ratio  0.75

配置文件也进行修改,但是不重启rabbitmq服务

 ansible -i /etc/ansible/hosts hc-3   -m shell -a ' sed  -i   s'#vm_memory_high_watermark.relative = 0.8#vm_memory_high_watermark.relative = 0.4#'g    /etc/rabbitmq/rabbitmq.conf"'  

5、进行检查

 rabbitmqctl status | grep "{vm_memory_high_watermark,0.8}"

6、验证修改配置

ll /etc/rabbitmq/rabbitmq.conf.20230629bak

 ansible -i /etc/ansible/hosts  hc-3   -m shell -a ' 11 /etc/rabbitmq/rabbitmq.conf.bak20230629'  

 ansible -i /etc/ansible/hosts hc-3   -m shell -a ' cat /etc/rabbitmq/rabbitmq.conf | grep -v "^#"  | grep -v "^$" | grep high | grep "vm_memory_high_watermark.relative"'

ansible -i /etc/ansible/hosts hc-3   -m shell -a "sed  -i   s'/vm_memory_high_watermark.relative = 0.8/vm_memory_high_watermark.relative = 0.4/'g    /etc/rabbitmq/rabbitmq.conf.20230629bak"

 
 

可能存在的隐患?暂时没有遇到

极端情况下,业务综合带宽突发饱和,多节点网络建复制会受到带宽影响消息需要同步到所有机器上,导致网络带宽压力和消耗很重,如果遇到这种情况,到时我们使用移动专有线路

 
 
 

改造五、rabbitmq规范定制、修订

 

规范定制:

RabbitMQ使用规范

必读:

测试环 

1. 测试环境rabbitmq限制为每个Q10000条消息,超过该消息则会丢弃

2. rabbitmq不对外暴露地址,如需对外提供数据,请通过接口提供

 

生产环境

1. 申请rabbitmq集群资源时请先评估业务高峰堆积量,并备注是否是核心链路,L0 链路单独提供集群使用

2. 服务不依赖 MQ 做健康检查

3. Q的创建由使用方自行创建,运维仅提供vhost和相关vhost权限,消息的大小限制请参阅3.3.2,持久化请参考3.5

4. 2023年10月后申请的集群(非迁移需要),提供直连集群

5. rabbitmq不对外网暴露地址,如需对外提供数据,请通过接口提供

6. 消费的消息(持久化的数据)和未消费的消息保留周期:7天

7. 默认对vhost做全镜像,即整个vhost下面的Q和Message都会进行镜像克隆(不支持对临时队列和排他队列的保障)

8. 单个连接请求的channel上限默认为2047个,请合理使用。

 

一、 制定目的

为规范公司RabbitMQ的使用,建立设计、使用、管理均统一标准,使得应用在使用前、中、后都有对应的规范参考和实践指导,确保公司业务系统稳定,特制定本办法。

二、 关于监控

监控请发送邮件进行申请,并登记到指定文档中,配置监控时请填写明确内容,如:集群名、vhost、queue、堆积数量、持续时长、发送通道等

三、 具体内容

1. 特定使用规范

连接rabbitmq的应用,当rabbitmq重启后,生产者和消费者要有自动重连机制。

所有exchange和queue要做持久化配置,服务重启后会自动创建。

除非需要,队列优先级级别建议默认为10(权重越大优先执行)。

延迟队列时间根据业务需求设定,开启死信队列,并有消费死信队列的处理机制。

2. Vhost命名规范

(1)一个产品一个vhost[产品名唯一且在系统能能查到]

(2)与bus交互mq集群放置规则:

1、B业务在科技中国C端,跟B业务集群有交互的都放到C

比如:A业务 是生产方调用B业务,同时也有自产自销的,那么A业务自产自销的vhost为A业务,消费者是棱镜vhost在B业务集群,vhostA业务-B业务

2、自产自销,根据业务来区分是是科技国际还是科技中国

3. Exchange命名和使用规范

产品名-应用名(即APPID)-类型

类型包括:direct、fault、topic和delayed等

没有特殊需要的情况,建议通过Exchange的direct模式发送消息,Routing key和Binding key使用生产者APPID,方便查看和追踪。

交换机的类型:direct、topic、fanout、headers,durability(是否需要持久化true需要)auto delete当最后一个绑定Exchange上的队列被删除Exchange也删除。其中fanout是性能最好的,其次direct,再创建交换机的时候,最好业务结合这些考虑来使用哪种比较合 

Queue命名和使用规范

生产者(APPID)-功能作用-消费者(APPID) 例如:pms20-viki-sendmail-pms20-hoggc

建议如果业务A是生产者,要生产消息由消费者B消费,建立一个direct模式队列

建议如果业务A是生产者,要生产消息由消费者B和C进行消费,需要建立两个direct模式队列。

建议如果业务A和B是生产者,要生产消息由消费者C进行消费,同样建立两个direct队列。

 

5. Message使用规范

 
 
 
      
 

字段

6.

 

类型

 

 

是否为空

 

 

是否可选

 

 

默认值

 

 

描述

 

 

生产者APPID

 

String

 

 

 

APPID

 

用于全链路跟踪

 

datetime

 

Long

 

 

 

当前时间戳

 

消息产生的时间戳

 

消费者APPID

 

String

 

 

 

APPID

 

用于全链路跟踪

 

Function

 

String

 

 

 

 

消息功能作用

 

Body

 

object

 

 

 

[]

 

业务消息内容

 
 
 

1、线上rabbitmq使用说明

Rabbitmq 是对AMQP协议的一种实现。使用范围也比较广泛,主要用于消息异步通讯,默认情况下Rabbitmq使用轮询(round-robin)方式转发消息。目前我们线上的版本是3.7.12

一、现网架构选型

统一使用双节点镜像模式,集群的负载请求由客户端自行完成。

 
 
 
 

高可用方式

 

实现方式

 

备注

 

数据高可用

 

镜像克隆

 

集群节点之间能够同步数据保证高可靠的存储,临时队列无法同步

 
 
 

2、rabbitmq最佳实践

2.1 队列相关

短队列原则

让队列长度尽可能地短,不要有堆积。大量堆积的消息会给RAM内存带来压力,为了释放RAM来容纳更多消息,RabbitMQ会将消息刷入磁盘 

这个过程会非常耗时并且阻塞队列处理消息,恶化出入队速度,大量的消息堆积对Broker的性能有着绝对的负面作用。

此外还会影响Broker的启动速度,RabbitMQ要花时间重建索引。

同时还会影响集群间同步消息,例如配置了队列Mirror。

开启惰性队列(Lazy Queues)换取可接受的性能
 
 
 
 
 

Plain Text

大消息量的队列,例如日志。一定要开启惰性队列(

Lazy Queues),不然一旦消费者出现问题或消费能力跟不上,会拖垮Broker。

 
 

RabbitMQ3.6新增了惰性队列,惰性队列会将所有消息刷入磁盘,最小化RAM内存的使用。但同时也会增加整体消息的通过时间。

当你有瞬间生产大量消息的场景,或者有消费能力总是跟不上生产能力的场景时,非常建议开启惰性队列。

不建议开启惰性队列的场景:1.高通过性。2.队列长度总是很短。3.设置了队列长度参数。

设置队列TTL或Max-Length,限制队列长度

当处于吞吐量无比重要的场景时,限制队列长度是个好主意。

队列的数量

RabbitMQ队列是单线程的,意味着单个队列的性能受限于单核,单个队列的吞吐量大概在5w左右。

可以选择将队列拆分成多个来利用多核提升性能,也可以将队列拆分至集群的不同节点。

不要为临时队列去显示命名

对于在消费者和发布者共享队列时,显示命名很重要。但临时队列应该让系统选择一个随机的队列名。

删除不再使用的队列

太多的多余队列对Mq的性能有一定影响,有三种方式自动删除多余队列。

1.给队列配置TTL策略。

2.设置auto-delete属性。

3.使用排他队列。

限制优先队列Level

每个priority level都会占用一个内部队列,这会消耗一定的资源。大部分情况下5个优先级已经够用了。

2.2 消息大小

消息大小限制:

在版本3.7中的源码,我们可以看到最大消息大小为2GiB,但是实际使用建议消息大小不要超过2MB

2.3 连接和通道

每个连接最高channel数为2047个,请勿开启大量channel,尽量复用和回收!

2.4 ACK和Confirm

 
 
 
 
 

Plain Text

· 禁止设置自动ACK(auto-ack)

·消费者完全处理完消息后再ACK,禁止先ACK再处理消息。

·消息确认必须开启。不要使用Tx模式,使用Confirm模式。编码优先使用异步confirm,

因为同步confirm效率低下。

 

消息在传输的过程中可能会丢失并且需要重新发送。通过ACK可以让程序知道何时需要重新发送。ACK对系统还是有负面性能影响的,可以关闭ACK换取更高的吞吐量。

一个消费者应用在接受重要消息时不会立刻发送ACK,直到它对消息处理完成为止。

Confirm等价于生产者的ACK,Broker等消息入队后才会发送ACK给客户端应用。Confirm同样对系统有负面性能影响。对于"至少一次"的重要消息,Confirm是必须的。

未ACK的消息会驻留在Broker内存中,大量的unack消息会导致OOM。一个简单的做法是设置prefech值,即Qos。

 
 

2.5 消息和队列的持久化

请自行配置队列的持久化和消息持久化,并设置相应的TTL,最长不超过7天

注意:!!!

由于我们的hboot框架使用的默认开启持久化,所以关于持久化问题不建议立即转发消息服务的场景也使用持久化,建议改成非持久化

 

2.6 Prefetch

 
 
 
 
 

Plain Text

 

不要设置为"1"或"不限制"。最好的方式是根据网络通信时间和消息处理时间做计算。计算公式为:

Qos=RTT / 消息处理时间

 
 

一般来讲不要设置Prefetch=1或Prefetch=Unlimited。Prefetch参数的目标是"让所有的消费者忙起来,同时最小化消费客户端的消息缓冲数"。思考一下如下场景:

该场景中的RTT需要125ms。若Prefetch=1,那么每花5ms处理完一个消息就需要等上120ms。消费者根本无法饱和运作。

如果Prefetch设置得非常大,会造成另一种极端场景:

某些消费者一次性从队列取出太多的消息,导致其他消费者空闲了,这也是消费者不饱和的一种情况。

因此需要根据"网络传输时间"、"消息处理时间"以及"消费者数量"综合计算一个合理的Prefetch值。

pretch参照官方文档建议配置如下:

https://blog.rabbitmq.com/posts/2014/04/finding-bottlenecks-with-rabbitmq-3-3

 
 
 

2.7 高吞吐实践

1.短队列原则

2.设置队列长度参数Max-Length

3.关闭惰性队列(Lazy Queues)

4.使用transient消息,而非persistent

5.拆分队列到多核多节点

6.禁用手动ack和confirm

7.避免集群启用HA

8.关闭队列镜像

9.开启HiPE

10.禁用没在使用的插件

 
 

2.8 高可用实践

· 短队列原则

· 开启惰性队列(可选)

· 集群开启HA配置

· 使用durable队列和persistent模式消息

· 开启集群间的federation

· 不要开启HiPE

· 限制优先队列level数

 
 

附录:

申请MQ需要提供的模板: 

ps:如果是新老切换,这种替换模式,直接提供原来的集群地址和vhost即可

如果是程序自动创建队列遵循如下规范

正常队列命名 : 系统类型+场景

死信队列命名 : 系统类型+Dead

 
 申请模板:
 
 

产品名

 

应用appid

 

是否使用延迟、死信队列、Exchange类型

 

Message queue预计流量峰值

 

生产者

 

消费者

 

业务吞吐

 

是否核心链路业务

               
               
               
               
 
 
 
 
 
 
 
 
 
 
 

posted on 2025-02-24 15:17  keepimprove  阅读(55)  评论(0)    收藏  举报

导航