3212 字
约 10 分钟
17
第17章 分布式应用
无标签

第17章 分布式应用

掌握MyBatis在分布式环境下的应用技术

课程进度:68% 完成
💻 查看完整代码 - 在线IDE体验

🎯 学习目标

  • 读写分离:掌握MyBatis读写分离的实现原理和配置方法
  • 分库分表:学习使用ShardingSphere实现数据分片
  • 分布式事务:了解分布式事务的处理方案和Seata集成
  • 数据源管理:掌握多数据源的配置和动态切换
  • 性能优化:学习分布式环境下的性能优化策略

1. 分布式应用概述

1.1 分布式数据库架构

🔄 读写分离

将读操作和写操作分离到不同的数据库实例,提高系统并发能力和可用性。

📊 分库分表

将数据分散到多个数据库和表中,解决单表数据量过大的问题。

🔗 分布式事务

保证跨多个数据源的事务一致性,确保数据的完整性。

⚡ 负载均衡

在多个数据库实例之间分配负载,提高系统整体性能。

1.2 技术选型对比

技术方案 适用场景 优势 劣势
读写分离 读多写少 提高读性能,降低主库压力 主从延迟,数据一致性
分库分表 海量数据 突破单库限制,线性扩展 跨片查询复杂,运维成本高
分布式事务 强一致性要求 保证数据一致性 性能开销大,复杂度高

2. 读写分离实现

2.1 多数据源配置

# application.yml
spring:
  datasource:
    master:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/mybatis_master
      username: root
      password: 123456
      type: com.alibaba.druid.pool.DruidDataSource
      
    slave:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3307/mybatis_slave
      username: root
      password: 123456
      type: com.alibaba.druid.pool.DruidDataSource

mybatis:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.example.entity
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

2.2 数据源配置类

@Configuration
public class DataSourceConfig {

    @Bean("masterDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.master")
    public DataSource masterDataSource() {
        return DruidDataSourceBuilder.create().build();
    }

    @Bean("slaveDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.slave")
    public DataSource slaveDataSource() {
        return DruidDataSourceBuilder.create().build();
    }

    @Bean("dynamicDataSource")
    @Primary
    public DataSource dynamicDataSource() {
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        Map<Object, Object> dataSourceMap = new HashMap<>();
        dataSourceMap.put(DataSourceType.MASTER, masterDataSource());
        dataSourceMap.put(DataSourceType.SLAVE, slaveDataSource());
        
        dynamicDataSource.setTargetDataSources(dataSourceMap);
        dynamicDataSource.setDefaultTargetDataSource(masterDataSource());
        return dynamicDataSource;
    }

    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(dynamicDataSource());
        factoryBean.setMapperLocations(
            new PathMatchingResourcePatternResolver()
                .getResources("classpath:mapper/*.xml")
        );
        return factoryBean.getObject();
    }
}

2.3 动态数据源实现

public class DynamicDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.getDataSourceType();
    }
}

public class DataSourceContextHolder {
    private static final ThreadLocal<DataSourceType> CONTEXT_HOLDER = 
        new ThreadLocal<>();

    public static void setDataSourceType(DataSourceType dataSourceType) {
        CONTEXT_HOLDER.set(dataSourceType);
    }

    public static DataSourceType getDataSourceType() {
        return CONTEXT_HOLDER.get();
    }

    public static void clearDataSourceType() {
        CONTEXT_HOLDER.remove();
    }
}

public enum DataSourceType {
    MASTER, SLAVE
}

2.4 AOP切面实现

@Aspect
@Component
@Order(1)
public class DataSourceAspect {

    @Pointcut("@annotation(com.example.annotation.DataSource)")
    public void dataSourcePointCut() {}

    @Around("dataSourcePointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        
        DataSource dataSource = method.getAnnotation(DataSource.class);
        if (dataSource != null) {
            DataSourceContextHolder.setDataSourceType(dataSource.value());
        } else {
            // 根据方法名自动判断
            String methodName = method.getName();
            if (methodName.startsWith("get") || methodName.startsWith("select") 
                || methodName.startsWith("find") || methodName.startsWith("query")) {
                DataSourceContextHolder.setDataSourceType(DataSourceType.SLAVE);
            } else {
                DataSourceContextHolder.setDataSourceType(DataSourceType.MASTER);
            }
        }
        
        try {
            return point.proceed();
        } finally {
            DataSourceContextHolder.clearDataSourceType();
        }
    }
}

3. 分库分表实现

3.1 ShardingSphere配置

<dependency>
    <groupId>org.apache.shardingsphere</groupId>
    <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
    <version>5.2.1</version>
</dependency>

3.2 分片配置

spring:
  shardingsphere:
    datasource:
      names: ds0,ds1
      ds0:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/mybatis_shard_0
        username: root
        password: 123456
      ds1:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/mybatis_shard_1
        username: root
        password: 123456
    
    rules:
      sharding:
        tables:
          dist_users:
            actual-data-nodes: ds$->{0..1}.dist_users_$->{0..1}
            table-strategy:
              standard:
                sharding-column: id
                sharding-algorithm-name: user-table-inline
            database-strategy:
              standard:
                sharding-column: id
                sharding-algorithm-name: user-database-inline
          dist_orders:
            actual-data-nodes: ds$->{0..1}.dist_orders_$->{0..1}
            table-strategy:
              standard:
                sharding-column: user_id
                sharding-algorithm-name: order-table-inline
            database-strategy:
              standard:
                sharding-column: user_id
                sharding-algorithm-name: order-database-inline
        
        sharding-algorithms:
          user-database-inline:
            type: INLINE
            props:
              algorithm-expression: ds$->{id % 2}
          user-table-inline:
            type: INLINE
            props:
              algorithm-expression: dist_users_$->{id % 2}
          order-database-inline:
            type: INLINE
            props:
              algorithm-expression: ds$->{user_id % 2}
          order-table-inline:
            type: INLINE
            props:
              algorithm-expression: dist_orders_$->{user_id % 2}

3.3 分片策略

分片类型 分片键 分片算法 适用场景
水平分库 用户ID 取模算法 用户数据分散
水平分表 用户ID 取模算法 单表数据量控制
垂直分库 业务模块 业务分离 业务解耦
复合分片 多字段 复合算法 复杂业务场景

4. 分布式事务管理

4.1 Seata集成

@Service
public class DistributedTransactionService {

    @Autowired
    private UserService userService;
    
    @Autowired
    private OrderService orderService;

    @GlobalTransactional
    public void createUserAndOrder(String username, String email, 
                                  Integer age, BigDecimal amount, 
                                  boolean simulateError) {
        // 创建用户
        User user = new User(username, email, age);
        Long userId = userService.createUser(user);
        
        // 创建订单
        Order order = new Order();
        order.setUserId(userId);
        order.setOrderNo("ORDER_" + System.currentTimeMillis());
        order.setAmount(amount);
        order.setStatus("PENDING");
        
        orderService.createOrder(order);
        
        // 模拟异常
        if (simulateError) {
            throw new RuntimeException("模拟分布式事务异常");
        }
    }
}

4.2 事务模式对比

事务模式 一致性 性能 适用场景
AT模式 最终一致性 高 大部分业务场景
TCC模式 强一致性 中 对一致性要求高
SAGA模式 最终一致性 高 长事务场景
XA模式 强一致性 低 传统分布式事务

5. 数据源注解使用

5.1 注解定义

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
    DataSourceType value() default DataSourceType.MASTER;
}

public enum DataSourceType {
    MASTER,
    SLAVE
}

5.2 使用示例

@Service
public class UserService {

    @DataSource(DataSourceType.MASTER)
    public Long createUser(User user) {
        // 强制使用主库
        return userMapper.insertUser(user);
    }

    @DataSource(DataSourceType.SLAVE)
    public List<User> getAllUsers() {
        // 强制使用从库
        return userMapper.selectAllUsers();
    }

    // 自动判断(推荐)
    public User getUserById(Long id) {
        // 自动使用从库(方法名以get开头)
        return userMapper.selectUserById(id);
    }

    public void updateUser(User user) {
        // 自动使用主库(方法名以update开头)
        userMapper.updateUser(user);
    }
}

6. 性能优化策略

6.1 连接池优化

spring:
  datasource:
    master:
      # 主库配置(写操作较多)
      initial-size: 10
      min-idle: 10
      max-active: 50
      max-wait: 60000
      
    slave:
      # 从库配置(读操作较多)
      initial-size: 5
      min-idle: 5
      max-active: 20
      max-wait: 60000

6.2 分片优化

  • 分片键选择:选择分布均匀的字段作为分片键
  • 避免跨片查询:尽量在单个分片内完成查询
  • 批量操作优化:按分片键分组进行批量操作
  • 索引优化:在分片键上建立索引

6.3 缓存策略

@Service
public class UserService {

    @Cacheable(value = "users", key = "#id")
    @DataSource(DataSourceType.SLAVE)
    public User getUserById(Long id) {
        return userMapper.selectUserById(id);
    }

    @CacheEvict(value = "users", key = "#user.id")
    @DataSource(DataSourceType.MASTER)
    public void updateUser(User user) {
        userMapper.updateUser(user);
    }
}

7. 监控与运维

7.1 数据源监控

  • Druid监控:连接池状态、SQL执行统计
  • 主从延迟监控:监控主从同步延迟
  • 连接数监控:监控各数据源连接使用情况
  • 慢SQL监控:识别和优化慢查询

7.2 分片监控

  • 分片路由追踪:监控SQL路由到的分片
  • 跨片查询监控:识别跨片查询并优化
  • 数据分布监控:监控各分片的数据分布
  • 性能指标监控:各分片的性能指标

7.3 事务监控

  • 事务状态监控:监控分布式事务状态
  • 事务链路追踪:追踪事务执行链路
  • 回滚统计:统计事务回滚情况
  • 性能分析:分析事务执行性能

8. 最佳实践

8.1 读写分离最佳实践

  • 主从延迟处理:重要查询使用主库,一般查询使用从库
  • 故障转移:从库故障时自动切换到主库
  • 负载均衡:多个从库之间进行负载均衡
  • 监控告警:监控主从同步状态,及时告警

8.2 分库分表最佳实践

  • 分片键选择:选择分布均匀且查询频繁的字段
  • 分片数量规划:根据数据增长预估合理的分片数量
  • 避免跨片事务:尽量在业务层面避免跨片事务
  • 数据迁移策略:制定分片扩容和数据迁移策略

8.3 分布式事务最佳实践

  • 减少使用:尽量通过业务设计避免分布式事务
  • 选择合适模式:根据业务需求选择合适的事务模式
  • 超时设置:设置合理的事务超时时间
  • 补偿机制:设计完善的补偿和重试机制

9. 常见问题与解决方案

9.1 主从延迟问题

问题:主从数据库之间存在同步延迟,可能导致读取到旧数据。解决方案:重要查询强制使用主库设置合理的延迟容忍度使用缓存减少对从库的依赖监控主从延迟,及时告警

9.2 跨片查询性能问题

问题:跨分片查询需要在多个分片上执行,性能较差。解决方案:优化业务逻辑,避免跨片查询使用冗余字段减少关联查询建立全局索引表使用搜索引擎处理复杂查询

9.3 分布式事务性能问题

问题:分布式事务涉及多个数据源,性能开销较大。解决方案:尽量避免使用分布式事务选择性能较好的AT模式优化事务范围,减少锁定时间使用异步处理提高性能

10. 实践练习

练习任务

  1. 配置主从数据库,实现读写分离
  2. 使用ShardingSphere实现分库分表
  3. 集成Seata实现分布式事务
  4. 编写数据源切换的单元测试
  5. 监控分布式应用的性能指标

10.1 环境准备

# 1. 启动多个MySQL实例
docker run -d --name mysql-master -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 mysql:8.0
docker run -d --name mysql-slave -p 3307:3306 -e MYSQL_ROOT_PASSWORD=123456 mysql:8.0

# 2. 配置主从复制
# 在主库执行
CREATE USER 'repl'@'%' IDENTIFIED BY 'repl123';
GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';
FLUSH PRIVILEGES;

# 在从库执行
CHANGE MASTER TO
  MASTER_HOST='主库IP',
  MASTER_USER='repl',
  MASTER_PASSWORD='repl123',
  MASTER_LOG_FILE='mysql-bin.000001',
  MASTER_LOG_POS=0;
START SLAVE;

# 3. 启动Seata Server
sh seata-server.sh -p 8091 -h 127.0.0.1 -m file

10.2 运行示例程序

# 1. 克隆项目
git clone https://github.com/example/mybatis-distributed.git
cd mybatis-distributed

# 2. 修改配置文件
vim src/main/resources/application.yml
# 修改数据库连接信息

# 3. 初始化数据库
mysql -h localhost -P 3306 -u root -p < src/main/resources/init-master.sql
mysql -h localhost -P 3307 -u root -p < src/main/resources/init-slave.sql

# 4. 编译运行
mvn clean compile
mvn spring-boot:run

# 5. 观察控制台输出
# 查看读写分离、分库分表、分布式事务的执行日志

10.3 测试结果示例

=== MyBatis 分布式应用演示 ===

1. 读写分离演示
写操作:创建用户
[MASTER] INSERT INTO users (username, email, age) VALUES (?, ?, ?)
用户创建成功,ID: 1
读操作:查询用户
[SLAVE] SELECT * FROM users WHERE id = ?
查询到用户: test_user_1703123456789
读操作:查询用户列表
[SLAVE] SELECT * FROM users
用户总数: 1

2. 分库分表演示
[SHARD ds1.dist_users_1] INSERT INTO dist_users_1 (username, email, age) VALUES (?, ?, ?)
分片用户创建成功,ID: 1
[SHARD ds1.dist_orders_1] INSERT INTO dist_orders_1 (user_id, order_no, amount, status) VALUES (?, ?, ?, ?)
分片订单创建成功,ID: 1

3. 分布式事务演示
执行分布式事务(成功场景)
[SEATA] Global transaction [192.168.1.100:8091:123456789] begin
[MASTER] INSERT INTO users (username, email, age) VALUES (?, ?, ?)
[MASTER] INSERT INTO orders (user_id, order_no, amount, status) VALUES (?, ?, ?, ?)
[SEATA] Global transaction [192.168.1.100:8091:123456789] commit
分布式事务执行成功

执行分布式事务(失败场景)
[SEATA] Global transaction [192.168.1.100:8091:123456790] begin
[SEATA] Global transaction [192.168.1.100:8091:123456790] rollback
分布式事务回滚成功: 模拟分布式事务异常

4. 数据源切换演示
使用主数据源查询
[MASTER] SELECT * FROM users
主数据源用户数量: 10
使用从数据源查询
[SLAVE] SELECT * FROM users
从数据源用户数量: 10

5. 性能对比演示
读分离100次查询耗时: 156ms
分片批量插入50条记录耗时: 89ms

注意事项:确保主从数据库配置正确,主从同步正常Seata Server需要正确配置和启动分片数据库需要预先创建分片表注意观察SQL执行的数据源路由情况监控分布式事务的执行状态

本章小结:通过本章学习,你应该掌握了MyBatis在分布式环境下的应用,包括读写分离的实现、分库分表的配置、分布式事务的管理等。这些技术是构建高可用、高性能分布式系统的重要基础。在实际项目中,需要根据具体的业务需求和技术架构选择合适的分布式方案。

本章小结

📖 本章小结

本章我们学习了 分布式应用 的相关内容,包括:

  • 核心概念和基本原理
  • 配置方法和实践技巧
  • 应用场景和最佳实践
  • 常见问题和解决方案

下一章节 🎉

恭喜完成第17章学习!

你已经掌握了 分布式应用 的核心知识
第18章微服务架构继续学习 MyBatis 的更多特性和应用⏱️预计 30 分钟📊中等难度开始学习 → ← 第16章 📚 课程目录 第18章 →

第17章 分布式应用
https://clxhxhhr.top/posts/3070/
作者
clxstart
发布于
2026-09-24
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。