【轉(zhuǎn)】限流框架的實現(xiàn)

轉(zhuǎn)自:http://blog.csdn.net/luohuacanyue/article/details/14055715

背景
開篇之前我一直在想怎么把這個項目給講清楚,如果在互聯(lián)網(wǎng)公司有高并發(fā)場景對于這個內(nèi)容的就比較容易接受。這里大概說一下背景:代碼寫于2013年四月份,最開始的雛形是在2012年寫的,從另外一個項目上進行,代碼侵入性比較強。我在今年四月份進行了剝離,實現(xiàn)可插拔式的監(jiān)控。言歸正傳,當(dāng)時對于性能領(lǐng)域非常有興趣,所以就在想如何寫一個框架對于現(xiàn)有項目對于"類級別(嚴(yán)格來講是方法級別)"做性能監(jiān)控。既然是性能監(jiān)控,很容易就會發(fā)現(xiàn)需要回答三個問題:一、這個方法的并發(fā)請求數(shù)有多少二、響應(yīng)時間是多少三、最佳并發(fā)請求數(shù)是多少?這三個問題其實就會衍生出我們今天要講的這個框架意義:保證應(yīng)用的可用性,實時監(jiān)控各個節(jié)點的并發(fā)數(shù),響應(yīng)時間等。(吞吐量和響應(yīng)時間相生相克,所以會在兩者之前找一個最佳平衡點,就是常說的最佳并發(fā)數(shù)。理想狀態(tài)下最佳并發(fā)請求數(shù)就是我們這里要設(shè)的限流上限閥值)。
限流的意義
下面先用一個簡單的圖來看看限流框架所處在的位置。


看到這張圖的時候估計有很多人下意識的會想到spring的攔截器,這里確實非常像,原理也類似。而且在我后面的實現(xiàn)中有兩種方式:其中的一種就是基于Spring的攔截器實現(xiàn)的。另外一種其實是JDK的動態(tài)代理去實現(xiàn)的。講到這里不知道有沒有人會問為什么要限流?這里就做一些簡單的解釋。舉一個淺顯的例子:聯(lián)想一下長江的三峽大壩,除了能發(fā)電之外另外一個作用就是防洪,如果洪水來了,沒有三峽大壩,很有可能對于下游產(chǎn)生重大的洪澇災(zāi)害。引用到我們這里其實是一樣的。當(dāng)請求數(shù)異常升高時(洪水來了),對于應(yīng)用來講所承擔(dān)的負(fù)載也會異常升高,這樣直接的影響就是整個響應(yīng)時間變慢,更糟糕的情況是系統(tǒng)直接崩潰。不僅如此,由于系統(tǒng)之前都是相關(guān)聯(lián)的,所以很容易就會對其依賴的相關(guān)應(yīng)用產(chǎn)生沖擊(有點像多米諾骨牌)。限流的一個很重大的意義就是保證應(yīng)用的可用性,講到這里應(yīng)該明白為什么需要限流了。
UML類圖
接下來看一下整個UML類圖:

這個圖是比較簡單。在這里我把整個限流做了一些簡化,在完整版里面會有數(shù)據(jù)的存儲和響應(yīng)時間的監(jiān)控。開始的數(shù)據(jù)是存儲在DB里面,后面為了學(xué)習(xí)數(shù)據(jù)庫的一些核心知識所以自己在寫一個很簡單的存儲(寫存儲的意義僅僅只是為了學(xué)習(xí),只是希望了解一下整個存儲在于空間分配的一些思想,對于SQL協(xié)議那一塊沒有更多的涉及)。這里將不在討論有關(guān)于存儲的細節(jié)。
回到上面這張圖,如我前面所說的,我用了兩種方式去實現(xiàn):一種就是Spring的攔截器方式,另外一種就是JDK的Proxy方式。
代碼展示
接下來看一下幾個類的代碼,這里面最核心的類就是FlowMonitor.java,限流的具體邏輯都在里面。下面會把具體的代碼貼出來,當(dāng)看到代碼的時候估計會覺得這東西太簡單了。下面我會拋出更多的問題,也是本文中沒有實現(xiàn)的。
FlowMonitor類

/** 
 *限流的作用 
 *實現(xiàn)一個緩沖隊列,讓一部分進入等待狀態(tài) 
 *區(qū)間監(jiān)控 
 *如果一個線程返回特別慢怎么辦,比如在release之前拋了異常 
 * @author 百惱 2013-04-11上午11:26:36 
 * 
 */  
public class FlowMonitor {      
  
    //默認(rèn)最大的并發(fā)數(shù)默認(rèn)為100,可以配置  
    private int maxFlowSize = 100;  
    //最大并發(fā)數(shù)  
    private int maxRunningSize = 0;  
      
    //當(dāng)前并發(fā)數(shù)  
    private AtomicInteger runningSize = new AtomicInteger();  
      
    //通過的數(shù)量  
    private AtomicInteger passSize = new AtomicInteger();  
      
    //失敗的數(shù)量  
    private AtomicInteger loseSize = new AtomicInteger();  
      
    public FlowMonitor(){  
        super();  
    }  
      
    public FlowMonitor(int maxFlowSize){  
        this();  
        this.maxFlowSize = maxFlowSize;  
    }  
  
    /** 
     * 線程進入開關(guān),即使這里用了一些Atomic類,這里仍然會有并發(fā)問題。 
     * @return 
     */  
    public boolean entry(){  
        //每個類中一個配置maxFlowSize  
        if(maxFlowSize>0){  
            if(maxFlowSize<=runningSize.get()){  
                //已經(jīng)超過最大限制  
                loseSize.incrementAndGet();  
                return false;  
            }  
            //并發(fā)數(shù)+1  
            runningSize.incrementAndGet();  
            if(runningSize.get()>maxRunningSize){  
                //記錄最大的并發(fā)數(shù),有并發(fā)問題  
                maxRunningSize = runningSize.get();  
            }  
            //記錄通過的線程數(shù)  
            passSize.incrementAndGet();  
        }  
        return true;  
    }  
      
    /** 
     * 執(zhí)行完后,并發(fā)數(shù)-1 
     * @param key 
     */  
    public void release(){  
        runningSize.decrementAndGet();  
    }  
      
    public AtomicInteger getRunningSize() {  
        return runningSize;  
    }  
  
    public AtomicInteger getPassSize() {  
        return passSize;  
    }  
  
    public AtomicInteger getLoseSize() {  
        return loseSize;  
    }  
  
    public int getMaxRunningSize() {  
        return maxRunningSize;  
    }  
      
    /** 
     * 重置,可以分時段進行監(jiān)控 
     */  
    public void reset(){  
        passSize.set(0);  
        loseSize.set(0);  
        maxRunningSize = 0;  
    }  
      
}  

上面類中最核心的兩個方法:一個是entry(),一個就是release()。entry()是在調(diào)用目標(biāo)方法之前調(diào)用,release()是在調(diào)用目標(biāo)方法之后調(diào)用。對于代碼層面這里就不做太多的解讀,代碼里也有一些注釋,估計學(xué)過Java的都能理解這上面所寫的。在類的注釋里面我寫了幾個問題:
第一個是實現(xiàn)一個緩沖隊列。上面我的處理策略是一種最簡單的方法:只要當(dāng)前并發(fā)數(shù)大于當(dāng)前所設(shè)定最大的并發(fā)數(shù)返回false,不做任何其他的處理。
第二個問題是如果在處理具體的邏輯的過程異常退出使得release()方法沒有執(zhí)行而導(dǎo)致當(dāng)前監(jiān)控的并發(fā)數(shù)不正常。所以這里需要一定的補救措施:可以實現(xiàn)一個隊列,隊列上每個節(jié)點都是一個并發(fā)請求,當(dāng)執(zhí)行entry()方法時,就會往隊列上插入一個節(jié)點,然后當(dāng)執(zhí)行release()時就把這個節(jié)點移出,然后當(dāng)發(fā)現(xiàn)這個并發(fā)請求異常(可以根據(jù)時間來判斷,比如說超過5s還未返回就對它進行中斷操作)就把這個節(jié)點移出。這樣可以比較準(zhǔn)確的監(jiān)控到當(dāng)前的并發(fā)數(shù)。
第三個問題是有關(guān)于監(jiān)控數(shù)據(jù)的問題,目前來看只能監(jiān)控幾個數(shù)據(jù):總的通過的數(shù)量,總的失敗的數(shù)量,最大的并發(fā)數(shù),當(dāng)前并發(fā)數(shù)。如果我想實現(xiàn)這樣一種需求:希望監(jiān)控某一時間段的情況,這個意義在于一旦出現(xiàn)問題(比如說失敗數(shù)量上升)我可以明顯知道在哪個時間節(jié)點出了問題。而目前的實現(xiàn)方式下是無法做到的。在上面的類中我留了一個reset()方法,其意義就是為了實現(xiàn)分時間段進行監(jiān)控提供一個重置幾個參數(shù)的接口。
第四個問題是在這里我并不知道該方法的RT(響應(yīng)時間:在這里就是執(zhí)行完這個方法的時間)監(jiān)控,這個也可以去實現(xiàn)。因為在此文中重點講限流,所以不想講太多有關(guān)于RT的的東西。各位有興趣也可以自己去實現(xiàn),原理也是一樣:仍然是用攔截器來實現(xiàn),具體的實現(xiàn)策略可以多種多樣。

MonitorHandler類

/** 
 * TODO Comment of MonitorHandler 
 * @author 百惱 2013-4-9上午10:16:43 
 * 
 */  
public interface MonitorHandler {  
      
    public boolean before();  
      
    public boolean after();  
  
}  

這個接口就不做任何解釋了,可以參考一下UML類圖中看看它所在的位置。

AbstractSpringMonitor類

/** 
 * TODO Comment of AbstractSpringMonitor 
 * @author 百惱 2013-4-9下午04:42:04 
 * 
 */  
public abstract class AbstractSpringMonitor implements MethodInterceptor,MonitorHandler{  
  
    /* (non-Javadoc) 
     * @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation) 
     */  
    @Override  
    public Object invoke(MethodInvocation method) throws Throwable {  
        boolean result = before();  
        if(result){  
            try{  
                method.proceed();  
            }catch(Exception e){  
                  
            } finally{  
                after();  
            }  
        }  
        return null;  
    }  
}  

SpringFlowMonitorHandler類

/** 
 * 使用spring攔截器實現(xiàn)監(jiān)控 
 *  
 * @author 百惱 2013-4-9下午06:36:24 
 */  
public class SpringFlowMonitorHandler extends AbstractSpringMonitor {  
  
    private FlowMonitor flowMonitor;  
      
    /* 
     * (non-Javadoc) 
     * @see com.yuzhipeng.monitor.MonitorHandler#before() 
     */  
    @Override  
    public boolean before() {  
        if (!flowMonitor.entry()) {  
            return false;  
        }  
        return true;  
    }  
  
    /* 
     * (non-Javadoc) 
     * @see com.yuzhipeng.monitor.MonitorHandler#after() 
     */  
    @Override  
    public boolean after() {  
        flowMonitor.release();  
        return true;  
    }  
  
    public FlowMonitor getFlowMonitor() {  
        return flowMonitor;  
    }  
  
    public void setFlowMonitor(FlowMonitor flowMonitor) {  
        this.flowMonitor = flowMonitor;  
    }  
}  

上面貼出的兩個類就是有關(guān)于Spring的方式來實現(xiàn),因為這種方法是會在項目中也會用的比較多。另外一種有關(guān)于JDK動態(tài)代理的方法代碼就不貼出來了,實現(xiàn)方式非常簡單。

總結(jié)
在這里簡單的分享了一個有關(guān)“限流”的意義和實現(xiàn)。在文中我也舉了一個例子為什么要限流。在一般傳統(tǒng)的內(nèi)部系統(tǒng)中對于限流的意義并不大。如果在互聯(lián)網(wǎng)公司,對于性能要求極高的時候就會用到限流。后面講到具體實現(xiàn)的時候估計會覺得比較簡單,代碼量也很少。這里又回歸到Spring的AOP,攔截器的使用。萬變不離其宗,Spring的使用(嚴(yán)格來講應(yīng)該是攔截器的這種思想)還是非常廣的。平時用的比較多的可能是權(quán)限校驗,日志記錄這些。
我每篇博客中所傳達的一個重要思想就是:“思考”。在此文中所實現(xiàn)方案并非完美,比如說我在FlowMonitor類中的注釋也說到了會有并發(fā)問題,但是我在那里并沒有加鎖,這又是為什么?在這里傳達一個很重要的思想就是:編程里面仍然講平衡。我們經(jīng)常碰到的一個例子就是”時間換空間,空間換時間“。所以在這里從代碼層面來講我確實要加鎖,但是從實際的應(yīng)用中不加鎖是一種更好的策略。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容