最近挺忙的,时间有限我就不写原理了,直接贴代码。是最近项目需要我整理的。
yml配置
#rabbit配置
spring:
rabbitmq:
host: 172.17.236.101
port: 5672
username: XXX
password: XXX
#rabbit扩展配置配置
spring:
rabbitmq:
virtual-host: /
# 开启发送确认
publisher-confirms: true
# 开启发送失败退回
publisher-returns: true
listener:
simple:
retry:
enabled: false #消费者端的重试
acknowledge-mode: manual # 开启ACK
auto-startup: true #启动时自动启动容器 true
发送确认配置
/**
* 设置rabbit配置
* @author 大仙
*/
@Component
@Slf4j
public class RabbitTemplateConfig implements RabbitTemplate.ConfirmCallback,RabbitTemplate.ReturnCallback{
@Autowired
private RabbitTemplate rabbitTemplate;
@Autowired
private RedisTemplate<String,String> redisTemplate;
@PostConstruct
public void init(){
rabbitTemplate.setConfirmCallback(this::confirm);
rabbitTemplate.setReturnCallback(this::returnedMessage);
}
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
//发送成功
if(ack){
//不做处理,等待消费成功,清楚缓存
log.info(correlationData.getId()+":发送成功");
}else{
//持久化到数据库
log.error(correlationData.getId()+":发送失败");
log.info("备份内容:"+redisTemplate.opsForValue().get(correlationData.getId()));
}
//不管成功与否读删除redis里面备份的数据
redisTemplate.delete(correlationData.getId());
}
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
log.error("消息主体 message : "+message);
log.error("描述:"+replyText);
log.error("消息使用的交换器 exchange : "+exchange);
log.error("消息使用的路由键 routing : "+routingKey);
}
}
config配置:
@Configuration
public class RabbitConfig{
/**
* direct直连模型
* fanout无路由模式,使用场景广播消息
* topic 模糊路由模式,适用业务分组
* fanout>direct>topic 这里是多消费模式,topic和fanout都能实现,通过性能对比选择fanout 11>10>6
*/
/**
* 消息交换机的名字
* */
public static final String ORDER_FANOUT_EXCHANGE = "order.success";
/**
* 队列的名字
* */
public static final String ORDER_FANOUT_QUEUE = "order.success.order";
/**
* 1.队列名字
* 2.durable="true" 是否持久化 rabbitmq重启的时候不需要创建新的队列
* 3.auto-delete 表示消息队列没有在使用时将被自动删除 默认是false
* 4.exclusive 表示该消息队列是否只在当前connection生效,默认是false
*/
@Bean
public Queue fanoutOrderQueue() {
return new Queue(ORDER_FANOUT_QUEUE,true,false,false);
}
/**
* 1.交换机名字
* 2.durable="true" 是否持久化 rabbitmq重启的时候不需要创建新的交换机
* 3.autoDelete 当所有消费客户端连接断开后,是否自动删除队列
*/
@Bean
public FanoutExchange fanoutOrderExchange(){
return new FanoutExchange(ORDER_FANOUT_EXCHANGE,true,false);
}
/**
* 绑定
* @return
*/
@Bean
public Binding bindingFanoutOrderQueue() {
return BindingBuilder.bind(fanoutOrderQueue()).to(fanoutOrderExchange());
}
//########################################渠道数据汇总开始########################################################//
/**
* 渠道活动数据统计
*/
public final static String EXCHANGE_ACTIVITY_COLLECT = "activity.collect";
public final static String ROUTING_ACTIVITY_COLLECT = "activity.collect";
public final static String QUEUE_ACTIVITY_COLLECT_CMP = "activity.collect.cmp";
/**
* 渠道活动统计
*/
@Bean
public DirectExchange exchangeActivityCollect() {
return new DirectExchange(EXCHANGE_ACTIVITY_COLLECT);
}
@Bean
public Queue queueActivityCollect() {
return new Queue(QUEUE_ACTIVITY_COLLECT_CMP);
}
@Bean
public Binding routingActivityCollect() {
return BindingBuilder
.bind(queueActivityCollect())
.to(exchangeActivityCollect())
.with(ROUTING_ACTIVITY_COLLECT);
}
//########################################渠道数据汇总结束########################################################//
}
抽象实体配置:
/**
* @Author: 朱维
* @Date 11:42 2019/12/10
*/
@Data
public class MqMessage implements Serializable {
/**
* 消息ID
*/
protected String id = UUID.randomUUID().toString();
}
发送业务接口配置
/**
* @Author: 朱维
* @Date 15:50 2019/12/10
*/
public interface ProducerService {
/**
* 发送消息
* @param content
* @param exchangeName
* @param routingKey
*/
void sendMsg(MqMessage content, String exchangeName, String routingKey);
}
发送业务实现类
/**
* @Author: 朱维
* @Date 15:51 2019/12/10
*/
@Service
public class ProducerServiceImpl implements ProducerService {
@Autowired
private RabbitTemplate rabbitTemplate;
@Autowired
private RedisTemplate<String,String> redisTemplate;
@Override
public void sendMsg (MqMessage content, String exchangeName, String routingKey){
Message message = MessageBuilder.withBody(JSONObject.toJSONString(content).getBytes())
.setContentType(MessageProperties.CONTENT_TYPE_JSON)
.setCorrelationId(content.getId()).build();
CorrelationData data = new CorrelationData(content.getId());
//存储到redis
redisTemplate.opsForValue().set(data.getId(),JSONObject.toJSONString(content));
rabbitTemplate.convertAndSend(exchangeName,routingKey,message,data);
}
}
接收监听抽象类
/**
* 消费者
* @author 大仙
*/
public abstract class AbstractReceiver {
protected static Logger logger = LoggerFactory.getLogger(AbstractReceiver.class);
@Autowired
protected ReceiveService receiveService;
/**
* 业务执行方法
* @param content
*/
protected abstract void execute(JSONObject content)throws Exception;
/**
* 失败执行
* @param content
*/
protected void failExecute(JSONObject content){
logger.info("开始失败补偿======");
}
protected void receiveMessage(Message message, Channel channel) throws IOException{
/**
* 防止重复消费,可以根据传过来的唯一ID先判断缓存数据库中是否有数据
* 1、有数据则不消费,直接应答处理
* 2、缓存没有数据,则进行消费处理数据,处理完后手动应答
* 3、如果消息处理异常则,可以存入数据库中,手动处理(可以增加短信和邮件提醒功能)
*/
try{
JSONObject content = receiveService.getContent(message);
//已经消费,直接返回
if(receiveService.canConsume(content,message.getMessageProperties().getConsumerQueue())){
logger.info(message.getMessageProperties().getConsumerQueue()+"已经消费过");
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
}else{
//消费当前消息
execute(content);
logger.info(message.getMessageProperties().getConsumerQueue()+"消费成功");
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
}
}catch (Exception e){
e.printStackTrace();
try {
if(receiveService.dealFailAck(message,channel)){
logger.info("回归队列:"+message);
}else{
logger.error("消费失败:"+message);
failExecute(receiveService.getContent(message));
}
}catch (Exception e1){
//扔掉数据
channel.basicNack(message.getMessageProperties().getDeliveryTag(),false,false);
logger.error("重试消费失败:"+message);
failExecute(receiveService.getContent(message));
}
}
}
}
接收业务接口
/**
* @Author: 朱维
* @Date 18:10 2019/12/10
*/
public interface ReceiveService {
/**
* 获取内容
* @param message
* @return
*/
JSONObject getContent(Message message);
/**
* 是否能消费,防止重复消费
* @param content
* @return
*/
Boolean canConsume(JSONObject content, String queueName);
/**
* 处理ack
* @param message
* @param channel
*/
Boolean dealFailAck(Message message, Channel channel) throws IOException, InterruptedException;
}
接收业务实现
/**
* @Author: 朱维
* @Date 18:11 2019/12/10
*/
@Service
@Slf4j
public class ReceiveServiceImpl implements ReceiveService {
@Autowired
private RedisTemplate<String,String> redisTemplate;
private static final String ENCODING = Charset.defaultCharset().name();
private static final SerializerMessageConverter SERIALIZER_MESSAGE_CONVERTER = new SerializerMessageConverter();
@Override
public JSONObject getContent(Message message) {
String body = getBodyContentAsString(message);
JSONObject content = JSONObject.parseObject(body);
return content;
}
@Override
public Boolean canConsume(JSONObject content,String queueName) {
if(redisTemplate.opsForValue().get(queueName+":"+content.getString("id"))==null){
return false;
}else{
//存储消费标志
redisTemplate.opsForValue().set(queueName+":"+content.getString("id"),"1",30, TimeUnit.MINUTES);
return true;
}
}
/**
* 消息异常的时候处理
* @param message
*/
@Override
public Boolean dealFailAck(Message message,Channel channel) throws IOException, InterruptedException{
JSONObject content = getContent(message);
//单个消息控制
String redisCountKey = "retry"+message.getMessageProperties().getConsumerQueue()+content.getString("id");
String retryCount = redisTemplate.opsForValue().get(redisCountKey);
long basic = 1000L;
long deliveryTag = message.getMessageProperties().getDeliveryTag();
//队列控制
String queueKey = "retry"+message.getMessageProperties().getConsumerQueue();
//没有重试过一次
if(StringUtils.isBlank(retryCount)){
if(!redisTemplate.opsForValue().setIfAbsent(queueKey,"lock")) {
channel.basicNack(deliveryTag,false,true);
log.info("deliveryTag:"+deliveryTag);
return true;
}
redisTemplate.opsForValue().set(redisCountKey,"1");
log.info("开始第一次尝试:");
}else{
switch (Integer.valueOf(retryCount)){
case 1:
Thread.sleep(basic*8);
redisTemplate.opsForValue().set(redisCountKey,"2");
log.info("开始第二次尝试:");
break;
case 2:
Thread.sleep(basic*15);
redisTemplate.opsForValue().set(redisCountKey,"3");
log.info("开始第三次尝试:");
break;
case 3:
Thread.sleep(basic*30);
redisTemplate.opsForValue().set(redisCountKey,"4");
log.info("开始第四次尝试:");
break;
default:
//扔掉消息,准备持久化
redisTemplate.delete(redisCountKey);
redisTemplate.delete(queueKey);
channel.basicNack(deliveryTag,false,false);
return false;
}
}
channel.basicNack(deliveryTag,false,true);
return true;
}
/**
* 获取message的body
* @param message
* @return
*/
private String getBodyContentAsString(Message message) {
if (message.getBody() == null) {
return null;
}
try {
String contentType = (message.getMessageProperties() != null) ? message.getMessageProperties().getContentType() : null;
if (MessageProperties.CONTENT_TYPE_SERIALIZED_OBJECT.equals(contentType)) {
return SERIALIZER_MESSAGE_CONVERTER.fromMessage(message).toString();
}
if (MessageProperties.CONTENT_TYPE_TEXT_PLAIN.equals(contentType)
|| MessageProperties.CONTENT_TYPE_JSON.equals(contentType)
|| MessageProperties.CONTENT_TYPE_JSON_ALT.equals(contentType)
|| MessageProperties.CONTENT_TYPE_XML.equals(contentType)) {
return new String(message.getBody(), ENCODING);
}
}
catch (Exception e) {
// ignore
}
// Comes out as '[B@....b' (so harmless)
return message.getBody().toString() + "(byte[" + message.getBody().length + "])";
}
}
消费者具体实现:
@Component
public class OrderPaySuccessListner extends AbstractReceiver {
@Autowired
private OrderService orderService;
@Autowired
private WeCodeOrderDao weCodeOrderDao;
@Override
protected void execute(JSONObject content) throws Exception {
//处理具体业务
}
@Override
@RabbitListener(queues = RabbitConfig.ORDER_FANOUT_QUEUE)
protected void receiveMessage(Message message, Channel channel) throws IOException {
super.receiveMessage(message,channel);
}
}