面对日益复杂的信息平台,消息队列使用是解决什么问题呢?初步总结一下可以解决如下场景问题:
本文将通过Rabbit MQ、Spring Boot集成使用来进行分享,大致章节如下:
RabbitMQ是实现了高级消息队列协议(AMQP)的开源消息代理软件(亦称面向消息的中间件)。
RabbitMQ服务器是用Erlang语言编写的,
而集群和故障转移是构建在开放电信平台框架上的。所有主要的编程语言均有与代理接口通讯的客户端库。
yum install erlang
#安装
rpm -ivh --nodeps rabbitmq-server-3.8.7-1.el6.noarch.rpm
#开启后台管理功能
sudo rabbitmq-plugins enable rabbitmq_management
#开机自启动
chkconfig rabbitmq-server on
# Rabbitmq常用命令
#启动服务
service rabbitmq-server start
#查看rabbitmq状态
service rabbitmq-server status
#重启服务
service rabbitmq-server restart
#停止服务
service rabbitmq-server stop
#查看账号列表
sudo rabbitmqctl list_users
#添加账号
sudo rabbitmqctl add_user username password
#设置用户标签
sudo rabbitmqctl set_user_tags username tagname
#删除用户
sudo rabbitmqctl delete_user username
#设置visualhost权限
sudo rabbitmqctl set_permissions -p / username ".*" ".*" ".*"
org.springframework.boot
spring-boot-starter-amqp
public static final String FANOUT_EXCHANGE = "fanout.exchange";
@Bean(name = FANOUT_EXCHANGE)
public FanoutExchange fanoutExchange() {
return new FanoutExchange(FANOUT_EXCHANGE, true, false);
}
public static final String FANOUT_QUEUE1 = "fanout.queue1";
@Bean(name = FANOUT_QUEUE1)
public Queue fanoutQueue1() {
return new Queue(FANOUT_QUEUE1, true, false, false);
}
@Bean
public Binding bindingSimpleQueue1(@Qualifier(FANOUT_QUEUE1) Queue fanoutQueue1,
@Qualifier(FANOUT_EXCHANGE) FanoutExchange fanoutExchange) {
return BindingBuilder.bind(fanoutQueue1).to(fanoutExchange);
}
@Component
public class RabbitMQSenderService {
@Autowired
private RabbitTemplate rabbitTemplate;
//发送消息,不需要实现任何接口,供外部调用。
public void send(String exchange,
String routingkey,
Message message) {
CorrelationData correlationId = new CorrelationData(UUID.randomUUID().toString());
System.out.println("开始发送消息 : " + message);
rabbitTemplate.convertAndSend(exchange, routingkey, message, correlationId);
System.out.println("结束发送消息 : " + message);
}
}
@Component
class RabbitMQReciver {
@RabbitListener(queues = RabbitFanoutExchangeConfig.FANOUT_QUEUE1)
public void reciveLogAll(String msg) throws Exception {
System.out.println("log.all:" + msg);
}
}
spring.rabbitmq.host=127.0.0.1
spring.rabbitmq.port=5672
spring.rabbitmq.username=rabbitmq
spring.rabbitmq.password=rabbitmq
spring.rabbitmq.virtual-host=study
PS:该模式在Spring boot中是很少用到的一个场景,一般都会通过Exchange进行消息分配到队列从而为以后扩展预留一个入口。
该模式性能最好,拿到消息直接放入队列
该模式通过routing key 进行全字匹配,匹配上将相关消息放入相关队列。
该模式通过routng key进行模糊匹配,匹配上将相关信息放入相关队列,具体匹配规则如下:
字符*:匹配单个单词,比如log.* 可以匹配log.all、log.error等
字符#:可以匹配0个或多个标识符,如log.# 可以匹配log.business.all,log.service.info等
通过message header头部信息进行比对,可以根据定义全匹配、部分匹配等规则
消息持久化需要对交换机持久化、队列持久化、消息持久化,代码如下:
#初始化Exchange,name是交换机名,durable是否持久化,autoDelete当前会话结束删除
public AbstractExchange(String name, boolean durable, boolean autoDelete)
#初始化Queue, name是队列名,durable是否持久化,exclusive排他性,autodelete当前会话结束删除
public Queue(String name, boolean durable, boolean exclusive, boolean autoDelete)
#发送消息持久化
message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
针对消息延迟发送可通过死性队列进行处理:
MessageProperties messageProperties=new MessageProperties();
messageProperties.setExpiration("2000"); //设置超时时间为2S
Message message=new Message(message_01.getBytes(),messageProperties);
@Bean(name = DELAY_QUEUE)
public Queue delayQueue() {
Map queueProperties = new HashMap<>();
queueProperties.put("x-dead-letter-exchange", DEAD_EXCHANGE);
queueProperties.put("x-dead-letter-routing-key", "lsf.dead.letter");
queueProperties.put("x-message-ttl",10000);#设置超时时间为10s
return new Queue(DELAY_QUEUE, true, false, false, queueProperties);
}
rabbitmq官网插件下载页面:https://www.rabbitmq.com/community-plugins.html
安装步骤如下:
第1步:选择rabbitmq_delayed_message_exchange进入具体插件下载页,选择相关版本号:rabbitmq_delayed_message_exchange-3.8.0.ez
第2步:copy文件到路径/usr/lib/rabbitmq/lib/rabbitmq_server-3.8.7/plugins中
第3步:启用插件:rabbitmq-plugins enable rabbitmq_delay_message_exchange
第4步:重启Rabbitmq服务
在SpringBoot中实现,交换机声明为x-delayed-message,然后消息发送设置delay属性,代码如下:
//声明交换机为x-delayed-message
public static final String CUSTOM_DELAY_EXCHANGE = "lsf.custom.delay.exhange";
@Bean(name = CUSTOM_DELAY_EXCHANGE)
public Exchange customDelayExchange() {
Map exchangeArgs = new HashMap<>();
exchangeArgs.put("x-delayed-type","direct");
return new CustomExchange(CUSTOM_DELAY_EXCHANGE,
"x-delayed-message",
true,
false,
exchangeArgs);
}
//消息发送
MessageProperties messageProperties=new MessageProperties();
messageProperties.setDelay(150000);
String message_01="第一条消息延迟:15s "+sdf.format(date);
Message message=new Message(message_01.getBytes(),messageProperties);
rabbitmqService.send(RabbitCustomDeleyConfig.CUSTOM_DELAY_EXCHANGE,
RabbitCustomDeleyConfig.CUSTOM_DELAY_MESSAGE,
message);
PS:消息会在交换机中等待,设置时间到了就会被消费,比消息过期机制好,并不会出现第一条消息过期时间长,第二条消息过期时间短还需要等第一条消费后才消费第二条。
//连接开启回调
connectionFactory.setPublisherConfirms(true);
//绑定回调触发方法
rabbitTemplate.setConfirmCallback(new RabbitConfirmCallback());
实现RabbitTemplate.ConfirmCallback封装实现方法
package lsf.rabbit.config;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
public class RabbitConfirmCallback implements RabbitTemplate.ConfirmCallback {
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
if (ack) {
System.out.println("消息发送成功:" + correlationData);
} else {
System.out.println("消息发送失败:" + cause);
}
}
}
//开启消息没有传递到队列回调
connectionFactory.setPublisherReturns(true);
//开启回调和绑定回调方法
rabbitTemplate.setMandatory(true);//开启强制委托模式
rabbitTemplate.setReturnCallback(new RabbitReturnsCallback());
继承接口RabbitTemplate.ReturnCallback实现消息未到队列业务处理
package lsf.rabbit.config;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
public class RabbitReturnsCallback implements RabbitTemplate.ReturnCallback {
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
String result = String.format("消息发送ReturnCallback-未到达消息队列:{%s}--{%s}--{%s}--{%s}--{%s}",
message, replyCode, replyText, exchange, routingKey);
System.out.println(result);
}
}
//配置确认模式为手动的
@RabbitListener(queues = RabbitRetryConfig.RETRY_NORMAL_QUEUE, ackMode = "MANUAL")
#手动拒绝消息,其中requeue为否再次入队
channel.basicReject(msg.getMessageProperties().getDeliveryTag(), false);
#手动否定确认,其中requeue为否再次入队
channel.basicNack(msg.getMessageProperties().getDeliveryTag(), false, false);
Spring Boot AMQP:https://spring.io/projects/spring-amqp