RocketMQ
新公司的消息隊列用的是RocketMQ,但是不是直接使用RocketMq,而是採用了阿里分佈式開放消息服務(ONS)
一、阿里分佈式開放消息服務(ONS)
ONS(Open Notification Service)即開放消息服務,是基於阿里開源消息中間件MetaQ(RocketMQ)打造的一款雲消息產品。實現生產者與消費者的代碼稍微和原生的有點差別
二、阿里雲中配置RocketMQ
獲取下面配置文件中的namesrv_addr,access_key,secret_key 參數
- 註冊阿里雲,然後搜索 消息隊列RocketMQ
- 購買了一個便宜的資源包
- 返回主頁創建一個實例
- 如果你是基於tcp發送MQ,則將namesrv_addr設置爲這個公網接入點
- 進入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);
}
}
六、整體加載流程與其參數解釋
-
AliMqConfig類被@Configuration標籤,將生產者/消費者配置完整參數(ons地址,access_key等參數)後,對生產者/消費者實體用@bean標籤,將他們bean動態代理,方便方法中調用生產者/消費者bean。
參數如下:
ProducerBean 生產者類,實現Producer接口。
Properties:生產者/消費者參數列表(本質是個HashTable),會在生產者/消費者實例啓動服務的時候,調用ONSFactory.createProducer()方法,將Properties參數傳入,並且返回一個Producer
ONS生產者/消費者參數如下:
注:
@Configuration修飾的類被在加載中定義成Spring容器(相當於老版本的xml文件中的)
@Bean 修飾的返回實例(上文中的ProducerBean和ConsumerBean)方法 默認爲單例作用域 -
AliMqComponent類: 是用於將AliMqConfig中加載的生產者/消費者bean,將拼接好的Message放入返回bean的send方法中,實現消息的發送
生產者bean發送消息
Message參數如下:
-
消費者:與生產者在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初始化條件 -
消費者監聽實現類
返回的Action的參數