服务发现
在微服务架构中,服务实例的数量和位置是动态变化的。容器编排、自动扩缩容、故障恢复等场景都会导致服务实例的创建和销毁。传统的硬编码服务地址方式已经无法适应这种动态环境,服务发现机制应运而生。
为什么需要服务发现
传统方式的局限
在传统的单体应用或固定部署环境中,服务之间的调用地址通常是硬编码的:
// 硬编码服务地址
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);
}
}
这种方式在动态环境中存在严重问题:
地址变更困难:当支付服务迁移到新服务器时,需要修改订单服务的配置并重新部署。如果有多处引用,遗漏任何一个都会导致调用失败。
无法自动扩缩容:流量高峰时需要增加支付服务实例,但订单服务不知道新实例的地址,无法将请求分发到新实例。
故障恢复延迟:某个支付服务实例宕机后,订单服务仍然会向其发送请求,直到人工介入修改配置。
负载均衡困难:即使有多个实例,也需要手动配置负载均衡器,无法自动感知实例变化。
服务发现的解决方案
服务发现机制解决的核心问题是:让服务消费者能够动态地找到服务提供者的地址。具体来说,它需要回答两个问题:
- 服务提供者在哪里? 服务注册——服务启动时将自己的地址告知服务发现系统。
- 如何找到服务提供者? 服务发现——服务消费者向服务发现系统查询目标服务的地址。
服务发现系统需要具备以下能力:
服务注册:服务实例启动时,向注册中心注册自己的网络位置(IP、端口)。
服务注销:服务实例停止时,从注册中心移除自己的注册信息。
健康检查:定期检测服务实例是否存活,自动剔除不健康的实例。
服务查询:服务消费者能够根据服务名称查询可用的实例列表。
服务发现模式
服务发现主要有两种模式:客户端发现和服务端发现。两种模式各有优劣,适用于不同的场景。
客户端发现模式
在客户端发现模式中,服务消费者直接向服务注册中心查询服务实例列表,然后自行选择实例发起调用。
工作流程:
- 服务实例启动时,向注册中心注册自己的地址
- 服务实例停止时,向注册中心注销
- 消费者调用服务时,先从注册中心获取实例列表
- 消费者根据负载均衡策略选择一个实例
- 消费者直接向选中的实例发起调用
// 客户端发现模式示例
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());
}
}
客户端发现的优点:
- 无额外网络跳转:消费者直接调用服务实例,不需要经过中间代理,延迟更低
- 架构简单:不需要额外的负载均衡器,减少运维复杂度
- 客户端智能:可以实现复杂的负载均衡策略,如一致性哈希、加权轮询、区域优先等
- 故障隔离:某个消费者与实例之间的问题不会影响其他消费者
客户端发现的缺点:
- 语言绑定:服务发现逻辑需要在每种语言的客户端中实现,增加了开发成本
- 客户端复杂度:客户端需要实现服务发现、负载均衡、故障转移等逻辑
- 注册中心压力:每个消费者都需要查询注册中心,可能导致注册中心负载较高
服务端发现模式
在服务端发现模式中,服务消费者不直接查询注册中心,而是通过一个中间代理(通常是负载均衡器)来获取服务实例。代理负责查询注册中心并转发请求。
工作流程:
- 服务实例启动时,向注册中心注册自己的地址
- 负载均衡器从注册中心订阅服务实例列表
- 消费者向负载均衡器发送请求(通常使用服务名作为主机名)
- 负载均衡器根据负载均衡策略选择一个实例
- 负载均衡器将请求转发到选中的实例
// 服务端发现模式示例
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);
}
}
注册中心对比
| 特性 | Consul | etcd | ZooKeeper | Nacos | Eureka |
|---|---|---|---|---|---|
| 一致性模型 | CP | CP | CP | AP/CP | AP |
| 健康检查 | 多种方式 | Lease | Session | 多种方式 | 心跳 |
| 多数据中心 | 支持 | 不支持 | 不支持 | 支持 | 不支持 |
| 配置管理 | KV 存储 | 原生支持 | 不支持 | 支持 | 不支持 |
| Watch 机制 | 支持 | 支持 | 支持 | 支持 | 不支持 |
| 协议 | HTTP/gRPC | gRPC | 自定义 | HTTP/gRPC | HTTP |
| 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