Java高并發解決方案

Java高并發解決方案

高并發是互聯網應用的一大特點,也是互聯網應用不可避免的一個問題;比如 淘寶雙11購物狂歡節,京東618購物促銷節,12306春節火車票,促銷,秒殺等。

解決高并發問題是一個系統工程,需要站在全局高度統籌謀劃,從多個角度進行架構設計;

解決高并發問題,不是一個或兩個方案就能解決的,需要從各個維度綜合施策才能完成;

在實踐中,我們總結和提煉出來了很多應對高并發的方案或者說手段,分別如下:

1. 硬件

⑴ 概述

系統訪問用戶增多,流量增大,導致服務器壓力增大,出現性能瓶頸,我們可以采用一個簡單粗暴的策略:提升服務器硬件配置,提升服務器硬件配置的策略,也稱為:單體應用垂直擴容。

比如:產品或者網站初期,通常功能較少,用戶量也不多,一般就采用一個項目工程來完成,這就是單體應用,也叫集中式應用。

按照經典的MVC三層架構設計,部署單臺服務器,使用單臺數據庫,應用系統和數據庫部署在同一臺服務器上,隨著應用系統功能的增加,訪問用戶的增多,單臺服務器已無法承受那么多的訪問流量。

此時,我們可以直接采用簡單粗暴的辦法:提升硬件配置來解決。

⑵ 單體應用垂直擴容方案

• CPU從32位提升為64位

• 內存從64GB提升為256GB(比如緩存服務器);

• 磁盤從HDD(Hard Disk Drive)提升為SSD(固態硬盤(Solid State Drives)),有大量讀寫的應用

• 磁盤擴容,1TB擴展到2TB,比如文件系統

• 千兆網卡提升為萬兆網卡

但是不管怎么提升硬件性能,硬件性能的提升不可能永無止盡,所以最終還是要靠分布式解決。

2. 緩存

⑴ 概述

緩存可以說是解決大流量高并發,優化系統性能非常重要的一個策略;

它是解決性能問題的利器,就像一把瑞士軍刀,鋒利強大;

緩存在高并發系統中無處不在(到處都是)。

⑵ http緩存

① 瀏覽器緩存

瀏覽器緩存是指當我們使用瀏覽器訪問一些網站頁面或者HTTP服務時,根據服務器端返回的緩存設置響應頭將響應內容緩存到瀏覽器,下次可以直接使用緩存內容或者僅需要去服務器端驗證內容是否過期即可,這樣可以減少瀏覽器和服務器之間來回傳輸的數據量,節省帶寬,提升性能;

比如新浪:http://www.sina.com.cn/

第一次訪問返回200,第二次刷新訪問,返回響應碼為304,表示頁面內容沒有修改過,瀏覽器緩存的內容還是最新的,不需要從服務器獲取,直接讀取瀏覽器緩存即可

我們也可以在Java代碼中通過設置響應頭,告訴前端瀏覽器進行緩存:

ateFormat format = new SimpleDateFormat("EEE,MMM yyyy HH: mm: ss 'GMT'", Locale. US);
//當前時間
long now = System.currentTimeMillis() * 1000 * 1000;
response.addHeader( "Date", format.format(new Date()));
//過期時間http 1. 0支持
response.addHeader("Expires", format.format (new Date(now+ 20 * 1000)));
//文檔生存時間http 1.1支持
response.addHeader("Cache-Control", "max-age=20");

② Nginx緩存

Nginx提供了expires指令來實現緩存控制,比如:

location /static {
  		root /opt/static/;
  		expires 1d;//全天
}

當用戶訪問時,Nginx攔截到請求后先從Nginx本地緩存查詢數據,如果有并且沒有過期,則直接返回緩存內容。

③ CDN緩存

CDN的全稱是Content Delivery Network,即內容分發網絡。CDN是構建在網絡之上的內容分發網絡,依靠部署在各地的邊緣服務器,通過中心平臺的負載均衡、內容分發、調度等功能模塊,使用戶就近獲取所需內容,降低網絡擁塞,提高用戶訪問響應速度和命中率。CDN的關鍵技術主要有內容存儲和分發技術。

CDN它本身也是一個緩存,它把后端應用的數據緩存起來,用戶要訪問的時候,直接從CDN上獲取,不需要走后端的Nginx,以及具體應用服務器Tomcat,它的作用主要是加速數據的傳輸,也提高穩定性,如果從CDN上沒有獲取到數據,再走后端的Nginx緩存,Nginx上也沒有,則走后端的應用服務器,CDN主要緩存靜態資源。

著名的廠商:(帝聯科技)http://www.dnion.com/

⑶ 應用緩存

① 內存緩存

在內存中緩存數據,效率高,速度快,應用重啟緩存丟失。

② 磁盤緩存

在磁盤緩存數據,讀取效率較之內存緩存稍低,應用重啟緩存不會丟失。

代碼組件:Guava、Ehcache

服務器:Redis、MemCache

⑷ 多級緩存

在整個應用系統的不同層級進行數據的緩存,多層次緩存,來提升訪問效率;

比如:瀏覽器 -> CDN -> Nginx -> Redis -> DB (磁盤、文件系統)

⑸ 緩存的使用場景

• 經常需要讀取的數據

• 頻繁訪問的數據

• 熱點數據緩存

• IO瓶頸數據

• 計算昂貴的數據

• 無需實時更新的數據

• 緩存的目的是減少對后端服務的訪問,降低后端服務的壓力

3. 集群

有一個單體應用,當訪問流量很大無法支撐,那么可以集群部署,也叫單體應用水平擴容,原來通過部署一臺服務器提供服務,現在就多部署幾臺,那么服務的能力就會提升。

部署了多臺服務器,但是用戶訪問入口只能是一個,比如www.web.com,所以就需要負載均衡,負載均衡是應用集群擴容后的必須步驟,集群部署后,用戶的會話session狀態要保持的話,就需要實現session共享。

4. 拆分

⑴ 應用拆分

應用的拆分:分布式 (微服務)

單體應用,隨著業務的發展,應用功能的增加,單體應用就逐步變得非常龐大,很多人維護這么一個系統,開發、測試、上線都會造成很大問題,比如代碼沖突,代碼重復,邏輯錯綜混亂,代碼邏輯復雜度增加,響應新需求的速度降低,隱藏的風險增大,所以需要按照業務維度進行應用拆分,采用分布式開發;

應用拆分之后,就將原來在同一進程里的調用變成了遠程方法調用,此時就需要使用到一些遠程調用技術:httpClient、hessian、dubbo、webservice等;

隨著業務復雜度增加,我們需要采用一些開源方案進行開發,提升開發和維護效率,比如Dubbo、SpringCloud;

通過應用拆分之后,擴容就變得容易,如果此時系統處理能力跟不上,只需要增加服務器即可(把拆分后的每一個服務再多做幾個集群)。

⑵ 數據庫拆分

數據庫拆分分為:垂直拆分和水平拆分 (分庫分表);

按照業務維度把相同類型的表放在一個數據庫,另一些表放在另一個數據庫,這種方式的拆分叫垂直拆分,也就是在不同庫建不同表,把表分散到各個數據庫;

比如產品、訂單、用戶三類數據以前在一個數據庫中,現在可以用三個數據庫,分別為 產品數據庫、訂單數據庫、用戶數據庫;

這樣可以將不同的數據庫部署在不同的服務器上,提升單機容量和性能問題,也解決多個表之間的IO競爭問題;

根據數據行的特點和規則,將表中的某些行切分到一個數據庫,而另外的某些行又切分到另一個數據庫,這種方式的拆分叫水平拆分;

單庫單表在數據量和流量增大的過程中,大表往往會成為性能瓶頸,所以數據庫要進行水平拆分;

數據庫拆分,采用一些開源方案,降低開發難度,比如:MyCat、Sharding-Sphere。

5. 靜態化

對于一些訪問量大,更新頻率較低的數據,可直接定時生成靜態html頁面,供前端訪問,而不是訪問jsp;

常用靜態化的技術:freemaker、velocity;

定時任務,每隔2分鐘生成一次首頁的靜態化頁面;

頁面靜態化首先可以大大提升訪問速度,不需要去訪問數據庫或者緩存來獲取數據,瀏覽器直接加載html頁即可;

頁面靜態化可以提升網站穩定性,如果程序或數據庫出了問題,靜態頁面依然可以正常訪問。

6. 動靜分離

采用比如Nginx實現動靜分離,Nginx負責代理靜態資源,Tomcat負責處理動態資源;

Nginx的效率極高,利用它處理靜態資源,可以為后端服務器分擔壓力;

動靜分離架構示意圖

redis和nginx并發量5w左右,tomcat和mysql700左右,當然可以通過一些方式調整。

7. 隊列

• 采用隊列是解決高并發大流量的利器

• 隊列的作用就是:異步處理/流量削峰/系統解耦

• 異步處理是使用隊列的一個主要原因,比如注冊成功了,發優惠券/送積分/送紅包/發短信/發郵件等操作都可以異步處理

• 使用隊列流量削峰,比如并發下單、秒殺等,可以考慮使用隊列將請求暫時入隊,通過隊列的方式將流量削平,變成平緩請求進行處理,避免應用系統因瞬間的巨大壓力而壓垮

• 使用隊列實現系統解耦,比如支付成功了,發消息通知物流系統,發票系統,庫存系統等,而無需直接調用這些系統;

• 隊列應用場景

不是所有的處理都必須要實時處理;

不是所有的請求都必須要實時告訴用戶結果;

不是所有的請求都必須100%一次性處理成功;

不知道哪個系統需要我的協助來實現它的業務處理,保證最終一致性,不需要強一致性。

常見的消息隊列產品:ActiveMQ/RabbitMQ/RocketMQ/kafka

• ActiveMQ是jms規范下的一個老牌的成熟的消息中間件/消息服務器

• RabbitMQ/RocketMQ 數據可靠性極好,性能也非常優秀,在一些金融領域、電商領域使用很廣泛;RocketMQ是阿里巴巴的;

• kafka主要運用在大數據領域,用于對數據的分析,日志的分析等處理,它有可能產生消息的丟失問題,它追求性能,性能極好,不追求數據的可靠性

8. 池化

在實際開發中,我們經常會采用一些池化技術,減少資源消耗,提升系統性能。

⑴ 對象池

通過復用對象,減少對象創建和垃圾收集器回收對象的資源開銷;

可以采用commons-pool2實現;

實際項目采用對象池并不常見,主要在開發框架或組件的時候會采用。

⑵ 數據庫連接池

Druid/DBCP/C3P0/BoneCP

⑶ Redis連接池

JedisPool(內部基于commons-pool2 實現)

⑷ HttpClient連接池

核心實現類:PoolingClientConnectionManager

http://hc.apache.org/httpcomponents-client-ga/httpclient/examples/org/apache/http/examples/client/ClientMultiThreadedExecution.java

⑸ 線程池

Java提供java.util.concurrent包可以實現線程池

Executors.newFixedThreadPool(8);線程數量固定

Executors.newSingleThreadExecutor();只有一個線程,避免關閉情況

Executors.newCachedThreadPool();可以自動擴容

Executors.newScheduledThreadPool(10);每隔多久執行

9. 優化

⑴  JVM優化

設置JVM參數

-server -Xmx4g -Xms4g -Xmn256m

-XX:PermSize=128m

-Xss256k

-XX:+DisableExplicitGC

-XX:+UseConcMarkSweepGC

-XX:+CMSParallelRemarkEnabled

-XX:+UseCMSCompactAtFullCollection

-XX:LargePageSizeInBytes=128m

-server VM有兩種運行模式Server與Client,兩種模式的區別在于,Client模式啟動速度較快,Server模式啟動較慢;但是啟動進入穩定期長期運行之后Server模式的程序運行速度比Client要快很多;
-Xmx2g 最大堆大小
-Xms2g 	初始堆大小
-Xmn256m 堆中年輕代大??;
-XX:PermSize設置非堆內存初始值,默認是物理內存的1/64;由XX:MaxPermSize設置最大非堆內存的大小,默認是物理內存的1/4.
-Xss 每個線程的Stack大小
-XX:+DisableExplicitGC,這個參數作用是禁止代碼中顯示調用GC。代碼如何顯示調用GC呢,通過System.gc()函數調用。如果加上了這個JVM啟動參數,那么代碼中調用System.gc()沒有任何效果,相當于是沒有這行代碼一樣。
-XX:+UseConcMarkSweepGC 并發標記清除(CMS)收集器,CMS收集器也被稱為短暫停頓并發收集器;
-XX:+CMSParallelRemarkEnabled 降低標記停頓;
-XX:+UseCMSCompactAtFullCollection:使用并發收集器時,開啟對年老代的壓縮.
-XX:LargePageSizeInBytes 指定 Java heap 的分頁頁面大小
-XX:+UseFastAccessorMethods 原始類型的快速優化
-XX:+UseCMSInitiatingOccupancyOnly  使用手動定義的初始化定義開始CMS收集
-XX:CMSInitiatingOccupancyFraction 使用cms作為垃圾回收使用70%后開始CMS收集;

⑵ Tomcat優化

• 設置JVM參數,可以參考JVM優化參數

在tomcat的bin目錄下的catalina.sh中設置jvm參數:

JAVA_OPTS="-server -XX:+PrintGCDetails -Xmx4g -Xms4g -Xmn256m

-XX:PermSize=128m

-Xss256k

-XX:+DisableExplicitGC

-XX:+UseConcMarkSweepGC

-XX:+CMSParallelRemarkEnabled

-XX:+UseCMSCompactAtFullCollection

-XX:LargePageSizeInBytes=128m

-XX:+UseFastAccessorMethods

-XX:+UseCMSInitiatingOccupancyOnly

-XX:CMSInitiatingOccupancyFraction=70"

• 設置tomcat的線程池大小

• 設置 IO 模式

• 配置 APR

10. Java程序優化

• 養成良好的編程習慣

• 不要重復創建太多對象

• 流/文件/連接 一定要記得在finally塊中關閉

• 少用重量級同步鎖synchronized,采用Lock

• 不要在循環體中使用try/catch

• 多定義局部變量,少定義成員變量

11. 數據庫優化

⑴ 數據庫服務器優化

修改數據庫服務器的配置文件的參數,偏DBA (數據庫管理員)

⑵ 數據庫架構優化

• 將數據庫服務器和應用服務器分離

• 讀寫分離:通過數據庫主從架構解決,寫數據時操作主庫,讀數據時操作從庫,分攤讀寫壓力

• 分庫分表:擴容數據庫,解決數據量容量問題

⑶ 數據庫索引優化

• 建立合適的索引

• 建立索引的字段盡量的小,最好是數值

• 盡量在唯一性高的字段上創建索引,主鍵、序號等

• 不要在性別這種唯一性很低的字段上創建索引

⑷ SQL優化

SQL優化很多,可以總結出很多經驗;

參考文章:https://blog.csdn.net/jie_liang/article/details/77340905

⑸ 采用數據搜索引擎

solr / elasticsearch

12. Ning優化

調整配置文件參數

worker_processes 16; 
gzip  on;  #開啟gzip壓縮輸出
events { 
worker_connections 65535; #極限值65535
  		multi_accept on; #開啟多路連接
  		use epoll; #使用epoll模型
}

13. Linux優化

優化Linux內核參數

修改/etc/sysctl.conf

http://blog.51cto.com/yangrong/1567427 偏運維的職責

14. 網絡優化

機房、帶寬、路由器等方面優化

網絡架構更合理

運維的職責

15. 前端優化

⑴  js優化

• 壓縮變小

• 壓縮工具

• 多個js合并成一個js文件,直接手動拷貝到一個文件中去,頁面只加載這一個文件或者利用程序,比如controller,/aa/js?path=xxx.js,xxx.js

⑵ css優化

• 壓縮變小

• 多個css文件合并成一個css文件

⑶ html頁面優化

• 不要加載太多js和css

• js和css加載放在頁面的尾部,從用戶體驗角度考慮的

• 頁面上減少到服務的請求數

16. 壓測

• 壓測就是壓力測試

• 在系統上線前,需要對系統各個環節進行壓力測試,發現系統的瓶頸點,然后對系統的瓶頸點,進行調優。調優完成后,還需要考慮另外一些風險因素,比如網絡不穩定,機房故障等。所以我們需要提前有故障預備方案,比如多機房部署容災、路由切換等。故障預備方案做好后,還需要提前進行演練,以確保預案的有效性

• 壓力測試工具:Apache JMeter / LoadRunner等,偏測試的工作

• CTO、架構師,技術團隊、測試團隊、運維團隊、DBA 等共同完成

總結

完成以上的工作,我們才能實現一個 高并發、高性能、高可用 的“三高”分布式系統。

全部教程