使用排隊論實現服務節點的動態擴展

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節點等等可以實現這樣的動態增刪多好。實際項目中我們很多的監控組件可獲取到平均到達率、平均服務率這些參數,也有自動部署的各類平臺,可自動部署服務節點。目前不太瞭解實際項目中有沒有大公司這樣做,或者有更好的算法,腦子一熱感覺排隊論好神奇,可以解決這類問題,哈哈哈哈。。。

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