Redis 给个人网站加速实战:Session 共享、页面缓存与持久化选型的完整落地

为什么个人站长该认真考虑 Redis

很多人做个人博客,第一反应是"加个 OPcache 就够了"。OPcache 确实能干掉 PHP 编译开销,但它只是在单机内存里缓存字节码,一旦你遇到下面三类问题,OPcache 就完全帮不上忙:

  • 数据库被同一条查询反复打——首页文章列表、侧边栏热门文章,每次刷新都查一遍 MySQL;
  • 多台机器(或 PHP-FPM 多容器)之间登录状态对不上,用户在前端机登录、在后端机掉线;
  • 爬虫或者恶意刷量把动态页面打到 CPU 100%,而你没有任何一层可以挡住读请求。

Redis 解决的正是这三个问题。它常驻内存、单线程无锁、支持过期时间,qps 轻松上万,对个人站来说一台 1C1G 的机器跑 Redis 都绰绰有余。本文只讲个人网站真正用得上的三件事:Session 共享、页面/数据缓存、持久化选型,不铺开集群那套。

一、安装与安全基线(十分钟)

以 Debian 12 为例:

apt update
apt install -y redis-server
systemctl enable --now redis-server
redis-cli ping   # 返回 PONG 即正常

默认配置有三个坑,必须改。打开 /etc/redis/redis.conf

# 1. 只监听本机(除非有内网多机共享需求)
bind 127.0.0.1 ::1

# 2. 一定要设密码,否则被扫到就是挖矿肉鸡
requirepass 换成你自己的长随机串

# 3. 关掉危险命令,防止误操作或入侵后被清库
rename-command FLUSHALL ""
rename-command FLUSHDB ""
rename-command CONFIG ""

改完 systemctl restart redis-server。注意:如果你的 PHP 和 Redis 不在同一台机器(比如 PHP 在应用机、Redis 在另一台),不要把 Redis 直接暴露公网,正确做法是走内网 IP,或者用 SSH 隧道 / WireGuard 打通,再配合防火墙只放行内网段。

验证:redis-cli -a 你的密码 ping。如果报 NOAUTH Authentication required 说明密码已生效。

二、场景一:多机 Session 共享(PHP 站最刚需)

Typecho、WordPress 这类 PHP 站默认把 session 存成 /var/lib/php/sessions/sess_xxx 文件。单机没问题,一旦你上了负载均衡、或者 PHP-FPM 跑在容器里,用户刷新一次换一台机器,登录态就丢了。

把 session 交给 Redis,两步搞定。

方案 A:PHP 配置(推荐,改动最小)

编辑 php.ini(PHP 8.x 通常被 FPM 拆到 /etc/php/8.2/fpm/php.ini):

session.save_handler = redis
session.save_path = "tcp://127.0.0.1:6379?auth=你的密码&database=0"
session.gc_maxlifetime = 86400

重启 php-fpm 后,去看 Redis:

redis-cli -a 你的密码 --scan --pattern 'PHPREDIS_SESSION:*'

能看到一串 key 就说明生效了。注意 session.save_path 里的密码如果含特殊字符(: @ / #)必须 URL 编码,否则 PHP 解析会失败——这是个非常隐蔽的坑,表现为 session 完全写不进去但页面不报错。

方案 B:应用层接管(更灵活)

如果你只想给某几个场景用 Redis 存登录态,可以在代码里直接写:

$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$redis->auth('你的密码');
$redis->setex('login:uid:' . $uid, 86400, json_encode($userInfo));

方案 B 的好处是可控、可观测;坏处是要自己处理序列化与过期。个人站我一般建议先用方案 A,省事。

三、场景二:页面与数据缓存(提升最大的部分)

首页、文章列表、热门推荐这类"读多写少"的页面,最值得缓存。这里给一个不会翻车的思路:缓存的是数据,不是整页 HTML,除非你确定页面里没有任何用户态内容(比如登录后显示的"你好,XX")。

反例(新手常踩):直接把首页整页 HTML 塞 Redis,结果 A 用户看到 B 用户的登录名——这是缓存穿透用户态的经典事故。

正确做法:缓存查询结果,PHP 层再渲染。

function getHotPosts($redis, $db) {
    $key = 'cache:hot_posts:v1';
    $data = $redis->get($key);
    if ($data !== false && $data !== null) {
        return json_decode($data, true);   // 命中,直接返回
    }
    // 未命中:查库
    $rows = $db->query('SELECT cid,title FROM contents WHERE type="post"
                        ORDER BY views DESC LIMIT 10')->fetchAll();
    $redis->setex($key, 300, json_encode($rows));  // 缓存 5 分钟
    return $rows;
}

几个必须记住的点:

  • 一定要设过期时间。永远不过期的缓存,最后会变成"改了内容前台不更新"的幽灵 bug。个人站 300~600 秒是舒服的区间。
  • key 里要带版本号(上面的 v1)。数据结构一变,改 v2 就整体失效,不用一个个删。
  • 写操作要主动删缓存。发了新文章后 $redis->del('cache:hot_posts:v1'),否则要等 5 分钟才更新,老板以为你没发出去。
  • 加个"缓存空值"防穿透。如果查库结果为空,也要缓存一个短过期(比如 30 秒)的空标记,避免恶意 id 反复穿透到 MySQL。

用这套跑下来,一个日 PV 几千的个人站,MySQL 查询量通常能降 70% 以上,首页 TTFB 从两三百毫秒降到几十毫秒。

四、场景三:持久化怎么选(RDB 还是 AOF)

这是被问得最多的问题。先记住一句话:Redis 是缓存,不是数据库。你的文章数据永远应该在 MySQL 里,Redis 里的是"可以随时重建的副本"。想通这点,持久化就好选了。

RDB(快照):每隔一段时间把内存 dump 到磁盘。文件小、恢复快、对性能影响小。缺点是两次快照之间的数据会丢。个人站的 session 丢了也无非是让用户重登,可以接受——大多数个人站选 RDB 就够了

save 900 1      # 900 秒内至少 1 次写就存盘
save 300 10
save 60 10000
dbfilename dump.rdb
dir /var/lib/redis

AOF(追加日志):把每条写命令追加到日志,重启时重放。默认 appendfsync everysec,最多丢 1 秒数据。数据安全得多,但文件大、重启慢。

appendonly yes
appendfsync everysec
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

选型建议

  • 只做页面/数据缓存(本文场景二)→ 纯 RDB,甚至可以直接关掉持久化,重启后缓存自然重建,反而更干净;
  • 当 session 存储用 → RDB + 每 60 秒存盘,用户最多被踢一次;
  • 如果你拿 Redis 存了"丢了会心痛"的数据 → 那是架构设计错误,应该挪回 MySQL,再上 AOF 也只是补救。

另外强烈建议单独监控 Redis 内存:redis-cli -a 密码 info memory | grep used_memory_human。一旦 maxmemory 被打满,Redis 会按 maxmemory-policy 淘汰 key。缓存场景一般用 allkeys-lru;如果 Redis 里混存了 session 和缓存,就别用 allkeys-*,会误淘汰 session,改用 volatile-lru 并保证所有缓存 key 都设了过期时间。

五、常见排错清单

  • PHP 写入 session 后立刻失效:Redis 内存满了触发淘汰,或 session.gc_maxlifetime 小于业务需要的登录时长。
  • 连接数暴涨报 ERR max number of clients reached:PHP-FPM 每个进程一条长连接,进程数 × 机器数不能超过 maxclients(默认 10000)。真到这一步通常是代码没复用连接,每次请求都 new Redis()。
  • 命中率上不去:先用 redis-cli info stats | grep keyspacekeyspace_hits/misses。命中率低一般是 key 设计有随机成分(比如把时间戳、随机数拼进 key),等于缓存永远不命中。
  • Redis 把内存吃满导致机器 OOM:务必设 maxmemory 512mb 之类的硬上限,并且给 Redis 配 vm.overcommit_memory=1,否则 fork 做 RDB 时可能失败。
  • 改完配置不生效:确认改的是正在用的那个 redis.conf(有些系统默认读 /etc/redis/redis.conf,有些读 /etc/redis/6379.conf),用 redis-cli config get dir 反查。

六、这套方案的实际收益

拿一个典型的 Typecho 个人站举例,改造前后对比大致是:

  • 首页 TTFB:约 260ms → 约 40ms(数据缓存命中);
  • MySQL QPS:峰值约 150 → 约 30;
  • 多机部署时登录态丢失问题:彻底消失;
  • 新增成本:一台机器上多占 100~200MB 内存,零额外费用。

Redis 对个人站最大的价值不是"高并发",而是用极低的成本把数据库从无意义的重复查询里解放出来。装完、改三行配置、包一层缓存函数,一个下午就能搞定,收益却长期存在。建议按 Session → 数据缓存 → 持久化调优的顺序推进,每步都验证再往下走,别一次性全上。

Last modification:September 20th, 2026 at 10:27 pm

Leave a Comment