写点什么

聊点缓存——Part 1

用户头像
姜雨生
关注
发布于: 2020 年 11 月 03 日
聊点缓存——Part 1



存储介质的瓶颈主要在磁盘,从而限制了数据库的查询,我们往往会使用缓存加快数据的访问,特别是对于并发性很高的服务,缓存的价值就大了很多,互联网没有银弹,如何更好地使用也是一个难题。

本地缓存



早些年一些ORM框架都提供者自己的一些基本缓存,例如Query Cache,根据查询语句的内容进行缓存,或者一些本地的local cache 框架等等。这个在单机时代基本能解决一大部分问题。不过随着分布式系统的诞生,多实例服务已经是一个很通用的场景,这个时候缓存的不一致问题就被放大了。比如说A服务有 a1 和 a2 两个机器,都有存张三的信息,如果在 a1 服务器上改变了张三的信息,那通过a2的服务器拿到的张三信息就都是错误的,因为a2的缓存不会更新。



解决内存级别的不一致



改变这种机制也很容易,通过轮询或者被动通知的机制即可。而轮询的代价很高,而且不具备确定性。所以会通过通知机制完成。



UDP 广播



最简单的方式是利用UDP协议进行广播式通信,告知所有的服务器某个Key的缓存发生了变更,要求服务器进行一次接收并Refresh 一次。



相对于TCP协议,UDP更快更迅速,配置好对应的服务IP与端口,就可以很快地完成,如果想动态的通知各个服务器,可能还需要使用一些注册发现机制来完成这个内容。缺点也很明显,UDP不保证到达,丢失的情况基本无法补救。

消息机制



这里面也可以使用kafka等一类的消息队列来实现,某一个key的变更通知其他的实例进行一次Refresh。确实有点笨重,不过对于已经使用了消息队列的Server,也就是增加一个topic和一个consumer group的事情,并没有很复杂。对于目前的消息队列,可以稳定的保证到达以及被消费。



以上的两种方案也都有一个问题就是延迟的问题,这个问题在本地缓存上没有特别好的解决方案,而且对于缓存的场景,必须要有一定的延迟容忍度,至于哪种方案可能就需要斟酌了。



缓存中间件



如果你需要使用缓存中间件来解决缓存的问题,那一定要想清楚为什么不能用本地缓存。不论是什么中间件,网络的开销都是省不掉的,还有序列化等等的问题,都是极大的开销,往往达不到想要的效果。



往往这里选择上,都是选择Redis作为中间件,Redis因为单线程的缘故,他同时也是一些简易版分布式锁的选择。



使用Redis这种No-Sql的分布式组件,更多的时候是要考虑好你的缓存的数据结构,很多人可能单间的把一个大的缓存对象搞成字符串直接放到Redis里面,这就是一种错误的使用方式,因为当你把这个对象在内存中序列化的时候,就会很耗时,而且可能还会产生GC的问题,因为这是一种大对象。我曾经遇到过一个问题,一个特别大的缓存对象(2MB),数据结构也比较复杂,每次获取这个缓存就要用掉1秒,这个完全不如我自己在构建一份快多少。



这里还想提一句,Redis的数据结构有多少种这个问题,百度一大堆资料都是5种,我特别想说,能不能查查Redis的官网,很早就不只5种了,虽然其他的数据结构不够常用,但是在某些场景还是很棒的,比如说一个坐标的数据结构,可以帮助你处理一些路径,范围查询等的问题。这里建议还是查询一下Redis官网了解更多的信息。



Redis还有两个概念,slot和hashTag,这两个内容可以帮助你将批量的key同时放在一个redis实例里面,减少重定向的开销,这块的内容不在我今天讨论范围内,有空单独拿出来再说说。



缓存什么内容



缓存的内容往往也是一门艺术,一般分为两种,缓存初始数据和缓存中间结果。

初始数据



比如说你有一个人员列表信息,存着各个地区的人员,而查询条件经常是根据地区的ID进行查询。那这种case,把数据全部存储到内存里面,然后每次遍历一遍拿到对应的数据返回即可。当然这个前提还是内存能够存下这个内容。这种场景就是存储数据库查询的原始数据。



存储中间结果



还是上面的case,更好的方式其实是按照地域存储多分结果集,构建一个类似字典的K-V结构,key是地域的Id,Value就是所有在这个地域的用户,这样减少一次遍历的结果。而这种case可能不够明显,比如说涉及一些复杂的结果集,比如说根据一些计算公式,计算出这个规则适用的人群的case,例如注册满5年,消费满1万的用户可以享受满300-40的优惠,这种计算再分类的过程每次处理是很耗时的。存储一些中间结果更为合适。



什么时候删掉缓存



缓存是占用内存的,长期占用就会产生内存泄漏,如何淘汰掉缓存也是要进行处理的,最简单的就是让一些不常用的数据缓存失效。这里我就用Redis的策略距离了。



Redis采用的是被动驱逐 + 主动轮询的策略



  • 每次访问Key的时候,检查缓存是否过期,如果过期就直接删除并返回不存在。

  • 定期轮询某一个或者几个slot(理解为分区即可),删除掉已经失效的缓存。



这种方式对于内存缓存来讲,被动策略比较好管理,而主动轮询策略,也可以通过定时任务来完成。只是需要注意的是处理并发的场景,用一些并发的集合类型,大部分场景都能处理。



未完待续,关注不迷路。



发布于: 2020 年 11 月 03 日阅读数: 22
用户头像

姜雨生

关注

还未添加个人签名 2018.07.23 加入

还未添加个人简介

评论

发布
暂无评论
聊点缓存——Part 1