$redis->setex("train:{$trainId}:seats", 30, $available);高并发票务系统中缓存余票的核心操作,通过 原子性写入 + 自动过期 实现高性能与最终一致性。


一、核心原理:为什么用 SETEX

▶ 1. 命令本质
  • SETEX key seconds value = SET key value + EXPIRE key seconds 的原子操作
  • 原子性保障
    • 避免 SET 后未及时 EXPIRE 导致缓存永不过期
▶ 2. 业务场景
  • 余票缓存
    • 高频查询(如 12306 放票瞬间)
    • 数据时效性要求高(30 秒内有效)
  • 优势
    • 减轻 DB 压力:99% 请求命中 Redis
    • 自动过期:避免脏数据长期滞留

💡 核心认知
SETEX = 原子写入 × 自动过期 × 高性能


二、底层机制:Redis 如何执行?

▶ 1. 执行流程
Redis 服务端 PHP 客户端 Redis 服务端 PHP 客户端 SETEX train:100:seats 30 150 1. 写入键值对 2. 设置过期时间(绝对时间戳) 3. 更新 expires dict OK
▶ 2. 内存结构
  • redisDb 结构
    typedef struct redisDb {
        dict *dict;     // 键空间(key → value)
        dict *expires;  // 过期字典(key → expire_time)
    } redisDb;
    
  • 过期策略
    • 惰性删除:访问时检查过期
    • 定期删除:随机抽样清理过期键
▶ 3. 性能数据
操作 耗时(RTT) QPS(单实例)
SETEX 0.2ms 100,000+

📌 关键点
SETEX 是 O(1) 操作,无锁设计,线程安全


三、工程实践:票务系统中的正确用法

▶ 1. 缓存更新策略
  • Cache-Aside 模式
    // 查询余票
    public function getAvailableSeats($trainId) {
        $key = "train:{$trainId}:seats";
        $available = $redis->get($key);
        
        if ($available === false) {
            // 缓存未命中 → 查 DB
            $available = $db->query("SELECT available FROM inventory WHERE train_id = ?", [$trainId]);
            
            // 回填缓存(30秒过期)
            $redis->setex($key, 30, $available);
        }
        return $available;
    }
    
▶ 2. 缓存击穿防护
  • 问题
    • 余票为 0 时,大量请求穿透到 DB
  • 解决方案
    // 空值缓存(防击穿)
    if ($available == 0) {
        $redis->setex($key, 60, 0); // 延长空值 TTL
    }
    
▶ 3. 缓存与 DB 一致性
  • 最终一致性方案
    • 扣减库存:先更新 DB,再删除缓存(Cache-Delete)
    // 扣减余票
    $db->exec("UPDATE inventory SET available = available - 1 WHERE train_id = ? AND available > 0", [$trainId]);
    if ($db->affectedRows() > 0) {
        $redis->del("train:{$trainId}:seats"); // 删除缓存
    }
    
  • 优势
    • 避免并发更新导致缓存与 DB 不一致

四、避坑指南

陷阱 破局方案
盲目设置短 TTL 根据业务调整(余票变化频率)
忽略空值缓存 余票=0 时缓存空值,防击穿
先更新缓存后 DB 必须先 DB 后缓存,否则可能写入旧数据

五、终极心法

**“SETEX 不是命令,
而是缓存的呼吸——

  • 当你 原子写入
    你在校准时效;
  • 当你 自动过期
    你在铸造纯净;
  • 当你 删除缓存
    你在守护一致。

真正的高并发,
始于对缓存的敬畏,
成于对细节的精控。”


结语

从今天起:

  1. 高频读数据必用 SETEX
  2. 空值场景缓存 0 并延长 TTL
  3. 更新时先 DB 后删缓存

因为最好的缓存设计,
不是盲目存储,
而是精准控制每一比特的流动。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐