爲了減小阻塞時間,加快響應速度,把無需返回結果的操做變成異步任務,用線程池來執行,這是提升性能的一種手段。
你可能要驚訝了,這麼作不對嗎? html
首先,咱們把異步任務分爲兩種:算法
顯然大多數時候都是第一種。那麼當你把任務丟給線程池,你知道它完成了沒有嗎? shell
若是服務器宕機、升級或重啓,那些還沒有完成或還在排隊的任務就丟了。後果是,用戶在促銷活動中搶到的優惠券,沒有發給用戶。更嚴重的後果是,一個訂單在送往倉庫系統的途中消失了。 數據庫
我推薦的作法是把任務投遞到消息中間件,讓它分發給消息消費者來執行(消費者多是發送者自身)。緩存
更重要的是,消息中間件有持久化功能,即便宕機也不丟消息,並且消息中間件能夠長期不升級、不重啓。消息中間件的缺點是,對失敗狀況的處理難以定製化——你可能想定製重試間隔、重試次數等細節。 性能優化
換個角度來看,要解決丟任務的問題,你不必定要用消息中間件。你能夠在應用代碼中把任務和完成狀態保存到數據庫中,用線程池執行,在完成後更新狀態。這是否是很像做業調度(例如Quartz)呢?是的。 服務器
然而,有些任務確實是無關緊要的,例如『刷新一個不重要的緩存』的任務,那麼就隨便丟到線程池吧。網絡
咱們知道,性能瓶頸一般都是I/O,尤爲是數據庫的I/O。所以咱們用了緩存,速度蹬的一下竄上來了——不必定哦。 運維
用緩存把I/O變成了內存計算,最大瓶頸消除,速度上升一個數量級。在這個數量級,一些本來不重要的因素開始產生影響了。 異步
日誌是一種I/O啊,雖然順序寫磁盤很快,但仍是比內存計算要慢啊。更糟的是,一個線程寫日誌時,另外一個線程必須等它寫完才能接着寫,不然日誌會亂,當日志量較大時,就stop the world了。
因此當你的系統性能高到必定程度,就要對日誌作性能優化了(有過提升3倍QPS的案例),兩個常見辦法:
今天少打日誌,明天排查bug就想哭。因此主要靠異步模式。
Logback能夠經過配置(網上搜一搜),在RollingFileAppender上套一個AsyncAppender,實際上就是加了個緩衝隊列,變成了批量寫磁盤。缺點是沒法看到最新日誌(還沒寫磁盤)。queueSize默認是256,即便設爲16,也有明顯的性能提高,還緩和了不能及時看到最新日誌的問題。
Log4j 2的異步模式有更深刻的優化,是否選用,以測試數據爲準。
網絡忘記設超時,系統隨時可能掛。
每個網絡操做,都記得設置超時時間,超過這個時間就放棄。訪問分佈式緩存也如此。
若是不設超時,可能會等到天荒地老。網絡,就是這麼不肯定。
一個命中率低下的緩存,不如沒有。雖然LRU算法很好用,但未必沒有例外狀況。頻繁做廢的數據、大致積數據均可能是負擔。
統計緩存命中率的實現辦法能夠是內存裏計數,按期寫到數據庫或文件;也能夠是把命中狀況打到日誌裏,往後彙總統計。也可能有更精巧的實現。
當你的系統進入精耕細做時代,這個問題必然擺上案頭。
緩存必定快嗎?我真的見過不快的。分佈式緩存要經由網絡,網絡抖一抖,緩存抖三抖;還依賴運維,運維抖一抖,緩存抖三抖。此事之微妙,不可不察也。
留個心,設個超時,記個響應時間。一個簡單辦法是,當響應時間過長時,打一行日誌,正常狀況不打日誌。這樣既留了記錄,又不產生過多日誌。
中央分佈式緩存是由緩存中間件組成的集羣,一致性較好,緩存的很快會同步到全部副本。可是畢竟要經由網絡,延遲爲亞毫秒級,並且負載彙集到中間件,可能因網絡擁塞或機器負載高而出現性能波動。
爲了進一步提升部分業務的性能和穩定性,可能要輔以本地緩存。
緩存要有過時策略,若是使用了Guava Cache,能夠選擇expireAfterAccess(不經常使用)、expireAfterWrite或refreshAfterWrite策略:
緩存的更新方式也影響着性能,@左耳朵耗子 的緩存更新的套路很好地介紹了三種套路,如今我來對比一下:
有的系統步入精耕細做時代後,想避免一種狀況——緩存做廢時,不少應用實例同時訪問數據庫,加劇負載,並且浪費資源。因而有了給緩存加鎖的方案。
簡單版是每一個實例內部設鎖,某條數據只許一個線程去數據庫取。複雜版是把鎖設在分佈式緩存中,某條數據只許一個實例去數據庫取,而後放入緩存讓其餘實例用。
複雜版的想法是好的,但注意,鎖要設置超時(還記得我上文說的嗎),不然萬一持有鎖的實例發生問題,就全體耽誤了。即便設了超時,也可能全體實例一直等待超時,浪費時間。因此這種方案不必定好,需以測試數據爲準。
不少公司常常加班,實際上效率低下、也不持久,只能複製既有經驗,靠不停換人來維持,其後果就是:需求混亂、bug巨多、創新乏力。
要把技術搞好,須要有條不紊,遇變不亂,持久輸出。疲於奔命的模式,作很差大型服務端開發,也難以作好各類領域的開發。