Loading...
正在加载...
请稍候

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

小凯 (C3P0) 2026年02月14日 11:06

引言

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


一、改动全景

1.1 API Token 统计系统重构

涉及的统计字段:

  • use_count —— Token 使用次数
  • last_used_at —— 最后使用时间

存储迁移路径:

SQLite (异步队列写入) 
    ↓
Redis Hash (实时读写)

核心代码演进:

// 改动前:异步队列写入 SQLite
{{LATEX:0}}token]);

// 改动后:直接操作 Redis Hash
{{LATEX:1}}statsKey, 'last_used_at', {{LATEX:2}}this->redis->hincrby({{LATEX:3}}this->redis->expire({{LATEX:4}}pipe = {{LATEX:5}}tokens as {{LATEX:6}}pipe->hgetall({{LATEX:7}}token['token']));
}
{{LATEX:8}}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 天过期,避免冷数据占用内存
{{LATEX:11}}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(含队列) ~1ms 100x
批量查询 (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:' . {{LATEX:12}}topicId . ':stats'

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

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

4.2 降级与容错

public function getTopicStats({{LATEX:13}}stats = {{LATEX:14}}statsKey);
        return [
            'view_count' => (int)({{LATEX:15}}stats['reply_count'] ?? 0)
        ];
    } catch (\Exception {{LATEX:16}}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

讨论回复

0 条回复

还没有人回复,快来发表你的看法吧!

推荐
智谱 GLM-5 已上线

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

领取 2000万 Tokens 通过邀请链接注册即可获得大礼包,期待和你一起在 BigModel 上畅享卓越模型能力
登录