自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

馬蜂窩推薦系統(tǒng)容災(zāi)緩存服務(wù)的設(shè)計(jì)與實(shí)現(xiàn)

存儲(chǔ) 存儲(chǔ)軟件
數(shù)據(jù)庫(kù)突然斷開(kāi)連接、第三方接口遲遲不返回結(jié)果、高峰期網(wǎng)絡(luò)發(fā)生抖動(dòng)...... 當(dāng)程序突發(fā)異常時(shí),我們的應(yīng)用可以告訴調(diào)用方或者用戶「對(duì)不起,服務(wù)器出了點(diǎn)問(wèn)題」;或者找到更好的方式,達(dá)到提升用戶體驗(yàn)的目的。

 數(shù)據(jù)庫(kù)突然斷開(kāi)連接、第三方接口遲遲不返回結(jié)果、高峰期網(wǎng)絡(luò)發(fā)生抖動(dòng)...... 當(dāng)程序突發(fā)異常時(shí),我們的應(yīng)用可以告訴調(diào)用方或者用戶「對(duì)不起,服務(wù)器出了點(diǎn)問(wèn)題」;或者找到更好的方式,達(dá)到提升用戶體驗(yàn)的目的。

背景

用戶在馬蜂窩 App 上「刷刷刷」時(shí),推薦系統(tǒng)需要持續(xù)給用戶推薦可能感興趣的內(nèi)容,主要分為根據(jù)用戶特性和業(yè)務(wù)場(chǎng)景,召回根據(jù)各種機(jī)器學(xué)習(xí)算法計(jì)算過(guò)的內(nèi)容,然后對(duì)這些內(nèi)容進(jìn)行排序后返回給前端這幾個(gè)步驟。

推薦的過(guò)程涉及到 MySQL 和 Redis 查詢、REST 服務(wù)調(diào)用、數(shù)據(jù)處理等一系列操作。對(duì)于推薦系統(tǒng)來(lái)說(shuō),對(duì)時(shí)延的要求比較高。馬蜂窩推薦系統(tǒng)對(duì)于請(qǐng)求的平均處理時(shí)延要求在 10ms 級(jí)別,時(shí)延的 99 線保持在 1s 以內(nèi)。

 

當(dāng)外部或者內(nèi)部系統(tǒng)出現(xiàn)異常時(shí),推薦系統(tǒng)就無(wú)法在限定時(shí)間內(nèi)返回?cái)?shù)據(jù)給到前端,導(dǎo)致用戶刷不出來(lái)新內(nèi)容,影響用戶體驗(yàn)。

 

所以我們希望通過(guò)設(shè)計(jì)一套容災(zāi)緩存服務(wù),實(shí)現(xiàn)在應(yīng)用本身或者依賴的服務(wù)發(fā)生超時(shí)等異常情況時(shí),可以返回緩存數(shù)據(jù)給到前端和用戶,來(lái)減少空結(jié)果數(shù)量,并且保證這些數(shù)據(jù)盡可能是用戶感興趣的。

設(shè)計(jì)與實(shí)現(xiàn)

設(shè)計(jì)思路和技術(shù)選型

不僅僅是推薦系統(tǒng),緩存技術(shù)在很多系統(tǒng)中已經(jīng)被廣泛應(yīng)用,小到 JVM 中的常用整型數(shù),大到網(wǎng)站用戶的 session 狀態(tài)。緩存的目的不盡相同,有些是為了提高效率,有些是為了備份;緩存的要求也高低不一,有些要求一致性,有些則沒(méi)有要求。我們需要根據(jù)業(yè)務(wù)場(chǎng)景選擇合適的緩存方案。

結(jié)合到我們上面提到的業(yè)務(wù)場(chǎng)景和需求,我們采用了基于 OHC 堆外緩存和 SpringBoot 的方案,實(shí)現(xiàn)在現(xiàn)有推薦系統(tǒng)中增加本地容災(zāi)緩存系統(tǒng)。主要是考慮到以下幾點(diǎn)因素:

1. 避免影響線上服務(wù),將業(yè)務(wù)邏輯和緩存邏輯隔離

為了不影響線上服務(wù),我們將緩存系統(tǒng)封裝為一個(gè) CacheService,配置在現(xiàn)有流程的末端,并提供讀、寫(xiě)的 API 給外部調(diào)用,將業(yè)務(wù)邏輯和緩存邏輯隔離。

2. 異步寫(xiě)入緩存,提高性能

讀、寫(xiě)緩存都會(huì)帶來(lái)時(shí)間消耗,特別是寫(xiě)入緩存。為了提高性能,我們考慮將寫(xiě)入緩存做成異步的方式。這部分使用的是 JDK 提供的線程池 ThreadPoolExecutor 來(lái)實(shí)現(xiàn),主線程只需要提交任務(wù)到線程池,由線程池里的 Worker 線程實(shí)現(xiàn)寫(xiě)入緩存。

3. 本地緩存,提高訪問(wèn)速度

在推薦系統(tǒng)中,給用戶推薦的內(nèi)容應(yīng)該是千人千面的,甚至同一位用戶每次刷新看到的內(nèi)容都可能不同,這就不要求緩存具有強(qiáng)一致性。因此,我們只需要進(jìn)行本地緩存,而不需要采用分布式的方式。這里使用到的是開(kāi)源緩存工具 OHC,緩存的數(shù)據(jù)來(lái)源于成功處理過(guò)的請(qǐng)求。

4. 備份緩存實(shí)例,保證可用性

為了保證緩存的可用性,我們不僅在內(nèi)存中進(jìn)行緩存,還定時(shí)備份到文件系統(tǒng)中,從而保證在可以應(yīng)用啟動(dòng)時(shí)從文件系統(tǒng)加載到內(nèi)存。具體可以使用 SpringBoot 提供的定時(shí)任務(wù)、ApplicationRunner 來(lái)實(shí)現(xiàn)。

整體架構(gòu)

我們保持了推薦系統(tǒng)的現(xiàn)有邏輯,并在現(xiàn)有流程的末端,配置了 CacheModule 和 CacheService,負(fù)責(zé)所有和緩存相關(guān)的邏輯。

 

其中,CacheService 是緩存的具體實(shí)現(xiàn),提供讀寫(xiě)接口;CacheModule 對(duì)本次請(qǐng)求的數(shù)據(jù)進(jìn)行處理,并決定是否需要調(diào)用 CacheService 對(duì)緩存進(jìn)行操作。

模塊解讀

1. CacheModule

在完成推薦系統(tǒng)的原有流程處理之后,CacheModule 會(huì)對(duì)得到的響應(yīng)報(bào)文進(jìn)行判斷,比如是否拋出了異常,響應(yīng)是否為空等,然后決定是否讀取緩存或者提交緩存任務(wù)。

CacheModule 的工作流程如圖所示,其中橘黃色部分代表對(duì) CacheService 的調(diào)用:

 

  • 提交緩存任務(wù)。如果該次請(qǐng)求沒(méi)有拋出異常,并且響應(yīng)結(jié)果也不為空,則會(huì)提交一個(gè)緩存任務(wù)到 CacheService。任務(wù)的 key 值為對(duì)應(yīng)的業(yè)務(wù)場(chǎng)景,value 為本次響應(yīng)計(jì)算得到的內(nèi)容。提交的動(dòng)作是非阻塞的,對(duì)接口的耗時(shí)影響很小。
  • 讀取緩存數(shù)據(jù)。當(dāng)應(yīng)用本身或者依賴應(yīng)用拋出異常時(shí),系統(tǒng)會(huì)根據(jù)業(yè)務(wù)場(chǎng)景的 key 值從 CacheService 中讀取緩存并返回給調(diào)用方。當(dāng)出現(xiàn)用戶本身已經(jīng)刷完所有可用數(shù)據(jù)的情況時(shí),就不需要讀取緩存,而是將請(qǐng)求的數(shù)據(jù)及時(shí)反饋給用戶。

2. CacheService

在緩存的具體實(shí)現(xiàn)上,CacheService 使用到了從 Apache Cassandra 項(xiàng)目中獨(dú)立出來(lái)的 OHC。另外因?yàn)槲覀冋麄€(gè)應(yīng)用是基于 SpringBoot 的,也用到了 SpringBoot 提供的各種功能。

上文說(shuō)到對(duì)緩存沒(méi)有強(qiáng)一致性的要求,所以我們采用的是本地緩存而非分布式緩存,并且抽象出一個(gè) CacheService 類負(fù)責(zé)對(duì)本地緩存進(jìn)行維護(hù)。

(1) 數(shù)據(jù)格式

推薦系統(tǒng)返回?cái)?shù)據(jù)時(shí),根據(jù)業(yè)務(wù)場(chǎng)景和用戶特征設(shè)定以「屏」為單位返回?cái)?shù)據(jù),每屏可以包含多個(gè)內(nèi)容項(xiàng),所以采取 key-set 的數(shù)據(jù)格式:key 值為業(yè)務(wù)場(chǎng)景,比如首頁(yè)的「視頻」頻道;緩存內(nèi)容則為「屏」的集合。

(2) 存儲(chǔ)位置

對(duì)于 Java 應(yīng)用,緩存可以存放在內(nèi)存中或者硬盤文件中。而內(nèi)存空間又分為 heap(堆內(nèi)存)和 off-heap(堆外內(nèi)存)。我們對(duì)這幾種方式進(jìn)行了對(duì)比:

 

為了保證較快的讀寫(xiě)速度,避免緩存 GC 影響線上服務(wù),所以選擇 off-heap 作為緩存空間。OHC 最早包含在 Apache Cassandra 項(xiàng)目中,之后獨(dú)立出來(lái),成為了基于 off-heap 的開(kāi)源緩存工具。它既可以維護(hù)大量的 off-heap 內(nèi)存空間,同時(shí)也使用于低開(kāi)銷的小型緩存實(shí)體。所以我們使用 OHC 作為 off-heap 的緩存實(shí)現(xiàn)。

(3) 文件備份

在應(yīng)用重啟時(shí),off-heap 中的緩存為空。為了盡快載入緩存,我們使用 SpringBoot 的 Scheduling Tasks 功能,定期將緩存從 off-heap 備份到文件系統(tǒng);通過(guò)繼承 SpringBoot 的 ApplicationRunner 監(jiān)聽(tīng)?wèi)?yīng)用啟動(dòng)的過(guò)程,啟動(dòng)完成后將硬盤中的備份文件加載到 off-heap,保證緩存數(shù)據(jù)的可用性。

CacheService 維護(hù)一個(gè)任務(wù)隊(duì)列,隊(duì)列中保存著 CacheModule 通過(guò)非阻塞的方式提交的緩存任務(wù),由 CacheService 決定是否要執(zhí)行這些緩存任務(wù)。

(4) 對(duì) CacheModule 提供的 API

讀取緩存時(shí),傳入 key 值,緩存模塊隨機(jī)從 set 中讀取數(shù)據(jù)返回。

寫(xiě)入緩存時(shí),將 key 和 value 封裝為一個(gè)任務(wù),提交到任務(wù)隊(duì)列,由任務(wù)隊(duì)列負(fù)責(zé)異步寫(xiě)入緩存。

(5) 任務(wù)隊(duì)列與異步寫(xiě)入

這里我們使用了 JDK 中的線程池來(lái)實(shí)現(xiàn)。在構(gòu)造線程池時(shí),使用 LinkedBlockingQueue 作為任務(wù)隊(duì)列,可以實(shí)現(xiàn)快速增刪元素;因?yàn)閼?yīng)用的 QPS 在 100 以內(nèi),所以工作線程數(shù)目固定為 1;隊(duì)列寫(xiě)滿之后,則執(zhí)行 DiscardPolicy,放棄插入隊(duì)列。

(6)緩存數(shù)量控制

如果緩存占用內(nèi)存空間過(guò)大,會(huì)影響線上應(yīng)用,我們可以采用為不同的業(yè)務(wù)場(chǎng)景配置最大緩存數(shù)量來(lái)控制緩存數(shù)量。沒(méi)有達(dá)到配置值時(shí),將成功處理過(guò)的數(shù)據(jù)寫(xiě)入緩存;達(dá)到配置值時(shí)可以隨機(jī)抽樣覆蓋原有緩存項(xiàng),來(lái)保證緩存的實(shí)時(shí)性。

綜合考慮以上各個(gè)方面,CacheService 的設(shè)計(jì)如下:

 

線上表現(xiàn)

為了驗(yàn)證容災(zāi)緩存的效果,我們?cè)诿芯彺鏁r(shí)進(jìn)行了埋點(diǎn),并通過(guò) Kibana 查看每小時(shí)緩存的命中數(shù)量。如圖所示,在 18:00 到 19:00 系統(tǒng)存在一定的超時(shí),而這段時(shí)間由于緩存服務(wù)發(fā)揮了作用,使系統(tǒng)的可用性得到提升。

 

我們還對(duì) OHC 的讀取和寫(xiě)入速度進(jìn)行了監(jiān)控。寫(xiě)入緩存的時(shí)延在毫秒級(jí)別,并且是異步寫(xiě)入;讀取緩存的時(shí)延在微秒級(jí)別?;緵](méi)有給系統(tǒng)增加額外的時(shí)間消耗。

 

踩過(guò)的坑

在將緩存寫(xiě)入 OHC 之前,需要進(jìn)行序列化,我們使用了開(kāi)源的 kryo 作為序列化工具。之前在使用 kyro 時(shí),發(fā)現(xiàn)對(duì)于沒(méi)有實(shí)現(xiàn) Serializable 的類,反序列化時(shí)可能失敗,比如使用 List#subList 方法返回的內(nèi)部類 java.util.ArrayList$SubList。這里可以手動(dòng)注冊(cè) Serializer 來(lái)解決這個(gè)問(wèn)題,在 Github 上開(kāi)源的 kryo-serializers 倉(cāng)庫(kù)提供了各種類型的 serializers。

另外一點(diǎn),需要注意根據(jù)具體使用場(chǎng)景,來(lái)配置 OHC 中的 capacity 和 maxEntrySize。如果配置的值太小的話,會(huì)導(dǎo)致寫(xiě)入緩存失敗??梢栽谏暇€之前測(cè)算緩存的空間占用,合理設(shè)置整個(gè)緩存空間的大小和每個(gè)緩存 entry 的大小。

優(yōu)化方向

基于 SpringBoot 和 OHC,我們?cè)诂F(xiàn)有的推薦系統(tǒng)中增加了一個(gè)本地容災(zāi)緩存系統(tǒng),當(dāng)依賴服務(wù)或者應(yīng)用本身突發(fā)異常時(shí)可以返回緩存的數(shù)據(jù)。

該緩存系統(tǒng)還存在一些不足,我們近期會(huì)針對(duì)以下幾點(diǎn)進(jìn)行重點(diǎn)優(yōu)化:

  • 緩存數(shù)目寫(xiě)滿之后,目前應(yīng)用會(huì)隨機(jī)覆寫(xiě)已經(jīng)存在的緩存。未來(lái)可以進(jìn)行優(yōu)化,將最老的緩存項(xiàng)替換。
  • 在某些場(chǎng)景下緩存的粒度不夠精細(xì),比如目的地頁(yè)推薦共用一個(gè)緩存的 key 值。未來(lái)可以根據(jù)目的地的 ID,為每個(gè)目的地配置一份緩存。
  • 現(xiàn)在推薦系統(tǒng)還有部分配置依賴于 MySQL,未來(lái)會(huì)考慮將在本地進(jìn)行文件緩存。

[參考資料]

1. Java Caching Benchmarks 2016 - Part 1

https://cruftex.net/2016/03/16/Java-Caching-Benchmarks-2016-Part-1.html

2. On Heap vs Off Heap Memory Usage

https://dzone.com/articles/heap-vs-heap-memory-usage

3. OHC - An off-heap-cache

https://github.com/snazy/ohc

4. kryo-serializers

https://github.com/magro/kryo-serializers

5. scheduling-tasks

https://spring.io/guides/gs/scheduling-tasks/

本文作者:孫興斌,馬蜂窩推薦和搜索后端研發(fā)工程師。

【本文是51CTO專欄作者馬蜂窩技術(shù)的原創(chuàng)文章,作者微信公眾號(hào)馬蜂窩技術(shù)(ID:mfwtech)】

 

戳這里,看該作者更多好文

責(zé)任編輯:武曉燕 來(lái)源: 51CTO專欄
相關(guān)推薦

2019-06-11 12:19:10

ABTest分流系統(tǒng)

2019-02-19 15:20:12

消息總線架構(gòu)異步

2019-02-27 15:24:54

馬蜂窩游搶單系統(tǒng)

2019-04-26 15:16:02

馬蜂窩火車票系統(tǒng)

2022-06-20 09:00:00

深度學(xué)習(xí)人工智能研究

2019-03-25 15:14:19

Flutter馬蜂窩開(kāi)發(fā)

2019-02-18 15:23:21

馬蜂窩MESLambda

2020-02-21 16:20:37

系統(tǒng)驅(qū)動(dòng)項(xiàng)目管理

2020-03-22 15:49:27

Kafka馬蜂窩大數(shù)據(jù)平臺(tái)

2020-01-03 09:53:36

Kafka集群優(yōu)化

2019-04-12 14:22:40

馬蜂窩機(jī)票訂單

2018-10-29 12:27:20

2019-03-29 08:21:51

馬蜂窩Golang并發(fā)代理

2019-12-17 14:59:27

數(shù)據(jù)中臺(tái)數(shù)據(jù)倉(cāng)庫(kù)馬蜂窩

2018-10-26 16:00:39

程序員爬蟲(chóng)馬蜂窩

2023-04-27 10:40:10

vivo容災(zāi)服務(wù)器

2020-03-16 12:39:47

容災(zāi)備份規(guī)劃

2018-08-15 08:52:49

爬蟲(chóng)出行城市數(shù)據(jù)

2024-04-02 08:45:08

ChatGPTAI會(huì)議人工智能

2017-10-26 09:22:26

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)