ONS, 全名Open Notification Service, 是阿里基于开源消息中间件RocketMQ的一个云产品。
请按照以下步骤开通消息队列 RocketMQ 服务:
登录阿里云主页,将鼠标依次移动到产品 > 企业应用 > 消息队列 MQ ,单击消息队列 RocketMQ 进入消息队列 RocketMQ 的产品主页。
在消息队列 RocketMQ 的产品主页上,单击立即开通进入消息队列 RocketMQ 服务开通页面,根据提示完成开通服务。
如果您已经开通消息队列 RocketMQ 服务,请直接登录消息队列 RocketMQ 控制台。
资源类型说明
一个新的应用接入消息队列 RocketMQ 需要先创建相关的消息队列 RocketMQ 资源,包括:
实例:用于消息队列 RocketMQ 服务的虚拟机资源,会存储消息主题(Topic)和客户端 ID(Group ID)信息。
消息主题(Topic):在消息队列 RocketMQ 的消息系统中,消息生产者将消息发送到某个指定的 Topic ,而消息消费者则通过订阅该指定的 Topic 来获取和消费消息。
Group ID:用于消息消费者(或生产者)的标识
阿里云 AccessKey:用于收发消息时进行账户鉴权
注意:当您删除某实例时,该实例中的所有 Topic 和 Group ID 也会在 10 分钟内被清理;若单独删除 Topic 或 Group ID,则不会对其他资源造成影响。
同一个GID(CID)的订阅逻辑必须完全一致,订阅的topic,tag。只要是同一个GID,所有实例订阅必须一致,不能出现一个GID,在实例A中订阅了2个topic,在实例B中订阅了1个topic。
一旦订阅关系不一致,消息消费的逻辑就会混乱,甚至导致消息丢失
消息类型说明:
普通消息:无特性的消息,区分于事务消息、定时/延时消息和顺序消息。
事务消息:提供类似 X/Open XA 的分布事务功能,能达到分布式事务的最终一致。
定时/延时消息:可指定消息延迟投递,即在未来的某个特定时间点或一段特定的时间后进行投递。
分区顺序消息:消息根据 sharding key 进行分区,提高整体并发度与使用性能。 同一个分区的消息严格按照 FIFO 的严格顺序进行生产和消费。
全局顺序消息:所有消息严格按照 FIFO 的严格顺序进行生产和消费。
建议:您可创建不同的 Topic 来发送不同类型的消息,例如用 Topic A 发送普通消息,Topic B 发送事务消息,Topic C 发送延时/定时消息。
创建完实例和 Topic 后,您需要为消息的消费者(或生产者)创建客户端 ID ,即 Group ID。
说明:消费者必须有对应的 Group ID,生产者不做强制要求。
请按照以下步骤创建 Group ID:
在控制台左侧导航栏选择 Group 管理。
在 Group 管理页面上方选择刚创建的实例。
根据您需使用的协议类型(TCP 或 HTTP),单击对应的页签。
说明:TCP 协议 和 HTTP 协议下的 Group ID 不可以共用,因此需分别创建。
单击创建 Group ID。
在创建 Group ID 对话框中,输入 Group ID 和描述,然后单击确定。
注意:
Group ID 必须在同一实例中是唯一的。
Group ID 和 Topic 的关系是 N:N,即一个消费者可以订阅多个 Topic,同一个 Topic 也可以被多个消费者订阅;一个生产者可以向多个 Topic 发送消息,同一个 Topic 也可以接收来自多个生产者的消息。
在控制台创建好资源后,您还需要通过控制台获取实例或地域的接入点。在收发消息时,您需要为生产端和消费端配置该接入点,以此接入某个具体实例或地域的服务。接入点性质因协议不同而不同,具体说明如下:
TCP 协议:您在控制台看到的 TCP 协议接入点是地域下某个具体实例的接入点。同一地域下的不同实例的接入点各不相同。
HTTP 协议:您在控制台看到 HTTP 协议接入点是某个地域的接入点,跟具体实例无关。您在收发消息时还需另外设置实例 ID。
调用 TCP Java SDK 发送消息
spring的配置文件中引入消息客户端配置文件
xml中配置对应的topic,以及tag
aliyun官方文档:https://help.aliyun.com/document_detail/34411.html?spm=5176.11065259.1996646101.searchclickresult.1484c2f02oFFz7
@Configuration
@Slf4j
public class MqConsumerConfig {
//可以通过阿波罗配置进行注入
@Value("${ons.accessKey}")
private String accessKey;
@Value("${ons.secretKey}")
private String secretKey;
@Value("${ons.nameSrvAddr}")
private String nameSrvAddr;
@Getter
@Value("${ons.accountGroupId}")
private String accountGroupId;
@Autowired
private MqListener mqListener;
@Autowired
private ConfigClient configClient;
@Bean(initMethod = "start", destroyMethod = "shutdown")
public ConsumerBean accountConsumer() {
log.info("begin start marketConsumer ");
ConsumerBean consumerBean = new ConsumerBean();
//配置文件
Properties properties = buildConsumerProperties();
properties.setProperty(PropertyKeyConst.GROUP_ID, accountGroupId);
properties.put(PropertyKeyConst.MessageModel, PropertyValueConst.CLUSTERING);
consumerBean.setProperties(properties);
//订阅关系
Map subscriptionTable = Maps.newHashMap();
{
Subscription subscription = new Subscription();
subscription.setTopic(configClient.getAccountTopic());
subscription.setExpression(MqEventCode.ACCOUNT_EARN);
subscriptionTable.put(subscription, mqListener);
}
consumerBean.setSubscriptionTable(subscriptionTable);
log.info("ons subscriptionTable {}", subscriptionTable);
log.info("end start accountConsumer ");
return consumerBean;
}
private Properties buildConsumerProperties() {
Properties properties = new Properties();
properties.setProperty(PropertyKeyConst.AccessKey, this.accessKey);
properties.setProperty(PropertyKeyConst.SecretKey, this.secretKey);
properties.setProperty(PropertyKeyConst.NAMESRV_ADDR, this.nameSrvAddr);
return properties;
}
}
@Service
@Slf4j
public class MqListener implements MessageListener, InitializingBean {
private Map eventHandlerMap = Maps.newConcurrentMap();
@Autowired
private ImpEventHandler impEventHandler;
@Override
public Action consume(Message message, ConsumeContext context) {
ThreadContext.put("traceId", UUID.randomUUID().toString());
String eventCode = message.getTag();
log.info("接收到消息 topic:{} eventCode:{} body:{} msgId:{}", message.getTopic(), eventCode,
new String(message.getBody()),
message.getMsgID());
try {
return Optional.ofNullable(eventHandlerMap.get(eventCode))
.map(eventHandler -> eventHandler.handleMsg(message))
.orElseGet(() -> {
log.error("不支持的eventCode:" + eventCode);
return Action.CommitMessage;
});
} catch (Exception ex) {
log.error("消息处理异常 eventCode:" + eventCode + " msgBody:" + new String(message.getBody()) + " message:" + message, ex);
throw new CommonSystemException("消息处理异常", "消息处理异常", ex);
} finally {
ThreadContext.remove("traceId");
}
}
@Override
public void afterPropertiesSet() throws Exception {
eventHandlerMap.put(MqEventCode.ACCOUNT_EARN, impEventHandler);
}
}
public interface EventHandler {
/**
* 处理消息
*
* @param message
*/
Action handleMsg(Message message);
}
@Slf4j
public class ImpEventHandler implements EventHandler {
@Override
public Action handleMsg(Message message) {
//执行service
return Action.CommitMessage;
}
}
@Service
public class MqClient {
private static final Logger logger = LoggerFactory.getLogger("MSG_LOGGER");
@Autowired
private ProducerBean producerBean;
@Autowired
private MqProducerConfig mqProducerConfig;
@Autowired
private TransactionProducerBean transactionProducerBean;
public void sendMsg(String topic, String eventCode, T msgBody) {
AssertUtil.notBlank(topic, () -> "topic empty");
AssertUtil.notBlank(eventCode, () -> "tag empty");
AssertUtil.notNull(msgBody, () -> "msgBody null");
Message message = new Message();
message.setTopic(topic);
message.setTag(eventCode);
if (msgBody instanceof String) {
message.setBody(((String) msgBody).getBytes());
} else {
message.setBody(JSON.toJSONString(msgBody).getBytes());
}
SendResult sendResult = producerBean.send(message);
logger.info("发送消息成功 {} {} {} ", topic, eventCode, sendResult.getMessageId());
}
public void sendTransactionMsg(String topic, String eventCode, T msgBody, LocalTransactionExecuter localTransactionExecuter
, Object arg) {
AssertUtil.notBlank(topic, () -> "topic empty");
AssertUtil.notBlank(eventCode, () -> "tag empty");
AssertUtil.notNull(msgBody, () -> "msgBody null");
AssertUtil.notNull(localTransactionExecuter, () -> "localTransactionExecuter null");
Message message = new Message();
message.setTopic(topic);
message.setTag(eventCode);
if (msgBody instanceof String) {
message.setBody(((String) msgBody).getBytes());
} else {
message.setBody(JSON.toJSONString(msgBody).getBytes());
}
message.putUserProperties(PropertyKeyConst.CheckImmunityTimeInSeconds, "60");
SendResult sendResult = transactionProducerBean.send(message, localTransactionExecuter, arg);
logger.info("发送消息成功 {} {} {}", topic, eventCode, sendResult.getMessageId());
}
public void sendDelayMsg(String topic, String eventCode, T msgBody, int delaySeconds) {
AssertUtil.notBlank(topic, () -> "topic empty");
AssertUtil.notBlank(eventCode, () -> "tag empty");
AssertUtil.notNull(msgBody, () -> "msgBody null");
AssertUtil.isTrue(delaySeconds >= 1, () -> "delaySeconds>=1");
Message message = new Message();
message.setTopic(topic);
message.setTag(eventCode);
if (msgBody instanceof String) {
message.setBody(((String) msgBody).getBytes());
} else {
message.setBody(JSON.toJSONString(msgBody).getBytes());
}
message.setStartDeliverTime(DateUtils.addSeconds(DateUtils.nowDate(), delaySeconds).getTime());
SendResult sendResult = producerBean.send(message);
logger.info("发送延时消息成功 {} {} {} ", topic, eventCode, sendResult.getMessageId());
}
}
@Configuration
public class MqProducerConfig {
@Value("${ons.accessKey}")
private String accessKey;
@Value("${ons.secretKey}")
private String secretKey;
@Value("${ons.nameSrvAddr}")
private String nameSrvAddr;
@Value("${ons.producerGroupId}")
private String producerGroupId;
@Autowired
private SimpleTransactionChecker simpleTransactionChecker;
@Bean(initMethod = "start", destroyMethod = "shutdown")
public ProducerBean buildProducer() {
ProducerBean producer = new ProducerBean();
producer.setProperties(buildProducerProperties());
return producer;
}
@Bean(initMethod = "start", destroyMethod = "shutdown")
public TransactionProducerBean buildTransactionProducer() {
TransactionProducerBean producer = new TransactionProducerBean();
producer.setProperties(buildProducerProperties());
producer.setLocalTransactionChecker(simpleTransactionChecker);
return producer;
}
private Properties buildProducerProperties() {
Properties properties = new Properties();
properties.setProperty(PropertyKeyConst.AccessKey, this.accessKey);
properties.setProperty(PropertyKeyConst.SecretKey, this.secretKey);
properties.setProperty(PropertyKeyConst.NAMESRV_ADDR, this.nameSrvAddr);
properties.setProperty(PropertyKeyConst.GROUP_ID, this.producerGroupId);
return properties;
}
}
/**
* 回查本地事务,Broker回调Producer,将未结束的事务发给Producer,由Producer来再次决定事务是提交还是回滚
*
* @param msg 消息
* @return {@link TransactionStatus} 事务状态, 包含提交事务、回滚事务、未知状态
*/
@Service
@Slf4j
public class SimpleTransactionChecker implements LocalTransactionChecker {
@Override
public TransactionStatus check(Message msg) {
Date bornTime = new Date(msg.getBornTimestamp());
log.info("LocalTransactionChecker 消息回调 {} {} {}", msg, new String(msg.getBody()),
DateUtils.toString(bornTime));
if (DateUtils.diffSeconds(bornTime, DateUtils.nowDate()) >= Globals.NUM_10) {
return TransactionStatus.CommitTransaction;
}
return TransactionStatus.Unknow;
}
}