Theia:可扩展的注解式配置注入组件

Theia 是一个 java 语言编写的,支持自定义扩展的注解式配置加载与注入组件,旨在以注解的方式加载任何可以被表示成 Properties 对象的配置,并注入给目标对象,同时支持当配置内容发生变更时回调更新。配置文件的来源可以是本地文件、网络,以及第三方配置系统。Theia 默认支持从 ClassPath 加载本地配置文件,并支持以 SPI 的方式扩展以支持更多的配置来源,例如从 ZK 加载配置等。

特性一览:

  • 支持以注解的方式加载多种配置数据源,并注入给配置对象。
  • 支持预注入,预注入会校验配置的合法性,如果不合法则会放弃注入,避免配置出错影响服务的正常运行。
  • 支持配置变更时回调更新,默认关闭,并允许用户配置是否启用。
  • 内置基本类型转换器,用于将 String 类型配置项转换成目标类型对象。
  • 支持自定义类型转换器,以实现一些定制化的类型转换。
  • 支持以原生字符串或 Properties 对象的形式注入。
  • 支持监听注入过程(InjectEventListener)和更新过程(UpdateEventListener)。
  • 支持加载系统环境变量,并注入给配置对象。
  • 支持 ${} 占位符替换,使用指定的配置项替换占位符。
  • 支持以 SPI 的方式扩展以支持更多类型的配置数据源。
  • 对于 Spring 应用,支持自动扫描、加载并初始化配置对象。
阅读全文

SOFA-JRaft 源码解析:线性一致性读

关于线性一致性读的定义,简单而言就是在 T 时刻执行写入操作,那么在 T 时刻之后一定能够读取到之前写入的值。Raft 算法能够至少保证集群节点数据的最终一致性,也就说就某一特定时刻而言各个节点之间的数据状态允许存在滞后。在分布式场景下如果允许各个节点随机响应用户的读请求,则可能会读取到脏数据。如果希望基于 Raft 算法实现线性一致性读语义,最简单的方式就是将读操作作为一个指令提交给 Raft 集群,由于 Raft 算法能够保证指令执行的顺序性,所以该读操作指令一定能够读取到在此之前写入的值(本文将此类线性一致性读策略命名为 RaftLog Read)。然而,RaftLog Read 的缺点也是显而易见的,每次读操作都需要走一遍完整的 Raft 算法流程势必效率低下,并且大部分的应用场景都具备读多写少的特征,所以该策略势必会让 Raft 集群产生大量的日志文件,增加磁盘和网络的开销。

换一个角度思考,在 Raft 算法中更新操作都由 Leader 节点负责响应,那么极端一点每次都从 Leader 节点读数据是不是就万事大吉了呢?先不说这种方式将一个分布式系统退化成了单机系统,我们还需要考虑下面两个问题:

  • 日志数据从提交到被业务状态机所应用这中间存在一定的时间滞后性,所以直接执行读操作不一定能够读取到最新的数据。
  • 当前 Leader 节点不一定是有效的,因为 Leader 节点的变更通常有一个时间差,而这中间存在导致脏读的可能性。
阅读全文

SOFA-JRaft 源码解析:快照机制

上一篇我们介绍了 JRaft 关于日志复制机制的设计与实现,其中提到了快照机制本质上也是一种对日志数据复制的优化手段,本文我们就对 JRaft 关于快照机制的设计与实现展开分析。在开始之前我们先聊聊为什么需要引入快照机制,思考以下两个问题:

  1. 因为日志数据需要落盘存储,当日志数据量大到磁盘空间无法容纳时,除了扩容是否还有其它的优化手段?
  2. 当一个新的节点加入 Raft 集群时需要重放集群之前接收到的所有指令以追赶上集群的数据状态,这一过程往往比较耗时和消费带宽,如何进行优化?

对于一个生产级别的 Raft 算法库而言必须能够解决好上述问题,而 Raft 算法也为解决上述问题提供了思路,即快照机制。该机制通过定期为本地的数据状态生成对应的快照文件,并删除对应的日志文件,从而降低对于磁盘空间的容量消耗。当一个新的节点加入集群时,不同于从 Leader 节点复制集群在此之前的所有日志文件,基于快照机制该节点只需要从 Leader 节点下载安装最新的快照文件即可。由于快照文件是对某一时刻数据状态的备份,相对于原生日志数据而言在容量上要小很多,所以既降低了本地磁盘空间占用,也降低了新加入节点从 Leader 节点同步历史数据的时间和网络开销,很好的解决了上面抛出的两个问题。

阅读全文

SOFA-JRaft 源码解析:日志复制机制

与上一篇介绍的主节点选举一样,日志复制(Log Replication)同样是 Raft 算法的核心组成部分,是支撑 Raft 节点达成共识的基础。Raft 中的日志主要可以分为两类:一类是协议自身运行所生成的日志,例如集群节点配置变更信息;另外一类就是用户向集群提交的指令所生成的日志。为了让集群中的各个节点达成共识,Leader 节点需要将日志数据复制给集群中的各个节点,并采用投票机制让这些节点决定是否许可日志对应的操作。对于被许可的操作日志,各个节点会严格按照相同的顺序在本地进行存储,并重放日志对应的操作,以此实现节点之间的共识。

JRaft 在设计和实现层面为每个 Follower 和 Learner 节点都绑定了一个复制器 Replicator 实例,由 Replicator 负责向目标节点复制日志数据,Replicator 实例之间彼此相互隔离,互不影响,并由 ReplicatorGroup 进行统一管理。日志复制需要涉及到集群中节点之间的频繁通信和数据传输,所以需要保证复制操作的高性能,并且不允许出现乱序和断层。为此,JRaft 引入了多种优化策略,包括:Follower 节点之间并发复制、批量发送,以及 Pipeline 机制等。

阅读全文

SOFA-JRaft 源码解析:主节点选举机制

主节点选举(Leader Election)是 Raft 算法的核心组成部分,也是 Raft 算法库的主要应用场景之一。Raft 算法设计了 term 和 logIndex 两个属性,分别用于表示 Leader 节点的任期,以及集群运行期间接收到的指令对应的日志条目的 ID,这两个属性都是单调递增的。一个 Leader 节点在任期内会始终向其管理的所有 Follower 节点宣示主权,以避免这些 Follower 节点发动革命,推翻自己的政权,成为新的 Leader 节点。然而,世事无常,如果 Leader 节点因为某些原因不能或未能即时向某些 Follower 节点宣示自己的主权,则这些 Follower 节点在等待一段随机的时间之后就会尝试竞选成为新的 Leader 节点。

之所以这里采用随机化的等待时间,是为了避免两个或多个 Follower 节点同时发起选举进程,进而出现这些节点都没有赢得过半数的选票。于是,这些节点又在同一时间发起下一轮选举进程,延长了集群无 Leader 节点的时间,而通过随机化各个 Follower 节点等待的时间则能够很好的解决此类问题。

阅读全文

SOFA-JRaft 源码解析:节点的启动过程

在《理解 Raft 分布式共识算法》一文中,我们对于 Raft 算法的理论进行了介绍。过去几年,围绕 Raft 算法涌现出了一系列各类语言的实现(参考 Raft 算法官网),这也充分证明了该算法相对于 Paxos 算法在理解和实现层面的友好性。从本文开始,我就以 SOFA-JRaft 为例,用多篇文章来分析一个生产级别的 Raft 算法应该如何实现。

SOFA-JRaft 是一个基于 Raft 算法的 java 语言实现算法库,提供生产级别的稳定性、容错性,以及高性能,支持 MULTI-RAFT-GROUP,适用于高负载低延迟的场景。

阅读全文

理解 Raft 分布式共识算法

Raft 算法是一类基于日志复制的分布式共识算法,旨在提供与 Multi-Paxos 共识算法相同的容错性和性能的前提下,追求更好的可理解性和工程可实现性。Paxos 算法为分布式系统面临的共识问题提供了解决思路,但其难以理解的特性一直被大家所诟病,更不用说工程实现,这也是 Raft 算法诞生的主要动因。

Raft 算法的作者认为可理解性和工程可实现性也是一个优秀分布式共识算法所应该具备的特性,并通过以下两点保证算法的可理解性:

  • 问题分解 :将分布式共识问题拆分成主节点选举、日志复制、安全点,以及成员变更 4 个独立子问题逐一进行解决。
  • 简化状态 :通过增强某些阶段的一致性程度(例如约束能够成为下一任 Leader 的参选节点条件),以减少算法需要考虑的系统状态数量。
阅读全文

Kafka 源码解析:集群协同运行控制器

Kafka 集群由一系列的 broker 节点构成,在这些 broker 节点中会选举一个节点成为所有 broker 节点的 leader(称之为 kafka controller),其余的 broker 节点均为 follower 角色。Kafka Controller 负责管理集群中所有 topic 分区和副本的状态,协调集群中所有 broker 节点的运行,同时也负责 Kafka 与 ZK 之间的交互,下文中如果不特殊说明,Kafka Controller 均指代 leader 角色。

阅读全文

Kafka 源码解析:Group 协调管理机制

在 Kafka 的设计中,消费者一般都有一个 group 的概念(当然,也存在不属于任何 group 的消费者),将多个消费者组织成一个 group 可以提升消息的消费处理能力,同时又能保证消息消费的顺序性,不重复或遗漏消费。一个 group 名下的消费者包含一个 leader 角色和多个 follower 角色,虽然在消费消息方面这两类角色是等价的,但是 leader 角色相对于 follower 角色还担负着管理整个 group 的职责。当 group 中有新的消费者加入,或者某个消费者因为一些原因退出当前 group 时,亦或是订阅的 topic 分区发生变化时,都需要为 group 名下的消费者重新分配分区,在服务端确定好分区分配策略之后,具体执行分区分配的工作则交由 leader 消费者负责,并在完成分区分配之后将分配结果反馈给服务端。

阅读全文

Kafka 源码解析:分区多副本容错机制

在分布式应用中,通常会引入冗余策略来保证集群中节点在宕机时的服务可用性,Kafka 在设计上也是如此。Kafka 会为每个 topic 分区创建多个副本,并将这些副本分散在多台 broker 节点上,以避免单点问题。一个分区的副本集合包含一个 leader 角色和多个 follower 角色,其中 leader 副本主要负责响应客户端对于指定 topic 分区消息的读写,并管理集合中的其它 follower 副本,而 follower 副本则主要负责与 leader 副本间保持数据同步,保证在 leader 副本失效时能够有新的 follower 选举成为新的 leader,以维持 Kafka 服务的正常运行。

阅读全文