在得物社区中凡是有内容消费的场景,都会有上面两个点赞场景的处理,以是整体点赞业务的QPS在社区都是非常高的。当我们在刷各种Feed流时,每一次下滑,都须要对数十篇内容进行登任命户是否点赞状态的判断。作为根本业务,内容点赞业务的高性能相应,对上游内容场景的消费体验有极大的影响。
本文对得物社区的点赞业务如何做到高性能相应以及历史上在缓存利用上关于高性能、稳定性、低本钱上的优化探索过程进行讲述,希望能给读者带来一些收成。
演进探索v1.0版本
功能需求
社区各种Feed流及内容详情页“登任命户是否已点赞内容” “内容被赞总数” “内容最新点赞用户列表”几个场景消费展示。
实现方案点赞业务整体的高性能是基于Redis+MySQL架构。MySQL做数据存储和查询支持,Redis撑起业务的高性能相应。在1.0版本中,做事架构还是单体PHP做事,技能方案年夜将动态下所有的点赞用户查询出来放到PHP数组里,然后序列化为Json字符串以Key/Value的办法存储到Redis中,当用户浏览内容时,取出缓存数据,反序列化Json为PHP数组,然后通过in_array和count方法判断是否已点赞及内容点赞数。在缓存的掩护上,则是每一次有新用户点赞或取消赞则直接打消Redis。
缓存构造图下:
cId => '[uid1,uid2,uid3...]'
流程图如下:
紧张问题
这个版本的方案存在比较多待优化点。
第一、缓存布局时要查询动态下所有点赞用户数据,数据量大,随意马虎产生慢SQL,对DB和带宽都可能有比较大的压力。
第二、缓存存储数据构造上为Key/Value构造,每次利用时需先从Redis查询,再反序列化成PHP数组,in_array()和count()方法都有比较大的开销,尤其是查询热门动态时,对做事器的CPU和MEM资源都有一定摧残浪费蹂躏,对Redis也产生了比较大的网络带宽开销。
第三、缓存掩护上,每次新增点赞都直接打消缓存,热门动态大量点赞操作下会涌现缓存击穿,会造成大量DB回查操作。
v2.0版本
大家都知道一些热点事宜很随意马虎在社区中发酵,得物社区自然也存在这种情形。在某一场热点事宜中,得物社区瞬间涌现多篇热点内容,大量用户进入得物社区浏览干系动态并点赞,从v1.0版本的点赞掩护流程上可以看出实行毛病,即每次有新点赞都会打消缓存!
当有大量用户浏览热点动态,同时又有大量用户在点赞而导致缓存打消的场景下,缓存被击穿的风险非常高,这样会导致大量查询要求打到DB层,研发侧在评估风险之后,连夜进行了缓存改造。
1、办理热点内容缓存击穿的风险。
2、优化代码层面对缓存数据序列化和反序列化导致的做事器资源花费。
实现方案这次改造,除了优化办理缓存击穿的风险外,也针对之前缓存本身的一些不敷之处,思考了一些更高效的实现。在缓存数据构造上摒弃了之前的Key/Value构造,采取了凑集构造。凑集的特性担保凑集中的用户ID不会涌现重复,可以准确掩护了动态下的点赞总数,通过查看用户是否在凑集中,可以高效判断用户是否点赞内容。这样办理每次查询时须要从Redis中获取全部数据和每次须要代码解析Json的过程,Redis凑集支持直接通过SISMEMBER和SCARD接口判断是否赞和打算点赞数,从而提升了全体模块的相应速率和做事负载。在缓存掩护上,每次有新增点赞时,主动向凑集中添加用户ID,并更新缓存过期韶光。每次查询时,也同样会查询缓存的剩余过期韶光,如果低于三分之一,就会重新更新过期韶光,这样避免了热门动态有大量新增点赞动作时,涌现缓存击穿的情形。
缓存构造如下:
cid => [uid1,uid2,uid3...]
流程图如下:
紧张问题
在技能方案中,会将动态下全部的点赞记录全部查出,放入一个凑集中,当动态是一个热门动态时,点赞用户量会非常大,此时凑集变成了一个大Key,而大Key的清理对Redis的稳定性有比较大的影响,随时可能会由于缓存过期,而引起Redis的抖动,进而引起做事的抖动。并且每次查询出全部的点赞用户,随意马虎产生慢SQL,对网络带宽也比较有压力。
v3.0版本
功能需求1、办理V2.0版本中缓存大Key风险。
2、优化缓存重修时查询内容全部点赞用户产生的慢SQL场景。
实现方案在3.0版本中,对大Key进行了打散处理,对同一个动态下的点赞用户,进行打散分片再掩护到缓存,每次操作缓存时先根据用户ID打算分片值,这样每个分片都具有更小的体积和更快的掩护和相应速率。而点赞总数的获取,此时社区做事已经迁移到Go做事架构,我们也搭建了单独的计数做事,单独掩护内容的被赞总数,节省了scard接口的花费。
缓存构造如下:
cid_slice1 => [uid1,uid11,uid111...] cid_slice2 => [uid2,uid22,uid222...] cid_slice3 => [uid3,uid33,uid333...] ...
流程图如下:
紧张问题
如果仅仅从技能实现上看v3.0版本,彷佛已经暂时达到了一个水平,在一定韶光内也能正常支撑社区点赞业务的高性能相应。但是如果从业务角度和全局不雅观念上去考虑,这个设计方案仍旧存在比较多的优化点。例如:
缓存分片中仍旧掩护了被浏览动态下全部的点赞用户数据,花费了非常大的Redis资源,也增加了缓存掩护难度。缓存数据的有效利用率很低,推举流场景下,用户浏览过的动态,险些不会再次浏览到。
当前技能方案针对单篇内容进行设计,在各种Feed流场景中,查询任务在点赞做事里实在放大了十数倍。这种放大对做事器、Redis以及DB,都产生一定的资源花费摧残浪费蹂躏。
一些点赞量特殊多的历史动态,有人访问时均会重修缓存,重修本钱高,但利用率不高。
缓存凑集分片的设计掩护了较多无用数数据,也产生了大量的Key,Key在Redis中同样是占用内存空间的。
... ...
总结一下,较高的做事器负载、Redis要求量、DB要求量。非常大的Redis资源利用(几十GB)。
以是我们须要一个更优的方案,办理优化以下征象:
1、Feed流场景下批量查询内容任务放大导致的做事器负载,Redis要求,DB要求放大征象。
2、缓存更高效的存储和利用,降落缓存整体的利用量。
3、缓存更高的命中率。
4、区分冷热数据。
实际Feed场景下的实现逻辑:批量查询动态点赞数据
V4.0版本功能需求
结合实际业务场景,大部分场景上游做事都是批量判断是否点赞,社区的动态本身也存在一定的新鲜度(冷热)。对新缓存的哀求是:
1、能办理Feed流场景下批量查询流量放大征象。
2、缓存数据区分冷热,减少无效存储(能在内容和点赞用户角度都区分冷热数据)。
3、缓存构造要大略易掩护,使业务实现要清晰明了。
实现方案设计思路:1、批量查询任务之以是放大是由于之前的缓存因此内容为维度进行设计,新方案要以用户为维度进行设计。
2、旧方案中访问内容点赞数据会重修缓存,有些老旧内容重修缓存性价比低,而且内容下的点赞用户并不是一贯生动和会重新访问内容,新方案要等区分冷热数据,冷数据直接访问DB,不再进行缓存的重修/更新掩护。
3、旧方案在掩护缓存过期韶光和延长过期韶光的设计中,每次操作缓存都会进行ttl接口操作,QPS直接x2。新方案要避免ttl操作,但同时又可以掩护缓存过期韶光。
4、缓存操作和掩护要大略,期望一个Redis接口操作能达到目的。
以是新方案Redis数据构造的选择中,能判断是否点赞、是否是冷热数据、是否须要延长过期韶光,之前的凑集是不能知足了,我们选择Hash表构造。用户ID做Key,contentId做field,考虑到社区内容ID是趋势递增的,一定程度上coententID能代表数据的冷热,在缓存中只掩护一定韶光和一定数量的contentID,并且增加minCotentnID用于区分冷热数据,为了减少ttl接口的调用,还增加ttl字段用户判断缓存有效期和延长缓存过期韶光。一举三得!
缓存构造如下:
{ "userId":{ "ttl":1653532653, //缓存新建或更新时时间戳 "cid1":1, //用户近一段韶光点赞过的动态id "cid2":1, //用户近一段韶光点赞过的动态id "cidn":1, //用户近一段韶光点赞过的动态id "minCid":3540575, //缓存中最小的动态id,用以区分冷热, }}
在实际业务场景流程如下:
通过流程图,我们可以清晰看到, 上游Feed流,一次批量查询要求,没有了循环逻辑,最优情形下,只有一次Redis操作,业务实现也非常大略明了。
优化结果优化前后Redis查询量QPS日常峰值低落了20倍。
优化前后接口均匀RT低落了10倍。
优化前后DB查询量QPS日常峰值低落了6倍。
优化前后缓存节省了16G旁边存储空间。
后续
优化不会结束,技能不会停滞,技能方案会随着业务的演进而演进。
总结本篇文章中对得物社区点赞业务缓存优化的探索演进做了干系历史背景和技能方案的解析,当前个中还有更多的细节。而这么多次版本的优化,都是根据实际的业务场景中涌现的风险点以及需求不断摸索出来的,每个版本的方案也都不是完美方案,v4.0也不是终极方案,还须要开拓职员也须要进一步思虑,探索更优的技能方案。
并且随着业务的不断发展迭代,会呈现出更多的场景和困难,我们一贯在优化探索的路上。
文/慎之
关注得物技能,每周一三五晚18:30更新技能干货假如以为文章对你有帮助的话,欢迎评论转发点赞~