1、Java 版本
- 依賴配置
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.8.0</version>
</dependency>
- 生產者 -- simple 模式
package com.vim.modules.web.controller;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
public class Product {
private static final String SIMPLE_QUEUE = "simple_queue";
public static void main(String[] args) throws Exception{
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
factory.setPort(5672);
factory.setVirtualHost("/admin");
factory.setUsername("admin");
factory.setPassword("admin");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(SIMPLE_QUEUE, false, false, false, null);
//持久化設置1,此處持久化的是隊列
//channel.queueDeclare(SIMPLE_QUEUE, true, false, false, null);
channel.basicPublish("", SIMPLE_QUEUE, null, "hello world".getBytes());
//持久化設置2,此處持久化的是消息
//channel.basicPublish("", SIMPLE_QUEUE, MessageProperties.PERSISTENT_BASIC, "hello world".getBytes());
channel.close();
connection.close();
}
}
- 默認採用的是公平分發的方式,也就是不管消費者處理速度的快慢,都是分配相同數量的消息。
- 默認是手動應答,如果一個消費者出現了異常或沒有應答,那麼MQ會將該消息發給另一個消費者;如果採用自動應答,一旦MQ將消息分發到消費者,不管是否邏輯出現異常,都會刪除該條消息。
- 持久化隊列:在申明隊列的時候durable參數爲true,那麼在MQ重啓的時候,該隊列沒有發送的數據還會存在。並且在數據發送的時候需要設置屬性 MessageProperties.PERSISTENT_BASIC
- 消費者 -- simple 模式
package com.vim.modules.web.controller;
import com.rabbitmq.client.*;
import java.io.IOException;
public class Consumer {
private static final String SIMPLE_QUEUE = "simple_queue";
public static void main(String[] args) throws Exception{
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
factory.setPort(5672);
factory.setVirtualHost("/admin");
factory.setUsername("admin");
factory.setPassword("admin");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(SIMPLE_QUEUE, false, false, false, null);
DefaultConsumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println(new String(body));
channel.basicAck(envelope.getDeliveryTag(), false);
}
};
channel.basicConsume(SIMPLE_QUEUE, consumer);
}
}
- 交換機:也交轉發器,生產者沒有將消息直接發送到隊列,而是發送到了交換機。但是交換機沒有存儲消息的能力,只有隊列有存儲消息的能力。
- 交換機的類型:FAOUT,不處理路由鍵,一個發送到交換機的消息都會被轉發到與該交換機綁定的所有隊列上;DIRECT,處理路由鍵,要求該消息與一個特定的路由鍵完全匹配;TOPIC,將路由鍵與某種模式匹配,有點類似正則匹配。
- 交換機模式下,需要先在MQ中申明交換機,否則在消費者端申明隊列,並綁定到交換機的時候,會出現異常。
- 生產者 -- exchange 模式
package com.vim.modules.web.exchange;
import com.rabbitmq.client.*;
public class Product {
private static final String EXCHANGE = "exchange";
public static void main(String[] args) throws Exception{
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
factory.setPort(5672);
factory.setVirtualHost("/admin");
factory.setUsername("admin");
factory.setPassword("admin");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
//聲明交換機
channel.exchangeDeclare(EXCHANGE, BuiltinExchangeType.FANOUT);
for(int i=0; i<10; i++){
//發送消息
channel.basicPublish(EXCHANGE, "", null, "hello exchange".getBytes());
}
channel.close();
connection.close();
}
}
- 消費者 -- exchange 模式
package com.vim.modules.web.exchange;
import com.rabbitmq.client.*;
import java.io.IOException;
public class Consumer1 {
private static final String EXCHANGE_QUEUE = "exchange_queue";
private static final String EXCHANGE = "exchange";
public static void main(String[] args) throws Exception{
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
factory.setPort(5672);
factory.setVirtualHost("/admin");
factory.setUsername("admin");
factory.setPassword("admin");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(EXCHANGE_QUEUE, false, false, false, null);
//相比Simple模式多了一個步驟,綁定到交換機
channel.queueBind(EXCHANGE_QUEUE, EXCHANGE, "");
DefaultConsumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println(new String(body));
channel.basicAck(envelope.getDeliveryTag(), false);
}
};
channel.basicConsume(EXCHANGE_QUEUE, false, consumer);
}
}
- 消息確認機制:生產者將消息發送出去之後,消息到底有沒有到達MQ服務器,MQ實現了事務機制。txSelect將當前channel設置爲transaction模式,txCommit用於提交事務,txRollBack用於回滾事務。這種模式降低了MQ的消息吞吐量。
package com.vim.modules.web.simple;
import com.rabbitmq.client.*;
public class Product {
private static final String SIMPLE_QUEUE = "simple_queue";
public static void main(String[] args) throws Exception{
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
factory.setPort(5672);
factory.setVirtualHost("/admin");
factory.setUsername("admin");
factory.setPassword("admin");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(SIMPLE_QUEUE, true, false, false, null);
channel.txSelect();
try {
channel.basicPublish("", SIMPLE_QUEUE, MessageProperties.PERSISTENT_BASIC, "hello world".getBytes());
//模擬事務回滾操作
int i=1/0;
channel.txCommit();
}catch (Exception e){
channel.txRollback();
}
channel.close();
connection.close();
}
}
- confirm模式,最大的好處是異步處理。
2、Spring 版本
- 配置依賴
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
</dependencies>
- Rabbit 配置類
package com.vim.common.config;
import com.vim.common.rabbitmq.MessageConsumer;
import org.springframework.amqp.core.AcknowledgeMode;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.amqp.rabbit.listener.adapter.MessageListenerAdapter;
import org.springframework.amqp.rabbit.support.CorrelationData;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.UUID;
@Configuration
public class RabbitConfig {
@Bean
public ConnectionFactory connectionFactory(){
CachingConnectionFactory factory = new CachingConnectionFactory();
factory.setHost("localhost");
factory.setPort(5672);
factory.setUsername("admin");
factory.setPassword("admin");
factory.setVirtualHost("/admin");
factory.setPublisherConfirms(true);
factory.setPublisherReturns(true);
return factory;
}
@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory){
RabbitTemplate template = new RabbitTemplate();
template.setConnectionFactory(connectionFactory);
template.setMandatory(true);
//控制消息的投遞可靠性模式
//message 從 producer 到 rabbitmq broker cluster 則會返回一個 confirmCallback
//CorrelationData 對象內部只有一個 id 屬性,用來表示當前消息唯一性。
//1.消息推送到server,找不到交換機,返回false,表示發送失敗
//2.消息推送到server,找不到交換機也找不到隊列,返回false,表示發送失敗
//3.消息推送到server,找到交換機了,但是沒找到隊列,返回true,表示發送成功
//4.消息推送成功,返回true,表示發送成功
template.setConfirmCallback((CorrelationData data, boolean ack, String cause)->{
System.out.println("ConfirmCallback: "+"相關數據:"+data);
System.out.println("ConfirmCallback: "+"確認情況:"+ack);
System.out.println("ConfirmCallback: "+"原因:"+cause);
});
//message 從 exchange->queue 投遞失敗則會返回一個 returnCallback
//如果未能投遞到目標 queue 裏將調用 returnCallback ,可以記錄下詳細到投遞數據,定期的巡檢或者自動糾錯都需要這些數據
//1.消息推送到server,找到交換機了,但是沒找到隊列,返回302
template.setReturnCallback((Message message, int replyCode, String replyText,
String exchange, String routingKey)->{
System.out.println("ReturnCallback: "+"消息:"+message);
System.out.println("ReturnCallback: "+"迴應碼:"+replyCode);
System.out.println("ReturnCallback: "+"迴應信息:"+replyText);
System.out.println("ReturnCallback: "+"交換機:"+exchange);
System.out.println("ReturnCallback: "+"路由鍵:"+routingKey);
});
return template;
}
@Bean
public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory){
RabbitAdmin admin = new RabbitAdmin(connectionFactory);
admin.setAutoStartup(true);
return admin;
}
@Bean
public SimpleMessageListenerContainer messageListenerContainer(ConnectionFactory connectionFactory){
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
//手動簽收
container.setAcknowledgeMode(AcknowledgeMode.MANUAL);
//監聽隊列
container.setQueueNames(QueueConstants.DIRECT_QUEUE);
//設置不重回隊列
container.setDefaultRequeueRejected(false);
//自定義consumer tag strategy
container.setConsumerTagStrategy((String queue)->queue+"_"+ UUID.randomUUID().toString());
//後置處理器
container.setAfterReceivePostProcessors(message -> {
message.getMessageProperties().getHeaders().put("desc","自定義描述");
return message;
});
//普通監聽模式
/*container.setMessageListener((ChannelAwareMessageListener)(message, channel) ->{
System.out.println(new String(message.getBody()));
System.out.println(message.getMessageProperties().getHeaders().get("desc"));
System.out.println(message.getMessageProperties().getHeaders().get("customHeader"));
//消息被拒絕並且requeue爲false,消息變成死信
//需要設置成false,然後通過arguments將未ack的消息轉發到其他exchange中
//properties.setExpiration("10000"); 消息過期的也會進入死信交換機
// channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
});*/
//適配器模式
MessageListenerAdapter adapter = new MessageListenerAdapter(new MessageConsumer());
adapter.setDefaultListenerMethod("process");
//測試轉換器
// adapter.setMessageConverter(new TextMessageConverter());
//json轉換器
adapter.setMessageConverter(new Jackson2JsonMessageConverter());
container.setMessageListener(adapter);
return container;
}
}
- 監聽類
package com.vim.common.rabbitmq;
import java.util.Map;
public class MessageConsumer {
public void process(Map<String, String> body){
System.out.println(body);
}
}
- 測試轉換器
package com.vim.common.rabbitmq;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.support.converter.MessageConversionException;
import org.springframework.amqp.support.converter.MessageConverter;
public class TextMessageConverter implements MessageConverter{
//Java對象轉換爲Message
@Override
public Message toMessage(Object object, MessageProperties messageProperties) throws MessageConversionException {
return new Message(object.toString().getBytes(), messageProperties);
}
//Message轉換爲Java對象
@Override
public Object fromMessage(Message message) throws MessageConversionException {
String contentType = message.getMessageProperties().getContentType();
if(null != contentType && contentType.contains("text")){
return new String(message.getBody());
}
return message.getBody();
}
}
- 隊列名稱靜態變量
package com.vim.common.config;
public class QueueConstants {
public static final String DIRECT_EXCHANGE = "directExchange";
public static final String DIRECT_QUEUE = "directQueue";
public static final String DIRECT_ROUTING = "directRouting";
public static final String DLX_QUEUE = "dlxQueue";
public static final String DLX_EXCHANGE = "dlxExchange";
public static final String DLX_ROUTING = "#";
}
- 交換機 -- 直連模式
package com.vim.common.config;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class DirectConfig {
@Bean
public Queue directQuque(){
Map<String, Object> arguments = new HashMap<>();
arguments.put("x-dead-letter-exchange", QueueConstants.DLX_EXCHANGE);
return new Queue(QueueConstants.DIRECT_QUEUE, true, false, false, arguments);
}
@Bean
public DirectExchange directExchange(){
return new DirectExchange(QueueConstants.DIRECT_EXCHANGE, true, false);
}
@Bean
Binding bindDirect(){
return BindingBuilder.bind(directQuque()).to(directExchange()).with(QueueConstants.DIRECT_ROUTING);
}
}
- 死信隊列
package com.vim.common.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class DlxConfig {
@Bean
public Queue dlxQueue(){
return new Queue(QueueConstants.DLX_QUEUE, true);
}
@Bean
public TopicExchange DlxExchange(){
return new TopicExchange(QueueConstants.DLX_EXCHANGE, true, false, null);
}
@Bean
public Binding bindingDlx(){
return BindingBuilder.bind(dlxQueue()).to(DlxExchange()).with(QueueConstants.DLX_ROUTING);
}
}
- 消息發送
package com.vim.modules.web.controller;
import com.alibaba.fastjson.JSONObject;
import com.vim.common.config.QueueConstants;
import org.apache.http.entity.ContentType;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageDeliveryMode;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.support.CorrelationData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
@RestController
public class SendMessageController {
@Autowired
private RabbitTemplate rabbitTemplate;
@RequestMapping(value = "/sendDirectMessage")
public String sendDirectMessage(){
//消息屬性
MessageProperties properties = new MessageProperties();
properties.setDeliveryMode(MessageDeliveryMode.PERSISTENT);
properties.getHeaders().put("customHeader", "自定義header");
properties.setExpiration("10000");
properties.setContentType(ContentType.APPLICATION_JSON.getMimeType());
//correlationData
CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
//組裝message
Map<String, String> data = new HashMap<>();
data.put("name", "admin");
Message message = new Message(JSONObject.toJSONString(data).getBytes(), properties);
rabbitTemplate.send(QueueConstants.DIRECT_EXCHANGE, QueueConstants.DIRECT_ROUTING, message, correlationData);
return "success";
}
}
- admin管理
package com.vim.modules.web.controller;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class AdminController {
@Autowired
private RabbitAdmin rabbitAdmin;
@RequestMapping(value = "/deleteExchange")
public String deleteExchange(){
rabbitAdmin.deleteExchange("fanoutExchange");
return "success";
}
@RequestMapping(value = "/deleteQueue")
public String deleteQueue(){
rabbitAdmin.deleteQueue("fanoutQueue1");
rabbitAdmin.deleteQueue("fanoutQueue2");
return "success";
}
}