使用 TiKV 构建分布式类 Redis 服务

什么是 Redis

Redis 是一个开源的,高性能的,支持多种数据结构的内存数据库,已经被普遍用于数据库,缓存,消息队列等领域。它有着丰富的数据结构支持,譬如 String,Hash,Set 和 Sorted Set,用户经过它们能构建本身的高性能应用。git

Redis 很是快,没准是世界上最快的数据库了,它虽然使用内存,但也提供了一些持久化机制以及异步复制机制来保证数据的安全。github

Redis 的不足

Redis 很是酷,但它也有一些问题:redis

  1. 内存很贵,并且并非无限容量的,因此咱们不可能将大量的数据存放到一台机器。
  2. 异步复制并不能保证 Redis 的数据安全。
  3. Redis 提供了 transaction mode,但其实并不知足 ACID 特性。
  4. Redis 提供了集群支持,但也不能支持跨多个节点的分布式事务。

因此有时候,咱们须要一个更强大的数据库,虽然在延迟上面可能赶不上 Redis,但也有足够多的特性,譬如:数据库

  1. 丰富的数据结构
  2. 高吞吐,能接受的延迟
  3. 强数据一致
  4. 水平扩展
  5. 分布式事务

为何选择 TiKV

大约 4 年前,我开始解决上面提到的 Redis 遇到的一些问题。为了让数据持久化,最直观的作法就是将数据保存到硬盘上面,而不是在内存里面。因此我开发了 LedisDB,一个使用 Redis 协议,提供丰富数据结构,但将数据放在 RocksDB 的数据库。LedisDB 并非彻底兼容 Redis,因此后来,我和其余同事继续建立了 RebornDB,一个彻底兼容 Redis 的数据库。
不管是 LedisDB 仍是 RebornDB,由于他们都是将数据放在硬盘,因此能存储更大量的数据。但它们仍然不能提供 ACID 的支持,另外,虽然咱们能够经过 codis 去提供集群的支持,咱们也不能很好的支持全局的分布式事务。缓存

因此咱们须要另外一种方式,幸运的是,咱们有 TiKV安全

TiKV 是一个高性能,支持分布式事务的 key-value 数据库。虽然它仅仅提供了简单的 key-value API,但基于 key-value,咱们能够构造本身的逻辑去建立更强大的应用。譬如,咱们就构建了 TiDB ,一个基于 TiKV 的,兼容 MySQL 的分布式关系型数据库。TiDB 经过将 database 的 schema 映射到 key-value 来支持了相关 SQL 特性。因此对于 Redis,咱们也能够采用一样的办法 - 构建一个支持 Redis 协议的服务,将 Redis 的数据结构映射到 key-value 上面。数据结构

如何开始

整个架构很是简单,咱们仅仅须要作的就是构建一个 Redis 的 Proxy,这个 Proxy 会解析 Redis 协议,而后将 Redis 的数据结构映射到 key-value 上面。架构

Redis Protocol

Redis 协议被叫作 RESP(Redis Serialization Protocol),它是文本类型的,可读性比较好,而且易于解析。它使用 “rn” 做为每行的分隔符而且用不一样的前缀来表明不一样的类型。例如,对于简单的 String,第一个字节是 “+”,因此一个 “OK” 行就是 “+OKrn”。
大多数时候,客户端会使用最通用的 Request-Response 模型用于跟 Redis 进行交互。客户端会首先发送一个请求,而后等待 Redis返回结果。请求是一个 Array,Array 里面元素都是 bulk strings,而返回值则多是任意的 RESP 类型。Redis 一样支持其余通信方式:异步

Pipeline - 这种模式下面客户端会持续的给 Redis 发送多个请求,而后等待 Redis 返回一个结果。
Push - 客户端会在 Redis 上面订阅一个 channel,而后客户端就会从这个 channel 上面持续受到 Redis push 的数据。分布式

下面是一个简单的客户端发送 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

客户端会发送一个带有两个 bulk string 的 array,第一个 bulk string 的长度是 4,而第二个则是 6。Redis 会返回一个 48293 整数。正如你所见,RESP 很是简单,天然而然的,写一个 RESP 的解析器也是很是容易的。

做者建立了一个 Go 的库 goredis,基于这个库,咱们能很是容易的从链接上面解析出 RESP,一个简单的例子:

// 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()

函数 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 transaction
txn.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 是一个高频的操做。

例子

做者构建了一个很是简单的例子 example ,里面只支持 String 和 Hash 的一些操做,咱们能够 clone 下来并编译:

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

在运行以前,咱们须要启动 TiKV,能够参考 instruction,而后执行:

./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 作了相同的事情。tidis 已经比较完善,若是你想替换本身的 Redis,能够尝试一下。

正如同你所见,TiKV 其实算是一个基础的组件,咱们能够在它的上面构建不少其余的应用。若是你对咱们如今作的事情感兴趣,欢迎联系我:tl@pingcap.com。

做者:唐刘
原文连接: https://www.jianshu.com/p/b4dee8372d8d

相关文章
相关标签/搜索