跳到主要内容

服务发现

在微服务架构中,服务实例的数量和位置是动态变化的。容器编排、自动扩缩容、故障恢复等场景都会导致服务实例的创建和销毁。传统的硬编码服务地址方式已经无法适应这种动态环境,服务发现机制应运而生。

为什么需要服务发现

传统方式的局限

在传统的单体应用或固定部署环境中,服务之间的调用地址通常是硬编码的:

// 硬编码服务地址
public class OrderService {
private static final String PAYMENT_SERVICE_URL = "http://192.168.1.100:8080";

public void processPayment(Order order) {
// 直接调用固定地址
http.post(PAYMENT_SERVICE_URL + "/pay", order);
}
}

这种方式在动态环境中存在严重问题:

地址变更困难:当支付服务迁移到新服务器时,需要修改订单服务的配置并重新部署。如果有多处引用,遗漏任何一个都会导致调用失败。

无法自动扩缩容:流量高峰时需要增加支付服务实例,但订单服务不知道新实例的地址,无法将请求分发到新实例。

故障恢复延迟:某个支付服务实例宕机后,订单服务仍然会向其发送请求,直到人工介入修改配置。

负载均衡困难:即使有多个实例,也需要手动配置负载均衡器,无法自动感知实例变化。

服务发现的解决方案

服务发现机制解决的核心问题是:让服务消费者能够动态地找到服务提供者的地址。具体来说,它需要回答两个问题:

  1. 服务提供者在哪里? 服务注册——服务启动时将自己的地址告知服务发现系统。
  2. 如何找到服务提供者? 服务发现——服务消费者向服务发现系统查询目标服务的地址。

服务发现系统需要具备以下能力:

服务注册:服务实例启动时,向注册中心注册自己的网络位置(IP、端口)。

服务注销:服务实例停止时,从注册中心移除自己的注册信息。

健康检查:定期检测服务实例是否存活,自动剔除不健康的实例。

服务查询:服务消费者能够根据服务名称查询可用的实例列表。

服务发现模式

服务发现主要有两种模式:客户端发现和服务端发现。两种模式各有优劣,适用于不同的场景。

客户端发现模式

在客户端发现模式中,服务消费者直接向服务注册中心查询服务实例列表,然后自行选择实例发起调用。

工作流程

  1. 服务实例启动时,向注册中心注册自己的地址
  2. 服务实例停止时,向注册中心注销
  3. 消费者调用服务时,先从注册中心获取实例列表
  4. 消费者根据负载均衡策略选择一个实例
  5. 消费者直接向选中的实例发起调用
// 客户端发现模式示例
public class ClientSideDiscovery {

private final ServiceRegistry registry;
private final LoadBalancer loadBalancer;
private final HttpClient httpClient;

public Response callService(String serviceName, Request request) {
// 1. 从注册中心获取服务实例列表
List<ServiceInstance> instances = registry.getInstances(serviceName);

if (instances.isEmpty()) {
throw new ServiceUnavailableException(serviceName);
}

// 2. 负载均衡选择实例
ServiceInstance instance = loadBalancer.select(instances);

// 3. 直接调用选中的实例
String url = String.format("http://%s:%d%s",
instance.getHost(),
instance.getPort(),
request.getPath());

return httpClient.post(url, request.getBody());
}
}

客户端发现的优点

  • 无额外网络跳转:消费者直接调用服务实例,不需要经过中间代理,延迟更低
  • 架构简单:不需要额外的负载均衡器,减少运维复杂度
  • 客户端智能:可以实现复杂的负载均衡策略,如一致性哈希、加权轮询、区域优先等
  • 故障隔离:某个消费者与实例之间的问题不会影响其他消费者

客户端发现的缺点

  • 语言绑定:服务发现逻辑需要在每种语言的客户端中实现,增加了开发成本
  • 客户端复杂度:客户端需要实现服务发现、负载均衡、故障转移等逻辑
  • 注册中心压力:每个消费者都需要查询注册中心,可能导致注册中心负载较高

服务端发现模式

在服务端发现模式中,服务消费者不直接查询注册中心,而是通过一个中间代理(通常是负载均衡器)来获取服务实例。代理负责查询注册中心并转发请求。

工作流程

  1. 服务实例启动时,向注册中心注册自己的地址
  2. 负载均衡器从注册中心订阅服务实例列表
  3. 消费者向负载均衡器发送请求(通常使用服务名作为主机名)
  4. 负载均衡器根据负载均衡策略选择一个实例
  5. 负载均衡器将请求转发到选中的实例
// 服务端发现模式示例
public class ServerSideDiscovery {

private final HttpClient httpClient;

public Response callService(String serviceName, Request request) {
// 直接调用负载均衡器(使用服务名作为主机名)
String url = String.format("http://%s%s",
serviceName, // 如 payment-service
request.getPath());

return httpClient.post(url, request.getBody());
}
}

// 负载均衡器配置示例(如 Nginx)
// upstream payment-service {
// server 192.168.1.100:8080;
// server 192.168.1.101:8080;
// server 192.168.1.102:8080;
// }

服务端发现的优点

  • 客户端简单:客户端只需要知道负载均衡器的地址,不需要实现服务发现逻辑
  • 语言无关:任何语言的客户端都可以使用,不需要为每种语言开发客户端库
  • 集中管理:负载均衡策略、安全策略等可以在一个地方统一配置
  • 注册中心压力小:只有负载均衡器需要查询注册中心

服务端发现的缺点

  • 额外跳转:请求需要经过负载均衡器,增加了一跳的网络延迟
  • 单点风险:负载均衡器成为系统关键节点,需要保证高可用
  • 运维复杂:需要部署和维护额外的负载均衡器
  • 扩展性受限:负载均衡器可能成为性能瓶颈

模式对比与选择

特性客户端发现服务端发现
网络延迟低(直连)高(经过代理)
客户端复杂度
语言绑定
运维复杂度
故障隔离一般
扩展性受限于代理
适用场景微服务内部调用跨网调用、遗留系统

选择建议

  • 内部微服务调用:优先选择客户端发现模式,延迟低、扩展性好
  • 跨网调用:使用服务端发现模式,便于安全控制、流量管理
  • 遗留系统改造:使用服务端发现模式,最小化代码改动
  • Kubernetes 环境:使用内置的服务发现(Service 资源)

服务注册中心

服务注册中心是服务发现的核心组件,负责存储和管理服务实例信息。主流的服务注册中心包括 Consul、etcd、ZooKeeper、Eureka、Nacos 等。

服务注册中心的核心功能

服务注册:服务实例启动时,将自己的信息(服务名、IP、端口、元数据)写入注册中心。

// 服务注册示例
public class ServiceRegistration {

private final ServiceRegistry registry;

public void register(String serviceName, String host, int port) {
ServiceInstance instance = ServiceInstance.builder()
.serviceName(serviceName)
.host(host)
.port(port)
.metadata(Map.of(
"version", "1.0.0",
"zone", "us-east-1"
))
.build();

registry.register(instance);
}
}

服务注销:服务实例停止时,从注册中心移除自己的信息。可以是主动注销或被动超时移除。

// 服务注销示例
public void unregister(String serviceName, String instanceId) {
registry.deregister(serviceName, instanceId);
}

健康检查:定期检测服务实例是否存活。健康检查方式包括:

  • 主动探测:注册中心主动向服务实例发送心跳或 TCP 探测
  • 被动心跳:服务实例主动向注册中心发送心跳
  • 自定义检查:服务实例暴露健康检查接口,注册中心调用检查
// 健康检查示例
public class HealthChecker {

private final ServiceRegistry registry;
private final ScheduledExecutorService scheduler;

public void startHeartbeat(String instanceId) {
scheduler.scheduleAtFixedRate(() -> {
try {
registry.sendHeartbeat(instanceId);
} catch (Exception e) {
log.error("心跳发送失败", e);
// 尝试重新注册
}
}, 0, 5, TimeUnit.SECONDS);
}
}

服务查询:消费者根据服务名称查询可用的实例列表。

// 服务查询示例
public List<ServiceInstance> discover(String serviceName) {
return registry.getInstances(serviceName);
}

Consul

Consul 是 HashiCorp 开源的服务发现和配置管理工具,功能全面,企业应用广泛。

核心特性

  • 服务发现:支持客户端和服务端两种发现模式
  • 健康检查:支持 TCP、HTTP、Script、TTL 等多种检查方式
  • KV 存储:提供分布式键值存储,可用于配置管理
  • 多数据中心:原生支持多数据中心部署
  • 服务网格:集成 Connect 功能,提供服务间安全通信

架构组成

Agent 模式

  • Client Agent:轻量级代理,负责服务注册、健康检查、DNS 转发
  • Server Agent:完整功能,参与共识选举、存储数据
// Consul 服务注册示例
public class ConsulRegistration {

private final ConsulClient consul;

public void registerService() {
NewService service = new NewService();
service.setName("payment-service");
service.setId("payment-service-1");
service.setAddress("192.168.1.100");
service.setPort(8080);

// 健康检查配置
NewService.Check check = new NewService.Check();
check.setHttp("http://192.168.1.100:8080/health");
check.setInterval("10s");
check.setTimeout("5s");
service.setCheck(check);

consul.agentServiceRegister(service);
}

public void deregisterService() {
consul.agentServiceDeregister("payment-service-1");
}
}

// Consul 服务发现示例
public class ConsulDiscovery {

private final ConsulClient consul;

public List<ServiceInstance> discover(String serviceName) {
Response<List<HealthService>> response = consul.getHealthServices(
serviceName,
true, // 只返回健康的实例
QueryParams.DEFAULT
);

return response.getValue().stream()
.map(health -> {
HealthService.Service service = health.getService();
return new ServiceInstance(
service.getService(),
service.getAddress(),
service.getPort()
);
})
.collect(Collectors.toList());
}
}

Consul DNS 接口

Consul 提供 DNS 接口,可以通过域名直接访问服务:

# 查询服务
dig @localhost -p 8600 payment-service.service.consul

# 返回服务地址
payment-service.service.consul. 0 IN A 192.168.1.100
payment-service.service.consul. 0 IN A 192.168.1.101

etcd

etcd 是 CoreOS 开源的分布式键值存储,使用 Raft 协议保证一致性。Kubernetes 默认使用 etcd 作为数据存储。

核心特性

  • 强一致性:基于 Raft 协议,保证线性一致性
  • Watch 机制:支持监听键变化,实时感知服务变化
  • Lease 机制:支持租约,自动过期删除,适合服务注册
  • 事务支持:支持原子性事务操作
// etcd 服务注册示例
public class EtcdRegistration {

private final Client client;
private final Lease leaseClient;
private final KV kvClient;

public void registerService(String serviceName, String address, int port) {
long leaseId = leaseClient.grant(30).get().getID();

String key = "/services/" + serviceName + "/" + address + ":" + port;
String value = address + ":" + port;

// 注册服务(关联租约)
kvClient.put(
ByteSequence.from(key, StandardCharsets.UTF_8),
ByteSequence.from(value, StandardCharsets.UTF_8),
PutOption.builder().withLease(leaseId).build()
).get();

// 保持租约(心跳)
leaseClient.keepAlive(leaseId, StreamObserver.of(
response -> log.debug("租约续期: {}", response.getID()),
error -> log.error("租约续期失败", error)
));
}

public void discoverService(String serviceName) {
String prefix = "/services/" + serviceName + "/";

// 查询服务实例
GetResponse response = kvClient.get(
ByteSequence.from(prefix, StandardCharsets.UTF_8),
GetOption.builder().withPrefix(true).build()
).get();

// 监听服务变化
Watch.Watcher watcher = client.getWatchClient().watch(
ByteSequence.from(prefix, StandardCharsets.UTF_8),
WatchOption.builder().withPrefix(true).build()
);
}
}

Nacos

Nacos 是阿里巴巴开源的服务发现和配置管理平台,在 Spring Cloud 生态中广泛使用。

核心特性

  • 服务发现:支持 DNS 和 RPC 两种服务发现模式
  • 配置管理:动态配置管理,支持配置热更新
  • 动态 DNS:支持权重路由,方便流量管理
  • 多语言支持:提供 Java、Go、Node.js 等客户端
// Nacos 服务注册示例
public class NacosRegistration {

private final NamingService namingService;

public void register(String serviceName, String ip, int port) throws NacosException {
namingService.registerInstance(
serviceName,
ip,
port,
"DEFAULT_GROUP" // 分组
);
}

public void deregister(String serviceName, String ip, int port) throws NacosException {
namingService.deregisterInstance(serviceName, ip, port);
}
}

// Nacos 服务发现示例
public class NacosDiscovery {

private final NamingService namingService;

public List<ServiceInstance> discover(String serviceName) throws NacosException {
List<Instance> instances = namingService.selectInstances(
serviceName,
true // 只返回健康的实例
);

return instances.stream()
.map(instance -> new ServiceInstance(
serviceName,
instance.getIp(),
instance.getPort(),
instance.getMetadata()
))
.collect(Collectors.toList());
}

// 订阅服务变化
public void subscribe(String serviceName, EventListener listener) throws NacosException {
namingService.subscribe(serviceName, listener);
}
}

注册中心对比

特性ConsuletcdZooKeeperNacosEureka
一致性模型CPCPCPAP/CPAP
健康检查多种方式LeaseSession多种方式心跳
多数据中心支持不支持不支持支持不支持
配置管理KV 存储原生支持不支持支持不支持
Watch 机制支持支持支持支持不支持
协议HTTP/gRPCgRPC自定义HTTP/gRPCHTTP
Spring Cloud 集成支持需适配需适配原生支持原生支持
运维复杂度

选择建议

  • Kubernetes 环境:使用 etcd(Kubernetes 内置)
  • Spring Cloud 生态:优先选择 Nacos,功能全面,易于使用
  • 多数据中心场景:选择 Consul,原生支持多数据中心
  • 对一致性要求高:选择 etcd 或 ZooKeeper
  • 对可用性要求高:选择 Nacos(AP 模式)或 Eureka

服务注册与注销

服务注册时机

服务实例应该在什么时候注册到注册中心?常见的策略有两种:

启动后注册:服务启动完成后注册,确保服务已经准备好接收请求。

// 启动后注册
@SpringBootApplication
public class Application {

public static void main(String[] args) {
SpringApplication.run(Application.class, args);

// 应用启动完成后注册
ServiceRegistry registry = new ServiceRegistry();
registry.register("payment-service", getLocalIp(), 8080);
}
}

延迟注册:服务启动后延迟一段时间再注册,等待预热完成(如缓存加载、连接池预热)。

// 延迟注册
@EventListener(ApplicationReadyEvent.class)
public void onApplicationReady() {
// 等待预热完成
ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
scheduler.schedule(() -> {
registry.register("payment-service", getLocalIp(), 8080);
}, 30, TimeUnit.SECONDS); // 延迟 30 秒
}

服务注销策略

服务实例停止时,需要从注册中心注销,避免消费者调用到已停止的实例。

主动注销:服务停止前主动调用注销接口。

// 主动注销
@PreDestroy
public void onShutdown() {
registry.deregister("payment-service", instanceId);
log.info("服务注销完成");
}

被动超时:依赖健康检查超时自动移除。当服务实例意外停止(如崩溃、断电)时,主动注销无法执行,只能依赖注册中心的超时机制。

// 注册时设置 TTL(etcd Lease 示例)
long leaseId = leaseClient.grant(30).get().getID(); // 30秒 TTL

// 服务正常运行时持续续约
leaseClient.keepAlive(leaseId, ...);

// 如果服务崩溃,续约停止,30秒后租约过期,服务自动注销

优雅停机:结合主动注销和被动超时,确保服务实例安全下线。

// 优雅停机
public class GracefulShutdown {

private final ServiceRegistry registry;
private volatile boolean shuttingDown = false;

public void shutdown() {
shuttingDown = true;

// 1. 从注册中心注销,不再接收新请求
registry.deregister("payment-service", instanceId);

// 2. 等待正在处理的请求完成
awaitPendingRequests();

// 3. 关闭服务
stopServer();
}

// 请求处理时检查
public Response handleRequest(Request request) {
if (shuttingDown) {
throw new ServiceShuttingDownException();
}
// 正常处理请求
return process(request);
}
}

负载均衡

服务发现返回的是多个实例的列表,消费者需要选择其中一个实例发起调用。负载均衡算法决定了如何选择实例,对服务的性能和稳定性有重要影响。

常见负载均衡算法

轮询(Round Robin):按顺序依次选择实例,简单公平。

public class RoundRobinLoadBalancer {

private final AtomicInteger index = new AtomicInteger(0);

public ServiceInstance select(List<ServiceInstance> instances) {
if (instances.isEmpty()) {
throw new NoInstanceAvailableException();
}

int idx = Math.abs(index.getAndIncrement() % instances.size());
return instances.get(idx);
}
}

加权轮询(Weighted Round Robin):根据实例的权重分配流量,权重高的实例获得更多请求。

public class WeightedRoundRobinLoadBalancer {

private final AtomicInteger currentIndex = new AtomicInteger(0);
private final Map<String, WeightedInstance> weightedInstances = new ConcurrentHashMap<>();

public ServiceInstance select(List<ServiceInstance> instances) {
// 计算总权重
int totalWeight = instances.stream()
.mapToInt(ServiceInstance::getWeight)
.sum();

if (totalWeight <= 0) {
return new RoundRobinLoadBalancer().select(instances);
}

// 加权轮询
int currentWeight = currentIndex.getAndIncrement() % totalWeight;

for (ServiceInstance instance : instances) {
currentWeight -= instance.getWeight();
if (currentWeight < 0) {
return instance;
}
}

return instances.get(0);
}
}

随机(Random):随机选择实例,适用于实例性能相近的场景。

public class RandomLoadBalancer {

private final Random random = new Random();

public ServiceInstance select(List<ServiceInstance> instances) {
if (instances.isEmpty()) {
throw new NoInstanceAvailableException();
}

int idx = random.nextInt(instances.size());
return instances.get(idx);
}
}

最少连接(Least Connections):选择当前连接数最少的实例,适用于请求处理时间差异大的场景。

public class LeastConnectionsLoadBalancer {

private final Map<String, AtomicInteger> connectionCounts = new ConcurrentHashMap<>();

public ServiceInstance select(List<ServiceInstance> instances) {
return instances.stream()
.min(Comparator.comparingInt(this::getConnectionCount))
.orElseThrow(NoInstanceAvailableException::new);
}

private int getConnectionCount(ServiceInstance instance) {
return connectionCounts
.computeIfAbsent(instance.getId(), id -> new AtomicInteger(0))
.get();
}

public void incrementConnection(ServiceInstance instance) {
connectionCounts.get(instance.getId()).incrementAndGet();
}

public void decrementConnection(ServiceInstance instance) {
connectionCounts.get(instance.getId()).decrementAndGet();
}
}

一致性哈希(Consistent Hash):根据请求的某个特征(如用户ID)计算哈希值,选择对应的实例。同一用户的请求总是路由到同一实例,适合有状态服务或缓存场景。

public class ConsistentHashLoadBalancer {

private final TreeMap<Integer, ServiceInstance> circle = new TreeMap<>();
private final int virtualNodes = 150;

public ConsistentHashLoadBalancer(List<ServiceInstance> instances) {
for (ServiceInstance instance : instances) {
for (int i = 0; i < virtualNodes; i++) {
String virtualNode = instance.getId() + "#" + i;
int hash = hash(virtualNode);
circle.put(hash, instance);
}
}
}

public ServiceInstance select(String key) {
if (circle.isEmpty()) {
throw new NoInstanceAvailableException();
}

int hash = hash(key);

// 找到第一个大于等于 hash 的节点
Map.Entry<Integer, ServiceInstance> entry = circle.ceilingEntry(hash);

if (entry == null) {
// 环回到开头
entry = circle.firstEntry();
}

return entry.getValue();
}

private int hash(String key) {
return Math.abs(key.hashCode());
}
}

区域优先(Zone Aware):优先选择同一区域(或机房)的实例,减少跨区域网络延迟。

public class ZoneAwareLoadBalancer {

private final String currentZone;
private final LoadBalancer fallbackLoadBalancer;

public ServiceInstance select(List<ServiceInstance> instances) {
// 优先选择同区域的实例
List<ServiceInstance> sameZoneInstances = instances.stream()
.filter(instance -> currentZone.equals(instance.getZone()))
.collect(Collectors.toList());

if (!sameZoneInstances.isEmpty()) {
return fallbackLoadBalancer.select(sameZoneInstances);
}

// 同区域无实例,选择其他区域
return fallbackLoadBalancer.select(instances);
}
}

负载均衡算法选择

算法适用场景优点缺点
轮询实例性能相近简单公平不考虑实例差异
加权轮询实例性能不同按能力分配需要配置权重
随机实例性能相近简单分布可能不均匀
最少连接请求处理时间差异大动态均衡需要维护连接数
一致性哈希有状态服务、缓存状态局部性实例变化时重新分布
区域优先多地域部署减少延迟需要区域感知

服务发现最佳实践

健康检查策略

健康检查类型

  • 存活检查:检测进程是否存活(TCP 连接、进程存在)
  • 就绪检查:检测服务是否准备好接收请求(依赖检查、缓存预热)
// 健康检查端点
@RestController
public class HealthController {

@GetMapping("/health/live")
public ResponseEntity<Void> liveness() {
// 存活检查:进程存活即可
return ResponseEntity.ok().build();
}

@GetMapping("/health/ready")
public ResponseEntity<Void> readiness() {
// 就绪检查:检查依赖、缓存等
if (isReady()) {
return ResponseEntity.ok().build();
} else {
return ResponseEntity.status(503).build();
}
}

private boolean isReady() {
return databaseConnected && cacheWarmed && dependenciesAvailable;
}
}

健康检查频率

  • 检查间隔过短:增加系统负载
  • 检查间隔过长:故障发现延迟

推荐值:

  • 心跳间隔:5-10 秒
  • 超时时间:10-20 秒
  • 故障阈值:连续 3 次失败

服务元数据

利用服务元数据传递额外信息,支持更灵活的路由策略:

// 注册时添加元数据
ServiceInstance instance = ServiceInstance.builder()
.serviceName("payment-service")
.host("192.168.1.100")
.port(8080)
.metadata(Map.of(
"version", "2.0.0", // 服务版本
"zone", "us-east-1a", // 可用区
"weight", "100", // 权重
"canary", "true" // 是否金丝雀实例
))
.build();

// 根据元数据路由
public ServiceInstance selectByVersion(List<ServiceInstance> instances, String version) {
return instances.stream()
.filter(instance -> version.equals(instance.getMetadata().get("version")))
.findFirst()
.orElse(randomSelect(instances));
}

缓存策略

消费者应缓存服务实例列表,避免每次调用都查询注册中心:

public class CachedServiceDiscovery {

private final ServiceRegistry registry;
private final LoadingCache<String, List<ServiceInstance>> cache;

public CachedServiceDiscovery(ServiceRegistry registry) {
this.registry = registry;

this.cache = Caffeine.newBuilder()
.expireAfterWrite(30, TimeUnit.SECONDS) // 缓存 30 秒
.refreshAfterWrite(10, TimeUnit.SECONDS) // 10 秒后异步刷新
.build(this::discoverFromRegistry);
}

public List<ServiceInstance> getInstances(String serviceName) {
return cache.get(serviceName);
}

private List<ServiceInstance> discoverFromRegistry(String serviceName) {
return registry.getInstances(serviceName);
}
}

故障容错

当服务发现失败或无可用实例时,应有合理的降级策略:

public class ResilientServiceDiscovery {

private final ServiceRegistry registry;
private final List<ServiceInstance> lastKnownInstances = new CopyOnWriteArrayList<>();

public List<ServiceInstance> getInstances(String serviceName) {
try {
List<ServiceInstance> instances = registry.getInstances(serviceName);

if (!instances.isEmpty()) {
lastKnownInstances.clear();
lastKnownInstances.addAll(instances);
return instances;
}
} catch (Exception e) {
log.warn("服务发现失败,使用缓存实例", e);
}

// 降级:使用最后已知的实例列表
if (!lastKnownInstances.isEmpty()) {
return new ArrayList<>(lastKnownInstances);
}

throw new NoInstanceAvailableException(serviceName);
}
}

小结

本章我们深入学习了服务发现的核心知识:

服务发现的作用:解决动态环境中服务地址的动态发现问题,支持自动扩缩容和故障恢复。

服务发现模式:客户端发现模式延迟低、扩展性好;服务端发现模式客户端简单、语言无关。根据场景选择合适的模式。

服务注册中心:Consul 功能全面、支持多数据中心;etcd 强一致性、Kubernetes 内置;Nacos Spring Cloud 集成好、易于使用。

服务注册与注销:启动后注册、延迟注册、主动注销、被动超时、优雅停机等策略确保服务实例的安全上下线。

负载均衡:轮询、加权轮询、随机、最少连接、一致性哈希、区域优先等算法适用于不同场景。

最佳实践:合理的健康检查策略、丰富的服务元数据、适当的缓存策略、完善的故障容错。

服务发现是微服务架构的基础设施,理解其原理和实现对于构建可靠的分布式系统至关重要。

如果你想深入学习服务发现,推荐阅读:

  • Consul 官方文档:developer.hashicorp.com/consul/docs
  • etcd 官方文档:etcd.io/docs
  • Nacos 官方文档:nacos.io/zh-cn/docs/what-is-nacos.html