RabbitMQ基础---根据b站视频记笔记

为什么会有这篇文章

还是为找工作,公司项目里面用到MQ,我也写到简历里了,但是面试官一问,我就说没做过,不对于两年半的我来说,不太容易找到工作;最近在b站学SpringCloud微服务开发与实战,记笔记,防止忘了;

如有错误,请指正;        

b站微服务开发中mq课程背景

 同步调用,异步调用

同步

余额支付: 1扣余额----2更新支付状态----3更新订单状态----4短信通知

扣余额和更新支付状态之间有关系,但是支付成功以后,更新订单状态,发送短信,增加用户积分之间不存在必要的关联,如果依次执行,耗时很长,扩展性也很差

 异步

 异步调用通常基于消息通知的方式,包含三个角色

消息发送者消息接收者消息代理

(商家),(顾客),(快递站)

上面的支付业务,就不用直接通过OpenFeign调用交易服务,而是发消息通知给代理,让代理去;

异步的问题,不能立即得到调用结果,不确定下游业务执行是否成功,业务安全依赖于Broker的可靠性

MQ

MQ(MessageQueue)中文是消息队列,字面来看就是存放消息的队列。也就是异步调用中的Broker。

RabbitMQActiveMQRocketMQKafka
公司/社区RabbitApache阿里Apache
开发语言ErlangJavaJavaScala&Java
协议支持AMQP,XMPP,SMTP,STOMPOpenWire,STOMP,REST,XMPP,AMQP自定义协议自定义协议
可用性一般
单机吞吐量一般非常高
消息延迟微秒级毫秒级毫秒级毫秒以内
消息可靠性一般一般

RabbitMQ

安装,部署

可以参考b站黑马MQ入门-05.RabbitMQ-安装部署_哔哩哔哩_bilibili

本笔记也是基于该视频书写,虎哥也有笔记,可以参考Docshttps://b11et3un53m.feishu.cn/wiki/OQH4weMbcimUSLkIzD6cCpN0nvc

 rabbitmq,不同项目有不同的virtualHost,类似VMware可以有不同虚拟机

 先熟悉一下控制台

先熟悉一下mq 

 在Queueu页面可以手动添加一个queue

我们使用amq.fanout名称的交换机,点进去

交换机只负责转发消息没有存储能力,路由失败消息就会丢失

交换机需要绑定队列

Bindings:绑定队列

Publish message:发送消息

点击publish messages 就给队列发送消息了

这时候可以overview,看到消息被发送

看到两个队列都收到消息,相当于广播

点进队列,能看到发送的消息

 数据隔离操作

先创建一个用户,但是 Can access virtual hosts 是空的,需要创建虚拟主机并绑定

 如果登录用户是新建的hmall,创建虚拟主机,就直接绑定到自己下面

交换机也会新增一份,右上角可以选virtual host来筛选

Java客户端

AMQP, advanced message queuing protocol,是一种标准协议,与语言和平台无关

Spring AMQP是一套基于AMQP的API规范,提供了模板来发送,接受消息

可以下载本笔记,资源里面的mq-demo查看

快速入门

先去除,exchange,只有queue体验收发消息的过程

控制台创建一个queue

 消息发送

首先配置MQ地址,在publisher服务的application.yml中添加配置:

spring:
  rabbitmq:
    host: 192.168.150.101 # 你的虚拟机IP
    port: 5672 # 端口
    virtual-host: /hmall # 虚拟主机
    username: hmall # 用户名
    password: 123 # 密码

然后在publisher服务中编写测试类SpringAmqpTest,并利用RabbitTemplate实现消息发送:

@SpringBootTest
public class SpringAmqpTest {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    public void testSimpleQueue() {
        // 队列名称
        String queueName = "simple.queue";
        // 消息
        String message = "hello, spring amqp!";
        // 发送消息
        rabbitTemplate.convertAndSend(queueName, message);
    }
}

测试以后发现,生产者发送了消息 

接受消息 

消费者和生产者,导入依赖,配置参数一样。。这里省略

@RabbitListener(queues = "simple.queue")  通过注解配置要监听的队列名称,将来SpringAMQP会把消息传递到当前方法

@Slf4j
@Component
public class SpringRabbitListener {

    @RabbitListener(queues = "simple.queue")
    public void listenSimpleQueueMessage(String msg) throws InterruptedException {
        log.info("spring 消费者接收到消息:【" + msg + "】");
    }
}

然后启动该项目,就能收到消息

Work Queues

任务模型,多个消费者,绑定一个队列,共同消费队列中的消息

生产者发送消息,是会被1还是2,消费掉????

rabbit控制台,新增一个 work.queue队列

代码中,添加两个消费者

 @RabbitListener(queues = "work.queue")
    public void listenWorkQueueMessage1(String msg) throws InterruptedException {
        System.out.println("消费者1号, 接收到消息:【" + msg + "】,"+ LocalTime.now());
    }

    @RabbitListener(queues = "work.queue")
    public void listenWorkQueueMessage2(String msg) throws InterruptedException {
        System.err.println("消费者2号, 接收到消息:【" + msg + "】,"+ LocalTime.now());
    }

生产者这里,新增一个发送50条数据的test

  @Test
    public void testWorkQueue() throws InterruptedException {
        // 队列名称
        String queueName = "work.queue";
        // 消息
        String message = "hello, message_";
        for (int i = 0; i < 50; i++) {
            // 发送消息,每20毫秒发送一次,相当于每秒发送50条消息
            rabbitTemplate.convertAndSend(queueName, message + i);
            Thread.sleep(20);
        }
        // 发送消息
        rabbitTemplate.convertAndSend(queueName, message);
    }

同一个消息,只会被一个消费者处理;这里只是测试用的,写两个方法,实际中是部署多个实例;

给1号,Thread.sleep(25); 2号Thread.sleep(200);模拟性能不一样

任务还是均匀分配

在消费者中配置,控制消费者预取的消息数量,实现能者多劳

spring:
  rabbitmq:
    listener:
      simple:
        prefetch: 1 # 每次只能获取一条消息,处理完成才能获取下一个消息

交换机

Exchange(交换机只负责转发消息,不具备存储消息的能力,因此如果没有任何队列与Exchange绑定,或者没有符合路由规则的队列,那么消息会丢失!

交换机的类型有四种:

  • Fanout:广播,将消息交给所有绑定到交换机的队列。我们最早在控制台使用的正是Fanout交换机

  • Direct:订阅,基于RoutingKey(路由key)发送给订阅了消息的队列

  • Topic:通配符订阅,与Direct类似,只不过RoutingKey可以使用通配符

  • Headers:头匹配,基于MQ的消息头匹配,用的较少。

Fanout交换机

会路由到每个队列,意思是,queue1和queue2都处理同样一份消息

声明队列fanout.queue1,fanout.queue2和交换机hmall.fanout,记得选类型

给交换机绑定队列

代码、中监听两个消费者

消费者代码,这里routingKey暂时为空

两个消费者都受到消息了,广播模式,一条消息发给多个队列

Direct交换机

queue和exchange绑定得key一样,就投递给谁;

先创建两个queue

创建direct交换机

交换机绑定queue,并设置routingkey

编写消费者代码

编写生产者代码,先试一下,routingkey是red的

发送blue

Topic交换机

就是bindingkey有通配符,类似路由

先创建两个队列

在创建交换机,创建时候,类型选topic

交换机绑定,并设置routing key

编写消费者

发送给消息,china.new 两个队列都符合,都会被匹配到

声明式队列,交换机

上面的测试都是通过,控制台设置的,不符合实际开发;下面通过Bean来声明

SpringAMQP提供了几个类,用来声明队列、交换机及其绑定关系:


Queue:用于声明队列,可以用工厂类QueueBuilder构建 

Exchange:用于声明交换机,可以用工厂类ExchangeBuilder构建

Binding:用于声明队列和交换机的绑定关系,可以用工厂类BindingBuilder构建

一般在消费者中申明,新建config类

@Configuration
public class FanoutConfiguration {
    @Bean
    public FanoutExchange fanoutExchange(){
        //return new FanoutExchange("hmall.fanout");
        return ExchangeBuilder.fanoutExchange("hmall.fanout").build();
    }

    @Bean
    public Queue fanoutQueue1(){
        return new Queue("fanout.queue1");
        //return QueueBuilder.durable("fanout.queue1").build();
    }

    @Bean
    public Binding fanoutQueueBinding(Queue fanoutQueue1, FanoutExchange fanoutExchange){
        return BindingBuilder.bind(fanoutQueue1).to(fanoutExchange);
    }

    @Bean
    public Queue fanoutQueue2(){
        return new Queue("fanout.queue2");
        //return QueueBuilder.durable("fanout.queue2").build();
    }

    @Bean
    public Binding fanoutQueueBinding2(Queue fanoutQueue2, FanoutExchange fanoutExchange){
        return BindingBuilder.bind(fanoutQueue2).to(fanoutExchange);
    }


}

 发现控制台有了

再试一下 direct exchange,这种绑定,一个bean只能设置,queue的一个routingkey绑定一个exchange;

@Configuration
public class DirectConfiguration {
    @Bean
    public DirectExchange directExchange(){
        //return new FanoutExchange("hmall.direct");
        return ExchangeBuilder.directExchange("hmall.direct").build();
    }

    @Bean
    public Queue directQueue1(){
        return new Queue("direct.queue1");
        //return QueueBuilder.durable("direct.queue1").build();
    }

    @Bean
    public Binding directQueueBindingRed(Queue directQueue1, DirectExchange directExchange){
        return BindingBuilder.bind(directQueue1).to(directExchange).with("red");
    }

    @Bean
    public Binding directQueueBindingBlue(Queue directQueue1, DirectExchange directExchange){
        return BindingBuilder.bind(directQueue1).to(directExchange).with("blue");
    }
}

接下来换@RabbitListener注解来声明队列和交换机

写在上面创建的SpringRabbitListener这个类里

 @RabbitListener( bindings = @QueueBinding(
            value = @Queue(name = "direct.queue1", durable = "true"),
            exchange = @Exchange(name="hmall.direct",type = ExchangeTypes.DIRECT),
            key = {"red", "blue"}
    ))
    public void listenDirectQueueMessage1(String msg) throws InterruptedException {
        System.err.println("消费者1号,direct.queue1, 接收到消息:【" + msg + "】,"+ LocalTime.now());
        Thread.sleep(200);
    }

    @RabbitListener( bindings = @QueueBinding(
            value = @Queue(name = "direct.queue2", durable = "true"),
            exchange = @Exchange(name="hmall.direct",type = ExchangeTypes.DIRECT),
            key = {"red", "yellow"}
    ))
    public void listenDirectQueueMessage2(String msg) throws InterruptedException {
        System.err.println("消费者2号,direct.queue2, 接收到消息:【" + msg + "】,"+ LocalTime.now());
        Thread.sleep(200);
    }

 消息转换器

发送消息的时候,message是object类型的,那消息转换器会把消息转为什么类型呢

测试一下

控制台创建一个queue:

发一条map

获得消息,这是序列化以后的结果,默认的用jdk的ObjectOutputStream完成序列化,有安全风险,消息变得很大,传输不方便,可读性差,不推荐

 用JSON 序列化来代替JDK序列化

publisher和consumer都引入jackson依赖,配置MessageConverter

 <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
 </dependency>

为了方便,注册bean,添加到启动类里面

 @Bean
    public MessageConverter messageConverter() {
        return new Jackson2JsonMessageConverter();
    }

消息变json类型了 

接受代码,就按发送得类型接受

@RabbitListener(queues = "object.queue")
    public void listenObjectQueueMessage(Map<String, Object> msg) throws InterruptedException {
        System.err.println("消费者号,object.queue, 接收到消息:【" + msg + "】,"+ LocalTime.now());
        Thread.sleep(200);
    }

 黑马商场业务改造

先添加 依赖

<!--消息发送-->
  <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-amqp</artifactId>
  </dependency>

抽取配置到nacos , 然后在bootstrap.yaml里面配置一下

在common-service服务里面,新建config配置类

@Configuration
public class Mqconfig {

    @Bean
    public MessageConverter messageConverter() {
        return new Jackson2JsonMessageConverter();
    }
}

在common服务里注册得,怎么被pay,trade服务扫描到,利用springboot的自动装配

trade-service 新增消费者代码

@Component
@RequiredArgsConstructor
public class PayStatusListener {

    private final IOrderService orderService;

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "trade.pay.success.queue",durable = "true"),
            exchange = @Exchange(name = "pay.direct"),
            key = "pay.success"
    ))
    public void listenPaySuccess(Long orderId) {
        orderService.markOrderPaySuccess(orderId);
    }
}

改造发送代码

        // 5.修改订单状态
        //tradeClient.markOrderPaySuccess(po.getBizOrderNo());
        try{
            rabbitTemplate.convertAndSend("pay.direct","pay.success",po.getBizOrderNo());
        }catch (Exception e){
            log.error("发送支付成功消息异常!",po.getBizOrderNo(),e);
        }

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

想跳槽的Java小白(炒股版)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值