├── .gitignore ├── .nojekyll ├── 01~存储基础 ├── BFT │ └── README.md └── README.md ├── 02~复制 ├── README.md ├── 主从复制 │ ├── README.md │ ├── 同步与异步.md │ ├── 复制延迟.md │ ├── 复制日志.md │ └── 宕机恢复.md ├── 多主复制 │ ├── CRDT.md │ ├── CRDT │ │ ├── 99~参考资料 │ │ │ ├── 2023-List CRDT Benchmarks.md │ │ │ ├── 2023~A Gentle Introduction to CRDTs.md │ │ │ └── 2023~An Interactive Intro to CRDTs.md │ │ └── CRDT 框架 │ │ │ └── Loro │ │ │ └── 99~参考资料 │ │ │ └── 2023~Introduction to Loro's Rich Text CRDT.md │ ├── README.md │ ├── 冲突解决.md │ └── 多主复制拓扑.md └── 无主复制 │ ├── README.md │ ├── 故障与仲裁.md │ └── 检测并发写入.md ├── 03~数据分片 ├── README.md ├── 一致性哈希 │ ├── Go.md │ ├── Java.md │ └── README.md ├── 元数据与调度 │ ├── README.md │ └── 请求路由.md └── 分片策略 │ ├── README.md │ ├── 分片再平衡.md │ ├── 次级索引.md │ └── 键的分片.md ├── 04~分布式 ID ├── ID 无序编码 │ └── README.md ├── Leaf.md ├── README.md ├── Snowflake.md ├── UUID.md └── 库自增 ID.md ├── 10~KV 存储 ├── Consul │ └── README.md ├── Etcd │ └── README.md ├── README.md ├── ZooKeeper │ ├── README.md │ ├── 基础概念 │ │ ├── ZAB.md │ │ ├── 应用场景.md │ │ └── 核心角色.md │ ├── 架构原理 │ │ └── 架构原理.md │ └── 部署与操作 │ │ ├── ACL.md │ │ ├── Apache Curator.md │ │ ├── 数据操作.md │ │ └── 部署与配置.md └── 配置方式对比 │ └── 基础配置方式.md ├── 20~分布式文件系统 ├── HDFS │ ├── HDFS 源代码分析.md │ ├── HDFS 编程.md │ ├── HDFS 读取原理.md │ └── README.md └── README.md ├── 30~对象存储 ├── Haystack.md ├── README.md └── 元数据管理.md ├── 40~区块链 └── README.link ├── INTRODUCTION.md ├── LICENSE ├── README.md ├── _sidebar.md ├── header.svg └── index.html /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore all 2 | * 3 | 4 | # Unignore all with extensions 5 | !*.* 6 | 7 | # Unignore all dirs 8 | !*/ 9 | 10 | .DS_Store 11 | 12 | # Logs 13 | logs 14 | *.log 15 | npm-debug.log* 16 | yarn-debug.log* 17 | yarn-error.log* 18 | 19 | # Runtime data 20 | pids 21 | *.pid 22 | *.seed 23 | *.pid.lock 24 | 25 | # Directory for instrumented libs generated by jscoverage/JSCover 26 | lib-cov 27 | 28 | # Coverage directory used by tools like istanbul 29 | coverage 30 | 31 | # nyc test coverage 32 | .nyc_output 33 | 34 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 35 | .grunt 36 | 37 | # Bower dependency directory (https://bower.io/) 38 | bower_components 39 | 40 | # node-waf configuration 41 | .lock-wscript 42 | 43 | # Compiled binary addons (https://nodejs.org/api/addons.html) 44 | build/Release 45 | 46 | # Dependency directories 47 | node_modules/ 48 | jspm_packages/ 49 | 50 | # TypeScript v1 declaration files 51 | typings/ 52 | 53 | # Optional npm cache directory 54 | .npm 55 | 56 | # Optional eslint cache 57 | .eslintcache 58 | 59 | # Optional REPL history 60 | .node_repl_history 61 | 62 | # Output of 'npm pack' 63 | *.tgz 64 | 65 | # Yarn Integrity file 66 | .yarn-integrity 67 | 68 | # dotenv environment variables file 69 | .env 70 | 71 | # next.js build output 72 | .next 73 | -------------------------------------------------------------------------------- /.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wx-chevalier/DistributedStorage-Notes/81c3489826e753c4420dd287e27ffe9e8c229341/.nojekyll -------------------------------------------------------------------------------- /01~存储基础/BFT/README.md: -------------------------------------------------------------------------------- 1 | # BFT 2 | -------------------------------------------------------------------------------- /01~存储基础/README.md: -------------------------------------------------------------------------------- 1 | # 分布式存储基础 2 | 3 | # 存储类型 4 | 5 | 存储的方式大致可以分为三类,对象存储(Object Storage)、块存储(Block Storage)和文件存储(File Storage)。不同的存储类型有各自的特点和优点,在选择的时候,我们需要根据具体情况来决定最合适的存储类型。 6 | 7 | | 比较项目 | 块存储 | 对象存储 | 文件存储 | 8 | | :------------------- | :------------------------------- | :----------------------- | :------------- | 9 | | 基础单位 | fixed-size block | object | file | 10 | | 支持 in-place update | 支持 | 否 | 支持 | 11 | | protocol | SCSI, Fibre Channel, SATA | REST and SOAP over HTTP | CIFS and NFS | 12 | | 性能 | 高 | 中等 | 高 | 13 | | 元数据 | 少量 | 大量 | 中等 | 14 | | 适用场景 | 交易型数据(经常需要变动的数据) | 相对静止(static)的数据 | 共享的文件 | 15 | | 最大优点 | 高性能 | 可扩展性强、分布式使用 | 相对简化地管理 | 16 | 17 | ![不同存储类型之间的对比](https://assets.ng-tech.icu/item/20230416204611.png) 18 | 19 | 块存储在低延时高 IOPS 上有明显优势,特别适合数据库等高性能 IO 场景;对象存储在海量和吞吐上有明显优势,特别适合互联网图片、音视频处理场景;文件存储在吞吐和共享上有明显优势,特别适合高性能计算、以及数据共享场景。 20 | 21 | ## 块存储 22 | 23 | 块存储,简单来说就是使用块设备为系统提供存储服务。块存储分多种类型,有单机块存储,网络存储(如 NAS,SAN 等),分布式块存储(目前主流的如 AWS 的 EBS,青云的云硬盘,阿里云的云磁盘,网易云硬盘等)。通常块存储的表现形式就是一块设备,用户看到的就是类似于 sda,sdb 这样的逻辑设备。 24 | 25 | 块存储,不论具体存储内容的大小,都会将它存储到一个固定大小的容器中。块存储的主要特点是可以抽象成块设备,也就是大家常见的云盘。可以理解块存储为将个人用品收纳在一个固定大小的盒子内。如果我们需要寻找某件衣服,我们需要将整个盒子打开,然后一件一件地寻找。这里的盒子就相当于一个 block,这个盒子内部的空间是连续的,而且怎样摆放衣物也是不固定的(相反地,如果使用抽屉,我们就需要按照抽屉的内部构造来摆放衣物,因为抽屉会有棱棱角角,也可能会有内部的多层结构) 26 | 27 | Block 就是一段连续的、没有硬性结构(unstructured)的比特(bytes)。通常情况下,考虑到数据存储的安全性和抗灾性,存储内容通常会被拆分成多个部分然后存储到不同的 Block 中,这样的设计同时也方便了更新过程。用户只需要去访问存储在某个 Block 中的数据信息,而非整个存储内容。 28 | 29 | ## 文件存储 30 | 31 | 文件存储,一般基于操作系统之上,其基本单位变成单个文件。我们最熟悉的个人电脑就将单个文件进行存储。在查找的时候,我们会通过路径来查找某个文件。通常情况下,对象会包含多个文件,我们可以理解为一个对象由多个文件组成,其中每个文件代表了一个小的部件,存储内容的时候我们会将这几个文件打包一并存储。 32 | 33 | ## 对象存储 34 | 35 | 对象存储,可以理解为将整个物体或者存储对象存储起来。一般来说,对象存储会同时存储大量元数据(meta data)以方便查找。通常来说,我们会把不同的衣物放进不同的抽屉或者同一抽屉的不同结构中,然后给它们贴上不同的标签。等到需要查找某个物件比如领带的时候,我们可以直接找到某一抽屉的固定地方,这样的设计简化了我们的查找过程。对象存储适合存储大量的小文件。 36 | 37 | # 不同存储的应用场景 38 | 39 | ## 分布式数据库 40 | 41 | 在分析分布式数据库之前,我们首先需要来分析下在什么情况下应该使用分布式数据库。虽然分布式数据库看起来是一个很酷炫,而且是可以解决一切大量存储,读取的完美解决方案,但是不可避免的,在实现方式上要比单机数据库麻烦一些。那么什么时候需要考虑分布式数据库呢?根据以往的经验,如果你的系统/应用比以下规模要小,那基本不用考虑分布式数据库。 42 | 43 | - 企业级方面:个人接触过的企业级应用,其作为数据仓库,每天从数十个系统导入数据,同时又分发到数十个系统。这样规模的系统,运行了 7,8 年,也仅仅是一个 Oracle 足够了。当然它的实时性要求不高。其他的系统,大量数据的也有用到 Oracle, DB2 cluster(一般是 2 个服务器),运行 10 多年也足够了。对于企业级数据库,例如 Oracle,SQL-Server,DB2 等,都有各自的 cluster(集群)解决方案。不过 cluster 并非真正意义上的分布式数据库,仅仅是解决高可用性(HA)下数据库的负载均衡问题。最大的缺点就是每个数据库都是冗余的。所谓冗余,就是每个数据库的数据都是一模一样的,正因为一模一样,所以可以很简单的解决负载问题。但是数据量上升到一定程度,对集群中的每个数据库同样会造成很大的压力。虽然如此,但正如上面“是否使用分布式数据库”,你的系统/应用运行 10 年,可能都不会遇到这样的问题:因为企业级数据库的性能很高,也很容易通过硬件来扩展性能。夸张点说,甚至有的人整个 IT 职业生涯,都不会遇到必须使用分布式数据库的场合。 44 | 45 | - 互联网方面:虽然没有什么经验,但是类似 iteye.com 这样的规模,根据其前站长分享的文章,也仅仅使用了缓存,没有使用分布式数据库。所以,如果你的系统/应用从长远来看,比以上两个规模都小,或者相当的话,是不用考虑分布式数据库的。 46 | 47 | ### 优势 48 | 49 | 单机房一旦死机,断电、维护根本无法挽回整个数据,想离线读取等都不行。当一个机房不可用,所有的业务就都不可用。荔枝 FM 要求业务离用户最近,南方的用户连南方的机房,北方的用户连北方的机房,国外的用户连国外的机房。大陆的网络和国外的网络有一定的隔离性,如果没有做多机房的连通性,数据的传输和实时性就会有问题。 50 | 51 | - 数据安全:机房不可用时数据无法访问 52 | - 业务可用性:机房不可用时业务停止 53 | - 用户响应:因为网络连通性,不同地域的用户的请求响应延迟不同,而多机房架构下,一个机房的数据放在另一个机房是异地多活。上面是数据容灾,下面的是业务容灾,第三个是让服务离用户最近。这是荔枝 FM 做跨机房的原因。 54 | -------------------------------------------------------------------------------- /02~复制/README.md: -------------------------------------------------------------------------------- 1 | # 复制(Replication) 2 | 3 | 复制意味着在通过网络连接的多台机器上保留相同数据的副本,我们希望能复制数据,可能出于各种各样的原因: 4 | 5 | - 高可用性:即使在一台机器(或多台机器,或整个数据中心)停机的情况下也能保持系统正常运行 6 | 7 | - 断开连接的操作:允许应用程序在网络中断时继续工作 8 | 9 | - 延迟:将数据放置在距离用户较近的地方,以便用户能够更快地与其交互 10 | 11 | - 可扩展性:能够处理比单个机器更高的读取量可以通过对副本进行读取来处理 12 | 13 | 如果复制中的数据不会随时间而改变,那复制就很简单:将数据复制到每个节点一次就万事大吉。复制的困难之处在于处理复制数据的变更(change),目前三种流行的变更复制算法:单领导者(single leader),多领导者(multi leader)和无领导者(leaderless)。 14 | 15 | - 单主复制:客户端将所有写入操作发送到单个节点(领导者),该节点将数据更改事件流发送到其他副本(追随者)。读取可以在任何副本上执行,但从追随者读取可能是陈旧的。 16 | 17 | - 多主复制:客户端发送每个写入到几个领导节点之一,其中任何一个都可以接受写入。领导者将数据更改事件流发送给彼此以及任何跟随者节点。 18 | 19 | - 无主复制:客户端发送每个写入到几个节点,并从多个节点并行读取,以检测和纠正具有陈旧数据的节点每种方法都有优点和缺点。单主复制是非常流行的,因为它很容易理解,不需要担心冲突解决。在出现故障节点,网络中断和延迟峰值的情况下,多领导者和无领导者复制可以更加稳健,但以更难以推理并仅提供非常弱的一致性保证为代价。 20 | 21 | 复制可以是同步的,也可以是异步的,在发生故障时对系统行为有深远的影响。尽管在系统运行平稳时异步复制速度很快,但是在复制滞后增加和服务器故障时要弄清楚会发生什么,这一点很重要。如果一个领导者失败了,并且你推动一个异步更新的追随者成为新的领导者,那么最近承诺的数据可能会丢失。我们研究了一些可能由复制滞后引起的奇怪效应,我们讨论了一些有助于决定应用程序在复制滞后时的行为的一致性模型: 22 | 23 | - 写后读:用户应该总是看到自己提交的数据。 24 | 25 | - 单调读:用户在一个时间点看到数据后,他们不应该在某个早期时间点看到数据。 26 | 27 | - 一致前缀读:用户应该将数据视为具有因果意义的状态:例如,按照正确的顺序查看问题及其答复。 28 | 29 | ## 复制与分区 30 | 31 | 复制即在几个不同的节点上保存数据的相同副本,可能放在不同的位置。复制提供了冗余:如果一些节点不可用,剩余的节点仍然可以提供数据服务。分区 (Partition/Shard)则是将一个大型数据库拆分成较小的子集,从而不同的分区可以指派给不同的节点(node)。 32 | 33 | 复制和分区是不同的机制,但它们经常同时使用。 34 | 35 | ![一个数据库切分为两个分区,每个分区都有两个副本](https://s2.ax1x.com/2020/02/07/12KwQO.md.png) 36 | 37 | # 复制与一致性 38 | 39 | 由于采用多机器进行分布式部署的方式提供服务,必然存在着数据的复制。分布式系统的数据复制需求能够保证系统的可用性与性能:将数据复制到分布式部署的多台机器中,可以消除单点故障,防止系统由于某台(些)机器宕机导致的不可用;而通过负载均衡技术,能够让分布在不同地方的数据副本全都对外提供服务。有效提高系统性能。 40 | 41 | 在分布式系统引入复制机制后,不同的数据节点之间由于网络延时等原因很容易产生数据不一致的情况;如果你在同一时刻查看两个数据库节点,则可能在两个节点上看到不同的数据,因为写请求在不同的时间到达不同的节点。无论数据库使用何种复制方法(单主复制,多主复制或无主复制),都会出现这些不一致情况。复制机制的目的是为了保证数据的一致性。但是数据复制面临的主要难题也是如何保证多个副本之间的分布式一致性。如何能既保证分布式一致性,又保证系统的性能,是每一个分布式系统都需要重点考虑和权衡的。在实际的分布式系统中,我们常常会面临以下具体的场景: 42 | 43 | - 比如在集中式系统中,有一些关键的配置信息,可以直接保存在服务器的内存中,但是在分布式系统中,如何保存这些配置信息,又如何保证所有机器上的配置信息都保持一致,又如何保证修改一个配置能够把这次修改同步到所有机器中,就是存在的问题。 44 | - 在集中式系统中,进行一个同步操作要写同一个数据的时候,可以直接使用事务+锁来管理保证数据的 ACID。但是,在分布式系统中如何保证多台机器不会同时写同一条数据。 45 | 46 | 大多数复制的数据库至少提供了最终一致性,这意味着如果你停止向数据库写入数据并等待一段不确定的时间,那么最终所有的读取请求都会返回相同的值。换句话说,不一致性是暂时的,最终会自行解决(假设网络中的任何故障最终都会被修复)。最终一致性的一个更好的名字可能是收敛(convergence),因为我们预计所有的复本最终会收敛到相同的值。然而,这是一个非常弱的保证,它并没有说什么什么时候副本会收敛。在收敛之前,读操作可能会返回任何东西或什么都没有。例如,如果你写入了一个值,然后立即再次读取,这并不能保证你能看到刚跟写入的值,因为读请求可能会被路由到另外的副本上。 47 | 48 | ## 如何保证一致性 49 | 50 | 实现分布式系统一致性,首先需要考虑的就是分布式时钟,我们需要解决不同节点之间的标准时间问题。从最初的网络时间协议(NTP)到逻辑时钟到向量时钟,较好地解决了不同节点间的顺序问题。CALM 原则的全称是 Consistency and Logical Monotonicity,主要描述的是分布式系统中单调逻辑与一致性的关系: 51 | 52 | - 在分布式系统中,单调的逻辑都能保证最终一致性,这个过程中不需要依赖中心节点的调度。 53 | - 任意分布式系统,如果所有的非单调逻辑都有中心节点调度,那么这个分布式系统就可以实现最终一致性。 54 | 55 | 接下来我们需要考虑合适的数据结构进行节点间的数据同步与合并,这也是一致性算法的前提,设计良好的数据结构加上精妙的算法可以高效的解决现实的问题。分布式系统的数据结构 CRDT(Conflict-Free Replicated Data Types) 即是分布式系统中被广泛采用的数据结构: 56 | 57 | - 基于状态(state-based):即将各个节点之间的 CRDT 数据直接进行合并,所有节点都能最终合并到同一个状态,数据合并的顺序不会影响到最终的结果。 58 | - 基于操作(operation-based):将每一次对数据的操作通知给其他节点。只要节点知道了对数据的所有操作(收到操作的顺序可以是任意的),就能合并到同一个状态。 59 | 60 | 最后需要来关注一下分布式系统的一些重要的协议 HATs(Highly Available Transactions),ZAB(Zookeeper Atomic Broadcast),这些往往是基于业界主流的一致性算法,譬如 Paxos, Raft 以及 Gossip 等。 61 | -------------------------------------------------------------------------------- /02~复制/主从复制/README.md: -------------------------------------------------------------------------------- 1 | # 主从复制 2 | 3 | 存储数据库副本的每个节点称为 副本(replica)。当存在多个副本时,会不可避免的出现一个问题:如何确保所有数据都落在了所有的副本上?每一次向数据库的写入操作都需要传播到所有副本上,否则副本就会包含不一样的数据。最常见的解决方案被称为基于领导者的复制(leader-based replication)(也称主动/被动(active/passive)或主/从(master/slave)复制),它的工作原理如下: 4 | 5 | - 副本之一被指定为 领导者(leader),也称为 主库(master|primary)。当客户端要向数据库写入时,它必须将请求发送给领导者,领导者会将新数据写入其本地存储。 6 | 7 | - 其他副本被称为追随者(followers),亦称为只读副本(read replicas),从库(slaves),备库(sencondaries),热备(hot-standby)。每当领导者将新数据写入本地存储时,它也会将数据变更发送给所有的追随者,称之为复制日志(replication log)记录或变更流(change stream)。每个跟随者从领导者拉取日志,并相应更新其本地数据库副本,方法是按照领导者处理的相同顺序应用所有写入。 8 | 9 | - 当客户想要从数据库中读取数据时,它可以向领导者或追随者查询但只有领导者才能接受写操作(从客户端的角度来看从库都是只读的)。 10 | 11 | ![基于领导者(主-从)的复制](https://s2.ax1x.com/2020/02/08/1WwR1O.png) 12 | 13 | 这种复制模式是许多关系数据库的内置功能,如 PostgreSQL(从 9.0 版本开始),MySQL,Oracle Data Guard 和 SQL Server 的 AlwaysOn 可用性组它也被用于一些非关系数据库,包括 MongoDB,RethinkDB 和 Espresso 最后,基于领导者的复制并不仅限于数据库:像 Kafka 和 RabbitMQ 高可用队列这样的分布式消息代理也使用它某些网络文件系统,例如 DRBD 这样的块复制设备也与之类似。 14 | -------------------------------------------------------------------------------- /02~复制/主从复制/同步与异步.md: -------------------------------------------------------------------------------- 1 | # 同步复制与异步复制 2 | 3 | 复制系统的一个重要细节是:复制是同步(synchronously)发生还是异步(asynchronously)发生,在关系型数据库中这通常是一个配置项,其他系统通常硬编码为其中一个。主从之间可以是异步复制,也可以是同步复制。例如 MySQL,在默认情况下采用异步复制。 4 | 5 | 譬如网站的用户更新他们的个人头像。在某个时间点,客户向主库发送更新请求;不久之后主库就收到了请求。在某个时刻,主库又会将数据变更转发给自己的从库。最后,主库通知客户更新成功。下图显示了系统各个组件之间的通信:用户客户端,主库和两个从库。时间从左到右流动。请求或响应消息用粗箭头表示。 6 | 7 | ![基于领导者的复制:一个同步从库和一个异步从库](https://s2.ax1x.com/2020/02/08/1WsVRx.png) 8 | 9 | 上图中,从库 1 的复制是同步的:在向用户报告写入成功,并使结果对其他用户可见之前,主库需要等待从库 1 的确认,确保从库 1 已经收到写入操作。以及在使写入对其他客户端可见之前接收到写入。跟随者 2 的复制是异步的:主库发送消息,但不等待从库的响应。从库 2 处理消息前存在一个显著的延迟。通常情况下,复制的速度相当快:大多数数据库系统能在一秒向从库应用变更,但它们不能提供复制用时的保证。有些情况下,从库可能落后主库几分钟或更久;例如:从库正在从故障中恢复,系统在最大容量附近运行,或者如果节点间存在网络问题。 10 | 11 | # 同步与异步的对比 12 | 13 | 同步复制的优点是,从库保证有与主库一致的最新数据副本。异步复制容易引起数据丢失。比如主从结构中,主节点的写入请求还没有复制到从节点就挂了,当从节点被选为新的主节点之后,在这之前写入没有同步的数据就会被丢失。虽然即便采用了同步复制,也只能提供相对较弱的基本保障。其他情况譬如主接收写入请求然后发到从节点,从节点写入成功后并发送确认给主,如果此时主节点正准备发送确认信息给客户端时挂了,那么客户端就会认为提交失败,可是从节点已经提交成功了,如果这是从节点被提升为主,那么就出现问题了。 14 | 15 | 同步复制的缺点是如果同步从库没有响应(比如它已经崩溃,或者出现网络故障,或其它任何原因),主库就无法处理写入操作。主库必须阻止所有写入,并等待同步副本再次可用。因此,将所有从库都设置为同步的是不切实际的:任何一个节点的中断都会导致整个系统停滞不前。实际上,如果在数据库上启用同步复制,通常意味着其中一个跟随者是同步的,而其他的则是异步的。如果同步从库变得不可用或缓慢,则使一个异步从库同步。这保证你至少在两个节点上拥有最新的数据副本:主库和同步从库。这种配置有时也被称为半同步(semi-synchronous)。 16 | 17 | 在主从复制结构里,异步复制相比同步复制具备更高的吞吐量和更低延迟,因此,结合同步和异步复制是一个常见选项。比如 Kafka,根据它的声称,这是一个 CA 系统,也就是同时达到数据一致和高可用。Kafka 的复制设计同时包含异步复制和同步复制,同步复制节点组成的集合称为 ISR(In-Sync Replicas),只有 ISR 内的所有节点都对写入确认之后,才算做写成功。当一个节点失效,Leader 会通过 ZooKeeper 感知并把它从 ISR 中移除。不过 Kafka 有一个问题,因为它声称 F 个节点可以容忍 F-1 个节点失效,这跟其他系统不同,通常类似的设计只能容忍 F/2-1 个节点失效,也就是说要确保大多数节点都能正常运行,而 Kafka 这把这个条件弱化成为只有 Leader 运行也可以。这样做是有问题的:假设 ISR 只剩下一个 Leader 在运行,如果此时 Leader 跟 ZooKeeper 的网络连接中断,就会产生一次选举,让 ISR 之外的节点(那些异步复制节点)加入 ISR,通常它会落后此前的 Leader 不少。当原先的 Leader 跟 ZooKeeper 网络连接恢复后,系统就产生脑裂,需要对 2 个 Leader 的数据做 Merge 或者舍弃,后者则会导致数据丢失。 18 | 19 | 最后,通常情况下,基于领导者的复制都配置为完全异步在这种情况下,如果主库失效且不可恢复,则任何尚未复制给从库的写入都会丢失这意味着即使已经向客户端确认成功,写入也不能保证 持久(Durable)然而,一个完全异步的配置也有优点:即使所有的从库都落后了,主库也可以继续处理写入。 20 | -------------------------------------------------------------------------------- /02~复制/主从复制/复制延迟.md: -------------------------------------------------------------------------------- 1 | # 复制延迟 2 | 3 | 容忍节点故障只是需要复制的一个原因,另一个原因是可扩展性(处理比单个机器更多的请求)和延迟(让副本在地理位置上更接近用户)。基于主库的复制要求所有写入都由单个节点处理,但只读查询可以由任何副本处理。所以对于读多写少的场景(Web 上的常见模式),一个有吸引力的选择是创建很多从库,并将读请求分散到所有的从库上去。这样能减小主库的负载,并允许向最近的副本发送读请求。在这种扩展体系结构中,只需添加更多的追随者,就可以提高只读请求的服务容量。但是,这种方法实际上只适用于异步复制——如果尝试同步复制到所有追随者,则单个节点故障或网络中断将使整个系统无法写入。而且越多的节点越有可能会被关闭,所以完全同步的配置是非常不可靠的。 4 | 5 | 不幸的是,当应用程序从异步从库读取时,如果从库落后,它可能会看到过时的信息。这会导致数据库中出现明显的不一致:同时对主库和从库执行相同的查询,可能得到不同的结果,因为并非所有的写入都反映在从库中。这种不一致只是一个暂时的状态——如果停止写入数据库并等待一段时间,从库最终会赶上并与主库保持一致。出于这个原因,这种效应被称为 最终一致性(eventually consistency)。“最终”一词故意含糊不清:总的来说,副本落后的程度是没有限制的。在正常的操作中,复制延迟(replication lag),即写入主库到反映至从库之间的延迟,可能仅仅是几分之一秒,在实践中并不显眼。但如果系统在接近极限的情况下运行,或网络中存在问题,延迟可以轻而易举地超过几秒,甚至几分钟。因为滞后时间太长引入的不一致性,可不仅是一个理论问题,更是应用设计中会遇到的真实问题。 6 | 7 | # 读己之写 8 | 9 | 许多应用让用户提交一些数据,然后查看他们提交的内容。可能是用户数据库中的记录,也可能是对讨论主题的评论,或其他类似的内容。提交新数据时,必须将其发送给领导者,但是当用户查看数据时,可以从追随者读取。如果数据经常被查看,但只是偶尔写入,这是非常合适的。但对于异步复制,问题就来了。如果用户在写入后马上就查看数据,则新数据可能尚未到达副本。对用户而言,看起来好像是刚提交的数据丢失了,用户会不高兴,可以理解。 10 | 11 | ![用户写入后从旧副本中读取数据。需要写后读(read-after-write)的一致性来防止这种异常](https://s2.ax1x.com/2020/02/09/1fwtG6.png) 12 | 13 | 在这种情况下,我们需要读写一致性(read-after-write consistency),也称为 读己之写一致性(read-your-writes consistency)。这是一个保证,如果用户重新加载页面,他们总会看到他们自己提交的任何更新。它不会对其他用户的写入做出承诺:其他用户的更新可能稍等才会看到。它保证用户自己的输入已被正确保存。 14 | 15 | 如何在主从复制中实现读后一致性?有各种可能的技术,这里说一些: 16 | 17 | - 读用户可能已经修改过的内容时,都从主库读;这就要求有一些方法,不用实际查询就可以知道用户是否修改了某些东西。举个例子,社交网络上的用户个人资料信息通常只能由用户本人编辑,而不能由其他人编辑。因此一个简单的规则是:从主库读取用户自己的档案,在从库读取其他用户的档案。 18 | 19 | - 如果应用中的大部分内容都可能被用户编辑,那这种方法就没用了,因为大部分内容都必须从主库读取(扩容读就没效果了)。在这种情况下可以使用其他标准来决定是否从主库读取。例如可以跟踪上次更新的时间,在上次更新后的一分钟内,从主库读。还可以监控从库的复制延迟,防止任向任何滞后超过一分钟到底从库发出查询。 20 | 21 | - 客户端可以记住最近一次写入的时间戳,系统需要确保从库为该用户提供任何查询时,该时间戳前的变更都已经传播到了本从库中。如果当前从库不够新,则可以从另一个从库读,或者等待从库追赶上来。 22 | 23 | - 如果您的副本分布在多个数据中心(出于可用性目的与用户尽量在地理上接近),则会增加复杂性。任何需要由领导者提供服务的请求都必须路由到包含主库的数据中心。 24 | 25 | 另一种复杂的情况是:如果同一个用户从多个设备请求服务,例如桌面浏览器和移动 APP。这种情况下可能就需要提供跨设备的写后读一致性:如果用户在某个设备上输入了一些信息,然后在另一个设备上查看,则应该看到他们刚输入的信息。在这种情况下,还有一些需要考虑的问题: 26 | 27 | - 记住用户上次更新时间戳的方法变得更加困难,因为一台设备上运行的程序不知道另一台设备上发生了什么。元数据需要一个中心存储。 28 | 29 | - 如果副本分布在不同的数据中心,很难保证来自不同设备的连接会路由到同一数据中心(例如,用户的台式计算机使用家庭宽带连接,而移动设备使用蜂窝数据网络,则设备的网络路线可能完全不同)。如果你的方法需要读主库,可能首先需要把来自同一用户的请求路由到同一个数据中心。 30 | 31 | # 单调读 32 | 33 | 从异步从库读取第二个异常例子是,用户可能会遇到时光倒流(moving backward in time)。如果用户从不同从库进行多次读取,就可能发生这种情况。例如,下图显示了用户 2345 两次进行相同的查询,首先查询了一个延迟很小的从库,然后是一个延迟较大的从库(如果用户刷新网页,而每个请求被路由到一个随机的服务器,这种情况是很有可能的。)第一个查询返回最近由用户 1234 添加的评论,但是第二个查询不返回任何东西,因为滞后的从库还没有拉取写入内容。在效果上相比第一个查询,第二个查询是在更早的时间点来观察系统。如果第一个查询没有返回任何内容,那问题并不大,因为用户 2345 可能不知道用户 1234 最近添加了评论。但如果用户 2345 先看见用户 1234 的评论,然后又看到它消失,那么对于用户 2345,就很让人头大了。 34 | 35 | ![用户首先从新副本读取,然后从旧副本读取。时光倒流。为了防止这种异常,我们需要单调的读取。](https://s2.ax1x.com/2020/02/09/1f0uFI.md.png) 36 | 37 | 单调读(Monotonic reads)是这种异常不会发生的保证。这是一个比强一致性(strong consistency)更弱,但比最终一致性(eventually consistency)更强的保证。当读取数据时,您可能会看到一个旧值;单调读取仅意味着如果一个用户顺序地进行多次读取,则他们不会看到时间后退,即,如果先前读取到较新的数据,后续读取不会得到更旧的数据。实现单调读取的一种方式是确保每个用户总是从同一个副本进行读取(不同的用户可以从不同的副本读取)。例如,可以基于用户 ID 的哈希来选择副本,而不是随机选择副本。但是,如果该副本失败,用户的查询将需要重新路由到另一个副本。 38 | 39 | # 一致前缀读 40 | 41 | 如果某些分区的复制速度慢于其他分区,那么观察者在看到问题之前可能会看到答案。防止这种异常,需要另一种类型的保证:一致前缀读(consistent prefix reads)这个保证说:如果一系列写入按某个顺序发生,那么任何人读取这些写入时,也会看见它们以同样的顺序出现。 42 | 43 | ![违反了因果律的例子](https://s2.ax1x.com/2020/02/09/1f0aYq.md.png) 44 | 45 | 这是分区(partitioned)(分片(sharded))数据库中的一个特殊问题,如果数据库总是以相同的顺序应用写入,则读取总是会看到一致的前缀,所以这种异常不会发生。但是在许多分布式数据库中,不同的分区独立运行,因此不存在全局写入顺序:当用户从数据库中读取数据时,可能会看到数据库的某些部分处于较旧的状态,而某些处于较新的状态。 46 | 47 | 一种解决方案是,确保任何因果相关的写入都写入相同的分区。对于某些无法高效完成这种操作的应用,还有一些显式跟踪因果依赖关系的算法。 48 | 49 | # 复制延迟的解决方案 50 | 51 | 在使用最终一致的系统时,如果复制延迟增加到几分钟甚至几小时,则应该考虑应用程序的行为。如果答案是“没问题”,那很好。但如果结果对于用户来说是不好体验,那么设计系统来提供更强的保证是很重要的,例如写后读。明明是异步复制却假设复制是同步的,这是很多麻烦的根源。 52 | 53 | 如前所述,应用程序可以提供比底层数据库更强有力的保证,例如通过主库进行某种读取。但在应用程序代码中处理这些问题是复杂的,容易出错。如果应用程序开发人员不必担心微妙的复制问题,并可以信赖他们的数据库“做了正确的事情”,那该多好呀。这就是事务(transaction)存在的原因:数据库通过事务提供强大的保证,所以应用程序可以更加简单。 54 | 55 | 单节点事务已经存在了很长时间。然而在走向分布式(复制和分区)数据库时,许多系统放弃了事务。声称事务在性能和可用性上的代价太高,并断言在可扩展系统中最终一致性是不可避免的。 56 | -------------------------------------------------------------------------------- /02~复制/主从复制/复制日志.md: -------------------------------------------------------------------------------- 1 | # 复制日志 2 | 3 | # 基于语句的复制 4 | 5 | 在最简单的情况下,主库记录下它执行的每个写入请求(语句(statement))并将该语句日志发送给其从库。对于关系数据库来说,这意味着每个 INSERT,UPDATE 或 DELETE 语句都被转发给每个从库,每个从库解析并执行该 SQL 语句,就像从客户端收到一样。 6 | 7 | 虽然听上去很合理,但有很多问题会搞砸这种复制方式: 8 | 9 | - 任何调用非确定性函数(nondeterministic)的语句,可能会在每个副本上生成不同的值。例如,使用 NOW()获取当前日期时间,或使用 RAND() 获取一个随机数。 10 | 11 | - 如果语句使用了自增列(auto increment),或者依赖于数据库中的现有数据(例如,UPDATE ... WHERE <某些条件>),则必须在每个副本上按照完全相同的顺序执行它们,否则可能会产生不同的效果。当有多个并发执行的事务时,这可能成为一个限制。 12 | 13 | - 有副作用的语句(例如,触发器,存储过程,用户定义的函数)可能会在每个副本上产生不同的副作用,除非副作用是绝对确定的。 14 | 15 | 我们可以尝试绕过这些问题,例如,当语句被记录时,主库可以用固定的返回值替换任何不确定的函数调用,以便从库获得相同的值。但是由于边缘情况实在太多了,现在通常会选择其他的复制方法。基于语句的复制在 5.1 版本前的 MySQL 中使用。因为它相当紧凑,现在有时候也还在用。但现在在默认情况下,如果语句中存在任何不确定性,MySQL 会切换到基于行的复制 VoltDB 使用了基于语句的复制,但要求事务必须是确定性的,以此来保证安全。 16 | 17 | # 传输预写式日志(WAL) 18 | 19 | 存储引擎常常会写操作都是追加到日志中: 20 | 21 | - 对于日志结构存储引擎(类似于 SSTables 和 LSM 树),日志是主要的存储位置。日志段在后台压缩,并进行垃圾回收。 22 | - 对于覆写单个磁盘块的 B 树,每次修改都会先写入 预写式日志(Write Ahead Log, WAL),以便崩溃后索引可以恢复到一个一致的状态。 23 | 24 | 在任何一种情况下,日志都是包含所有数据库写入的仅追加字节序列。可以使用完全相同的日志在另一个节点上构建副本:除了将日志写入磁盘之外,主库还可以通过网络将其发送给其从库。当从库应用这个日志时,它会建立和主库一模一样数据结构的副本。PostgreSQL 和 Oracle 等使用这种复制方法。 25 | 26 | 这种复制方式的主要缺点是日志记录的数据非常底层:WAL 包含哪些磁盘块中的哪些字节发生了更改。这使复制与存储引擎紧密耦合。如果数据库将其存储格式从一个版本更改为另一个版本,通常不可能在主库和从库上运行不同版本的数据库软件。 27 | 28 | 看上去这可能只是一个微小的实现细节,但却可能对运维产生巨大的影响。如果复制协议允许从库使用比主库更新的软件版本,则可以先升级从库,然后执行故障切换,使升级后的节点之一成为新的主库,从而执行数据库软件的零停机升级。如果复制协议不允许版本不匹配(传输 WAL 经常出现这种情况),则此类升级需要停机。 29 | 30 | # 逻辑日志复制(基于行) 31 | 32 | 另一种方法是,复制和存储引擎使用不同的日志格式,这样可以使复制日志从存储引擎内部分离出来。这种复制日志被称为逻辑日志,以将其与存储引擎的(物理)数据表示区分开来。关系数据库的逻辑日志通常是以行的粒度描述对数据库表的写入的记录序列: 33 | 34 | - 对于插入的行,日志包含所有列的新值。 35 | - 对于删除的行,日志包含足够的信息来唯一标识已删除的行。通常是主键,但是如果表上没有主键,则需要记录所有列的旧值。 36 | - 对于更新的行,日志包含足够的信息来唯一标识更新的行,以及所有列的新值(或至少所有已更改的列的新值)。 37 | 38 | 修改多行的事务会生成多个这样的日志记录,后面跟着一条记录,指出事务已经提交 MySQL 的二进制日志(当配置为使用基于行的复制时)使用这种方法。由于逻辑日志与存储引擎内部分离,因此可以更容易地保持向后兼容,从而使领导者和跟随者能够运行不同版本的数据库软件甚至不同的存储引擎。对于外部应用程序来说,逻辑日志格式也更容易解析。如果要将数据库的内容发送到外部系统(如数据),这一点很有用,例如复制到数据仓库进行离线分析,或建立自定义索引和缓存这种技术被称为数据变更捕获(change data capture)。 39 | 40 | # 基于触发器的复制 41 | 42 | 到目前为止描述的复制方法是由数据库系统实现的,不涉及任何应用程序代码。在很多情况下,这就是你想要的。但在某些情况下需要更多的灵活性。例如,如果您只想复制数据的一个子集,或者想从一种数据库复制到另一种数据库,或者如果您需要冲突解决逻辑,则可能需要将复制移动到应用程序层。 43 | 44 | 一些工具,如 Oracle Golden Gate,可以通过读取数据库日志,使得其他应用程序可以使用数据。另一种方法是使用许多关系数据库自带的功能:触发器和存储过程。 45 | 46 | 触发器允许您注册在数据库系统中发生数据更改(写入事务)时自动执行的自定义应用程序代码。触发器有机会将更改记录到一个单独的表中,使用外部程序读取这个表,再加上任何业务逻辑处理,会后将数据变更复制到另一个系统去。例如,Databus for Oracle 和 Bucardo for Postgres 就是这样工作的。 47 | 48 | 基于触发器的复制通常比其他复制方法具有更高的开销,并且比数据库的内置复制更容易出错,也有很多限制。然而由于其灵活性,仍然是很有用的。 49 | -------------------------------------------------------------------------------- /02~复制/主从复制/宕机恢复.md: -------------------------------------------------------------------------------- 1 | # 宕机恢复 2 | 3 | 系统中的任何节点都可能宕机,可能因为意外的故障,也可能由于计划内的维护(例如,重启机器以安装内核安全补丁)。对运维而言,能在系统不中断服务的情况下重启单个节点好处多多。我们的目标是,即使个别节点失效,也能保持整个系统运行,并尽可能控制节点停机带来的影响。 4 | 5 | # 设置新从库 6 | 7 | 有时候需要设置一个新的从库:也许是为了增加副本的数量,或替换失败的节点。如何确保新的从库拥有主库数据的精确副本?最简单地将数据文件从一个节点复制到另一个节点通常是不够的:客户端不断向数据库写入数据,数据总是在不断变化,标准的数据副本会在不同的时间点总是不一样。复制的结果可能没有任何意义。 8 | 9 | 可以通过锁定数据库(使其不可用于写入)来使磁盘上的文件保持一致,但是这会违背高可用的目标。幸运的是,拉起新的从库通常并不需要停机。从概念上讲,过程如下所示: 10 | 11 | 1. 在某个时刻获取主库的一致性快照(如果可能),而不必锁定整个数据库。大多数数据库都具有这个功能,因为它是备份必需的。对于某些场景,可能需要第三方工具,例如 MySQL 的 innobackupex。 12 | 2. 将快照复制到新的从库节点。 13 | 3. 从库连接到主库,并拉取快照之后发生的所有数据变更。这要求快照与主库复制日志中的位置精确关联。该位置有不同的名称:例如,PostgreSQL 将其称为 日志序列号(log sequence number, LSN),MySQL 将其称为 二进制日志坐标(Binlog coordinates)。 14 | 4. 当从库处理完快照之后积压的数据变更,我们说它赶上(caught up)了主库。现在它可以继续处理主库产生的数据变化了。 15 | 16 | 建立从库的实际步骤因数据库而异。在某些系统中,这个过程是完全自动化的,而在另外一些系统中,它可能是一个需要由管理员手动执行的,有点神秘的多步骤工作流。 17 | 18 | # 从库失效:追赶恢复(Catch-up Recovery) 19 | 20 | 在其本地磁盘上,每个从库记录从主库收到的数据变更。如果从库崩溃并重新启动,或者,如果主库和从库之间的网络暂时中断,则比较容易恢复:从库可以从日志中知道,在发生故障之前处理的最后一个事务。因此,从库可以连接到主库,并请求在从库断开连接时发生的所有数据变更。当应用完所有这些变化后,它就赶上了主库,并可以像以前一样继续接收数据变更流。 21 | 22 | # 主库失效:故障切换(Failover) 23 | 24 | 主库失效处理起来相当棘手:其中一个从库需要被提升为新的主库,需要重新配置客户端,以将它们的写操作发送给新的主库,其他从库需要开始拉取来自新主库的数据变更。这个过程被称为故障切换(failover)。故障切换可以手动进行(通知管理员主库挂了,并采取必要的步骤来创建新的主库)或自动进行。自动故障切换过程通常由以下步骤组成: 25 | 26 | - 确认主库失效。有很多事情可能会出错:崩溃,停电,网络问题等等。没有万无一失的方法来检测出现了什么问题,所以大多数系统只是简单使用 超时(Timeout):节点频繁地相互来回传递消息,并且如果一个节点在一段时间内(例如 30 秒)没有响应,就认为它挂了(因为计划内维护而故意关闭主库不算)。 27 | 28 | - 选择一个新的主库。这可以通过选举过程(主库由剩余副本以多数选举产生)来完成,或者可以由之前选定的控制器节点(controller node)来指定新的主库。主库的最佳人选通常是拥有旧主库最新数据副本的从库(最小化数据损失)。让所有的节点同意一个新的领导者,是一个共识问题。 29 | 30 | - 重新配置系统以启用新的主库。客户端现在需要将它们的写请求发送给新主库。如果老领导回来,可能仍然认为自己是主库,没有意识到其他副本已经让它下台了。系统需要确保老领导认可新领导,成为一个从库。 31 | 32 | 值得注意的是,故障切换会出现很多大麻烦: 33 | 34 | - 如果使用异步复制,则新主库可能没有收到老主库宕机前最后的写入操作。在选出新主库后,如果老主库重新加入集群,新主库在此期间可能会收到冲突的写入,那这些写入该如何处理?最常见的解决方案是简单丢弃老主库未复制的写入,这很可能打破客户对于数据持久性的期望。 35 | 36 | - 如果数据库需要和其他外部存储相协调,那么丢弃写入内容是极其危险的操作。例如在 GitHub 的一场事故中,一个过时的 MySQL 从库被提升为主库。数据库使用自增 ID 作为主键,因为新主库的计数器落后于老主库的计数器,所以新主库重新分配了一些已经被老主库分配掉的 ID 作为主键。这些主键也在 Redis 中使用,主键重用使得 MySQL 和 Redis 中数据产生不一致,最后导致一些私有数据泄漏到错误的用户手中。 37 | 38 | - 发生某些故障时可能会出现两个节点都以为自己是主库的情况。这种情况称为 脑裂(split brain),非常危险:如果两个主库都可以接受写操作,却没有冲突解决机制,那么数据就可能丢失或损坏。一些系统采取了安全防范措施,即所谓的屏蔽(fencing)机制:当检测到两个主库节点同时存在时会关闭其中一个节点,但设计粗糙的机制可能最后会导致两个节点都被关闭。 39 | 40 | - 主库被宣告死亡之前的正确超时应该怎么配置?在主库失效的情况下,超时时间越长,意味着恢复时间也越长。但是如果超时设置太短,又可能会出现不必要的故障切换。例如,临时负载峰值可能导致节点的响应时间超时,或网络故障可能导致数据包延迟。如果系统已经处于高负载或网络问题的困扰之中,那么不必要的故障切换可能会让情况变得更糟糕。 41 | 42 | 这些问题没有简单的解决方案。因此,即使软件支持自动故障切换,不少运维团队还是更愿意手动执行故障切换。 43 | -------------------------------------------------------------------------------- /02~复制/多主复制/CRDT.md: -------------------------------------------------------------------------------- 1 | # 无冲突复制数据类型 2 | 3 | 冲突解决规则可能很快变得复杂,并且自定义代码可能容易出错。亚马逊是一个经常被引用的例子,由于冲突解决处理程序令人惊讶的效果:一段时间以来,购物车上的冲突解决逻辑将保留添加到购物车的物品,但不包括从购物车中移除的物品。因此,顾客有时会看到物品重新出现在他们的购物车中,即使他们之前已经被移走。已经有一些有趣的研究来自动解决由于数据修改引起的冲突。有几行研究值得一提: 4 | 5 | - 无冲突复制数据类型(Conflict-free replicated datatypes)(CRDT)是可以由多个用户同时编辑的集合,映射,有序列表,计数器等的一系列数据结构,它们以合理的方式自动解决冲突。一些 CRDT 已经在 Riak 2.0 中实现。 6 | 7 | - 可合并的持久数据结构(Mergeable persistent data structures)显式跟踪历史记录,类似于 Git 版本控制系统,并使用三向合并功能(而 CRDT 使用双向合并)。 8 | 9 | - 可执行的转换(operational transformation)[42]是 Etherpad 和 Google Docs 等合作编辑应用背后的冲突解决算法。它是专为同时编辑项目的有序列表而设计的,例如构成文本文档的字符列表。 10 | -------------------------------------------------------------------------------- /02~复制/多主复制/CRDT/99~参考资料/2023-List CRDT Benchmarks.md: -------------------------------------------------------------------------------- 1 | > [原文地址](https://jsonjoy.com/blog/list-crdt-benchmarks) 2 | 3 | # List CRDT Benchmarks 4 | -------------------------------------------------------------------------------- /02~复制/多主复制/CRDT/99~参考资料/2023~A Gentle Introduction to CRDTs.md: -------------------------------------------------------------------------------- 1 | # A Gentle Introduction to CRDTs 2 | 3 | # Links 4 | 5 | - https://vlcn.io/blog/gentle-intro-to-crdts.html 6 | -------------------------------------------------------------------------------- /02~复制/多主复制/CRDT/99~参考资料/2023~An Interactive Intro to CRDTs.md: -------------------------------------------------------------------------------- 1 | > [原文地址](https://jakelazaroff.com/words/an-interactive-intro-to-crdts/) 2 | 3 | # An Interactive Intro to CRDTs 4 | -------------------------------------------------------------------------------- /02~复制/多主复制/CRDT/CRDT 框架/Loro/99~参考资料/2023~Introduction to Loro's Rich Text CRDT.md: -------------------------------------------------------------------------------- 1 | > [原文地址](https://www.loro.dev/blog/loro-richtext#loros-rich-text-crdt) 2 | -------------------------------------------------------------------------------- /02~复制/多主复制/README.md: -------------------------------------------------------------------------------- 1 | # 多主复制 2 | 3 | 主从复制一个主要的缺点:只有一个主库,而所有的写入都必须通过它。如果出于任何原因(例如和主库之间的网络连接中断)无法连接到主库,就无法向数据库写入。如果数据库被分区,每个分区都有一个领导不同的分区可能在不同的节点上有其领导者,但是每个分区必须有一个领导者节点。 4 | 5 | 基于领导者的复制模型的自然延伸是允许多个节点接受写入复制仍然以同样的方式发生:处理写入的每个节点都必须将该数据更改转发给所有其他节点称之为多领导者配置(也称多主、多活复制)在这种情况下,每个领导者同时扮演其他领导者的追随者。 6 | 7 | # 应用场景 8 | 9 | 在单个数据中心内部使用多个主库很少是有意义的,因为好处很少超过复杂性的代价但在一些情况下,多活配置是也合理的。 10 | 11 | ## 运维多个数据中心 12 | 13 | 假如你有一个数据库,副本分散在好几个不同的数据中心(也许这样可以容忍单个数据中心的故障,或地理上更接近用户)使用常规的基于领导者的复制设置,主库必须位于其中一个数据中心,且所有写入都必须经过该数据中心。多领导者配置中可以在每个数据中心都有主库。下图展示了这个架构的样子在每个数据中心内使用常规的主从复制;在数据中心之间,每个数据中心的主库都会将其更改复制到其他数据中心的主库中。 14 | 15 | ![跨多个数据中心的多主复制](https://s2.ax1x.com/2020/02/09/1fDFqe.md.png) 16 | 17 | 我们来比较一下在运维多个数据中心时,单主和多主的适应情况。 18 | 19 | - 性能:在单活配置中,每个写入都必须穿过互联网,进入主库所在的数据中心。这可能会增加写入时间,并可能违背了设置多个数据中心的初心。在多活配置中,每个写操作都可以在本地数据中心进行处理,并与其他数据中心异步复制。因此,数据中心之间的网络延迟对用户来说是透明的,这意味着感觉到的性能可能会更好。 20 | 21 | - 容忍数据中心停机:在单主配置中,如果主库所在的数据中心发生故障,故障切换可以使另一个数据中心里的追随者成为领导者。在多活配置中,每个数据中心可以独立于其他数据中心继续运行,并且当发生故障的数据中心归队时,复制会自动赶上。 22 | 23 | - 容忍网络问题:数据中心之间的通信通常穿过公共互联网,这可能不如数据中心内的本地网络可靠。单主配置对这数据中心间的连接问题非常敏感,因为通过这个连接进行的写操作是同步的。采用异步复制功能的多活配置通常能更好地承受网络问题:临时的网络中断并不会妨碍正在处理的写入。 24 | 25 | 有些数据库默认情况下支持多主配置,但使用外部工具实现也很常见,例如用于 MySQL 的 Tungsten Replicator,用于 PostgreSQL 的 BDR 以及用于 Oracle 的 GoldenGate。 26 | 27 | 尽管多主复制有这些优势,但也有一个很大的缺点:两个不同的数据中心可能会同时修改相同的数据,写冲突是必须解决的。由于多主复制在许多数据库中都属于改装的功能,所以常常存在微妙的配置缺陷,且经常与其他数据库功能之间出现意外的反应。例如自增主键、触发器、完整性约束等,都可能会有麻烦。因此,多主复制往往被认为是危险的领域,应尽可能避免。 28 | 29 | ## 需要离线操作的客户端 30 | 31 | 多主复制的另一种适用场景是:应用程序在断网之后仍然需要继续工作。例如,考虑手机,笔记本电脑和其他设备上的日历应用。无论设备目前是否有互联网连接,你需要能随时查看你的会议(发出读取请求),输入新的会议(发出写入请求)。如果在离线状态下进行任何更改,则设备下次上线时,需要与服务器和其他设备同步。 32 | 33 | 在这种情况下,每个设备都有一个充当领导者的本地数据库(它接受写请求),并且在所有设备上的日历副本之间同步时,存在异步的多主复制过程。复制延迟可能是几小时甚至几天,具体取决于何时可以访问互联网。从架构的角度来看,这种设置实际上与数据中心之间的多领导者复制类似,每个设备都是一个“数据中心”,而它们之间的网络连接是极度不可靠的。从历史上各类日历同步功能的破烂实现可以看出,想把多活配好是多么困难的一件事。 34 | 35 | 有一些工具旨在使这种多领导者配置更容易。例如,CouchDB 就是为这种操作模式而设计的。 36 | 37 | ## 协同编辑 38 | 39 | 实时协作编辑应用程序允许多个人同时编辑文档。例如,Etherpad 和 Google Docs 允许多人同时编辑文本文档或电子表格。我们通常不会将协作式编辑视为数据库复制问题,但与前面提到的离线编辑用例有许多相似之处。当一个用户编辑文档时,所做的更改将立即应用到其本地副本(Web 浏览器或客户端应用程序中的文档状态),并异步复制到服务器和编辑同一文档的任何其他用户。 40 | 41 | 如果要保证不会发生编辑冲突,则应用程序必须先取得文档的锁定,然后用户才能对其进行编辑。如果另一个用户想要编辑同一个文档,他们首先必须等到第一个用户提交修改并释放锁定。这种协作模式相当于在领导者上进行交易的单领导者复制。 42 | 43 | 但是,为了加速协作,您可能希望将更改的单位设置得非常小(例如,一个按键),并避免锁定。这种方法允许多个用户同时进行编辑,但同时也带来了多领导者复制的所有挑战,包括需要解决冲突。 44 | -------------------------------------------------------------------------------- /02~复制/多主复制/冲突解决.md: -------------------------------------------------------------------------------- 1 | # 多主复制中的冲突解决 2 | 3 | 多领导者复制的最大问题是可能发生写冲突,这意味着需要解决冲突。例如,两个写操作并发地修改了同一条记录中的同一个字段,并将其设置为两个不同的值。考虑一个由两个用户同时编辑的维基页面,如下图所示。用户 1 将页面的标题从 A 更改为 B,并且用户 2 同时将标题从 A 更改为 C。每个用户的更改已成功应用到其本地主库。但当异步复制时,会发现冲突。单主数据库中不会出现此问题。 4 | 5 | ![两个主库同时更新同一记录引起的写入冲突](https://s2.ax1x.com/2020/02/09/1fsqr8.md.png) 6 | 7 | 其他类型的冲突可能更为微妙,难以发现。例如,考虑一个会议室预订系统:它记录谁订了哪个时间段的哪个房间。应用需要确保每个房间只有一组人同时预定(即不得有相同房间的重叠预订)。在这种情况下,如果同时为同一个房间创建两个不同的预订,则可能会发生冲突。即使应用程序在允许用户进行预订之前检查可用性,如果两次预订是由两个不同的领导者进行的,则可能会有冲突。 8 | 9 | # 同步与异步冲突检测 10 | 11 | 在单主数据库中,第二个写入将被阻塞,并等待第一个写入完成,或中止第二个写入事务,强制用户重试。另一方面,在多活配置中,两个写入都是成功的,并且在稍后的时间点仅仅异步地检测到冲突。那时要求用户解决冲突可能为时已晚。 12 | 13 | 原则上,可以使冲突检测同步 - 即等待写入被复制到所有副本,然后再告诉用户写入成功。但是,通过这样做,您将失去多主复制的主要优点:允许每个副本独立接受写入。如果您想要同步冲突检测,那么您可以使用单主程序复制。 14 | 15 | # 避免冲突 16 | 17 | 处理冲突的最简单的策略就是避免它们:如果应用程序可以确保特定记录的所有写入都通过同一个领导者,那么冲突就不会发生。由于多领导者复制处理的许多实现冲突相当不好,避免冲突是一个经常推荐的方法。 18 | 19 | 例如,在用户可以编辑自己的数据的应用程序中,可以确保来自特定用户的请求始终路由到同一数据中心,并使用该数据中心的领导者进行读写。不同的用户可能有不同的“家庭”数据中心(可能根据用户的地理位置选择),但从任何用户的角度来看,配置基本上都是单一的领导者。 20 | 21 | 但是,有时您可能需要更改指定的记录的主库——可能是因为一个数据中心出现故障,您需要将流量重新路由到另一个数据中心,或者可能是因为用户已经迁移到另一个位置,现在更接近不同的数据中心。在这种情况下,冲突避免会中断,你必须处理不同主库同时写入的可能性。 22 | 23 | # 收敛至一致的状态 24 | 25 | 单主数据库按顺序应用写操作:如果同一个字段有多个更新,则最后一个写操作将确定该字段的最终值。在多主配置中,写入顺序没有定义,所以最终值应该是什么并不清楚。在标题修改的例子中,在主库 1 中标题首先更新为 B 而后更新为 C;在主库 2 中,首先更新为 C,然后更新为 B。两个顺序都不是“更正确”的。 26 | 27 | 如果每个副本只是按照它看到写入的顺序写入,那么数据库最终将处于不一致的状态:最终值将是在主库 1 的 C 和主库 2 的 B。这是不可接受的,每个复制方案都必须确保数据在所有副本中最终都是相同的。因此,数据库必须以一种收敛(convergent)的方式解决冲突,这意味着所有副本必须在所有变更复制完成时收敛至一个相同的最终值。 28 | 29 | 实现冲突合并解决有多种途径: 30 | 31 | - 给每个写入一个唯一的 ID(例如,一个时间戳,一个长的随机数,一个 UUID 或者一个键和值的哈希),挑选最高 ID 的写入作为胜利者,并丢弃其他写入。如果使用时间戳,这种技术被称为最后写入胜利(LWW, last write wins)。虽然这种方法很流行,但是很容易造成数据丢失。 32 | - 为每个副本分配一个唯一的 ID,ID 编号更高的写入具有更高的优先级。这种方法也意味着数据丢失。 33 | - 以某种方式将这些值合并在一起 - 例如,按字母顺序排序,然后连接它们。 34 | - 在保留所有信息的显式数据结构中记录冲突,并编写解决冲突的应用程序代码(也许通过提示用户的方式)。 35 | 36 | # 自定义冲突解决逻辑 37 | 38 | 作为解决冲突最合适的方法可能取决于应用程序,大多数多主复制工具允许使用应用程序代码编写冲突解决逻辑。该代码可以在写入或读取时执行。 39 | 40 | ## 写时执行 41 | 42 | 只要数据库系统检测到复制更改日志中存在冲突,就会调用冲突处理程序。例如,Bucardo 允许您为此编写一段 Perl 代码。这个处理程序通常不能提示用户——它在后台进程中运行,并且必须快速执行。 43 | 44 | ## 读时执行 45 | 46 | 当检测到冲突时,所有冲突写入被存储。下一次读取数据时,会将这些多个版本的数据返回给应用程序。应用程序可能会提示用户或自动解决冲突,并将结果写回数据库。例如,CouchDB 以这种方式工作。请注意,冲突解决通常适用于单个行或文档层面,而不是整个事务。因此,如果您有一个事务会原子性地进行几次不同的写入,则对于冲突解决而言,每个写入仍需分开单独考虑。 47 | -------------------------------------------------------------------------------- /02~复制/多主复制/多主复制拓扑.md: -------------------------------------------------------------------------------- 1 | # 多主复制拓扑 2 | 3 | 复制拓扑描述写入从一个节点传播到另一个节点的通信路径。如果你有两个领导者,只有一个合理的拓扑结构:领导者 1 必须把他所有的写到领导者 2,反之亦然。有两个以上的领导,各种不同的拓扑是可能的。 4 | 5 | ![三个可以设置多领导者复制的示例拓扑。](https://s2.ax1x.com/2020/02/09/1fy5eU.png) 6 | 7 | 最普遍的拓扑是全部到全部,其中每个领导者将其写入每个其他领导。但是,也会使用更多受限制的拓扑:例如,默认情况下,MySQL 仅支持环形拓扑(circular topology)。其中每个节点接收来自一个节点的写入,并将这些写入(加上自己的任何写入)转发给另一个节点。另一种流行的拓扑结构具有星形的形状,一个指定的根节点将写入转发给所有其他节点。星型拓扑可以推广到树。 8 | 9 | 在圆形和星形拓扑中,写入可能需要在到达所有副本之前通过多个节点。因此,节点需要转发从其他节点收到的数据更改。为了防止无限复制循环,每个节点被赋予一个唯一的标识符,并且在复制日志中,每个写入都被标记了所有已经通过的节点的标识符。当一个节点收到用自己的标识符标记的数据更改时,该数据更改将被忽略,因为节点知道它已经被处理。 10 | 11 | 循环和星型拓扑的问题是,如果只有一个节点发生故障,则可能会中断其他节点之间的复制消息流,导致它们无法通信,直到节点修复。拓扑结构可以重新配置为在发生故障的节点上工作,但在大多数部署中,这种重新配置必须手动完成。更密集连接的拓扑结构(例如全部到全部)的容错性更好,因为它允许消息沿着不同的路径传播,避免单点故障。 12 | 13 | 另一方面,全能拓扑也可能有问题。特别是,一些网络链接可能比其他网络链接更快(例如,由于网络拥塞),结果是一些复制消息可能“超过”其他复制消息,如下图所示。 14 | 15 | ![使用多主程序复制时,可能会在某些副本中写入错误的顺序](https://s2.ax1x.com/2020/02/09/1f6Y0U.md.png) 16 | 17 | 客户端 A 向主库 1 的表中插入一行,客户端 B 在主库 3 上更新该行。然而,主库 2 可以以不同的顺序接收写入:它可以首先接收更新(其中,从它的角度来看,是对数据库中不存在的行的更新),并且仅在稍后接收到相应的插入(其应该在更新之前)。这是一个因果关系的问题,类似于我们在“一致前缀读”中看到的:更新取决于先前的插入,所以我们需要确保所有节点先处理插入,然后再处理更新。仅仅在每一次写入时添加一个时间戳是不够的,因为时钟不可能被充分地同步,以便在主库 2 处正确地排序这些事件。 18 | -------------------------------------------------------------------------------- /02~复制/无主复制/README.md: -------------------------------------------------------------------------------- 1 | # 无主复制 2 | 3 | 我们目前为止所讨论的复制方法,单主复制、多主复制,都是这样的想法:客户端向一个主库发送写请求,而数据库系统负责将写入复制到其他副本。主库决定写入的顺序,而从库按相同顺序应用主库的写入。 4 | 5 | 一些数据存储系统采用不同的方法,放弃主库的概念,并允许任何副本直接接受来自客户端的写入。最早的一些的复制数据系统是无领导的(leaderless),但是在关系数据库主导的时代,这个想法几乎已被忘却。在亚马逊将其用于其内部的 Dynamo 系统之后,它再一次成为数据库的一种时尚架构 Riak,Cassandra 和 Voldemort 是由 Dynamo 启发的无领导复制模型的开源数据存储,所以这类数据库也被称为 Dynamo 风格。 6 | 7 | 在一些无领导者的实现中,客户端直接将写入发送到到几个副本中,而另一些情况下,一个协调者(coordinator)节点代表客户端进行写入。但与主库数据库不同,协调者不执行特定的写入顺序。我们将会看到,这种设计上的差异对数据库的使用方式有着深远的影响。 8 | -------------------------------------------------------------------------------- /02~复制/无主复制/故障与仲裁.md: -------------------------------------------------------------------------------- 1 | # 节点故障 2 | 3 | 假设你有一个带有三个副本的数据库,而其中一个副本目前不可用,或许正在重新启动以安装系统更新。在主从复制中,如果要继续处理写入,则可能需要执行故障切换。另一方面,在无领导配置中,故障切换不存在:客户端(用户 1234)并行发送写入到所有三个副本,并且两个可用副本接受写入,但是不可用副本错过了它。假设三个副本中的两个承认写入是足够的:在用户 1234 已经收到两个确定的响应之后,我们认为写入成功。客户简单地忽略了其中一个副本错过了写入的事实。 4 | 5 | ![仲裁写入,法定读取,并在节点中断后读修复](https://s2.ax1x.com/2020/02/09/1fg7Fg.md.png) 6 | 7 | 现在想象一下,不可用的节点重新联机,客户端开始读取它。节点关闭时发生的任何写入都从该节点丢失。因此,如果您从该节点读取数据,则可能会将陈旧(过时)值视为响应。为了解决这个问题,当一个客户端从数据库中读取数据时,它不仅仅发送它的请求到一个副本:读请求也被并行地发送到多个节点。客户可能会从不同的节点获得不同的响应。即来自一个节点的最新值和来自另一个节点的陈旧值。版本号用于确定哪个值更新。 8 | 9 | # 读修复和反熵 10 | 11 | 复制方案应确保最终将所有数据复制到每个副本。在一个不可用的节点重新联机之后,它如何赶上它错过的写入?在 Dynamo 风格的数据存储中经常使用两种机制:读修复与反熵过程。 12 | 13 | ## 读修复(Read repair) 14 | 15 | 当客户端并行读取多个节点时,它可以检测到任何陈旧的响应。例如用户 2345 获得了来自 Replica 3 的版本 6 值和来自副本 1 和 2 的版本 7 值。客户端发现副本 3 具有陈旧值,并将新值写回复制品。这种方法适用于频繁阅读的值。 16 | 17 | ![仲裁写入,法定读取,并在节点中断后读修复](https://s2.ax1x.com/2020/02/09/1fg7Fg.md.png) 18 | 19 | ## 反熵过程(Anti-entropy process) 20 | 21 | 此外,一些数据存储具有后台进程,该进程不断查找副本之间的数据差异,并将任何缺少的数据从一个副本复制到另一个副本。与基于领导者的复制中的复制日志不同,此反熵过程不会以任何特定的顺序复制写入,并且在复制数据之前可能会有显著的延迟。 22 | 23 | 并不是所有的系统都实现了这两个;例如,Voldemort 目前没有反熵过程。请注意,如果没有反熵过程,某些副本中很少读取的值可能会丢失,从而降低了持久性,因为只有在应用程序读取值时才执行读修复。 24 | 25 | # 读写的法定人数 26 | 27 | 我们认为即使仅在三个副本中的两个上进行处理,写入仍然是成功的。如果三个副本中只有一个接受了写入,会怎样?我们能推多远呢?如果我们知道,每个成功的写操作意味着在三个副本中至少有两个出现,这意味着至多有一个副本可能是陈旧的。因此,如果我们从至少两个副本读取,我们可以确定至少有一个是最新的。如果第三个副本停机或响应速度缓慢,则读取仍可以继续返回最新值。 28 | 29 | 更一般地说,如果有 n 个副本,每个写入必须由 w 节点确认才能被认为是成功的,并且我们必须至少为每个读取查询 r 个节点(在我们的例子中,$n = 3,w = 2,r = 2$)。只要$w + r> n$,我们期望在读取时获得最新的值,因为 r 个读取中至少有一个节点是最新的。遵循这些 r 值,w 值的读写称为法定人数(quorum)^vii 的读和写,你可以认为,r 和 w 是有效读写所需的最低票数。 30 | 31 | 在 Dynamo 风格的数据库中,参数 n,w 和 r 通常是可配置的。一个常见的选择是使 n 为奇数(通常为 3 或 5)并设置 $w = r =(n + 1)/ 2$(向上取整)。但是可以根据需要更改数字。例如,设置$w = n$和$r = 1$的写入很少且读取次数较多的工作负载可能会受益。这使得读取速度更快,但具有只有一个失败节点导致所有数据库写入失败的缺点。 32 | 33 | 集群中可能有多于 n 的节点。(集群的机器数可能多于副本数目),但是任何给定的值只能存储在 n 个节点上这允许对数据集进行分区,从而支持可以放在一个节点上的数据集更大的数据集。仲裁条件$w + r> n$允许系统容忍不可用的节点,如下所示: 34 | 35 | - 如果$w n$,读取r个副本,至少有一个r副本必然包含了最近的成功写入](https://s2.ax1x.com/2020/02/09/1f2O4e.md.png) 42 | 43 | 如果少于所需的 w 或 r 节点可用,则写入或读取将返回错误由于许多原因,节点可能不可用:因为由于执行操作的错误(由于磁盘已满而无法写入)导致节点关闭(崩溃,关闭电源),由于客户端和服务器之间的网络中断 节点,或任何其他原因我们只关心节点是否返回了成功的响应,而不需要区分不同类型的错误。 44 | 45 | # 仲裁一致性的局限性 46 | 47 | 如果你有 n 个副本,并且你选择 w 和 r,使得$w + r> n$,你通常可以期望每个读取返回为一个键写的最近的值。情况就是这样,因为你写的节点集合和你读过的节点集合必须重叠。也就是说,您读取的节点中必须至少有一个具有最新值的节点。通常,r 和 w 被选为多数(超过 $n/2$)节点,因为这确保了$w + r> n$,同时仍然容忍多达$n/2$个节点故障。但是,法定人数不一定必须是大多数,只是读写使用的节点交集至少需要包括一个节点。其他法定人数的配置是可能的,这使得分布式算法的设计有一定的灵活性。 48 | 49 | 您也可以将 w 和 r 设置为较小的数字,以使$w + r≤n$(即法定条件不满足)。在这种情况下,读取和写入操作仍将被发送到 n 个节点,但操作成功只需要少量的成功响应。较小的 w 和 r 更有可能会读取过时的数据,因为您的读取更有可能不包含具有最新值的节点。另一方面,这种配置允许更低的延迟和更高的可用性:如果存在网络中断,并且许多副本变得无法访问,则可以继续处理读取和写入的机会更大。只有当可达副本的数量低于 w 或 r 时,数据库才分别变得不可用于写入或读取。 50 | 51 | 但是,即使在 $w + r> n$ 的情况下,也可能存在返回陈旧值的边缘情况。这取决于实现,但可能的情况包括: 52 | 53 | - 如果使用松散的法定人数,w 个写入和 r 个读取落在完全不同的节点上,因此 r 节点和 w 之间不再保证有重叠节点。 54 | - 如果两个写入同时发生,不清楚哪一个先发生。在这种情况下,唯一安全的解决方案是合并并发写入(请参阅第 171 页的“处理写入冲突”)。如果根据时间戳(最后写入胜利)挑选出胜者,则由于时钟偏差,写入可能会丢失。 55 | - 如果写操作与读操作同时发生,写操作可能仅反映在某些副本上。在这种情况下,不确定读取是返回旧值还是新值。 56 | - 如果写操作在某些副本上成功,而在其他节点上失败(例如,因为某些节点上的磁盘已满),在小于 w 个副本上写入成功。所以整体判定写入失败,但整体写入失败并没有在写入成功的副本上回滚。这意味着如果一个写入虽然报告失败,后续的读取仍然可能会读取这次失败写入的值。 57 | - 如果携带新值的节点失败,需要读取其他带有旧值的副本。并且其数据从带有旧值的副本中恢复,则存储新值的副本数可能会低于 w,从而打破法定人数条件。 58 | - 即使一切工作正常,有时也会不幸地出现关于时序(timing)的边缘情况,在这种情况下,您可能会感到不安。 59 | 60 | 因此,尽管法定人数似乎保证读取返回最新的写入值,但在实践中并不那么简单 Dynamo 风格的数据库通常针对可以忍受最终一致性的用例进行优化。允许通过参数 w 和 r 来调整读取陈旧值的概率,但把它们当成绝对的保证是不明智的。 61 | 62 | ## 监控陈旧度 63 | 64 | 从运维的角度来看,监视你的数据库是否返回最新的结果是很重要的。即使应用可以容忍陈旧的读取,您也需要了解复制的健康状况。如果显著落后,应该提醒您,以便您可以调查原因(例如,网络中的问题或超载节点)。 65 | 66 | 对于基于领导者的复制,数据库通常会公开复制滞后的度量标准,您可以将其提供给监视系统。这是可能的,因为写入按照相同的顺序应用于领导者和追随者,并且每个节点在复制日志中具有一个位置(在本地应用的写入次数)。通过从领导者的当前位置中减去随从者的当前位置,您可以测量复制滞后量。 67 | 68 | 然而,在无领导者复制的系统中,没有固定的写入顺序,这使得监控变得更加困难。而且,如果数据库只使用读修复(没有反熵过程),那么对于一个值可能会有多大的限制是没有限制的 - 如果一个值很少被读取,那么由一个陈旧副本返回的值可能是古老的。 69 | 70 | 已经有一些关于衡量无主复制数据库中的复制陈旧度的研究,并根据参数 n,w 和 r 来预测陈旧读取的预期百分比。不幸的是,这还不是很常见的做法,但是将过时测量值包含在数据库的标准度量标准中是一件好事。最终的一致性是故意模糊的保证,但是对于可操作性来说,能够量化“最终”是很重要的。 71 | 72 | # 松散法定人数与带提示的接力 73 | 74 | 合理配置的法定人数可以使数据库无需故障切换即可容忍个别节点的故障。也可以容忍个别节点变慢,因为请求不必等待所有 n 个节点响应——当 w 或 r 节点响应时它们可以返回。对于需要高可用、低延时、且能够容忍偶尔读到陈旧值的应用场景来说,这些特性使无主复制的数据库很有吸引力。 75 | 76 | 然而,法定人数(如迄今为止所描述的)并不像它们可能的那样具有容错性。网络中断可以很容易地将客户端从大量的数据库节点上切断。虽然这些节点是活着的,而其他客户端可能能够连接到它们,但是从数据库节点切断的客户端,它们也可能已经死亡。在这种情况下,剩余的可用节点可能会少于可用节点,因此客户端可能无法达到法定人数。 77 | 78 | 在一个大型的群集中(节点数量明显多于 n 个),网络中断期间客户端可能连接到某些数据库节点,而不是为了为特定值组成法定人数的节点们。在这种情况下,数据库设计人员需要权衡一下: 79 | 80 | - 将错误返回给我们无法达到 w 或 r 节点的法定数量的所有请求是否更好? 81 | - 或者我们是否应该接受写入,然后将它们写入一些可达的节点,但不在 n 值通常存在的 n 个节点之间? 82 | 83 | 后者被认为是一个松散的法定人数(sloppy quorum):写和读仍然需要 w 和 r 成功的响应,但是那些可能包括不在指定的 n 个“主”节点中的值。比方说,如果你把自己锁在房子外面,你可能会敲开邻居的门,问你是否可以暂时停留在沙发上。一旦网络中断得到解决,代表另一个节点临时接受的一个节点的任何写入都被发送到适当的“本地”节点。这就是所谓的带提示的接力(hinted handoff)(一旦你再次找到你的房子的钥匙,你的邻居礼貌地要求你离开沙发回家。) 84 | 85 | 松散法定人数对写入可用性的提高特别有用:只要有任何 w 节点可用,数据库就可以接受写入。然而,这意味着即使当$w + r> n$时,也不能确定读取某个键的最新值,因为最新的值可能已经临时写入了 n 之外的某些节点。因此,在传统意义上,一个松散的法定人数实际上不是一个法定人数。这只是一个保证,即数据存储在 w 节点的地方。不能保证 r 节点的读取直到提示已经完成。 86 | 87 | 在所有常见的 Dynamo 实现中,松散法定人数是可选的。在 Riak 中,它们默认是启用的,而在 Cassandra 和 Voldemort 中它们默认是禁用的。 88 | 89 | ## 运维多个数据中心 90 | 91 | 无主复制还适用于多数据中心操作,因为它旨在容忍冲突的并发写入,网络中断和延迟尖峰。 92 | 93 | Cassandra 和 Voldemort 在正常的无主模型中实现了他们的多数据中心支持:副本的数量 n 包括所有数据中心的节点,在配置中,您可以指定每个数据中心中您想拥有的副本的数量。无论数据中心如何,每个来自客户端的写入都会发送到所有副本,但客户端通常只等待来自其本地数据中心内的法定节点的确认,从而不会受到跨数据中心链路延迟和中断的影响。对其他数据中心的高延迟写入通常被配置为异步发生,尽管配置有一定的灵活性。 94 | 95 | Riak 将客户端和数据库节点之间的所有通信保持在一个数据中心本地,因此 n 描述了一个数据中心内的副本数量。数据库集群之间的跨数据中心复制在后台异步发生,其风格类似于多领导者复制。 96 | -------------------------------------------------------------------------------- /02~复制/无主复制/检测并发写入.md: -------------------------------------------------------------------------------- 1 | # 检测并发写入 2 | 3 | Dynamo 风格的数据库允许多个客户端同时写入相同的 Key,这意味着即使使用严格的法定人数也会发生冲突。这种情况与多领导者复制相似,但在 Dynamo 样式的数据库中,在读修复或带提示的接力期间也可能会产生冲突。问题在于,由于可变的网络延迟和部分故障,事件可能在不同的节点以不同的顺序到达。例如,下图显示了两个客户机 A 和 B 同时写入三节点数据存储区中的键 X: 4 | 5 | - 节点 1 接收来自 A 的写入,但由于暂时中断,从不接收来自 B 的写入。 6 | - 节点 2 首先接收来自 A 的写入,然后接收来自 B 的写入。 7 | - 节点 3 首先接收来自 B 的写入,然后从 A 写入。 8 | 9 | ![并发写入Dynamo风格的数据存储:没有明确定义的顺序](https://s2.ax1x.com/2020/02/09/1f52Ax.md.png) 10 | 11 | 如果每个节点只要接收到来自客户端的写入请求就简单地覆盖了某个键的值,那么节点就会永久地不一致,如上图中的最终获取请求所示:节点 2 认为 X 的最终值是 B,而其他节点认为值是 A。为了最终达成一致,副本应该趋于相同的值。如何做到这一点?有人可能希望复制的数据库能够自动处理,但不幸的是,大多数的实现都很糟糕:如果你想避免丢失数据,你(应用程序开发人员)需要知道很多有关数据库冲突处理的内部信息。 12 | 13 | # 最后写入胜利,LWW 14 | 15 | 实现最终融合的一种方法是声明每个副本只需要存储最近的值,并允许更旧的值被覆盖和抛弃。然后,只要我们有一种明确的方式来确定哪个写是“最近的”,并且每个写入最终都被复制到每个副本,那么复制最终会收敛到相同的值。不过,当客户端向数据库节点发送写入请求时,客户端都不知道另一个客户端,因此不清楚哪一个先发生了。事实上,说“发生”是没有意义的:我们说写入是并发(concurrent)的,所以它们的顺序是不确定的。 16 | 17 | 即使写入没有自然的排序,我们也可以强制任意排序。例如,可以为每个写入附加一个时间戳,挑选最“最近”的最大时间戳,并丢弃具有较早时间戳的任何写入。这种冲突解决算法被称为最后写入胜利(LWW, last write wins),是 Cassandra 唯一支持的冲突解决方法,也是 Riak 中的一个可选特征。 18 | 19 | LWW 实现了最终收敛的目标,但以持久性为代价:如果同一个 Key 有多个并发写入,即使它们都被报告为客户端成功(因为它们被写入 w 个副本),但只有一个写入将存活,而其他写入将被静默丢弃。有一些情况,如缓存,其中丢失的写入可能是可以接受的。如果丢失数据不可接受,LWW 是解决冲突的一个很烂的选择。与 LWW 一起使用数据库的唯一安全方法是确保一个键只写入一次,然后视为不可变,从而避免对同一个密钥进行并发更新。例如,Cassandra 推荐使用的方法是使用 UUID 作为键,从而为每个写操作提供一个唯一的键。 20 | 21 | # “此前发生”的关系和并发 22 | 23 | 我们如何判断两个操作是否是并发的?为了建立一个直觉,让我们看看一些例子: 24 | 25 | - 两个写入不是并发的:A 的插入发生在 B 的增量之前,因为 B 递增的值是 A 插入的值。换句话说,B 的操作建立在 A 的操作上,所以 B 的操作必须有后来发生。我们也可以说 B 是因果依赖(causally dependent)于 A。 26 | - 当每个客户端启动操作时,它不知道另一个客户端也正在执行操作同样的 Key。因此,操作之间不存在因果关系。 27 | 28 | 如果操作 B 了解操作 A,或者依赖于 A,或者以某种方式构建于操作 A 之上,则操作 A 在另一个操作 B 之前发生。在另一个操作之前是否发生一个操作是定义什么并发的关键。事实上,我们可以简单地说,如果两个操作都不在另一个之前发生,那么两个操作是并发的(即,两个操作都不知道另一个)。 29 | 30 | 因此,只要有两个操作 A 和 B,就有三种可能性:A 在 B 之前发生,或者 B 在 A 之前发生,或者 A 和 B 并发。我们需要的是一个算法来告诉我们两个操作是否是并发的。如果一个操作发生在另一个操作之前,则后面的操作应该覆盖较早的操作,但是如果这些操作是并发的,则存在需要解决的冲突。 31 | 32 | ## 并发性,时间和相对性 33 | 34 | 如果两个操作同时发生,似乎应该称为并发——但事实上,它们在字面时间上重叠与否并不重要。由于分布式系统中的时钟问题,现实中是很难判断两个事件是否同时发生的为了定义并发性,确切的时间并不重要:如果两个操作都意识不到对方的存在,就称这两个操作并发,而不管它们发生的物理时间。人们有时把这个原理和狭义相对论的物理学联系起来,它引入了信息不能比光速更快的思想。因此,如果事件之间的时间短于光通过它们之间的距离,那么发生一定距离的两个事件不可能相互影响。 35 | 36 | 在计算机系统中,即使光速原则上允许一个操作影响另一个操作,但两个操作也可能是并行的。例如,如果网络缓慢或中断,两个操作间可能会出现一段时间间隔,且仍然是并发的,因为网络问题阻止一个操作意识到另一个操作的存在。 37 | 38 | ## 捕获"此前发生"关系 39 | 40 | 来看一个算法,它确定两个操作是否为并发的,还是一个在另一个之前。为了简单起见,我们从一个只有一个副本的数据库开始。一旦我们已经制定了如何在单个副本上完成这项工作,我们可以将该方法概括为具有多个副本的无领导者数据库。下图显示了两个客户端同时向同一购物车添加项目(如果这样的例子让你觉得太麻烦了,那么可以想象,两个空中交通管制员同时把飞机添加到他们正在跟踪的区域)最初,购物车是空的。在它们之间,客户端向数据库发出五次写入: 41 | 42 | ![捕获两个客户端之间的因果关系,同时编辑购物车。](https://s2.ax1x.com/2020/02/09/1hkUyQ.md.png) 43 | 44 | 1. 客户端 1 将牛奶加入购物车。这是该键的第一次写入,服务器成功存储了它并为其分配版本号 1,最后将值与版本号一起回送给客户端。 45 | 2. 客户端 2 将鸡蛋加入购物车,不知道客户端 1 同时添加了牛奶(客户端 2 认为它的鸡蛋是购物车中的唯一物品)。服务器为此写入分配版本号 2,并将鸡蛋和牛奶存储为两个单独的值。然后它将这两个值都反回给客户端 2,并附上版本号 2。 46 | 3. 客户端 1 不知道客户端 2 的写入,想要将面粉加入购物车,因此认为当前的购物车内容应该是 [牛奶,面粉]。它将此值与服务器先前向客户端 1 提供的版本号 1 一起发送到服务器。服务器可以从版本号中知道[牛奶,面粉]的写入取代了[牛奶]的先前值,但与[鸡蛋]的值是并发的。因此,服务器将版本 3 分配给[牛奶,面粉],覆盖版本 1 值[牛奶],但保留版本 2 的值[蛋],并将所有的值返回给客户端 1。 47 | 4. 同时,客户端 2 想要加入火腿,不知道客端户 1 刚刚加了面粉。客户端 2 在最后一个响应中从服务器收到了两个值[牛奶]和[蛋],所以客户端 2 现在合并这些值,并添加火腿形成一个新的值,[鸡蛋,牛奶,火腿]。它将这个值发送到服务器,带着之前的版本号 2。服务器检测到新值会覆盖版本 2 [鸡蛋],但新值也会与版本 3 [牛奶,面粉]并发,所以剩下的两个是 v3 [牛奶,面粉],和 v4:[鸡蛋,牛奶,火腿] 48 | 5. 最后,客户端 1 想要加培根。它以前在 v3 中从服务器接收[牛奶,面粉]和[鸡蛋],所以它合并这些,添加培根,并将最终值[牛奶,面粉,鸡蛋,培根]连同版本号 v3 发往服务器。这会覆盖 v3[牛奶,面粉](请注意[鸡蛋]已经在最后一步被覆盖),但与 v4[鸡蛋,牛奶,火腿]并发,所以服务器保留这两个并发值。 49 | 50 | 箭头表示哪个操作发生在其他操作之前,意味着后面的操作知道或依赖于较早的操作在这个例子中,客户端永远不会完全掌握服务器上的数据,因为总是有另一个操作同时进行但是,旧版本的值最终会被覆盖,并且不会丢失任何写入。 51 | 52 | ![图5-13中的因果依赖关系图](https://s2.ax1x.com/2020/02/09/1hkhwR.png) 53 | 54 | 请注意,服务器可以通过查看版本号来确定两个操作是否是并发的——它不需要解释该值本身(因此该值可以是任何数据结构)。该算法的工作原理如下: 55 | 56 | - 服务器为每个键保留一个版本号,每次写入键时都增加版本号,并将新版本号与写入的值一起存储。 57 | - 当客户端读取键时,服务器将返回所有未覆盖的值以及最新的版本号。客户端在写入前必须读取。 58 | - 客户端写入键时,必须包含之前读取的版本号,并且必须将之前读取的所有值合并在一起(来自写入请求的响应可以像读取一样,返回所有当前值,这使得我们可以像购物车示例那样连接多个写入。) 59 | - 当服务器接收到具有特定版本号的写入时,它可以覆盖该版本号或更低版本的所有值(因为它知道它们已经被合并到新的值中),但是它必须保持所有值更高版本号(因为这些值与传入的写入同时发生)。 60 | 61 | 当一个写入包含前一次读取的版本号时,它会告诉我们写入的是哪一种状态。如果在不包含版本号的情况下进行写操作,则与所有其他写操作并发,因此它不会覆盖任何内容:只会在随后的读取中作为其中一个值返回。 62 | 63 | # 合并同时写入的值 64 | 65 | 这种算法可以确保没有数据被无声地丢弃,但不幸的是,客户端需要做一些额外的工作:如果多个操作并发发生,则客户端必须通过合并并发写入的值来擦屁股 Riak 称这些并发值兄弟(siblings)。合并兄弟值,本质上是与多领导者复制中的冲突解决相同的问题,一个简单的方法是根据版本号或时间戳(最后写入胜利)选择一个值,但这意味着丢失数据。所以,你可能需要在应用程序代码中做更聪明的事情。 66 | 67 | 以购物车为例,一种合理的合并兄弟方法就是集合求并,在购物车的例子中,最后的两个兄弟是[牛奶,面粉,鸡蛋,熏肉]和[鸡蛋,牛奶,火腿]。注意牛奶和鸡蛋出现在两个,即使他们每个只写一次。合并的价值可能是像[牛奶,面粉,鸡蛋,培根,火腿],没有重复。 68 | 69 | 然而,如果你想让人们也可以从他们的手推车中删除东西,而不是仅仅添加东西,那么把兄弟求并可能不会产生正确的结果:如果你合并了两个兄弟手推车,并且只在其中一个兄弟值里删掉了它,那么被删除的项目会重新出现在兄弟的并集中。为了防止这个问题,一个项目在删除时不能简单地从数据库中删除;相反,系统必须留下一个具有合适版本号的标记,以指示合并兄弟时该项目已被删除。这种删除标记被称为墓碑(tombstone)。 70 | 71 | 因为在应用程序代码中合并兄弟是复杂且容易出错的,所以有一些数据结构被设计出来用于自动执行这种合并,例如,Riak 的数据类型支持使用称为 CRDT 的数据结构家族可以以合理的方式自动合并兄弟,包括保留删除。 72 | 73 | # 版本向量 74 | 75 | 之前我们使用单个版本号来捕获操作之间的依赖关系,但是当多个副本并发接受写入时,这是不够的。相反,除了对每个键使用版本号之外,还需要在每个副本中使用版本号。每个副本在处理写入时增加自己的版本号,并且跟踪从其他副本中看到的版本号。这个信息指出了要覆盖哪些值,以及保留哪些值作为兄弟。 76 | 77 | 所有副本的版本号集合称为版本向量(version vector),Riak 2.0 中使用的分散版本矢量(dotted version vector)当读取值时,版本向量会从数据库副本发送到客户端,并且随后写入值时需要将其发送回数据库(Riak 将版本向量编码为一个字符串,它称为因果上下文(causal context))。版本向量允许数据库区分覆盖写入和并发写入。 78 | 79 | 另外,就像在单个副本的例子中,应用程序可能需要合并兄弟。版本向量结构确保从一个副本读取并随后写回到另一个副本是安全的。这样做可能会创建兄弟,但只要兄弟姐妹合并正确,就不会丢失数据。 80 | -------------------------------------------------------------------------------- /03~数据分片/README.md: -------------------------------------------------------------------------------- 1 | # 分片 2 | 3 | 对于大型数据集或非常高的吞吐量的情况下,仅仅进行复制是不够的,我们需要将数据进行分区(partitions),也称为分片(sharding)。分区是一种有意将大型数据库分解成小型数据库的方式,它与网络分区(net splits)无关。分区(partition),在 MongoDB,Elasticsearch 和 Solr Cloud 中被称为分片(shard),在 HBase 中称之为区域(Region),Bigtable 中则是 表块(tablet),Cassandra 和 Riak 中是虚节点(vnode), Couchbase 中叫做虚桶(vBucket).但是分区(partition) 是约定俗成的叫法。 4 | 5 | 通常情况下,每条数据(每条记录,每行或每个文档)属于且仅属于一个分区,分区主要是为了可扩展性。不同的分区可以放在不共享集群中的不同节点上,对于在单个分区上运行的查询,每个节点可以独立执行对自己的查询,因此可以通过添加更多的节点来扩大查询吞吐量。大型,复杂的查询可能会跨越多个节点并行处理,尽管这也带来了新的困难。 6 | 7 | 分区数据库在 20 世纪 80 年代由 Teradata 和 NonStop SQL 等产品率先推出,最近因为 NoSQL 数据库和基于 Hadoop 的数据仓库重新被关注。有些系统是为事务性工作设计的,有些系统则用于分析:这种差异会影响系统的运作方式,但是分区的基本原理均适用于这两种工作方式。 8 | 9 | ## 分区与复制 10 | 11 | 分区通常与复制结合使用,使得每个分区的副本存储在多个节点上这意味着,即使每条记录属于一个分区,它仍然可以存储在多个不同的节点上以获得容错能力。一个节点可能存储多个分区如果使用主从复制模型,则分区和复制的组合如下图所示每个分区领导者(主)被分配给一个节点,追随者(从)被分配给其他节点每个节点可能是某些分区的领导者,同时是其他分区的追随者。大多数情况下,分区方案的选择与复制方案的选择是独立的。 12 | 13 | ![组合使用复制和分区:每个节点充当某些分区的领导者,其他分区充当追随者。](https://s2.ax1x.com/2020/02/09/1huDR1.md.png) 14 | 15 | # Links 16 | 17 | - https://blog.csdn.net/jiafu1115/article/details/53908715 18 | -------------------------------------------------------------------------------- /03~数据分片/一致性哈希/Go.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wx-chevalier/DistributedStorage-Notes/81c3489826e753c4420dd287e27ffe9e8c229341/03~数据分片/一致性哈希/Go.md -------------------------------------------------------------------------------- /03~数据分片/一致性哈希/Java.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wx-chevalier/DistributedStorage-Notes/81c3489826e753c4420dd287e27ffe9e8c229341/03~数据分片/一致性哈希/Java.md -------------------------------------------------------------------------------- /03~数据分片/一致性哈希/README.md: -------------------------------------------------------------------------------- 1 | # 一致性哈希 2 | 3 | 当我们在做数据库分库分表或者是分布式缓存时,不可避免的都会遇到一个问题:如何将数据均匀的分散到各个节点中,并且尽量的在加减节点时能使受影响的数据最少。 4 | 5 | 最经典的方式就是 hash 取模了,即将传入的 Key 按照 `index=hash(key)%N` 这样来计算出需要存放的节点。其中 hash 函数是一个将字符串转换为正整数的哈希映射方法,N 就是节点的数量。这样可以满足数据的均匀分配,但是这个算法的容错性和扩展性都较差。比如增加或删除了一个节点时,所有的 Key 都需要重新计算,显然这样成本较高,为此需要一个算法满足分布均匀同时也要有良好的容错性和拓展性。 6 | 7 | 一致性 hash 作为一个负载均衡算法,可以用在分布式缓存、数据库的分库分表等场景中,还可以应用在负载均衡器中作为作为负载均衡算法。在有多台服务器时,对于某个请求资源通过 hash 算法,映射到某一个台服务器,当增加或减少一台服务器时,可能会改变这些资源对应的 hash 值,这样可能导致一部分缓存或数据失效了。一致性 hash 就是尽可能在将同一个资源请求路由到同一台服务器中。 8 | 9 | 一致性哈希是一种特殊的哈希算法。在使用一致哈希算法后,哈希表槽位数(大小)的改变平均只需要对 K/n 个关键字重新映射,其中 K 是关键字的数量,n 是槽位数量。然而在传统的哈希表中,添加或删除一个槽位的几乎需要对所有关键字进行重新映射。 10 | 11 | # 环构建 12 | 13 | 一致 Hash 算法是将所有的哈希值构成了一个环,其范围在 0~2^32-1。之后将各个节点哈希到这个环上,可以用节点的 IP、hostname 这样的唯一性字段作为 Key 进行 hash 计算取值。客户端根据自身的某些数据 hash 之后也定位到这个环中,通过顺时针找到离他最近的一个节点,也就是这次路由的服务节点。 14 | 15 | 当节点发生故障下线,或者增加新的节点时候,依然根据顺时针方向,k2 和 k3 保持不变,只有 k1 被重新映射到了 N3。这样就很好的保证了容错性,当一个节点宕机时只会影响到少少部分的数据。 16 | 17 | # 虚拟节点 18 | 19 | 考虑到服务节点的个数以及 hash 算法的问题导致环中的数据分布不均匀时引入了虚拟节点。 20 | 21 | # Java 中一致性哈希算法的实现 22 | 23 | 我们可以选择用某个有序数组或者 TreeMap 来模拟一致性哈希中的环: 24 | 25 | - 初始化一个长度为 N 的数组。 26 | - 将服务节点通过 hash 算法得到的正整数,同时将节点自身的数据(hashcode、ip、端口等)存放在这里。 27 | - 完成节点存放后将整个数组进行排序(排序算法有多种)。 28 | - 客户端获取路由节点时,将自身进行 hash 也得到一个正整数; 29 | - 遍历这个数组直到找到一个数据大于等于当前客户端的 hash 值,就将当前节点作为该客户端所路由的节点。 30 | - 如果没有发现比客户端大的数据就返回第一个节点(满足环的特性)。 31 | 32 | 只使用了 TreeMap 的一些 API: 33 | 34 | - 写入数据候,TreeMap 可以保证 key 的自然排序。 35 | - tailMap 可以获取比当前 key 大的部分数据。 36 | - 当这个方法有数据返回时取第一个就是顺时针中的第一个节点了。 37 | - 如果没有返回那就直接取整个 Map 的第一个节点,同样也实现了环形结构。 38 | 39 | # Go 中一致性哈希算法的实现与应用 40 | 41 | ```go 42 | func (m *Map) Add(nodes ...string) { 43 | for _, n := range nodes { 44 | for i := 0; i < m.replicas; i++ { 45 | hash := int(m.hash([]byte(strconv.Itoa(i) + " " + n))) 46 | m.nodes = append(m.nodes, hash) 47 | m.hashMap[hash] = n 48 | } 49 | } 50 | sort.Ints(m.nodes) 51 | } 52 | ``` 53 | 54 | ```go 55 | func (m *Map) Get(key string) string { 56 | hash := int(m.hash([]byte(key))) 57 | idx := sort.Search(len(m.keys), 58 | func(i int) bool { return m.keys[i] >= hash } 59 | ) 60 | if idx == len(m.keys) { 61 | idx = 0 62 | } 63 | return m.hashMap[m.keys[idx]] 64 | } 65 | ``` 66 | 67 | # Links 68 | 69 | - https://mp.weixin.qq.com/s?__biz=MzIyMzgyODkxMQ==&mid=2247484108&idx=1&sn=87df95335ca97aa5d44475fc2c8d1e4b&scene=21#wechat_redirect 70 | - http://blog.carlosgaldino.com/consistent-hashing.html 71 | - https://blog.csdn.net/ydyang1126/article/details/70313981 72 | -------------------------------------------------------------------------------- /03~数据分片/元数据与调度/README.md: -------------------------------------------------------------------------------- 1 | # 元数据与调度 2 | 3 | 路由表(Routing Table)是存储所有区域分布信息的非常重要的模块。路由表必须保证准确性和高可用性。此外,如上所述,为了重新平衡数据,我们需要一个具有全局视角的调度程序。为了动态调整每个节点中区域的分布,调度程序需要知道哪个节点容量不足,哪个节点承受的压力更大,哪个节点具有更多的 Region Leader。这是因为所有节点几乎都是无状态的,并且它们无法自动迁移数据。相反,他们必须依靠调度程序来启动数据迁移。 4 | 5 | 您可能已经注意到,可以将调度程序和路由表集成到一个模块中 Google 的 Spanner 数据库使用这种单模块方法,并将其称为展示位置驱动程序(Placement Driver),简称为 PDPD 主要负责上述两个作业:路由表和调度程序。 6 | 7 | # PD 8 | 9 | Google 的 Spanner 论文没有详细介绍展示位置驱动程序的设计。但是,可以肯定的是,设计大型分布式存储系统的一个核心思想是假设任何模块都可能崩溃。如果模块的状态相互依赖是非常危险的。这是因为一旦实例崩溃,备用实例必须立即启动,但是此新启动实例的状态可能与崩溃的实例不一致。这时,我们必须足够小心,以免引起可能的问题。 10 | 11 | 以一个简单的案例为例。PD 路由表存储在 etcd 中。但是,节点本身确定区域的划分。这样,节点可以快速知道其区域之一的大小是否超过阈值。当此拆分事件从节点主动推送到 PD 时,如果 PD 收到此事件但在将状态保持为 etcd 之前崩溃了,则新启动的 PD 不会知道拆分。此时,路由表中的信息可能是错误的。 12 | 13 | 我们要做的是将 PD 设计为完全无状态的。只有使其完全无状态,我们才能避免由于无法持久保存状态而导致的各种问题。每个节点定期使用心跳将有关其上区域的信息发送给 PD。然后,PD 获取接收到的信息并创建一个全局路由表。这样,即使 PD 崩溃,在新的 PD 启动之后,也只需等待一些心跳,然后就可以再次获取全局路由信息。此外,PD 可以将 etcd 用作缓存来加速此过程。也就是说,新的 PD 启动后,它将从 etcd 中提取路由信息,等待一些心跳,然后提供服务。 14 | 15 | ## Epoch mechanism 16 | 17 | 但是,您可能已经注意到仍然存在问题。如果群集在某个部分中具有分区,则有关某些节点的信息可能是错误的。例如,某些区域在拆分后重新启动选举和拆分,但是另一批隔离的节点仍通过心跳将过时的信息发送给 PD。因此,对于一个地区,两个节点中的任何一个都可能会说它是领导者,而该地区也不知道可以信任谁。 18 | 19 | 在 TiKV 中,我们使用 Epoch mechanism,使用此机制,会用两个逻辑时钟标记更改:一个是 Raft 的配置更改版本,另一个是 Region 版本。对于每个配置更改,配置更改版本都会自动增加。同样,对于每个区域更改(例如拆分或合并),区域版本也会自动增加。 20 | 21 | PD 采取的时代策略是通过比较两个节点的逻辑时钟值来获得更大的值。PD 首先比较两个节点的 Region 版本的值。如果值相同,PD 将比较配置更改版本的值。配置更改版本较大的节点必须具有较新的信息。 22 | -------------------------------------------------------------------------------- /03~数据分片/元数据与调度/请求路由.md: -------------------------------------------------------------------------------- 1 | # 请求路由 2 | 3 | 现在我们已经将数据集分割到多个机器上运行的多个节点上。但是仍然存在一个悬而未决的问题:当客户想要发出请求时,如何知道要连接哪个节点?随着分区重新平衡,分区对节点的分配也发生变化。为了回答这个问题,需要有人知晓这些变化:如果我想读或写键“foo”,需要连接哪个 IP 地址和端口号? 4 | 5 | 这个问题可以概括为 服务发现(service discovery),它不仅限于数据库。任何可通过网络访问的软件都有这个问题,特别是如果它的目标是高可用性(在多台机器上运行冗余配置)。概括来说,这个问题有几种不同的方案: 6 | 7 | - 允许客户联系任何节点(例如,通过循环策略的负载均衡(Round-Robin Load Balancer))。如果该节点恰巧拥有请求的分区,则它可以直接处理该请求;否则,它将请求转发到适当的节点,接收回复并传递给客户端。 8 | 9 | - 首先将所有来自客户端的请求发送到路由层,它决定了应该处理请求的节点,并相应地转发。此路由层本身不处理任何请求;它仅负责分区的负载均衡。 10 | 11 | - 要求客户端知道分区和节点的分配。在这种情况下,客户端可以直接连接到适当的节点,而不需要任何中介。 12 | 13 | 以上所有情况中的关键问题是:作出路由决策的组件(可能是节点之一,还是路由层或客户端)如何了解分区-节点之间的分配关系变化? 14 | 15 | ![将请求路由到正确节点的三种不同方式。](https://s2.ax1x.com/2020/02/09/1hBqmD.md.png) 16 | 17 | 许多分布式数据系统都依赖于一个独立的协调服务,比如 ZooKeeper 来跟踪集群元数据。每个节点在 ZooKeeper 中注册自己,ZooKeeper 维护分区到节点的可靠映射其他参与者(如路由层或分区感知客户端)可以在 ZooKeeper 中订阅此信息只要分区分配发生的改变,或者集群中添加或删除了一个节点,ZooKeeper 就会通知路由层使路由信息保持最新状态。 18 | 19 | ![使用ZooKeeper跟踪分区分配给节点。](https://s2.ax1x.com/2020/02/09/1hD8AJ.png) 20 | 21 | 例如,LinkedIn 的 Espresso 使用 Helix 进行集群管理(依靠 ZooKeeper),实现了如图 6-8 所示的路由层 HBase,SolrCloud 和 Kafka 也使用 ZooKeeper 来跟踪分区分配 MongoDB 具有类似的体系结构,但它依赖于自己的配置服务器(config server)实现和 mongos 守护进程作为路由层。Cassandra 和 Riak 采取不同的方法:他们在节点之间使用流言协议(gossip protocol)来传播群集状态的变化。请求可以发送到任意节点,该节点会转发到包含所请求的分区的适当节点。这个模型在数据库节点中增加了更多的复杂性,但是避免了对像 ZooKeeper 这样的外部协调服务的依赖。Couchbase 不会自动重新平衡,这简化了设计。通常情况下,它配置了一个名为 moxi 的路由层,它会从集群节点了解路由变化。 22 | 23 | 当使用路由层或向随机节点发送请求时,客户端仍然需要找到要连接的 IP 地址。这些地址并不像分区的节点分布变化的那么快,所以使用 DNS 通常就足够了。 24 | 25 | # 执行并行查询 26 | 27 | 到目前为止,我们只关注读取或写入单个键的非常简单的查询(对于文档分区的二级索引,另外还有分散/聚集查询)。这与大多数 NoSQL 分布式数据存储所支持的访问级别有关。然而,通常用于分析的大规模并行处理(MPP, Massively parallel processing)关系型数据库产品在其支持的查询类型方面要复杂得多。一个典型的数据仓库查询包含多个连接,过滤,分组和聚合操作 MPP 查询优化器将这个复杂的查询分解成许多执行阶段和分区,其中许多可以在数据库集群的不同节点上并行执行。涉及扫描大规模数据集的查询特别受益于这种并行执行。 28 | -------------------------------------------------------------------------------- /03~数据分片/分片策略/README.md: -------------------------------------------------------------------------------- 1 | # 分片策略 2 | 3 | 分片是一种数据库分区策略,可将数据集分成较小的部分,并将它们存储在不同的物理节点中。分片单元(Sharding Unit)是数据移动与平衡的最小单元,集群中的每个物理节点都存储多个分片单元。 4 | 5 | 在数据库系统中我们熟知的分片策略,譬如 Cobar 之于 MySQL,Twemproxy 之于 Redis,都是典型的静态分片策略,它们都难以无感扩展。我们常用的两个分片策略是基于范围的分片(Range-based Sharding)与基于 Hash 的分片(Hash-based Sharding),不同的系统会选用不同的分片策略。典型的分布式分片系统就是 HDFS: 6 | 7 | ![HDFS](https://s2.ax1x.com/2020/01/25/1eu8hT.md.png) 8 | -------------------------------------------------------------------------------- /03~数据分片/分片策略/分片再平衡.md: -------------------------------------------------------------------------------- 1 | # 分区再平衡 2 | 3 | 随着时间的推移,数据库会有各种变化。 4 | 5 | - 查询吞吐量增加,所以您想要添加更多的 CPU 来处理负载。 6 | - 数据集大小增加,所以您想添加更多的磁盘和 RAM 来存储它。 7 | - 机器出现故障,其他机器需要接管故障机器的责任。 8 | 9 | 所有这些更改都需要数据和请求从一个节点移动到另一个节点将负载从集群中的一个节点向另一个节点移动的过程称为再平衡(reblancing)。无论使用哪种分区方案,再平衡通常都要满足一些最低要求: 10 | 11 | - 再平衡之后,负载(数据存储,读取和写入请求)应该在集群中的节点之间公平地共享。 12 | - 再平衡发生时,数据库应该继续接受读取和写入。 13 | - 节点之间只移动必须的数据,以便快速再平衡,并减少网络和磁盘 I/O 负载。 14 | 15 | # hash mod N 16 | 17 | 在分片策略中我们讨论过最好将可能的哈希分成不同的范围,并将每个范围分配给一个分区(例如,如果 $0≤hash(key) maxWorkerId || workerId < 0) { 64 | throw new IllegalArgumentException( 65 | String.format( 66 | "worker Id can't be greater than %d or less than 0", 67 | maxWorkerId 68 | ) 69 | ); 70 | } 71 | 72 | if (datacenterId > maxDatacenterId || datacenterId < 0) { 73 | throw new IllegalArgumentException( 74 | String.format( 75 | "datacenter Id can't be greater than %d or less than 0", 76 | maxDatacenterId 77 | ) 78 | ); 79 | } 80 | this.workerId = workerId; 81 | this.datacenterId = datacenterId; 82 | this.sequence = sequence; 83 | } 84 | 85 | private long twepoch = 1288834974657L; 86 | private long workerIdBits = 5L; 87 | private long datacenterIdBits = 5L; 88 | 89 | // 这个是二进制运算,就是5 bit最多只能有31个数字,也就是说机器id最多只能是32以内 90 | private long maxWorkerId = -1L ^ (-1L << workerIdBits); 91 | 92 | // 这个是一个意思,就是5 bit最多只能有31个数字,机房id最多只能是32以内 93 | private long maxDatacenterId = -1L ^ (-1L << datacenterIdBits); 94 | private long sequenceBits = 12L; 95 | private long workerIdShift = sequenceBits; 96 | private long datacenterIdShift = sequenceBits + workerIdBits; 97 | private long timestampLeftShift = 98 | sequenceBits + workerIdBits + datacenterIdBits; 99 | private long sequenceMask = -1L ^ (-1L << sequenceBits); 100 | private long lastTimestamp = -1L; 101 | 102 | public long getWorkerId() { 103 | return workerId; 104 | } 105 | 106 | public long getDatacenterId() { 107 | return datacenterId; 108 | } 109 | 110 | public long getTimestamp() { 111 | return System.currentTimeMillis(); 112 | } 113 | 114 | // 这个是核心方法,通过调用nextId()方法,让当前这台机器上的snowflake算法程序生成一个全局唯一的id 115 | public synchronized long nextId() { 116 | // 这儿就是获取当前时间戳,单位是毫秒 117 | long timestamp = timeGen(); 118 | if (timestamp < lastTimestamp) { 119 | System.err.printf( 120 | "clock is moving backwards. Rejecting requests until %d.", 121 | lastTimestamp 122 | ); 123 | throw new RuntimeException( 124 | String.format( 125 | "Clock moved backwards. Refusing to generate id for %d milliseconds", 126 | lastTimestamp - timestamp 127 | ) 128 | ); 129 | } 130 | 131 | // 下面是说假设在同一个毫秒内,又发送了一个请求生成一个id 132 | // 这个时候就得把seqence序号给递增1,最多就是4096 133 | if (lastTimestamp == timestamp) { 134 | // 这个意思是说一个毫秒内最多只能有4096个数字,无论你传递多少进来, 135 | //这个位运算保证始终就是在4096这个范围内,避免你自己传递个sequence超过了4096这个范围 136 | sequence = (sequence + 1) & sequenceMask; 137 | if (sequence == 0) { 138 | timestamp = tilNextMillis(lastTimestamp); 139 | } 140 | } else { 141 | sequence = 0; 142 | } 143 | 144 | // 这儿记录一下最近一次生成id的时间戳,单位是毫秒 145 | lastTimestamp = timestamp; 146 | 147 | // 这儿就是最核心的二进制位运算操作,生成一个64bit的id 148 | // 先将当前时间戳左移,放到41 bit那儿;将机房id左移放到5 bit那儿;将机器id左移放到5 bit那儿;将序号放最后12 bit 149 | // 最后拼接起来成一个64 bit的二进制数字,转换成10进制就是个long型 150 | return ( 151 | ((timestamp - twepoch) << timestampLeftShift) | 152 | (datacenterId << datacenterIdShift) | 153 | (workerId << workerIdShift) | 154 | sequence 155 | ); 156 | } 157 | 158 | private long tilNextMillis(long lastTimestamp) { 159 | long timestamp = timeGen(); 160 | 161 | while (timestamp <= lastTimestamp) { 162 | timestamp = timeGen(); 163 | } 164 | return timestamp; 165 | } 166 | 167 | private long timeGen() { 168 | return System.currentTimeMillis(); 169 | } 170 | 171 | //---------------测试--------------- 172 | public static void main(String[] args) { 173 | IdWorker worker = new IdWorker(1, 1, 1); 174 | 175 | for (int i = 0; i < 30; i++) { 176 | System.out.println(worker.nextId()); 177 | } 178 | } 179 | } 180 | ``` 181 | -------------------------------------------------------------------------------- /04~分布式 ID/UUID.md: -------------------------------------------------------------------------------- 1 | # UUID 2 | 3 | 什么是 UUID? 4 | 5 | UUID 是 Universally Unique Identifier 的缩写,它是在一定的范围内(从特定的名字空间到全球)唯一的机器生成的标识符。UUID 具有以下涵义: 6 | 7 | UUID(Universally Unique Identifier)的标准型式包含 32 个 16 进制数字,以连字号分为五段,形式为 8-4-4-4-12 的 36 个字符,示例:550e8400-e29b-41d4-a716-446655440000,到目前为止业界一共有 5 种方式生成 UUID,详情见 IETF 发布的 UUID 规范 A Universally Unique IDentifier (UUID) URN Namespace。 8 | 9 | 经由一定的算法机器生成 10 | 为了保证 UUID 的唯一性,规范定义了包括网卡 MAC 地址、时间戳、名字空间(Namespace)、随机或伪随机数、时序等元素,以及从这些元素生成 UUID 的算法。UUID 的复杂特性在保证了其唯一性的同时,意味着只能由计算机生成。 11 | 非人工指定,非人工识别 12 | UUID 是不能人工指定的,除非你冒着 UUID 重复的风险。UUID 的复杂性决定了“一般人“不能直接从一个 UUID 知道哪个对象和它关联。 13 | 在特定的范围内重复的可能性极小 14 | UUID 的生成规范定义的算法主要目的就是要保证其唯一性。但这个唯一性是有限的,只在特定的范围内才能得到保证,这和 UUID 的类型有关(参见 UUID 的版本)。 15 | 16 | UUID 是 16 字节 128 位长的数字,通常以 36 字节的字符串表示,示例如下: 17 | 18 | 3F2504E0-4F89-11D3-9A0C-0305E82C3301 19 | 20 | 其中的字母是 16 进制表示,大小写无关。 21 | 22 | GUID(Globally Unique Identifier)是 UUID 的别名;但在实际应用中,GUID 通常是指微软实现的 UUID。 23 | 24 | UUID 的版本 25 | 26 | UUID 具有多个版本,每个版本的算法不同,应用范围也不同。 27 | 28 | 首先是一个特例--Nil UUID--通常我们不会用到它,它是由全为 0 的数字组成,如下: 29 | 30 | 00000000-0000-0000-0000-000000000000 31 | 32 | UUID Version 1:基于时间的 UUID 33 | 34 | 基于时间的 UUID 通过计算当前时间戳、随机数和机器 MAC 地址得到。由于在算法中使用了 MAC 地址,这个版本的 UUID 可以保证在全球范围的唯一性。但与此同时,使用 MAC 地址会带来安全性问题,这就是这个版本 UUID 受到批评的地方。如果应用只是在局域网中使用,也可以使用退化的算法,以 IP 地址来代替 MAC 地址--Java 的 UUID 往往是这样实现的(当然也考虑了获取 MAC 的难度)。 35 | 36 | UUID Version 2:DCE 安全的 UUID 37 | 38 | DCE(Distributed Computing Environment)安全的 UUID 和基于时间的 UUID 算法相同,但会把时间戳的前 4 位置换为 POSIX 的 UID 或 GID。这个版本的 UUID 在实际中较少用到。 39 | 40 | UUID Version 3:基于名字的 UUID(MD5) 41 | 42 | 基于名字的 UUID 通过计算名字和名字空间的 MD5 哈希值得到。这个版本的 UUID 保证了:相同名字空间中不同名字生成的 UUID 的唯一性;不同名字空间中的 UUID 的唯一性;相同名字空间中相同名字的 UUID 重复生成是相同的。 43 | 44 | UUID Version 4:随机 UUID 45 | 46 | 根据随机数,或者伪随机数生成 UUID。这种 UUID 产生重复的概率是可以计算出来的,但随机的东西就像是买彩票:你指望它发财是不可能的,但狗屎运通常会在不经意中到来。 47 | 48 | UUID Version 5:基于名字的 UUID(SHA1) 49 | 50 | 和版本 3 的 UUID 算法类似,只是哈希值计算使用 SHA1(Secure Hash Algorithm 1)算法。 51 | 52 | UUID 的应用 53 | 54 | 从 UUID 的不同版本可以看出,Version 1/2 适合应用于分布式计算环境下,具有高度的唯一性;Version 3/5 适合于一定范围内名字唯一,且需要或可能会重复生成 UUID 的环境下;至于 Version 4,我个人的建议是最好不用(虽然它是最简单最方便的)。 55 | 56 | 通常我们建议使用 UUID 来标识对象或持久化数据,但以下情况最好不使用 UUID: 57 | 58 | 映射类型的对象。比如只有代码及名称的代码表。 59 | 人工维护的非系统生成对象。比如系统中的部分基础数据。 60 | 对于具有名称不可重复的自然特性的对象,最好使用 Version 3/5 的 UUID。比如系统中的用户。如果用户的 UUID 是 Version 1 的,如果你不小心删除了再重建用户,你会发现人还是那个人,用户已经不是那个用户了。(虽然标记为删除状态也是一种解决方案,但会带来实现上的复杂性。) 61 | 62 | UUID 生成器 63 | 64 | ```sh 65 | [a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12} 66 | ``` 67 | -------------------------------------------------------------------------------- /04~分布式 ID/库自增 ID.md: -------------------------------------------------------------------------------- 1 | # 基于数据库的 ID 生成 2 | 3 | # sequence 单表方案 4 | 5 | 在单数据源情况下,基于原理 2 生成 id,此时数据库就是中心节点,所有序列号由中心节点派发;而在多数据源和单元化场景下,单元化是多数据源的特例,支持多数据源就能方便实现单元化支持。 6 | 7 | 这种情况下一个序列只存储于一张 sequence 表,使用 SequenceDao 进行表的读写。一张 sequence 表可以存储多个序列,不同的序列用名字(name)字段区分,所以一般而言一个应用一个 sequence 表就够了,甚至多个应用可以共享一张 sequence 表。通过这种单点数据源的方式,可以实现序列全局唯一,由于数据库知道目前派发出去的最大 id 号,后续的请求,只要在现在基础上递增即可。现在应用申请一个 id,需要用乐观锁(CAS)的方式更新序列的值,但是每个 id 都要乐观锁更新 db 实在太慢,因此为了加速 id 分配,一次 CAS 操作批量分配 id,分配 id 的数量就是 sequence 的内步长,默认的内步长是 1000,这样可以降低 1000 倍的数据库访问,不过有得必有失,这种方式生成的序列就不能保证全局有序了,并且在应用重启的时候,将会重新申请一段 id。sequence 所用的数据库表结构如下: 8 | 9 | ```sql 10 | -- ---------------------------- 11 | -- Table structure for `sequence` 12 | -- ---------------------------- 13 | DROP TABLE IF EXISTS `sequence`; 14 | CREATE TABLE `sequence` ( 15 | `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'Id', 16 | `name` varchar(64) NOT NULL COMMENT 'sequence name', 17 | `value` bigint(20) NOT NULL COMMENT 'sequence current value', 18 | `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', 19 | `updated_at` timestamp NULL DEFAULT NULL COMMENT '修改时间', 20 | PRIMARY KEY (`id`), 21 | UNIQUE KEY `uk_name` (`name`) 22 | ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='sequence' 23 | ; 24 | ``` 25 | 26 | sequence 表的核心字段就两个:name 和 value,name 就是当前序列的名称,value 就是当前 sequence 已经分配出去的 id 最大值。多数据源的场景除了能够自适应后面的单元化场景之外,还可以避免单点故障,即一个 sequence 表不可用情况下,sequence 能够用其他数据源继续提供 id 生成服务。 27 | 28 | 即为每个数据源分配一个序号,数据源根据自己的序号在序列空间中独占一组序列号,原理如下图: 29 | 30 | ![image](https://user-images.githubusercontent.com/5803001/50880267-d1044780-1418-11e9-826b-f368f440b151.png) 31 | 32 | # 水平扩展 33 | 34 | 单台机器自然存在可用性问题,最简单的方式就是考虑将其扩展到多台机器,在前文的 sequence 单表方案中,我们是基于单表的自增;在分布式情况下,我们可以通过设置不同数据库的 auto_increment_increment 以及 auto_increment_offset 来不同的自增规则: 35 | 36 | ```sql 37 | begin; 38 | REPLACE INTO Tickets64 (stub) VALUES ('a'); 39 | SELECT LAST_INSERT_ID(); 40 | commit; 41 | ``` 42 | 43 | ![](https://assets.ng-tech.icu/item/20230417213824.png) 44 | 45 | 在分布式系统中我们可以多部署几台机器,每台机器设置不同的初始值,且步长和机器数相等。比如有两台机器。设置步长 step 为 2,TicketServer1 的初始值为 1(1,3,5,7,9,11…)、TicketServer2 的初始值为 2(2,4,6,8,10…)。如下所示,为了实现上述方案分别设置两台机器对应的参数,TicketServer1 从 1 开始发号,TicketServer2 从 2 开始发号,两台机器每次发号之后都递增 2: 46 | 47 | ```sh 48 | TicketServer1: 49 | auto-increment-increment = 2 50 | auto-increment-offset = 1 51 | 52 | TicketServer2: 53 | auto-increment-increment = 2 54 | auto-increment-offset = 2 55 | ``` 56 | 57 | 假设我们要部署 N 台机器,步长需设置为 N,每台的初始值依次为 0,1,2…N-1 那么整个架构就变成了如下图所示: 58 | 59 | ![](https://assets.ng-tech.icu/item/20230417213916.png) 60 | 61 | 不过这种方式也存在问题,系统水平扩展比较困难,比如定义好了步长和机器台数之后,很难进行增删。ID 没有了单调递增的特性,只能趋势递增,并且数据库压力还是很大,每次获取 ID 都得读写一次数据库,只能靠堆机器来提高性能。 62 | -------------------------------------------------------------------------------- /10~KV 存储/Consul/README.md: -------------------------------------------------------------------------------- 1 | # Consul 2 | -------------------------------------------------------------------------------- /10~KV 存储/Etcd/README.md: -------------------------------------------------------------------------------- 1 | # Etcd 2 | -------------------------------------------------------------------------------- /10~KV 存储/README.md: -------------------------------------------------------------------------------- 1 | # 配置管理 2 | 3 | 配置是软件开发,部署,运维全生命周期的重要组成,传统的配置方式包括了硬编码,配置文件,数据库配置表等。 4 | 5 | # 成员与协调服务 6 | 7 | 像 ZooKeeper 或 etcd 这样的项目通常被描述为“分布式键值存储”或“协调与配置服务”。这种服务的 API 看起来非常像数据库:你可以读写给定键的值,并遍历键。所以如果它们基本上算是数据库的话,为什么它们要把工夫全花在实现一个共识算法上呢?是什么使它们区别于其他任意类型的数据库?为了理解这一点,简单了解如何使用 ZooKeeper 这类服务是很有帮助的。作为应用开发人员,你很少需要直接使用 ZooKeeper,因为它实际上不适合当成通用数据库来用。更有可能的是,你会通过其他项目间接依赖它,例如 HBase,Hadoop YARN,OpenStack Nova 和 Kafka 都依赖 ZooKeeper 在后台运行。这些项目从它那里得到了什么? 8 | 9 | ZooKeeper 和 etcd 被设计为容纳少量完全可以放在内存中的数据(虽然它们仍然会写入磁盘以保证持久性),所以你不会想着把所有应用数据放到这里。这些少量数据会通过容错的全序广播算法复制到所有节点上。正如前面所讨论的那样,数据库复制需要的就是全序广播:如果每条消息代表对数据库的写入,则以相同的顺序应用相同的写入操作可以使副本之间保持一致。ZooKeeper 模仿了 Google 的 Chubby 锁服务,不仅实现了全序广播(因此也实现了共识),而且还构建了一组有趣的其他特性,这些特性在构建分布式系统时变得特别有用。 10 | 11 | ## 线性一致性的原子操作 12 | 13 | 使用原子 CAS 操作可以实现锁:如果多个节点同时尝试执行相同的操作,只有一个节点会成功。共识协议保证了操作的原子性和线性一致性,即使节点发生故障或网络在任意时刻中断。分布式锁通常以**租约(lease)**的形式实现,租约有一个到期时间,以便在客户端失效的情况下最终能被释放。 14 | 15 | ## 操作的全序排序 16 | 17 | 当某个资源受到锁或租约的保护时,你需要一个防护令牌来防止客户端在进程暂停的情况下彼此冲突。防护令牌是每次锁被获取时单调增加的数字。ZooKeeper 通过全局排序操作来提供这个功能,它为每个操作提供一个单调递增的事务 ID(zxid)和版本号(cversion)。 18 | 19 | ## 失效检测 20 | 21 | 客户端在 ZooKeeper 服务器上维护一个长期会话,客户端和服务器周期性地交换心跳包来检查节点是否还活着。即使连接暂时中断,或者 ZooKeeper 节点失效,会话仍保持在活跃状态。但如果心跳停止的持续时间超出会话超时,ZooKeeper 会宣告该会话已死亡。当会话超时(ZooKeeper 调用这些临时节点)时,会话持有的任何锁都可以配置为自动释放(ZooKeeper 称之为临时节点(ephemeral nodes))。 22 | 23 | ## 变更通知 24 | 25 | 客户端不仅可以读取其他客户端创建的锁和值,还可以监听它们的变更。因此,客户端可以知道另一个客户端何时加入集群(基于新客户端写入 ZooKeeper 的值),或发生故障(因其会话超时,而其临时节点消失)。通过订阅通知,客户端不用再通过频繁轮询的方式来找出变更。 26 | 27 | 在这些功能中,只有线性一致的原子操作才真的需要共识。但正是这些功能的组合,使得像 ZooKeeper 这样的系统在分布式协调中非常有用。 28 | 29 | ## 将工作分配给节点 30 | 31 | ZooKeeper/Chubby 模型运行良好的一个例子是,如果你有几个进程实例或服务,需要选择其中一个实例作为主库或首选服务。如果领导者失败,其他节点之一应该接管。这对单主数据库当然非常实用,但对作业调度程序和类似的有状态系统也很好用。另一个例子是,当你有一些分区资源(数据库,消息流,文件存储,分布式 Actor 系统等),并需要决定将哪个分区分配给哪个节点时。当新节点加入集群时,需要将某些分区从现有节点移动到新节点,以便重新平衡负载。当节点被移除或失效时,其他节点需要接管失效节点的工作。 32 | 33 | 这类任务可以通过在 ZooKeeper 中明智地使用原子操作,临时节点与通知来实现。如果设计得当,这种方法允许应用自动从故障中恢复而无需人工干预。不过这并不容易,尽管已经有不少在 ZooKeeper 客户端 API 基础之上提供更高层工具的库,例如 Apache Curator 。但它仍然要比尝试从头实现必要的共识算法要好得多,这样的尝试鲜有成功记录。 34 | 35 | 应用最初只能在单个节点上运行,但最终可能会增长到数千个节点。试图在如此之多的节点上进行多数投票将是非常低效的。相反,ZooKeeper 在固定数量的节点(通常是三到五个)上运行,并在这些节点之间执行其多数票,同时支持潜在的大量客户端。因此,ZooKeeper 提供了一种将协调节点(共识,操作排序和故障检测)的一些工作“外包”到外部服务的方式。 36 | 37 | 通常,由 ZooKeeper 管理的数据的类型变化十分缓慢:代表“分区 7 中的节点运行在 10.1.1.23 上”的信息可能会在几分钟或几小时的时间内发生变化。它不是用来存储应用的运行时状态的,每秒可能会改变数千甚至数百万次。如果应用状态需要从一个节点复制到另一个节点,则可以使用其他工具(如 Apache BookKeeper)。 38 | 39 | ## 服务发现 40 | 41 | ZooKeeper,etcd 和 Consul 也经常用于服务发现——也就是找出你需要连接到哪个 IP 地址才能到达特定的服务。在云数据中心环境中,虚拟机连续来去常见,你通常不会事先知道服务的 IP 地址。相反,你可以配置你的服务,使其在启动时注册服务注册表中的网络端点,然后可以由其他服务找到它们。 42 | 43 | 但是,服务发现是否需要达成共识还不太清楚。DNS 是查找服务名称的 IP 地址的传统方式,它使用多层缓存来实现良好的性能和可用性。从 DNS 读取是绝对不线性一致性的,如果 DNS 查询的结果有点陈旧,通常不会有问题。DNS 的可用性和对网络中断的鲁棒性更重要。 44 | 45 | 尽管服务发现并不需要共识,但领导者选举却是如此。因此,如果你的共识系统已经知道领导是谁,那么也可以使用这些信息来帮助其他服务发现领导是谁。为此,一些共识系统支持只读缓存副本。这些副本异步接收共识算法所有决策的日志,但不主动参与投票。因此,它们能够提供不需要线性一致性的读取请求。 46 | 47 | ## 成员服务 48 | 49 | ZooKeeper 和它的小伙伴们可以看作是成员服务研究的悠久历史的一部分,这个历史可以追溯到 20 世纪 80 年代,并且对建立高度可靠的系统(例如空中交通管制)非常重要。 50 | 51 | 成员资格服务确定哪些节点当前处于活动状态并且是群集的活动成员。正如我们在第 8 章中看到的那样,由于无限的网络延迟,无法可靠地检测到另一个节点是否发生故障。但是,如果你通过一致的方式进行故障检测,那么节点可以就哪些节点应该被认为是存在或不存在达成一致。 52 | 53 | 即使它确实存在,仍然可能发生一个节点被共识错误地宣告死亡。但是对于一个系统来说,在哪些节点构成当前的成员关系方面是非常有用的。例如,选择领导者可能意味着简单地选择当前成员中编号最小的成员,但如果不同的节点对现有成员的成员有不同意见,则这种方法将不起作用。 54 | 55 | # Links 56 | 57 | - https://mp.weixin.qq.com/s/3JIzG7HhIgL1dGzYMS37pw 58 | -------------------------------------------------------------------------------- /10~KV 存储/ZooKeeper/README.md: -------------------------------------------------------------------------------- 1 | # ZooKeeper 2 | 3 | ZooKeeper 是一种用于分布式应用程序的分布式开源协调服务。它公开了一组简单的原语,分布式应用程序可以构建这些原语,以实现更高级别的服务,以实现同步,配置维护以及组和命名。它被设计为易于编程,并使用在熟悉的文件系统目录树结构之后设计的数据模型。它在 Java 中运行,并且具有 Java 和 C 的绑定。 4 | 5 | ZooKeeper 最早起源于雅虎研究院的一个研究小组。当时,雅虎内部很多大型系统基本都需要依赖一个类似的系统来进行分布式协调,但是这些系统往往都存在分布式单点问题。所以,雅虎的开发人员就试图开发一个通用的无单点问题的分布式协调框架,以便让开发人员将精力集中在处理业务逻辑上。 6 | 7 | - 顺序一致性,来自任意特定客户端的更新都会按其发送顺序被提交。也就是说,如果一个客户端将 Znode z 的值更新为 a,在之后的操作中,它又将 z 的值更新为 b,则没有客户端能够在看到 z 的值是 b 之后再看到值 a(如果没有其他对 z 的更新)。 8 | 9 | - 原子性,每个更新要么成功,要么失败。这意味着如果一个更新失败,则不会有客户端会看到这个更新的结果。 10 | 11 | - 单一系统映像,一个客户端无论连接到哪一台服务器,它看到的都是同样的系统视图。这意味着,如果一个客户端在同一个会话中连接到一台新的服务器,它所看到的系统状态不会比 在之前服务器上所看到的更老。当一台服务器出现故障,导致它的一个客户端需要尝试连接集合体中其他的服务器时,所有滞后于故障服务器的服务器都不会接受该 连接请求,除非这些服务器赶上故障服务器。 12 | 13 | - 持久性/可靠性,一个更新一旦成功,其结果就会持久存在并且不会被撤销。这表明更新不会受到服务器故障的影响。 14 | 15 | - 实时性:一旦一个事务被成功应用后,Zookeeper 可以保证客户端立即可以读取到这个事务变更后的最新状态的数据。 16 | 17 | ## 二、Zookeeper 设计目标 18 | 19 | Zookeeper 致力于为那些高吞吐的大型分布式系统提供一个高性能、高可用、且具有严格顺序访问控制能力的分布式协调服务。它具有以下四个目标: 20 | 21 | ### 2.1 目标一:简单的数据模型 22 | 23 | Zookeeper 通过树形结构来存储数据,它由一系列被称为 ZNode 的数据节点组成,类似于常见的文件系统。不过和常见的文件系统不同,Zookeeper 将数据全量存储在内存中,以此来实现高吞吐,减少访问延迟。 24 | 25 |
26 | 27 | ### 2.2 目标二:构建集群 28 | 29 | 可以由一组 Zookeeper 服务构成 Zookeeper 集群,集群中每台机器都会单独在内存中维护自身的状态,并且每台机器之间都保持着通讯,只要集群中有半数机器能够正常工作,那么整个集群就可以正常提供服务。 30 | 31 |
32 | 33 | ### 2.3 目标三:顺序访问 34 | 35 | 对于来自客户端的每个更新请求,Zookeeper 都会分配一个全局唯一的递增 ID,这个 ID 反映了所有事务请求的先后顺序。 36 | 37 | ### 2.4 目标四:高性能高可用 38 | 39 | ZooKeeper 将数据存全量储在内存中以保持高性能,并通过服务集群来实现高可用,由于 Zookeeper 的所有更新和删除都是基于事务的,所以其在读多写少的应用场景中有着很高的性能表现。 40 | -------------------------------------------------------------------------------- /10~KV 存储/ZooKeeper/基础概念/ZAB.md: -------------------------------------------------------------------------------- 1 | ## 四、ZAB 协议 2 | 3 | ### 4.1 ZAB 协议与数据一致性 4 | 5 | ZAB 协议是 Zookeeper 专门设计的一种支持崩溃恢复的原子广播协议。通过该协议,Zookeeper 基于主从模式的系统架构来保持集群中各个副本之间数据的一致性。具体如下: 6 | 7 | Zookeeper 使用一个单一的主进程来接收并处理客户端的所有事务请求,并采用原子广播协议将数据状态的变更以事务 Proposal 的形式广播到所有的副本进程上去。如下图: 8 | 9 |
10 | 11 | 具体流程如下: 12 | 13 | 所有的事务请求必须由唯一的 Leader 服务来处理,Leader 服务将事务请求转换为事务 Proposal,并将该 Proposal 分发给集群中所有的 Follower 服务。如果有半数的 Follower 服务进行了正确的反馈,那么 Leader 就会再次向所有的 Follower 发出 Commit 消息,要求将前一个 Proposal 进行提交。 14 | 15 | ### 4.2 ZAB 协议的内容 16 | 17 | ZAB 协议包括两种基本的模式,分别是崩溃恢复和消息广播: 18 | 19 | #### 1. 崩溃恢复 20 | 21 | 当整个服务框架在启动过程中,或者当 Leader 服务器出现异常时,ZAB 协议就会进入恢复模式,通过过半选举机制产生新的 Leader,之后其他机器将从新的 Leader 上同步状态,当有过半机器完成状态同步后,就退出恢复模式,进入消息广播模式。 22 | 23 | #### 2. 消息广播 24 | 25 | ZAB 协议的消息广播过程使用的是原子广播协议。在整个消息的广播过程中,Leader 服务器会每个事物请求生成对应的 Proposal,并为其分配一个全局唯一的递增的事务 ID(ZXID),之后再对其进行广播。具体过程如下: 26 | 27 | Leader 服务会为每一个 Follower 服务器分配一个单独的队列,然后将事务 Proposal 依次放入队列中,并根据 FIFO(先进先出) 的策略进行消息发送。Follower 服务在接收到 Proposal 后,会将其以事务日志的形式写入本地磁盘中,并在写入成功后反馈给 Leader 一个 Ack 响应。当 Leader 接收到超过半数 Follower 的 Ack 响应后,就会广播一个 Commit 消息给所有的 Follower 以通知其进行事务提交,之后 Leader 自身也会完成对事务的提交。而每一个 Follower 则在接收到 Commit 消息后,完成事务的提交。 28 | 29 |
30 | 31 | # Links 32 | 33 | - https://mp.weixin.qq.com/s/fQzTZXoNyht6mbJYW9Ha2Q?from=groupmessage&isappinstalled=0 34 | -------------------------------------------------------------------------------- /10~KV 存储/ZooKeeper/基础概念/应用场景.md: -------------------------------------------------------------------------------- 1 | ## 五、Zookeeper 的典型应用场景 2 | 3 | ### 5.1 数据的发布/订阅 4 | 5 | 数据的发布/订阅系统,通常也用作配置中心。在分布式系统中,你可能有成千上万个服务节点,如果想要对所有服务的某项配置进行更改,由于数据节点过多,你不可逐台进行修改,而应该在设计时采用统一的配置中心。之后发布者只需要将新的配置发送到配置中心,所有服务节点即可自动下载并进行更新,从而实现配置的集中管理和动态更新。 6 | 7 | Zookeeper 通过 Watcher 机制可以实现数据的发布和订阅。分布式系统的所有的服务节点可以对某个 ZNode 注册监听,之后只需要将新的配置写入该 ZNode,所有服务节点都会收到该事件。 8 | 9 | ### 5.2 命名服务 10 | 11 | 在分布式系统中,通常需要一个全局唯一的名字,如生成全局唯一的订单号等,Zookeeper 可以通过顺序节点的特性来生成全局唯一 ID,从而可以对分布式系统提供命名服务。 12 | 13 | ### 5.3 Master 选举 14 | 15 | 分布式系统一个重要的模式就是主从模式 (Master/Salves),Zookeeper 可以用于该模式下的 Matser 选举。可以让所有服务节点去竞争性地创建同一个 ZNode,由于 Zookeeper 不能有路径相同的 ZNode,必然只有一个服务节点能够创建成功,这样该服务节点就可以成为 Master 节点。 16 | 17 | ### 5.4 分布式锁 18 | 19 | 可以通过 Zookeeper 的临时节点和 Watcher 机制来实现分布式锁,这里以排它锁为例进行说明: 20 | 21 | 分布式系统的所有服务节点可以竞争性地去创建同一个临时 ZNode,由于 Zookeeper 不能有路径相同的 ZNode,必然只有一个服务节点能够创建成功,此时可以认为该节点获得了锁。其他没有获得锁的服务节点通过在该 ZNode 上注册监听,从而当锁释放时再去竞争获得锁。锁的释放情况有以下两种: 22 | 23 | - 当正常执行完业务逻辑后,客户端主动将临时 ZNode 删除,此时锁被释放; 24 | - 当获得锁的客户端发生宕机时,临时 ZNode 会被自动删除,此时认为锁已经释放。 25 | 26 | 当锁被释放后,其他服务节点则再次去竞争性地进行创建,但每次都只有一个服务节点能够获取到锁,这就是排他锁。 27 | 28 | ### 5.5 集群管理 29 | 30 | Zookeeper 还能解决大多数分布式系统中的问题: 31 | 32 | - 如可以通过创建临时节点来建立心跳检测机制。如果分布式系统的某个服务节点宕机了,则其持有的会话会超时,此时该临时节点会被删除,相应的监听事件就会被触发。 33 | - 分布式系统的每个服务节点还可以将自己的节点状态写入临时节点,从而完成状态报告或节点工作进度汇报。 34 | - 通过数据的订阅和发布功能,Zookeeper 还能对分布式系统进行模块的解耦和任务的调度。 35 | - 通过监听机制,还能对分布式系统的服务节点进行动态上下线,从而实现服务的动态扩容。 36 | -------------------------------------------------------------------------------- /10~KV 存储/ZooKeeper/基础概念/核心角色.md: -------------------------------------------------------------------------------- 1 | ## 三、核心概念 2 | 3 | ### 3.1 集群角色 4 | 5 | Zookeeper 集群中的机器分为以下三种角色: 6 | 7 | - **Leader** :为客户端提供读写服务,并维护集群状态,它是由集群选举所产生的; 8 | - **Follower** :为客户端提供读写服务,并定期向 Leader 汇报自己的节点状态。同时也参与写操作“过半写成功”的策略和 Leader 的选举; 9 | - **Observer** :为客户端提供读写服务,并定期向 Leader 汇报自己的节点状态,但不参与写操作“过半写成功”的策略和 Leader 的选举,因此 Observer 可以在不影响写性能的情况下提升集群的读性能。 10 | 11 | ### 3.2 会话 12 | 13 | Zookeeper 客户端通过 TCP 长连接连接到服务集群,会话 (Session) 从第一次连接开始就已经建立,之后通过心跳检测机制来保持有效的会话状态。通过这个连接,客户端可以发送请求并接收响应,同时也可以接收到 Watch 事件的通知。 14 | 15 | 关于会话中另外一个核心的概念是 sessionTimeOut(会话超时时间),当由于网络故障或者客户端主动断开等原因,导致连接断开,此时只要在会话超时时间之内重新建立连接,则之前创建的会话依然有效。 16 | 17 | ### 3.3 数据节点 18 | 19 | Zookeeper 数据模型是由一系列基本数据单元 `Znode`(数据节点) 组成的节点树,其中根节点为 `/`。每个节点上都会保存自己的数据和节点信息。Zookeeper 中节点可以分为两大类: 20 | 21 | - **持久节点** :节点一旦创建,除非被主动删除,否则一直存在; 22 | - **临时节点** :一旦创建该节点的客户端会话失效,则所有该客户端创建的临时节点都会被删除。 23 | 24 | 临时节点和持久节点都可以添加一个特殊的属性:`SEQUENTIAL`,代表该节点是否具有递增属性。如果指定该属性,那么在这个节点创建时,Zookeeper 会自动在其节点名称后面追加一个由父节点维护的递增数字。 25 | 26 | ### 3.4 节点信息 27 | 28 | 每个 ZNode 节点在存储数据的同时,都会维护一个叫做 `Stat` 的数据结构,里面存储了关于该节点的全部状态信息。如下: 29 | 30 | | **状态属性** | **说明** | 31 | | -------------- | ------------------------------------------------------------------------------------------ | 32 | | czxid | 数据节点创建时的事务 ID | 33 | | ctime | 数据节点创建时的时间 | 34 | | mzxid | 数据节点最后一次更新时的事务 ID | 35 | | mtime | 数据节点最后一次更新时的时间 | 36 | | pzxid | 数据节点的子节点最后一次被修改时的事务 ID | 37 | | cversion | 子节点的更改次数 | 38 | | version | 节点数据的更改次数 | 39 | | aversion | 节点的 ACL 的更改次数 | 40 | | ephemeralOwner | 如果节点是临时节点,则表示创建该节点的会话的 SessionID;如果节点是持久节点,则该属性值为 0 | 41 | | dataLength | 数据内容的长度 | 42 | | numChildren | 数据节点当前的子节点个数 | 43 | 44 | ### 3.5 Watcher 45 | 46 | Zookeeper 中一个常用的功能是 Watcher(事件监听器),它允许用户在指定节点上针对感兴趣的事件注册监听,当事件发生时,监听器会被触发,并将事件信息推送到客户端。该机制是 Zookeeper 实现分布式协调服务的重要特性。 47 | 48 | ### 3.6 ACL 49 | 50 | Zookeeper 采用 ACL(Access Control Lists) 策略来进行权限控制,类似于 UNIX 文件系统的权限控制。它定义了如下五种权限: 51 | 52 | - **CREATE**:允许创建子节点; 53 | - **READ**:允许从节点获取数据并列出其子节点; 54 | - **WRITE**:允许为节点设置数据; 55 | - **DELETE**:允许删除子节点; 56 | - **ADMIN**:允许为节点设置权限。 57 | -------------------------------------------------------------------------------- /10~KV 存储/ZooKeeper/架构原理/架构原理.md: -------------------------------------------------------------------------------- 1 | # 集群架构 2 | 3 | ![](https://ww1.sinaimg.cn/large/007rAy9hgy1g0wrbva8sqj30k70ffab4.jpg) 4 | 5 | Leader 服务器是整个 ZooKeeper 集群工作机制中的核心,其主要工作有以下两个: 6 | 7 | - 事务请求的唯一调度和处理者,保证集群事务处理的顺序性。 8 | - 集群内部各服务器的调度者。 9 | 10 | 从角色名字上可以看出,Follewer 服务器是 ZooKeeper 集群状态的跟随者,其主要工作有以下三个: 11 | 12 | - 处理客户端非事务请求,转发事务请求给 Leader 服务器。 13 | - 参与事务请求 Proposal 的投票。 14 | - 参与 Leader 选举投票。 15 | 16 | Observer 充当了一个观察者的角色,在工作原理上基本和 Follower 一致,唯一的区别在于,它不参与任何形式的投票。 17 | 18 | ![](https://ww1.sinaimg.cn/large/007rAy9hgy1g0wrbva8sqj30k70ffab4.jpg) 19 | 20 | - 在 Client 向 Follower 发出一个写请求。 21 | - Follower 把请求转发给 Leader。 22 | - Leader 接收到以后开始发起投票并通知 Follower 进行投票。 23 | - Follower 把投票结果发送给 Leader。 24 | - Leader 将结果汇总后,如果需要写入,则开始写入,同时把写入操作通知给 Follower,然后 commit。 25 | - Follower 把请求结果返回给 Client。 26 | 27 | # 节点组件 28 | 29 | ![](https://ww1.sinaimg.cn/large/007rAy9hgy1g0wrbva8sqj30k70ffab4.jpg) 30 | 31 | - ServerCnxnFactory,ZooKeeper 服务端网络连接工厂。在早期版本中,ZooKeeper 都是自己实现 NIO 框架,从 3.4.0 版本开始,引入了 Netty。可以通过 zookeeper.serverCnxnFactory 来指定使用具体的实现。 32 | 33 | - SessionTracker,ZooKeeper 服务端会话管理器。创建时,会初始化 expirationInterval、nextExpirationTime、sessionsWithTimeout(用于保存每个会话的超时时间),同时还会计算出一个初始化的 sessionID。 34 | 35 | - RequestProcessor,ZooKeeper 的请求处理方式是典型的责任链模式,在服务端,会有多个请求处理器依次来处理一个客户的请求。在服务器启动的时候,会将这些请求处理器串联起来形成一个请求处理链。 36 | 37 | - LearnerCnxAcceptor,Learner 服务器(等于 Follower 服务器)连接请求接收器。负责 Leader 服务器和 Follower 服务器保持连接,以确定集群机器存活情况,并处理连接请求。 38 | 39 | - LearnerHandler,Leader 接收来自其他机器的连接创建请求后,会创建一个 LearnerHandler 实例。每个 LearnerHandler 实例都对应了一个 Leader 和 Learner 服务器之间的连接,其负责 Leader 和 Learner 服务器之间几乎所有的消息通信和数据同步。 40 | ZKDatabase,ZooKeeper 内存数据库,负责管理 ZooKeeper 的所有会话记录以及 DataTree 和事务日志的存储。 41 | 42 | - FileTxnSnapLog,ZooKeeper 上层服务和底层数据存储之间的对接层,提供了一系列的操作数据文件的接口,包括事务文件和快照数据文件。ZooKeeper 根据 zoo.cfg 文件中解析出的快照数据目录 dataDir 和事务日志目录 dataLogDir 来创建 FileTxnSnapLog。 43 | 44 | - LeaderElection,ZooKeeper 会根据 zoo.cfg 中的配置,创建相应的 Leader 选举算法实现。在 ZooKeeper 中,默认提供了三种 Leader 选举算法的实现,分别是 LeaderElection、AuthFastLeaderElection、FastLeaderElection,可以通过配置文件中 electionAlg 属性来指定,分别用 0 ~ 3 来表示。从 3.4.0 版本开始,ZooKeeper 废弃了前两种算法,只支持 FastLeaderEletion 选举算法。 45 | -------------------------------------------------------------------------------- /10~KV 存储/ZooKeeper/部署与操作/ACL.md: -------------------------------------------------------------------------------- 1 | # Zookeeper ACL 2 | 3 | ## 一、前言 4 | 5 | 为了避免存储在 Zookeeper 上的数据被其他程序或者人为误修改,Zookeeper 提供了 ACL(Access Control Lists) 进行权限控制。只有拥有对应权限的用户才可以对节点进行增删改查等操作。下文分别介绍使用原生的 Shell 命令和 Apache Curator 客户端进行权限设置。 6 | 7 | ## 二、使用 Shell 进行权限管理 8 | 9 | ### 2.1 设置与查看权限 10 | 11 | 想要给某个节点设置权限 (ACL),有以下两个可选的命令: 12 | 13 | ```shell 14 | # 1.给已有节点赋予权限 15 | setAcl path acl 16 | 17 | # 2.在创建节点时候指定权限 18 | create [-s] [-e] path data acl 19 | ``` 20 | 21 | 查看指定节点的权限命令如下: 22 | 23 | ```shell 24 | getAcl path 25 | ``` 26 | 27 | ### 2.2 权限组成 28 | 29 | Zookeeper 的权限由[scheme : id :permissions]三部分组成,其中 Schemes 和 Permissions 内置的可选项分别如下: 30 | 31 | **Permissions 可选项**: 32 | 33 | - **CREATE**:允许创建子节点; 34 | - **READ**:允许从节点获取数据并列出其子节点; 35 | - **WRITE**:允许为节点设置数据; 36 | - **DELETE**:允许删除子节点; 37 | - **ADMIN**:允许为节点设置权限。 38 | 39 | **Schemes 可选项**: 40 | 41 | - **world**:默认模式,所有客户端都拥有指定的权限。world 下只有一个 id 选项,就是 anyone,通常组合写法为 `world:anyone:[permissons]`; 42 | - **auth**:只有经过认证的用户才拥有指定的权限。通常组合写法为 `auth:user:password:[permissons]`,使用这种模式时,你需要先进行登录,之后采用 auth 模式设置权限时,`user` 和 `password` 都将使用登录的用户名和密码; 43 | - **digest**:只有经过认证的用户才拥有指定的权限。通常组合写法为 `auth:user:BASE64(SHA1(password)):[permissons]`,这种形式下的密码必须通过 SHA1 和 BASE64 进行双重加密; 44 | - **ip**:限制只有特定 IP 的客户端才拥有指定的权限。通常组成写法为 `ip:182.168.0.168:[permissions]`; 45 | - **super**:代表超级管理员,拥有所有的权限,需要修改 Zookeeper 启动脚本进行配置。 46 | 47 | ### 2.3 添加认证信息 48 | 49 | 可以使用如下所示的命令为当前 Session 添加用户认证信息,等价于登录操作。 50 | 51 | ```shell 52 | # 格式 53 | addauth scheme auth 54 | 55 | #示例:添加用户名为heibai,密码为root的用户认证信息 56 | addauth digest heibai:root 57 | ``` 58 | 59 | ### 2.4 权限设置示例 60 | 61 | #### 1. world 模式 62 | 63 | world 是一种默认的模式,即创建时如果不指定权限,则默认的权限就是 world。 64 | 65 | ```shell 66 | [zk: localhost:2181(CONNECTED) 32] create /hadoop 123 67 | Created /hadoop 68 | [zk: localhost:2181(CONNECTED) 33] getAcl /hadoop 69 | 'world,'anyone #默认的权限 70 | : cdrwa 71 | [zk: localhost:2181(CONNECTED) 34] setAcl /hadoop world:anyone:cwda # 修改节点,不允许所有客户端读 72 | .... 73 | [zk: localhost:2181(CONNECTED) 35] get /hadoop 74 | Authentication is not valid : /hadoop # 权限不足 75 | 76 | ``` 77 | 78 | #### 2. auth 模式 79 | 80 | ```shell 81 | [zk: localhost:2181(CONNECTED) 36] addauth digest heibai:heibai # 登录 82 | [zk: localhost:2181(CONNECTED) 37] setAcl /hadoop auth::cdrwa # 设置权限 83 | [zk: localhost:2181(CONNECTED) 38] getAcl /hadoop # 获取权限 84 | 'digest,'heibai:sCxtVJ1gPG8UW/jzFHR0A1ZKY5s= #用户名和密码 (密码经过加密处理),注意返回的权限类型是 digest 85 | : cdrwa 86 | 87 | #用户名和密码都是使用登录的用户名和密码,即使你在创建权限时候进行指定也是无效的 88 | [zk: localhost:2181(CONNECTED) 39] setAcl /hadoop auth:root:root:cdrwa #指定用户名和密码为 root 89 | [zk: localhost:2181(CONNECTED) 40] getAcl /hadoop 90 | 'digest,'heibai:sCxtVJ1gPG8UW/jzFHR0A1ZKY5s= #无效,使用的用户名和密码依然还是 heibai 91 | : cdrwa 92 | 93 | ``` 94 | 95 | #### 3. digest 模式 96 | 97 | ```shell 98 | [zk:44] create /spark "spark" digest:heibai:sCxtVJ1gPG8UW/jzFHR0A1ZKY5s=:cdrwa #指定用户名和加密后的密码 99 | [zk:45] getAcl /spark #获取权限 100 | 'digest,'heibai:sCxtVJ1gPG8UW/jzFHR0A1ZKY5s= # 返回的权限类型是 digest 101 | : cdrwa 102 | ``` 103 | 104 | 到这里你可以发现使用 `auth` 模式设置的权限和使用 `digest` 模式设置的权限,在最终结果上,得到的权限模式都是 `digest`。某种程度上,你可以把 `auth` 模式理解成是 `digest` 模式的一种简便实现。因为在 `digest` 模式下,每次设置都需要书写用户名和加密后的密码,这是比较繁琐的,采用 `auth` 模式就可以避免这种麻烦。 105 | 106 | #### 4. ip 模式 107 | 108 | 限定只有特定的 ip 才能访问。 109 | 110 | ```shell 111 | [zk: localhost:2181(CONNECTED) 46] create /hive "hive" ip:192.168.0.108:cdrwa 112 | [zk: localhost:2181(CONNECTED) 47] get /hive 113 | Authentication is not valid : /hive # 当前主机已经不能访问 114 | ``` 115 | 116 | 这里可以看到当前主机已经不能访问,想要能够再次访问,可以使用对应 IP 的客户端,或使用下面介绍的 `super` 模式。 117 | 118 | #### 5. super 模式 119 | 120 | 需要修改启动脚本 `zkServer.sh`,并在指定位置添加超级管理员账户和密码信息: 121 | 122 | ```shell 123 | "-Dzookeeper.DigestAuthenticationProvider.superDigest=heibai:sCxtVJ1gPG8UW/jzFHR0A1ZKY5s=" 124 | ``` 125 | 126 |
127 | 128 | 修改完成后需要使用 `zkServer.sh restart` 重启服务,此时再次访问限制 IP 的节点: 129 | 130 | ```shell 131 | [zk: localhost:2181(CONNECTED) 0] get /hive #访问受限 132 | Authentication is not valid : /hive 133 | [zk: localhost:2181(CONNECTED) 1] addauth digest heibai:heibai # 登录 (添加认证信息) 134 | [zk: localhost:2181(CONNECTED) 2] get /hive #成功访问 135 | hive 136 | cZxid = 0x158 137 | ctime = Sat May 25 09:11:29 CST 2019 138 | mZxid = 0x158 139 | mtime = Sat May 25 09:11:29 CST 2019 140 | pZxid = 0x158 141 | cversion = 0 142 | dataVersion = 0 143 | aclVersion = 0 144 | ephemeralOwner = 0x0 145 | dataLength = 4 146 | numChildren = 0 147 | ``` 148 | 149 | ## 三、使用 Java 客户端进行权限管理 150 | 151 | ### 3.1 主要依赖 152 | 153 | 这里以 Apache Curator 为例,使用前需要导入相关依赖,完整依赖如下: 154 | 155 | ```xml 156 | 157 | 158 | 159 | org.apache.curator 160 | curator-framework 161 | 4.0.0 162 | 163 | 164 | org.apache.curator 165 | curator-recipes 166 | 4.0.0 167 | 168 | 169 | org.apache.zookeeper 170 | zookeeper 171 | 3.4.13 172 | 173 | 174 | 175 | junit 176 | junit 177 | 4.12 178 | 179 | 180 | ``` 181 | 182 | ### 3.2 权限管理 API 183 | 184 | Apache Curator 权限设置的示例如下: 185 | 186 | ```java 187 | public class AclOperation { 188 | 189 | private CuratorFramework client = null; 190 | private static final String zkServerPath = "192.168.0.226:2181"; 191 | private static final String nodePath = "/hadoop/hdfs"; 192 | 193 | @Before 194 | public void prepare() { 195 | RetryPolicy retryPolicy = new RetryNTimes(3, 5000); 196 | client = CuratorFrameworkFactory.builder() 197 | .authorization("digest", "heibai:123456".getBytes()) //等价于 addauth 命令 198 | .connectString(zkServerPath) 199 | .sessionTimeoutMs(10000).retryPolicy(retryPolicy) 200 | .namespace("workspace").build(); 201 | client.start(); 202 | } 203 | 204 | /** 205 | * 新建节点并赋予权限 206 | */ 207 | @Test 208 | public void createNodesWithAcl() throws Exception { 209 | List aclList = new ArrayList<>(); 210 | // 对密码进行加密 211 | String digest1 = DigestAuthenticationProvider.generateDigest("heibai:123456"); 212 | String digest2 = DigestAuthenticationProvider.generateDigest("ying:123456"); 213 | Id user01 = new Id("digest", digest1); 214 | Id user02 = new Id("digest", digest2); 215 | // 指定所有权限 216 | aclList.add(new ACL(Perms.ALL, user01)); 217 | // 如果想要指定权限的组合,中间需要使用 | ,这里的|代表的是位运算中的 按位或 218 | aclList.add(new ACL(Perms.DELETE | Perms.CREATE, user02)); 219 | 220 | // 创建节点 221 | byte[] data = "abc".getBytes(); 222 | client.create().creatingParentsIfNeeded() 223 | .withMode(CreateMode.PERSISTENT) 224 | .withACL(aclList, true) 225 | .forPath(nodePath, data); 226 | } 227 | 228 | 229 | /** 230 | * 给已有节点设置权限,注意这会删除所有原来节点上已有的权限设置 231 | */ 232 | @Test 233 | public void SetAcl() throws Exception { 234 | String digest = DigestAuthenticationProvider.generateDigest("admin:admin"); 235 | Id user = new Id("digest", digest); 236 | client.setACL() 237 | .withACL(Collections.singletonList(new ACL(Perms.READ | Perms.DELETE, user))) 238 | .forPath(nodePath); 239 | } 240 | 241 | /** 242 | * 获取权限 243 | */ 244 | @Test 245 | public void getAcl() throws Exception { 246 | List aclList = client.getACL().forPath(nodePath); 247 | ACL acl = aclList.get(0); 248 | System.out.println(acl.getId().getId() 249 | + "是否有删读权限:" + (acl.getPerms() == (Perms.READ | Perms.DELETE))); 250 | } 251 | 252 | @After 253 | public void destroy() { 254 | if (client != null) { 255 | client.close(); 256 | } 257 | } 258 | } 259 | ``` 260 | 261 | > 完整源码见本仓库: https://github.com/heibaiying/BigData-Notes/tree/master/code/Zookeeper/curator 262 | 263 |
264 | -------------------------------------------------------------------------------- /10~KV 存储/ZooKeeper/部署与操作/Apache Curator.md: -------------------------------------------------------------------------------- 1 | # Zookeeper Java 客户端 ——Apache Curator 2 | 3 | ## 一、基本依赖 4 | 5 | Curator 是 Netflix 公司开源的一个 Zookeeper 客户端,目前由 Apache 进行维护。与 Zookeeper 原生客户端相比,Curator 的抽象层次更高,功能也更加丰富,是目前 Zookeeper 使用范围最广的 Java 客户端。本篇文章主要讲解其基本使用,项目采用 Maven 构建,以单元测试的方法进行讲解,相关依赖如下: 6 | 7 | ```xml 8 | 9 | 10 | 11 | org.apache.curator 12 | curator-framework 13 | 4.0.0 14 | 15 | 16 | org.apache.curator 17 | curator-recipes 18 | 4.0.0 19 | 20 | 21 | org.apache.zookeeper 22 | zookeeper 23 | 3.4.13 24 | 25 | 26 | 27 | junit 28 | junit 29 | 4.12 30 | 31 | 32 | ``` 33 | 34 | > 完整源码见本仓库: https://github.com/heibaiying/BigData-Notes/tree/master/code/Zookeeper/curator 35 | 36 | ## 二、客户端相关操作 37 | 38 | ### 2.1 创建客户端实例 39 | 40 | 这里使用 `@Before` 在单元测试执行前创建客户端实例,并使用 `@After` 在单元测试后关闭客户端连接。 41 | 42 | ```java 43 | public class BasicOperation { 44 | 45 | private CuratorFramework client = null; 46 | private static final String zkServerPath = "192.168.0.226:2181"; 47 | private static final String nodePath = "/hadoop/yarn"; 48 | 49 | @Before 50 | public void prepare() { 51 | // 重试策略 52 | RetryPolicy retryPolicy = new RetryNTimes(3, 5000); 53 | client = CuratorFrameworkFactory.builder() 54 | .connectString(zkServerPath) 55 | .sessionTimeoutMs(10000).retryPolicy(retryPolicy) 56 | .namespace("workspace").build(); //指定命名空间后,client 的所有路径操作都会以/workspace 开头 57 | client.start(); 58 | } 59 | 60 | @After 61 | public void destroy() { 62 | if (client != null) { 63 | client.close(); 64 | } 65 | } 66 | } 67 | ``` 68 | 69 | ### 2.2 重试策略 70 | 71 | 在连接 Zookeeper 时,Curator 提供了多种重试策略以满足各种需求,所有重试策略均继承自 `RetryPolicy` 接口,如下图: 72 | 73 |
74 | 75 | 这些重试策略类主要分为以下两类: 76 | 77 | - **RetryForever** :代表一直重试,直到连接成功; 78 | - **SleepingRetry** : 基于一定间隔时间的重试。这里以其子类 `ExponentialBackoffRetry` 为例说明,其构造器如下: 79 | 80 | ```java 81 | /** 82 | * @param baseSleepTimeMs 重试之间等待的初始时间 83 | * @param maxRetries 最大重试次数 84 | * @param maxSleepMs 每次重试间隔的最长睡眠时间(毫秒) 85 | */ 86 | ExponentialBackoffRetry(int baseSleepTimeMs, int maxRetries, int maxSleepMs) 87 | ``` 88 | 89 | ### 2.3 判断服务状态 90 | 91 | ```scala 92 | @Test 93 | public void getStatus() { 94 | CuratorFrameworkState state = client.getState(); 95 | System.out.println("服务是否已经启动:" + (state == CuratorFrameworkState.STARTED)); 96 | } 97 | ``` 98 | 99 | ## 三、节点增删改查 100 | 101 | ### 3.1 创建节点 102 | 103 | ```java 104 | @Test 105 | public void createNodes() throws Exception { 106 | byte[] data = "abc".getBytes(); 107 | client.create().creatingParentsIfNeeded() 108 | .withMode(CreateMode.PERSISTENT) //节点类型 109 | .withACL(ZooDefs.Ids.OPEN_ACL_UNSAFE) 110 | .forPath(nodePath, data); 111 | } 112 | ``` 113 | 114 | 创建时可以指定节点类型,这里的节点类型和 Zookeeper 原生的一致,全部类型定义在枚举类 `CreateMode` 中: 115 | 116 | ```java 117 | public enum CreateMode { 118 | // 永久节点 119 | PERSISTENT (0, false, false), 120 | //永久有序节点 121 | PERSISTENT_SEQUENTIAL (2, false, true), 122 | // 临时节点 123 | EPHEMERAL (1, true, false), 124 | // 临时有序节点 125 | EPHEMERAL_SEQUENTIAL (3, true, true); 126 | .... 127 | } 128 | ``` 129 | 130 | ### 2.2 获取节点信息 131 | 132 | ```scala 133 | @Test 134 | public void getNode() throws Exception { 135 | Stat stat = new Stat(); 136 | byte[] data = client.getData().storingStatIn(stat).forPath(nodePath); 137 | System.out.println("节点数据:" + new String(data)); 138 | System.out.println("节点信息:" + stat.toString()); 139 | } 140 | ``` 141 | 142 | 如上所示,节点信息被封装在 `Stat` 类中,其主要属性如下: 143 | 144 | ```java 145 | public class Stat implements Record { 146 | private long czxid; 147 | private long mzxid; 148 | private long ctime; 149 | private long mtime; 150 | private int version; 151 | private int cversion; 152 | private int aversion; 153 | private long ephemeralOwner; 154 | private int dataLength; 155 | private int numChildren; 156 | private long pzxid; 157 | ... 158 | } 159 | ``` 160 | 161 | 每个属性的含义如下: 162 | 163 | | **状态属性** | **说明** | 164 | | -------------- | ------------------------------------------------------------------------------------------ | 165 | | czxid | 数据节点创建时的事务 ID | 166 | | ctime | 数据节点创建时的时间 | 167 | | mzxid | 数据节点最后一次更新时的事务 ID | 168 | | mtime | 数据节点最后一次更新时的时间 | 169 | | pzxid | 数据节点的子节点最后一次被修改时的事务 ID | 170 | | cversion | 子节点的更改次数 | 171 | | version | 节点数据的更改次数 | 172 | | aversion | 节点的 ACL 的更改次数 | 173 | | ephemeralOwner | 如果节点是临时节点,则表示创建该节点的会话的 SessionID;如果节点是持久节点,则该属性值为 0 | 174 | | dataLength | 数据内容的长度 | 175 | | numChildren | 数据节点当前的子节点个数 | 176 | 177 | ### 2.3 获取子节点列表 178 | 179 | ```java 180 | @Test 181 | public void getChildrenNodes() throws Exception { 182 | List childNodes = client.getChildren().forPath("/hadoop"); 183 | for (String s : childNodes) { 184 | System.out.println(s); 185 | } 186 | } 187 | ``` 188 | 189 | ### 2.4 更新节点 190 | 191 | 更新时可以传入版本号也可以不传入,如果传入则类似于乐观锁机制,只有在版本号正确的时候才会被更新。 192 | 193 | ```scala 194 | @Test 195 | public void updateNode() throws Exception { 196 | byte[] newData = "defg".getBytes(); 197 | client.setData().withVersion(0) // 传入版本号,如果版本号错误则拒绝更新操作,并抛出 BadVersion 异常 198 | .forPath(nodePath, newData); 199 | } 200 | ``` 201 | 202 | ### 2.5 删除节点 203 | 204 | ```java 205 | @Test 206 | public void deleteNodes() throws Exception { 207 | client.delete() 208 | .guaranteed() // 如果删除失败,那么在会继续执行,直到成功 209 | .deletingChildrenIfNeeded() // 如果有子节点,则递归删除 210 | .withVersion(0) // 传入版本号,如果版本号错误则拒绝删除操作,并抛出 BadVersion 异常 211 | .forPath(nodePath); 212 | } 213 | ``` 214 | 215 | ### 2.6 判断节点是否存在 216 | 217 | ```java 218 | @Test 219 | public void existNode() throws Exception { 220 | // 如果节点存在则返回其状态信息如果不存在则为 null 221 | Stat stat = client.checkExists().forPath(nodePath + "aa/bb/cc"); 222 | System.out.println("节点是否存在:" + !(stat == null)); 223 | } 224 | ``` 225 | 226 | ## 三、监听事件 227 | 228 | ### 3.1 创建一次性监听 229 | 230 | 和 Zookeeper 原生监听一样,使用 `usingWatcher` 注册的监听是一次性的,即监听只会触发一次,触发后就销毁。示例如下: 231 | 232 | ```java 233 | @Test 234 | public void DisposableWatch() throws Exception { 235 | client.getData().usingWatcher(new CuratorWatcher() { 236 | public void process(WatchedEvent event) { 237 | System.out.println("节点" + event.getPath() + "发生了事件:" + event.getType()); 238 | } 239 | }).forPath(nodePath); 240 | Thread.sleep(1000 * 1000); //休眠以观察测试效果 241 | } 242 | ``` 243 | 244 | ### 3.2 创建永久监听 245 | 246 | Curator 还提供了创建永久监听的 API,其使用方式如下: 247 | 248 | ```java 249 | @Test 250 | public void permanentWatch() throws Exception { 251 | // 使用 NodeCache 包装节点,对其注册的监听作用于节点,且是永久性的 252 | NodeCache nodeCache = new NodeCache(client, nodePath); 253 | // 通常设置为 true, 代表创建 nodeCache 时,就去获取对应节点的值并缓存 254 | nodeCache.start(true); 255 | nodeCache.getListenable().addListener(new NodeCacheListener() { 256 | public void nodeChanged() { 257 | ChildData currentData = nodeCache.getCurrentData(); 258 | if (currentData != null) { 259 | System.out.println("节点路径:" + currentData.getPath() + 260 | "数据:" + new String(currentData.getData())); 261 | } 262 | } 263 | }); 264 | Thread.sleep(1000 * 1000); //休眠以观察测试效果 265 | } 266 | ``` 267 | 268 | ### 3.3 监听子节点 269 | 270 | 这里以监听 `/hadoop` 下所有子节点为例,实现方式如下: 271 | 272 | ```scala 273 | @Test 274 | public void permanentChildrenNodesWatch() throws Exception { 275 | 276 | // 第三个参数代表除了节点状态外,是否还缓存节点内容 277 | PathChildrenCache childrenCache = new PathChildrenCache(client, "/hadoop", true); 278 | /* 279 | * StartMode 代表初始化方式: 280 | * NORMAL: 异步初始化 281 | * BUILD_INITIAL_CACHE: 同步初始化 282 | * POST_INITIALIZED_EVENT: 异步并通知,初始化之后会触发 INITIALIZED 事件 283 | */ 284 | childrenCache.start(StartMode.POST_INITIALIZED_EVENT); 285 | 286 | List childDataList = childrenCache.getCurrentData(); 287 | System.out.println("当前数据节点的子节点列表:"); 288 | childDataList.forEach(x -> System.out.println(x.getPath())); 289 | 290 | childrenCache.getListenable().addListener(new PathChildrenCacheListener() { 291 | public void childEvent(CuratorFramework client, PathChildrenCacheEvent event) { 292 | switch (event.getType()) { 293 | case INITIALIZED: 294 | System.out.println("childrenCache 初始化完成"); 295 | break; 296 | case CHILD_ADDED: 297 | // 需要注意的是: 即使是之前已经存在的子节点,也会触发该监听,因为会把该子节点加入 childrenCache 缓存中 298 | System.out.println("增加子节点:" + event.getData().getPath()); 299 | break; 300 | case CHILD_REMOVED: 301 | System.out.println("删除子节点:" + event.getData().getPath()); 302 | break; 303 | case CHILD_UPDATED: 304 | System.out.println("被修改的子节点的路径:" + event.getData().getPath()); 305 | System.out.println("修改后的数据:" + new String(event.getData().getData())); 306 | break; 307 | } 308 | } 309 | }); 310 | Thread.sleep(1000 * 1000); //休眠以观察测试效果 311 | } 312 | ``` 313 | 314 |
315 | -------------------------------------------------------------------------------- /10~KV 存储/ZooKeeper/部署与操作/数据操作.md: -------------------------------------------------------------------------------- 1 | # Zookeeper 常用 Shell 命令 2 | 3 | ## 一、节点增删改查 4 | 5 | ### 1.1 启动服务和连接服务 6 | 7 | ```shell 8 | # 启动服务 9 | bin/zkServer.sh start 10 | 11 | #连接服务 不指定服务地址则默认连接到localhost:2181 12 | zkCli.sh -server hadoop001:2181 13 | ``` 14 | 15 | ### 1.2 help 命令 16 | 17 | 使用 `help` 可以查看所有命令及格式。 18 | 19 | ### 1.3 查看节点列表 20 | 21 | 查看节点列表有 `ls path` 和 `ls2 path` 两个命令,后者是前者的增强,不仅可以查看指定路径下的所有节点,还可以查看当前节点的信息。 22 | 23 | ```shell 24 | [zk: localhost:2181(CONNECTED) 0] ls / 25 | [cluster, controller_epoch, brokers, storm, zookeeper, admin, ...] 26 | [zk: localhost:2181(CONNECTED) 1] ls2 / 27 | [cluster, controller_epoch, brokers, storm, zookeeper, admin, ....] 28 | cZxid = 0x0 29 | ctime = Thu Jan 01 08:00:00 CST 1970 30 | mZxid = 0x0 31 | mtime = Thu Jan 01 08:00:00 CST 1970 32 | pZxid = 0x130 33 | cversion = 19 34 | dataVersion = 0 35 | aclVersion = 0 36 | ephemeralOwner = 0x0 37 | dataLength = 0 38 | numChildren = 11 39 | ``` 40 | 41 | ### 1.4 新增节点 42 | 43 | ```shell 44 | create [-s] [-e] path data acl #其中-s 为有序节点,-e 临时节点 45 | ``` 46 | 47 | 创建节点并写入数据: 48 | 49 | ```shell 50 | create /hadoop 123456 51 | ``` 52 | 53 | 创建有序节点,此时创建的节点名为指定节点名 + 自增序号: 54 | 55 | ```shell 56 | [zk: localhost:2181(CONNECTED) 23] create -s /a "aaa" 57 | Created /a0000000022 58 | [zk: localhost:2181(CONNECTED) 24] create -s /b "bbb" 59 | Created /b0000000023 60 | [zk: localhost:2181(CONNECTED) 25] create -s /c "ccc" 61 | Created /c0000000024 62 | ``` 63 | 64 | 创建临时节点,临时节点会在会话过期后被删除: 65 | 66 | ```shell 67 | [zk: localhost:2181(CONNECTED) 26] create -e /tmp "tmp" 68 | Created /tmp 69 | ``` 70 | 71 | ### 1.5 查看节点 72 | 73 | #### 1. 获取节点数据 74 | 75 | ```shell 76 | # 格式 77 | get path [watch] 78 | ``` 79 | 80 | ```shell 81 | [zk: localhost:2181(CONNECTED) 31] get /hadoop 82 | 123456 #节点数据 83 | cZxid = 0x14b 84 | ctime = Fri May 24 17:03:06 CST 2019 85 | mZxid = 0x14b 86 | mtime = Fri May 24 17:03:06 CST 2019 87 | pZxid = 0x14b 88 | cversion = 0 89 | dataVersion = 0 90 | aclVersion = 0 91 | ephemeralOwner = 0x0 92 | dataLength = 6 93 | numChildren = 0 94 | ``` 95 | 96 | 节点各个属性如下表。其中一个重要的概念是 Zxid(ZooKeeper Transaction Id),ZooKeeper 节点的每一次更改都具有唯一的 Zxid,如果 Zxid1 小于 Zxid2,则 Zxid1 的更改发生在 Zxid2 更改之前。 97 | 98 | | **状态属性** | **说明** | 99 | | -------------- | ------------------------------------------------------------------------------------------ | 100 | | cZxid | 数据节点创建时的事务 ID | 101 | | ctime | 数据节点创建时的时间 | 102 | | mZxid | 数据节点最后一次更新时的事务 ID | 103 | | mtime | 数据节点最后一次更新时的时间 | 104 | | pZxid | 数据节点的子节点最后一次被修改时的事务 ID | 105 | | cversion | 子节点的更改次数 | 106 | | dataVersion | 节点数据的更改次数 | 107 | | aclVersion | 节点的 ACL 的更改次数 | 108 | | ephemeralOwner | 如果节点是临时节点,则表示创建该节点的会话的 SessionID;如果节点是持久节点,则该属性值为 0 | 109 | | dataLength | 数据内容的长度 | 110 | | numChildren | 数据节点当前的子节点个数 | 111 | 112 | #### 2. 查看节点状态 113 | 114 | 可以使用 `stat` 命令查看节点状态,它的返回值和 `get` 命令类似,但不会返回节点数据。 115 | 116 | ```shell 117 | [zk: localhost:2181(CONNECTED) 32] stat /hadoop 118 | cZxid = 0x14b 119 | ctime = Fri May 24 17:03:06 CST 2019 120 | mZxid = 0x14b 121 | mtime = Fri May 24 17:03:06 CST 2019 122 | pZxid = 0x14b 123 | cversion = 0 124 | dataVersion = 0 125 | aclVersion = 0 126 | ephemeralOwner = 0x0 127 | dataLength = 6 128 | numChildren = 0 129 | ``` 130 | 131 | ### 1.6 更新节点 132 | 133 | 更新节点的命令是 `set`,可以直接进行修改,如下: 134 | 135 | ```shell 136 | [zk: localhost:2181(CONNECTED) 33] set /hadoop 345 137 | cZxid = 0x14b 138 | ctime = Fri May 24 17:03:06 CST 2019 139 | mZxid = 0x14c 140 | mtime = Fri May 24 17:13:05 CST 2019 141 | pZxid = 0x14b 142 | cversion = 0 143 | dataVersion = 1 # 注意更改后此时版本号为 1,默认创建时为 0 144 | aclVersion = 0 145 | ephemeralOwner = 0x0 146 | dataLength = 3 147 | numChildren = 0 148 | ``` 149 | 150 | 也可以基于版本号进行更改,此时类似于乐观锁机制,当你传入的数据版本号 (dataVersion) 和当前节点的数据版本号不符合时,zookeeper 会拒绝本次修改: 151 | 152 | ```shell 153 | [zk: localhost:2181(CONNECTED) 34] set /hadoop 678 0 154 | version No is not valid : /hadoop #无效的版本号 155 | ``` 156 | 157 | ### 1.7 删除节点 158 | 159 | 删除节点的语法如下: 160 | 161 | ```shell 162 | delete path [version] 163 | ``` 164 | 165 | 和更新节点数据一样,也可以传入版本号,当你传入的数据版本号 (dataVersion) 和当前节点的数据版本号不符合时,zookeeper 不会执行删除操作。 166 | 167 | ```shell 168 | [zk: localhost:2181(CONNECTED) 36] delete /hadoop 0 169 | version No is not valid : /hadoop #无效的版本号 170 | [zk: localhost:2181(CONNECTED) 37] delete /hadoop 1 171 | [zk: localhost:2181(CONNECTED) 38] 172 | ``` 173 | 174 | 要想删除某个节点及其所有后代节点,可以使用递归删除,命令为 `rmr path`。 175 | 176 | ## 二、监听器 177 | 178 | ### 2.1 get path [watch] 179 | 180 | 使用 `get path [watch]` 注册的监听器能够在节点内容发生改变的时候,向客户端发出通知。需要注意的是 zookeeper 的触发器是一次性的 (One-time trigger),即触发一次后就会立即失效。 181 | 182 | ```shell 183 | [zk: localhost:2181(CONNECTED) 4] get /hadoop watch 184 | [zk: localhost:2181(CONNECTED) 5] set /hadoop 45678 185 | WATCHER:: 186 | WatchedEvent state:SyncConnected type:NodeDataChanged path:/hadoop #节点值改变 187 | ``` 188 | 189 | ### 2.2 stat path [watch] 190 | 191 | 使用 `stat path [watch]` 注册的监听器能够在节点状态发生改变的时候,向客户端发出通知。 192 | 193 | ```shell 194 | [zk: localhost:2181(CONNECTED) 7] stat /hadoop watch 195 | [zk: localhost:2181(CONNECTED) 8] set /hadoop 112233 196 | WATCHER:: 197 | WatchedEvent state:SyncConnected type:NodeDataChanged path:/hadoop #节点值改变 198 | ``` 199 | 200 | ### 2.3 ls\ls2 path [watch] 201 | 202 | 使用 `ls path [watch]` 或 `ls2 path [watch]` 注册的监听器能够监听该节点下所有**子节点**的增加和删除操作。 203 | 204 | ```shell 205 | [zk: localhost:2181(CONNECTED) 9] ls /hadoop watch 206 | [] 207 | [zk: localhost:2181(CONNECTED) 10] create /hadoop/yarn "aaa" 208 | WATCHER:: 209 | WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/hadoop 210 | ``` 211 | 212 | ## 三、zookeeper 四字命令 213 | 214 | | 命令 | 功能描述 | 215 | | ---- | --------------------------------------------------------------------------------------------------------------------------- | 216 | | conf | 打印服务配置的详细信息。 | 217 | | cons | 列出连接到此服务器的所有客户端的完整连接/会话详细信息。包括接收/发送的数据包数量,会话 ID,操作延迟,上次执行的操作等信息。 | 218 | | dump | 列出未完成的会话和临时节点。这只适用于 Leader 节点。 | 219 | | envi | 打印服务环境的详细信息。 | 220 | | ruok | 测试服务是否处于正确状态。如果正确则返回“imok”,否则不做任何相应。 | 221 | | stat | 列出服务器和连接客户端的简要详细信息。 | 222 | | wchs | 列出所有 watch 的简单信息。 | 223 | | wchc | 按会话列出服务器 watch 的详细信息。 | 224 | | wchp | 按路径列出服务器 watch 的详细信息。 | 225 | 226 | > 更多四字命令可以参阅官方文档:https://zookeeper.apache.org/doc/current/zookeeperAdmin.html 227 | 228 | 使用前需要使用 `yum install nc` 安装 nc 命令,使用示例如下: 229 | 230 | ```shell 231 | [root@hadoop001 bin]# echo stat | nc localhost 2181 232 | Zookeeper version: 3.4.13-2d71af4dbe22557fda74f9a9b4309b15a7487f03, 233 | built on 06/29/2018 04:05 GMT 234 | Clients: 235 | /0:0:0:0:0:0:0:1:50584[1](queued=0,recved=371,sent=371) 236 | /0:0:0:0:0:0:0:1:50656[0](queued=0,recved=1,sent=0) 237 | Latency min/avg/max: 0/0/19 238 | Received: 372 239 | Sent: 371 240 | Connections: 2 241 | Outstanding: 0 242 | Zxid: 0x150 243 | Mode: standalone 244 | Node count: 167 245 | ``` 246 | 247 |
248 | -------------------------------------------------------------------------------- /10~KV 存储/ZooKeeper/部署与操作/部署与配置.md: -------------------------------------------------------------------------------- 1 | # Zookeeper 单机环境和集群环境搭建 2 | 3 | ## 一、单机环境搭建 4 | 5 | ### 1.1 下载 6 | 7 | 下载对应版本 Zookeeper,这里我下载的版本 `3.4.14`。官方下载地址:https://archive.apache.org/dist/zookeeper/ 8 | 9 | ```shell 10 | # wget https://archive.apache.org/dist/zookeeper/zookeeper-3.4.14/zookeeper-3.4.14.tar.gz 11 | ``` 12 | 13 | ### 1.2 解压 14 | 15 | ```shell 16 | # tar -zxvf zookeeper-3.4.14.tar.gz 17 | ``` 18 | 19 | ### 1.3 配置环境变量 20 | 21 | ```shell 22 | # vim /etc/profile 23 | ``` 24 | 25 | 添加环境变量: 26 | 27 | ```shell 28 | export ZOOKEEPER_HOME=/usr/app/zookeeper-3.4.14 29 | export PATH=$ZOOKEEPER_HOME/bin:$PATH 30 | ``` 31 | 32 | 使得配置的环境变量生效: 33 | 34 | ```shell 35 | # source /etc/profile 36 | ``` 37 | 38 | ### 1.4 修改配置 39 | 40 | 进入安装目录的 `conf/` 目录下,拷贝配置样本并进行修改: 41 | 42 | ``` 43 | # cp zoo_sample.cfg zoo.cfg 44 | ``` 45 | 46 | 指定数据存储目录和日志文件目录(目录不用预先创建,程序会自动创建),修改后完整配置如下: 47 | 48 | ```properties 49 | # The number of milliseconds of each tick 50 | tickTime=2000 51 | # The number of ticks that the initial 52 | # synchronization phase can take 53 | initLimit=10 54 | # The number of ticks that can pass between 55 | # sending a request and getting an acknowledgement 56 | syncLimit=5 57 | # the directory where the snapshot is stored. 58 | # do not use /tmp for storage, /tmp here is just 59 | # example sakes. 60 | dataDir=/usr/local/zookeeper/data 61 | dataLogDir=/usr/local/zookeeper/log 62 | # the port at which the clients will connect 63 | clientPort=2181 64 | # the maximum number of client connections. 65 | # increase this if you need to handle more clients 66 | #maxClientCnxns=60 67 | # 68 | # Be sure to read the maintenance section of the 69 | # administrator guide before turning on autopurge. 70 | # 71 | # http://zookeeper.apache.org/doc/current/zookeeperAdmin.html#sc_maintenance 72 | # 73 | # The number of snapshots to retain in dataDir 74 | #autopurge.snapRetainCount=3 75 | # Purge task interval in hours 76 | # Set to "0" to disable auto purge feature 77 | #autopurge.purgeInterval=1 78 | ``` 79 | 80 | > 配置参数说明: 81 | > 82 | > - **tickTime**:用于计算的基础时间单元。比如 session 超时:N\*tickTime; 83 | > - **initLimit**:用于集群,允许从节点连接并同步到 master 节点的初始化连接时间,以 tickTime 的倍数来表示; 84 | > - **syncLimit**:用于集群,master 主节点与从节点之间发送消息,请求和应答时间长度(心跳机制); 85 | > - **dataDir**:数据存储位置; 86 | > - **dataLogDir**:日志目录; 87 | > - **clientPort**:用于客户端连接的端口,默认 2181 88 | 89 | ### 1.5 启动 90 | 91 | 由于已经配置过环境变量,直接使用下面命令启动即可: 92 | 93 | ``` 94 | zkServer.sh start 95 | ``` 96 | 97 | ### 1.6 验证 98 | 99 | 使用 JPS 验证进程是否已经启动,出现 `QuorumPeerMain` 则代表启动成功。 100 | 101 | ```shell 102 | [root@hadoop001 bin]# jps 103 | 3814 QuorumPeerMain 104 | ``` 105 | 106 | ## 二、集群环境搭建 107 | 108 | 为保证集群高可用,Zookeeper 集群的节点数最好是奇数,最少有三个节点,所以这里演示搭建一个三个节点的集群。这里我使用三台主机进行搭建,主机名分别为 hadoop001,hadoop002,hadoop003。 109 | 110 | ### 2.1 修改配置 111 | 112 | 解压一份 zookeeper 安装包,修改其配置文件 `zoo.cfg`,内容如下。之后使用 scp 命令将安装包分发到三台服务器上: 113 | 114 | ```shell 115 | tickTime=2000 116 | initLimit=10 117 | syncLimit=5 118 | dataDir=/usr/local/zookeeper-cluster/data/ 119 | dataLogDir=/usr/local/zookeeper-cluster/log/ 120 | clientPort=2181 121 | 122 | # server.1 这个1是服务器的标识,可以是任意有效数字,标识这是第几个服务器节点,这个标识要写到dataDir目录下面myid文件里 123 | # 指名集群间通讯端口和选举端口 124 | server.1=hadoop001:2287:3387 125 | server.2=hadoop002:2287:3387 126 | server.3=hadoop003:2287:3387 127 | ``` 128 | 129 | ### 2.2 标识节点 130 | 131 | 分别在三台主机的 `dataDir` 目录下新建 `myid` 文件,并写入对应的节点标识。Zookeeper 集群通过 `myid` 文件识别集群节点,并通过上文配置的节点通信端口和选举端口来进行节点通信,选举出 Leader 节点。 132 | 133 | 创建存储目录: 134 | 135 | ```shell 136 | # 三台主机均执行该命令 137 | mkdir -vp /usr/local/zookeeper-cluster/data/ 138 | ``` 139 | 140 | 创建并写入节点标识到 `myid` 文件: 141 | 142 | ```shell 143 | # hadoop001主机 144 | echo "1" > /usr/local/zookeeper-cluster/data/myid 145 | # hadoop002主机 146 | echo "2" > /usr/local/zookeeper-cluster/data/myid 147 | # hadoop003主机 148 | echo "3" > /usr/local/zookeeper-cluster/data/myid 149 | ``` 150 | 151 | ### 2.3 启动集群 152 | 153 | 分别在三台主机上,执行如下命令启动服务: 154 | 155 | ```shell 156 | /usr/app/zookeeper-cluster/zookeeper/bin/zkServer.sh start 157 | ``` 158 | 159 | ### 2.4 集群验证 160 | 161 | 启动后使用 `zkServer.sh status` 查看集群各个节点状态。如图所示:三个节点进程均启动成功,并且 hadoop002 为 leader 节点,hadoop001 和 hadoop003 为 follower 节点。 162 | 163 |
164 | 165 |
166 | 167 |
168 | 169 |
170 | -------------------------------------------------------------------------------- /10~KV 存储/配置方式对比/基础配置方式.md: -------------------------------------------------------------------------------- 1 | # 基础配置方式 2 | 3 | # 硬编码 4 | 5 | 硬编码是最基础的配置方案,其简单易用: 6 | 7 | ```java 8 | public class AppConfig { 9 | private int connectTimeoutInMills = 5000; 10 | 11 | public int getConnectTimeoutInMills() { 12 | return connectTimeoutInMills; 13 | } 14 | 15 | public void setConnectTimeoutInMills(int connectTimeoutInMills) { 16 | this.connectTimeoutInMills = connectTimeoutInMills; 17 | } 18 | } 19 | ``` 20 | 21 | 这种形式主要有三个问题: 22 | 23 | - 难以动态修改,如果配置是需要动态修改的话,需要当前应用去暴露管理该配置项的接口,至于是 Controller 的 API 接口,还是 JMX,都是可以做到。 24 | 25 | - 难以持久化与恢复,配置变更都是发生在内存中,并没有持久化。因此,在修改配置之后重启应用,配置又会变回代码中的默认值了。 26 | 27 | - 难以分布式多机器同步,当你有多台机器的时候,要修改一个配置,每一台都得去操作一遍,运维成本可想而知。 28 | 29 | # 配置文件 30 | 31 | Spring 中常见的 properties、yml 文件,或其他自定义的,如,“conf”后缀等: 32 | 33 | ```yaml 34 | # application.properties 35 | connectTimeoutInMills=5000 36 | ``` 37 | 38 | 相比“硬编码”的形式,它解决了第二个问题,持久化了配置。但是,另外两个问题并没有解决,运维成本依旧还是很高的。 39 | 40 | 配置动态变更,可以是通过类似“硬编码”暴露管理接口的方式,这时,代码中会多一步持久化新配置到文件的逻辑。或者,简单粗暴点,直接登录机器上去修改配置文件,再重启应用,让配置生效。当然,你也可以在代码中增加一个定时任务,如每隔 10s 读取配置文件内容,让最新的配置能够及时在应用中生效,这样也就免去了重启应用这个“较重”的运维操作。 41 | 42 | # DB 配置表 43 | 44 | 这里的 DB 可以是 MySQL 等的关系型数据库,也可以是 Redis 等的非关系型数据库。数据表如: 45 | 46 | ```sql 47 | CREATE TABLE `config` ( 48 | `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, 49 | `key` varchar(50) NOT NULL DEFAULT '' COMMENT '配置项', 50 | `value` varchar(50) NOT NULL DEFAULT '' COMMENT '配置内容', 51 | `updated_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 52 | `created_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 53 | PRIMARY KEY (`id`), 54 | UNIQUE KEY `idx_key` (`key`) 55 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='配置信息'; 56 | 57 | INSERT INTO `config` (`key`, `value`, `updated_time`, `created_time`) VALUES ('connectTimeoutInMills', '5000', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP); 58 | ``` 59 | 60 | 它相对于前两者,更进一步,将配置从应用中抽离出来,集中管理,能较大的降低运维成本。 61 | 62 | 那么,它能怎么解决动态更新配置的问题呢?据我所知,有两种方式。 63 | 64 | 其一,如同之前一样,通过暴露管理接口去解决,当然,也一样得增加持久化的逻辑,只不过,之前是写文件,现在是将最新配置写入数据库。不过,程序中还需要有定时从数据库读取最新配置的任务,这样,才能做到只需调用其中一台机器的管理配置接口,就能把最新的配置下发到整个应用集群所有的机器上,真正达到降低运维成本的目的。 65 | 66 | 其二,直接修改数据库,程序中通过定时任务从数据库读取最新的配置内容。 67 | 68 | “DB 配置表”的形式解决了主要的问题,但是它不够优雅,带来了一些“累赘”。 69 | -------------------------------------------------------------------------------- /20~分布式文件系统/HDFS/HDFS 源代码分析.md: -------------------------------------------------------------------------------- 1 | 1. 基础包(包括工具包和安全包) 2 | 3 | 包括工具和安全包。其中,hdfs.util 包含了一些 HDFS 实现需要的辅助数据结构;hdfs.security.token.block 和 hdfs.security.token.delegation 结合 Hadoop 的安全框架,提供了安全访问 HDFS 的机制。 4 | 5 | hdfs.util (一些 HDFS 实现需要的辅助数据结构) 6 | 7 | AtomicFileOutputStream.java---- 继承实现类:原子文件输出流类;DataTransferThrottler.java---- 独立内存类:数据传送时的调节参数配置表;这个类是线程安全的,能够被多个线程所共享;LightWeightGSet.java---- 继承实现类:一个低内存占用的实现类; 8 | 9 | hdfs.security.token.block (安全访问 HDFS 机制) 10 | 11 | BlockKey.java---- 继承实现类:Key 用于生成和校验块的令牌;BlockTokenIdentifier.java---- 继承实现类:块令牌的标识符;BlockTokenSecretManager.java---- 继承实现类:块令牌管理类;BlockTokenSecretManager 能够被实例化为两种模式,主模式和从模式。主节点能够生成新的块 key,并导出块 key 到从节点。从节点只能导入并且使用从主节点接收的块 key。主机和从机都可以生成和验证块令牌。BlockTokenSelector.java---- 继承实现类:为 HDFS 的块令牌选择;ExportedBlockKeys.java---- 继承实现类:传递块 key 对象;InvalidBlockTokenException.java---- 继承实现类:访问令牌验证失败; 12 | 13 | hdfs.security.token.delegation (安全访问 HDFS 机制) 14 | 15 | DelegationTokenIdentifier.java---- 继承实现类:特定针对 HDFS 的代表令牌的标识符;DelegationTokenRenewer.java---- 继承实现类:这是一个守护进程,实现等待下一个文件系统的接续;DelegationTokenSecretManager.java---- 继承实现类:这个类实现了 HDFS 特定授权的令牌的管理;这个管理类实现了生成并接受每一个令牌的密码;DelegationTokenSelector.java---- 继承实现类:专门针对 HDFS 的令牌; 16 | 17 | 2.HDFS 实体实现包 18 | 19 | 这是代码分析的重点,包含 8 个包: 20 | 21 | hdfs.server.common 包含了一些名字节点和数据节点共享的功能,如系统升级、存储空间信息等。 22 | 23 | hdfs.protocol 和 hdfs.server.protocol 提供了 HDFS 各个实体间通过 IPC 交互的接口的定义和实现。 24 | 25 | hdfs.server.namenode、hdfs.server.datanode 和 hdfs 分别包含了名字节点、数据节点和客户端的实现。上述代码是 HDFS 代码分析的重点。 26 | 27 | hdfs.server.namenode.metrics 和 hdfs.server.datanode.metrics 实现了名字节点和数据节点上度量数据的收集功能。度量数据包括名字节点进程和数据节点进程上事件的计数,例如数据节点上就可以收集到写入字节数、被复制的块的数量等信息。 28 | 29 | hdfs.server.common (一些名字节点和数据节点共享的功能) 30 | 31 | GenerationStamp.java---- 继承实现类:生成时间戳的功能及读写访问类;HdfsConstants.java---- 接口类:一些 HDFS 内部的常数;Hdfs 常量字段及取值的定义;InconsistentFSStateException.java---- 继承实现类:不一致的文件系统状态异常;文件状态检查出错提示信息;IncorrectVersionException.java---- 继承实现类:不正确的版本异常;版本不正确检查时提示信息;Storage.java---- 继承实现类:存储信息文件;本地存储信息存储在一个单独的文件版本中;它包含了节点类型、存储布局版本、命名空间 ID 以及文件系统状态创建时间;内存中以扩展表记录方式记录当前 namenode 索引信息及状态信息(文件是否在打开);StorageInfo.java---- 独立内存类:存储信息的通用类;内存中文件索引信息基本表;基本作用是保存地储上的文件系统元信息;Upgradeable.java---- 接口类:分布式升级对象的通用接口;对象升级接口方法集定义;UpgradeManager.java---- 独立内存类、抽象:通用升级管理;UpgradeObject.java---- 继承实现类:抽象升级对象;包含常用接口方法的实现;可升级对象的接口方法实现;UpgradeObjectCollection.java---- 独立内存类:可升级对象的集合容器实现;升级对象在使用前应该先进行注册,才能使用;UpgradeStatusReport.java---- 继承实现类:系统升级基类;升级过程状态信息表定义;Util.java---- 独立内存类:获取当前系统时间; 32 | 33 | hdfs.protocol ( HDFS 各个实体间通过 IPC 交互的接口) 34 | 35 | AlreadyBeingCreatedException.java---- 继承实现类:文件已经建立异常;Block.java---- 继承实现类:数据块在 HDFS 中的抽象;Bolck 内存基本块结构定义与读写访问;这个类是大量和数据块相关的类的基础,在客户端接口上,这样的类有 LocatedBlock、LocateBlocks 和 BlockLocalPathInfo。BlockListAsLongs.java---- 独立内存类:这个类提供了一个访问块列表的接口;Bolck 块的索引构成数组;该类的作用,就是将块数组 blcokArray 中的数据,“ 原封不动 ” 的转换成一个 long 类型的数组 blockList;BlockLocalPathInfo.java---- 继承实现类:应用于 ClientDatanodeProtocol 接口中,用于 HDFS 读文件的数据节点本地读优化。当客户端发现它和它要读取的数据块正好位于同一台主机上的时候,它可以不通过数据节点读数据块,而是直接读取本地文件,以获取数据块的内容。这大大降低了对应数据节点的负载。ClientDatanodeProtocol.java---- 接口类:客户端与数据节点间的接口。用于客户端和数据节点进行交互,这个接口用得比较少,客户端和数据节点间的主要交互是通过流接口进行读 / 写文件数据的操作。错误发生时,客户端需要数据节点配合进行恢复,或当客户端进行本地文件读优化时,需要通过 IPC 接口获取一些信息。ClientProtocol.java---- 接口类:客户端与 namenode 之间的接口;是 HDFS 客户访问文件系统的入口;客户端通过这个接口访问名字节点,操作文件或目录的元数据信息;读写文件也必须先访问名字节点,接下来再和数据节点进行交互,操作文件数据;另外,从名字节点能获取分布式文件系统的一些整体运行状态信息,也是通过这个接口进行的;用于访问 NameNode。它包含了文件角度上的 HDFS 功能。和 GFS 一样,HDFS 不提供 POSIX 形式的接口,而是使用了一个私有接口。一般来说,程序员通过 org.apache.hadoop.fs.FileSystem 来和 HDFS 打交道,不需要直接使用该接口。DatanodeID.java---- 继承实现类:用于在 HDFS 集群中确定一个数据节点;DatanodeInfo.java---- 继承实现类:继承自 DatanodeID,在 DatanodeID 的基础上,提供了数据节点上的一些度量信息;Datanode 状态信息结构定义及访问读写类;DataTransferProtocol.java---- 接口类:客户端与数据节点之间应用流协议传输数据的实现;DirectoryListing.java---- 继承实现类:用于一次返回一个目录下的多个文件 / 子目录的属性;DSQuotaExceededException.java---- 继承实现类:磁盘空间超出配额异常类;FSConstants.java---- 接口类:一些有用的常量;HdfsFileStatus.java---- 继承实现类:保存了 HDFS 文件 / 目录的属性;LayoutVersion.java---- 独立内存类:这个类跟踪了 HDFS 布局版本中的改变信息;LocatedBlock.java---- 继承实现类:已经确认了存储位置的数据块;可以用于一次定位多个数据块;LocatedBlocks.java---- 继承实现类:块的位置和文件长度集合;一组数据块及文件长度说明信息表的定义及读写;NSQuotaExceededException.java---- 继承实现类:命名空间超出配额异常类;QuotaExceededException.java---- 继承实现类:超出配额异常;UnregisteredDatanodeException.java---- 继承实现类:未注册的数据节点异常; 36 | 37 | hdfs.server.protocol ( HDFS 各个实体间接口的实现) 38 | 39 | BalancerBandwidthCommand.java---- 继承实现类:管理员通过调用 "dfsadmin -setBalanacerBandwidth newbandwidth" 实现动态的调整 balancer 的带宽参数;BlockCommand.java---- 继承实现类:BlockCommand 类实现的是数据节点控制下的块的指令;数据块命令定义及实现类;BlockMetaDataInfo.java---- 继承实现类:一个块的元数据信息;数据块的元数据信息定义及实现;BlockRecoveryInfo.java---- 继承实现类:块恢复操作信息;BlocksWithLocations.java---- 继承实现类:BlockLocations 序列的实现类;带位置的块信息的读写;DatanodeCommand.java---- 抽象类:数据节点命令;数据节点基本信息定义及实现;DatanodeProtocol.java---- 接口类:服务器间接口:数据节点与名字节点间的接口。在 HDFS 的主从体系结构中,数据节点作为从节点,不断的通过这个接口主节点名字节点报告一些信息,同步信息到名字节点;同时,该接口的一些方法,方法的返回值会带回名字节点指令,根据这些指令,数据节点或移动、或删除,或恢复本地磁盘上的数据块,或者执行其他的操作。DatanodeRegistration.java---- 继承实现类:DatanodeRegistration 类包含名字节点识别和验证数据节点的所有信息;数据节点的注册信息读写方法定义及实现;DisallowedDatanodeException.java---- 继承实现类:不允许的数据节点异常;InterDatanodeProtocol.java---- 接口类:服务器间的接口:数据节点与数据节点间的接口。数据节点通过这个接口,和其他数据节点进行通信,恢复数据块,保证数据的一致性。KeyUpdateCommand.java---- 继承实现类:key 升级命令;NamenodeProtocol.java---- 接口类:服务期间接口:第二名字节点、HDFS 均衡器与名字节点间的接口。第二名字节点会不停的获取名字节点上某一个时间点的命名空间镜像和镜像的变化日志,然后会合并得到一个新的镜像,并将该结果发送回名字节点,在这个过程中,名字节点会通过这个接口,配合第二名字节点完成元数据的合并。该接口也为 HDFS 均衡器 balancer 的正常工作提供一些信息。NamespaceInfo.java---- 继承实现类:NamespaceInfo 类实现了返回名字节点针对数据节点的握手;UpgradeCommand.java---- 继承实现类:这是一个通用的分布式升级命令类;升级数据块命名实现; 40 | 41 | hdfs.server.namenode (名字节点的实现) 42 | 43 | BlockPlacementPolicy.java---- 抽象类:这个接口用于选择放置块副本的目标磁盘的所需的数目;BlockPlacementPolicyDefault.java---- 继承实现类:这个类实现了选择放置块副本的目标磁盘的所需的数目;BlockPlacementPolicyWithNodeGroup.java---- 继承实现类:这个类实现了在 node-group 层上选择放置块副本的目标磁盘的所需的数目;BlockInfo.java---- 独立内存类:这个类维护了块到它元数据的映射;CancelDelegationTokenServlet.java---- 继承实现类:取消代表令牌服务;CheckpointSignature.java---- 继承实现类:检查点签名类;存贮信息的签名信息表定义;ContentSummaryServlet.java---- 继承实现类:文件校验服务;CorruptReplicasMap.java---- 独立内存类:存储文件系统中所有损坏的块的信息;DatanodeDescriptor.java---- 继承实现类:DatanodeDescriptor 类跟踪并统计了给定的数据节点上的信息,比如可用存储空间,上次更新时间等等;数据节点的状态信息定义及实现;DecommissionManager.java---- 独立内存类:管理节点的解除;DfsServlet.java---- 抽象类:DFS 服务的基类;Web 方式操作 DFS 的代理接口;EditLogInputStream.java---- 抽象类:一个通用的抽象类用来支持从持久型存储读取编辑日志数据;读取日志数据的类方法定义及实现;EditLogOutputStream.java---- 继承实现类:一个通用的抽象类用来支持从持久型存储记录编辑日志数据;写日志数据的类方法定义及实现;FileChecksumServlets.java---- 独立内存类:文件校验服务;文件较验 web 操作命令的代理实现;FileDataServlet.java---- 继承实现类:文件数据 web 操作命令的代理实现;FsckServlet.java---- 继承实现类:名字节点上 fsck 的 web 服务;文件系统检查 web 操作命令的代理实现;FSClusterStats.java---- 接口类:这个接口用于检索集群相关的统计;FSDirectory.java---- 继承实现类:类 FSDirectory 实现了存储文件系统的目录状态;文件目录结构的定义及实现;FSEditLog.java---- 独立内存类:FSEditLog 类实现了维护命名空间改动的日志记录;文件系统日志表的定义;FSImage.java---- 继承实现类:FSImage 实现对命名空间的编辑进行检查点操作和日志记录操作;文件系统的目录、文件、数据的索引及关系信息定义;FSInodeInfo.java---- 接口类:文件系统相关信息;FSNamesystem.java---- 继承实现类:FSNamesystem 类实现了为数据节点进行实际的记账工作;为数据节点命名的信息结构定义;FSPermissionChecker.java---- 独立内存类:实现用于检测文件系统权限的类;GetDelegationTokenServlet.java---- 继承实现类:获取委托令牌服务;GetImageServlet.java---- 继承实现类:这个类用于在命名系统中检索文件;通常用于第二名字节点来检索镜像以及为周期性的检测点进行编辑文件;Host2NodesMap.java---- 独立内存类:主机到节点的映射;INode.java---- 继承实现类:这个抽象类包含了文件和目录索引节点的通用字段;节点基础信息结构定义;INodeDirectory.java---- 继承实现类:表示目录的索引节点的类;INodeDirectoryWithQuota.java---- 继承实现类:有配额限制的目录索引节点类;INodeFile.java---- 继承实现类:目录索引节点文件;文件节点信息结构定义;INodeFileUnderConstruction.java---- 继承实现类:建立目录索引节点文件;在创建之下的文件节点信息结构定义;JspHelper.java---- 独立内存类:JSP 实现辅助类;LeaseExpiredException.java---- 继承实现类:创建的文件已过期异常;LeaseManager.java---- 独立内存类:LeaseManager 实现了写文件的租赁管理;这个类还提供了租赁恢复的有用的静态方法;契约信息结构定义及实现;ListPathsServlet.java---- 继承实现类:获取一个文件系统的元信息;MetaRecoveryContext.java---- 独立内存类:正在进行的名字节点恢复进程的上下文数据;NameCache.java---- 独立内存类:缓存经常使用的名称以便再用;NameNode.java---- 继承实现类:名字节点功能管理和实现类;名称节点的核心服务器类;NamenodeFsck.java---- 独立内存类:这个类提供了 DFS 卷基本的检测;名称节点的系统检测类;NameNodeMXBean.java---- 接口类:这个类是名字节点信息的 JMX 管理接口;NotReplicatedYetException.java---- 继承实现类:文件还未赋值异常类;PendingReplicationBlocks.java---- 独立内存类:这个类 PendingReplicationBlocks 实现了所有快复制的记录;正在复制数据块的信息表定义;PermissionChecker.java---- 独立内存类:这个类实现了执行权限检查操作;权限检查表结构定义及实现;RenewDelegationTokenServlet.java---- 继承实现类:续订令牌服务;SafeModeException.java---- 继承实现类:当名字节点处于安全模式的时候抛出这个异常;客户端不能够修改名字空间直到安全模式关闭;SecondaryNameNode.java---- 继承实现类:第二名字节点功能的管理和实现类;SerialNumberManager.java---- 独立内存类:为用户和组管理名称到序列号的映射;StreamFile.java---- 继承实现类:流文件类的实现;TransferFsImage.java---- 继承实现类:这个类实现了从名字节点获取一个指定的文件的功能;通过 http 方式来获取文件的镜像信息;UnderReplicatedBlocks.java---- 继承实现类:复制块的类的实现;复制完成后的块信息表定义;UnsupportedActionException.java---- 继承实现类:操作不支持的异常;UpgradeManagerNamenode.java---- 继承实现类:名字节点升级的管理;UpgradeObjectNamenode.java---- 抽象类:名字节点对象更新类;数据节点的更新运行在单独的线程上;升级名称节点的对象信息; 44 | 45 | hdfs.server.datanode (数据节点的实现) 46 | 47 | BlockAlreadyExistsException.java---- 继承实现类:目标块已经存在异常;BlockMetadataHeader.java---- 独立内存类:数据块头部结构定义及实现;BlockReceiver.java---- 继承实现类:这个类实现了接收一个块并写到自己的磁盘的功能,同时也可以复制它到另外一个磁盘;数据块接收容器信息结构及实现写入到盘中;接收一个数据块,并写到本地的磁盘,同时可能会拷贝到其他的节点上;BlockSender.java---- 继承实现类:从磁盘读取块,并发送它到接收的目的地;从磁盘中读数据块并发送到相应接收者;BlockTransferThrottler.java---- 独立内存类:调节数据块的传输;块传送时的调节参数配置表;DataBlockScanner.java---- 继承实现类:数据块的扫描工具实现;DataBlockScanner 拥有它单独的线程,能定时地从目前 DataNode 管理的数据块文件进行校验;其实最重要的方法就是 verifyBlock;DataBlockScanner 其他的辅助方法用于对 DataBlockScanner 管理的数据块文件信息进行增加 / 删除,排序操作;DataNode.java---- 继承实现类:数据节点的功能的管理和实现;数据块的核心管理器;DatanodeBlockInfo.java---- 独立内存类:这个类用于数据节点保持从数据块到它的元数据的映射的功能;建立块文件和其属于哪个 FSVolume 之间的映射关系;DataNodeMXBean.java---- 接口类:数据节点信息的 JMX 管理接口的实现;DataStorage.java---- 继承实现类:数据存储信息文件;DataXceiver.java---- 继承实现类:用于处理输入 / 输出数据流的线程;DataXceiverServer.java---- 继承实现类:用于接收和发送数据块的服务;FSDataset.java---- 继承实现类:FSDataset 类实现管理数据块集合的功能;FSDatasetAsyncDiskService.java---- 独立内存类:这个类实现了每个卷上多个线程池的容器,所以我们能够轻松地调度异步磁盘操作;为每个数据块目录创建一个线程池,并用作为一个线程组,池的最小值为 1,最大值为 4;当前版本,只有 delete 操作会将任务经过线程池调度来进行异步处理,读写操作都是调文件操作同步去执行的;FSDatasetInterface.java---- 接口类:这是一个接口,这个接口实现了一个数据节点存储块的底层存储;FSDatasetInterface 是 DataNode 对底局存储的抽象;SecureDataNodeStarter.java---- 继承实现类:这个类实现在一个安全的集群中启动一个数据节点,在启动前需要获得特权资源,并把它们递交给数据节点;UpgradeManagerDatanode.java---- 继承实现类:这个类实现了数据节点的升级管理;UpgradeObjectDatanode.java---- 抽象类:这个类是数据节点升级对象的基类;数据节点升级运行在一个单独的线程中; 48 | 49 | hdfs (客户端的实现) 50 | 51 | BlockReader.java---- 接口类:这个接口供本地和远程块读取共享;BlockReaderLocal.java---- 继承实现类:本地块读取;ByteRangeInputStream.java---- 抽象类:为了支持 HTTP 字节流,每次都需要建立一个针对 HTTP 服务的新的连接;ChecksumDistributedFileSystem.java---- 继承实现类:分布式文件系统检测;DFSClient.java---- 独立内存类:DFSClient 类能够连接到 Hadoop 文件系统并执行基本的文件操作;DFSConfigKeys.java---- 继承实现类:这个类包含了 HDFS 中使用的常数;DFSUtil.java---- 独立内存类:DFS 实用工具;DistributedFileSystem.java---- 继承实现类:DFS 系统的抽象文件系统实现;分布式文件系统功能的实现;HftpFileSystem.java---- 继承实现类:通过 HTTP 访问文件系统的协议的执行;采用 HTTP 协议来访问 HDFS 文件;HsftpFileSystem.java---- 继承实现类:通过 HTTPs 访问文件系统的协议的执行;采用 HTTPs 协议来访问 HDFS 文件;LeaseRenewer.java---- 独立内存类:更新租赁; 52 | 53 | hdfs.server.namenode.metrics (名字节点上度量数据的收集功能) 54 | 55 | FSNamesystemMBean.java---- 接口类:这个接口定义了获取一个名字节点的 FSNamesystem 状态的方法;取名称节点的文件状态信息;NameNodeInstrumentation.java---- 继承实现类:名字节点规范类; 56 | 57 | hdfs.server.datanode.metrics (数据节点上度量数据的收集功能) 58 | 59 | DataNodeInstrumentation.java---- 继承实现类:数据节点的一些规范;FSDatasetMBean.java---- 接口类:这个接口中定义了方法来获取一个数据节点的 FSDataset 的状态;数据集的职能定义; 60 | 61 | 3. 应用包 62 | 63 | 包括 hdfs.tools 和 hdfs.server.balancer,这两个包提供查询 HDFS 状态信息工具 dfsadmin、文件系统检查工具 fsck 和 HDFS 均衡器 balancer(通过 start-balancer.sh 启动)的实现。 64 | 65 | hdfs.tools (查询 HDFS 状态信息工具 dfsadmin、文件系统检查工具 fsck 的实现) 66 | 67 | DelegationTokenFetcher.java---- 独立内存类:这个类实现了从当前的名字节点获取 DelegationToken,并存储它到指定的文件当中;DFSAdmin.java---- 继承实现类:这个类实现了提供一些 DFS 访问管理;管理员命令的实现;DFSck.java---- 继承实现类:这个类实现了对 DFS 卷进行基本的检查;文件系统检查命令的实现;HDFSConcat.java---- 独立内存类:HDFS 串接; 68 | 69 | hdfs.server.balancer ( HDFS 均衡器 balancer 的实现) 70 | 71 | Balancer.java---- 继承实现类:负载均衡进程,各节点基于他来进行任务量的平衡;balance 是一个工具,用于当一些数据节点全部使用或者有新的节点加入集群的时候,来平衡 HDFS 集群上的磁盘空间使用率; 72 | 73 | 4.WebHDFS 相关包 74 | 75 | 包括 hdfs.web.resources、hdfs.server.namenode.metrics.web.resources、hdfs.server.datanode.web.resources 和 hdfs.web 共 4 个包。 76 | 77 | WebHDFS 是 HDFS 1.0 中引入的新功能,它提供了一个完整的、通过 HTTP 访问 HDFS 的机制。对比只读的 hftp 文件系统,WebHDFS 提供了 HTTP 上读写 HDFS 的能力,并在此基础上实现了访问 HDFS 的 C 客户端和用户空间文件系统(FUSE )。 78 | -------------------------------------------------------------------------------- /20~分布式文件系统/HDFS/HDFS 编程.md: -------------------------------------------------------------------------------- 1 | ```java 2 | public class TestHDFSFile { 3 | private String localPath = "C:/D/JavaWorkSpace/bigdata/temp/"; 4 | private String hdfsPath = "hdfs://192.168.2.6:9000/user/hadoop/temp/"; 5 | 6 | public static void main(String[] args) throws Exception { 7 | // new TestHDFSFile().testUpload(); 8 | // new TestHDFSFile().testCreate(); 9 | //new TestHDFSFile().testRename(); 10 | //new TestHDFSFile().testDel(); 11 | //new TestHDFSFile().testgetModifyTime(); 12 | //new TestHDFSFile().testExists(); 13 | //new TestHDFSFile().testFileBlockLocation(); 14 | new TestHDFSFile().testGetHostName(); 15 | } 16 | 17 | // 上传本地文件到HDFS 18 | public void testUpload() throws Exception { 19 | Configuration conf = new Configuration(); 20 | 21 | // conf.addResource(new Path(localPath + "core-site.xml")); 22 | FileSystem hdfs = FileSystem.get(conf); 23 | Path src = new Path(localPath + "file01.txt"); 24 | Path dst = new Path(hdfsPath); 25 | hdfs.copyFromLocalFile(src, dst); 26 | 27 | System.out.println("Upload to " + conf.get("fs.default.name")); 28 | FileStatus files[] = hdfs.listStatus(dst); 29 | for (FileStatus file : files) { 30 | System.out.println(file.getPath()); 31 | } 32 | } 33 | 34 | // 创建HDFS文件 35 | public void testCreate() throws Exception { 36 | Configuration conf = new Configuration(); 37 | byte[] buff = "hello world!".getBytes(); 38 | 39 | FileSystem hdfs = FileSystem.get(conf); 40 | Path dst = new Path(hdfsPath + "hello.txt"); 41 | FSDataOutputStream outputStream = null; 42 | try { 43 | outputStream = hdfs.create(dst); 44 | outputStream.write(buff, 0, buff.length); 45 | } catch (Exception e) { 46 | e.printStackTrace(); 47 | } finally { 48 | if (outputStream != null) { 49 | outputStream.close(); 50 | } 51 | } 52 | 53 | FileStatus files[] = hdfs.listStatus(dst); 54 | for (FileStatus file : files) { 55 | System.out.println(file.getPath()); 56 | } 57 | } 58 | 59 | // 重命名HDFS文件 60 | public void testRename() throws Exception { 61 | Configuration conf = new Configuration(); 62 | 63 | FileSystem hdfs = FileSystem.get(conf); 64 | Path dst = new Path(hdfsPath); 65 | 66 | Path frpath = new Path(hdfsPath + "hello.txt"); 67 | Path topath = new Path(hdfsPath + "hello2.txt"); 68 | 69 | hdfs.rename(frpath, topath); 70 | 71 | FileStatus files[] = hdfs.listStatus(dst); 72 | for (FileStatus file : files) { 73 | System.out.println(file.getPath()); 74 | } 75 | } 76 | 77 | // 刪除HDFS文件 78 | public void testDel() throws Exception { 79 | Configuration conf = new Configuration(); 80 | 81 | FileSystem hdfs = FileSystem.get(conf); 82 | Path dst = new Path(hdfsPath); 83 | 84 | Path topath = new Path(hdfsPath + "hello2.txt"); 85 | 86 | boolean ok = hdfs.delete(topath, false); 87 | System.out.println(ok ? "删除成功" : "删除失败"); 88 | 89 | FileStatus files[] = hdfs.listStatus(dst); 90 | for (FileStatus file : files) { 91 | System.out.println(file.getPath()); 92 | } 93 | } 94 | 95 | // 查看HDFS文件的最后修改时间 96 | public void testgetModifyTime() throws Exception { 97 | Configuration conf = new Configuration(); 98 | 99 | FileSystem hdfs = FileSystem.get(conf); 100 | Path dst = new Path(hdfsPath); 101 | 102 | FileStatus files[] = hdfs.listStatus(dst); 103 | for (FileStatus file : files) { 104 | System.out.println(file.getPath() + "\t" + file.getModificationTime()); 105 | 106 | System.out.println( 107 | file.getPath() + "\t" + new Date(file.getModificationTime()) 108 | ); 109 | } 110 | } 111 | 112 | // 查看HDFS文件是否存在 113 | public void testExists() throws Exception { 114 | Configuration conf = new Configuration(); 115 | 116 | FileSystem hdfs = FileSystem.get(conf); 117 | Path dst = new Path(hdfsPath + "file01.txt"); 118 | 119 | boolean ok = hdfs.exists(dst); 120 | System.out.println(ok ? "文件存在" : "文件不存在"); 121 | } 122 | 123 | // 查看某个文件在HDFS集群的位置 124 | public void testFileBlockLocation() throws Exception { 125 | Configuration conf = new Configuration(); 126 | 127 | FileSystem hdfs = FileSystem.get(conf); 128 | Path dst = new Path(hdfsPath + "file01.txt"); 129 | 130 | FileStatus fileStatus = hdfs.getFileStatus(dst); 131 | BlockLocation[] blockLocations = hdfs.getFileBlockLocations( 132 | fileStatus, 133 | 0, 134 | fileStatus.getLen() 135 | ); 136 | for (BlockLocation block : blockLocations) { 137 | System.out.println( 138 | Arrays.toString(block.getHosts()) + 139 | "\t" + 140 | Arrays.toString(block.getNames()) 141 | ); 142 | } 143 | } 144 | 145 | // 获取HDFS集群上所有节点名称 146 | public void testGetHostName() throws Exception { 147 | Configuration conf = new Configuration(); 148 | 149 | DistributedFileSystem hdfs = (DistributedFileSystem) FileSystem.get(conf); 150 | DatanodeInfo[] dataNodeStats = hdfs.getDataNodeStats(); 151 | 152 | for (DatanodeInfo dataNode : dataNodeStats) { 153 | System.out.println(dataNode.getHostName() + "\t" + dataNode.getName()); 154 | } 155 | } 156 | } 157 | ``` 158 | -------------------------------------------------------------------------------- /20~分布式文件系统/HDFS/HDFS 读取原理.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | HDFS 开创性地设计出一套文件存储方式,即对文件分割后分别存放;HDFS 将要存储的大文件进行分割,分割后存放在既定的存储块(Block )中,并通过预先设定的优化处理,模式对存储的数据进行预处理,从而解决了大文件储存与计算的需求;一个 HDFS 集群包括两大部分,即 NameNode 与 DataNode。一般来说,一个集群中会有一个 NameNode 和多个 DataNode 共同工作;NameNode 是集群的主服务器,主要是用于对 HDFS 中所有的文件及内容数据进行维护,并不断读取记录集群中 DataNode 主机情况与工作状态,并通过读取与写入镜像日志文件的方式进行存储;DataNode 在 HDFS 集群中担任任务具体执行角色,是集群的工作节点。文件被分成若干个相同大小的数据块,分别存储在若干个 DataNode 上,DataNode 会定期向集群内 NameNode 发送自己的运行状态与存储内容,并根据 NameNode 发送的指令进行工作;NameNode 负责接受客户端发送过来的信息,然后将文件存储位置信息发送给提交请求的客户端,由客户端直接与 DataNode 进行联系,从而进行部分文件的运算与操作。Block 是 HDFS 的基本存储单元,默认大小是 64M;HDFS 还可以对已经存储的 Block 进行多副本备份,将每个 Block 至少复制到 3 个相互独立的硬件上,这样可以快速恢复损坏的数据;用户可以使用既定的 API 接口对 HDFS 中的文件进行操作;当客户端的读取操作发生错误的时候,客户端会向 NameNode 报告错误,并请求 NameNode 排除错误的 DataNode 后后重新根据距离排序,从而获得一个新的 DataNode 的读取路径。如果所有的 DataNode 都报告读取失败,那么整个任务就读取失败;对于写出操作过程中出现的问题,FSDataOutputStream 并不会立即关闭。客户端向 NameNode 报告错误信息,并直接向提供备份的 DataNode 中写入数据。备份 DataNode 被升级为首选 DataNode,并在其余 2 个 DataNode 中备份复制数据。NameNode 对错误的 DataNode 进行标记以便后续对其进行处理 4 | 5 | # Quick Start 6 | 7 | HDFS 基本命令 8 | 9 | hadoop fs -cmd 10 | 11 | cmd: 具体的操作,基本上与 UNIX 的命令行相同 12 | 13 | args: 参数 14 | 15 | HDFS 资源 URI 格式: 16 | 17 | scheme://authority/path 18 | 19 | scheme:协议名,file 或 hdfs 20 | 21 | authority:namenode 主机名 22 | 23 | path:路径 24 | 25 | 示例:hdfs://localhost:9000/user/chunk/test.txt 26 | 27 | 假设已经在 core-site.xml 里配置了 fs.default.name=hdfs://localhost:9000,则仅使用 /user/chunk/test.txt 即可。 28 | 29 | hdfs 默认工作目录为 /user/$USER,$USER 是当前的登录用户名。 30 | 31 | HDFS 命令示例: 32 | 33 | hadoop fs -mkdir /user/trunk 34 | 35 | hadoop fs -ls /user 36 | 37 | hadoop fs -lsr /user ( 递归的 ) 38 | 39 | hadoop fs -put test.txt /user/trunk 40 | 41 | hadoop fs -put test.txt . ( 复制到 hdfs 当前目录下,首先要创建当前目录 ) 42 | 43 | hadoop fs -get /user/trunk/test.txt . ( 复制到本地当前目录下 ) 44 | 45 | hadoop fs -cat /user/trunk/test.txt 46 | 47 | hadoop fs -tail /user/trunk/test.txt ( 查看最后 1000 字节 ) 48 | 49 | hadoop fs -rm /user/trunk/test.txt 50 | 51 | hadoop fs -help ls ( 查看 ls 命令的帮助文档 ) 52 | 53 | 图中的 2:文件备份数量,因为采用了两台机器的全分布模式,所以此处为 2. 对于目录,使用 -。 54 | 55 | 在 put 的时候遇到问题: 56 | 57 | ## Configuration 58 | 59 | ## Read File 60 | 61 | # 存储结构 62 | 63 | ## 行存储 64 | 65 | HDFS 块内行存储的例子: ![](http://dl.iteye.com/upload/attachment/0083/5102/c5adc6f6-4a57-3994-b44c-2a943152bc58.png) 66 | 67 | 基于 Hadoop 系统行存储结构的优点在于快速数据加载和动态负载的高适应能力,这是因为行存储保证了相同记录的所有域都在同一个集群节点,即同一个 HDFS 块。不过,行存储的缺点也是显而易见的,例如它不能支持快速查询处理,因为当查询仅仅针对多列表中的少数几列时,它不能跳过不必要的列读取;此 外,由于混合着不同数据值的列,行存储不易获得一个极高的压缩比,即空间利用率不易大幅提高。 68 | 69 | ## 列存储 70 | 71 | ![](http://dl.iteye.com/upload/attachment/0083/5104/a432e6af-9a73-355c-ac77-b7c185da959c.jpg) 在 HDFS 上按照列组存储表格的例子。在这个例子中,列 A 和列 B 存储在同一列组,而列 C 和列 D 分别存储在单独的列组。查询时列存储能够避免读不必要的列,并且压缩一个列中的相似数据能够达到较高的压缩比。然而,由于元组重构的较高开销,它并不能提供基于 Hadoop 系统的快速查询处理。列存储不能保证同一 记录的所有域都存储在同一集群节点,行存储的例子中,记录的 4 个域存储在位于不同节点的 3 个 HDFS 块中。因此,记录的重构将导致通过集群节点网络的大 量数据传输。尽管预先分组后,多个列在一起能够减少开销,但是对于高度动态的负载模式,它并不具备很好的适应性。 72 | 73 | # 数据读取 74 | 75 | hdfs 读取数据流程图: ![](http://img.blog.csdn.net/20160525114335782) 1、首先调用 FileSystem 对象的 open 方法,其实获取的是一个 DistributedFileSystem 的实例。2、DistributedFileSystem 通过 RPC( 远程过程调用 ) 获得文件的第一批 block 的 locations,同一 block 按照重复数会返回多个 locations,这些 locations 按照 hadoop 拓扑结构排序,距离客户端近的排在前面。3、前两步会返回一个 FSDataInputStream 对象,该对象会被封装成 DFSInputStream 对象,DFSInputStream 可以方便的管理 datanode 和 namenode 数据流。客户端调用 read 方 法,DFSInputStream 就会找出离客户端最近的 datanode 并连接 datanode。4、数据从 datanode 源源不断的流向客户端。5、如果第一个 block 块的数据读完了,就会关闭指向第一个 block 块的 datanode 连接,接着读取下一个 block 块。这些操作对客户端来说是透明的,从客户端的角度来看只是读一个持续不断的流。6、如果第一批 block 都读完了,DFSInputStream 就会去 namenode 拿下一批 blocks 的 location,然后继续读,如果所有的 block 块都读完,这时就会关闭掉所有的流。 76 | 77 | # 数据写入 78 | 79 | hdfs 写数据流程: ![](http://img.blog.csdn.net/20160525131839917) 1. 客户端通过调用 DistributedFileSystem 的 create 方法,创建一个新的文件。2.DistributedFileSystem 通过 RPC(远程过程调用)调用 NameNode,去创建一个没有 blocks 关联的新文件。创建前,NameNode 会做各种校验,比如文件是否存在,客户端有无权限去创建等。如果校验通过,NameNode 就会记录下新文件,否则就会抛出 IO 异常。3. 前两步结束后会返回 FSDataOutputStream 的对象,和读文件的时候相似,FSDataOutputStream 被封装成 DFSOutputStream,DFSOutputStream 可以协调 NameNode 和 DataNode。客户端开始写数据到 DFSOutputStream,DFSOutputStream 会把数据切成一个个小 packet,然后排成队列 data queue。4.DataStreamer 会去处理接受 data queue,它先问询 NameNode 这个新的 block 最适合存储的在哪几个 DataNode 里,比如重复数是 3,那么就找到 3 个最适合的 DataNode,把它们排成一个 pipeline。DataStreamer 把 packet 按队列输出到管道的第一个 DataNode 中,第一个 DataNode 又把 packet 输出到第二个 DataNode 中,以此类推。5.DFSOutputStream 还有一个队列叫 ack queue,也是由 packet 组成,等待 DataNode 的收到响应,当 pipeline 中的所有 DataNode 都表示已经收到的时候,这时 akc queue 才会把对应的 packet 包移除掉。6. 客户端完成写数据后,调用 close 方法关闭写入流。7.DataStreamer 把剩余的包都刷到 pipeline 里,然后等待 ack 信息,收到最后一个 ack 后,通知 DataNode 把文件标示为已完成。 80 | 81 | ![](http://img.blog.csdn.net/20160525133509937) 82 | 83 | # NameNode HA 84 | 85 | NameNode HA 架构如下: ![](http://img.blog.csdn.net/20160525134854724) 86 | 87 | - Active NameNode 和 Standby NameNode: 88 | 89 | 两台 NameNode 形成互备,一台处于 Active 状态,为主 NameNode,另外一台处于 Standby 状态,为备 NameNode,只有主 NameNode 才能对外提供读写服务。 90 | 91 | - 主备切换控制器 ZKFailoverController: 92 | 93 | ZKFailoverController 作为独立的进程运行,对 NameNode 的主备切换进行总体控制。ZKFailoverController 能及时检测到 NameNode 的健康状况,在主 NameNode 故障时借助 Zookeeper 实现自动的主备选举和切换。 94 | 95 | - Zookeeper 集群: 96 | 97 | 为主备切换控制器提供主备选举支持。 98 | 99 | - 共享存储系统: 100 | 101 | 共享存储系统是实现 NameNode 的高可用最为关键的部分,共享存储系统保存了 NameNode 在运行过程中所产生的 HDFS 的元数据。主 NameNode 和备用 NameNode 通过共享存储系统实现元数据同步。在进行主备切换的时候,新的主 NameNode 在确认元数据完全同步之后才能继续对外提供服务。 102 | -------------------------------------------------------------------------------- /20~分布式文件系统/HDFS/README.md: -------------------------------------------------------------------------------- 1 | # HDFS 2 | 3 | ![HDFS Architecture](https://assets.ng-tech.icu/item/20230430222711.png) 4 | -------------------------------------------------------------------------------- /20~分布式文件系统/README.md: -------------------------------------------------------------------------------- 1 | # 分布式文件系统 2 | -------------------------------------------------------------------------------- /30~对象存储/Haystack.md: -------------------------------------------------------------------------------- 1 | # Haystack 2 | 3 | # Links 4 | 5 | - https://studygolang.com/articles/4256 6 | -------------------------------------------------------------------------------- /30~对象存储/README.md: -------------------------------------------------------------------------------- 1 | # 对象存储 2 | -------------------------------------------------------------------------------- /30~对象存储/元数据管理.md: -------------------------------------------------------------------------------- 1 | # 元数据管理 2 | 3 | 元数据的目录树结构转化成基于 LSMT 的 KV,核心思想是把树的父子节点关系转变成 parentInode+currentPath 到 Inode 的映射。分布式的 KV 是由多个 partition 组成,单个文件系统的所有元数据放在某个 partition 内。架构上支持动态分裂,如果某个文件成为热点,按照目录动态分裂,并调度到不同机器的 partition 上。 4 | 5 | ![用户分区示意](https://assets.ng-tech.icu/item/20230502140552.png) 6 | -------------------------------------------------------------------------------- /40~区块链/README.link: -------------------------------------------------------------------------------- 1 | https://github.com/wx-chevalier/Blockchain-Notes -------------------------------------------------------------------------------- /INTRODUCTION.md: -------------------------------------------------------------------------------- 1 | # 本篇导读 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International 2 | Public License 3 | 4 | By exercising the Licensed Rights (defined below), You accept and agree 5 | to be bound by the terms and conditions of this Creative Commons 6 | Attribution-NonCommercial-ShareAlike 4.0 International Public License 7 | ("Public License"). To the extent this Public License may be 8 | interpreted as a contract, You are granted the Licensed Rights in 9 | consideration of Your acceptance of these terms and conditions, and the 10 | Licensor grants You such rights in consideration of benefits the 11 | Licensor receives from making the Licensed Material available under 12 | these terms and conditions. 13 | 14 | 15 | Section 1 -- Definitions. 16 | 17 | a. Adapted Material means material subject to Copyright and Similar 18 | Rights that is derived from or based upon the Licensed Material 19 | and in which the Licensed Material is translated, altered, 20 | arranged, transformed, or otherwise modified in a manner requiring 21 | permission under the Copyright and Similar Rights held by the 22 | Licensor. For purposes of this Public License, where the Licensed 23 | Material is a musical work, performance, or sound recording, 24 | Adapted Material is always produced where the Licensed Material is 25 | synched in timed relation with a moving image. 26 | 27 | b. Adapter's License means the license You apply to Your Copyright 28 | and Similar Rights in Your contributions to Adapted Material in 29 | accordance with the terms and conditions of this Public License. 30 | 31 | c. BY-NC-SA Compatible License means a license listed at 32 | creativecommons.org/compatiblelicenses, approved by Creative 33 | Commons as essentially the equivalent of this Public License. 34 | 35 | d. Copyright and Similar Rights means copyright and/or similar rights 36 | closely related to copyright including, without limitation, 37 | performance, broadcast, sound recording, and Sui Generis Database 38 | Rights, without regard to how the rights are labeled or 39 | categorized. For purposes of this Public License, the rights 40 | specified in Section 2(b)(1)-(2) are not Copyright and Similar 41 | Rights. 42 | 43 | e. Effective Technological Measures means those measures that, in the 44 | absence of proper authority, may not be circumvented under laws 45 | fulfilling obligations under Article 11 of the WIPO Copyright 46 | Treaty adopted on December 20, 1996, and/or similar international 47 | agreements. 48 | 49 | f. Exceptions and Limitations means fair use, fair dealing, and/or 50 | any other exception or limitation to Copyright and Similar Rights 51 | that applies to Your use of the Licensed Material. 52 | 53 | g. License Elements means the license attributes listed in the name 54 | of a Creative Commons Public License. The License Elements of this 55 | Public License are Attribution, NonCommercial, and ShareAlike. 56 | 57 | h. Licensed Material means the artistic or literary work, database, 58 | or other material to which the Licensor applied this Public 59 | License. 60 | 61 | i. Licensed Rights means the rights granted to You subject to the 62 | terms and conditions of this Public License, which are limited to 63 | all Copyright and Similar Rights that apply to Your use of the 64 | Licensed Material and that the Licensor has authority to license. 65 | 66 | j. Licensor means the individual(s) or entity(ies) granting rights 67 | under this Public License. 68 | 69 | k. NonCommercial means not primarily intended for or directed towards 70 | commercial advantage or monetary compensation. For purposes of 71 | this Public License, the exchange of the Licensed Material for 72 | other material subject to Copyright and Similar Rights by digital 73 | file-sharing or similar means is NonCommercial provided there is 74 | no payment of monetary compensation in connection with the 75 | exchange. 76 | 77 | l. Share means to provide material to the public by any means or 78 | process that requires permission under the Licensed Rights, such 79 | as reproduction, public display, public performance, distribution, 80 | dissemination, communication, or importation, and to make material 81 | available to the public including in ways that members of the 82 | public may access the material from a place and at a time 83 | individually chosen by them. 84 | 85 | m. Sui Generis Database Rights means rights other than copyright 86 | resulting from Directive 96/9/EC of the European Parliament and of 87 | the Council of 11 March 1996 on the legal protection of databases, 88 | as amended and/or succeeded, as well as other essentially 89 | equivalent rights anywhere in the world. 90 | 91 | n. You means the individual or entity exercising the Licensed Rights 92 | under this Public License. Your has a corresponding meaning. 93 | 94 | 95 | Section 2 -- Scope. 96 | 97 | a. License grant. 98 | 99 | 1. Subject to the terms and conditions of this Public License, 100 | the Licensor hereby grants You a worldwide, royalty-free, 101 | non-sublicensable, non-exclusive, irrevocable license to 102 | exercise the Licensed Rights in the Licensed Material to: 103 | 104 | a. reproduce and Share the Licensed Material, in whole or 105 | in part, for NonCommercial purposes only; and 106 | 107 | b. produce, reproduce, and Share Adapted Material for 108 | NonCommercial purposes only. 109 | 110 | 2. Exceptions and Limitations. For the avoidance of doubt, where 111 | Exceptions and Limitations apply to Your use, this Public 112 | License does not apply, and You do not need to comply with 113 | its terms and conditions. 114 | 115 | 3. Term. The term of this Public License is specified in Section 116 | 6(a). 117 | 118 | 4. Media and formats; technical modifications allowed. The 119 | Licensor authorizes You to exercise the Licensed Rights in 120 | all media and formats whether now known or hereafter created, 121 | and to make technical modifications necessary to do so. The 122 | Licensor waives and/or agrees not to assert any right or 123 | authority to forbid You from making technical modifications 124 | necessary to exercise the Licensed Rights, including 125 | technical modifications necessary to circumvent Effective 126 | Technological Measures. For purposes of this Public License, 127 | simply making modifications authorized by this Section 2(a) 128 | (4) never produces Adapted Material. 129 | 130 | 5. Downstream recipients. 131 | 132 | a. Offer from the Licensor -- Licensed Material. Every 133 | recipient of the Licensed Material automatically 134 | receives an offer from the Licensor to exercise the 135 | Licensed Rights under the terms and conditions of this 136 | Public License. 137 | 138 | b. Additional offer from the Licensor -- Adapted Material. 139 | Every recipient of Adapted Material from You 140 | automatically receives an offer from the Licensor to 141 | exercise the Licensed Rights in the Adapted Material 142 | under the conditions of the Adapter's License You apply. 143 | 144 | c. No downstream restrictions. You may not offer or impose 145 | any additional or different terms or conditions on, or 146 | apply any Effective Technological Measures to, the 147 | Licensed Material if doing so restricts exercise of the 148 | Licensed Rights by any recipient of the Licensed 149 | Material. 150 | 151 | 6. No endorsement. Nothing in this Public License constitutes or 152 | may be construed as permission to assert or imply that You 153 | are, or that Your use of the Licensed Material is, connected 154 | with, or sponsored, endorsed, or granted official status by, 155 | the Licensor or others designated to receive attribution as 156 | provided in Section 3(a)(1)(A)(i). 157 | 158 | b. Other rights. 159 | 160 | 1. Moral rights, such as the right of integrity, are not 161 | licensed under this Public License, nor are publicity, 162 | privacy, and/or other similar personality rights; however, to 163 | the extent possible, the Licensor waives and/or agrees not to 164 | assert any such rights held by the Licensor to the limited 165 | extent necessary to allow You to exercise the Licensed 166 | Rights, but not otherwise. 167 | 168 | 2. Patent and trademark rights are not licensed under this 169 | Public License. 170 | 171 | 3. To the extent possible, the Licensor waives any right to 172 | collect royalties from You for the exercise of the Licensed 173 | Rights, whether directly or through a collecting society 174 | under any voluntary or waivable statutory or compulsory 175 | licensing scheme. In all other cases the Licensor expressly 176 | reserves any right to collect such royalties, including when 177 | the Licensed Material is used other than for NonCommercial 178 | purposes. 179 | 180 | 181 | Section 3 -- License Conditions. 182 | 183 | Your exercise of the Licensed Rights is expressly made subject to the 184 | following conditions. 185 | 186 | a. Attribution. 187 | 188 | 1. If You Share the Licensed Material (including in modified 189 | form), You must: 190 | 191 | a. retain the following if it is supplied by the Licensor 192 | with the Licensed Material: 193 | 194 | i. identification of the creator(s) of the Licensed 195 | Material and any others designated to receive 196 | attribution, in any reasonable manner requested by 197 | the Licensor (including by pseudonym if 198 | designated); 199 | 200 | ii. a copyright notice; 201 | 202 | iii. a notice that refers to this Public License; 203 | 204 | iv. a notice that refers to the disclaimer of 205 | warranties; 206 | 207 | v. a URI or hyperlink to the Licensed Material to the 208 | extent reasonably practicable; 209 | 210 | b. indicate if You modified the Licensed Material and 211 | retain an indication of any previous modifications; and 212 | 213 | c. indicate the Licensed Material is licensed under this 214 | Public License, and include the text of, or the URI or 215 | hyperlink to, this Public License. 216 | 217 | 2. You may satisfy the conditions in Section 3(a)(1) in any 218 | reasonable manner based on the medium, means, and context in 219 | which You Share the Licensed Material. For example, it may be 220 | reasonable to satisfy the conditions by providing a URI or 221 | hyperlink to a resource that includes the required 222 | information. 223 | 3. If requested by the Licensor, You must remove any of the 224 | information required by Section 3(a)(1)(A) to the extent 225 | reasonably practicable. 226 | 227 | b. ShareAlike. 228 | 229 | In addition to the conditions in Section 3(a), if You Share 230 | Adapted Material You produce, the following conditions also apply. 231 | 232 | 1. The Adapter's License You apply must be a Creative Commons 233 | license with the same License Elements, this version or 234 | later, or a BY-NC-SA Compatible License. 235 | 236 | 2. You must include the text of, or the URI or hyperlink to, the 237 | Adapter's License You apply. You may satisfy this condition 238 | in any reasonable manner based on the medium, means, and 239 | context in which You Share Adapted Material. 240 | 241 | 3. You may not offer or impose any additional or different terms 242 | or conditions on, or apply any Effective Technological 243 | Measures to, Adapted Material that restrict exercise of the 244 | rights granted under the Adapter's License You apply. 245 | 246 | 247 | Section 4 -- Sui Generis Database Rights. 248 | 249 | Where the Licensed Rights include Sui Generis Database Rights that 250 | apply to Your use of the Licensed Material: 251 | 252 | a. for the avoidance of doubt, Section 2(a)(1) grants You the right 253 | to extract, reuse, reproduce, and Share all or a substantial 254 | portion of the contents of the database for NonCommercial purposes 255 | only; 256 | 257 | b. if You include all or a substantial portion of the database 258 | contents in a database in which You have Sui Generis Database 259 | Rights, then the database in which You have Sui Generis Database 260 | Rights (but not its individual contents) is Adapted Material, 261 | including for purposes of Section 3(b); and 262 | 263 | c. You must comply with the conditions in Section 3(a) if You Share 264 | all or a substantial portion of the contents of the database. 265 | 266 | For the avoidance of doubt, this Section 4 supplements and does not 267 | replace Your obligations under this Public License where the Licensed 268 | Rights include other Copyright and Similar Rights. 269 | 270 | 271 | Section 5 -- Disclaimer of Warranties and Limitation of Liability. 272 | 273 | a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE 274 | EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS 275 | AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF 276 | ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, 277 | IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, 278 | WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR 279 | PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, 280 | ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT 281 | KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT 282 | ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. 283 | 284 | b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE 285 | TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, 286 | NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, 287 | INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, 288 | COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR 289 | USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN 290 | ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR 291 | DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR 292 | IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. 293 | 294 | c. The disclaimer of warranties and limitation of liability provided 295 | above shall be interpreted in a manner that, to the extent 296 | possible, most closely approximates an absolute disclaimer and 297 | waiver of all liability. 298 | 299 | 300 | Section 6 -- Term and Termination. 301 | 302 | a. This Public License applies for the term of the Copyright and 303 | Similar Rights licensed here. However, if You fail to comply with 304 | this Public License, then Your rights under this Public License 305 | terminate automatically. 306 | 307 | b. Where Your right to use the Licensed Material has terminated under 308 | Section 6(a), it reinstates: 309 | 310 | 1. automatically as of the date the violation is cured, provided 311 | it is cured within 30 days of Your discovery of the 312 | violation; or 313 | 314 | 2. upon express reinstatement by the Licensor. 315 | 316 | For the avoidance of doubt, this Section 6(b) does not affect any 317 | right the Licensor may have to seek remedies for Your violations 318 | of this Public License. 319 | 320 | c. For the avoidance of doubt, the Licensor may also offer the 321 | Licensed Material under separate terms or conditions or stop 322 | distributing the Licensed Material at any time; however, doing so 323 | will not terminate this Public License. 324 | 325 | d. Sections 1, 5, 6, 7, and 8 survive termination of this Public 326 | License. 327 | 328 | 329 | Section 7 -- Other Terms and Conditions. 330 | 331 | a. The Licensor shall not be bound by any additional or different 332 | terms or conditions communicated by You unless expressly agreed. 333 | 334 | b. Any arrangements, understandings, or agreements regarding the 335 | Licensed Material not stated herein are separate from and 336 | independent of the terms and conditions of this Public License. 337 | 338 | 339 | Section 8 -- Interpretation. 340 | 341 | a. For the avoidance of doubt, this Public License does not, and 342 | shall not be interpreted to, reduce, limit, restrict, or impose 343 | conditions on any use of the Licensed Material that could lawfully 344 | be made without permission under this Public License. 345 | 346 | b. To the extent possible, if any provision of this Public License is 347 | deemed unenforceable, it shall be automatically reformed to the 348 | minimum extent necessary to make it enforceable. If the provision 349 | cannot be reformed, it shall be severed from this Public License 350 | without affecting the enforceability of the remaining terms and 351 | conditions. 352 | 353 | c. No term or condition of this Public License will be waived and no 354 | failure to comply consented to unless expressly agreed to by the 355 | Licensor. 356 | 357 | d. Nothing in this Public License constitutes or may be interpreted 358 | as a limitation upon, or waiver of, any privileges and immunities 359 | that apply to the Licensor or You, including from the legal 360 | processes of any jurisdiction or authority. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Contributors][contributors-shield]][contributors-url] 2 | [![Forks][forks-shield]][forks-url] 3 | [![Stargazers][stars-shield]][stars-url] 4 | [![Issues][issues-shield]][issues-url] 5 | [![license: CC BY-NC-SA 4.0](https://img.shields.io/badge/license-CC%20BY--NC--SA%204.0-lightgrey.svg)][license-url] 6 | 7 | 8 |
9 |

10 | 11 | Logo 12 | 13 | 14 |

15 | 在线阅读 >> 16 |
17 |
18 | 代码案例 19 | · 20 | 参考资料 21 | 22 |

23 |

24 | 25 | ![](https://assets.ng-tech.icu/item/20230416204546.png) 26 | 27 | # 分布式存储 28 | 29 | 分布式存储系统,广义上来讲,将文件存储抽象化,之前提到的块存储和对象存储都建立在这个系统之上。从某些角度来讲,存储系统相当于中间件,建立在底层的 SATA 或者 SSD 磁盘之上,而服务于上层的块存储。 30 | 31 | ## 挑战 32 | 33 | 分布式存储系统(Distributed Storage System)的核心不外乎两点:分片策略(Sharding Strategy)与元数据存储(Metadata Storage)机制,我们在保证存储系统弹性可扩展(Elastic Scalability)的同时需要保证系统的透明性(Transpant)与一致性(Consistent)。 34 | 35 | ## Nav | 关联导航 36 | 37 | - 如果你想了解微服务/云原生等分布式系统的应用实践,可以参阅;如果你想了解数据库相关,可以参阅 [Database-Notes](https://github.com/wx-chevalier/Database-Notes);如果你想了解虚拟化与云计算相关,可以参阅 [Cloud-Notes](https://github.com/wx-chevalier/Cloud-Notes);如果你想了解 Linux 与操作系统相关,可以参阅 [Linux-Notes](https://github.com/wx-chevalier/Linux-Notes)。 38 | 39 | # About 40 | 41 | ## Copyright & More | 延伸阅读 42 | 43 | 笔者所有文章遵循 [知识共享 署名-非商业性使用-禁止演绎 4.0 国际许可协议](https://creativecommons.org/licenses/by-nc-nd/4.0/deed.zh),欢迎转载,尊重版权。您还可以前往 [NGTE Books](https://ng-tech.icu/books-gallery/) 主页浏览包含知识体系、编程语言、软件工程、模式与架构、Web 与大前端、服务端开发实践与工程架构、分布式基础架构、人工智能与深度学习、产品运营与创业等多类目的书籍列表: 44 | 45 | [![NGTE Books](https://s2.ax1x.com/2020/01/18/19uXtI.png)](https://ng-tech.icu/books-gallery/) 46 | 47 | 48 | 49 | 50 | [contributors-shield]: https://img.shields.io/github/contributors/wx-chevalier/DistributedSystem-Notes.svg?style=flat-square 51 | [contributors-url]: https://github.com/wx-chevalier/DistributedSystem-Notes/graphs/contributors 52 | [forks-shield]: https://img.shields.io/github/forks/wx-chevalier/DistributedSystem-Notes.svg?style=flat-square 53 | [forks-url]: https://github.com/wx-chevalier/DistributedSystem-Notes/network/members 54 | [stars-shield]: https://img.shields.io/github/stars/wx-chevalier/DistributedSystem-Notes.svg?style=flat-square 55 | [stars-url]: https://github.com/wx-chevalier/DistributedSystem-Notes/stargazers 56 | [issues-shield]: https://img.shields.io/github/issues/wx-chevalier/DistributedSystem-Notes.svg?style=flat-square 57 | [issues-url]: https://github.com/wx-chevalier/DistributedSystem-Notes/issues 58 | [license-shield]: https://img.shields.io/github/license/wx-chevalier/DistributedSystem-Notes.svg?style=flat-square 59 | [license-url]: https://github.com/wx-chevalier/DistributedSystem-Notes/blob/master/LICENSE.txt 60 | -------------------------------------------------------------------------------- /_sidebar.md: -------------------------------------------------------------------------------- 1 | - [1 01~存储基础 [1]](/01~存储基础/README.md) 2 | - [1.1 BFT](/01~存储基础/BFT/README.md) 3 | 4 | - [2 02~复制 [3]](/02~复制/README.md) 5 | - [2.1 主从复制 [4]](/02~复制/主从复制/README.md) 6 | - [2.1.1 同步与异步](/02~复制/主从复制/同步与异步.md) 7 | - [2.1.2 复制延迟](/02~复制/主从复制/复制延迟.md) 8 | - [2.1.3 复制日志](/02~复制/主从复制/复制日志.md) 9 | - [2.1.4 宕机恢复](/02~复制/主从复制/宕机恢复.md) 10 | - [2.2 多主复制 [4]](/02~复制/多主复制/README.md) 11 | - [2.2.1 CRDT [2]](/02~复制/多主复制/CRDT) 12 | - 2.2.1.1 99~参考资料 [3] 13 | - [2.2.1.1.1 List CRDT Benchmarks](/02~复制/多主复制/CRDT/99~参考资料/2023-List%20CRDT%20Benchmarks.md) 14 | - [2.2.1.1.2 2023~A Gentle Introduction to CRDTs](/02~复制/多主复制/CRDT/99~参考资料/2023~A%20Gentle%20Introduction%20to%20CRDTs.md) 15 | - [2.2.1.1.3 2023~An Interactive Intro to CRDTs](/02~复制/多主复制/CRDT/99~参考资料/2023~An%20Interactive%20Intro%20to%20CRDTs.md) 16 | - 2.2.1.2 CRDT 框架 [1] 17 | - 2.2.1.2.1 Loro [1] 18 | - 2.2.1.2.1.1 99~参考资料 [1] 19 | - [2.2.1.2.1.1.1 2023~Introduction to Loro's Rich Text CRDT](/02~复制/多主复制/CRDT/CRDT%20框架/Loro/99~参考资料/2023~Introduction%20to%20Loro's%20Rich%20Text%20CRDT.md) 20 | - [2.2.2 冲突解决](/02~复制/多主复制/冲突解决.md) 21 | - [2.2.3 多主复制拓扑](/02~复制/多主复制/多主复制拓扑.md) 22 | - [2.3 无主复制 [2]](/02~复制/无主复制/README.md) 23 | - [2.3.1 故障与仲裁](/02~复制/无主复制/故障与仲裁.md) 24 | - [2.3.2 检测并发写入](/02~复制/无主复制/检测并发写入.md) 25 | - [3 03~数据分片 [3]](/03~数据分片/README.md) 26 | - [3.1 一致性哈希 [2]](/03~数据分片/一致性哈希/README.md) 27 | - [3.1.1 Go](/03~数据分片/一致性哈希/Go.md) 28 | - [3.1.2 Java](/03~数据分片/一致性哈希/Java.md) 29 | - [3.2 元数据与调度 [1]](/03~数据分片/元数据与调度/README.md) 30 | - [3.2.1 请求路由](/03~数据分片/元数据与调度/请求路由.md) 31 | - [3.3 分片策略 [3]](/03~数据分片/分片策略/README.md) 32 | - [3.3.1 分片再平衡](/03~数据分片/分片策略/分片再平衡.md) 33 | - [3.3.2 次级索引](/03~数据分片/分片策略/次级索引.md) 34 | - [3.3.3 键的分片](/03~数据分片/分片策略/键的分片.md) 35 | - [4 04~分布式 ID [5]](/04~分布式%20ID/README.md) 36 | - [4.1 ID 无序编码](/04~分布式%20ID/ID%20无序编码/README.md) 37 | 38 | - [4.2 Leaf](/04~分布式%20ID/Leaf.md) 39 | - [4.3 Snowflake](/04~分布式%20ID/Snowflake.md) 40 | - [4.4 UUID](/04~分布式%20ID/UUID.md) 41 | - [4.5 库自增 ID](/04~分布式%20ID/库自增%20ID.md) 42 | - [5 10~KV 存储 [4]](/10~KV%20存储/README.md) 43 | - [5.1 Consul](/10~KV%20存储/Consul/README.md) 44 | 45 | - [5.2 Etcd](/10~KV%20存储/Etcd/README.md) 46 | 47 | - [5.3 ZooKeeper [3]](/10~KV%20存储/ZooKeeper/README.md) 48 | - 5.3.1 基础概念 [3] 49 | - [5.3.1.1 ZAB](/10~KV%20存储/ZooKeeper/基础概念/ZAB.md) 50 | - [5.3.1.2 应用场景](/10~KV%20存储/ZooKeeper/基础概念/应用场景.md) 51 | - [5.3.1.3 核心角色](/10~KV%20存储/ZooKeeper/基础概念/核心角色.md) 52 | - 5.3.2 架构原理 [1] 53 | - [5.3.2.1 架构原理](/10~KV%20存储/ZooKeeper/架构原理/架构原理.md) 54 | - 5.3.3 部署与操作 [4] 55 | - [5.3.3.1 ACL](/10~KV%20存储/ZooKeeper/部署与操作/ACL.md) 56 | - [5.3.3.2 Apache Curator](/10~KV%20存储/ZooKeeper/部署与操作/Apache%20Curator.md) 57 | - [5.3.3.3 数据操作](/10~KV%20存储/ZooKeeper/部署与操作/数据操作.md) 58 | - [5.3.3.4 部署与配置](/10~KV%20存储/ZooKeeper/部署与操作/部署与配置.md) 59 | - 5.4 配置方式对比 [1] 60 | - [5.4.1 基础配置方式](/10~KV%20存储/配置方式对比/基础配置方式.md) 61 | - [6 20~分布式文件系统 [1]](/20~分布式文件系统/README.md) 62 | - [6.1 HDFS [3]](/20~分布式文件系统/HDFS/README.md) 63 | - [6.1.1 HDFS 源代码分析](/20~分布式文件系统/HDFS/HDFS%20源代码分析.md) 64 | - [6.1.2 HDFS 编程](/20~分布式文件系统/HDFS/HDFS%20编程.md) 65 | - [6.1.3 HDFS 读取原理](/20~分布式文件系统/HDFS/HDFS%20读取原理.md) 66 | - [7 30~对象存储 [2]](/30~对象存储/README.md) 67 | - [7.1 Haystack](/30~对象存储/Haystack.md) 68 | - [7.2 元数据管理](/30~对象存储/元数据管理.md) 69 | - [8 40~区块链 [1]](/40~区块链/README.md) 70 | - [8.1 共识算法 [1]](/40~区块链/共识算法/README.md) 71 | - [8.1.1 主流共识算法](/40~区块链/共识算法/主流共识算法.md) 72 | - [9 INTRODUCTION](/INTRODUCTION.md) -------------------------------------------------------------------------------- /header.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 101 | 366 |
367 |

368 | Distributed Storage Series by 王下邀月熊 369 |

370 |

371 | 分布式系统实战 372 |

373 |
374 | 375 |
376 |
377 |
378 |
379 |
380 |
381 |
382 |
383 |
384 |
385 |
386 |
387 |
388 |
389 |
390 | 391 | 392 |
393 |
394 |
-------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | DistributedStorage-Series 7 | 8 | 9 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 34 | 38 | 40 | 45 | 46 |
47 | 64 | 97 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 143 | 144 | 145 | 146 | 155 | 156 | 157 | --------------------------------------------------------------------------------