Java多線程與高並發:高並發解決思路
緩存並發

當大量請求訪問同一個沒有被緩存的數據的時候,會發送大量請求給數據庫,導致數據庫壓力過大,還會導致一致性問題,所以解決方式就是在緩存獲取的時候加上針對單個數據的鎖,直到緩存被重建成功得到最新數據
緩存擊穿/穿透

查詢一個數據庫中不存在的數據,比如商品詳情,查詢一個不存在的ID,每次都會訪問DB,如果有人惡意破壞,很可能直接對DB造成過大地壓力。
解決方案:
當通過某一個key去查詢數據的時候,如果對應在數據庫中的數據都不存在,我們將此key對應的value設置為一個默認的值。
緩存失效
在高並發的環境下,如果此時key對應的緩存失效,此時有多個進程就會去同時去查詢DB,然后再去同時設置緩存。這個時候如果這個key是系統中的熱點key或者同時失效的數量比較多時,DB訪問量會瞬間增大,造成過大的壓力。
解決方案:
- 將系統中key的緩存失效時間均勻地錯開。
- 當我們通過key去查詢數據時,首先查詢緩存,如果此時緩存中查詢不到,就通過分布式鎖進行加鎖。
熱點key
緩存中的某些Key(可能對應用與某個促銷商品)對應的value存儲在集群中一台機器,使得所有流量涌向同一機器,成為系統的瓶頸,該問題的挑戰在於它無法通過增加機器容量來解決。
解決方案:
- 客戶端熱點key緩存:將熱點key對應value並緩存在客戶端本地,並且設置一個失效時間。
- 將熱點key分散為多個子key,然后存儲到緩存集群的不同機器上,這些子key對應的value都和熱點key是一樣的。
消息隊列
消息隊列是為了解決生產和消費的速度不一致導致的問題,有以下好處:
- 減少請求響應時間。比如注冊功能需要調用第三方接口來發短信,如果等待第三方響應可能會需要很多時間
- 服務之間解耦。主服務只關心核心的流程,其他不重要的、耗費時間流程是否如何處理完成不需要知道,只通知即可
- 流量削鋒。對於不需要實時處理的請求來說,當並發量特別大的時候,可以先在消息隊列中作緩存,然后陸續發送給對應的服務去處理
如果想要實現一個消息隊列,可以參考這里
最簡單的消息隊列就是一個消息轉發器,基本功能只有三個:消息存儲、消息發送、消息刪除,可使用LinkedBlockingQueue、ConcurrentLinkedQueue實現
應用拆分
之前翻譯過的一篇博文已經提到,如何將已經存在的巨無霸單體應用重構成微服務,點擊上面鏈接即可
限流

限流是為了解決高並發情況下,大量請求導致數據庫或服務器壓力過大出現延遲或出錯的方式,在圖中,如果一次性將100多萬數據發送給master庫,那么服務器與數據庫的IO性能將會被大量占用,導致其他服務對數據庫的不可用,master庫還需要很久的時間將數據同步給slave庫
控制某段代碼在一定時間內的執行次數,可通過Guava或Semaphore實現
數據庫切庫、分庫、分表
切庫:數據庫讀寫分離導致的數據庫切換操作
當單個數據庫的讀寫性能達到瓶頸的時候,可根據業務來判斷讀與寫的比重,然后通過將數據庫設置為Master-Slave模式完成讀寫分離並配置好所有庫的讀寫權限。
當查詢業務多余讀取業務的時候,通過負載均衡,將查詢的操作分擔給不同的從庫,從而減輕主庫的壓力。
可以通過Spring注解來完成配置
分庫分表
當單庫的性能達到瓶頸,或當單表容量達到瓶頸,通過SQL與索引的優化之后還是很慢,那么就需要分表
水平分表:表結構保持不變,根據固定的ID將數據划分到不同表中,需要在寫入與查詢的時候進行ID的路由
垂直分表:將表結構根據數據的活躍度拆分成多個表,來分別提高不同的單表處理能力
問題:
- 事務問題。在執行分庫之后,由於數據存儲到了不同的庫上,數據庫事務管理出現了困難。如果依賴數據庫本身的分布式事務管理功能去執行事務,將付出高昂的性能代價;如果由應用程序去協助控制,形成程序邏輯上的事務,又會造成編程方面的負擔。
- 跨庫跨表的join問題。在執行了分庫分表之后,難以避免會將原本邏輯關聯性很強的數據划分到不同的表、不同的庫上,我們無法join位於不同分庫的表,也無法join分表粒度不同的表,結果原本一次查詢能夠完成的業務,可能需要多次查詢才能完成。
- 額外的數據管理負擔和數據運算壓力。額外的數據管理負擔,最顯而易見的就是數據的定位問題和數據的增刪改查的重復執行問題,這些都可以通過應用程序解決,但必然引起額外的邏輯運算。