侧边栏壁纸
博主头像
Haenu的Blog 博主等级

坚持学习,慢慢进步!

  • 累计撰写 35 篇文章
  • 累计创建 10 个标签
  • 累计收到 2 条评论

目 录CONTENT

文章目录

有关用户注册功能中缓存穿透的思考

Haenu
2024-06-26 / 0 评论 / 0 点赞 / 69 阅读 / 0 字

什么是缓存穿透?

缓存穿透是指在使用缓存系统时,恶意或频繁地请求一个不存在于缓存中的数据,导致每次请求都需要查询数据库或其他数据存储系统,从而绕过了缓存的效果,严重影响系统性能。

这种情况通常发生在恶意攻击、大量请求缓存中不存在的数据或缓存数据过期后的高并发访问。

缓存穿透会导致以下问题:

  1. 频繁的查询数据库或其他数据存储系统,增加了数据库负载,降低了系统的吞吐量。

  2. 大量的缓存不存在的数据请求可能会导致缓存服务器的内存被耗尽,影响其他正常的缓存操作。

  3. 用户体验下降,因为请求的数据无法从缓存中获取,导致响应时间延长。

常见的解决方案

  1. 缓存空对象

  2. 布隆过滤器

  3. Redis Set集合

  4. 分布式锁,只允许一个线程去访问数据库

1. 缓存空对象

1、空值做了缓存,意味着缓存层中存了更多的键,需要更多内存空间。

比较有效的方法是针对这类数据设置一个较短的过期时间。

2、缓存层和存储层的数据会有一段时间窗口的不一致,可能会对业务有一定影响。

2. 布隆过滤器

使用布隆过滤器,将所有已注册的用户名存入布隆过滤器,判断时先判断该用户名是否在布隆过滤器中,不在的一定不存在,避免直接查询数据库。
这种解决方案算是网上八股说的比较多的一个版本。但是依然不能解决实际场景问题。
如果用户注销了账号,该用户名就可以再次被使用。然而,布隆过滤器由于无法删除元素,因此无法处理这种情况。
结论:布隆过滤器不能删除元素的限制,导致该方案无法正式使用生产。

目前网上貌似有使用过关于布谷鸟过滤器的

3. Redis Set 存储已注册用户名

使用确定的数据结构如 Redis 的 Set 集合来存储已注册用户名,判断时检查是否在集合内。
永久存储十几亿的用户信息到 Redis 缓存中显然不太现实,因为这会占用大量的内存资源。
即使是临时存储,如果在缓存中查询不到数据,仍然无法避免查询数据库的场景。
此外,对于这么多的用户信息,是否应该将其存储在一个 Key 中呢?显然是不可行的。即使进行分片,也会增加系统的复杂度。
结论:由于该方案占用内存较多且复杂度较高,因此不适合实际应用。

4. 分布式锁

针对高并发注册场景,可以先查询缓存,如果不命中则使用分布式锁来保证只有一个线程访问数据库,避免重复查询。
相对于上述解决方案,该方案在一定程度上可以解决会员注册缓存穿透的问题。
但是,如果在用户注册高峰期,只有一个线程访问数据库,这可能会导致大量用户的注册请求缓慢或超时。
结论:这对用户的使用体验来说并不友好,因此我们不建议使用该方案。

综上所述,目前我项目中还是是用布隆过滤器作为方案

什么是布隆过滤器?

布隆过滤器(Bloom Filter)是一种高效的数据结构,用于判断一个元素是否存在于一个集合中。它利用位数组和多个哈希函数来实现快速的成员查询。
布隆过滤器的核心思想是用一个位数组(通常用二进制位表示)来表示一个集合,初始时所有位都置为0。
然后,对于每个要加入集合的元素,通过多个哈希函数计算出多个哈希值,然后将位数组中对应的位置置为1。
判断一个元素是否存在于集合时,同样使用多个哈希函数计算出对应的哈希值,然后检查位数组中对应的位置是否都为1,如果有任意一个位置为0,则说明该元素不存在于集合中;如果所有位置都为1,则该元素可能存在于集合中(因为有可能发生哈希碰撞),需要进一步查询底层数据结构来确认。

布隆过滤器的缺点

  1. 无法删除

  2. 哈希冲突

最终解决方案

万事都可以套一层,一层不行那就多套一层

这里加一层缓存是为了解决布隆过滤器不能删除元素的问题:如果数据库删除了用户名,布隆过滤器无法删除,就会导致这个用户名在数据库不存在但是布隆过滤器还是判断存在,为了让这样的注销的用户名再次可用,就将注销了的用户名写缓存。布隆过滤器判断存在实际上以及注销了,因此走缓存看注销集合里有没有,有就说明布隆误判了,用户名可用;没有就说明没有误判,用户名不可用。但是这里没有解决:哈希碰撞冲突导致的误判。也就是说加了一层缓存解决了注销的用户名再次可用的问题,但是没有解决布隆过滤器天然存在的哈希碰撞问题。当然集合业务场景考虑判断用户名是否可用允许将不存在的用户名判断为存在这种误判,只是牺牲了一些用户名.

假设我们有一条用户名为 "mading" 的数据,注册后是如何不被重复注册,以及注销后又是如何能被再次使用的。
1. 用户名 "mading" 成功注册后,将其添加至布隆过滤器。
2. 当其他用户查询"mading"是否已被使用时,首先检查布隆过滤器是否包含该用户名。
3. 如果布隆过滤器中不存在该用户名,根据布隆过滤器的特点,可以确认该用户名一定没有被使用过,因此返回成功,证明该用户名可用。
4. 如果布隆过滤器中存在该用户名,进一步检查Redis Set结构中是否包含该用户名。如果存在,表示该用户名已被注销,同样可被再次使用。
5. 如果布隆过滤器中存在该用户名,但 Redis Set 结构中不存在,说明该用户名已被使用且尚未被注销,因此不可用。

缺点

  1. 查询性能消耗增加:由于采用了额外的 Redis Set 结构,查询过程需要进行两次查询,一次是查询布隆过滤器,另一次是查询Redis Set结构,这导致查询性能相比之前有所增加。

  2. 存储损耗增加:相较于之前仅使用布隆过滤器的存储,现在需要额外存储 Redis Set 结构,这导致存储开销增加。

存在大key问题

缓存分片处理:对 Username Redis Set 结构进行分片。即使我们对异常行为进行了限制,如果有大量用户注销账户,存储这些数据在一个 Redis Set 结构中可能成为一个灾难,可能出现 Redis 大 Key 问题。因此,我将 Set 结构进行分片,根据用户名的 HashCode 进行取模操作,将数据分散存储在 1024 个 Set 结构中,从而有效地解决了这个问题。

0

评论区