RabbitMQ
基于amqp协议
四种场景
- 异步处理
用户注册后,需要发送邮件和短信,两种方式:
1.串行:一个一个发
2.并行:两个同时发
- 应用解耦
将订单与库存的解耦
- 流量削峰
用在秒杀活动
- 消息通信
五种队列
配置
//连接的主机
spring.rabbitmq.host=8.130.102.114
//端口
spring.rabbitmq.port=5672
//用户名
spring.rabbitmq.username=admin
//密码
spring.rabbitmq.password=admin
spring.rabbitmq.virtual-host=/
- helloword
默认交换机 一个生产者发送消息到队列一个消费者接收
- 生产者
//生产者创建队列 消费者也可
@Configuration
public class RabbitMQConfig {
@Bean
public Queue queue(){
Queue queue = new Queue("spb-hello");
return queue;
}
}
- 消费者
//消费者消费消息
@Component
public class Listen {
//监听的消息队列
@RabbitListener(queues = "spb-hello")
public void one(String msg){
System.out.println(msg);
}
}
- Work queue
默认交换机 一个生产者发送消息到队列多个消费者接收(抢)
- 生产者
同上
- 消费者
同上两个
- Publish
一个生产者发送到交换机 交换机绑定队列 队列给多个消费者推送消息(同时)
- 生产者
@Configuration
public class PublishConfig {
//第一个队列
@Bean("sms-queue")
public Queue smsQueue(){
return new Queue("publish-sms-queue");
}
//第二个队列
@Bean("email-queue")
public Queue emailQueue(){
return new Queue("publish-email-queue");
}
/**
* 发布订阅交换机
* @return
*/
@Bean
public FanoutExchange exchange(){
return new FanoutExchange("sms-email-publish-exchange");
}
//绑定第一个队列
@Bean
Binding bindSmsToExchange(@Qualifier("sms-queue") Queue queue, FanoutExchange fanoutExchange){
return BindingBuilder.bind(queue).to(fanoutExchange);
}
//绑定第二个队列
@Bean
Binding bindEmailToExchange(@Qualifier("email-queue") Queue queue, FanoutExchange fanoutExchange){
return BindingBuilder.bind(queue).to(fanoutExchange);
}
}
- 消费者
同上监听队列得到信息
- Routing
一个生产者发送到交换机 交换机绑定队列 队列给多个消费者推送消息 通过关键词来判断队那个队列接收
- 生产者
@Configuration
public class RoutingConfig {
//第一个队列
@Bean("smsqueue")
public Queue smsqueue(){
return new Queue("routing-sms-queue");
}
//第二个队列
@Bean("emailqueue")
public Queue emailqueue(){
return new Queue("routing-email-queue");
}
//交换机
@Bean
public DirectExchange exchange(){
return new DirectExchange("routing-email-sms-diret");
}
//绑定第一个队列设置关键词
@Bean
Binding smsbinding(@Qualifier("smsqueue") Queue queue, DirectExchange exchange){
return BindingBuilder.bind(queue).to(exchange).with("sms");
}
//绑定第二个队列设置关键词
@Bean
Binding emailbinding(@Qualifier("emailqueue") Queue queue, DirectExchange exchange){
return BindingBuilder.bind(queue).to(exchange).with("email");
}
//同时绑定第一个第二个队列设置关键词
@Bean
Binding smsemailbinding(@Qualifier("smsqueue") Queue queue, DirectExchange exchange){
return BindingBuilder.bind(queue).to(exchange).with("sms.email");
}
@Bean
Binding emailsmsbinding(@Qualifier("emailqueue") Queue queue, DirectExchange exchange){
return BindingBuilder.bind(queue).to(exchange).with("sms.email");
}
}
//发消息通过关键字给指定队列发送
@Test
public void producerPublish(){
rabbitTemplate.convertAndSend("sms-email-publish-exchange","sms","这是短信服务");
}
- 消费者
同上
- Topics
一个生产者发送到交换机 交换机绑定队列 队列给多个消费者推送消息 通过关键词来判断队那个队列接收
关键词改变
'*' 代表一个单词
'#' 代表 0个 或 多个 单词
- 生产者
@Configuration
public class TopicConfig {
//队列1
@Bean("smsqueue")
public Queue smsqueue(){
return new Queue("topic-sms-queue");
}
//队列2
@Bean("emailqueue")
public Queue emailqueue(){
return new Queue("topic-email-queue");
}
//交换机
@Bean
public TopicExchange exchange(){
return new TopicExchange("topic-sms-email-topic");
}
//绑定队列1
@Bean
Binding smsbind(@Qualifier("smsqueue") Queue queue, TopicExchange exchange){
return BindingBuilder.bind(queue).to(exchange).with("sms.#");
}
//绑定队列二
@Bean
Binding emailbind(@Qualifier("emailqueue") Queue queue, TopicExchange exchange){
return BindingBuilder.bind(queue).to(exchange).with("#.email");
}
//发消息
//同时绑定只需发消息时同时满足两个队列的关键词
@Test
public void producerTopic2(){
rabbitTemplate.convertAndSend("topic-sms-email-topic","sms.email","这是短信+邮箱+topic");
}
}
- 消费者
同上
消息确认机制
- 交换机确认
继承RabbitTemplate.ConfirmCallback类重写以下方法
@Override
public void confirm(CorrelationData correlationData, boolean b, String s) {
System.out.println("发送的消息"+correlationData);
System.out.println("是否成功"+b);
System.out.println("错误原因"+s);
String id = correlationData.getId();
}
一般错误都是交换机名字错误
- 队列确认
继承RabbitTemplate.ReturnCallback类重写以下方法
@Override
public void returnedMessage(Message message, int i, String s, String s1, String s2) {
System.out.println("消息体"+message);
System.out.println("应答码"+i);
System.out.println("错误消息"+s);
System.out.println("交换机"+s1);
System.out.println("路由键"+s2);
}
一般错误都是路由键错误
如果监听到消息 在使用时出现异常不算消费
延迟队列
发送消息给死信队列设置时间 时间一到给正常的交换价发信息
//创建死信队列
@Bean("dead")
public Queue deadqueue(){
Map map = new HashMap();
map.put("x-dead-letter-exchange","normal-exchange");
map.put("x-dead-letter-routing-key","normal");
/**
1、name: 队列的名称;
2、durable: 是否持久化;
3、exclusive: 是否独享、排外的;
4、autoDelete: 是否自动删除;
5.arguments:队列的其他属性参数,
(1)x-message-ttl:消息的过期时间,单位:毫秒;
(2)x-expires:队列过期时间,队列在多长时间未被访问将被删除,单位:毫秒;
(3)x-max-length:队列最大长度,超过该最大值,则将从队列头部开始删除消息;
(4)x-max-length-bytes:队列消息内容占用最大空间,受限于内存大小,超过该阈值则从队列头部开始删除消息;
(5)x-overflow:设置队列溢出行为。这决定了当达到队列的最大长度时消息会发生什么。有效值是drop-head、reject-publish或reject-publish-dlx。仲裁队列类型仅支持drop-head;
(6)x-dead-letter-exchange:死信交换器名称,过期或被删除(因队列长度超长或因空间超出阈值)的消息可指定发送到该交换器中;
(7)x-dead-letter-routing-key:死信消息路由键,在消息发送到死信交换器时会使用该路由键,如果不设置,则使用消息的原来的路由键值
(8)x-single-active-consumer:表示队列是否是单一活动消费者,true时,注册的消费组内只有一个消费者消费消息,其他被忽略,false时消息循环分发给所有消费者(默认false)
(9)x-max-priority:队列要支持的最大优先级数;如果未设置,队列将不支持消息优先级;
(10)x-queue-mode(Lazy mode):将队列设置为延迟模式,在磁盘上保留尽可能多的消息,以减少RAM的使用;如果未设置,队列将保留内存缓存以尽可能快地传递消息;
(11)x-queue-master-locator:在集群模式下设置镜像队列的主节点信息。
*/
return new Queue("dead",true,true,true,map);
}
@RequestMapping("/send/{msg}/{key}")
//msg消息体key路由键
public String test3(@PathVariable("msg")String msg,@PathVariable("key")String key){
//唯一标识
String uuid = UUID.randomUUID().toString();
CorrelationData correlationData = new CorrelationData();
correlationData.setId(uuid);
//发给死信队列
rabbitTemplate.convertAndSend("dead-exchange", key, msg, new MessagePostProcessor() {
//设置时间
@Override
public Message postProcessMessage(Message message) throws AmqpException {
message.getMessageProperties().setExpiration("10000");
return message;
}
},correlationData);
return "ok";
}
手动ACK
- 配置
spring.rabbitmq.listener.simple.acknowledge-mode = manual
- 使用
@RabbitHandler
public void processMessage2(String message, Channel channel,@Headers Map<String,Object> map) {
System.out.println(message);
if (map.get("error")!= null){
System.out.println("错误的消息");
try {
channel.basicNack((Long)map.get(AmqpHeaders.DELIVERY_TAG),false,true); //否认消息
return;
} catch (IOException e) {
e.printStackTrace();
}
}
try {
channel.basicAck((Long)map.get(AmqpHeaders.DELIVERY_TAG),false); //手动确认消息
} catch (IOException e) {
e.printStackTrace();
}
}