AWS MySQL 读写分离配置指南

发布于:2025-07-06 ⋅ 阅读:(20) ⋅ 点赞:(0)

# AWS JDBC Wrapper读写分离配置实战:Spring Boot + MyBatis Plus完整解决方案

## 前言

在微服务架构中,数据库读写分离是提升系统性能的重要手段。本文将详细介绍如何在Spring Boot项目中使用AWS JDBC Wrapper实现自动读写分离,重点解决MyBatis Plus框架下的配置难点,并对比Spring JPA的差异。

**核心结论**:AWS JDBC Wrapper需要连接的`readOnly`状态来判断路由,MyBatis Plus需要手动添加`@Transactional(readOnly = true)`,而Spring JPA会自动处理。

## 一、AWS JDBC Wrapper简介

### 1.1 什么是AWS JDBC Wrapper

AWS JDBC Wrapper是Amazon提供的数据库连接增强工具,支持:

- 自动故障转移

- 读写分离

- 连接池管理

- 性能监控

### 1.2 读写分离原理

```mermaid

graph TD

A[应用程序] --> B[AWS JDBC Wrapper]

B --> C{检查Connection.readOnly}

C -->|true| D[Aurora Reader Endpoint]

C -->|false| E[Aurora Writer Endpoint]

D --> F[只读副本]

E --> G[主库]

```

**关键机制**:AWS JDBC Wrapper通过检测JDBC连接的`readOnly`属性来决定路由目标。

## 二、基础配置

### 2.1 Maven依赖

```xml

<dependency>

<groupId>software.amazon.jdbc</groupId>

<artifactId>aws-advanced-jdbc-wrapper</artifactId>

<version>2.2.0</version>

</dependency>

<dependency>

<groupId>com.baomidou</groupId>

<artifactId>mybatis-plus-boot-starter</artifactId>

<version>3.4.3</version>

</dependency>

```

### 2.2 数据源配置

```yaml

spring:

datasource:

type: com.zaxxer.hikari.HikariDataSource

driver-class-name: software.amazon.jdbc.Driver

username: ${DB_USERNAME}

password: ${DB_PASSWORD}

url: jdbc:aws-wrapper:mysql://${AURORA_CLUSTER_ENDPOINT}:3306/${DATABASE_NAME}?wrapperPlugins=readWriteSplitting,failover&characterEncoding=utf-8&wrapperLogLevel=FINEST&useSSL=true&requireSSL=true

```

**重要参数说明**:

- `wrapperPlugins=readWriteSplitting,failover`:启用读写分离和故障转移

- `wrapperLogLevel=FINEST`:启用详细日志,便于调试

### 2.3 日志配置

```xml

<!-- logback.xml -->

<configuration>

<!-- AWS JDBC Wrapper日志 -->

<logger name="software.amazon.jdbc" level="TRACE"/>

<logger name="software.amazon.jdbc.plugin.readwritesplitting" level="TRACE"/>

<logger name="software.amazon.jdbc.plugin.failover" level="TRACE"/>

<!-- HikariCP连接池日志 -->

<logger name="com.zaxxer.hikari" level="DEBUG"/>

</configuration>

```

## 三、核心问题:MyBatis Plus的读写分离挑战

### 3.1 问题现象

**预期**:查询操作自动路由到只读副本

**实际**:所有操作都路由到主库

**关键日志**:

```

TRACE ReadWriteSplittingPlugin - Writer connection set to 'cluster-endpoint:3306'

```

### 3.2 根本原因分析

**Spring JPA vs MyBatis Plus的差异**:

| 框架 | 事务配置 | readOnly设置 | 读写分离效果 |

|------|----------|--------------|-------------|

| Spring JPA | Repository方法自动添加`@Transactional(readOnly=true)` | ✅ 自动 | ✅ 有效 |

| MyBatis Plus | ServiceImpl无自动事务配置 | ❌ 手动 | ❌ 无效 |

**技术原理**:

1. AWS JDBC Wrapper依赖`Connection.setReadOnly(true)`来判断路由

2. Spring事务管理器负责设置连接的readOnly状态

3. 只有在`@Transactional(readOnly=true)`时,Spring才会调用`connection.setReadOnly(true)`

## 四、解决方案

### 4.1 方案一:Service层添加只读事务(推荐)

```java

@Service

@Slf4j

public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {

/**

* 查询方法 - 走读库

*/

@Override

@Transactional(readOnly = true)

public List<User> list(QueryWrapper<User> queryWrapper) {

return super.list(queryWrapper);

}

/**

* 分页查询 - 走读库

*/

@Override

@Transactional(readOnly = true)

public IPage<User> page(IPage<User> page, QueryWrapper<User> queryWrapper) {

return super.page(page, queryWrapper);

}

/**

* 统计查询 - 走读库

*/

@Override

@Transactional(readOnly = true)

public int count(QueryWrapper<User> queryWrapper) {

return super.count(queryWrapper);

}

/**

* 写操作 - 走写库

*/

@Override

@Transactional

public boolean save(User entity) {

return super.save(entity);

}

}

```

### 4.2 方案二:Controller层添加只读事务

```java

@RestController

@RequestMapping("/api/users")

public class UserController {

@Autowired

private IUserService userService;

/**

* 查询接口 - 走读库

*/

@GetMapping("/list")

@Transactional(readOnly = true)

public Result<List<User>> getUserList() {

List<User> users = userService.list();

return Result.success(users);

}

/**

* 创建接口 - 走写库

*/

@PostMapping

@Transactional

public Result<Boolean> createUser(@RequestBody User user) {

boolean success = userService.save(user);

return Result.success(success);

}

}

```

### 4.3 方案三:创建专门的只读Service

```java

@Service

@Transactional(readOnly = true) // 类级别只读事务

public class UserReadOnlyService {

@Autowired

private UserMapper userMapper;

public List<User> queryUsers(QueryWrapper<User> queryWrapper) {

return userMapper.selectList(queryWrapper);

}

public IPage<User> queryUsersPage(IPage<User> page, QueryWrapper<User> queryWrapper) {

return userMapper.selectPage(page, queryWrapper);

}

public long queryCount(QueryWrapper<User> queryWrapper) {

return userMapper.selectCount(queryWrapper);

}

}

```

### 4.4 方案四:混合使用JDBC和MyBatis Plus

```java

@Service

public class UserHybridService {

@Autowired

private DataSource dataSource;

@Autowired

private IUserService userService;

/**

* 简单查询用JDBC - 走读库

*/

public long getUserCount() throws SQLException {

try (Connection conn = dataSource.getConnection()) {

conn.setReadOnly(true);

try (PreparedStatement stmt = conn.prepareStatement("SELECT COUNT(*) FROM user")) {

try (ResultSet rs = stmt.executeQuery()) {

return rs.next() ? rs.getLong(1) : 0;

}

}

}

}

/**

* 复杂操作用MyBatis Plus - 走写库

*/

@Transactional

public boolean createUserWithRelations(User user) {

return userService.save(user);

}

}

```

## 五、验证方法

### 5.1 测试代码

```java

@RestController

@RequestMapping("/api/test")

public class ReadWriteTestController {

@Autowired

private IUserService userService;

@Autowired

private DataSource dataSource;

/**

* 测试JDBC读操作

*/

@GetMapping("/jdbc-read")

public String testJdbcRead() throws SQLException {

try (Connection conn = dataSource.getConnection()) {

conn.setReadOnly(true);

// 执行查询...

return "JDBC读测试完成";

}

}

/**

* 测试MyBatis Plus读操作

*/

@GetMapping("/mybatis-read")

@Transactional(readOnly = true)

public String testMybatisRead() {

userService.list();

return "MyBatis Plus读测试完成";

}

}

```

### 5.2 期望的日志输出

**走读库的日志**:

```

TRACE ReadWriteSplittingPlugin - Reader connection set to 'cluster-ro-endpoint:3306'

TRACE ReadWriteSplittingPlugin - Routing read operation to reader endpoint

```

**走写库的日志**:

```

TRACE ReadWriteSplittingPlugin - Writer connection set to 'cluster-endpoint:3306'

TRACE ReadWriteSplittingPlugin - Routing write operation to writer endpoint

```

## 六、最佳实践

### 6.1 设计原则

1. **查询操作**:统一添加`@Transactional(readOnly = true)`

2. **写操作**:使用`@Transactional`或不添加注解

3. **事务边界**:在Service层或Controller层明确定义

4. **职责分离**:考虑创建专门的只读Service类

### 6.2 实施优先级

**高优先级**:

- 核心业务Service(订单、支付、用户等)

- 高频查询接口

- 报表和统计功能

**中优先级**:

- 基础数据Service

- 管理后台查询

- 定时任务查询

**低优先级**:

- 低频管理功能

- 工具类查询

### 6.3 注意事项

1. **事务传播**:在事务中的所有操作都会走主库

2. **连接复用**:HikariCP可能复用连接,观察日志时注意时间戳

3. **故障转移**:读库不可用时会自动转移到主库

4. **复制延迟**:业务逻辑需要考虑主从复制延迟

## 七、Spring JPA对比

### 7.1 为什么Spring JPA更容易实现读写分离

```java

// Spring Data JPA - 自动只读

@Repository

public interface UserRepository extends JpaRepository<User, Long> {

// 框架自动为查询方法添加 @Transactional(readOnly = true)

List<User> findByStatus(String status); // 自动走读库

// 框架自动为写方法添加 @Transactional

User save(User user); // 自动走写库

}

```

### 7.2 JPA vs MyBatis Plus总结

| 特性 | Spring JPA | MyBatis Plus |

|------|------------|--------------|

| 学习曲线 | 简单,开箱即用 | 需要理解事务配置 |

| 自动化程度 | 高度自动化 | 需要手动配置 |

| 性能控制 | 抽象层较厚 | 更接近原生SQL |

| 读写分离 | 自动支持 | 需要手动实现 |

| SQL优化 | 相对困难 | 灵活度高 |

## 八、故障排查

### 8.1 常见问题

**问题1:看不到ReadWriteSplittingPlugin日志**

- 检查URL中的`wrapperLogLevel=FINEST`

- 确认logback.xml中的日志级别

- 重启应用重新观察

**问题2:所有操作都连接同一endpoint**

- 检查`@Transactional(readOnly = true)`是否正确添加

- 确认Aurora集群是否有只读副本

- 验证URL中的`wrapperPlugins`参数

**问题3:连接失败**

- 检查SSL证书配置

- 验证网络连通性

- 确认Aurora集群状态

### 8.2 调试技巧

1. **启用详细日志**:

```yaml

logging:

level:

software.amazon.jdbc: TRACE

com.zaxxer.hikari: DEBUG

```

2. **诊断连接状态**:

```java

@GetMapping("/diagnose")

public Map<String, Object> diagnoseDatasource() {

try (Connection conn = dataSource.getConnection()) {

DatabaseMetaData metaData = conn.getMetaData();

Map<String, Object> info = new HashMap<>();

info.put("driverName", metaData.getDriverName());

info.put("url", metaData.getURL());

info.put("isAwsWrapper", metaData.getDriverName().contains("Amazon"));

return info;

}

}

```

## 九、总结

AWS JDBC Wrapper是一个强大的数据库连接工具,但在MyBatis Plus环境下需要正确配置事务注解才能实现读写分离。核心要点:

1. **理解原理**:读写分离依赖`Connection.setReadOnly()`状态

2. **正确配置**:为查询方法添加`@Transactional(readOnly = true)`

3. **验证效果**:通过日志确认路由行为

4. **渐进实施**:按优先级逐步改造现有代码

通过本文的配置方案,可以有效提升系统的数据库读性能,减轻主库压力,为系统的高可用和高性能打下坚实基础。

---

> **作者经验**:在实际项目中,建议先在测试环境验证配置,观察日志确认读写分离生效后,再逐步推广到生产环境。同时要注意监控Aurora集群的读写负载分布,确保达到预期的性能提升效果。

**技术栈**:Spring Boot 2.x + MyBatis Plus 3.4.x + AWS JDBC Wrapper 2.2.x + Aurora MySQL