互联网的后台提倡大系统小做,微服务化。所以后台服务之间相互依赖,我依赖别人的,别人也依赖我的,这很正常。
但是后台服务讲稳定性。只有一切可控,才能谈稳定性。
为了不冲垮下游的服务,我们有两种做法:一种是下游服务做一个自我保护(具体实现方法下次再写),一种是上游保护下游。
比如A服务向B服务发送消息,B给A分配了每分钟3000条消息的访问量。那么A如何控制自己每分钟的访问量在3000次以内呢?
基本思路:
这是个分布式的问题,A服务可能包含了堕胎机器,所有的机器共享一个设定的配额 3000次/每分。
借助redis来管理配额。
每次A向B发起请求时,先检查一下配额够不够。不够就延时等待,使用下一分钟的配额。
既然使用了redis, 还要考虑到redis服务挂了的情况,也要能正常发送消息。
如果Redis挂了,作为应急方案,可以使用简单睡眠的方法来控制速度。
/**
* 以分钟为单位,每分钟发送的消息数控制在指定范围内, 缺省 每分钟 3000条消息/分钟。
* 如果返回false, 对于调用者来说,需要睡眠一段时间再次检查是否可以发送消息。
* */
private boolean allowSendMsg(int msgNum) {
boolean allowSendMsgRet = true;
long curMinute = new Date().getTime()/60000;
//在redis中查找当前这一分钟的配额
String key = "msg_flowctl_" + String.valueOf(curMinute);
boolean redisException = false;
try {
if (redis.exists(key)) {
long remainingMsgNum = redis.decrBy(key, msgNum);
if (remainingMsgNum <= 0) { /**当前这分钟的额度用完了*/
allowSendMsgRet = false;
}
} else {
//这一分钟的配额在redis还没有,那么进行初次设置。如果一次要求的已经大于3000。 这里采取了比较宽松的策略,就是允许它过。
//其实还可以要求调用方对请求进行拆分。
long remainingMsgNum = msgNum >
3000
? 0 : 3000 - msgNum;
redisTemplate.getJedisCmd().set(key, String.valueOf(remainingMsgNum));
redisTemplate.getJedisCmd().expire(key, 60);
}
} catch (Exception e) {
//redis不可靠要记下来
redisException = true;
}
if (redisException) {
/**redis 失效以后的流控应急方案。
*
* 通过睡眠线程来限制发往企信后台的速度.
* 3000个人(消息)/分钟,算出来每条消息要睡眠20ms.
* 假设当前A服务总共有两条服务器,因此每条消息需要睡眠40ms.
*
* 这种方法简单粗暴而有效,只能保证单台服务器 发送1500条消息到B服务,时间段肯定是超过一分钟的。
* 缺点:(1) 时间粒度太粗。
* (2) 不易运维。需要代码结合部署的服务器数量。
* 比如代码转手几次以后,扩容机器。扩容的同学没有意识到,发往B服务的速度变快了。
* */
try {
Thread.sleep(msgNum * sleepMiniSecPerMsg);
} catch (Exception e) {}
}
return allowSendMsgRet;
}