RocketMQ消息拉取-负载均衡算法
负载均衡
默认-平衡hash队列算法
Average Hashing queue algorithm
org.apache.rocketmq.client.consumer.rebalance.AllocateMessageQueueAveragely
/**
* Allocating by consumer id
*
* @param consumerGroup current consumer group
* @param currentCID current consumer id
* @param mqAll message queue set in current topic
* @param cidAll consumer set in current consumer group
* @return The allocate result of given strategy
*/
public List<MessageQueue> allocate(String consumerGroup, String currentCID, List<MessageQueue> mqAll,
List<String> cidAll) {
List<MessageQueue> result = new ArrayList<MessageQueue>();
if (!check(consumerGroup, currentCID, mqAll, cidAll)) {
return result;
}
// 当前消费者在消费者组数组坐标, 如5个队列 % 3个消费者,当前第3个(2),当前第2个(1),当前第一个(0)
int index = cidAll.indexOf(currentCID);
// 当前MQ主题的消息队列多少 % 消费者的多少,如5个队列 % 3个消费者 = 2
int mod = mqAll.size() % cidAll.size();
// 平均一个消费者要消费队列的大小
// 队列数比消费者数少,则平均大小就是1,有些消费者会分配不到
// 队列数比消费者数多
// 当前消费者整除,是除数,如5个队列 % 3个消费者,当前第3个(3/3 = 1);
// 当前消费者不整除,是除数+1,如5个队列 % 3个消费者,当前第2个(5/3 + 1 = 2), 第1个(5/3 + 1 = 2)
int averageSize =
mqAll.size() <= cidAll.size() ? 1 : (mod > 0 && index < mod ? mqAll.size() / cidAll.size()
+ 1 : mqAll.size() / cidAll.size());
// 当前消费者的mq坐标, 当前消费者不整除
// 下标小于余数,当前消费坐标 * 平均大小,如5个队列,3个消费者,当前第2个(1 * 2 = 2),当前第1个(0 * 2 = 0)
// 下标大于等于余数,当前消费者整除,是当前消费坐标 * 平均大小,如5个队列,3个消费者,当前第3个(2 * 1 + 2 = 4),
int startIndex = (mod > 0 && index < mod) ? index * averageSize : index * averageSize + mod;
// 环大小 = min(平均大小,队列大小-开始坐标),如5个队列,3个消费者,当前第3个(min(1,5-4) = 1), 当前第2个(min(2,5-2)=2,当前第1个(min(2,5-0) = 2)
int range = Math.min(averageSize, mqAll.size() - startIndex);
for (int i = 0; i < range; i++) {
// 当前第3个(4),当前第2个(2,3),当前第1个(0,1)
result.add(mqAll.get((startIndex + i) % mqAll.size()));
}
return result;
}
核心思想
1.通过取模算出每个消费者负责的队列数量;
2.通过当前在消费者组的坐标,和余数大小比较,比余数小的坐标,按照平均大小倍数连续平分,剩余部分就是大于余数部分的坐标
循环平衡hash队列算法
org.apache.rocketmq.client.consumer.rebalance.AllocateMessageQueueAveragelyByCircle
public List<MessageQueue> allocate(String consumerGroup, String currentCID, List<MessageQueue> mqAll, List<String> cidAll) {
List<MessageQueue> result = new ArrayList<MessageQueue>();
if (!check(consumerGroup, currentCID, mqAll, cidAll)) {
return result;
}
// 消费者在消息队列,当前坐标, 3个消费者0,1,2
int index = cidAll.indexOf(currentCID);
for (int i = index; i < mqAll.size(); i++) {
// Mq队列 / 消费者组 的余数 == 当前消费者 / 消费者组 的余数,就是匹配关系
if (i % cidAll.size() == index) {
result.add(mqAll.get(i));
}
}
return result;
}
核心思想
- 计算消费者在消费者组的坐标
- 遍历队列,队列 / 消费者组 的余数 == 消费者 / 消费者组 的余数,就是匹配关系
一致性hash分配算法
org.apache.rocketmq.client.consumer.rebalance.AllocateMessageQueueConsistentHash
public List<MessageQueue> allocate(String consumerGroup, String currentCID, List<MessageQueue> mqAll,
List<String> cidAll) {
List<MessageQueue> result = new ArrayList<MessageQueue>();
if (!check(consumerGroup, currentCID, mqAll, cidAll)) {
return result;
}
// 将所有消费者维护成物理节点列表
Collection<ClientNode> cidNodes = new ArrayList<ClientNode>();
for (String cid : cidAll) {
cidNodes.add(new ClientNode(cid));
}
// 将所有物理节点同虚拟节点个数维护成一致性hash环
final ConsistentHashRouter<ClientNode> router; //for building hash ring
if (customHashFunction != null) {
router = new ConsistentHashRouter<ClientNode>(cidNodes, virtualNodeCnt, customHashFunction);
} else {
router = new ConsistentHashRouter<ClientNode>(cidNodes, virtualNodeCnt);
}
// 遍历队列, 获取相应的虚拟节点的物理节点
List<MessageQueue> results = new ArrayList<MessageQueue>();
for (MessageQueue mq : mqAll) {
ClientNode clientNode = router.routeNode(mq.toString());
// 物理节点匹配就添加
if (clientNode != null && currentCID.equals(clientNode.getKey())) {
results.add(mq);
}
}
return results;
}
一致性hash表核心实现
org.apache.rocketmq.common.consistenthash.ConsistentHashRouter#addNode
// 虚拟节点环
private final SortedMap<Long, VirtualNode<T>> ring = new TreeMap<Long, VirtualNode<T>>();
// 增加节点
public void addNode(T pNode, int vNodeCount) {
if (vNodeCount < 0)
throw new IllegalArgumentException("illegal virtual node counts :" + vNodeCount);
// 获取已存在的节点备份数量
int existingReplicas = getExistingReplicas(pNode);
// 塞入红黑树
for (int i = 0; i < vNodeCount; i++) {
VirtualNode<T> vNode = new VirtualNode<T>(pNode, i + existingReplicas);
ring.put(hashFunction.hash(vNode.getKey()), vNode);
}
}
org.apache.rocketmq.common.consistenthash.ConsistentHashRouter#getExistingReplicas
// 获取已存在的节点备份数量
public int getExistingReplicas(T pNode) {
int replicas = 0;
for (VirtualNode<T> vNode : ring.values()) {
if (vNode.isVirtualNodeOf(pNode)) {
replicas++;
}
}
return replicas;
}
org.apache.rocketmq.common.consistenthash.ConsistentHashRouter#routeNode
// 路由节点
public T routeNode(String objectKey) {
if (ring.isEmpty()) {
return null;
}
Long hashVal = hashFunction.hash(objectKey);
SortedMap<Long, VirtualNode<T>> tailMap = ring.tailMap(hashVal);
Long nodeHashVal = !tailMap.isEmpty() ? tailMap.firstKey() : ring.firstKey();
return ring.get(nodeHashVal).getPhysicalNode();
}
核心思想
1.消费者包装为虚拟节点插入hash环后
2.将队列的hash值进行环路由,找到最近的消费者虚拟节点,就是相应的对应关系
机房分配hash队列算法
org.apache.rocketmq.client.consumer.rebalance.AllocateMessageQueueByMachineRoom
Computer room Hashing queue algorithm, such as Alipay logic room
public List<MessageQueue> allocate(String consumerGroup, String currentCID, List<MessageQueue> mqAll,
List<String> cidAll) {
List<MessageQueue> result = new ArrayList<MessageQueue>();
if (!check(consumerGroup, currentCID, mqAll, cidAll)) {
return result;
}
int currentIndex = cidAll.indexOf(currentCID);
if (currentIndex < 0) {
return result;
}
List<MessageQueue> premqAll = new ArrayList<MessageQueue>();
for (MessageQueue mq : mqAll) {
String[] temp = mq.getBrokerName().split("@");
if (temp.length == 2 && consumeridcs.contains(temp[0])) {
premqAll.add(mq);
}
}
int mod = premqAll.size() / cidAll.size();
int rem = premqAll.size() % cidAll.size();
int startIndex = mod * currentIndex;
int endIndex = startIndex + mod;
for (int i = startIndex; i < endIndex; i++) {
result.add(premqAll.get(i));
}
if (rem > currentIndex) {
result.add(premqAll.get(currentIndex + mod * cidAll.size()));
}
return result;
}