资讯动态

数据库专题19:Redis 热点计数与排行榜——INCR 很快,但不是永久事实

发布时间:2026/9/12 17:48:04 来源:尧图企业网站定制
数据库专题19Redis 热点计数与排行榜——INCR 很快但不是永久事实每次打开文章都去 PostgreSQLcount(*)会把热点文章变成数据库压力源。Redis 的INCR和 Sorted Set 能以原子操作记录浏览增量、生成排行榜再由后台批量落回 PostgreSQL。本篇实现按文章和日期分桶的计数、幂等落库批次、排行榜重建并明确 Redis 丢失后的恢复路径。上一篇练习讲解缓存旁路练习要求随机 TTL、Redis 超时回源和 single-flight。写路径必须先提交 PostgreSQL再删除 detail/list key删除失败进入重试事务回滚不能删除旧缓存。缓存 key 要包含版本和筛选条件权限敏感数据不能共用公共缓存。1. 浏览事件和 key 设计article:views:2025-01-01:42 - String counter article:hot:2025-01-01 - Sorted Setmember42 score浏览量 article:views:pending:2025-01-01 - 已落库批次标记按日期分桶便于自然过期和生成“日榜”。UTC 作为分桶基准展示时转换时区跨天时服务端根据事件时间计算 key不能直接使用服务器本地时间。2. 原子记录浏览fromdatetimeimportdatetime,timezonedefrecord_view(redis,article_id:int,now:datetime|NoneNone)-int:INCR 与 ZINCRBY 都是 Redis 原子命令多 worker 不会互相覆盖。day(nowordatetime.now(timezone.utc)).strftime(%Y-%m-%d)counterfarticle:views:{day}:{article_id}rankingfarticle:hot:{day}valueredis.incr(counter)redis.zincrby(ranking,1,article_id)# 计数 key 保留 8 天给批量落库和补偿留时间redis.expire(counter,8*24*3600)redis.expire(ranking,8*24*3600)returnvalue重复刷新会重复计数产品若要求“每用户每天一次”加SET article:viewed:{day}:{user_id}:{article_id} NX EX只有返回 True 才执行 INCR。不要在 Python 中先 GET 再 SET两个请求会丢增量。3. 批量落回 PostgreSQL主库增加持久计数altertablearticlesaddcolumnifnotexistsview_countbigintnotnulldefault0;createtableifnotexistsview_batches(batch_id uuidprimarykey,daydatenotnull,article_idbigintnotnullreferencesarticles(id),deltabigintnotnull,applied_at timestamptznotnulldefaultnow());落库服务读取 Redis 增量并使用批次 idfromuuidimportuuid4fromsqlalchemyimporttextdefflush_view(engine,batch_id:str,article_id:int,day:str,delta:int)-str:使用消息携带的固定 batch_id重试同一批次不会再次累加。withengine.begin()asconn:insertedconn.execute(text( insert into view_batches(batch_id,day,article_id,delta) values (:batch_id,:day,:article_id,:delta) on conflict (batch_id) do nothing returning batch_id ),{batch_id:batch_id,day:day,article_id:article_id,delta:delta}).first()ifinserted:conn.execute(text(update articles set view_countview_count:delta where id:id),{delta:delta,id:article_id})returnbatch_id真实 worker 应把批次 id 固定在消息中重试同一个批次示例每次生成新 id 只是演示结构不能直接用于重试。更严谨的实现是先把 Redis 增量原子转移到pendingkey再落库防止落库过程中 Redis 丢失增量。4. 读取排行榜和重建deftop_articles(redis,day:str,limit:int10):limitmax(1,min(limit,100))returnredis.zrevrange(farticle:hot:{day},0,limit-1,withscoresTrue)defrebuild_ranking(conn,redis,day:str):keyfarticle:hot:{day}:rebuild:{uuid4()}rowsconn.execute(text( select id,view_count from articles where statuspublished )).all()forarticle_id,countinrows:redis.zadd(key,{article_id:count})redis.rename(key,farticle:hot:{day})临时 key 填充完成后再RENAME读者不会看到半套排行榜。若排行榜包含“当天增量 持久总量”重建前先确定两者时间窗口避免重复累加。验收与故障排查并发记录100次浏览 - article:views:day:42 100 ZREVRANGE - 按 score 倒序前10条正确 批次重复消费 - view_count 只增加一次 清空 Redis 后重建 - 排行榜与 PostgreSQL view_count 一致 用户日去重开启 - 同用户同文章同日只增加1如果view_count比 Redis 大检查落库批次是否重复如果跨天统计错位统一使用 UTC 并记录事件时间如果排行榜 key 被误删按持久计数重建不要手工猜分数。课后练习实现按日期分桶的排行榜 API增加has_next和 TTL设计 Redis 增量原子转移 Lua 脚本或临时 key 流程压测 1000 次并发浏览记录 Redis QPS、落库批次耗时和数据库连接数。下一篇处理缓存与数据库更新失败的一致性和故障降级。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价