目录
安装
docker下载指令两种方式,建议第一种
docker run -d --restart=always --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3.10-management
docker run -it --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3.10-management
下载后直接在网址输入ip端口:15672就能进去管理界面如果进不去可以尝试关闭防火墙,指令为
systemctl stop firewalld
查看防火强状态
systemctl status firewalld
查看所有下载好的指令
docker ps -a
查看下载好的镜像
docker images
查IP端口有没有被占用指令为
netsta -anp |grep 5672
卸载
删除下载的MQ
先查看所有下载好的指令,删除关于mq的文件指令为
docker rm -f 需要卸载的CONTAINER ID
查看下载好的镜像,删除关于mq的镜像
docker rmi -f 需要删除的IMAGE ID
启动RabbitMQ容器
关闭容器
docker stop 容器名
打开容器
docker start 容器名
查看是否能访问容器
cmd里面输入
telnet ip地址
RabbitMQ分为三种:
1.Direct Exchange
直流式交换机,根据消息携带的路由键将消息投递给对应队列
2.Fanout Exchange
扇形交换机,这个交换机没有路由概念,就算绑了路由也是无视的(没有routingket)。这个交换机再接收到消息后会直接转发到绑定到他的所有队列
3.Topic Exchange
主题交换机,这个交换机其实跟直连交换机流程差不多。一般来说都是用主题交换机,因为上面两个交换机能做的主题交换机都能做。但是他的特点就是在他的路由键与绑定键之间是有规则的。
规则:
* (星号) 用来表示一个单词 (必须出现的)
# (井号) 用来表示任意数量(零个或多个)单词
* 代表两点之间一个占位单词
# 代表后面所有,匹配所有
idea使用RabbitMQ
MQ依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
创建springboot项目,创建子项目,输入配置信息
#??RabbitMQ?IP??
spring.rabbitmq.host=你的IP地址
#??rabbitmq???????
spring.rabbitmq.port=5672
#??rabbitmq???????
spring.rabbitmq.virtual-host=/
#??rabbitmq??????
spring.rabbitmq.username=guest
#??rabbitmq?????
spring.rabbitmq.password=guest
spring.rabbitmq.listener.simple.prefetch=1
#手动应答
#spring.rabbitmq.listener.simple.acknowledge-mode=manual
#自动应答 z
spring.rabbitmq.listener.simple.acknowledge-mode=auto
# 开启自动重试
spring.rabbitmq.listener.simple.retry.enabled=true
# 最大重试次数
spring.rabbitmq.listener.simple.retry.max-attempts=5
#最大间隔时间
spring.rabbitmq.listener.simple.retry.max-interval=20000ms
#重试间隔时间 3秒
spring.rabbitmq.listener.simple.retry.initial-interval=3000ms
#乘子 重试间隔*乘子得出下次重试间隔 3s 6s 12s 24s 此处24s>20s 走20s
spring.rabbitmq.listener.simple.retry.multiplier=2
#重试次数超过上面的设置之后是否丢弃(false不丢弃时需要写相应代码将该消息加入死信队列)
spring.rabbitmq.listener.simple.default-requeue-rejected=false
Direct Exchange 直流式交换机
需要路由键——routingkey
创建配置类:在配置类写交换机的配置,队列的配置,绑定信息
@Bean public DirectExchange directExchange(){ return new DirectExchange("DirectExchange-01",true,false) ; }
队列的配置
@Bean public Queue DirectQueue(){ return QueueBuilder.durable("DirectQueue-01").build(); // 自动删除 // return QueueBuilder.durable("DirectQueue-01").autoDelete().build(); }
绑定信息
@Bean
public Binding binding(){
return BindingBuilder.bind(DirectQueue()).to(directExchange()).with("Direct-RoutingKey-01");
}
如果仅仅是写好配置信息却没有调用的话,是不会往队列里发送信息的,这时候我们就要调用我们写好的配置,首先我们创建消费者加入注解 @RabbitHandler这要知道springMVC的执行流程。了解过后知道Handler是执行类。加入注解@RabbitListener用来监听我们想监听的地方,这里我们要监听的是我们配置类中创建的队列。这时候如果我们运行的话就会向队列里发出请求。队列也可以接收到请求。
消费者包,consumer
建立一个DirectConsumer
@Component public class DirectConsumer { @RabbitHandler @RabbitListener(queues = "DirectQueue-01") public void process(Message message) { String test = new String(message.getBody()); System.out.println(test); } }
@RabbitHandler | handler类用来执行的 |
@RabbitListener | 用来监听队列的 |
这是用来消费队列数据的
还有一种写法是不需要配置类config,直接在消费类上面通过注解来进行配置信息
@Component public class DirectConsumer { @RabbitHandler @RabbitListener(bindings = @QueueBinding( value = @Queue(value = "DirectQueue-01", durable = "true", autoDelete = "false"), exchange = @Exchange(value = "DirectExchange-01", type = ExchangeTypes.DIRECT), key = "Direct-RoutingKey-01")) public void process(Message message) { String test = new String(message.getBody()); System.out.println(test); } }
生产者包provider
建立一个DirectProvider
@Component public class DirectProvider { @Resource private RabbitTemplate rabbitTemplate; public void send(){ Message message=new Message("你好 ".getBytes()); rabbitTemplate.send("DirectExchange-01","Direct-RoutingKey-01",message); } }
多个消费者消耗同一条队列
我们设置一个生产者生产十个消息
@Component
public class DirectProvider {
@Resource
private RabbitTemplate rabbitTemplate;
public void sendLong() {
for (int i = 0; i < 10; i++) {
Message message = new Message("消耗".getBytes());
rabbitTemplate.send("Exchange01","RoutingKey01",message);
}
}
设置两个消费者进行消费,消费者对于这十个消息是如何分配的?
@Component
public class DirectConsumer {
@RabbitHandler
@RabbitListener(queues = "Queue01")
public void consumer1(Message message) {
String password = new String(message.getBody());
System.out.println("消费者1号"+ password);
}
@RabbitHandler
@RabbitListener(queues = "Queue01")
public void consumer2(Message message) {
String password = new String(message.getBody());
System.out.println("消费者2号"+ password);
}
可以看出来,对于这十个消息,两个消费者是进行平均分配的,一人一个,一人一个的循环着。
不过一般都是创建一个消费者进行打包,如果需要第二个消费者,则将打包出来的消费者再发出去一份就变成了两个消费者
多个队列连接同一个交换机
多个队列可以绑定在一个交换机上面
队列传递对象
创建一个实体类
@Data
public class OrderIngOK implements Serializable {
private Integer id;
private String OrderNo;
private String userName;
}
要继承这个接口不然在传递对象的时候就会报错,显示只能传递Serializable 类的对象
Serializable
生产者传递对象
@Component
public class DirectProvider {
@Resource
private RabbitTemplate rabbitTemplate;
public void sendLong() {
OrderIngOK orderIngOK = new OrderIngOK();
orderIngOK.setOrderNo("202025077101" );
orderIngOK.setId(1);
orderIngOK.setUserName("买核弹的小姑娘");
rabbitTemplate.convertAndSend("Exchange01","RoutingKey01",orderIngOK);
}
send | 传递参数 |
convertAndSend | 传递对象用 |
传递的对象到达MQ后的样子是base64的样子
我们要做的就是将bsae64字节码转换为utf-8,在配置类里面添加一个配置
@Bean
public MessageConverter messageConverter(){
return new Jackson2JsonMessageConverter();
}
但是在正常开发中不可能我们让每个人都把json数据转换好发送,所以我们需要一种让他自动转换为对象 ,所以我们在消费者的参数里面加入实体类的形参就可以了。
消费者应答模式
配置里面有对于应答方式的两种选择,一种为自动应答,一种为手动应答
#手动应答 spring.rabbitmq.listener.simple.acknowledge-mode=manual #自动应答 spring.rabbitmq.listener.simple.acknowledge-mode=auto
当改成手动应答后MQ就会显示接受未应答消息。
当我们设置为手动应答后,程序出现错误时会调用自动重试的配置
# 开启自动重试 spring.rabbitmq.listener.simple.retry.enabled=true # 最大重试次数 遇到异常重试次数 spring.rabbitmq.listener.simple.retry.max-attempts=5 #最大间隔时间 spring.rabbitmq.listener.simple.retry.max-interval=30000ms #重试间隔时间 3秒 spring.rabbitmq.listener.simple.retry.initial-interval=1000ms #乘子 重试间隔*乘子得出下次重试间隔 3s 6s 12s 24s 此处24s>20s 走20s spring.rabbitmq.listener.simple.retry.multiplier=1
配置中有一个最大重试次数,我在配置的时候配成5,所以会在自动重连五次后取消自动重连,后面的是自动重连的间隔实践。有一个最大的间隔时间,就是当每次重连的时间是
spring.rabbitmq.listener.simple.retry.initial-interval=1000ms
的次方
spring.rabbitmq.listener.simple.retry.multiplier=1
上面的意思是,如果第一条为2秒的时候,下面为5,那么第一次重连是2秒,第二次重连就是间隔4秒,依次类推到达最大的32秒,但是
spring.rabbitmq.listener.simple.retry.max-interval=30000ms
这个是最大间隔时间,就是如果最大的间隔时间大于这个间隔时间的时候就会断开,不进行重试,比如最大30秒,五次间隔32秒,那么这次的间隔时间为30秒。
还有就是如果手动应答,超过应答次数后,这条数据就会直接被丢掉,这是不允许的,所以一般不建议使用手动应答,除非有补救措施,但是补救措施也不是万能的,还是存在隐患,所以不建议使用手动应答。不过对于丢掉数据这个有死信队列可以解决,后面又详细介绍
#手动应答
spring.rabbitmq.listener.simple.acknowledge-mode=manual
一旦使用手动应答,下面的一系列的配置都会失效
Channel
导包的时候,要看清楚是这个包,不要导错了。
import com.rabbitmq.client.Channel;
channel.后能出来目前会使用的,一个是basicAck接受应答,一个是basicReject 拒绝应答,一个是baseNack
basicAck使用是手动应答的方式,关于它的源码
/** * Acknowledge one or several received * messages. Supply the deliveryTag from the {@link com.rabbitmq.client.AMQP.Basic.GetOk} * or {@link com.rabbitmq.client.AMQP.Basic.Deliver} method * containing the received message being acknowledged. * @see com.rabbitmq.client.AMQP.Basic.Ack * @param deliveryTag the tag from the received {@link com.rabbitmq.client.AMQP.Basic.GetOk} or {@link com.rabbitmq.client.AMQP.Basic.Deliver} * @param multiple true to acknowledge all messages up to and * including the supplied delivery tag; false to acknowledge just * the supplied delivery tag. * @throws java.io.IOException if an error is encountered */ void basicAck(long deliveryTag, boolean multiple) throws IOException;
翻译过来的意思就是
/**
* 确认应答
* basicAck(long deliveryTag, boolean multiple)
* deliveryTag:当前消息在队列中的的索引;
* multiple:为true的话就是批量确认
*/
void basicAck(long deliveryTag, boolean multiple) throws IOException;
deliveryTag | 当前消息在队列中的的索引 |
multiple | 是否按照批处理,如果是true就会收集,收集到一定的数量后整体应答,如果是false就是一个一个应答,处理一个解决一个。在工作中,我们的准则就是能不产生这个数据也不要产生一个错误的数据。所以一般我们采用的是flase |
使用的方式,在消费的时候加入一个参数为Channel
@RabbitHandler
@RabbitListener(queues = RabbitConfig.QUEUE_LONG)
public void consumer(OrderIngOK orderIngOK,Message message,Channel channel) throws IOException {
System.out.println("消费者龙号" + orderIngOK);
long deliveryTag = message.getMessageProperties().getDeliveryTag();
channel.basicAck(deliveryTag,false);}
basicReject 是拒绝应答,配合basicAck使用,使用场景是,当数据发生错误的时候,拒绝应答,这样的话数据还在队列里面
@RabbitHandler @RabbitListener(queues = RabbitConfig.QUEUE_LONG) public void consumer(OrderIngOK orderIngOK, Message message, Channel channel) throws IOException { long deliveryTag = message.getMessageProperties().getDeliveryTag(); try { int a=0; int b=9/a; System.out.println("消费者龙号" + orderIngOK); channel.basicAck(deliveryTag, false); } catch (Exception ex) { channel.basicReject(deliveryTag, false); System.out.println("出现异常"); System.out.println(ex.getMessage()); } }
注意:
如果拒绝应答的是否批处理是true的话就会一直进行重复的验证是否错误,或者是否应答,如果处理不当的话就会造成光日志打印的超乎你想象的事情
如果拒绝应答的是否批处理是false的时候,会将数据直接丢掉,就会造成数据丢失的情况。
Fanout Exchange 扇形交换机
与直流交换机一样,唯一不同的就是在使用绑定的时候,roudingkey是null
@Bean
public Binding binding2(){
return BindingBuilder.bind("fanoutqueue").to("fanoutexchange").with("null");
}
Topic Exchange 主题交换机
与直流交换机一样,唯一不同的就是在使用绑定的时候,roudingkey是是规则,是自己想要的东西
@Bean public Binding binding1() { return BindingBuilder.bind(hedan()).to(topicExchange()).with("T.1.#"); } @Bean public Binding binding2() { return BindingBuilder.bind(feiji()).to(topicExchange()).with("T.#"); } @Bean public Binding binding3() { return BindingBuilder.bind(yuandidan()).to(topicExchange()).with("T.*"); }
死信队列
死信队列其实就是一种单门的队列,是我们自己根据需要添加进去的。也可以叫做超时信息处理。比如我们在上面举的列子,当有消息出现异常的时候,我们的程序会自动进行重试,连续五次重试后,消息直接就被丢掉了。当手动的时候,就不会触发自动重试机制,会一直进行重试,或者直接将消息丢掉。对于这种消息我们就可以将他们放到死信队列里面。这是一种解决方法,我们还可以将他们放到redis里面或者暂时贮存在我们的数据库里面。
我们建立一个死信队列的配置
@Configuration
public class DeadLetterConfig {
public static final String DEAD_QUEUE_NAME = "Dead-死信队列";
public static final String DEAD_EXCHANGE_NAME = "Dead-Exchange";
public static final String DEAD_ROUTING_KEY = "Dead-Routing-Key";
@Bean
public Queue deadQueue() {
return QueueBuilder.durable(DEAD_QUEUE_NAME).build();
}
@Bean
public DirectExchange deadExchange() {
return new DirectExchange(DEAD_EXCHANGE_NAME);
}
@Bean
public Binding deadBind() {
return BindingBuilder.bind(deadQueue()).to(deadExchange()).with(DEAD_ROUTING_KEY);
}
}
在消费者前面进行加入死信队列的操作
long deliveryTag = message.getMessageProperties().getDeliveryTag();