ons消息队列实战

ONS, 全名Open Notification Service, 是阿里基于开源消息中间件RocketMQ的一个云产品。

步骤一:开通服务

请按照以下步骤开通消息队列 RocketMQ 服务:

  1. 登录阿里云主页,将鼠标依次移动到产品 > 企业应用 > 消息队列 MQ ,单击消息队列 RocketMQ 进入消息队列 RocketMQ 的产品主页。

  2. 在消息队列 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。

一旦订阅关系不一致,消息消费的逻辑就会混乱,甚至导致消息丢失

创建消息主题(Topic)

消息类型说明:

  • 普通消息:无特性的消息,区分于事务消息、定时/延时消息和顺序消息。

  • 事务消息:提供类似 X/Open XA 的分布事务功能,能达到分布式事务的最终一致。

  • 定时/延时消息:可指定消息延迟投递,即在未来的某个特定时间点或一段特定的时间后进行投递。

  • 分区顺序消息:消息根据 sharding key 进行分区,提高整体并发度与使用性能。 同一个分区的消息严格按照 FIFO 的严格顺序进行生产和消费。

  • 全局顺序消息:所有消息严格按照 FIFO 的严格顺序进行生产和消费。

建议:您可创建不同的 Topic 来发送不同类型的消息,例如用 Topic A 发送普通消息,Topic B 发送事务消息,Topic C 发送延时/定时消息。

创建 Group ID

创建完实例和 Topic 后,您需要为消息的消费者(或生产者)创建客户端 ID ,即 Group ID。

说明:消费者必须有对应的 Group ID,生产者不做强制要求。

请按照以下步骤创建 Group ID:

  1. 在控制台左侧导航栏选择 Group 管理

  2. 在 Group 管理页面上方选择刚创建的实例。

  3. 根据您需使用的协议类型(TCP 或 HTTP),单击对应的页签。

    说明:TCP 协议 和 HTTP 协议下的 Group ID 不可以共用,因此需分别创建。

  4. 单击创建 Group ID

  5. 创建 Group ID 对话框中,输入 Group ID 和描述,然后单击确定

    注意:

    • Group ID 必须在同一实例中是唯一的。

    • Group ID 和 Topic 的关系是 N:N,即一个消费者可以订阅多个 Topic,同一个 Topic 也可以被多个消费者订阅;一个生产者可以向多个 Topic 发送消息,同一个 Topic 也可以接收来自多个生产者的消息。

步骤三:获取接入域名

在控制台创建好资源后,您还需要通过控制台获取实例或地域的接入点。在收发消息时,您需要为生产端和消费端配置该接入点,以此接入某个具体实例或地域的服务。接入点性质因协议不同而不同,具体说明如下:

  • TCP 协议:您在控制台看到的 TCP 协议接入点是地域下某个具体实例的接入点。同一地域下的不同实例的接入点各不相同。

  • HTTP 协议:您在控制台看到 HTTP 协议接入点是某个地域的接入点,跟具体实例无关。您在收发消息时还需另外设置实例 ID。

步骤四:发送消息

调用 TCP Java SDK 发送消息

  1. 通过下面两种方式可以引入依赖(任选一种):

    • Maven 方式引入依赖:

spring的配置文件中引入消息客户端配置文件

 xml中配置对应的topic,以及tag

aliyun官方文档:https://help.aliyun.com/document_detail/34411.html?spm=5176.11065259.1996646101.searchclickresult.1484c2f02oFFz7

Consumer使用详情

MqConsumerConfig


@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<Subscription, MessageListener> 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;
    }
}

MqListener

@Service
@Slf4j
public class MqListener implements MessageListener, InitializingBean {


    private Map<String, EventHandler> 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);
    }
}

EventHandler

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;

    }
}

Producer使用详情

MqClient

@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 <T> 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 <T> 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 <T> 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());
    }
}

MqProducerConfig 配置

@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;
    }


}

SimpleTransactionChecker

 /**
     * 回查本地事务,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;
    }


}

猜你喜欢

转载自blog.csdn.net/weixin_39082432/article/details/93757055