问题解决系列: 后台服务流量控制- 控制访问别的服务的速度

互联网的后台提倡大系统小做,微服务化。所以后台服务之间相互依赖,我依赖别人的,别人也依赖我的,这很正常。

但是后台服务讲稳定性。只有一切可控,才能谈稳定性。

为了不冲垮下游的服务,我们有两种做法:一种是下游服务做一个自我保护(具体实现方法下次再写),一种是上游保护下游。

比如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;
    }
posted @ 2017-05-14 23:39  老码识途  阅读(1302)  评论(0编辑  收藏  举报