强啊,点赞业务缓存设计优化探索之路。

背景

v1.0版本

功能需求

实现方案

缓存结构如下:

cId => [uid1,uid2,uid3...]

流程图如下:

主要问题

这个版本的方案存在比较多待优化点。

第二、缓存存储数据结构上为Key/Value结构,每次使用时需先从Redis查询,再反序列化成PHP数组,in_array()和count()方法都有比较大的开销,尤其是查询热门动态时,对服务器的CPU和MEM资源都有一定浪费,对Redis也产生了比较大的网络带宽开销。

v2.0版本

功能需求

    1、解决热点内容缓存击穿的风险。 2、优化代码层面对缓存数据序列化和反序列化导致的服务器资源消耗。

实现方案

这次改造,除了优化解决缓存击穿的风险外,也针对之前缓存本身的一些不足之处,思考了一些更高效的实现。

在缓存数据结构上摒弃了之前的Key/Value结构,采用了集合结构。

缓存结构如下:

cid => [uid1,uid2,uid3...]

流程图如下:

主要问题

v3.0版本

功能需求

    1、解决V2.0版本中缓存大Key风险。

实现方案

缓存结构如下:

cid_slice1 => [uid1,uid11,uid111...] 
cid_slice2 => [uid2,uid22,uid222...] 
cid_slice3 => [uid3,uid33,uid333...] 
...

流程图如下:

主要问题

但是如果从业务角度和全局观念上去考虑,这个设计方案仍旧存在比较多的优化点。例如:

    缓存集合分片的设计维护了较多无用数数据,也产生了大量的Key,Key在Redis中同样是占用内存空间的。 总结一下,较高的服务器负载、Redis请求量、DB请求量。非常大的Redis资源使用(几十GB)。

所以我们需要一个更优的方案,解决优化以下现象:

    1、Feed流场景下批量查询内容任务放大导致的服务器负载,Redis请求,DB请求放大现象。 2、缓存更高效的存储和使用,降低缓存整体的使用量。 3、缓存更高的命中率。 4、区分冷热数据。

实际Feed场景下的实现逻辑:

V4.0版本

功能需求

    1、能解决Feed流场景下批量查询流量放大现象。 3、缓存结构要简单易维护,使业务实现要清晰明了。

实现方案

设计思路如下:

    1、批量查询任务之所以放大是因为之前的缓存是以内容为维度进行设计,新方案要以用户为维度进行设计。 3、旧方案在维护缓存过期时间和延长过期时间的设计中,每次操作缓存都会进行ttl接口操作,QPS直接x2。新方案要避免ttl操作,但同时又可以维护缓存过期时间。 4、缓存操作和维护要简单,期望一个Redis接口操作能达到目的。

用户ID做Key,contentId做field,考虑到社区内容ID是趋势递增的,一定程度上coententID能代表数据的冷热,在缓存中只维护一定时间和一定数量的contentID,并且增加minCotentnID用于区分冷热数据,为了减少ttl接口的调用,还增加ttl字段用户判断缓存有效期和延长缓存过期时间。

一举三得!

缓存结构如下:

在实际业务场景流程如下:

通过流程图,我们可以清晰看到, 上游Feed流,一次批量查询请求,没有了循环逻辑,最优情况下,只有一次Redis操作,业务实现也非常简单明了。

优化结果

优化前后Redis查询量QPS日常峰值下降了20倍。

优化前后接口平均RT下降了10倍。

优化前后DB查询量QPS日常峰值下降了6倍。

优化前后缓存节省了16G左右存储空间。

总结

而这么多次版本的优化,都是根据实际的业务场景中出现的风险点以及需求不断摸索出来的,每个版本的方案也都不是完美方案,v4.0也不是最终方案,还需要开发人员也需要进一步思索,探索更优的技术方案。

·· ······ ··········END················

经验分享 程序员 微信小程序 职场和发展