分布式微服务系统架构第95集:基于 Redisson 延迟队列,springboot,springcloud启动过程,策略模式

加群联系作者vx:xiaoda0423

仓库地址:https://webvueblog.github.io/JavaPlusDoc/

https://1024bat.cn/

事件类型策略模式

重构目标

原问题 策略模式方案
多个 if-else 处理不同事件类型 每种事件类型用一个独立的策略类处理
修改时容易误改其他逻辑 新增类型只需增加一个策略类
方法臃肿,难测试 单一职责原则,每个策略只管自己逻辑

结构说明

go 复制代码
swift
复制编辑
src/main/java/com/example/batteryswap/
├── BatterySwapApplication.java       // 启动类
├── controller/
│   └── SwapEventController.java      // 模拟外部请求入口
├── strategy/
│   ├── BatterySwapStrategy.java
│   ├── BatterySwapStrategyFactory.java
│   ├── LabelSwapStrategy.java
│   ├── DoorGetStrategy.java
│   └── ErrMsgSwapStrategy.java
├── service/
│   └── DummyBusinessService.java     // 模拟服务类
├── model/
│   ├── CabinetsBizEvent.java
│   └── BExchSvcOrder.java
└── dto/
    └── RestRet.java                  // 响应封装类

目标:

  1. 职责分离: 拆分常量类,按照业务维度归类。

  2. 避免魔法值: 可通过 enum 替代字符串常量,提高可读性与类型安全。

  3. 方法优化: 替换多重 if-else 为更清晰的策略/映射方式。

  4. 命名统一规范: 常量命名建议全部大写,并用 _ 分隔词语。

总结:

  1. 高并发性能优化:
  • 使用 computeIfAbsent 替代手动判断延迟队列是否存在,提高线程安全性。

  • 避免重复调用 initDelayQueue

  • 代码结构优化:

    • 提取公共日志方法,减少重复。

    • 分类注释清晰,增强可维护性。

  • 可扩展性提升:

    • 使用泛型支持更强类型检查。

    • 提前暴露接口方法,提高使用灵活性。

  • 线程安全与可用性优化:

    • delayedQueueMap 使用 ConcurrentHashMap 保证线程安全。

    • 使用统一异常处理方式,方便故障排查。

    优化点 描述
    缓存队列对象 避免重复初始化 RBlockingDequeRDelayedQueue 提升性能,支持高并发。
    双重检查锁 防止多线程下并发初始化,提高线程安全性和效率。
    统一日志格式 清晰易懂,便于排查问题。
    异步投递接口 提升吞吐能力,适用于高并发场景。
    sendIfPresent 逻辑优化 移除已存在元素后重新添加,确保更新逻辑准确。
    高扩展性结构 支持多个队列名称,便于扩展不同业务队列。
    . 线程安全本地缓存
    • 使用 ConcurrentHashMapcomputeIfAbsent,避免并发初始化导致队列重复创建。
    2. 统一序列化配置
    • 使用 JsonJacksonCodec,确保延迟消息的序列化在 Redis 分布式环境中兼容性强,支持多服务跨语言。
    3. 异步发送接口优化
    • 提供 sendAsyncsendAsyncIfAbsent,适合高并发不阻塞场景。
    4. 队列重用与延迟队列封装解耦
    • 支持动态创建多个队列,适合大规模系统中多个业务模块隔离使用。
    5. 可扩展性设计
    • 后续可扩展为队列优先级、回调通知、队列消费监听等模块。
    6. 兼容 Redis Cluster 架构
    • Redisson 内部会处理 Cluster 路由逻辑,避免手动分片。
    go 复制代码
    /**
     * 延迟消息生产者,基于 Redisson 实现高可用、高并发的分布式延迟队列
     */
    @Component
    public class DelayMessageProducer {
        private static final Logger log = LoggerFactory.getLogger(DelayMessageProducer.class);
    
        @Autowired
        private RedissonClient redissonClient;
    
        // 本地缓存已创建的延迟队列,提升性能,避免重复创建
        private final Map<String, RDelayedQueue<Object>> delayedQueueMap = new ConcurrentHashMap<>(4);
    
        /**
         * 初始化延迟队列,使用本地缓存减少 Redisson 重复创建开销
         */
        private RDelayedQueue<Object> initDelayQueue(String queueName) {
            return delayedQueueMap.computeIfAbsent(queueName, name -> {
                RBlockingDeque<Object> blockingDeque = redissonClient.getBlockingDeque(name, new JsonJacksonCodec());
                return redissonClient.getDelayedQueue(blockingDeque);
            });
        }
    }

    springboot,springcloud启动过程

    1. 启动类的执行(Main方法)

    Spring Boot 的启动过程从 @SpringBootApplication 注解的类的 main 方法开始。该方法调用了 SpringApplication.run() 来启动整个 Spring Boot 应用。@SpringBootApplication 注解是一个组合注解,它包含了:

    • @Configuration:表明该类是配置类。

    • @EnableAutoConfiguration:启用 Spring Boot 的自动配置。

    • @ComponentScan:启动组件扫描,扫描该类所在包及其子包中的 Bean。

    2. SpringApplication.run() 方法

    SpringApplication.run() 是 Spring Boot 启动过程中的关键方法。它会完成以下任务:

    • 创建并配置 SpringApplication 对象。

    • 启动嵌入式的 Web 服务器(如 Tomcat、Jetty)。

    • 初始化 Spring 容器(ApplicationContext)。

    • 执行一些其他的初始化操作,比如命令行参数解析、环境配置等。

    3. 初始化 SpringApplication 对象

    在执行 SpringApplication.run() 时,SpringApplication 会被初始化,它会做以下几件事:

    • 设置 ApplicationContext(默认是 AnnotationConfigApplicationContext)。

    • 设置 Banner(Banner 是 Spring Boot 启动时显示的 ASCII 字符图标)。

    • 设置 CommandLineRunnerApplicationRunner 接口的 Bean,它们会在应用启动后执行。

    Spring Boot 启动过程大致如下:

    1. 执行 main 方法,调用 SpringApplication.run() 启动应用。

    2. 初始化 SpringApplication,加载配置。

    3. 创建并初始化 ApplicationContext,扫描并注册 Bean。

    4. 自动配置系统根据依赖自动配置应用。

    5. 发布应用启动事件。

    6. 执行 CommandLineRunnerApplicationRunner 中的代码(如果定义)。

    7. 启动完成,应用开始运行。

    Spring Cloud 是基于 Spring Boot 构建的分布式系统的开发框架,旨在简化微服务架构的构建。它提供了很多用于微服务架构的解决方案,如服务发现、负载均衡、配置管理、消息驱动等。

    Spring Cloud 的启动过程依赖于 Spring Boot 启动过程,但它有自己的一些额外步骤,主要用于处理服务注册与发现、配置管理、服务通信等。下面是 Spring Cloud 启动的主要过程:

    1. 启动类的执行(Main 方法)

    与 Spring Boot 类似,Spring Cloud 的应用通常也从一个 main 方法开始。在这个方法中,调用 SpringApplication.run() 来启动应用。@SpringCloudApplication 注解是 Spring Cloud 的启动注解,它是一个组合注解,包含了:

    • @SpringBootApplication:包含了 Spring Boot 启动的所有功能。

    • @EnableDiscoveryClient@EnableEurekaClient:启用服务发现客户端,这让服务能够注册到 Eureka 或其他服务注册中心。

    • @EnableCircuitBreaker:启用熔断器机制,防止调用失败时影响其他服务。

    Spring Cloud 是基于 Spring Boot 构建的分布式系统的开发框架,旨在简化微服务架构的构建。它提供了很多用于微服务架构的解决方案,如服务发现、负载均衡、配置管理、消息驱动等。

    Spring Cloud 的启动过程依赖于 Spring Boot 启动过程,但它有自己的一些额外步骤,主要用于处理服务注册与发现、配置管理、服务通信等。下面是 Spring Cloud 启动的主要过程:

    1. 启动类的执行(Main 方法)

    与 Spring Boot 类似,Spring Cloud 的应用通常也从一个 main 方法开始。在这个方法中,调用 SpringApplication.run() 来启动应用。@SpringCloudApplication 注解是 Spring Cloud 的启动注解,它是一个组合注解,包含了:

    • @SpringBootApplication:包含了 Spring Boot 启动的所有功能。

    • @EnableDiscoveryClient@EnableEurekaClient:启用服务发现客户端,这让服务能够注册到 Eureka 或其他服务注册中心。

    • @EnableCircuitBreaker:启用熔断器机制,防止调用失败时影响其他服务。

    2. SpringApplication.run() 方法

    SpringApplication.run() 是 Spring Boot 启动的关键方法,也同样适用于 Spring Cloud,它会执行以下步骤:

    • 创建并配置 SpringApplication 对象。

    • 启动应用的上下文(ApplicationContext)。

    • 加载并初始化 Spring Boot 自动配置。

    • 初始化服务注册中心,开始与服务发现交互。

    在 Spring Cloud 中,如果应用涉及服务注册与发现(如 Eureka 或 Consul),这一步会连接到注册中心,进行服务注册。

    3. 服务发现与注册

    如果应用是一个服务消费者或提供者,并且启用了服务发现机制(如 Eureka),则 Spring Cloud 会:

    • 启动服务发现客户端(如 EurekaClient),并自动注册到服务发现平台。

    • 每个服务都会将其实例信息(如 IP 地址、端口等)注册到服务注册中心(如 Eureka)。

    • 服务消费者在启动时会自动从服务注册中心获取服务列表,使用负载均衡进行调用。

    4. 配置管理

    Spring Cloud 提供了配置中心(如 Spring Cloud Config Server),允许将应用的配置从集中式的配置服务器中获取。Spring Cloud 在启动时会尝试连接配置服务器,并加载应用的配置。

    如果启用了 Spring Cloud Config,启动时会从配置服务器获取配置信息(如数据库连接信息、微服务配置等),这些信息会被自动加载到 Spring 环境中。

    5. 熔断器与负载均衡

    Spring Cloud 还集成了 Netflix 的一些工具,如 Hystrix (熔断器)和 Ribbon(负载均衡),帮助处理服务调用的可靠性和负载均衡。启动时,Spring Cloud 会根据配置启动这些组件,确保服务调用的容错性。

    • Hystrix :通过 @EnableCircuitBreaker 注解启用熔断器,防止调用失败时影响其他服务。

    • Ribbon:启用客户端负载均衡,服务消费者会根据 Ribbon 自动选择一台合适的服务提供者。

    Spring Cloud 启动过程的主要步骤如下:

    1. 执行 main 方法,调用 SpringApplication.run() 启动应用。

    2. 初始化 Spring Boot 的核心功能,加载配置。

    3. 启动服务注册与发现,服务注册到注册中心。

    4. 加载和初始化 Spring Cloud 相关功能(如配置中心、熔断器、负载均衡等)。

    5. 执行 CommandLineRunnerApplicationRunner 中的代码(如果定义)。

    6. 启动完成,应用开始接受请求。

    • 提高并发性能

      • 使用异步处理Kafka消息发送,避免在主线程中阻塞,提升性能。

      • 可以通过增加并发线程池来处理多个消息,提高吞吐量。

    • 提高高可用性

      • 如果Kafka发送失败,可以进行重试或者备用处理,以确保消息不丢失。

      • 对于消息处理,可以考虑使用队列处理和死信队列的机制,避免消息丢失和延时。

    • 优化代码逻辑

      • 代码中没有处理异常情况,我们可以增加异常处理和日志记录。

      • 可以使用缓存机制减少频繁调用的数据库操作。

    解释:

    1. 异步消息处理
    • handle() 方法中,使用 ExecutorService 提交异步任务来处理 Kafka 消息的发送。这样可以避免消息处理阻塞主线程,提高系统的并发处理能力。
  • 日志记录和异常处理

    • 增加了异常捕获和日志记录,确保在发生异常时能够捕获并记录,方便后续排查问题。

    • 使用 Kafka 的 addCallback 方法来捕获消息发送成功与失败的回调,分别进行相应的日志记录。

  • ExecutorService 线程池

    • 通过 ExecutorService 使用线程池来处理高并发任务。线程池的大小可以根据实际业务需求进行调整(这里使用了一个固定大小的线程池,大小为10)。线程池能够有效地管理并发任务,避免因线程创建过多而消耗过多的资源。
  • 提高可扩展性和高可用性

    • 通过异步操作和线程池,可以根据业务需要调整并发度,并发量增加时,只需要扩展线程池即可。

    • Kafka 发送失败时,增加了失败回调,可以根据需要做重试机制,或者将失败消息放入死信队列进行后续处理。

  • 消息格式化

    • 使用 GsonUtils.getJsonFromObject(event)OrderOverdueEvent 对象转化为 JSON 字符串。虽然这里没有修改,但可以根据需要优化 JSON 序列化方式。

    代码:

    go 复制代码
    @Component
    public class OrderOverdueMessageHandler extends AbstractDelayMessageHandler<String> {
        
        private static final Logger log = LoggerFactory.getLogger(OrderOverdueMessageHandler.class);
        
        @Autowired
        @Qualifier("bizKafkaTemplate")
        KafkaTemplate<String, String> bizKafkaTemplate;
    
        @Autowired
        private ExecutorService executorService;  // 用于处理异步消息发送
        
        /**
         * 订单逾期消息处理,异步发送到 Kafka
         * 
         * @param message 订单ID
         */
        @Override
        public void handle(String message) {
            // 异步执行发送Kafka的操作,避免阻塞主线程
            executorService.submit(() -> {
                try {
                    log.info("接收到订单逾期延时消息,订单Id:{}", message);
                    OrderOverdueEvent event = OrderOverdueEvent.builder()
                            .source(OrderOverdueSourceEnum.DELAY_QUEUE.getCode())
                            .orderId(message)
                            .build();
                    
                    // 将事件发送到 Kafka
                    bizKafkaTemplate.send(KafkaConstant.ORDER_OVERDUE_TOPIC, GsonUtils.getJsonFromObject(event))
                        .addCallback(
                            result -> log.info("订单逾期消息成功发送到 Kafka, 订单Id:{}", message),
                            ex -> log.error("订单逾期消息发送到 Kafka 失败,订单Id:{}", message, ex)
                        );
                } catch (Exception e) {
                    log.error("处理订单逾期消息时发生异常,订单Id:{}", message, e);
                }
            });
        }
    
        /**
         * 获取队列名称
         * 
         * @return 队列名称
         */
        @Override
        public String queueName() {
            return RedissonConstant.ORDER_OVERDUE_DELAY_QUEUE;
        }
        
        /**
         * 定义ExecutorService用于并发执行消息发送操作
         * 
         * @return ExecutorService
         */
        @Bean
        public ExecutorService executorService() {
            return Executors.newFixedThreadPool(10);  // 创建固定大小的线程池,根据业务需求调整线程数
        }
    }
    1. KafkaTemplate 注入 :使用 @Autowired 注解注入了一个 KafkaTemplate<String, String>,并且通过 @Qualifier 指定了具体的 Kafka 模板(bizKafkaTemplate)。

    2. **消息处理方法 handle**:

    • handle 方法接收一个 String 类型的消息(即订单 ID)。

    • 创建一个 OrderOverdueEvent 对象,并将订单 ID 和事件来源(OrderOverdueSourceEnum.DELAY_QUEUE.getCode())等信息封装到事件中。

    • 调用 bizKafkaTemplate.send 方法将事件对象转化为 JSON 字符串后,发送到 Kafka 中,使用的主题是 KafkaConstant.ORDER_OVERDUE_TOPIC

  • 队列名称

    • queueName() 方法返回了延时队列的名称,RedissonConstant.ORDER_OVERDUE_DELAY_QUEUE,这个名称通常对应于 Redis 中的延时队列。

    使用场景:

    该类用于订单逾期的消息处理,特别是在延时消息处理的场景下。假设某个订单逾期了,并且该逾期事件被放入 Redis 的延时队列中,当延时到达时,这个类会从队列中取出订单 ID,然后生成一个事件并通过 Kafka 发送到相关系统,进行进一步的处理。

    配置 Kafka 和 Redis

    application.ymlapplication.properties 文件中配置 Kafka 和 Redis(或者使用默认配置):

    Kafka 配置

    go 复制代码
    spring:
      kafka:
        producer:
          bootstrap-servers: localhost:9092
          key-serializer: org.apache.kafka.common.serialization.StringSerializer
          value-serializer: org.apache.kafka.common.serialization.StringSerializer

    Redis 配置(如果没有自动配置):

    go 复制代码
    spring:
      redis:
        host: localhost
        port: 6379
    调用 OrderOverdueMessageHandler

    在你的服务或控制器中调用 OrderOverdueMessageHandler,可以模拟一个订单逾期消息的处理:

    go 复制代码
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    @Service
    public class OrderOverdueService {
    
        @Autowired
        private OrderOverdueMessageHandler orderOverdueMessageHandler;
    
        public void handleOrderOverdue(String orderId) {
            // 调用 handler 处理订单逾期消息
            orderOverdueMessageHandler.handle(orderId);
        }
    }

    定时任务中定期检查订单状态,当发现订单逾期时,调用上面的服务方法处理逾期:

    go 复制代码
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.scheduling.annotation.Scheduled;
    import org.springframework.stereotype.Component;
    
    @Component
    public class OrderOverdueTask {
    
        @Autowired
        private OrderOverdueService orderOverdueService;
    
        @Scheduled(fixedRate = 5000) // 每 5 秒检查一次
        public void checkOrderOverdue() {
            // 假设从数据库或其他地方获取到逾期订单的 ID
            String orderId = "123456"; // 模拟订单 ID
            orderOverdueService.handleOrderOverdue(orderId);
        }
    }

    实际应用中应该有一个 Kafka 消费者来接收这些消息并进行处理。以下是一个简单的 Kafka 消费者示例:

    go 复制代码
    import org.springframework.kafka.annotation.KafkaListener;
    import org.springframework.stereotype.Service;
    
    @Service
    public class OrderOverdueConsumer {
    
        @KafkaListener(topics = "order-overdue-topic", groupId = "order-overdue-group")
        public void consume(String message) {
            // 处理订单逾期事件
            System.out.println("消费到订单逾期消息: " + message);
            // 解析消息并做后续处理
        }
    }
    查看 Kafka 消息

    你可以在 Kafka 的消费者端接收到订单逾期的消息,消息内容是一个 JSON 字符串,包含订单 ID 和来源信息。例如:

    go 复制代码
    {
      "source": "DELAY_QUEUE",
      "orderId": "123456"
    }

    总结

    类别 优化内容
    ✅ 可读性 使用 switch 替代多个 if-else
    ✅ 性能 避免重复调用、日志统一处理
    ✅ 安全性 finally 中解锁加 isLocked && rLock.isHeldByCurrentThread() 判断
    ✅ 可维护 各种业务分支更清晰易扩展,核心流程注释明确
相关推荐
郭涤生37 分钟前
Chapter 10: Batch Processing_《Designing Data-Intensive Application》
笔记·分布式
王佑辉1 小时前
【系统架构设计师】系统架构评估中的重要概念
系统架构
小杨4042 小时前
springboot框架项目实践应用十五(扩展sentinel区分来源)
spring boot·后端·spring cloud
风铃儿~2 小时前
RabbitMQ
java·微服务·rabbitmq
风铃儿~3 小时前
Sentinel深度解析:微服务流量防卫兵的原理与实践
java·微服务·sentinel
郭涤生3 小时前
微服务系统记录
笔记·分布式·微服务·架构
马达加斯加D3 小时前
MessageQueue --- RabbitMQ可靠传输
分布式·rabbitmq·ruby
cg50174 小时前
Spring Boot 中的 Bean
java·前端·spring boot
Gy-1-__4 小时前
【springcloud】快速搭建一套分布式服务springcloudalibaba(三)
后端·spring·spring cloud
橘猫云计算机设计5 小时前
基于springboot科研论文检索系统的设计(源码+lw+部署文档+讲解),源码可白嫖!
java·spring boot·后端·毕业设计