應(yīng)用服務(wù)器性能優(yōu)化

應(yīng)用服務(wù)器就是處理網(wǎng)站業(yè)務(wù)的服務(wù)器,網(wǎng)站的業(yè)務(wù)代碼部署在這里,是網(wǎng)站開發(fā)最復(fù)雜、變化最多的地方,優(yōu)化手段主要是緩存、集群、異步。

分布式緩存

網(wǎng)站性能優(yōu)化第一定律:優(yōu)先考慮使用緩存優(yōu)化性能。
整個(gè)網(wǎng)站應(yīng)用中,緩存幾乎無所不在,既存在于瀏覽器,也存在于應(yīng)用服務(wù)器和數(shù)據(jù)庫服務(wù)器;既可以對數(shù)據(jù)緩存,也可以對文件緩存,還可以對頁面片段緩存。合理使用緩存,對網(wǎng)站性能優(yōu)化意義重大。

緩存的基本原理

緩存指將數(shù)據(jù)存儲(chǔ)在相對較高訪問速度的存儲(chǔ)介質(zhì)中,以供系統(tǒng)處理。一方面緩存訪問速度快,可以減少數(shù)據(jù)訪問的時(shí)間,另一方面如果緩存的數(shù)據(jù)是經(jīng)過計(jì)算處理得到的,那么被緩存的數(shù)據(jù)無需重復(fù)計(jì)算即可直接使用,因此緩存還起到減少計(jì)算時(shí)間的作用。

緩存的本質(zhì)是一個(gè)內(nèi)存 Hash 表,網(wǎng)站應(yīng)用中,數(shù)據(jù)緩存以一對 Key、Value 的形式存儲(chǔ)在內(nèi)存 Hash 表中。Hash 表數(shù)據(jù)讀寫的時(shí)間復(fù)雜度為 O(1)。

緩存主要用來存放那些讀寫比很高、很少變化的數(shù)據(jù)。應(yīng)用程序讀取數(shù)據(jù)時(shí),先到緩存中讀取,如果讀取不到或數(shù)據(jù)已失效,再訪問數(shù)據(jù)庫,并將數(shù)據(jù)寫入緩存。

合理使用緩存

頻繁修改的數(shù)據(jù)

如果緩存中保存的是頻繁修改的數(shù)據(jù),就會(huì)出現(xiàn)數(shù)據(jù)寫入緩存后,應(yīng)用還來不及讀取緩存,數(shù)據(jù)就已失效的情形,徒增系統(tǒng)負(fù)擔(dān)。一般說來,數(shù)據(jù)的讀寫比在 2 : 1 以上,即寫入一次緩存,在數(shù)據(jù)更新前至少讀取兩次,緩存才有意義。實(shí)踐中,這個(gè)讀寫比通常非常高,比如新浪微博的熱門微博,緩存以后可能會(huì)被讀取數(shù)百萬次。

沒有熱點(diǎn)的訪問

緩存使用內(nèi)存作為存儲(chǔ),內(nèi)存資源寶貴而有限,不可能將所有數(shù)據(jù)都緩存起來,只能將最新訪問的數(shù)據(jù)緩存起來,而將歷史數(shù)據(jù)清理出緩存。如果應(yīng)用系統(tǒng)訪問數(shù)據(jù)沒有熱點(diǎn),不遵循二八定律,即大部分?jǐn)?shù)據(jù)訪問并沒有集中在小部分?jǐn)?shù)據(jù)上,那么緩存就沒有意義,因?yàn)榇蟛糠謹(jǐn)?shù)據(jù)還沒有被再次訪問就已經(jīng)被擠出緩存了。

數(shù)據(jù)不一致與臟讀

一般會(huì)對緩存的數(shù)據(jù)設(shè)置失效時(shí)間,一旦超過失效時(shí)間,就要從數(shù)據(jù)庫中重新加載。因此應(yīng)用要容忍一定時(shí)間的數(shù)據(jù)不一致,如賣家已經(jīng)編輯了商品屬性,但是需要過一段時(shí)間才能被買家看到。這種延遲通常是可以接受的,但是具體應(yīng)用仍需慎重對待。還有一種策略是數(shù)據(jù)更新時(shí)立即更新緩存,不過這也會(huì)帶來更多系統(tǒng)開銷和事務(wù)一致性的問題。

緩存可用性

緩存是為提高數(shù)據(jù)讀取性能的,緩存數(shù)據(jù)丟失或者緩存不可用不會(huì)影響到應(yīng)用程序的處理——它可以從數(shù)據(jù)庫直接獲取數(shù)據(jù)。但是隨著業(yè)務(wù)的發(fā)展,緩存會(huì)承擔(dān)大部分?jǐn)?shù)據(jù)訪問的壓力,數(shù)據(jù)庫已經(jīng)習(xí)慣了有緩存的日子,所以當(dāng)緩存服務(wù)崩潰時(shí),數(shù)據(jù)庫會(huì)因?yàn)橥耆荒艹惺苋绱舜蟮膲毫Χ礄C(jī),進(jìn)而導(dǎo)致整個(gè)網(wǎng)站不可用。這種情況被稱作緩存雪崩,發(fā)生這種故障,甚至不能簡單地重啟緩存服務(wù)器和數(shù)據(jù)庫服務(wù)器來恢復(fù)網(wǎng)站訪問。

實(shí)踐中,有的網(wǎng)站通過緩存熱備等手段提高緩存可用性:當(dāng)某臺(tái)緩存服務(wù)器宕機(jī)時(shí),將緩存訪問切換到熱備服務(wù)器上。但是這種設(shè)計(jì)顯然有違緩存的初衷,緩存根本就不應(yīng)該被當(dāng)做一個(gè)可靠的數(shù)據(jù)源來使用。

通過分布式緩存服務(wù)器集群,將緩存數(shù)據(jù)分布到集群多臺(tái)服務(wù)器上可在一定程度上改善緩存的可用性。當(dāng)一臺(tái)緩存服務(wù)器宕機(jī)的時(shí)候,只有部分緩存數(shù)據(jù)丟失,重新從數(shù)據(jù)庫加載這部分?jǐn)?shù)據(jù)不會(huì)對數(shù)據(jù)庫產(chǎn)生很大影響。

緩存預(yù)熱

緩存中存放的是熱點(diǎn)數(shù)據(jù),熱點(diǎn)數(shù)據(jù)又是緩存系統(tǒng)利用 LRU(最近最久未用算法)對不斷訪問的數(shù)據(jù)篩選淘汰出來的,這個(gè)過程需要花費(fèi)較長的時(shí)間。新啟動(dòng)的緩存系統(tǒng)如果沒有任何數(shù)據(jù),在重建緩存數(shù)據(jù)的過程中,系統(tǒng)的性能和數(shù)據(jù)庫負(fù)載都不太好,那么最好在緩存系統(tǒng)啟動(dòng)時(shí)就把熱點(diǎn)數(shù)據(jù)加載好,這個(gè)緩存預(yù)加載手段叫作緩存預(yù)熱(warm up)。對于一些元數(shù)據(jù)如城市地名列表、類目信息,可以在啟動(dòng)時(shí)加載數(shù)據(jù)庫中全部數(shù)據(jù)到緩存進(jìn)行預(yù)熱。

緩存穿透

如果因?yàn)椴磺‘?dāng)?shù)臉I(yè)務(wù)、或者惡意攻擊持續(xù)高并發(fā)地請求某個(gè)不存在的數(shù)據(jù),由于緩存沒有保存該數(shù)據(jù),所有的請求都會(huì)落到數(shù)據(jù)庫上,會(huì)對數(shù)據(jù)庫造成很大壓力,甚至崩潰。一個(gè)簡單的對策是將不存在的數(shù)據(jù)也緩存起來(其 value 值為 null)。

異步

使用消息隊(duì)列將調(diào)用異步化,可改善網(wǎng)站的擴(kuò)展性,同時(shí)可以改善網(wǎng)站系統(tǒng)的性能。

不使用消息隊(duì)列

使用消息隊(duì)列

在不使用消息隊(duì)列的情況下,用戶的請求數(shù)據(jù)直接寫入數(shù)據(jù)庫,在高并發(fā)地情況下,會(huì)對數(shù)據(jù)庫造成巨大的壓力,同時(shí)也使得響應(yīng)延遲加劇。在使用消息隊(duì)列后,用戶請求的數(shù)據(jù)發(fā)送給消息隊(duì)列后立即返回,再由消息隊(duì)列的消費(fèi)者進(jìn)程(通常該進(jìn)程獨(dú)立部署在專門的服務(wù)器集群上)從消息隊(duì)列中獲取數(shù)據(jù),異步寫入數(shù)據(jù)庫。由于消息隊(duì)列服務(wù)器處理速度遠(yuǎn)快于數(shù)據(jù)庫(消息隊(duì)列服務(wù)器也比數(shù)據(jù)庫具有更好的伸縮性),因此用戶的響應(yīng)延遲可得到有效改善。

消息隊(duì)列具有很好地削峰作用—即通過異步處理,將短時(shí)間高并發(fā)產(chǎn)生的事務(wù)消息存儲(chǔ)在消息隊(duì)列中,從而削平高峰期的并發(fā)事務(wù)。需要注意的是,由于數(shù)據(jù)寫入消息隊(duì)列后立即返回給用戶,數(shù)據(jù)在后續(xù)的業(yè)務(wù)校驗(yàn)、寫入數(shù)據(jù)庫等操作可能失敗,因此在使用消息隊(duì)列進(jìn)行業(yè)務(wù)異步處理后,需要適當(dāng)修改業(yè)務(wù)流程進(jìn)行配合,如訂單提交后,訂單數(shù)據(jù)寫入消息隊(duì)列,不能立即返回用戶訂單提交成功,需要在消息隊(duì)列的訂單消費(fèi)者進(jìn)程真正處理完該訂單,甚至商品出庫后,再通過電子郵件通知用戶訂單成功,以免交易糾紛。

在這里關(guān)于數(shù)據(jù)庫相關(guān)的優(yōu)化做個(gè)總結(jié):

  • DB的讀優(yōu)化:緩存;
  • DB的寫優(yōu)化:消息隊(duì)列。

集群

在網(wǎng)站高并發(fā)訪問的場景下,使用負(fù)載均衡技術(shù)為一個(gè)應(yīng)用構(gòu)建一個(gè)由多臺(tái)服務(wù)器組成的服務(wù)器集群,將并發(fā)訪問請求分發(fā)到多臺(tái)服務(wù)器上處理,避免單一服務(wù)器因負(fù)載壓力過大而響應(yīng)緩慢,使用戶請求具有更好的響應(yīng)延遲特性。

利用負(fù)載均衡技術(shù)改善性能

代碼優(yōu)化

1. 多線程

從資源利用的角度看,使用多線程的原因主要有兩個(gè):IO 阻塞與多 CPU。

假設(shè)服務(wù)器上執(zhí)行的都是相同類型任務(wù),針對該類任務(wù)啟動(dòng)的線程數(shù)有個(gè)簡化估算公式:
啟動(dòng)線程數(shù)=[任務(wù)執(zhí)行時(shí)間/(任務(wù)執(zhí)行時(shí)間-IO等待時(shí)間)xCPU內(nèi)核數(shù)]

最佳啟動(dòng)線程數(shù)和 CPU 內(nèi)核數(shù)量呈正比,和 IO 阻塞時(shí)間成反比。如果任務(wù)都是 CPU 計(jì)算型任務(wù),那么線程數(shù)最多不超過 CPU 內(nèi)核數(shù)。

多線程編程需要關(guān)注線程安全問題,所有資源——對象、內(nèi)存、文件、數(shù)據(jù)庫,乃至另一個(gè)線程都可能被多線程訪問。

解決線程安全的主要手段:

  • 將對象設(shè)計(jì)為無狀態(tài)對象:無狀態(tài)對象是指對象本身不存儲(chǔ)狀態(tài)信息(對象無成員變量,或者成員變量也是無狀態(tài)對象),這樣多線程并發(fā)訪問時(shí)候就不會(huì)出現(xiàn)狀態(tài)不一致。Servlet 對象就設(shè)計(jì)為無狀態(tài)對象,可以被多線程并發(fā)調(diào)用處理用戶請求。

  • 使用局部對象:在方法內(nèi)部創(chuàng)建對象,這些對象會(huì)被每個(gè)進(jìn)入該方法的線程創(chuàng)建,除非程序有意識(shí)地將這些對象傳遞給其他線程,否則不會(huì)出現(xiàn)對象被多線程并發(fā)訪問的清形。

  • 并發(fā)訪問資源時(shí)使用鎖:多線程訪問資源的時(shí)候,通過鎖的方式使多線程并發(fā)操作轉(zhuǎn)化為順序操作,從而避免資源被并發(fā)修改。但是鎖導(dǎo)致線程同步順序執(zhí)行,可能會(huì)對系統(tǒng)性能產(chǎn)生嚴(yán)重影響。

2. 資源復(fù)用

系統(tǒng)運(yùn)行時(shí),要盡量減少那些開銷很大的系統(tǒng)資源的創(chuàng)建和銷毀,比如數(shù)據(jù)庫連接、網(wǎng)絡(luò)通信連接、線程、復(fù)雜對象等。從編程角度,資源復(fù)用主要有兩種模式:

  • 單例:由于目前 Web 開發(fā)中主要使用貧血模式,從 Service 到 Dao 都是些無狀態(tài)對象,無需重復(fù)創(chuàng)建,使用單例模式就自然而然了。Java 開發(fā)常用的對象容器 Spring 默認(rèn)構(gòu)造的對象都是單例(要注意的是 Spring 的單例是 Spring 容器管理的單例,而不是用單例模式構(gòu)造的單例)。

  • 對象池:通過復(fù)用對象實(shí)例,減少對象創(chuàng)建和資源消耗。對于數(shù)據(jù)庫連接對象,每次創(chuàng)建連接,數(shù)據(jù)庫服務(wù)端都需要?jiǎng)?chuàng)建專門的資源以應(yīng)對,因此頻繁創(chuàng)建關(guān)閉數(shù)據(jù)庫連接,對數(shù)據(jù)庫服務(wù)器而言是災(zāi)難性的,同時(shí)頻繁創(chuàng)建關(guān)閉連接也需要花費(fèi)較長的時(shí)間。因此在實(shí)踐中,應(yīng)用程序的數(shù)據(jù)庫連接基本都使用連接池(Connection Pool)的方式。數(shù)據(jù)庫連接對象創(chuàng)建好以后,將連接對象放入對象池容器中,應(yīng)用程序要連接的時(shí)候,就從對象池中獲取一個(gè)空閑的連接使用,使用完再將該對象歸還到對象池中即可,不需要?jiǎng)?chuàng)建新的連接。

對于每個(gè) Web 請求,應(yīng)用服務(wù)器都需要?jiǎng)?chuàng)建一個(gè)獨(dú)立的線程去處理。這方面,應(yīng)用服務(wù)器也采用線程池(Thread Pool)的方式。線程池本質(zhì)上也是對象池,池管理方式基本相同。

3. 數(shù)據(jù)結(jié)構(gòu)

Hash 算法必須滿足:

  • 沖突少
  • 相似字符串的 HashCode 不能太接近

這種情況下,一個(gè)可行的方案是對字符串取信息指紋,再對信息指紋求 HashCode。由于字符串微小的變化就可以引起信息指紋的巨大不同,因此可以獲得較好的隨機(jī)散列。

通過 MD5 計(jì)算 HashCode

4. 垃圾回收

垃圾回收可能會(huì)對系統(tǒng)性能產(chǎn)生巨大影響。理解垃圾回收有助于程序優(yōu)化和參數(shù)調(diào)優(yōu),以及編寫內(nèi)存安全的代碼。

以 JVM 為例,其內(nèi)存主要可劃分為堆(heap ) 和堆棧(stack)。堆棧用于存儲(chǔ)線程上下文信息,如方法參數(shù)、局部變量等。堆則是存儲(chǔ)對象的內(nèi)存空間,對象的創(chuàng)建和釋放、垃圾回收就在這里進(jìn)行。通過對對象生命周期的觀察,發(fā)現(xiàn)大部分對象的生命周期都極其短暫,這部分對象產(chǎn)生的垃圾應(yīng)該被更快地收集,以釋放內(nèi)存,這就是 JVM 分代垃圾回收。

JVM 分代垃圾回收機(jī)制

在 JVM 分代垃圾回收機(jī)制中,將應(yīng)用程序可用的堆空間分為年輕代(Young Generation)和年老代(Old Generation),又將年輕代分為 Eden 區(qū)(Eden Space)、From 區(qū)和 To 區(qū),新建對象總是在 Eden 區(qū)中被創(chuàng)建,當(dāng) Eden 區(qū)空間已滿,就觸發(fā)一次 Young GC,將還被使用的對象復(fù)制到 From 區(qū),這樣整個(gè) Eden 區(qū)都是未被使用的空間,可供繼續(xù)創(chuàng)建對象,當(dāng) Eden 區(qū)再次用完,再觸發(fā)一次 Young GC,將 Eden 區(qū)和 From 區(qū)還在被使用的對象復(fù)制到 To 區(qū),下一次 Young GC 則是將 Eden 區(qū)和 To 區(qū)還被使用的對象復(fù)制到 From 區(qū)。因此,經(jīng)過多次 Young GC,某些對象會(huì)在 From 區(qū)和 To 區(qū)多次復(fù)制,如果超過某個(gè)閥值對象還未被釋放,則將該對象復(fù)制到 Old Generation。如果 Old Generation 空間也已用完,就會(huì)觸發(fā) Full GC,即所謂的全量回收。全量回收會(huì)對系統(tǒng)性能產(chǎn)生較大影響,因此應(yīng)合理設(shè)置 Young Generation 和 Old Generation 大小,盡量減少 Full GC。事實(shí)上,某些 Web 應(yīng)用在整個(gè)運(yùn)行期間可以做到從不進(jìn)行 Full GC。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 從三月份找實(shí)習(xí)到現(xiàn)在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂視家的研發(fā)崗...
    時(shí)芥藍(lán)閱讀 42,360評論 11 349
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,886評論 18 139
  • 在服務(wù)器端程序開發(fā)領(lǐng)域,性能問題一直是備受關(guān)注的重點(diǎn)。業(yè)界有大量的框架、組件、類庫都是以性能為賣點(diǎn)而廣為人知。然而...
    dreamer_lk閱讀 1,032評論 0 17
  • 我查了很多資料,問了很多朋友,發(fā)現(xiàn)一個(gè)很奇怪的現(xiàn)象。現(xiàn)今在我們國家很多地方工作崗位外地戶籍的人很多,都已經(jīng)超過了一...
    秋月醉閱讀 398評論 0 5
  • 微笑是一種態(tài)度,一種灑脫人生的態(tài)度,世界是客觀唯物的,有許多的事情我能不能左右,但是人生的態(tài)度我們是可以選擇的。世...
    chujianna閱讀 110評論 0 1