文章目录
Producer 概述
生产者向消息队列里写人消息,不同的业务场景需要生产者采用不同的写人策略。比如同步发送、异步发送、延迟发送、发送事务消息等
发送状态 SendStatus
- 发送消息时,将获得发送结果 SendResult,其中包含发送状态 SendStatus。我们假设消息的
isWaitStoreMsgOK=true(默认为true)
。如果没有抛出异常,我们将始终得到SEND_OK
。 - 不同状态在不同的刷盘策略和同步策略的配置下含义是不同的。
SendStatus.SEND_OK
:表示发送成功(不代表没有问题),发送成功的具体含义,比如消息是否已经被存储到磁盘?消息是否被同步到了Slave 上?消息在Slave 上是否被写人磁盘?需要结合所配置的刷盘策略、主从策略来定。这个状态还可以简单理解为,没有发生上面列出的三个问题状态就是SEND_OK
SendStatus.FLUSH_DISK_TIMEOUT
:表示没有在规定时间内完成刷盘(需要Broker 的刷盘策略创立设置成SYNC_FLUSH
才会报这个错误)。FlushDiskType 默认是ASYNC_FLUSH
,并且syncFlushTimeout
默认是5sSendStatus.FLUSH_SLAVE_TIMEOUT
:表示在主备方式下,并且Broker 被设置成SYNC_MASTER
方式(默认是ASYNC_MASTER
),没有在设定时间内完成主从同步,默认syncFlushTimeout
是 5s。SendStatus.SLAVE_NOT_AVAILABLE
:表示在主备方式下,并且Broker 被设置成SYNC_MASTER
,但是没有找到被配置成Slave 的Broker 。
消息重复和消息丢失 Duplication or Missing
FLUSH_DISK_TIMEOUT
、FLUSH_SLAVE_TIMEOUT
的发送状态并且 Broker 挂了,导致消息丢失的风险,这时有两种处理方式:
- 什么都不做,导致消息丢失
- 重发,有可能导致消息重复。推荐的方式,并且在消费的时候处理重复的情况。
- 注意:但是当发送状态
SLAVE_NOT_AVAILABLE
时,重新发送是无用的。如果发生这种情况,应该保持场景并警告集群管理器。
发送超时 Timeout
- 客户机将请求发送给Broker,并等待响应,但是如果超过最大等待时间,且没有返回响应,则客户机将抛出 RemotingTimeoutException。默认等待时间为3秒。可以使用
send(msg, timeout)
传递超时参数。 - 不建议等待时间过短,因为 Broker 需要一些时间来刷新磁盘或。
- 如果该值超过 syncFlushTimeout 很多,那么它可能没有什么影响,因为代理可能在超时之前返回带有FLUSH_SLAVE_TIMEOUT或FLUSH_SLAVE_TIMEOUT的响应
消息大小 Message Size
官方建议不大于512K
异步发送 Async Sending
默认 send(msg)
将阻塞,直到返回响应。如果关心性能,建议使用send(msg, callback)
,它将以异步方式工作
生产者组 Producer Group
- 正常情况下,producer group没有什么实际的作用。但是如果是有事务的,需要关注一下。
- 默认情况下,一个的JVM中的同一个 producer group 建议使用一个 producer 。因为生成器在发送消息方面足够强大,每个生成器组建议只允许一个实例,以避免不必要的生成器实例初始化。
线程安全 Thread Safety
producer 是线程安全的
发送性能 Performance
如果想在一个JVM中使用多个producer进行大数据处理,建议:
- 使用多个producer异步发送,建议3~5个
- 为每个producer,设置 setInstanceName
DefaultMQProducer 简单使用
发送消息5步骤
- 设置Producer 的 GroupName
- 设置lnstanceName ,当一个Jvm 需要启动多个Producer 的时候,通过设置不同的InstanceName 来区分,不设置的话系统使用默认名称
DEFAULT
- 设置NameServer 地址
- 设置发送失败重试次数,当网络出现异常的时候,这个次数影响消息的重复投递次数。想保证不丢消息,可以设置多重试几次
- 组装消息并发送。消息的发送有同步和异步两种方式。
例子
写一个高质量的生产者程序,重点在于对发送结果的处理,要充分考虑各种异常,写清对应的处理逻辑
- 同步发送
public class SimpleSyncProducer {
public static void main(String[] args) throws MQClientException, UnsupportedEncodingException, RemotingException, InterruptedException, MQBrokerException {
// 设置 producerGroup
DefaultMQProducer producer = new DefaultMQProducer("base_producer_group");
// 设置 nameServer
producer.setNamesrvAddr("10.0.64.106:9876;10.0.64.107:9876");
// 设置实例名称 InstanceName
producer.setInstanceName("SimpleSyncProducer");
// 设置重试次数
producer.setRetryTimesWhenSendFailed(3);
producer.start();
for (int i = 0; i < 100; i++) {
// 组装消息
Message message = new Message();
message.setTopic("base_topic");
message.setTags("base_api");
message.setBody(("Hello RocketMQ > " + i).getBytes(RemotingHelper.DEFAULT_CHARSET));
// 同步发送
SendResult result = producer.send(message);
System.out.printf("%s%n", result);
}
producer.shutdown();
}
}
- 异步发送
public class SimpleAsyncProducer {
public static void main(String[] args) throws MQClientException, UnsupportedEncodingException, RemotingException, InterruptedException, MQBrokerException {
// 1- 设置 GroupName
DefaultMQProducer producer = new DefaultMQProducer("base_producer_group");
// 设置NameServer 地址
producer.setNamesrvAddr("10.0.64.106:9876;10.0.64.107:9876");
// 设置 lnstanceName ,当一个Jvm 需要启动多个Producer 的时候,通过 设置不同的InstanceName 来区分,不设置的话系统使用默认名称“DEFAULT” 。
producer.setInstanceName("base_producer");
// 设置发送失败重试次数
producer.setRetryTimesWhenSendFailed(3);
producer.start();
// 组装消息并发送
// 消息的发送有同步和异步两种方式
for (int i = 0; i < 100; i++) {
Message message = new Message("base_topic", "base_api", ("Hello RocketMQ > " + i).getBytes(RemotingHelper.DEFAULT_CHARSET));
// 异步发送
producer.send(message, new SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
System.out.printf("%s%n", sendResult);
sendResult.getSendStatus();
}
@Override
public void onException(Throwable e) {
e.printStackTrace();
}
});
}
producer.shutdown();
}
}
- 异步 AsyncProducer 集合 CountDownLatch 实现
public class AsyncProducer {
public static void main(String[] args) throws MQClientException, InterruptedException {
DefaultMQProducer producer = new DefaultMQProducer("Jodie_Daily_test");
producer.start();
// 异步重试
producer.setRetryTimesWhenSendAsyncFailed(0);
// 定义 CountDownLatch
int messageCount = 100;
final CountDownLatch countDownLatch = new CountDownLatch(messageCount);
for (int i = 0; i < messageCount; i++) {
try {
final int index = i;
// 组装消息
Message msg = new Message("Jodie_topic_1023", "TagA", "OrderID188", "Hello world".getBytes(RemotingHelper.DEFAULT_CHARSET));
// 异步发送
producer.send(msg, new SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
countDownLatch.countDown();
System.out.printf("%-10d OK %s %n", index, sendResult.getMsgId());
}
@Override
public void onException(Throwable e) {
countDownLatch.countDown();
System.out.printf("%-10d Exception %s %n", index, e);
e.printStackTrace();
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
countDownLatch.await(5, TimeUnit.SECONDS);
producer.shutdown();
}
}
延迟消息:setDelayTimeLevel
- RocketMQ 支持发送延迟消息,Broker 收到这类消息后,延迟一段时间再处理, 使消息在规定的一段时间后生效。
- 延迟消息的使用方法是在创建Message 对象时,调用
setDelayTimeLevel(int level)
方法设置延迟时间,然后再把这个消息发送出去。目前延迟的时间不支持任意设置,仅支持预设值的时间长度(1s/5s/1Os/30s/1m/2m/3m/4m/5m/6m/7m/8m/9m/1Om/20m/30m/1h/2h)
。比如setDelayTimeLevel(3)
表示延迟10s 。
// 发送延迟消息 延迟10s
message.setDelayTimeLevel(3);
自定义消息发送规则:MessageQueueSelector
- 消息发送与消费的黑盒:
- 一个 Topic 会有多个Message Queue ,如果使用Producer 的默认配置,这个Producer 会轮流向各个MessageQueue 发送消息。
- Consumer 在消费消息的时候,会根据负载均衡策略,消费被分配到的Message Queue ,如果不经过特定的设置,某条消息被发往哪个MessageQueue ,被哪个Consumer 消费是未知的。
- 如果业务需要把消息发送到指定的MessageQueue 里,比如把同一类型的消息都发往相同的MessageQueue,可以用 MessageQueueSelector 实现
- 发送消息的时候,使用
public SendResult send ( Message msg, MessageQueueSelector selector, Object arg )
函数发送消息即可。在MessageQueueSelector 的实现中,根据传人的Object 参数,或者根据Message 消息内容确定把消息发往那个Message Queue ,返回被选中的Message Queue 。
public class CusProducer {
public static void main(String[] args) throws Exception {
// 设置 producerGroup
DefaultMQProducer producer = new DefaultMQProducer("base_producer_group");
// 设置 nameServer
producer.setNamesrvAddr("10.0.64.106:9876;10.0.64.107:9876");
producer.start();
for (int i = 0; i < 100; i++) {
Message message = new Message();
message.setTopic("base_topic");
message.setTags("base_api");
message.setBody(("Hello RocketMQ > " + i).getBytes(RemotingHelper.DEFAULT_CHARSET));
// 设置自定义规则
SendResult result = producer.send(message, new OrderMessageQueueSelector(), 13);
System.out.printf("%s%n", result);
}
producer.shutdown();
}
/**
* 自定义规则: 返回被选中的Message Queue
* 在MessageQueueSelector 的实现中,根据传人的Object 参数,或者根据Message 消息内容确定把消息发往那个Message Queue
*/
public static class OrderMessageQueueSelector implements MessageQueueSelector {
@Override
public MessageQueue select(List<MessageQueue> mqs, Message msg, Object orderKey) {
int id = Integer.parseInt(orderKey.toString());
int idMainIndex = id / 100;
int size = mqs.size();
int index = idMainIndex % size;
return mqs.get(index);
}
}
}
事务消息
事务消息与实现思路
-
RocketMQ 的事务消息,是指发送消息事件和其他事件需要同时成功或同时失败(发送消息与本地业务一致性)。比如银行转账, A 银行的某账户要转一万元到B 银行的某账户。A 银行发送
B 银行账户增加一万元
这个消息,要和从A 银行账户扣除一万元
这个操作同时成功或者同时失败 -
RocketMQ 采用两阶段提交的方式实现事务消息,使用 TransactionMQProducer 类实现,先发消息,然后业务操作,根据业务操作结果确认之前消息是提交还是回滚(做commit 还是rollback)。具体步骤如下:
- 1)发送方向RocketMQ 发送
待确认
消息 - 2)RocketMQ 将收到的
待确认
消息持久化成功后,向发送方回复消息已经发送成功,此时第一阶段消息发送完成。 - 3)发送方开始执行本地事件逻辑,即业务操作
- 4)发送方根据本地事件执行结果向 RocketMQ 发送
二次确认( Commit 或是Rollback ) 消息
- RocketMQ 收到Commit 状态则将第一阶段消息标记为可投递,订阅方将能够收到该消息;
- 收到Rollback 状态则删除第一阶段的消息,订阅方接收不到该消息。
- 5)如果出现异常情况,步骤4)提交的二次确认最终未到达 RocketMQ,服务器在经过固定时间段后将对
待确认
消息、发起回查请求。 - 6)发送方收到消息回查请求后(如果发送一阶段消息的Producer 不能工作,回查请求将被发送到和Producer 在同一个Group 里的其他Producer ),通过检查对应消息的本地事件执行结果返回Co mmit 或Roolback 状态
- 7)RocketMQ 收到回查请求后,按照步骤4 ) 的逻辑处理
-
以上是 RocketMQ 之前的版本实现事务消息的逻辑。但是因为RocketMQ 依赖将
数据顺序写到磁盘
这个特征来提高性能,步骤4 )却需要更改第一阶段消息的状态,这样会造成磁盘Catch 的脏页过多,降低系统的性能。所以RocketMQ 在4.x 的版本中将这部分功能去除。系统中的一些上层Class 都还在,用户可以根据实际需求实现自己的事务功能。 -
客户端有三个类来支持用户实现事务消息:
LocalTransactionExecuter
用来实例化步骤3 )的逻辑,根据情况返回LocalTransactionState.ROLLBACK_MESSAGE
或者LocalTransactionState.COMMIT_MESSAGE
状态。TransactionMQProducer
其用法和 DefaultMQProducer 类似,要通过它启动一个Producer 并发消息,但是比DefaultMQProducer 多设置本地事务处理函数和回查状态函数。TransactionCheckListener
实现步骤5 )中MQ 服务器的回查请求,返回LocalTransactionState.ROLLBACK_MESSAGE
或者LocalTransactionState.COMMIT_MESSAGE
官方例子代码
- 消息发送
public class TransactionProducer {
public static void main(String[] args) throws MQClientException, InterruptedException {
TransactionListener transactionListener = new TransactionListenerImpl();
// 使用 TransactionMQProducer
TransactionMQProducer producer = new TransactionMQProducer("please_rename_unique_group_name");
// 自定义 线程池并发
ExecutorService executorService = new ThreadPoolExecutor(2, 5, 100, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(2000), new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setName("client-transaction-msg-check-thread");
return thread;
}
});
// 设置 线程池与事务监听
producer.setExecutorService(executorService);
producer.setTransactionListener(transactionListener);
producer.start();
String[] tags = new String[]{"TagA", "TagB", "TagC", "TagD", "TagE"};
for (int i = 0; i < 10; i++) {
try {
Message msg = new Message("TopicTest1234", tags[i % tags.length], "KEY" + i, ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET));
SendResult sendResult = producer.sendMessageInTransaction(msg, null);
System.out.printf("%s%n", sendResult);
Thread.sleep(10);
} catch (MQClientException | UnsupportedEncodingException e) {
e.printStackTrace();
}
}
for (int i = 0; i < 100000; i++) {
Thread.sleep(1000);
}
producer.shutdown();
}
}
- 本地事务监听
executeLocalTransaction
当发送prepare
消息成功后, 回调这个方法,处理本地事务业务,返回事务状态。checkLocalTransaction
用于检查本地事务状态并响应MQ检查请求
public class TransactionListenerImpl implements TransactionListener {
private AtomicInteger transactionIndex = new AtomicInteger(0);
private ConcurrentHashMap<String, Integer> localTrans = new ConcurrentHashMap<>();
@Override
public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
int value = transactionIndex.getAndIncrement();
int status = value % 3;
localTrans.put(msg.getTransactionId(), status);
return LocalTransactionState.UNKNOW;
}
@Override
public LocalTransactionState checkLocalTransaction(MessageExt msg) {
Integer status = localTrans.get(msg.getTransactionId());
if (null != status) {
switch (status) {
case 0:
return LocalTransactionState.UNKNOW;
case 1:
return LocalTransactionState.COMMIT_MESSAGE;
case 2:
return LocalTransactionState.ROLLBACK_MESSAGE;
default:
return LocalTransactionState.COMMIT_MESSAGE;
}
}
return LocalTransactionState.COMMIT_MESSAGE;
}
}