Linux离线部署Memcached:三大方案与依赖处理实战
刚接到一个活儿要在不能访问外网的 Linux 服务器上装 Memcached。我一开始觉得这事简单不就是把包拷上去编译一下嘛。真正动手才发现离线部署最麻烦的从来不是 Memcached 本身而是它背后的依赖链、编译环境和运行时的各种隐性要求。整个过程折腾了两天踩了不少坑也把三种主流方案RPM 包、本地软件源、源码编译都试了一遍。这篇文章就把完整的离线部署思路、实操步骤和排错经验写出来给后面要干同样事情的朋友省点时间。1. 离线部署的真正难点版本、依赖链与工具箱的三重准备1.1 为什么离线环境最容易卡在依赖链上Memcached 本身是个轻量级服务核心代码量不大但它依赖 libevent 这个事件驱动库。在线部署的时候包管理器会自动把 libevent 拉下来一条命令搞定根本不用关心版本和进度。离线部署就不一样了目标机器上有没有 libevent版本够不够如果版本太老Memcached 可能编译不过去如果目标机器上根本没有编译器那源码包下载得再全也白搭。我在目标机器上先扫了一圈这是一台较老内核的 Linux 服务器装的是最小化系统gcc 居然没装。这意味着如果走源码编译路线我得先把 gcc、make、autoconf 这一整套编译工具链搞定这可比装 Memcached 本身麻烦多了。如果你运维的是一台压箱底的老服务器极有可能遇到这个问题。1.2 动手前必须先盘点的四件事离线部署前不要一上来就下载软件包先盘点目标机器的实际情况。我在实际操作中按下面四个维度检查缺一个后面都会出问题发行版类型和版本是红帽系还是 Debian 系版本是多少。这决定了你该用 RPM 包、deb 包还是干脆源码编译。基础编译工具是否齐全gcc -v、make -v各执行一遍看返回结果。最小化安装的机器大概率缺这个。是否已有 libevent我是用ldconfig -p | grep libevent查的同时也找找/usr/lib64下有没有对应的库文件。有旧版本的话要么升级要么编译时把 Memcached 指向新装的 libevent。磁盘空间和权限确认/usr/local或自定义安装目录有足够的空间当前用户是否有权限写入。别小看这个我曾经在部署时因为/usr/local所在分区只有 200MB 剩余空间编译到一半报错只能临时清理腾挪。检查完这四项你会对部署方案有一个基本的判断如果编译器齐全源码编译是最灵活的如果目标机器是批量化的同型号服务器本地软件源最省事如果只想快点把服务跑起来且对版本没有特殊要求直接找现成 RPM 包最快。2. 三条安装路径怎么选RPM、本地软件源和源码编译的权衡2.1 三种方式的适用场景和优劣势为了让你有一个直观的判断依据我把三条常见路径做了一张对比表。后面每个方案我就按照表格里的思路来展开部署方式依赖处理方式部署速度灵活性最适合的场景RPM 包直接安装需手动下载依赖 RPM如 libevent较快较低版本固定单机快速部署发行版自带现成包本地软件源一次性准备所有 RPM 和依赖配置 repo批量最快中可指定版本多台相同系统的服务器批量交付源码编译依赖库源码需自行编译安装最慢最高可定制编译参数个性化配置需求比如修改编译选项或指定安装路径2.2 RPM 包安装的细节RPM 方式适合那些对 Memcached 版本没有特别要求的场景。比如某发行版的官方软件仓库里有 1.6.x 版本的 Memcached同时对 libevent 版本也有配套的 RPM 包。我在一台机器上试过这条路操作确实简单rpm -ivh libevent-2.1.12-11.el8.x86_64.rpm rpm -ivh memcached-1.6.9-1.el8.x86_64.rpm需要注意RPM 安装时依赖检查不是吃素的libevent 如果没先装好直接装 memcached 会报 libevent 被需要 这类错误。所以顺序必须先把依赖前置。另外如果你从非官方源下载 RPM 包要留意签名校验建议用rpm -K检查一下包的 GPG 签名免得引入安全隐患。2.3 本地软件源适合批量交付本地软件源的逻辑是找一台能上网的机器把 memcached 及其依赖的所有 RPM 包一次性拉下来放到一个目录里然后用createrepo生成仓库元数据。在目标机器上写一个.repo文件指向这个本地目录之后用yum或dnf安装就跟在线环境一样了。yumdownloader memcached libevent --destdir/tmp/memcache_repo cd /tmp/memcache_repo createrepo .把这个目录想办法拷到目标机器上然后在/etc/yum.repos.d/local.repo里配置[local] nameLocal Memcached Repo baseurlfile:///opt/memcache_repo enabled1 gpgcheck0配置完成后执行yum install memcached即可。这个方式的好处是依赖关系由包管理器统一处理批量部署多台机器时不会出现依赖漏装的情况。坏处是前期准备相对繁琐而且生成 repo 的机器和目标机器必须是同一发行版大版本否则依赖关系可能对不上。2.4 源码编译一切尽在掌握代价是坑更多如果你想自定义安装路径或者想在编译时加上 SASL 认证支持这类特性那就要走源码编译。这是我在这次离线部署中最终采用的方式后面会花大篇幅来写详细过程。选择源码编译的一个核心原因是离线环境中你无法预估目标机器上会被装成什么样源码编译能让你显式地控制 libevent 的版本和安装路径避免和系统自带的旧版本染在一起排查问题时思路也清晰很多。代价就是步骤多、对编译工具链有硬性要求。如果目标机器 gcc 都缺失那你得先解决工具链这就得回到 RPM 包的路线去补 gcc或者从其他地方拷贝一份完整的编译环境过来。3. 源码编译安装的全流程实录libevent 前置与 Memcached 编译3.1 准备编译环境和传输文件既然选择源码编译我就把所有需要的源码包在联网环境下准备好了libevent-2.1.12-stable.tar.gz和memcached-1.6.17.tar.gz。这两个版本是经过多次实测比较稳定的组合推荐直接抄作业。文件传输到目标机器后先做校验和验证防止传输过程中文件损坏md5sum libevent-2.1.12-stable.tar.gz memcached-1.6.17.tar.gz如果跟你下载时的 MD5 值不一致就不要继续部署了重新传输一次。这个步骤看起来繁琐却是离线部署最容易踩的一个隐形坑文件不完整解压的时候可能不会报错但到了 configure 阶段会跳出一堆奇怪的错误信息让你满脑子问号。3.2 先编译 libevent记住这个标准化流程libevent 是 Memcached 运行的核心依赖编译方式非常标准。我选择了自定义安装路径/usr/local/libevent把系统自带的 libevent 隔离出来tar zxvf libevent-2.1.12-stable.tar.gz cd libevent-2.1.12-stable ./configure --prefix/usr/local/libevent make -j 4 make install--prefix参数指定了 libevent 的安装目录这不是随便写写的。如果直接编译安装到默认的/usr/local在部分系统上可能会覆盖系统自带的 libevent引起其他工具的连锁问题。安装到独立目录是最稳妥的做法。编译过程中如果报错缺少某些头文件大概率是系统缺少基础开发包比如openssl-devel。libevent 的 configure 脚本默认会探测 OpenSSL 的支持情况没有相关开发包时可以加上--disable-openssl参数避开。3.3 编译 Memcached真正考验注意力的步骤libevent 装好后就可以编译 Memcached 了。这里有一个关键点要把 Memcached 的 configure 指向刚才安装的 libevent 路径tar zxvf memcached-1.6.17.tar.gz cd memcached-1.6.17 ./configure --prefix/usr/local/memcached --with-libevent/usr/local/libevent make -j 4 make install--with-libevent参数的作用是告诉编译过程去哪里找 libevent 的头文件和库文件。如果这里不明确指定configure 会按照默认路径搜索可能找到系统里旧的、不一致的 libevent导致编译出来运行时报各种找不到符号的错误。我第一次编译时没带这个参数结果 configure 居然也通过了但运行memcached的时候直接报错提示找不到 libevent 的相关函数。这就是典型的运行时错误排查起来比编译期错误麻烦得多。所以建议这里不要省事老老实实把两个参数都写上。3.4 编译完成后的关键配置链接库路径Memcached 编译安装完成后直接运行二进制文件大概率还是跑不起来错误信息通常是error while loading shared libraries: libevent-2.1.so.7: cannot open shared object file。这是因为动态链接器还没找到位于自定义路径下的 libevent 库。解决办法是告诉动态链接器去哪里找库写入一个配置文件并刷新echo /usr/local/libevent/lib /etc/ld.so.conf.d/libevent.conf ldconfig然后执行ldconfig -p | grep libevent能看到libevent-2.1.so.7出现在列表里就说明库路径生效了。这一步我称之为三连坑之首几乎每个从源码编译 Memcached 的人都会遇到。不做这一步后面无论你用 systemd 还是命令行启动都会被这个错误卡住。3.5 验证可执行文件库路径解决后再执行/usr/local/memcached/bin/memcached -h如果能看到一长串的启动参数说明就说明二进制文件可以正常加载运行了。我习惯在这里先跑一个前台模式的测试/usr/local/memcached/bin/memcached -u root -p 11211 -m 64 -f 1.25 -vvv前台模式加-vvv可以最大化日志输出能看到服务在监听端口上的连接情况。确认没有报错后按 CtrlC 终止然后进入下一步服务化配置。这个测试的价值在于可以预先发现那些只有在运行期才会暴露的问题而不是直接把服务装成 systemd 再慢慢排查。4. 让 Memcached 被系统优雅地管理参数详解与 systemd 固化4.1 启动参数简析Memcached 的启动参数不算复杂但有几个必须理解清楚否则后果很直接。我放一下最终用的完整启动命令再逐个拆解参数含义/usr/local/memcached/bin/memcached \ -d \ -p 11211 \ -l 127.0.0.1 \ -u memcached \ -m 2048 \ -c 1024 \ -P /var/run/memcached/memcached.pid-d以守护进程方式运行。不加这个参数进程会在前台跑断开终端就退出。但因为后面要交给 systemd 管理这个参数实际上不会在 systemd 里用。-p 11211监听端口默认就是 11211写上是为明确。-l 127.0.0.1监听地址。这是非常重要的安全参数。默认情况下 Memcached 会监听所有网卡如果你的服务器暴露在公网不设置这个参数等同于把缓存数据裸奔在互联网上。生产环境建议明确填写内网 IP只允许内网应用访问。-u memcached指定运行用户。Memcached 出于安全考虑如果以 root 用户启动必须通过-u降权到普通用户否则启动会拒绝。系统里如果没有memcached这个用户需要先创建。useradd -r -s /sbin/nologin memcached-m 2048设置最大内存为 2048MB。注意这个参数的单位是 MB不是 KB 也不是百分比。我见过有同事把 2048 当成 KB 来理解结果设置了大得离谱的值服务器内存直接告警。请记住-m后面的数字就是 MB。-c 1024最大并发连接数。默认值是 1024如果应用并发量高可以适当调大但要注意系统文件描述符的限制必要时得同步调整ulimit -n。-P指定 PID 文件路径。这个参数在使用-d后台方式时才有意义systemd 管理时不依赖它但保留也能在手动排查时快速找到进程号。4.2 systemd 单元的编写要点离线环境部署完成后我建议顺手把 Memcached 做成 systemd 服务而不是简单用-d拉一个后台进程。原因很简单服务器重启后服务能不能自动拉起这是运维稳定性的一票否决项。在/etc/systemd/system/memcached.service中写入如下单元文件[Unit] DescriptionMemcached Caching Server Afternetwork.target [Service] Typesimple Usermemcached Groupmemcached ExecStart/usr/local/memcached/bin/memcached -p 11211 -l 127.0.0.1 -m 2048 -c 1024 ExecReload/bin/kill -HUP $MAINPID Restarton-failure RestartSec5s LimitNOFILE65535 [Install] WantedBymulti-user.target这里有几个细节值得特别说明Typesimple是合理的因为 Memcached 本身就是前台进程没有 fork 行为。systemd 会直接把该进程作为主进程管理。User和Group直接指定为memcached这里不用在 ExecStart 里再加-u参数了否则可能出现权限不一致的冲突。LimitNOFILE65535是常常被忽略的一项。如果应用侧有高并发大量连接的需求系统默认的 1024 文件描述符上限会成为瓶颈报错表现为连接被拒绝或者无法建立新连接。在 systemd 里把这个值调大比在/etc/security/limits.conf里折腾更直接有效。写完单元文件后执行systemctl daemon-reload systemctl enable memcached systemctl start memcached启动后查看状态systemctl status memcached这一步如果看到状态为active (running)然后ss -lntp | grep 11211也能看到端口在监听服务就算是真正部署完成了。4.3 一个容易踩的系统化坑PID 文件目录不存在如果你在使用-d方式引入 systemd 单元时踩了坑很可能是因为我上面提到的一个细节没有处理好systemd 托管时务必去掉-d参数。-d会让进程后台化并退出主进程systemd 检测到主进程退出就会认为服务异常触达Restarton-failure的规则不断重启。另外如果启用了-P /var/run/memcached/memcached.pid要确认/var/run/memcached/目录存在且属主正确。我一开始图省事没先建目录服务起来后 PID 文件写不进去日志里报了一行 Permission denied排查半天才发现是这种低级问题。建议mkdir -p /var/run/memcached chown memcached:memcached /var/run/memcached把这个隐患提前消掉。5. 上线前的功能验证与基础压测用数据说话5.1 使用 telnet 手动验证读写流程服务起来后验证它真的是能用的缓存而不是仅仅进程活着。最简单的验证方式是用 telnet 连接 Memcached 并执行命令telnet 127.0.0.1 11211 Trying 127.0.0.1... Connected to 127.0.0.1. Escape character is ^].连接成功后先输入stats回车能看到大量统计信息包括当前连接数、命中次数、内存使用情况等。如果stats有响应说明 TCP 连接和进程状态都正常。接下来验证读写set foo 0 60 5 hello STORED get foo VALUE foo 0 5 hello END这里注意几个细节set后面的五个参数分别是键名、标志位、过期时间、字节数。上面例子里的60表示 60 秒后过期5表示下面要传入的数据是 5 个字符长度即 hello。输入完5后回车紧接着输入数据体再回车才能看到STORED响应。这个回车换行的节奏不对会直接报ERROR。验证完写入后再验证删除和过期逻辑delete foo DELETED get foo END手动读写验证通过就说明 Memcached 的核心功能正常。如果这一步你懒得做直接跳到配置业务应用一旦发现问题到时候还得回头排查是特性问题还是部署问题。5.2 内存和连接数的疲劳测试功能验证通过后我会建议做一个简单的疲劳测试确认-m设置的内存参数和-c连接数参数没有被过度配置或者配置不足。模拟连接数可以用一个简单的 Python 脚本import socket import time conns [] for i in range(500): s socket.create_connection((127.0.0.1, 11211)) s.sendall(bstats\r\n) time.sleep(0.01) data s.recv(1024) conns.append(s) print(连接数:, len(conns)) for s in conns: s.close()如果脚本能完成 500 个连接且都有响应说明并发连接的处理能力是有的。如果这里中途报Connection refused基本就是-c连接数上限或者LimitNOFILE没有调高导致的。内存部分的验证我主要看stats输出里的curr_items、total_malloc和bytes这三个字段。当缓存写入数据接近-m设置的上限时Memcached 的 LRU 淘汰算法会开始工作evictions字段会增长。如果业务场景里非常在意缓存的命中率evictions飙高是个值得警惕的信号说明内存设置偏小需要扩容。在生产环境压测时不要直接用暴力灌数据的写法我见过有同事拿循环去set几十万条数据把 Memcached 的内存直接打满导致其他业务受牵连。压测前请评估一下这台服务器上的其他业务内存和 CPU 都不能无节制占用。5.3 验证过程中的一个真实教训缓存穿透是个系统问题在验证 Memcached 功能时我顺便想提醒一件事Memcached 只是缓存组件它不会帮你解决穿透、击穿这类型的问题这些需要在应用层做设计。比如访问一个不存在的 keyMemcached 会直接返回END如果业务代码不做空值缓存每次请求都会打到数据库。我见过部署完 Memcached 后应用响应速度反而下降的案例排查到最后发现是缓存穿透每个请求都查缓存未命中再查数据库多了一次网络开销比不用缓存还慢。正确的做法是对于查询结果为空的数据也设置一个短过期时间的缓存占位值比如 30 秒保证短时间内同样的查询不会穿透到后端数据库。我在这次验证阶段就把这个策略写进了业务接入方的对接文档里算是一个额外的收获。6. 离线部署后的排查清单从编译失败到运行异常6.1 编译阶段的高频错误和原因离线部署中编译阶段的问题占了大多数。我把实际遇到的几个错误按错误信息归类整理成下面的表格后面的人可以直接参照错误信息根本原因解决方案configure: error: no acceptable C compiler found in $PATHgcc 未安装先通过离线 RPM 包安装 gcc/make或换用 RPM 部署方式configure: error: libevent is required找不到 libevent 头文件确认--with-libevent参数指向了正确的路径fatal error: event.h: No such file or directorylibevent 开发包未安装完整确认/usr/local/libevent/include下存在 event.hmake: *** No rule to make target包文件完整性问题重新解压源码包检查 MD5连接时Address already in use端口被占用换端口或先确认占用进程并妥善处理你在排查的时候一定注意configure 报错的提示往往不会直接告诉你缺了什么包而是给你一段面包屑式的提示。阅读 configure 日志时要往上翻几行看checking for ...的结果那里才是关键哪一项结果为no哪个依赖就有问题。6.2 运行阶段的问题排查思路服务成功启动后也不是万事大吉。运行阶段的故障常见有几个表现一是服务进程还在但突然无法连接二是连接正常但读写性能断崖式下降三是内存持续上涨直到触发 OOM 被系统杀掉。无法连接先查防火墙。很多服务器默认开了防火墙即使你改监听地址为127.0.0.1本机访问没问题但内网其他机器访问就会被防火墙拦截。我用systemctl status memcached看进程存活用ss -tlnp | grep 11211看监听地址再用另一台内网机器telnet 目标IP 11211测连通性三步定位。性能下降看stats里的get_hits和get_misses的比值。命中率骤降说明缓存内容丢失频繁最常见原因是-m内存太小LRU 不断淘汰数据。另外一个隐蔽原因是键名设计不当导致 key 分布过于集中某些 slab 类别的内存被提前耗尽。这个属于调优层面需要结合业务 key 分布分析。内存持续上涨-m设置了固定上限Memcached 不会超过这个值。但如果容量接近上限且写入频繁系统的内存页分配压力会变大特别是在其他进程占用内存较多的服务器上有可能触发系统级别的内存压力。我处理过一次类似问题最后是下调了-m并开启了 swap 才稳住。6.3 我这次离线部署沉淀下来的操作习惯经过这次完整流程我总结出几个离线部署的通用习惯分享给你第一任何离线安装都先准备安装介质清单。一个文件列表包含软件包本身、依赖库、编译工具以及每项的版本号和来源地址。云上还是离线环境这个清单都能帮你快速定位缺失项。第二编译过程每一步都做验证。configure 成功后不等于 make 能成功make 成功后不等于能运行。每执行一步观察输出状态有问题当场记下。我会习惯把所有输出重定向到日志文件比如./configure --prefix/usr/local/memcached 21 | tee configure.log这样排查时能回看详细输出。第三离线操作时尽量不要用裸拷贝代替安装。我看到有些人为了省事直接把二进制文件从一台机器上scp到另一台机器。这能跑起来是运气好跑不起来才是常态。因为二进制编译时链接的库路径、依赖的 glibc 版本在另一台机器上未必存在。还是老老实实走 RPM 安装或者源码编译别拿玄学当方法论。最后离线部署这件事情本质上就是对服务器环境做一次彻底的摸底。你越了解目标机器的底细部署过程就越顺畅。我这次把 Memcached 从源码编译到 systemd 托管全部跑通虽然过程曲折但后续再遇到类似的离线交付任务心里就很有数了。唯一的建议是正式部署前先在虚拟机里把完整的流程演练一遍把可能踩的坑提前踩完这样到了生产环境你才能真正做到从容不迫。