本文簡單介紹下後端服務開發中經常使用的一些性能優化策略。html
優化代碼實現是第一位的,特別是一些不合理的複雜實現。若是結合需求能從代碼實現的角度,使用更高效的算法或方案實現,進而解決問題,那是最簡單有效的。java
數據庫的優化,整體上有3個方面:git
1) SQL調優:除了掌握SQL基本的優化手段,使用慢日誌定位到具體問題SQL,使用explain、profile等工具來逐步調優。github
2) 鏈接池調優:選擇高效適用的鏈接池,結合當前使用鏈接池的原理、具體的鏈接池監控數據和當前的業務量做一個綜合的判斷,經過反覆的幾回調試獲得最終的調優參數。redis
3) 架構層面:包括讀寫分離、主從庫負載均衡、水平和垂直分庫分表等方面,通常須要的改動較大,須要從總體架構方面綜合考慮。算法
本地緩存(HashMap/ConcurrentHashMap、Ehcache、RocksDB、Guava Cache等)。數據庫
緩存服務(Redis/Tair/Memcache等)。後端
一、何時更新緩存?如何保障更新的可靠性和實時性?緩存
更新緩存的策略,須要具體問題具體分析。基本的更新策略有兩個:性能優化
1) 接收變動的消息,準實時更新。
2) 給每個緩存數據設置5分鐘的過時時間,過時後從DB加載再回設到DB。這個策略是對第一個策略的有力補充,解決了手動變動DB不發消息、接收消息更新程序臨時出錯等問題致使的第一個策略失效的問題。經過這種雙保險機制,有效地保證了緩存數據的可靠性和實時性。
二、緩存是否會滿,緩存滿了怎麼辦?
對於一個緩存服務,理論上來講,隨着緩存數據的日益增多,在容量有限的狀況下,緩存確定有一天會滿的。如何應對?
1) 給緩存服務,選擇合適的緩存逐出算法,好比最多見的LRU。
2) 針對當前設置的容量,設置適當的警惕值,好比10G的緩存,當緩存數據達到8G的時候,就開始發出報警,提早排查問題或者擴容。
3) 給一些沒有必要長期保存的key,儘可能設置過時時間。
三、緩存是否容許丟失?丟失了怎麼辦?
根據業務場景判斷,是否容許丟失。若是不容許,就須要帶持久化功能的緩存服務來支持,好比Redis或者Tair。更細節的話,能夠根據業務對丟失時間的容忍度,還能夠選擇更具體的持久化策略,好比Redis的RDB或者AOF。
一、緩存穿透
描述:緩存穿透是指緩存和數據庫中都沒有的數據,而用戶不斷髮起請求,如發起爲id爲「-1」的數據或id爲特別大不存在的數據。這時的用戶極可能是攻擊者,攻擊會致使數據庫壓力過大。
解決方案:
1) 接口層增長校驗,如用戶鑑權校驗,id作基礎校驗,id<=0的直接攔截;
2) 從緩存取不到的數據,在數據庫中也沒有取到,這時也能夠將key-value對寫爲key-null,緩存有效時間能夠設置短點,如30秒(設置太長會致使正常狀況也無法使用)。這樣能夠防止攻擊用戶反覆用同一個id暴力攻擊。
二、緩存擊穿
描述:緩存擊穿是指緩存中沒有但數據庫中有的數據(通常是緩存時間到期),這時因爲併發用戶特別多,同時讀緩存沒讀到數據,又同時去數據庫去取數據,引發數據庫壓力瞬間增大,形成過大壓力。
解決方案:
1) 設置熱點數據永遠不過時。
2) 加互斥鎖,業界比較經常使用的作法,是使用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; } }
三、緩存雪崩
描述:緩存雪崩是指緩存中數據大批量到過時時間,而查詢數據量巨大,引發數據庫壓力過大甚至down機。和緩存擊穿不一樣的是,緩存擊穿是併發查同一條數據,緩存雪崩是不一樣數據都過時了,不少數據都查不到從而查數據庫。
解決方案:
1)緩存數據的過時時間設置隨機,防止同一時間大量數據過時現象發生。
2)若是緩存系統是分佈式部署,將熱點數據均勻分佈在不一樣的緩存節點中。
3)設置熱點數據永遠不過時。
四、緩存更新
Cache Aside 模式:這是最經常使用最經常使用的pattern了。其具體邏輯以下:
失效:應用程序先從cache取數據,沒有獲得,則從數據庫中取數據,成功後,放到緩存中。
命中:應用程序從cache中取數據,取到後返回。
更新:先把數據存到數據庫中,成功後,再讓緩存失效。
針對某些客戶端的請求,在服務端可能須要針對這些請求作一些附屬額外的事情,這些事情其實用戶並不關心或者不須要當即拿到這些事情的處理結果,這種狀況就比較適合用異步的方式去處理。
異步處理的好處:
1) 縮短接口響應時間,使用戶的請求快速返回,用戶體驗更好。
2) 避免線程長時間處於運行狀態,這樣會引發服務線程池的可用線程長時間不夠用,進而引發線程池任務隊列長度增大,從而阻塞更多請求任務,使得更多請求得不到及時處理。
3) 提高服務的處理性能。
一、線程(線程池)
採用額外開闢一個線程或者使用線程池的作法,在IO線程(處理請求響應)以外的線程來處理相應的任務,在IO線程中讓response先返回。
若是異步線程處理的任務設計的數據量很是大,那麼能夠引入阻塞隊列BlockingQueue做進一步的優化。具體作法是讓一批異步線程不斷地往阻塞隊列裏添加要處理的數據,而後額外起一個或一批處理線程,循環批量從隊列裏拿預設大小的數據,來進行批處理,這樣進一步提升了性能。
二、消息隊列(MQ)
使用消息隊列(MQ)中間件服務,MQ天生就是異步的。一些額外的任務,可能不須要這個系統來處理,可是須要其餘系統來處理。這個時候能夠先把它封裝成一個消息,扔到消息隊列裏面,經過消息中間件的可靠性保證把消息投遞到關心它的系統,而後讓其餘系統來作相應的處理。
這裏介紹的NoSQL和緩存不同,雖然可能會使用同樣的數據存儲方案(好比Redis或者Tair),可是使用的方式不同,這一節介紹的是把它做爲DB來用。若是看成DB來用,須要有效保證數據存儲方案的可用性、可靠性。
須要結合具體的業務場景,看這塊業務涉及的數據是否適合用NoSQL來存儲,對數據的操做方式是否適合用NoSQL的方式來操做,或者是否須要用到NoSQL的一些額外特性(好比原子加減等)。
若是業務數據不須要和其餘數據做關聯,不須要事務或者外鍵之類的支持,並且有可能寫入會異常頻繁,這個時候就比較適合用NoSQL(好比HBase)。監控類、日誌類系統一般會採集大量的時序數據,這類時序指標數據每每都是「讀少寫多」的類型,可使用Elasticsearch、OpenTSDB等。
離線任務、異步任務、大數據任務、耗時較長任務的運行,適當地利用,可達到加速的效果。
注意:線上對響應時間要求較高的場合,儘可能少用多線程,尤爲是服務線程須要等待任務線程的場合(不少重大事故就是和這個息息相關),若是必定要用,能夠對服務線程設置一個最大等待時間。
若是單機的處理能力能夠知足實際業務的需求,那麼儘量地使用單機多線程的處理方式,減小複雜性;反之,則須要使用多機多線程的方式。
對於單機多線程,能夠引入線程池的機制,做用有二:
1) 提升性能,節省線程建立和銷燬的開銷。
2) 限流,給線程池一個固定的容量,達到這個容量值後再有任務進來,就進入隊列進行排隊,保障機器極限壓力下的穩定處理能力在使用JDK自帶的線程池時,必定要仔細理解構造方法的各個參數的含義,如core pool size、max pool size、keepAliveTime、worker queue等,在理解的基礎上經過不斷地測試調整這些參數值達到最優效果。
若是單機的處理能力不能知足需求,這個時候須要使用多機多線程的方式。這個時候就須要一些分佈式系統的知識了,能夠選用一些開源成熟的分佈式任務調度系統如xxl-job。
我的主要的後端語言是JAVA,對JVM進行優化也能必定程度上的提高JAVA程序的性能。JVM一般可以在軟件開發後期進行,如在開發完畢或者是軟件開發的某一里程碑階段,JVM的各項參數將會直接影響JAVA程序的性能。
關注如下指標:CPU使用率、CPU load、GC count、GC time、GC日誌
查看java進程GC狀態:jstat -gcutil {pid} 1000
查看java進程CPU高緣由:
1) 獲取java進程pid:ps –ef|grep java
2) 分析是哪一個線程佔用率太高:top -H -p ‘PID’
3) 線程id轉換爲16進制:printf "%x\n" ‘NID’
4) Jstack查看線程堆棧:jstack PID | grep 'NID' -C行數 –color
推薦2個java工具:1)show-busy-java-threads 2)arthas
好比,JVM的堆大小(Xms、Xmx),垃圾回收策略等。要進行JVM層面的調優,須要對JVM的執行原理有必定的瞭解,如內存的結構,GC的種類等,而後根據應用程序的特色設置合理的JVM參數,可是GC tuning is the last task to be done.
參考:
https://tech.meituan.com/2016/12/02/performance-tunning.html