快取熱點,快取穿透,終極解決方案看過來

鉑賽東發表於2021-08-27

背景

前不久,因為公司業務需要,需要解決在大促場景下後端業務的熱點快取問題,所以研究了下快取熱點解決方案。

很多公司的快取都是基於redis來做的,redis的效能其實已經足以能應付大部分的場景,但是對於大促期間或者活動搶購期間的某個爆品,可能會出現在幾秒時間內流入大量的流量,由於某個爆品的資料在redis cluster場景下會按照hash規則被存放在某個redis分片上,那麼這幾秒的流量都會壓到這個redis分片,從而在瞬間會導致這個redis分片的癱瘓,也會影響後續的redis請求的阻塞。

還有個場景,就是公司並不是所有的服務端邏輯都有快取。在流量起來的時候,這些熱key還是會壓到資料庫層面。導致壓力。

解決方案

一般常見的解決方案就是增加二級快取,對於熱點資料寫到jvm裡一份。設定過期時間。但是什麼時候設定,熱點如何探測,規則如何設定,過期時間設定多少。甚至於如何快速落地,這都是需要研究的問題。

我們希望有一個統一的方案來解決這些問題。

我們發現了Hotkey這款開源框架。

Hotkey源於京東,hotkey能自動地對任意突發性的無法預知的熱點資料,按照配置的規則進行毫秒級別的探測,探測到的熱資料會推送到所有的服務端JVM中,大幅減輕對後端資料層的衝擊。這些熱資料在整個微服務叢集會保持一致性,當熱點消失的時候,自動從jvm中進行移除。

Hotkey的特效能很好的實現我們的目標。並且京東內部也用Hotkey實戰了618大促,穩定性有所保障。

Hotkey的架構圖(以下圖引用自Hotkey在Gitee的主頁)

Hotkey整個架構共分為以下幾個部分:

worker:負責採集上報資訊,根據規則計算出熱點資訊,規則來自於etcd。熱點資訊推送到client裡

client: 每個client連線etcd,獲取每個worker的ip和埠,和worker保持長連結,接受worker的熱點資訊推送

etcd:分散式的協調者,接受每個worker的心跳上報,並把worker的連線資訊推送給client。監聽規則的改變,推送給worker

dashboard:ui介面,檢視例項以及worker的狀態,檢視以及修改規則資料。規則存到mysql,同時由etcd推送給worker

下面給出hotkey的專案地址

https://gitee.com/jd-platform-opensource/hotkey

關於Hotkey的介紹和如何搭建,大家可以看這篇文章來了解,這裡就不多贅述。

https://mp.weixin.qq.com/s/xOzEj5HtCeh_ezHDPHw6Jw

碰到的問題

我們在搭建hotkey環境和落地實施中,碰到2個問題:

  • Hotkey雖然開源,但是相關client jar包並未上傳中央倉庫,dashboard和worker啟動包也並未提供下載。需要下載原始碼進行編譯,編譯過程中也碰到一些包依賴的問題。
  • Hotkey的client jar只提供了api級別的方法供程式使用,如果要落地到業務專案中,需要大規模的修改程式碼才能實施。

我們更希望提供一種侵入更少的方式,在RPC以及介面的層面進行代理包裝。使用者無論使用什麼RPC框架,只是在相關介面上打上標註,而無需動業務的任何程式碼。就可以在這個介面層面進行檢測熱點。如果該介面的某個引數為熱點的話,就自動進行代理,走jvm的熱點資料,等熱點消除後,依舊走原來的呼叫。

如果你覺得上述的描述過於難以理解的話,那麼直白點說就是:

比如某個活動大促期間有個商品S001進行搶購,有大量的流量進入了商品詳情頁面。這個商品詳情RPC方式呼叫了商品服務的以下介面方法獲取商品資訊:

public interface ProductService{
  SkuInfo getSkuInfo(String skuCode);
}

那麼我們希望只在這個介面上打上標註。就可以適配Hotkey框架進行探測熱點,當商品S001被大量請求時,S001這個商品就可以成為熱點,這時getSkuInfo這個介面就會被自動代理,從而只從Jvm中獲取資料,而不會真正走RPC呼叫。等熱點消除後,這個介面依舊呼叫RPC獲取資料。

這樣的方式無疑侵入性更小,更容易使Hotkey框架落地。

Hotlink客戶端

為此我們基於Hotkey client研發了Hotlink客戶端框架,該客戶端框架能讓Hotkey更完美的落地,增強了Hotkey客戶端的能力。

Hotlink的專案地址:https://gitee.com/openbeast/hotlink

該客戶端框架有以下特點:

  • 業務接入簡單,只需要一個標註,1分鐘就能使你的RPC介面接入熱點探測框架
  • 啟動時動態掃描所有Hotlink標註的介面,建立動態代理
  • 基於動態代理去對介面做增強,理論上只要有介面,就支援任何RPC框架
  • 本地方法只要有介面,也能使用熱點探測

結合Hotkey的架構圖,Hotlink在整個架構圖中的位置如下圖:

Hotlink如何使用

第一步

按照Hotkey的部署要求,搭建好worker和dashboard。具體方式請參照:

https://gitee.com/jd-platform-opensource/hotkey

同時為了方便大家搭建,我把編譯好的worker和dashboard包也進行了上傳

worker下載地址:

公網IP版本(適合除錯用,本地能連上worker):

https://gitee.com/openbeast/hotlink/attach_files/813746/download/worker-0.0.4-SNAPSHOT-public.jar

內網IP版本:

https://gitee.com/openbeast/hotlink/attach_files/813747/download/worker-0.0.4-SNAPSHOT.jar

Dashboard:

https://gitee.com/openbeast/hotlink/attach_files/813749/download/dashboard-0.0.2-SNAPSHOT.jar

第二步

本地業務專案依賴jar包(此jar包並未上傳到中央倉庫,需要大家自己deploy到自己公司的私庫)

<dependency>
  <groupId>com.thebeastshop</groupId>
  <artifactId>hotlink-spring-boot-starter</artifactId>
  <version>1.0.12</version>
</dependency>

hotlink需要的fastjson和groovy版本有點要求,如果你專案中的這2個包版本過低又同時覆蓋了hotlink的傳遞依賴包時,需要額外指定版本:

<fastjson.version>1.2.70</fastjson.version>
<guava.version>29.0-jre</guava.version>

第三步

本地springboot配置檔案里加入引數

#此app-name不配置的話,會優先讀取spring.application.name屬性
hotlink.app-name=test
#etcd地址和埠
hotlink.etcd-url=http://xxx.xxx.xxx.xxx:2379

第四步

在你的介面裡加入標籤@Hotlink

在介面上加:介面裡所有的方法都會自動探測熱點

在方法上加:只有這個方法會自動探測熱點

比如:

public interface ProductService{
  @Hotlink
  SkuInfo getSkuInfo(String skuCode);
}

那麼當某一個SKU001成為熱點時,那麼傳入引數SKU001會自動代理從JVM裡取到資料,而SKU002則繼續走RPC呼叫。

這樣就完成了所有的配置。啟動皆可。

使用Hotlink需要注意的事項

由於Hotlink的實現是用動態代理來實現,只要滿足這兩個條件,即可在啟動時會掃描器掃到:

  • 介面層面上標註@Hotlink
  • 相關實現會被注入Spring上下文中

在標註介面的時候,儘量標註在一定時間範圍內是冪等的介面。比如會員查詢,sku資訊查詢,相關活動資訊的查詢,這些資訊在一定時間範圍內不會頻繁變動,那麼就適合做熱點探測。

非冪等性的介面,即便是相同引數,每次返回也會不一樣。那就不建議做熱點探測。比如下單,庫存的查詢,餘額的查詢。這樣的介面如果一旦被升級成熱點。那會影響業務介面的正確性和後續邏輯的判斷錯誤。

關於我

我是一個開源作者,也是一名內容創作者。「元人部落」是一個堅持做原創的技術科技分享號,會一直分享原創的技術文章,陪你一起成長。

相關文章