一,RabbitMQ简介
RabbitMQ是实现了高级消息队列协议(AMQP)的开源消息代理软件(亦称面向消息的中间件)。RabbitMQ服务器是用Erlang语言编写的,而集群和故障转移是构建在开放电信平台框架上的。所有主要的编程语言均有与代理接口通讯的客户端库。
优点:跨语言,高并发。
二,实际项目中作用
(一)任务异步处理
场景:用户下单后,根据订单信息进行减库存以及增加积分。
传统:必须要整个流程走完。
如今:下完单,用户操作就可以结束了,不必等积分增加完。
(二) 应用程序解耦
场景:下单减库存。
传统模式的缺点:当前是基于同步调用方式,订单服务与库存服务处于耦合状态,假设库存服务状态异常,则订单服务在调用库存服务时,也会出现失败。
如今:订单服务:将用户订单信息写入订单数据库,并向消息队列发送订单消息,成功发送之后,向用户返回下单成功。
库存服务:监听消息队列,发现消息队列中存在订单消息,则取回,根据订单信息进行库存操作
此时两个服务之前不存在耦合关系,两个服务之间基于消息队列完成消息传递。
(三)流量削峰
场景:秒杀或抢购活动,
传统:很有可能因为出现瞬时流量暴增,导致后端服务无法承受这些流量,最终导致服务宕机。
如今:架设消息队列,对流量进行限流计数。可以控制前端请求数量。
三,几种常见模式案例
1.工作对列模式:一个producer生产者
应用场景:对于 任务过重或任务较多情况使用工作队列可以提高任务处理的速度。
测试:
1、使用入门程序,启动多个消费者。
2、生产者发送多个消息。
生产者:
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class QuickProducer {
public static final String QUEUE="helloworld";
public static void main(String[] args){
Connection connection =null;
Channel channel = null;
try{
//声明连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
//设置ip
connectionFactory.setHost("127.0.0.1");
//设置端口
connectionFactory.setPort(5672);
//设置用户名
connectionFactory.setUsername("guest");
//设置密码
connectionFactory.setPassword("guest");
//设置虚拟主机
//虚拟主机:当安装好了一个rabbitMQ服务之后,内部可以去设置多个虚拟的rabbitmq,对于虚拟主机的设置是通过名称进行划分的。且相互独立
connectionFactory.setVirtualHost("/");
//声明连接对象与通道对象
connection = connectionFactory.newConnection();
channel = connection.createChannel();
//声明队列
/**
* String queue, :队列名称
* boolean durable, :队列是否持久化。当为true,队列信息会被保存到磁盘
* boolean exclusive, :是否独占此连接,当为true,则此连接中只有这一个队列,当为false,可以存在多个
* boolean autoDelete,:是否自动删除,当为true,此队列一旦没有消费者监听,则自动从rabbitmq中删除,当为false,则不会
* Map<String, Object> arguments:队列的属性设置
*/
channel.queueDeclare(QUEUE,true,false,false,null);
//发送消息
/**
* String exchange, :交换机
* String routingKey, :路由key
* BasicProperties props,:消息的属性设置
* byte[] body:被发送的消息
*/
String message ="第三条消息";
channel.basicPublish("",QUEUE,null,message.getBytes());
}catch (Exception e){
e.printStackTrace();
}finally {
try {
channel.close();
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
try {
connection.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
消费者1:
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class QuickConsumer {
public static final String QUEUE="helloworld";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = null;
Channel channel = null;
//声明连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("127.0.0.1");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
connectionFactory.setVirtualHost("/");
//声明连接对象与通道对象
connection = connectionFactory.newConnection();
channel = connection.createChannel();
Consumer consumer = new DefaultConsumer(channel){
@Override
/**
* String consumerTag, : 消费者标识
* Envelope envelope,:信封
* AMQP.BasicProperties properties,:默认属性值
* byte[] body:被消费的数据
*/
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String value = new String(body);
System.out.println("消费者一号"+value);
}
};
//设置监听
/**
* String queue,:被监听的队列名称
* boolean autoAck,:是否开启自动应答。当为true, 。。当为false,需要开发人员手动的编码去通知rabbitmq
* Consumer callback:获取消息的回调方法
*/
channel.basicConsume(QUEUE,true,consumer);
}
}
消费者2:
public class QuickConsumer2 {
public static final String QUEUE="helloworld";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = null;
Channel channel = null;
//声明连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("127.0.0.1");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
connectionFactory.setVirtualHost("/");
//声明连接对象与通道对象
connection = connectionFactory.newConnection();
channel = connection.createChannel();
Consumer consumer = new DefaultConsumer(channel){
@Override
/**
* String consumerTag, : 消费者标识
* Envelope envelope,:信封
* AMQP.BasicProperties properties,:默认属性值
* byte[] body:被消费的数据
*/
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String value = new String(body);
System.out.println("消费者二号"+value);
}
};
//设置监听
/**
* String queue,:被监听的队列名称
* boolean autoAck,:是否开启自动应答。当为true, 。。当为false,需要开发人员手动的编码去通知rabbitmq
* Consumer callback:获取消息的回调方法
*/
channel.basicConsume(QUEUE,true,consumer);
}
}
结果:
默认轮询策略
消费者一号第一条消息
消费者二号第二条消息
消费者一号第三条消息
2.发布、订阅模式
1 import com.rabbitmq.client.BuiltinExchangeType;
2 import com.rabbitmq.client.Channel;
3 import com.rabbitmq.client.Connection;
4 import com.rabbitmq.client.ConnectionFactory;
5
6 import java.io.IOException;
7 import java.util.concurrent.TimeoutException;
8
9 public class PublishProducer {
10
11 //队列名称
12 public static final String QUEUE_INFORM_EMAIL ="queue_inform_email";
13 public static final String QUEUE_INFORM_SMS ="queue_inform_sms";
14
15 //交换机名称
16 private static final String EXCHANGE_FANOUT_INFORM="exchange_fanout_inform";
17
18 public static void main(String[] args) {
19
20 Connection connection = null;
21 Channel channel = null;
22
23 try{
24
25 //声明连接工厂
26 ConnectionFactory connectionFactory = new ConnectionFactory();
27 connectionFactory.setHost("127.0.0.1");
28 connectionFactory.setPort(5672);
29 connectionFactory.setUsername("guest");
30 connectionFactory.setPassword("guest");
31 connectionFactory.setVirtualHost("/");
32
33 //声明连接对象与通道对象
34 connection = connectionFactory.newConnection();
35 channel = connection.createChannel();
36
37 //声明队列
38 channel.queueDeclare(QUEUE_INFORM_EMAIL,true,false,false,null);
39 channel.queueDeclare(QUEUE_INFORM_SMS,true,false,false,null);
40
41 //声明交换机
42 /**
43 * name:交换机名称
44 * type: 交换机的类型 当工作模式为发布/订阅模式,交换机的类型:fanount
45 */
46 channel.exchangeDeclare(EXCHANGE_FANOUT_INFORM, BuiltinExchangeType.FANOUT);
47
48 //队列绑定交换机
49 channel.queueBind(QUEUE_INFORM_EMAIL,EXCHANGE_FANOUT_INFORM,"");
50 channel.queueBind(QUEUE_INFORM_SMS,EXCHANGE_FANOUT_INFORM,"");
51
52 //生产者会将消息发送到交换机,交换机会按照一定的规则将消息转发给一个或多个队列
53
54 //发送消息
55 channel.basicPublish(EXCHANGE_FANOUT_INFORM,"",null,"publish message".getBytes());
56
57 }catch (Exception e){
58 e.printStackTrace();
59 }finally {
60 try {
61 channel.close();
62 } catch (IOException e) {
63 e.printStackTrace();
64 } catch (TimeoutException e) {
65 e.printStackTrace();
66 }
67 try {
68 connection.close();
69 } catch (IOException e) {
70 e.printStackTrace();
71 }
72 }
73 }
74 }
1 import com.rabbitmq.client.*;
2
3 import java.io.IOException;
4 import java.util.concurrent.TimeoutException;
5
6 public class PublishConsumer {
7
8 //队列名称
9 public static final String QUEUE_INFORM_EMAIL ="queue_inform_email";
10 public static final String QUEUE_INFORM_SMS ="queue_inform_sms";
11
12 public static void main(String[] args) throws IOException, TimeoutException {
13
14 Connection connection = null;
15 Channel channel = null;
16
17 //声明连接工厂
18 ConnectionFactory connectionFactory = new ConnectionFactory();
19 connectionFactory.setHost("127.0.0.1");
20 connectionFactory.setPort(5672);
21 connectionFactory.setUsername("guest");
22 connectionFactory.setPassword("guest");
23 connectionFactory.setVirtualHost("/");
24
25 //声明连接对象与通道对象
26 connection = connectionFactory.newConnection();
27 channel = connection.createChannel();
28
29
30 //设置监听
31 Consumer consumer = new DefaultConsumer(channel){
32 @Override
33 public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
34 String value = new String(body);
35 System.out.println(value);
36 }
37 };
38 channel.basicConsume( QUEUE_INFORM_EMAIL,true,consumer);
39
40 }
41 }
channel.basicConsume( QUEUE_INFORM_EMAIL,true,consumer);
当
channel.basicConsume( QUEUE_INFORM_SMS,true,consumer);
总结
1、每个消费者监听自己的队列。
2、生产者将消息发给broker,由交换机将消息转发到绑定此交换机的每个队列,每个绑定交换机的队列都将接收到消息
3. Routing路由模式
1、每个消费者监听自己的队列,并且设置routingkey。
2、生产者将消息发给交换机,由交换机根据routingkey来转发消息到指定的队列。
生产者:
1 package com.itheima.producer;
2
3 import com.rabbitmq.client.BuiltinExchangeType;
4 import com.rabbitmq.client.Channel;
5 import com.rabbitmq.client.Connection;
6 import com.rabbitmq.client.ConnectionFactory;
7
8 import java.io.IOException;
9 import java.util.concurrent.TimeoutException;
10
11 public class RoutingProducer {
12
13 //队列名称
14 private static final String QUEUE_ROUTING_EMAIL = "queue_routing_email";
15 private static final String QUEUE_ROUTING_SMS = "queue_routing_sms";
16
17 //交换机名称
18 private static final String EXCHANGE_ROUTING_INFORM="exchange_routing_inform";
19
20 public static void main(String[] args) {
21
22 Connection connection = null;
23 Channel channel = null;
24
25 try{
26
27 //声明连接工厂
28 //声明连接工厂
29 ConnectionFactory connectionFactory = new ConnectionFactory();
30 connectionFactory.setHost("127.0.0.1");
31 connectionFactory.setPort(5672);
32 connectionFactory.setUsername("guest");
33 connectionFactory.setPassword("guest");
34 connectionFactory.setVirtualHost("/");
35
36 //声明连接对象与通道对象
37 connection = connectionFactory.newConnection();
38 channel = connection.createChannel();
39
40 //声明队列
41 channel.queueDeclare(QUEUE_ROUTING_EMAIL,true,false,false,null);
42 channel.queueDeclare(QUEUE_ROUTING_SMS,true,false,false,null);
43
44 //声明交换机
45 //当使用路由模式,交换机类型必须为direct
46 channel.exchangeDeclare(EXCHANGE_ROUTING_INFORM, BuiltinExchangeType.DIRECT);
47
48 //队列绑定交换机
49 channel.queueBind(QUEUE_ROUTING_EMAIL,EXCHANGE_ROUTING_INFORM,"info");
50 channel.queueBind(QUEUE_ROUTING_SMS,EXCHANGE_ROUTING_INFORM,"info");
51
52 //发送消息
53 channel.basicPublish(EXCHANGE_ROUTING_INFORM,"info",null,"info message".getBytes());
54
55 }catch (Exception e){
56 e.printStackTrace();
57 }finally {
58 try {
59 channel.close();
60 } catch (IOException e) {
61 e.printStackTrace();
62 } catch (TimeoutException e) {
63 e.printStackTrace();
64 }
65 try {
66 connection.close();
67 } catch (IOException e) {
68 e.printStackTrace();
69 }
70 }
71 }
72 }
消费者:
1 import com.rabbitmq.client.*;
2
3 import java.io.IOException;
4 import java.util.concurrent.TimeoutException;
5
6 public class RoutingConsumer {
7
8 //队列名称
9 private static final String QUEUE_ROUTING_EMAIL = "queue_routing_email";
10 private static final String QUEUE_ROUTING_SMS = "queue_routing_sms";
11
12 public static void main(String[] args) throws IOException, TimeoutException {
13
14 Connection connection = null;
15 Channel channel = null;
16
17 //声明连接工厂
18 ConnectionFactory connectionFactory = new ConnectionFactory();
19 connectionFactory.setHost("127.0.0.1");
20 connectionFactory.setPort(5672);
21 connectionFactory.setUsername("guest");
22 connectionFactory.setPassword("guest");
23 connectionFactory.setVirtualHost("/");
24
25 //声明连接对象与通道对象
26 connection = connectionFactory.newConnection();
27 channel = connection.createChannel();
28
29
30 //设置监听
31 Consumer consumer = new DefaultConsumer(channel){
32 @Override
33 public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
34 String value = new String(body);
35 System.out.println(value);
36 }
37 };
38 channel.basicConsume(QUEUE_ROUTING_EMAIL,true,consumer);
39
40 }
41 }
结果:
info message
info message
4.通配符模式
1、每个消费者监听自己的队列,并且设置带统配符的routingkey。
2、生产者将消息发给broker,由交换机根据routingkey来转发消息到指定的队列
1 import com.rabbitmq.client.BuiltinExchangeType;
2 import com.rabbitmq.client.Channel;
3 import com.rabbitmq.client.Connection;
4 import com.rabbitmq.client.ConnectionFactory;
5
6 import java.io.IOException;
7 import java.util.concurrent.TimeoutException;
8
9 public class TopicProducer {
10
11 //队列名称
12 public static final String QUEUE_EMAIL="queue_topic_email";
13 public static final String QUEUE_SMS="queue_topic_sms";
14
15 //交换机名称
16 public static final String EXCHANGE_TOPIC_INFORM="exchange_topic_inform";
17
18 //路由key
19 public static final String ROUTING_EMAIL="inform.#.email.#"; // inform.email
20 public static final String ROUTING_SMS="inform.#.sms.#";//
21
22 public static void main(String[] args) {
23
24 Connection connection = null;
25
26 Channel channel = null;
27
28 try{
29
30 //声明连接工厂
31 //声明连接工厂
32 ConnectionFactory connectionFactory = new ConnectionFactory();
33 connectionFactory.setHost("127.0.0.1");
34 connectionFactory.setPort(5672);
35 connectionFactory.setUsername("guest");
36 connectionFactory.setPassword("guest");
37 connectionFactory.setVirtualHost("/");
38
39 //声明连接对象与通道对象
40 connection = connectionFactory.newConnection();
41 channel = connection.createChannel();
42
43 //声明队列
44 channel.queueDeclare(QUEUE_EMAIL,true,false,false,null);
45 channel.queueDeclare(QUEUE_SMS,true,false,false,null);
46
47 //声明交换机
48 //当使用通配符模式,交换机类型必须为topic
49 channel.exchangeDeclare(EXCHANGE_TOPIC_INFORM, BuiltinExchangeType.TOPIC);
50
51 //队列绑定交换机
52 channel.queueBind(QUEUE_EMAIL,EXCHANGE_TOPIC_INFORM,ROUTING_EMAIL);
53 channel.queueBind(QUEUE_SMS,EXCHANGE_TOPIC_INFORM,ROUTING_SMS);
54
55 //发送消息
56 //发送消息到邮件队列
57 //channel.basicPublish(EXCHANGE_TOPIC_INFORM,"inform.email",null,"email message".getBytes());
58
59 //发送消息到短信队列
60 //channel.basicPublish(EXCHANGE_TOPIC_INFORM,"inform.sms",null,"sms message".getBytes());
61
62 //发送消息到邮件与短信队列
63 channel.basicPublish(EXCHANGE_TOPIC_INFORM,"inform.sms.email",null,"sms email message".getBytes());
64 }catch(Exception e)
65
66 {
67 e.printStackTrace();
68 }finally{
69 try {
70 channel.close();
71 } catch (IOException e) {
72 e.printStackTrace();
73 } catch (TimeoutException e) {
74 e.printStackTrace();
75 }
76 try {
77 connection.close();
78 } catch (IOException e) {
79 e.printStackTrace();
80 }
81 }
82 }
83 }
1 import com.rabbitmq.client.*;
2
3 import java.io.IOException;
4 import java.util.concurrent.TimeoutException;
5
6 public class TopicConsumer {
7
8 //队列名称
9 public static final String QUEUE_EMAIL="queue_topic_email";
10 public static final String QUEUE_SMS="queue_topic_sms";
11
12 public static void main(String[] args) throws IOException, TimeoutException {
13
14 Connection connection = null;
15 Channel channel = null;
16
17 //声明连接工厂
18 ConnectionFactory connectionFactory = new ConnectionFactory();
19 connectionFactory.setHost("127.0.0.1");
20 connectionFactory.setPort(5672);
21 connectionFactory.setUsername("guest");
22 connectionFactory.setPassword("guest");
23 connectionFactory.setVirtualHost("/");
24
25 //声明连接对象与通道对象
26 connection = connectionFactory.newConnection();
27 channel = connection.createChannel();
28
29
30 //设置监听
31 Consumer consumer = new DefaultConsumer(channel){
32 @Override
33 public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
34 String value = new String(body);
35 System.out.println(value);
36 }
37 };
38 channel.basicConsume(QUEUE_SMS,true,consumer);
39
40 }
41 }
sms email message
总结几种模式:
四,springboot整合RabbitMQ
1.消费者config
1 import org.springframework.amqp.core.*;
2 import org.springframework.beans.factory.annotation.Qualifier;
3 import org.springframework.context.annotation.Bean;
4 import org.springframework.context.annotation.Configuration;
5
6 @Configuration
7 public class RabbitMQConfig {
8
9 public static final String QUEUE_INFORM_EMAIL = "queue_inform_email";
10 public static final String QUEUE_INFORM_SMS = "queue_inform_sms";
11
12 public static final String EXCHANGE_TOPICS_INFORM="exchange_topics_inform";
13
14
15 //声明队列
16 @Bean(QUEUE_INFORM_EMAIL)
17 public Queue QUEUE_INFORM_EMAIL(){
18 Queue queue = new Queue(QUEUE_INFORM_EMAIL);
19 return queue;
20 }
21
22 @Bean(QUEUE_INFORM_SMS)
23 public Queue QUEUE_INFORM_SMS(){
24 Queue queue = new Queue(QUEUE_INFORM_SMS);
25 return queue;
26 }
27
28 //声明交换机
29 @Bean(EXCHANGE_TOPICS_INFORM)
30 public Exchange EXCHANGE_TOPICS_INFORM(){
31 return ExchangeBuilder.topicExchange(EXCHANGE_TOPICS_INFORM).durable(true).build();
32 }
33
34 //队列绑定交换机
35 @Bean
36 public Binding BIND_QUEUE_INFORM_EMAIL(@Qualifier(QUEUE_INFORM_EMAIL)Queue queue,@Qualifier(EXCHANGE_TOPICS_INFORM)Exchange exchange){
37 return BindingBuilder.bind(queue).to(exchange).with(QUEUE_INFORM_EMAIL).noargs();
38 }
39
40 @Bean
41 public Binding BIND_QUEUE_INFORM_SMS(@Qualifier(QUEUE_INFORM_SMS)Queue queue,@Qualifier(EXCHANGE_TOPICS_INFORM)Exchange exchange){
42 return BindingBuilder.bind(queue).to(exchange).with(QUEUE_INFORM_SMS).noargs();
43 }
44
45 }
消费者config
1 import org.springframework.amqp.core.Queue;
2 import org.springframework.context.annotation.Bean;
3 import org.springframework.context.annotation.Configuration;
4
5 @Configuration
6 public class RabbitMQConfig {
7
8 public static final String QUEUE_INFORM_EMAIL = "queue_inform_email";
9 public static final String QUEUE_INFORM_SMS = "queue_inform_sms";
10
11
12 //声明队列
13 @Bean(QUEUE_INFORM_EMAIL)
14 public Queue QUEUE_INFORM_EMAIL(){
15 Queue queue = new Queue(QUEUE_INFORM_EMAIL);
16 return queue;
17 }
18
19 @Bean(QUEUE_INFORM_SMS)
20 public Queue QUEUE_INFORM_SMS(){
21 Queue queue = new Queue(QUEUE_INFORM_SMS);
22 return queue;
23 }
24
25 }
监听消费者:
import com.itheima.springboot.consumer.config.RabbitMQConfig;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
public class MyMessageConsumer {
//设置监听方法 , 每一个监听方法可以去设置监听一个或多个队列
@RabbitListener(queues = {RabbitMQConfig.QUEUE_INFORM_EMAIL})
public void receivceMessage(String message){
System.out.println(message);
}
}
五,全链路消息不丢失
TPS,QPS是指标。
1.生产者宕机:
A:此时可以考虑进行持久化操作,此处需要将==队列与消息==都进行持久化操作,将这两部分信息写入磁盘。
情形二; Q:当rabbitmq未将消息写入到磁盘时,消息队列宕机了,怎么办?
RabbitMQ针对生产者投递数据丢失,已经提供了两种解决机制, 分别是 ==重量级的事务机制== 与 ==轻量级的confirm机制==.
选择confirm机制:(事务同步效率低):
confirm模式需要基于channel进行设置, 一旦某条消息被投递到队列之后,消息队列就会发送一个确认信息给生产者。
情形三:高并发下生产者
1.阿里sql,对比mysql性能提高15%,对比nosql数据可以保存在磁盘不用考虑在内存空间的大小。生产者实例收到一个消息ack之后,就从kv存储中删除这条临时消息;收到一个消息nack之后,就从kv存储提取这条消息然后重新投递一次即可;也可以自己对kv存储里的消息做监控,如果超过一定时长没收到ack,就主动重发消息。(生产者有个记录表,每个一段时间扫描此表)。
2.预抓取总数100-300,根据压力测试临界值,阈值向下减100左右来设定,不可过大不可过小(堆积,效率低)。
2.消费者宕机
情形一
消费者成功接收到消息之后,可以自动的向rabbitMQ返回一个应答信息,通知rabbitMQ此条消息已经被成功的接收,当rabbitMQ接收到这条消息之后,则会将此条消息删除。这叫做自动ACK(自动应答)。
可以将自动应答改为手动应答,另外提高效率还可以批量ACK,集中返回此时当消费者接收到消息,不会马上自动向消息队列发送应答消息,而是需要开发人员手动编码发送应答消息, 从而保证消息队列不会自动删除这条消息,而是等到消费者返回ACK确认消息才会进行删除。(开发人员要做的就是当且只有库存服务执行完毕并调用成功物流系统之后才会向消息队列对返回一条确认消息,当消息队列接收到这条消息之后才删除此消息),现某个库存服务(消费者)宕机了. 那么就会将这个订单消息发送给其他的库存服务实例(消费者)去使用这条消息.
情形二;
A消费者宕机后,另一台消费者B机器发货,A重启,又发之前没发的货,发了两遍。
解决方案:避免重复发货,发货时判断状态码(幂等性)。
六。幂等性
对于相同的一次请求操作,不管操作多少次,得到的结果都是相同的。
2场景:
在开启消费者手动ack的情况下, 库存服务的消费者已经接收到了消息,并调用物流系统并成功更改状态进行发送, 但是在ack返回消息的时候, 这个消费者服务可能突然宕机, 因为消费者ack未返回, 所以会导致这个消息一直在MQ中, 当消费者重新启动就会又接收这个消息进行发货, 这样的话,就会导致相同的货品发货了两次!!!
3解决方案:
1)状态判断:在消费者接收消息执行之前, 先根据消息的标识信息查询DB,判断状态, 如果为已发货/待发货状态的话,则直接ack返回MQ,删除这条消息
2)乐观锁
七,有序性
将上述流程改造为只有一个消费者监听此队列,或这些消息只会推送给一个消费者即可。