SpringBoot3读写分离进阶:手写@Master注解,用AOP控制ShardingJDBC强制走主库

张开发
2026/4/4 2:57:55 15 分钟阅读
SpringBoot3读写分离进阶:手写@Master注解,用AOP控制ShardingJDBC强制走主库
SpringBoot3读写分离进阶手写Master注解实现主库强制路由在微服务架构中数据库读写分离是提升系统吞吐量的常见方案。但当你在SpringBoot3项目中配置好ShardingJDBC的读写分离后可能会遇到一个典型问题写入数据后立即查询结果却显示数据不存在。这种写完立即读不一致的现象正是读写分离架构需要解决的核心痛点之一。1. 读写分离的隐藏陷阱与解决方案设计1.1 主从同步延迟引发的业务问题在标准的读写分离架构中写操作发生在主库(Master)读操作分散到从库(Slave)。由于主从同步存在毫秒级延迟当业务逻辑需要写入后立即读取时可能会出现以下几种典型场景用户注册后立即查询个人信息订单支付成功后立即查看订单状态库存扣减后立即查询剩余数量这些场景下如果读请求被路由到从库而主从同步尚未完成就会返回数据不存在的错误结果。对于用户体验和业务逻辑来说这都是不可接受的。1.2 现有解决方案的局限性常见的临时解决方案包括强制读主库配置在配置文件中设置所有读操作都走主库但这完全丧失了读写分离的优势延迟查询业务代码中人为添加Thread.sleep但延迟时间难以精确控制重试机制当查询不到数据时自动重试增加了系统复杂性这些方案要么牺牲性能要么引入额外复杂度。我们需要一种更优雅的解决方案方法级别的动态主库路由。2. 基于HintManager的核心路由机制2.1 ShardingSphere的Hint强制路由原理ShardingJDBC提供了HintManager机制允许开发者通过编程方式覆盖默认的路由策略。关键API包括HintManager hintManager HintManager.getInstance(); hintManager.setWriteRouteOnly(); // 强制路由到主库 // ...执行数据库操作... hintManager.close(); // 必须手动关闭以避免内存泄漏这种机制的特点是线程绑定HintManager基于ThreadLocal实现不会影响其他线程临时生效仅在当前代码块内有效优先级最高会覆盖其他所有路由规则2.2 HintManager的资源管理陷阱在实际使用HintManager时有几个关键注意事项必须显式关闭不关闭会导致内存泄漏和后续路由异常异常处理确保在发生异常时也能正确释放资源嵌套调用多个HintManager同时激活时的行为需要特别处理以下是一个典型的错误示例// 错误示例可能造成资源泄漏 public void updateAndQuery() { HintManager hintManager HintManager.getInstance(); try { // 业务代码可能抛出异常 updateData(); queryData(); } finally { // 忘记调用hintManager.close() } }3. 实现自定义Master注解3.1 注解设计与AOP切面为了业务代码的简洁性我们设计一个自定义注解MasterRetention(RetentionPolicy.RUNTIME) Target(ElementType.METHOD) public interface Master { }对应的AOP切面实现核心逻辑Aspect Component Slf4j public class MasterRouteAspect { Around(annotation(com.example.annotation.Master)) public Object aroundMasterMethod(ProceedingJoinPoint joinPoint) throws Throwable { HintManager hintManager null; try { hintManager HintManager.getInstance(); hintManager.setWriteRouteOnly(); return joinPoint.proceed(); } finally { if (hintManager ! null) { hintManager.close(); } } } }3.2 切面实现的最佳实践在实现AOP切面时有几个优化点值得注意精确的切点表达式避免拦截不必要的方法// 精确到特定包下的Service层 Around(execution(* com.yourpackage..service.*.*(..)) annotation(Master))性能监控添加执行时间日志long start System.currentTimeMillis(); try { return joinPoint.proceed(); } finally { log.debug(Master route execution time: {}ms, System.currentTimeMillis() - start); }异常处理策略根据业务需求决定是否包装异常4. 在SpringBoot3中的完整集成方案4.1 项目依赖配置确保pom.xml中包含必要依赖dependencies !-- SpringBoot3基础依赖 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- ShardingJDBC最新版 -- dependency groupIdorg.apache.shardingsphere/groupId artifactIdshardingsphere-jdbc-core/artifactId version5.5.0/version /dependency !-- AOP支持 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-aop/artifactId /dependency /dependencies4.2 完整的sharding.yaml配置示例dataSources: master: dataSourceClassName: com.zaxxer.hikari.HikariDataSource driverClassName: com.mysql.cj.jdbc.Driver jdbcUrl: jdbc:mysql://master-host:3306/db username: root password: master-password slave1: dataSourceClassName: com.zaxxer.hikari.HikariDataSource driverClassName: com.mysql.cj.jdbc.Driver jdbcUrl: jdbc:mysql://slave1-host:3306/db username: root password: slave-password rules: - !READWRITE_SPLITTING dataSources: readwrite_ds: writeDataSourceName: master readDataSourceNames: - slave1 loadBalancerName: round_robin loadBalancers: round_robin: type: ROUND_ROBIN4.3 业务层使用示例在Service实现类中可以这样使用Master注解Service RequiredArgsConstructor public class OrderServiceImpl implements OrderService { private final OrderMapper orderMapper; Override Master // 强制走主库 public OrderDTO createOrder(OrderCreateVO vo) { Order order convertToEntity(vo); orderMapper.insert(order); // 立即查询确保数据一致性 return getOrderDetail(order.getId()); } Override public OrderDTO getOrderDetail(Long id) { // 默认走从库 return orderMapper.selectById(id); } }5. 高级应用与性能优化5.1 读写分离策略的混合应用在实际项目中我们可以根据业务特点组合多种策略场景类型策略选择实现方式强一致性要求强制主库Master注解弱一致性要求默认从库不添加注解批量操作主库执行在类级别添加Master统计查询从库执行添加Slave注解(如有)5.2 性能监控与调优建议为了确保系统稳定运行建议监控以下指标主库负载监控Master注解的使用频率同步延迟监控主从库之间的数据延迟时间AOP性能监控切面执行耗时可以通过Spring Actuator添加自定义指标Aspect Component RequiredArgsConstructor public class MasterRouteAspect { private final MeterRegistry meterRegistry; Around(annotation(Master)) public Object aroundMasterMethod(ProceedingJoinPoint joinPoint) throws Throwable { Timer.Sample sample Timer.start(meterRegistry); try { // ...原有逻辑... } finally { sample.stop(meterRegistry.timer(master.route.time)); } } }5.3 分布式事务的特别考虑当Master注解方法与分布式事务(如Seata)一起使用时需要特别注意HintManager与全局事务的交互确保两者不会互相干扰资源释放顺序先关闭HintManager再结束分布式事务异常处理需要更复杂的回滚逻辑一个典型的处理模式GlobalTransactional Master public void distributedOperation() { // 同时需要分布式事务和主库路由的业务逻辑 }在实现这类复杂场景时建议进行充分的集成测试验证各种异常情况下的系统行为。

更多文章