閱讀816 返回首頁    go 技術社區[雲棲]


關乎性能的思考

        現在工程師們越發的喜歡談性能(CPU, memery, IO, container, middleware等),性能調優似乎也成為軟件行業基礎架構師越來越倚重的技藝之一。通常情況下,性能shooting也無外乎分為幾個過程。其一,performance diagnostic & bottleneck positioning,這一步往往是關鍵,當然手法也是千奇百怪,這裏不做贅述。第二,performance tuning,真正的調優過程是一個很Galileo,很Continuous的事情,當然也有一些模型,規則可以遵守。正如大夥疾唿:編碼是一種藝術。在這,允許我呐喊一下:調優更是一門藝術。不過這門藝術要求還是有點高的,你不僅要有紮實的硬件基本知識,還要對諸多軟件工作模式有一探到底的精氣神。這裏我們不談性能調優如何藝術,更多的是想和大家分享一些性能調優後的思考(試圖去完善一本性能調優沉思錄)。思考過後,我們可以嚐試著問問自己能不能做到性能架構,性能編碼?注意,這裏的性能架構,性能編碼是自己發明的一個詞匯,說的直白點,就是將性能更早的引入軟件生命周期中來,別忘了,銀行家算法是避免死鎖的有效策略~
        那麼,現在步入正題。首先我想分享的是使用Jmeter做DBCP的調優。調優的初衷很簡單,提高TPS,同時減少數據庫災難帶來的連接池exhaust(提高可用性)。第一個調優目標,是個很業務的事情,需要根據你的業務場景進行調整,策略上最值得注意的是maxIdle和minIdle兩個參數,類似Java的-Xmx和-Xms的設置。剩下的一些參數調整,個人覺得沒什麼借鑒意義,所以就不贅述了,畢竟這是一件很業務的事情。再看另一個目標,這裏我主要想說的是removeAbandoned和removeAbandonedTimeout。從軟件的健壯性來講,邊界值問題尤為重要。在資源獲取型編碼過程中,最好的體現就是超時時間的設置,這是個很經驗,很統計概率的事情。對樣本空間進行充分取樣,一定可以得出該樣本空間樣本點的均值,當然還有均方差。後者更多的在關注樣本點與平均值的偏離程度。仔細體會一下Jmeter的90% Line性能指標(這隻是一個很簡單的數學場景,後麵有機會和大家分享一篇性能調優中的數學之美的博文)。好了,話題收斂一下,我來解釋一下removeAbandonedTimeout的重要性:當connection idle時間超越這個數值時, AbandonedObjectPool會在borrowObject和returnObject時會先進行連接的有效性判斷。很好理解,在借出對象時,需要判斷該對象是否處於活躍狀態(不滿足removeAbandoned條件則歸還,滿足需要看超時時間,然後…);歸還時的判斷原則大致類同,這個很容易想到。代碼如下:
/**
     * Get a db connection from the pool.
     *
     * If removeAbandoned=true, recovers db connections which
     * have been idle > removeAbandonedTimeout and
     * getNumActive() > getMaxActive() - 3 and
     * getNumIdle() < 2
     * 
     * @return Object jdbc Connection
     * @throws Exception if an exception occurs retrieving a 
     * connection from the pool
     */
    public Object borrowObject() throws Exception {
        if (config != null
                && config.getRemoveAbandoned()
                && (getNumIdle() < 2)
                && (getNumActive() > getMaxActive() - 3) ) {
            removeAbandoned();
        }
        Object obj = super.borrowObject();
        if (obj instanceof AbandonedTrace) {
            ((AbandonedTrace) obj).setStackTrace();
        }
        if (obj != null && config != null && config.getRemoveAbandoned()) {
            synchronized (trace) {
                trace.add(obj);
            }
        }
        return obj;
    }
/**
     * Return a db connection to the pool.
     *
     * @param obj db Connection to return
     * @throws Exception if an exception occurs returning the connection
     * to the pool
     */
    public void returnObject(Object obj) throws Exception {
        if (config != null && config.getRemoveAbandoned()) {
            synchronized (trace) {
                boolean foundObject = trace.remove(obj);
                if (!foundObject) {
                    return; // This connection has already been invalidated.  Stop now.
                }
            }
        }
        super.returnObject(obj);
    }

        通過分析,壓測,調整參數驗證,我們達到了優化目標(主要是基於以上參數的調整)。而後,我們需要認真反思一下:在拿到一款開源軟件並成功run起來,進入到production環境前,是不是需要做點啥?每依賴一個新Jar包,就壓測一通,是不是很沒章法,顯得?能不能理論指導實踐一下呢?我的回答是當然可以,但需要仔細研讀一下關乎性能的相關參數說明。當然,哪些會成為你著重關注的性能參數,這又是一個很經驗的事情。再舉個例子,之前做過幾個Connection調優的事情,問題定位到最後,發現兩邊的Connection timeout設置的不具備“包容性”。具體來說,在Tomcat的AJP connector和apache的mod_jk connector pipe對接時,處於請求響應數據流向的AJP connector端的timeout是不是可以大於等於mod_Jk counterpart呢?試想一下,倘若前者過小,當連接數大於connection_pool_minsize,並且connection_pool_timeout 超時,mod_jk會主動斷開連接。而Tomcat 這邊隻要到了connectionTimeout超時時間,就會立即放棄連接。這就導致了mod_jk 繼續持有連接,而Tomcat這邊卻放棄了這條連接。後果很嚴重吧(不作不死的Timewait問題)?為了更形象點,我還是把關鍵配置項摘錄下來吧:
workers.properties:
worker.node1.connection_pool_minsize=25
worker.node1.connection_pool_timeout=600

server.xml :
<Connector port="8009" address="${jboss.bind.address}" protocol="AJP/1.3" emptySessionPath="true" enableLookups="false" redirectPort="8443" URIEncoding="UTF-8" backlog="256" maxThreads="250" connectionTimeout="600000"/>

         Hmm,既然已經注意到了這點,我們就可以把Tomcat的connectionTimeout調得比較大,讓它完全包容mod_Jk counterpart,又或者把mod_jk的connection_pool_minsize 設成0,再或者在mod_jk中設置worker.node1.prepost_timeout=10000 ,通過斷開死連接的方式,都是可以解決這個問題的。寫到這,忽然想起之前封裝的那個高性能的HttpClient。為什麼我敢“吹牛”它是高性能呢?原因很簡單,Apache的性能參數之前我們已經進行了調優(通過分析日誌統計出連續HTTP請求出現的次數、間隔時間、訪問量, 以確定 MaxKeepAliveRequests 和 KeepAliveTimeout 的值。說到這裏,我有必要提醒大家注意一下Timeout和KeepAliveTimeout的區別,用兩段官方的解釋說明一下吧,個人覺得很貼切:

The TimeOut directive currently defines the amount of time Apache will wait for three things: 

1. The total amount of time it takes to receive a GET request. 
2. The amount of time between receipt of TCP packets on a POST or PUT request. 
3. The amount of time between ACKs on transmissions of TCP packets in responses. 

The number of seconds Apache will wait for a subsequent request before closing the connection.Once a request has been received, the timeout value specified by the Timeout directive applies. 

),那麼我封裝HttpClient的時候,隻需要對接apache的MPM參數(具體可參看:https://blog.csdn.net/fengjia10/article/details/7315279),那麼在設計,編碼階段就可以解決大部分性能問題,剩下的就是根據業務場景稍作調優即可。

小結: 

通過這篇博客,我想說的是,作為職業架構師,我們不僅要讀的通源碼,玩得起perf tool(分析問題,解決問題),更需要的是將你的優化意識盡可能早的引入到軟件的研發周期中(根據以往的經驗,過早考慮性能,也可能會導致代碼晦澀難懂。建議在設計中作為非功能需求考量,並在代碼的優化階段(也可能是提測後)實施性能調優),即架構設計,甚至是基礎代碼的編寫過程中。盡可能的運用數學模型去驗證你的性能參數選擇。請牢牢記住:意識永遠比技術更重要。


參考資料:

1. https://commons.apache.org/dbcp/configuration.html

2. https://software.intel.com/zh-cn/articles/book-Multicore-Multithread-Technology_tuning_cycle/

最後更新:2017-04-02 16:48:14

  上一篇:go Makefile生成工具和方法(autoconf 和 automake)
  下一篇:go android優秀網站收集中