Redis 实战 —— 03. Redis 简单实践 - Web应用 (4)

此时,可以定时删除浏览量不在前 10 000 的商品缓存,同时为了保证新的热点商品能够不被已有的热点商品影响,所以在删除商品缓存后,要对没删除的商品次数进行折半处理。可以使用 ZINTERSTORE ,并配置WEIGHTS 选项可以对所有商品分值乘以相同的权重(当只有一个有序集合时,ZUNIONSTORE 效果一样) P33

// 删除非热点商品缓存,重设热点商品浏览量 执行周期,每 5分钟一次 const RESCALE_ITEM_VIEWED_NUM_INTERVAL = 300 // 热点商品浏览量权重 const ITEM_VIEWED_NUM_WEIGHT = 0.5 // 最大缓存商品数量 const MAX_ITEM_CACHED_NUM = 10000 // 删除非热点商品缓存,重设降权热点商品浏览量 // 内部死循环,可用 go 调用,当作定时任务 func RescaleItemViewedNum(conn redis.Conn) { for ; ; { // 删除浏览量最小的 [0, 倒数 20 001] 商品,留下浏览量最大的 20 000 件商品 // 此处留下的浏览量记录是最大缓存商品数量的 2 倍,可以让新热点数据不被删掉 _, _ = conn.Do("ZREMRANGEBYRANK", ITEM_VIEWED_NUM, "0", -((MAX_ITEM_CACHED_NUM << 1) + 1)) // 浏览量折半,保证新热点数据不被影响太多 _, _ = conn.Do("ZINTERSTORE", ITEM_VIEWED_NUM, "1", ITEM_VIEWED_NUM, "WEIGHTS", ITEM_VIEWED_NUM_WEIGHT) // 等待 5min 后,再执行下一次操作 time.Sleep(RESCALE_ITEM_VIEWED_NUM_INTERVAL * time.Second) } }

此处书中疑点

P33 倒数第二段:

新添加的代码记录了所有商品的浏览次数,并根据浏览次数对商品进行了排序,被浏览得最多的商品将被放到有序集合的索引 0 位置上,并且具有整个有序集合最少的分值。

相应 Python 代码片段:

conn.zincrby('viewed:', item, -1)

而进行删除排名在 20 000 名之后的商品操作时如下:

conn.zremrangebyrank('viewed:', 0, -20001)

Redis 命令 ZREMRANGEBYRANK 移除有序集合中排名在区间内的所有元素(按升序排序),按此理解,上述代码的结果会留下浏览量最小的 20 000 个商品,与实际需求不符。

后来看到其他命令使用方式不同,想到可能是 Python 的命令有点不一样,网上一搜发现两种结果都有,还是要自己实践证明。

经过在 Python 中进行实践,上述删除操作的确是按升序排序,删除分值(浏览量)最低的部分,留下分值(浏览量)最高的部分。(当然,实践比较简单,有可能有其他配置会影响结果,就不再探究了)

接下来书上通过以下代码获取浏览量排名,并进行排名判断: P34

rank = conn.zrank('viewed:', item_id) return rank is not None and rank < 10000

可以看到作者在此处的是符合负数浏览量的设置方式的,但是正数浏览量可以通过 ZREVRANK 获取降序排名,可以猜测作者在删除排名在 20 000 后的商品时没有考虑清楚。

按照负数浏览量时,可以使用以下代码正确删除排名在 20 000 后的商品:

conn.zremrangebyrank('viewed:', 20000, -1)

至此,我们就可以实现前面提到过的 canCache 函数,只有能被缓存的商品页面,并且排名在 10 000 内的商品页面才能被缓存。 P34

// 从请求从获取 itemId,不存在则返回 错误(随实际业务场景处理,此处不关心,默认都是 1) func getItemId(request http.Request) (int, error) { return 1, nil } // 判断当前请求是否是动态的(随实际业务场景处理,此处不关心,默认都不是) func isDynamic(request http.Request) bool { return false } // 判断当前请求是否可以缓存(随实际业务场景处理,此处不关心,默认都可以缓存) func canCache(conn redis.Conn, request http.Request) bool { itemId, err := getItemId(request) // 如果没有 itemId(即不是商品页面) 或者 结果是动态的,则不能缓存 if err == nil || isDynamic(request) { return false } // 获取请求的商品页面的浏览量排名 itemRank, err := redis.Int(conn.Do("ZREVRANK", ITEM_VIEWED_NUM, itemId)) // 如果 不存在 或者 排名大于 10000 名,则不缓存 if err != nil || itemRank >= MAX_ITEM_CACHED_NUM { return false } return true } 小结

要随着需求的改变重构已有的代码

所思所想

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpywxj.html