简介:本资源是一份面向计算机专业本科生与考研学生的《分布式数据库系统》核心考点复习文档,聚焦课程重点、难点与高频考题,助力快速梳理知识体系、应对期末考试或研究生复试。文档以填空、简答、论述三大题型组织内容,系统覆盖分布式数据库分类(同构/异构型、集中/分散/可变控制型)、数据分片(水平/垂直/混合)与分布策略(集中/分割/复制/混合)、多层模式结构、DDBMS四大功能模块、分布透明性三层次、事务ACID特性及并发控制机制(悲观/乐观、封锁算法、死锁检测)等关键模块。资源为单个Word文档(.doc格式),体积精简仅38KB,便于下载查阅与离线背诵。已有656人学习下载,内容条理清晰、答案准确、术语规范,适合作为课堂笔记补充、考前冲刺提纲或概念自查清单。
1. 分布式数据库系统复习:不是背概念,是理清「数据怎么分、事务怎么跨、故障怎么扛」的实战逻辑
“分布式数据库系统-复习.doc”——看到这个标题,很多人第一反应是:又一份考前突击材料?但实际翻过几十份高校课程复习文档、带过三届数据库方向毕设后,我越来越确信:这份文档背后藏着一个被严重低估的实操分水岭。它不考你能不能默写CAP定理的定义,而是逼你回答:当用户在杭州下单、库存服务在成都更新、支付日志写入深圳节点时,一致性到底是靠两阶段提交硬扛,还是用TCC柔性补偿兜底?分区网络恢复后,那些“已确认”的订单会不会变成幽灵订单?这类问题,在单机MySQL里根本不会出现,但在真实业务中,它直接决定系统是稳如磐石还是三天一抖。本篇不讲PPT式理论,只聚焦一线工程师真正要动手验证、调试、压测的三个核心战场:分片策略如何选(不是所有表都适合按user_id哈希)、分布式事务的落地取舍(为什么Seata的AT模式在金融场景反而要禁用)、以及故障注入后数据收敛的黄金时间窗口(别只盯着“是否恢复”,要看“恢复到哪一刻的状态”)。适合正在准备校招数据库岗、参与中间件改造或接手遗留分布式系统的开发者——你不需要从零造轮子,但必须清楚每个开关拧紧后,系统会往哪个方向偏。
2. 分片策略选型:从“能分”到“分得稳”的三道硬门槛
分布式数据库的起点永远是分片(Sharding),但分片不是把表切开就完事。很多团队踩坑的根源在于:用单机思维设计分片键,结果上线后发现90%的查询要跨节点聚合,QPS直接腰斩。真正的分片设计,必须同时满足路由可预测、热点可隔离、扩容可平滑三个硬约束。下面以最常见的用户订单场景为例,拆解三种主流策略的落地细节。
2.1 按用户ID哈希分片:高并发读写的默认选择,但有隐藏陷阱
这是最常被推荐的方案,原理简单:shard_id = hash(user_id) % shard_count。看似完美,但实际部署时必须直面两个反直觉问题:
- 用户ID非均匀分布:新注册用户集中在某几个ID段(比如用雪花算法生成的ID,高位时间戳导致新用户ID连续),哈希后大量落在同一分片;
- 冷热用户差异巨大:头部1%的KOL用户产生80%的订单,其关联的订单表、评论表、消息表全部挤在同一分片。
提示:不要直接用
user_id做哈希源。我一般会先对user_id做一次crc32再取模,或者更稳妥地引入二级分片键(如user_id % 1000作为分片内局部ID),把热点打散。
# Python示例:生产环境推荐的哈希分片函数(避免长尾) import zlib def get_shard_id(user_id: int, shard_count: int) -> int: # 使用zlib.crc32替代简单hash(),保证跨语言一致性 # user_id转为bytes,避免不同语言hash结果差异 key_bytes = str(user_id).encode('utf-8') crc = zlib.crc32(key_bytes) & 0xffffffff return crc % shard_count # 验证:检查10万用户ID的分片分布均衡性 shard_stats = [0] * 8 for uid in range(1, 100001): shard_stats[get_shard_id(uid, 8)] += 1 print("分片负载比:", [f"{s/100000*100:.1f}%" for s in shard_stats]) # 输出应接近 ['12.4%', '12.6%', '12.5%', '12.3%', '12.7%', '12.4%', '12.5%', '12.6%']这段代码的关键不在算法多炫酷,而在于强制统一哈希实现。曾有个项目因Java端用Objects.hash()、Go端用fnv1a,导致同个user_id路由到不同分片,数据写错位置,排查三天才发现是哈希函数不一致。所以,分片函数必须写死在配置中心,所有语言SDK调用同一套预编译逻辑。
2.2 按时间范围分片:解决历史数据归档与冷热分离,但需警惕“时间倾斜”
当订单表按月分片(如order_202401,order_202402)时,优势明显:历史数据可独立下线、备份;查询最近3个月订单无需扫全表。但问题在于:业务时间 ≠ 数据写入时间。促销期间(如双11)单月订单量可能是平时的20倍,order_202411分片瞬间成为性能瓶颈。
解决方案不是放弃时间分片,而是叠加一层动态权重:
- 监控各分片QPS、慢查率、磁盘IO;
- 当某分片负载超阈值(如QPS > 5000且慢查率 > 3%),自动触发“子分片”:将
order_202411按user_id % 4再拆成4个物理表; - 应用层路由逻辑同步升级:先按月定位主分片,再按user_id定位子分片。
这种“时间+哈希”二级分片,在某电商大促系统中将单分片峰值QPS从12000压到2800,且扩容过程对业务无感。关键参数如下表:
| 参数 | 推荐值 | 说明 |
|---|---|---|
| 单分片QPS告警阈值 | 4000~6000 | 取决于硬件配置,SSD集群可设更高 |
| 子分片数量 | 2/4/8 | 必须是2的幂,便于位运算快速路由 |
| 负载评估周期 | 30秒 | 太短易误判,太长无法及时响应 |
2.3 地理位置分片:本地化优先的刚需,但跨域事务成本极高
当业务明确要求“用户请求必须路由到最近机房”(如游戏登录、实时聊天),地理位置分片(Geo-sharding)成为刚需。典型做法是:根据用户IP解析出城市/省份,映射到对应机房分片(如shard_beijing,shard_shanghai)。
但血泪经验是:千万别让跨地域事务成为常态。曾有个社交App尝试在用户A(北京)发帖、用户B(广州)评论时,强制将评论写入北京分片以保强一致,结果广州用户评论延迟高达800ms,投诉激增。最终方案是改为“读本地、写异步”:
- 评论写入广州本地分片(低延迟);
- 通过CDC(Change Data Capture)将变更同步到北京分片;
- 用户A刷新页面时,先查本地缓存(可能旧),再异步拉取最新评论(最终一致)。
这本质是用“读延迟”换“写可用性”,符合PACELC定理中“Partition, choose Availability over Consistency; Else, choose Latency over Consistency”的权衡。技术上,我们用Debezium捕获MySQL binlog,经Kafka分发后,由Flink作业按post_id哈希路由到目标分片执行INSERT ON DUPLICATE KEY UPDATE。
3. 分布式事务落地:AT、TCC、Saga不是选择题,是成本核算表
复习文档里总爱列一堆分布式事务模型,但真实世界里,没有银弹,只有取舍。我见过太多团队因为盲目追求“强一致”,在支付链路里硬上XA协议,结果TPS从3000掉到400,最后不得不回滚。分布式事务的本质,是用计算资源、开发成本、运维复杂度去购买一致性等级。下面用一张表说清三种主流模式的硬性成本:
| 模式 | 一致性等级 | 开发成本 | 回滚机制 | 典型适用场景 | 我的建议 |
|---|---|---|---|---|---|
| AT(自动事务) | 弱一致(最终一致) | ★☆☆☆☆(低) | 依赖undo_log自动回滚 | 订单创建、积分发放等非金融场景 | Seata AT模式够用,但必须关掉全局锁(lock-table),否则高并发下锁表雪崩 |
| TCC(Try-Confirm-Cancel) | 强一致(业务层面) | ★★★★☆(高) | 手动编写Confirm/Cancel逻辑 | 金融转账、库存扣减等资金敏感操作 | Confirm必须幂等,Cancel必须可重试;建议用状态机管理事务生命周期,避免悬挂 |
| Saga(长事务) | 最终一致 | ★★☆☆☆(中) | 补偿事务(Compensating Transaction) | 跨多个微服务的复杂流程(如机票预订:占座→出票→保险→短信) | 补偿动作必须100%可靠;建议用状态持久化+定时任务兜底,而非纯事件驱动 |
3.1 AT模式避坑:undo_log不是保险丝,是定时炸弹
Seata的AT模式之所以流行,是因为它对业务代码“无侵入”。但生产环境最大的翻车点,恰恰藏在undo_log这张表里:
- 现象:系统运行一周后,
undo_log表暴涨到200GB,MySQL主从延迟飙升至30分钟; - 原因:AT模式要求每个分支事务提交前,先将原始数据快照写入
undo_log,但默认配置undo.log.save.days=7,且未开启自动清理;更致命的是,当应用异常退出(如OOM Kill),未完成的全局事务不会自动清理对应undo_log,形成“僵尸日志”; - 解决:
- 在Seata Server配置中强制设置
undo.log.delete.period=86400(24小时); - 在应用启动时,加一段初始化SQL:
-- 每日凌晨2点自动清理7天前的undo_log CREATE EVENT clean_undo_log ON SCHEDULE EVERY 1 DAY STARTS '2024-01-01 02:00:00' DO DELETE FROM undo_log WHERE log_status = 1 AND sys_gmt_modified < DATE_SUB(NOW(), INTERVAL 7 DAY); - 在Seata Server配置中强制设置
注意:
log_status = 1表示已提交但未清理的日志,status = 0是进行中事务,绝不能删!曾有DBA误删全部undo_log,导致全局事务无法回滚,数据永久不一致。
3.2 TCC模式避坑:Confirm失败不是Bug,是设计缺陷
TCC要求业务方提供三个接口:try(预留资源)、confirm(确认执行)、cancel(释放资源)。新手常犯的错误是:把confirm写成“执行业务逻辑”,结果遇到网络抖动,confirm超时重试,同一笔钱被扣两次。
- 现象:用户发起一笔100元转账,
try成功冻结余额,confirm因网络超时返回失败,Seata重试confirm,结果账户被扣款两次; - 原因:
confirm接口未实现幂等性,且未校验前置状态(如冻结金额是否已被其他事务消耗); - 解决:
confirm必须先查try阶段生成的事务ID和冻结金额,仅当状态为“冻结中”且金额匹配时才执行扣款;- 扣款SQL强制加乐观锁:
UPDATE account SET balance = balance - 100 WHERE user_id = ? AND freeze_amount = 100 AND version = ?; - 版本号
version字段在try时生成,confirm时校验并自增,确保同一事务只执行一次。
// Spring Boot中TCC confirm方法的幂等骨架 @TwoPhaseBusinessAction(name = "transferConfirm", commitMethod = "confirm", rollbackMethod = "cancel") public boolean prepareTransfer(BusinessActionContext actionContext, @Param("userId") Long userId, @Param("amount") BigDecimal amount) { // try阶段:冻结余额,记录freeze_amount和version return accountMapper.freezeBalance(userId, amount); } public boolean confirm(BusinessActionContext actionContext) { Long userId = (Long) actionContext.getActionContext("userId"); BigDecimal amount = (BigDecimal) actionContext.getActionContext("amount"); // 关键:先查当前冻结状态,再执行扣款 Account account = accountMapper.selectByUserIdForUpdate(userId); // SELECT ... FOR UPDATE if (account.getFreezeAmount().compareTo(amount) == 0 && account.getStatus().equals("FROZEN")) { // 执行扣款,带版本号校验 return accountMapper.deductBalance(userId, amount, account.getVersion()); } return true; // 已处理,直接返回成功 }这段代码的核心思想是:把业务状态检查前置到数据库层面,用SELECT FOR UPDATE锁住行,再用UPDATE的WHERE条件做原子校验。比在Java层判断更可靠,避免了缓存不一致导致的重复扣款。
3.3 Saga模式避坑:补偿事务不是“撤销”,是“覆盖”
Saga将长事务拆成一系列本地事务,每个步骤都有对应的补偿动作。但很多团队把补偿理解为“回滚到之前状态”,结果在库存场景翻车:
- 现象:用户下单扣减库存(Step1),发货时更新物流状态(Step2),若Step2失败触发补偿,执行“库存回滚”,但此时商品已售罄,回滚后库存变负数;
- 原因:补偿动作未考虑外部状态变化,单纯“加回库存”违背业务规则;
- 解决:补偿动作必须是幂等的正向操作,而非逆向操作。正确做法是:
- Step1:
UPDATE inventory SET stock = stock - 1 WHERE sku_id = ? AND stock >= 1; - Compensate:
UPDATE inventory SET stock = stock + 1 WHERE sku_id = ? AND status = 'CANCELLED'; - 关键是给订单加
status字段,只有状态为CANCELLED时才允许补偿,且补偿SQL必须包含AND stock < original_stock防止超补。
- Step1:
4. 故障注入与数据收敛:别只看“是否恢复”,要看“恢复到哪一刻”
分布式系统复习文档里,CAP定理常被当作考点,但真实压测中,我们更关心:当网络分区发生、节点宕机、磁盘损坏时,数据最终收敛到什么状态?这个过程耗时多久?有没有丢失?这就是“数据收敛性”(Convergence),它比“是否可用”更能反映系统健壮性。下面用TiDB和CockroachDB的真实压测对比,说清三个必须验证的收敛维度。
4.1 时间收敛:从故障发生到数据一致的黄金15秒
在某支付系统压测中,我们模拟机房断网(iptables -A INPUT -s 10.0.1.0/24 -j DROP),观察订单表主键冲突率:
| 数据库 | 故障持续时间 | 主键冲突率 | 平均收敛时间 | 说明 |
|---|---|---|---|---|
| TiDB v6.5 | 30秒 | 0.02% | 8.3秒 | 依赖PD(Placement Driver)调度,Leader切换快,但Region分裂后需重新同步 |
| CockroachDB v22.2 | 30秒 | 0.003% | 12.7秒 | Raft组内多数派写入即返回,但跨区域同步延迟略高 |
| MySQL Group Replication | 30秒 | 1.8% | 42秒 | 基于binlog的异步复制,网络恢复后需重传大量日志 |
提示:收敛时间不是越短越好。TiDB的8秒收敛,是以牺牲“写入延迟”为代价的——它默认开启
raft-store.raft-log-gc-threshold(Raft日志GC阈值),频繁GC导致磁盘IO升高。我们最终将阈值从100MB调至500MB,收敛时间升至11秒,但平均写入延迟下降37%。稳定性优先于极致速度,这是血泪教训。
4.2 状态收敛:最终一致 ≠ 最终正确,必须校验业务语义
很多团队只验证“所有节点数据行数相同”,这远远不够。真正的状态收敛,必须校验业务关键约束是否被破坏。例如订单表的status字段,合法值只有CREATED,PAID,SHIPPED,COMPLETED,但故障后可能出现PAID状态的订单,其关联的支付流水表却查不到记录(数据不完整)。
我们自研了一套收敛校验工具,核心逻辑是:
- 抽取关键业务规则:如“所有status='PAID'的订单,payment_id必须在payment_log表中存在”;
- 故障注入后,定时(每5秒)执行校验SQL:
-- 查找状态异常的订单 SELECT o.order_id, o.status, p.payment_id FROM orders o LEFT JOIN payment_log p ON o.payment_id = p.id WHERE o.status = 'PAID' AND p.id IS NULL; - 当异常记录数归零,且持续3个周期(15秒)无新增,才判定状态收敛。
这套方法在某银行核心系统上线前,暴露出一个深埋的BUG:当支付服务超时重试时,订单表被更新两次,但第二次更新未携带payment_id,导致状态变为PAID但无支付记录。这个BUG在单机测试中完全无法复现,只有在分布式故障注入时才会触发。
4.3 故障注入避坑:别用kill -9,用tc netem模拟真实网络病
很多团队用kill -9进程模拟节点宕机,这会导致Raft日志丢失、WAL未刷盘,属于“灾难级故障”,但生产中最常见的是“亚健康”:网络延迟突增、丢包率升高、DNS解析缓慢。这些场景下,系统表现和kill -9截然不同。
- 现象:用
kill -9干掉TiDB节点,集群30秒内自动恢复;但用tc netem delay 200ms loss 5%模拟弱网,客户端持续报ERROR 9005 (HY000): Region is unavailable,持续5分钟; - 原因:
kill -9是瞬时故障,Raft能快速选举新Leader;而网络抖动导致心跳超时、Proposal反复失败,TiKV的raftstore.store-pool-size(Raft线程池)被占满,新请求排队; - 解决:
- 调大
raftstore.store-pool-size(默认2,建议设为4~8); - 在客户端增加重试逻辑,但重试间隔必须指数退避(100ms, 300ms, 900ms...),避免雪崩;
- 关键业务接口增加熔断器(如Hystrix),当错误率超30%时,自动降级为本地缓存读取。
- 调大
# 正确的网络故障注入命令(模拟运营商级弱网) # 在TiDB节点上执行: tc qdisc add dev eth0 root netem delay 100ms 20ms distribution normal loss 1% 25% # 解释:基础延迟100ms,抖动±20ms(正态分布),丢包率1%,丢包突发概率25%这条命令比ping -c 100 -i 0.1 10.0.1.100真实得多——它复现了4G/5G切换、跨省骨干网拥塞等真实场景。记住:压测的目标不是让系统崩溃,而是让它在崩溃边缘依然可控。
5. 复习文档里的“伪重点”:这些概念你必须亲手验证,而不是背诵
翻开任何一份《分布式数据库系统-复习.doc》,你都会看到“CAP定理”“BASE理论”“Paxos算法”“Raft日志复制”这些词。它们很重要,但如果你只停留在背诵层面,这份复习文档对你价值为零。真正决定你能否拿下Offer或搞定线上故障的,是那些需要你亲手敲命令、改配置、看日志、画时序图才能理解的“伪重点”。下面挑三个最常被误解的概念,给出可验证的实操路径。
5.1 “CAP只能三选二”?先验证你的系统到底在选哪个
CAP定理常被简化为“一致性、可用性、分区容错性三选二”,但这是对定理的严重误读。分区容错性(P)在分布式系统中是必选项,不存在“不选P”的可能。CAP的真正含义是:当网络分区(P)发生时,你必须在C(强一致)和A(高可用)之间做实时取舍。
验证方法很简单:用tc netem制造分区,然后并发执行以下操作:
# 终端1:持续向节点A写入 while true; do echo "key_$(date +%s%N)" | redis-cli -h 10.0.1.10 SET test_key; sleep 0.1; done # 终端2:持续从节点B读取 while true; do redis-cli -h 10.0.1.11 GET test_key; sleep 0.1; done观察现象:
- 如果节点B始终返回最新值(如
key_171234567890123),说明系统选择了CP(如ZooKeeper); - 如果节点B在分区期间返回旧值(如
key_171234567800000),但永不报错,说明系统选择了AP(如Cassandra); - 如果节点B在分区期间直接返回
CLUSTERDOWN错误,说明它既不选C也不选A,而是拒绝服务(CA,但现实中几乎不存在)。
注意:Redis Cluster默认是AP,但可通过
cluster-require-full-coverage no配置,在部分节点宕机时仍提供服务。这个配置项,比背10遍CAP定理更能让你理解“可用性”的真实代价。
5.2 “Raft日志复制”不是玄学,是可追踪的三阶段流水线
复习文档里总说“Raft通过日志复制保证一致性”,但没告诉你日志复制具体卡在哪一步。实际上,Raft日志复制是严格的三阶段:
- Leader接收客户端请求,追加到本地日志(Log Append);
- 并行发送AppendEntries RPC给所有Follower,等待多数派响应(Replicate);
- Leader将已提交日志应用到状态机(Apply)。
验证方法:在TiKV日志中搜索关键词:
# 查看Raft日志追加耗时(阶段1) grep "append_entries" /var/log/tikv/tikv.log | tail -20 | awk '{print $NF}' # 输出类似:duration: 1.23ms → 追加到本地日志耗时 # 查看RPC响应耗时(阶段2) grep "handle_raft_ready" /var/log/tikv/tikv.log | tail -20 | grep "replicate" # 输出类似:replicate to peer 2 took 8.7ms → 发送给Follower耗时 # 查看状态机应用耗时(阶段3) grep "apply_snap" /var/log/tikv/tikv.log | tail -20 # 输出类似:apply snapshot cost 120ms → 应用快照耗时(大事务时显著)你会发现:阶段2(网络RPC)通常是瓶颈,尤其在跨机房部署时。这时优化方向就非常清晰:不是调大raftstore.raft-log-gc-threshold,而是减少单次AppendEntries的数据量(调小raftstore.raft-max-inflight-msgs),或增加Follower节点数(提高多数派响应概率)。
5.3 “分布式唯一ID”不是UUID,是时钟+序列的精密仪器
复习文档总说“用Snowflake生成分布式ID”,但很少提它的致命弱点:时钟回拨。当服务器NTP时间校准导致时间倒退5ms,Snowflake生成的ID就会重复。
验证方法:手动触发时钟回拨,观察ID生成器行为:
# 在测试机上执行(模拟NTP校准) sudo date -s "2024-01-01 10:00:00" # 等待ID生成器输出一批ID,记下最后10个 sudo date -s "2024-01-01 09:59:55" # 回拨5秒 # 再生成10个ID,检查是否与之前重复真实案例:某社交平台因NTP服务异常,导致3台机器时钟回拨200ms,Snowflake ID重复率飙升至0.3%,引发Feed流乱序、点赞计数错误。最终方案是:
- 改用
Leaf-segment(号段模式),ID生成器从DB预取1000个ID,时钟回拨不影响已分配号段; - 或用
MongoDB ObjectId,其时间戳精度为秒级,回拨影响极小; - 绝对不要用
System.currentTimeMillis()做Snowflake时间戳源,必须用SystemClock.now()(内部带时钟漂移检测)。
6. 把复习文档变成你的故障响应手册:三个必须写进预案的检查清单
复习文档的价值,不在于帮你通过考试,而在于当你凌晨三点收到“订单状态不一致”的告警时,能立刻打开它,找到对应章节,执行标准化排查。我把这份文档重构成了三份可直接粘贴进公司Wiki的故障响应清单,每份都经过某跨平台系统线上事故验证。
6.1 订单状态不一致:5分钟定位法
当监控发现orders.status在不同节点值不同时,按此顺序执行:
| 步骤 | 命令/操作 | 预期结果 | 说明 |
|---|---|---|---|
| 1. 检查分片路由是否错乱 | SELECT /*+ SHARDING_HINT('shard_id=3') */ * FROM orders WHERE order_id = '202404010001'; | 返回空或错误分片数据 | TiDB需加SHARDING_HINT强制路由,排除应用层路由错误 |
| 2. 检查Binlog同步延迟 | SHOW SLAVE STATUS\G(MySQL从库)或crdb_internal.node_status()(CRDB) | Seconds_Behind_Master < 1或replica_lag_nanos < 1000000000 | 延迟超1秒即需告警,立即检查网络和磁盘IO |
| 3. 检查分布式事务状态 | SELECT * FROM xa_recover;(MySQL XA)或SELECT * FROM seata_global_table WHERE status != 1;(Seata) | 无status=0(prepared)或status=2(rollbacking)记录 | 存在未完成事务,需人工介入或触发seata-server清理脚本 |
提示:第1步必须放在最前!曾有个事故,DBA花2小时查复制延迟,最后发现是应用配置错了分片键,所有
order_id都路由到了shard_0,其他分片根本没数据。
6.2 跨节点查询变慢:三层诊断法
当SELECT * FROM orders JOIN users ON orders.user_id = users.id执行超时,按此顺序排查:
| 层级 | 检查点 | 命令 | 关键指标 |
|---|---|---|---|
| 应用层 | 是否启用了绑定变量 | EXPLAIN FORMAT=VERBOSE SELECT ... | 查看execution_info中bind_vars是否为空,空则未启用预编译 |
| 中间件层 | 分片键是否参与JOIN条件 | SELECT /*+ USE_INDEX(orders, idx_user_id) */ ... | 强制走索引,若仍慢,说明分片键未用于JOIN,需改写SQL或建冗余字段 |
| 存储层 | 跨分片JOIN是否触发Broadcast Join | EXPLAIN ANALYZE SELECT ... | 查看execution_info中broadcast_join是否为true,是则需调整tidb_broadcast_join_threshold_size(默认10MB) |
这个三层法帮我们在某物流系统中,将一个12秒的跨分片JOIN优化到320ms——关键是发现users表虽小(2MB),但未建idx_user_id索引,导致每次JOIN都全表扫描。
6.3 节点频繁重启:内存泄漏定位四步法
当TiKV或CockroachDB节点每小时重启一次,执行:
- 抓取OOM Killer日志:
dmesg -T | grep -i "killed process",确认是否因内存超限被杀; - 检查JVM堆外内存(TiKV用Rust,但可通过
/proc/<pid>/smaps看Rss):# 查看TiKV进程内存占用 cat /proc/$(pgrep tikv-server)/smaps | awk '/^Rss:/ {sum+=$2} END {print sum/1024 " MB"}' # 若>80%物理内存,需调小`rocksdb.block-cache-size`(默认1GB) - 分析慢日志中的大Scan:
grep "scan" /var/log/tikv/tikv.log | awk '{print $NF}' | sort -n | tail -10,找出最大Scan范围; - 检查Region Balance:
curl http://pd-server:2379/pd/api/v1/stores,查看各Store的region_count是否相差>30%,是则需手动pd-ctl迁移Region。
最后一句:我坚持把每份复习文档都重构成可执行的故障手册,不是为了显得多专业,而是因为经历过太多次——当告警电话响起,你根本没时间翻PPT,能救你的只有那几行标红的命令和参数。希望帮到你。
本文还有配套的精品资源,点击获取