緩存熱點,緩存穿透,終極解決方案看過來

背景

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

很多公司的緩存都是基於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的項目地址

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

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

//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的項目地址://gitee.com/openbeast/hotlink

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

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

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

Hotlink如何使用

第一步

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

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

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

worker下載地址:

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

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

內網IP版本:

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

Dashboard:

//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=//xxx.xxx.xxx.xxx:2379

第四步

在你的接口裡加入標籤@Hotlink

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

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

比如:

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

那麼當某一個SKU001成為熱點時,那麼傳入參數SKU001會自動代理從JVM里取到數據,而SKU002則繼續走RPC調用。

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

使用Hotlink需要注意的事項

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

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

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

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

關於我

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