rabbitmq全局架构图
这是一张经典的rabbitMQ模型图
我们可以简单把角色分为两类
客户端和服务端,客户端就是请求处理消息的机器,服务端就是完成路由,具体处理消息的机器。
客户端分为两个角色,生产者和消费者,生产者是发送消息方,消费者接收消息方。
服务端也称为broker。
Connection就是一次TCP连接,但是网络通信总是有阻塞的,所以我们需要更细粒度的channel来接收任务,执行任务,如果是消费端还要返还ack值。
配置项
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
spring:
rabbitmq:
host: 192.168.0.0 # 主机名
port: 5672 # 默认端口
virtual-host: / # 虚拟主机
username: rabbitmqadmin # 用户名
password: rabbitmqadmin # 密码
序列化
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.36</version>
</dependency>
有两个重点类JSON和JSONObject,其中JSON是个抽象类,是JSONObject的父类
所以它们都有序列化的方法toJsonString(),以及反序列化的方法parseObject
//序列化过程
String msg = JSONObject.toJSONString(message, SerializerFeature.WriteNullListAsEmpty);
//反序列化过程
Message<MonitorMessageBody> msg = JSON.parseObject(msg,Message.class);
需要注意的是,反序列化只是一种浅层反序列化,自己的成员只解析它的结构,如果这个成员内部还有reference,就用JSONObject进行包装而不是继续解析,因为实际上并没有这个类的类文件,而只提供了外层类文件如Message.class;
Message<MonitorMessageBody> m = JSON.parseObject(msg,Message.class);
//单层get是允许的
MonitorMessageBody message = m.getBody().getMessageBody();
//对内部对象再次get是会报错的,因为此时是一个JSONObject对象
有两种解决方法:
第一种是根据JSON字符串自己写一个方法深拷贝这个对象(建议使用BeanUtils.copyproperties()这样的工具帮忙get和set属性加快速度)
第二种是使用JSON的API取到需要的层次再解析
JSONObject jsonObject = JSON.parseObject(msg);
JSONObject jsonObject1 = (JSONObject) jsonObject.get("body");
JSONObject jsonObject2 = (JSONObject) jsonObject1.get("messageBody");
MonitorMessageBody message = jsonObject2.toJavaObject(MonitorMessageBody.class);
队列和交换机
需要声明一个交换机,队列,和之间的绑定,绑定的原因是,可以有多个交换机,并且交换机被多个队列绑定
@Configuration
public class MQConfig {
public static final String MONITOR_EXCHANGE = "monitor.direct";
public static final String MONITOR_QUEUE= "monitor.queue";
/**
* * 交换机
* @return
*/
@Bean("monitorExchange")
public DirectExchange monitorExchange(){
return new DirectExchange(MONITOR_EXCHANGE);
}
/**
* * 队列
* @return
*/
@Bean("monitorQueue")
public Queue monitorQueue(){
return QueueBuilder.durable(MONITOR_QUEUE).build();
}
/**
* * 队列和交换机的绑定关系
* @param queue
* @param exchange
* @return
*/
@Bean
public Binding queueABinding(@Qualifier("monitorQueue") Queue queue,
@Qualifier("monitorExchange") DirectExchange exchange){
return BindingBuilder.bind(queue).to(exchange).with("");
}
}
生产消费
@Component
public class RabbitMqSender{
@Autowired
private RabbitTemplate rabbitTemplate;
public final String ROUTING_KEY = "key";
/**
* @param msg 需要发送的信息
*/
public void send(String msg,String messageNo) {
//让消息绑定一个 id 值
rabbitTemplate.convertAndSend(MQConfig.MONITOR_EXCHANGE,ROUTING_KEY,msg);
}
}
@Component
public class RabbitMqListener{
/*
* 消费方法
* @param msg
*/
@RabbitListener(queues = MQConfig.MONITOR_QUEUE)
public void listenDemo(String msg) {
}
}
可靠性保证
可靠性分析
在RabbitMQ服务器非集群情况下,可以根据图把可靠分为三个阶段,保证到交换机的可靠,保证到队列的可靠,保证消费者收到的可靠。
到交换机的可靠
spring:
rabbitmq:
publisher-confirm-type: correlated
@Component
public class RabbitMqSender implements RabbitTemplate.ConfirmCallback{
@Autowired
private RabbitTemplate rabbitTemplate;
@PostConstruct
public void init(){
rabbitTemplate.setConfirmCallback(this);
}
/**
* @param msg 需要发送的信息
*/
public void send(String msg,String messageNo) {
//让消息绑定一个 id 值
CorrelationData correlationData = new CorrelationData(messageNo);
log.info("发送消息id为:{}内容为{}",correlationData.getId(),msg);
rabbitTemplate.convertAndSend(MQConfig.MONITOR_EXCHANGE,"",msg,correlationData);
System.out.println("发送完成");
}
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
String id = correlationData != null ? correlationData.getId() : "unknown";
System.out.println(ack);
if(!ack) {
log.error("消息 id:{}未成功投递到交换机,原因是:{}", id, cause);
}
}
}
到队列的可靠
spring:
rabbitmq:
template:
mandatory: true
@Component
public class RabbitMqSender implements RabbitTemplate.ReturnsCallback{
@Autowired
private RabbitTemplate rabbitTemplate;
@PostConstruct
public void init(){
rabbitTemplate.setReturnsCallback(this);
}
/**
* @param msg 需要发送的信息
*/
public void send(String msg,String messageNo) {
//让消息绑定一个 id 值
CorrelationData correlationData = new CorrelationData(messageNo);
log.info("发送消息id为:{}内容为{}",correlationData.getId(),msg);
rabbitTemplate.convertAndSend(MQConfig.MONITOR_EXCHANGE,"",msg,correlationData);
System.out.println("发送完成");
}
@Override
public void returnedMessage(ReturnedMessage returned) {
String id = returned.getMessage().getMessageProperties().getHeader("spring_returned_message_correlation").toString();
log.error("消息 id:{}未成功投递到队列,内容为:{}",id,returned.getReplyText());
}
}
服务器到消费者的可靠
在direct或fanout模式下,两个队列绑定交换机,会报错,交换机只要一个ack。
但是在simple模式下不会报错,simple模式下会等待两个ack。
spring:
rabbitmq:
listener:
direct:
acknowledge-mode: manual
// simple:
// acknowledge-mode: manual
配置之后只有手动ack,才会消费掉。
@RabbitListener(queues = MQConfig.MONITOR_QUEUE)
public void listenAlarmDirect(String msg, org.springframework.amqp.core.Message mes, Channel channel) throws IOException {
try{
long tag = mes.getMessageProperties().getDeliveryTag();
//do something
//逐个回应
channel.basicAck(tag,false);
}catch(Exception e){
//自动返回队列
channel.basicReject(tag,true);
}
}
可靠性不一定非要保证
笔者接受过需求,每三秒发一条状态信息,这时候根本不需要应答,而自动应答反而可能会因为消息逻辑错误(尝试了向数据库存记录),不断重发消息,导致消息累加,这时候不如可以使用不应答或者手动直接应答ack
listener:
simple:
acknowledge-mode: NONE
来源
本人语雀笔记(语雀分享竟然收费了!失踪人口回归)