Consul 服务注册与发现
导学
白歌成功用 Eureka 搭建了服务注册中心,但产品经理孔蓝提出了新需求:"公司还有一部分物流系统是 Go 语言写的,Python 写的爬虫服务也需要注册进来,Eureka 能搞定吗?"
白歌查了一下:Eureka 的客户端仅支持 Java(虽然有 REST API,但 Go/Python 团队需要自己实现注册逻辑)。CTO 大翔拍板:"用 Consul,原生多语言支持,还能做健康检查和 KV 配置。"
定位与问题场景
Eureka 解决了 Java 微服务的注册发现问题,但在以下场景有局限性:
| 场景 | Eureka 的限制 | Consul 的优势 |
|---|---|---|
| 多语言异构系统 | 仅 Java 客户端成熟 | 原生 HTTP/DNS/gRPC 接口,任何语言可调用 |
| 健康检查 | 仅依赖客户端心跳 | 支持 HTTP/TCP/Script/gRPC 多维度健康检查 |
| 多数据中心 | 需手动配置 Region/Zone | 原生多数据中心(Multi-Datacenter)支持 |
| 配置管理 | 不支持 | 内置 KV Store,可作简单配置中心 |
Consul 是 HashiCorp 公司开发的服务网格解决方案,基于 Raft 共识协议保证强一致性(CP 系统)。
Consul 核心原理
架构与角色
| 角色 | 职责 |
|---|---|
| Consul Server | 集群核心,基于 Raft 选举 Leader,负责存储注册信息、处理查询、同步数据。生产环境建议 3 台或 5 台 |
| Consul Agent | 运行在每台节点的守护进程,负责转发请求、执行健康检查、维护本地缓存 |
| Consul Client | 应用通过 HTTP API 或 DNS 接口查询服务 |
健康检查机制
Consul 的健康检查远比 Eureka 丰富:
Consul vs Eureka 核心对比
| 维度 | Eureka | Consul |
|---|---|---|
| CAP | AP(高可用 + 分区容错) | CP(强一致 + 分区容错) |
| 一致性协议 | Peer to Peer 复制 | Raft 共识协议 |
| 健康检查 | 仅客户端心跳 | HTTP / TCP / Script / gRPC / TTL |
| 多数据中心 | 需要额外配置 | 原生 WAN Gossip 支持 |
| KV 存储 | 无 | 内置 KV Store |
| 语言支持 | Java(REST API 需自行实现) | HTTP/DNS/gRPC 原生多语言 |
| CAP 权衡 | 网络分区时仍可注册新服务 | 网络分区时非 Leader 分区不可写 |
选型建议:纯 Java 生态 + 对一致性要求不高 → Eureka;多语言异构系统 + 需要强一致性 + 健康检查 → Consul;需要注册中心 + 配置中心一体化 → Nacos。
完整示例:飞翔科技多语言服务注册
场景描述
飞翔科技有三个异构服务需要注册到 Consul:
feixiang-order-service(Java / Spring Boot)feixiang-logistics-service(Go 语言物流服务)feixiang-crawler-service(Python 爬虫服务)
操作前后对比:
| 维度 | 引入 Consul 前 | 引入 Consul 后 |
|---|---|---|
| 异构语言注册 | Go/Python 服务手动维护 IP 列表 | 统一通过 Consul API 注册 |
| 健康检查 | 人工巡检 | 自动 HTTP/TCP 多维度检查 |
| 服务发现 | 各语言各自实现 | 统一 DNS / HTTP API |
步骤一:启动 Consul(Docker)
docker run -d --name consul-server \
-p 8500:8500 \
-p 8600:8600/udp \
consul:1.15 agent -server -bootstrap-expect=1 -ui -client=0.0.0.0
访问 Consul UI:http://localhost:8500
步骤二:Java 服务注册(Spring Cloud Consul)
依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
配置:
spring:
application:
name: feixiang-order-service
cloud:
consul:
host: localhost
port: 8500
discovery:
service-name: ${spring.application.name}
health-check-path: /actuator/health
health-check-interval: 15s
prefer-ip-address: true # 使用 IP 而非主机名注册
启动类无需特殊注解,@SpringBootApplication + 依赖即可自动注册。
步骤三:Go 物流服务注册(Consul HTTP API)
package main
import (
"encoding/json"
"net/http"
"consulapi "github.com/hashicorp/consul/api"
)
func main() {
config := consulapi.DefaultConfig()
client, _ := consulapi.NewClient(config)
// 注册服务
registration := &consulapi.AgentServiceRegistration{
ID: "feixiang-logistics-1",
Name: "feixiang-logistics-service",
Port: 9090,
Check: &consulapi.AgentServiceCheck{
HTTP: "http://localhost:9090/health",
Interval: "10s",
Timeout: "3s",
},
}
client.Agent().ServiceRegister(registration)
http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(200)
json.NewEncoder(w).Encode(map[string]string{"status": "UP"})
})
http.ListenAndServe(":9090", nil)
}
步骤四:Java 订单服务通过 Consul 发现物流服务
@SpringBootApplication
@EnableDiscoveryClient
public class OrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
@RestController
public class OrderController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/order/ship/{orderId}")
public Map<String, Object> shipOrder(@PathVariable String orderId) {
// 服务名即可调用,无需关心是 Java 还是 Go
return restTemplate.getForObject(
"http://feixiang-logistics-service/ship/" + orderId,
Map.class
);
}
}
步骤五:验证
curl http://localhost:8080/order/ship/ORD-2024-001
# 返回:{"orderId":"ORD-2024-001", "status":"SHIPPED", "carrier":"SF Express"}
在 Consul UI 中可以看到 Java 服务和 Go 服务均列为健康实例。
易错场景
1. Consul Agent 未启动导致注册失败
Spring Cloud Consul 默认连接 localhost:8500,如果本地没有运行 Consul Agent,应用启动时会报 ConnectException。解决方案:每个节点运行一个 Consul Agent(开发环境可用 consul agent -dev)。
2. 健康检查路径配置错误
Consul 的健康检查默认使用 spring.cloud.consul.discovery.health-check-path(默认 /actuator/health)。如果项目未引入 Actuator 依赖,所有实例都会被标记为 critical。
3. Raft 协议要求奇数节点
Consul Server 集群节点数必须是奇数(推荐 3 或 5),否则可能发生脑裂(Split Brain)导致选举失败。
4. Consul 的 CP 特性在网络分区时的表现
当 Consul 集群发生网络分区时,非 Leader 分区无法处理写操作(注册、注销),直到分区恢复。这与 Eureka 的 AP 行为完全相反。
面试考点
Consul 的 Raft 协议如何保证一致性?
Consul Server 集群通过 Raft 协议选举一个 Leader,所有写操作必须经过 Leader,Leader 将日志复制到 Follower(过半确认即提交)。读操作可以从任意节点读取(可能有短暂过期数据)。当 Leader 宕机时,Follower 发起新一轮选举,任期号递增,保证不会出现两个 Leader。
Consul 健康检查有哪几种?各适用什么场景?
- HTTP:定期 GET 健康端点,检查返回码,适合 Web 服务
- TCP:定期尝试 TCP 连接,检查端口是否可达,适合非 HTTP 服务
- Script:执行自定义脚本,根据退出码判断,适合复杂逻辑(如检查数据库连接、磁盘空间)
- gRPC:适用于 gRPC 服务的健康检查
- TTL:应用主动上报状态,适合无法被外部探测的服务
Eureka(AP)vs Consul(CP):如何选择?
如果优先保证服务可用性——即使注册中心网络分区,各服务仍能正常注册和调用 → 选 Eureka。如果优先保证数据一致性——宁可拒绝注册也不能返回过期实例列表 → 选 Consul。国内互联网公司通常优先可用性(AP),金融/支付系统倾向于强一致性(CP)。
小结
Consul 在 Eureka 的基础上,通过原生多语言支持、多维度健康检查、多数据中心、内置 KV Store 等特性,提供了更全面的服务治理能力。它的 CP 特性使其在强一致性场景下比 Eureka 更有优势,但运维复杂度也更高。下一章我们来看注册中心返回了多个实例后,消费者如何选择调用哪个——客户端负载均衡。