本文正在参加「Java主题月 - Java Debug笔记活动」,详情查看活动链接
名词解释
redis雪崩:就是在一瞬间,redis大量的值在同一时间全部过期失效,恰好在失效的这个时间有高并发的请求过来,这些请求全部打到数据库,这样数据库不挂也差不多了。
问题
调用企业微信获取token,单个ip被限制调用获取token的频率,就是该ip下获取token频率达到最大,不能调用了导致整个调用微信的接口瘫痪。入下图微信官方频率限制。
原代码
在企业号对接的时候,企业微信需要缓存企业微信的token,这个token失效时间是两小时,我的做法是先调用微信的接口获取token,获取到token之后把token存到redis里面,设置失效时间为1小时59分钟。那么下次获取token的时候就先从token中获取,获取到就用token去调用企业微信的接口API(调用企业微信的其他接口都需要token参数),如果token失效就调用企业微信的接口获取token,获取到再缓存到redis。这样周而复始,来上伪代码:
@Override
public String accessTokenBusiness(String auth_corpid,String suiteId) throws WXErrorException{
//redis获取token
Object access_token_object= redisService.get("businessCode",key);
//redis存在直接返回
if(access_token_object!=null){
return access_token_object.toString();
}
//不存在调用微信API接口获取token对象
JSONObject jsonObject= wxAuthorizationService.accessToken();
accessTokenDTO=JSONObject.toJavaObject(jsonObject,AccessTokenDTO.class);
if(accessTokenDTO!=null&&accessTokenDTO.getAccess_token()!=null){
//从微信API获取到再缓存到redis
redisService.set("businessCode","key",accessTokenDTO.getAccess_token(),7200-60L);
//并返回token
return accessTokenDTO.getAccess_token();
}
throw new WXErrorException(WXError.fromJson(jsonObject.toJSONString()));
}
代码很好理解
- 去redis取token数据,取到返回。
- 第一步取不到,直接调用微信API去获取token,获取到值返回并同时存入redis
问题分析
每个ip调用每分钟不超过2万次,token每两小时获取一次,一天24小时最多获取12次,怎么就调用超频率了??大写的问号。然后发现接口超限是偶尔发生并且都是在学生下课考勤的时候,这个时候需要发送消息给家长,接口需要用到token去调用消息接口,然后就提示超限。
token在redis有失效的时间,代码中的失效时间是1小时59分钟,微信那边失效是2小时整,失效恰好是在学生放学考勤的时候,如果刚失效的时候下次请求来了,注意这次流量很大超过了每分钟2万的频率限制,比如是3万次请求进来,那么这三万个请求在很短的时间内去调用微信的接口获取token是不是就超限制了。那么如愿以偿的调用失败,如果这个打卡考勤数量一直持续,那么在持续的时间范围内就会一直超时,并且企业微信规定在一个小时内不超过60万,我这样一直调用如果超过一个小时的频率,那么这个超限会一直延迟下去超过一个小时。。。想想是不是头皮发麻!
解决方案
既然知道问题的发生原因解决问题相对简单点,如下方案:
- 永久让token不过期,因为微信接口一个token只能用两小时,所以肯定需要过期去重新调用。此方案不通。
- 在这个ip服务上写个定时任务,比如每隔一个小时55分钟去获取每个企业号的token,然后缓存,我开始采用的该方案,但是发生了一个极小的概率事件,就是我第一次定时任务执行的时候,恰好这时候有企业号的token过期了,恰好又是大流量过来,然后又超频率限制了。此方案也不通。
- 最终方案:我在另一台服务器上就是另一个ip上去部署了一个定时任务,这个定时任务的唯一作用就是每隔一个小时55分钟去定时调用微信接口获取所有企业号的token,然后缓存到redis里面,为什么是1小时55分钟,要留5分钟的时间去执行每个企业号获取token的定时任务,比如我设置为1小时59分钟去定时任务,那么在一分钟我没有把所有企业号的token刷到redis里面,那不又挂了,所以我留了5分钟缓冲时间足够我把全部的企业号的token刷到redis里面,由于是另一个ip那么就不会和用户流量发生在一个ip上。不会受用户流量的限制,这个ip每一个小时55分钟去调用一次微信的接口获取token,一天也就十几次,没有任何问题,用户的服务器获取token全是从redis里面拿,永远不会过期,用户的服务器也不会调用企业微信获取token的接口,就不可能再发生频率的限制问题。
说明
由于方案有了,写定时任务去刷新redis就不贴代码了,此问题主要是解决方案和思路,代码不是重点。先写到这把,写够7篇打卡有点难呀,我写了那么多bug,为啥不把每个bug都记下来呢!后悔中......