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";
}
}