数据层设计
→ 返回 高并发
高并发场景下,数据层应扛持久化、对账与复杂查询,而不是扛每一次用户点击。核心是:写路径异步、批量、幂等、可分区。
设计原则
| 原则 | 说明 |
|---|---|
| 写少读多 | 写合并后批量入库 |
| 最终一致 | 实时态在 Redis,DB 滞后分钟~小时可接受 |
| 幂等 | 业务键 (user_id, step_date) UPSERT |
| 可分区 | 按日期 / 用户 ID 分表,避免单表过大 |
| 可回溯 | MQ / Binlog 保留重放能力 |
写路径演进
❌ 同步:API → UPDATE mysql → 返回
✅ 推荐:API → MQ → 聚合 → Redis → 异步批量 → MySQL
| 阶段 | 存储 | 延迟 |
|---|---|---|
| 实时 | Redis | ms |
| 近线 | MQ 持久化 | 秒级 lag |
| 持久 | MySQL / TiDB | 分钟~T+1 |
表结构设计(步数示例)
CREATE TABLE user_daily_steps (
user_id BIGINT UNSIGNED NOT NULL,
step_date DATE NOT NULL,
steps INT UNSIGNED NOT NULL DEFAULT 0,
source VARCHAR(16) NULL COMMENT 'ios/android',
updated_at DATETIME(3) NOT NULL,
PRIMARY KEY (user_id, step_date),
KEY idx_date_steps (step_date, steps)
) ENGINE=InnoDB
PARTITION BY RANGE (TO_DAYS(step_date)) (
PARTITION p202406 VALUES LESS THAN (TO_DAYS('2024-07-01')),
PARTITION p202407 VALUES LESS THAN (TO_DAYS('2024-08-01'))
);| 字段 | 说明 |
|---|---|
| 联合主键 | 天然幂等 UPSERT |
step_date | 日历日,与 Redis key 对齐 |
| 分区 | 历史分区可归档、删除 |
批量写入
INSERT INTO user_daily_steps (user_id, step_date, steps, updated_at)
VALUES (?,?,?,?), (?,?,?,?)
ON DUPLICATE KEY UPDATE
steps = GREATEST(steps, VALUES(steps)),
updated_at = VALUES(updated_at);消费者从 Redis 或 MQ 每 500~5000 条一批,控制单事务大小。
读写分离
| 读 | 写 |
|---|---|
| 历史榜单、运营报表 | 批量入库任务 |
| 从库 | 主库 |
实时榜不读从库(延迟)。见 数据库架构。
分库分表
当 单表日增量亿级 或 QPS 超单机:
| 维度 | 策略 |
|---|---|
按 user_id 分库 | db = uid % 16 |
按 step_date 分表 | 与分区配合 |
| 路由 | ShardingSphere、应用内路由 |
排行榜实时读仍在 Redis;分库分表解决持久化与历史查询。
详见 ShardingSphere、分库分表。
分布式数据库选项
| 产品 | 特点 |
|---|---|
| TiDB | MySQL 协议,自动分片,HTAP |
| PolarDB-X | 云原生分布式 |
| 单机 + 分表 | 运维简单,多数业务够用 |
对账与补偿
定时任务:
扫描 Redis step:date:* 与 DB 不一致
以 Redis 为准修复 DB(或人工规则)
MQ 重放:
按 offset 重新消费某日上报
| 不一致原因 | 处理 |
|---|---|
| 消费失败 | 重试 + 死信队列 |
| DB 批量失败 | 记录失败批次,重跑 |
| Redis 丢数据 | 从 MQ 重放(需 MQ 保留期足够) |
CDC 同步其它系统
MySQL Binlog → Canal / Debezium → Kafka → ES / 数仓
搜索、BI 不走业务写路径。见 Canal。
数据层容量估算(示意)
| 量 | 估算 |
|---|---|
| 1 亿 DAU 上报 1 次/日 | 1 亿行/日 |
| 行 50B | ~5GB/日 裸数据 |
| 保留 1 年 | 需分区归档 + 冷存储 |
结论:必须分区 + 归档;实时态不放 MySQL。