高并發(fā)是互聯(lián)網(wǎng)應(yīng)用的一大特點(diǎn),也是互聯(lián)網(wǎng)應(yīng)用不可避免的一個(gè)問題;比如 淘寶雙11購物狂歡節(jié),京東618購物促銷節(jié),12306春節(jié)火車票,促銷,秒殺等。
解決高并發(fā)問題是一個(gè)系統(tǒng)工程,需要站在全局高度統(tǒng)籌謀劃,從多個(gè)角度進(jìn)行架構(gòu)設(shè)計(jì);
解決高并發(fā)問題,不是一個(gè)或兩個(gè)方案就能解決的,需要從各個(gè)維度綜合施策才能完成;
在實(shí)踐中,我們總結(jié)和提煉出來了很多應(yīng)對高并發(fā)的方案或者說手段,分別如下:
系統(tǒng)訪問用戶增多,流量增大,導(dǎo)致服務(wù)器壓力增大,出現(xiàn)性能瓶頸,我們可以采用一個(gè)簡單粗暴的策略:提升服務(wù)器硬件配置,提升服務(wù)器硬件配置的策略,也稱為:單體應(yīng)用垂直擴(kuò)容。
比如:產(chǎn)品或者網(wǎng)站初期,通常功能較少,用戶量也不多,一般就采用一個(gè)項(xiàng)目工程來完成,這就是單體應(yīng)用,也叫集中式應(yīng)用。
按照經(jīng)典的MVC三層架構(gòu)設(shè)計(jì),部署單臺(tái)服務(wù)器,使用單臺(tái)數(shù)據(jù)庫,應(yīng)用系統(tǒng)和數(shù)據(jù)庫部署在同一臺(tái)服務(wù)器上,隨著應(yīng)用系統(tǒng)功能的增加,訪問用戶的增多,單臺(tái)服務(wù)器已無法承受那么多的訪問流量。
此時(shí),我們可以直接采用簡單粗暴的辦法:提升硬件配置來解決。
• CPU從32位提升為64位
• 內(nèi)存從64GB提升為256GB(比如緩存服務(wù)器);
• 磁盤從HDD(Hard Disk Drive)提升為SSD(固態(tài)硬盤(Solid State Drives)),有大量讀寫的應(yīng)用
• 磁盤擴(kuò)容,1TB擴(kuò)展到2TB,比如文件系統(tǒng)
• 千兆網(wǎng)卡提升為萬兆網(wǎng)卡
但是不管怎么提升硬件性能,硬件性能的提升不可能永無止盡,所以最終還是要靠分布式解決。
緩存可以說是解決大流量高并發(fā),優(yōu)化系統(tǒng)性能非常重要的一個(gè)策略;
它是解決性能問題的利器,就像一把瑞士軍刀,鋒利強(qiáng)大;
緩存在高并發(fā)系統(tǒng)中無處不在(到處都是)。
① 瀏覽器緩存
瀏覽器緩存是指當(dāng)我們使用瀏覽器訪問一些網(wǎng)站頁面或者HTTP服務(wù)時(shí),根據(jù)服務(wù)器端返回的緩存設(shè)置響應(yīng)頭將響應(yīng)內(nèi)容緩存到瀏覽器,下次可以直接使用緩存內(nèi)容或者僅需要去服務(wù)器端驗(yàn)證內(nèi)容是否過期即可,這樣可以減少瀏覽器和服務(wù)器之間來回傳輸?shù)臄?shù)據(jù)量,節(jié)省帶寬,提升性能;
比如新浪:http://www.sina.com.cn/
第一次訪問返回200,第二次刷新訪問,返回響應(yīng)碼為304,表示頁面內(nèi)容沒有修改過,瀏覽器緩存的內(nèi)容還是最新的,不需要從服務(wù)器獲取,直接讀取瀏覽器緩存即可
我們也可以在Java代碼中通過設(shè)置響應(yīng)頭,告訴前端瀏覽器進(jìn)行緩存:
ateFormat format = new SimpleDateFormat("EEE,MMM yyyy HH: mm: ss 'GMT'", Locale. US);
//當(dāng)前時(shí)間
long now = System.currentTimeMillis() * 1000 * 1000;
response.addHeader( "Date", format.format(new Date()));
//過期時(shí)間http 1. 0支持
response.addHeader("Expires", format.format (new Date(now+ 20 * 1000)));
//文檔生存時(shí)間http 1.1支持
response.addHeader("Cache-Control", "max-age=20");
② Nginx緩存
Nginx提供了expires指令來實(shí)現(xiàn)緩存控制,比如:
location /static {
root /opt/static/;
expires 1d;//全天
}
當(dāng)用戶訪問時(shí),Nginx攔截到請求后先從Nginx本地緩存查詢數(shù)據(jù),如果有并且沒有過期,則直接返回緩存內(nèi)容。
③ CDN緩存
CDN的全稱是Content Delivery Network,即內(nèi)容分發(fā)網(wǎng)絡(luò)。CDN是構(gòu)建在網(wǎng)絡(luò)之上的內(nèi)容分發(fā)網(wǎng)絡(luò),依靠部署在各地的邊緣服務(wù)器,通過中心平臺(tái)的負(fù)載均衡、內(nèi)容分發(fā)、調(diào)度等功能模塊,使用戶就近獲取所需內(nèi)容,降低網(wǎng)絡(luò)擁塞,提高用戶訪問響應(yīng)速度和命中率。CDN的關(guān)鍵技術(shù)主要有內(nèi)容存儲(chǔ)和分發(fā)技術(shù)。
CDN它本身也是一個(gè)緩存,它把后端應(yīng)用的數(shù)據(jù)緩存起來,用戶要訪問的時(shí)候,直接從CDN上獲取,不需要走后端的Nginx,以及具體應(yīng)用服務(wù)器Tomcat,它的作用主要是加速數(shù)據(jù)的傳輸,也提高穩(wěn)定性,如果從CDN上沒有獲取到數(shù)據(jù),再走后端的Nginx緩存,Nginx上也沒有,則走后端的應(yīng)用服務(wù)器,CDN主要緩存靜態(tài)資源。
著名的廠商:(帝聯(lián)科技)http://www.dnion.com/
① 內(nèi)存緩存
在內(nèi)存中緩存數(shù)據(jù),效率高,速度快,應(yīng)用重啟緩存丟失。
② 磁盤緩存
在磁盤緩存數(shù)據(jù),讀取效率較之內(nèi)存緩存稍低,應(yīng)用重啟緩存不會(huì)丟失。
代碼組件:Guava、Ehcache
服務(wù)器:Redis、MemCache
在整個(gè)應(yīng)用系統(tǒng)的不同層級進(jìn)行數(shù)據(jù)的緩存,多層次緩存,來提升訪問效率;
比如:瀏覽器 -> CDN -> Nginx -> Redis -> DB (磁盤、文件系統(tǒng))
• 經(jīng)常需要讀取的數(shù)據(jù)
• 頻繁訪問的數(shù)據(jù)
• 熱點(diǎn)數(shù)據(jù)緩存
• IO瓶頸數(shù)據(jù)
• 計(jì)算昂貴的數(shù)據(jù)
• 無需實(shí)時(shí)更新的數(shù)據(jù)
• 緩存的目的是減少對后端服務(wù)的訪問,降低后端服務(wù)的壓力
有一個(gè)單體應(yīng)用,當(dāng)訪問流量很大無法支撐,那么可以集群部署,也叫單體應(yīng)用水平擴(kuò)容,原來通過部署一臺(tái)服務(wù)器提供服務(wù),現(xiàn)在就多部署幾臺(tái),那么服務(wù)的能力就會(huì)提升。
部署了多臺(tái)服務(wù)器,但是用戶訪問入口只能是一個(gè),比如www.web.com,所以就需要負(fù)載均衡,負(fù)載均衡是應(yīng)用集群擴(kuò)容后的必須步驟,集群部署后,用戶的會(huì)話session狀態(tài)要保持的話,就需要實(shí)現(xiàn)session共享。
應(yīng)用的拆分:分布式 (微服務(wù))
單體應(yīng)用,隨著業(yè)務(wù)的發(fā)展,應(yīng)用功能的增加,單體應(yīng)用就逐步變得非常龐大,很多人維護(hù)這么一個(gè)系統(tǒng),開發(fā)、測試、上線都會(huì)造成很大問題,比如代碼沖突,代碼重復(fù),邏輯錯(cuò)綜混亂,代碼邏輯復(fù)雜度增加,響應(yīng)新需求的速度降低,隱藏的風(fēng)險(xiǎn)增大,所以需要按照業(yè)務(wù)維度進(jìn)行應(yīng)用拆分,采用分布式開發(fā);
應(yīng)用拆分之后,就將原來在同一進(jìn)程里的調(diào)用變成了遠(yuǎn)程方法調(diào)用,此時(shí)就需要使用到一些遠(yuǎn)程調(diào)用技術(shù):httpClient、hessian、dubbo、webservice等;
隨著業(yè)務(wù)復(fù)雜度增加,我們需要采用一些開源方案進(jìn)行開發(fā),提升開發(fā)和維護(hù)效率,比如Dubbo、SpringCloud;
通過應(yīng)用拆分之后,擴(kuò)容就變得容易,如果此時(shí)系統(tǒng)處理能力跟不上,只需要增加服務(wù)器即可(把拆分后的每一個(gè)服務(wù)再多做幾個(gè)集群)。
數(shù)據(jù)庫拆分分為:垂直拆分和水平拆分 (分庫分表);
按照業(yè)務(wù)維度把相同類型的表放在一個(gè)數(shù)據(jù)庫,另一些表放在另一個(gè)數(shù)據(jù)庫,這種方式的拆分叫垂直拆分,也就是在不同庫建不同表,把表分散到各個(gè)數(shù)據(jù)庫;
比如產(chǎn)品、訂單、用戶三類數(shù)據(jù)以前在一個(gè)數(shù)據(jù)庫中,現(xiàn)在可以用三個(gè)數(shù)據(jù)庫,分別為 產(chǎn)品數(shù)據(jù)庫、訂單數(shù)據(jù)庫、用戶數(shù)據(jù)庫;
這樣可以將不同的數(shù)據(jù)庫部署在不同的服務(wù)器上,提升單機(jī)容量和性能問題,也解決多個(gè)表之間的IO競爭問題;
根據(jù)數(shù)據(jù)行的特點(diǎn)和規(guī)則,將表中的某些行切分到一個(gè)數(shù)據(jù)庫,而另外的某些行又切分到另一個(gè)數(shù)據(jù)庫,這種方式的拆分叫水平拆分;
單庫單表在數(shù)據(jù)量和流量增大的過程中,大表往往會(huì)成為性能瓶頸,所以數(shù)據(jù)庫要進(jìn)行水平拆分;
數(shù)據(jù)庫拆分,采用一些開源方案,降低開發(fā)難度,比如:MyCat、Sharding-Sphere。
對于一些訪問量大,更新頻率較低的數(shù)據(jù),可直接定時(shí)生成靜態(tài)html頁面,供前端訪問,而不是訪問jsp;
常用靜態(tài)化的技術(shù):freemaker、velocity;
定時(shí)任務(wù),每隔2分鐘生成一次首頁的靜態(tài)化頁面;
頁面靜態(tài)化首先可以大大提升訪問速度,不需要去訪問數(shù)據(jù)庫或者緩存來獲取數(shù)據(jù),瀏覽器直接加載html頁即可;
頁面靜態(tài)化可以提升網(wǎng)站穩(wěn)定性,如果程序或數(shù)據(jù)庫出了問題,靜態(tài)頁面依然可以正常訪問。
采用比如Nginx實(shí)現(xiàn)動(dòng)靜分離,Nginx負(fù)責(zé)代理靜態(tài)資源,Tomcat負(fù)責(zé)處理動(dòng)態(tài)資源;
Nginx的效率極高,利用它處理靜態(tài)資源,可以為后端服務(wù)器分擔(dān)壓力;
動(dòng)靜分離架構(gòu)示意圖
redis和nginx并發(fā)量5w左右,tomcat和mysql700左右,當(dāng)然可以通過一些方式調(diào)整。
• 采用隊(duì)列是解決高并發(fā)大流量的利器
• 隊(duì)列的作用就是:異步處理/流量削峰/系統(tǒng)解耦
• 異步處理是使用隊(duì)列的一個(gè)主要原因,比如注冊成功了,發(fā)優(yōu)惠券/送積分/送紅包/發(fā)短信/發(fā)郵件等操作都可以異步處理
• 使用隊(duì)列流量削峰,比如并發(fā)下單、秒殺等,可以考慮使用隊(duì)列將請求暫時(shí)入隊(duì),通過隊(duì)列的方式將流量削平,變成平緩請求進(jìn)行處理,避免應(yīng)用系統(tǒng)因瞬間的巨大壓力而壓垮
• 使用隊(duì)列實(shí)現(xiàn)系統(tǒng)解耦,比如支付成功了,發(fā)消息通知物流系統(tǒng),發(fā)票系統(tǒng),庫存系統(tǒng)等,而無需直接調(diào)用這些系統(tǒng);
• 隊(duì)列應(yīng)用場景
不是所有的處理都必須要實(shí)時(shí)處理;
不是所有的請求都必須要實(shí)時(shí)告訴用戶結(jié)果;
不是所有的請求都必須100%一次性處理成功;
不知道哪個(gè)系統(tǒng)需要我的協(xié)助來實(shí)現(xiàn)它的業(yè)務(wù)處理,保證最終一致性,不需要強(qiáng)一致性。
常見的消息隊(duì)列產(chǎn)品:ActiveMQ/RabbitMQ/RocketMQ/kafka
• ActiveMQ是jms規(guī)范下的一個(gè)老牌的成熟的消息中間件/消息服務(wù)器
• RabbitMQ/RocketMQ 數(shù)據(jù)可靠性極好,性能也非常優(yōu)秀,在一些金融領(lǐng)域、電商領(lǐng)域使用很廣泛;RocketMQ是阿里巴巴的;
• kafka主要運(yùn)用在大數(shù)據(jù)領(lǐng)域,用于對數(shù)據(jù)的分析,日志的分析等處理,它有可能產(chǎn)生消息的丟失問題,它追求性能,性能極好,不追求數(shù)據(jù)的可靠性
在實(shí)際開發(fā)中,我們經(jīng)常會(huì)采用一些池化技術(shù),減少資源消耗,提升系統(tǒng)性能。
通過復(fù)用對象,減少對象創(chuàng)建和垃圾收集器回收對象的資源開銷;
可以采用commons-pool2實(shí)現(xiàn);
實(shí)際項(xiàng)目采用對象池并不常見,主要在開發(fā)框架或組件的時(shí)候會(huì)采用。
Druid/DBCP/C3P0/BoneCP
JedisPool(內(nèi)部基于commons-pool2 實(shí)現(xiàn))
核心實(shí)現(xiàn)類:PoolingClientConnectionManager
http://hc.apache.org/httpcomponents-client-ga/httpclient/examples/org/apache/http/examples/client/ClientMultiThreadedExecution.java
Java提供java.util.concurrent包可以實(shí)現(xiàn)線程池
Executors.newFixedThreadPool(8);線程數(shù)量固定
Executors.newSingleThreadExecutor();只有一個(gè)線程,避免關(guān)閉情況
Executors.newCachedThreadPool();可以自動(dòng)擴(kuò)容
Executors.newScheduledThreadPool(10);每隔多久執(zhí)行
設(shè)置JVM參數(shù)
-server -Xmx4g -Xms4g -Xmn256m
-XX:PermSize=128m
-Xss256k
-XX:+DisableExplicitGC
-XX:+UseConcMarkSweepGC
-XX:+CMSParallelRemarkEnabled
-XX:+UseCMSCompactAtFullCollection
-XX:LargePageSizeInBytes=128m
-server VM有兩種運(yùn)行模式Server與Client,兩種模式的區(qū)別在于,Client模式啟動(dòng)速度較快,Server模式啟動(dòng)較慢;但是啟動(dòng)進(jìn)入穩(wěn)定期長期運(yùn)行之后Server模式的程序運(yùn)行速度比Client要快很多;
-Xmx2g 最大堆大小
-Xms2g 初始堆大小
-Xmn256m 堆中年輕代大小;
-XX:PermSize設(shè)置非堆內(nèi)存初始值,默認(rèn)是物理內(nèi)存的1/64;由XX:MaxPermSize設(shè)置最大非堆內(nèi)存的大小,默認(rèn)是物理內(nèi)存的1/4.
-Xss 每個(gè)線程的Stack大小
-XX:+DisableExplicitGC,這個(gè)參數(shù)作用是禁止代碼中顯示調(diào)用GC。代碼如何顯示調(diào)用GC呢,通過System.gc()函數(shù)調(diào)用。如果加上了這個(gè)JVM啟動(dòng)參數(shù),那么代碼中調(diào)用System.gc()沒有任何效果,相當(dāng)于是沒有這行代碼一樣。
-XX:+UseConcMarkSweepGC 并發(fā)標(biāo)記清除(CMS)收集器,CMS收集器也被稱為短暫停頓并發(fā)收集器;
-XX:+CMSParallelRemarkEnabled 降低標(biāo)記停頓;
-XX:+UseCMSCompactAtFullCollection:使用并發(fā)收集器時(shí),開啟對年老代的壓縮.
-XX:LargePageSizeInBytes 指定 Java heap 的分頁頁面大小
-XX:+UseFastAccessorMethods 原始類型的快速優(yōu)化
-XX:+UseCMSInitiatingOccupancyOnly 使用手動(dòng)定義的初始化定義開始CMS收集
-XX:CMSInitiatingOccupancyFraction 使用cms作為垃圾回收使用70%后開始CMS收集;
⑵ Tomcat優(yōu)化
• 設(shè)置JVM參數(shù),可以參考JVM優(yōu)化參數(shù)
在tomcat的bin目錄下的catalina.sh中設(shè)置jvm參數(shù):
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"
• 設(shè)置tomcat的線程池大小
• 設(shè)置 IO 模式
• 配置 APR
• 養(yǎng)成良好的編程習(xí)慣
• 不要重復(fù)創(chuàng)建太多對象
• 流/文件/連接 一定要記得在finally塊中關(guān)閉
• 少用重量級同步鎖synchronized,采用Lock
• 不要在循環(huán)體中使用try/catch
• 多定義局部變量,少定義成員變量
修改數(shù)據(jù)庫服務(wù)器的配置文件的參數(shù),偏DBA (數(shù)據(jù)庫管理員)
• 將數(shù)據(jù)庫服務(wù)器和應(yīng)用服務(wù)器分離
• 讀寫分離:通過數(shù)據(jù)庫主從架構(gòu)解決,寫數(shù)據(jù)時(shí)操作主庫,讀數(shù)據(jù)時(shí)操作從庫,分?jǐn)傋x寫壓力
• 分庫分表:擴(kuò)容數(shù)據(jù)庫,解決數(shù)據(jù)量容量問題
• 建立合適的索引
• 建立索引的字段盡量的小,最好是數(shù)值
• 盡量在唯一性高的字段上創(chuàng)建索引,主鍵、序號等
• 不要在性別這種唯一性很低的字段上創(chuàng)建索引
SQL優(yōu)化很多,可以總結(jié)出很多經(jīng)驗(yàn);
參考文章:https://blog.csdn.net/jie_liang/article/details/77340905
solr / elasticsearch
調(diào)整配置文件參數(shù)
worker_processes 16;
gzip on; #開啟gzip壓縮輸出
events {
worker_connections 65535; #極限值65535
multi_accept on; #開啟多路連接
use epoll; #使用epoll模型
}
13. Linux優(yōu)化
優(yōu)化Linux內(nèi)核參數(shù)
修改/etc/sysctl.conf
http://blog.51cto.com/yangrong/1567427 偏運(yùn)維的職責(zé)
機(jī)房、帶寬、路由器等方面優(yōu)化
網(wǎng)絡(luò)架構(gòu)更合理
運(yùn)維的職責(zé)
• 壓縮變小
• 壓縮工具
• 多個(gè)js合并成一個(gè)js文件,直接手動(dòng)拷貝到一個(gè)文件中去,頁面只加載這一個(gè)文件或者利用程序,比如controller,/aa/js?path=xxx.js,xxx.js
• 壓縮變小
• 多個(gè)css文件合并成一個(gè)css文件
• 不要加載太多js和css
• js和css加載放在頁面的尾部,從用戶體驗(yàn)角度考慮的
• 頁面上減少到服務(wù)的請求數(shù)
• 壓測就是壓力測試
• 在系統(tǒng)上線前,需要對系統(tǒng)各個(gè)環(huán)節(jié)進(jìn)行壓力測試,發(fā)現(xiàn)系統(tǒng)的瓶頸點(diǎn),然后對系統(tǒng)的瓶頸點(diǎn),進(jìn)行調(diào)優(yōu)。調(diào)優(yōu)完成后,還需要考慮另外一些風(fēng)險(xiǎn)因素,比如網(wǎng)絡(luò)不穩(wěn)定,機(jī)房故障等。所以我們需要提前有故障預(yù)備方案,比如多機(jī)房部署容災(zāi)、路由切換等。故障預(yù)備方案做好后,還需要提前進(jìn)行演練,以確保預(yù)案的有效性
• 壓力測試工具:Apache JMeter / LoadRunner等,偏測試的工作
• CTO、架構(gòu)師,技術(shù)團(tuán)隊(duì)、測試團(tuán)隊(duì)、運(yùn)維團(tuán)隊(duì)、DBA 等共同完成
總結(jié)
完成以上的工作,我們才能實(shí)現(xiàn)一個(gè) 高并發(fā)、高性能、高可用 的“三高”分布式系統(tǒng)。