rabbit结合实际案例的介绍

1信息队列的作用

  • 缓冲数据

    信息队列可以作为一个缓冲区域。在很多系统中,数据的产生和处理速度是不一致的。例如,在一个电商系统中,用户下单的操作是频繁且不可预测的,这些订单信息就是数据。订单产生的速度可能会在促销活动期间非常快,而后台的库存管理系统、发货系统等处理订单的速度可能相对较慢。信息队列就可以先将这些订单信息缓存起来,防止系统因为瞬间涌入大量订单而崩溃。

    以一个简单的消息队列 RabbitMQ 为例,当生产者(如电商网站的前端服务器)快速产生消息(订单消息)时,这些消息会被放入队列中,等待消费者(如库存管理服务器、发货服务器等)在合适的时间进行处理。

  • 解耦系统组件

    信息队列有助于解耦不同的系统组件。在一个复杂的软件系统中,各个模块之间的直接调用会导致紧密耦合。比如,一个在线支付系统,它包含用户认证模块、支付处理模块和银行接口模块。如果这些模块之间是直接调用关系,当银行接口因为维护等原因发生变更时,很可能会影响到支付处理模块和用户认证模块。

    通过信息队列,用户认证模块完成认证后可以将支付请求信息放入队列,支付处理模块从队列中获取请求进行处理,再将与银行交互的信息放入另一个队列,银行接口模块从这个队列中获取信息与银行系统交互。这样,各个模块之间只依赖于信息队列,而不是直接相互依赖,使得系统更加灵活,方便组件的独立开发、升级和维护。

  • 实现异步通信

    信息队列能够支持异步通信方式。以一个文件上传系统为例,用户上传一个大型文件,传统的同步方式是用户上传请求发出后,一直等待服务器完成文件存储等后续操作,在这个过程中用户界面可能会出现卡顿或者无响应的情况。

    利用信息队列,用户发出上传请求后,文件上传的任务信息被放入队列,服务器立即返回一个上传成功的提示给用户,然后后台的文件存储服务从队列中获取任务并进行文件存储操作。这样用户不需要等待文件存储的漫长过程,可以继续进行其他操作,提高了用户体验和系统的整体效率。

  • 流量削峰填谷

    在高并发场景下,信息队列可以进行流量削峰。比如,一个热门的新闻网站在发布重大新闻时,瞬间可能会有大量用户访问该新闻页面并进行评论、点赞等操作。如果没有信息队列,这些请求直接涌向服务器的数据库(用于存储评论、点赞数据等),数据库可能会因为承受不住巨大的负载而出现性能下降甚至崩溃。

    有了信息队列,这些请求可以先进入队列,服务器按照自己的处理能力从队列中逐步取出请求进行处理,将流量高峰时期的请求均匀地分布在一段时间内处理,起到了削峰填谷的作用,保证系统的稳定运行。

2.调用的方式:

2.1.最简单的调用

在这里插入图片描述

publisher:

public class SpringAmqpTest {
    //自动装配RabbitTemplate模板对象
    @Autowired
    private RabbitTemplate rabbitTemplate;

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

consumer:

@Component
public class SpringRabbitListener {
    //监听名称simple.queue队列,Spring只要接收到该队列的消息就会接收消息
    @RabbitListener(queues = "simple.queue") //对应publisher的队列名称
    //Spring自动将接收的消息给方法参数msg
    public void listenSimpleQueueMessage(String msg) throws InterruptedException {
        System.out.println("spring 消费者接收到消息:【" + msg + "】");
    }
}

我们通常把consumer定义为listener

2.2.最常用的方式:Direct

在Direct模型下:

  • 队列与交换机的绑定,不能是任意绑定了,而是要指定一个RoutingKey(路由key)
  • 消息的发送方在 向 Exchange发送消息时,也必须指定消息的 RoutingKey
  • Exchange不再把消息交给每一个绑定的队列,而是根据消息的Routing Key进行判断,只有队列的Routingkey与消息的 Routing key完全一致,才会接收到消息

在这里插入图片描述

交换机的名称在publis出定义

队列的名称,RoutingKey,绑定交换机都在consumer处定义

publisher:

public void testSendDirectExchange() {
    // 交换机名称
    String exchangeName = "itcast.direct";
    // 消息
    String message = "红色警报!日本乱排核废水,导致海洋生物变异,惊现哥斯拉!";
    // 发送消息
    rabbitTemplate.convertAndSend(exchangeName, "red", message);
}
//都能收到
public void testSendDirectExchange() {
    // 交换机名称
    String exchangeName = "itcast.direct";
    // 消息
    String message = "蓝色警报!日本乱排核废水,导致海洋生物变异,惊现哥斯拉!";
    // 发送消息
    rabbitTemplate.convertAndSend(exchangeName, "blue", message);
}
//只有绑定了蓝色RoutingKey的才能收到

public void testSendDirectExchange() {
    // 交换机名称
    String exchangeName = "itcast.direct";
    // 消息
    String message = "黄色警报!日本乱排核废水,导致海洋生物变异,惊现哥斯拉!";
    // 发送消息
    rabbitTemplate.convertAndSend(exchangeName, "yellow", message);
}
//只有绑定了黄色的RoutingKey才能

consumer:

 /*
        TODO:
            1. value = @Queue(name = "direct.queue1") 表示绑定的第一个队列
            2.exchange = @Exchange(name = "itcast.direct", type = ExchangeTypes.DIRECT) 表示交换机名和类型
            3.key = {"red", "blue"} 表示路由key
     */
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "direct.queue1"),
            exchange = @Exchange(name = "itcast.direct", type = ExchangeTypes.DIRECT),
            key = {"red", "blue"}
    ))
    public void listenDirectQueue1(String msg){
        System.out.println("消费者接收到direct.queue1的消息:【" + msg + "】");
    }

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "direct.queue2"),
            exchange = @Exchange(name = "itcast.direct", type = ExchangeTypes.DIRECT),
            key = {"red", "yellow"}
    ))
    public void listenDirectQueue2(String msg){
        System.out.println("消费者接收到direct.queue2的消息:【" + msg + "】");
    }

3.实际开发的案例:

1.使用springmvc绑定好前端的操作

@ApiOperation("免费课立刻报名接口")
@PostMapping("/freeCourse/{courseId}")
public PlaceOrderResultVO enrolledFreeCourse(@ApiParam("免费课程id") @PathVariable("courseId") Long courseId) {
    return orderService.enrolledFreeCourse(courseId);
}

2.实现类中实现publisher的操作

//封装了一个send方法,实际调用的还是convertAndSend 在此处定义了交换机
public <T> void send(String exchange, String routingKey, T t) {
    log.debug("准备发送消息,exchange:{}, RoutingKey:{}, message:{}", exchange, routingKey, t);
    // 1.设置消息标示,用于消息确认,消息发送失败直接抛出异常,交给调用者处理
    String id = UUID.randomUUID().toString(true);
    CorrelationData correlationData = new CorrelationData(id);
    // 2.设置发送超时时间为500毫秒
    rabbitTemplate.setReplyTimeout(500);
    // 3.发送消息,同时设置消息id
    rabbitTemplate.convertAndSend(exchange, routingKey, t, processor, correlationData);
}
//具体使用rabbitmq的地方,调用了send方法
        rabbitMqHelper.send(
                MqConstants.Exchange.ORDER_EXCHANGE,
                MqConstants.Key.ORDER_PAY_KEY,
                OrderBasicDTO.builder()
                        .orderId(orderId)
                        .userId(userId)
                        .courseIds(cIds)
                        .finishTime(order.getFinishTime())
                        .build()
        );

3.consumer:

@Slf4j
@Component
@RequiredArgsConstructor
public class LessonChangeListener {

    private final ILearningLessonService lessonService;

    /**
     * 监听订单支付或课程报名的消息
     * @param order 订单信息
     */
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(value = "learning.lesson.pay.queue", durable = "true"),
            exchange = @Exchange(name = MqConstants.Exchange.ORDER_EXCHANGE, type = ExchangeTypes.TOPIC),
            key = MqConstants.Key.ORDER_PAY_KEY
    ))
    public void listenLessonPay(OrderBasicDTO order){
        // 1.健壮性处理
        if(order == null || order.getUserId() == null || CollUtils.isEmpty(order.getCourseIds())){
            // 数据有误,无需处理
            log.error("接收到MQ消息有误,订单数据为空");
            return;
        }
        // 2.添加课程
        log.debug("监听到用户{}的订单{},需要添加课程{}到课表中", order.getUserId(), order.getOrderId(), order.getCourseIds());
        lessonService.addUserLessons(order.getUserId(), order.getCourseIds());
    }
}

// 2.添加课程
log.debug(“监听到用户{}的订单{},需要添加课程{}到课表中”, order.getUserId(), order.getOrderId(), order.getCourseIds());
lessonService.addUserLessons(order.getUserId(), order.getCourseIds());
}
}


这里consumer会接收到publisher 发来的信息,进行处理
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值