之因此把代碼放到第一位,是由於這一點最容易引發技術人員的忽視。不少技術人員拿到一個性能優化的需求之後,言必稱緩存、異步、JVM等。實際上,第一步就應該是分析相關的代碼,找出相應的瓶頸,再來考慮具體的優化策略。有一些性能問題,徹底是因爲代碼寫的不合理,經過直接修改一下代碼就能解決問題的,好比for循環次數過多、做了不少無謂的條件判斷、相同邏輯重複屢次等。html
數據庫的調優,總的來講分爲如下三部分:java
這是最經常使用、每個技術人員都應該掌握基本的SQL調優手段(包括方法、工具、輔助系統等)。這裏以MySQL爲例,最多見的方式是,由自帶的慢查詢日誌或者開源的慢查詢系統定位到具體的出問題的SQL,而後使用explain、profile等工具來逐步調優,最後通過測試達到效果後上線。這方面的細節,能夠參考MySQL索引原理及慢查詢優化。mysql
這一類調優包括讀寫分離、多從庫負載均衡、水平和垂直分庫分表等方面,通常須要的改動較大,可是頻率沒有SQL調優高,並且通常須要DBA來配合參與。那麼何時須要作這些事情?咱們能夠經過內部監控報警系統(好比Zabbix),按期跟蹤一些指標數據是否達到瓶頸,一旦達到瓶頸或者警惕值,就須要考慮這些事情。一般,DBA也會按期監控這些指標值。redis
咱們的應用爲了實現數據庫鏈接的高效獲取、對數據庫鏈接的限流等目的,一般會採用鏈接池類的方案,即每個應用節點都管理了一個到各個數據庫的鏈接池。隨着業務訪問量或者數據量的增加,原有的鏈接池參數可能不能很好地知足需求,這個時候就須要結合當前使用鏈接池的原理、具體的鏈接池監控數據和當前的業務量做一個綜合的判斷,經過反覆的幾回調試獲得最終的調優參數。算法
本地緩存(HashMap/ConcurrentHashMap、Ehcache、Guava Cache等),緩存服務(Redis/Tair/Memcache等)。sql
什麼狀況適合用緩存?考慮如下兩種場景:數據庫
更新緩存的策略,須要具體問題具體分析。這裏以門店POI的緩存數據爲例,來講明一下緩存服務型的緩存更新策略是怎樣的?目前約10萬個POI數據採用了Tair做爲緩存服務,具體更新的策略有兩個:後端
對於一個緩存服務,理論上來講,隨着緩存數據的日益增多,在容量有限的狀況下,緩存確定有一天會滿的。如何應對?
① 給緩存服務,選擇合適的緩存逐出算法,好比最多見的LRU。
② 針對當前設置的容量,設置適當的警惕值,好比10G的緩存,當緩存數據達到8G的時候,就開始發出報警,提早排查問題或者擴容。
③ 給一些沒有必要長期保存的key,儘可能設置過時時間。緩存
根據業務場景判斷,是否容許丟失。若是不容許,就須要帶持久化功能的緩存服務來支持,好比Redis或者Tair。更細節的話,能夠根據業務對丟失時間的容忍度,還能夠選擇更具體的持久化策略,好比Redis的RDB或者AOF。性能優化
對於一些設置了過時時間的key,若是這些key可能會在某些時間點被超高併發地訪問,是一種很是「熱點」的數據。這個時候,須要考慮另一個問題:緩存被「擊穿」的問題。
如何解決:業界比較經常使用的作法,是使用mutex。簡單地來講,就是在緩存失效的時候(判斷拿出來的值爲空),不是當即去load db,而是先使用緩存工具的某些帶成功操做返回值的操做(好比Redis的SETNX或者Memcache的ADD)去set一個mutex key,當操做返回成功時,再進行load db的操做並回設緩存;不然,就重試整個get緩存的方法。相似下面的代碼:
public String get(key) { String value = redis.get(key); if (value == null) { //表明緩存值過時 //設置3min的超時,防止del操做失敗的時候,下次緩存過時一直不能load db if (redis.setnx(key_mutex, 1, 3 * 60) == 1) { //表明設置成功 value = db.get(key); redis.set(key, value, expire_secs); redis.del(key_mutex); } else { //這個時候表明同時候的其餘線程已經load db並回設到緩存了,這時候重試獲取緩存值便可 sleep(50); get(key); //重試 } } else { return value; } }
針對某些客戶端的請求,在服務端可能須要針對這些請求作一些附屬的事情,這些事情其實用戶並不關心或者用戶不須要當即拿到這些事情的處理結果,這種狀況就比較適合用異步的方式處理這些事情。
一種作法,是額外開闢線程,這裏能夠採用額外開闢一個線程或者使用線程池的作法,在IO線程(處理請求響應)以外的線程來處理相應的任務,在IO線程中讓response先返回。
若是異步線程處理的任務設計的數據量很是巨大,那麼能夠引入阻塞隊列BlockingQueue做進一步的優化。具體作法是讓一批異步線程不斷地往阻塞隊列裏扔數據,而後額外起一個處理線程,循環批量從隊列裏拿預設大小的一批數據,來進行批處理(好比發一個批量的遠程服務請求),這樣進一步提升了性能。
另外一種作法,是使用消息隊列(MQ)中間件服務,MQ天生就是異步的。一些額外的任務,可能不須要我這個系統來處理,可是須要其餘系統來處理。這個時候能夠先把它封裝成一個消息,扔到消息隊列裏面,經過消息中間件的可靠性保證把消息投遞到關心它的系統,而後讓這個系統來作相應的處理。
好比C端在完成一個提單動做之後,可能須要其它端作一系列的事情,可是這些事情的結果不會馬上對C端用戶產生影響,那麼就能夠先把C端下單的請求響應先返回給用戶,返回以前往MQ中發一個消息便可。並且這些事情理應不是C端的負責範圍,因此這個時候用MQ的方式,來解決這個問題最合適。
先說明一下,這裏介紹的和緩存那一節不同,雖然可能會使用同樣的數據存儲方案(好比Redis或者Tair),可是使用的方式不同,這一節介紹的是把它做爲DB來用。若是看成DB來用,須要有效保證數據存儲方案的可用性、可靠性。
須要結合具體的業務場景,看這塊業務涉及的數據是否適合用NoSQL來存儲,對數據的操做方式是否適合用NoSQL的方式來操做,或者是否須要用到NoSQL的一些額外特性(好比原子加減等)。
若是業務數據不須要和其餘數據做關聯,不須要事務或者外鍵之類的支持,並且有可能寫入會異常頻繁,這個時候就比較適合用NoSQL(好比HBase)。
好比,美團點評內部有一個對exception作的監控系統,若是在應用系統發生嚴重故障的時候,可能會短期產生大量exception數據,這個時候若是選用MySQL,會形成MySQL的瞬間寫壓力飆升,容易致使MySQL服務器的性能急劇惡化以及主從同步延遲之類的問題,這種場景就比較適合用Hbase相似的NoSQL來存儲。
經過監控系統(如沒有現成的系統,本身作一個簡單的上報監控的系統也很容易)上對一些機器關鍵指標(gc time、gc count、各個分代的內存大小變化、機器的Load值與CPU使用率、JVM的線程數等)的監控報警,也能夠看gc log和jstat等命令的輸出,再結合線上JVM進程服務的一些關鍵接口的性能數據和請求體驗,基本上就能定位出當前的JVM是否有問題,以及是否須要調優。
離線任務、異步任務、大數據任務、耗時較長任務的運行**,適當地利用,可達到加速的效果。
注意:線上對響應時間要求較高的場合,儘可能少用多線程,尤爲是服務線程須要等待任務線程的場合(不少重大事故就是和這個息息相關),若是必定要用,能夠對服務線程設置一個最大等待時間。
若是單機的處理能力能夠知足實際業務的需求,那麼儘量地使用單機多線程的處理方式,減小複雜性;反之,則須要使用多機多線程的方式。
對於單機多線程,能夠引入線程池的機制,做用有二:
若是單機的處理能力不能知足需求,這個時候須要使用多機多線程的方式。這個時候就須要一些分佈式系統的知識了。首先就必須引入一個單獨的節點,做爲調度器,其餘的機器節點都做爲執行器節點。調度器來負責拆分任務,和分發任務到合適的執行器節點;執行器節點按照多線程的方式(也多是單線程)來執行任務。這個時候,咱們整個任務系統就由單擊演變成一個集羣的系統,並且不一樣的機器節點有不一樣的角色,各司其職,各個節點之間還有交互。這個時候除了有多線程、線程池等機制,像RPC、心跳等網絡通訊調用的機制也不可少。後續我會出一個簡單的分佈式調度運行的框架。
嚴格來講,度量系統不屬於性能優化的範疇,可是這方面和性能優化息息相關,能夠說爲性能優化提供一個強有力的數據參考和支撐。沒有度量系統,基本上就沒有辦法定位到系統的問題,也沒有辦法有效衡量優化後的效果。不少人不重視這方面,但我認爲它是系統穩定性和性能保障的基石。
若是要設計這套系統,整體來講有哪些關鍵流程須要設計呢?
① 肯定指標
② 採集數據
③ 計算數據,存儲結果
④ 展示和分析
按照需求出發,主要須要二方面的指標:
一般採用異步上報的方式,具體作法有兩種:第一種,發到本地的Flume端口,由Flume進程收集到遠程的Hadoop集羣或者Storm集羣來進行運算;第二種,直接在本地運算好之後,使用異步和本地隊列的方式,發送到監控服務器。
能夠採用離線運算(MapReduce/Hive)或者實時/準實時運算(Storm/Spark)的方式,運算後的結果存入MySQL或者HBase;某些狀況,也能夠不計算,直接採集發往監控服務器。
提供統一的展示分析平臺,須要帶報表(列表/圖表)監控和報警的功能。