什么是 Redis
Redis 是一個(gè)開(kāi)源的程腹,高性能的匣吊,支持多種數(shù)據(jù)結(jié)構(gòu)的內(nèi)存數(shù)據(jù)庫(kù),已經(jīng)被廣泛用于數(shù)據(jù)庫(kù)寸潦,緩存色鸳,消息隊(duì)列等領(lǐng)域。它有著豐富的數(shù)據(jù)結(jié)構(gòu)支持见转,譬如 String命雀,Hash,Set 和 Sorted Set斩箫,用戶(hù)通過(guò)它們能構(gòu)建自己的高性能應(yīng)用吏砂。
Redis 非常快乘客,沒(méi)準(zhǔn)是世界上最快的數(shù)據(jù)庫(kù)了狐血,它雖然使用內(nèi)存,但也提供了一些持久化機(jī)制以及異步復(fù)制機(jī)制來(lái)保證數(shù)據(jù)的安全易核。
Redis 的不足
Redis 非承僦酷,但它也有一些問(wèn)題:
- 內(nèi)存很貴牡直,而且并不是無(wú)限容量的缀匕,所以我們不可能將大量的數(shù)據(jù)存放到一臺(tái)機(jī)器。
- 異步復(fù)制并不能保證 Redis 的數(shù)據(jù)安全井氢。
- Redis 提供了 transaction mode弦追,但其實(shí)并不滿足 ACID 特性。
- Redis 提供了集群支持花竞,但也不能支持跨多個(gè)節(jié)點(diǎn)的分布式事務(wù)劲件。
所以有時(shí)候掸哑,我們需要一個(gè)更強(qiáng)大的數(shù)據(jù)庫(kù),雖然在延遲上面可能趕不上 Redis零远,但也有足夠多的特性苗分,譬如:
- 豐富的數(shù)據(jù)結(jié)構(gòu)
- 高吞吐,能接受的延遲
- 強(qiáng)數(shù)據(jù)一致
- 水平擴(kuò)展
- 分布式事務(wù)
為什么選擇 TiKV
大約 4 年前牵辣,我開(kāi)始解決上面提到的 Redis 遇到的一些問(wèn)題摔癣。為了讓數(shù)據(jù)持久化,最直觀的做法就是將數(shù)據(jù)保存到硬盤(pán)上面纬向,而不是在內(nèi)存里面择浊。所以我開(kāi)發(fā)了 LedisDB,一個(gè)使用 Redis 協(xié)議逾条,提供豐富數(shù)據(jù)結(jié)構(gòu)琢岩,但將數(shù)據(jù)放在 RocksDB 的數(shù)據(jù)庫(kù)。LedisDB 并不是完全兼容 Redis师脂,所以后來(lái)担孔,我和其他同事繼續(xù)創(chuàng)建了 RebornDB,一個(gè)完全兼容 Redis 的數(shù)據(jù)庫(kù)吃警。
無(wú)論是 LedisDB 還是 RebornDB糕篇,因?yàn)樗麄兌际菍?shù)據(jù)放在硬盤(pán),所以能存儲(chǔ)更大量的數(shù)據(jù)酌心。但它們?nèi)匀徊荒芴峁?ACID 的支持拌消,另外,雖然我們可以通過(guò)codis 去提供集群的支持安券,我們也不能很好的支持全局的分布式事務(wù)拼坎。
所以我們需要另一種方式,幸運(yùn)的是完疫,我們有TiKV。
TiKV 是一個(gè)高性能债蓝,支持分布式事務(wù)的 key-value 數(shù)據(jù)庫(kù)壳鹤。雖然它僅僅提供了簡(jiǎn)單的 key-value API,但基于 key-value饰迹,我們可以構(gòu)造自己的邏輯去創(chuàng)建更強(qiáng)大的應(yīng)用芳誓。譬如,我們就構(gòu)建了 TiDB 啊鸭,一個(gè)基于 TiKV 的锹淌,兼容 MySQL 的分布式關(guān)系型數(shù)據(jù)庫(kù)。TiDB 通過(guò)將 database 的 schema 映射到 key-value 來(lái)支持了相關(guān) SQL 特性赠制。所以對(duì)于 Redis赂摆,我們也可以采用同樣的辦法 - 構(gòu)建一個(gè)支持 Redis 協(xié)議的服務(wù),將 Redis 的數(shù)據(jù)結(jié)構(gòu)映射到 key-value 上面。
如何開(kāi)始
整個(gè)架構(gòu)非常簡(jiǎn)單烟号,我們僅僅需要做的就是構(gòu)建一個(gè) Redis 的 Proxy绊谭,這個(gè) Proxy 會(huì)解析 Redis 協(xié)議,然后將 Redis 的數(shù)據(jù)結(jié)構(gòu)映射到 key-value 上面汪拥。
Redis Protocol
Redis 協(xié)議被叫做 RESP(Redis Serialization Protocol)达传,它是文本類(lèi)型的,可讀性比較好迫筑,并且易于解析宪赶。它使用 “rn” 作為每行的分隔符并且用不同的前綴來(lái)代表不同的類(lèi)型。例如脯燃,對(duì)于簡(jiǎn)單的 String搂妻,第一個(gè)字節(jié)是 “+”,所以一個(gè) “OK” 行就是 “+OKrn”曲伊。
大多數(shù)時(shí)候叽讳,客戶(hù)端會(huì)使用最通用的 Request-Response 模型用于跟 Redis 進(jìn)行交互》啬迹客戶(hù)端會(huì)首先發(fā)送一個(gè)請(qǐng)求岛蚤,然后等待 Redis返回結(jié)果。請(qǐng)求是一個(gè) Array懈糯,Array 里面元素都是 bulk strings涤妒,而返回值則可能是任意的 RESP 類(lèi)型。Redis 同樣支持其他通訊方式:
- Pipeline - 這種模式下面客戶(hù)端會(huì)持續(xù)的給 Redis 發(fā)送多個(gè)請(qǐng)求赚哗,然后等待 Redis 返回一個(gè)結(jié)果她紫。
- Push - 客戶(hù)端會(huì)在 Redis 上面訂閱一個(gè) channel,然后客戶(hù)端就會(huì)從這個(gè) channel 上面持續(xù)受到 Redis push 的數(shù)據(jù)屿储。
下面是一個(gè)簡(jiǎn)單的客戶(hù)端發(fā)送 LLEN mylist
命令到 Redis 的例子:
C: *2\r\n
C: $4\r\n
C: LLEN\r\n
C: $6\r\n
C: mylist\r\n
S: :48293\r\n
客戶(hù)端會(huì)發(fā)送一個(gè)帶有兩個(gè) bulk string 的 array贿讹,第一個(gè) bulk string 的長(zhǎng)度是 4,而第二個(gè)則是 6够掠。Redis 會(huì)返回一個(gè) 48293 整數(shù)民褂。正如你所見(jiàn),RESP 非常簡(jiǎn)單疯潭,自然而然的赊堪,寫(xiě)一個(gè) RESP 的解析器也是非常容易的。
作者創(chuàng)建了一個(gè) Go 的庫(kù) goredis竖哩,基于這個(gè)庫(kù)哭廉,我們能非常容易的從連接上面解析出 RESP,一個(gè)簡(jiǎn)單的例子:
// Create a buffer IO from the connection.
br := bufio.NewReaderSize(conn, 4096)
// Create a RESP reader.
r := goredis.NewRespReader(br)
// Parse the Request
req := r.ParseRequest()
函數(shù) ParseRequest
返回一個(gè)解析好的 request相叁,它是一個(gè) [][]byte
類(lèi)型遵绰,第一個(gè)字段是函數(shù)名字辽幌,譬如 “LLEN”,然后后面的字段則是這個(gè)命令的參數(shù)街立。
TiKV 事務(wù) API
在我們開(kāi)始之前舶衬,作者將會(huì)給一個(gè)簡(jiǎn)單實(shí)用 TiKV 事務(wù) API 的例子,我們調(diào)用 Begin
開(kāi)始一個(gè)事務(wù):
txn, err := db.Begin()
函數(shù) Begin
創(chuàng)建一個(gè)事務(wù)赎离,如果出錯(cuò)了逛犹,我們需要判斷 err,不過(guò)后面作者都會(huì)忽略 err 的處理梁剔。
當(dāng)我們開(kāi)始了一個(gè)事務(wù)之后虽画,我們就可以干很多操作了:
value, err := txn.Get([]byte(“key”))
// Do something with value and then update the newValue to the key.
txn.Put([]byte(“key”), newValue)
上面我們得到了一個(gè) key 的值,并且將其更新為新的值荣病。TiKV 使用樂(lè)觀事務(wù)模型码撰,它會(huì)將所有的改動(dòng)都先緩存到本地,然后在一起提交給 Server个盆。
// Commit the transaction
txn.Commit(context.TODO())
跟其他事務(wù)處理一樣脖岛,我們也可以回滾這個(gè)事務(wù):
txn.Rollback()
如果兩個(gè)事務(wù)操作了相同的 key,它們就會(huì)沖突颊亮。一個(gè)事務(wù)會(huì)提交成功柴梆,而另一個(gè)事務(wù)會(huì)出錯(cuò)并且回滾。
映射 Data structure 到 TiKV
現(xiàn)在我們知道了如何解析 Redis 協(xié)議终惑,如何在一個(gè)事務(wù)里面做操作绍在,下一步就是支持 Redis 的數(shù)據(jù)結(jié)構(gòu)了。Redis 主要有 4 中數(shù)據(jù)結(jié)構(gòu):String雹有,Hash偿渡,Set 和 Sorted Set,但是對(duì)于 TiKV 來(lái)說(shuō)霸奕,它只支持 key-value溜宽,所以我們需要將這些數(shù)據(jù)結(jié)構(gòu)映射到 key-value。
首先质帅,我們需要區(qū)分不同的數(shù)據(jù)結(jié)構(gòu)坑质,一個(gè)非常容易的方式就是在 key 的后面加上 Type flag。例如临梗,我們可以將 ’s’ 添加到 String,所以一個(gè) String key “abc” 在 TiKV 里面其實(shí)就是 “abcs”稼跳。
對(duì)于其他類(lèi)型盟庞,我們可能需要考慮更多,譬如對(duì)于 Hash 類(lèi)型汤善,我們需要支持如下操作:
HSET key field1 value1
HSET key field2 value2
HLEN key
一個(gè) Hash 會(huì)有很多 fields什猖,我有時(shí)候想知道整個(gè) Hash 的個(gè)數(shù)票彪,所以對(duì)于 TiKV,我們不光需要將 Hash 的 key 和 field 合在一起變成 TiKV 的一個(gè) key不狮,也同時(shí)需要用另一個(gè) key 來(lái)保存整個(gè) Hash 的長(zhǎng)度降铸,所以整個(gè) Hash 的布局類(lèi)似:
key + ‘h’ -> length
key + ‘f’ + field1 -> value
key + ‘f’ + field2 -> value
如果我們不保存 length,那么如果我們想知道 Hash 的 length摇零,每次都需要去掃整個(gè) Hash 得到所有的 fields推掸,這個(gè)其實(shí)并不高效。但如果我們用另一個(gè) key 來(lái)保存 length驻仅,任何時(shí)候谅畅,當(dāng)我們加入一個(gè)新的 field,我們都需要去更新這個(gè) length 的值噪服,這也是一個(gè)開(kāi)銷(xiāo)毡泻。對(duì)于我來(lái)說(shuō),我傾向于使用另一個(gè) key 來(lái)保存 length粘优,因?yàn)?HLEN
是一個(gè)高頻的操作仇味。
例子
作者構(gòu)建了一個(gè)非常簡(jiǎn)單的例子 example ,里面只支持 String 和 Hash 的一些操作雹顺,我們可以 clone 下來(lái)并編譯:
git clone https://github.com/siddontang/redis-tikv-example.git $GOPATH/src/github.com/siddontang/redis-tikv-example
cd $GOPATH/src/github.com/siddontang/redis-tikv-example
go build
在運(yùn)行之前丹墨,我們需要啟動(dòng) TiKV,可以參考instruction无拗,然后執(zhí)行:
./redis-tikv-example
這個(gè)例子會(huì)監(jiān)聽(tīng)端口 6380带到,然后我們可以用任意的 Redis 客戶(hù)端,譬如 redis-cli
去連接:
redis-cli -p 6380
127.0.0.1:6380> set k1 a
OK
127.0.0.1:6380> get k1
"a"
127.0.0.1:6380> hset k2 f1 a
(integer) 1
127.0.0.1:6380> hget k2 f1
"a"
尾聲
現(xiàn)在已經(jīng)有一些公司基于 TiKV 來(lái)構(gòu)建了他們自己的 Redis Server英染,并且也有一個(gè)開(kāi)源的項(xiàng)目tidis 做了相同的事情揽惹。tidis
已經(jīng)比較完善,如果你想替換自己的 Redis四康,可以嘗試一下搪搏。
正如同你所見(jiàn),TiKV 其實(shí)算是一個(gè)基礎(chǔ)的組件闪金,我們可以在它的上面構(gòu)建很多其他的應(yīng)用疯溺。如果你對(duì)我們現(xiàn)在做的事情感興趣,歡迎聯(lián)系我:tl@pingcap.com哎垦。