缓存代理服务已经帮我们解决这些问题了,例如:Twemproxy 不但可以帮助缓存路由,同时可以管理缓存节点 。
这里有介绍三种缓存数据分片的算法,有了这些算法缓存代理就可以方便的找到分片的数据了 。
①哈希算法
Hash 表是最常见的数据结构,实现方式是,对数据记录的关键值进行 Hash,然后再对需要分片的缓存节点个数进行取模得到的余数进行数据分配 。
例如:有三条记录数据分别是 R1,R2,R3 。他们的 ID 分别是 01,02,03,假设对这三个记录的 ID 作为关键值进行 Hash 算法之后的结果依旧是 01,02,03 。
我们想把这三条数据放到三个缓存节点中,可以把这个结果分别对 3 这个数字取模得到余数,这个余数就是这三条记录分别放置的缓存节点 。

文章插图
Hash 算法是某种程度上的平均放置,策略比较简单,如果要增加缓存节点,对已经存在的数据会有较大的变动 。
②一致性哈希算法
一致性 Hash 是将数据按照特征值映射到一个首尾相接的 Hash 环上,同时也将缓存节点映射到这个环上 。
如果要缓存数据,通过数据的关键值(Key)在环上找到自己存放的位置 。这些数据按照自身的 ID 取 Hash 之后得到的值按照顺序在环上排列 。

文章插图
如果这个时候要插入一条新的数据其 ID 是 115,那么就应该插入到如下图的位置 。

文章插图
同理如果要增加一个缓存节点 N4 150,也可以放到如下图的位置 。

文章插图
这种算法对于增加缓存数据,和缓存节点的开销相对比较小 。
③Range Based 算法
这种方式是按照关键值(例如 ID)将数据划分成不同的区间,每个缓存节点负责一个或者多个区间 。跟一致性哈希有点像 。
例如:存在三个缓存节点分别是 N1,N2,N3 。他们用来存放数据的区间分别是,N1(0, 100], N2(100, 200], N3(300, 400] 。
那么数据根据自己 ID 作为关键字做 Hash 以后的结果就会分别对应放到这几个区域里面了 。
可用性
根据事物的两面性,在分布式缓存带来高性能的同时,我们也需要重视它的可用性 。那么哪些潜在的风险是我们需要防范的呢?
①缓存雪崩
当缓存失效,缓存过期被清除,缓存更新的时候 。请求是无法命中缓存的,这个时候请求会直接回源到数据库 。
如果上述情况频繁发生或者同时发生的时候,就会造成大面积的请求直接到数据库,造成数据库访问瓶颈 。我们称这种情况为缓存雪崩 。
从如下两方面来思考解决方案:
- 缓存方面
- 设计方面
- 避免缓存同时失效,不同的 key 设置不同的超时时间 。
- 增加互斥锁,对缓存的更新操作进行加锁保护,保证只有一个线程进行缓存更新 。缓存一旦失效可以通过缓存快照的方式迅速重建缓存 。对缓存节点增加主备机制,当主缓存失效以后切换到备用缓存继续工作 。
- 熔断机制:某个缓存节点不能工作的时候,需要通知缓存代理不要把请求路由到该节点,减少用户等待和请求时长 。
- 限流机制:在接入层和代理层可以做限流(之前的文章讲到过),当缓存服务无法支持高并发的时候,前端可以把无法响应的请求放入到队列或者丢弃 。
- 隔离机制:缓存无法提供服务或者正在预热重建的时候,把该请求放入队列中,这样该请求因为被隔离就不会被路由到其他的缓存节点 。
②缓存穿透
缓存一般是 Key,Value 方式存在,一个 Key 对应的 Value 不存在时,请求会回源到数据库 。
假如对应的 Value 一直不存在,则会频繁的请求数据库,对数据库造成访问压力 。如果有人利用这个漏洞攻击,就麻烦了 。
解决方法:如果一个 Key 对应的 Value 查询返回为空,我们仍然把这个空结果缓存起来,如果这个值没有变化下次查询就不会请求数据库了 。
将所有可能存在的数据哈希到一个足够大的 Bitmap 中,那么不存在的数据会被这个 Bitmap 过滤器拦截掉,避免对数据库的查询压力 。
推荐阅读
- UI进阶知识-按钮设计,看这篇文章就够了
- 关于消息队列的优缺点,看这篇就行
- 看完这篇你还不知道这些队列,我这些图白作了
- 红薯粉怎么辨别真假?干货店老板:教你5招,看一眼就能区别
- 汽车是真皮座椅,还需要座垫套吗?这篇文章来给你答案
- 茶是极简
- 看了这篇文章 我才知道PS混合模式这么简单
- 这篇文章把 Linux 系统讲的如此详细,看完你一定会有质的飞跃
- 梦见冒烟但没看见火焰浇灭了 梦见冒烟但没看见火焰一直逃避
- PHP的缓冲区你了解过吗
