
先说说我为什么会写这个题。Memcached 用到现在的人不少但真正把 stats 命令用明白的说实话不多。很多人查缓存问题第一反应是看命中率盯着 get_hits 和 get_misses 两个数字看半天然后不知道该干什么。其实 stats 这一条命令后面藏着的是一整套诊断体系从内存 slab 分布到 LRU 驱逐情况从连接状态到 item 过期回收全都能在它的输出里找到线索。这篇就把我这几年来在生产环境里用 stats 排查问题的经验整理出来从最基本的连接方式讲起一直到每个字段项的含义、常用子命令的用法以及我自己踩过的一些坑尽量做到看完全篇就能直接上手。1. stats 命令的基本玩法与连接方式1.1 先解决怎么连上 MemcachedMemcached 本身没有像 MySQL 那样的客户端命令行日常运维基本都是通过 telnet 建立 TCP 连接然后手动敲命令。当然还有 ncnetcat这类工具本质上是一样的套路。最常见的连接方式就一条命令telnet 127.0.0.1 11211连接成功后会进入一个交互式会话没有任何欢迎横幅光标直接停在空行等你输入。这时候敲一个stats再回车就能看到一大串STAT开头的输出最后以END结束。如果你不想进入交互模式想直接一条命令拿结果可以用 ncprintf stats\r\n | nc 127.0.0.1 11211这里有个小细节Memcached 的协议是文本行协议命令必须以\r\n结尾。用 printf 的时候如果把\r漏了有的版本能正常响应有的版本会直接不鸟你。我为了这个折腾过好几次后来统一用上面这种写法。如果连接失败常见原因无非这么几个Memcached 没启动、监听端口不对、防火墙拦了、或者绑定的地址不是你想连的那个。启动 Memcached 时如果只写了-l 127.0.0.1那远端机器肯定连不上这个问题太常见了。查网络连通性的时候可以先用系统自带的 ping 确认主机通不通再用 telnet 测端口通不通这个思路跟排查其他 TCP 服务是一样的。注意我这里说的是测试端口连通性跟你系统里用不用 telnet 客户端是两回事很多服务器上没装 telnet 客户端那就用 nc 来测。1.2 基础 stats 命令与响应格式解读执行stats后输出大体是这个样子的STAT pid 2371 STAT uptime 812343 STAT time 1718000000 STAT version 1.6.18 STAT libevent 2.1.12-stable STAT pointer_size 64 STAT rusage_user 287.562500 STAT rusage_system 299.312500 STAT max_connections 1024 STAT curr_connections 512 STAT total_connections 300234 STAT rejected_connections 0 STAT connection_structures 520 STAT response_obj_oom 0 STAT response_obj_count 0 STAT curr_items 152345 STAT total_items 3456789 STAT evicted_active 23 STAT evicted_unfetched 10567 STAT evicted 19687 STAT evicted_time 154 STAT outofmemory 0 STAT tailrepairs 0 STAT reclaimed 876543 STAT expired_unfetched 65432 STAT crawler_reclaimed 4321 STAT lrutail_reflocked 12 STAT moves_to_cold 789123 STAT moves_to_warm 456789 STAT moves_within_lru 123456 STAT reclaimed_hashtable 3456 STAT direct_reclaims 234 STAT expired 56789 STAT get_hits 987654321 STAT get_misses 9080706 STAT get_expired 12345 STAT get_flushed 6789 STAT delete_misses 1234 STAT delete_hits 4567 STAT incr_misses 0 STAT incr_hits 0 STAT decr_misses 0 STAT decr_hits 0 STAT cas_misses 0 STAT cas_hits 0 STAT cas_badval 0 STAT touch_hits 0 STAT touch_misses 0 STAT auth_cmds 0 STAT auth_errors 0 STAT bytes_read 1234567890123 STAT bytes_written 456789012345 STAT limit_maxbytes 1073741824 STAT accepting_conns 1 STAT listen_disabled_num 0 STAT time_in_listen_disabled_us 0 STAT threads 4 STAT conn_yields 0 STAT hash_power_level 22 STAT hash_bytes 8388608 STAT hash_is_expanding 0 STAT slab_reassign_running 0 STAT slabs_moved 12 STAT slab_automove 1 STAT lru_crawler_running 0 STAT lru_crawler_starts 456 STAT lru_maintainer_thread 0 STAT malloc_fails 0 STAT log_worker_writes 0 STAT log_worker_written 456789 STAT log_worker_dropped 0 STAT log_watcher_skipped 0 STAT log_watcher_sent 345678 STAT bytes 734003200 STAT touch_requests 0 STAT thread_local_hits 0 STAT conn_yields 0 END不同版本字段有差异1.5 以后加入了expired、get_expired、moves_*这些与 LRU 维护线程相关的统计1.6 又增加了log_*。不用背全部字段但是要理解每一类字段的用途。我习惯把它分成四块来看进程运行状态、数据与内存、命中与淘汰、连接与网络。下一节逐个拆。2. 核心统计指标逐项拆解2.1 服务运行与进程相关的指标pid、uptime、version、time这几个属于最基础的运行态信息。uptime的单位是秒用当前time减去uptime就能算出 Memcached 实例的启动时刻。这个在很多场景下非常有用比如你怀疑缓存是不是半夜被重启过不用去翻系统日志跑一下 stats 自己算一遍就知道了。threads表示启动时的 worker 线程数量默认 4这是-t参数决定的。如果线上请求量很大你可能会看到 CPU 在多个核上都有消耗但 Memcached 单实例的吞吐瓶颈其实很少在线程数上更多是网络中断和内存分配。pointer_size是 64 表示这是 64 位编译版本这个字段主要影响你对内存上限的预期32 位版本下单个进程能管理的内存非常有限生产环境尽量用 64 位。rusage_user和rusage_system是进程累计消耗的用户态和内核态 CPU 时间单位是秒。怎么用比如你重启一个实例后过了一个小时再看这两个值如果rusage_system涨得异常快说明系统调用开销偏高通常跟网络包处理有关这时候去检查网卡中断或者连接数是否过高。2.2 数据量与内存使用指标curr_items是当前所有 slab class 里的 item 总数total_items是实例启动以来累计写入的 item 数量包括被淘汰的和过期的。这两个一对比就有意思了如果total_items涨得飞快而curr_items纹丝不动说明写入大量新 key 的同时也在大量淘汰旧 key你的内存可能不够了。bytes表示当前存储数据实际占用的内存字节数不含管理结构开销。limit_maxbytes是你给 Memcached 配置的最大内存字节数也就是启动参数-m的值乘以 1024 再乘以 1024。如果你配置的是 1G那limit_maxbytes是 1073741824。那么实际使用率就是bytes / limit_maxbytes这个比例才是你需要盯的核心指标而不是光看curr_items。这里有个容易踩的坑bytes只算 item 的 key、value 以及部分元数据的存储占用系统为每个 item 分配的内存块还有额外的 chunk 浪费特别是 value 大小跟 chunk size 不匹配的时候浪费可能超过 10%。所以实际 RSS 内存占用会明显高于bytes你在操作系统里用ps看到的RES才是真实值两者对不上是正常的不用慌。hash_power_level和hash_bytes是哈希表的信息。hash_bytes是哈希表本身占用的内存hash_power_level是哈希表的大小等级Memcached 会随着 key 数量增长自动扩容哈希表。如果看到hash_is_expanding为 1说明正在扩容过程中这时候 stats 的输出可能有一点抖动属正常现象。哈希表占用的内存不在bytes里所以也别拿bytes去核算内存上限。2.3 命中率与淘汰策略指标命中率是大家最关心的公式很简单命中率 get_hits / (get_hits get_misses)。这个结果的百分比就是你缓存的实际价值。如果命中率低于 80%你就要想想是不是 key 设计有问题或者过期时间设得太短再或者数据访问局部性不强。很多文章只讲 get_hits 和 get_misses但现代 Memcached 的淘汰相关指标远比这两个重要。evicted是从 LRU 尾部逐出的 item 数量evicted_time是最近一次驱逐的 item 距离现在多少秒这个字段很有诊断价值。如果evicted_time很小说明 LRU 正在高频驱逐内存压力极大缓存雪崩可能正在发生。evicted_active表示驱逐的 item 里有不少是最近被访问过的热数据这比驱逐冷数据严重得多说明内存已经连热数据都保不住了。evicted_unfetched表示被驱逐的 item 中有多少从未被 get 过这个数很大倒是好事说明驱逐的主要是垃圾数据空间利用率不高。reclaimed表示新写入的 item 复用了已过期 item 的内存空间不用走 LRU 驱逐。这个数字大说明过期 key 处理得很有效率对性能有利。expired_unfetched表示过期的 item 里有多少从未被读取过这个数字越高说明你写入了大量一次性数据从设计角度可以考虑缩短过期时间或者换用更轻量的存储方案。还有一个关键概念Memcached 的懒惰过期机制。默认情况下过期 item 不会立刻被后台线程清理只有被 get 访问到时才会发现它已过期。所以你会发现get_misses里有一部分其实是get_expired也就是 key 确实存在过只是已经过期了。如果get_expired占比很高说明大量请求在读取已经过期的 key缓存命中率低是预期内的事而不是故障。2.4 网络与连接指标curr_connections是当前打开的连接数total_connections是实例启动以来累计建立的连接总数。rejected_connections是超出max_connections被拒绝的连接数如果这个值持续增长说明连接池配置得太大了或者服务端连接数上限跟不上。connection_structures是 Memcached 为每个连接分配的管理结构体数量通常略大于curr_connections。如果这个值远大于curr_connections说明有大量连接正处于关闭过程中可能存在连接泄漏。listen_disabled_num是监听端口被暂时关闭的次数Memcached 在达到max_connections后会自动关掉监听等连接数降下来再重新开启。如果这个数一直在涨说明连接数频繁触顶你该动手调大-c参数或者优化客户端连接复用了。bytes_read和bytes_written是累计的读写字节数。这两个值在排查询吞吐问题时有用比如你怀疑带宽被打满算一下(bytes_written - bytes_before) / 时间间隔就能得到实时写流量。很遗憾 stats 不直接提供速率字段只能自己算差值。3. stats 家族子命令定位问题的利器3.1 stats items 与 stats slabs内存分配的真相stats items输出每个 slab class 的 item 统计stats items STAT items:1:number 152 STAT items:1:age 1089 STAT items:1:mem_requested 14592 STAT items:1:evicted 0 STAT items:1:evicted_nonzero 0 STAT items:1:evicted_time 0 STAT items:1:outofmemory 0 STAT items:1:tailrepairs 0 STAT items:1:reclaimed 1324 STAT items:1:expired_unfetched 221 STAT items:1:evicted_unfetched 33 STAT items:1:crawler_reclaimed 456 STAT items:1:crawler_items_checked 10240 STAT items:1:crawler_items_wasted 12.5 STAT items:1:crawler_reclaimed_expired 432 ... ENDitems:1:number表示该 slab class 的 item 数items:1:age表示这个 class 里最老 item 在缓存中存活的秒数。如果某个 class 的age特别大但evicted_time很小说明这个 class 空间紧张内部 LRU 一直在驱逐而最老的 item 很久没有被访问也没被淘汰这种状态往往意味着 key 分布不均匀。stats slabs输出每个 slab class 的存储结构信息stats slabs STAT 1:chunk_size 96 STAT 1:chunks_per_page 10922 STAT 1:total_pages 1 STAT 1:total_chunks 10922 STAT 1:used_chunks 152 STAT 1:free_chunks 0 STAT 1:free_chunks_end 10770 STAT 1:mem_requested 14592 STAT 1:get_hits 404715 STAT 1:cmd_set 3459 STAT 1:delete_hits 0 STAT 1:incr_hits 0 STAT 1:decr_hits 0 STAT 1:cas_hits 0 STAT 1:cas_badval 0 STAT 1:touch_hits 0 STAT active_slabs 3 STAT total_malloced 327660 ENDchunk_size是这个 class 每个内存块的大小chunks_per_page是一页默认 1MB能切分多少个 chunktotal_pages是分配给该 class 的页数。used_chunks是当前使用的 chunk 数free_chunks是已经被该 class 申请但没有被占用的 chunk 数free_chunks_end是当前页末尾还没被切分使用的空间。如果free_chunks长期为 0 而free_chunks_end很大说明这个 class 有可用空间但不够分配一整块内存碎片化明显。根因是 Memcached 的 slab 分配机制内存按 1MB 的页划分每页进一步切成固定大小的 chunk不同 slab class 的 chunk 大小按增长因子递增。比如默认 1.25 倍增长96、120、150、187……以此类推。一个 value 大小落在哪个区间就会被放到对应 class。如果 class 太小装不下数据Memcached 不会破块存储而是寻找更大的 class所以你会看到某些 class 的 chunk 大小远大于实际 value 体积这就是内存浪费的主要来源。3.2 stats cachedump 与 stats settings查 key 和看配置stats cachedump按 slab class 导出该 class 的 item 列表格式是stats cachedump 1 100 ITEM some_key [96 bytes; 1717999999 s] ITEM another_key [64 bytes; 1717999950 s] END括号里前面是 item 占用的字节数后面是最近访问时间的时间戳。但注意两点第一cachedump输出不保证完整它走的是 LRU 链遍历生产环境大实例下可能只输出部分第二在新版本中这个命令已经被标记为调试用途官方并不推荐在线上依赖它。想确认某个 key 是否存在直接用get key更靠谱。stats settings输出当前实例运行参数相当于把启动参数都回显一遍stats settings STAT maxbytes 1073741824 STAT maxconns 1024 STAT tcpport 11211 STAT udpport 11211 STAT verbosity 0 STAT oldest 0 STAT evictions on STAT domain_socket NULL STAT umask 700 STAT growth_factor 1.25 STAT chunk_size 96 STAT num_threads 4 STAT num_threads_per_udp 4 STAT stat_key_prefix : STAT detail_enabled no STAT reclaim 1 STAT hashpower_init 0 STAT item_size_max 1048576 STAT slab_chunk_size_max 524288 STAT max_item_size 1048576 STAT requests_per_event 50 STAT cas_enabled 1 STAT tcp_backlog 1024 STAT auth_enabled_sasl no STAT maxconns_fast 1 STAT slab_reassign 1 STAT slab_automove 1 STAT lru_crawler 1 STAT lru_crawler_sleep 100 STAT lru_crawler_tocrawl 0 STAT lru_maintainer 1 STAT lru_maintainer_sleep 0 STAT hot_lru_pct 20 STAT warm_lru_pct 40 STAT lru_segmented 1 STAT temporary_ttl 0 STAT idle_timeout 0 STAT watcher_logbuf_size 262144 STAT worker_logbuf_size 65536 STAT track_sizes 0 STAT wait_failover_time 0 END排障第一步先看stats settings确认当前实例的启动参数跟你预期一致。我曾经遇到过一台服务器上起了两个 Memcached 进程一个旧配置一个小内存负载均衡把请求分发到了小内存那个命中率怎么调都上不去最后就是通过stats settings里的maxbytes发现的。这里也推荐你记一下启动命令或者部署时统一用 systemd unit 文件避免两套 Memcached 配置混淆。3.3 各子命令实战场景对比这几个子命令的侧重点完全不同用哪个取决于你想回答什么问题。为了不让你到用的时候现翻我列一个对照表命令核心信息典型使用场景stats全局统计指标日常巡检、命中率计算、内存水位监控stats items每个 slab class 的 item 数量与驱逐情况定位热 key 导致的 LRU 驱逐、检查过期回收效率stats slabschunk 大小、内存页分配、碎片情况分析内存浪费、调整增长因子或 item 大小上限stats cachedump指定 class 的 key 列表查看某个 class 里有哪些 key、检查 key 分布stats settings实例运行参数确认配置、排查配置变更问题stats reset清空部分计数统计长时间运行后重置计数器便于观察新周期stats sizesitem 大小分布直方图需要确认 value 大小分布时使用这里单独说一下stats sizes这个子命令输出一个直方图格式类似STAT 96 15234 STAT 120 4500 STAT 150 233 ... END但它有个前提启动时要加-o track_sizes否则输出是空的或者提示不支持。我在生产环境一直开着这个选项因为它对分析缓存适合度很有帮助。如果某个区间堆积了大量 item而你的应用实际上只需要很小的 value那说明序列化之后的数据比预期大可能是把整个对象硬塞进去而没有做精简。4. 基于 stats 的典型故障排查实录4.1 缓存命中率持续走低命中率低最常见的表象是get_misses的速率明显高于get_hits。但具体原因要看场景。有一次我遇到的情况是命中率从 95% 掉到 60%跑 stats 发现curr_items没怎么变化但expired_unfetched涨得厉害。一问开发才知道他们把缓存过期时间统一设成了 5 分钟而业务的突发流量每 4 分钟一波。由于过期时间设计不合理大量 key 在刚过期后马上被重新请求全部触发 miss然后回源数据库。排查思路可以这样走先看get_misses和get_expired的占比。如果get_expired占比高就是过期策略问题调整 TTL 或者加一点随机抖动。如果get_misses高但get_expired不高再去看evicted_time。如果evicted_time很新说明是内存不足导致的驱逐 miss。还有一种情况是客户端发送的 key 集合变化太快比如每次请求都拼接带时间戳的 key这种不是调参能解决的要从业务侧下手。4.2 内存逐出激增导致缓存雪崩内存逐出是所有 Memcached 运维必须警惕的问题。evicted不断上涨说明新数据一直在抢占旧数据的位置。比较危险的是evicted_active持续走高这表示被逐出的 item 里有很大比例是最近还在被访问的数据相当于热数据被垃圾数据挤掉了后续请求又会把这些热数据重新写回来形成反复驱逐。我遇到过一次典型的雪崩场景某大促活动上线后大量一次性优惠券 key 打入缓存把原本的商品缓存全部挤出了。当时 stats 里evicted十分钟内增长了十几万evicted_active占比超过 30%同时业务方反馈响应时间暴涨数据库压力直接打满。解决思路有几层第一层是业务侧优化一次性 key 不要放进全局缓存或者使用更短的 TTL第二层是 Memcached 层面调大内存、优化 slab 分配第三层是在代理或网关层对非核心 key 做限流避免瞬时写入冲垮缓存。stats items可以帮你精确到是哪个 slab class 在被驱逐从而反推那条链路的数据。4.3 连接数异常与并发瓶颈连接问题通常先从curr_connections和rejected_connections看起。有一次线上服务报警Memcached 的rejected_connections开始增长listen_disabled_num也在涨。进一步看是因为客户端连接池设置的连接数总和超过了服务端max_connections。单个客户端看起来没多少连接但几十个服务实例乘起来就破千了。处理办法不是简单粗暴调大-c这只能缓解一时。更合理的做法是让客户端连接池保持长连接并限制空闲连接回收同时检查服务实例数量是否被过度水平扩展。connection_structures如果明显高于curr_connections说明 TIME_WAIT 状态的连接没有被及时回收可以看下系统层的 TCP 参数比如tcp_tw_reuse相关的设置但这属于系统调优范畴了不要随便在线上乱开。还有一个很容易忽略的点conn_yields表示单个 worker 线程在处理一个连接时主动让出 CPU 的次数这个值很高说明有大 value 的请求阻塞了事件循环。当某个 value 超大接近item_size_max默认 1MB时读写该 value 会长时间占用 worker 线程其它连接上的请求只能排队等待。这种问题在 stats 中直接看不到但conn_yields持续增长是重要信号一旦发现就要去排查应用里有没有存大对象。5. 实战脚本与监控集成经验5.1 一行命令快速观测核心指标命令行手工敲 stats 适合临时排查日常巡检最好还是写个小脚本。我自己的习惯是把下面这个命令存成 shell 函数用到的时候直接跑mem_stats() { local host${1:-127.0.0.1} local port${2:-11211} printf stats\r\n | nc $host $port | awk \ /STAT (curr_items|total_items|bytes|limit_maxbytes|get_hits|get_misses|evicted|evicted_time|curr_connections|reclaimed|expired_unfetched)/ {print} }输出大概是这样STAT curr_items 152345 STAT total_items 3456789 STAT bytes 734003200 STAT limit_maxbytes 1073741824 STAT get_hits 987654321 STAT get_misses 9080706 STAT evicted 19687 STAT evicted_time 154 STAT curr_connections 512 STAT reclaimed 876543 STAT expired_unfetched 65432如果想计算命中率和内存使用率可以写一段更完整的脚本。要注意的是 stats 输出里有小数和整数混在一起awk 计算时直接用浮点除法就行最后再用printf格式化两位小数。mem_summary() { local host${1:-127.0.0.1} local port${2:-11211} local data data$(printf stats\r\n | nc $host $port) local hits misses bytes limit hits$(echo $data | awk /^STAT get_hits /{print $3}) misses$(echo $data | awk /^STAT get_misses /{print $3}) bytes$(echo $data | awk /^STAT bytes /{print $3}) limit$(echo $data | awk /^STAT limit_maxbytes /{print $3}) awk -v h$hits -v m$misses -v b$bytes -v l$limit BEGIN { rate (h m) 0 ? h / (h m) * 100 : 0 mem l 0 ? b / l * 100 : 0 printf 命中率: %.2f%%\n, rate printf 内存使用率: %.2f%%\n, mem } }5.2 命中率计算的完整公式与临界值很多人只算get_hits / (get_hits get_misses)但在 Memcached 的统计里get_misses包含get_expired和get_flushed。如果你只想看“有效 miss”可以进一步细分。不过实际排障我一般先看总命中率再单独看get_expired的绝对数值。假设get_hits是 900get_misses是 100其中get_expired占 60那么纯正的 key 不存在 miss 只有 40。这种情况下你该优化的是过期时间策略而不是数据加载逻辑。什么算健康我的经验值读多写少的缓存场景命中率长期维持在 95% 以上才算正常数据频繁过期或写入量很大的场景80% 到 90% 也可以接受低于 70% 基本能断定缓存设计有问题需要重点排查 key 的复用度和 TTL 设置。注意这只是一般经验具体业务模型不同会有波动最好以基线数据为准。监控上还有一个容易被忽略的指标curr_items与limit_maxbytes的比值。如果每个 item 平均占用很小但数量巨大可能导致哈希表膨胀、遍历 LRU 变慢。反过来如果 item 数量不多但单个价值很大内存浪费就成了主要矛盾。这时用stats sizes看分布最直观。5.3 监控告警的合理阈值设置接入监控系统的时候不建议直接对原始值设阈值因为get_hits这类累计计数器跟运行时长强相关涨上天也不代表有问题。更好的做法是把它们转成速率或者比率再告警。我在 Prometheus 体系里通常采集这么几个指标命中率低于阈值持续 5 分钟内存使用率超过 90% 并且继续上升evicted_active速率突增比如过去 5 分钟的驱逐量超过基线 3 倍rejected_connections大于 0listen_disabled_num大于 0curr_connections超过max_connections的 80%导入 Prometheus 最省事的办法是直接用memcached_exporter它本质上就是在后台不断调用 stats 命令把指标拉出来然后转换成监控指标。如果你已经有现成的二进制监控方案也可以自己在客户端里定时执行stats解析上报效果一样。告警阈值怎么定我建议先观察一周的正常业务水位把日均峰值和低谷记下来阈值设在峰值的 1.5 倍到 2 倍之间。比如连接数正常峰值为 400告警线可以设在 600 到 800低于这个值容易误报高于这个值又可能错过隐患。另外驱逐量的告警要结合业务大促等场景动态调整固定阈值在活动期间基本天天误报。注意stats reset会把大部分累计计数归零生产环境慎用。如果你确实需要一段干净的数据来观察问题我建议挑业务低峰期执行并事先告知相关同事避免影响监控系统的历史曲线。6. 进阶使用技巧与版本变化提醒6.1 通过文本协议手工模拟 stats 调用有时候线上环境没有 nc、没有 telnet 客户端甚至没有完整的 Python 环境。这时候可以用 bash 的/dev/tcp特性部分发行版默认支持exec 3/dev/tcp/127.0.0.1/11211 printf stats\r\n 3 cat 3 exec 3- exec 3-这段脚本做的事和 nc 完全一样只是走的是 bash 内置的伪设备。注意某些安全加固过的系统会禁用/dev/tcp那就换其他方式。C 语言或者 Go 里实现起来也不难关键是记得\r\n结尾和读到END才算响应完成。6.2 版本差异导致的字段变化Memcached 演进过程中stats 输出一直在变。1.4 时代还没有expired_unfetched、evicted_active这些细分字段1.5 引入 LRU 维护线程后增加了一大批lru_*和moves_*相关统计1.6 又加了log_*字段。如果你在网上搜到一篇老文章对不上字段先确认一下版本不要照搬。stats items里比较新的字段是crawler_reclaimed_expired和crawler_items_checked它们来自后台 LRU crawler 线程对过期 item 的清理。crawler_reclaimed表示 crawler 线程实际回收的过期 item 数量。这个数字如果一直在涨说明系统有好多过期 item 在等待被清理配合lru_crawler_running为 1 表示当前 crawler 正在工作这是正常现象。6.3 用 stats 分析真实业务场景举一个实际发生过的例子某个服务的 value 是用户信息 JSON大小在 200 字节到 2KB 之间。上线一段时间后发现内存使用率很高但命中率正常。我用stats slabs看了一下发现大量 item 集中在 chunk size 为 1024 的 class 里而实际 value 平均只有 300 字节这意味着每个 item 浪费了约 70% 的内存。原因在于增长因子 1.25 的阶梯太宽200 到 300 字节的 value 会直接跳到 384 甚至 512 的 chunk而如果调整增长因子到 1.15这个区间会更平滑但也不是越小越好因为 slab class 数量有限增长因子过小会导致每个 class 的 chunk 大小差异不明显反而让大 value 需要跨多个 class 找位置。最终我们选择保留默认增长因子但业务侧做了 value 压缩和字段精简把平均体积压了下来内存使用率立刻降了 20%。再举一个应用报错说缓存读取超时我用 stats 看curr_connections正常conn_yields却高得离谱。进一步排查发现有个接口会把一张几 MB 的图片 base64 后塞进缓存远超默认的 1MBitem_size_max写入请求一直被拒绝而且每次尝试写入都引发了 worker 线程的长时间阻塞。最后改成了图片对象存储缓存只存 URL问题彻底解决。7. 一些个人使用经验与避坑建议写到这里基本上把 stats 命令的每个角度都过了一遍最后再分享几个我个人的使用习惯。第一stats 输出里的time和uptime看似无用其实非常关键。我遇到过两次诡异问题一次是缓存命中率在某个时间点突然暴跌查遍代码没找到原因后来发现是 devops 平台自动重启了 Memcached另一次是两台机器上的 Memcached 版本不一致导致行为差异用version字段一下就定位了。养成看到 stats 先扫一眼这几个基础字段的习惯能省很多事。第二不要依赖stats cachedump去做 key 遍历。这个命令在大数据量下效率差而且输出不完整还会给实例增加额外负担。真要遍历 key用lru_crawler相关的调试手段或者直接在设计层面记录 key 前缀列表都比在线扫描稳妥。第三每次变更完配置后都要重新跑一遍 stats 确认。比如调大-m后limit_maxbytes的数值是否更新调整连接数后stats settings里的maxconns是否生效。Memcached 有些参数是启动时一次性加载的热改接口有限别想当然以为改了启动参数重启就完事重启前做好数据失效预案。最后把 stats 的关键指标沉淀成基线。第一次部署完 Memcached 后记录正常运行时的命中率、连接数、内存使用率、驱逐情况之后每次排障先跟基线对比。没有基线的监控只能说是在碰运气。有了基线很多问题一眼就能看出偏离比临时查文档高效得多。Memcached 不复杂但 stats 命令给了你一个非常完整的内部视野。多花点时间把这几十个字段的含义吃透再结合命令行的灵活组合很多看起来玄乎的缓存问题几分钟内就能理出头绪。