本文采用directExchange,为保证针对同个用户的消息要有消费顺序,采用多队列,每条队列单线程消费进行配置
消息发送端配置
配置参数接收类
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
* 配置类
*/
@Configuration
@ConfigurationProperties(prefix = "spring.rabbitmq")
public class RabbitMqProperties {
private String addresses;//地址
private String username;//用户名
private String password;//密码
private Boolean publisherConfirms;//是否发送校验
private String virtualHost;//虚拟地址
private Boolean defaultDurable;// 是否持久化
private Boolean autoDelete;//当所有消费客户端连接断开后,是否自动删除队列
public String getAddresses() {
return addresses;
}
public void setAddresses(String addresses) {
this.addresses = addresses;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Boolean getPublisherConfirms() {
return publisherConfirms;
}
public void setPublisherConfirms(Boolean publisherConfirms) {
this.publisherConfirms = publisherConfirms;
}
public String getVirtualHost() {
return virtualHost;
}
public void setVirtualHost(String virtualHost) {
this.virtualHost = virtualHost;
}
public Boolean getDefaultDurable() {
return defaultDurable;
}
public void setDefaultDurable(Boolean defaultDurable) {
this.defaultDurable = defaultDurable;
}
public Boolean getAutoDelete() {
return autoDelete;
}
public void setAutoDelete(Boolean autoDelete) {
this.autoDelete = autoDelete;
}
}
配置参数
#rabbitmq配置信息
spring.rabbitmq.addresses=127.0.0.1:5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
spring.rabbitmq.publisherConfirms=true
spring.rabbitmq.virtualHost=/rabbit
spring.rabbitmq.defaultDurable=true
spring.rabbitmq.autoDelete=false
rabbitmq.routingKey.count=3
rabbitmq.exchange=local.exchange
rabbitmq.routingKey=local.queue_
配置类
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.support.CorrelationData;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.beans.factory.annotation.Autowired;
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 org.springframework.messaging.converter.MappingJackson2MessageConverter;
@Configuration
public class AmqpConfig {
private static Logger log = LoggerFactory.getLogger(AmqpConfig.class);
@Autowired
private RabbitMqProperties rabbitMqProperties;
@Bean
public ConnectionFactory connectionFactory(){
CachingConnectionFactory connectionFactory=new CachingConnectionFactory();
connectionFactory.setAddresses(rabbitMqProperties.getAddresses());
connectionFactory.setUsername(rabbitMqProperties.getUsername());
connectionFactory.setPassword(rabbitMqProperties.getPassword());
connectionFactory.setVirtualHost(rabbitMqProperties.getVirtualHost());
connectionFactory.setPublisherConfirms(rabbitMqProperties.getPublisherConfirms());
return connectionFactory;
}
// 这里作用域设置为原型模式 每次获取都会得到一个新的实例
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public RabbitTemplate rabbitTemplate(){
RabbitTemplate rabbitTemplate=new RabbitTemplate(connectionFactory());
//数据转换为json存入消息队列
rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter());
//发布确认
rabbitTemplate.setConfirmCallback((CorrelationData correlationData, boolean ack, String cause) -> {
if (!ack){
log.error("发送到消息失败");
throw new RuntimeException("send error " + cause);
}
});
return rabbitTemplate;
}
/**
* rabbitAdmin代理类
* @return
*/
@Bean
public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory){
return new RabbitAdmin(connectionFactory);
}
}
发送消息util类
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageDeliveryMode;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.UnsupportedEncodingException;
/**
* 消息发送util
*/
@Component
public class SendMsgUtils {
private Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private AmqpTemplate rabbitTemplate;
public void sendMsg(String exchange, String routingKey,String content){
MessageProperties messageProperties = new MessageProperties();
// 设置消息持久化
messageProperties.setDeliveryMode(MessageDeliveryMode.PERSISTENT);
try {
Message message = new Message(content.getBytes("utf-8"),messageProperties);
rabbitTemplate.send(exchange, routingKey, message);
} catch (UnsupportedEncodingException e) {
logger.info("发送消息失败 exchange{},routingKey:{},content",exchange,routingKey,content);
e.printStackTrace();
}
}
}
测试controller类
@RestController
@RequestMapping(path = "/test")
public class TestController {
@Value("${rabbitmq.exchange}")
private String AUDIENCE_EXCHANGE;// 交换器名
@Value("${rabbitmq.routingKey}")
private String ROUTING_KEY;// 路由键
@Value("${rabbitmq.routingKey.count}")
private Integer ROUTING_KEY_COUNT;// 路由键个数
@Autowired
private SendMsgComponent sendMsgComponent;
@PostMapping(path = "/sendMessage")
public void testSend(String userId,String message){
// 计算路由键
int routeKey = HashUtils.getHash(userId) % ROUTING_KEY_COUNT;
// 发送消息
sendMsgComponent.sendMsg(AUDIENCE_EXCHANGE,ROUTING_KEY+routeKey,message);
}
}
<!-- 整合mq需要加上下面依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
消息接收端配置
配置参数
#rabbitmq配置信息
spring.rabbitmq.addresses=127.0.0.1:5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
spring.rabbitmq.publisherConfirms=true
spring.rabbitmq.virtualHost=/rabbitpre
spring.rabbitmq.defaultDurable=true
spring.rabbitmq.autoDelete=false
# exchange
rabbitmq.exchange=local.exchange
# 3个队列消费
rabbitmq.queue_0=local.queue_0
rabbitmq.queue_1=local.queue_1
rabbitmq.queue_2=local.queue_2
#消费队列数
rabbitmq.queue.audience.count=3
xml配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xmlns:task="http://www.springframework.org/schema/task"
xsi:schemaLocation="http://www.springframework.org/schema/rabbit
http://www.springframework.org/schema/rabbit/spring-rabbit-1.5.xsd
http://www.springframework.org/schema/task
http://www.springframework.org/schema/task/spring-task.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--配置connection-factory,指定连接rabbit server参数 -->
<rabbit:connection-factory id="connectionFactory"
username="${spring.rabbitmq.username}" password="${spring.rabbitmq.password}" addresses="${spring.rabbitmq.addresses}" virtual-host="${spring.rabbitmq.virtualHost}"/>
<!-- 自动声明队列 交换器 -->
<rabbit:admin id="rabbitAdmin" connection-factory="connectionFactory" />
<!-- 队列 durable持久化 auto-delete空闲自动删除 exclusive消费者是否独占队列 -->
<rabbit:queue id="queue_0" name="${rabbitmq.queue_0}" durable="true" auto-delete="false" exclusive="false"/>
<rabbit:queue id="queue_1" name="${rabbitmq.queue_1}" durable="true" auto-delete="false" exclusive="false"/>
<rabbit:queue id="queue_2" name="${rabbitmq.queue_2}" durable="true" auto-delete="false" exclusive="false"/>
<!-- 将队列绑定到交换路 路由键与队列名一致 -->
<rabbit:direct-exchange name="${rabbitmq.exchange}" durable="true" auto-delete="false">
<rabbit:bindings>
<rabbit:binding queue="queue_0" key="${rabbitmq.queue_0}"/>
<rabbit:binding queue="queue_1" key="${rabbitmq.queue_1}"/>
<rabbit:binding queue="queue_2" key="${rabbitmq.queue_2}"/>
</rabbit:bindings>
</rabbit:direct-exchange>
<!-- 线程池 -->
<task:executor id="taskExecutor" pool-size="3" queue-capacity="200" />
<!-- 监听配置 保证更新顺序 一个队列只能同时一条线程消费 -->
<!-- acknowledge确认方式 manual是手动确认 concurrency是消费初始线程数
max-concurrency最大消费线程数 prefetch消费者一次取多少消息进行消费 -->
<rabbit:listener-container connection-factory="connectionFactory" acknowledge="manual" concurrency="1" max-concurrency="1" task-executor="taskExecutor" prefetch="${rabbitmq.queue.audience.count}" >
<rabbit:listener queues="queue_0" ref="mqListener" />
<rabbit:listener queues="queue_1" ref="mqListener" />
<rabbit:listener queues="queue_2" ref="mqListener" />
</rabbit:listener-container>
</beans>
消息监听器
import com.alibaba.fastjson.JSON;
import com.rabbitmq.client.Channel;
import com.zbmy.rabbit.workbench.data.server.utils.HashUtils;
import org.elasticsearch.client.transport.TransportClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.core.ChannelAwareMessageListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
@Component
public class MqListener implements ChannelAwareMessageListener {
private static final Logger LOGGER = LoggerFactory.getLogger(UpdateAudienceListener.class);
@Override
public void onMessage(Message message, Channel channel) throws Exception {
try {
byte[] body = message.getBody();
String messageStr = new String(body, "UTF-8");
doSomething(parseMessage(messageStr));
// 确认 false不批量确认
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
} catch (Exception e) {
LOGGER.error("处理消息失败:{}", e);
// 拒绝消息 false不重新放入队列
channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);
}
}
private Map<String,Object> parseMessage(String messageStr){
Map<String,Object> resultMap = new HashMap<>();
Map<String,Object> map = JSON.parseObject(messageStr);
for (Map.Entry<String, Object> entry : map.entrySet()) {
resultMap.put(entry.getKey(),entry.getValue());
}
return resultMap;
}
}
基础知识推荐这篇文章 https://www.jianshu.com/p/79ca08116d57
mq官方各版本文档地址 https://docs.spring.io/spring-amqp/docs/