记一次生产优化-优化定时提前加载用户信息
背景
最近,有不少用户反映登录我们的APP后,进入首页展示用户数据时要很久很久才能展示出来。刚开始还没在意,以为是用户自己的网络慢导致的,后来有好几个用户都反映了此问题,这不得不引起我们的重视了。
后来经过我们排查日志发现一个现象,提出该问题的用户都是基础数据比较多的,因为我们是金融软件,所以当用户的基础数据比较多的时候,在首页展示时会先去查询基础数据,然后在轮询这些基础数据查询接口得到结果之后再进行一些逻辑运算。有不少用户多达几十甚至上百条基础数据,所以导致查询时非常慢。
问题分析
这个问题的关键是用户基础数据的查询,以及对基础数据轮询查询接口次数较多导致的。所以当了解了问题的根源所在,我们就从根源出发解决这个问题。
我们想到的是,既然是因为查询基础数据和轮询基础数据次数较多导致的,那么我们就减少次数。
怎么减少呢?我们的方案是提前查询用户基础数据到Redis进行缓存,而不是在客户登录时再去加载,这样用户登录时的基础数据查询次数就减少到了0。因为我们的用户基础数据变化的不是那么频繁,所以我们可以这样提前缓存。
那么什么时候去加载用户的数据到Redis里呢?显然是在用户使用APP之前且较少人使用的时间段,我们暂且定在凌晨3、4点吧。我们可以在这个时间把这些特殊用户的数据通过定时跑批的方式提前加载至Redis。
那什么样的用户才去提前加载呢?肯定不是所有用户,我们只针对基础数据条数达到某一阈值时的用户(称为特殊用户,将用户添加至白名单用户表white_user表)。比如基础数据达到5条时,该阈值可参数化配置,并根据条数和页面响应时间测试得出该值。比如当有5条基础数据时,达到页面响应时间用户可忍受程度的极限。
我们必须要自动发现特殊用户,将其添加至white_user表。我们选择在用户登录时开启线程判断用户是否满足白名单用户的条件,满足则添加至white_user表,不满足的先判断是否在表里,在则删除掉。(用户可自己操作减少基础数据)
实施方案
分析完问题,也大致确定了解决方案。我们来总结下解决方案:
1、自动发现特殊用户,添加至white_user表。-根据基础数据阈值,判定是否为特殊用户。
2、凌晨定时加载白名单用户基础数据至Redis。-定时跑批,执行任务。
具体实现
1、在跑批系统,创建 Spring Quartz 定时任务。设定每天凌晨3点执行定时任务,具体执行的任务就是通过Redis发布订阅功能通知APP服务系统,APP服务系统收到消息后查询白名单用户,然后查询用户基础数据到Redis。
2、客户登录时,执行异步操作。根据基础数据个数与阈值比较,判定是否可以列为特殊用户,并添加至白名单用户表;不满足特殊条件时若用户在白名单用户表,则删除。
3、异步刷新用户基础数据,当用户修改基础数据时,异步更新基础数据。
4、因为加载到Redis的数据设置的都有过期时间,所以。。。。
需要注意
1、跑批系统和APP服务系统都是集群部署的,所以如何实现只有一台跑批服务器去执行跑批任务利用Redis发布消息通知APP服务系统?
2、如何实现APP集群服务不重复加载用户信息?(因为集群下都会收到跑批系统的消息,又不能控制只有一台收到消息,所以就控制只有一台执行)
第一个问题解决方案,redis分布式锁
定时任务执行时,利用Redis实现分布式锁,使得只有一台跑批系统执行任务。
第二个问题解决方案,可以和第一种一样也使用分布式锁,但还可以用另一种
由于APP服务系统执行加载客户数据的操作是基于用户ID的,所以查询白名用户ID的操作可以交给跑批系统来做,跑批系统将客户号放入Redis(list类型实现队列),APP服务系统就算是多台都执行,但是他们都是要去Redis中取用户ID的,对Redis的list类型实现的队列执行getAndDel操作。所以即使是集群环境下多台机器都执行,也不会重复操作同一用户数据,不会做重复操作。
总结
用到的技术:
Spring Quartz 实现定时任务,Redis 实现分布式锁,Redis 实现数据缓存,Redis 发布订阅。
PS:
至于Redis发布订阅功能的使用和代码实现我之前的文章可以参考一下。Redis发布订阅功能是redis的一个重要功能,redis的客户端订阅一个频道,当此频道发布消息时,所有订阅这个频道的客户端都会收到,收到消息后可以执行具体的自定义的操作。