天天看點

【王喆-推薦系統】線上服務篇-(task2)用Redis存儲特征

學習總結

  • 本次task學習推薦系統的存儲子產品(遵循“分級存儲”原則,在開銷和性能中平衡;具體而言:把越頻繁通路的資料放到越快的資料庫甚至緩存中,把海量的全量資料放到廉價但是查詢速度較慢的資料庫中)和對Sparrow Recsys中的redis實踐。其實和計算機的儲存設備一樣(分為寄存器、Cache、記憶體、SSD等金字塔形)。我們麻雀推薦系統的存儲結構如下:
【王喆-推薦系統】線上服務篇-(task2)用Redis存儲特征
  • 使用記憶體資料庫redis(兩大特點:key-value形式存儲;純記憶體資料庫)。在具體的特征存取過程中,要熟悉利用 redis 執行​

    ​SET​

    ​​,​

    ​GET​

    ​ 等 Redis 常用操作的方法。
  • 搭建一套完整的推薦服務,有3個大問題:用 Jetty Server 搭建推薦伺服器問題、用 Redis 解決特征存儲的問題,還有下個task的線上服務召回層的設計。
【王喆-推薦系統】線上服務篇-(task2)用Redis存儲特征

文章目錄

  • ​​學習總結​​
  • ​​一、推薦系統的存儲子產品​​
  • ​​二、SparrowRecsys 的存儲系統​​
  • ​​三、Redis 基礎知識​​
  • ​​3.1 所有的資料都以 Key-value 的形式存儲。​​
  • ​​3.2 所有的資料都存儲在記憶體中​​
  • ​​四、Sparrow Recsys中的Redis實踐​​
  • ​​4.1 安裝 Redis​​
  • ​​4.2 運作離線程式,通過 jedis 用戶端寫入 Redis​​
  • ​​4.3 在推薦伺服器中把 Redis 資料讀取出來​​
  • ​​五、資料庫分類​​
  • ​​5.1 SQL​​
  • ​​5.2 NoSQL​​
  • ​​(1)基于文檔(基于查詢的資料)​​
  • ​​(2)圖形存儲​​
  • ​​(3)Key-value 存儲​​
  • ​​(4)柱狀資料庫(不斷增加的資料)​​
  • ​​六、作業​​
  • ​​七、課後答疑​​
  • ​​Reference​​

一、推薦系統的存儲子產品

大資料平台的資料處理是離線的,而上次task我們搭建Jetty推薦伺服器是線上環境,本次task的内容是讓離線的特征資料導入到線上供伺服器使用,所使用的redis資料庫存儲特征。

【王喆-推薦系統】線上服務篇-(task2)用Redis存儲特征

圖4 推薦系統技術架構示意圖,源自王喆課程

Netflix 采用了非常經典的 Offline、Nearline、Online 三層推薦系統架構。架構圖中最核心的位置就是在圖中用紅框标出的部分,它們是三個資料庫 Cassandra、MySQL 和 EVcache,用來存儲特征和模型參數。

【王喆-推薦系統】線上服務篇-(task2)用Redis存儲特征

圖1 Netflix推薦系統架構中的特征與模型資料庫

并非簡單的将推薦特征和模型用資料庫存儲起來,然後給推薦伺服器寫幾個SQL讓它取出資料就可以。之是以Netflix搞這三個資料庫是因為:

(1)速度要快:由于線上的 QPS 壓力巨大,每次有推薦請求到來,推薦伺服器都需要把相關的特征取出。這就要求推薦伺服器一定要“快”。

(2)資料很大:多使用者和物品特征所需的存儲量。

幾乎所有的工業級推薦系統都會把特征的存儲做成分級存儲,把越頻繁通路的資料放到越快的資料庫甚至緩存中,把海量的全量資料放到便宜但是查詢速度較慢的資料庫中。

舉栗子:如果你把特征資料放到基于 HDFS 的 HBase 中,雖然你可以輕松放下所有的特征資料,但要讓你的推薦伺服器直接通路 HBase 進行特征查詢,等到查詢完成,這邊使用者的請求早就逾時中斷了,而 Netflix 的三個資料庫正好滿足了這樣分級存儲的需求。
【王喆-推薦系統】線上服務篇-(task2)用Redis存儲特征

圖2 分級存儲的設計

比如說,Netflix 使用的 Cassandra,它作為流行的 NoSQL 資料庫,具備大資料存儲的能力,

(1)但為支援推薦伺服器高 QPS 的需求,還需要把最常用的特征和模型參數存入 EVcache 這類記憶體資料庫。

(2)而對于更常用的資料,我們可以把它們存儲在 Guava Cache 等伺服器内部緩存,甚至是伺服器的記憶體中。

(3)而對于 MySQL 來說,由于它是一個強一緻性的關系型資料庫,一般存儲的是比較關鍵的要求強一緻性的資訊,比如物品是否可以被推薦這種控制類的資訊,物品分類的層級關系,使用者的注冊資訊等等。這類資訊一般是由推薦伺服器進行階段性的拉取,或者利用分級緩存進行階段性的更新,避免因為過于頻繁的通路壓垮 MySQL。

強一緻性:可以了解為在任意時刻,所有節點中的資料是一樣的 弱一緻性:相當于異步,系統并不保證續程序或者線程的通路都會傳回最新的更新過的值

推薦系統存儲子產品的設計原則:“分級存儲,把越頻繁通路的資料放到越快的資料庫甚至緩存中,把海量的全量資料放到廉價但是查詢速度較慢的資料庫中”。

二、SparrowRecsys 的存儲系統

麻雀推薦系統的存儲子產品:使用基礎的檔案系統儲存全量的離線特征和模型資料,用 Redis 儲存線上所需特征和模型資料,使用伺服器記憶體緩存頻繁通路的特征。

在實作技術方案之前,對于問題的整體分析永遠都是重要的。我們需要先确定具體的存儲方案,這個方案必須精确到哪級存儲對應哪些具體特征和模型資料。

【王喆-推薦系統】線上服務篇-(task2)用Redis存儲特征

圖3 特征和模型資料

根據上面的特征資料,做一個初步的分析:

  • 首先,使用者特征的總數比較大,它們很難全部載入到伺服器記憶體中,是以把使用者特征載入到 Redis 之類的記憶體資料庫中。
  • 其次,物品特征的總數比較小,而且每次使用者請求,一般隻會用到一個使用者的特征,但為了物品排序,推薦伺服器需要通路幾乎所有候選物品的特征。是以可以把所有物品特征階段性地載入到伺服器記憶體中,大大減少 Redis 的線上壓力。
  • 最後,我們還要找一個地方去存儲特征曆史資料、樣本資料等體量比較大,但不要求實時擷取的資料。可以放到分布式檔案系統(單機環境下以本機檔案系統為例)——因為類似 HDFS 之類的分布式檔案系統具有近乎無限的存儲空間,是以可以把每次處理的全量特征,每次訓練的 Embedding 全部儲存到分布式檔案系統中,友善離線評估時使用。
【王喆-推薦系統】線上服務篇-(task2)用Redis存儲特征

圖4 SparrowRecsys的存儲方案

檔案系統的存儲操作較為簡單,在 SparrowRecsys 中就是利用 Spark 的輸出功能實作的。而伺服器(如我們上次task搭的Jetty伺服器)内部的存儲操作主要是跟 Redis 進行互動,下面學習Redis 的特性以及寫入和讀取方法。

三、Redis 基礎知識

Redis 是當今業界最主流的記憶體資料庫。

3.1 所有的資料都以 Key-value 的形式存儲。

Key 隻能是字元串,value 可支援的資料結構包括 string(字元串)、list(連結清單)、set(集合)、zset(有序集合) 和 hash(哈希)。這個特點決定了 Redis 的使用方式,無論是存儲還是擷取,都應該以鍵值對的形式進行,并且根據你的資料特點,設計值的資料結構。

3.2 所有的資料都存儲在記憶體中

磁盤隻在持久化備份或恢複資料時起作用。

這個特點決定了 Redis 的特性:一是 QPS 峰值可以很高,二是資料易丢失,是以我們在維護 Redis 時要充分考慮資料的備份問題,或者說,不應該把關鍵的業務資料唯一地放到 Redis 中。但對于可恢複,不關乎關鍵業務邏輯的推薦特征資料,就非常适合利用 Redis 提供高效的存儲和查詢服務。

QPS:每秒查詢率QPS是對一個特定的查詢伺服器在規定時間内所處理流量多少的衡量标準,在網際網路上,作為域名系統伺服器的機器的性能經常用每秒查詢率來衡量。

四、Sparrow Recsys中的Redis實踐

在實際的 Sparrow Recsys 的 Redis 部分中,用到了 Redis 最基本的操作,set、get 和 keys,value 的資料類型用到了 string。

4.1 安裝 Redis

Redis 的安裝過程在 linux/Unix 環境下安裝參考:(​​http://www.redis.cn/download.html​​​)。 Windows 環境下的安裝:。

在啟動 Redis 之後,如果沒有特殊的設定,Redis 服務會預設運作在 6379 端口,沒有特殊情況保留這個預設的設定就可以了,Sparrow RecSys 也是預設從 6379 端口存儲和讀取 Redis 資料的。

下圖即安裝好redis後,在指令行中進入redis的目錄,然後啟動對應的redis用戶端程式,顯示正确端口号,表示服務已經啟動。然後可以測試一下讀寫:

【王喆-推薦系統】線上服務篇-(task2)用Redis存儲特征

4.2 運作離線程式,通過 jedis 用戶端寫入 Redis

在 Redis 運作起來之後,我們就可以在離線 Spark 環境下把特征資料寫入 Redis。這裡以(​​【王喆-深度學習推薦系統實戰】特征工程篇-(task5)Embedding實踐​​)中生成的 Embedding 資料為例,來實作 Redis 的特征存儲過程。

實際的過程:

(1)首先利用最常用的 Redis Java 用戶端 Jedis 生成 redisClient;

(2)然後周遊訓練好的 Embedding 向量;

(3)将 Embedding 向量以字元串的形式存入 Redis,并設定過期時間(ttl)。

具體實作請參考下面的代碼(代碼參考 com.wzhe.sparrowrecsys.offline.spark.featureeng.Embedding 中的 ​

​trainItem2vec​

​ 函數):

if (saveToRedis) {
  //建立redis client
  val redisClient = new Jedis(redisEndpoint, redisPort)
  val params = SetParams.setParams()
  //設定ttl為24小時
  params.ex(60 * 60 * 24)
  //周遊存儲embedding向量
  for (movieId <- model.getVectors.keys) {
    //key的形式為字首+movieId,例如i2vEmb:361
    //value的形式是由Embedding向量生成的字元串,例如 "0.1693846 0.2964318 -0.13044095 0.37574086 0.55175656 0.03217995 1.327348 -0.81346786 0.45146862 0.49406642"
    redisClient.set(redisKeyPrefix + ":" + movieId, model.getVectors(movieId).mkString(" "), params)
  }
  //關閉用戶端連接配接
  redisClient.close()
}      

4.3 在推薦伺服器中把 Redis 資料讀取出來

剛才的存儲方案說了,把所有物品 Embedding 階段性地全部緩存在伺服器内部,使用者 Embedding 則進行實時查詢。緩存物品 Embedding 的代碼如下:

就是先用 ​

​keys​

​ 操作把所有物品 Embedding 字首的鍵找出,然後依次将 Embedding 載入記憶體。

//建立redis client
Jedis redisClient = new Jedis(REDIS_END_POINT, REDIS_PORT);
//查詢出所有以embKey為字首的資料
Set<String> movieEmbKeys = redisClient.keys(embKey + "*");
int validEmbCount = 0;
//周遊查出的key
for (String movieEmbKey : movieEmbKeys){
    String movieId = movieEmbKey.split(":")[1];
    Movie m = getMovieById(Integer.parseInt(movieId));
    if (null == m) {
        continue;
    }
    //用redisClient的get方法查詢出key對應的value,再set到記憶體中的movie結構中
    m.setEmb(parseEmbStr(redisClient.get(movieEmbKey)));
    validEmbCount++;
}
redisClient.close();      

在具體為使用者推薦的過程中,我們再利用相似的接口查詢出使用者的 Embedding,與記憶體中的 Embedding 進行相似度的計算,就可以得到最終的推薦清單了。

具體操作:

(1)安裝好 Redis;

(2)運作 SparrowRecsys 中 Offline 部分 Embedding 主函數,先把物品和使用者 Embedding 生成并且插入 Redis(注意把 ​​

​saveToRedis​

​​ 變量改為 true)。

(3)然後再運作 Online 部分的 ​​

​RecSysServer​

​​,看一下推薦伺服器有沒有正确地從 Redis 中讀出物品和使用者 Embedding 并産生正确的推薦結果(注意,記得要把 ​

​util.Config​

​​ 中的 ​

​EMB_DATA_SOURCE​

​​ 配置改為 ​

​DATA_SOURCE_REDIS​

​)。

五、資料庫分類

除了 Redis,還有多種不同的緩存和資料庫,如 Cassandra、EVcache、GuavaCache 等等,都是業界非常流行的存儲特征的工具。在掌握了特征存儲的基本原則之後,了解每個資料庫的不同和它們最合适的應用場景。

取決于使用哪個資料庫的因素有:

資料結構;查詢模式;需要處理的數量或規模。

5.1 SQL

結構取決于我們用來确定将使用哪種類型的因素

如果需要​​​ACID​​​屬性,則需要使用關系DBMS。如MySQL,Oracle,Postgres等

付款系統主要需要交易和原子性。

​​​強一緻性​​主要可以通過SQL資料庫來實作。

5.2 NoSQL

假設正在嘗試為諸如Amazon之類的商品建立目錄,想在其中存儲有關具有各種屬性的不同産品的資訊。例如,不同産品的這些屬性通常不同。藥品将有有效期,但冰箱将具有能量等級。

在這種情況下,我們的資料不能表示為表格。這意味着我們需要使用NoSQL資料庫。

如果需要​​​BASE​​​屬性,則可以使用非關系資料庫前進。

對于​​​最終一緻性​​​,我們可以使用NoSQL資料庫

最常見的NoSQL DB是MongoDB,Cassandra,DynamoDB.

(1)基于文檔(基于查詢的資料)

如果我們擁有大量資料-不僅是數量,而且還有各種各樣的屬性-并且我們需要運作各種各樣的查詢,則需要使用一種稱為Document DB的東西。

使用文檔資料庫,随機查詢或其他查詢最有效

Couchbase或MongoDB是一些常用的文檔資料庫

(2)圖形存儲

這些類型的資料庫使資料可視化更加容易。

它們非常善于在節點的幫助下存儲不同資料點之間的關系。

圖形存儲可能不是最可擴充的資料庫。

但是,它們在防止欺詐等使用案例方面效率很高。

圖形資料庫的常見示例是Neo4j 和 JanusGraph。

(3)Key-value 存儲

這些都是非常簡單的資料庫管理系統,存儲關鍵值對。

最終目标是快速擷取基本資料。

這些類型的資料庫的常見用例是排行榜和購物車資料。

redis是流行的key value 存儲。

(4)柱狀資料庫(不斷增加的資料)

有限的查詢種類,但是資料庫的大小持續快速增加。例如訂單,目錄

現在,Uber司機的數量将逐日增加,即每天收集的資料也會逐日增加。這成為越來越多的資料。

在這種情況下,我們使用諸如Cassandra或HBase之類的列式資料庫。

六、作業

課程中存儲 Embedding 的方式還有優化的空間嗎?除了 string,我們是不是還可以用其他 Redis value 的資料結構存儲 Embedding 資料,那從效率的角度考慮,使用 string 和使用其他資料結構的優缺點有哪些?為什麼?

(1)redis keys指令不能用在生産環境中,如果數量過大效率十分低,導緻redis長時間堵塞在keys上。生産環境我們一般選擇提前載入一些warm up物品id的方式載入物品embedding。

(2)Redis value 可以用pb格式(protobuf)存儲, 存儲上節省空間. 解析起來相比string, cpu的效率也應該會更高

(3)1.redis這種緩存中盡量放活躍的資料,存放全量的embedding資料,對記憶體消耗太大。尤其物品庫,使用者embedding特别多的情況下。

2.分布式kv可以做這種embedding的存儲

3.關于embedding的編碼可以用pb來解決。embedding次元太大的時候,redis裡的資料結構占用空間會變大,因為除了embedding本身的空間,還有資料結構本身占用的空間。

七、課後答疑

(1)文中的兩部分redis相關的代碼,可以在Maven項目中找到嗎?可不可以提供以下路徑資訊友善找到?

參照 com.wzhe.sparrowrecsys.offline.spark.embedding.Embedding中的​​

​trainItem2vec​

​​函數

以及com.wzhe.sparrowrecsys.online.datamanager.DataManager中的​​

​loadMovieEmb​

​函數

(2)在IntelliJ的Maven porject裡用到的工具比如spark, redis, 這些需要我們額外下載下傳安裝到電腦上嗎?還是說在Maven項目中已經通過代碼添加依賴,就已經完成了安裝?

Reference