spring:
rabbitmq:
username: guest #默认账号
port: 5672 #默认端口
password: guest #密码
host: 127.0.0.1 #主机地址
virtual-host: / #虚拟路径
publisher-returns: true #exchange到queue失败,则回调return(需设置mandatory=true,否则不回回调,消息就丢了)
publisher-confirms: true # 如果消息没有到exchange,则confirm回调 ack消息确认机制
listener:
exchange: face.exchange #默认交换机
direct:
acknowledge-mode: manual #手动ack
simple:
acknowledge-mode: manual #手动ack
retry:
multiplier: 2 #应用于前一重试间隔的乘法器 比如第一次10秒 第二次就是20秒 第三次就是40秒
max-attempts: 5 #最大重试次数
enabled: true #开启重试机制
max-interval: 30000ms #最大30秒
initial-interval: 10000ms #第一次10秒
default-requeue-rejected: true #消费者抛出异常将会把消息重新放回队列 false则不放回 默认为true
concurrency: 10 #并发监听器线程调用最小数量
max-concurrency: 50 #并发监听器线程调用最大数量
生产者 Provider
@Component
public class Sender{
protected static final Logger logger = LoggerFactory.getLogger(Sender.class);
@Autowired
private AmqpTemplate amqpTemplate;
/**
*
* @param exchange 交换机名称
* @param routingKey 路由键
* @param message 消息内容
*/
public void send(String exchange, String routingKey, String message){
amqpTemplate.convertAndSend(exchange,routingKey,message);
}
}
监听者 Listener
import com.alibaba.fastjson.JSON;
import com.rabbitmq.client.Channel;
import facerecoftp.entity.DataModuleTest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.support.AmqpHeaders;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.stereotype.Component;
import java.io.IOException;
/**
* @author whh
* @date 2019/3/11 11:38
*/
@Component
public class Listener {
protected static final Logger logger = LoggerFactory.getLogger(Listener.class);
/**
* ExchangeTypes.TOPIC 交换机类型
* durable 是否持久化
* concurrency 并发量 可以理解为监听者数量 可以配置在yml文件里 用"{变量名}"去取
*/
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "队列名称", durable = "true"),
exchange = @Exchange(name = "交换机名称", type = ExchangeTypes.TOPIC),
key = {"路由键"}
),concurrency = "1")
public void sendImg(@Payload String content, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag){
// dataModuleTest为我生产者发送的序列化之前的对象 这里使用fastJSON序列化
DataModuleTest dataModuleTest = JSON.parseObject(content, DataModuleTest.class);
try {
// 手动ack ack之后消息队列的消息就会取出 如果抛出异常 监听者会重复收到消息
channel.basicAck(deliveryTag,false);
} catch (IOException e) {
e.printStackTrace();
}
logger.debug("发送message-->{}",dataModuleTest);
}
}
如果生产者需要ack 可以使用以下代码
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import handler.util.UuidUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeoutException;
/**
* @author whh
* @date 2019/3/11 10:57
*/
@Component
public class Sender implements RabbitTemplate.ConfirmCallback{
protected static final Logger logger = LoggerFactory.getLogger(Sender.class);
@Autowired
private RabbitTemplate rabbitTemplate;
@Value("${spring.rabbitmq.username}")
private String userName;
@Value("${spring.rabbitmq.password}")
private String password;
@Value("${spring.rabbitmq.host}")
private String host;
@Value("${spring.rabbitmq.virtual-host}")
private String virtualHost;
@Value("${spring.rabbitmq.port}")
private int port;
/**
* rabbitTemplate如果为单例的话,那回调就是最后设置的内容
* @param rabbitTemplate
*/
@Autowired
public Sender(RabbitTemplate rabbitTemplate) {
this.rabbitTemplate = rabbitTemplate;
rabbitTemplate.setConfirmCallback(this);
}
public Map<String,String[]> hashmap = new ConcurrentHashMap<>(1);
/**
*
* @param exchange 交换机名称
* @param routingKey 路由关键字
* @param content 消息内容
*/
public void send(String exchange,String queue, String routingKey, String content) throws IOException, TimeoutException {
String msgId = UuidUtil.get32UUID();
String[] arr = {exchange,queue,routingKey,content};
hashmap.put(msgId,arr);
Message message = MessageBuilder.withBody(content.getBytes()).setContentType(MessageProperties.CONTENT_TYPE_TEXT_PLAIN).setCorrelationId(msgId).build();
try {
rabbitTemplate.convertAndSend(exchange,routingKey,message,new CorrelationData(msgId));
} catch (AmqpException e) {
// TODO
logger.debug("消息发送失败补偿--{}",e.getMessage());
}
}
/**
* 消息发送交换机成功 回调方法 成功ack为true 失败为false
* @param correlationData
* @param ack
* @param cause
*/
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
String key = correlationData.getId();
if (!ack) {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setPort(port);
connectionFactory.setUsername(userName);
connectionFactory.setPassword(password);
connectionFactory.setHost(host);
connectionFactory.setVirtualHost(virtualHost);
Channel channel = null;
String[] split = hashmap.get(key);
String exchange = split[0];
String queue = split[1];
String routingKey = split[2];
String content = split[3];
try {
Connection connection = connectionFactory.newConnection();
channel = connection.createChannel();
channel.exchangeDeclare(exchange, "topic", true);
channel.queueDeclare(queue,true,false,false,null);
channel.queueBind(queue,exchange,routingKey);
send(exchange,queue,routingKey,content);
} catch (IOException | TimeoutException e) {
logger.debug("获取通道失败---{}",e.getMessage());
}
}
hashmap.remove(key);
}
}
防止消息丢失
发送方confirm方法的ack变量为false时,做补偿
消费者ack确认
channel.basicAck(deliveryTag,false);
队列与交换机持久化
channel.exchangeDeclare(exchange, "topic", true);
channel.queueDeclare(queue,true,false,false,null);