入门使用
实际工作中使用阿里云rocketmq时,是通过底层工具类引入;工具类涉及定义通用mq消息模型,生产者,消费者,以及消息发送的方法。
0 工具类首先需要添加rocketmq java客户端依赖
<dependency>
<groupId>com.aliyun.openservices</groupId>
<artifactId>ons-client</artifactId>
<version>1.8.8.8.Final</version>
</dependency>
1 定义通用的消息
也就是发送消息时的消息对象
public class MqMessage implements Serializable {
private static final long serialVersionUID = 5686153644127179622L;
/**
* 标签,一个业务点一个
*/
private String tag;
/**
* 发送的消息体
*/
private byte[] body;
/**
* key 用来阿里云后台检索消息
*/
private String key;
/**
* 延迟时间,毫秒值
*/
private long delayTime;
private MqMessage(){}
....
public class MqMsg implements Serializable {
private static final long serialVersionUID = -1688230884284293203L;
/**
* 通用参数
*/
private String userId;
private Date serviceTime;
private String uniqueId;
private boolean refresh;
/**
* mq消息体body 不同业务消息体不同,可定义不同的java PO处理
*/
private Map<String, Object> param;
public MqMsg() {
}
2 创建自定义的生产者
生产者不直接对外提供,发送消息的工具类使用自定义的生产者
public class MyProducer {
private static final Logger logger = LoggerFactory.getLogger(MyProducer.class);
private static Producer producer;
private static Config CFG = new ConfigImpl();
/**
* Spring bean init-method
*/
public static Producer getDefaultMQProducer() {
if(producer == null){
synchronized (MyProducer.class) {
Properties properties = new Properties();
properties.put(PropertyKeyConst.AccessKey, CFG.getString(ConfigConstants.ROCKETMQ_ACCESSKEY));// AccessKey 阿里云身份验证,在阿里云服务器管理控制台创建
properties.put(PropertyKeyConst.SecretKey, CFG.getString(ConfigConstants.ROCKETMQ_SECRETKEY));// SecretKey 阿里云身份验证,在阿里云服务器管理控制台创建
properties.put(PropertyKeyConst.ONSAddr,CFG.getString(ConfigConstants.ROCKETMQ_ONSADDR));//mq实例访问地址
properties.put(PropertyKeyConst.ProducerId, CFG.getString(ConfigConstants.ROCKETMQ_PRODUCERGROUP));//您在控制台创建的Producer ID
properties.setProperty(PropertyKeyConst.SendMsgTimeoutMillis, "3000");
producer = ONSFactory.createProducer(properties);
producer.start();
logger.info("DefaultMQProudcerV2 start success! {}", properties.get(PropertyKeyConst.AccessKey));
}
}
return producer;
}
/**
* Spring bean destroy-method
*/
public static void destroy() {
logger.info("rocketmq Producer destroy!");
if(producer!=null){
producer.shutdown();
logger.info("rocketmq Producer destroy success!");
producer = null;
}
}
}
3 工具类创建自定义的消费者
public class MyConsumer {
private static final Logger logger = LoggerFactory.getLogger(MyConsumer.class);
private MessageListener listener;
private static Consumer consumer;
public void init(InitInfo initInfo){
Properties properties = new Properties();
properties.put(PropertyKeyConst.ONSAddr, initInfo.getoNSAddr());
properties.put(PropertyKeyConst.AccessKey, initInfo.getAccessKey());
properties.put(PropertyKeyConst.SecretKey, initInfo.getSecretKey());
properties.put(PropertyKeyConst.GROUP_ID, initInfo.getGroupID());
properties.setProperty(PropertyKeyConst.ConsumeThreadNums,String.valueOf(initInfo.getConsumeThreadNums()));
properties.setProperty(PropertyKeyConst.MaxReconsumeTimes, String.valueOf(initInfo.getMaxReconsumeTimes()));
properties.setProperty(PropertyKeyConst.MessageModel, MessageModel.CLUSTERING.name()); //指定消费者模式 集群或广播
consumer = ONSFactory.createConsumer(properties);
String[] topicTags = initInfo.getTopictag().split(",");
for (String topicTag : topicTags) {
String[] s = topicTag.split(":");
// 订阅消息
//参数1:topic – 消息主题
//参数2:subExpression – 订阅过滤表达式字符串,ONS服务器依据此表达式进行过滤。只支持或运算 eg: "tag1 || tag2 || tag3" 如果subExpression等于null或者*,则表示全部订阅
//参数3:listener – 消息回调监听器
consumer.subscribe(s[0], s[1], listener);
}
consumer.start();
logger.info("###### MQ consumer started, topiocTags - {} ######", initInfo.getTopictag());
Thread shutdownHookOne = new Thread() {
@Override
public void run() {
MyConsumer.destroy();
}
};
Runtime.getRuntime().addShutdownHook(shutdownHookOne);
}
public static void destroy() {
logger.info("rocketmq Consumer shutdown!");
if(consumer!=null){
consumer.shutdown();
logger.info("rocketmq Consumer shutdown success!");
}
}
public MessageListener getListener() {
return listener;
}
public void setListener(MessageListener listener) {
this.listener = listener;
}
}
4 定义发消息工具类
public class MqUtil {
/**
* 阿里提供的生产者
*/
private static Producer producer;
private static Logger log = LoggerFactory.getLogger(cn.org.bjca.ywq.common.base.mq.rocketmqV2.MqUtil.class);
private static Config cfg = new ConfigImpl();
public MqUtil() {
producer = MyProducer.getDefaultMQProducer();
}
/**
* <p>
* sendMsgAsync
* </p>
*
* @param msg
* @Description:异步发消息
*/
public static void sendMsgAsync(String topic, final MqMessage msg) {
//topic 后缀环境
String topicEnv = cfg.getString(ConfigConstants.ROCKETMQ_TOPIC_ENV);
if (StringUtils.isBlank(topicEnv)) {
throw new BizException(GlobalExcpEnum.TOPIC_ENV_EMPTY);
}
topic = topic + topicEnv;
Message msgs = new Message(topic, msg.getTag(), msg.getKey(), msg.getBody());
long delayTime = msg.getDelayTime();
if (delayTime > 0){
long millis = System.currentTimeMillis();
msgs.setStartDeliverTime(millis + delayTime);
}
try {
if (producer == null) {
producer = MyProducer.getDefaultMQProducer();
}
producer.sendAsync(msgs, new SendCallback() {
@Override
// 成功的回调函数
public void onSuccess(SendResult sendResult) {
log.debug("send message success. topic:{}, msgId:{}, tag:{}, key:{}", sendResult.getTopic(),
sendResult.getMessageId(), msg.getTag(), msg.getKey());
}
@Override
public void onException(OnExceptionContext context) {
log.error("send message failed. topic:{}, msgId:{}, tag:{}, key:{} " , context.getTopic(),
context.getMessageId(), msg.getTag(), msg.getKey());
log.error("mq send exception", context.getException());
}
});
} catch (ONSClientException e) {
log.error("TMS-MQ发消息异常", e);
throw new RuntimeException("MQ发消息异常");
}
}
public void destroy(){
MyProducer.destroy();
}
}
5 应用端启动类mq配置
@Configuration
public class MqConfig {
private static Logger log = LoggerFactory.getLogger(MqConfig.class);
@Autowired
private ConfigNacosImpl config4Nacos;
@Resource
private Config conf;
/**
* 初始化自动签消费者监听
* @return
*/
@Bean
public MyConsumer initAAAAConsumer() {
String topicEnv = conf.getString(ConfigConstants.ROCKETMQ_TOPIC_ENV);
if (StringUtils.isBlank(topicEnv)) {
throw new BizException(GlobalExcpEnum.TOPIC_ENV_EMPTY);
}
MyConsumer myConsumer = new MyConsumer();
//配置所在dataId = es_ywx
String onsAddr = config4Nacos.getString(ConfigConstants.ROCKETMQ_ONSADDR.getName(), "");
String accessKey = conf.getString(ConfigConstants.ROCKETMQ_ACCESSKEY);
String secretKey = conf.getString(ConfigConstants.ROCKETMQ_SECRETKEY);
String consumerGroupID =
config4Nacos.getString(ConfigConstants.ROCKETMQ_CONSUMERGROUP.getName(), "") + RecipeInfoMQCons.SELF_SIGN + "_" + topicEnv;
String topicTag = config4Nacos.getString(ConfigConstants.ROCKETMQ_TOPICTAG_SELFSIGN.getName(), "");
int consumeThreadNums = config4Nacos.getInt(ConfigConstants.ROCKETMQ_CONSUMER_THREAD_NUMS_SELF_SIGN.getName(), 10);
int maxReconsumeTimes = config4Nacos.getInt(ConfigConstants.ROCKETMQ_MAX_RECONSUME_TIMES.getName(), 16);
myConsumer.setListener(AAAAListener()); //设置消息监听处理类
myConsumer.init(new InitInfo(onsAddr, accessKey, secretKey, consumerGroupID, topicTag, consumeThreadNums,
maxReconsumeTimes));
log.info("selfSign消费者初始化成功:groupID:{}, topicTag:{}", consumerGroupID, topicTag);
return myConsumer;
}
/**
* 初始化mq生产者组件 用于业务发消息
* @return
*/
@Bean(destroyMethod = "destroy")
public MqUtil initMqProducer() {
log.info("rocketmq release!");
return new MqUtil();
}
@Bean
public AAAAListener aAAAListener(){
//
return new AAAAListener ();
}
}
6 应用端发送消息(根据不同业务,消息体也不一样,但流程大概一致)
//创建自定义消息体
***Msg = new ...
// es异步写入消息
public void sendEsAdd(String orderId, Integer signType, String userId, String clientId, boolean isTimeStamp, ***Msg **msg) {
MqMsg mqMsg = new MqMsg();
mqMsg.setRefresh(false);
mqMsg.setUserId(userId);
mqMsg.setServiceTime(new Date());
Map param = new HashMap();
mqMsg.setParam(param);
mqMsg.getParam().put(MqParamKeyEnum....getMapKey(), **msg);
String jsonString = JsonUtils.toJSONString(mqMsg);
MqMessage mes = new MqMessage(MqTagEnum.ADD.getType(), jsonString, orderId);
String topicName = MqTopicEnum.****.getTopicName();
try {
MqUtil.sendMsgAsync(topicName, mes);
} catch (Exception e) {
log.error("发送ES写消息失败sendEsCoolAddMq recipeIds#{}#", orderId, e);
}
}
7 应用端消费消息Listener
public class AAAAListener implements MessageListener {
private static Logger log = LoggerFactory.getLogger(AAAAListener.class);
/**
* 第几次重试 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17
* 与上次重试间隔 10s, 30s, 1m, 2m, 3m, 4m, 5m, 6m, 7m, 8m, 9m, 10m, 20m, 30m, 1h, 2h, 2h
*
*/
@Override
public Action consume(Message message, ConsumeContext context) {
String topic = message.getTopic();
String tag = message.getTag();
String key = message.getKey();
MqMsg mqMsg = JSONObject.parseObject(message.getBody(), MqMsg.class);
Map<String, Object> param = mqMsg.getParam();
try {
boolean isOk = false;
// isOk = 消费逻辑处理
if (isOk){
return Action.CommitMessage;
}else {
log.error("self_mq消费失败 topic:{}, tag:{}, key:{}, 次数:{}, body:{} ", topic, tag, key, message.getReconsumeTimes(),JSONObject.toJSONString(mqMsg));
return Action.ReconsumeLater;
}
}catch (Exception e){
log.error("消费异常 topic:{}, tag:{}, key:{}, 次数上限:{}, body:{}", topic, tag, key, message.getReconsumeTimes(),JSONObject.toJSONString(mqMsg));
return Action.ReconsumeLater;
}
}
消费失败可以设置重试次数,阿里云官方文档描述的重试策略如下图所示
生产问题
某个节点的mq堆积了。。
疑问点
1 看阿里云日志,消费者日志从凌晨就没有了,listener挂了?为什么?
尚未找到原因,java进程已重启,当时的线程堆栈也无法jstack分析了...
2 为什么消息还会一直往这个消费者放,不应该将消息分发到其他节点的消费么?
这个只能说自己技术不佳,使用rocketmq不够熟练,基本概念没搞清楚,消费者模式有2种,集群消费和广播消费,默认不配置的话就是集群消费。
阿里云官方文档这样描述:
云消息队列 RocketMQ 版的消费方式支持在消费者客户端修改,您需要在订阅消息的SDK代码中设置相关参数。若未设置,则默认使用集群消费方式。
初始化消费者时,可以指定消息消费模式 properties.setProperty(PropertyKeyConst.MessageModel, MessageModel.CLUSTERING.name()); //指定消费者模式 集群或广播
补充下官网集群消费的概念
未完待续,需要学习。。。有知情的同学欢迎留言指教 感谢:)