服务端5月30日 02:24
RPC 和 RESTful API 有什么区别?什么时候选 RPC?RPC 和 RESTful API 的核心区别是抽象不同:RPC 像调用远程函数,关注方法、参数和返回值;REST 更像操作资源,关注 URL、HTTP 方法和状态码。内部微服务、低延迟、高吞吐、强类型契约、双向流式通信,通常选 RPC,比如 gRPC、Dubbo。对外开放接口、浏览器直接访问、需要易调试和缓存语义,REST 更合适。面试里不要说谁替代谁,关键是边界:内部效率优先选 RPC,对外通用性优先选 REST。
## 追问
### RPC 为什么通常性能更好?
很多 RPC 框架使用二进制序列化和长连接,协议开销更小,也更容易做连接复用、流式传输和代码生成。
### REST 的优势是什么?
它基于 HTTP 语义,curl、Postman、浏览器都好调试;URL、状态码、缓存头也更适合开放平台和前后端协作。
### gRPC 能直接给浏览器用吗?
原生 gRPC 对浏览器不友好,通常需要 gRPC-Web 或网关转换。面向公网用户时,很多团队会在外层提供 REST。
### 项目里怎么组合两者?
常见做法是外部 REST 网关,内部服务之间用 RPC。网关负责鉴权、限流、协议转换,内部服务专注高效调用。标签
RPC
远程过程调用(Remote Procedure Call,简称 RPC)是一种计算机通信协议,允许一台计算机(客户端)执行另一台计算机(服务器)上的程序或过程,就好像它是本地程序一样。RPC 隐藏了底层的网络通信细节,使得开发分布式应用程序变得更简单。

服务端5月30日 02:24
RPC 常见序列化协议有哪些?各自怎么选?RPC 常见序列化协议有 Protobuf、Thrift、JSON、Hessian、MessagePack 和 Avro。面试先给结论:内部高性能微服务优先 Protobuf 或 Thrift;需要可读、易调试、对外兼容用 JSON;Java 旧系统可能见到 Hessian;需要类 JSON 但更小的体积可考虑 MessagePack;大数据和日志链路常用 Avro。选择时看四件事:体积、速度、跨语言、schema 演进能力。
## 追问
### Protobuf 为什么常用于 RPC?
它是二进制格式,体积小、解析快,靠 .proto 定义字段和类型,跨语言代码生成成熟。缺点是调试不如 JSON 直观。
### Thrift 和 Protobuf 有什么区别?
Thrift 更像一整套 RPC 方案,包含 IDL、协议和传输;Protobuf 更专注数据序列化,常和 gRPC 搭配使用。
### JSON 为什么还没被淘汰?
因为它人类可读、生态通用、排查方便。对外 API、管理接口、低频调用里,调试成本往往比极致性能更重要。
### 协议升级最容易出什么问题?
字段删除、类型变更、枚举兼容最容易翻车。新增字段通常安全,变更字段语义要同时考虑新老客户端。
## 写段代码
```proto
message UserReq {
int64 id = 1;
string name = 2;
}
```服务端5月30日 02:24
如何优化 RPC 调用性能并降低网络延迟?优化 RPC 性能先看调用链路:连接、序列化、网络传输、线程模型、服务端处理和观测。面试可以先答:复用长连接,开启连接池和预热;选 Protobuf、Thrift 这类二进制协议,减少字段和大对象;小包低延迟场景开启 TCP_NODELAY;用异步调用、批量请求、就近路由和客户端缓存降低等待时间。最后用 P95/P99、错误率、QPS、线程池队列和链路追踪定位瓶颈,不要只凭感觉调参数。
## 追问
### TCP_NODELAY 一定要开吗?
不一定。它能减少小包等待,但可能增加包数量。低延迟 RPC 常开,吞吐优先的批量传输要压测后决定。
### 序列化为什么影响延迟?
序列化影响 CPU、对象分配和网络包大小。JSON 好调试但体积大;Protobuf 体积小、速度快,更适合内部高频调用。
### 异步调用能让单次 RPC 更快吗?
不一定降低单次网络耗时,但能减少线程阻塞,提高并发吞吐。真正耗时的服务端逻辑仍要单独优化。
### 项目里怎么排查慢 RPC?
先看 P99 和超时分布,再用 Trace 拆成客户端排队、网络、服务端处理、序列化几段。定位后再调连接池、线程池、负载均衡或缓存。
## 写段代码
```java
bootstrap.option(ChannelOption.TCP_NODELAY, true)
.option(ChannelOption.SO_KEEPALIVE, true)
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 300);
```服务端5月30日 02:24
RPC 负载均衡算法有哪些?如何选择合适策略?RPC 负载均衡常见算法有随机、轮询、加权随机/轮询、最少连接、最少活跃、最短响应时间、一致性哈希和 IP Hash。面试里先说选择原则:实例差不多用随机或轮询;机器配置不同用加权;请求耗时差异大用最少连接/最少活跃;需要会话保持或本地缓存命中用一致性哈希。真正落地还要配健康检查、熔断、权重动态调整,否则算法再好也会把流量打到故障节点。
## 追问
### 随机和轮询有什么区别?
随机实现最简单,长期看分布均匀;轮询更可预测,但如果某台机器变慢,仍会按顺序分流。两者都适合实例能力接近的场景。
### 为什么 Dubbo 默认常用加权随机?
它简单、开销低,配合权重能表达机器能力差异。比普通轮询更不容易在短时间内形成固定流量节奏。
### 一致性哈希解决什么问题?
它让同一个 key 尽量落到同一台实例,适合会话、缓存、分片类场景。节点增减时只迁移少量 key,但要用虚拟节点缓解倾斜。
### 实际项目里会踩什么坑?
只看请求数不看耗时会误判负载;服务注册中心实例下线不及时,会出现短时间错误流量。通常要把负载均衡和健康检查、超时、重试一起设计。
## 写段代码
```java
ServiceInstance select(List<ServiceInstance> list) {
return list.stream()
.filter(ServiceInstance::isHealthy)
.min(Comparator.comparingInt(ServiceInstance::getActive))
.orElseThrow();
}
```服务端5月30日 01:39
什么是分布式链路追踪?OpenTelemetry、Jaeger 和 SkyWalking 怎么选?分布式链路追踪就是给一次请求打上 Trace ID,把它经过的网关、服务、数据库、消息队列调用都串起来。面试里先答核心:Trace 表示一次完整请求,Span 表示其中一次操作,Span 之间用 parentId 形成调用树;上下文通常通过 HTTP Header、RPC Metadata 传播;数据由 SDK 或 Agent 采集,再异步上报到 Jaeger、SkyWalking、Zipkin 等后端。现在更推荐用 OpenTelemetry 做统一采集标准,后端再按团队习惯选择 Jaeger、SkyWalking 或商业 APM。
## 追问
### Trace、Span、Trace ID 有什么区别?
Trace 是整条调用链,Span 是链路上的一个节点,比如一次 HTTP 调用或 SQL 查询。Trace ID 贯穿全链路,Span ID 标识当前节点,Parent Span ID 用来还原父子关系。
### OpenTelemetry 和 Jaeger 是什么关系?
OpenTelemetry 主要解决“怎么埋点、怎么采集、怎么传输”的标准化问题;Jaeger 更像存储、查询和展示链路的后端。实际项目里常见组合是 OTel SDK/Collector + Jaeger。
### Jaeger、SkyWalking、Zipkin 怎么选?
Java 微服务、想要 APM 能力更全,可以选 SkyWalking;多语言、高并发链路追踪,Jaeger 更常见;Zipkin 简单稳定,适合轻量场景。新项目优先保证采集侧接 OpenTelemetry,避免后续迁移被某个后端绑死。
### 项目里最容易踩什么坑?
第一是异步线程、消息队列、定时任务没传上下文,链路会断。第二是采样率过高拖慢系统,过低又抓不到问题;线上通常按流量、错误率和核心接口分层采样。
### 链路追踪和日志、监控有什么区别?
监控告诉你“哪里慢了”,日志告诉你“发生了什么”,链路追踪告诉你“一次请求到底卡在哪个调用”。排障时三者结合,Trace ID 要能在日志里直接检索。
## 写段代码
```java
Span span = tracer.spanBuilder("queryUser").startSpan();
try (Scope scope = span.makeCurrent()) {
return userClient.getUser(id);
} catch (Exception e) {
span.recordException(e);
span.setStatus(StatusCode.ERROR);
throw e;
} finally {
span.end();
}
```服务端5月27日 23:30
gRPC 的核心特性与优势是什么?## gRPC 是什么
gRPC 是 Google 开源的高性能 RPC 框架,基于 HTTP/2 传输协议和 Protocol Buffers 序列化格式构建。它不仅是一个远程调用工具,更是一套完整的跨语言服务通信方案——通过 .proto 文件定义接口,自动生成多语言客户端和服务端桩代码,让不同语言的服务之间高效互通。
## 核心特性
### HTTP/2 传输层
HTTP/2 是 gRPC 高性能的底层基石,带来三个关键能力:
- **多路复用**:一条 TCP 连接上并行收发多个请求/响应,彻底解决 HTTP/1.1 的队头阻塞问题。实际效果是微服务间无需维护大量连接池,单连接即可支撑高并发调用。
- **头部压缩(HPACK)**:对请求头进行差分编码压缩,频繁调用的场景下头部开销可降低 80% 以上,这对移动端和低带宽环境尤其重要。
- **流式传输**:HTTP/2 的 Stream 机制天然支持服务端流、客户端流和双向流三种流式 RPC 模式。
### Protocol Buffers 序列化
Protobuf 是 gRPC 默认的 IDL 和序列化方案:
- **二进制编码**:相比 JSON 的文本格式,Protobuf 编码体积通常小 3-10 倍,序列化速度快 5 倍以上。原理是采用变长整数(varint)和字段编号而非字段名来标识数据。
- **强类型约束**:.proto 文件就是接口契约,编译时就能发现类型不匹配的问题,而不是等到运行时才报错。
- **向后兼容**:新增字段只分配新编号,老代码读到未知字段自动跳过,这在微服务滚动升级中至关重要。
- **跨语言代码生成**:一个 .proto 文件可以生成 Go、Java、Python、C++ 等 10+ 语言的客户端和服务端代码,保证多语言团队的接口一致性。
### 四种服务模式
gRPC 定义了四种通信模式,覆盖从简单请求到实时交互的全部场景:
| 模式 | 客户端 | 服务端 | 典型场景 |
|------|--------|--------|----------|
| 一元 RPC(Unary) | 单个请求 | 单个响应 | 查询用户信息 |
| 服务端流式 | 单个请求 | 流式响应 | 订阅实时行情 |
| 客户端流式 | 流式请求 | 单个响应 | 批量上传文件 |
| 双向流式 | 流式请求 | 流式响应 | 聊天、游戏同步 |
## 为什么选择 gRPC 而不是 REST 或其他 RPC 框架
### gRPC vs REST
| 维度 | gRPC | REST |
|------|------|------|
| 传输协议 | HTTP/2 | HTTP/1.1 为主 |
| 数据格式 | Protobuf 二进制 | JSON 文本 |
| 流式通信 | 四种模式原生支持 | 需 WebSocket 或 SSE |
| 接口定义 | .proto 文件强约束 | OpenAPI 规范(可选) |
| 代码生成 | 自动生成多语言桩代码 | 需第三方工具 |
| 浏览器支持 | 需 gRPC-Web 代理 | 原生支持 |
核心结论:内部微服务间通信选 gRPC,对外 API 尤其是面向浏览器/移动端选 REST。两者不是互斥的,很多团队用 gRPC 做内部通信,同时通过 gRPC-Gateway 暴露 REST 接口给外部。
### gRPC vs Thrift
Thrift 同样支持二进制序列化和多语言,但 gRPC 的优势在于 HTTP/2 原生支持(Thrift 通常走 TCP 自定义协议)、流式通信、以及 Google 生态(Kubernetes、Envoy 原生支持 gRPC 健康检查和负载均衡)。
## gRPC 的核心优势
**性能突出**:HTTP/2 多路复用 + Protobuf 二进制编码,端到端延迟通常比 REST+JSON 低 30%-50%,吞吐量提升 5-10 倍。
**开发效率高**:写好 .proto 文件后,一行命令生成所有语言的客户端和服务端代码,接口变更时重新生成即可,无需手工同步。
**跨语言无缝集成**:多语言微服务架构中,Go 写网关、Java 写业务、Python 写算法服务,都用同一份 .proto 定义,类型安全、调用方式统一。
**流式通信能力**:双向流是 gRPC 独特的杀手锏,让实时推送、聊天、监控等场景的实现从 hack 变成标准用法。
**生态成熟**:拦截器(Interceptor)实现鉴权和日志、健康检查协议配合 Kubernetes、内置超时和重试机制、与 OpenTelemetry 链路追踪无缝集成。
## 代码示例
定义服务和消息:
```protobuf
service OrderService {
// 一元调用:查询订单
rpc GetOrder(GetOrderRequest) returns (Order) {}
// 服务端流:订阅订单状态变更
rpc SubscribeOrder(SubscribeRequest) returns (stream OrderStatus) {}
// 客户端流:批量创建订单
rpc BatchCreateOrders(stream CreateOrderRequest) returns (BatchResult) {}
// 双向流:实时议价
rpc Negotiate(stream PriceRequest) returns (stream PriceResponse) {}
}
message GetOrderRequest {
string order_id = 1;
}
message Order {
string order_id = 1;
string status = 2;
int64 created_at = 3;
}
```
Go 语言服务端实现核心逻辑:
```go
func (s *Server) GetOrder(ctx context.Context, req *pb.GetOrderRequest) (*pb.Order, error) {
order, err := s.repo.FindByID(ctx, req.OrderId)
if err != nil {
return nil, status.Errorf(codes.NotFound, "order %s not found", req.OrderId)
}
return order, nil
}
func (s *Server) SubscribeOrder(req *pb.SubscribeRequest, stream pb.OrderService_SubscribeOrderServer) error {
ch := s.eventBus.Subscribe(req.OrderId)
for status := range ch {
if err := stream.Send(status); err != nil {
return err
}
}
return nil
}
```
## 面试追问方向
- gRPC 的 HTTP/2 多路复用如何避免队头阻塞?和 HTTP/1.1 的队头阻塞有什么本质区别?
- Protobuf 的 varint 编码原理是什么?负数如何处理?
- gRPC 的拦截器分哪两种?分别用在什么场景?
- gRPC-Web 的原理是什么?为什么浏览器不能直接调用 gRPC?
- 生产环境中 gRPC 连接如何做负载均衡?为什么客户端负载均衡更常见?
面试中回答 gRPC 问题,关键是把特性说清楚、把和 REST 的对比讲透、把流式通信的场景用实际例子说明,而不是泛泛罗列特性。掌握了 HTTP/2 和 Protobuf 两个底层原理,其他问题都能顺理成章地展开。服务端5月27日 23:30
Dubbo 的核心架构是怎样的?服务治理如何实现?## Dubbo 架构的五大角色
Dubbo 的架构围绕五个核心角色展开,理解它们之间的协作关系是掌握 Dubbo 的第一步。
调用链路:Consumer 发起调用 → 从 Registry 获取 Provider 地址列表 → 通过负载均衡选一台 Provider → Provider 执行并返回结果 → Monitor 记录调用数据。
**Provider(服务提供者)** 暴露服务接口,启动时将自己的地址和元数据注册到 Registry。一个服务可以部署多个 Provider 实例,Consumer 端通过负载均衡策略选择调用哪个实例。
**Consumer(服务消费者)** 从 Registry 订阅所需服务,获取 Provider 列表后缓存在本地。后续调用直接使用本地缓存,即使 Registry 宕机也不影响已有连接。
**Registry(注册中心)** 是服务发现的核心。Dubbo 支持 Zookeeper、Nacos、Redis 等实现,其中 Zookeeper 和 Nacos 是生产环境最常用的选择。Registry 通过长连接推送机制,在 Provider 上线或下线时实时通知 Consumer 更新本地缓存。
**Monitor(监控中心)** 负责统计调用次数和耗时,数据先在内存汇总,每分钟发送一次。Monitor 不参与实际调用链路,宕机不影响服务运行,只丢失采样数据。
**Container(服务运行容器)** 负责启动和加载 Provider,Spring Container 和 Spring Boot 是主流选择。
架构的健壮性设计值得注意:注册中心集群对等部署,任意节点宕机自动切换;注册中心全部宕机后,Consumer 仍能通过本地缓存与 Provider 通信;Provider 无状态,单节点宕机不影响整体服务。
## 服务调用与协议选择
Dubbo 支持多种通信协议,选择合适的协议直接影响系统性能。
**Dubbo 协议**是默认选项,基于 Netty 的长连接 + NIO 异步传输,采用单一长连接和 Hessian 二进制序列化,适合小数据量高并发的服务间调用。这也是多数生产环境的首选。
**Triple 卆议**是 Dubbo 3.x 推出的新协议,基于 HTTP/2,兼容 gRPC,支持流式通信。如果系统需要跨语言调用或与 gRPC 生态对接,Triple 是更好的选择。
其他协议如 HTTP、Hessian、REST 适用于特定场景:HTTP 适合与前端直接交互的网关服务,REST 适合对外暴露 API。
调用方式上,Dubbo 支持同步调用、异步调用和泛化调用。异步调用通过 `CompletableFuture` 实现,适合需要并行调用多个服务的场景;泛化调用不需要 Provider 端的接口定义,适合网关或测试平台这类通用调用方。
## 集群容错策略
当 Provider 出现故障时,Dubbo 提供六种容错策略应对不同场景:
**Failover(失败自动重试)** 是默认策略,自动切换到其他 Provider 重试。通过 `retries` 参数控制重试次数(不含首次调用),默认重试 2 次。适合读操作,写操作需谨慎——重试可能导致数据重复写入。
**Failfast(快速失败)** 只发起一次调用,失败立即报错。适合非幂等的写操作,如创建订单、扣款,避免重试带来的副作用。
**Failsafe(失败安全)** 出现异常时忽略,不抛出异常。适合日志记录、监控上报等非核心操作。
**Failback(失败自动恢复)** 将失败请求记录到后台队列,定时重发。适合消息通知这类最终一致性场景。
**Forking(并行调用)** 同时调用多个 Provider,只要一个成功即返回。通过 `forks` 参数控制并行数。适合对延迟敏感但资源消耗可以接受的场景。
**Broadcast(广播调用)** 逐个调用所有 Provider,任一失败则报错。适合通知所有节点更新缓存或配置的场景。
实际选型建议:读操作用 Failover,写操作用 Failfast,边缘操作用 Failsafe,这是最常见的选择。
## 负载均衡机制
Dubbo 内置四种负载均衡策略,核心区别在于请求分发的方式:
**Random(加权随机)** 是默认策略,按权重设置随机概率。在高并发场景下,随机策略的调用分布趋于均匀,且实现简单、性能开销小。
**RoundRobin(加权轮询)** 按权重比例依次轮询分配请求。存在慢请求累积问题——某个 Provider 响应慢时,轮询到它的请求都会阻塞。
**LeastActive(最少活跃调用数)** 优先将请求分配给当前处理中请求数最少的 Provider。这是一种自适应策略,响应越快的 Provider 接收越多请求,能有效避免慢节点堆积。
**ConsistentHash(一致性哈希)** 相同参数的请求总是路由到同一 Provider。当某个 Provider 下线时,其请求会平滑迁移到相邻节点,不会引起大面积重新分配。适合有状态依赖的场景,如用户会话、分片数据。
权重调节是线上运维的常用手段:通过 Dubbo Admin 或配置中心动态调整权重,可以实现灰度发布和流量倾斜,无需重启服务。
## 服务治理的核心能力
服务治理是 Dubbo 区别于简单 RPC 框架的关键,涵盖降级、限流、路由三大能力。
### 服务降级
降级是在 Provider 不可用或响应过慢时,提供兜底方案避免级联故障。
**Mock 降级**是最常用的方式。在 Consumer 端配置 Mock 类,当调用失败时返回预设数据而非抛出异常:
```java
// Mock 类命名规则:接口名 + Mock
public class UserServiceMock implements UserService {
@Override
public User getUserById(Long id) {
return new User(id, "default_user");
}
}
// 配置方式
<dubbo:reference interface="com.example.UserService" mock="true"/>
```
也可以使用 `force:return` 强制返回指定值,不发起远程调用,用于手动降级。
### 服务限流
限流从 Provider 和 Consumer 两个维度控制流量:
Provider 端通过 `executes` 限制每个方法的并发执行数,超出拒绝请求。Consumer 端通过 `actives` 限制每个服务的并发调用数。Dubbo 3.x 还支持基于 QPS 的限流配置。
生产环境中,限流配置通常放在配置中心,根据监控数据动态调整。
### 服务路由
路由规则决定请求分发给哪些 Provider,是实现流量控制的核心机制:
**条件路由**是最基础的规则,支持按 IP、应用名、服务名等条件过滤。典型场景:将测试流量路由到灰度机器,线上流量路由到正式机器。
**标签路由**是 Dubbo 3.x 推荐的方式,通过给 Provider 打标签实现流量隔离。例如给灰度机器打上 `gray` 标签,Consumer 端指定 `gray` 标签即可将流量路由到灰度环境。
**脚本路由**支持通过 JavaScript 等脚本编写复杂路由逻辑,灵活性最高但维护成本大,生产环境慎用。
## 注册中心与服务发现
注册中心的选择直接影响服务发现的稳定性和功能:
**Zookeeper** 是最早支持且使用最广泛的实现,基于树形节点存储服务数据,支持临时节点自动清理下线服务。CAP 模型中偏向 CP,在 Leader 选举期间不可用。适合对一致性要求高的场景。
**Nacos** 是阿里推出的注册中心,同时支持 AP 和 CP 模式切换,内置配置中心功能,与 Spring Cloud 生态兼容。如果项目同时使用 Spring Cloud 和 Dubbo,Nacos 是统一注册中心的最佳选择。
服务发现流程:Provider 启动 → 向 Registry 注册 → Consumer 启动 → 从 Registry 订阅 → Registry 推送 Provider 列表 → Consumer 本地缓存并监听变更。关键点是 Consumer 会缓存 Provider 列表,Registry 推送变更时增量更新,即使 Registry 全部宕机,Consumer 仍能通过本地缓存调用 Provider。
## 配置中心的作用
Dubbo 3.x 将配置中心独立出来,与注册中心解耦。配置中心负责外部化配置管理、动态配置推送和配置版本管理,支持 Nacos、Zookeeper、Apollo 等实现。
动态配置是配置中心的核心价值。修改服务超时时间、负载均衡策略、权重等参数后,配置中心实时推送到所有节点,无需重启服务。这在处理线上问题时非常关键——某个服务响应变慢,可以立即调大超时时间而不是等待发布。
## 面试追问方向
**Q:Dubbo 和 Spring Cloud 如何选型?**
Dubbo 专注 RPC 通信和服务治理,性能优于 Spring Cloud 的 HTTP 通信;Spring Cloud 提供更完整的微服务解决方案(网关、配置、链路追踪等)。内部服务间调用选 Dubbo,需要完整微服务栈选 Spring Cloud,两者也可以通过 Nacos 共存。
**Q:Dubbo 3.x 相比 2.x 有哪些重大变化?**
Triple 协议替代 Dubbo 协议成为推荐协议;应用级服务发现替代接口级服务发现,减少注册中心压力;服务路由引入标签路由作为推荐方案。理解这些变化有助于理解 Dubbo 的演进方向。服务端5月27日 23:26
什么是服务注册与发现?注册中心如何选型?## 答案
服务注册与发现是微服务中解决"实例动态管理"的核心机制:服务注册是实例启动时将地址信息写入注册中心,服务发现是调用方从注册中心拉取可用实例列表。注册中心是两者的协调中介,同时负责健康检查和故障剔除。
主流注册中心按 CAP 模型分两派:
- **AP 派**:Eureka — 自我保护机制,网络分区时保留过期数据仍可查询,但可能拿到已下线实例。Spring Cloud 集成好,2.x 已停维。
- **CP 派**:Zookeeper(ZAB)、Etcd(Raft)、Consul(Raft) — 主节点宕机时拒绝写入直到选主完成,牺牲可用性换一致性。
- **混合派**:Nacos — 临时实例走 AP(Distro 协议),持久化实例走 CP(Raft 协议),是国内微服务首选。
## 追问一:Eureka 自我保护机制?
心跳续约比例低于阈值(默认85%)时进入自我保护:不再剔除过期实例。防止网络抖动导致误剔除,代价是可能调用到已死实例,需配合重试和熔断。
## 追问二:Nacos 如何实现 AP/CP 切换?
临时实例(ephemeral)用 Distro 协议(AP),节点平等写入并异步同步;持久化实例(persistent)用 Raft 协议(CP),写入需多数派确认。无状态服务用 AP,有状态服务用 CP。
## 追问三:客户端发现 vs 服务端发现?
- **客户端发现**:调用方自己拉取实例列表做负载均衡(Eureka/Nacos),少一跳但客户端逻辑重。
- **服务端发现**:请求先到代理(K8s Service/Nginx)再转发,客户端无感知但多一跳延迟。
## 选型速判
Spring Cloud Alibaba → Nacos;K8s → Etcd + CoreDNS;多数据中心 + 服务网格 → Consul;强一致性 → Zookeeper。Eureka 仅适合已有 Netflix 栈项目维护。
```yaml
# Nacos 临时实例配置(AP 模式)
spring:
cloud:
nacos:
discovery:
server-addr: localhost:8848
namespace: dev
# ephemeral: true # 默认 true,即 AP 模式
# Nacos 持久化实例配置(CP 模式)
spring:
cloud:
nacos:
discovery:
server-addr: localhost:8848
ephemeral: false # 切换为 CP 模式
```前端5月27日 23:25
RPC 调用中分布式事务怎么保证一致性?## 核心答案
RPC 调用跨服务操作数据,本地事务无法覆盖,必须用分布式事务方案保证一致性。核心思路只有两条路:**强一致性**(2PC/XA)或**最终一致性**(TCC/Saga/消息事务)。实际生产中,绝大多数场景选最终一致性。
## 为什么不用 2PC 解决一切?
2PC 通过协调者让所有参与者先准备再统一提交,理论上能保证强一致,但有两个致命问题:
- **同步阻塞**:准备阶段所有参与者锁住资源,高并发下性能崩溃
- **单点故障**:协调者挂了,参与者永远锁着等,整个系统卡死
3PC 加了超时机制和预提交阶段,减少了阻塞窗口,但网络分区时仍可能出现数据不一致,治标不治本。
## 生产中怎么选?
**高并发短事务 → TCC**
Try 预留资源、Confirm 确认执行、Cancel 回滚释放。性能好但代码侵入强,每个服务要写三个接口:
```java
public interface OrderTccService {
boolean tryCreateOrder(Order order); // 预扣库存
void confirmCreateOrder(Long orderId); // 确认下单
void cancelCreateOrder(Long orderId); // 释放库存
}
```
关键点:Confirm 和 Cancel 必须幂等,网络重试不能导致重复扣减。
**长流程多步骤 → Saga**
把长事务拆成多个本地事务串行执行,每步配一个补偿操作。某步失败则反向执行已完成步骤的补偿。适合业务流程长的场景(如订单→支付→物流),但要接受中间态的脏读。
**异步解耦 → 事务消息**
RocketMQ 半消息机制:先发半消息 → 执行本地事务 → 提交或回滚消息。本地事务和消息发送原子性保证,消费端幂等消费即可。适合"下单后异步扣积分"这类场景。
**快速落地 → Seata AT 模式**
一行注解搞定,对业务代码几乎无侵入:
```java
@GlobalTransactional(rollbackFor = Exception.class)
public void createOrder(Order order) {
inventoryService.deduct(order.getProductId(), order.getQuantity());
orderMapper.insert(order);
accountService.deduct(order.getUserId(), order.getAmount());
}
```
底层自动记录数据前后镜像,回滚时用镜像恢复。代价是性能比 TCC 低,适合一致性要求高但并发不极端的场景。
## 面试追问问什么?
- **幂等怎么设计?** 数据库唯一键 + 状态机,消费端去重表
- **Seata AT 性能瓶颈在哪?** 全局锁竞争,热点数据场景退化为串行
- **TCC 空回滚和悬挂怎么处理?** Try 未执行就收到 Cancel 是空回滚,Try 在 Cancel 之后才执行是悬挂,都要靠事务控制表判状态服务端5月27日 23:24
RPC 调用中的容错机制有哪些?## 核心容错策略有哪些?
RPC 调用面临网络抖动、服务宕机、过载等故障,容错机制围绕**快速失败**和**优雅降级**两个原则展开,主要包括超时、重试、熔断、限流、降级五种策略。
## 超时与重试:第一道防线
**超时**防止调用方无限阻塞。需区分连接超时和读取超时,根据 P99 延迟动态调整。Dubbo 的 timeout、gRPC 的 deadline 是典型实现。
**重试**应对临时性故障(网络抖动、GC 停顿),但必须满足两个前提:
- 接口幂等:重复调用不产生副作用,可通过唯一请求 ID 保证
- 退避策略:指数退避(1s → 2s → 4s)避免重试风暴
```java
// Dubbo 重试配置示例
@DubboReference(retries = 2, timeout = 3000)
private UserService userService;
```
> 面试追问:非幂等接口(如下单)如何重试?——不重试,改用异步确认或 TCC 补偿。
## 熔断:防止故障雪崩
当下游故障率超过阈值,熔断器**主动切断调用**,直接返回失败,避免大量请求堆积拖垮上游。
三个状态转换:
- **Closed** → 正常调用,统计失败率
- **Open** → 失败率超阈值,快速失败,不发起调用
- **Half-Open** → 经过恢复时间窗口后,放少量请求探测,成功则回 Closed,失败则回 Open
实现:Hystrix(已停更)、Resilience4j、Sentinel。
> 关键参数:失败率阈值(如 50%)、超时时间、恢复窗口。
## 限流与降级:保护与兜底
**限流**从入口控制流量,核心算法对比:
| 算法 | 特点 | 适用场景 |
|------|------|----------|
| 令牌桶 | 允许突发流量,匀速生成令牌 | 一般业务 |
| 漏桶 | 严格匀速输出 | 流量整形 |
| 滑动窗口 | 精确统计窗口内请求数 | 精确限流 |
**降级**在服务不可用时提供兜底方案:返回缓存数据、默认值或简化逻辑。与熔断的区别——熔断是**切断调用**,降级是**提供替代结果**,两者常配合使用。
## 如何组合使用?
生产环境通常多层组合:
1. 入口限流 → 防止流量冲击
2. 调用超时 + 重试 → 应对临时故障
3. 熔断 + 降级 → 防止级联失败
4. 隔离(线程池/信号量)→ 防止单服务耗尽资源
按业务重要性分级配置:核心链路(支付)用严格熔断+快速降级;非核心链路(推荐)可放宽重试、允许失败。服务端5月27日 23:24
什么是服务治理?RPC 框架如何实现服务治理?## 答案前置
服务治理是微服务架构中保障服务稳定、可控运行的一整套机制。RPC 框架中的服务治理核心包括:**服务注册与发现、负载均衡、容错降级、限流熔断、路由与灰度、监控与配置管理**六大能力。其本质是在远程调用链路上,加入"发现谁能调、怎么调更优、调失败了怎么办、流量怎么控"的决策逻辑。
## 服务注册与发现
服务提供者启动时将自身地址注册到注册中心(Nacos、ZooKeeper、Consul),消费者从注册中心订阅地址列表。核心要解决的是实例上下线的实时感知:通过心跳检测剔除不健康实例,通过推送或拉取机制更新地址列表。
```java
// Dubbo 注册配置
<dubbo:registry address="nacos://127.0.0.1:8848"/>
```
## 负载均衡与路由
拿到地址列表后,需要决定请求打到哪个实例。常见算法有随机、轮询、最少连接、一致性哈希。路由则在负载均衡之上增加条件过滤,比如灰度发布时按标签将流量导向新版本实例。
```java
<dubbo:reference loadbalance="consistenthash"/>
<dubbo:router rule="host = 10.0.0.* => provider.version = 2.0"/>
```
**追问:一致性哈希为什么适合有状态服务?** 因为同一 key 始终路由到同一节点,避免状态迁移。
## 容错、降级与熔断
这三者常被混淆,但解决的问题不同:
- **容错**:调用失败后的重试策略(Failover 重试其他实例、Failfast 快速失败)
- **降级**:服务不可用时返回兜底结果,保证主流程不中断
- **熔断**:当错误率超过阈值,直接切断调用,避免级联故障(雪崩)
熔断器有三个状态:Closed(正常)→ Open(熔断,快速失败)→ Half-Open(试探恢复)。
```java
// Sentinel 熔断降级
@SentinelResource(value = "getUser", fallback = "fallback",
blockHandler = "handleBlock")
public User getUser(Long id) { return userService.getUser(id); }
public User fallback(Long id) { return new User(id, "默认用户"); }
```
## 限流
保护服务不被突发流量压垮。令牌桶允许突发流量,漏桶强制匀速,滑动窗口统计更精准。实际选型看场景:对外网关用令牌桶,内部服务用滑动窗口。
```java
RateLimiter rateLimiter = RateLimiter.create(100); // 每秒100个令牌
if (rateLimiter.tryAcquire()) { /* 处理请求 */ }
```
## 监控与配置
监控是治理的眼睛:QPS、RT、错误率是核心指标,Prometheus + Grafana 是主流方案。配置中心(Nacos Config、Apollo)支持动态下发,无需重启即可调整超时、限流阈值等参数。
## 总结
服务治理的各能力不是孤立的:注册发现是基础,负载均衡和路由决定流量去向,限流熔断降级保障稳定性,监控配置让整个系统可观测可调整。面试中重点讲清楚三者区别(容错 vs 降级 vs 熔断)和选型思路,比罗列功能更有价值。服务端5月27日 23:23
如何实现 RPC 的异步调用?## 答案
RPC 异步调用的核心思路是:客户端发起调用后不阻塞等待响应,而是通过 Future、回调或响应式流等机制在结果就绪时获取。常见有三种模式:
1. **Future/Promise** — 调用立即返回 Future 对象,调用方自行决定何时获取结果。Dubbo 原生支持 `async=true`,通过 `RpcContext.getFuture()` 拿到返回值。
2. **Callback 回调** — 传入回调接口,服务端响应到达时自动触发。适合事件驱动场景,但多层嵌套易产生回调地狱。
3. **响应式流(Reactive)** — 基于 Reactor/RxJava,以 Mono/Observable 表示异步结果,支持背压和链式组合,适合流式处理。
此外,Java 8 的 **CompletableFuture** 兼具 Future 和回调的优点,支持 `thenCombine` 组合多个异步结果,是目前最常用的异步编排工具。gRPC 则通过 **StreamObserver** 实现异步,并原生支持双向流通信。
## 异步调用的优势
- **不阻塞调用线程**,单线程可同时处理多个请求,提高并发能力和吞吐量
- **并行调用多个服务**,用 `CompletableFuture.allOf` 或响应式 `zip` 组合结果,显著降低总延迟
- **避免资源浪费**,线程不必在 I/O 等待上空转
## 关键挑战
- **上下文传递**:异步线程切换时 TraceId、用户信息等上下文易丢失,需用 TransmittableThreadLocal 显式传递
- **超时控制**:必须设超时并取消,否则请求可能无限挂起。`future.get(timeout)` + `future.cancel(true)` 是基本模式
- **线程池管理**:异步任务不能无限制创建线程,需配置有界线程池并监控队列积压
## 追问
**Q: 同步调用和异步调用怎么选?**
调用方需要立即拿到结果才能继续(如下单扣库存),用同步;调用方不依赖结果或可以后续处理(如发通知、写日志),用异步。实际中大部分 RPC 调用用同步,并行调用多个服务时用异步组合。
**Q: CompletableFuture 和 Reactor 的区别是什么?**
CompletableFuture 处理单值异步结果,API 简单,适合多数业务场景;Reactor 基于响应式流规范,支持多值序列和背压,适合流式数据处理,但学习成本更高。如果只是组合几个 RPC 调用,CompletableFuture 足够。
**Q: 异步调用失败怎么重试?**
不要简单循环重试,应采用指数退避策略(如初始 100ms,每次翻倍,最多 3 次),配合熔断器在连续失败时快速失败。CompletableFuture 可用 `exceptionally` 或 `handle` 捕获异常后触发重试逻辑。服务端5月27日 23:23
RPC 调用如何保证安全性?认证、加密与授权怎么做?RPC 调用走的是网络,任何中间人都能截获、篡改甚至伪造请求,所以安全性不是可选项,而是必选项。核心要解决三个问题:你是谁(认证)、数据别被偷看(加密)、你能干什么(授权)。
## 身份认证:确认调用方身份
最常见的是 Token 认证,客户端每次请求携带 JWT 或 OAuth2 Token,服务端校验后放行。gRPC 中通常用拦截器统一拦截:
```java
public class AuthInterceptor implements ServerInterceptor {
@Override
public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
ServerCall<ReqT, RespT> call, Metadata headers,
ServerCallHandler<ReqT, RespT> next) {
String token = headers.get(Metadata.Key.of("authorization", Metadata.ASCII_STRING_MARSHALLER));
if (!validateToken(token)) {
call.close(Status.UNAUTHENTICATED.withDescription("Invalid token"), headers);
return new ServerCall.Listener<ReqT>() {};
}
return next.startCall(call, headers);
}
}
```
内部服务间调用更简单的做法是 API Key,给每个服务分配固定密钥,缺点是一旦泄露很难快速更换。
安全性要求高的场景用双向 TLS(mTLS):客户端和服务端互相验证证书,只有持有合法证书的才能通信,即使 Token 被盗也无法伪造连接。
## 数据加密:防止传输中被窃听和篡改
传输层加密是第一道防线。所有 RPC 框架都支持 TLS,gRPC 配置示例:
```java
NettyChannelBuilder.forAddress(host, port)
.sslContext(GrpcSslContexts.forClient()
.trustManager(new File("ca.pem"))
.build())
.build();
```
仅靠 TLS 不够,敏感字段还应做应用层加密(AES/RSA),这样即便 TLS 被中间人攻破,核心数据仍有保护。
完整性校验用 HMAC 或数字签名。发送方对请求体计算签名,接收方验证签名是否一致,能发现任何篡改行为。
## 授权:控制调用方能访问什么
认证通过后还要判断有没有权限。最实用的是 RBAC,给服务或用户分配角色,角色绑定权限集合。也可以做到方法级别的细粒度控制,比如只有 `admin` 角色才能调用 `DeleteUser`。
实际项目中通常用注解 + 拦截器的方式:
```java
@RequireRole("admin")
public void deleteUser(UserRequest req) { ... }
```
拦截器在调用前统一校验角色,业务代码无需关心权限逻辑。
## 防重放和限流
重放攻击是拿合法请求重复发送。解法是请求中加时间戳 + Nonce(一次性随机数),服务端校验时间窗口内的请求是否重复。再加上请求签名,把时间戳、Nonce 和参数一起签名,篡改任何一项都会验签失败。
限流用令牌桶算法,防止某个调用方吃满资源:
```java
RateLimiter rateLimiter = RateLimiter.create(100);
if (!rateLimiter.tryAcquire()) {
throw new RateLimitExceededException();
}
```
配合 IP 黑白名单,可疑来源直接拦截。
## 安全实践要点
- 最小权限:服务只开通必需的调用权限
- 密钥轮换:定期更换 Token、证书,用配置中心管理而非硬编码
- 审计日志:记录调用方、时间、参数,异常模式及时告警
- 框架差异:gRPC 原生支持 TLS + 拦截器;Dubbo 用 Filter 扩展;Thrift 用 TSSLTransport
**追问:mTLS 和普通 TLS 的区别?**
普通 TLS 只验证服务端证书,客户端不提供证书;mTLS 要求双方都提供证书互相验证,安全性更高但证书管理成本也更大,适合零信任网络架构。服务端5月27日 23:23
什么是 RPC?RPC 的基本原理和工作流程是什么?## RPC 是什么?
RPC(Remote Procedure Call)让一个进程像调用本地函数一样调用远端进程的函数,调用方无需关心底层网络细节。简单说,你写 result = addUser(user) 的时候,addUser 可能跑在另一台机器上,但你代码看起来和本地调用没区别。
## 一次 RPC 调用经历了什么?
以调用 userServer.getUser(id) 为例:
1. **客户端调用 Stub**:调用方发起调用,实际先到客户端存根(Stub),Stub 负责把方法名、参数打包
2. **序列化**:将参数对象转成二进制流(如 Protobuf、Hessian),这是"编组"过程
3. **网络传输**:二进制数据通过 TCP/HTTP2 发往服务端,gRPC 默认走 HTTP/2
4. **服务端反序列化**:Skeleton 接收数据,还原出方法名和参数
5. **执行本地方法**:服务端找到真实实现类执行,拿到返回值
6. **结果序列化回传**:返回值同样序列化后走网络回客户端
7. **客户端反序列化**:Stub 把二进制还原成结果对象,返回给调用方
整个过程对业务代码透明,网络通信、序列化、寻址全部由框架处理。
## 核心组件
- **Stub/Skeleton**:客户端和服务端的代理层,屏蔽网络细节
- **序列化协议**:Protobuf 体积最小性能最好,JSON 通用但空间开销大,Hessian 折中
- **传输协议**:TCP 直连延迟最低,HTTP/2 支持多路复用,gRPC 底层用 HTTP/2 + Protobuf
- **注册中心**:服务端注册地址,客户端从注册中心发现服务(Consul、Etcd、Nacos)
## 为什么不直接用 HTTP?
HTTP 1.1 是文本协议,头部冗余大,每次请求都要建连接;RPC 框架通常基于长连接 + 自定义二进制协议,序列化体积小、连接复用,吞吐量高出一个量级。当然 gRPC 基于 HTTP/2 也能兼顾性能和通用性,具体选型看场景。
## 常见追问
**RPC 和本地调用有什么区别?**
本地调用可靠且零延迟;RPC 存在网络抖动、超时、服务不可用等问题,需要重试、熔断、降级等容错机制。
**序列化怎么选?**
对性能敏感选 Protobuf,跨语言交互且可读性优先选 JSON,Java 体系内 Dubbo 默认 Hessian 也够用。
**服务挂了怎么办?**
注册中心心跳检测摘除异常节点,客户端侧配合熔断器(如 Sentinel)快速失败,避免级联雪崩。
服务端5月27日 23:23
主流 RPC 框架 gRPC、Dubbo、Thrift 该怎么选?## 核心结论
选 RPC 框架先定语言生态,再看服务治理需求,最后看性能瓶颈:Java 项目优先 Dubbo,跨语言项目优先 gRPC,遗留系统多协议兼容考虑 Thrift。
## 三个框架的本质区别
选型之前,先搞清楚它们在通信模型和序列化机制上的根本差异,这决定了它们的性能天花板和适用边界。
**通信协议不同:**
- gRPC 基于 HTTP/2,天然支持多路复用、流式通信、头部压缩
- Dubbo 默认走 TCP 长连接 + 自定义协议,单连接上传输更紧凑
- Thrift 支持多种传输方式(TSocket、THttp、TFramed),灵活但需要自己选
**序列化机制不同:**
- gRPC 用 Protobuf,强类型 + 二进制,序列化体积最小,但需要 .proto 文件编译生成代码
- Dubbo 默认 Hessian2,支持多序列化切换(Kryo、Protobuf 等),Java 原生兼容好
- Thrift 用自己的 IDL 生成代码,支持 Binary/Compact/JSON 多种格式
## 按场景选型
### Java 单栈微服务 → Dubbo
Dubbo 的核心优势不在性能,而在服务治理体系的完整度:内置注册中心(ZooKeeper/Nacos)、负载均衡策略(随机/轮询/一致性哈希)、熔断降级、服务分组和版本控制。Spring Cloud Alibaba 生态下,Dubbo 与 Nacos、Sentinel 的整合几乎是零成本。如果团队全是 Java 栈,选 Dubbo 省的是运维和治理的成本。
注意:Dubbo 3.x 已经支持 Triple 协议(基于 HTTP/2),可以和 gRPC 互通,跨语言能力在补齐。
### 跨语言微服务 → gRPC
gRPC 的跨语言是第一等公民:一个 .proto 文件生成 Go、Java、Python、C++ 等十几种语言的客户端和服务端代码,接口定义即契约。HTTP/2 带来的流式通信(客户端流、服务端流、双向流)是 Dubbo 和 Thrift 不原生支持的特性,适合实时数据推送、大文件分片传输等场景。
代价是调试不方便——二进制协议无法直接用 curl 打,需要 grpcurl 或 grpc-web 做代理;服务治理需要自己搭(配合 Consul/etcd 做服务发现,配合 Jaeger 做链路追踪)。
### 多协议兼容 / 遗留系统 → Thrift
Thrift 最大的价值是灵活性:传输层可选 TSocket/THttp/TFramed,协议层可选 TBinary/TCompact/TJSON,序列化格式可以混搭。对于既有 C++ 老系统又要对接 Java 新服务的场景,Thrift 的多协议支持比 gRPC 的单一 Protobuf 更容易兼容。但社区活跃度不如 gRPC 和 Dubbo,遇到问题排查成本高。
## 性能数据参考
同一环境下的 QPS 对比(小型请求体,单连接):
- gRPC (Protobuf):约 35k QPS
- Dubbo (Hessian2):约 28k QPS
- Thrift (TBinary):约 30k QPS
- Feign (JSON/HTTP1.1):约 12k QPS
差距在日常业务中感知不大,瓶颈通常在数据库和 IO 而非框架本身。不要为了 20% 的框架性能差异牺牲 50% 的开发效率。
## 面试追问方向
- Dubbo 的服务降级和熔断怎么实现?(Sentinel 集成 + mock 机制)
- gRPC 的流式通信在什么业务场景下不可替代?
- Protobuf 和 JSON 序列化的性能差距在什么量级?什么场景下 JSON 更合适?
- Dubbo 3.x 的 Triple 协议解决了什么问题?