刚买的 Redis 没多久就满了?罪魁祸首竟是这个

背景

大家好,我是小顾,负责开发一款面试刷题工具 —— 面试鸭 ~

在每周例行开完需求评审会后,我按照需求文档进行开发需求时,写着写着代码,面试鸭告警群里突然报了好多异常,我一看是 Redis 报内存溢出异常,赶紧去阿里云监控平台看。

去了阿里云监控平台查看了下,/api/question/get/vo 这个接口 5 分钟内报了好几个 OOM 异常,汗流浃背了。

这个接口是获取题目详情和推荐答案的,并且作了限流和防爬警告处理,当用户获取题目频率过高时会在企微告警,防止有用户恶意爬接口。但是如果频率过高时,一调接口就会告警,群里通知会很频繁,体验不是很好,所以我会把一段时间内的告警信息进行汇总起来一起发送出去,会使用用户 id 为 key 存储在 Redis 里,一段时间后过期。因为这里有 Redis 的写入操作,导致用户刷题时一直报这个错误。

我赶紧打开腾讯云 Redis 云监控,看了下,好家伙,内存使用率都干到 100% 了!

通过 Redis 客户端工具可以看到,11 库是面试鸭的 Redis 库,一共有 290 多万条数据,这么多明显不正常,面试鸭根本没有这么多的用户,居然产生了这么多 key,而另外一个项目一共才 40 多万的 key,并且用户数和面试鸭差不多,所以面试鸭的 Redis 明显不正常 ~

分析

在 Redis 里,有 94% 是 spring 的 key,明显这是占用最多的键,这个存的是面试鸭的登录态,不到 10 万的用户登录态有两百多万,肯定是有问题的。这个 Redis 数据是 Spring Session 框架生成的。我们是把用户登录态使用这个框架存在Redis里来解决分布式场景下的登录问题,平常单机应用都是存储在 Spring 的 Tomcat 里,但我们的面试鸭是分布式的应用,一个 web 应用可能部署在不同的 docker 容器中,通过 CPU 或者内存占用情况进行自动扩容,通过负载均衡算法将请求分配到不同的 web 容器中,这样就会导致用户的登录态不一致,比如用户登录时的请求发送到服务器 A ,获取登录信息的请求发送到服务器 B,这样就获取不到用户的登录态了,所以我们需要把登录态存在 Redis 里。

继续看这个 sping 的 key 里面有什么信息,里面存放了三种类型的数据:

第一种,"spring:session:sessions" + sessionId这个是存储的是,creationTime(创建时间),lastAccessedTime(最后访问时间),maxInactiveInterval(session 失效的间隔时长),以及存放在 session 中的用户数据会存放于此,比如在代码中:

session.setAttribute(key, user)

这里的用户数据就会存放在这个类型的缓存里。

{
  "lastAccessedTime": 1523933008926
  "creationTime": 1523933008926, 
  "maxInactiveInterval": 1800,
  "sessionAttr:user_login": {...}
}

第二种,"spring:session:expirations" + 时间戳,这是存储的是这一分钟所有要失效的 sessionId。当会话被创建或更新时,Spring Session 会计算该会话的过期时间。将当前的时间加上 session 失效的间隔时长(maxInactiveInterval),得出会话的过期时间点。Spring Session 通常会将计算出的 expirationTime 向下取整到最近的分钟。如果这个分钟时间点有多个 sessionId,会添加到Set类型的值里。这种类型的键便作为了一个“桶”,存放着这一分钟应当过期的 sessionId

第三种,"spring:session:sessions:expires" + sessionId,不存储任何有用数据,它仅仅是 sessionId 在 redis 中的一个引用,只是表示 Session 的过期而设置。这个值在 Redis 中的过期时间即为 Session的过期时间。

我正在研究这些数据分别存储什么数据时,同事 S 给我发了一张图,说这都是啥数据,占用这么多?

我打开一看,占用前 100 个 都是上面第二种类型的数据,最多的将近有 1M 了。这种缓存数据是每当登录时,session 过期时间会被重新从当前时间计算,会得到一个新的值并存进去。如果这一分钟有很多用户同时登录时,相同键里的“桶”会存非常多的sessionId,导致这条数据非常大。那么 Spring 为什么会引入这个类型的键呢?

我们知道 Session 是有过期机制的,Redis 也是有过期机制的,那么 Spring 是否可以直接沿用 Redis 的自动过期呢,显然是没有,因为 Spring 设计了上面三种类型的键,那么为什么会这么设计?这岂不是比直接使用一条数据来过期多出了两倍的内存空间?

Redis 对于过期数据,一般有三种删除策略:

  1. 定时删除:当设置键的过期时间的同时会创建一个定时器,当键到达过期时间的同时,定时器会删除这个键。
  2. 惰性删除:在获取键的时候,会判断有没有过期,如果过期了,直接返回空并且删除,否则返回这个键
  3. 定期删除:每隔一段时间,Redis 会取出一定数量的键,然后过滤出所有的过期的键并删除。

Redis 里默认一般采用惰性删除和定期删除两个策略组合来使用。也就是说 Redis 的键一旦过期了并不会立即删除,而是等某个事件触发时才会删除,这就会有不及时删除的情况。所以单纯依赖 Redis 是不可靠,于是就有了上面三个键的设计。通过源码可以发现,为了保证 Session 过期的及时性,Spring 采用了定时任务去删除过期的 Key,定时任务的频率是 1 分钟,它会检查所有一分钟前应该过期的 sessionId的值,即"spring:session:expirations" + 时间戳,然后根据“桶”里面的 sessionId,立即删除 "spring:session:sessions:expires" + sessionId

通过 Redis 客户端工具可以看到,"spring:session:sessions:" + sessionId 的 TTL是2591772, "spring:session:expirations" + 时间戳的 TTL 是 2591472,第一种类型的键比第二种类型的键多了整整 5 分钟,目的就是为了当 Session 过期时,其他的监听事件还能获取到 Session 的原始数据。第三中类型的缓存的 TTL 其实就是为了标记的真实的过期时间,作用也是为了过期用的,所以值是空的。

所以综上所述,第一种类型的键存放的就是 Session 的真实数据,第二种类型的键是为了能及时删除过期的 Session,第三种类型的键就是真正标记过期用的。

知道了 Session 原理就简单了,用户如果打开网站或者小程序,请求需要登录的接口后,比如 /api/user/get/login获取登录态的接口,这个接口因为涉及到 Session 的获取操作,如果在 Redis 里没有找到这个 SessionId,用户无论有没有登录,都会在 Redis 里添加一条 Session 记录。如果请求登录接口后,会在第一种类型的键里加一条用户数据,然后在第二种类型里重新计算用户过期的时间,它的键为当前登录用户 Session 要过期的时间戳,如果用户频繁请求接口接口,这个类型的键会一直增加,因为他是根据当前登录时间动态计算的过期时间。在 web 端如果用户登录之后,刷新页面会自动获取登录态的,不用重复登录,所以排除了 web 端的问题,那么就是小程序了,因为小程序的自动登录机制,用户打开小程序会自动进行登录,通过日志可以看到,小程序关闭之后再次打开,还是会调用 /api/user/login/miniapp接口,"spring:session:expirations" + 时间戳 的键会增加一条记录,所以这个类型的数据会越来越多。

解决

终于知道问题出在哪里了,那就要开始解决了 ~

小程序登录之后是可以获取到登录态的,所以不需要每次打开都调用登录接口,可以通过/api/user/get/login获取登录信息,如果获取不到,才调用登录的接口,这样就会在 Redis 里节省了很多的空间呢 ~

总结

随着用户数越来越多,Redis 里的登录态也越来越多,内存增加也越来越快,用户只要打开网站就会有 Session 记录,所以要合理利用 Redis 空间,后续可以再看看还有没有可以优化的地方 ~

阅读全文
下载说明:
1、本站所有资源均从互联网上收集整理而来,仅供学习交流之用,因此不包含技术服务请大家谅解!
2、本站不提供任何实质性的付费和支付资源,所有需要积分下载的资源均为网站运营赞助费用或者线下劳务费用!
3、本站所有资源仅用于学习及研究使用,您必须在下载后的24小时内删除所下载资源,切勿用于商业用途,否则由此引发的法律纠纷及连带责任本站和发布者概不承担!
4、本站站内提供的所有可下载资源,本站保证未做任何负面改动(不包含修复bug和完善功能等正面优化或二次开发),但本站不保证资源的准确性、安全性和完整性,用户下载后自行斟酌,我们以交流学习为目的,并不是所有的源码都100%无错或无bug!如有链接无法下载、失效或广告,请联系客服处理!
5、本站资源除标明原创外均来自网络整理,版权归原作者或本站特约原创作者所有,如侵犯到您的合法权益,请立即告知本站,本站将及时予与删除并致以最深的歉意!
6、如果您也有好的资源或教程,您可以投稿发布,成功分享后有站币奖励和额外收入!
7、如果您喜欢该资源,请支持官方正版资源,以得到更好的正版服务!
8、请您认真阅读上述内容,注册本站用户或下载本站资源即您同意上述内容!
原文链接:https://www.shuli.cc/?p=20936,转载请注明出处。
0

评论0

显示验证码
没有账号?注册  忘记密码?