刚线上又出现一个问题。。。热乎的,又是缓存!

简介: 刚线上又出现一个问题。。。热乎的,又是缓存!

大家好,我是yes。

我又来送线上排查经历啦!

事情是这样的,今天同事给我反馈了一个问题。


image.png


因为我们的应用需要从第三方那边同步订单的信息,如果用户有一段时间未进入订单页面,则再次进入之后会自动进行一次全量的从第三方拉取订单的操作,这样能可及时更新订单信息,防止用户操作过期的订单。

近期,这个同事发现每次点订单列表都会触发全量拉取,这明显就不合理,非常耗费后端任务的资源。

一开始我觉得这跟我肯定没关系,可能是前端代码出了 BUG (哈哈哈,上次也是这样想)。

所以我告知了前端的同事,经过排查,他很确定的告诉我代码肯定没问题,只有超过一小时没同步过订单的用户,再次进入订单页面进去才会触发拉取。

我看他信誓旦旦的样子,信了。没办法,只能我自己去研究研究了。

这一研究还真被我发现了问题,并追根溯源发生竟是以前碰到的一个问题引起的,真是一环扣这一环!


开始排查


我先登录测试账号,发现无法复现同事所说的每次点击订单列表都会触发全量拉取订单的情况。

好嘛,出师不利。

随即跟他进行了一番沟通,我发现,还竟然是个例?于是,找出个别会出现这样情况的用户。

模拟一看,全量订单拉取任务执行的时候,其实报错了,报的错是 accessToken 过期。

我们和第三方授权走的是 oauth2

也就是说,第三方授权给我们的 token 过期了,导致我们订单拉取接口报错了,于是任务执行失败。

于是乎,我又怀疑上刷新 token 的代码了,因为我们有个任务,会根据 token 的过期时间,提前利用 refreshToken 去换取最新的 token 。

所以,讲道理不可能会出现 token 过期的报错,所以我目测这肯定是刷新 token 的任务有问题,导致 token 过期,使得订单拉取任务执行失败。然后前端不会记录失败的任务时间,因此再次进入订单页面发现超过一小时未同步过,随即触发全量拉取。

这时,我想找寻负责刷新 token 的任务的同事,找了一圈之后,发现原来是我写的....


image.png

从上面的代码可以看到,刷新 token 的接口只需要传这两个参数,没啥别的运算之类的。

并且,当时看到这个错误的时候,我立马在本地拿 refreshToken 进行测试调用,发现根本不报错,能成功返回 accessToken。

并且经过我多天的观察,我发现有些用户的刷新是可以成功的,而有些是不行的。

因为,刷新 token 的接口如此简单,且报错是对方返回的,且从报错信息来看跟我好像没啥关系,理所当然,我认为肯定是对方接口有问题,我怎么看我这边根本就没有犯错的余地(记住这句话)。

所以,之前遇到这个问题我就说处理不了,直接甩锅给第三方(因为第三方出过很多次问题)了,谁知道现在又绕回来了。

没办法,又遇上了这个问题,现在我只能拿这个用户的 refreshToken 在本地再试试看了。

而恰巧,之前我都从数据库从库中查找 refreshToken ,这次我用公司内部的工具来获取,然后就发现了华点!


image.png

麻了,我又麻了,所以发生什么事了??

我立马去检查了刷新 token 任务的代码,确认了我的 sql 确实会获取 refreshToken ,既然数据库里有值,那我可以“断定”我去刷新任务的时候 refreshToken 肯定不为空!

而突然,我发现这个获取是有缓存的!


image.png

随即,我就否认了这个想法,我们应该不可能有这种需求和实现....

没啥思路,我去看了看公司内部工具调用获取 token 的代码,发现是调用的是一个 rpc 接口,由于我没有那个服务的代码,于是就去问一位老同事,他有点印象,来了句:


image.png

至此是破案了....

这位同事的思路是这样的:他认为平日里获取 token 是不需要用到 refreshToken 的,所以出于 select 啥拿啥的规则,他选择了不拿 refreshToken,这么一来切面的缓存塞入里就没塞 refreshToken 值。

然后授权服务是最开始写的,那时候还未抽离出这位同事负责的服务 A,因此关于 token 的获取和写入都是授权服务自身操作数据库实现的,所以我很确定我的代码确实从数据库拿 refreshToken ,压根就不会想到 refreshToken 会是空。

问题就出在两者共用了一个缓存 key ,服务 A 出于节省原则,获取用户授权信息的时候并未在缓存里塞入 refreshToken ,这导致授权服务去获取用户授权信息时,由于命中了缓存,直接从缓存里拿值,而缓存里并没有 refreshToken 的值,所以调用第三方刷新 token 接口的时候, refreshToken 传的值是空!

所以第三方返回了一个错:


image.png


至此,我才明白这个缺少 code 的含义....我想说报错信息返回 refreshToken 参数为空不香吗,给我整个 code,我都不知道是啥 code!

然后,对于那些授权服务先于服务 A 塞入缓存的用户来说,他们的刷新授权是正常的,因为授权服务会把 refreshToken 塞到缓存中。

好了,排查完毕,最终的处理方式是服务 A 也将 refreshToken 塞到缓存中。


最后


可以看到,这次的排查其实不涉及到什么高深的技术,其实就是多方联动,且考虑不周导致的错误。其实生产环境大部分出错都是一些细节问题,例如参数配置的不对啊,多写了一个判断等等。

我们来小结一下这次的经历:

  • 数据的获取要考虑缓存的正确性,不能仅以数据库为准,不要忘了缓存
  • 收敛服务的操作,即服务划分清晰独立,尽量不要在内部实现其他服务的功能,这样在需求变更时可以避免多改和漏改,也不会发生上述的问题,统一约束,最为舒服
  • 报错信息清晰,像上面的报错如果不是缺少 code 而是 refreshToken 参数为空,我可能在第一次看到这个报错的时候就排查完了,也不用等到现在(信任值也很重要,出错多了,就渐渐不信任对方的服务)
  • 全局意识很关键。即使你负责只一个服务,有机会也要多了解了解别人的服务,特别是自己的上下游,这样出了问题,脑子能很清晰地扫描全局,快速的定位可能发现问题的地方,这就是大牛和普通人的区别(你处理不了,人家两分钟搞定)。


相关文章
|
7月前
|
测试技术
线上问题,如何处理?
线上问题,如何处理?
180 37
|
1月前
|
缓存 NoSQL 关系型数据库
大厂面试高频:如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题
本文详解缓存雪崩、缓存穿透、缓存并发及缓存预热等问题,提供高可用解决方案,帮助你在大厂面试和实际工作中应对这些常见并发场景。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题
|
6月前
|
缓存 NoSQL Java
避免缓存失效的三大杀手:缓存击穿、穿透与雪崩的解决方案
避免缓存失效的三大杀手:缓存击穿、穿透与雪崩的解决方案
136 0
|
7月前
|
Oracle 数据库 UED
后台查询接口影响响应时间最大的因素:用空间换时间的优缺点及解决方案
1.当数据库的一个表记录很多显然查询数据很慢。 2.当数据库的一个表记录不大,但是数据很大也可能很慢。 我们的一个用户表中一个building很大,当查询100条数据就会把服务器的内存搞爆掉。 当然查询时要查询筛选有用字段,不可以直接把记录的所有字段都查拆来。这样能减少内存消耗和提高查询速度。 3.在经常查询字段上建立索引。据说oracle上用索查询和不用索引查询在超多记录的情况下相差1000倍。 4.若出现嵌套查询显然会大大增加相应查询时间。要先预处理用管道操作把能合并的查询合并到一个查询中,然后生成map,然后再处理。这是标准的用空间换时间的方案。
102 8
|
7月前
|
缓存 数据库 NoSQL
【后端面经】【缓存】35|缓存问题:怎么解决缓存穿透、击穿和雪崩问题?--主从切换方案
【5月更文挑战第16天】该方案提出了解决Redis缓存穿透、击穿和雪崩问题的策略。通过使用两个或多个互为备份的Redis集群,确保在单个集群故障时,另一个可以接管。在故障发生时,业务会与备用集群保持心跳检测,并根据业务重要性分批转移流量,逐步增加对备用集群的依赖,同时监控系统稳定性。对于成本敏感的小型公司,可以采用低成本的单机或小规模自建Redis备份。此方案强调渐进式流量转移,以保护系统免受突然压力冲击。
56 1
【后端面经】【缓存】35|缓存问题:怎么解决缓存穿透、击穿和雪崩问题?--主从切换方案
|
7月前
|
存储 缓存 监控
|
7月前
|
SQL 存储 关系型数据库
真正线上索引失效的问题是如何排查的
MySQL索引失效是一种常见问题,在处理慢查询时经常需要考虑索引失效的可能性。 针对索引失效的排查,关键步骤包括确定需要分析的SQL语句,并通过`EXPLAIN`查看其执行计划。主要关注`type`、`key`和`extra`这几个字段。
真正线上索引失效的问题是如何排查的
|
7月前
|
NoSQL 关系型数据库 MySQL
热点数据更新导致CPU100%的解决方案
热点数据更新导致CPU100%的解决方案
84 0
面试官:谈关于缓存穿透+击穿+雪崩,热点数据失效问题的解决方案
当我们查询一条数据时,先去查询缓存,如果缓存有就直接返回,如果没有就去查询数据库,然后返回。这种情况下就可能出现下面的一些现象。 2.缓存穿透