亿级分布式系统架构演进实战(三)- 横向扩展(数据库读写分离)

亿级分布式系统架构演进实战(一)- 总体概要
亿级分布式系统架构演进实战(二)- 横向扩展(服务无状态化)

核心目标

分散数据库压力,提升读性能


1. 数据库架构设计

数据库由原理的单实例变成主从模式,主主要负责写,从负责读。

1.1 主从角色定义
节点类型数据流向核心职责
主库读写(Write)处理事务性写操作(INSERT/UPDATE/DELETE)/部分读
从库只读(Read)承担查询请求(SELECT),支持水平扩展
1.2 半同步复制实现

目标:确保主从数据强一致性,避免异步复制导致数据丢失。
我司系统是强一致优先,各位可以根据业务情况去选择

MySQL半同步配置

-- 主库配置
INSTALL PLUGIN rpl_semi_sync_master SONAME 'semisync_master.so';
SET GLOBAL rpl_semi_sync_master_enabled = 1;
SET GLOBAL rpl_semi_sync_master_timeout = 1000; -- 超时1秒后降级为异步

-- 从库配置
INSTALL PLUGIN rpl_semi_sync_slave SONAME 'semisync_slave.so';
SET GLOBAL rpl_semi_sync_slave_enabled = 1;

数据可靠性保障
• 主库提交事务前,至少一个从库ACK确认收到日志
• 超时未确认则自动降级为异步,记录告警日志


2. 透明路由实现

选择ShardingSphere客户端模式实现

2.1 数据源配置(Druid连接池)
dataSources:
  master:
    dataSourceClassName: com.alibaba.druid.pool.DruidDataSource
    driverClassName: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://master:3306/db?useSSL=false
    username: root
    password: 123456
    initialSize: 5
    minIdle: 5
    maxActive: 20
    maxWait: 60000
    timeBetweenEvictionRunsMillis: 60000
    validationQuery: SELECT 1

  slave1:
    dataSourceClassName: com.alibaba.druid.pool.DruidDataSource
    url: jdbc:mysql://slave1:3306/db?useSSL=false
    # 其他参数同master

  slave2:
    dataSourceClassName: com.alibaba.druid.pool.DruidDataSource
    url: jdbc:mysql://slave2:3306/db?useSSL=false
    # 其他参数同master
2.2 读写分离规则
rules:
- !READWRITE_SPLITTING
  dataSources:
    pr_ds:
      writeDataSourceName: master
      readDataSourceNames:
        - slave1
        - slave2
      loadBalancerName: round_robin  # 轮询负载均衡

路由逻辑
• 默认SELECT路由到从库
• 事务内的所有操作(包括SELECT)强制走主库


3. 事务内强制读主库实现

虽然数据库采用了半同步机制,但是从库还是有几率因为延迟使得请求读取了旧数据,对于对数据实时性很高的场景,可以强制走主库

3.1 基于Hint强制路由

代码示例

// 事务方法中强制读主库
@Transactional
public Order getOrderWithMasterRead(String orderId) {
    // 开启Hint强制路由主库
    HintManager hintManager = HintManager.getInstance();
    hintManager.setPrimaryRouteOnly();
    
    try {
        Order order = orderRepository.findById(orderId); // 强制走主库
        return order;
    } finally {
        hintManager.close(); // 关闭Hint
    }
}

Spring AOP全局配置

@Aspect
@Component
public class MasterRouteAspect {
    @Around("@annotation(transactional)")
    public Object forceMasterRead(ProceedingJoinPoint joinPoint, Transactional transactional) throws Throwable {
        HintManager hintManager = HintManager.getInstance();
        hintManager.setPrimaryRouteOnly();
        try {
            return joinPoint.proceed();
        } finally {
            hintManager.close();
        }
    }
}

还可以自定义注释去实现,这里就不展开。

3.2 事务传播控制

配置项

spring:
  shardingsphere:
    props:
      sql-show: true
      # 强制事务内读写均走主库
      transaction-type: BASE
      query-with-cipher-column: true

4. 主从同步延迟监控与治理
4.1 延迟监控(Prometheus + Grafana)

监控指标
mysql_slave_status_seconds_behind_master
mysql_global_status_wsrep_flow_control_paused

告警规则

groups:
- name: mysql_alerts
  rules:
  - alert: HighSlaveLag
    expr: mysql_slave_status_seconds_behind_master > 5
    for: 2m
    labels:
      severity: warning
    annotations:
      summary: "从库同步延迟超过阈值 ({{ $value }}秒)"
4.2 延迟解决方案

强制主库读
缓存兜底

@Cacheable(value = "orderCache", key = "#orderId", unless = "#result == null")
public Order getOrder(String orderId) {
    return orderRepository.findById(orderId); // 从库查询
}

通过Prometheus监测,触发告警动态摘除节点


5. 从库水平扩展(ShardingSphere负载均衡)
5.1 动态权重配置
loadBalancers:
  weighted_round_robin:
    type: WEIGHT
    props:
      slave1: 2
      slave2: 1  # slave1的权重是slave2的2倍
5.2 自动扩缩容流程

这里比较繁琐可以选择手动实现,我们目前就是手动

监控从库QPS/CPU
超过阈值?
调用Kubernetes API扩容新Slave
自动注册到ShardingSphere
更新负载均衡权重
维持现状

6. 连接池优化(Druid高级配置)

我们采用的是Druid连接池,这里介绍此中间件的高级优化

6.1 动态参数调整
// 根据负载动态调整连接数
public void adjustDruidPool() {
    DruidDataSource masterDataSource = (DruidDataSource) dataSource.getDataSource();
// 监控线程动态调整
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
scheduler.scheduleAtFixedRate(() -> {
    int active = masterDataSource .getActiveConnections();
    if (active > 80) {
        masterDataSource.setMaxActive(120);  // 扩容
    } else if (active < 20) {
       masterDataSource.setMaxActive(80);   // 缩容
    }
}, 0, 5, TimeUnit.SECONDS);

}

6.3应用层连接池核心连接数、最大连接数设置
CPU 密集型任务(如计算、压缩)

核心线程数CPU 核心数 + 1
(+1 是为了防止线程偶发缺页中断导致的 CPU 空闲)
最大线程数核心线程数 × 1.5
(避免过多线程竞争 CPU,引发上下文切换开销)

示例
• 服务器:8 核 CPU
• 配置:

int corePoolSize = Runtime.getRuntime().availableProcessors() + 1; // 9
int maxPoolSize = (int) (corePoolSize * 1.5); // 13
IO 密集型任务(如数据库查询、HTTP 请求)

核心线程数CPU 核心数 × 2
(利用 CPU 在 IO 等待时的空闲时间)
最大线程数CPU 核心数 × (2 + 平均 IO 等待时间占比)
(例如 IO 等待占 50% → 2 + 0.5/0.5 = 3 → 最大线程数 = 8×3=24)

示例
• 服务器:8 核 CPU,IO 平均等待时间占比 70%
• 配置:

int corePoolSize = 8 * 2; // 16
double ioWaitFactor = 0.7 / (1 - 0.7); // 2.33
int maxPoolSize = 8 * (2 + ioWaitFactor); // ≈ 34
混合型任务(CPU + IO 混合)

核心线程数CPU 核心数 × 1.5
最大线程数核心线程数 × (1 + IO 占比)
(例如 CPU 占 60%,IO 占 40% → 1 + 0.4 = 1.4 倍)


动态调优与监控指标

性能监控关键指标

指标健康阈值异常处理建议
CPU 使用率< 70%若持续 >80%,需减少线程数或优化代码
线程池活跃线程数接近核心线程数若长期 >核心数,考虑增大队列或最大线程数
任务队列堆积量< 队列容量的 50%若队列常满,需扩容队列或增大最大线程数
平均任务处理时间TP99 < 1秒优化慢任务(如 SQL、缓存)

动态调整策略
使用 Resilience4jSpring Cloud Gateway 实现自适应线程池:

// 根据 CPU 负载动态调整核心线程数
ScheduledExecutorService monitor = Executors.newSingleThreadScheduledExecutor();
monitor.scheduleAtFixedRate(() -> {
    double cpuLoad = ManagementFactory.getOperatingSystemMXBean().getSystemLoadAverage();
    int newCoreSize = (cpuLoad > 80) ? 
        threadPool.getCorePoolSize() - 1 : 
        threadPool.getCorePoolSize() + 1;
    threadPool.setCorePoolSize(Math.min(maxPoolSize, Math.max(1, newCoreSize)));
}, 5, 5, TimeUnit.SECONDS);

行业最佳实践与配置示例

电商秒杀系统(IO 密集型)
硬件:16 核 CPU,128GB 内存
配置:

# Spring Boot 配置
task:
  execution:
    pool:
      core-size: 32      # 16核 × 2
      max-size: 64       # 16核 × 4
      queue-capacity: 1000

拒绝策略CallerRunsPolicy(避免雪崩)

实时数据分析(CPU 密集型)

硬件:32 核 CPU,256GB 内存
配置:

ThreadPoolExecutor executor = new ThreadPoolExecutor(
    33,                  // 32核 + 1
    49,                  // 33 × 1.5 ≈ 49
    30, TimeUnit.SECONDS,
    new LinkedBlockingQueue<>(5000),
    new ThreadPoolExecutor.AbortPolicy()
);
微服务网关(混合型)

硬件:4 核 CPU,8GB 内存
配置:

// Netty 风格事件循环组(IO 密集型 + 少量计算)
EventLoopGroup bossGroup = new NioEventLoopGroup(4);         // 核心线程 = CPU核数
EventLoopGroup workerGroup = new NioEventLoopGroup(8);        // 最大线程 = 核数 × 2


6.3 数据库监控集成

启用Druid监控界面

@Bean
public ServletRegistrationBean<StatViewServlet> druidServlet() {
    ServletRegistrationBean<StatViewServlet> reg = new ServletRegistrationBean<>();
    reg.setServlet(new StatViewServlet());
    reg.addUrlMappings("/druid/*");
    reg.addInitParameter("loginUsername", "admin");
    reg.addInitParameter("loginPassword", "admin");
    return reg;
}

7. 慢SQL治理方案

开启慢sql会对性能造成一定影响

7.1 监控采集

Druid内置监控

spring:
  datasource:
    druid:
      filter:
        stat:
          enabled: true
          slow-sql-millis: 1000  # 定义慢SQL阈值
7.2 慢sql优化流程
慢SQL日志采集
分析TOP 10语句
索引缺失?
新增/优化索引
SQL重写优化
执行工单
验证执行计划

8 升级效果

总结:
1、mysql实现主从架构,采用ShardingSphere路由实现读写分离
以上系统已经实现了应用层、数据库读层性能横向扩张。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

power-辰南

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值