RPC的概念是什么?
面试官您好,RPC,全称是远程过程调用(Remote Procedure Call)。它的核心思想,就是让一个程序能够像调用本地方法一样,去调用另一台机器上另一个程序的方法,而完全不需要关心底层的网络通信细节。
RPC的目标,就是为开发者屏蔽掉所有复杂的网络编程,让我们能够以一种简单、透明的方式,来构建分布式系统。
1. RPC解决了什么问题?
在没有RPC的时代,如果服务A要调用服务B的功能,我们需要手动处理很多繁琐的事情:
- 需要知道服务B的IP地址和端口号。
- 需要和服务B约定好一个应用层协议(比如用JSON还是XML)。
- 需要自己处理网络连接、数据序列化、反序列化、网络异常等。
这个过程非常复杂、低效且容易出错。RPC框架的诞生,就是为了把所有这些脏活累活都封装起来,让分布式调用变得像本地调用一样简单。
2. 一个典型的RPC调用流程
假设我们有一个UserService
,客户端想要调用它在远端的getUserById(123)
方法。
-
客户端调用(像本地调用一样):
- 开发者在客户端代码中,就像调用一个本地接口一样,直接写下:
User user = userService.getUserById(123);
- 这里的
userService
实际上不是真正的实现,而是一个由RPC框架生成的代理对象(Proxy),也叫存根(Stub)。
- 开发者在客户端代码中,就像调用一个本地接口一样,直接写下:
-
代理层封装(客户端Stub):
- 这个代理对象会“拦截”这次调用。它会将调用的接口名(
UserService
)、方法名(getUserById
)、参数类型和参数值(123
)等所有元信息,打包成一个标准的请求对象。
- 这个代理对象会“拦截”这次调用。它会将调用的接口名(
-
序列化与网络传输:
- 客户端的RPC框架会将这个请求对象,通过一个序列化协议(如Protobuf, JSON, Kryo)转换成二进制字节流。
- 然后,通过底层的网络库(如Netty),将这个字节流发送到服务端的指定地址。
-
服务端接收与反序列化(服务端Skeleton):
- 服务端的RPC框架(通常称为骨架,Skeleton)接收到这个网络字节流。
- 它使用相同的序列化协议,将字节流反序列化,还原成请求对象。
-
服务端执行与返回:
- 服务端框架根据请求对象中的接口名和方法名,通过反射或动态代理,找到真正的
UserService
实现类,并调用其getUserById(123)
方法。 - 得到业务方法的执行结果(比如一个
User
对象)后,服务端框架再将这个结果对象序列化成字节流,通过网络发送回客户端。
- 服务端框架根据请求对象中的接口名和方法名,通过反射或动态代理,找到真正的
-
客户端接收与唤醒:
- 客户端的RPC框架接收到响应字节流,将其反序列化为
User
对象。 - 最后,唤醒之前被阻塞的业务线程,并将这个
User
对象作为返回值,赋给变量user
。
- 客户端的RPC框架接收到响应字节流,将其反序列化为
至此,一次完整的RPC调用结束。对于上层的业务开发者来说,他完全感觉不到这中间发生了序列化、网络传输、反序列化等一系列复杂的过程。
3. RPC框架的核心组成部分
一个成熟的RPC框架,通常包含以下几个核心模块:
- 服务定义:用于定义跨网络调用的服务接口和数据结构(如gRPC的
.proto
文件)。 - 客户端/服务端桩代码:自动生成的代理和骨架代码。
- 序列化/反序列化层:负责对象和字节流之间的转换。
- 网络传输层:基于TCP/HTTP等协议,负责高效的数据传输(Netty是Java领域的首选)。
- 服务治理:这是一个高级RPC框架的标志,包括:
- 服务注册与发现:服务提供者将自己注册到注册中心(如ZooKeeper, Nacos),消费者可以动态地发现服务地址。
- 负载均衡:当一个服务有多个提供者时,客户端可以根据策略(如随机、轮询)选择一个进行调用。
- 熔断、降级、限流:保证系统在极端压力下的稳定性。
4. 主流的RPC框架
- gRPC:Google出品,基于HTTP/2,使用Protobuf作为默认序列化协议,性能极高,跨语言支持好,是云原生时代的首选。
- Thrift:Facebook出品,历史悠久,支持多种协议和序列化格式,非常灵活。
- Dubbo:阿里巴巴开源的、专为Java设计的高性能RPC框架,提供了非常完善的服务治理能力,在国内有极其广泛的应用。
总的来说,RPC是构建现代微服务架构的“神经系统”,它使得服务之间的通信变得高效、透明和可靠。
ZooKeeper拿来做什么?核心的原理是什么?
面试官您好,ZooKeeper是一个开源的、为分布式应用提供高性能协调服务的中间件。它的定位不是用来做数据存储,而是作为分布式系统中各个服务之间的一个 “协调员” 或 “仲裁者”。
它能解决的核心问题是,在复杂的分布式环境下,如何可靠地管理和同步那些共享的、关键的元数据(Metadata)。
1. ZooKeeper能拿来做什么?(应用场景)
基于其强大的协调能力,ZooKeeper在分布式系统中有三大经典应用场景:
a. 分布式锁 (Distributed Lock)
- 场景:在分布式环境下,多个服务实例需要竞争同一个共享资源。
- ZK实现:利用ZooKeeper的有序临时节点特性。每个想获取锁的服务,都在一个约定的父节点下创建一个有序临时节点。然后,通过比较谁的节点序号最小来决定谁持有锁。其他服务则监视(Watch) 比自己序号小的前一个节点。当锁被释放(即节点被删除)时,下一个服务会被唤醒,从而实现一个公平、可靠的分布式锁。同时,因为是临时节点,如果持有锁的服务宕机,锁会自动释放,避免了死锁。
b. 服务注册与发现 (Service Registry & Discovery)
- 场景:在微服务架构中,服务提供者的地址是动态变化的,消费者需要能动态地发现它们。
- ZK实现:
- 注册:服务提供者在启动时,在ZK的一个约定目录下(如
/services/order-service
),创建一个代表自己服务实例的临时节点,节点中可以存储IP和端口信息。 - 发现:服务消费者启动时,订阅这个目录,获取所有子节点列表,从而得到所有可用的服务提供者地址。
- 健康检查:消费者可以监视这些子节点。如果某个服务提供者宕机,它对应的临时节点会自动消失,消费者会立即收到通知,并从其本地的服务列表中移除该实例。
- Dubbo框架就曾广泛使用ZooKeeper作为其默认的注册中心。
- 注册:服务提供者在启动时,在ZK的一个约定目录下(如
c. 配置管理 (Configuration Management)
- 场景:分布式系统中,大量的服务实例需要共享同一份配置信息,并希望在配置变更时能被实时通知。
- ZK实现:将配置信息存储在ZK的某个数据节点(Znode)上。所有服务实例在启动时都去读取这个节点的数据作为自己的配置。同时,它们都监视这个节点。当运维人员修改了该节点的数据时,所有监视该节点的服务都会立即收到通知,然后重新拉取最新的配置,实现配置的动态更新。
2. ZooKeeper的核心原理是什么?
ZooKeeper之所以能提供如此可靠的协调服务,主要依赖于它独特的数据模型和一致性协议。
a. 数据模型:类似文件系统的树形结构
- Znode:ZooKeeper的数据都存储在一个层次化的、类似文件系统的树形结构中。每个节点被称为一个Znode。
- Znode类型:
- 持久节点 (PERSISTENT):默认类型,节点一旦创建,除非被显式删除,否则一直存在。
- 临时节点 (EPHEMERAL):节点的生命周期与创建它的客户端会话(Session)绑定。一旦客户端会话因超时或断开而失效,该节点会被自动删除。这是实现分布式锁和健康检查的关键。
- 顺序节点 (SEQUENTIAL):在创建节点时,ZK会自动在其名称后追加一个单调递增的、全局唯一的序号。这是实现分布式锁和队列的关键。
- Watch机制:客户端可以对Znode注册一个一次性的监视器(Watcher)。当该Znode的数据发生变化或其子节点列表发生变化时,客户端会收到一个通知。这个机制是实现服务发现、配置管理等实时通知功能的基础。
b. 一致性协议:ZAB协议 (ZooKeeper Atomic Broadcast)
这是ZooKeeper的灵魂,它保证了集群数据的一致性。ZAB协议是一种为ZooKeeper专门设计的、支持崩溃恢复的原子广播协议。
- 角色:ZK集群采用主从(Leader-Follower) 架构。
- Leader:负责处理所有写请求(事务)。
- Follower:可以处理读请求,同时作为Leader的候选者。
- 原子广播 (写操作流程):
- 所有写请求必须先发给Leader。
- Leader将这个写操作,作为一个事务提议(Proposal),广播给所有Follower。
- Follower收到提议后,会先将其以日志形式写入本地磁盘,然后向Leader发送一个ACK。
- 当Leader收到超过半数(Quorum)的Follower的ACK后,它就会向所有Follower发送一个COMMIT消息,并执行本地的写操作。
- Follower收到COMMIT后,也执行本地的写操作。
- 这个过程类似于一个两阶段提交(2PC),但通过“过半数即提交”的机制,保证了数据最终的一致性和系统的可用性。
- 崩溃恢复:当Leader宕机时,所有Follower会进入选举模式,通过内部算法(基于
zxid
和myid
)选举出一个数据最新的Follower成为新的Leader。选举完成后,集群会进行数据同步,确保所有节点的数据与新Leader一致,然后才重新对外提供服务。
总结
- 做什么:ZooKeeper主要用于实现分布式锁、服务注册与发现、配置管理等分布式协调任务。
- 怎么做:它通过类似文件系统的树形数据模型(Znode)和强大的Watch机制,为上层应用提供了丰富的操作接口。
- 如何保证可靠:其底层通过ZAB协议,以主从复制和过半数提交的方式,保证了在分布式环境下数据的一致性和系统的容错能力。
总的来说,ZooKeeper是一个CP系统,它在任何时候都优先保证数据的一致性,是构建可靠分布式系统的关键基础设施。
常见的限流算法你知道哪些?
面试官您好,限流是构建高可用、高弹性系统的关键一环,它的核心目标是通过控制请求的速率,来保护下游服务不被瞬时的高并发流量冲垮。我了解到的常见限流算法主要有以下几种,它们各有优劣,适用于不同的场景。
1. 固定窗口/计数器算法 (Fixed Window Counter)
这是最简单、最直观的限流算法。
- 核心思想:在一个固定的时间窗口内(比如1秒钟),维护一个计数器,记录这个窗口内接收到的请求数量。
- 工作流程:
- 当一个请求到来时,将计数器的值加一。
- 如果计数器的值小于或等于限流阈值(比如100 QPS),则允许请求通过。
- 如果计数器的值超过了阈值,则拒绝该请求。
- 当时间窗口结束时,将计数器清零。
- 优点:实现非常简单,容易理解。
- 缺点:存在 “临界突发” 问题。
- 举例:假设限流阈值是100 QPS。如果在第一个窗口的最后100毫秒,突然来了100个请求;然后在第二个窗口的最开始100毫秒,又来了100个请求。那么,在这短短的200毫秒内,系统实际承受了200个请求,这个瞬时流量是限流阈值的两倍,可能会对系统造成冲击。
2. 滑动窗口算法 (Sliding Window)
这是对固定窗口算法的改进,旨在解决“临界突发”问题。
- 核心思想:将一个大的时间窗口(比如1分钟),再细分成多个更小的子窗口(比如分成60个1秒的子窗口)。然后,在一个滑动的时间范围内,统计请求总数。
- 工作流程:
- 假设我们将1分钟划分为60个子窗口,每个子窗口都有自己独立的计数器。
- 当一个请求到来时,当前子窗口的计数器加一。
- 然后,统计当前时间点向前推移的整个大窗口(过去1分钟)内,所有子窗口的请求总数。
- 如果总数超过了限流阈值,则拒绝请求。
- 随着时间的推移,这个大的统计窗口会平滑地向右滑动,旧的子窗口会被移出,新的子窗口被纳入。
- 优点:通过更细粒度的控制,完美地解决了临界突发问题,限流更加平滑和精确。
- 缺点:实现相对复杂,需要更多的内存来存储所有子窗口的计数。
3. 漏桶算法 (Leaky Bucket)
这个算法的思路发生了转变,它更关注于以一个恒定的速率处理请求。
- 核心思想:可以把系统想象成一个容量固定、底部有一个小洞的漏桶。
- 工作流程:
- 所有的请求,无论来得多快多猛,都先被扔进这个“漏桶”里排队。
- 系统以一个恒定的、固定的速率,从桶的底部“漏出”(即处理)请求。
- 如果请求到来的速率,超过了漏出的速率,桶里的“水”(请求)就会越积越多。
- 如果桶被装满了,新来的请求就会被直接溢出(即拒绝)。
- 优点:
- 流量整形(Traffic Shaping):它能强制性地将不规则的、突发的流量,“整形”成平滑的、匀速的流量,对下游系统的保护效果非常好。
- 缺点:
- 无法应对突发流量:即使系统当前处于空闲状态,它也只能以那个固定的速率处理请求,无法利用空闲资源来快速处理突发的流量。对于那些需要快速响应的场景,这可能会增加请求的延迟。
4. 令牌桶算法 (Token Bucket)
这是目前应用最广泛、综合效果最好的限流算法。它既能限制平均速率,又能允许一定程度的突发流量。
- 核心思想:可以把系统想象成一个容量固定的、装令牌的桶。
- 工作流程:
- 系统以一个恒定的速率,不断地向这个桶里放入令牌(Token)。
- 如果桶里的令牌已经满了,新生成的令牌就会被丢弃。
- 当一个请求到来时,它必须先尝试从桶里获取一个令牌。
- 如果能拿到令牌,请求就被允许通过。
- 如果桶里没有令牌了,请求就会被拒绝或排队等待。
- 优点:
- 兼顾平均速率和突发处理:
- 因为令牌是以恒定速率生成的,所以从长期来看,请求的平均速率被限制住了。
- 当系统处于低谷时,桶里的令牌会逐渐积攒起来。当突发流量到来时,系统可以一次性地消耗掉这些积攒的令牌,来快速处理掉这波流量。这个“积攒的令牌数”,就代表了系统能应对的突发流量的上限。
- 兼顾平均速率和突发处理:
- 应用:Google的Guava库中的
RateLimiter
就是基于令牌桶算法实现的。
总结对比
算法名称 | 核心思想 | 优点 | 缺点 |
---|---|---|---|
固定窗口 | 固定时间窗口内计数 | 实现简单 | 有临界突发问题 (2倍流量) |
滑动窗口 | 细分窗口,平滑统计 | 限流精确,解决了临界问题 | 实现复杂,内存占用高 |
漏桶 | 以固定速率流出 | 流量整形,对下游保护效果好 | 无法应对突发,增加延迟 |
令牌桶 | 以固定速率生成令牌,请求消耗令牌 | 能限制平均速率,又能应对一定突发 | 实现相对复杂 |
在实际选型中,令牌桶算法因其出色的综合表现,成为了绝大多数场景下的首选方案。而滑动窗口算法,则在一些需要进行精确统计和分析的场景下(如API网关的用量统计)有很好的应用。
Raft协议和Paxos协议的原理?
面试官您好,Raft和Paxos都是分布式系统领域,用来解决一致性(Consensus) 问题的核心算法。它们的目标是相同的:确保在一个可能出现节点故障、网络延迟的分布式集群中,所有节点最终能对某个值或一系列操作的顺序达成完全一致的共识。
尽管目标相同,但它们在设计哲学、可理解性和实现方式上存在巨大的差异。
1. Paxos 协议:共识算法的“鼻祖”
Paxos由计算机科学巨匠兰伯特提出,它是一种非常通用、理论性极强的共识算法。它的核心思想是 “少数服从多数”,并将一个复杂的共识问题,分解为一系列的提议(Proposal)和投票过程。
核心角色:
- 提议者 (Proposer):提出一个值的提议。
- 接受者 (Acceptor):对提议进行投票和决策。
- 学习者 (Learner):被动地学习最终达成的共识结果。
Basic Paxos 的工作流程(两阶段提交):
-
准备阶段 (Prepare Phase):
- 目标:提议者需要“抢占”一个提案的“所有权”,并了解历史情况。
- 流程:提议者选择一个全局唯一的、单调递增的提案编号N,然后向所有接受者发送一个
Prepare(N)
请求。 - 接受者收到请求后,会检查N是否比它之前响应过的所有
Prepare
请求的编号都大。如果是,它就向提议者承诺:“我以后不会再响应任何编号小于N的提议”,并把自己之前接受过的、编号最大的提案值返回给提议者。
-
接受阶段 (Accept Phase):
- 目标:让大多数接受者就一个具体的值达成一致。
- 流程:如果提议者收到了超过半数接受者的承诺响应,它就会从这些响应中,选择一个编号最大的提案值作为本次的提议值(如果响应中都没有值,就用自己的值)。然后,它向所有接受者发送一个
Accept(N, Value)
请求。 - 接受者收到
Accept
请求后,只要这个请求的编号N不小于它之前承诺过的编号,它就会接受这个值。
-
达成共识:当一个提议值被超过半数的接受者接受后,这个值就达成了共识。
Paxos的特点:
- 优点:理论上非常强大和通用,是后续很多共识算法的基础。
- 缺点:极其难以理解和实现。兰伯特的论文非常晦涩,而且Basic Paxos只解决了对“单个值”达成共识的问题。要将它应用到实际系统中(如实现一个分布式日志),需要进行大量的扩展和工程实践(如Multi-Paxos),这个过程非常复杂且容易出错。
2. Raft 协议:为“可理解性”而生
Raft协议的诞生,就是为了解决Paxos“难以理解”的痛点。它的作者认为,一个算法如果不能被大多数工程师理解,就很难在工程上被正确地实现和应用。
因此,Raft的核心设计哲学就是 “可理解性(Understandability)”。它通过强Leader模型,将复杂的共识问题,分解为几个更简单、更独立的子问题。
核心子问题与机制:
a. Leader选举 (Leader Election)
- 角色:Raft将节点明确划分为Leader、Follower、Candidate三种角色。
- 机制:系统在任何时候,都必须有且仅有一个Leader。Leader负责处理所有客户端请求。其他节点都是Follower,被动地从Leader同步数据。
- 选举流程:
- 如果Follower在一段时间内(选举超时)没有收到Leader的心跳,它就会转变为Candidate,并开启一个新的任期(Term)。
- 它会向所有其他节点发送请求投票的消息。
- 如果一个Candidate获得了超过半数的选票,它就成为新一任的Leader。
- 这个强Leader的设计,极大地简化了一致性管理的逻辑。
b. 日志复制 (Log Replication)
这是Raft实现状态机复制的核心。
- 流程:
- 所有客户端的写请求,都必须发给Leader。
- Leader会将这个请求作为一个日志条目(Log Entry) 追加到自己的日志中。
- 然后,Leader会并行地将这个日志条目,通过
AppendEntries
RPC,复制给所有的Follower。 - 当Leader收到超过半数Follower的成功响应后,它就认为这条日志是 “已提交”(Committed) 的了。
- 一旦日志被提交,Leader就会将它应用到自己的状态机中,并可以向客户端返回成功。
- Leader也会在后续的心跳或
AppendEntries
RPC中,通知Follower哪些日志已经被提交,Follower收到通知后,也将这些日志应用到自己的状态机。
c. 安全性 (Safety)
Raft通过一系列的规则(如选举限制、提交规则等),严格地保证了系统的安全性,比如:
- 一个任期最多只有一个Leader。
- 拥有最新、最完整日志的节点,才有可能当选为Leader。
- 一个已提交的日志条目,永远不会被覆盖。
对比总结
对比维度 | Paxos | Raft |
---|---|---|
设计哲学 | 理论通用性,解决最根本的共识问题 | 工程可理解性,为实际系统构建而设计 |
核心模型 | 对等节点,通过多轮提议和投票达成共识 | 强Leader模型,Leader统一协调 |
问题分解 | 分为Prepare和Accept两个阶段 | 分为Leader选举、日志复制、安全性三个子问题 |
可理解性 | 非常困难 | 相对容易 |
实现复杂度 | 极高 | 中等 |
工业应用 | 作为理论基础,但直接实现少 | 非常广泛,如etcd, Consul, TiDB等 |
简单来说:
- Paxos 就像是在一群平等的、互相不信任的议员中,通过复杂的议会程序,艰难地就某项法案达成一致。
- Raft 则是先选举出一个“总统”(Leader),然后由这位总统来主导所有决策,其他人(Follower)只需要听从和复制即可。这个过程显然更清晰、更有序。
正因为Raft的易于理解和实现,它已经成为当今构建新的分布式一致性系统的首选算法。
有什么框架或技术用了Raft协议?
面试官您好,是的,Raft协议因为其出色的可理解性和相对容易实现的特性,在现代分布式系统中得到了极其广泛的应用。它已经成为构建需要强一致性的、高可用的分布式协调服务和存储系统的首选共识算法。
以下是我了解到的、使用了Raft协议的一些著名框架和技术:
1. 分布式协调服务 & 配置中心
这是Raft协议最直接、最经典的应用场景,用于替代过去由ZooKeeper(基于ZAB协议)所主导的领域。
-
etcd:
- 简介:这是由CoreOS团队开发的一个高可用的、分布式的键值存储系统。
- Raft的应用:
etcd
的核心就是Raft协议。它通过Raft来保证集群中所有节点上存储的键值数据是强一致的。客户端的任何写操作,都必须通过Raft的日志复制和共识流程,被复制到大多数节点后,才能被确认。 - 业界地位:
etcd
是 Kubernetes (K8s) 官方指定的、唯一的后端存储。K8s集群中所有的状态信息,如Pod的定义、Service的配置、节点的状态等等,都存储在etcd中。K8s的稳定性和一致性,完全依赖于etcd的可靠性,而etcd的可靠性,则源于Raft协议。
-
Consul:
- 简介:这是由HashiCorp公司推出的一个功能非常全面的服务网格(Service Mesh)解决方案,提供了服务发现、健康检查、配置管理和服务隔离等功能。
- Raft的应用:
Consul
的Server节点之间,通过Raft协议组成一个高可用的集群。这个集群负责维护服务目录、配置信息、ACL策略等所有关键数据的一致性。当服务注册、健康状态变更或配置更新时,这些变更都会通过Raft协议在Server集群中达成共识。
2. 分布式数据库 & 存储系统
Raft协议也被广泛应用于新一代分布式数据库的底层,以实现数据的多副本一致性和自动故障转移。
-
TiDB:
- 简介:这是一个开源的、兼容MySQL协议的分布式HTAP(混合事务/分析处理)数据库。
- Raft的应用:TiDB的存储层是 TiKV。TiKV会将数据切分成许多个小的区域(Region),每个Region都是一个独立的Raft组(Raft Group),通常有3个副本。所有的写操作,都必须在一个Region的Raft组内部,通过Raft协议达成共识后才能成功。这保证了数据在多个副本间的强一致性。
-
CockroachDB:
- 简介:这是一款开源的、高可用的分布式SQL数据库,设计目标之一就是能在地理上分布,并能容忍数据中心级别的故障。
- Raft的应用:与TiDB类似,CockroachDB也将数据划分为多个范围(Range),每个范围都是一个Raft组,通过Raft协议来保证数据的多副本一致性和自动故障转移。
-
InfluxDB Enterprise:
- 简介:这是一个领先的**开源时序数据库(TSDB)**的企业版。
- Raft的应用:其集群版本的元数据管理,就是通过一个内嵌的Raft共识组来实现的,确保了集群拓扑、数据库和用户等元信息的一致性。
3. 分布式消息队列
- RocketMQ (Dledger模式):
- 简介:阿里巴巴开源的高性能消息队列。
- Raft的应用:在4.5版本之后,RocketMQ引入了基于Raft协议的Dledger模式,作为一种可选的主从同步和Leader选举方案。当启用Dledger后,一个Broker组内的多个节点会形成一个Raft组。当Master节点宕机时,Dledger可以自动地、快速地从Slave中选举出新的Master,实现了自动故障转移,提升了MQ的可用性。
总结
领域 | 框架/技术 | Raft的应用场景 |
---|---|---|
分布式协调/配置 | etcd (K8s核心), Consul | 核心:保证集群元数据、配置、服务注册信息的一致性 |
分布式数据库 | TiDB, CockroachDB | 核心:保证数据分片(Region/Range)在多副本间的一致性 |
分布式消息队列 | RocketMQ (Dledger) | 高可用:实现Broker主从之间的自动Leader选举和故障转移 |
可以看到,Raft协议凭借其出色的可理解性和坚实的理论基础,已经成为构建新一代分布式系统的事实标准。从底层的键值存储,到上层的数据库和消息队列,都能看到它的身影。可以说,理解Raft协议,是理解现代分布式系统工作原理的一把钥匙。
参考小林 coding