微服务架构中,服务实例动态扩缩容,客户端无法硬编码地址。服务注册与发现机制让服务自动注册自己的地址,并让调用方自动发现可用实例。
1. 核心模型
服务注册:
Service A (Instance1: 10.0.1.1:8080) ──register──→ Registry
Service A (Instance2: 10.0.1.2:8080) ──register──→ Registry
服务发现:
Client ──query "Service A"──→ Registry ──返回 [10.0.1.1:8080, 10.0.1.2:8080]──→ Client
两种模式:
- 客户端发现:客户端直接查注册中心,自行选择实例(Eureka)
- 服务端发现:通过负载均衡器转发,LB 查注册中心(Consul + Fabio/Nginx)
2. 组件对比
| 特性 | Eureka | Consul | Nacos | etcd |
|---|---|---|---|---|
| 协议 | HTTP | HTTP/DNS | HTTP/gRPC | gRPC/HTTP |
| 一致性 | AP (Peer 复制) | CP (Raft) | AP/CP 可切换 | CP (Raft) |
| 健康检查 | 客户端心跳 | TCP/HTTP/脚本 | TCP/HTTP/MySQL | 租约 (Lease) |
| 多数据中心 | 需配置 | 原生支持 | 支持 | 无 |
| K8s 集成 | 一般 | 好 | 好 | 原生(CoreDNS) |
| 语言 | Java | Go | Java | Go |
| Spring Cloud | 原生 | 支持 | 原生 | 需适配 |
3. Nacos 实践
3.1 服务注册
# application.yml
spring:
application:
name: order-service
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
namespace: dev
group: DEFAULT_GROUP
metadata:
version: v1
region: cn-beijing
@SpringBootApplication
@EnableDiscoveryClient
public class OrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}
}
3.2 服务发现与调用
@Service
public class OrderService {
@Autowired
private LoadBalancerClient loadBalancer;
@Autowired
private RestTemplate restTemplate;
public User getUser(Long userId) {
// 方式1:使用 LoadBalancerClient
ServiceInstance instance = loadBalancer.choose("user-service");
String url = instance.getUri() + "/users/" + userId;
return restTemplate.getForObject(url, User.class);
// 方式2:使用 @LoadBalanced RestTemplate
return restTemplate.getForObject(
"http://user-service/users/{id}", User.class, userId);
}
}
3.3 配置中心
# bootstrap.yml
spring:
cloud:
nacos:
config:
server-addr: 127.0.0.1:8848
file-extension: yaml
group: DEFAULT_GROUP
namespace: dev
@RestController
@RefreshScope // 配置变更自动刷新
public class ConfigController {
@Value("${app.timeout:3000}")
private int timeout;
}
4. Consul 实践
4.1 服务注册
{
"service": {
"name": "web-api",
"tags": ["v1", "primary"],
"port": 8080,
"check": {
"http": "http://localhost:8080/health",
"interval": "10s",
"timeout": "5s"
}
}
}
4.2 DNS 方式发现
# Consul DNS 查询
dig @127.0.0.1 -p 8600 web-api.service.consul
# SRV 记录(含端口)
dig @127.0.0.1 -p 8600 web-api.service.consul SRV
5. etcd 服务发现
import etcd3
client = etcd3.Client(host='localhost', port=2379)
# 注册服务(带租约,自动过期)
lease = client.lease(ttl=10)
client.put('/services/user-service/10.0.1.1:8080',
json.dumps({'host': '10.0.1.1', 'port': 8080}),
lease=lease)
# 续约(心跳)
lease.refresh()
# 发现服务
instances = client.get_prefix('/services/user-service/')
for value, metadata in instances:
info = json.loads(value.decode())
print(f"Found: {info['host']}:{info['port']}")
# Watch 变更
watch_iter, cancel = client.watch_prefix('/services/user-service/')
for event in watch_iter:
print(f"Event: {event}")
6. 健康检查策略
| 类型 | 方式 | 优点 | 缺点 |
|---|---|---|---|
| 客户端心跳 | 服务主动发送心跳 | 简单 | 假死时仍发心跳 |
| 服务端探测 | 注册中心主动探测 | 可靠 | 增加注册中心负载 |
| 双向心跳 | 双向检测 | 最可靠 | 复杂度高 |
// Spring Boot Actuator 健康检查
@Component
public class DatabaseHealthIndicator implements HealthIndicator {
@Override
public Health health() {
if (database.isConnected()) {
return Health.up().build();
}
return Health.down().withDetail("error", "DB connection failed").build();
}
}
总结
- Spring Cloud 生态:首选 Nacos(注册+配置一体)
- 多语言/跨平台:Consul(DNS 支持 + 多数据中心)
- K8s 原生:etcd + CoreDNS
- 遗留系统:Eureka(稳定但已停更,建议迁移)
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。