第4章 数据层高可用:从单点到集群的进化之路

数据层的高可用,说白了就是保证你的数据不丢、服务不断。我做了这么多年交易系统,见过太多因为数据层挂掉导致整个平台瘫痪的案例。今天咱们就聊聊这块的核心技术。

4.1 数据库主从复制:高可用的基石

MySQL的主从复制,是数据层高可用的第一步。它的原理其实不复杂:主库写binlog,从库拉取binlog并回放。

我个人习惯用异步复制,性能好。但要注意,异步复制有数据丢失的风险。我在项目中遇到过主库宕机,从库差了0.5秒的数据,结果对账怎么都对不上。

核心配置示例:

# 主库配置
server-id = 1
log-bin = mysql-bin
binlog-format = ROW
sync-binlog = 1

# 从库配置
server-id = 2
relay-log = mysql-relay-bin
read-only = 1
log-slave-updates = 1

我的建议:交易系统建议用半同步复制。虽然性能会降一点,但数据一致性更有保障。我曾经因为省这点性能,吃了大亏。

4.2 读写分离:让数据库喘口气

读写分离,就是把查询请求打到从库,写请求走主库。你想想看,交易系统里读请求往往是写请求的10倍以上,不分离开怎么扛得住?

实现方式有两种:

  • 应用层实现:代码里手动指定数据源。灵活,但侵入性强。
  • 中间件实现:比如ShardingSphere、MyCat。透明,但多了一层网络开销。

嗯,这里要注意主从延迟的问题。我记得有一次,用户刚下单就查订单,结果从库还没同步到,显示订单不存在。用户直接炸了。

避坑指南:我曾经在项目中用强制路由解决这个问题。关键查询走主库,普通查询走从库。虽然麻烦点,但数据一致性有保障。

4.3 分库分表:量变引起质变

当单库数据量超过500万,或者QPS超过5000,就该考虑分库分表了。ShardingSphere是我用得最多的方案。

分片策略我一般这样选:

策略 适用场景 我的经验
取模分片 数据均匀分布 适合订单表,但扩容麻烦
范围分片 按时间查询多 适合日志表,容易产生热点
一致性哈希 需要动态扩容 我推荐这个,扩容影响最小

ShardingSphere配置示例:

# 分片规则
sharding:
  tables:
    t_order:
      actualDataNodes: ds$->{0..1}.t_order_$->{0..1}
      tableStrategy:
        inline:
          shardingColumn: order_id
          algorithmExpression: t_order_$->{order_id % 2}
      databaseStrategy:
        inline:
          shardingColumn: user_id
          algorithmExpression: ds$->{user_id % 2}

4.4 数据库连接池:别让连接成为瓶颈

连接池优化,很多人不重视。其实这里坑特别多。我见过一个项目,连接池设了200个,结果数据库连接数被打满,整个系统都卡死了。

我个人习惯用HikariCP,性能确实好。关键参数这样调:

  • maximumPoolSize:核心数*2 + 1,别贪多
  • minimumIdle:和maximum一样,避免频繁创建
  • connectionTimeout:3000ms,超时就快速失败
  • maxLifetime:1800000ms,30分钟,避免连接泄漏

我的经验:连接池不是越大越好。我曾经把连接池从50调到200,QPS反而下降了。因为上下文切换太频繁了。

4.5 缓存策略:Redis集群与本地缓存

缓存是数据层的加速器。我一般用两级缓存:本地缓存扛热点,Redis集群扛容量。

Redis集群部署要点:

  • 至少3主3从,保证高可用
  • 内存不要超过总内存的70%,留点给持久化
  • 开启AOF持久化,每秒刷盘一次

本地缓存我常用Caffeine:

// 本地缓存配置
Cache localCache = Caffeine.newBuilder()
    .maximumSize(10000)
    .expireAfterWrite(5, TimeUnit.SECONDS)
    .recordStats()
    .build();

// 查询逻辑
Order order = localCache.get(key, k -> {
    // 先查Redis,再查数据库
    Order redisOrder = redisCache.get(k);
    if (redisOrder != null) return redisOrder;
    return orderMapper.selectById(k);
});

避坑指南:我曾经在项目中遇到缓存雪崩,Redis集群挂了,所有请求直接打到数据库。从那以后,我强制要求本地缓存必须兜底,哪怕数据旧一点,也不能让数据库挂掉。

知识体系总览

下面这张图,是我对数据层高可用架构的理解。你看一眼,基本就明白整体脉络了。

数据层高可用架构 应用层 数据库连接池 本地缓存(Caffeine) Redis集群 ShardingSphere分库分表 主库(写) 从库1(读) 从库2(读) 主从复制 主从复制 应用层 连接池 缓存 分库分表 数据库 主从复制

数据层高可用,说白了就是一层一层地加防护。连接池扛连接,缓存扛热点,分库分表扛容量,主从复制扛故障。每一层都做好了,系统才能稳如泰山。


公众号:蓝海资料掘金营,微信deep3321