在分布式系统架构中,RPC(Remote Procedure Call)框架是实现服务间通信的核心基础设施。从 Dubbo、gRPC 到 Spring Cloud OpenFeign,各类 RPC 框架虽实现各异,但其底层设计思想高度一致。本文将深入剖析 RPC 框架的核心机制——服务发现与负载均衡。
RPC 允许开发者像调用本地方法一样调用远程服务:
// 看起来像本地调用
User user = userService.getUserById(1001L);
// 实际上是跨越网络的远程调用
一个完整的 RPC 框架通常包含以下模块:
┌─────────────────────────────────────────────────────────────┐
│ RPC 调用流程 │
├─────────────────────────────────────────────────────────────┤
│ Client RPC Framework Registry Server │
│ │ │ │ │ │
│ │ 1. getService │ │ │ │
│ │──────────────────▶│ │ │ │
│ │ │ 2. lookup │ │ │
│ │ │───────────────────▶│ │ │
│ │ │ 3. return URLs │ │ │
│ │ │◀───────────────────│ │ │
│ │ 4. select by LB │ │ │ │
│ │◀──────────────────│ │ │ │
│ │ │ 5. invoke │ │ │
│ │ │────────────────────────────────▶│ │
│ │ │ 6. return result │ │
│ │◀────────────────────────────────────────────────────│ │
└─────────────────────────────────────────────────────────────┘
| 组件 | 职责 |
|---|---|
| Proxy | 生成客户端代理,屏蔽远程调用细节 |
| Codec | 序列化/反序列化,对象与字节流转换 |
| Transport | 网络传输层,管理连接与通信 |
| Registry | 服务注册与发现,维护服务地址列表 |
| LoadBalance | 负载均衡策略,选择最优服务节点 |
| Cluster | 集群容错,处理失败重试与熔断 |
在传统的单体架构中,服务地址通常是硬编码的配置:
# 传统配置方式
user-service:
host: 192.168.1.100
port: 8080
在微服务架构下,这种静态配置存在严重问题:
| 注册中心 | 一致性协议 | 健康检查 | 多数据中心 | 适合场景 |
|---|---|---|---|---|
| ZooKeeper | ZAB | 临时节点 | 弱支持 | 强一致性要求 |
| etcd | Raft | Lease 机制 | 支持 | Kubernetes 生态 |
| Consul | Raft | HTTP/TCP | 原生支持 | 云原生应用 |
| Nacos | Distro/AP | HTTP/心跳 | 支持 | 阿里生态 |
| Eureka | AP | 心跳 | 弱支持 | Spring Cloud |
Nacos 是当前国内使用最广泛的注册中心,其实现原理如下:
// 服务注册示例
NamingService naming = NamingFactory.createNamingService("localhost:8848");
// 注册服务实例
Instance instance = new Instance();
instance.setIp("192.168.1.100");
instance.setPort(8080);
instance.setServiceName("order-service");
instance.setMetadata(Map.of("version", "1.0", "region", "beijing"));
naming.registerInstance("order-service", instance);
Nacos 的核心数据结构:
// 服务实例信息
public class Instance {
private String instanceId; // 实例唯一标识
private String ip; // IP 地址
private int port; // 端口
private double weight; // 权重(负载均衡用)
private boolean healthy; // 健康状态
private Map<String, String> metadata; // 元数据(版本、区域等)
}
// 服务信息
public class Service {
private String name; // 服务名
private List<Instance> instances; // 实例列表
private Selector selector; // 路由选择器
}
// Dubbo 采用的客户端发现
public class ClientDiscovery {
private Registry registry;
private LoadBalancer loadBalancer;
public <T> T invoke(String serviceName, Method method, Object[] args) {
// 1. 从注册中心获取服务实例列表
List<Instance> instances = registry.lookup(serviceName);
// 2. 本地负载均衡选择实例
Instance selected = loadBalancer.select(instances);
// 3. 发起 RPC 调用
return doInvoke(selected, method, args);
}
}
优点:直接通信,无中间代理,性能高
缺点:客户端需要维护服务列表逻辑
┌─────────┐ ┌─────────────┐ ┌─────────────┐
│ Client │───▶│ API Gateway │───▶│ Server │
└─────────┘ │ (Service │ │ Instances │
│ Discovery)│ └─────────────┘
└─────────────┘
优点:客户端无感知,支持异构语言
缺点:增加网络跳转,网关成为单点
public class RandomLoadBalancer implements LoadBalancer {
private final Random random = new Random();
@Override
public Instance select(List<Instance> instances) {
int index = random.nextInt(instances.size());
return instances.get(index);
}
}
适用场景:实例性能相近、请求量均匀的简单场景
public class RoundRobinLoadBalancer implements LoadBalancer {
private final AtomicInteger counter = new AtomicInteger(0);
@Override
public Instance select(List<Instance> instances) {
int index = counter.getAndIncrement() % instances.size();
return instances.get(index);
}
}
变种:加权轮询(Weighted Round Robin),按权重比例分配
public class ConsistentHashLoadBalancer implements LoadBalancer {
private final TreeMap<Long, Instance> virtualNodes = new TreeMap<>();
private final int virtualNodeCount = 150; // 虚拟节点数
public ConsistentHashLoadBalancer(List<Instance> instances) {
// 构建哈希环
for (Instance instance : instances) {
for (int i = 0; i < virtualNodeCount; i++) {
long hash = hash(instance.getIp() + "#" + i);
virtualNodes.put(hash, instance);
}
}
}
@Override
public Instance select(List<Instance> instances, Invocation invocation) {
// 根据请求参数计算 hash(如 userId)
long hash = hash(invocation.getAttachment("userId"));
Map.Entry<Long, Instance> entry = virtualNodes.ceilingEntry(hash);
return entry != null ? entry.getValue() : virtualNodes.firstEntry().getValue();
}
}
优点:相同请求路由到同一节点,利于缓存命中
适用场景:有状态服务、缓存密集型应用
public class LeastActiveLoadBalancer implements LoadBalancer {
@Override
public Instance select(List<Instance> instances) {
Instance best = null;
int leastActive = Integer.MAX_VALUE;
for (Instance instance : instances) {
// 活跃数 = 当前正在处理的请求数
int active = RpcStatus.getStatus(instance).getActive();
if (active < leastActive) {
leastActive = active;
best = instance;
}
}
return best != null ? best : instances.get(0);
}
}
优点:智能感知实例负载,自动避让繁忙节点
适用场景:实例性能差异大、请求处理时长不均
public class ShortestResponseLoadBalancer implements LoadBalancer {
@Override
public Instance select(List<Instance> instances) {
Instance best = null;
long shortestResponse = Long.MAX_VALUE;
for (Instance instance : instances) {
// 统计平均响应时间
long avgResponse = RpcStatus.getStatus(instance).getAverageResponse();
if (avgResponse < shortestResponse) {
shortestResponse = avgResponse;
best = instance;
}
}
return best != null ? best : instances.get(0);
}
}
| 策略 | 实现类 | 特点 |
|---|---|---|
| Random | RandomLoadBalance | 默认策略,可配置权重 |
| RoundRobin | RoundRobinLoadBalance | 轮询,存在慢提供者累积问题 |
| LeastActive | LeastActiveLoadBalance | 活跃数+权重选择 |
| ConsistentHash | ConsistentHashLoadBalance | 相同参数总是到同一提供者 |
| ShortestResponse | ShortestResponseLoadBalance | 响应时间+活跃数+权重 |
以下是一个基于业务规则的自定义负载均衡器:
/**
* 灰度发布负载均衡器:根据用户 ID 灰度比例路由
*/
public class CanaryLoadBalancer implements LoadBalancer {
@Override
public <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) {
// 获取用户 ID
Long userId = Long.valueOf(invocation.getAttachment("userId", "0"));
// 分离新旧版本实例
List<Invoker<T>> stableInstances = new ArrayList<>();
List<Invoker<T>> canaryInstances = new ArrayList<>();
for (Invoker<T> invoker : invokers) {
String version = invoker.getUrl().getParameter("version", "stable");
if ("canary".equals(version)) {
canaryInstances.add(invoker);
} else {
stableInstances.add(invoker);
}
}
// 灰度策略:userId % 100 < 10 的用户路由到新版本(10% 灰度)
boolean routeToCanary = userId % 100 < 10 && !canaryInstances.isEmpty();
List<Invoker<T>> targetList = routeToCanary ? canaryInstances : stableInstances;
// 在目标列表中随机选择
int index = ThreadLocalRandom.current().nextInt(targetList.size());
return targetList.get(index);
}
}
配置使用:
# Dubbo 配置
dubbo:
consumer:
loadbalance: canary # 使用自定义负载均衡器
/**
* Failover:失败自动切换(默认策略)
* 当调用失败时,自动重试其他服务器
*/
public class FailoverClusterInvoker<T> extends AbstractClusterInvoker<T> {
@Override
public Result doInvoke(Invocation invocation, List<Invoker<T>> invokers) {
int retries = getUrl().getMethodParameter(invocation.getMethodName(),
Constants.RETRIES_KEY, 2) + 1;
for (int i = 0; i < retries; i++) {
Invoker<T> invoker = select(invokers, invocation);
try {
return invoker.invoke(invocation);
} catch (RpcException e) {
if (e.isBiz()) { // 业务异常不重试
throw e;
}
// 记录失败,继续重试
}
}
throw new RpcException("Failed after " + retries + " retries");
}
}
| 策略 | 说明 | 适用场景 |
|---|---|---|
| Failover | 失败重试其他节点 | 读操作、幂等操作 |
| Failfast | 快速失败,只调用一次 | 非幂等写操作 |
| Failsafe | 失败安全,忽略异常 | 日志记录等非核心调用 |
| Failback | 失败定时重发 | 消息通知等可延迟操作 |
| Forking | 并行调用多个节点 | 实时性要求高的读操作 |
使用 Sentinel 实现熔断:
// Sentinel 熔断规则
DegradeRule rule = new DegradeRule("orderService");
rule.setGrade(CircuitBreakerStrategy.ERROR_RATIO.getType());
rule.setCount(0.5); // 错误率阈值 50%
rule.setTimeWindow(30); // 熔断时长 30 秒
rule.setMinRequestAmount(10); // 最小请求数
DegradeRuleManager.loadRules(Collections.singletonList(rule));
// 业务代码
@SentinelResource(value = "orderService",
fallback = "orderFallback",
exceptionsToIgnore = BusinessException.class)
public Order getOrder(Long orderId) {
return orderServiceRpc.getOrder(orderId);
}
public Order orderFallback(Long orderId, Throwable ex) {
// 降级逻辑:返回缓存数据或默认值
return cache.get(orderId);
}
# Dubbo 连接配置
dubbo:
protocol:
name: dubbo
connections: 5 # 单服务提供者连接数
accepts: 1000 # 最大接受连接数
consumer:
connections: 5 # 消费端连接数
lazy: true # 延迟建立连接
| 协议 | 性能 | 体积 | 兼容性 | 适用场景 |
|---|---|---|---|---|
| Hessian2 | ★★★ | ★★★ | ★★★★★ | 跨语言,通用 |
| Protobuf | ★★★★★ | ★★★★★ | ★★★ | 高性能,需 IDL |
| Kryo | ★★★★★ | ★★★★ | ★★ | Java 内部通信 |
| Fastjson2 | ★★★ | ★★ | ★★★★ | JSON 可读性优先 |
RPC 框架是分布式系统的通信基石,其核心设计要点:
理解这些原理,不仅能更好地使用 Dubbo、gRPC 等框架,在排查分布式系统问题时也能更快定位问题根因。
参考文档:
本文首发于 Java 技术博客,转载请注明出处。