消息隊列(RocketMQ):springcloud結合ONS實現Tcp/Http通信方式的生產者和消費者實例

RocketMQ

新公司的消息隊列用的是RocketMQ,但是不是直接使用RocketMq,而是採用了阿里分佈式開放消息服務(ONS)

一、阿里分佈式開放消息服務(ONS)

ONS(Open Notification Service)即開放消息服務,是基於阿里開源消息中間件MetaQ(RocketMQ)打造的一款雲消息產品。實現生產者與消費者的代碼稍微和原生的有點差別

二、阿里雲中配置RocketMQ

獲取下面配置文件中的namesrv_addr,access_key,secret_key 參數

  1. 註冊阿里雲,然後搜索 消息隊列RocketMQ
  2. 購買了一個便宜的資源包
  3. 返回主頁創建一個實例在這裏插入圖片描述
  4. 如果你是基於tcp發送MQ,則將namesrv_addr設置爲這個公網接入點在這裏插入圖片描述
  5. 進入access管理獲取access_key和access_secret在這裏插入圖片描述

三、創建topic和group

Topic:生產者在發送消息和消費者在拉取消息的類別 可以看作每個路的路名
Tag:標籤,換句話的意思就是子主題,爲用戶提供了額外的靈活性。有了標籤,來自同一業務模塊(同一topic)的具有不同目的的消息可以具有相同的主題和不同的標記
Group:生產/消費組,在集羣情況下,新增一臺服務,只要配置與之前一致的Group即可加入集羣

阿里雲中配置:
在這裏插入圖片描述

四、實現代碼

下文中所有代碼在git地址中

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.1.7.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.example</groupId>
	<artifactId>demo</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>war</packaging>
	<name>demo</name>
	<description>Demo project for Spring Boot</description>

	<properties>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>

		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>fastjson</artifactId>
			<version>1.2.47</version>
		</dependency>

		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-web</artifactId>
			<version>5.1.6.RELEASE</version>
		</dependency>
		<!--rocketmq  用阿里雲的ons去調用rocketmq-->
		<dependency>
			<groupId>com.aliyun.openservices</groupId>
			<artifactId>ons-client</artifactId>
			<version>1.8.4.Final</version>
		</dependency>
	</dependencies>


	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

配置文件

server:
    port: 8090
#進入阿里雲官網,註冊一下,然後購買一下RocketMQ服務,就能看到地址
aliyun:
    mq:
        namesrv_addr: http://MQ_INST_213213213221_BccQ0pjs.mq-internet-access.mq-internet.aliyuncs.com:80 #阿里雲RocketMQ地址
        access_key: LTAI4GJhu8qPHPZTssssssseFD  #阿里雲個人中心的accesskey
        secret_key: X9Vdllp3ssssssssssssssss   #阿里雲個人中心的secret_key
        groupId: GID_TCP_001  #在
        topic: ww_test_topic
        tag: ww_test_tag
        suspend_time_millis: 100
        max_reconsume_times: 20

將生產者和消費者的ProducerBean和ConsumerBean納入Spring管理,Configuration

package com.example.demo.config.mq;

import com.aliyun.openservices.ons.api.MessageListener;
import com.aliyun.openservices.ons.api.ONSFactory;
import com.aliyun.openservices.ons.api.Producer;
import com.aliyun.openservices.ons.api.PropertyKeyConst;
import com.aliyun.openservices.ons.api.bean.ConsumerBean;
import com.aliyun.openservices.ons.api.bean.ProducerBean;
import com.aliyun.openservices.ons.api.bean.Subscription;
import com.example.demo.mq.AliMqComponent;
import com.example.demo.mq.rece.MqMessageListener;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.UUID;

/**
 * @Author ww
 * @Date 2020-04-27
 */
@Configuration
public class AliMqConfig {

    @Value("${aliyun.mq.namesrv_addr}")
    private String namesrvAddr;

    @Value("${aliyun.mq.suspend_time_millis}")
    private String suspendTimeMillis;

    @Value("${aliyun.mq.max_reconsume_times}")
    private String maxReconsumeTimes;
    @Value("${aliyun.mq.access_key}")
    private String accessKey;
    @Value("${aliyun.mq.secret_key}")
    private String secretKey;

    @Value("${aliyun.mq.groupId}")
    private String groupId;
    @Value("${aliyun.mq.topic}")
    private String topic;
    @Value("${aliyun.mq.tag}")
    private String tag;
    /**
 *      指定組建的init方法和destroy的幾種方法
 *      在new該類的時候 會自動執行initMethod
 *      1:在配置類中 @Bean(initMethod = "init",destroyMethod = "destory")註解指定
 *      2:實現InitializingBean接口重寫其afterPropertiesSet方法,實現DisposableBean接口重寫destroy方法
 *      3:利用java的JSR250規範中的@PostConstruct標註在init方法上,@PreDestroy標註在destroy註解上
 */

    /**
     *  tcp 註冊生產者Bean
     *
     * @return
     */
    @Bean(initMethod = "start", destroyMethod = "shutdown")
    public ProducerBean getProducer() {
        ProducerBean producerBean = new ProducerBean();
        Properties properties = new Properties();
        properties.put(PropertyKeyConst.GROUP_ID, groupId);
        // AccessKey 阿里雲身份驗證,在阿里雲服務器管理控制檯創建
        properties.put(PropertyKeyConst.AccessKey, accessKey);
        // SecretKey 阿里雲身份驗證,在阿里雲服務器管理控制檯創建
        properties.put(PropertyKeyConst.SecretKey, secretKey);
        // 設置 TCP 接入域名,進入控制檯的實例管理頁面的“獲取接入點信息”區域查看
        properties.put(PropertyKeyConst.NAMESRV_ADDR, namesrvAddr);
        producerBean.setProperties(properties);
        return producerBean;
    }

    /**
     * tcp 生產者
     *
     * @return
     */
    @Bean(initMethod = "start", destroyMethod = "shutdown")
    public ProducerBean getCommonProducer() {
        ProducerBean producerBean = new ProducerBean();
        Properties properties = new Properties();
        properties.put(PropertyKeyConst.GROUP_ID, groupId);
        // AccessKey 阿里雲身份驗證,在阿里雲服務器管理控制檯創建
        properties.put(PropertyKeyConst.AccessKey, accessKey);
        // SecretKey 阿里雲身份驗證,在阿里雲服務器管理控制檯創建
        properties.put(PropertyKeyConst.SecretKey, secretKey);
        // 設置 TCP 接入域名,進入控制檯的實例管理頁面的“獲取接入點信息”區域查看
        properties.put(PropertyKeyConst.NAMESRV_ADDR, namesrvAddr);
        properties.put(PropertyKeyConst.InstanceName, UUID.randomUUID().toString());
        producerBean.setProperties(properties);
        return producerBean;
    }

    /**
     * tcp 消費者  MqMessageListener沒有放入Spring管理 所以在中間無法直接依賴注入。需要以前放在接口裏面
     *
     * @param
     * @return
     */
    @Bean(initMethod = "start", destroyMethod = "shutdown")
    @ConditionalOnBean(name = "aliMqComponent") //如果需要注入其他的類或者接口 需要放在@ConditionalOnBean標籤
    public ConsumerBean getCommonConsumer(AliMqComponent aliMqComponent) {
        ConsumerBean consumerBean = new ConsumerBean();
        Properties properties = new Properties();
        properties.put(PropertyKeyConst.GROUP_ID, groupId);
        // AccessKey 阿里雲身份驗證,在阿里雲服務器管理控制檯創建
        properties.put(PropertyKeyConst.AccessKey, accessKey);
        // SecretKey 阿里雲身份驗證,在阿里雲服務器管理控制檯創建
        properties.put(PropertyKeyConst.SecretKey, secretKey);
        // 設置 TCP 接入域名,進入控制檯的實例管理頁面的“獲取接入點信息”區域查看
        properties.put(PropertyKeyConst.NAMESRV_ADDR, namesrvAddr);
        consumerBean.setProperties(properties);
        //組裝訂閱者消息
        Map<Subscription, MessageListener> map = new HashMap<Subscription, MessageListener>();
        Subscription subscription = new Subscription();
        subscription.setTopic(topic);//設置需要消費的消息所屬的topic
        subscription.setExpression(tag);//設置需要消費的消息所屬的tag
        map.put(subscription, new MqMessageListener(aliMqComponent));//MqMessageListener實現MessageListener接口,並且在consume方法中實現消費邏輯
        consumerBean.setSubscriptionTable(map);//將訂閱者消息放入consumerBean中,在Spring初始加載該bean的時候,監聽MQ中的Topic和tag下的消息
        return consumerBean;
    }

    @Autowired
    MqMessageSpringListener mqMessageListener;
    /**
     * tcp 消費者  將MqMessageListener放入Spring管理 可以在邏輯裏依賴注入其他類也可以獲取配置文件
     *
     * @param
     * @return
     */
    @Bean(initMethod = "start", destroyMethod = "shutdown")
    public ConsumerBean getCommonConsumer() {
        ConsumerBean consumerBean = new ConsumerBean();
        Properties properties = new Properties();
        properties.put(PropertyKeyConst.GROUP_ID, groupId);
        // AccessKey 阿里雲身份驗證,在阿里雲服務器管理控制檯創建
        properties.put(PropertyKeyConst.AccessKey, accessKey);
        // SecretKey 阿里雲身份驗證,在阿里雲服務器管理控制檯創建
        properties.put(PropertyKeyConst.SecretKey, secretKey);
        // 設置 TCP 接入域名,進入控制檯的實例管理頁面的“獲取接入點信息”區域查看
        properties.put(PropertyKeyConst.NAMESRV_ADDR, namesrvAddr);
        consumerBean.setProperties(properties);
        //組裝訂閱者消息
        Map<Subscription, MessageListener> map = new HashMap<Subscription, MessageListener>();
        Subscription subscription = new Subscription();
        subscription.setTopic(topic);//設置需要消費的消息所屬的topic
        subscription.setExpression(tag);//設置需要消費的消息所屬的tag
        map.put(subscription, mqMessageListener);//MqMessageListener實現MessageListener接口,並且在consume方法中實現消費邏輯
        consumerBean.setSubscriptionTable(map);//將訂閱者消息放入consumerBean中,在Spring初始加載該bean的時候,監聽MQ中的Topic和tag下的消息
        return consumerBean;
    }

}

消費者邏輯

package com.example.demo.mq.rece;

import com.aliyun.openservices.ons.api.Action;
import com.aliyun.openservices.ons.api.ConsumeContext;
import com.aliyun.openservices.ons.api.Message;
import com.aliyun.openservices.ons.api.MessageListener;
import com.example.demo.mq.AliMqComponent;

/**
 * 消費者消費邏輯
 * 該監聽被裝載消費者bean中 consumer.setSubscriptionTable()
 * @Author ww
 * @Date 2020-04-27
 */
public class MqMessageListener implements MessageListener {

    private AliMqComponent aliMqComponent;
    public MqMessageListener(AliMqComponent aliMqComponent) {
        this.aliMqComponent=aliMqComponent;
    }

    @Override
    public Action consume(Message message, ConsumeContext consumeContext) {

        try {
            String msg = new String(message.getBody(),"UTF-8");
            System.out.println("消息消費成功:"+msg);
            return Action.ReconsumeLater;
            // }
        } catch (Exception e) {
            return Action.ReconsumeLater;
        } finally {
        }
        // return null;
    }


}

發送消息類(1.獲取producer 2.將topic和tag設置在Message 3.調用produce.send(Message))

package com.example.demo.mq;

import com.aliyun.openservices.ons.api.Message;
import com.aliyun.openservices.ons.api.Producer;
import com.aliyun.openservices.ons.api.SendResult;
import com.example.demo.config.mq.AliMqConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * @Author ww
 * @Date 2020-04-27
 */
@Component
public class AliMqComponent {

    @Autowired
    private AliMqConfig aliMqConfig;

    /**
     * 普通發送消息
     *
     * @param topic 主題
     * @param tag   多個用| * 代表所有
     * @param key
     * @param body
     */
    public SendResult sendMessage(String topic, String tag, String key, byte[] body) {
        Producer producer = aliMqConfig.getProducer();
        Message msg = new Message(topic, tag, body);
        msg.setKey(key);
        try {
            SendResult sendResult = producer.send(msg);
            if (null != sendResult) {
                return sendResult;
            }
            return null;
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }

    /**
         * 發送延時消息
         *
         * @param topic     主題
         * @param tag       多個用| * 代表所有
         * @param key
         * @param body      內容
         * @param delayTime 延時時間,單位毫秒
         * @return
         */
        public SendResult sendDelayMessage(String topic, String tag, String key, byte[] body, long delayTime) {
            Producer producer = aliMqConfig.getProducer();
            Message msg = new Message(topic, tag, body);
            msg.setKey(key);
            msg.setStartDeliverTime(delayTime);
            try {
                SendResult sendResult = producer.send(msg);
                if (null != sendResult) {
                    return sendResult;
                }
                return null;
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
    }

    /**
     * 普通發送信息
     * @param key
     * @param body
     * @return
     */
    public String sendCommonMessage(String topic, String tag, String key, byte[] body) {
        Producer producer = aliMqConfig.getCommonProducer();
        Message msg = new Message(topic, tag, body);
        msg.setKey(key);
        try {
            SendResult sendResult = producer.send(msg);
            if (null != sendResult) {
                return sendResult.getMessageId();
            }
            return null;
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("xxx");
        }

        return null;
    }


}

在同一個項目中生產消息發送到MQ和從MQ中消費該條消息

package com.example.demo.controller;

import com.alibaba.fastjson.JSON;
import com.example.demo.entity.SubmitUserPayOrderRequest;
import com.example.demo.entity.UserPayOrder;
import com.example.demo.mq.AliMqComponent;
import com.example.demo.service.UserPayOrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController{
    @Autowired
    private UserPayOrderService userPayOrderService;

    @Autowired
    private AliMqComponent aliMqComponent;
    /**
     * 根據訂單號獲取商戶id
     * @param userOrderId 訂單id
     * @return
     */
    @PostMapping("/userOrderMerchantId")
    public String userOrderMerchantId(@RequestParam("userOrderId") String userOrderId){
        UserPayOrder vo=userPayOrderService.userOrderMerchantId(userOrderId);
        //1.向MQ中topic=ww_test_topic tag:ww_test_tag 發送一條消息
        String messageId = aliMqComponent.sendCommonMessage("ww_test_topic","ww_test_tag","ssss",JSON.toJSONBytes(vo));
        //同時MqMessageListener從MQ中監聽到消息隊列有數據,然後執行消費邏輯
        //結果爲:消息消費成功:{"amountOfConsumption":0.01,"channelId":3,"createTime":1576115647000,"discountAmount":0.00,"freeAmount":0.00,"id":"20191212095406497_232109_3042","merchantId":811001,"merchantReceiveAmount":0.01,"payAbleAmount":0.01,"payTime":1576115648000,"payType":0,"status":1,"updateTime":1576115648000,"userPayId":232109}
        return JSON.toJSONString(vo);
    }
  
}

五、Http通信方式的生產者和消費者

pom.xml

		<!-- MQ Http通信方式依賴-->
		<dependency>
			<groupId>com.aliyun.mq</groupId>
			<artifactId>mq-http-sdk</artifactId>
			<version>1.0.1</version>
		</dependency>

application.yml

aliyun:
    mq:
        http:
            instanceId: xx
            access_key: xx
            secret_key: xx
            http_addr: http://cccccccccccccccccccccccccccc.mq-internet-access.mq-internet.aliyuncs.com:80
            group_id: GID_TCP_001
            topic: ww_test_topic
            tag: ww_test_tag

AliMqConfig.java

package com.example.demo.config.mq;

import com.aliyun.mq.http.MQClient;
import com.aliyun.mq.http.MQConsumer;
import com.aliyun.mq.http.MQProducer;
import com.aliyun.mq.http.common.AckMessageException;
import com.aliyun.mq.http.model.Message;
import com.aliyun.mq.http.model.TopicMessage;
import com.aliyun.openservices.ons.api.MessageListener;
import com.aliyun.openservices.ons.api.PropertyKeyConst;
import com.aliyun.openservices.ons.api.bean.ConsumerBean;
import com.aliyun.openservices.ons.api.bean.ProducerBean;
import com.aliyun.openservices.ons.api.bean.Subscription;
import com.example.demo.mq.AliMqComponent;
import com.example.demo.mq.rece.MqMessageListener;
import com.example.demo.mq.rece.MqMessageSpringListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.*;

/**
 * @Author ww
 * @Date 2020-04-27
 */
@Configuration
public class AliMqConfig {

    /**
     * Http 跨平臺
     */
    @Value("${aliyun.mq.http.instanceId}")
    private String instanceId;//實例id
    @Value("${aliyun.mq.http.access_key}")
    private String httpAccessKey;
    @Value("${aliyun.mq.http.secret_key}")
    private String httpSecretKey;
    @Value("${aliyun.mq.http.http_addr}")
    private String httpHttpAddr;//http地址
    /**
     * topic、group、tag
     */
    @Value("${aliyun.mq.http.group_id}")
    private String httpGroupId;
    @Value("${aliyun.mq.http.topic}")
    private String httpTopic;
    @Value("${aliyun.mq.http.tag}")
    private String httpTag;




    /**
     * Http發送mq消息
     *
     * @param requestContext 發送內容
     * @param tag
     * @return
     */
    public String sendMessageByHttp(String requestContext, String tag) {
        MQClient mqClient = new MQClient(httpHttpAddr,  httpAccessKey, secretKey);
        MQProducer producer = mqClient.getProducer(instanceId, httpSecretKey);
        TopicMessage topicMessage = new TopicMessage(
                requestContext.getBytes(),
                tag
        );
        TopicMessage pubResultMsg = producer.publishMessage(topicMessage);
        String messageId = pubResultMsg.getMessageId();
        mqClient.close();
        return messageId;
    }
    /**
     * Http消費mq消息
     * //@Bean(initMethod = "start", destroyMethod = "shutdown")註釋是因爲沒有消息 所以會一直報錯
     * @param
     * @return
     */
    //@Bean(initMethod = "start", destroyMethod = "shutdown")
    public ConsumerBean getCxUserSynchronizeUserPlatform() {
        MQClient mqClient = new MQClient(httpHttpAddr, httpAccessKey, httpSecretKey);
        MQConsumer consumer = mqClient.getConsumer(instanceId, httpTopic, httpGroupId, null);

        // 在當前線程循環消費消息,建議是多開個幾個線程併發消費消息
        do {
            List<Message> messages = null;
            try {
                // 長輪詢消費消息
                // 長輪詢表示如果topic沒有消息則請求會在服務端掛住3s,3s內如果有消息可以消費則立即返回
                messages = consumer.consumeMessage(
                        3,// 一次最多消費3條(最多可設置爲16條)
                        3// 長輪詢時間3秒(最多可設置爲30秒)
                );
            } catch (Throwable e) {
                e.printStackTrace();
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e1) {
                    e1.printStackTrace();
                }
            }
            // 沒有消息
            if (messages == null || messages.isEmpty()) {
                continue;
            }

            // 處理業務邏輯
            for (Message message : messages) {
                try {
                    //將傳輸正文轉換成utf-8的json字符串
                    String msg = new String(message.getMessageBodyBytes(), "utf-8");
                }catch (Exception e){

                }

            }

            // Message.nextConsumeTime前若不確認消息消費成功,則消息會重複消費
            // 消息句柄有時間戳,同一條消息每次消費拿到的都不一樣
            {
                List<String> handles = new ArrayList<String>();
                for (Message message : messages) {
                    handles.add(message.getReceiptHandle());
                }

                try {
                    consumer.ackMessage(handles);
                } catch (Throwable e) {
                    // 某些消息的句柄可能超時了會導致確認不成功
                    if (e instanceof AckMessageException) {
                        AckMessageException errors = (AckMessageException) e;
                        System.out.println("Ack message fail, requestId is:" + errors.getRequestId() + ", fail handles:");
                        if (errors.getErrorMessages() != null) {
                            for (String errorHandle : errors.getErrorMessages().keySet()) {
                                System.out.println("Handle:" + errorHandle + ", ErrorCode:" + errors.getErrorMessages().get(errorHandle).getErrorCode()
                                        + ", ErrorMsg:" + errors.getErrorMessages().get(errorHandle).getErrorMessage());
                            }
                        }
                        continue;
                    }
                    e.printStackTrace();
                }
            }
        } while (true);
    }

}

這樣會阻塞主線程,所以需要重新開一個子線程去處理。

package com.cx.user.config.mq;

import com.alibaba.fastjson.JSONObject;
import com.aliyun.mq.http.MQClient;
import com.aliyun.mq.http.MQConsumer;
import com.aliyun.mq.http.common.AckMessageException;
import com.aliyun.mq.http.model.Message;
import com.cx.user.service.UserPayOrderService;
import com.xz.log.utils.LogTrace;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import java.util.ArrayList;
import java.util.List;

/**
 * HTTP消費者 新開線程去消費消息。
 *
 * @param
 * @return
 */
@Configuration
public class SyCxOrderInfoHttpMqConsumer implements ServletContextListener {
    /**
     * Http 跨平臺
     */
    @Value("${aliyun.mq.http.instanceId}")
    private String instanceId;//實例id
    @Value("${aliyun.mq.http.access_key}")
    private String httpAccessKey;
    @Value("${aliyun.mq.http.secret_key}")
    private String httpSecretKey;
    @Value("${aliyun.mq.http.http_addr}")
    private String httpHttpAddr;//http地址
    /**
     * topic、group、tag
     */
    @Value("${aliyun.mq.http.group_id}")
    private String httpGroupId;
    @Value("${aliyun.mq.http.topic}")
    private String httpTopic;
    @Value("${aliyun.mq.http.tag}")
    private String httpTag;

    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
    	/**
    	* 在項目加載的時候去新建一個線程去處理
    	*/
        for (int i = 1; i <= 2; i++) {
            int finalI = i;
            new Thread(() -> cxtConsumer(finalI)).start();
        }

    }

    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {

    }

    public void cxtConsumer(int index) {


        MQClient mqClient = new MQClient(scoreHttpAddr, scoreAccessKey, scoreSecretKey);
        MQConsumer consumer = mqClient.getConsumer(instanceId, scoreOrderTopic, scoreGroupId, null);

        // 在當前線程循環消費消息,建議是多開個幾個線程併發消費消息
        do {
            List<Message> messages = null;
            try {
                // 長輪詢消費消息
                // 長輪詢表示如果topic沒有消息則請求會在服務端掛住3s,3s內如果有消息可以消費則立即返回
                messages = consumer.consumeMessage(
                        3,// 一次最多消費3條(最多可設置爲16條)
                        3// 長輪詢時間3秒(最多可設置爲30秒)
                );
            } catch (Throwable e) {
                e.printStackTrace();
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e1) {
                    e1.printStackTrace();
                }
            }
            // 沒有消息
            if (messages == null || messages.isEmpty()) {
                continue;
            }

            // 處理業務邏輯
            messages.forEach(message ->(System.out.println("xxx")));

            // Message.nextConsumeTime前若不確認消息消費成功,則消息會重複消費
            // 消息句柄有時間戳,同一條消息每次消費拿到的都不一樣
            {
                List<String> handles = new ArrayList<>();
                messages.forEach(message -> handles.add(message.getReceiptHandle()));
                try {
                    consumer.ackMessage(handles);
                } catch (Throwable e) {
                    // 某些消息的句柄可能超時了會導致確認不成功
                    if (e instanceof AckMessageException) {
                        AckMessageException errors = (AckMessageException) e;
                        LogTrace.error("消費者積分服務消息", errors);
                        continue;
                    }
                    e.printStackTrace();
                }
            }
        } while (true);

    }
}

六、整體加載流程與其參數解釋

  1. AliMqConfig類被@Configuration標籤,將生產者/消費者配置完整參數(ons地址,access_key等參數)後,對生產者/消費者實體用@bean標籤,將他們bean動態代理,方便方法中調用生產者/消費者bean。
    參數如下:
    ProducerBean 生產者類,實現Producer接口。
    在這裏插入圖片描述
    Properties:生產者/消費者參數列表(本質是個HashTable),會在生產者/消費者實例啓動服務的時候,調用ONSFactory.createProducer()方法,將Properties參數傳入,並且返回一個Producer在這裏插入圖片描述
    ONS生產者/消費者參數如下:
    在這裏插入圖片描述
    注:
    @Configuration修飾的類被在加載中定義成Spring容器(相當於老版本的xml文件中的)
    @Bean 修飾的返回實例(上文中的ProducerBean和ConsumerBean)方法 默認爲單例作用域

  2. AliMqComponent類: 是用於將AliMqConfig中加載的生產者/消費者bean,將拼接好的Message放入返回bean的send方法中,實現消息的發送
    生產者bean發送消息
    在這裏插入圖片描述
    Message參數如下:
    在這裏插入圖片描述

  3. 消費者:與生產者在AliMqConfig中配置bean不同。消費者需要在配置好GROUP_ID、AccessKey、SecretKey、NAMESRV_ADDR等參數後去組裝一個訂閱者的,並且將需要消費的消息的Topic和tag放在Subscription(訂閱參數)和MessageListener(消息監聽類,一般消費者類都實現該接口,將消費邏輯放在consume方法中)
    在這裏插入圖片描述
    注:
    @Bean(initMethod = “start”, destroyMethod = “shutdown”) 會在對象創建完成,賦值完成的時候調用initMethod的的方法,也就是Producer/Consumer的start()方法,在該容器(bean所屬的類)被關閉後會調用destroyMethod
    單實例bean和多實例bean的區別
    a.單實例是容器啓動後創建,多實例是獲取時創建對象
    b.初始化initMethod 一樣 而銷燬不同 單實例是容器關閉調用銷燬方法 多實例是手動調用銷燬方式
    @ConditionOnBean 條件註解是Spring4提供的一種bean加載特性,主要用於控制配置類和bean初始化條件

  4. 消費者監聽實現類在這裏插入圖片描述
    返回的Action的參數
    在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章