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

124 阅读12分钟

加群联系作者vx:xiaoda0423

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

1024bat.cn/

事件类型策略模式

重构目标

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

结构说明

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
  2. 代码结构优化:

    • 提取公共日志方法,减少重复。
    • 分类注释清晰,增强可维护性。
  3. 可扩展性提升:

    • 使用泛型支持更强类型检查。
    • 提前暴露接口方法,提高使用灵活性。
  4. 线程安全与可用性优化:

    • delayedQueueMap 使用 ConcurrentHashMap 保证线程安全。
    • 使用统一异常处理方式,方便故障排查。
优化点描述
缓存队列对象避免重复初始化 RBlockingDequeRDelayedQueue 提升性能,支持高并发。
双重检查锁防止多线程下并发初始化,提高线程安全性和效率。
统一日志格式清晰易懂,便于排查问题。
异步投递接口提升吞吐能力,适用于高并发场景。
sendIfPresent 逻辑优化移除已存在元素后重新添加,确保更新逻辑准确。
高扩展性结构支持多个队列名称,便于扩展不同业务队列。

. 线程安全本地缓存

  • 使用 ConcurrentHashMapcomputeIfAbsent,避免并发初始化导致队列重复创建。

2. 统一序列化配置

  • 使用 JsonJacksonCodec,确保延迟消息的序列化在 Redis 分布式环境中兼容性强,支持多服务跨语言。

3. 异步发送接口优化

  • 提供 sendAsyncsendAsyncIfAbsent,适合高并发不阻塞场景。

4. 队列重用与延迟队列封装解耦

  • 支持动态创建多个队列,适合大规模系统中多个业务模块隔离使用。

5. 可扩展性设计

  • 后续可扩展为队列优先级、回调通知、队列消费监听等模块。

6. 兼容 Redis Cluster 架构

  • Redisson 内部会处理 Cluster 路由逻辑,避免手动分片。

/**
 * 延迟消息生产者,基于 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. 启动完成,应用开始接受请求。

image.png

  • 提高并发性能

    • 使用异步处理Kafka消息发送,避免在主线程中阻塞,提升性能。
    • 可以通过增加并发线程池来处理多个消息,提高吞吐量。
  • 提高高可用性

    • 如果Kafka发送失败,可以进行重试或者备用处理,以确保消息不丢失。
    • 对于消息处理,可以考虑使用队列处理和死信队列的机制,避免消息丢失和延时。
  • 优化代码逻辑

    • 代码中没有处理异常情况,我们可以增加异常处理和日志记录。
    • 可以使用缓存机制减少频繁调用的数据库操作。

解释:

  1. 异步消息处理

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

    • 增加了异常捕获和日志记录,确保在发生异常时能够捕获并记录,方便后续排查问题。
    • 使用 Kafka 的 addCallback 方法来捕获消息发送成功与失败的回调,分别进行相应的日志记录。
  3. ExecutorService 线程池

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

    • 通过异步操作和线程池,可以根据业务需要调整并发度,并发量增加时,只需要扩展线程池即可。
    • Kafka 发送失败时,增加了失败回调,可以根据需要做重试机制,或者将失败消息放入死信队列进行后续处理。
  5. 消息格式化

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

代码:


@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
  3. 队列名称

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

使用场景:

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

配置 Kafka 和 Redis

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

Kafka 配置


spring:
  kafka:
    producer:
      bootstrap-servers: localhost:9092
      key-serializer: org.apache.kafka.common.serialization.StringSerializer
      value-serializer: org.apache.kafka.common.serialization.StringSerializer

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


spring:
  redis:
    host: localhost
    port: 6379

调用 OrderOverdueMessageHandler

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


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);
    }
}

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


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 消费者示例:


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 和来源信息。例如:


{
  "source": "DELAY_QUEUE",
  "orderId": "123456"
}

总结

类别优化内容
✅ 可读性使用 switch 替代多个 if-else
✅ 性能避免重复调用、日志统一处理
✅ 安全性finally 中解锁加 isLocked && rLock.isHeldByCurrentThread() 判断
✅ 可维护各种业务分支更清晰易扩展,核心流程注释明确