不久之前,FoundationDB (后面用 fdb 简化) 重新开源,对于大家来说,这真的是一个非常好的消息。我也在第一时间下载了 fdb 的源码,开始研究,一方面是看我们能在什么方面能够借鉴,另一方面也是需要给一些朋友回答,TiKV 到底跟 fdb 有什么不一样这样的问题。
关于 fdb 的研究,自己预计会有几篇,这次是第一篇,来聊聊我最关注的一个问题 - fdb 是如何实现分布式事务的。
关键组件
在开始介绍之前,要先说说 fdb 的关键组件。
1. Coordinators
所有的 clients 和 servers 都是通过 cluster file 来连接到 fdb cluster,而这个 cluster file 就包含的是 coordinators 的 IP:PORT 列表。所有的 clients 和 servers 都会使用 coordinators 连接到 cluster controller。
2. Cluster controller
Cluster controller 是通过选举产生的(fdb 貌似使用的是 Paxos,这个后面会详细研究一下)。Cluster Controller 就是控制整个集群的,它是所有进程的入口,会监控进程是否挂掉,告诉某个进程相关的 role,以及在所有进程之间传递系统的信息。Clients 也通过 cluster controller 来实时的同步最新的 proxies。
3. Master
Master 主要是用来协调写子系统的,一个写子系统包括 master,proxies,resolvers 和 transaction logs。Proxy,resolver 和 transaction log 是一个整体单元,如果任意一个失败了,那么我们就会重新找一个来将他们全部替换。Master 会给 proxies 分配 commit versions,对数据进行分布,以及全局的流速控制。
4. Proxies
Proxies 会提供 read versions,提交事务以及跟踪 storage servers 的 key ranges。如果要提供一个 read version,一个 proxy 会问其他所有的 proxies 当前最大的 committed version,并且同步的检查 transaction logs 并没有被停止。Master 流速控制可能会减缓提供 read versions 的频率。
对于一次事务提交,当只有下面操作全部完成,才能认为成功:
- 从 master 得到一个 commit version
- 使用 resolvers 来确定当前事务并没有跟之前已经提交的事务冲突
- 让 transaction 持久化到 transaction logs
所有以 xff 开头的 key 是系统保留前缀,用来存放系统的元信息。任何对这段 key range 的修改都会 通过 resolvers 同步到所有的 proxies。元信息包括数据的 key ranges 以及哪些 storage servers 有这些 range,其实也就是数据的路由表了。Proxies 也给 clients 提供相关的信息,让 clients 进行缓存,如果缓存缺失,就从 proxies 重新更新。
5. Transaction Logs
Transaction logs 会按照 version 的顺序接受 proxy 发过来的提交,并会使用 append only 的方式将修改的提交持久化到硬盘。在数据被写入到磁盘的时候,也会通知 storage servers 有相关的修改操作,让 storage servers 去获取并且 apply 到 storage servers 里面。
6. Resolvers
Resolvers 用来确定不同事务的冲突。当一个事务的 read version,读取了一个 key,在 commit 之前,另一个事务写入了新的值,这时候就会有冲突。 Resovler 会在内存里面保存 5s 的所有写入提交,用来判断冲突,这也就是意味着,fdb 的事务执行时间不能超过 5s。
7. Storage Servers
Storage servers 就是存放数据的地方,fdb 会将数据按照 range 切分,存储到不同的 storage servers 上面。Storage servers 会在内存里面保存最近 5s 的修改(Versioned data),如果一个 client 的 read version 超过了 5s,那就会过期出错了。Storage server 有 ssd 和 memory 两种,ssd 其实用的是 sqlite3。
流程
上面大概介绍了 fdb 的关键组件,这里就先来说说事务。Clients 会先用一个 read version 读取所有的数据,然后在本地修改,最后再将所有的修改一起提交到 proxies,这其实也就是一个乐观事务模型。具体流程如下:
- 开始事务
- Clients 从 proxy 获取一个 read version
- Proxy 会批量接受 clients 的请求,如果超过了限流控制,额外的请求会排队
- Proxy 问其它的 proxies 当前最大的 commit version
- Proxy 返回最大的 commit version 作为 read version
- 读流程
- Client 根据 read version 以及需要访问的数据的 key 或者 key range 找到对应的 storage servers。Storage server 接受到之后,如果发现 version 太老,结果返回错误。如果发现数据还不存在,就等或者超时
- Storage server 会根据 read version 找对应的数据,并返回给 client
- 提交事务
- Client 将修改,read version 以及 read ranges 和 write ranges 提交给 proxy
- Proxy 仍然是批量的接受请求
- Proxy 将 range 切分并且发到不同的 resolvers,如果 resolver 判断有冲突,结束事务
- Proxy 通过 master 得到最近的 commit version
- Proxy 将修改的数据按照实际的数据分布切分,加上 tag,推送到 transaction log servers
- Transaction log servers 回复 proxy 说 log 已经落盘
- Proxy 给 client 返回事务提交成功
可以看到,整个流程还是很简单的,这里还需要注意几个后台流程。一个是 storage server 从 transaction logs 读取数据:
- 根据提供的 version 和 tag 从 transaction logs 拿数据
- 将数据读入到 storage server 的 Versioned data
- 将数据写入 storage engine
另一个就是 version 的更新,proxies 会定期的生成一个空的 commit request 来增加 commit version,这样 transaction logs 和 storage servers 的 version 都能增加,就能处理一个集群如果没有任何写入,后面新的读取也能按照 version 读到对应的版本,不会无限制的等待。如果我的 read version 比当前 storage server 的最大 version 要大,其实并不能保证读到正确的数据。为啥会做这个,主要是 fdb 用的时间戳来当的 version。
小结
上面仅仅是对 fdb 事务流程的简单介绍,几个 concern 的点:
- Proxy 会跟其他的 proxies 交互问最大的 commit version,如果 proxy 多了会不会有性能问题
- Resolver 如果 range 太多会不会也有性能问题
可以看到,fdb 在 resolver 那边其实就是将事务排队了,所以虽然外面看起来是乐观事务,但对于冲突严重的情况,性能也比较不错。之前我一直以为 resovler 会是个单点,但后面知道 resolver 也是可以 scale 的。而且 fdb 自己也说做了很多的优化,保证了整个的性能。
后面我会详尽的捣鼓折腾下 FoundationDB,做下 benchmark,也正在将它集成到我们的 YCSB 里面,毕竟对我来说,至少 fdb 那套 deterministic 理念是可以借鉴学习的。如果你对我们相关的 TiKV 工作感兴趣,欢迎联系我 tl@pingcap.com。
Read full article from FoundationDB 学习 - 事务流程
No comments:
Post a Comment