小码农 - 后端开发
type: channel
link: https://www.kidcodes.cn/index.php/category/backend/
description: 记录 Java、PHP、Python、Go 等后端开发技术，包括框架、数据库、微服务等相关内容。
items: 记录下从SpringBoot2.7升级到SpringBoot3.2.4问题处理
items: 聊一聊规则引擎
items: Flowable工作流引擎源码深度解析
items: 分布式锁如何自动续期（Watchdog 机制解析）
items: DDD业务服务的目录规划
items: ShardingSphere使用中的重点问题剖析
items: 聊聊Hystrix
items: Spring Security OAuth2在微前端架构下实现统一退出
items: larvel 实现注册eureka
items: Spring Security OAuth2与Spring Session Redis整合实现微服务会话共享

记录下从SpringBoot2.7升级到SpringBoot3.2.4问题处理
type: item
date: 2025-02-16T16:01:11+08:00
description: Dataroom从SpringBoot2.7升级到SpringBoot3.2.4 为了使用让DataRoom引入MCP，整合Spring AI体系，升级SrpingBoot 升级到Jakarta EE Java EE 已更改为 Jakarta EE，Spring Boot 3.x 的所有依赖项 API 也从 Java EE 升级为 Jakarta EE。 简单来说，您需要将所有 javax 的 imports 都替换为 jakarta。具体如下： javax.persistence.* -> jakarta.persistence.* javax.validation.* -> jakarta.validation.* javax.servlet.* -> jakarta.servlet.* javax.annotation.* -> jakarta.annotation.* javax.transaction.* -> jakarta.transaction.* 换mybatis依赖 Invalid value type for attribute ‘factoryBeanObjectType‘: java.lang.String 所以升级 Mybatis-Plus 版本为 3.5.5 版本即可，需要注意下 Maven 的坐标标识 是mybatis-plus-spring-boot3-starter，这点和SpringBoot 2 的依赖坐标mybatis-plus-boot-starter有所区别 第一步：注释spring-boot-starter <!-- <dependency>--> <!-- <groupId>com.baomidou</groupId>--> <!-- <artifactId>mybatis-plus-boot-starter</artifactId>--> <!-- <version>${mybatis.plus.version}</version>--> <!-- </dependency>--> 第二步：引入新的依赖 <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-spring-boot3-starter</artifactId> <version>3.5.5</version> </dependency> 第三步 <dependencyManagement> <dependencies> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>3.0.3</version> </dependency> </dependencies> </dependencyManagement> 动态数据源修改 第一步注释如下代码 <dependency> <groupId>com.baomidou</groupId> <artifactId>dynamic-datasource-spring-boot-starter</artifactId> <version>3.5.0</version> </dependency> 第二步：引入依赖 <dependency> <groupId>com.baomidou</groupId> <artifactId>dynamic-datasource-spring-boot3-starter</artifactId> <version>4.2.0</version> </dependency>

聊一聊规则引擎
type: item
date: 2022-01-29T10:31:22+08:00
description: 规则引擎能力 一、规则集 规则集也叫决策集，是由一组普通规则和循环规则构成的规则集合，是使用频率最高的一种业务规则实现方式。 鲁班提供循环、逻辑（IF/ELSE）、赋值、执行、跳出规则等指令。 经典实例 新用户注册优惠： 如果用户是新注册用户，并且首次购买订单金额超过一定数额，则发送一张10%折扣的优惠券。 购物满减优惠： 如果用户购物车中的商品总价达到一定金额阈值，则自动应用满减优惠。比如，购物车金额大于100元可以享受减免10元的优惠。 特定商品优惠： 如果用户购买特定商品（如促销商品、季节性商品），则提供针对该商品额外的折扣或优惠。 生日优惠券： 在用户生日时发送一张生日优惠券，提供额外的折扣或者赠送特定商品。 用户活跃度奖励： 对于长时间未下单的用户，发送特定额度的优惠券以鼓励再次购买。 地理位置优惠： 针对特定地理位置的用户发送定向的优惠券，以促进当地的销售。 病人风险评估系统规则集示例 考虑以下条件和操作： 年龄： 30岁以下（Young）、30到50岁（Middle-aged）、50岁以上（Elderly）。 生活方式： 锻炼频率（High、Moderate、Low）、饮食习惯（Healthy、Unhealthy）。 慢性病史： 有（Yes）或无（No）。 风险评估等级： 低风险（Low）、中风险（Moderate）、高风险（High）。 规则集示例： FOR（用户集合 ） 用户。年龄 IF 年龄 = Elderly AND (锻炼频率 = Low OR 饮食习惯 = Unhealthy) THEN 风险评估等级 = 高风险 IF 年龄 = Middle-aged AND 慢性病史 = Yes THEN 风险评估等级 = 高风险 IF 年龄 = Young AND 锻炼频率 = High THEN 风险评估等级 = 低风险 IF 年龄 = Elderly AND 锻炼频率 = Moderate THEN 风险评估等级 = 中风险 在这个例子中，规则集根据病人的年龄、生活方式和慢性病史等条件制定了不同的风险评估规则。例如，对于年龄较大且锻炼频率低或饮食不健康的病人，系统会评估其为高风险群体，需要更频繁的检查或干预措施。 这种规则集的应用有助于医疗保健领域更精准地对病人进行风险评估，并根据其个体情况提供相应的医疗建议，从而改善病人的健康状况。 二、决策树 决策树又称为规则树，规则引擎中提供的另外一种构建规则的方式，它以一棵树形结构来表现规则，决策树表现业务规则更为形象，其中每个节点代表一个测试条件，每个分支代表一个测试结果，而每个叶子节点包含一个决策结果。 信用评分系统决策树示例 考虑以下特征和目标： 年龄： 25岁以下（Young）、25到40岁（Middle-aged）、40岁以上（Elderly）。 收入水平： 低收入（Low）、中等收入（Medium）、高收入（High）。 信用历史： 良好（Good）、一般（Fair）、差劣（Poor）。 是否有抵押物： 有（Yes）或无（No）。 信用申请结果： 批准（Approved）或拒绝（Rejected）。 决策树示例： IF 信用历史 = 良好 AND 收入水平 = 高收入 THEN 信用申请结果 = 批准 ELSE IF 信用历史 = 良好 AND 收入水平 = 中等收入 THEN 信用申请结果 = 批准 ELSE IF 信用历史 = 一般 AND 是否有抵押物 = 有 THEN 信用申请结果 = 批准 ELSE IF 信用历史 = 一般 AND 是否有抵押物 = 无 THEN 信用申请结果 = 拒绝 ELSE IF 信用历史 = 差劣 THEN 信用申请结果 = 拒绝 在这个例子中，决策树根据申请者的年龄、收入水平、信用历史和是否有抵押物等特征，自动判断信用申请的结果。例如，如果申请者信用历史良好且收入水平高，那么系统会批准其信用申请；而如果信用历史一般但有抵押物，同样会批准。 这种决策树的应用使得信用评分系统能够自动化、可解释地做出信用决策，提高了效率同时保持了透明度。 三、决策表 决策表是一种以表格形式表现规则的工具，它非常适用于描述处理判断条件较多，各条件又相互组合、有多种决策方案的情况，决策表提供精确而简洁描述复杂逻辑的方式。 视图 使用excel 决策表的应用场景： 业务规则管理： 用于管理和维护大量的业务规则，使得业务规则的管理更加简便和直观。 风险评估： 在金融领域，可以使用决策表来定义风险评估规则，根据客户的不同特征进行信用评级。 产品定价： 在销售领域，可以使用决策表来制定产品定价策略，根据市场条件和产品特性进行灵活的定价。 合规性检查： 在法务和合规性领域，决策表可以用于定义符合法规的业务行为，确保组织的合规性。 推荐系统： 在电商领域，…

Flowable工作流引擎源码深度解析
type: item
date: 2021-10-22T17:10:11+08:00
description: Flowable工作流引擎源码深度解析 前言 Flowable是一个轻量级的业务流程引擎，基于BPMN 2.0规范实现，是Activiti项目的一个分支。作为Java生态中最流行的工作流引擎之一，了解其内部实现对于定制化开发和性能优化至关重要。本文将深入分析Flowable的核心源码结构和执行逻辑，帮助开发者更好地理解和使用这一强大工具。 核心架构概览 Flowable的源码主要分为以下几个核心模块： flowable-engine：核心引擎实现 flowable-bpmn-converter：BPMN模型转换器 flowable-process-validation：流程验证模块 flowable-image-generator：流程图生成模块 flowable-rest：REST API实现 其中，flowable-engine是最核心的部分，我们的分析也将主要集中在这个模块上。 ProcessEngine初始化流程 Flowable的入口是ProcessEngine接口，通常通过ProcessEngineConfiguration来创建。让我们看看其初始化过程： public class ProcessEngineConfigurationImpl extends ProcessEngineConfiguration { public ProcessEngine buildProcessEngine() { init(); ProcessEngineImpl processEngine = new ProcessEngineImpl(this); postProcessEngineInitialisation(); return processEngine; } public void init() { initCommandContextFactory(); initTransactionContextFactory(); initCommandExecutors(); initServices(); initDataSource(); initDbSchema(); initBeans(); initTransactionFactory(); // 其他初始化方法... } } 初始化过程主要包括： 初始化命令上下文工厂 初始化事务上下文工厂 初始化命令执行器 初始化各种服务 初始化数据源和数据库架构 初始化事务工厂等 这种设计遵循了良好的工厂模式和构建器模式。 命令模式的应用 Flowable大量使用了命令模式，所有对流程的操作都被封装为Command对象： public interface Command<T> { T execute(CommandContext commandContext); } 执行命令的是CommandExecutor，它主要有两个实现： CommandExecutorImpl：普通实现 TransactionCommandExecutor：带事务的实现 让我们看看CommandExecutorImpl的实现： public class CommandExecutorImpl implements CommandExecutor { protected CommandContextFactory commandContextFactory; protected TransactionContextFactory transactionContextFactory; public <T> T execute(Command<T> command) { CommandContext commandContext = commandContextFactory.createCommandContext(command); try { T result = command.execute(commandContext); commandContext.close(); return result; } catch (Exception e) { commandContext.exception(e); } finally { try { commandContext.close(); } catch (Exception e) { // 日志记录 } } return null; } } 这种设计确保了所有流程操作都在一个一致的上下文中执行，并且可以正确处理事务和异常。 流程定义加载 当我们部署一个BPMN文件时，Flowable会解析它并转换为内部模型。核心类是BpmnParser： public class BpmnParser { public BpmnParse createParse(…

分布式锁如何自动续期（Watchdog 机制解析）
type: item
date: 2021-09-06T15:15:19+08:00
description: 分布式锁如何自动续期（Watchdog 机制解析） 在分布式系统中，分布式锁广泛应用于防止多个进程同时操作共享资源。其中，基于 Redis 的分布式锁（如 Redisson）提供了一种高效的实现方式。然而，锁的超时管理是一个关键问题：如果业务执行时间较长，锁可能会在任务完成前过期，从而导致多个客户端同时持有锁，造成数据竞争。为了解决这个问题，我们可以使用 Watchdog（看门狗）机制，实现分布式锁的自动续期。 1. 分布式锁的基本流程 以 Redis 为例，分布式锁的获取和释放流程通常如下： 客户端1 发送 SET lock_key unique_id NX PX 30000 请求，尝试获取锁。 Redis 服务器 存储 lock_key，过期时间设定为 30 秒。 客户端1 成功获取锁，开始执行任务。 如果任务在 30 秒内完成，客户端1 发送 DEL lock_key 释放锁。 其他客户端（如 客户端2）在锁释放后才能成功获取锁。 上述流程有一个关键问题：如果 客户端1 任务执行时间超过 30 秒，但还未完成，锁会被 Redis 自动删除，从而导致其他客户端获取锁，出现并发问题。 2. Watchdog 机制的作用 为了解决锁过期的问题，Redisson 等库引入了 Watchdog 机制。Watchdog 的基本原理如下： 客户端1 获取锁后，启动 Watchdog 线程，每隔 lockLeaseTime / 3 秒（例如 10 秒）检查锁是否仍然持有。 如果 客户端1 仍然活跃，则自动向 Redis 发送 PEXPIRE lock_key 30000 请求，延长锁的有效期。 当 客户端1 任务执行完成 时，主动调用 DEL lock_key 释放锁。 如果 客户端1 进程崩溃，Watchdog 不会继续续期，锁会在到期后自动释放。 3. Watchdog 续期的流程示意 根据你的流程图，分布式锁 Watchdog 续期的流程如下： 客户端1 获取锁（SET lock_key unique_id NX PX 30000）。 客户端1 启动 Watchdog 线程，间隔 10 秒检查锁状态。 Watchdog 检查 Redis 是否仍然持有 lock_key。 如果 lock_key 仍然属于 客户端1，执行 PEXPIRE lock_key 30000 延长锁的有效期。 如果 客户端1 任务完成，调用 DEL lock_key 释放锁，Watchdog 线程退出。 如果 客户端1 进程意外终止，Watchdog 失效，锁超时后自动释放。 4. Watchdog 机制的优缺点 优点 自动续期：任务执行时间不确定时，确保锁不会过早释放。 进程故障自动释放：如果进程崩溃，锁在超时后自动释放，避免死锁。 减少人为干预：无需手动计算任务时间，提高系统健壮性。 缺点 需要持久化心跳检测：Watchdog 线程需要定期与 Redis 通信，增加了 Redis 负载。 续期间隔需优化：如果 Watchdog 续期间隔过长，锁可能仍然会过期。 5. 代码示例（基于 Redisson） // 创建 Redisson 客户端 RedissonClient redisson = Redisson.create(); // 获取分布式锁 RLock lock = redisson.getLock("myLock"); boolean locked = lock.tryLock(10, 30, TimeUnit.SECONDS); if (locked) { try { // 执行业务逻辑 System.out.println("业务处理中..."); Thread.sleep(40000); // 模拟业务执行超时 } finally { // 释放锁 lock.unlock(); } } 在 Redisson 中，如果 lock.tryLock() 仅传递 租约时间 lockLeaseTime，则默认会启用 Watchdog 机制，自动续期，直到 unlock() 被调用。 6. 总结 Watchdog 机制通过定期续约，避免 Redis 锁过期导致的并发问题。 适用于执行时间不确定的任务，能够在任务完成前自动续期。 需要权衡 Watchdog 续期频率和 Redis 负载，以优化性能。 如果你的业务场景对分布式锁有较高的可靠性要求，建议使用 Redisson 内置的 Watchdog 机制，而非手动管理锁的生命周期。

DDD业务服务的目录规划
type: item
date: 2020-12-06T18:18:11+08:00
description: DDD架构下的工程目录结构划分详解 在领域驱动设计(Domain-Driven Design, DDD)中，合理的工程目录结构对于实现复杂业务逻辑的清晰组织至关重要。本文将详细介绍基于DDD思想的工程目录划分方案，帮助开发团队更好地组织代码，提高系统的可维护性和可扩展性。 DDD工程目录的四层架构 根据思维导图所示，一个标准的DDD架构通常分为四个主要层次： 1. 接口层（interfaces） 接口层是系统与外部交互的门户，负责处理来自外部的请求，并将请求转发到应用层。 facade（统一对外接口）：提供RESTful API接口，处理HTTP请求和响应 config（spring mvc相关配置）：包含接口层相关的配置，如控制器配置、请求拦截器等 src/main/java/com/company/project/interfaces/ ├── facade/ # 外观模式，统一接口 │ ├── dto/ # 数据传输对象 │ ├── assembler/ # DTO与领域对象转换 │ └── controller/ # 控制器 └── config/ # 接口层配置 2. 应用层（application） 应用层是业务流程的协调者，负责组织领域对象完成特定的应用功能。 service（应用服务接口和实现包）：协调领域对象完成业务流程 assembler（DTO与Entity互相转换包）：负责应用层与领域层数据模型的相互转换 event（事件发布/订阅） ： publish（事件发布包）：发布领域事件 subscribe（消息订阅包）：订阅并处理领域事件 src/main/java/com/company/project/application/ ├── service/ # 应用服务 │ ├── impl/ # 应用服务实现 │ └── dto/ # 应用服务数据传输对象 ├── assembler/ # DTO与领域对象转换 └── event/ # 事件处理 ├── publish/ # 事件发布 └── subscribe/ # 事件订阅 3. 领域层（domain） 领域层是业务核心，包含业务规则和逻辑。 aggregate（聚合包） ：聚合是一组关联的实体和值对象的集合 entity（聚合根、实体、值对象）：领域模型核心元素 service（领域服务，处理特殊领域内业务）：处理跨实体的业务逻辑 src/main/java/com/company/project/domain/ ├── aggregate/ # 聚合 │ ├── order/ # 订单聚合（示例） │ │ ├── Order.java # 聚合根 │ │ ├── OrderItem.java # 实体 │ │ ├── OrderStatus.java # 值对象 │ │ └── OrderRepository.java # 仓储接口 │ └── user/ # 用户聚合（示例） └── service/ # 领域服务 └── impl/ # 领域服务实现 4. 基础设施层（infrastructure） 基础设施层为其他层提供技术支持。 repository（仓储数据库ORM层） ： po（数据库对象）：数据库表映射对象 assembler（entity转po）：领域对象与数据库对象转换 domain.impl（领域服务实现包）：领域服务的具体实现 config（基础配置）：系统基础配置 utils（基础工具类）：通用工具类 src/main/java/com/company/project/infrastructure/ ├── repository/ # 仓储实现 │ ├── po/ # 持久化对象 │ ├── assembler/ # 领域对象与持久化对象转换 │ ├── mapper/ # MyBatis映射接口 │ └── impl/ # 仓储接口实现 ├── domain/ # 领域层实现 │ └── impl/ # 领域服务实现类 ├── config/ # 基础配置 └── utils/ # 工具类 目录结构的实践建议 按领域划分而非技术功能：在DDD中，首先应该按照业务领域进行划分，而非按照技术功能（如controller、service、dao等） 考虑限界上下文：不同的业务上下文应该有清晰的边界，可以在目录结构中体现 聚合根作为目录划分的基础：每个聚合根可以作为一个子目录，包含相关的实体和值对象 保持领域逻辑的纯净：领域层不应依赖其他层，特别是不应依赖基础设施层 接口与实现分离：定义清晰的接口，并将实现放在适当的包中 示例目录结构（电子商务系统） src/main/java/com/ecommerce/ ├── interf…

ShardingSphere使用中的重点问题剖析
type: item
date: 2020-02-02T18:11:10+08:00
description: ShardingSphere使用中的重点问题剖析 一、引言 ShardingSphere是一款强大的分布式数据库中间件，在助力应用实现数据分片、读写分离等功能方面表现卓越。然而，在实际应用过程中，会遇到一些关键问题。本文将结合思维导图，深入探讨ShardingSphere在读写分离和事务处理方面的重点问题及应对策略。 二、读写分离相关问题 （一）数据库主从同步延迟问题 在读写分离架构下，主库负责写入操作，从库承担读取操作。但由于主从库之间的同步存在延迟，可能导致从库读取到的数据并非最新。 解决方案一：提供同一个线程的Ddl执行统一库。即对于在同一个线程内的一系列数据定义语言（DDL）操作，确保都在主库上执行，这样可以避免主从同步延迟带来的数据不一致问题，因为在同一线程内操作主库，能保证数据的实时性和一致性。 解决方案二：提供Hint强制读写库。通过特定的Hint机制，开发者可以在代码中明确指定某些查询操作必须在主库上执行，绕过从库，从而获取最新的数据，适用于对数据实时性要求极高的场景，如金融交易中的账户余额查询等。 （二）强制读主问题 当应用需要获取最新数据时，可能会面临强制读主的需求。但在现有的读写分离架构下，常规的基于对象关系映射（ORM）的基础增删改查（CRUD）操作可能无法直接满足这一要求。 解决方法：需要重构ORM封装的基础CRUD。通过对ORM框架进行定制化开发或扩展，使其能够支持根据业务需求灵活选择从主库还是从库进行读取操作。例如，在特定的业务方法中，通过修改ORM的查询逻辑，添加强制读主的标识，让查询直接路由到主库，保证数据的及时性。 （三）Sql兼容问题 在使用ShardingSphere进行读写分离时，部分SQL语句可能会出现兼容性问题。 部分Sql语句不支持，需要换语句：例如“Select Distinct Test_Int From T_Order;”这样的语句可能无法直接在ShardingSphere环境下正常执行。此时，开发者需要根据ShardingSphere的语法和规则，对SQL语句进行改写，以满足数据库的操作要求。 Union（合并）相关语句示例：像“Select User_Id, Order_Id, Status From T_Order Union All Select Item_Id, Order_Id, User_Id From T_Order_Item”这样的Union操作语句，在使用时也需要注意其在ShardingSphere中的兼容性。可能需要对表结构、字段类型等进行进一步的检查和调整，确保Union操作能够正确执行，实现数据的合并查询需求。 三、事务问题 （一）Sharding-Jdbc不支持强事务 Sharding-Jdbc作为ShardingSphere的一种使用模式，在事务处理方面存在一定局限性，它不支持传统的强事务（如严格的ACID事务）。 解决策略一：尽量在业务处理事务。在应用层的业务逻辑中，通过合理的流程设计和代码编写，将相关的操作进行分组和协调，以满足业务对数据一致性的要求。例如，在电商系统的下单流程中，通过在业务代码中依次处理库存扣减、订单生成等操作，并进行异常捕获和回滚处理，来模拟事务的一致性效果。 解决策略二：使用Sharding-Proxy，支持多种分布式事务。Sharding-Proxy提供了对分布式事务更好的支持，它可以采用两阶段提交（2PC）、TCC（Try - Confirm - Cancel）等多种分布式事务解决方案。对于一些对事务一致性要求较高的场景，如银行转账业务，可以使用Sharding-Proxy结合合适的分布式事务方案，确保跨库操作的数据一致性。 四、总结 ShardingSphere在实现读写分离和处理事务时，虽然存在一些棘手的问题，但通过合理的技术选型和针对性的解决方案，能够有效应对。在实际项目中，开发者需要深入理解这些问题的本质和解决方案的原理，根据业务场景的特点，灵活运用相关策略，以充分发挥ShardingSphere的优势，构建高效、稳定、可靠的分布式数据库应用系统。

聊聊Hystrix
type: item
date: 2019-11-12T19:24:20+08:00
description: 聊聊Hystrix 一、引言 在分布式系统中，服务之间的调用错综复杂，一个服务的故障可能会引发连锁反应，导致整个系统的雪崩效应。Hystrix作为一款强大的容错库，通过一系列机制保障系统的稳定性和可用性。本文将深入探讨Hystrix的核心原理。 二、Hystrix线程隔离原理 （一）初始化 Set参数对象：Hystrix在初始化时，需要设置一系列参数对象，包括分组（用于将相关的命令归为一组，方便资源管理和统计）、名称（标识具体的命令）、队列（用于存放待执行的任务） 、线程数（指定线程池中的线程数量，决定了能同时处理的请求量）以及线程池Key（用于唯一标识线程池）。 创建线程池（Threadpoolexcutor）：根据设置的参数创建线程池，线程池是Hystrix实现线程隔离的关键组件。通过线程池，不同的服务调用可以在各自独立的线程中执行，避免了一个服务调用的阻塞影响其他调用。 存入Concurrenthashmap：将创建好的线程池存入Concurrenthashmap中，便于后续快速获取和管理。 （二）执行Command的方式 Execute()：以同步的堵塞方式执行Run() 方法。这意味着调用线程会一直等待命令执行完成并返回结果，如果命令执行时间较长，调用线程会被阻塞，适用于需要立即获取结果且对响应时间要求不特别高的场景。 Queue()：采用异步阻塞方式执行Run() 。它会立即返回一个Future对象，调用线程可以通过这个Future对象在后续合适的时机获取执行结果，在获取结果之前，调用线程不会被一直阻塞，适用于一些后台任务处理场景。 Observe()：在事件注册前执行Run()/Construct() 。它返回一个Observable对象，通过订阅这个对象可以获取命令执行结果，并且可以在命令执行前进行一些事件注册和预处理操作。 Toobservable()：在事件注册后执行Run()/Construct() 。同样返回Observable对象，不过是在事件注册之后才开始执行命令逻辑，提供了更灵活的事件处理和结果订阅机制。 （三）底层使用Rxjava Hystrix的底层实现依赖于Rxjava，Rxjava是一个基于事件流、实现异步操作的库。它提供了丰富的操作符来处理异步任务、事件流的组合和转换等。在Hystrix中，利用Rxjava的特性可以更方便地实现异步执行、事件监听以及对命令执行结果的处理等功能，使得Hystrix的异步操作更加简洁和高效。 三、Hystrix的其他关键机制 （一）熔断器（Circuit Breaker） 熔断器就像一个开关，当服务调用的失败率达到一定阈值（比如在一定时间内失败率超过50% ），熔断器会被打开，后续对该服务的调用将不再实际执行，而是直接返回一个预设的 fallback 结果（比如默认值或者缓存数据）。这样可以快速失败，避免大量无效的调用，防止故障扩散。当熔断器打开一段时间后（称为休眠窗口），会尝试半开状态，允许少量请求通过，来检测服务是否已经恢复正常，如果恢复则关闭熔断器，否则继续保持打开状态。 （二）降级（Fallback） 降级是Hystrix的重要保护机制。当服务调用失败（如超时、异常等情况）或者熔断器打开时，会触发降级逻辑。开发人员可以自定义fallback方法，返回默认值、缓存数据或者一些简单的错误提示信息等，从而保证即使在服务不可用的情况下，系统仍能给用户提供一定的响应，而不是直接报错，提升了用户体验和系统的稳定性。 （三）请求缓存（Request Caching） Hystrix支持请求缓存功能，在一次请求过程中，如果相同的命令被多次调用，Hystrix可以从缓存中直接获取结果，而不需要重新执行命令。这在一些频繁调用相同服务且结果相对稳定的场景下，可以大大减少不必要的服务调用，提高系统的响应速度和性能。 四、总结 Hystrix通过线程隔离、熔断器、降级、请求缓存等一系列精心设计的原理和机制，为分布式系统构建了强大的容错体系。理解这些原理，有助于我们在实际开发中合理配置和使用Hystrix，提升系统的健壮性和可靠性，更好地应对复杂多变的分布式环境中的各种故障和挑战。 五 实操 1. 引入依赖 在pom.xml文件中添加相关依赖： <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring - cloud - starter - hystrix</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring - boot - sta…

Spring Security OAuth2在微前端架构下实现统一退出
type: item
date: 2019-07-21T11:17:11+08:00
description: Spring Security OAuth2在微前端架构下实现统一退出 在微前端架构中，当一个应用退出登录时，实现所有应用同步退出的功能是一个常见需求。这个问题主要源于微前端架构下各个子应用之间的分离特性，导致一个应用注销后，其他应用仍保持登录状态。下面我将详细介绍如何解决这个问题。 问题分析 微前端架构下，登出问题主要有以下几个原因： 各子应用可能使用独立的会话管理 OAuth2 token可能在各应用中独立存储 缺乏统一的退出机制来协调各个子应用 解决方案 1. 实现单点登出(Single Sign-Out) @RestController public class LogoutController { private final TokenStore tokenStore; private final ClientRegistrationRepository clientRegistrationRepository; public LogoutController(TokenStore tokenStore, ClientRegistrationRepository clientRegistrationRepository) { this.tokenStore = tokenStore; this.clientRegistrationRepository = clientRegistrationRepository; } @PostMapping("/api/logout") public ResponseEntity<Map<String, String>> logout( @RequestParam(value = "token", required = false) String token, HttpServletRequest request, HttpServletResponse response, @AuthenticationPrincipal OAuth2User principal) { // 获取当前认证信息 Authentication auth = SecurityContextHolder.getContext().getAuthentication(); // 如果存在认证信息，则进行处理 if (auth != null) { // 撤销token if (auth instanceof OAuth2AuthenticationToken) { OAuth2AuthenticationToken oauthToken = (OAuth2AuthenticationToken) auth; String clientRegistrationId = oauthToken.getAuthorizedClientRegistrationId(); // 根据需要调用OAuth2服务器的撤销接口 revokeToken(clientRegistrationId, token); } // 清除安全上下文 SecurityContextHolder.clearContext(); // 使session失效 HttpSession session = request.getSession(false); if (session != null) { session.invalidate(); } // 清除相关cookie deleteCookies(request, response); } Map<String, String> result = new HashMap<>(); result.put("message", "成功退出登录"); return ResponseEntity.ok(result); } private void revokeToken(String clientRegistrationId, String token) { try { ClientRegistration clientRegistration = clientRegistrationRepository.findByRegistrationId(clientRegistrationId); if (clientRegistration != null && token != null) { // 根据OAuth2服务器提供的撤销端点进行token撤销 String revokeEndpoint = clientRegistration.getProviderDetails().getConfigurationMetadata().get("revocation_endpoint").toStrin…

larvel 实现注册eureka
type: item
date: 2019-02-16T15:11:51+08:00
description: larvel 实现注册eureka 项目需要，php部分功能作为微服务注册到eureka 如下实现 <?php namespace App\Services; use GuzzleHttp\Client; use GuzzleHttp\Exception\GuzzleException; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Cache; class EurekaService { protected $client; protected $config; protected $instanceId; protected $appName; protected $ipAddress; protected $port; protected $vipAddress; protected $heartbeatInterval; protected $eurekaUrl; protected $renewalTimer; /** * EurekaService 构造函数 */ public function __construct() { $this->client = new Client(); $this->loadConfig(); $this->setupInstance(); } /** * 从Laravel配置文件加载配置 */ protected function loadConfig() { $this->config = config('eureka'); $this->eurekaUrl = $this->config['eureka_url']; $this->appName = $this->config['app_name']; $this->ipAddress = $this->config['ip_address'] ?? gethostbyname(gethostname()); $this->port = $this->config['port'] ?? 80; $this->vipAddress = $this->config['vip_address'] ?? $this->appName; $this->heartbeatInterval = $this->config['heartbeat_interval'] ?? 30; $this->instanceId = $this->appName . ':' . $this->ipAddress . ':' . $this->port; } /** * 设置实例详细信息 */ protected function setupInstance() { // 默认实例数据 $this->instanceData = [ 'instance' => [ 'instanceId' => $this->instanceId, 'hostName' => $this->ipAddress, 'app' => strtoupper($this->appName), 'ipAddr' => $this->ipAddress, 'status' => 'UP', 'port' => [ '$' => $this->port, '@enabled' => 'true', ], 'securePort' => [ '$' => 443, '@enabled' => 'false', ], 'homePageUrl' => "http://{$this->ipAddress}:{$this->port}/", 'statusPageUrl' => "http://{$this->ipAddress}:{$this->port}/info", 'healthCheckUrl' => "http://{$this->ipAddress}:{$this->port}/health", 'dataCenterInfo' => [ '@class' => 'com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo', 'name' => 'MyOwn', ], 'leaseInfo' => [ 'renewalIntervalInSecs' => 30, 'durationInSecs' => 90, 'registrationTimestamp' => 0, 'lastRenewalTimestamp' => 0, 'evictionTimestamp' => 0, 'serviceUpTimestamp' => 0, ], 'me…

Spring Security OAuth2与Spring Session Redis整合实现微服务会话共享
type: item
date: 2018-11-15T16:14:28+08:00
description: Spring Security OAuth2与Spring Session Redis整合实现微服务会话共享 引言 在微服务架构中，会话管理是一个常见的挑战。当用户通过OAuth2认证后，如何在多个微服务之间共享会话信息成为一个亟待解决的问题。本文将详细介绍如何利用Spring Security OAuth2结合Spring Session Redis实现微服务架构下的会话共享方案。 技术栈 Spring Boot Spring Security OAuth2 Spring Session Redis Spring Cloud (可选，用于服务发现等) 为什么选择Redis作为会话存储 在分布式系统中，使用Redis存储会话有以下优势： 高性能：Redis是基于内存的数据库，读写速度极快 数据持久化：支持数据持久化，防止数据丢失 丰富的数据结构：支持多种数据类型，适合存储复杂的会话数据 原生的过期机制：便于管理会话生命周期 实现步骤 1. 添加依赖 首先，我们需要在各个微服务的pom.xml文件中添加必要的依赖： <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-oauth2-client</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-oauth2-resource-server</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session-data-redis</artifactId> </dependency> </dependencies> 2. 配置Redis与Spring Session 在每个微服务的application.yml文件中添加Redis和Spring Session的配置： spring: redis: host: localhost port: 6379 password: # 如有密码则填写 database: 0 session: store-type: redis redis: namespace: spring:session # Redis中存储会话的命名空间前缀 timeout: 1800 # 会话超时时间（秒） security: oauth2: client: registration: custom-client: client-id: client-id client-secret: client-secret authorization-grant-type: authorization_code redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}" scope: read,write provider: custom-provider: authorization-uri: http://auth-server/oauth/authorize token-uri: http://auth-server/oauth/token user-info-uri: http://auth-server/userinfo user-name-attribute: name server: servlet: session: cookie: http-only: true secure: true # 在生产环境中应设置为true 3. 启用Spring Session 在主应用类上添加@EnableRedisHttpSession注解： package com.example.microservice; import org.sprin…