使用RabbitMQ实现流量削峰填谷

原理

流量削峰填谷 是指在面对突发的高流量时,通过消息队列将瞬时大量请求暂时存储起来,并逐步处理这些请求,从而避免系统过载。RabbitMQ 作为消息中间件可以很好地支持这一需求,特别是结合其延时消息插件(rabbitmq_delayed_message_exchange),可以在处理消息时加入延时逻辑,进一步优化系统的负载。

  1. 生产者发送消息:当有大量请求涌入时,生产者将这些请求转化为消息并发送到 RabbitMQ 队列中。
  2. 消费者异步处理:消费者从队列中异步获取消息并进行处理。由于消息是逐步被消费的,因此即使短时间内有大量的请求进入系统,也不会导致系统崩溃。
  3. 延时消息处理:对于某些需要延时处理的消息,可以通过 RabbitMQ 的延时消息插件来设置消息的延时时间,使得这些消息在指定的时间后才被消费者处理。

详细步骤

一、环境准备

1. 安装 RabbitMQ 并启用延时消息插件

首先确保你已经安装了 RabbitMQ,并启用了管理插件以便通过 Web 界面进行管理。如果还没有安装 RabbitMQ,可以通过 Docker 快速启动一个 RabbitMQ 实例:

bash 复制代码
# 启动 RabbitMQ 容器
docker run -d --name rabbitmq 
-p 5672:5672 -p 15672:15672 
rabbitmq:3-management //指向特定的 3.x 版本并包含管理插件

访问 http://localhost:15672 进入 RabbitMQ 的管理界面,默认用户名和密码都是 guest

接下来,安装并启用 RabbitMQ 延时消息插件:

bash 复制代码
# 下载插件
wget https://github.com/rabbitmq/rabbitmq-delayed-message-exchange/releases/download/3.8.0/rabbitmq_delayed_message_exchange-3.8.0.ez -P /usr/lib/rabbitmq/lib/rabbitmq_server-<version>/plugins/

# 启用插件
rabbitmq-plugins enable rabbitmq_delayed_message_exchange

# 重启 RabbitMQ 服务
systemctl restart rabbitmq-server

二、RabbitMQ 配置与队列设置

1. 创建队列和交换机

我们使用延时插件的延时交换机来处理延时消息。以下是使用命令行工具 rabbitmqadmin 创建队列和交换机的示例:

bash 复制代码
# 使用 RabbitMQ 管理插件或命令行工具创建队列
rabbitmqadmin declare queue name=order_queue durable=true arguments='{"x-max-length": 10000, "x-overflow": "drop-head"}'

# 创建延时交换机
rabbitmqadmin declare exchange name=delayed_exchange type=x-delayed-message durable=true arguments='{"x-delayed-type": "direct"}'

# 绑定队列到交换机
rabbitmqadmin declare binding source=delayed_exchange destination=order_queue routing_key=order_routing_key

三、Spring Boot 应用程序配置

1. pom.xml 添加依赖

在 Spring Boot 项目中添加 RabbitMQ 和相关依赖:

XML 复制代码
<dependencies>
    <!-- Spring Boot Starter for AMQP -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-amqp</artifactId>
    </dependency>

    <!-- Jackson for JSON processing -->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
    </dependency>

    <!-- Lombok for reducing boilerplate code -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>
2. application.yml 配置文件

src/main/resources/application.yml 中配置 RabbitMQ 连接信息:

javascript 复制代码
spring:
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest
    virtual-host: /
    listener:
      simple:
        acknowledge-mode: manual  # 手动确认模式
        concurrency: 10           # 消费者并发数
        max-concurrency: 20       # 最大消费者并发数
3. RabbitMQConfig.java 配置类

定义 RabbitMQ 的配置类,用于声明队列、交换机和绑定关系:

java 复制代码
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitMQConfig {

    @Bean
    public Queue orderQueue() {
        return QueueBuilder.durable("order_queue")
                .withArgument("x-max-length", 10000)  // 设置最大长度为10000
                .withArgument("x-overflow", "drop-head")  // 当队列满时丢弃最早的未消费消息
                .build();
    }

    @Bean
    public CustomExchange delayExchange() {
        Map<String, Object> args = new HashMap<>();
        args.put("x-delayed-type", "direct");
        return new CustomExchange("delayed_exchange", "x-delayed-message", true, false, args);
    }

    @Bean
    public Binding binding(Queue orderQueue, CustomExchange delayExchange) {
        return BindingBuilder.bind(orderQueue).to(delayExchange).with("order_routing_key").noargs();
    }
}

四、生产者发送消息

1. OrderProducer.java

编写生产者代码,将订单信息作为消息发送到 order_queue 中:

java 复制代码
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class OrderProducer {

    private final RabbitTemplate rabbitTemplate;

    @Autowired
    public OrderProducer(RabbitTemplate rabbitTemplate) {
        this.rabbitTemplate = rabbitTemplate;
    }

    public void sendOrder(String orderData) {
        System.out.println(" [x] Sent order: " + orderData);
        rabbitTemplate.convertAndSend("delayed_exchange", "order_routing_key", orderData);
    }

    public void sendDelayedOrder(String orderData, long delayTime) {
        System.out.println(" [x] Sent delayed order: " + orderData + " with delay: " + delayTime + " ms");

        // 设置消息后处理器,添加延迟时间
        MessagePostProcessor messagePostProcessor = message -> {
            message.getMessageProperties().setHeader("x-delay", delayTime);
            return message;
        };

        rabbitTemplate.convertAndSend("delayed_exchange", "order_routing_key", orderData, messagePostProcessor);
    }
}
2. OrderController.java

编写控制器以接收 HTTP 请求并调用生产者发送消息:

java 复制代码
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class OrderController {

    private final OrderProducer orderProducer;

    @Autowired
    public OrderController(OrderProducer orderProducer) {
        this.orderProducer = orderProducer;
    }

    @PostMapping("/submitOrder")
    public ResponseEntity<String> submitOrder(@RequestBody String orderData) {
        orderProducer.sendOrder(orderData);
        return ResponseEntity.ok("Order submitted successfully!");
    }

    @PostMapping("/submitDelayedOrder")
    public ResponseEntity<String> submitDelayedOrder(@RequestBody String orderData, @RequestParam long delayTime) {
        orderProducer.sendDelayedOrder(orderData, delayTime);
        return ResponseEntity.ok("Delayed order submitted successfully!");
    }
}

五、消费者处理消息

1. OrderConsumer.java

编写消费者代码,从队列中获取消息并处理订单:

java 复制代码
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Service
public class OrderConsumer {

    private static final Logger logger = LoggerFactory.getLogger(OrderConsumer.class);

    @RabbitListener(queues = "order_queue")
    public void receiveOrder(String orderData) {
        logger.info("Received order: {}", orderData);
        processOrder(orderData);
    }

    private void processOrder(String orderData) {
        logger.info("Processing order: {}", orderData);
        try {
            // 模拟订单处理逻辑
            Thread.sleep(2000);  // 模拟处理时间
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

六、测试与验证

1. 启动 RabbitMQ 服务
bash 复制代码
docker start rabbitmq
2. 编译并启动 Spring Boot 应用程序
bash 复制代码
mvn spring-boot:run
3. 使用 Postman 或 curl 测试订单提交接口
提交普通订
提交延时订单
bash 复制代码
curl -X POST 
http://localhost:8080/submitDelayedOrder?delayTime=5000 
-H "Content-Type: application/json" 
-d '{"user_id": 12345, "product_id": 67890, "quantity": 2, "price": 199.99}'


1. curl
curl 是一个用于在不同协议之间传输数据的命令行工具。它支持多种协议,包括HTTP、HTTPS、FTP等。在这个例子中,它被用来发送HTTP请求。
2. -X POST
-X 参数允许您指定HTTP请求的方法。这里使用的是POST方法,通常用于向服务器提交数据或更新资源。
3. http://localhost:8080/submitDelayedOrder?delayTime=5000
这是目标URL,表示请求将被发送到运行在本地机器(localhost)上的服务,监听端口为8080。路径/submitDelayedOrder指定了API的具体端点,而查询参数delayTime=5000可能指示该订单将会延迟5秒(5000毫秒)处理。
4. -H "Content-Type: application/json"
-H 标志用于添加HTTP头信息。这里的头信息指定了内容类型为application/json,意味着请求体中的数据将以JSON格式进行编码。
5. -d '{"user_id": 12345, "product_id": 67890, "quantity": 2, "price": 199.99}'
-d 参数用于指定HTTP请求的数据体。在这个例子中,数据是以JSON格式提供的,包含用户ID、产品ID、购买数量和单价的信息。

查看控制台输出,确认消息被发送到队列并由消费者处理。

七、优化与扩展

1. 动态调整消费者数量

为了动态调整消费者的数量,可以使用 Kubernetes 的 Horizontal Pod Autoscaler (HPA) 来根据队列长度自动扩展消费者实例。例如:

Kubernetes Deployment YAML 文件

javascript 复制代码
apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-consumer
spec:
  replicas: 3  # 初始副本数
  selector:
    matchLabels:
      app: order-consumer
  template:
    metadata:
      labels:
        app: order-consumer
    spec:
      containers:
      - name: order-consumer
        image: your-order-consumer-image
        env:
        - name: SPRING_RABBITMQ_HOST
          value: "rabbitmq-service"
        resources:
          requests:
            memory: "64Mi"
            cpu: "250m"
          limits:
            memory: "128Mi"
            cpu: "500m"
---
apiVersion: v1
kind: Service
metadata:
  name: rabbitmq-service
spec:
  selector:
    app: rabbitmq
  ports:
    - protocol: TCP
      port: 5672
      targetPort: 5672

自动扩展策略

使用 Horizontal Pod Autoscaler (HPA) 来根据队列长度自动扩展消费者实例:

bash 复制代码
kubectl autoscale deployment order-consumer --min=3 --max=10 --cpu-percent=80
2. 监控与报警

为了确保系统的稳定性,还需要对 RabbitMQ 进行监控和报警。可以使用 Prometheus 和 Grafana 来监控 RabbitMQ 的状态,并设置报警规则。

Prometheus Adapter 配置

为了监控 RabbitMQ 队列长度,需要安装 Prometheus Adapter:

javascript 复制代码
# custom-metrics-config-map.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: adapter-config
data:
  config.yaml: |
    rules:
      - seriesQuery: '{__name__="rabbitmq_queue_messages", queue="order_queue"}'
        seriesFilters: []
        resources:
          overrides:
            namespace:
              resource: namespace
            pod:
              resource: pod
        name:
          matches: ""
          as: "rabbitmq_queue_length"
        metricsQuery: sum(rabbitmq_queue_messages{queue="order_queue"})

应用配置并更新 HPA 规则:

bash 复制代码
kubectl apply -f custom-metrics-config-map.yaml
kubectl apply -f hpa-custom-metrics.yaml

其中,hpa-custom-metrics.yaml 文件定义了如何基于自定义指标进行扩展:

javascript 复制代码
apiVersion: autoscaling/v2beta2
kind: HorizontalPodAutoscaler
metadata:
  name: order-consumer-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: order-consumer
  minReplicas: 3
  maxReplicas: 10
  metrics:
  - type: Pods
    pods:
      metric:
        name: rabbitmq_queue_length
      target:
        type: AverageValue
        averageValue: 5000
3. 日志记录与分析

为了更好地排查问题和优化系统性能,建议启用日志记录和分析功能。可以使用 ELK Stack(Elasticsearch, Logstash, Kibana)来进行日志管理和分析。

OrderConsumer.java 中添加日志记录

java 复制代码
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Service
public class OrderConsumer {

    private static final Logger logger = LoggerFactory.getLogger(OrderConsumer.class);

    @RabbitListener(queues = "order_queue")
    public void receiveOrder(String orderData) {
        logger.info("Received order: {}", orderData);
        processOrder(orderData);
    }

    private void processOrder(String orderData) {
        logger.info("Processing order: {}", orderData);
        try {
            // 模拟订单处理逻辑
            Thread.sleep(2000);  // 模拟处理时间
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

使用 ELK Stack 进行日志分析

ELK Stack(Elasticsearch, Logstash, Kibana)是一个强大的日志管理和分析工具集。你可以将日志集中存储在 Elasticsearch 中,并通过 Kibana 进行可视化分析。

八、高级特性与最佳实践

1. 消息确认机制

为了确保消息可靠传递,可以使用手动确认机制。修改 OrderConsumer.java 中的消息监听器:

java 复制代码
@RabbitListener(queues = "order_queue", ackMode = "MANUAL")
public void receiveOrder(Channel channel, Message message, String orderData) throws IOException {
    logger.info("Received order: {}", orderData);
    try {
        processOrder(orderData);
        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
    } catch (Exception e) {
        channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
    }
}
2. 消息重试机制

当消费者处理失败时,可以设置重试机制。可以在 application.yml 中配置重试策略:

javascript 复制代码
spring:
  rabbitmq:
    listener:
      simple:
        retry:
          enabled: true
          initial-interval: 1000
          max-attempts: 3
          max-interval: 10000
          multiplier: 2
3. 消息持久化

为了防止 RabbitMQ 重启导致消息丢失,可以将消息设置为持久化:

java 复制代码
MessagePostProcessor messagePostProcessor = message -> {
    message.getMessageProperties().setHeader("x-delay", delayTime);
    message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
    return message;
};

总结

通过上述步骤,我们实现了基于 RabbitMQ 的流量削峰填谷系统,并利用 RabbitMQ 的延时消息插件处理延迟消息。

相关推荐
2301_7930868736 分钟前
SpringCloud 02 服务治理 Nacos
java·spring boot·spring cloud
MacroZheng2 小时前
还在用WebSocket实现即时通讯?试试MQTT吧,真香!
java·spring boot·后端
midsummer_woo2 小时前
基于springboot的IT技术交流和分享平台的设计与实现(源码+论文)
java·spring boot·后端
别惹CC4 小时前
Spring AI 进阶之路01:三步将 AI 整合进 Spring Boot
人工智能·spring boot·spring
柯南二号5 小时前
【Java后端】Spring Boot 集成 MyBatis-Plus 全攻略
java·spring boot·mybatis
javachen__6 小时前
SpringBoot整合P6Spy实现全链路SQL监控
spring boot·后端·sql
IT毕设实战小研12 小时前
基于Spring Boot 4s店车辆管理系统 租车管理系统 停车位管理系统 智慧车辆管理系统
java·开发语言·spring boot·后端·spring·毕业设计·课程设计
一只爱撸猫的程序猿13 小时前
使用Spring AI配合MCP(Model Context Protocol)构建一个"智能代码审查助手"
spring boot·aigc·ai编程
甄超锋13 小时前
Java ArrayList的介绍及用法
java·windows·spring boot·python·spring·spring cloud·tomcat
武昌库里写JAVA16 小时前
JAVA面试汇总(四)JVM(一)
java·vue.js·spring boot·sql·学习