MQ(Message Queue)消息队列,是基础数据结构中“先进先出”的⼀种数据结构。⼀般⽤来解决应⽤解耦,异步消息,流量削峰等问题,实现⾼性能,⾼可⽤,可伸缩和最终⼀致性架构。把要传输的数据(消息)放在队列中,⽤队列机制来实现消息传递——⽣产者产⽣消息并把消息放⼊队列,然后由消费者去处理。消费者可以到指定队列拉取消息,或者订阅相应的队列,由MQ服务端给其推送消息。
docker run -d --name rabbitmq5672 -p 15672:15672 -p 5672:5672 rabbitmq:management
可视乎访问端口号:15672 其他5672
账号密码均为guest
RabbitMQ消息中间件,发送和消费消息的软件
普通消息,⼀对⼀,⼀个队列有⼀个发送端和⼀个消费端。
最常⻅的,其中发送端和消费端互不影响
1.依赖jar
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-amqpartifactId>
dependency>
2.配置application.yml
spring:
rabbitmq:
host: 47.92.168.143
port: 5672 #端口号
username: guest
password: guest
3.创建队列
@Configuration //spring配置注解
public class RabbitMQConfig {
//创造队列 P2P
@Bean
public Queue createQueue1(){
return new Queue("k-P2P");//队列名称 唯一
}
}
4.编写发送消息的代码
@SpringBootTest
class RabbitMqApplicationTests {
@Resource
private RabbitTemplate rabbitTemplate;//ioc自动创建
//P2P
@Test
void contextLoads() {
//发送消息 参数说明:1.交换器 2.队列名或路由名 3.消息内容
rabbitTemplate.convertAndSend("","k-P2P","测试rabbit-P2P");
}
}
5.消费者接收
@Component
@RabbitListener(queues = "k-P2P" ) //对应队列名称
public class QueueListener {
private Logger logger= LoggerFactory.getLogger(QueueListener.class);
//接受消息
@RabbitHandler //参数为发送的类型
public void listener(String msg){
logger.error(msg);
}
}
public class RabbitUtil {
//1.连接服务器
//1.1 创建连接工厂 设计模式:工厂模式
private static ConnectionFactory factory=new ConnectionFactory();
/**手动连接并发送消息*/
public static void RabbitMQTransport(String queue,String value) throws IOException, TimeoutException {
factory.setHost("47.92.168.143");
factory.setPort(5672);
factory.setUsername("guest");
factory.setPassword("guest");
//1.2 创建连接对象
Connection connection= factory.newConnection();
//1.3 创建通道对象
Channel channel=connection.createChannel();
//2.创建队列
/**
* 参数说明:
* 1.队列名
* 2.是否持久化
* 3.是否为排他队列 一次性
* 4.是否自动删除 */
channel.queueDeclare(queue,false,false,false,null);
//3.发送消息
channel.basicPublish("",queue,null,value.getBytes());
//4.关闭释放
channel.close();
connection.close();
}
}
@Component
@RabbitListener(queues = "k-P2PTwo" ) //对应队列名称
public class QueueListenerTwo {
private Logger logger= LoggerFactory.getLogger(QueueListenerTwo.class);
@RabbitHandler //参数类型为返回类型
public void listener(Object msg){
//验证
if (msg instanceof Message){
Message message= (Message) msg;
logger.error("原生接受消息队列:"+new String(message.getBody()));
}else {
logger.error("原生接受消息队列:"+msg);
}
}
}
先执行发送-创建-接收(若未创建队列-写接收则会报错 找不到队列)
⼀个队列可以有多个消费者,这些消费者之间
如果存在发送的快,消费的慢,这样就会消息堆积,需要为队列多加点消费者
代码实现:
1.创建队列
//创造队列 Work
@Bean
public Queue createQueue2(){
return new Queue("k-Work");
}
2.编写发送消息的代码
//Word ⼀个队列可以有多个消费者 数据丢失
@Test
void t1(){
for (int i = 0; i < 10; i++) {
rabbitTemplate.convertAndSend("", "k-Work", "测试rabbit-Word" + i);
}
3.配置两个消费者-监听器
@Component
@RabbitListener(queues = "k-Work")
public class QueueListenerWork {
private Logger logger= LoggerFactory.getLogger(QueueListenerWork.class);
@RabbitHandler //参数类型为返回类型
public void listener(String msg){
logger.error("消费者1:Word"+msg);
}
}
@Component
@RabbitListener(queues = "k-Work")
public class QueueListenerWorkTwo {
private Logger logger= LoggerFactory.getLogger(QueueListenerWork.class);
@RabbitHandler //参数类型为返回类型
public void listener(String msg){
logger.error("消费者2:Word"+msg);
}
}
结果:
扩展 使用SpringBootTest时会启动springboot 这时springboot相当于另一个一样的消费者进行强夺
这些消费者之间是竞争关系,最终⼀个消息只能被消费⼀次
发布订阅消息,基于Exchange实现的消息可以被发送到多个队列中。其中交换器有四种类型:
fanout、direct、topic、header(基本上不⽤)。⼀般⽤来实现⼀个消息需要发送到多个队列中。消息同
时被多个消费
Exchange的常⽤的交换器类型,第⼀种:fanout 直接转发
代码实现:
1.创建所需的队列、交换器、绑定
@Configuration
public class RabbitMQFanout {
//1.创建队列
@Bean
public Queue createexQ1(){
return new Queue("kz-fanout-1");
}
@Bean
public Queue createexQ2(){
return new Queue("kz-fanout-2");
}
//2.创建交换器 IOC创建容器
@Bean
public FanoutExchange createFe(){
return new FanoutExchange("kz-ex-fanout");
}
//3.创建绑定关系
@Bean
public Binding createBe1(FanoutExchange fe){
return BindingBuilder.bind(createexQ1()).to(fe);
}
@Bean
public Binding createBe2(FanoutExchange fe){
return BindingBuilder.bind(createexQ2()).to(fe);
}
}
2.发送
// Exchange交换器 类型 通过fanout交换器 实现消息发送
@Test
public void t3(){
//发送消息 参数说明:1.交换器 2.队列名或路由名 3.消息内容
rabbitTemplate.convertAndSend("kz-ex-fanout", "", "通过交换器来发送!");
}
direct类型,发送消息到交换器,交换器根据消息的路由匹配内容,进⾏匹配队列,最终把把消息发送
给匹配到所有队列
根据名称进行匹配转发
向交换器发送消息
只有error队列可以接受到转发的消息
topic类型,⽀持路由匹配,其中路由内容⽀持模糊匹配,特殊符号
* ⼀个单词,通过.区分单词
# 0或多个单词,通过.区分单词
死信:RabbitMQ的队列中的消息,满⾜以下条件任意其⼀:
1.消息被拒绝
2.消息过期
3.队列已满
死信交换器:专⻔⽤来转发死信消息。
死信队列:通过死信交换器发送过来的消息,存储的都是死信消息
延迟队列:可以实现延迟消息处理的队列。⼀般把具备有效期的队列并且设置的有死信交换器的队列并且没有消费者成为延迟队列
1.订单超时⾃处理
⽐如
外卖订单15分钟不付款,⾃动取消
12306订单30分钟不付款,⾃动取消
延迟消息处理,5分钟后处理
⾃动确认收货
1.创建延迟队列
/**延迟队列 生成死信 */
public static final String DL_ONE_NAME="kz-dl-001";
/**死信队列 接收死信消息 .*/
public static final String DL_TWO_NAME="kz-dl-002";
/**死信转换器 */
public static final String DL_EXCHANGE_NAME="kz-dl-exchange";
/**死信路由转换*/
public static final String DL_RK="kz-dl-rk";
2.延迟队列创建方式
/**延迟队列*/
@Bean
public Queue createDl1(){
//1.创建队列属性信息
Map<String, Object> params = new HashMap<>();
//设置队列中每个消息的有效期 毫秒
params.put("x-message-ttl",10000);
//设置对应的死信交换器
params.put("x-dead-letter-exchange",DL_EXCHANGE_NAME);
//设置交换器匹配的路由名称 (队列或交换器)
params.put("x-dead-letter-routing-key",DL_RK);
//2.创建延迟队列 设置队列信息
return QueueBuilder.durable(DL_ONE_NAME).withArguments(params).build();
}
设置队列中每个消息的有效期
设置对应的死信交换器
设置交换器匹配的路由名称 (队列或交换器)
创建延迟队列
3.绑定
/**死信接收队列*/
@Bean
public Queue createDl2(){
return new Queue(DL_TWO_NAME);
}
/**死信交换器 路由匹配*/
@Bean
public DirectExchange createDLEc(){
return new DirectExchange(DL_EXCHANGE_NAME);
}
/**实现绑定*/
@Bean
public Binding createDlBe(DirectExchange de){
// 死信接收队列 死信交换器 死信路由匹配
return BindingBuilder.bind(createDl2()).to(de).with(DL_RK);
}
@RestController
@RequestMapping("/api/rabbit/")
public class RabbitDlController {
@Resource
private RabbitTemplate rabbitTemplate;//ioc自动创建
private Logger logger= LoggerFactory.getLogger(RabbitDlController.class);
@GetMapping("save.do")
public String save(String msg){
logger.warn("发送消息" +msg+System.currentTimeMillis()); // 死信队列
rabbitTemplate.convertAndSend("", RabbitDlConfig.DL_ONE_NAME,msg);
return "OK";
}
//路由传参
@GetMapping("{msg}")
public String save2(@PathVariable String msg){
logger.warn("发送消息" +msg+System.currentTimeMillis()); // 死信队列
rabbitTemplate.convertAndSend("", RabbitDlConfig.DL_ONE_NAME,msg);
return "OK";
}
}
6.接收队列-消费者
@Component
@RabbitListener(queues = RabbitDlConfig.DL_TWO_NAME)
public class RabbitDlListener {
private Logger logger= LoggerFactory.getLogger(RabbitDlListener.class);
@RabbitHandler
public void dlListener(String msg){
logger.warn("死信接受延迟队列:"+msg+System.currentTimeMillis());
}
}
设置了有效期为10秒、10秒之内没有消费者接收、转到死信交换器、根据绑定转发到指定的队列
我们需要⼀次性发多条消息,需要开启事务,事务⽤来保证多条消息的发送,如果有异常出
现,消息都不发送
实现步骤:
1.创建RabbitMQ事务管理
//2.创建RabbitMQ管理事务
@Bean
public RabbitTransactionManager create(ConnectionFactory factory){
return new RabbitTransactionManager(factory);
}
2.在发送时开启事务
/**加事务*/ //回滚 异常类型
@Transactional(rollbackFor = Exception.class)
@GetMapping("save.do")
public String save(String msg){
//开启事务
rabbitTemplate.setChannelTransacted(true);
logger.warn("发送消息:" +msg+System.currentTimeMillis());
rabbitTemplate.convertAndSend("", RabbitMQTranConfig.TRAN,msg);
System.err.println(1/0);
return "OK";
}
RabbitMQ防⽌消息的重复消费或者消息丢失的时候,都可以使⽤⼿动应答代替⾃动应答
针对消费端 在消费者代码实现
实现步骤:
@Component //设置手动
@RabbitListener(queues = RabbitAckConfig.QUEUE_ACK,ackMode = "MANUAL")
public class RabbitAckListener {
private Logger logger= LoggerFactory.getLogger(RabbitAckListener.class);
@RabbitHandler //channel可以理解为一个先进先出的消息队列 消息内部唯一id
public void ackListener(String msg, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long tag) throws IOException {
//手动操作 //1.消息id 2.是否应答 true 成功 false失败 3.消息是否还添加到队列
channel.basicNack(tag,false,true);
logger.warn("手动应答:"+msg);
}
}
//自动
// channel.basicAck(tag,true);
1.发送端如何保证消息不丢失
事务机制和Confirm机制,注意:事务机制和 Confirm 机制是互斥的,两者不能共存,会导致
RabbitMQ 报错。
2.RabbitMQ服务器端如何保证消息不丢失
持久化、集群、普通模式、镜像模式。
3.消费端如何保证消息不丢失
basicAck机制、死信队列、消息补偿机制
在编程中⼀个幂等操作的特点是其任意多次执⾏所产⽣的结果与⼀次执⾏的产⽣的结果相同,在mq中
由于⽹络故障或客户端延迟消费mq⾃动重试过程中可能会导致消息的重复消费,那我们如何保证消息
的幂等问题
1、⽣成全局id(雪花算法),存⼊redis或者数据库,在消费者消费消息之前,查询⼀下该消息是否有消费过。
2、如果该消息已经消费过,则告诉mq消息已经消费,将该消息丢弃(⼿动ack)。
3、如果没有消费过,将该消息进⾏消费并将消费记录写进redis或者数据库中。
RabbitMQ 有三种模式: 单机模式 , 普通集群模式 , 镜像集群模式 。
单机模式:就是demo级别的,⼀般就是你本地启动了玩玩⼉的,没⼈⽣产⽤单机模式
普通集群模式:意思就是在多台机器上启动多个RabbitMQ实例,每个机器启动⼀个。
镜像集群模式:这种模式,才是所谓的RabbitMQ的⾼可⽤模式,跟普通集群模式不⼀样的是,你创建
的queue,⽆论元数据(元数据指RabbitMQ的配置数据)还是queue⾥的消息都会存在于多个实例上,然后每次你写消息到queue的时候,都会⾃动把消息到多个实例的queue⾥进⾏消息同步
RabbitMQ是MQ的⼀种,消息队列或消息中间件,实现消息的异步处理,降低耦合度,提⾼性能
RabbitMQ安装-Docker
RabbitMQ的消息
1.消息⼀对⼀ 发送消息直接到队列,从队列中进⾏消费
2.消息⼀对多 发送消息到了交换器(
4种),从交换器到队列中,从队列中进⾏消费
**RabbitMQ交换器 **
1.fanout类型直接转发
2.direct类型对消息过滤,路由匹配,精确
3.topic类型对消息过滤,路由匹配,模糊 * #
4.header类型 对消息过滤,指定的消息头
**RabbitMQ死信和延迟 **
1.死信:当消息满⾜以下条件之⼀:1.队列拒绝 2.队列满了 3.过期
2.延迟:借助死信机制,如果⼀个队列⽣成了死信(时间过期),可以⾃动传递死信交换器,可以
把死信消息发送到要处理的队列
RabbitMQ的事务
保证多个消息要么都成功,要么都失败
RabbitMQ的⼿动应答
防⽌消息丢失,可以在消费端开启⼿动处理