6.分布式数据库与分库分表

目录

一、分库分表核心概念

核心目标:突破单库性能瓶颈,应对海量数据与高并发 • ​​垂直拆分​​:按业务模块拆分(用户库、订单库、商品库) • ​​水平拆分​​:单表数据分片(用户ID取模、时间范围分片)


二、分片策略与避坑指南

分片键选择:高基数字段(用户ID)、业务关联性、数据均衡性 • ​​分片算法​​:哈希取模(均匀分布)、一致性哈希(扩容友好)、范围分片(冷热分离) • ​​避坑要点​​:禁止无分片键查询、避免后期改分片键、分片数预留扩容空间


三、ShardingSphere 企业级实战

技术选型:Sharding-JDBC(轻量级) vs Sharding-Proxy(多语言支持) • ​​Spring Boot整合​​:分片规则配置、读写分离、分布式主键(Snowflake) • ​​高阶功能​​:数据脱敏、柔性事务(BASE)、多租户隔离


四、分布式事务解决方案

刚性事务:Seata AT模式(全局锁+反向SQL回滚) • ​​柔性事务​​:TCC(Try-Confirm-Cancel)、本地消息表(最终一致) • ​​大厂实践​​:支付宝异步通知补偿、美团分布式事务中间件


五、数据迁移与动态扩容

全量迁移:DataX工具、停机窗口控制 • ​​增量同步​​:Canal监听binlog、双写过渡校验 • ​​在线扩容​​:虚拟节点动态迁移、用户无感知切换


六、生产监控与调优

核心指标:连接池水位、慢SQL率、分片路由耗时 • ​​调优手段​​:避免跨分片查询、异步化聚合统计、热点数据二级分片 • ​​工具链​​:Prometheus监控、SkyWalking链路追踪


七、大厂真实案例

电商订单库:用户ID取模分片 + 冷热数据归档HBase • ​​社交Feed流​​:用户ID+时间联合分片 + 读写分离 • ​​物流轨迹库​​:地理位置GeoHash分片 + Elasticsearch检索


八、高频面试题精选

• 分库分表后如何高效分页? • ShardingSphere如何解析SQL路由? • 分片键数据倾斜的应急方案? • 如何设计全局唯一ID(雪花算法 vs 号段模式)?

一、分库分表核心概念

核心目标

突破单库性能瓶颈: • 连接数限制:单库连接池上限(如MySQL默认151连接),高并发下易阻塞。 • 磁盘IO瓶颈:单机硬盘读写速率如SATA SSD 约500MB/s)。 • 锁竞争严重:高频更新场景(如秒杀库存)导致行锁/表锁冲突。

应对海量数据与高并发: • 数据量爆炸:单表数据超千万级后,B+树层级增加,查询性能指数级下降。 • 业务解耦需求:微服务架构下,不同业务模块需独立扩缩容(如用户服务与订单服务)。


垂直拆分

定义:按业务功能模块拆分数据库,每个库独立部署。 • 典型拆分方案: ◦ 用户库:存储用户信息、登录凭证。 ◦ 订单库:订单记录、支付流水。 ◦ 商品库:商品详情、库存信息。

优势: • 资源隔离:CPU、内存、磁盘资源按业务独立分配,避免互相影响。 • 专业优化:针对业务特性定制索引和存储引擎(如订单库用InnoDB,日志库用TokuDB)。

痛点与解决方案: • 跨库事务:避免跨库事务,改用最终一致性(如本地消息表)。 • JOIN操作难:通过业务层多次查询或数据冗余(如订单表冗余用户昵称)。


水平拆分

定义:将单表数据按分片规则(如用户ID、时间)拆分到多个库/表。 • 典型分片方案: ◦ 用户ID取模分片号 = user_id % 分片总数(均匀分布,用户查询精准路由)。 ◦ 时间范围分片:按月/年拆分(如order_202301, order_202302),天然支持冷热数据分离。

分片键选择原则: • 高基数:分片键值足够分散(如用户ID而非性别)。 • 业务相关性:高频查询条件字段(如订单查询常用user_idcreate_time)。

挑战与应对: • 跨分片查询复杂:避免无分片键查询,改用ES聚合或业务层分批查询。 • 扩容成本高:分片数预分配为2的N次方(如16→32),用一致性哈希减少数据迁移量。


常见问题(QA)

Q1:什么时候该用垂直拆分,什么时候用水平拆分?垂直拆分优先:业务模块清晰、数据增长可控(如初期快速验证阶段)。 • 水平拆分必选:单表数据超千万且持续增长(如电商订单、社交动态)。

Q2:分片键选错如何补救?数据迁移:新建分片键正确的表,通过DataX/Spark迁移数据,逐步切换读写流量。 • 双写过渡:新旧分片键同时写入,直到旧数据淘汰。

Q3:如何保证跨库事务一致性?强一致场景:用Seata AT模式(性能要求不高时)。 • 最终一致场景:消息队列异步补偿(如订单创建后发MQ通知库存服务)。


大厂案例

  1. 支付宝用户库垂直拆分: • 拆分用户基础信息库、账户余额库、交易记录库。 • 结果:数据库负载下降60%,故障隔离能力提升。

  2. 淘宝订单表水平分片: • 按user_id % 1024分成1024张表,单表数据控制在500万以内。 • 结果:订单查询响应时间从2s降至200ms。

二、分片策略与避坑指南

分片键选择

1. 高基数字段优先

定义:分片键的取值可能性足够多(如用户ID、订单号)。 • 案例: • 错误示范:性别字段(基数2)作为分片键 → 数据集中在2个分片,无法扩展。 • 正确方案:用户ID(百万级基数)哈希分片 → 数据均匀分布。

2. 业务强关联性

原则:分片键必须是业务高频查询条件(避免全库扫描)。 • 场景: • 订单查询条件:user_id(用户查订单)和 create_time(运营统计)。 • 联合分片键user_id + create_time(兼顾查询与分布)。

3. 数据均衡性保障

算法优化

// 用户ID哈希后取模(简单均匀分片)  
int shardNo = Math.abs(userId.hashCode()) % shardCount;  
// 增加随机因子防止热点  
int shardNo = (userId.hashCode() + ThreadLocalRandom.current().nextInt(100)) % shardCount;  

监控手段:通过ShardingSphere的 SHOW SHARDING TABLE RULES 检查各分片数据量。


分片算法

1. 哈希取模

原理分片号 = hash(key) % 分片总数优势:数据均匀分布,查询直接定位分片。 • 缺陷: • 扩容困难:分片数变化需全量数据迁移。 • 范围查询弱:如时间范围查询需遍历所有分片。 • 适用场景:用户表、订单表等无范围查询需求的数据。

2. 一致性哈希

原理:构建哈希环,节点虚拟化分散在环上,数据哈希值顺时针找到最近节点。 • 优势:扩容时仅迁移相邻节点数据,影响小。 • 生产配置(Java示例):

// 使用TreeMap实现一致性哈希环  
TreeMap hashRing = new TreeMap<>();  
for (int i = 0; i < virtualNodes; i++) {  
    for (String node : nodes) {  
        hashRing.put(MurmurHash.hash(node + "#" + i), node);  
    }  
}  

适用场景:需要频繁扩容的社交动态、评论系统。

3. 范围分片

原理:按区间划分(如时间、ID范围)。 • 优势: • 冷热分离:历史数据归档低成本存储(如OSS)。 • 分页友好:按时间排序查询天然有序。 • 缺陷:易产生数据倾斜(如某时间段订单暴涨)。 • 案例:物流轨迹表按 YYYYMM 分片,每月自动创建新表。


避坑要点

1. 禁止无分片键查询

风险:全库全表扫描 → 性能雪崩。 • 解决方案: • 代码强制校验:DAO层拦截无分片键查询请求。 • 中间件拦截:ShardingSphere配置 allowRangeQueryWithoutShardingKey=false。 • 大厂实践:抖音订单系统要求所有查询必须携带 user_idorder_id

2. 避免后期修改分片键

风险:数据迁移成本高,需停服或灰度切换。 • 应对策略: • 预分片设计:初期采用联合分片键(如 user_id + 预留字段 - 双写过渡:新旧分片键同步写入,逐步迁移。 • 案例:美团外卖订单表从 order_id 分片改为 rider_id + order_id 分片,耗时3个月。

3. 分片数预留扩容空间

经验公式:预估3年数据量,分片数按2的N次方设计(如16 → 32)。 • 弹性方案: • 虚拟分片:物理分片数少于逻辑分片数,动态调整映射关系。 • 自动迁移:阿里云DRDS支持在线分片数倍增,数据自动均衡。 • 监控指标:单分片数据量超过500万时触发报警。


高频面试题

  1. 分片键选择不合理导致数据倾斜怎么办?答案:临时方案:写入时加随机后缀;长期方案:改用一致性哈希重新分片。

  2. 如何实现跨分片分页查询?答案:业务层排序(如ES聚合结果)或折衷方案(禁止深度分页)。

  3. ShardingSphere分片算法如何自定义?答案:实现 StandardShardingAlgorithm 接口,注入分片逻辑。


生产级代码片段

// ShardingSphere 分片规则配置(按user_id哈希分片)  
shardingRuleConfig.getTableRuleConfigs().add(  
    new TableRuleConfiguration("user", "ds${0..1_${0..15}")  
        .setDatabaseShardingStrategyConfig(  
            new StandardShardingStrategyConfiguration("user_id", "dbHashMod")  
        )  
        .setTableShardingStrategyConfig(  
            new StandardShardingStrategyConfiguration("user_id", "tableHashMod")  
        )  
);  
// 自定义分片算法(哈希取模)  
public final class HashModShardingAlgorithm implements StandardShardingAlgorithm {  
    @Override  
    public String doSharding(Collection availableTargetNames, RangeShardingValue shardingValue) {  
        // 实现分片逻辑  
    }  
}  

三、ShardingSphere 企业级实战

技术选型

Sharding-JDBC(轻量级): • 定位:Java应用的JDBC驱动层扩展,透明化分库分表 • 优势:无代理层性能损耗,与Spring Boot深度整合 • 适用场景:中小团队快速落地分库分表(如电商订单分片) • Sharding-Proxy(支持): • 定位:独立部署的数据库代理,兼容MySQL/PostgreSQL协议 • 优势:支持多语言(PHP/Python可视化(如阿里云DMS) • 适用场景:跨技术栈团队(如Java+Go混合开发) • 选型建议: • 单语言技术栈优先Sharding-JDBC(性能最优) • 需运维管控或混合语言选Sharding-Proxy(牺牲10%~15%性能)


Spring Boot整合

1. 分片规则配置(YAML示例)

spring:  
  shardingsphere:  
    datasource:  
      names: ds0, ds1  
      ds0:  
        url: jdbc:mysql://db0:3306/order  
        username: root  
        password: 123456  
      ds1:  
        url: jdbc:mysql://db1:3306/order  
    rules:  
      sharding:  
        tables:  
          order:  
            actualDataNodes: ds$->{0..1}.order_$->{0..15}  # 2库x16表  
            databaseStrategy:  
              standard:  
                shardingColumn: user_id  
                shardingAlgorithmName: db-hash-mod  
            tableStrategy:  
              standard:  
                shardingColumn: order_id  
                shardingAlgorithmName: table-hash-mod  
        shardingAlgorithms:  
          db-hash-mod:  
            type: HASH_MOD  
            props:  
              sharding-count: 2  
          table-hash-mod:  
            type: HASH_MOD  
            props:  
              sharding-count: 16  

2. 读写分离配置

spring:  
  shardingsphere:  
    rules:  
      replica-query:  
        dataSources:  
          pr_ds:  
            primaryDataSourceName: ds-primary  
            replicaDataSourceNames: ds-replica1, ds-replica2  
            loadBalancerName: round-robin  
        loadBalancers:  
          round-robin:  
            type: ROUND_ROBIN  

3. 分布式主键生成

// flake算法(防止时钟回拨)  
@Bean  
public KeyGenerateAlgorithm keyGenerator() {  
    return new SnowflakeKeyGenerateAlgorithm()  
        .setProps(Collections.singletonMap("max-tolerate-time-difference-milliseconds", "60000"));  
}  

高阶功能

1. 数据

场景:手机号、身份证号等敏感信息加密存储 • 实现

spring:  
  shardingsphere:  
    rules:  
      encrypt:  
        encryptors:  
          mobile_encryptor:  
            type: AES  
            props:  
              aes-key-value: 123456  
        tables:  
          user:  
            columns:  
              phone:  
                cipherColumn: phone_cipher  
                encryptorName: mobile_encryptor  

查询处理:自动加解密,业务代码无感知

2. 柔性事务(BASE)

本地消息表实现

  1. 业务事务提交时,写入本地消息表

  2. 定时任务扫描并发送消息到MQ

  3. 消费者处理成功后更新消息状态 • ShardingSphere集成

spring:  
  shardingsphere:  
    rules:  
      transaction:  
        defaultType: BASE  
        providerType: Local  

3. 多租户隔离

场景:SaaS系统按租户分库(如企业ID分片) • 配置

tables:  
  report:  
    actualDataNodes: ds_${0..9}.report_${0..9}  
    databaseStrategy:  
      standard:  
        shardingColumn: tenant_id  
        shardingAlgorithmName: tenant-mod  
shardingAlgorithms:  
  tenant-mod:  
    type: MOD  
    props:  
      sharding-count: 10  

数据隔离:通过HintManager强制路由租户上下文

HintManager.getInstance().setDatabaseShardingValue(tenantId);  

生产经验

  1. 分片算法预热: • 启动时预加载分片路由规则,避免首次查询延迟

  2. 监控告警: • 通过ShardingSphere-UI监控慢查询与分片负载

  3. 灰度发布: • 新旧分片规则并存,通过AB测试逐步切流


四、分布式事务解决方案

刚性事务:Seata AT模式

核心原理

全局锁机制: • 事务协调器(TC)在事务开始时注册全局锁,锁定涉及的行记录。 • 其他事务修改同一数据时,需等待锁释放(默认锁超时时间30秒)。 • 反向SQL回滚: • 提交阶段:各分支事务提交本地事务,释放全局锁。 • 回滚阶段:生成反向SQL(如INSERT→DELETE)撤销已提交的操作。

Spring Boot整合配置

# application.yml  
seata:  
  enabled: true  
  application-id: order-service  
  tx-service-group: my-tx-group  
  registry:  
    type: nacos  
    nacos:  
      server-addr: 127.0.0.1:8848  
  config:  
    type: nacos  
    nacos:  
      server-addr: 127.0.0.1:8848  
@GlobalTransactional  // 开启全局事务  
public void placeOrder() {  
    orderService.create();  
    stockService.deduct();  
}  

适用场景与限制

适用场景:短事务(执行时间<1秒)、简单业务逻辑(如扣减库存+生成订单)。 • 限制: • 不支持嵌套事务。 • 高并发场景下全局锁可能成为性能瓶颈。


柔性事务:TCC与本地消息表

1. TCC(Try-Confirm-Cancel)

三阶段流程

阶段 动作 案例(转账业务)
Try 资源预留(冻结账户金额) account.freeze(100元)
Confirm 确认操作(实际扣款) account.debit(100元)
Cancel 取消预留(解冻金额) account.unfreeze(100元)

Java实现示例

@Transactional  
public boolean tryTransfer(String from, String to, BigDecimal amount) {  
    // 冻结转出账户资金  
    accountService.freeze(from, amount);  
    // 预增转入账户可用额度  
    accountService.prepareCredit(to, amount);  
    return true;  
}  

@Transactional  
public boolean confirmTransfer(String txId) {  
    // 实际扣减转出账户  
    accountService.debit(txId);  
    // 实际增加转入账户  
    accountService.credit(txId);  
    return true;  
}  

@Transactional  
public boolean cancelTransfer(String txId) {  
    // 解冻转出账户资金  
    accountService.unfreeze(txId);  
    // 撤销转入账户预增  
    accountService.cancelCredit(txId);  
    return true;  
}  

2. 本地消息表(最终一致性)

实现流程

  1. 业务事务提交时,向本地消息表插入事件记录(与业务操作同一事务)。

  2. 定时任务扫描未处理事件,发送到消息队列(如RocketMQ)。

  3. 消费者处理成功后更新事件状态。

Spring Boot集成

@Transactional  
public void createOrder(Order order) {  
    orderRepository.save(order);  
    // 写入本地消息表(同一事务)  
    eventRepository.save(new Event("order_created", order.getId()));  
}  

@Scheduled(fixedDelay = 5000)  
public void processEvents() {  
    List events = eventRepository.findByStatus(EventStatus.PENDING);  
    events.forEach(event -> {  
        try {  
            rocketMQTemplate.send("order_topic", event.getPayload());  
            event.setStatus(EventStatus.SUCCESS);  
        } catch (Exception e) {  
            event.setStatus(EventStatus.FAILED          }  
        eventRepository.save(event);  
    });  
}  

大厂实践

1. 支付宝异步通知补偿

场景:支付成功后通知商户系统,确保最终到达。 • 实现: • 支付成功时写入本地消息表。 • 异步重试通知(1s、10s、1m、10m、1h间隔),最多重试24小时。 • 商户系统幂等处理(通过支付流水号去重)。

2. 美团分布式事务中间件

架构设计: • 事务协调器:基于Raft协议实现高可用。 • TCC适配层:自动生成Try/Confirm/Cancel接口模板。 • 监控看板:实时追踪事务状态,支持手动冲正。 • 核心指标: • 事务成功率:99.995%(依赖自动补偿机制)。 • 平均处理耗时:Confirm阶段<50ms,Cancel阶段<100ms。


选型决策树

场景特征 推荐方案 理由
短事务、强一致性需求 Seata AT模式 简单易用,无需业务改造
长事务、高并发(如金融转账) TCC 细粒度控制,避免资源长期锁定
允许延迟(如通知类业务) 本地消息表 吞吐量高,对业务侵入性低
跨多语言服务(如Java+Go) 消息队列+本地事务 无中心化依赖,兼容异构系统

五、数据迁移与动态扩容

全量迁移

1. DataX工具实战

核心能力: • 支持MySQL、Oracle、HDFS等20+数据源异构迁移 • 分布式架构(Job+Task)提升吞吐量(单机可达500MB/s) • 断点续传、脏数据跳过机制保障稳定性 • 迁移流程

  1. 数据探查:统计表大小、主键分布(避免大事务超时)

  2. 作业配置

{  
  "job": {  
    "content": [{  
      "reader": {  
        "name": "mysqlreader",  
        "parameter": {  
          "username": "root",  
          "password": "123456",  
          "column": ["id", "user_id", "amount"],  
          "splitPk": "id",  // 按主键分片读取  
          "connection": [{  
            "table": ["orders"],  
            "jdbcUrl": ["jdbc:mysql://old-db:3306/order"]  
          }]  
        }  
      },  
      "writer": {  
        "name": "mysqlwriter",  
        "parameter": {  
          "username": "root",  
          "password": "123456",  
          "column": ["id", "user_id", "amount"],  
          "connection": [{  
            "jdbcUrl": "jdbc:mysql://new-db:3306/order",  
            "table": ["orders"]  
          }]  
        }  
      }  
    }]  
  }  
}  
  1. 执行与监控: ◦ 日志实时查看: -f datax.log◦ 进度监控:curl http://datax-server:port/job/metrics`

2. 停机窗口控制

步骤

  1. 停写:关闭业务写入入口(如Nginx流量拦截)

  2. 增量追赶:通过Binlog同步最后N分钟数据

  3. 切换验证:对比新旧库数据checksum(mysqldbcompare工具)

  4. 恢复写入:开启新库旧库下线 • 时间估算

• 数据量100GB,网络带宽1Gbps → 全量迁移约15分钟  
• 增量追赶(Binlog延迟) → 5~10分钟  
• 总停机时间 ≈ 30分钟  

增量同步

1. Canal监听Binlog

架构原理: • Canal Server伪装为MySQL从库,接收主库Binlog • MQ(Kafka/RocketMQ)解耦生产与消费速率 • Java客户端消费消息,写入目标库(如ES、分片后的MySQL) • Spring Boot整合

canal:  
  server: 192.168.1.100:11111  
  destination: example  
  filter: .*\\..*  
@CanalEventListener  
public class OrderEventListener {  
    @ListenPoint(table = "orders")  
    public void onEvent(EventType eventType, RowData rowData) {  
        if (eventType == EventType.INSERT) {  
            Order order = convertRowToOrder(rowData);  
            orderRepository.save(order);  // 写入新库  
        }  
    }  
}  

2. 双写过渡校验

双写策略: • 同步双写:事务内同时写入新旧库(强一致,性能低) • 异步双写:写入旧库后发MQ异步写入新库(最终一致,高吞吐) • 数据校验

-- 新旧库数据比对(定时任务)  
SELECT COUNT(*) FROM old_db.orders  
UNION ALL  
SELECT COUNT(*) FROM new_db.orders;  

-- 差异数据修复  
INSERT INTO new_db.orders  
SELECT * FROM old_db.orders WHERE id NOT IN (SELECT id FROM new_db.orders);  

在线扩容

1. 虚拟节点动态迁移

一致性哈希优化: • 物理节点映射多个虚拟节点(如每个物理节点1000虚拟节点) • 扩容时新增虚拟节点,数据迁移仅影响相邻节点 • 迁移流程

  1. 新节点加入:向集群注册,分配虚拟节点范围

  2. 数据迁移: ◦ 扫描旧节点数据,按新路由规则迁移至新节点 ◦ 迁移期间旧节点仍可读写(双写模式)

  3. 流量切换:更新路由配置,逐步切流至新节点

2. 用户无感知切换

灰度发布: • 按用户ID分流:10%流量切至新节点,观察错误率 • 按地域切流:先切非核心地区(如海外用户) • 回滚方案: • 监控新节点QPS/延迟,超阈值自动回退旧配置 • 数据双写期间保留旧节点数据,支持快速回滚


生产级Checklist

  1. 数据一致性验证: • 全量校验:mysqldump + md5sum • 增量校验:对比新旧库Binlog位点

  2. 性能压测: • 模拟双写压力(如JMeter模拟200%流量) • 监控连接池等待、锁竞争指标

  3. 容灾演练: • 随机Kill节点,验证数据自愈能力 • 网络分区模拟(如iptables阻断节点通信)


六、生产监控与调优

核心指标

1. 连接池水位监控

关键指标: • 活跃连接数(active):实时处理请求的连接数 • 空闲连接数(idle):等待复用的空闲连接 • 最大等待时间(maxWait):获取连接的超时阈值(超过则抛异常) • 报警规则(Prometheus示例):

# prometheus-rules.yml  
- alert: HighConnectionPoolUsage  
  expr: sum(shardingsphere_datasource_active_connections) / sum(shardingsphere_datasource_max_connections) > 0.8  
  for: 5m  
  labels:  
    severity: critical  
  annotations:  
    summary: "数据库连接池使用率超过80%"  

优化手段: • 动态扩容:HikariCP的maximumPoolSize根据QPS自动调整(需配合微服务动态配置中心) • 连接泄漏检测:Druid的removeAbandoned=true + 告警通知

2. 慢SQL率分析

采集方式: • MySQL慢查询日志sql SET GLOBAL slow_query_log = 'ON'; SET GLOBAL long_query_time = 2; -- 超过2秒的SQL记录ShardingSphere全链路追踪yaml spring: shardingsphere: props: sql-show: true # 打印逻辑SQL与真实SQL治理流程

  1. TOP N慢SQL定位:通过mysqldumpslow工具分析日志

  2. 执行计划分析EXPLAIN查看索引使用情况

  3. 优化方案: ◦ 缺失索引 → 添加联合索引 ◦ 复杂JOIN → 冗余字段或拆分为多次查询

3. 分片路由耗时

监控项: • 路由计算耗时:ShardingSphere的sql_route_time指标 • 跨分片查询比例shardingsphere_routed_sql_total{type="select", is_broadcast="false"}调优方案: • 强制分片键:拦截无分片键查询(ShardingSphere配置allowRangeQueryWithoutShardingKey=false) • 本地缓存路由表:预热高频查询分片位置(如用户ID与分片映射关系)


调优手段

1. 避免跨分片查询

分片键强制校验

// AOP拦截无分片键查询  
@Around("execution(* com.example.repository.*.*(..))")  
public Object checkShardingKey(ProceedingJoinPoint joinPoint) {  
    MethodSignature signature = (MethodSignature) joinPoint.getSignature();  
    ShardingKeyRequired annotation = signature.getMethod().getAnnotation(ShardingKeyRequired.class);  
    if (annotation != null) {  
        Object[] args = joinPoint.getArgs();  
        if (!hasShardingKey(args)) {  
            throw new IllegalStateException("查询必须包含分片键");  
        }  
    }  
    return joinPoint.proceed();  
}  

冗余字段设计:订单表冗余user_id,避免关联查询用户表

2. 异步化聚合统计

方案对比

方案 优点 缺点
MQ+Elasticsearch 实时性高(秒级延迟) 数据需同步到ES
Flink实时计算 支持复杂计算(如UV统计) 架构复杂度高
本地缓存+定时批处理 资源消耗低 实时性差(分钟级延迟)
代码示例
// 订单金额统计异步化  
@Async("statsThreadPool")  
public void asyncOrderStats(LocalDate date) {  
    List orders = orderRepository.findByDate(date);  // 直接查分片  
    BigDecimal total = orders.stream().map(Order::getAmount).reduce(BigDecimal.ZERO, BigDecimal::add);  
    statsCache.put(date, total);  
}  

3. 热点数据二级分片

动态分片键: • 场景:某直播间用户评论突增导致单分片过热 • 方案:在原分片键(直播间ID)基础上追加随机后缀(如room_id:123#slot=5) • 本地缓存

// Caffeine缓存热点评论  
Cache> cache = Caffeine.newBuilder()  
    .maximumSize(10_000)  
    .expireAfterWrite(10, TimeUnit.SECONDS)  // 短时间缓存降低DB压力  
    .build();  

public List getHotComments(String roomId) {  
    return cache.get(roomId, key -> commentRepository.findHotComments(key));  
}  

工具链

1. Prometheus + Grafana监控

数据采集: • ShardingSphere Exporter:暴露shardingsphere_datasource_active_connections等指标 • 自定义指标:通过Micrometer注册业务指标(如跨分片查询次数) • 看板配置

// Grafana面板示例(分片负载均衡)  
{  
  "panels": [{  
    "type": "graph",  
    "title": "分片查询分布",  
    "targets": [{  
      "expr": "sum(shardingsphere_routed_sql_total) by (datasource)"  
    }]  
  }]  
}  

2. SkyWalking链路追踪

集成配置

# skywalking-agent.config  
agent.service_name=order-service  
collector.backend_service=127.0.0.1:11800  
plugin.mysql.trace_sql_parameters=true  

链路分析: • 跨分片查询追踪:自动标记跨库查询的Span • 慢事务根因定位:分析事务链路中的慢SQL或远程调用 • 生产案例: • 美团外卖订单链路:通过SkyWalking定位到跨分片JOIN导致慢查询,优化后RT降低60%


高频面试题

  1. 如何快速定位慢SQL的瓶颈? • 答:SkyWalking链路追踪 + EXPLAIN执行计划分析,优先检查索引缺失与数据倾斜。

  2. 分片路由耗时过高可能是什么原因? • 答:路由规则复杂(如多分片键联合计算)、未预热路由缓存、跨分片查询过多。

  3. 如何设计一个高可用的监控系统? • 答:Prometheus联邦架构 + Thanos长期存储,配合Grafana多数据源聚合展示。


七、大厂真实案例

电商订单库:用户ID取模分片 + 冷热数据归档HBase

背景与挑战

业务场景:日订单量超千万,单表数据一年破百亿,查询性能从秒级跌至分钟级。 • 核心痛点: • 用户高频查询“我的订单”接口(强依赖user_id)。 • 历史订单占用90%存储但访问频率低(冷热数据混杂)。

分库分表方案

  1. 水平分片: • 分片键user_id % 1024(1024个分片,单分片控制在500万行内)。 • 路由规则

    // 根据用户ID计算分片  
    int shardNo = Math.abs(userId.hashCode()) % 1024;  
    String tableName = "orders_" + shardNo;  
  2. 冷热分离: • 热数据:近3个月订单存MySQL,索引优化(user_id + create_time联合索引)。 • 冷数据:3个月前数据归档HBase,ROW_KEY设计为user_id|order_id(范围扫描优化)。

技术细节

数据同步:Canal监听MySQL Binlog,触发冷数据迁移至HBase。 • 查询优化: • 热数据查询直接走MySQL分片。 • 冷数据查询走HBase的PrefixFilteruser_id前缀匹配)。

优化效果

查询性能:用户订单列表响应时间从12s降至200ms。 • 存储成本:HBase压缩比提升60%,存储费用降低75%。


社交Feed流:用户ID+时间联合分片 + 读写分离

业务场景

• 用户发布动态实时推送粉丝,读QPS峰值百万级。 • 单表存储用户动态,数据量日均十亿级。

分片方案

  1. 联合分片键: • 主分片键user_id % 256(256个分片)。 • 二级分片键create_time按月分表(如feed_202301)。

  2. 读写分离: • 写节点:主库处理发布请求,分片规则为user_id。 • 读节点:从库按user_id + create_time分片,支撑粉丝Feed流拉取。

技术实现

动态发布

INSERT INTO feed_{user_id%256} (content, user_id, create_time)  
VALUES ('Hello World', 123456, NOW());  

Feed流读取

-- 查询用户关注列表的动态(按时间倒序)  
SELECT * FROM feed_*  
WHERE user_id IN (SELECT followed_user_id FROM follow WHERE fan_user_id = 123)  
ORDER BY create_time DESC LIMIT 100;  

优化手段: ◦ 粉丝关系缓存Redis(Sorted Set存储关注列表)。 ◦ Feed流结果缓存CDN,降低DB压力。

性能提升

发布吞吐量:从5K TPS提升至50K TPS。 • 读延迟:Feed流加载从3s降至800ms。


物流轨迹库:地理位置GeoHash分片 + Elasticsearch检索

业务需求

• 存储全国物流轨迹点,每日新增轨迹数据十亿级。 • 需支持两类查询: • 精确查询:根据运单号查全链路轨迹。 • 区域查询:查询某地所有待派送订单。

分片方案

  1. GeoHash分片: • 原理:将经纬度编码为字符串(如wx4g0),按前缀分片。 • 分片键geohash.substring(0, 3)(前3位作为分片键,256个分片)。

  2. Elasticsearch辅助索引: • 空间索引(geo_point):支持半径1km内的订单搜索。 • 联合查询:运单号走MySQL分片,地理位置走Elasticsearch。

技术实现

写入流程

// 计算GeoHash(纬度31.23, 经度121.47)  
String geoHash = GeoHash.encode(31.23, 121.47, 5);  
// 插入MySQL分片  
String table = "track_" + geoHash.substring(0, 3);  
jdbcTemplate.update("INSERT INTO " + table + " VALUES (?, ?, ?)", orderId, geoHash, time);  
// 同步到Elasticsearch  
esClient.index(new IndexRequest("track").id(orderId)  
    .source(JsonUtils.toMap(new TrackPoint(orderId, geoHash, time))));  

区域查询

GET /track/_search  
{  
  "query": {  
    "geo_distance": {  
      "distance": "1km",  
      "location": "31.23,121.47"  
    }  
  }  
}  

优化效果

精确查询:运单号查询走MySQL分片,RT<50ms。 • 区域查询:Elasticsearch百公里范围检索,RT<200ms。


八、高频面试题精选

1. 分库分表后如何高效分页?

问题分析

传统分页失效LIMIT 10000, 10需扫描并丢弃前10000行,跨分片时性能灾难。 • 解决方案: • 业务折衷: ◦ 禁止跳页(仅允许“下一页”按钮),用连续游标(如Search After)。 sql -- 第一页 SELECT * FROM orders WHERE user_id=123 ORDER BY id LIMIT 10; -- 第二页(使用上一页最后一条ID) SELECT * FROM orders WHERE user_id=123 AND id > last_id ORDER BY id LIMIT 10;Elasticsearch辅助:复杂条件分页走ES,结果反查MySQL获取明细。 • 内存分页:若数据可缓存(如Redis),全量加载后内存中分页。


2. ShardingSphere如何解析SQL路由?

核心流程

  1. SQL解析: • 解析引擎生成抽象语法树(AST),提取分片条件(如user_id=123)。

  2. 路由计算: • 精确路由:分片键等值查询(user_id=123)直接定位分片。 • 广播路由:无分片键的更新(如UPDATE config SET value=1)全分片执行。

  3. 结果归并: • 跨分片查询结果在内存中排序、聚合(如ORDER BY time DESC)。

配置示例

rules:  
  - !SHARDING  
    tables:  
      orders:  
        actualDataNodes: ds_${0..1}.orders_${0..15}  
        databaseStrategy:  
          standard:  
            shardingColumn: user_id  
            shardingAlgorithmName: hash_mod  
    shardingAlgorithms:  
      hash_mod:  
        type: HASH_MOD  
        props:  
          sharding-count: 2  

3. 分片键数据倾斜的应急方案?

临时措施

虚拟节点再平衡

// 原分片:user_id % 8  
// 扩容后:(user_id.hashCode() + virtual_node) % 16  
int newShard = (userId.hashCode() + slot) % 16;  

热点数据二级分片: • 例如对热点用户(如网红)的订单按user_id + order_id联合分片。

长期方案

分片键改造:联合业务高基字段(如user_id + city_code)。 • 动态分片:根据数据分布自动调整分片映射(如一致性哈希)。


4. 如何设计全局唯一ID(雪花算法 vs 号段模式)?

方案对比

维度 雪花算法(Snowflake) 号段模式(Segment)
唯一性 全局唯一(数据中心ID+机器ID+时间戳+序列号) 依赖数据库唯一性保障(如自增主键)
性能 本地生成,无网络开销(单机每秒百万级) 需预取号段,DB宕机影响ID生成
缺点 时钟回拨导致ID重复(需处理NTP同步) 号段耗尽时需访问DB,存在尖峰压力
适用场景 高并发分布式系统(如电商订单、支付流水) 中小规模系统(如内部管理平台)

Snowflake避坑实践

public class SnowflakeIdWorker {  
    private long twepoch = 1288834974657L;  // 起始时间戳  
    private long sequence = 0L;  
    // 解决时钟回拨  
    public synchronized long nextId() {  
        long timestamp = timeGen();  
        if (timestamp < lastTimestamp) {  
            long offset = lastTimestamp - timestamp;  
            if (offset <= 5) {   // 允许回拨5ms内等待  
                Thread.sleep(offset << 1);  
                timestamp = timeGen();  
            } else {  
                throw new RuntimeException("时钟回拨超过5ms");  
            }  
        }  
        // ...生成ID逻辑  
    }  
}  

总结:本章涵盖的案例与面试题均来自阿里、美团、字节等一线大厂真题,掌握这些内容可从容应对95%的分库分表相关技术挑战。

你可能感兴趣的:(数据库架构,分布式)