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 协议相信大家都不陌生,作为分布式共识算法的一员,旨在提供与 Paxos 共识算法相同的容错性和性能的前提下,追求更好的可理解性和工程可实现性。过去几年,围绕 Raft 协议涌现出了一系列各类语言的实现(参考 Raft 协议官网),这也充分证明了该算法相对于 Paxos 算法在理解和实现层面的友好性。

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

阅读全文

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 服务的正常运行。

阅读全文

Kafka 源码解析:延时任务调度策略

Kafka 一些组件的命名很是有趣,比如炼狱(purgatory)、死神(reaper)等,在日常开发中也建议大家在类和方法命名上能够以一些能够表达类或方法意图的人或事物的名词进行命名,让项目显得更加的生动。今天我们要分析的组件就是以 purgatory 命名的 DelayedOperationPurgatory,DelayedOperationPurgatory 是一个相对独立的组件,我们可以将其抽取出来用于自己的日常项目中,DelayedOperationPurgatory 主要用于管理延时任务,底层依赖于分层时间轮算法实现。

说到延时任务调度,对于 java 开发者来说,日常用到比较多的可能是 JDK 自带的 Timer、ScheduledThreadPoolExecutor 和 DelayQueue 等,但是对于 kafka 这类需要频繁执行复杂延时任务的分布式系统来说,这些组件在性能上还稍显不足,所以 kafka 自定义了分层时间轮算法,提供了 O(1) 时间复杂度的任务读取和移除性能,要优于 JDK 自带的基于堆实现的 O(log(n)) 时间复杂度的延时任务调度策略。

阅读全文