RabbitMQ
准备
安装
rpm -ivh erlang-22.3.4.21-1.el7.x86_64.rpm
yum install socat -y
rpm -ivh rabbitmq-server-3.8.11-1.el7.noarch.rpm
启动
systemctl start rabbitmq-server.service
开启web管理插件
rabbitmq-plugins enable rabbitmq_management
创建新的用户
# 创建账户
rabbitmqctl add_user admin 123456
# 设置用户角色
rabbitmqctl set_user_tags admin administrator
# 设置用户权限
set_permissions [-p <vhostpath>] <user> <conf> <write> <read>
rabbitmqctl set_permissions -p "/" admin ".*" ".*" ".*"
# 查看用户列表
rabbitmqctl list_users
消费流程
生产消费
依赖:
<dependencies>
<!--rabbitmq依赖客户端-->
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.9.0</version>
</dependency>
<!--操作文件流的一个依赖-->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
</plugins>
</build>
生产者:
public class Producer {
//队列名称
public static final String QUEUE_NAME = "hello";
//发消息
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接工厂
ConnectionFactory factory=new ConnectionFactory();
//工厂ip 连接RabbitMQ的队列
factory.setHost("192.168.168.130");
factory.setUsername("admin");
factory.setPassword("123456");
//创建连接
Connection connection=factory.newConnection();
//获取信道
Channel channel = connection.createChannel();
/**
* 声明一个队列
* 1.队列名称
* 2.队列里面的消息是否持久化(磁盘),默认存储在内存
* 3.该队列是否供只供个消费者消费,是否不进行消息共享,默认为false
* 4.是否自动删除,最后一个消费者断开连接,该队列是否删除
* 5.其他参数,map对象,可以设置队列最大长度,或者死信等等,详见死信
*/
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//发消息
String message = "hello world";
/**
* 发送一个消息
* 1.发送到哪个交换机,""表示无名交换机
* 2.路由的key值,本次是队列名称
* 3.其他参数信息,ps:MessageProperties.PERSISTENT_TEXT_PLAIN(消息持久化)
* 4.发送消息的消息体
*/
channel.basicPublish("",QUEUE_NAME,null,message.getBytes(StandardCharsets.UTF_8));
System.out.println("消息发送完毕");
}
}
消费者:
public class Consumer {
//队列的名称
public static final String QUEUE_NAME = "hello";
//接受消息
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接工厂
ConnectionFactory factory=new ConnectionFactory();
factory.setHost("192.168.168.130");
factory.setUsername("admin");
factory.setPassword("123456");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
/**
* 消费者消费消息
* 1.消费哪个队列
* 2.消费成功后是否自动应答
* 3.消费者未成功消费的回调
* 4.消费者取消消费的回调
*/
channel.basicConsume(AUTO_QUEUE_NAME,false,(consumerTag,message)->{
SleepUtils.sleep(1);
System.out.println("接收到消息:"+new String(message.getBody(), StandardCharsets.UTF_8));
},consumerTag->{
System.out.println(consumerTag+"消费者取消消费接口回调逻辑");
});
}
}
手动应答
//第二个false表示不自动应答,手动应答
channel.basicConsume(AUTO_QUEUE_NAME,false,(consumerTag,message)->{
//手动应答,应答后才说明消息利用成功,否则消息还会回到队列,继续被应用,false表示不批量应答
channel.basicAck(message.getEnvelope().getDeliveryTag(),false);
//拒绝这条消息,false表示不再放回队列
channel.basicReject(message.getEnvelope().getDeliveryTag(),false);
},consumerTag->{
System.out.println(consumerTag+"消费者取消消费接口回调逻辑");
});
不公平分发
//设置为不公平分发,由消费者设置,设置为1就表示不公平分发
channel.basicQos(1);
预取值分发
//设置prefetch的值
int prefetchCount=3;
channel.basicQos(prefetchCount);
发布确认
- 单个确认
//开启发布确认
channel.confirmSelect();
channel.basicPublish("",QUEUE_NAME,MessageProperties.PERSISTENT_TEXT_PLAIN,
message.getBytes(StandardCharsets.UTF_8));
//true表示确认发布成功
int flag=channel.waitForConfirms();
- 批量确认
//开启发布确认
channel.confirmSelect();
//批量确认消息大小
int batchSize=100;
for(int i=0;i<1000;i++){
String message=i+"";
channel.basicPublish("",QUEUE_NAME,MessageProperties.PERSISTENT_TEXT_PLAIN,
message.getBytes(StandardCharsets.UTF_8));
if((i+1)%batchSize==0){
channel.waitForConfirms();
}
}
- 异步批量确认
channel.addConfirmListener((deliveryTag,message)->{
//发布成功
},(deliveryTag,multiple)->{
//发布失败
});
for (int i = 0; i < 1000; i++) {
String message="消息"+i;
channel.basicPublish("",queueName,MessageProperties.PERSISTENT_TEXT_PLAIN,
message.getBytes(StandardCharsets.UTF_8));
};
交换机
fanout交换机
// 消费者
channel.exchangeDeclare(EXCHANGE_NAME,"fanout");
channel.queueDeclare(queueName,true,false,false,null);
channel.queueBind(queueName,EXCHANGE_NAME,"");
channel.queueBind(queueName,EXCHANGE_NAME,"");
channel.basicConsume(queueName,true,()->{},()->{});
// 生产者
channel.exchangeDeclare(EXCHANGE_NAME,"fanout");
channel.queueDeclare(queueName,true,false,false,null);
channel.basicPublish(EXCHANGE_NAME,"",null,message);
direct交换机
// 消费者
channel.exchangeDeclare(EXCHANGE_NAME,"direct");
channel.queueDeclare(queueName,true,false,false,null);
channel.queueBind(queueName,EXCHANGE_NAME,"aaa");
channel.queueBind(queueName,EXCHANGE_NAME,"bbb");
channel.basicConsume(queueName,true,()->{},()->{});
// 生产者
channel.exchangeDeclare(EXCHANGE_NAME,"direct");
channel.queueDeclare(queueName,true,false,false,null);
channel.basicPublish(EXCHANGE_NAME,"aaa",null,message);
topic交换机
// 消费者
channel.exchangeDeclare(EXCHANGE_NAME,"topic");
channel.queueDeclare(queueName,true,false,false,null);
channel.queueBind(queueName,EXCHANGE_NAME,"www.*.com");
channel.queueBind(queueName,EXCHANGE_NAME,"www.#");
channel.basicConsume(queueName,true,()->{},()->{});
// 生产者
channel.exchangeDeclare(EXCHANGE_NAME,"topic");
channel.queueDeclare(queueName,true,false,false,null);
channel.basicPublish(EXCHANGE_NAME,"www.liucheng.com",null,message);
死信
来源:
- 消息TTL过期
- 队列达到最大长度
- 消息被拒绝(basicReject||basicNack||requeue=false)
// 消费者
Map<String,Object> map=new HashMap<>();
map.put("x-message-ttl",1000); //过期时间ms,一般是发送的时候指定,大多不写在这
map.put("x-dead-letter-exchange",DEAD_EXCHANGE); //设置死信交换机
map.put("x-dead-letter-routing-key","lisi"); //设置死信routingkey
map.put("x-max-length",6); //设置队列的最大长度
channel.exchangeDeclare(NORMAL_EXCHANGE,"direct");
channel.exchangeDeclare(DEAD_EXCHANGE,"direct");
channel.queueDeclare(normal_queue,true,false,false,map);
channel.queueDeclare(dead_queue,true,false,false,null);
channel.queueBind(normal_queue,NORMAL_EXCHANGE,"zhangsan");
channel.queueBind(dead_queue,DEAD_EXCHANGE,"lisi");
channel.basicConsume(NORMAL_QUEUE,true,()->{},()->{});
// 生产者
//过期时间ms
AMQP.BasicProperties properties=new AMQP.BasicProperties().builder().expiration("10000").build();
channel.basicPublish(NORMAL_EXCHANGE,"zhangsan",properties,message);
SpringBoot整合
依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit-test</artifactId>
<scope>test</scope>
</dependency>
交换机与队列声明绑定
@Configuration
public class TTLQueueConfig {
public static final String NORMAL_EXCHANGE = "X";
public static final String DEAD_EXCHANGE = "Y";
public static final String NORMAL_QUEUE_A = "QA";
public static final String DEAD_QUEUE = "QD";
@Bean("normalExchange")
public DirectExchange normalExchange(){
return new DirectExchange(NORMAL_EXCHANGE);
}
@Bean("deadExchange")
public DirectExchange deadExchange(){
return new DirectExchange(DEAD_EXCHANGE);
}
@Bean("normalQueueA")
public Queue normalQueueA(){
Map<String,Object> map=new HashMap<>();
map.put("x-dead-letter-exchange",DEAD_EXCHANGE);
map.put("x-dead-letter-routing-key","YD");
map.put("x-message-ttl",10000);
return QueueBuilder.durable(NORMAL_QUEUE_A).withArguments(map).build();
}
@Bean("deadQueue")
public Queue deadQueue(){
return QueueBuilder.durable(DEAD_QUEUE).build();
}
@Bean
public Binding ABindingNormal(@Qualifier("normalQueueA") Queue normalQueueA,
@Qualifier("normalExchange") DirectExchange normalExchange){
return BindingBuilder.bind(normalQueueA).to(normalExchange).with("XA");
}
@Bean
public Binding DBindingDead(@Qualifier("deadQueue") Queue deadQueue,
@Qualifier("deadExchange") DirectExchange deadExchange){
return BindingBuilder.bind(deadQueue).to(deadExchange).with("YD");
}
}
生产者
@Slf4j
@RestController
@RequestMapping("/ttl")
public class SendMsgController {
private final RabbitTemplate rabbitTemplate;
@Autowired
public SendMsgController(RabbitTemplate rabbitTemplate) {
this.rabbitTemplate = rabbitTemplate;
}
@GetMapping("/sendMsg/{message}")
public void sendMsg(@PathVariable String message){
log.info("当前时间:{},发送一条信息给两个TTL队列:{}",new Date().toString(),message);
rabbitTemplate.convertAndSend("X","XA","消息来自ttl为10s的队列:"+message);
rabbitTemplate.convertAndSend("X","XB","消息来自ttl为40s的队列:"+message);
}
@GetMapping("/sendMsg/{message}/{ttlTime}")
public void sendMsg(@PathVariable String message,@PathVariable String ttlTime){
log.info("当前时间:{},发送一条信息给一个{}毫秒TTL队列:{}",new Date().toString(),ttlTime,message);
rabbitTemplate.convertAndSend("X","XC","消息来自自定义时间的队列"+message,(msg)->{
msg.getMessageProperties().setExpiration(ttlTime);
return msg;
});
}
}
消费者
@Slf4j
@Component
public class DeadLetterQueueConsumer {
@RabbitListener(queues = "QD")
public void receiveD(Message message, Channel channel){
String s = new String(message.getBody());
log.info("当前时间:{},收到死信队列的消息:{}",new Date().toString(),s);
}
}
延时队列问题
- Community Plugins — RabbitMQ下载插件
- 进入RabbitMQ安装目录下的plgins目录/usr/lib/rabbitmq/lib/rabbitmq_server-3.8.11/plugins
- 把插件移到上述目录下
- 输入命令rabbitmq-plugins enable rabbitmq_delayed_message_exchange
优化后的延时队列
public static final String DELAYED_QUEUE_NAME="delayed.queue";
public static final String DELAYED_EXCHANGE_NAME="delayed.exchange";
public static final String DELAYED_ROUTING_KEY="routing.key";
@Bean
public CustomExchange delayedExchange(){
Map<String,Object> map=new HashMap<>();
map.put("x-delayed-type","direct");
return new CustomExchange(DELAYED_EXCHANGE_NAME,"x-delayed-message",true,false,map);
}
@Bean
public Queue delayedQueue(){
return QueueBuilder.durable(DELAYED_QUEUE_NAME).build();
}
@Bean
public Binding delayedQueueBindingExchange(@Qualifier("delayedQueue") Queue delayedQueue,
@Qualifier("delayedExchange") Exchange delayedExchange){
return BindingBuilder.bind(delayedQueue).to(delayedExchange).with(DELAYED_ROUTING_KEY).noargs();
}
//生产者
@GetMapping("/sendDelayedMsg/{message}/{ttlTime}")
public void sendDelayedMsg(@PathVariable String message,@PathVariable Integer ttlTime){
log.info("当前时间:{},发送一条信息给一个{}毫秒延时TTL队列:{}",new Date().toString(),ttlTime,message);
rabbitTemplate.convertAndSend("delayed.exchange","routing.key","消息来自延时队列"+message,(msg)->{
msg.getMessageProperties().setDelay(ttlTime);
return msg;
});
}
//消费者
@Slf4j
@Component
public class DeadLetterQueueConsumer {
@RabbitListener(queues = "QD")
public void receiveD(Message message, Channel channel){
String s = new String(message.getBody());
log.info("当前时间:{},收到死信队列的消息:{}",new Date().toString(),s);
}
}
发布确认高级
<!--springboot配置文件中加入,开启异步确认消息-->
spring.rabbitmq.publisher-confirm-type=correlated
@Component
@Slf4j
public class MyCallBack implements RabbitTemplate.ConfirmCallback {
@Autowired
private RabbitTemplate rabbitTemplate;
@PostConstruct
public void init(){
rabbitTemplate.setConfirmCallback(this);
}
/**
* 回调函数
* @param correlationData 保存回调信息的ID和其他信息,由发送消息的时候指定的对象
* @param ack 是否成功收到消息
* @param cause 失败的原因
*/
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
String id=correlationData!=null?correlationData.getId():"";
if(ack){
log.info("交换机已经收到了id为{}的消息",id);
}else{
log.info("交换机没有收到了id为{}的消息,原因是{}",id,cause);
}
}
}
队列回退消息
- Mandatory参数
在交换机收到消息后,会直接给消息生产者发送确认消息,如果发现该消息不可路由,那么这条消息会直接丢弃,此时生产者是不知道这条消息被丢弃的。
所以我们需要交换机在这个时候通知生产者一声,所以我们可以通过设置mandatory参数可以在当消息传递过程中不可达目的地是讲消息返回给生产者。
<!--springboot配置文件中加入-->
spring.rabbitmq.publisher-returns=true
//同上,实现RabbitTemplate.ReturnCallback接口并实现方法
@Override
public void returnedMessage(Message message, int replyCode,
String replyText, String exchange, String routingKey) {
}
备份交换机
//把主交换机连接备份交换机
ExchangeBuilder.directExchange("exchange_name").durable(true).
withArgument("alternate-exchange","backup_exchange_name").build();