1. Descrição
Existem dois tipos. Um é enviar mensagens diretamente. Existe um algoritmo para selecionar a fila dentro do cliente e nenhuma alteração externa é permitida. Há também um algoritmo de seleção de fila personalizado (três algoritmos são integrados, se você não gostar, você pode personalizar o algoritmo a ser alcançado).
public class org.apache.rocketmq.client.producer.DefaultMQProducer {
// 只发送消息,queue的选择由默认的算法来实现
@Override
public SendResult send(Collection<Message> msgs) {}
// 自定义选择queue的算法进行发消息
@Override
public SendResult send(Collection<Message> msgs, MessageQueue messageQueue) {}
}
Em segundo lugar, o código-fonte
1 、 enviar (msg, mq)
1.1, cenários de uso
Às vezes, não queremos o algoritmo de seleção de fila padrão, mas precisamos personalizá-lo. Geralmente, o cenário mais comumente usado são as mensagens sequenciais . O envio de mensagens sequenciais geralmente especifica que as mensagens com um determinado conjunto de características são enviadas na mesma fila, para que o pedido possa ser garantido., Porque a fila única é ordenada.
Se você não entender a mensagem de sequência, consulte meu artigo anterior de mensagem de sequência.
1.2, análise de princípio
Existem três algoritmos integrados, os quais implementam uma interface comum:
org.apache.rocketmq.client.producer.MessageQueueSelector
-
SelectMessageQueueByRandom
-
SelectMessageQueueByHash
-
SelectMessageQueueByMachineRoom
-
Se você deseja personalizar a lógica, pode implementar diretamente a interface e substituir o método de seleção.
No modo de estratégia muito típico , diferentes algoritmos têm diferentes classes de implementação e há uma interface de nível superior.
1.2.1 、 SelectMessageQueueByRandom
public class SelectMessageQueueByRandom implements MessageQueueSelector {
private Random random = new Random(System.currentTimeMillis());
@Override
public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
// mqs.size():队列的个数。假设队列个数是4,那么这个value就是0-3之间随机。
int value = random.nextInt(mqs.size());
return mqs.get(value);
}
}
tão fácil é pura aleatoriedade.
mqs.size (): O número de filas. Supondo que o número de filas seja 4, esse valor é aleatório entre 0-3.
1.2.2 、 SelectMessageQueueByHash
public class SelectMessageQueueByHash implements MessageQueueSelector {
@Override
public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
int value = arg.hashCode();
// 防止出现负数,取个绝对值,这也是我们平时开发中需要注意到的点
if (value < 0) {
value = Math.abs(value);
}
// 直接取余队列个数。
value = value % mqs.size();
return mqs.get(value);
}
}
tão fácil é simplesmente pegar o resto.
mqs.size (): O número de filas. Suponha que o número de filas seja 4 e o hashcode do valor seja 3, então 3% 4 = 3, então é a última fila, ou seja, a quarta fila, porque o subscrito começa em 0.
1.2.3 、 SelectMessageQueueByMachineRoom
public class SelectMessageQueueByMachineRoom implements MessageQueueSelector {
private Set<String> consumeridcs;
@Override
public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
return null;
}
public Set<String> getConsumeridcs() {
return consumeridcs;
}
public void setConsumeridcs(Set<String> consumeridcs) {
this.consumeridcs = consumeridcs;
}
}
Eu não entendo o que é o uso de pássaros, apenas retorne nulo; então, se houver um requisito personalizado, basta personalizá-lo diretamente, essa coisa não tem uso para ovos.
1.2.4, algoritmo personalizado
public class MySelectMessageQueue implements MessageQueueSelector {
@Override
public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
return mqs.get(0);
}
}
Sempre escolha a fila 0, que é a primeira fila. Só para dar um exemplo, realmente depende das necessidades do seu negócio.
1.3, cadeia de chamadas
org.apache.rocketmq.client.producer.DefaultMQProducer#send(Message msg, MessageQueueSelector selector, Object arg)
->
org.apache.rocketmq.client.producer.DefaultMQProducer#send(Message msg, MessageQueueSelector selector, Object arg)
->
org.apache.rocketmq.client.producer.DefaultMQProducer#send(Message msg, MessageQueueSelector selector, Object arg, long timeout)
->
org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl#sendSelectImpl(xxx)
->
mq = mQClientFactory.getClientConfig().queueWithNamespace(selector.select(messageQueueList, userMessage, arg));
->
selector.select(messageQueueList, userMessage, arg)
->
org.apache.rocketmq.client.producer.MessageQueueSelector#select(final List<MessageQueue> mqs, final Message msg, final Object arg)
2 、 enviar (msg)
2.1, cenários de uso
Geralmente, isso é usado em cenários onde não há requisitos especiais. Como seu algoritmo de seleção de fila padrão é muito bom, vários cenários de otimização foram pensados para nós.
2.2. Análise de princípio
// {@link org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl#sendDefaultImpl}
// 这是发送消息核心原理,不清楚的看我之前发消息源码分析的文章
// 选择消息要发送的队列
MessageQueue mq = null;
for (int times = 0; times < 3; times++) {
// 首次肯定是null
String lastBrokerName = null == mq ? null : mq.getBrokerName();
// 调用下面的方法进行选择queue
MessageQueue mqSelected = this.selectOneMessageQueue(topicPublishInfo, lastBrokerName);
if (mqSelected != null) {
// 给mq赋值,如果首次失败了,那么下次重试的时候(也就是下次for的时候),mq就有值了。
mq = mqSelected;
......
// 很关键,能解答下面会提到的两个问题:
// 1.faultItemTable是什么时候放进去的?
// 2.isAvailable() 为什么只是判断一个时间就可以知道Broker是否可用?
this.updateFaultItem(mq.getBrokerName(), endTimestamp - beginTimestampPrev, false);
}
}
Selecione a entrada principal da fila
public MessageQueue selectOneMessageQueue(final TopicPublishInfo tpInfo, final String lastBrokerName) {
// 默认为false,代表不启用broker故障延迟
if (this.sendLatencyFaultEnable) {
try {
// 随机数且+1
int index = tpInfo.getSendWhichQueue().getAndIncrement();
// 遍历
for (int i = 0; i < tpInfo.getMessageQueueList().size(); i++) {
// 先(随机数 +1) % queue.size()
int pos = Math.abs(index++) % tpInfo.getMessageQueueList().size();
if (pos < 0) {
pos = 0;
}
MessageQueue mq = tpInfo.getMessageQueueList().get(pos);
// 看找到的这个queue所属的broker是不是可用的
if (latencyFaultTolerance.isAvailable(mq.getBrokerName())) {
// 非失败重试,直接返回到的队列
// 失败重试的情况,如果和选择的队列是上次重试是一样的,则返回
// 也就是说如果你这个queue所在的broker可用,
// 且不是重试进来的或失败重试的情况,如果和选择的队列是上次重试是一样的,那你就是天选之子了。
if (null == lastBrokerName || mq.getBrokerName().equals(lastBrokerName)) {
return mq;
}
}
}
// 如果所有队列都不可用,那么选择一个相对好的broker,不考虑可用性的消息队列
final String notBestBroker = latencyFaultTolerance.pickOneAtLeast();
int writeQueueNums = tpInfo.getQueueIdByBroker(notBestBroker);
if (writeQueueNums > 0) {
final MessageQueue mq = tpInfo.selectOneMessageQueue();
if (notBestBroker != null) {
mq.setBrokerName(notBestBroker);
mq.setQueueId(tpInfo.getSendWhichQueue().getAndIncrement() % writeQueueNums);
}
return mq;
} else {
latencyFaultTolerance.remove(notBestBroker);
}
} catch (Exception e) {
log.error("Error occurred when selecting message queue", e);
}
// 随机选择一个queue
return tpInfo.selectOneMessageQueue();
}
// 当sendLatencyFaultEnable=false的时候选择queue的方法,默认就是false。
return tpInfo.selectOneMessageQueue(lastBrokerName);
}
2.2.1. Não habilitar atraso de falha do corretor
Como sendLatencyFaultEnable é falso por padrão, primeiro olhe para a lógica quando sendLatencyFaultEnable = false
public MessageQueue selectOneMessageQueue(final String lastBrokerName) {
// 第一次就是null,第二次(也就是重试的时候)就不是null了。
if (lastBrokerName == null) {
// 第一次选择队列的逻辑
return selectOneMessageQueue();
} else {
// 第一次选择队列发送消息失败了,第二次重试的时候选择队列的逻辑
int index = this.sendWhichQueue.getAndIncrement();
for (int i = 0; i < this.messageQueueList.size(); i++) {
int pos = Math.abs(index++) % this.messageQueueList.size();
if (pos < 0)
pos = 0;
MessageQueue mq = this.messageQueueList.get(pos);
// 过滤掉上次发送消息失败的队列
if (!mq.getBrokerName().equals(lastBrokerName)) {
return mq;
}
}
return selectOneMessageQueue();
}
}
Em seguida, continue examinando a lógica de selecionar a fila pela primeira vez:
public MessageQueue selectOneMessageQueue() {
// 当前线程有个ThreadLocal变量,存放了一个随机数 {@link org.apache.rocketmq.client.common.ThreadLocalIndex#getAndIncrement}
// 然后取出随机数根据队列长度取模且将随机数+1
int index = this.sendWhichQueue.getAndIncrement();
int pos = Math.abs(index) % this.messageQueueList.size();
if (pos < 0) {
pos = 0;
}
return this.messageQueueList.get(pos);
}
Bem, na verdade significa um pouco aleatório. Mas o destaque é que tirando o módulo do número aleatório do comprimento da fila e adicionando o número aleatório +1, este +1 é aceso (getAndIncrement cas +1).
Quando a mensagem não for enviada pela primeira vez, lastBrokerName armazenará o corretor que falhou na seleção atual (mq = mqSelected). Após tentar novamente, o lastBrokerName tem um valor, o que significa que o último boker selecionado falhou ao enviar e a variável de encadeamento local sendWhichQueue + 1. Percorra a fila de mensagens de seleção até que ela não seja o último broker, o que evita a lógica do broker que falhou ao enviar da última vez.
Por exemplo: desta vez, seu número aleatório é 1, o comprimento da fila é 4, 1% 4 = 1 e falha neste momento e entra na nova tentativa. Então, antes da nova tentativa, ou seja, após a etapa anterior 1% 4, ele coloca 1 Depois que a operação ++ é executada, ela se torna 2, portanto, quando você tentar novamente desta vez, é 2% 4 = 2, que filtra diretamente o corretor que falhou.
Em seguida, continue a ver a lógica da segunda fila de seleção de nova tentativa:
// +1
int index = this.sendWhichQueue.getAndIncrement();
for (int i = 0; i < this.messageQueueList.size(); i++) {
// 取模
int pos = Math.abs(index++) % this.messageQueueList.size();
if (pos < 0)
pos = 0;
MessageQueue mq = this.messageQueueList.get(pos);
// 过滤掉上次发送消息失败的队列
if (!mq.getBrokerName().equals(lastBrokerName)) {
return mq;
}
}
// 没找到能用的queue的话继续走默认的那个
return selectOneMessageQueue();
Tão fácil, você não falhou da última vez, você tentou de novo depois de entrar em mim? Também sou muito simples. Ainda tiro o número aleatório +1 e modulo o comprimento da fila. Vejo se o corretor falhou da última vez. Se for filho dele, vou filtrar e continuar a percorrer a fila para encontrar o próximo que pode ser usado.
2.2.2, habilitar atraso de falha do corretor
Essa é a lógica a seguir, se
if (this.sendLatencyFaultEnable) {
....
}
Basta olhar para o comentário acima. É muito claro. Deixe-me primeiro (随机数 +1) % queue.size()
e depois ver se o corretor ao qual sua fila pertence está disponível. Se estiver disponível, não haverá nova tentativa ou falha. Se a fila selecionada for a última repetir O teste é o mesmo, basta retornar e pronto. Então, como você vê se o corretor está disponível?
// {@link org.apache.rocketmq.client.latency.LatencyFaultToleranceImpl#isAvailable(String)}
public boolean isAvailable(final String name) {
final FaultItem faultItem = this.faultItemTable.get(name);
if (faultItem != null) {
return faultItem.isAvailable();
}
return true;
}
// {@link org.apache.rocketmq.client.latency.LatencyFaultToleranceImpl.FaultItem#isAvailable()}
public boolean isAvailable() {
return (System.currentTimeMillis() - startTimestamp) >= 0;
}
dúvida:
Quando foi colocado o faultItemTable?
isAvailable () Por que podemos saber se o Broker está disponível apenas por meio de um julgamento por um período de tempo?
Isso requer o método chamado após o envio da mensagem acima:
// {@link org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl#updateFaultItem}
// 发送开始时间
beginTimestampPrev = System.currentTimeMillis();
// 进行发送
sendResult = this.sendKernelImpl(msg, mq, communicationMode, sendCallback, topicPublishInfo, timeout);
// 发送结束时间
endTimestamp = System.currentTimeMillis();
// 更新broker的延迟情况
this.updateFaultItem(mq.getBrokerName(), endTimestamp - beginTimestampPrev, false);
A lógica detalhada é a seguinte:
// {@link org.apache.rocketmq.client.latency.MQFaultStrategy#updateFaultItem}
public void updateFaultItem(final String brokerName, final long currentLatency, boolean isolation) {
if (this.sendLatencyFaultEnable) {
// 首次isolation传入的是false,currentLatency是发送消息所耗费的时间,如下
// this.updateFaultItem(mq.getBrokerName(), endTimestamp - beginTimestampPrev, false);
long duration = computeNotAvailableDuration(isolation ? 30000 : currentLatency);
this.latencyFaultTolerance.updateFaultItem(brokerName, currentLatency, duration);
}
}
private long[] latencyMax = {50L, 100L, 550L, 1000L, 2000L, 3000L, 15000L};
private long[] notAvailableDuration = {0L, 0L, 30000L, 60000L, 120000L, 180000L, 600000L};
// 根据延迟时间对比MQFaultStrategy中的延迟级别数组latencyMax 不可用时长数组notAvailableDuration 来将该broker加进faultItemTable中。
private long computeNotAvailableDuration(final long currentLatency) {
for (int i = latencyMax.length - 1; i >= 0; i--) {
// 假设currentLatency花费了10ms,那么latencyMax里的数据显然不符合下面的所有判断,所以直接return 0;
if (currentLatency >= latencyMax[i])
return this.notAvailableDuration[i];
}
return 0;
}
// {@link org.apache.rocketmq.client.latency.LatencyFaultToleranceImpl#updateFaultItem()}
@Override
// 其实主要就是给startTimestamp赋值为当前时间+computeNotAvailableDuration(isolation ? 30000 : currentLatency);的结果,给isAvailable()所用
// 也就是说只有notAvailableDuration == 0的时候,isAvailable()才会返回true。
public void updateFaultItem(final String name, final long currentLatency, final long notAvailableDuration) {
FaultItem old = this.faultItemTable.get(name);
if (null == old) {
final FaultItem faultItem = new FaultItem(name);
faultItem.setCurrentLatency(currentLatency);
// 给startTimestamp赋值为当前时间+computeNotAvailableDuration(isolation ? 30000 : currentLatency);的结果,给isAvailable()所用
faultItem.setStartTimestamp(System.currentTimeMillis() + notAvailableDuration);
old = this.faultItemTable.putIfAbsent(name, faultItem);
if (old != null) {
old.setCurrentLatency(currentLatency);
// 给startTimestamp赋值为当前时间+computeNotAvailableDuration(isolation ? 30000 : currentLatency);的结果,给isAvailable()所用
old.setStartTimestamp(System.currentTimeMillis() + notAvailableDuration);
}
} else {
old.setCurrentLatency(currentLatency);
// 给startTimestamp赋值为当前时间+computeNotAvailableDuration(isolation ? 30000 : currentLatency);的结果,给isAvailable()所用
old.setStartTimestamp(System.currentTimeMillis() + notAvailableDuration);
}
}
As duas linhas de código a seguir são explicadas em detalhes:
private long[] latencyMax = {50L, 100L, 550L, 1000L, 2000L, 3000L, 15000L};
private long[] notAvailableDuration = {0L, 0L, 30000L, 60000L, 120000L, 180000L, 600000L};
latencyMax | notAvailableDuration |
---|---|
50L | 0L |
100L | 0L |
550L | 30000L |
1000L | 60000L |
2000L | 120000L |
3000L | 180000L |
15000L | 600000L |
qual é
-
currentLatency é maior ou igual a 50 e menor que 100, então notAvailableDuration é 0
-
currentLatency é maior ou igual a 100 e menor que 550, então notAvailableDuration é 0
-
currentLatency é maior ou igual a 550 e menor que 1000, então notAvailableDuration é 300000
-
…e muitos mais
Vamos dar outro exemplo:
Assumindo que o isolamento é passado para verdadeiro,
long duration = computeNotAvailableDuration(isolation ? 30000 : currentLatency);
Então notAvailableDuration será transmitido em 600000L. Combinado com o método isAvailable, o processo aproximado é o seguinte:
RocketMQ prevê um tempo disponível (hora atual + notAvailableDuration) para cada Broker. Quando o tempo atual for maior que esse tempo, significa que o Broker está disponível, e notAvailableDuration tem 6 níveis que correspondem ao intervalo de latencyMax um para um e prever, de acordo com a currentLatency de entrada, quando o Broker estará disponível.
Então olhe para isso de novo
public boolean isAvailable() {
return (System.currentTimeMillis() - startTimestamp) >= 0;
}
De acordo com o tempo de execução para ver em qual intervalo ele se enquadra, notAvailableDuration é 0 dentro do tempo de 0 ~ 100, que estão todos disponíveis. Após o valor ser maior que esse valor, o tempo disponível começará a aumentar e será considerado não é a solução ideal.
2.3, cadeia de chamadas
org.apache.rocketmq.client.producer.DefaultMQProducer#send(org.apache.rocketmq.common.message.Message)
->
org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl#send(org.apache.rocketmq.common.message.Message)
->
org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl#send(org.apache.rocketmq.common.message.Message, long)
->
org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl#sendDefaultImpl(xxx)
->
MessageQueue mqSelected = this.selectOneMessageQueue(topicPublishInfo, lastBrokerName);
->
org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl#selectOneMessageQueue(xxx)
org.apache.rocketmq.client.latency.MQFaultStrategy#selectOneMessageQueue(final TopicPublishInfo tpInfo, final String lastBrokerName)
2.4. Resumo
-
Quando a tolerância a falhas não está ativada, pesquise a fila para envio. Se falhar, filtre o Broker com falha ao tentar novamente
-
Se a estratégia de tolerância a falhas estiver habilitada, o mecanismo de previsão do RocketMQ será usado para prever se um corretor está disponível
-
Se o corretor que falhou da última vez estiver disponível, a fila do corretor ainda estará selecionada
-
Se a situação acima falhar, selecione aleatoriamente um para enviar
-
Ao enviar uma mensagem, ele registrará a hora da chamada e se um erro foi relatado, e prevê o tempo disponível do corretor com base no tempo
Três, resumo
1. Perguntas
Ele tem dois métodos send () sobrecarregados, um suporta seletor de algoritmo e o outro não suporta seleção de algoritmo.A seleção de algoritmo de fila é um modo de estratégia típico. Por que send(message)
o algoritmo de seleção de fila integrado não é extraído em uma classe separada e, em seguida, essa classe implementa a org.apache.rocketmq.client.producer.MessageQueueSelector
interface? Por exemplo, é chamado de : SelectMessageQueueByBest
, como o seguinte:
public class org.apache.rocketmq.client.producer.DefaultMQProducer {
// 只发送消息,queue的选择由默认的算法来实现
@Override
public SendResult send(Collection<Message> msgs) {
this.send(msgs, new SelectMessageQueueByBest().select(xxx));
}
// 自定义选择queue的算法进行发消息
@Override
public SendResult send(Collection<Message> msgs, MessageQueue messageQueue) {}
}
Meu palpite é que este algoritmo pode ser muito complicado e há muitas interações com outros tipos e os parâmetros podem ser diferentes dos outros três integrados, então eles não os juntaram, mas ainda eram padronizados juntos Eles fizeram a mesma coisa, mas O algoritmo é diferente, é um modo de estratégia típico.
2. Entrevista
P: Quais são os algoritmos para selecionar a fila ao enviar mensagens?
Resposta: Existem dois tipos, um é enviar mensagens diretamente e você não pode selecionar a fila. O algoritmo de seleção de fila é o seguinte:
-
Quando a tolerância a falhas não está ativada, pesquise a fila para envio. Se falhar, filtre o Broker com falha ao tentar novamente
-
Se a estratégia de tolerância a falhas estiver habilitada, o mecanismo de previsão do RocketMQ será usado para prever se um corretor está disponível
-
Se o corretor que falhou da última vez estiver disponível, a fila do corretor ainda estará selecionada
-
Se a situação acima falhar, selecione aleatoriamente um para enviar
-
Ao enviar uma mensagem, ele registrará a hora da chamada e se um erro foi relatado, e prevê o tempo disponível do corretor com base no tempo
A outra é selecionar um algoritmo ao enviar uma mensagem e até mesmo implementar um algoritmo personalizado de interface:
-
SelectMessageQueueByRandom
:acaso -
SelectMessageQueueByHash
:cerquilha -
SelectMessageQueueByMachineRoom
-
Implementar personalização de interface