名词解释
Producer(生产者):消息队列生产者,向消息队列发布消息,产生需求的。
Consumer(消费者):消息队列的消费者,从消息队列里获取消息,处理需求的。
Broker(代理):接受消息,保存消息,分配消息的,rabbitMQ server就是一个broker。
上面这三个是分布式标准的三个角色。rabbitmq扮演的就是Broker这个角色。
rabbitmq实现的是AMQP标准,在这个标准中定义了几个功能要求:
- exchange:交换机。作用和交换机一样,生产者将消息发送到交换机,然后交换机负责分发消息到绑定的消息队列。这里的绑定就是下面的binding,在bingding这个动作中定义了queue和exchange绑定的规则。exchange在rabbitmq中对应的类就是各种xxxxExchange(TopicExchange,DirectExchange…)
- binding:定义了队列和交换机的绑定规则,一个交换机可以绑定多个队列。
- queue:也就是我们所说的队列。消费者从队列中取出消息。
在上述说道,exchange有不同的类型。类型如下: - DirectExchange.java,这个交换机为直连交换机,是默认类型的交换机,也是最简单的模式.即创建消息队列的时候,指定一个routingKey(一个字符串).当发送者发送消息的时候,指定对应的Key(也是一个字符串).当Key和消息队列的routingKey一致的时候,消息将会被发送到该消息队列中。
- TopicExchange.java,这个交换机从翻译上为主题交换机。这个交换机可以在和消息队列绑定时的routingKey使用通配符,例如(user.#)。我们可以在发送时的routingKey填成user.save或者user.add,都会匹配到这个队列。
- FanoutExchange.java,这个交换机为广播队列,在这个队列routingKey没用。只要发送到这个交换机,那么所有队列都会加入这条消息。
- HeadExchange.java,这个交换机翻译上为头交换机,不能直接看出这个交换机的作用。这个交换机使用略少,在使用头交换机时,需要设置一些键值对为参数,例如:usefor=update,type=user。这些在绑定时需要放入map数组作为参数。
在springboot中使用RabbitMQ:
Producer(发送消费到队列的)
- 导包。pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
导包就是这么简单。
2. java的配置类。
配置队列的
// topicExchange用的
@Bean("user")
public Queue userQueue() {
return new Queue("user");
}
@Bean("other.err")
public Queue otherErrQueue() {
return new Queue("other.err");
}
// 下两个dirctExchange用的
@Bean("log.err")
public Queue logErrQueue() {
return new Queue("log.err");
}
@Bean("log.info")
public Queue logInfoQueue() {
return new Queue("log.info");
}
配置交换机
@Bean
public DirectExchange directExchange() {
// 参数1为交换机的名称,在发送时需要指定交换机名字
return new DirectExchange("directExchange");
}
@Bean
public TopicExchange topicExchange() {
return new TopicExchange("topicExchange");
}
@Bean
public FanoutExchange fanoutExchange() {
return new FanoutExchange("fanoutExchange");
}
@Bean
public HeadersExchange headersExchange() {
return new HeadersExchange("headExchange");
}
配置绑定规则,即Binding
@Bean
public Binding bindingLogErrExchangeDirect(@Qualifier("log.err") Queue logErrQueue, DirectExchange directExchange) {
// 将logErrQueue和directExchange绑定,并指定routingKey为log.err
return BindingBuilder.bind(logErrQueue).to(directExchange).with("log.err");
}
@Bean
public Binding bindingLogInfoExchangeDirect(@Qualifier("log.info") Queue logInfoQueue,
DirectExchange directExchange) {
return BindingBuilder.bind(logInfoQueue).to(directExchange).with("log.info");
}
@Bean
public Binding bindingOtherErrExchangeDirect(@Qualifier("other.err") Queue logErrQueue, TopicExchange topicExchange) {
// 将otherErrQueue和topicExchange绑定,并指定routingKey为log.err
return BindingBuilder.bind(logErrQueue).to(topicExchange).with("other.#");
}
@Bean
public Binding bindingExchangeMessages(@Qualifier("user") Queue userQueue, TopicExchange topicExchange) {
return BindingBuilder.bind(userQueue).to(topicExchange).with("user.#");
}
还需要配置一些其他的东西,比如确认消息是否到了exchange的回调。还有消息失败返回,比如路由不到队列时触发回调。
@Bean
public RabbitTemplate rabbitTemplate() {
// 若使用confirm-callback或return-callback,必须要配置publisherConfirms或publisherReturns为true
// 每个rabbitTemplate只能有一个confirm-callback和return-callback,如果这里配置了,那么写生产者的时候不能再写confirm-callback和return-callback
// 使用return-callback时必须设置mandatory为true,或者在配置中设置mandatory-expression的值为true
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
rabbitTemplate.setMandatory(true);
/**
* 如果消息没有到exchange,则confirm回调,ack=false
* 如果消息到达exchange,则confirm回调,ack=true
*/
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
}
});
// * exchange到queue成功,则不回调return
// * exchange到queue失败,则回调return(需设置mandatory=true,否则不回回调,消息就丢了)
rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange,
String routingKey) {
}
});
return rabbitTemplate;
}
配置消息对象序列化和反序列话时的策略
/**
* 修改默认的消息转换器
* @return
*/
@Bean
public MessageConverter messageConverter(){
return new Jackson2JsonMessageConverter();
}
还需要在项目中配置(application.yml)一些东西
spring:
rabbitmq:
#和mysql一样,配置地址,端口,用户名和密码
host: localhost
port: 35672
username: admin
password: 123456
#两个回调用的
publisher-confirms: true
publisher-returns: true
listener:
simple:
#工作队列有两种工作方式:轮询分发(默认)、公平分发即当某个消费者没有消费完成之前不用再分发消息。
# 消费者每次从队列获取的消息数量。此属性当不设置时为:轮询分发,设置为1为:公平分发
prefetch: 1
#设置消费者应答方式为手动
#acknowledge-mode: manual
retry:
#开启重试,重试次数为3
enabled: true
max-attempts: 3
发送消息类
//获取到rabbitTemplate
//可以在这里再次设置消息转化器
rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter());
rabbitTemplate.convertAndSend("topicExchange", "user.save", 消息内容);
consumer(获取并处理消息的)
导包:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
yml配置文件
#有些配置不知道该不该要
spring:
rabbitmq:
host: localhost
port: 35672
username: admin
password: 123456
listener:
simple:
#工作队列有两种工作方式:轮询分发(默认)、公平分发即当某个消费者没有消费完成之前不用再分发消息。
# 消费者每次从队列获取的消息数量。此属性当不设置时为:轮询分发,设置为1为:公平分发
prefetch: 1
#设置消费者应答方式为手动
acknowledge-mode: manual
处理代码
//绑定队列的名字
@RabbitListener(queues = { "log.err" })
public void saveErrLog(Message message, Channel channel) throws IOException {
System.out.println("save err log :" + new String(message.getBody()) + ". success !");
// 手动应答,false标识当前consumer处理成功,true为所有consumer处理成功
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
//判断消息是否为重复入队的消息,如果是重复入队的消息再次处理失败就不再入队
/*if (message.getMessageProperties().getRedelivered()) {
System.out.println("消息已重复处理失败,拒绝再次接收!");
// 拒绝消息,requeue=false 表示不再重新入队,如果配置了死信队列则进入死信队列
channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);
} else {
System.out.println("消息即将再次返回队列处理!");
// requeue为是否重新回到队列,true重新入队
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
}*/
}