主要是确认消息被消费者消费完成后通知服务器将队列里面的消息清除,手动自动就是对异常的处理态度不同。
-
不配置Ack的话,此时的服务是no_ack=true的模式,就是说只要我发现你是消费了这个数据,至于异常不异常的,我不管了。
-
通知Ack机制就是这么来的,更加灵活的,我们需要Ack不自动,而是手动,这样做的好处,就是使得我们开发人员更加人性化或者灵活的来处理我们的业务代码,异常发生不会丢数据,更加方便的处理异常的问题以及数据的返回处理等。下面是通话机制的四条原则:
-
basic.Ack 发回给 RabbitMQ 以说明消费完了,可以将相应 message 从 RabbitMQ 的消息缓存中移除。
-
Basic.Ack 未被 consumer 发回给 RabbitMQ 前出现了异常,消费者过掉了,RabbitMQ 发现与该 consumer 对应的连接被断开,之后将该 message 以轮询方式发送给其他 consumer (假设存在多个 consumer 订阅同一个 queue)。
-
在 no_ack=true 就是默认的情况下,RabbitMQ 认为 message 一旦被 deliver 出去了,就已被确认了,所以会立即将缓存中的 message 删除。所以在 consumer 异常时会导致消息丢失。
-
来自 consumer 的 Basic.Ack 与 发送给 Producer 的 Basic.Ack 没有直接关系。
代码操作一波:
生产消费都是一样配置,这是fanpout模式可配置多个队列,就自己加吧
配置文件:
# 配置Rabbit链接信息(集群)
spring.rabbitmq.addresses=
spring.rabbitmq.username=
spring.rabbitmq.password=
server.port=8080
# 开启发送确认
spring.rabbitmq.publisher-confirms=true
# 开启发送失败退回
spring.rabbitmq.publisher-returns=true
# 开启ACK
spring.rabbitmq.listener.direct.acknowledge-mode=manual
spring.rabbitmq.listener.simple.acknowledge-mode=manual
config:
package com.mbyte.easy.emailProducer.config;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RabbitMQConfig {
//配置交换机
@Bean
public DirectExchange directExchange(){
return new DirectExchange("bootDirectExchange");
}
//配置队列
@Bean
public Queue directQueue(){
return new Queue("bootDirectQueue");
}
@Bean
public Queue fanoutQueue(){
return new Queue("bootFanoutQueue");
}
/**
* 配置交换机队列的绑定
* @param fanoutQueue 需要绑定队列对象,参数名和方法名一至就自动注入
* @param fanoutExchange 需要绑定交换机对象,参数名和@Bean方法名一至就自动注入
* @return
*/
@Bean
public Binding fanoutBinding(Queue fanoutQueue, FanoutExchange fanoutExchange){
return BindingBuilder.bind(fanoutQueue).to(fanoutExchange);
}
//配置FanoutExchange交换机
@Bean
public FanoutExchange fanoutExchange(){
return new FanoutExchange("fanoutExchange");
}
// //配置topicExchange交换机
// @Bean
// public TopicExchange topicExchange(){
// return new TopicExchange("topicExchange");
// }
}
实体模板:
package com.mbyte.easy.emailProducer.entity;
import com.mbyte.easy.mailInfo.entity.MailInfo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.List;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class MailTemplate implements Serializable {
/**
* 邮件主题
*/
private String subject;
/**
* 邮件类型
*/
private Integer emailType;
/**
* 邮件内容
*/
private String content;
private String email;
}
重点的实现业务:
package com.mbyte.easy.emailProducer.service.emailRecevie.impl;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.toolkit.SerializationUtils;
import com.mbyte.easy.emailProducer.entity.MailTemplate;
import com.mbyte.easy.emailProducer.service.emailRecevie.EmailService;
import com.mbyte.easy.mailInfo.entity.MailInfo;
import com.mbyte.easy.sysUser.entity.User;
import com.mbyte.easy.sysUser.service.IUserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
@Service
@Slf4j
public class EmailServiceImpl implements EmailService ,RabbitTemplate.ReturnCallback {
//注入amqp模版,利用这个就收发送
@Resource
private RabbitTemplate rabbitTemplate;
@Autowired
private IUserService userService;
@Override
public void sendEmailMessage(MailInfo mailInfo) {
List<User> list = userService.list();
for (User user : list) {
MailTemplate mailTemplate=new MailTemplate(mailInfo.getSubject(),mailInfo.getEmailType(),mailInfo.getContent(),user.getEmail());
this.rabbitTemplate.setReturnCallback(this);
this.rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
if (!ack) {
System.out.println("HelloSender消息发送失败" + cause + correlationData.toString());
} else {
System.out.println("HelloSender 消息发送成功 ");
}
});
this.rabbitTemplate.convertAndSend("bootFanoutQueue", JSON.toJSONString(mailTemplate));
log.info("邮件: "+JSON.toJSONString(mailTemplate));
}
}
@Override
public void returnedMessage(Message message, int i, String s, String s1, String s2) {
System.out.println("sender return success" + message.toString()+"==="+i+"==="+s1+"==="+s2);
}
}
现在就是把消息数据放对列了,消费者去拿,注意:生产者消费者不能是部署在同一个服务器,学习的话,可以开个别的端口假装是两个而服务器,就象并发和并行一样,天下武功为快不破,快的好像是并行。
生产者:
package com.emailProducer.handler;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.emailProducer.entity.MailTemplate;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.mail.DefaultAuthenticator;
import org.apache.commons.mail.Email;
import org.apache.commons.mail.EmailException;
import org.apache.commons.mail.SimpleEmail;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.Date;
@Slf4j
@Component
@RabbitListener(queues = "bootFanoutQueue")
public class HelloReceiver {
@RabbitHandler
public void process(String mailTemplate, Channel channel, Message message) throws IOException {
try {
javaSendEmail(mailTemplate);
//告诉服务器收到这条消息 已经被我消费了 可以在队列删掉 这样以后就不会再发了 否则消息服务器以为这条消息没处理掉 后续还会在发
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
System.out.println("receiver success");
} catch (IOException e) {
e.printStackTrace();
//丢弃这条消息
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false,false);
System.out.println("receiver fail");
}
}
public void javaSendEmail (String mailTemplateStr){
log.info("------------------------------------------"+mailTemplateStr);
MailTemplate mailTemplate= null;
try {
JSONObject jsonObject = JSONObject.parseObject(mailTemplateStr);
mailTemplate = JSON.toJavaObject(jsonObject, MailTemplate.class );
} catch (Exception e) {
e.printStackTrace();
}
log.info("------------------------------------------"+mailTemplate);
Email email = new SimpleEmail();
try {
email.setHostName("smtp.qq.com");
email.setSmtpPort(587);
XXXXXXXXX
email.setAuthenticator(new DefaultAuthenticator("XXXXXXXXX@qq.com", "授权码"));
email.setSSLOnConnect(true);
email.setFrom("XXXXX@qq.com");
email.setSubject(mailTemplate.getSubject());
email.setMsg(mailTemplate.getContent());
email.addTo(mailTemplate.getEmail());
email.send();
log.info("发送成功!");
} catch (EmailException e) {
e.printStackTrace();
log.info(e.getMessage());
}
}
}