使用 TiKV 構建分散式類 Redis 服務

coyan發表於2021-09-09

什麼是 Redis

是一個開源的,高效能的,支援多種資料結構的記憶體資料庫,已經被廣泛用於資料庫,快取,訊息佇列等領域。它有著豐富的資料結構支援,譬如 String,Hash,Set 和 Sorted Set,使用者透過它們能構建自己的高效能應用。

Redis 非常快,沒準是世界上最快的資料庫了,它雖然使用記憶體,但也提供了一些持久化機制以及非同步複製機制來保證資料的安全。

Redis 的不足

Redis 非常酷,但它也有一些問題:

  1. 記憶體很貴,而且並不是無限容量的,所以我們不可能將大量的資料存放到一臺機器。

  2. 非同步複製並不能保證 Redis 的資料安全。

  3. Redis 提供了 transaction mode,但其實並不滿足 ACID 特性。

  4. Redis 提供了叢集支援,但也不能支援跨多個節點的分散式事務。

所以有時候,我們需要一個更強大的資料庫,雖然在延遲上面可能趕不上 Redis,但也有足夠多的特性,譬如:

  1. 豐富的資料結構

  2. 高吞吐,能接受的延遲

  3. 強資料一致

  4. 水平擴充套件

  5. 分散式事務

為什麼選擇 TiKV

大約 4 年前,我開始解決上面提到的 Redis 遇到的一些問題。為了讓資料持久化,最直觀的做法就是將資料儲存到硬碟上面,而不是在記憶體裡面。所以我開發了 ,一個使用 Redis 協議,提供豐富資料結構,但將資料放在 RocksDB 的資料庫。LedisDB 並不是完全相容 Redis,所以後來,我和其他同事繼續建立了 ,一個完全相容 Redis 的資料庫。

無論是 LedisDB 還是 RebornDB,因為他們都是將資料放在硬碟,所以能儲存更大量的資料。但它們仍然不能提供 ACID 的支援,另外,雖然我們可以透過 去提供叢集的支援,我們也不能很好的支援全域性的分散式事務。

所以我們需要另一種方式,幸運的是,我們有。

TiKV 是一個高效能,支援分散式事務的 key-value 資料庫。雖然它僅僅提供了簡單的 key-value API,但基於 key-value,我們可以構造自己的邏輯去建立更強大的應用。譬如,我們就構建了 ,一個基於 TiKV 的,相容 MySQL 的分散式關係型資料庫。TiDB 透過將 database 的 schema 對映到 key-value 來支援了相關 SQL 特性。所以對於 Redis,我們也可以採用同樣的辦法 - 構建一個支援 Redis 協議的服務,將 Redis 的資料結構對映到 key-value 上面。

如何開始

圖片描述

整個架構非常簡單,我們僅僅需要做的就是構建一個 Redis 的 Proxy,這個 Proxy 會解析 Redis 協議,然後將 Redis 的資料結構對映到 key-value 上面。

Redis Protocol

Redis 協議被叫做 (Redis Serialization Protocol),它是文字型別的,可讀性比較好,並且易於解析。它使用 “rn” 作為每行的分隔符並且用不同的字首來代表不同的型別。例如,對於簡單的 String,第一個位元組是 “+”,所以一個 “OK” 行就是 “+OKrn”。

大多數時候,客戶端會使用最通用的 Request-Response 模型用於跟 Redis 進行互動。客戶端會首先傳送一個請求,然後等待 Redis返回結果。請求是一個 Array,Array 裡面元素都是 bulk strings,而返回值則可能是任意的 RESP 型別。Redis 同樣支援其他通訊方式:

  1. Pipeline - 這種模式下面客戶端會持續的給 Redis 傳送多個請求,然後等待 Redis 返回一個結果。

  2. Push - 客戶端會在 Redis 上面訂閱一個 channel,然後客戶端就會從這個 channel 上面持續受到 Redis push 的資料。

下面是一個簡單的客戶端傳送  LLEN mylist 命令到 Redis 的例子:

C: *2rn
C: $4rn
C: LLENrn
C: $6rn
C: mylistrn

S: :48293rn

客戶端會傳送一個帶有兩個 bulk string 的 array,第一個 bulk string 的長度是 4,而第二個則是 6。Redis 會返回一個 48293 整數。正如你所見,RESP 非常簡單,自然而然的,寫一個  RESP 的解析器也是非常容易的。

作者建立了一個 Go 的庫 ,基於這個庫,我們能非常容易的從連線上面解析出 RESP,一個簡單的例子:

// Create a buffer IO from the connection.br := bufio.NewReaderSize(conn, 4096)// Create a RESP reader.r := goredis.NewRespReader(br)// Parse the Requestreq := r.ParseRequest()

函式 ParseRequest 返回一個解析好的 request,它是一個 [][]byte 型別,第一個欄位是函式名字,譬如 “LLEN”,然後後面的欄位則是這個命令的引數。

TiKV 事務 API

在我們開始之前,作者將會給一個簡單實用 TiKV 事務 API 的例子,我們呼叫 Begin  開始一個事務:

txn, err := db.Begin()

函式 Begin 建立一個事務,如果出錯了,我們需要判斷 err,不過後面作者都會忽略 err 的處理。

當我們開始了一個事務之後,我們就可以幹很多操作了:

value, err := txn.Get([]byte(“key”))// Do something with value and then update the newValue to the key.txn.Put([]byte(“key”), newValue)

上面我們得到了一個 key 的值,並且將其更新為新的值。TiKV 使用樂觀事務模型,它會將所有的改動都先快取到本地,然後在一起提交給 Server。

// Commit the transactiontxn.Commit(context.TODO())

跟其他事務處理一樣,我們也可以回滾這個事務:

txn.Rollback()

如果兩個事務操作了相同的 key,它們就會衝突。一個事務會提交成功,而另一個事務會出錯並且回滾。

對映 Data structure 到 TiKV

現在我們知道了如何解析 Redis 協議,如何在一個事務裡面做操作,下一步就是支援 Redis 的資料結構了。Redis 主要有 4 中資料結構:String,Hash,Set 和 Sorted Set,但是對於 TiKV 來說,它只支援 key-value,所以我們需要將這些資料結構對映到 key-value。

首先,我們需要區分不同的資料結構,一個非常容易的方式就是在 key 的後面加上 Type flag。例如,我們可以將 ’s’ 新增到 String,所以一個 String key “abc” 在 TiKV 裡面其實就是 “abcs”。

對於其他型別,我們可能需要考慮更多,譬如對於 Hash 型別,我們需要支援如下操作:

HSET key field1 value1
HSET key field2 value2
HLEN key

一個 Hash 會有很多 fields,我有時候想知道整個 Hash 的個數,所以對於 TiKV,我們不光需要將 Hash 的 key 和 field 合在一起變成 TiKV 的一個 key,也同時需要用另一個 key 來儲存整個 Hash 的長度,所以整個 Hash 的佈局類似:

key + ‘h’ -> length
key + ‘f’ + field1 -> value
key + ‘f’ + field2 -> value

如果我們不儲存 length,那麼如果我們想知道 Hash 的 length,每次都需要去掃整個 Hash 得到所有的 fields,這個其實並不高效。但如果我們用另一個 key 來儲存 length,任何時候,當我們加入一個新的 field,我們都需要去更新這個 length 的值,這也是一個開銷。對於我來說,我傾向於使用另一個 key 來儲存 length,因為 HLEN 是一個高頻的操作。

例子

作者構建了一個非常簡單的例子 ,裡面只支援 String 和 Hash 的一些操作,我們可以 clone 下來並編譯:

git clone  $GOPATH/src/github.com/siddontang/redis-tikv-examplecd $GOPATH/src/github.com/siddontang/redis-tikv-example
go build

在執行之前,我們需要啟動 TiKV,可以參考,然後執行:

./redis-tikv-example

這個例子會監聽埠 6380,然後我們可以用任意的 Redis 客戶端,譬如 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"

尾聲

現在已經有一些公司基於 TiKV 來構建了他們自己的 Redis Server,並且也有一個開源的專案 做了相同的事情。tidis 已經比較完善,如果你想替換自己的 Redis,可以嘗試一下。

正如同你所見,TiKV 其實算是一個基礎的元件,我們可以在它的上面構建很多其他的應用。如果你對我們現在做的事情感興趣,歡迎聯絡我:。





來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/2236/viewspace-2806665/,如需轉載,請註明出處,否則將追究法律責任。

相關文章