生产与消费
生产方式
producer有三种生产消息的方式
- 同步(sync):消息投递出去之后,broker同步返回成功或失败
- 异步(async):消息投递出去之后,不用等结果,自己写一个callback来接收投递结果
- 单向(oneway):就是啥也不管,无脑投递
这里实际是要结合broker的刷盘方式,来看用哪种生产方式(多方配合)
消费方式
消费方式一般分为推和拉两种
- PUSH:消息队列主动将消息推给消费者(优点是消息实时性高,缺点是忽略了客户端的消费能力)
- PULL:消费者主动向消息队列拉取消息(缺点是消息实时性低,可能造成大量无效请求)
RocketMQ的消费方式介于推和拉之间,它使用了一种长轮询机制,来平衡推拉各自的缺点
- Consumer发送拉取消息请求
- Broker hold 住请求,直到有新消息再返回
- 请求超时(超时时间默认30s),Consumer再次发起请求
这样保证了实时性(一直有一个连接在),也没有过多的无效请求(30s才超时,没有数据时,一分钟才发俩请求)
而且客户端收到请求的回复之后,如果处理不过来,可以等数据处理完,再发起下一次拉取请求,不用立即发起
所以它是按照客户端的处理能力去尽量实时的拉取消息,兼顾了性能和实时时性
集群消费
- 集群内部:单条消息只会被消费一次,且各节点会均匀消费topic消息
- 多个集群:则各集群消费全量的消息,且单条消息在每个集群也只会被消费一次
也就是说,集群消费的模式,它要求一个队列只能被一个消费者消费(但一个消费者可以消费多个队列)
因为一个队列如果被多个消费者消费,那消费的offset该怎么移动,就是件麻烦事儿
比如有User集群和Order集群,都去消费同一个topic消息
那么单条消息既会被User集群消费到,也会被Order集群消费到
且在每个集群内都只会被消费一次(具体被集群里的哪个节点消费,则由负载均衡决定)
并且这俩集群都会消费到该topic的全量消息(各自集群内部根据节点数量均匀的消费)
可以把User集群和Order集群理解成两个group,即:组内竞争消费,组间广播消费
负载均衡
RocketMQ 中的负载均衡都是在 Client 端完成的
Producer端负载均衡
生产者端会定时获取到主题的队列信息,这样知道了topic在哪几个broker上都有哪些queue
于是,投递消息时,就可以通过本地算法,指定这个消息分发到具体broker上的具体queue上去
默认的负载均衡算法是采用随机递增取模(生成一个随机数,然后按队列数取模)的方式
并且producer端的容错机制是以故障延迟的方式实现的
即:当若某队列出问题导致发消息失败,那么再次取模到该队列时,会跳过并为其设置一个 N 毫秒的失效时间
等到下次再碰到该队列时,如果失效时间还没到,就继续跳过,要是时间到了就看还能不能往上面发消息
能发消息就用它,不能发消息就再为其置一个失效时间(比上一次的失效时间还要长一些)
Consumer端负载均衡
由于客户端本身并不知道彼此的存在,所以客户端独自很难实现均衡消费
而RocketMQ是通过Rebalance机制来实现的Consumer端负载均衡
它可以从broker收集到客户端数据(客户端会上报心跳),再加上定时触发的Rebalance(大概20s一次)
consumer就会得到同一个topic的所有队列信息和所有订阅了的消费者信息
再根据consumer的数量和队列数量,来平均分配(有点类似分页,你分几个,我分几个,大家平均着来)
最后,分配完,再和本地结果(也就是上一次结果)做一次对比
如果,上一次结果中分配的某个队列不在本次分配的队列列表里,那就剔除该队列
最终,就是要保证一个队列只会被一个消费者使用,不会出现一个队列被两个人消费的情况
不过,当consumer节点数超过topic队列的数量时,则必然会有至少一个节点分配不到队列而处于空闲
注意:每个队列的长度均匀,消息量均匀,这是由生产者做的