上篇講的是springboot整合rabbitmq實現延時隊列之TTL方式實現rabbitmq的延時隊列功能,在消息死亡時間比較靈活複雜的時候我們不可能聲明很多死信隊列去管理,而且聲明一個就要6個bean,很蛋疼,所以希望能夠有種方式使其消息死亡異步化,到期即死即消費,不會被阻塞,這裏介紹使用插件的方式,不過需要rabbitmq要是3.6版本以上,也就是說,加入你的rabbitmq版本太老只能用TTL。
基於插件方式實現流程:
這裏和TTL方式有個很大的不同就是TTL存放消息在死信隊列(delayqueue)裏,二基於插件存放消息在延時交換機裏(x-delayed-message exchange)。
①:生產者將消息(msg)和路由鍵(routekey)發送指定的延時交換機(exchange)上
②:延時交換機(exchange)存儲消息等待消息到期根據路由鍵(routekey)找到綁定自己的隊列(queue)並把消息給它
③:隊列(queue)再把消息發送給監聽它的消費者(customer)
下載的插件放到rabbitmq的plugins裏,執行命令安裝插件
rabbitmq-plugins enable rabbitmq_delayed_message_exchange
流程介紹完了,看下具體代碼吧!
- 1首先pom因爲依賴,和上一個一樣:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
- 2 配置文件配置rabbitmq的信息
# rabbitmq
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
spring.rabbitmq.virtual-host=/
# 手動ACK 不開啓自動ACK模式,目的是防止報錯後未正確處理消息丟失 默認 爲 none
spring.rabbitmq.listener.simple.acknowledge-mode=manual
- 3 編寫rabbitmq配置類,聲明幾個bean,我這裏的名字和上篇一樣,注意修改或者刪掉原來的
/**
* rabbitmq配置類
* 員工系統配置延時隊列
* @author zhanghang
* @date 2019/1/7
*/
@Configuration
public class RabbitUserConfig {
/**
* 延時隊列交換機
* 注意這裏的交換機類型:CustomExchange
* @return
*/
@Bean
public CustomExchange delayExchange(){
Map<String, Object> args = new HashMap<>();
args.put("x-delayed-type", "direct");
return new CustomExchange("delay_exchange","x-delayed-message",true, false,args);
}
/**
* 延時隊列
* @return
*/
@Bean
public Queue delayQueue(){
return new Queue("delay_queue",true);
}
/**
* 給延時隊列綁定交換機
* @return
*/
@Bean
public Binding cfgDelayBinding(Queue cfgDelayQueue,CustomExchange cfgUserDelayExchange){
return BindingBuilder.bind(cfgDelayQueue).to(cfgUserDelayExchange).with("delay_key").noargs();
}
}
- 4 編寫rabbitmq生產者:
/**
* rabbitMq生產者類
* @author zhanghang
* @date 2018/12/13
*/
@Component
@Slf4j
public class RabbitProduct{
@Autowired
private RabbitTemplate rabbitTemplate;
public void sendDelayMessage(List<Integer> list) {
//這裏的消息可以是任意對象,無需額外配置,直接傳即可
log.info("===============延時隊列生產消息====================");
log.info("發送時間:{},發送內容:{}", LocalDateTime.now(), list.toString());
this.rabbitTemplate.convertAndSend(
"delay_exchange",
"delay_key",
list,
message -> {
//注意這裏時間可以使long,而且是設置header
message.getMessageProperties().setHeader("x-delay",60000);
return message;
}
);
log.info("{}ms後執行", 60000);
}
- 5 編寫rabbitmq消費者:
/**
* activeMq消費者類
* @author zhanghang
* @date 2017/12/19
*/
@Component
@Slf4j
public class RabbitConsumer {
@Autowired
private CcqCustomerCfgService ccqCustomerCfgService;
/**
* 默認情況下,如果沒有配置手動ACK, 那麼Spring Data AMQP 會在消息消費完畢後自動幫我們去ACK
* 存在問題:如果報錯了,消息不會丟失,但是會無限循環消費,一直報錯,如果開啓了錯誤日誌很容易就吧磁盤空間耗完
* 解決方案:手動ACK,或者try-catch 然後在 catch 裏面將錯誤的消息轉移到其它的系列中去
* spring.rabbitmq.listener.simple.acknowledge-mode = manual
* @param list 監聽的內容
*/
@RabbitListener(queues = "delay_queue")
public void cfgUserReceiveDealy(List<Integer> list, Message message, Channel channel) throws IOException {
log.info("===============接收隊列接收消息====================");
log.info("接收時間:{},接受內容:{}", LocalDateTime.now(), list.toString());
//通知 MQ 消息已被接收,可以ACK(從隊列中刪除)了
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
try {
dosomething.....
} catch (Exception e) {
log.error("============消費失敗,嘗試消息補發再次消費!==============");
log.error(e.getMessage());
/**
* basicRecover方法是進行補發操作,
* 其中的參數如果爲true是把消息退回到queue但是有可能被其它的consumer(集羣)接收到,
* 設置爲false是隻補發給當前的consumer
*/
channel.basicRecover(false);
}
}
}
- 6 編寫測試類:
/**
* @author zhanghang
* @date 2019/1/3 17:57
*/
@RestController
@RequestMapping("/test")
public class TestController {
@Autowired
private RabbitProduct rabbitProduct;
@GetMapping("/sendMessage")
public void sendMessage(){
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
rabbitProduct.sendDelayMessage(list);
}
}