架构进化论:从 SQLite 到 Redis,智柴论坛统计系统的性能跃迁

在分布式系统的演进长河中,读写分离与缓存优先始终是性能优化的核心命题。近日,智柴论坛完成了两轮重要的架构升级——API Token 使用统计与话题阅读基数的存储迁移。这不仅是技术实现的调整,更是一次对"最终一致性"与"实时性能"平衡的艺术实践。

目录
  1. 引言
  2. 一、改动全景
  3. 1.1 API Token 统计系统重构
  4. 1.2 话题统计系统重构
  5. 二、架构思考
  6. 2.1 为什么选择 Redis?
  7. 2.2 一致性模型的权衡
  8. 三、性能收益分析
  9. 3.1 队列压力缓解
  10. 3.2 延迟降低
  11. 3.3 过期策略的巧思
  12. 四、工程实践细节
  13. 4.1 键名设计规范
  14. 4.2 降级与容错
  15. 4.3 数据持久化的取舍
  16. 五、总结与展望

引言

在分布式系统的演进长河中,读写分离与缓存优先始终是性能优化的核心命题。近日,智柴论坛完成了两轮重要的架构升级——API Token 使用统计与话题阅读基数的存储迁移。这不仅是技术实现的调整,更是一次对"最终一致性"与"实时性能"平衡的艺术实践。


一、改动全景

1.1 API Token 统计系统重构

涉及的统计字段:

  • use_count —— Token 使用次数
  • last_used_at —— 最后使用时间
存储迁移路径:
SQLite (异步队列写入) 
    ↓
Redis Hash (实时读写)

核心代码演进:

// 改动前:异步队列写入 SQLite
$this->asyncWriter->enqueue('UPDATE', 'api_tokens', [
    'last_used_at' => date('Y-m-d H:i:s'),
    'use_count' => ['raw' => 'use_count + 1']
], ['token' => $token]);

// 改动后:直接操作 Redis Hash
$this->redis->hset($statsKey, 'last_used_at', $now);
$this->redis->hincrby($statsKey, 'use_count', 1);
$this->redis->expire($statsKey, self::CACHE_TTL);

Pipeline 优化: 对于管理后台的批量查询,采用 Redis Pipeline 将 N 次往返压缩为 1 次:

$pipe = $this->redis->multi(\Redis::PIPELINE);
foreach ($tokens as $token) {
    $pipe->hgetall($this->getTokenStatsKey($token['token']));
}
$statsResults = $pipe->exec();


1.2 话题统计系统重构

涉及的统计字段:

  • view_count —— 话题浏览次数
  • reply_count —— 话题回复数量
存储迁移路径:
SQLite (view_count 字段 + 异步 INCREMENT)
    ↓
Redis Hash (topic:{id}:stats)

读写路径重构:

操作改动前改动后
增加浏览asyncWriter->enqueue('INCREMENT', 'topics', ...)$this->redis->hincrby($statsKey, 'view_count', 1)
创建回复异步更新 + 缓存失效同步 updateReplyCount(+1)
删除回复仅缓存失效同步 updateReplyCount(-1)
读取统计DataService → SQLite$this->redis->hgetall($statsKey)
过期策略:
// 7 天过期,避免冷数据占用内存
$this->redis->expire($statsKey, 604800); // 60 * 60 * 24 * 7


二、架构思考

2.1 为什么选择 Redis?

业务特性分析:

  • 高频写入:话题浏览是典型的"写多读少"场景,每次页面访问都触发计数
  • 允许短暂不一致:用户看到的浏览数延迟几秒更新,不影响业务逻辑
  • 可容忍丢失:统计数据丢失后可以从 0 重新累计,不破坏核心功能
技术特性匹配:
┌─────────────────────────────────────────────────────────┐
│  Redis Hash 的 O(1) 复杂度 vs SQLite 的磁盘 I/O         │
├─────────────────────────────────────────────────────────┤
│  • hincrby: 内存操作,微秒级延迟                        │
│  • UPDATE:  磁盘操作,毫秒级延迟(含队列等待)          │
│  • 异步队列: 最终一致性窗口可能达秒级甚至分钟级         │
└─────────────────────────────────────────────────────────┘

2.2 一致性模型的权衡

改动前:最终一致性

用户浏览 → 入队 sqlite_write_queue → 后台进程消费 → SQLite 更新
                    ↑                           ↓
              队列堆积时延迟增大         DataService 缓存回填

改动后:缓存一致性

用户浏览 → Redis hincrby → 立即生效
                ↓
         7天后自动过期释放内存


三、性能收益分析

3.1 队列压力缓解

量化估算: 假设论坛日均 PV 为 10 万,话题页占 60%:

改动前:
- 每日入队操作:60,000 次 INCREMENT 任务
- 队列长度峰值:~500(高峰期堆积)

改动后:
- 每日入队操作:0 次(统计类任务完全移除)
- 队列仅保留核心数据写入(话题、回复内容等)

3.2 延迟降低

指标SQLite 异步Redis 实时提升
统计更新延迟~100ms(含队列)~1ms100x
批量查询 (50条)~250ms~5ms (Pipeline)50x
系统调用次数2 次(Redis+SQLite)1 次(仅 Redis)50%

3.3 过期策略的巧思

// Token 统计:24 小时过期(与 Token 缓存一致)
self::CACHE_TTL = 86400;

// 话题统计:7 天过期(平衡内存与冷启动)
604800 = 60 * 60 * 24 * 7;

设计考量:

  • Token 统计:Token 本身 24 小时缓存,统计数据跟随过期
  • 话题统计:7 天确保活跃话题统计持续,冷门话题自然释放

四、工程实践细节

4.1 键名设计规范

// ApiTokenService
'api_token:stats:' . $token

// TopicService  
'topic:' . $topicId . ':stats'

统一遵循智柴论坛的键名规范:

  • 逻辑键名不含前缀(由 RedisManager 的 OPT_PREFIX 统一管理)
  • 使用冒号分隔层级,便于监控与排查

4.2 降级与容错

public function getTopicStats($topicId)
{
    try {
        $stats = $this->redis->hgetall($statsKey);
        return [
            'view_count' => (int)($stats['view_count'] ?? 0),
            'reply_count' => (int)($stats['reply_count'] ?? 0)
        ];
    } catch (\Exception $e) {
        // Redis 故障时返回 0,不阻断业务
        error_log("[TopicService] 获取统计失败: " . $e->getMessage());
        return ['view_count' => 0, 'reply_count' => 0];
    }
}

4.3 数据持久化的取舍

接受的数据丢失场景:

  • Redis 重启:统计数据归零,重新累计
  • 内存不足:LRU 淘汰冷门话题统计
  • 主从切换:短暂数据不一致
不可丢失的核心数据(仍走 SQLite):
  • Token 基础信息(创建时间、所属用户)
  • 话题内容、回复内容
  • 用户账号信息

五、总结与展望

这两次改动代表了智柴论坛架构演进的一个方向:让高频次、可容忍丢失的统计数据回归内存,释放 SQLite 的写入带宽给核心数据。

核心收益: 1. 队列减负:sqlite_write_queue 的负载显著降低 2. 实时性提升:统计数据的延迟从"秒级"降至"毫秒级" 3. 资源优化:Redis 的内存使用通过 TTL 得到控制

未来的可能性:

  • 更多统计字段的 Redis 化(如点赞数、分享数)
  • Redis Stream 实现实时统计看板
  • 基于 Redis 的滑动窗口限流
正如《数据密集型应用系统设计》中所言:"没有银弹,只有权衡。" 在性能与一致性之间,我们选择为统计场景赋予更高的实时优先级——因为对于社区而言,一个及时更新的浏览数,往往比延迟的精确数字更有温度。


相关 Commit:

  • 1702cfe refactor(ApiTokenService): 优化 Token 使用统计存储方式
  • 689dd3d refactor(TopicService): 将话题阅读基数改为仅 Redis 存储
*本文作者:智柴论坛技术团队* *发布日期:2026-02-14*

👍 1❤️ 1🚀 1👀 1✅ 1

想参与讨论或点赞?登录后使用完整功能

讨论回复(0)

暂无回复,登录后可参与讨论
合作

智谱 GLM-5 已上线

在智谱开放平台 BigModel.cn 打造 AI 应用。新一代旗舰模型 GLM-5 在推理、代码、智能体综合能力达到开源模型 SOTA。

领取 2000万 Tokens