閱讀目錄html
add by zhj: 我我的感受性能優化分析影響性能的因素有哪些,而後按影響力的大小進行排序,而後進行排序。前端
而後進一步分析每一個因素爲什麼會影響性能,把這些因素再找出來,再按影響力大小進行排序。基本上,通過mysql
這兩層的分析,基本就夠用了。對這些因素思考解決辦法。git
1. 數據庫層github
咱們的目標是減小IO訪問,或者將IO訪問進行負載均衡,分配到多臺服務器,並行計算。redis
1.1 數據庫的數據存儲在硬盤,硬盤訪問速度比內存慢太多,即IO多算法
1.2 數據量大致使掃描記錄多,間接致使IO多sql
1.3 全部數據庫訪問都集中在一臺數據庫服務器,負載重數據庫
2. 應用層後端
負載均衡
3. 前端
原文:http://tech.meituan.com/performance_tunning.html
本文要感謝我職級評定過程當中的一位評委,他建議把以前所作的各類性能優化的案例和方案加以提煉、總結,以文檔的形式沉澱下來,並在內部進行分享。力求達到以下效果:
1. 造成可實踐、可借鑑、可參考的各類性能優化的方案以及選型考慮點,同時配合具體的真實案例,其餘人遇到類似問題時,不用從零開始。
2. 有助於開闊視野,除了性能優化以外,也能提供通用的常見思路以及方案選型的考慮點,幫助你們培養在方案選型時的意識、思惟以及作各類權衡的能力。
文章在內部分享後,引發強烈分享,獲得了很多同事和朋友的承認和好評,以爲對平常的工做有很好的指導做用。考慮到這些經驗可能對業界同行也有幫助,因此在美團點評技術團隊博客公開。
之因此把代碼放到第一位,是由於這一點最容易引發技術人員的忽視。不少技術人員拿到一個性能優化的需求之後,言必稱緩存、異步、JVM等。實際上,第一步就應該是分析相關的代碼,找出相應的瓶頸,再來考慮具體的優化策略。有一些性能問題,徹底是因爲代碼寫的不合理,經過直接修改一下代碼就能解決問題的,好比for循環次數過多、做了不少無謂的條件判斷、相同邏輯重複屢次等。
數據庫的調優,總的來講分爲如下三部分:
這是最經常使用、每個技術人員都應該掌握基本的SQL調優手段(包括方法、工具、輔助系統等)。這裏以MySQL爲例,最多見的方式是,由自帶的慢查詢日誌或者開源的慢查詢系統定位到具體的出問題的SQL,而後使用explain、profile等工具來逐步調優,最後通過測試達到效果後上線。這方面的細節,能夠參考MySQL索引原理及慢查詢優化。
這一類調優包括讀寫分離、多從庫負載均衡、水平和垂直分庫分表等方面,通常須要的改動較大,可是頻率沒有SQL調優高,並且通常須要DBA來配合參與。那麼何時須要作這些事情?咱們能夠經過內部監控報警系統(好比Zabbix),按期跟蹤一些指標數據是否達到瓶頸,一旦達到瓶頸或者警惕值,就須要考慮這些事情。一般,DBA也會按期監控這些指標值。
咱們的應用爲了實現數據庫鏈接的高效獲取、對數據庫鏈接的限流等目的,一般會採用鏈接池類的方案,即每個應用節點都管理了一個到各個數據庫的鏈接池。隨着業務訪問量或者數據量的增加,原有的鏈接池參數可能不能很好地知足需求,這個時候就須要結合當前使用鏈接池的原理、具體的鏈接池監控數據和當前的業務量做一個綜合的判斷,經過反覆的幾回調試獲得最終的調優參數。
本地緩存(HashMap/ConcurrentHashMap、Ehcache、Guava Cache等),緩存服務(Redis/Tair/Memcache等)。
什麼狀況適合用緩存?考慮如下兩種場景:
更新緩存的策略,須要具體問題具體分析。這裏以門店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;某些狀況,也能夠不計算,直接採集發往監控服務器。
提供統一的展示分析平臺,須要帶報表(列表/圖表)監控和報警的功能。
這是一個每小時按期運行一次的job,做用是用來刷新商家與控制區的關係。具體規則就是根據商家的配送範圍(多個)與控制區是否有交集,若是有交集,就把這個商家劃到這個控制區的範圍內。
須要這個過程越短越好,最好保持在20分鐘內。
原有代碼的主要處理流程是:
分析代碼,發現第2步的a步驟和b步驟,找出和某控制區相交的配送範圍集合並對商家ID去重,能夠採用R樹空間索引的方式來優化。具體作法是:
這個優化已經在第一期優化中上線,整個過程耗時由40多分鐘縮短到20分鐘之內。
第一期優化改成R樹之後,運行了一段時間,隨着數據量增大,性能又開始逐漸惡化,一個月後已經惡化到50多分鐘。因而繼續深刻代碼分析,尋找了兩個優化點,安排第二期優化並上線。
這兩個優化點是:
經過日誌觀察,執行時間由50多分鐘縮短到15分鐘之內,下圖是截取了一天的4臺機器的日誌時間(單位:毫秒):
能夠看到,效果仍是很是明顯的。
2014年Q4,數據庫中關於POI(這裏能夠簡單理解爲外賣的門店)相關的數據的讀流量急劇上升,雖說加入從庫節點能夠解決一部分問題,可是畢竟節點的增長是會達到極限的,達到極限後主從複製會達到瓶頸,可能會形成數據不一致。因此此時,急需引入一種新的技術方案來分擔數據庫的壓力,下降數據庫POI相關數據的讀流量。另外,任何場景都考慮加DB從庫的作法,會對資源形成必定的浪費。
基於已有的通過考驗的技術方案,我選擇Tair來做爲緩存的存儲方案,來幫DB分擔來自於各應用端的POI數據的讀流量的壓力。理由主要是從可用性、高性能、可擴展性、是否通過線上大規模數據和高併發流量的考驗、是否有專業運維團隊、是否有成熟工具等幾個方面綜合考量決定。
緩存的更新策略,根據業務的特色、已有的技術方案和實現成本,選擇了用MQ來接收POI改變的消息來觸發緩存的更新,可是這個過程有可能失敗;同時啓用了key的過時策略,而且調用端會先判斷是否過時,如過時,會從後端DB加載數據並回設到緩存,再返回。經過兩個方面雙保險確保了緩存數據的可用。
初版設計運行到一段時間之後,咱們發現了兩個問題:
爲了解決上述問題,咱們從美團點評負責基礎架構的同事那裏瞭解到Databus能夠解決緩存數據在某些狀況下不一致的問題,而且能夠去掉過時時間機制,從而提升查詢效率,避免tair在內存不命中時查詢硬盤。並且爲了防止DataBus單點出現故障影響咱們的業務,咱們保留了以前接MQ消息更新緩存的方案,做了切換開關,利用這個方案做容錯,總體架構以下:
上線後,經過持續地監控數據發現,隨着調用量的上升,到DB的流量有了明顯地減小,極大地減輕了DB的壓力。同時這些數據接口的響應時間也有了明顯地減小。緩存更新的雙重保障機制,也基本保證了緩存數據的可用。見下圖:
隨着業務的快速發展,帶來的訪問量和數據量的急劇上升,經過咱們相應的監控系統能夠發現,系統的某些頁面的性能開始出現惡化。 從用戶方的反饋,也證實了這點。此時此刻,有必要迅速排期,敏捷開發,對這些頁面進行調優。
除了上面介紹的以外,優化還涉及前端、分佈式文件系統、CDN、全文索引、空間索引等幾方面。限於篇幅,咱們留到將來再作介紹。
https://www.cnblogs.com/ajianbeyourself/p/6132546.html