news 2026/10/1 3:12:47

Linux下memcached实战指南:从缓存原理、安装配置到高并发调优

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Linux下memcached实战指南:从缓存原理、安装配置到高并发调优

Linux下面做性能优化、扛高并发,memcached早晚绕不开。这篇博文是我在实际运维和项目开发中反复使用memcached之后整理出来的入门实战笔记,从原理、安装到命令行操作、项目接入、调优排错,一条线讲清楚,新手看完能直接上手干活,老手也能查漏补缺。

1. memcached到底是什么,为什么要学它

1.1 先说清楚它是干什么的

memcached是一个开源的高性能分布式内存对象缓存系统,本质就是一个住在内存里的键值对数据库。你在项目里把热点数据提前塞进去,等真正要读的时候直接从内存拿,不再每次打数据库,这样数据库压力小一大截,接口响应速度也快得多。

它最早是LiveJournal这个网站搞出来的,后来被Facebook、Wikipedia这些流量大户大规模使用。Linux服务器上部署memcached几乎是后端架构的标配技能,跟Nginx、Redis一样属于基础中间件。

我见过很多刚入门的同学把memcached想得太玄乎。其实它的核心就三件事:存、取、过期。存就是set,取就是get,过期就是你设一个存活时间,到了之后自动淘汰。所有高级用法都是从这三件事延伸出来的。你把这个底层逻辑吃透,后面不管接什么语言的项目心里都有底。

1.2 它跟Redis的区别,怎么选

网上讨论最多的就是memcached和Redis的区别,我直接用一张表说明,方便新手上路时做选择:

对比项memcachedRedis
数据类型仅支持字符串键值String、Hash、List、Set、ZSet等
持久化无持久化,重启即丢支持RDB和AOF持久化
集群模式客户端一致性哈希,服务端无集群官方Cluster模式
多线程多线程处理网络IO默认单线程事件驱动
内存管理Slab Allocator+LRU多种内存淘汰策略
适用场景单纯缓存热点数据缓存+队列+分布式锁等

并不是Redis一定比memcached好。如果你的业务就是纯缓存、对数据结构要求简单、希望多核CPU把吞吐量压到极致,memcached依然是很务实的选择。很多老牌高并发系统跑了好多年还是memcached,稳定得很。

1.3 学它之前需要哪些Linux基础

想顺利走完这篇入门,你最好已经掌握几个Linux基本功:会用终端敲命令、会用vim编辑文件、会用systemctl管理服务,懂一点网络端口的基本概念。如果这些还不熟,建议先去把Linux常用命令过一遍,比如grep、ps、netstat、systemctl、firewall-cmd这些,否则后面操作会卡壳。

此外最好知道一点简单的TCP/UDP概念,因为memcached默认走TCP端口11211,后面我们排查连接问题时会用telnet、nc这类工具去手动模拟协议交互,不懂端口和套接字的基础知识,排查起来会像无头苍蝇。

2. 环境准备与安装部署

2.1 CentOS/RHEL系安装memcached

在CentOS或者Rocky Linux这类RHEL系发行版上,安装memcached直接走yum/dnf仓库最省事:

sudo dnf install -y memcached libmemcached

装完之后先看一眼版本,确认装上了:

memcached -version

我之前遇到过一种情况,部分精简安装的Linux服务器默认仓库里没有memcached包,这时候先执行sudo dnf makecache刷新元数据,再安装一般就能找到。如果还是不行,可以用EPEL源,先sudo dnf install epel-release再装。

libmemcached这个库强烈建议一起装,它是一个C语言客户端库,后面我们用memcstat、memccat这些工具排查数据时要用到,省得自己封装协议。

2.2 Ubuntu/Debian系安装

Ubuntu系用apt安装同样简单:

sudo apt update sudo apt install -y memcached libmemcached-tools

装完Debian系之后默认服务是自动启动的,你可以直接检查一下端口:

sudo systemctl status memcached ss -lntp | grep 11211

看到11211端口在监听了,说明已经跑起来了。我建议不管哪个发行版,装完都顺手执行一次memcached -h,把帮助信息扫一遍,里面有不少默认参数,对理解配置很有帮助。

2.3 用源码编译安装的方式

如果生产环境对版本有强制要求,或者官方仓库版本太老,就得走源码编译。我从实践角度说,编译安装最核心的依赖是libevent,memcached是基于libevent做的事件驱动模型,这个库必须先搞定。

# 安装编译需要的工具和依赖 sudo dnf install -y gcc make libevent libevent-devel # 下载源码包,这里假设版本号例如1.6.21 wget https://memcached.org/files/memcached-1.6.21.tar.gz tar zxvf memcached-1.6.21.tar.gz cd memcached-1.6.21 # 配置、编译、安装 ./configure --prefix=/usr/local/memcached make -j4 sudo make install

这里有个坑,如果./configure时报找不到libevent,说明devel包没装全。RHEL系是libevent-devel,Ubuntu系是libevent-dev,别搞混。

2.4 配置文件详解与建议参数

rpm或apt方式安装的memcached,配置文件一般在/etc/sysconfig/memcached(CentOS系)或者/etc/memcached.conf(Debian系)。CentOS系默认内容大致如下:

PORT="11211" USER="memcached" MAXCONN="1024" CACHESIZE="64" OPTIONS="-l 127.0.0.1,::1"

这几个参数我用大白话解释一下:

  • PORT:监听端口,一般默认11211不用改。
  • USER:以哪个系统用户身份运行,绝不建议用root,权限最小化原则。
  • MAXCONN:最大连接数。连接数满了之后新的连接直接拒绝,表现为客户端报连接超时。
  • CACHESIZE:单位是MB,这个就是memcached最多能吃掉多少内存。默认64MB太小了,生产环境一般按机器内存的1/4到1/3分配,绝不是越大越好,要留内存给系统本身和应用程序。
  • OPTIONS:额外的启动参数,-l指定监听地址。如果只跑本机,监听127.0.0.1就够了;需要给其他服务器提供缓存服务,就监听内网IP。

我这里放一个生产环境常用的配置参考:

PORT="11211" USER="memcached" MAXCONN="4096" CACHESIZE="1024" OPTIONS="-l 0.0.0.0 -U 0 -m 1024 -t 4"

其中-U 0表示关闭UDP端口。memcached默认同时开UDP 11211端口,这个端口历史上出过放大攻击漏洞,非必要直接关掉。-t 4是启用4个工作线程处理网络请求。

2.5 systemd管理memcached服务

改完配置后需要重启服务让配置生效:

sudo systemctl restart memcached sudo systemctl enable memcached

检查服务是否正常运行,我习惯用三步确认法:先看进程状态,再看端口监听,最后实际set一个键验证:

systemctl status memcached ss -lntup | grep 11211 printf 'set health 0 0 2\r\nok\r\n' | nc 127.0.0.1 11211

nc命令这里如果没装,可以用telnet 127.0.0.1 11211手动连上去做同样的操作。最后一步能在命令行里跟memcached直接对话,非常直观,这也是我强烈建议新手掌握的调试技巧。

防火墙方面,如果开启了firewalld,需要放行11211端口:

sudo firewall-cmd --permanent --add-port=11211/tcp sudo firewall-cmd --reload

但这里我多提醒一句:除非业务架构真的需要跨服务器访问,否则别把memcached暴露在非必要的位置。缓存里可能有用户会话、商品数据,一旦裸奔被扫到,数据泄露或者被刷流量都是很麻烦的事。

3. 核心操作:命令行与文本协议实战

3.1 手动用telnet/nc对话memcached

memcached有一个非常朴素的文本协议,直接用telnet或者nc就能模拟客户端操作。我先把最常用的一组命令列出来:

命令作用示例
set新增或覆盖一个键值对set name 0 60 5
add仅当键不存在时新增add name 0 60 5
replace仅当键已存在时覆盖replace name 0 60 5
get读取一个或多个键get name age
delete删除一个键delete name
incr/decr对数值类型的值做原子自增/自减incr num 1
stats查看统计信息stats
flush_all清空所有数据flush_all

set命令的具体格式要注意,它实际上有四行协议交互。假设我要存一个name,值为Tom:

telnet 127.0.0.1 11211 Trying 127.0.0.1... Connected to 127.0.0.1. Escape character is '^]'. set name 0 60 3 Tom STORED

set name 0 60 3这四个字段拆开看:name是键名,0是标志位(客户端用来区分数据类型,memcached本身不关心),60是存活时间(单位秒),3是value的字节数,必须跟实际字节数一致,多一个少一个都会报错。下一行直接输入Tom,回车,返回STORED就说明存成功了。

这地方新手最容易栽跟头的是字节数数错。字符串Tom刚好3个字节没问题,但如果内容是中文,比如张三,在UTF-8编码下是6个字节,这时候写set user 0 60 6然后输入张三,才会返回STORED。我见过有人在这个细节上卡了半小时,一直报CLIENT_ERROR bad data chunk。

3.2 get查询和delete删除

在telnet里执行:

get name VALUE name 0 3 Tom END

返回结果里VALUE name 0 3就是告诉你有这个键,标志位是0,数据长度是3字节。紧接着下一行是真正的数据,然后以END结束。如果get一个不存在的键,直接返回END,不会有VALUE行。

删除操作更简单:

delete name DELETED

如果键不存在,会返回NOT_FOUND。实际项目中,delete常用于清除缓存,比如后台改了文章标题,就要立刻把前台对应的缓存删掉,防止用户读到脏数据。

3.3 incr/decr做原子计数

memcached的incr/decr命令在秒杀、限流、统计这类场景非常实用。存一个数值需要先set,注意存活时间设为0表示永不过期:

set visitor_count 0 0 1 5 STORED incr visitor_count 1 6 get visitor_count VALUE visitor_count 0 1 6 END

你可能会问,为什么不用get然后程序里+1再set回去?因为那两步操作之间有间隙,高并发下两个请求同时读到旧值,就会丢失一次自增。incr是服务端原子操作,不管多少请求同时打过来,最后的结果一定是准确的。这个特性在做计数器功能时比读改写稳妥得多。

3.4 过期时间的几种写法

set命令第三个参数是过期时间,写法上有两种方式:

  • 0到2592000之间的数字,表示相对时间,单位秒,意思是从现在开始多少秒后过期。
  • 超过2592000的数字,会当作Unix时间戳处理,表示到某个绝对时间点过期。

为什么是2592000这个数?因为memcached内部约定30天以内的当作相对秒数,超过30天当作绝对时间戳。你不小心把过期时间设成3600000这种大数字,本意是1小时,结果它被解释成了时间戳,缓存瞬间就过期了。我专门在代码评审时抓出过这种bug,新手值得留意。

另外,过期时间不一定被立即物理清除。memcached用的是惰性过期,就是说一个键过期了,但占用的内存并不会马上释放,而是等到下次访问这个键时发现过期,才会标记为可复用。还有一种情况是内存不够时由LRU算法被动淘汰。理解这个机制,就能明白为什么有时stats里的curr_items不会立刻降下来。

3.5 stats命令快速判断缓存健康度

对运维来说,stats信息是判断缓存系统是否健康的仪表盘。我在排查问题时的第一件事就是敲stats:

stats STAT pid 12345 STAT uptime 3600 STAT curr_items 1024 STAT total_items 2048 STAT get_hits 5000 STAT get_misses 1000 STAT delete_misses 100 STAT bytes 1048576 STAT curr_connections 5 STAT total_connections 100 STAT cmd_get 6000 STAT cmd_set 2048 STAT evictions 10 STAT limit_maxbytes 1073741824 END

这里重点关注两个指标:命中率(get_hits/cmd_get)和驱逐数(evictions)。命中率太低说明缓存利用率不行,可能key设计有问题或者缓存预热不够。evictions大于0说明内存不够装了,旧的key被强制踢出去,这时候该考虑扩容缓存内存或者优化缓存策略了。计算命中率的公式是get_hits/(get_hits+get_misses)*100%,我一般以90%为健康分界线。

还有一个stats cachedump 1 100命令可以用来枚举某个slab类里的key,但这个命令在生产环境要慎用,它会遍历整个类别的数据,量大时对性能有影响,调试环境用用就好,生产我基本不用它。

4. 实际项目中的使用套路

4.1 缓存读写的经典流程

到了真实项目中,我们不会直接拿命令行操作memcached,而是通过PHP、Python、Java这些语言的客户端库去调用。但不管用什么语言,代码里的读写逻辑长成一个套路,也就是面试喜欢考的缓存范式:

第一步,先查缓存。第二步,缓存没有命中,就查数据库。第三步,数据库查到结果之后,回写一份到缓存,同时设置过期时间。第四步,返回数据给调用方。

我用伪代码描述一下这个流程:

def get_user_profile(user_id): cache_key = "user:profile:%s" % user_id data = mc.get(cache_key) if data: return data data = db.query("select * from user where id=%s", user_id) if data: mc.set(cache_key, data, time=300) return data

这段代码看起来简单,但里面藏了好几个细节。第一个是缓存key的设计必须有统一规范,:分隔的业务前缀很有讲究,user:profile:123比裸写123好一万倍,既能避免键冲突,又方便出问题时用工具精确删除某类缓存。第二个细节是设置过期时间时要加一点随机扰动,比如300秒你写成300 + random.randint(0, 60),防止大量key在同一时刻集中过期,导致缓存雪崩。

4.2 缓存穿透、击穿、雪崩的应对思路

使用缓存必然要考虑三个经典问题,我把它们和memcached场景对应起来:

缓存穿透是指查一个必然不存在的数据,比如查一个已被删除的文章ID。因为缓存和数据库里都没有,每次请求都会穿透到数据库,高并发下数据库可能被压垮。应对方案有用布隆过滤器提前拦截,或者在查到空结果时也往缓存里写一个占位符,比如把空值缓存一分钟,而这个空值的过期时间要设置得比正常数据短。

缓存击穿是指某一刻某个热点key恰好过期,同时大量需要这个key的请求涌入,结果全部打到了数据库。解决办法一般有两种思路:一是用互斥锁,只允许一个请求去重建缓存;二是把热点key的过期时间设成永不过期,然后由后台任务主动更新值,这个方案在集中式缓存系统里更稳。

缓存雪崩是指大量key在同一时间段失效,导致一批请求全部到达数据库。原因是前面说的过期时间太整齐,解决办法也很简单,初始化缓存时把过期时间打散,加一个随机偏移量。

这三个问题面试常问,工作中也一定会遇到,我的建议是不要死记概念,画一下请求流向图,搞清楚数据在哪个环节断档,自然会知道在哪个环节补措施。

4.3 PHP项目接入memcached

PHP接入memcached用得最多的是php-memcached扩展,它基于libmemcached,功能比较全。安装方式在CentOS上:

sudo dnf install -y php-pecl-memcached

代码示例,这是我在实际项目里常用的写法:

$mc = new Memcached(); $mc->addServer('127.0.0.1', 11211); $key = 'product:detail:1001'; $detail = $mc->get($key); if ($detail === false) { // 从数据库读取 $detail = getProductDetailFromDb(1001); $mc->set($key, $detail, 300); }

这里有个注意点,$mc->get($key)返回false有两种情况,一种是缓存不存在,另一种是缓存的值恰好就是false。所以如果你要缓存的值本身就是布尔型false,建议在外面包一层数组或者用字符串'0'避免歧义。另一种更稳妥的写法是先用$mc->getResultCode()判断是否命中:

$detail = $mc->get($key); if ($mc->getResultCode() === Memcached::RES_NOTFOUND) { // 确实没有缓存,读数据库 }

PHP的Memcached类还支持设置多个服务器,做一致性哈希分布:

$mc->addServers([ ['192.168.1.10', 11211], ['192.168.1.11', 11211], ['192.168.1.12', 11211], ]); $mc->setOption(Memcached::OPT_DISTRIBUTION, Memcached::DISTRIBUTION_CONSISTENT);

多服务器时一致性哈希能尽量减少增删节点时key重新映射的数量,不然某台缓存服务器宕机,会导致大量缓存key失效,数据库压力瞬间飙升。

4.4 Python和Java接入简例

Python生态里最常用的客户端是pymemcache和python-memcached。我推荐pymemcache,纯Python实现,维护状态好,而且支持连接池:

from pymemcache.client.base import PooledClient client = PooledClient('127.0.0.1', 11211, max_pool_size=10) client.set('foo', 'bar', expire=60) value = client.get('foo')

pymemcache的set过期时间参数是expire,单位秒。还有一点要注意,pymemcache序列化时默认不处理Python对象,你要自己传字符串或者字节,实际项目中我一般会配合json做序列化,用serializer参数指定,目前pymemcache提供pickle和json的序列化模块,需要显式声明。

Java接入则最常用spymemcached或xmemcached。我举一个spymemcached的例子:

MemcachedClient client = new MemcachedClient( AddrUtil.getAddresses("127.0.0.1:11211") ); client.set("foo", 60, "bar"); Object value = client.get("foo");

Java客户端相对重一些,但功能完善,内置了连接池和失败重连机制。这里提醒一下,Java里使用set时存活时间是秒,但有些客户端版本接口签名不同,有的是int秒,有的是long毫秒,建议写代码前先看一眼客户端接口定义,别想当然。

4.5 缓存与数据库的一致性处理

使用缓存绕不开一个问题:数据库更新了,缓存怎么办。常见的策略是Cache Aside模式,也就是更新数据库时直接删缓存,再等下次读请求来时回填。

也许你会想,为什么不更新数据库的同时也更新缓存?因为更新缓存的时机很难把握,高并发下两个线程同时更新同一个key,可能出现数据库先更新的值后写进缓存,造成新旧数据互相覆盖。删缓存虽然也是在数据库操作后执行,但它不怕覆盖问题,因为下次读时缓存不存在,自然会把最新数据写进去。

更稳妥的做法是延迟双删,先删一次缓存,再更新数据库,最后休眠几百毫秒再删一次。这个方案主要针对极端情况:第一个删缓存后,有旧请求把旧数据回填了缓存,然后数据库更新,第二次删除就清掉了这份旧数据。实际生产里,如果业务允许极短时间的不一致,简单的一次删缓存加expire兜底也够用。

5. 性能监控与参数调优

5.1 内存管理机制:Slab Allocator

memcached的内存管理不同于常规的malloc直接分配,它用了一套叫Slab Allocator的机制。刚运行时,memcached把分配的内存划分成一系列slab类别,每个类别里又分出固定大小的chunk。比如slab类1的chunk是96字节,slab类2是120字节,按倍数递增,最到最大chunk上限。

存入一个100字节的数据时,memcached会把它放进chunk大小能容下它的最小slab类,比如120字节的chunk。这个机制的优点是避免了频繁分配和释放内存碎片,缺点是会造成内存浪费。如果你大量存入的是1KB大小的数据,但memcached的slab增长因子是1.25,它可能被分配到1.5KB或者2KB的chunk里,浪费率就比较明显。

在实际项目中,如果数据大小差异特别大,比如既有几十字节的小key又有几百KB的大value,可以考虑调整-f增长因子,或者干脆拆分业务,小key和大文件各自用独立的memcached实例,内存利用率会高不少。

5.2 evictions与LRU

当memcached内存满了之后,新的写入不会报错,而是通过LRU算法把最久没用的数据淘汰掉。stats里evictions字段就是被淘汰的数据条数,它和过期不一样,过期是时间到了,淘汰是内存不够被强制挤掉。

我的经验是,当evictions开始稳定增长,第一反应不是加内存,而是检查是不是有些缓存key永远不会再被读取了,比如脏数据或者无效会话。把无用key清掉,比无脑加内存更理性。如果你确认所有缓存数据都是高价值热点,再考虑调大-m参数。

memcached还提供了LRU爬虫机制,默认情况下过期数据会等被访问时才清除,但你可以开启-o lru_crawler参数让后台线程主动扫描并回收过期key占用的空间。开启后能一定程度减少过期数据积压,但会带来一点额外CPU消耗,机器资源紧的服务器要权衡。

5.3 线程模型与连接数调优

memcached虽然是多线程处理网络事件,但要注意,它的线程模型是多个线程分别处理不同的连接,不是多线程处理同一个连接上的命令。所以调整-t参数时,socket连接会被分发到不同线程,单个连接的命令还是串行执行的。

stats里的curr_connections如果长期靠近MAXCONN上限,先检查是不是有客户端忘了关连接。很多语言里每次请求如果都new一个client而不是复用连接,很容易把连接数打满。这时候优化方向是使用连接池,而不是盲目调大MAXCONN。连接数开得过大,每个连接都有接收缓冲和发送缓冲,内存会被无谓吃掉。

5.4 命中率优化实战

命中率是缓存系统最核心的指标。我手头一个真实case,原先某个查询接口的命中率只有70%,数据库压力很大。分析后发现问题出在缓存key的设计上,接口用的是user:list作为key,根本没有把分页参数带进去。也就是说,用户翻第1页和第100页,缓存里都是同一份数据,而这份数据只能展示默认首页的内容。

把key调整成user:list:page:1:size:20之后,每个分页都有自己的缓存,命中率直接升到95%。类似的情况还有很多,比如查询条件里不同排序维度、不同省份、不同时间段,都应该体现在缓存key里。

5.5 用memcstat和memcslap做体检

安装了libmemcached-tools之后,可以用memcstat快速查看运行状态:

memcstat --servers=127.0.0.1:11211

它会把stats信息整理成可读格式,比自己在telnet里看舒服。还有一个memcslap工具,可以用来自带压测:

memcslap --servers=127.0.0.1:11211 --concurrency=100 --execute-number=10000

它会模拟并发请求打set和get,输出QPS和响应时间统计。这是我在上线缓存服务之前必做的基础压测,确认机器承受得起预期流量。压测结果如果QPS远低于预期,优先检查网络协议栈、连接数和实例本身的内存分配,不要一开始就怀疑memcached性能差,很多瓶颈其实在客户端侧。

6. 常见问题与排查技巧实录

6.1 问题速查表

我在这个部分把实际踩过的坑整理成一张表,遇到类似问题可以先对照:

问题现象可能原因排查与解决
客户端连接被拒绝监听地址不是客户端能访问到的地址;防火墙未放行检查-l参数和firewalld规则,确认11211端口可达
能ping通但连不上11211UDP关闭后TCP未监听;服务未启动ss -lntp看监听列表,systemctl status看服务状态
set返回SERVER_ERROR out of memory内存配置过小或者数据量超出查看stats的limit_maxbytes和bytes,评估是否调大-m
set返回SERVER_ERROR object too large for cachevalue超过单条最大限制(默认1MB)压缩大value,或者调大-I参数,一般建议压缩而不是调大
缓存get不到数据但数据库有key拼写不一致;过期时间太短用命令行手工get验证key,检查代码里key的拼接逻辑
缓存数据不更新代码只写缓存不删缓存;没有过期时间采用Cache Aside模式,更新数据库后主动delete缓存key
高并发时数据不准用了get后修改再set,非原子操作改用incr/decr做原子计数
stats中evictions持续增长内存不足,LRU被迫淘汰清理无效key,调整过期时间,再考虑扩容
重启服务后缓存全部丢失这是memcached正常行为,不持久化前置做缓存预热,或者把需要长存的切到Redis
get返回false但代码判定不存在PHP里值本身就是false导致歧义用getResultCode判断是否RES_NOTFOUND

6.2 排查实录一:连接超时问题

有一回线上某个接口突然变慢,日志里全是Connection timed out。我先看了memcached的stats,发现curr_connections已经顶到1024上限了,而total_connections增长异常快,说明有客户端在不断建立新连接。

后来定位到是某个Java服务升级了连接池配置,默认把超时时间从1000ms改成了100ms,导致在访问高峰时连接池里的连接还没来得及返回就直接超时报错,于是服务频繁重建连接,连接数越堆越多,最终把memcached连接数打满。解决办法是把超时时间调回合理值,同时给连接池设置足够的最大空闲连接。这个案例让我养成了一个习惯:每次改缓存客户端的参数,都要看一遍curr_connections和total_connections的变化趋势。

6.3 排查实录二:缓存穿透把数据库打挂

另一个case是商品详情页在秒杀活动开始瞬间数据库CPU飙升到100%。现象是缓存里根本没有那些活动商品的key,每次请求都直接打到MySQL。

排查后发现,这批活动商品在管理员后台批量上架时,代码没有做缓存预热。活动开始后大量用户瞬间访问新商品,缓存里没有数据,于是全部穿透到数据库。处理方案分为两步:第一步在后台商品上架时主动把商品详情写入缓存,提前预热;第二步对查询不到的key,在缓存里也写一个短暂的空值占位,防止恶意遍历或哈希攻击穿透。这两个措施加上之后,数据库CPU马上就下来了。

6.4 排查实录三:内存碎片与Slab分配失衡

有一台memcached服务器内存配置了2GB,但每次压测到一定程度时set就开始报错。我用stats slabs查看各slab类的chunk使用情况,发现某个slab类的chunk分配了大量内存但命中率极低,而另一个slab类内存不足导致大量evictions。

原因是我们把几百万个小session和一个大JSON混在同一个memcached实例里,小数据占的slab类别和大数据占的slab类别内存隔离,大JSON的slab类因为chunk大,数量不多就吃光了内存。解决方案是把不同大小的数据拆成两个memcached实例,分别配置不同的-f增长因子,session把-f调到1.15,大JSON用默认1.25。拆分后两个实例的内存利用率都健康了,set错误也消失了。

6.5 安全加固建议

前面说了UDP端口默认关闭,这里再补充几条安全加固措施。第一,监听地址尽量不对外网公开,如果业务方必须跨网络访问,限制源IP为已知服务网段,在云安全组层面做访问控制。第二,禁止在公网环境下无认证部署memcached,它本身没有内置认证机制,暴露出去等于给全网开放了读写权限。第三,如果希望加认证,可以考虑SASL机制,配置比较繁琐但能挡住大部分未授权访问需求。第四,定期检查stats里的连接来源,如有异常连续建立连接的来源IP要及时封禁。

7. 从一个memcached到缓存架构演进

7.1 什么时候该引入缓存代理层

业务体量小的时候,客户端直连memcached服务器没什么问题。但当服务器数量变多,客户端需要感知所有缓存节点的变化时,管理成本就会显著上升。我见过一个项目里,业务方在代码里维护了十几台缓存节点,每上下线一台都要改配置发版本,非常痛苦。

这个阶段可以考虑引入一层代理,比如twemproxy或者更现代的方案。代理层负责把key分布到不同缓存节点的路由逻辑,客户端只跟代理通信,节点变动时只需要在代理层配置热加载,业务代码零改动。代价是代理层本身成为新的故障点,需要部署多台冗余。

7.2 egg缓存抽取与复用

再往后走一步,缓存架构优化不只能靠堆机器,还可以把缓存key的生成规则和失效策略统一成一个单独的模块。我经常建议团队做一层通用的缓存封装,对外暴露get(key, expire, loaderFunction),内部逻辑固定:先查缓存,没有就执行loaderFunction读数据库,拿到结果写缓存。这样业务代码里不再散落set/get/delete逻辑,长此以往维护成本会低很多,排查问题时也只需要看封装层。

7.3 与Redis共存的场景拆解

很多团队最终会同时使用memcached和Redis,而不是二选一。我目前维护的系统就是这么做的:memcached负责承载高吞吐的纯KV热点数据,比如用户session、查询结果缓存;Redis负责需要数据结构的场景,如排行榜、队列、分布式锁、布隆过滤器。

这种分工的原因很实际:memcached在纯KV读写场景下延迟极低且多线程扩展性好,代码简单不容易出错;而Redis的数据结构丰富,可以替代更多手工编码逻辑。运维上两类中间件分别监控,memcached关注命中率和驱逐数,Redis关注内存使用率和持久化策略,故障定位起来反而更清晰。

7.4 Quit命令与连接优雅关闭

最后提一个容易被忽略的细节,文本协议里还有一个quit命令。客户端在不需要连接时应该发送quit,或者直接关闭socket,而不是让连接挂着等服务端超时。不礼貌的客户端连接会占用curr_connections,最终影响其他正常业务的连接。

用telnet测试完我习惯输入quit退出,生产代码里客户端连接池会自动管理空闲连接,但如果你写自定义脚本去连memcached,记得在结束前关闭连接。这些都是细节,但细节才见经验。

7.5 从入门到进阶的路径建议

如果看完这篇你打算深入下去,我的建议路线是这样的:先用stats里的各项指标把内存计算清楚,会看slabs的增长情况,再试着给现有缓存写一个命中率统计脚本,然后模拟缓存穿透和雪崩场景做一次应急预案演练。等这套跑通了,可以说你已经具备日常运维和调优memcached的基本功了。另一步可以继续研究内存分配器源码实现,比如LRU队列、slab划分逻辑、crawler机制,这些都是高性能缓存的经典设计,值得反复琢磨。

我这个操作过程中最大的体会是,别急着上多复杂的架构,先把一台memcached的安装、命令行、统计数据吃透,再去谈分布式、集群、代理层。缓存系统的问题往往不在性能,而在使用姿势和数据设计,把这两件事做对了,绝大多数问题都不会主动找上门。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/1 3:12:47

SpringBoot+Vue+MyBatis前后端分离人事系统开发部署实战

前后端分离人事系统,听起来像是教科书里的课程设计,但真正把这套东西从零跑到线上的人都知道,它背后涉及的不只是代码,而是一整套完整的前后端协作模式。SpringBoot负责接口,Vue渲染页面,MyBatis操作MySQL&…

作者头像 李华
网站建设 2026/10/1 3:12:33

Linux /etc/passwd 字段详解与账号管理实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/1 3:12:12

GOOSE-KELM故障诊断:生物启发式超参优化方法

简介:本资源是一套基于Matlab实现的GOOSE-KELM鹅算法优化核极限学习机(KELM)的故障诊断完整方案,面向计算机、电子信息工程及数学等专业的本科生与研究生,适用于课程设计、期末大作业及毕业设计等实践场景,…

作者头像 李华
网站建设 2026/10/1 3:11:50

从“无标题”到项目落地:模糊创意的实战推进指南

前两天整理旧项目文件夹,翻到一堆命名为“未命名文档”“新建文件夹(3)”的烂尾工程,其中一个文件夹里还躺着当年信誓旦旦要做完的产品原型。盯着那个“无标题”的文件夹,我突然意识到一个很扎心的事实:我们…

作者头像 李华
网站建设 2026/10/1 3:11:45

自动驾驶交通物体检测数据集实战指南

简介:本资源是面向自动驾驶算法工程师与计算机视觉学习者的多类别交通物体检测数据集,专为YOLO系列模型(含YOLOv12等新版本)训练与验证设计,覆盖真实道路场景中30类关键目标,包括行人、车辆、交通设施、障碍…

作者头像 李华
网站建设 2026/10/1 3:10:10

Jev模型Agent实战:工具调用与结构化输出解析

1. 一个"不会聊天"的模型,为什么反而在Agent圈炸了第一次看到Jev这个名字,是在几个Agent开发群里。有人甩了一张截图,说某个模型在工具调用任务上跑出了很离谱的成绩,然后群里就炸了。我当时的反应是:又一个…

作者头像 李华