一、消息隊列介紹
1、消息隊列概念
1、MQ全稱爲Message Queue,消息隊列(MQ)是⼀種應⽤程序對應⽤程序的通信⽅法。應⽤程序通過讀寫出⼊隊列的消息(針對應⽤程序的數據)來通信,⽽⽆需專⽤連接來鏈接它們。2、消息傳遞指的是程序之間通過在消息中發送數據進⾏通信,⽽不是通過直接調⽤彼此來通信,直接調⽤通常是⽤於諸如遠程過程調⽤的技術。
2、常⽤的消息隊列產品
1、RabbitMQ 穩定可靠,數據⼀致,⽀持多協議,有消息確認,基於erlang語⾔2、Kafka ⾼吞吐,⾼性能,快速持久化,⽆消息確認,⽆消息遺漏,可能會有有重複消息,依賴於zookeeper,成本⾼.3、ActiveMQ 不夠靈活輕巧,對隊列較多情況⽀持不好.4、RocketMQ 性能好,⾼吞吐,⾼可⽤性,⽀持⼤規模分佈式,協議⽀持單⼀
⼆、RabbitMQ
1、RabbitMQ介紹
1、RabbitMQ是⼀個在AMQP基礎上完成的,可復⽤的企業消息系統。他遵循MozillaPublic License開源協議。2、AMQP,即Advanced Message Queuing Protocol, ⼀個提供統⼀消息服務的應⽤層標準⾼級消息隊列協議,是應⽤層協議的⼀個開放標準,爲⾯向消息的中間件設計。基於此協議的客戶端與消息中間件可傳遞消息,並不受客戶端/中間件不同產品,不同的開發語⾔等條件的限制。Erlang中的實現有 RabbitMQ等。3、主要特性:
- 保證可靠性 :使⽤⼀些機制來保證可靠性,如持久化、傳輸確認、發佈確認
- 靈活的路由功能
- 可伸縮性:⽀持消息集羣,多臺RabbitMQ服務器可以組成⼀個集羣
- ⾼可⽤性 :RabbitMQ集羣中的某個節點出現問題時隊列仍然可⽤
- ⽀持多種協議
- ⽀持多語⾔客戶端
- 提供良好的管理界⾯
- 提供跟蹤機制:如果消息出現異常,可以通過跟蹤機制分析異常原因
- 提供插件機制:可通過插件進⾏多⽅⾯擴展
2、RabbitMQ安裝和配置
3、RabbitMQ邏輯結構
三、RabbitMQ⽤戶管理
RabbitMQ默認提供了⼀個guests賬號,但是此賬號不能⽤作遠程登錄,也就是不能在管理系統的登錄;我們可以創建⼀個新的賬號並授予響應的管理權限來實現遠程登錄
1、邏輯結構
⽤戶虛擬主機隊列
2、⽤戶管理
2.1、命令⾏⽤戶管理
1、在linux中使⽤命令⾏創建⽤戶
## 進⼊到rabbit_mq的sbin⽬錄 cd /usr/local/rabbitmq_server-3.7.0/sbin ## 新增⽤戶 ./rabbitmqctl add_user ytao admin1232、設置⽤戶級別
## ⽤戶級別: ## 1.administrator 可以登錄控制檯、查看所有信息、可以對RabbitMQ進⾏管理 ## 2.monitoring 監控者 登錄控制檯、查看所有信息 ## 3.policymaker 策略制定者 登錄控制檯、指定策略 ## 4.managment 普通管理員 登錄控制檯 ./rabbitmqctl set_user_tags ytao administrator
2.2、管理系統進⾏⽤戶管理
管理系統登錄:訪問http://localhost:15672/
四、RabbitMQ⼯作⽅式
RabbitMQ提供了多種消息的通信⽅式—⼯作模式 https://www.rabbitmq.com/getstarted.html消息通信是由兩個⻆⾊完成:消息⽣產者(producer)和 消息消費者(Consumer)
1、簡單模式
⼀個隊列只有⼀個消費者
2、⼯作模式
多個消費者監聽同⼀個隊列
3、訂閱模式
⼀個交換機綁定多個消息隊列,每個消息隊列有⼀個消費者監聽
4、路由模式
⼀個交換機綁定多個消息隊列,每個消息隊列都由⾃⼰唯⼀的key,每個消息隊列有⼀個消費者監聽
五、RabbitMQ交換機和隊列管理
1、創建隊列
2、創建交換機
3、交換機綁定隊列
六、在普通的Maven應⽤中使⽤MQ
1、簡單模式
1.1、消息⽣產者
1、創建Maven項⽬
2、添加RabbitMQ連接所需要的依賴
<!-- https://mvnrepository.com/artifact/com.rabbitmq/amqp-client --> <dependency> <groupId>com.rabbitmq</groupId> <artifactId>amqp-client</artifactId> <version>4.10.0</version> </dependency> <!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-log4j12 --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.25</version> <scope>test</scope> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.commons/commonslang3 --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.9</version> </dependency>3、在resources⽬錄下創建log4j.properties
log4j.rootLogger=DEBUG,A1 log4j.logger.com.taotao = DEBUG log4j.logger.org.mybatis = DEBUG log4j.appender.A1=org.apache.log4j.ConsoleAppender log4j.appender.A1.layout=org.apache.log4j.PatternLayout log4j.appender.A1.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss,SSS} [%t] [%c]-[%p] %m%n4、創建MQ連接工具類
import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; import java.io.IOException; import java.util.concurrent.TimeoutException; public class ConnectionUtil { public static Connection getConnection() throws IOException, TimeoutException { //1.創建連接⼯⼚ ConnectionFactory factory = new ConnectionFactory(); //2.在⼯⼚對象中設置MQ的連接信息 (ip,port,virtualhost,username,password) factory.setHost("47.96.11.185"); factory.setPort(5672); factory.setVirtualHost("host1"); factory.setUsername("ytao"); factory.setPassword("admin123"); //3.通過⼯⼚對象獲取與MQ的鏈接 Connection connection = factory.newConnection(); return connection; } }5、消息⽣產者發送消息
import com.qfedu.mq.utils.ConnectionUtil; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; public class SendMsg { public static void main(String[] args) throws Exception{ String msg = "Hello HuangDaoJun!"; Connection connection = ConnectionUtil.getConnection(); Channel channel = connection.createChannel(); //定義隊列(使⽤Java代碼在MQ中新建⼀個隊列) //參數1:定義的隊列名稱 //參數2:隊列中的數據是否持久化(如果選擇了持久化) //參數3: 是否排外(當前隊列是否爲當前連接私有) //參數4:⾃動刪除(當此隊列的連接數爲0時,此隊列會銷燬(⽆論隊列中是否還有數據)) //參數5:設置當前隊列的參數 //channel.queueDeclare("queue7",false,false,false,null); //參數1:交換機名稱,如果直接發送信息到隊列,則交換機名稱爲"" //參數2:⽬標隊列名稱 //參數3:設置當前這條消息的屬性(設置過期時間 10) //參數4:消息的內容 channel.basicPublish("","queue1",null,msg.getBytes()); System.out.println("發送:" + msg); channel.close(); connection.close(); } }
1.2、消息消費者
1、創建Maven項⽬2、添加依賴3、log4j.properties4、ConnetionUtil.java5、消費者消費消息import com.qfedu.mq.utils.ConnectionUtil; import com.rabbitmq.client.*; import java.io.IOException; import java.util.concurrent.TimeoutException; public class ReceiveMsg { public static void main(String[] args) throws IOException, TimeoutException { Connection connection = ConnectionUtil.getConnection(); Channel channel = connection.createChannel(); Consumer consumer = new DefaultConsumer(channel){ @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { //body就是從隊列中獲取的數據 String msg = new String(body); System.out.println("接收:"+msg); } }; channel.basicConsume("queue1",true,consumer); } }
2、⼯作模式
⼀個發送者多個消費者
2.1、發送者
public class SendMsg { public static void main(String[] args) throws Exception{ System.out.println("請輸⼊消息:"); Scanner scanner = new Scanner(System.in); String msg = null; while(!"quit".equals(msg = scanner.nextLine())){ Connection connection = ConnectionUtil.getConnection(); Channel channel = connection.createChannel(); channel.basicPublish("","queue2",null,msg.getBytes()); System.out.println("發送:" + msg); channel.close(); connection.close(); } } }
2.2、消費者1
public class ReceiveMsg { public static void main(String[] args) throws Exception { Connection connection = ConnectionUtil.getConnection(); Channel channel = connection.createChannel(); Consumer consumer = new DefaultConsumer(channel){ @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { //body就是從隊列中獲取的數據 String msg = new String(body); System.out.println("Consumer1接收:"+msg); if("wait".equals(msg)){ try { Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } } } }; channel.basicConsume("queue2",true,consumer); } }
2.3、消費者2
public class ReceiveMsg { public static void main(String[] args) throws IOException, TimeoutException { Connection connection = ConnectionUtil.getConnection(); Channel channel = connection.createChannel(); Consumer consumer = new DefaultConsumer(channel){ @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { //body就是從隊列中獲取的數據 String msg = new String(body); System.out.println("Consumer2接收:"+msg); } }; channel.basicConsume("queue2",true,consumer); } }
3、訂閱模式
1、發送者 發送消息到交換機
public class SendMsg { public static void main(String[] args) throws Exception{ System.out.println("請輸⼊消息:"); Scanner scanner = new Scanner(System.in); String msg = null; while(!"quit".equals(msg = scanner.nextLine())){ Connection connection = ConnectionUtil.getConnection(); Channel channel = connection.createChannel(); channel.basicPublish("ex1","",null,msg.getBytes()); System.out.println("發送:" + msg); channel.close(); connection.close(); } } }
2、消費者1
public class ReceiveMsg1 { public static void main(String[] args) throws Exception { Connection connection = ConnectionUtil.getConnection(); Channel channel = connection.createChannel(); Consumer consumer = new DefaultConsumer(channel){ @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { //body就是從隊列中獲取的數據 String msg = new String(body); System.out.println("Consumer1接收:"+msg); if("wait".equals(msg)){ try { Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } } } }; channel.basicConsume("queue3",true,consumer); } }
3、消費者2
public class ReceiveMsg2 { public static void main(String[] args) throws IOException, TimeoutException { Connection connection = ConnectionUtil.getConnection(); Channel channel = connection.createChannel(); Consumer consumer = new DefaultConsumer(channel){ @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { //body就是從隊列中獲取的數據 String msg = new String(body); System.out.println("Consumer2接收:"+msg); } } ; channel.basicConsume("queue4",true,consumer); } }
4、路由模式
1、發送者 發送消息到交換機
public class SendMsg { public static void main(String[] args) throws Exception{ System.out.println("請輸⼊消息:"); Scanner scanner = new Scanner(System.in); String msg = null; while(!"quit".equals(msg = scanner.nextLine())){ Connection connection = ConnectionUtil.getConnection(); Channel channel = connection.createChannel(); if(msg.startsWith("a")){ channel.basicPublish("ex2","a",null,msg.getBytes()); } else if(msg.startsWith("b")){ channel.basicPublish("ex2","b",null,msg.getBytes()); } System.out.println("發送:" + msg); channel.close(); connection.close(); } } }
2、消費者1
public class ReceiveMsg1 { public static void main(String[] args) throws Exception { Connection connection = ConnectionUtil.getConnection(); Channel channel = connection.createChannel(); Consumer consumer = new DefaultConsumer(channel){ @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { //body就是從隊列中獲取的數據 String msg = new String(body); System.out.println("Consumer1接收:"+msg); if("wait".equals(msg)){ try { Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } } } }; channel.basicConsume("queue5",true,consumer); } }
3、消費者2
public class ReceiveMsg2 { public static void main(String[] args) throws IOException, TimeoutException { Connection connection = ConnectionUtil.getConnection(); Channel channel = connection.createChannel(); Consumer consumer = new DefaultConsumer(channel){ @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { //body就是從隊列中獲取的數據 String msg = new String(body); System.out.println("Consumer2接收:"+msg); } }; channel.basicConsume("queue6",true,consumer); } }
七、在SpringBoot應⽤中使⽤MQ
SpringBoot應⽤可以完成⾃動配置及依賴注⼊——可以通過Spring直接提供與MQ的連接對象
1、消息⽣產者
1、創建SpringBoot應⽤,添加依賴
2、配置application.yml
server: port: 9001 spring: application: name: producer rabbitmq: host: 47.96.11.185 port: 5672 virtual-host: host1 username: ytao password: admin1233、發送消息
@Service public class TestService { @Resource private AmqpTemplate amqpTemplate; public void sendMsg(String msg){ //1. 發送消息到隊列 amqpTemplate.convertAndSend("queue1",msg); //2. 發送消息到交換機(訂閱交換機) amqpTemplate.convertAndSend("ex1","",msg); //3. 發送消息到交換機(路由交換機) amqpTemplate.convertAndSend("ex2","a",msg); } }
2、消息消費者
1、創建項⽬添加依賴2、配置yml3、接收消息@Service //@RabbitListener(queues = {"queue1","queue2"}) @RabbitListener(queues = "queue1") public class ReceiveMsgService { @RabbitHandler public void receiveMsg(String msg){ System.out.println("接收MSG:"+msg); } }
⼋、使⽤RabbitMQ傳遞對象
RabbitMQ是消息隊列,發送和接收的都是字符串/字節數組類型的消息
1、使⽤序列化對象
要求:傳遞的對象實現序列化接⼝傳遞的對象的包名、類名、屬性名必須⼀致
1、消息提供者
@Service public class MQService { @Resource private AmqpTemplate amqpTemplate; public void sendGoodsToMq(Goods goods){ //消息隊列可以發送 字符串、字節數組、序列化對象 amqpTemplate.convertAndSend("","queue1",goods); } }2、消息消費者
@Component @RabbitListener(queues = "queue1") public class ReceiveService { @RabbitHandler public void receiveMsg(Goods goods){ System.out.println("Goods---"+goods); } }
2、使⽤序列化字節數組
要求:傳遞的對象實現序列化接⼝傳遞的對象的包名、類名、屬性名必須⼀致
1、消息提供者
@Service public class MQService { @Resource private AmqpTemplate amqpTemplate; public void sendGoodsToMq(Goods goods){ //消息隊列可以發送 字符串、字節數組、序列化對象 byte[] bytes = SerializationUtils.serialize(goods); amqpTemplate.convertAndSend("","queue1",bytes); } }2、消息消費者
@Component @RabbitListener(queues = "queue1") public class ReceiveService { @RabbitHandler public void receiveMsg(byte[] bs){ Goods goods = (Goods) SerializationUtils.deserialize(bs); System.out.println("byte[]---"+goods); } }
3、使⽤JSON字符串傳遞
要求:對象的屬性名⼀直
1、消息提供者
@Service public class MQService { @Resource private AmqpTemplate amqpTemplate; public void sendGoodsToMq(Goods goods) throws JsonProcessingException { //消息隊列可以發送 字符串、字節數組、序列化對象 ObjectMapper objectMapper = new ObjectMapper(); String msg = objectMapper.writeValueAsString(goods); amqpTemplate.convertAndSend("","queue1",msg); } }2、消息消費者
@Component @RabbitListener(queues = "queue1") public class ReceiveService { @RabbitHandler public void receiveMsg(String msg) throws JsonProcessingException { ObjectMapper objectMapper = new ObjectMapper(); Goods goods = objectMapper.readValue(msg,Goods.class); System.out.println("String---"+msg); } }
九、基於Java的交換機與隊列創建
我們使⽤消息隊列,消息隊列和交換機可以通過管理系統完成創建,也可以在應⽤程序中通過Java代碼來完成創建
1、普通Maven項⽬交換機及隊列創建
1、使⽤Java代碼新建隊列
//1.定義隊列 (使⽤Java代碼在MQ中新建⼀個隊列) //參數1:定義的隊列名稱 //參數2:隊列中的數據是否持久化(如果選擇了持久化) //參數3: 是否排外(當前隊列是否爲當前連接私有) //參數4:⾃動刪除(當此隊列的連接數爲0時,此隊列會銷燬(⽆論隊列中是否還有數據)) //參數5:設置當前隊列的參數 channel.queueDeclare("queue7",false,false,false,null);2、新建交換機
//定義⼀個“訂閱交換機” channel.exchangeDeclare("ex3", BuiltinExchangeType.FANOUT); //定義⼀個“路由交換機” channel.exchangeDeclare("ex4", BuiltinExchangeType.DIRECT);3、綁定隊列到交換機
//綁定隊列 //參數1:隊列名稱 //參數2:⽬標交換機 //參數3:如果綁定訂閱交換機參數爲"",如果綁定路由交換機則表示設置隊列的key channel.queueBind("queue7","ex4","k1"); channel.queueBind("queue8","ex4","k2");
2、SpringBoot應⽤中通過配置完成隊列的創建
@Configuration public class RabbitMQConfiguration { //聲明隊列 @Bean public Queue queue9(){ Queue queue9 = new Queue("queue9"); //設置隊列屬性 return queue9; } @Bean public Queue queue10(){ Queue queue10 = new Queue("queue10"); //設置隊列屬性 return queue10; } //聲明訂閱模式交換機 @Bean public FanoutExchange ex5(){ return new FanoutExchange("ex5"); } //聲明路由模式交換機 @Bean public DirectExchange ex6(){ return new DirectExchange("ex6"); } //綁定隊列 @Bean public Binding bindingQueue9(Queue queue9, DirectExchange ex6){ return BindingBuilder.bind(queue9).to(ex6).with("k1"); } @Bean public Binding bindingQueue10(Queue queue10, DirectExchange ex6){ return BindingBuilder.bind(queue10).to(ex6).with("k2"); } }
⼗、消息的可靠性
消息的可靠性:從 ⽣產者發送消息 —— 消息隊列存儲消息 —— 消費者消費消息 的整個過程中消息的安全性及可控性。
- ⽣產者
- 消息隊列
- 消費者
1、RabbitMQ事務
RabbitMQ事務指的是基於客戶端實現的事務管理,當在消息發送過程中添加了事務,處理效率降低⼏⼗倍甚⾄上百倍
Connection connection = RabbitMQUtil.getConnection(); //connection 表示與 host1的連接 Channel channel = connection.createChannel(); channel.txSelect();//開啓事務 try{ channel.basicPublish("ex4", "k1", null, msg.getBytes()); channel.txCommit();//提交事務 } catch (Exception e){ channel.txRollback();//事務回滾 } finally{ channel.close(); connection.close(); }
2、RabbitMQ消息確認和return機制
1、消息確認機制:確認消息提供者是否成功發送消息到交換機2、return機制:確認消息是否成功的從交換機分發到隊列
2.1、普通Maven項⽬的消息確認
1、普通confirm⽅式
//1.發送消息之前開啓消息確認 channel.confirmSelect(); channel.basicPublish("ex1", "a", null, msg.getBytes()); //2.接收消息確認 Boolean b = channel.waitForConfirms(); System.out.println("發送:" +(b?"成功":"失敗"));2、批量confirm⽅式
//1.發送消息之前開啓消息確認 channel.confirmSelect(); //2.批量發送消息 for (int i=0 ; i<10 ; i++){ channel.basicPublish("ex1", "a", null, msg.getBytes()); } //3.接收批量消息確認:發送的所有消息中,如果有⼀條是失敗的,則所有消息發送直接失敗,拋出IO異常 Boolean b = channel.waitForConfirms();3、異步confirm⽅式
//發送消息之前開啓消息確認 channel.confirmSelect(); //批量發送消息 for (int i=0 ; i<10 ; i++){ channel.basicPublish("ex1", "a", null, msg.getBytes()); } //假如發送消息需要10s,waitForConfirms會進⼊阻塞狀態 //boolean b = channel.waitForConfirms(); //使⽤監聽器異步confirm channel.addConfirmListener(new ConfirmListener() { //參數1: long l 返回消息的表示 //參數2: boolean b 是否爲批量confirm public void handleAck(long l, Boolean b) throws IOException { System.out.println("~~~~~消息成功發送到交換機"); } public void handleNack(long l, Boolean b) throws IOException { System.out.println("~~~~~消息發送到交換機失敗"); } } );
2.2、普通Maven項⽬的return機制
1、添加return監聽器2、發送消息是指定第三個參數爲true3、由於監聽器監聽是異步處理,所以在消息發送之後不能關閉channelString msg = "Hello HuangDaoJun!"; Connection connection = ConnectionUtil.getConnection(); //相當於JDBC操作的數據庫連接 Channel channel = connection.createChannel(); //相當於JDBC操作的statement //return機制:監控交換機是否將消息分發到隊列 channel.addReturnListener(new ReturnListener() { public void handleReturn(int i, String s, String s1, String s2,AMQP.BasicProperties basicProperties,byte[] bytes) throws IOException { //如果交換機分發消息到隊列失敗,則會執⾏此⽅法(⽤來處理交換機分發消息到隊列失敗的情況) System.out.println("*****"+i);//標識 System.out.println("*****"+s);// System.out.println("*****"+s1);//交換機名 System.out.println("*****"+s2);//交換機對應的隊列的key System.out.println("*****"+new String(bytes));//發送的消息 } } ); //發送消息 //channel.basicPublish("ex2", "c", null, msg.getBytes()); channel.basicPublish("ex2", "c", true, null, msg.getBytes());
2.3、在SpringBoot應⽤實現消息確認與return監聽
1、配置application.yml,開啓消息確認和return監聽
spring: rabbitmq: publisher-confirm-type: simple ## 開啓消息確認模式 publisher-returns: true ##使⽤return監聽機制2、創建confirm和return監聽
2.1、消息確認
@Component public class MyConfirmListener implements RabbitTemplate.ConfirmCallback { @Autowired private AmqpTemplate amqpTemplate; @Autowired private RabbitTemplate rabbitTemplate; @PostConstruct public void init(){ rabbitTemplate.setConfirmCallback(this); } @Override public void confirm(CorrelationData correlationData, Boolean b, String s) { //參數b 表示消息確認結果 //參數s 表示發送的消息 if(b){ System.out.println("消息發送到交換機成功!"); } else{ System.out.println("消息發送到交換機失敗!"); amqpTemplate.convertAndSend("ex4","",s); } } }2.2、return機制
@Component public class MyReturnListener implements RabbitTemplate.ReturnsCallback { @Autowired private AmqpTemplate amqpTemplate; @Autowired private RabbitTemplate rabbitTemplate; @PostConstruct public void init(){ rabbitTemplate.setReturnsCallback(this); } @Override public void returnedMessage(ReturnedMessage returnedMessage) { System.out.println("消息從交換機分發到隊列失敗"); String exchange = returnedMessage.getExchange(); String routingKey = returnedMessage.getRoutingKey(); String msg = returnedMessage.getMessage().toString(); amqpTemplate.convertAndSend(exchange,routingKey,msg); } }
3、RabbitMQ消費者⼿動應答
@Component @RabbitListener(queues="queue01") public class Consumer1 { @RabbitHandler public void process(String msg,Channel channel, Message message) throws IOException { try { System.out.println("get msg1 success msg = "+msg); /** * 確認⼀條消息:<br> * channel.basicAck(deliveryTag, false); <br> * deliveryTag:該消息的index <br> * multiple:是否批量.true:將⼀次性ack所有⼩於deliveryTag的消息 <br> */ channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); } catch (Exception e) { //消費者處理出了問題,需要告訴隊列信息消費失敗 /** * 拒絕確認消息:<br> * channel.basicNack(long deliveryTag, boolean multiple, boolean requeue) ; <br> * deliveryTag:該消息的index<br> * multiple:是否批量.true:將⼀次性拒絕所有⼩於deliveryTag的消息。<br> * requeue:被拒絕的是否重新⼊隊列 <br> */ channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true); System.err.println("get msg1 failed msg = "+msg); } } }
4、消息消費的冪等性問題
消息消費的冪等性——多次消費的執⾏結果時相同的 (避免重複消費)解決⽅案:處理成功的消息setnx到redis
⼗⼀、延遲機制
1、延遲隊列
1、延遲隊列——消息進⼊到隊列之後,延遲指定的時間才能被消費者消費2、AMQP協議和RabbitMQ隊列本身是不⽀持延遲隊列功能的,但是可以通過TTL(Time To Live)特性模擬延遲隊列的功能3、TTL就是消息的存活時間。RabbitMQ可以分別對隊列和消息設置存活時間
1、在創建隊列的時候可以設置隊列的存活時間,當消息進⼊到隊列並且在存活時間內沒有消費者消費,則此消息就會從當前隊列被移除;2、創建消息隊列沒有設置TTL,但是消息設置了TTL,那麼當消息的存活時間結束,也會被移除;3、當TTL結束之後,我們可以指定將當前隊列的消息轉存到其他指定的隊列
2、使⽤延遲隊列實現訂單⽀付監控
1、實現流程圖
2、創建交換機和隊列
⼗⼆、消息隊列作⽤/使⽤場景總結
1、解耦
場景說明:⽤戶下單之後,訂單系統要通知庫存系統
2、異步
場景說明:⽤戶註冊成功之後,需要發送註冊郵件及註冊短信提醒
3、消息通信
場景說明:應⽤系統之間的通信,例如聊天室
4、流量削峯
場景說明:秒殺業務
5、⽇志處理
場景說明:系統中⼤量的⽇志處理