maven(基于阿里云SDK)
com.aliyun.openservices
ons-client
1.8.8.5.Final
private static RPCHook getAclRPCHook() {
return new AclClientRPCHook(new SessionCredentials(MqConfig.ACCESS_KEY, MqConfig.SECRET_KEY));
}
public static void main(String[] args) throws Exception {
DefaultMQProducer producer = new DefaultMQProducer(MqConfig.GROUP_ID, getAclRPCHook());
DefaultMQProducer producer = new DefaultMQProducer(MqConfig.GROUP_ID, getAclRPCHook(), true, null);
producer.setAccessChannel(AccessChannel.CLOUD);
producer.setNamesrvAddr(MqConfig.NAMESRV_ADDR);
producer.start();
// 普通消息
SendResult sendResult = producer.send(createMsg("student 放学了"));
producer.shutdown();
}
public static Message createMsg(String msgBody) throws UnsupportedEncodingException {
Message msg = new Message(MqConfig.TOPIC, MqConfig.TAG, msgBody.getBytes(RemotingHelper.DEFAULT_CHARSET));
msg.setBody("hello mq".getBytes());
return msg;
}
SendResult sendResult = producer.send(createMsg("student 放学了"));
当前线程需要等待mq返回插入消息的结果
// 普通消息,不会等待返回结果,可能出现失败的问题
producer.sendOneway(createMsg("普通消息-不会等待返回结果"));
问题:发送失败会不会丢失?
猜测:会
应用场景:日志收集(丢失部分无关紧要)
//方法 msg消息体 selector 选择queue的方法 arg:selector中需要的参数 orderId:就是select中的arg
public SendResult send(Message msg, MessageQueueSelector selector, Object arg)
public static void main(String[] args) throws Exception {
DefaultMQProducer producer = new DefaultMQProducer(MqConfig.ORDER_GROUP_ID, getAclRPCHook(), true, null);
producer.setAccessChannel(AccessChannel.CLOUD);
producer.setNamesrvAddr(MqConfig.NAMESRV_ADDR);
producer.start();
int orderId=100;
SendResult sendResult = producer.send(createMsg("分区顺序消息"), new MessageQueueSelector() {
@Override
public MessageQueue select(List mqs, Message msg, Object arg) {
Integer id = (Integer) arg;
int index = id % mqs.size();
return mqs.get(index);
}
}, orderId);
producer.shutdown();
}
public static Message createMsg(String msgBody) throws UnsupportedEncodingException {
Message msg = new Message(MqConfig.TOPIC, MqConfig.TAG, msgBody.getBytes(RemotingHelper.DEFAULT_CHARSET));
msg.setBody("hello mq".getBytes());
return msg;
}
将所有的数据放入同一个分区(一般分区有序就够使用了)
问题:无法并行的消费;broker故障转移影响的范围大
发送半消息并确认
private static RPCHook getAclRPCHook() {
return new AclClientRPCHook(new SessionCredentials(MqConfig.ACCESS_KEY, MqConfig.SECRET_KEY));
}
public static void main(String[] args) throws Exception {
/**
* 创建事务消息Producer
*/
TransactionMQProducer transactionMQProducer = new TransactionMQProducer(MqConfig.GROUP_ID, getAclRPCHook());
transactionMQProducer.setNamesrvAddr(MqConfig.NAMESRV_ADDR);
transactionMQProducer.setTransactionCheckListener(new LocalTransactionCheckerImpl());
transactionMQProducer.setAccessChannel(AccessChannel.CLOUD);
transactionMQProducer.start();
// 需要保存sendResult中的msgId,因为二次提交消息失败后,mq会通过回调方式查询结果
SendResult sendResult = transactionMQProducer.sendMessageInTransaction(createMsg("事务消息"), new LocalTransactionExecuter() {
@Override
public LocalTransactionState executeLocalTransactionBranch(Message msg, Object arg) {
System.out.println("开始执行本地事务: " + msg);
return LocalTransactionState.COMMIT_MESSAGE;
}
}, null);
}
public static Message createMsg(String msgBody) throws UnsupportedEncodingException {
Message msg = new Message(MqConfig.TOPIC, MqConfig.TAG, msgBody.getBytes(RemotingHelper.DEFAULT_CHARSET));
msg.setBody("hello mq".getBytes());
return msg;
}
mq回调反查本地事务结果
public class LocalTransactionCheckerImpl implements TransactionCheckListener {
@Override public LocalTransactionState checkLocalTransactionState(MessageExt msg) {
System.out.println("收到事务消息的回查请求, MsgId: " + msg.getMsgId());
return LocalTransactionState.COMMIT_MESSAGE;
}
}
实现与本地事务一致性的处理(如解决问题:本地事务回滚/未保存,但是向MQ提交的消息可能已经倍消费端消费;如果本地事务先提交,然后再发送到MQ,会出现丢失问题)
mq首先将消息放入半消息队列中,然后等待Producer再次确认是否提交。mq一直没有收到确认结果,会回调反查
db创建一张"事件表",将向mq发送的事务消息,insert到mysql。
开一个线程一直读取该表,然后向MQ同步写
定时清理已处理完成的事件,避免事件表过大导致查询慢
优点:简单、性能尚可
Message 对象属性
public class Message implements Serializable {
private String topic;
private int flag;
private Map properties;
private byte[] body;
private String transactionId;
}
flag
properties:其他属性(如:tags, keys)
body:消息体
transactionId: 使用事务消息时的相关字段
properties中重要的属性:
DELAY 延迟
WAIT 消息发送时是否等消息存储完成后再返回 (消息落到磁盘) 默认true
UNIQ_KEY 自定义msgId (不知道自定义后有什么作用,也不会触发幂等去重)
MessageClientIDSetter # createUniqID
原理:
建议:使用try-catch包裹发送的消息。 理由:如果mq客户端有bug,导致重试失败,我们自己的业务逻辑可以重试,重试失败,可以暂存DB
ps:曾经有一种异常,该异常的返回值code没有被重试机制捕捉到,导致重试机制失败
举例:我们通过eureka找到被调用的集群,但是调用哪一台呢??
feign实现负载均衡,通过轮询、权重、hash等算法确定本地调用哪一台服务
问题: 客户端的clientId重复,导致的问题
高流量下,导致某台broker堆积消息(其他broker肯能是空闲的),进而导致 producter发送消息增加延迟
2021 年 3 月 producter 的 InstanceName 加上纳秒的原因也是避免producter 生成相同的clientID
public DefaultMQProducer(String namespace, String producerGroup, RPCHook rpcHook) {
this.log = ClientLogger.getLog();
this.createTopicKey = "TBW102";
this.defaultTopicQueueNums = 4;
this.sendMsgTimeout = 3000; // 超时时间
this.compressMsgBodyOverHowmuch = 4096; //发送的消息压缩阈值4K
this.retryTimesWhenSendFailed = 2; // 同步消息重试次数
this.retryTimesWhenSendAsyncFailed = 2; // 异步消息重试次数
this.retryAnotherBrokerWhenNotStoreOK = false;
this.maxMessageSize = 4194304;
this.traceDispatcher = null;
this.namespace = namespace;
this.producerGroup = producerGroup;
this.defaultMQProducerImpl = new DefaultMQProducerImpl(this, rpcHook);
}
待续......