小米开源数据库<pegasus>简介

更好的样式前往 我的Github笔记 查看

<md文档排版不好>

数据模型

组合键:Table + HashKey + SortKey

  1. Table实现业务数据的隔离

  2. HashKey决定数据在那个分片

  3. SortKey决定数据在分片内的排序

一致性协议

使用PacificA协议,保证多副本数据的一致性。

<!-- addition -->

机器

Pegasus分布式集群至少需要准备这些机器:

  • MetaServer

    • link:Meta Server 的设计

    • 要求:2~3台机器,无需SSD盘。

    • 作用:用来保存表和表的分片信息。

  • ReplicaServer:

    • link:Replica Server 的设计

    • 要求至少3台机器,建议挂SSD盘。譬如一台服务器挂着8块或者12块SSD盘。这些机器要求是同构的,即具有相同的配置。

    • 作用:至少三台ReplicaServer。每一个ReplicaServer都是由多个Replica组成的。每一个Replica表示的是一个数据分片的Primary或Secondary。

  • Collector:可选角色,1台机器,无需SSD盘。该进程主要用于收集和汇总集群的统计信息,负载很小,建议放在MetaServer的其中一台机器上。

Replica

  • 一个数据分片对应3个Replica。

  • Replica有两个种类:Primary和Secondary。

  • 在一个数据分片对应的 3 个或以上的Replica中,只有1个Primary,其余的均是Secondary。

  • 很多个Replica组成一个ReplicaServer,同一个ReplicaServer中的Replica种类不相同。

    • 同一个ReplicaServer中的Replica不一定全是Primary,也不一定全是Secondary。

  • 一个最基本的Pegasus集群,最少需要 3 个ReplicaServer。

在Pegasus中,一个Replica有如下几种状态:

  • Primary

  • Secondary

  • PotentialSecondary(learner):

    • 当group中新添加一个成员时,在它补全完数据成为Secondary之前的状态

  • Inactive:

    • 和MetaServer断开连接时候的状态,或者在向MetaServer请求修改group的PartitionConfiguration时的状态

  • Error:

    • 当Replica发生IO或者逻辑错误时候的状态

写流程

写流程类似于两段提交:

  1. 客户端根据Key首先查询MetaServer,查询到这个Key对应的分片的对应的ReplicaServer。具体来说,客户端需要的其实是分片Primary所在的ReplicaServer。

  2. 客户端向Primary ReplicaServer发起写请求。

  3. Primary ReplicaServer向其对应的两台或以上的Secondary ReplicaServer复制数据。

  4. Secondary ReplicaServer将数据写入成功后,Primary ReplicaServer向客户端返回成功的响应。

可能导致ReplicaServer不能响应写请求的原因有:

  1. ReplicaServer无法向MetaServer持续汇报心跳,自动下线。

  2. Replica在IO上发生了一些无法恢复的异常故障,自动下线。

  3. MetaServer将Replica的Primary进行了迁移。

  4. Primary在和MetaServer进行group成员变更的操作,拒绝写。

  5. 当前Secondary个数太少,Replica出于安全性考虑拒绝写。

  6. 出于流控考虑而拒绝写。

读流程

只从Primary读。

宕机恢复

  • MetaServer和所有的ReplicaServer维持心跳。

  • 通过心跳来实现失败检测。

  • 宕机恢复的几种情况:

    • Primary Failover:如果某个分区的Primary所在的ReplicaServer宕机了,那么MetaServer 就会选择一个Secondary成为Primary。过后再添加Secondary。

    • Secondary Failover:如果某个分区的Secondary所在的ReplicaServer宕机了,那么暂时使用一主一副的机构继续提供服务。过后再添加Secondary。

    • MetaServer Failover:主MetaServer宕机了,备用的MetaServer通过zookeeper抢主成为新的主MetaServer。从zookeeper恢复状态,然后重新和所有ReplicaServer建立心跳。

  • 宕机恢复过程中,尽量避免数据的跨节点复制。

zookeeper抢主

<!-- zookeeper抢主 -->

link:zookeeper抢主

单机存储

一个ReplicaServer包括多个Replica,Replica使用RocksDB作为存储引擎:

  • 关闭掉了rocksdb的WAL。

  • PacificA对每条写请求都编了SequenceID,RocksDB对写请求也有内部的SequenceID。Pegasus对二者做了融合,来支持自定义的checkpoint的生成。

  • Pegasus给RocksDB添加了一些compaction filter以支持Pegasus的语义:例如某个value的TTL。

和很多一致性协议的实现一样,Pegasus中PacificA的实现也是和存储引擎解耦的。

RocksDB

<!-- RocksDB -->

link:RocksDB

数据安全

  • Table软删除

    • Table删除后,数据会保留一段时间,防止误删除

  • 元数据恢复

    • Zookeeper损坏时,从各ReplicaServer收集并重建元数据

  • 远程冷备份

    • 数据定期备份到异地,譬如HDFS或者金山云 • 在需要的时候可快速恢复

  • 跨机房同步

    • 在多个机房部署集群

    • 采用异步复制的方式同步数据

冷备份

Pegasus的冷备份功能用来将Pegasus中的数据定期生成快照文件,并备份到其他存储介质上,从而为数据容灾多提供一层保障。但由于备份的是某个时间点的数据快照文件,所以冷备份并不保证可以保留所有最新的数据,也就是说,恢复的时候可能会丢失最近一段时间的数据。

具体来看,冷备份过程要涉及到如下一些参数:

  • 存储介质(backup_provider):

    • 指其他的文件存储系统或服务,如本地文件系统或者HDFS。

  • 数据冷备份的周期(backup_interval):

    • 周期的长短决定了备份数据的覆盖范围。如果周期是1个月,那么恢复数据时,就可能只恢复一个月之前的数据。但如果周期设的太短,备份就会太频繁,从而使得备份开销很大。在小米内部,冷备份的周期通常是1天。

  • 保留的冷备份个数(backup_history_count):

    • 保留的备份个数越多,存储的空间开销就越大。在小米内部,一般保留最近的3个冷备份。

  • 进行冷备份的表的集合(backup_app_ids):

    • 并不是所有的表都值得进行冷备份。在小米内部,对于经常重灌全量数据的表,我们是不进行冷备份的。

在Pegasus中,以上这几个参数的组合称为一个冷备份策略(backup_policy)。数据的冷备份就行按照policy为单位进行的。

跨机房同步

link:跨机房同步文档

小米内部有些业务对服务可用性有较高要求,但又不堪每年数次机房故障的烦恼,于是向 pegasus 团队寻求帮助,希望在机房故障时,服务能够切换流量至备用机房而数据不致丢失。因为成本所限,在小米内部以双机房为主。

通常解决该问题有几种思路:

  1. 由 client 将数据同步写至两机房。这种方法较为低效,容易受跨机房专线带宽影响,并且延时高,同机房 1ms 内的写延时在跨机房下通常会放大到几十毫秒,优点是一致性强,但需要 client 实现。服务端的复杂度小,客户端的复杂度大。

  2. 使用 raft/paxos 协议进行 quorum write 实现机房间同步。这种做法需要至少 3 副本分别在 3 机房部署,延时较高但提供强一致性,因为要考虑跨集群的元信息管理,这是实现难度最大的一种方案。

  3. 在两机房下分别部署两个 pegasus 集群,集群间进行异步复制。机房 A 的数据可能会在 1 分钟后复制到机房 B,但 client 对此无感知,只感知机房 A。在机房 A 故障时,用户可以选择写机房 B。这种方案适合 最终一致性/弱一致性 要求的场景。后面会讲解我们如何实现 “最终一致性”。

基于实际业务需求考虑,我们选择方案3。

即使同样是做方案 3 的集群间异步同步,业内的做法也有不同:

  1. 各集群单副本:这种方案考虑到多集群已存在冗余的情况下,可以减少单集群内的副本数,同时既然一致性已没有保证,大可以索性脱离一致性协议,完全依赖于稳定的集群间网络,保证即使单机房宕机,损失的数据量也是仅仅几十毫秒内的请求量级。考虑机房数为 5 的时候,如果每个机房都是 3 副本,那么全量数据就是 3*5=15 副本,这时候简化为各集群单副本的方案就是几乎最自然的选择。

  2. 同步工具作为外部依赖使用:跨机房同步自然是尽可能不影响服务是最好,所以同步工具可以作为外部依赖部署,单纯访问节点磁盘的日志(WAL)并转发日志。这个方案对日志 GC 有前提条件,即日志不可以在同步完成前被删除,否则就丢数据了,但存储服务日志的 GC 是外部工具难以控制的。所以可以把日志强行保留一周以上,但缺点是磁盘空间的成本较大。同步工具作为外部依赖的优点在于稳定性强,不影响服务,缺点在于对服务的控制能力差,很难处理一些琐碎的一致性问题(后面会讲到),难以实现最终一致性

  3. 同步工具嵌入到服务内部:这种做法在工具稳定前会有一段阵痛期,即工具的稳定性影响服务的稳定性。但实现的灵活性肯定是最强的。

最初 Pegasus 的热备份方案借鉴于 HBase Replication,基本只考虑了第三种方案。而事实证明这种方案更容易保证 Pegasus 存储数据不丢的属性。

每个 replica (这里特指每个分片的 primary,注意 secondary 不负责热备份复制)独自复制自己的 private log 到远端,replica 之间互不影响。复制直接通过 pegasus client 来完成。每一条写入 A 的记录(如 set / multiset)都会通过 pegasus client 复制到 B。为了将热备份的写与常规写区别开,我们这里定义 duplicate_rpc 表示热备写。

A->B 的热备写,B 也同样会经由三副本的 PacificA 协议提交,并且写入 private log 中。这里有一个问题是,在 A,B 互相同步的场景,一份写操作将形成循环:A->B->A,同样的写会无数次地被重放。为了避免循环写,我们引入 cluster id 的概念,每条 duplicate_rpc 都会标记发送者的 cluster id。

[duplication-group]
A=1
B=2
void set(String tableName, byte[] hashKey, byte[] sortKey, byte[] value, int ttlSeconds)

直接使用pegasus优化,直接对pegasus读、写。可以替代redis缓存的架构。

  1. 读写逻辑复杂

  2. 要特意维护数据一致性

  3. 服务可用性不高

  4. 机器成本高

问题:

读取:先读取缓存,如果缓存中不存在,那么再读取数据库中的数据。

写入:双写,既写入缓存、也要写入数据库。

原先:Redis 作为缓存 + HBase/mysql/MongoDB 作为数据库。

业务应用

 HashKeySortKeyValue
map MapId key value
set SetId key null
list ListId index value

Pegasus本身不支持容器类型,但是其HashKey + SortKey的数据模型可以模拟容器。

容器支持

对HashKey或者SortKey进行字符串匹配, 只有符合条件的结果才会返回。对HashKey或者SortKey进行字符串匹配,只有符合条件的

条件过滤

同一个HashKey的数据写入同一个Replica,同一个Replica的操作,在同一个线程中串行执行。这样就避免了同步的问题。

对同一个HashKey的写操作,保证总是原子的,包括set、multiSet、del、multiDel、incr、 checkAndSet。

单行事务

支持对数据指定过期时间, 数据过期后就无法读取到。

TTL过期策略
  1. 线程安全

    • 所有的接口都是线程安全的,不用担心多线程的问题。

  2. 并发性能

    • 客户端底层使用异步的方式实现,可以支持大的并发,不用担心性能的问题。

  3. Client单例

    • 通过 getSingletonClient() 获得的Client是单例, 可以重复使用。

  4. 翻页功能

    • 通过客户端提供的接口,能够轻松实现数据翻页功能 。

Java客户端

集群使用falcon进行监控。

集群监控

使用

热备份同时也需要容忍在 replica 主备切换下复制的进度不会丢失,例如当前 replica1 复制到日志 decree=5001,此时发生主备切换,我们不想看到 replica1 从 0 开始,所以为了能够支持 断点续传,我们引入 confirmed_decree。replica 定期向 meta 汇报当前进度(如 confirmed_decree = 5001),一旦meta将该进度持久化至 zookeeper,当replica故障恢复时即可安全地从 5001重新开始热备份。

所以当 B 重放某条 duplicate_rpc 时,发现其 cluster_id = 1,识别到这是一条发自 A 的热备写,则不会将它再发往 A。

posted @ 2019-12-21 21:52  小徐同学是个coder  阅读(...)  评论(...编辑  收藏