加群联系作者vx:xiaoda0423
仓库地址:https://webvueblog.github.io/JavaPlusDoc/
事件类型策略模式
重构目标
原问题 | 策略模式方案 |
---|---|
多个 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 // 响应封装类
目标:
-
职责分离: 拆分常量类,按照业务维度归类。
-
避免魔法值: 可通过
enum
替代字符串常量,提高可读性与类型安全。 -
方法优化: 替换多重
if-else
为更清晰的策略/映射方式。 -
命名统一规范: 常量命名建议全部大写,并用
_
分隔词语。
总结:
- 高并发性能优化:
-
使用
computeIfAbsent
替代手动判断延迟队列是否存在,提高线程安全性。 -
避免重复调用
initDelayQueue
。
-
代码结构优化:
-
-
提取公共日志方法,减少重复。
-
分类注释清晰,增强可维护性。
-
-
可扩展性提升:
-
-
使用泛型支持更强类型检查。
-
提前暴露接口方法,提高使用灵活性。
-
-
线程安全与可用性优化:
-
-
delayedQueueMap
使用ConcurrentHashMap
保证线程安全。 -
使用统一异常处理方式,方便故障排查。
优化点 描述 缓存队列对象 避免重复初始化 RBlockingDeque
和RDelayedQueue
提升性能,支持高并发。双重检查锁 防止多线程下并发初始化,提高线程安全性和效率。 统一日志格式 清晰易懂,便于排查问题。 异步投递接口 提升吞吐能力,适用于高并发场景。 sendIfPresent
逻辑优化移除已存在元素后重新添加,确保更新逻辑准确。 高扩展性结构 支持多个队列名称,便于扩展不同业务队列。 . 线程安全本地缓存
- 使用
ConcurrentHashMap
和computeIfAbsent
,避免并发初始化导致队列重复创建。
2. 统一序列化配置
- 使用
JsonJacksonCodec
,确保延迟消息的序列化在 Redis 分布式环境中兼容性强,支持多服务跨语言。
3. 异步发送接口优化
- 提供
sendAsync
和sendAsyncIfAbsent
,适合高并发不阻塞场景。
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 字符图标)。 -
设置
CommandLineRunner
和ApplicationRunner
接口的 Bean,它们会在应用启动后执行。
Spring Boot 启动过程大致如下:
-
执行
main
方法,调用SpringApplication.run()
启动应用。 -
初始化
SpringApplication
,加载配置。 -
创建并初始化
ApplicationContext
,扫描并注册 Bean。 -
自动配置系统根据依赖自动配置应用。
-
发布应用启动事件。
-
执行
CommandLineRunner
和ApplicationRunner
中的代码(如果定义)。 -
启动完成,应用开始运行。
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 启动过程的主要步骤如下:
-
执行
main
方法,调用SpringApplication.run()
启动应用。 -
初始化 Spring Boot 的核心功能,加载配置。
-
启动服务注册与发现,服务注册到注册中心。
-
加载和初始化 Spring Cloud 相关功能(如配置中心、熔断器、负载均衡等)。
-
执行
CommandLineRunner
和ApplicationRunner
中的代码(如果定义)。 -
启动完成,应用开始接受请求。
-
提高并发性能:
-
-
使用异步处理Kafka消息发送,避免在主线程中阻塞,提升性能。
-
可以通过增加并发线程池来处理多个消息,提高吞吐量。
-
-
提高高可用性:
-
-
如果Kafka发送失败,可以进行重试或者备用处理,以确保消息不丢失。
-
对于消息处理,可以考虑使用队列处理和死信队列的机制,避免消息丢失和延时。
-
-
优化代码逻辑:
-
-
代码中没有处理异常情况,我们可以增加异常处理和日志记录。
-
可以使用缓存机制减少频繁调用的数据库操作。
-
解释:
- 异步消息处理:
- 在
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); // 创建固定大小的线程池,根据业务需求调整线程数 } }
-
KafkaTemplate 注入 :使用
@Autowired
注解注入了一个KafkaTemplate<String, String>
,并且通过@Qualifier
指定了具体的 Kafka 模板(bizKafkaTemplate
)。 -
**消息处理方法
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.yml
或application.properties
文件中配置 Kafka 和 Redis(或者使用默认配置):Kafka 配置:
gospring: kafka: producer: bootstrap-servers: localhost:9092 key-serializer: org.apache.kafka.common.serialization.StringSerializer value-serializer: org.apache.kafka.common.serialization.StringSerializer
Redis 配置(如果没有自动配置):
gospring: redis: host: localhost port: 6379
调用
OrderOverdueMessageHandler
在你的服务或控制器中调用
OrderOverdueMessageHandler
,可以模拟一个订单逾期消息的处理:goimport 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); } }
定时任务中定期检查订单状态,当发现订单逾期时,调用上面的服务方法处理逾期:
goimport 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 消费者示例:
goimport 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()
判断✅ 可维护 各种业务分支更清晰易扩展,核心流程注释明确