使用排队论实现服务节点的动态扩展

1、背景

通常我们的服务器面对高并发时会采取一些限流、熔断、降级的手段,条件允许的时候采取水平扩展提升系统的吞吐量。最近脑子一热在想若是不考虑水平扩展的成本,我们是不是可以设计一款支持动态扩展的系统,当并发量达到限流上限时自动增加服务节点,当并发量很小时自动销毁空闲节点。
其实想一想我们的服务模型,与如下的排队模型很相似。
在这里插入图片描述
其实排队论就是为解决这类问题的,原理见参考

2、实现服务指标的计算

动态扩展的前提应该是能根据当前QPS和服务自身的处理能力,计算出性能指标,根据这些指标来衡量当前是否需要增删节点。
这里选用了比较简单的M/M/S模型,该模型需要知道服务节点总数s、请求到达率和平均服务率来计算排队长、队长、平均等待时间、平均逗留时间、到达时必须等待的概率、服务空闲概率等。
服务空闲概率p0:
在这里插入图片描述
请求到来时必须等待的概率
在这里插入图片描述
平均排队长 Lq 为:
在这里插入图片描述
平均等待时间(Wq)和平均逗留时间(Ws):
在这里插入图片描述
java的实现:

public class MmsModel {

    /**
     * 计算M/M/S排队模型的各项指标
     *
     * @param queueParamDto
     * @return
     */
    public static ServerEvaluateDto evaluateMmsModel(QueueParamDto queueParamDto) {
        ServerEvaluateDto serverEvaluateDto = new ServerEvaluateDto();
        double rho = queueParamDto.getLamda() / queueParamDto.getMu();
        double rhoS = rho / queueParamDto.getServerNum();
        int s = queueParamDto.getServerNum();
        if (rhoS < 1) {
            //计算空闲概率p(0)
            double p0 = pn(rho, s, rhoS, 0);
            serverEvaluateDto.setIdleProbability(p0);
            //计算必须等待的概率
            double mustWaitProp = Math.pow(rho, s) * p0 / factorial(s) / (1 - rhoS);
            serverEvaluateDto.setMustWaitProp(mustWaitProp);
            //计算平均排队长
            serverEvaluateDto.setAvgQueueLengthQ(mustWaitProp * rhoS / (1 - rhoS));
            //计算平均队长
            serverEvaluateDto.setAvgQueueLength(serverEvaluateDto.getAvgQueueLengthQ() + rho);
            //计算平均等待时间
            serverEvaluateDto.setAvgWaitTime(serverEvaluateDto.getAvgQueueLengthQ() / queueParamDto.getLamda());
            //计算平均逗留时间
            serverEvaluateDto.setAvgStayTime(serverEvaluateDto.getAvgQueueLength() / queueParamDto.getLamda());
        }
        return serverEvaluateDto;
    }

    /**
     * 计算阶乘
     *
     * @param num
     * @return
     */
    private static Integer factorial(int num) {
        if (num == 0) {
            return 1;
        }
        List<BigInteger> list = new ArrayList<>();
        list.add(BigInteger.valueOf(1));
        for (int i = list.size(); i <= num; i++) {
            BigInteger lastfact = list.get(i - 1);
            BigInteger nextfact = lastfact.multiply(BigInteger.valueOf(i));
            list.add(nextfact);
        }
        return list.get(num).intValue();
    }

    /**
     * p(n)
     *
     * @param rho
     * @param s-服务台数
     * @param rhoS
     * @return
     */
    private static double pn(double rho, int s, double rhoS, int n) {
        if (n == 0) {
            double result = 0;
            for (int i = 0; i < s; i++) {
                result += Math.pow(rho, i) / factorial(i);
            }
            result += Math.pow(rho, s) / factorial(s) / (1 - rhoS);
            return 1.0 / result;
        }
        if (n <= s) {
            return pn(rho, s, rhoS, 0) * Math.pow(rho, n) / factorial(n);
        } else {
            return pn(rho, s, rhoS, 0) * Math.pow(rho, n) / factorial(s) / Math.pow(s, n - s);
        }
    }


    public static void main(String[] args) {
        QueueParamDto queueParamDto = new QueueParamDto();
        queueParamDto.setServerNum(3);
        queueParamDto.setLamda(0.9);
        queueParamDto.setMu(0.4);
        System.out.println(MmsModel.evaluateMmsModel(queueParamDto));    
    }
}

3、模拟请求响应模型

使用这个例子模拟实现请求响应模型,改造监控部分代码,当必须等待概率大于0.5时增加服务节点。
监控线程:

			long current = System.currentTimeMillis();
            //平均每秒接收的请求
            double lamda = new Double(CustomerQuene.getTotal())/(current - serverStartTime)*1000.0;
            //平均每秒服务处理的请求数
            double mu = new Double(ServantThread.getCustomerNum())/(current - serverStartTime)*1000.0;
            System.out.println("总达到顾客数:" + CustomerQuene.getTotal());
            System.out.println("总服务顾客数:" + ServantThread.getCustomerNum());
            System.out.println("平均服务时间: " + mu);
            System.out.println("平均到达时间:" + lamda);
            QueueParamDto queueParamDto = new QueueParamDto();
            queueParamDto.setServerNum(servantNum);
            queueParamDto.setLamda(lamda);
            queueParamDto.setMu(mu);
            ServerEvaluateDto serverEvaluateDto = MmsModel.evaluateMmsModel(queueParamDto);
            System.out.println("服务评价指标:" + serverEvaluateDto);
            if(serverEvaluateDto.getMustWaitProp()>0.5){
                servantNum++;
                System.out.println("水平扩容增加服务器一台,当前服务器" + servantNum +"台");
                ServantThread threadAdd = new ServantThread("服务台" + (servantNum-1));
                threadAdd.start();
            }

            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (Exception e) {
                e.printStackTrace();
            }

部分监控日志:

平均服务时间: 4.7041346868036635
平均到达时间:17.908723281340265
服务评价指标:ServerEvaluateDto(idleProbability=0.004934283986573964, avgQueueLengthQ=17.658817680236186, avgQueueLength=21.465835224095837, avgWaitTime=0.9860455936931883, avgStayTime=1.1986245410616096, mustWaitProp=0.8951474400119712)
水平扩容增加服务器一台,当前服务器5台
服务台3 服务顾客耗时:381ms	顾客等待:8744ms
服务台4 服务顾客耗时:418ms	顾客等待:8789ms
服务台2 服务顾客耗时:1420ms	顾客等待:8023ms
服务台0 服务顾客耗时:1448ms	顾客等待:8224ms
服务台1 服务顾客耗时:1367ms	顾客等待:8582ms
服务台4 服务顾客耗时:774ms	顾客等待:9088ms
服务台0 服务顾客耗时:757ms	顾客等待:9265ms
服务台3 服务顾客耗时:1201ms	顾客等待:9056ms
服务台2 服务顾客耗时:1227ms	顾客等待:9114ms
服务台1 服务顾客耗时:856ms	顾客等待:9558ms
服务台2 服务顾客耗时:309ms	顾客等待:9886ms
总达到顾客数:245
总服务顾客数:68
平均服务时间: 4.8138184907263195
平均到达时间:17.34390485629336
服务评价指标:ServerEvaluateDto(idleProbability=0.02271825440912265, avgQueueLengthQ=1.0609071142777542, avgQueueLength=4.6638482907483425, avgWaitTime=0.06116887304607165, avgStayTime=0.26890416716371873, mustWaitProp=0.41137214635259833)

可以看出当必须等待概率达到0.8951474400119712时,增加一个节点,立马降到了0.41137214635259833。

4、总结

demo中仅使用到了到达时必须等待概率这一个性能指标,实际中可根据多个参数综合考量是否需动态增删节点。
幻想有一天我们的微服务架构中服务节点、redis集群节点、hadoop节点等等可以实现这样的动态增删多好。实际项目中我们很多的监控组件可获取到平均到达率、平均服务率这些参数,也有自动部署的各类平台,可自动部署服务节点。目前不太了解实际项目中有没有大公司这样做,或者有更好的算法,脑子一热感觉排队论好神奇,可以解决这类问题,哈哈哈哈。。。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章