解决注册缓存穿透

出现原因

在高并发情况下,可能有大量新用户同时注册,输入的用户名极有可能不存于数据库中,而且这些查询数据库的key也不会被写入缓存,造成每次请求都查不到缓存,直接查询数据库,给数据库造成巨大压力。

其他解决方案

对不存在的key进行缓存

对不存在的 Key 进行缓存,值设为 Null,并设置短暂过期时间,如 60 秒。

问题

对用户体验不好,假如有一个人注册了a名字,那别人在过期时间都不能注册这名字

而且如果同时有大量并发请求查询不同的名字,数据库还是会秒挂

Redis Set 存储已注册用户名

使用确定的数据结构如 Redis 的 Set 集合来存储已注册用户名,判断时检查是否在集合内。

问题

永久存储十几亿的用户名到 Redis 缓存中显然不太现实,因为这会占用大量的内存资源。

即使是临时存储,注册用户名还是很有可能在缓存中查询不到数据,仍然无法避免查询数据库的场景。

即使进行分片,也会增加系统的复杂度。

由于该方案占用内存较多且复杂度较高,因此不适合实际应用。

分布式锁

针对高并发注册场景,可以先查询缓存,如果不命中则使用分布式锁来保证只有一个线程访问数据库。

问题

虽然一定程度上可以解决缓存穿透问题,但是如果在用户注册高峰期,只有一个线程访问数据库,这可能会导致大量用户的注册请求缓慢或超时,对用户的使用体验来说并不友好。

12306 解决注册穿透方案

12306如何解决用户注册缓存穿透问题?

使用布隆过滤器,将所有已注册的用户名存入布隆过滤器,查询时先判断该用户名是否在布隆过滤器中,不在的一定不存在,避免直接查询数据库。

但是如果用户注销了账号,该用户名就可以再次被使用。然而,布隆过滤器由于无法删除元素,因此无法处理这种情况。

为此,我们可以用Redis Set结构储存已注销的用户名。

当其他用户查询用户名是否已被使用时,首先检查布隆过滤器是否包含该用户名。

如果布隆过滤器不存在这个名字,说明未被注册,返回成功

如果布隆过滤器存在此名字,进一步检查Redis Set结构中是否包含该用户名。如果有,则代表用户名虽然已被注册但是后来又被注销,可以使用此用户名,返回成功。

如果不存在,则代表该用户名在使用中,尚未注销,因此不可用,返回失败。

出现问题

如果用户频繁申请账号再注销,可能导致用户注销可复用的 Username Redis Set 结构变得庞大,增加了存储和查询的负担。

为了防止这种情况,我采取了以下解决方案:

  1. 异常行为限制:每次用户注销时,记录用户的证件号,并限制证件号仅可用于注销五次。超过这个限制的次数,将禁止该证件号再次用于注册账号。
  2. 缓存分片处理:对Set 结构进行分片,根据用户名的 HashCode 进行取模操作,将数据分散存储在 1024 个 Set 结构中,从而有效防止Redis 大 Key 问题。

解决注册缓存穿透
https://xichicheng.github.io/2024/11/20/解决注册缓存穿透/
作者
陈兮迟
发布于
2024年11月20日
许可协议