Redis 4.0.2源码编译安装全指南:从依赖配置到systemd托管
最近有个老项目要统一缓存组件版本我本来想直接用发行版仓库里的 Redis 包结果一查版本就傻眼了——CentOS 7 默认给的还是 3.2项目基线却定在 4.0.2。我问了一圈身边的同事大多数人的第一反应都是“直接用 yum 装不就行了”但真到了生产环境版本锁定、安装路径、启动参数、权限规划这些细节包管理器根本给不了你控制权。于是我把 Redis 4.0.2 的源码编译安装从头到尾完整走了一遍从 tarball 下载、依赖预检、 make 参数、 PREFIX 安装、 systemd 托管到安装后的压测和排障全部记成了这篇笔记。这篇文章只围绕“Redis 4.0.2 源码编译安装”这一件事展开适合被包管理器版本锁死、想自定义安装目录、或者需要在同一台机器上管理多套 Redis 实例的读者。如果你从来没有碰过源码编译跟着下面每一步操作也能在一台干净的 Linux 机器上把 Redis 跑起来。1. 为什么偏偏要源码编译 Redis 4.0.21.1 发行版仓库里那个“能用就行”的 Redis 版本先别急着鄙视源码编译我们先看看包管理器给了我们什么。在 CentOS 7 上执行yum install redis大概率装到的是 3.2.12在 Ubuntu 16.04 上执行apt-get install redis-server默认是 3.0.6。这两个版本不是不能用但离 4.0 分支整整差了一代功能。Redis 4.0 引入的模块系统Modules是当时的一个分水岭。之前你想给 Redis 加一个自定义数据结构基本等于要 fork 一份源码改 C 代码重新编译而模块系统把扩展能力变成了“编译时加载一个 .so 文件”门槛一下子低了很多。4.0 还改了主从同步的 PSYNC2 机制故障切换之后再重新全量同步的概率明显降低持久化方面也新增了混合 RDB-AOF 格式重启恢复速度比纯 AOF 快不少。所以很多团队在做版本选型的时候把 4.0 当作一个“既往前看、又别太激进”的平衡点。那为什么偏偏是 4.0.2因为 4.0.0 刚发布的时候还是有不少雷的4.0.2 作为这个分支的早期补丁版本修复了一部分稳定性问题。而 4.0.2 之后的 4.0.x 基本都是修修补补5.x 又引入了更大的行为变化。对一些讲究“版本锁定、可控升级”的内部系统来说4.0.2 就是一个宁可保守也不乱动的基线。这里我不评价这个选择对错但我要说的是你想精确拿到官方 release 的 4.0.2而不是某个发行版魔改过的 3.2.x源码编译是最直接的路径。1.2 源码编译能解开的几道锁我总结了四个场景是包管理器装 Redis 解决不了的也是源码编译真正的价值所在。第一是版本锁。包管理器能给你的版本完全取决于仓库维护者的更新节奏云厂商镜像里的版本又可能做过后台定制。你无法保证线上跑的二进制就是官方原版。源码编译不一样你可以从官方下载指定的 tar.gz解压完可以手工核对校验值编译出来的二进制是从干净源码来的。第二是路径锁。用 yum 或者 apt 安装 Redis文件会散落在/usr/bin、/var/lib/redis、/etc/redis等多个位置卸载的时候你都不知道它还留了多少残留。源码编译通过make PREFIX/usr/local/redis install能把所有可执行文件收拢到一个目录里。想更新版本整个目录换掉想回滚把备份目录还原回去就行。第三是编译期选项锁。比如内存分配器的选择Redis 默认会用 jemalloc 来降低内存碎片但如果你在某个特定环境里跟 jemalloc 有过不去的冲突包管理器安装时没得选源码编译时一条make MALLOClibc就能切换。第四是依赖锁。源码编译本身就是把 redis-server 和它自己的运行时绑定在一起只要当前操作系统满足最低 libc 要求你基本能在一个相对老的系统上把新版本 Redis 装起来。这种灵活性在离线内网环境里尤其重要你只需要准备一份源码包而不需要让运维帮你搞定一整套依赖源。2. 前置检查依赖、编译工具链与目标路径2.1 Redis 4.0.2 需要哪些编译依赖精确到包名很多人一上来就make报错了才开始补依赖这样也不是不行但效率太低。编译 Redis 4.0.2 需要的东西其实很少我在下面列一个表依赖用途缺失时的表现gcc编译 C 代码直接提示cc: command not foundmake驱动整个构建过程make: command not foundtcl运行官方测试套件make test报tclsh: command not found或版本太低CentOS / RHEL 系列建议这么装yum groupinstall Development Tools -y yum install -y tclUbuntu / Debian 系列apt-get update apt-get install -y build-essential tcl这里有一个容易被忽略的点如果你只打算编译、安装、启动不跑官方make test那 tcl 其实不是必须的。但我的建议是顺手装上因为测试套件是验证编译产物可靠性的最直接方式你保不齐哪次编译完就想跑一遍。装完后顺手确认一下工具链版本gcc --version make --version tclsh --version如果tclsh不存在说明 tcl 没装成功或者是装成了tcl但没带解释器。CentOS 上还要确认一下有没有tcl-devel不过 Redis 4.0.2 的测试主要依赖tclsh大部分发行版基础包里已经够用。2.2 编译期参数和安装路径提前想清楚源码编译最忌讳的就是不看Makefile、不看 README上来就无脑make。在开始之前你至少要回答两个问题编译产物装到哪里内存分配器用哪个。Redis 4.0.2 的 Makefile 在编译时会自动探测内存分配器。默认情况下它会优先选择 jemalloc这是设计上为了减少长期运行场景下的内存碎片。但如果你的系统环境跟 jemalloc 有冲突编译中会直接报错。报错信息长这样zmalloc.h:50:31: fatal error: jemalloc/jemalloc.h: No such file or directory compilation terminated.这个错误我后面排障章节会细讲现在先记住一句话如果默认编译报这种错就改成make MALLOClibc重新来。安装路径我强烈建议用PREFIX指定不要直接装到系统默认的/usr/local/bin。这样做的理由前面已经说了——收拢路径、方便卸载、方便多版本并存。我习惯把主程序目录放在/usr/local/redis数据目录单独放/var/lib/redis配置目录放/etc/redis日志目录放/var/log/redis。这样二进制、配置、数据、日志四类文件完全隔离不会乱。3. 从 tarball 到可执行文件完整安装过程3.1 下载、校验、解压源码包先建一个干净的源码工作目录我一般用/usr/local/src。然后从官方下载链接拿 4.0.2 的压缩包cd /usr/local/src wget https://download.redis.io/releases/redis-4.0.2.tar.gz tar -zxvf redis-4.0.2.tar.gz cd redis-4.0.2下载之后别急着解压先校验一下文件完整性。官方发布页会给出对应的 SHA-256 校验值你可以用sha256sum手动对比。别小看这一步在内网环境或者从第三方源下载时文件被篡改或者传输损坏都是真实发生过的而 Redis 这种直接进内存的组件二进制有问题是最难排查的。解压完成后我习惯性的第一件事是执行一次make distclean。你可能好奇源码目录刚解压出来干干净净的为什么要 clean因为如果你之前在这个目录编译过其他版本或者上一次编译中断源码树里会残留很多.o目标文件这些残留物很容易导致本次编译使用到过期产物。make distclean相当于把所有编译产物清空回到干净的初始状态。3.2 make 与 make install 参数解析现在进入正式的编译环节。最基本的一条命令就是makeRedis 4.0.2 的 make 默认会构建这几个目标redis-server、redis-cli、redis-benchmark、redis-check-aof、redis-check-rdb、redis-sentinel。你会看到终端里不断滚动 gcc 编译命令这个过程大概持续几分钟取决于机器性能。如果你遇到了前面说的 jemalloc 问题或者你只是想用系统自带的 malloc 替代 jemalloc就改成make MALLOClibc注意这里有个细节MALLOC 参数是传给 Makefile 的环境变量或者 make 命令行参数不是 C 语言里的宏。你就算直接改 zmalloc.h 里的#define也不如这个参数来得干净。编译完先不要急着安装可以先看一眼 src 目录下生成的二进制文件ls -lh src/redis-server src/redis-cli src/redis-benchmark如果这几个文件大小正常不是 0 字节说明核心编译成功了。此时你可以直接把src/redis-server跑起来试试但我不建议现在就跑因为还没有做安装目录规范。正式的安装命令是关键一步make PREFIX/usr/local/redis install这条命令会把所有编译好的可执行文件放到/usr/local/redis/bin目录下。注意 PREFIX 只决定二进制文件的安装位置它不会自动帮你在/etc下建配置目录也不会自动建数据目录这些活儿后面得我们自己干。这也正是很多人源码安装 Redis 后找不到配置文件的原因——配置文件其实还在源码目录里Redis 没有像 RPM 包那样自动帮你 copy。如果你不带 PREFIX 直接执行make install那所有二进制会装到/usr/local/bin跟发行版预装的 redis 可能产生冲突。我是强烈不推荐这种装法的。如果你想在安装前验证编译产物可以跑官方测试套件。测试依赖 tclmake test这一跑可能要花好几分钟因为 Redis 的测试套件会启动大量临时实例跑各种主从、持久化、过期策略等场景。如果全部通过会看到类似All tests passed的结尾输出。如果测试环境资源不足比如容器里内存太小、文件描述符限制太低个别用例可能误报失败这种情况我建议放宽环境限制后重试而不是直接断定编译有问题。3.3 安装完成后目录里都有什么装完以后你会在/usr/local/redis/bin下看到差不多这样几个文件/usr/local/redis/bin/ ├── redis-benchmark ├── redis-check-aof ├── redis-check-rdb ├── redis-cli ├── redis-sentinel └── redis-server每个文件的用途很简单redis-server核心服务端平时启动就靠它。redis-cli命令行客户端日常调试、执行指令都靠它。redis-benchmark基准测试工具用来压 Redis 的读写性能。redis-check-aofAOF 持久化文件损坏时的修复工具。redis-check-rdbRDB 快照文件损坏时的修复工具。redis-sentinel哨兵模式入口实现高可用时使用实际上多数场景下它也可以直接通过redis-server --sentinel方式启动。到这一步Redis 4.0.2 的可执行文件已经编译安装好了。但这个状态还谈不上“可用”因为 Redis 默认配置下守护进程方式、日志位置、持久化目录都不符合我们的预期接下来要做的是让 Redis 以一个受控的系统服务形式运行。4. 配置字典与守护进程让 Redis 像系统服务那样安分工作4.1 服务账号和数据目录提前规划直接拿 root 用户跑 Redis 是个非常糟糕的习惯万一有人通过客户端执行了CONFIG SET dir之类的危险指令后果会很严重。所以第一步先建一个专用账号useradd -M -s /sbin/nologin redis-M表示不创建家目录-s /sbin/nologin表示禁止这个用户登录 shell。Redis 进程对系统资源的访问权限被压到最低。然后创建目录并授权mkdir -p /etc/redis /var/lib/redis /var/log/redis chown -R redis:redis /var/lib/redis /var/log/redis注意这里我只对数据和日志目录授权/etc/redis目录保持 root 所有因为配置文件是 root 管理的防止 Redis 运行账号反过来改自己的配置。4.2 redis.conf 首轮修改清单Redis 4.0.2 在解压目录下自带了一份默认配置文件redis.conf先把它复制到/etc/redis/cp /usr/local/src/redis-4.0.2/redis.conf /etc/redis/redis.conf我强烈建议复制而不是直接原地改。源码目录可能之后会被你整个删掉而配置属于系统资料必须独立存放。另外每次修改前记得留备份cp /etc/redis/redis.conf /etc/redis/redis.conf.bak.$(date %F)接下来打开/etc/redis/redis.conf重点调整下面几项daemonize no supervised systemd port 6379 bind 127.0.0.1 protected-mode yes pidfile /var/run/redis_6379.pid logfile /var/log/redis/redis.log dir /var/lib/redis先解释daemonize no这一项。很多人传统印象里 Redis 就是应该后台运行所以默认配置里daemonize yes很常见。但我们既然要用 systemd 管理就让 Redis 留在前台运行systemd 负责监控它、拉起它。如果你保持daemonize yes并且再交给 systemd就会出现 systemd 认为服务没起来、实际进程却已经在跑的双重管理混乱。既然用 systemd配置里还要有supervised systemd这一项让 Redis 在 systemd 环境下正确地报告“我已经准备好提供服务了”的通知systemd 就会使用合适的服务类型来管理它。再看几条安全相关的。bind 127.0.0.1说明 Redis 只监听本地回环地址外部网络访问不到。protected-mode yes是 Redis 4.0 开始引入到默认配置的行为当没有设置密码、且绑定了非本机地址或所有网卡时Redis 会拒绝来自外部的危险指令。这套组合能挡住绝大多数字典扫描和内网漫游工具。如果你确实需要让其他机器访问 Redis不要简单地把bind改成0.0.0.0就完事除非你同时设置了requirepass强密码并且防火墙层面做了网段限制。否则一台没有认证的 Redis 暴露在网络上等于把一台机器给别人当肉鸡用。这个优先级比任何性能参数都高。持久化方面如果你需要更稳妥的落盘可以在配置里加一行appendonly yes4.0.2 支持 RDB 和 AOF 混合持久化这条开启后 Redis 会在合适时机生成appendonly.aof文件后续重启恢复数据会优先读取 AOF丢失窗口明显小于纯 RDB。要不要开取决于你的数据容忍度但开了就要接受一定程度的 IO 开销。4.3 systemd 服务单元编写与开机自启配置改好后在/etc/systemd/system/redis.service写一个服务单元文件[Unit] DescriptionRedis 4.0.2 Server Afternetwork.target [Service] Typesimple Userredis Groupredis ExecStart/usr/local/redis/bin/redis-server /etc/redis/redis.conf ExecStop/bin/kill -s TERM $MAINPID Restarton-failure LimitNOFILE65535 [Install] WantedBymulti-user.target这个单元文件里有几个细节值得展开说。Typesimple配合前面配置里的daemonize no是正确的组合systemd 直接认为redis-server这个进程就是服务主体。Userredis和Groupredis让进程以低权限账号运行。ExecStop里我选择向主进程发送 SIGTERM这是给 Redis 一个优雅退出的机会它会先做必要的数据保存再退出比kill -9安全得多。Redis 对 SIGTERM 信号有专门处理逻辑你不用手动写一堆 stop 脚本。LimitNOFILE65535这个我特别提一句。高并发环境下 Redis 需要大量文件描述符来维持客户端连接默认 ulimit 很多时候只有 1024生产环境跑起来没多久就会报 “Too many open files”。在 systemd 单元里显式提升是最可控的方式。文件写好后依次执行systemctl daemon-reload systemctl enable redis systemctl start redis systemctl status redissystemctl status输出会显示 active (running)并且能看到进程的启动时间和用户。到这里Redis 4.0.2 已经以一个受 systemd 管控的系统服务形式在跑了重启机器它也会自动拉起。5. 安装不是终点连通性验证与基准摸底5.1 用 redis-cli 完成基本读写验证服务起来的第一件事肯定是确认它真的能干活。先用 ping 指令探活/usr/local/redis/bin/redis-cli -h 127.0.0.1 -p 6379 ping如果返回PONG说明服务端正常响应了。接着做一次最小读写验证redis-cli set hello world redis-cli get hello能返回world说明redis-server和redis-cli这套链路没问题。再验证一次持久化目录的状态ls -lh /var/lib/redis/ ls -lh /var/log/redis/正常情况下会看到日志文件写入如果开启了appendonly yes还会有appendonly.aof文件。如果目录空空如也说明持久化配置没生效多半是dir参数配的位置不对或者配置里被后面的参数覆盖了回头仔细检查/etc/redis/redis.conf里是否有重复项。5.2 redis-benchmark 压测与结果读数功能验证通过后我建议用自带的redis-benchmark快速跑一轮性能摸底。不是所有环境都需要精确调优但你至少要知道这套源码编译出来的版本在当前机器上是什么水平。先跑一个最基础的混合场景redis-benchmark -h 127.0.0.1 -p 6379 -c 50 -n 100000 -t set,get,incr,lpush参数含义-c 50并发 50 个客户端连接。-n 100000总共发 10 万条请求。-t set,get,incr,lpush只测试这几个指令。-q安静模式只输出最终吞吐量适合快速阅读。典型输出大致长这样SET: 81234.56 requests per second GET: 80016.23 requests per second INCR: 74532.11 requests per second LPUSH: 77566.88 requests per second不同机器数字差异很大别拿云上压测的数字跟物理机硬比。更重要的是看输出的延迟分位值比如p99是多少有没有明显的毛刺。如果p99比平均值高出一两个数量级说明这台机器可能存在内存分配或 CPU 调度瓶颈可以考虑跑第二轮看是否稳定。我习惯连续跑三轮取中位数因为第一轮往往受 CPU 缓存冷启动影响数字会偏低。5.3 用 info 命令核对持久化和内存细节压测之后再通过redis-cli info检查一组关键指标。不要等到出故障才看监控安装完了看一眼基线状态以后出问题才好对比。redis-cli info persistence redis-cli info memoryinfo persistence里重点看rdb_last_save_time、aof_enabled、aof_last_write_status这几项。rdb_last_save_time如果是一串很老的时间戳说明 Redis 启动后还没触发过 RDB 保存需要确认save参数是否符合业务容忍度。aof_last_write_status必须是ok如果是err说明 AOF 写入链路有问题赶紧看日志。info memory里看used_memory_human和used_memory_peak_human。知道当前进程到底吃掉了多少内存一方面是为监控系统采集数据准备基线另一方面也是判断后面有没有必要调整maxmemory策略。这些命令本身不产生额外依赖任何一个 redis-cli 都能跑关键是你要养成“装完就记录基线”的习惯。很多线上事故回溯的时候最缺的就是一份“正常状态长什么样”的参照。6. 源码安装现场的高频翻车点与修复笔记6.1 内存分配器冲突jemalloc 编译失败的常规解法源码编译 Redis最高频的报错就是文章前面提到的zmalloc.h:50:31: fatal error: jemalloc/jemalloc.h: No such file or directory。看起来像是缺少 jemalloc 头文件其实大多数时候不是头文件缺失而是当前系统环境下 jemalloc 的编译过程出了问题。Redis 默认调用make时会进入deps/jemalloc目录尝试构建 jemalloc。这个过程在一个环境干净的机器上一般没问题但如果你用的是某些特殊的内核、容器镜像或者系统里预装的 malloc 实现过于特殊jemalloc 编译出来的库可能没法跟 Redis 主体链上。这时候最直接的解法是切回 libcmake distclean make MALLOClibcmake distclean很重要它可以把上一轮编译产生的.o文件、临时链接库全部清掉避免旧产物跟新参数打架。切换成 libc 后Redis 仍然完全可用只是在极端内存碎片场景下可能比 jemalloc 多占一些内存。结论是先跑起来再谈优化。6.2 tcl 缺失导致 make test 中断如果你按照常规流程走到make test结果终端开始刷几句日志就停下来大概率是测试框架没起来。常见报错是You need tcl 8.5 or newer in order to run the Redis test make[1]: tclsh: Command not found这属于测试环境的依赖问题不是源码问题。在 CentOS 上yum install -y tclUbuntu 上apt-get install -y tcl装完后再执行一次make test。如果还是报版本太低检查一下tclsh的实际版本tclsh puts [info patchlevel]低于 8.5 的旧版本建议手动升级或更换发行版源。另外一个容易被忽略的情况是编译机器上根本没有图形环境但某些测试用例会占用较多临时端口和内存资源受限的容器里make test偶尔会跑出几个假失败这时候放宽ulimit -n或者换一台资源充足的机器重新跑即可不用过度紧张。6.3 老系统 GCC 版本过低导致的奇异编译错误Redis 4.0.2 整体对编译器的要求不算高但遇到很老的系统比如 RHEL 6 系列自带 GCC 4.4有概率出现一些不容易看懂的语法报错。你仔细看报错内容可能跟 Redis 源码本身没多大关系更像是老编译器对 C 代码的解析限制。这类问题我的处理思路是先确认gcc --version如果版本真的太老优先在系统层面安装新一点的工具链而不是去改源码。在 RHEL 系可以通过软件集合SCL等方式启用新版本 gcc在 Ubuntu 上通常直接apt-get install gcc就能拿到可用版本。还有一个实践技巧如果当前机器的工具链实在搞不动你可以在另一台内核版本相近、但编译工具链更新的机器上先编译打包然后把整个/usr/local/redis目录打包拷贝过来。只要两边的动态库兼容性没问题Redis 4.0.2 这种以静态链接内部依赖为主的项目拷贝二进制通常能直接跑。但这个方案终究是过渡方案部署机器和编译机器尽量保持一致才是治本之策。6.4 多套 Redis 共存时的端口与目录混战源码编译安装最常见的后遗症是你可能同时存在发行版自带的 Redis 和自编译的 Redis。如果你没有刻意区分启动时很容易遇到端口占用Could not create server TCP listening socket *:6379: bind: Address already in use这个报错背后往往不是单一原因可能是旧服务没退干净也可能是你自己手滑把两个实例的配置都指到了 6379。我先教一个排查链路ps -ef | grep redis ss -lntp | grep 6379看进程列表里到底有几个 redis-server每个进程对应的配置路径是什么。然后看端口是谁占的用ss或者lsof -i:6379找到占用进程号。如果确实需要多实例共存最干净的做法是每个实例一套独立的配置独立端口、独立 pidfile、独立日志、独立数据目录、独立 systemd 单元。比如第二个实例配置port 6380 pidfile /var/run/redis_6380.pid logfile /var/log/redis/redis_6380.log dir /var/lib/redis_6380然后复制一份独立的 systemd 单元文件把ExecStart里的配置路径改成新实例的配置服务名就不要叫 redis 了改成redis-6380.service。这样一来多套实例在进程、目录、日志三个维度上彻底隔离不会互相打架。最后再分享一个我自己的习惯每次编译完我都会在/etc/redis/redis.conf顶部留一行注释写清楚当前版本、编译参数、编译时间比如# built from redis-4.0.2 with PREFIX/usr/local/redis on 2024-xx-xx。这行注释看着不起眼但半年后再回来接手的人哪怕你换了部门他也能一眼看出这台机器的 Redis 是哪个版本、怎么装出来的。实际操作中这句注释帮我省下来的排查时间比任何监控面板都值。