Tao Hui http://weibo.com/taohui3
English Version
設計理念
lushan 在1.0版本的時候是一個輕量級的key-value數據庫,使用memcached協議,可以掛載多個庫。使用lushan你可以很容易的像memcached一樣在幾台機器上搭建一個集群。 lushan 2.0 同時是一個輕量級的應用框架,可以掛載多個共享庫,這樣你在一個進程內同時具有訪問數據和計算的能力,這使得編寫大數據量、高性能的服務變得前所未有的簡單。特別適合互聯網推薦、廣告和搜索的業務場景。目前lushan 已經在新浪微博推薦和廣告業務中應用多年。
大概在2013年,我在開發“錯過的微博“推薦的時候。我需要在線提供幾份數據存儲來實驗不同的算法,以及他們的在線和測試版本。這樣需要部署幾套系統。這種做法太low了。所以,在某個週末時間我開發了lushan,他可以把你從這些事情中解放出來,非常cool。
事實也是如此,lushan後來已經成為微博推薦和廣告業務的基礎設施。現在運維著兩個集群,分別有12台機器,服務著上T的在線查詢數據,每天10億以上的查詢。
在開發完lushan 第一個版本的時候,我一直有讓lushan 同時能夠掛載共享庫的衝動。但遲遲沒有下定決心,一是因為我相信一個框架最好能有自己的定位,二是架構上應該把易變更部分和穩定部分分開。但在2015年,開發微博廣告的時候,用戶的興趣數據、關係數據、CTR預估的特徵數據都很容易通過Hadoop整理後存儲到lushan,這個時候很簡單的編寫兩個模塊即可實現targeting和ctr預估功能,而且性能非常強勁。於是我放棄了原來的想法,實現了第二版,讓lushan 功能更加強大。實際應用中你仍然可以把lushan 只作為key-value database 使用,或者把只提供數據的lushan 集群和同時提供計算的集群分開單獨部署,在微博廣告內部也是這麼使用的。
在examples目錄下已經提供了一個樣例庫,按照下面的步驟去掛載:
輸出就是key 123456所對應的value.
解釋一下每一步:
作為計算框架來使用的時候lushan 支持了兩種協議,類似於HTTP GET的單行"URL"協議,以及類似於HTTP POST的指定發送value長度的協議,後者同時支持發送二進制數據。在modules 目錄下提供了兩個例子:lproxy和lecho,分別演示了這兩個協議。
lproxy 的例子,對於一個請求的key, 先查詢redis,redis裡有的則直接返回,redis沒有的查詢本機掛載的庫。在簡單的情況下這個例子也可以用於生產環境,如果有更複雜的需求請修改此代碼。
按下面步驟操作:
創建一個文本文件x.txt,輸入下面兩行, 第一個tab之前的是key, 後面的是value,如下:
168 hello lushan
187 line 2
用tools裡的lushan_line_maker 轉成lushan 的文件格式,假如為hdict_20180428192000,通過前面數據存取例子掛載到lushan 編號為1的目錄下。
在本機起一個redis, set dbno 為1, 然後加入一條記錄, key為168, value為"hello redis"
在hproxy目錄下執行make, 把生成的hmodule.so和hmodule.conf放到hmod/15/1.0.0目錄下, 修改hmodule.conf裡的host 和port 為你部署的redis 的ip 和端口。
執行:
echo -ne "hmod_open /mnt/lushan/hmod/15/1.0.0/ 15rn" | nc 127.0.0.1 9999
如果返回OPENED 則打開成功,否則檢查libhiredis 是否在LD_LIBRARY_PATH 裡。
查詢:
echo -ne "get m15?k=1-168rn" | nc 127.0.0.1 9999
VALUE m15?k=1-168 0 11
hello redis
END
echo -ne "get m15?k=1-187rn" | nc 127.0.0.1 9999
VALUE m15?k=1-187 0 6
line 2
END
則如我們預期,redis 存在則返回redis 結果,否則查詢lushan 數據。
關閉一個模塊:
echo -ne "hmod_close 15rn" | nc 127.0.0.1 9999
如果你的所有模塊都沒有全局變量,可以用hmod_open 把一個新的庫直接替換舊的庫,這樣對線上的服務沒有任何損失。
lecho 的例子類似,只是簡單返回你請求的數據,這個例子很簡單,不再詳細描述。
hdict 是lushan所掛載的庫格式。他非常簡單。在hdict_xxxx目錄下有兩個必須的文件,dat和idx。前者包含你的數據,後者是key到value在dat文件中位置偏移的映射。 定義:
typedef struct {
uint64_t key;
uint64_t pos;
} idx_t;
key 是64位無符號長整形,不包含庫編號。 pos 是由value的長度和其在dat文件中的偏移組合而成的:
pos = (length << 40) | offset;
idx文件必須是按照idx_t.key升序排列的。 dat文件則不需要。你既可以對已經存在的一個dat文件創建索引,也可以在輸出文件的時候同時生成索引。
有序文件在map-reduce計算模型中非常常見。你可以在hadoop中指定輸出文件格式,從而生成hdict格式的庫。例如如下命令:
job.setOutputFormat(LushanFileOutputFormat.class);
有三個命令可以獲得統計狀態數據: stats, info 和hmod_info。前者輸出全局數據,後兩者輸出每個庫和每個模塊的數據。
echo -n -e "statsrn" | nc 127.0.0.1 9999
STAT pid 13810
STAT uptime 1435075686
STAT curr_connections 1411
STAT connection_structures 4061
STAT cmd_get 2099151223
STAT get_hits 3950240117
STAT get_misses 2443878402
STAT threads 16
STAT timeouts 117
STAT waiting_requests 0
STAT ialloc_failed 0
END
echo -n -e "inforn" | nc 127.0.0.1 9999
id label state ref num_qry idx_num open_time path
----------------------------------------------------------------
1 interest_CF_trends OPEN 0 139922 18419392 150824-042654 /mnt/lushan/hdb/12/hdict_20150711204737
2 interest_CF_trends OPEN 0 190508 26175141 150824-050246 /mnt/lushan/hdb/12/hdict_20150711204737
echo -ne "hmod_inforn" | nc 127.0.0.1 9999
id label state ref num_qry open_time path
----------------------------------------------------------------
0 OPEN 0 267130787 180419-174502 /mnt/lushan/hmod/0
5 OPEN 0 336829974 180419-174503 /mnt/lushan/hmod/5
你可以利用lushan.php創建圖形化的統計狀態頁面。
如果你有mysql的經驗,會很容易搭建一個簡單的集群。首先,你要把你的數據分成若干組,通常是你機器數的某個倍數。然後考慮你要部署幾套服務,通常是兩套分佈在不同的IDC。然後按照分組的規則去通過memcached客戶端查詢你的數據。
儘管非常簡單,但lushan仍然提供了一個簡單的框架去幫助你處理一些數據傳輸方面的細節,名字叫做transfer.py,可以幫助你:
memcached 的協議比較簡單,get只支持簡單的請求,但可以返回相對複雜的結果。 set類命令支持複雜的請求,但只支持相對簡單的結果。 lushan對此做了兩個修改,使用的時候類似於HTTP的GET和POST協議,下面的key通常可以理簡單對應HTTP裡的URL:
get請求的“key”可以超過250字節限制。 在發送的時候設置:
memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_VERIFY_KEY, 0);
這樣通過libmemcached發送沒有問題。返回結果的時候需要返回在250字節內的key,可以選擇的是返回請求的簽名,也可以返回前250個字節。只要讀取的時候按照截斷的key讀取,並且截斷後的key不衝突即可。
用gets來支持發送多行的請求。通常簡單get請求就夠用了,但如果想要發送的是類似json的請求則需要更複雜的協議。 lushan重新修改了gets協議,修改成和set一樣的協議。在使用客戶端的時候同樣按上面設置不檢查key的合法性,然後發送一個下面格式的包過去:
gets key 0 0 value_lenrn
valuern
在返回結果裡直接fetch 這個key即可。在lutil.h裡做了封裝,直接調用hrequest_pack即可。
stat裡timeout比較多,或者你從客戶端記錄大於等於你的配置的TIMEOUT時間的請求比較多,你可以嘗試設置更大的NUM_THREADS.
upload某個編號下有很多沒有傳完的hdict文件,這通常意味著你的傳輸腳本在沒有傳完的時候就中斷。