源起自一个生产消息时候的报错:
2024/07/31 19:40:58 CODE: INTERNAL_SERVER_ERROR, MESSAGE: org.apache.rocketmq.proxy.common.ProxyException: service not available now. It may be caused by one of the following reasons: the broker's disk is full [CL: 0.02 CQ: 0.02 INDEX: 0.02], messages are put to the slave, message store has been shut down, etc.
我们使用的服务端是5.2,客户端sdk是
github.com/apache/rocketmq-clients/golang v0.0.0-20230321063829-41bfbcf6130d。
https://github.com/apache/rocketmq-clients.git
这个错误指明磁盘满,检查我们的服务端配置
图片
指的是88%,开始一度怀疑应该使用小数,后来查了下代码,发现这么设置没问题就应该是整数。
图片
进去getDiskPartitionSpaceUsedPercent 看到该函数返回的就是百分比,所以,日志打出的0.02就是百分之0.02,使用率极低。然后网上搜到这么一篇帖子,
图片
他说是写到slave上了,仔细一看日志还真是,那条消息写的是slave节点的ip。于是怀疑是使用新版本的proxy模式不可靠,去掉proxy模式,启动mq之后,使用sdk连接nameserver,发现根本找不到topic,还是得启用proxy模式。然后查看sdk源码,发现他是sdk先根据topic拿到所有队列,然后客户端做负载均衡,但是rocketmq-clients从proxy拿到的是所有节点的队列,包括slave节点,sdk又没有能力区分主从,所以导致写到了从节点上。 换成rocketmq-client-go客户端之后,收发消息测试都没问题了。

一,rocketmq-clients

在send0这个函数里,跟proxy进行交互,拿到队列信息,然后选择一个队列发消息。

func (p *defaultProducer) send0(ctx context.Context, msgs []*UnifiedMessage, txEnabled bool) ([]*SendReceipt, error) {
...
pubLoadBalancer, err := p.getPublishingTopicRouteResult(ctx, topicName)
...
candidates, err = p.takeMessageQueues(pubLoadBalancer)}

在 getPublishingTopicRouteResult函数里边,通过向proxy调用rpc,请求接口 "/apache.rocketmq.v2.MessagingService/QueryRoute",来拿到该topic的所有队列列表。返回结果如下:
图片
他会返回集群中所有的该topic下的队列,其中,主节点和从节点返回的内容除了host的ip不一样,其他都是一样的,包括permission属性都是一样的,都是在创建topic的时候指定的。尝试使用命令行mqadmin工具,来修改从节点为只读,发现行不通,必须是主从一致才能修改。然后就是TakeMessageQueues函数,
图片
该函数使用发送的消息序号,通过轮询的算法做负载均衡。当轮询到slave节点上的队列的时候,发送就会报
2024/07/31 19:40:58 CODE: INTERNAL_SERVER_ERROR, MESSAGE: org.apache.rocketmq.proxy.common.ProxyException: service not available now. It may be caused by one of the following reasons: the broker's disk is full [CL: 0.02 CQ: 0.02 INDEX: 0.02], messages are put to the slave, message store has been shut down, etc.
这种错误了。

二,rocketmq-client-go

这里用的是 github.com/apache/rocketmq-client-go/v2 v2.1.2 版本。https://github.com/apache/rocketmq-client-go.git
调用的是SendSync方法在SendSync方法里,会先调用selectMessageQueue,根据topic去nameserver取队列的配置信息和该集群broker列表。然后调用FindBrokerAddrByName拿到一个broker的地址,然后是InvokeSync实际的向一个broker发送消息。
图片

图片

图片
1,selectMessageQueue
图片
该函数先调用tryToFindTopicPublishInfo,从nameserver根据topic名字获取他的队列信息和所在broker列表,nameserver返回结果是这样的,其中queueDatas就是我们在创建topic的时候,给指定的队列数和队列权限,perm:2表示只写,4表示只读,6表示读写。
图片
然后再拼装一个队列MqList,排除掉没有写权限的队列。
图片
然后交给 p.options.Selector.Select(msg, result.MqList, lastBrokerName) 去选择一个队列,目前有4种选择算法,
图片
如果在创建procuder对象的时候不指定,就是默认的使用roundRobin算法。
2,FindBrokerAddrByName
在拿到一个目标队列之后,FindBrokerAddrByName,会根据队列的broker名字找到给broker的ip列表,然后从所有的broker里找一个master身份的broker,
图片

图片
master身份是根据broker的id来识别的,硬编码为0,这个跟集群的配置文件要对应起来,
图片
master节点的brokerId一定要指定为0, slave节点的要大于0。
3,InvokeSync
最后一步就是调用InvokeSync向指定的broker写消息了,这里需要注意的是,消息id是客户端生成的,他是根据当前时间戳,拼上计数器,然后转成了16进制数,所以如果是多客户端大并发量的场景下,还是有msgId冲突的可能。
图片
最后说明的就是发送请求的timeout硬编码写死了3秒,生成producer对象的时候可以指定重试次数,默认是3次。

附:以下转述自某位mq commiter的话,
rmq 的 nameserver 是独立存在的,ns 之间几乎不会做数据同步,broker 会定期给多个 namesaver 做信息同步。
rmq 的 master 挂了后,其 slave 不会提升成 msater,只会当 slave 接收异常下的读操作。


英雄之旅
8 声望1 粉丝