网上也搜过很多案例,但是大多是都还是存在一些问题,比如队列、交换机的持久化,还有更严重的问题就是 很多地方没有提到消费端的消息确认。
这里结合下 个人自己的理解 整合的例子。
首先 需要安装好rabbitmq的服务器,可百度。
1.加入springboot rabbitmq依赖:
<!--rabbitmq-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
2.配置文件中 加入mq的配置:
spring:
rabbitmq:
host: 127.0.0.1
port: 5672
username: guest
password: guest
publisher-confirms: true
virtual-host: /
publisher-returns: true
listener:
direct:
acknowledge-mode: manual
simple:
acknowledge-mode: manual
# 其他的自定义配置
3.注入mq的一些属性以及ConfirmCallback
package com.statistica.common.config;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
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.ChannelAwareMessageListener;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import java.io.IOException;
/**
* @author :zhangqiang
* @create :2018-08-31 10:54
**/
@Configuration
public class RabbitMqConfig {
@Value("${spring.rabbitmq.host}")
private String springRabbitmqHost;
@Value("${spring.rabbitmq.port}")
private int springRabbitmqPort;
@Value("${spring.rabbitmq.username}")
private String springRabbitmqUsername;
@Value("${spring.rabbitmq.password}")
private String springRabbitmqPassword;
@Value("${spring.rabbitmq.publisher-confirms}")
private boolean springRabbitmqPublisher_confirms;
@Value("${spring.rabbitmq.virtual-host}")
private String springRabbitmqVirtual_host;
@Autowired
private QueueConfig queueConfig;
@Autowired
private ExchangeConfig exchangeConfig;
@Bean
public ConnectionFactory connectionFactory(){
CachingConnectionFactory cachingConnectionFactory=new CachingConnectionFactory();
cachingConnectionFactory.setHost(springRabbitmqHost);
cachingConnectionFactory.setPort(springRabbitmqPort);
cachingConnectionFactory.setUsername(springRabbitmqUsername);
cachingConnectionFactory.setPassword(springRabbitmqPassword);
cachingConnectionFactory.setPublisherConfirms(springRabbitmqPublisher_confirms);
cachingConnectionFactory.setVirtualHost(springRabbitmqVirtual_host);
return cachingConnectionFactory;
}
@Bean
public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory){
return new RabbitAdmin(connectionFactory);
}
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)//需要消费者回调
public RabbitTemplate rabbitTemplate(){
RabbitTemplate rabbitTemplate=new RabbitTemplate(connectionFactory());
rabbitTemplate.setConfirmCallback(msgSendConfirmCallBack());
rabbitTemplate.setReturnCallback(msgSendReturnCallBack());
rabbitTemplate.setMandatory(true);
return rabbitTemplate;
}
/**
* 消息确认机制
* Confirms给客户端一种轻量级的方式,能够跟踪哪些消息被broker处理,
* 哪些可能因为broker宕掉或者网络失败的情况而重新发布。
* 确认并且保证消息被送达,提供了两种方式:发布确认和事务。(两者不可同时使用)
* 在channel为事务时,不可引入确认模式;同样channel为确认模式下,不可使用事务。
* @return
*/
@Bean
public MsgSendConfirmCallBack msgSendConfirmCallBack(){
return new MsgSendConfirmCallBack();
}
@Bean
public MsgSendReturnCallBack msgSendReturnCallBack(){
return new MsgSendReturnCallBack();
}
//消费队列和交换机绑定
@Bean
public Binding binding_one(){
return BindingBuilder.bind(queueConfig.oneQueue()).to(exchangeConfig.EXCHANGE_1()).with(QueueConfig.ONE_NAME_1);
}
@Bean
public Binding binding_two(){
return BindingBuilder.bind(queueConfig.twoQueue()).to(exchangeConfig.EXCHANGE_1()).with(QueueConfig.TWO_NAME_2);
}
//消费者配置
@Bean
public SimpleMessageListenerContainer messageListenerContainer(){
SimpleMessageListenerContainer container=new SimpleMessageListenerContainer();
container.setConnectionFactory(connectionFactory());
container.setConcurrentConsumers(3);
container.setMaxConcurrentConsumers(10);
return container;
}
}
package com.statistica.common.config;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.support.CorrelationData;
/**
* @author :zhangqiang
* @create :2018-08-31 15:12
**/
public class MsgSendConfirmCallBack implements RabbitTemplate.ConfirmCallback {
/**
* 当消息发送到交换机(exchange)时,该方法被调用.
* 1.如果消息没有到exchange,则 ack=false
* 2.如果消息到达exchange,则 ack=true
* @param correlationData
* @param ack
* @param cause
*/
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
System.out.println("消息id: "+correlationData);
if (ack){
System.out.println("消息发送到交换机-成功。");
}else {
System.out.println("消息发送到交换机-失败,重新发送!");
}
}
}
package com.statistica.common.config;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
/**
* @author :zhangqiang
* @create :2018-08-31 15:26
**/
public class MsgSendReturnCallBack implements RabbitTemplate.ReturnCallback {
/**
* 当消息从交换机到队列失败时,该方法被调用。(若成功,则不调用)
* 需要注意的是:该方法调用后,{@link MsgSendConfirmCallBack}中的confirm方法也会被调用,且ack = true
* @param message
*/
@Override
public void returnedMessage(Message message, int i, String s, String s1, String s2) {
System.out.println("消息从交换机到队列-失败");
}
}
4.创建queue、exchange 默认都是持久化的
package com.statistica.common.config;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/** 配置多个持久化的队列
* @author :zhangqiang
* @create :2018-08-31 14:42
**/
@Configuration
public class QueueConfig {
public static final String ONE_NAME_1="one_name_1";
public static final String TWO_NAME_2="two_name_2";
//所有队列必须持久化(Queue默认durable:true),防止mq服务重启后消息丢失
@Bean
public Queue oneQueue(){
/**
durable="true" 持久化消息队列 , rabbitmq重启的时候不需要创建新的队列
auto-delete 表示消息队列没有在使用时将被自动删除 默认是false
exclusive 表示该消息队列是否只在当前connection生效,默认是false
*/
return new Queue(ONE_NAME_1,true,false,false);//等同于 return new Queue(ONE_NAME_1);
}
@Bean
public Queue twoQueue(){
return new Queue(TWO_NAME_2);
}
}
package com.statistica.common.config;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**配置多个持久化的消息交换机(不同类型)
* @author :zhangqiang
* @create :2018-08-31 14:52
**/
@Configuration
public class ExchangeConfig {
public static final String EXCHANGE_1="exchange_1";
/**
* 1.定义direct exchange,绑定first_exchange
* 2.durable="true" 持久化交换机, rabbitmq重启的时候不需要创建新的交换机
* 3.direct交换器相对来说比较简单,匹配规则为:如果路由键匹配,消息就被投送到相关的队列
* fanout交换器中没有路由键的概念,他会把消息发送到所有绑定在此交换器上面的队列中。
* topic交换器你采用模糊匹配路由键的原则进行转发消息到队列中
*/
@Bean
public DirectExchange EXCHANGE_1(){
DirectExchange exchange=new DirectExchange(EXCHANGE_1,true,false);
return exchange;
}
//TopicExchange Topic转发模式 根据规则去匹配
//FanoutExchange 广播形式
}
5.生产者
package com.statistica.service;
import com.alibaba.fastjson.JSONObject;
import com.statistica.common.config.ExchangeConfig;
import com.statistica.common.config.QueueConfig;
import com.statistica.util.ResultUtil;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageBuilder;
import org.springframework.amqp.core.MessagePostProcessor;
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.stereotype.Service;
/**
* @author :zhangqiang
* @create :2018-08-31 18:32
**/
@Service
public class RabbitServiceTests {
@Autowired
private RabbitTemplate rabbitTemplate;
public Object sendTest(int i){
// rabbitTemplate.convertAndSend(ExchangeConfig.EXCHANGE_1,QueueConfig.ONE_NAME_1,message,correlationData);
for (int i1 = 0; i1 < i; i1++) {
try {
Thread.sleep(i*2);
} catch (InterruptedException e) {
e.printStackTrace();
}
send(i1);
}
return "调用成功";
}
public void send(int size){
String msgid=size+"";
// Message message=MessageBuilder.withBody(JSONObject.toJSONString(size).getBytes())
// .setContentType(MessageProperties.CONTENT_TYPE_JSON)
// .setCorrelationId(msgid).build();
CorrelationData correlationData=new CorrelationData(msgid);
rabbitTemplate.convertAndSend(QueueConfig.ONE_NAME_1, size, correlationData);
}
}
package com.statistica.controller;
import com.statistica.service.KafkaServiceTests;
import com.statistica.service.RabbitServiceTests;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
/**
* @author :zhangqiang
* @create :2018-08-31 16:46
**/
@RestController
public class TestController {
@Autowired
RabbitServiceTests rabbitServiceTests;
@Autowired
KafkaServiceTests kafkaServiceTests;
@GetMapping(value = "/rabbit")
public Object rabbit(@RequestParam("size") int size){
return rabbitServiceTests.sendTest(size);
}
}
6.消费者监听,消费完之后 一定要有消息确认 ,否则 项目重启后 会导致重复消费 !!!
这里为了测试,加入了两个消费者。
package com.statistica.common.listener;
import com.alibaba.fastjson.JSONObject;
import com.rabbitmq.client.Channel;
import com.statistica.common.config.QueueConfig;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.io.IOException;
/** 消费者1
* @author :zhangqiang
* @create :2018-08-31 16:32
**/
@Component
@RabbitListener(queues = {QueueConfig.ONE_NAME_1})
public class TestReceiver1{
@RabbitHandler
public void proess(Object ojb,Channel channel, Message message) throws IOException {
System.out.println("TestReceiver1:我被消费了:"+JSONObject.toJSONString(ojb));
try{
System.out.println("TestReceiver1 message:"+message.getMessageProperties()+":"+new String(message.getBody()));
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
System.out.println("TestReceiver1-消息已确认!!!");
}catch(Exception e){
e.printStackTrace();//TODO 业务处理
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false,false);
System.out.println("TestReceiver1-重新放入队列!!!");
}
}
}
package com.statistica.common.listener;
import com.alibaba.fastjson.JSONObject;
import com.rabbitmq.client.Channel;
import com.statistica.common.config.QueueConfig;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.io.IOException;
/** 消费者1
* @author :zhangqiang
* @create :2018-08-31 16:32
**/
@Component
@RabbitListener(queues = {QueueConfig.ONE_NAME_1})
public class TestReceiver2 {
@RabbitHandler
public void proess(Object ojb,Message message, Channel channel) throws IOException {
System.out.println("TestReceiver2:我被消费了:"+JSONObject.toJSONString(ojb));
try{
System.out.println("TestReceiver2 message:"+message.getMessageProperties()+":"+new String(message.getBody()));
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
System.out.println("TestReceiver2-消息已确认!!!");
}catch(Exception e){
e.printStackTrace();//TODO 业务处理
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false,false);
System.out.println("TestReceiver2-重新放入队列!!!");
}
}
}
来源:oschina
链接:https://my.oschina.net/u/2972511/blog/2046143