消息队列是应用系统之间通信的方法,本质是队列,具有先进先出(FIFO)的特点,队列的元素是消息,所以叫消息队列,是一种中间件。
RabbitMQ的并发性最好,因为是由erlang语言编写的,综合来看也是RabbitMQ最好,基于AMQP协议。RabbitMQ默认的服务端口是5672,后台管理端口是15672。
Message:消息,消息是没有名字的,由消息头和不透明的消息体组成,消息体包括routing-key、priority等属性
Publisher:消息生产者
Exchange:交换器,用来接收生产者发送的消息并且将消息按路由传给服务器中的队列。有四种交换器,跟不同的转发策略有关
Queue:消息队列,用于保存消息直到发送给消费者
Binding:绑定,指交换器和消息队列之间基于路由键的关联
Connection:网络连接,位于一个客户端和Broker之间的TCP连接
Channel:信道,用于复用TCP连接的虚拟通道,一个Connection可以包含多个Channel。RabbitMQ建议客户端线程之间不要共用Channel,尽量共用Connection
Consumer:消息消费者,就是客户端线程
Virtual Host:虚拟主机,mini版的mq服务器
Broker:消息队列的服务器主体
Publisher生产的Message的routing-key如果和Binding中的binding-key相同的话,Exchange就将消息转发到对应的队列中,是完全匹配、单播的模式
忽略掉routing-key,将消息分发到Exchange绑定的所有Queue上,是广播模式
Message的routing-key与Binding的binding-key进行模糊匹配,交换器转发匹配的消息,用.号分割单词,用#号匹配0个或多个单词,*匹配不多不少一个单词
忽略routing-key,Exchange通过Message的头部信息过滤转发,很少用
关于两个属性:durable和autoDelete
exchange和queue的durable属性代表是否将exchange或queue保存在磁盘持久化,在rabbitmq服务器关闭后,如果不持久化则丢失exchange或queue,持久化则不会丢失
queue的autoDelete代表在所有消费者断开连接后是否自动删除队列
exchange的qutoDelete代表在所有绑定队列都不在使用时,是否自动删除交换器
生产者发送确认
发送确认有两种方法:
选择发布确认机制需要做3步:
消费者接收确认
接受确认可以通过ACK机制来确认消息是否被正确接收,可以手动ACK和自动ACK,在ACK后,消息会被队列删除。
自动ACK在消息发送后立即ACK,假如消费者在消息接收过程中网络异常,消息就有丢失的可能。手动ACK则可以在接收到消息之后再进行确认。
假如某服务忘记ACK,则RabbitMQ不再发送此类消息给消费者。
默认是不确认,即默认消费者都消费成功
一个队列只有一个消费者,应用于单发邮件、短信
一个队列有多个消费者,应用于抢红包,抢券
上面两种主要描述的是消费者的结构,下面三种模式主要是描述生产者的结构
生产者生产的消息发送fanout交换器,fanout交换器把消息发送给所有队列,应用于群发广告
生产者生产的消息发送direct交换器,direct交换器把消息发送给对应的某个队列,应用于聊天对话
生产者生产的消息发送topic交换器,topic交换器把消息发送给对应的某个队列,应用于按类分派
/*
* pom.xml
*/
<!-- rabbitmq -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
/*
* application.yml
*/
#=================== rabbitmq配置 ======================
rabbitmq:
host: 127.0.0.1
port: 5672
virtual-host: testHost
username: guest
password: guest
/*
* RabbitMQConfig.java
*/
@Slf4j
@Configuration
@ConfigurationProperties(prefix = "spring.rabbitmq")
public class RabbitMQConfig {
/**
* 主机地址
*/
private String host;
/**
* 端口
*/
private Integer port;
/**
* 用户名
*/
private String username;
/**
* 密码
*/
private String password;
/**
* 虚拟主机
*/
private String virtualHost;
//getter and setter..
@Bean
public ConnectionFactory rabbitmqConnectionFactory(){
//ConnectionFactory首选CachingConnectionFactory,可缓存生产者和消费者
CachingConnectionFactory cachingConnectionFactory = new CachingConnectionFactory(host,port);
cachingConnectionFactory.setUsername(username);
cachingConnectionFactory.setPassword(password);
cachingConnectionFactory.setVirtualHost(virtualHost);
//发布确认机制确保消息正确投递
//启用PublisherConfirms
//producer发送message给exchange,回调ConfirmCallback的confirm方法
//如果message到达exchange,则ack=true
//如果message没到达exchange,则ack=false
cachingConnectionFactory.setPublisherConfirms(true);
//启用PublisherReturns
//exchange传递消息给queue失败时,回调ReturnCallback的returnedMessage方法
//PublisherReturns需启用RabbitTemplate的Mandatory,消息才会返回给生产者,否则直接丢弃
cachingConnectionFactory.setPublisherReturns(true);
return cachingConnectionFactory;
}
@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory rabbitmqConnectionFactory){
RabbitTemplate rabbitTemplate = new RabbitTemplate(rabbitmqConnectionFactory);
//设置MessageConverter
rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter());
//设置接收确认为manual手动
//启用Mandatory
rabbitTemplate.setMandatory(true);
//重写ConfirmCallback的confirm方法
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
if(ack){
System.out.println(String.format("消息发送成功:correlationData(%s) , ack(%b) , cause(%s)",correlationData,ack,cause));
log.info("消息发送成功:correlationData({}) , ack({}) , cause({})",correlationData,ack,cause);
}else{
System.out.println( String.format("消息发送失败:correlationData(%s) , ack(%b) , cause(%s)",correlationData,ack,cause));
log.info("消息发送失败:correlationData({}) , ack({}) , cause({})",correlationData,ack,cause);
}
}
});
//重写ReturnCallback的returnedMessage方法
rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
System.out.println( String.format("消息丢失:message(%s) , replyCode(%b) , replyText(%s) , exchange(%s) , routingKey(%s)",message,replyCode,replyText,exchange,routingKey));
log.info("消息丢失:message({}) , replyCode({}) , replyText({}) , exchange({}) , routingKey({})",message,replyCode,replyText,exchange,routingKey);
}
});
return rabbitTemplate;
}
@Bean
public SimpleRabbitListenerContainerFactory simpleRabbitListenerContainerFactory(ConnectionFactory rabbitmqConnectionFactory){
SimpleRabbitListenerContainerFactory simpleRabbitListenerContainerFactory = new SimpleRabbitListenerContainerFactory();
//关联rabbitmq连接工厂
simpleRabbitListenerContainerFactory.setConnectionFactory(rabbitmqConnectionFactory);
//设置消息序列化器
simpleRabbitListenerContainerFactory.setMessageConverter(new Jackson2JsonMessageConverter());
//设置接收消息确认模式为手动确认
simpleRabbitListenerContainerFactory.setAcknowledgeMode(AcknowledgeMode.MANUAL);
return simpleRabbitListenerContainerFactory;
}
}
/*
* RabbitMQService.java
*/
public interface RabbitMQService {
/**
* 创建单播交换器
* @param exchangeName
*/
void createDirectExchange(String exchangeName);
/**
* 创建广播交换器
* @param exchangeName
*/
void createFanoutExchange(String exchangeName);
/**
* 创建模式匹配交换器
* @param exchangeName
*/
void createTopicExchange(String exchangeName);
/**
* 创建队列
* @param queueName
*/
void createQueue(String queueName);
/**
* 创建交换器和队列的绑定
* @param exchangeName
* @param queueName
* @param routingKey
*/
void bindExchangeQueue(String exchangeName, String queueName,String routingKey);
}
/*
* RabbitMQServiceImpl.java
*/
@Service
public class RabbitMQServiceImpl implements RabbitMQService {
@Autowired
private AmqpAdmin amqpAdmin;
@Override
public void createDirectExchange(String exchangeName) {
amqpAdmin.declareExchange(new DirectExchange(exchangeName,true,false));
}
@Override
public void createFanoutExchange(String exchangeName) {
amqpAdmin.declareExchange(new FanoutExchange(exchangeName,true,false));
}
@Override
public void createTopicExchange(String exchangeName) {
amqpAdmin.declareExchange(new TopicExchange(exchangeName,true,false));
}
/**
* 创建队列
* @param queueName
*/
@Override
public void createQueue(String queueName) {
amqpAdmin.declareQueue(new Queue(queueName,true,false,false));
}
/**
* 声明交换器和队列间的绑定
* @param exchangeName
* @param queueName
*/
@Override
public void bindExchangeQueue(String exchangeName, String queueName,String routingKey) {
amqpAdmin.declareBinding(new Binding(queueName,Binding.DestinationType.QUEUE,exchangeName,routingKey,null));
}
}
/*
* ProducerService.java
*/
public interface ProducerService {
void send();
}
/*
* ProducerServiceImpl.java
*/
@Service
public class ProducerServiceImpl implements ProducerService {
@Resource
private RabbitTemplate rabbitTemplate;
@Override
public void send() {
for(int i=0;i<200;i++){
Map<String,Object> map = new HashMap<>();
map.put("msg","这是第【"+i+"】个消息");
map.put("data", Arrays.asList("helloworld",123,true));
//exchange:交换器名字
//routing-key:消息的路由键
//object:消息对象,自动序列化
rabbitTemplate.convertAndSend("exchange.direct","atguigu.news",map);
}
}
}
/*
* ConsumerService.java
*/
public interface ConsumerService {
void receive(Message message, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long tag , @Headers Map<String,Object> headers);
}
/*
* ConsumerServiceImpl.java
*/
@RabbitListener(queues = "atguigu.news" , containerFactory = "simpleRabbitListenerContainerFactory")
@Service
public class ConsumerServiceImpl implements ConsumerService {
@Resource
private RabbitTemplate rabbitTemplate;
@Override
@RabbitHandler
public void receive(Message message, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long tag , @Headers Map<String,Object> headers) {
if(headers.get("error") == null){
//正确接收
try{
Object obj = rabbitTemplate.getMessageConverter().fromMessage(message);
Map<String,Object> map = (Map<String,Object>)obj;
System.out.println("消费者【1】接收到消息:"+map);
//手动确认
//deliveryTag:当消费者向rabbitmq注册后,会建立一个channel,这个channel内的消息标志就是tag,自增
//multiple:是否一次确认deliveryTag<=tag的所有消息
channel.basicAck(tag,false);
//可批量拒绝的basicNack
//deliveryTag,multiple,requeue:是否重新入队列
//channel.basicNack(tag,false,true);
//单个拒绝的basicReject
//deliveryTag,requeue
//channel.basicReject(tag,false);
}catch (Exception e){
e.printStackTrace();
}
}else{
try {
//错误接收
//单个拒绝的basicReject
//deliveryTag,requeue
channel.basicReject(tag,false);
}catch (Exception e){
e.printStackTrace();
}
}
}
}
/*
* ProducerServiceImplTest.java
*/
@RunWith(SpringRunner.class)
@SpringBootTest
public class ProducerServiceImplTest {
@Autowired
private ProducerServiceImpl producerService;
@Test
public void send() {
producerService.send();
try {
//假如没有停止2秒的话,测试程序结束马上关闭线程了,此时消息不一定能发送到交换器或者队列
Thread.sleep(10000);
}catch (Exception e){
e.printStackTrace();
}
}
}
/*
* ConsumerServiceImplTest.java
*/
@RunWith(SpringRunner.class)
@SpringBootTest
public class ConsumerServiceImplTest {
@Autowired
@Qualifier(value = "consumerServiceImpl")
private ConsumerService consumerService1;
private ConsumerService consumerService2;
@Test
public void receiveMsg() {
try{
Thread.sleep(2000);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}