Redis / SSDB 簡介與使用說明


#1

###最後更新時間為 20161103,這邊文長想到隨時補,看到不瞭解的也可以提出,然後我再補述進去

這邊簡介一下 Redis / SSDB 這兩套 NoSQL 的使用方式(混在一起講)

Redis : 記憶體 only,定時回寫硬碟,正常關閉會回寫,關閉會重新讀入記憶體,可用 aof mode 增加持久性(把指令先丟到硬碟去,不是資料,回復時重做,但效能會稍低),不適合大量資料,否則會吃到 swap …

SSDB : 記憶體 + 硬碟 + google leveldb(同 bitcoin core 的 db),建議用 SSD 來當硬碟

兩個都用 command queue 來做,很類似 single process + single thread,所以不用擔心 race condition 問題( 其他的 request 先會卡著 )

##資料型別(資料結構)

Redis:key-value / hash / list / set / sorted set,還有 pubsub 來做推播
SSDB : key-value / hash / list / sorted set,缺 set 是因為可用 hash 完成

還有比較大的差異處

Redis 的 main key 是 global,而 SSDB 的 main key 是依照 date type 有不同的 name space
Redis 的 key search 是用 wildcard,可用 *? 之類的字元,沒有 limit (per page) 可用,而 SSDB 的是 string range,所以搜尋時需要輸入 start_string & end_string & limit

有以上基本概念才看得懂下面我所寫的就是

Linux 安裝略過,不過說一下如果你用 OSX 用 brew 來裝

brew install redis
brew install ssdb
brew services start redis
brew services start ssdb
brew services list # 看到開啟列表

這樣就好了,而你只需要一個 gem

gem i redis

require 'redis'
$redis = Redis.new #default port : 6379
 $ssdb = Redis.new(:port => 8888)

okay,開始 … 先去看 doc … 以下 code 要對照兩邊 doc 來看就是

http://redis.io/commands
http://ssdb.io/docs/commands/index.html

首先你要先看你有哪些資料

$redis.keys #等同以下
$redis.keys('*')
 $ssdb.keys #出Exception噴掉
 $ssdb.keys('' , '' , -1) #出Exception噴掉
 $ssdb.client.call(['keys'  , '' , '' , -1]) #key value
 $ssdb.client.call(['hlist' , '' , '' , -1]) #hash
 $ssdb.client.call(['qlist' , '' , '' , -1]) #list
 $ssdb.client.call(['zlist' , '' , '' , -1]) #sorted set

這邊解釋了『輸入值』是不一樣的,畢竟 redis gem 是針對 Redis 寫的,而 SSDB 用上面的方式就可以進行原生 call 且不限指令(不過有些指令還是會有問題||||)

這邊只做基本示範,重點都是資料型別和使用方式,類似

3.times{$redis.hset('testme' , rand , rand)}
3.times{ $ssdb.hset('testme' , rand , rand)}
$redis.hgetall('testme') #=> {'0.9' => '0.8' , '0.8' => '0.7' , '0.7' => '0.6'}
 $ssdb.hgetall('testme') #=> {'0.9' => '0.8' , '0.8' => '0.7' , '0.7' => '0.6'}

當然 hash 類型你就可以塞一個或是取一個,這兩個的值只有 string,我們剛剛上面寫的是這群 string 的資料結構,但真正 data type 只有 string

然後你知道 KeyValue (一般 1 : 1 變數) , Hash (Hash / Obj / Dictionary) , List (Array / Queue) , Set (Set) , Sorted Set (Order List / Tree) 你應該繼續配 doc 來念應該就會玩他們兩個了

##name space 設計和建議:

這邊很重要 … 請耐心的看完,因為這是新手失落的那一塊,首先你『必須』拋棄掉你 RDBMS 的概念,如何用上面所提的資料型別達成一樣的事情

SSDB 內可以塞一票鬼東西,但當你破百萬或千萬筆時,你不會希望把所有的東西都丟到記憶體內,包括 Redis 也是,全掃全歷遍很蠢,所以在實務上不建議歷遍,所以怎麼做?

####自己建index

#schema
Item => {key => {kind , name}}
Item:KIND => {key_only}
#data
Item => {1 => Marshal{kind: 1 , name: 'HelloKitty'}}
Item:1 => {1 => ''}

結束,簡單的來這句話全等於完成這個結構

Item.where(:status => 1)
SELECT * FROM Item WHERE status = 1

當我想要這樣找時,歷遍 Item:1 的 keys,然後再去拉 Item,則我取得所有 status = 1 的 Item

這種 key value 結構的 No SQL 因為沒有 conditions ( SQL 的 where ) 可以用,所以最差就是全掃,但全掃不允許時,你應該自建 index 出來,建立的 index 來源於使用時的狀況,所以塞入 / 修改資料時,要順便建立 / 更新 index,則之後就一直往 index 內撈出關連即可,當然你可已有多重關連的狀況,類似

#schema Item:KIND:STATUS:USER_ID
Item:1:2:0000023513 => {relate datas}
#SSDB 用類似 Item:1:2:0000000000 , Item:1:2:9999999999 , 100
#的方式來列 key,並更換 start_string
#Redis 用類似 Item:1:2:* 的方式來列

當然你這樣可能會需要尋找 N(kind) x M(status) 次,然而你其實可以在 Item 內寫入目前的 status / kind,就可以方便的列出所有的 users

hash mapping

A => hash(checksum / MD5 / SHA1) => B

類似網址,文字的來源,直接用 hash 最快,也不用全掃,當然你可以把你的 A 的字串格式規定好,也可以用 hash 直接對照到

MD5('Item:#{status}:#{kind}:#{user}') => Hash => B

這個也是完全一樣的意思,你可以把一票 index 都 checksum 後取得 B,這樣就可以節省多對多掃描的壓力

####zset 好好用

定時要執行 job 時,可以把 now = TIMESTAMP{Time.now.to_i} 丟成 zset 的 score,然後用 score < now 的方式取得所有目前該做的事情,把做完未來還要做的事情往後丟(給未來時間)則你就有排程好用了

如果你有兩個 set 要做交集,也盡可能用 zset,用兩個集合來做,一個放 A 一個放 B,A 用 score 正向排序取第一筆,B 用 score 逆向排序取第一筆,把兩筆資料結束掉,以上 loop 則可以把交集處全部處理掉(之前做買賣交易所經驗談)

###補充一些經驗之類的鬼:

只有 key-value 才能設定 expire,hash 之類的不行(自動清除時間)

要做 lock 請用 incr (把某數 +1 回傳,取得時如果不是 1 則已被使用)

zset 是用分數來排序的 set,其資料格式是『score => key』這句話已經描述一切,所以他的值是 key 而非 value,所以同 key 會被刪除,可同 score,score 的格式應該是 int 8 byte (…一個很大的數字)

SSDB 因為用 string_range 來做 key search,所以如果是"數字"前面記得補 0

Redis / SSDB 都不太建議用 UTF8 來當作 key,尤其你的 key 可能是亂碼的狀況下,Ruby 的 charset 順序可能會和兩套的順序不一樣,可能會得到很奇怪的結果

Redis / SSDB 的 value 內都可以塞 Marshal,所以你可以開心的把 Ruby 物件塞進去

所有的 index 都應該視其為 cache,不能當資料實體用,應該可以一鍵 rebuild,類似上面的 kind / status 有 index 了,但可能因為程式出錯造成有兩個 index(不同的 kind / status 類似變更時忘記刪除),則必須以 Item 實體內的為基準,所以這樣想法,所有 index 應該都可以被清除,然後從實體重建,或是用某種運算再次重建(類似上面的 hash mapping )

盡量使用 multi 系列,因為一次只取只修改一筆還滿蠢的,不過這邊要依照實作而定

最後是很多時候你不必使用『exists』,你應該直接用 get / set 即可,取得資訊用 get 當 nil 時則不存在,來源是相同資料來源會得到一樣結果時,也不用檢查直接 set 即可,如果是變動則是 get + set

可以的話盡量儲存非 transaction 或可以 rebuild 的資料,畢竟 transaction 對這兩個 NoSQL 而言是弱項,Redis 可用 multi 來達成 command set 的確保執行,但 SSDB 缺這塊就是了,不過心臟大一點累一點,你可以在 RDBMS 內完成這個,但不是只有 RDBMS


.length VS .count VS .size 效能