内网单点登录系统慢接口(/user/varify)优化总结
背景
最近接到平台研发同事反馈说inpass的/user/varify接口慢,经过沟通后开始排查了,对方给了cat监控的历史,每天大概有个100~200个样子,说是验证超过1s,我这边监控平台除了这个接口其他接口都监控到了,并无异常,因为没有接调用链,架构部也无法给出准确答复,由于历史原因接入调用链的时机并不成熟所以也不好说什么,自己通过access日志排查,发现也没有多慢,因为这个接口是高频接口,本能上告诉对方不会有多慢,但是对方说人家俩接口也是高频接口,,所以我这边也只能说继续跟进了。
说明
1.inpass:内部passport的简单实现,登录一个系统后,登录其他系统不需要再次登录;
2./user/varify:该接口的作用是验证cookie和ticket的有效性,也就是说相当于一个拦截器,在业务系统的每个http请求之后,后端的拦截器会调用这个接口进行登录状态的验证。所以调用量会非常多。
正好,最近有个inpass单设备登录的需求,因此进行了排查
1.首先在接口开始的地方获取当前时间戳,然后在返回的地方获取当前时间戳,然后取时间差,就是毫秒数了。有4个地方进行了返回,因此在4个地方都打印了执行的时间差。
2.经过测试环境的日志打印发现,确实有些有问题,时不时的有100 ~ 600ms之间的调用量,但是90%的情况下是10 ~ 30ms之内返回验证成功。
查代码

上图代码中的逻辑是登录成功之后,调用服务填充信息,并返回,然后打印执行时间。
重点就在画红框的地方。
下面跟踪分析一下代码逻辑

再进上图中的RPC方法里(在另外一个工程里),由于逻辑有点多,不贴代码了,下面使用伪代码说明一下调用情况
User user = userDB.getByid(id);
if(user != null){
userB = user.copy();//属性复制
staffRedis = staffservice.getredis();
if(staffRedis == null){
Staff staff = staffDB.getById(id)
if(staff != null){
staffB = staff.copy();
if(staffB.deptid !=null){
Depart depart = departservice.getredis(staffB.deptid);
if(depart == null){
depart = departDB.getById(id)
while(depart.superid != 0){
depart = departDB.getById(depart.superid)
}
departB = depart.copy();
departB.putredis();
}
}
User userC = userDB.getByStaff(id);
staffB.setuser(userC.copy());
staffB.putredis();
}
}else{
Depart depart = departservice.getredis(staffB.deptid);
if(depart == null){
depart = departDB.getById(id)
while(depart.superid != 0){
depart = departDB.getById(depart.superid)
}
departB = depart.copy();
departB.putredis();
}
}
}
当然,通过上面代码我们可以知道,为啥很少的情况下会出现100~600之间的调用返回了,同时,由于有多次属性copy,多次redis调用,多次db调用从而导致接口抖动,后面再看,登录成功或者验证登录状态成功之后我们需要返回哪些信息
userId,userName,staffId,nickName,handphone,jobNumber,email,departmentId,departmentName
那么分析到这里该怎么优化呢
- 重写后端getById逻辑
- 提供新接口获取以上字段数据
- 提供新接口获取以上字段数据并实时缓存,保证缓存永久有效
三种思路
- 接口可能被别的地方调用,重写影响稳定性
- 按字段逻辑需要取三张表的数据(user,staff,depart),由于是高频访问接口(DB+Redis)的普通访问逻辑同样存在多次调用的情况,连表查+Redis的方案也不行,高频访问的话,总会有redis key失效的时候,由于底层DB被多个上层应用共享,导致Redis key失效的情况很多,不可能让其他服务专门清这些redis,所以也不完美
- 提供新接口的时候做如下操作
- 调用RCP服务直接根据userID取Redis
- 使用定时任务线程从User表中获取所有在职的员工的账号,然后取所有关联表的staff,department表中的相关字段的数据
- 然后根据userId遍历Redis中的相关key,进行更新
- 最后可以设置userID_redis的失效时间是2小时,定时任务的执行频率是30分钟一次,即可满足Redis数据永不失效,同时提供近实时的数据
看数据量:user:33370,staff:26437,department:2456,虽然不是很多,由于是高频访问(qps200左右),且其他有很多应用系统也是直连这个库,所以有必要采用第三种方案优化
总结
由于很长时间没有反馈说这个系统有问题了,毕竟这个系统是个只负责登录的系统,同时肩负着全公司近200多个业务系统的使用和验证,因此非常有必要进行排查以及代码优化。幸好有同事反馈,不然还不知道背后的调用过程如此繁琐,影响接入方。