• 核心目标:突破单库性能瓶颈,应对海量数据与高并发 • 垂直拆分:按业务模块拆分(用户库、订单库、商品库) • 水平拆分:单表数据分片(用户ID取模、时间范围分片)
• 分片键选择:高基数字段(用户ID)、业务关联性、数据均衡性 • 分片算法:哈希取模(均匀分布)、一致性哈希(扩容友好)、范围分片(冷热分离) • 避坑要点:禁止无分片键查询、避免后期改分片键、分片数预留扩容空间
• 技术选型: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_id
和create_time
)。
• 挑战与应对: • 跨分片查询复杂:避免无分片键查询,改用ES聚合或业务层分批查询。 • 扩容成本高:分片数预分配为2的N次方(如16→32),用一致性哈希减少数据迁移量。
Q1:什么时候该用垂直拆分,什么时候用水平拆分? • 垂直拆分优先:业务模块清晰、数据增长可控(如初期快速验证阶段)。 • 水平拆分必选:单表数据超千万且持续增长(如电商订单、社交动态)。
Q2:分片键选错如何补救? • 数据迁移:新建分片键正确的表,通过DataX/Spark迁移数据,逐步切换读写流量。 • 双写过渡:新旧分片键同时写入,直到旧数据淘汰。
Q3:如何保证跨库事务一致性? • 强一致场景:用Seata AT模式(性能要求不高时)。 • 最终一致场景:消息队列异步补偿(如订单创建后发MQ通知库存服务)。
支付宝用户库垂直拆分: • 拆分用户基础信息库、账户余额库、交易记录库。 • 结果:数据库负载下降60%,故障隔离能力提升。
淘宝订单表水平分片: • 按user_id % 1024
分成1024张表,单表数据控制在500万以内。 • 结果:订单查询响应时间从2s降至200ms。
• 定义:分片键的取值可能性足够多(如用户ID、订单号)。 • 案例: • 错误示范:性别字段(基数2)作为分片键 → 数据集中在2个分片,无法扩展。 • 正确方案:用户ID(百万级基数)哈希分片 → 数据均匀分布。
• 原则:分片键必须是业务高频查询条件(避免全库扫描)。 • 场景: • 订单查询条件:user_id
(用户查订单)和 create_time
(运营统计)。 • 联合分片键:user_id + create_time
(兼顾查询与分布)。
• 算法优化:
// 用户ID哈希后取模(简单均匀分片) int shardNo = Math.abs(userId.hashCode()) % shardCount; // 增加随机因子防止热点 int shardNo = (userId.hashCode() + ThreadLocalRandom.current().nextInt(100)) % shardCount;
• 监控手段:通过ShardingSphere的 SHOW SHARDING TABLE RULES
检查各分片数据量。
• 原理:分片号 = hash(key) % 分片总数
• 优势:数据均匀分布,查询直接定位分片。 • 缺陷: • 扩容困难:分片数变化需全量数据迁移。 • 范围查询弱:如时间范围查询需遍历所有分片。 • 适用场景:用户表、订单表等无范围查询需求的数据。
• 原理:构建哈希环,节点虚拟化分散在环上,数据哈希值顺时针找到最近节点。 • 优势:扩容时仅迁移相邻节点数据,影响小。 • 生产配置(Java示例):
// 使用TreeMap实现一致性哈希环 TreeMaphashRing = new TreeMap<>(); for (int i = 0; i < virtualNodes; i++) { for (String node : nodes) { hashRing.put(MurmurHash.hash(node + "#" + i), node); } }
• 适用场景:需要频繁扩容的社交动态、评论系统。
• 原理:按区间划分(如时间、ID范围)。 • 优势: • 冷热分离:历史数据归档低成本存储(如OSS)。 • 分页友好:按时间排序查询天然有序。 • 缺陷:易产生数据倾斜(如某时间段订单暴涨)。 • 案例:物流轨迹表按 YYYYMM
分片,每月自动创建新表。
• 风险:全库全表扫描 → 性能雪崩。 • 解决方案: • 代码强制校验:DAO层拦截无分片键查询请求。 • 中间件拦截:ShardingSphere配置 allowRangeQueryWithoutShardingKey=false
。 • 大厂实践:抖音订单系统要求所有查询必须携带 user_id
或 order_id
。
• 风险:数据迁移成本高,需停服或灰度切换。 • 应对策略: • 预分片设计:初期采用联合分片键(如 user_id + 预留字段
- 双写过渡:新旧分片键同步写入,逐步迁移。 • 案例:美团外卖订单表从 order_id
分片改为 rider_id + order_id
分片,耗时3个月。
• 经验公式:预估3年数据量,分片数按2的N次方设计(如16 → 32)。 • 弹性方案: • 虚拟分片:物理分片数少于逻辑分片数,动态调整映射关系。 • 自动迁移:阿里云DRDS支持在线分片数倍增,数据自动均衡。 • 监控指标:单分片数据量超过500万时触发报警。
分片键选择不合理导致数据倾斜怎么办? • 答案:临时方案:写入时加随机后缀;长期方案:改用一致性哈希重新分片。
如何实现跨分片分页查询? • 答案:业务层排序(如ES聚合结果)或折衷方案(禁止深度分页)。
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) { // 实现分片逻辑 } }
• Sharding-JDBC(轻量级): • 定位:Java应用的JDBC驱动层扩展,透明化分库分表 • 优势:无代理层性能损耗,与Spring Boot深度整合 • 适用场景:中小团队快速落地分库分表(如电商订单分片) • Sharding-Proxy(支持): • 定位:独立部署的数据库代理,兼容MySQL/PostgreSQL协议 • 优势:支持多语言(PHP/Python可视化(如阿里云DMS) • 适用场景:跨技术栈团队(如Java+Go混合开发) • 选型建议: • 单语言技术栈优先Sharding-JDBC(性能最优) • 需运维管控或混合语言选Sharding-Proxy(牺牲10%~15%性能)
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
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
// flake算法(防止时钟回拨) @Bean public KeyGenerateAlgorithm keyGenerator() { return new SnowflakeKeyGenerateAlgorithm() .setProps(Collections.singletonMap("max-tolerate-time-difference-milliseconds", "60000")); }
• 场景:手机号、身份证号等敏感信息加密存储 • 实现:
spring: shardingsphere: rules: encrypt: encryptors: mobile_encryptor: type: AES props: aes-key-value: 123456 tables: user: columns: phone: cipherColumn: phone_cipher encryptorName: mobile_encryptor
• 查询处理:自动加解密,业务代码无感知
• 本地消息表实现:
业务事务提交时,写入本地消息表
定时任务扫描并发送消息到MQ
消费者处理成功后更新消息状态 • ShardingSphere集成:
spring: shardingsphere: rules: transaction: defaultType: BASE providerType: Local
• 场景: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);
分片算法预热: • 启动时预加载分片路由规则,避免首次查询延迟
监控告警: • 通过ShardingSphere-UI
监控慢查询与分片负载
灰度发布: • 新旧分片规则并存,通过AB测试逐步切流
• 全局锁机制: • 事务协调器(TC)在事务开始时注册全局锁,锁定涉及的行记录。 • 其他事务修改同一数据时,需等待锁释放(默认锁超时时间30秒)。 • 反向SQL回滚: • 提交阶段:各分支事务提交本地事务,释放全局锁。 • 回滚阶段:生成反向SQL(如INSERT→DELETE)撤销已提交的操作。
# 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秒)、简单业务逻辑(如扣减库存+生成订单)。 • 限制: • 不支持嵌套事务。 • 高并发场景下全局锁可能成为性能瓶颈。
• 三阶段流程:
阶段 | 动作 | 案例(转账业务) |
---|---|---|
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; }
• 实现流程:
业务事务提交时,向本地消息表插入事件记录(与业务操作同一事务)。
定时任务扫描未处理事件,发送到消息队列(如RocketMQ)。
消费者处理成功后更新事件状态。
• 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() { Listevents = 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); }); }
• 场景:支付成功后通知商户系统,确保最终到达。 • 实现: • 支付成功时写入本地消息表。 • 异步重试通知(1s、10s、1m、10m、1h间隔),最多重试24小时。 • 商户系统幂等处理(通过支付流水号去重)。
• 架构设计: • 事务协调器:基于Raft协议实现高可用。 • TCC适配层:自动生成Try/Confirm/Cancel接口模板。 • 监控看板:实时追踪事务状态,支持手动冲正。 • 核心指标: • 事务成功率:99.995%(依赖自动补偿机制)。 • 平均处理耗时:Confirm阶段<50ms,Cancel阶段<100ms。
场景特征 | 推荐方案 | 理由 |
---|---|---|
短事务、强一致性需求 | Seata AT模式 | 简单易用,无需业务改造 |
长事务、高并发(如金融转账) | TCC | 细粒度控制,避免资源长期锁定 |
允许延迟(如通知类业务) | 本地消息表 | 吞吐量高,对业务侵入性低 |
跨多语言服务(如Java+Go) | 消息队列+本地事务 | 无中心化依赖,兼容异构系统 |
• 核心能力: • 支持MySQL、Oracle、HDFS等20+数据源异构迁移 • 分布式架构(Job+Task)提升吞吐量(单机可达500MB/s) • 断点续传、脏数据跳过机制保障稳定性 • 迁移流程:
数据探查:统计表大小、主键分布(避免大事务超时)
作业配置:
{ "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"] }] } } }] } }
执行与监控: ◦ 日志实时查看: -f datax.log◦ 进度监控:
curl http://datax-server:port/job/metrics`
• 步骤:
停写:关闭业务写入入口(如Nginx流量拦截)
增量追赶:通过Binlog同步最后N分钟数据
切换验证:对比新旧库数据checksum(mysqldbcompare
工具)
恢复写入:开启新库旧库下线 • 时间估算:
• 数据量100GB,网络带宽1Gbps → 全量迁移约15分钟 • 增量追赶(Binlog延迟) → 5~10分钟 • 总停机时间 ≈ 30分钟
• 架构原理: • 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); // 写入新库 } } }
• 双写策略: • 同步双写:事务内同时写入新旧库(强一致,性能低) • 异步双写:写入旧库后发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);
• 一致性哈希优化: • 物理节点映射多个虚拟节点(如每个物理节点1000虚拟节点) • 扩容时新增虚拟节点,数据迁移仅影响相邻节点 • 迁移流程:
新节点加入:向集群注册,分配虚拟节点范围
数据迁移: ◦ 扫描旧节点数据,按新路由规则迁移至新节点 ◦ 迁移期间旧节点仍可读写(双写模式)
流量切换:更新路由配置,逐步切流至新节点
• 灰度发布: • 按用户ID分流:10%流量切至新节点,观察错误率 • 按地域切流:先切非核心地区(如海外用户) • 回滚方案: • 监控新节点QPS/延迟,超阈值自动回退旧配置 • 数据双写期间保留旧节点数据,支持快速回滚
数据一致性验证: • 全量校验:mysqldump
+ md5sum
• 增量校验:对比新旧库Binlog位点
性能压测: • 模拟双写压力(如JMeter模拟200%流量) • 监控连接池等待、锁竞争指标
容灾演练: • 随机Kill节点,验证数据自愈能力 • 网络分区模拟(如iptables阻断节点通信)
• 关键指标: • 活跃连接数(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
+ 告警通知
• 采集方式: • 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
• 治理流程:
TOP N慢SQL定位:通过mysqldumpslow
工具分析日志
执行计划分析:EXPLAIN
查看索引使用情况
优化方案: ◦ 缺失索引 → 添加联合索引 ◦ 复杂JOIN → 冗余字段或拆分为多次查询
• 监控项: • 路由计算耗时:ShardingSphere的sql_route_time
指标 • 跨分片查询比例:shardingsphere_routed_sql_total{type="select", is_broadcast="false"}
• 调优方案: • 强制分片键:拦截无分片键查询(ShardingSphere配置allowRangeQueryWithoutShardingKey=false
) • 本地缓存路由表:预热高频查询分片位置(如用户ID与分片映射关系)
• 分片键强制校验:
// 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
,避免关联查询用户表
• 方案对比:
方案 | 优点 | 缺点 |
---|---|---|
MQ+Elasticsearch | 实时性高(秒级延迟) | 数据需同步到ES |
Flink实时计算 | 支持复杂计算(如UV统计) | 架构复杂度高 |
本地缓存+定时批处理 | 资源消耗低 | 实时性差(分钟级延迟) |
• 代码示例: |
// 订单金额统计异步化 @Async("statsThreadPool") public void asyncOrderStats(LocalDate date) { Listorders = orderRepository.findByDate(date); // 直接查分片 BigDecimal total = orders.stream().map(Order::getAmount).reduce(BigDecimal.ZERO, BigDecimal::add); statsCache.put(date, total); }
• 动态分片键: • 场景:某直播间用户评论突增导致单分片过热 • 方案:在原分片键(直播间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)); }
• 数据采集: • ShardingSphere Exporter:暴露shardingsphere_datasource_active_connections
等指标 • 自定义指标:通过Micrometer注册业务指标(如跨分片查询次数) • 看板配置:
// Grafana面板示例(分片负载均衡) { "panels": [{ "type": "graph", "title": "分片查询分布", "targets": [{ "expr": "sum(shardingsphere_routed_sql_total) by (datasource)" }] }] }
• 集成配置:
# 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%
如何快速定位慢SQL的瓶颈? • 答:SkyWalking链路追踪 + EXPLAIN
执行计划分析,优先检查索引缺失与数据倾斜。
分片路由耗时过高可能是什么原因? • 答:路由规则复杂(如多分片键联合计算)、未预热路由缓存、跨分片查询过多。
如何设计一个高可用的监控系统? • 答:Prometheus联邦架构 + Thanos长期存储,配合Grafana多数据源聚合展示。
• 业务场景:日订单量超千万,单表数据一年破百亿,查询性能从秒级跌至分钟级。 • 核心痛点: • 用户高频查询“我的订单”接口(强依赖user_id
)。 • 历史订单占用90%存储但访问频率低(冷热数据混杂)。
水平分片: • 分片键:user_id % 1024
(1024个分片,单分片控制在500万行内)。 • 路由规则:
// 根据用户ID计算分片 int shardNo = Math.abs(userId.hashCode()) % 1024; String tableName = "orders_" + shardNo;
冷热分离: • 热数据:近3个月订单存MySQL,索引优化(user_id + create_time
联合索引)。 • 冷数据:3个月前数据归档HBase,ROW_KEY设计为user_id|order_id
(范围扫描优化)。
• 数据同步:Canal监听MySQL Binlog,触发冷数据迁移至HBase。 • 查询优化: • 热数据查询直接走MySQL分片。 • 冷数据查询走HBase的PrefixFilter
(user_id
前缀匹配)。
• 查询性能:用户订单列表响应时间从12s降至200ms。 • 存储成本:HBase压缩比提升60%,存储费用降低75%。
• 用户发布动态实时推送粉丝,读QPS峰值百万级。 • 单表存储用户动态,数据量日均十亿级。
联合分片键: • 主分片键:user_id % 256
(256个分片)。 • 二级分片键:create_time
按月分表(如feed_202301
)。
读写分离: • 写节点:主库处理发布请求,分片规则为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分片: • 原理:将经纬度编码为字符串(如wx4g0
),按前缀分片。 • 分片键:geohash.substring(0, 3)
(前3位作为分片键,256个分片)。
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。
• 传统分页失效: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),全量加载后内存中分页。
SQL解析: • 解析引擎生成抽象语法树(AST),提取分片条件(如user_id=123
)。
路由计算: • 精确路由:分片键等值查询(user_id=123
)直接定位分片。 • 广播路由:无分片键的更新(如UPDATE config SET value=1
)全分片执行。
结果归并: • 跨分片查询结果在内存中排序、聚合(如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
• 虚拟节点再平衡:
// 原分片:user_id % 8 // 扩容后:(user_id.hashCode() + virtual_node) % 16 int newShard = (userId.hashCode() + slot) % 16;
• 热点数据二级分片: • 例如对热点用户(如网红)的订单按user_id + order_id
联合分片。
• 分片键改造:联合业务高基字段(如user_id + city_code
)。 • 动态分片:根据数据分布自动调整分片映射(如一致性哈希)。
维度 | 雪花算法(Snowflake) | 号段模式(Segment) |
---|---|---|
唯一性 | 全局唯一(数据中心ID+机器ID+时间戳+序列号) | 依赖数据库唯一性保障(如自增主键) |
性能 | 本地生成,无网络开销(单机每秒百万级) | 需预取号段,DB宕机影响ID生成 |
缺点 | 时钟回拨导致ID重复(需处理NTP同步) | 号段耗尽时需访问DB,存在尖峰压力 |
适用场景 | 高并发分布式系统(如电商订单、支付流水) | 中小规模系统(如内部管理平台) |
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%的分库分表相关技术挑战。