目录
- Consul概述
- 核心概念
- 架构设计
- 环境搭建
- 服务注册与发现
- 键值存储
- 健康检查
- 服务网格
- ACL权限控制
- 集群配置
- API使用
- 客户端集成
- 监控运维
- 故障排查
- 实战案例
- 最佳实践
Consul概述
Consul是HashiCorp开源的一个服务网格解决方案,提供服务发现、配置管理、健康检查等功能的完整控制平面。它支持多数据中心部署,提供强一致性保证。
核心特性
服务发现:自动服务注册和发现
健康检查:内置多种健康检查机制
键值存储:分布式键值存储系统
多数据中心:原生支持多数据中心
服务网格:提供服务间安全通信
Web UI:直观的管理界面
应用场景
微服务架构 → Consul → 服务治理
配置中心 → Consul KV → 配置管理
DNS服务 → Consul DNS → 服务发现
服务网格 → Consul Connect → 安全通信
微服务注册中心:替代Eureka、Zookeeper
配置中心:基于KV存储的配置管理
DNS服务器:提供DNS接口的服务发现
API网关:动态路由和负载均衡
服务网格:提供服务间加密通信
核心概念
Agent(代理)
Consul集群中的每个节点都运行一个Consul Agent,分为Client和Server两种模式:
java
# Server模式 - 参与共识算法,存储数据
consul agent -server -bootstrap-expect=3 -data-dir=/opt/consul/data -node=server1
# Client模式 - 转发请求到Server,无状态
consul agent -data-dir=/opt/consul/data -node=client1
Datacenter(数据中心)
java
Datacenter-1 Datacenter-2
┌─────────────┐ ┌─────────────┐
│ Server1 │ │ Server4 │
│ Server2 │◄────►│ Server5 │
│ Server3 │ │ Server6 │
│ Client1 │ │ Client4 │
│ Client2 │ │ Client5 │
│ Client3 │ │ Client6 │
└─────────────┘ └─────────────┘
Service(服务)
java
{
"service": {
"id": "user-service-1",
"name": "user-service",
"tags": ["v1.0", "production"],
"address": "192.168.1.100",
"port": 8080,
"meta": {
"version": "1.0.0",
"environment": "production"
},
"check": {
"http": "http://192.168.1.100:8080/health",
"interval": "10s",
"timeout": "3s"
}
}
}
Node(节点)
java
{
"node": {
"id": "40e4a748-2192-161a-0510-9bf59fe950b5",
"name": "node1",
"address": "192.168.1.100",
"datacenter": "dc1",
"meta": {
"consul-network-segment": "",
"zone": "us-east-1a"
}
}
}
架构设计
整体架构
java
┌─────────────────────────────────────────────────────────────┐
│ Consul架构图 │
├─────────────────────────────────────────────────────────────┤
│ Client Applications │
│ ┌─────────┬─────────┬─────────┬─────────┬─────────┐ │
│ │HTTP API │DNS API │gRPC API │ Web UI │ CLI │ │
│ └─────────┴─────────┴─────────┴─────────┴─────────┘ │
├─────────────────────────────────────────────────────────────┤
│ Consul Agent │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Client Agent │ Server Agent │ │
│ │ - Service Reg │ - Raft Consensus │ │
│ │ - Health Check │ - Data Storage │ │
│ │ - DNS/HTTP API │ - Cross-DC │ │
│ └─────────────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ Core Components │
│ ┌─────────┬─────────┬─────────┬─────────┬─────────┐ │
│ │Catalog │ Health │ KV │Connect │ ACL │ │
│ │ │ Checker │ Store │ (Mesh) │ │ │
│ └─────────┴─────────┴─────────┴─────────┴─────────┘ │
├─────────────────────────────────────────────────────────────┤
│ Storage Layer │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Raft Consensus Log │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
Raft共识算法
Consul使用Raft算法保证数据一致性:
Leader选举过程:
1.Follower → Candidate (选举超时)
2.Candidate → Leader (获得多数投票)
3.Leader → Follower (发现更高term)
日志复制过程:
1.Client → Leader (写请求)
2.Leader → Followers (日志条目)
3.Followers → Leader (确认)
4.Leader → Client (提交确认)
环境搭建
单节点部署
java
# 下载Consul
wget https://releases.hashicorp.com/consul/1.16.1/consul_1.16.1_linux_amd64.zip
unzip consul_1.16.1_linux_amd64.zip
sudo mv consul /usr/local/bin/
# 创建目录
sudo mkdir -p /opt/consul/{data,config,logs}
sudo useradd --system --home /etc/consul.d --shell /bin/false consul
sudo chown -R consul:consul /opt/consul
# 配置文件
sudo tee /opt/consul/config/consul.json > /dev/null <<EOF
{
"datacenter": "dc1",
"data_dir": "/opt/consul/data",
"log_level": "INFO",
"server": true,
"bootstrap_expect": 1,
"bind_addr": "0.0.0.0",
"client_addr": "0.0.0.0",
"retry_join": ["127.0.0.1"],
"ui_config": {
"enabled": true
},
"connect": {
"enabled": true
},
"ports": {
"grpc": 8502
},
"acl": {
"enabled": false,
"default_policy": "allow"
}
}
EOF
# 启动服务
consul agent -config-dir=/opt/consul/config
Docker部署
java
# docker-compose.yml
version:'3.8'
services:
consul-server:
image:consul:1.16.1
container_name:consul-server
restart:unless-stopped
volumes:
-./consul/data:/consul/data
-./consul/config:/consul/config
ports:
-"8500:8500"
-"8600:8600/udp"
-"8300:8300"
-"8301:8301"
-"8302:8302"
command:>
consul agent
-server
-bootstrap-expect=1
-datacenter=dc1
-data-dir=/consul/data
-bind=0.0.0.0
-client=0.0.0.0
-retry-join=consul-server
-ui
environment:
CONSUL_LOCAL_CONFIG:|
{
"connect": { "enabled": true },
"ports": { "grpc": 8502 },
"acl": {
"enabled": false,
"default_policy": "allow"
}
}
consul-agent:
image:consul:1.16.1
container_name:consul-agent
restart:unless-stopped
volumes:
-./consul/agent/data:/consul/data
ports:
-"8501:8500"
command:>
consul agent
-datacenter=dc1
-data-dir=/consul/data
-bind=0.0.0.0
-client=0.0.0.0
-retry-join=consul-server
depends_on:
-consul-server
集群部署
java
# Server节点配置
# server1.json
{
"datacenter": "dc1",
"data_dir": "/opt/consul/data",
"log_level": "INFO",
"server": true,
"bootstrap_expect": 3,
"bind_addr": "192.168.1.10",
"client_addr": "0.0.0.0",
"retry_join": ["192.168.1.10", "192.168.1.11", "192.168.1.12"],
"ui_config": {
"enabled": true
},
"connect": {
"enabled": true
},
"ports": {
"grpc": 8502
},
"acl": {
"enabled": true,
"default_policy": "deny",
"enable_token_persistence": true
},
"encrypt": "uDBV4e+LbFW3019YKPxIrg=="
}
# 启动集群
# Node 1
consul agent -config-file=/opt/consul/config/server1.json
# Node 2
consul agent -config-file=/opt/consul/config/server2.json
# Node 3
consul agent -config-file=/opt/consul/config/server3.json
服务注册与发现
服务注册
java
# 1. 通过HTTP API注册
curl -X PUT http://localhost:8500/v1/agent/service/register \
-d '{
"id": "user-service-1",
"name": "user-service",
"tags": ["v1.0", "production"],
"address": "192.168.1.100",
"port": 8080,
"meta": {
"version": "1.0.0",
"environment": "production"
},
"check": {
"http": "http://192.168.1.100:8080/health",
"interval": "10s",
"timeout": "3s"
}
}'
# 2. 通过配置文件注册
# /opt/consul/config/services/user-service.json
{
"service": {
"id": "user-service-1",
"name": "user-service",
"tags": ["v1.0", "production"],
"address": "192.168.1.100",
"port": 8080,
"meta": {
"version": "1.0.0",
"environment": "production"
},
"checks": [
{
"id": "user-service-health",
"name": "User Service Health Check",
"http": "http://192.168.1.100:8080/health",
"interval": "10s",
"timeout": "3s"
},
{
"id": "user-service-tcp",
"name": "User Service TCP Check",
"tcp": "192.168.1.100:8080",
"interval": "30s",
"timeout": "5s"
}
]
}
}
服务发现
java
# 1. HTTP API查询
# 查询所有服务
curl http://localhost:8500/v1/catalog/services
# 查询特定服务
curl http://localhost:8500/v1/catalog/service/user-service
# 查询健康的服务实例
curl http://localhost:8500/v1/health/service/user-service?passing
# 2. DNS查询
# 查询服务
dig @localhost -p 8600 user-service.service.consul
# 查询特定标签的服务
dig @localhost -p 8600 production.user-service.service.consul
# 查询SRV记录
dig @localhost -p 8600 user-service.service.consul SRV
Spring Boot集成
java
<!-- pom.xml -->
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
java
# application.yml
spring:
application:
name:user-service
cloud:
consul:
host:localhost
port:8500
discovery:
enabled:true
service-name:${spring.application.name}
instance-id:${spring.application.name}:${spring.cloud.client.ip-address}:${server.port}
hostname:${spring.cloud.client.ip-address}
port:${server.port}
prefer-ip-address:true
health-check-path:/actuator/health
health-check-interval:10s
health-check-timeout:3s
health-check-critical-timeout:30s
heartbeat:
enabled:true
tags:
-version=1.0.0
-environment=dev
metadata:
version:1.0.0
zone:zone-1
config:
enabled:true
format:yaml
prefix:config
default-context:application
profile-separator:','
data-key:data
watch:
enabled:true
delay:1000
server:
port:8080
management:
endpoints:
web:
exposure:
include:"*"
endpoint:
health:
show-details:always
java
@SpringBootApplication
@EnableDiscoveryClient
publicclass UserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceApplication.class, args);
}
}
java
@RestController
@RequestMapping("/users")
@Slf4j
publicclass UserController {
@Autowired
private DiscoveryClient discoveryClient;
@Value("${server.port}")
privateint port;
@GetMapping("/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
User user = User.builder()
.id(id)
.name("张三")
.email("zhangsan@example.com")
.port(port)
.build();
log.info("获取用户信息:{}", user);
return ResponseEntity.ok(user);
}
@GetMapping("/services")
public ResponseEntity<List<String>> getServices() {
List<String> services = discoveryClient.getServices();
return ResponseEntity.ok(services);
}
@GetMapping("/instances/{serviceName}")
public ResponseEntity<List<ServiceInstance>> getInstances(@PathVariable String serviceName) {
List<ServiceInstance> instances = discoveryClient.getInstances(serviceName);
return ResponseEntity.ok(instances);
}
}
java
// 服务调用
@Service
@Slf4j
publicclass OrderService {
@Autowired
private DiscoveryClient discoveryClient;
@Autowired
private RestTemplate restTemplate;
public User getUserById(Long userId) {
List<ServiceInstance> instances = discoveryClient.getInstances("user-service");
if (instances.isEmpty()) {
thrownew ServiceUnavailableException("用户服务不可用");
}
// 简单的负载均衡
ServiceInstance instance = instances.get(
ThreadLocalRandom.current().nextInt(instances.size())
);
String url = String.format("http://%s:%d/users/%d",
instance.getHost(), instance.getPort(), userId);
log.info("调用用户服务:{}", url);
return restTemplate.getForObject(url, User.class);
}
}
键值存储
KV存储操作
java
# 1. 存储数据
curl -X PUT http://localhost:8500/v1/kv/config/database/url \
-d 'jdbc:mysql://localhost:3306/mydb'
curl -X PUT http://localhost:8500/v1/kv/config/database/username \
-d 'dbuser'
curl -X PUT http://localhost:8500/v1/kv/config/redis/host \
-d 'redis.example.com'
# 2. 获取数据
curl http://localhost:8500/v1/kv/config/database/url
# 获取目录下所有键值
curl http://localhost:8500/v1/kv/config/?recurse
# 3. 删除数据
curl -X DELETE http://localhost:8500/v1/kv/config/database/url
# 删除目录
curl -X DELETE http://localhost:8500/v1/kv/config/?recurse
# 4. 原子操作
# CAS (Compare-And-Swap)
curl -X PUT http://localhost:8500/v1/kv/config/counter?cas=0 -d '1'
Spring Cloud Config集成
java
# bootstrap.yml
spring:
application:
name:user-service
cloud:
consul:
config:
enabled:true
format:yaml
prefix:config
default-context:${spring.application.name}
profile-separator:','
data-key:data
watch:
enabled:true
delay:1000
在Consul中存储配置:
java
# 存储应用配置
curl -X PUT http://localhost:8500/v1/kv/config/user-service/data \
-d 'server:
port: 8080
app:
name: User Service
version: 1.0.0
database:
url: jdbc:mysql://localhost:3306/userdb
username: user
password: password
redis:
host: localhost
port: 6379
logging:
level:
com.example: DEBUG'
# 存储环境特定配置
curl -X PUT http://localhost:8500/v1/kv/config/user-service,dev/data \
-d 'database:
url: jdbc:mysql://dev-db:3306/userdb
username: dev_user
password: dev_password
redis:
host: dev-redis
port: 6379'
配置监听和刷新
java
@Component
@Slf4j
publicclass ConfigWatcher {
@Autowired
private ConsulTemplate consulTemplate;
@Value("${app.name:Unknown}")
private String appName;
privatefinal AtomicLong lastIndex = new AtomicLong(0);
@PostConstruct
public void startWatching() {
watchConfig("config/user-service");
}
@Async
public void watchConfig(String keyPrefix) {
while (true) {
try {
QueryParams queryParams = QueryParams.builder()
.setIndex(lastIndex.get())
.setWaitTime(30)
.build();
Response<List<GetValue>> response = consulTemplate.getKVValues(keyPrefix, queryParams);
if (response.getConsulIndex() > lastIndex.get()) {
lastIndex.set(response.getConsulIndex());
log.info("检测到配置变更,重新加载配置");
handleConfigChange(response.getValue());
}
} catch (Exception e) {
log.error("监听配置变更失败", e);
try {
Thread.sleep(5000);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
break;
}
}
}
}
private void handleConfigChange(List<GetValue> values) {
for (GetValue value : values) {
String key = value.getKey();
String decodedValue = value.getDecodedValue();
log.info("配置项变更:key={}, value={}", key, decodedValue);
// 触发配置刷新事件
applicationEventPublisher.publishEvent(
new ConfigChangeEvent(key, decodedValue)
);
}
}
}
java
@ConfigurationProperties(prefix = "app")
@RefreshScope
@Component
@Data
publicclass AppConfig {
private String name;
private String version;
private Database database;
private Redis redis;
@Data
publicstaticclass Database {
private String url;
private String username;
private String password;
}
@Data
publicstaticclass Redis {
private String host;
privateint port;
}
}
健康检查
健康检查类型
java
{
"checks": [
{
"id": "http-check",
"name": "HTTP Health Check",
"http": "http://localhost:8080/health",
"method": "GET",
"header": {
"Authorization": ["Bearer token"]
},
"interval": "10s",
"timeout": "3s"
},
{
"id": "tcp-check",
"name": "TCP Health Check",
"tcp": "localhost:8080",
"interval": "30s",
"timeout": "5s"
},
{
"id": "script-check",
"name": "Script Health Check",
"args": ["/usr/local/bin/check_service.sh"],
"interval": "60s",
"timeout": "10s"
},
{
"id": "grpc-check",
"name": "gRPC Health Check",
"grpc": "localhost:9090",
"grpc_use_tls": false,
"interval": "15s",
"timeout": "5s"
},
{
"id": "docker-check",
"name": "Docker Health Check",
"docker_container_id": "container_id",
"shell": "/bin/bash",
"args": ["/app/health_check.sh"],
"interval": "30s",
"timeout": "10s"
}
]
}
自定义健康检查
java
#!/bin/bash
# check_service.sh
# 检查进程是否运行
PID=$(pgrep -f user-service)
if [ -z "$PID" ]; then
echo"Service not running"
exit 1
fi
# 检查端口是否监听
if ! nc -z localhost 8080; then
echo"Port 8080 not listening"
exit 1
fi
# 检查数据库连接
if ! mysql -h localhost -u user -ppassword -e "SELECT 1" > /dev/null 2>&1; then
echo"Database connection failed"
exit 1
fi
# 检查磁盘空间
DISK_USAGE=$(df / | awk 'NR==2 {print $5}' | sed 's/%//')
if [ "$DISK_USAGE" -gt 90 ]; then
echo"Disk usage too high: ${DISK_USAGE}%"
exit 1
fi
echo"All checks passed"
exit 0
Spring Boot健康检查
java
@Component
@Slf4j
publicclass ConsulHealthIndicator implements HealthIndicator {
@Autowired
private ConsulTemplate consulTemplate;
@Override
public Health health() {
try {
// 检查Consul连接
Response<Map<String, Object>> response = consulTemplate.getStatus();
if (response != null) {
return Health.up()
.withDetail("consul", "Connected")
.withDetail("leader", response.getValue().get("leader"))
.withDetail("peers", response.getValue().get("peers"))
.build();
} else {
return Health.down()
.withDetail("consul", "Disconnected")
.build();
}
} catch (Exception e) {
return Health.down()
.withDetail("consul", "Error")
.withDetail("error", e.getMessage())
.build();
}
}
}
java
@RestController
@RequestMapping("/health")
publicclass HealthController {
@Autowired
private Environment environment;
@Autowired
private DataSource dataSource;
@GetMapping
public ResponseEntity<Map<String, Object>> health() {
Map<String, Object> health = new HashMap<>();
try {
// 检查应用状态
health.put("status", "UP");
health.put("timestamp", Instant.now());
health.put("port", environment.getProperty("server.port"));
// 检查数据库连接
try (Connection conn = dataSource.getConnection()) {
health.put("database", "UP");
health.put("db_url", conn.getMetaData().getURL());
}
// 检查内存使用
Runtime runtime = Runtime.getRuntime();
long totalMemory = runtime.totalMemory();
long freeMemory = runtime.freeMemory();
long usedMemory = totalMemory - freeMemory;
Map<String, Object> memory = new HashMap<>();
memory.put("total", totalMemory);
memory.put("used", usedMemory);
memory.put("free", freeMemory);
memory.put("usage_percent", (double) usedMemory / totalMemory * 100);
health.put("memory", memory);
return ResponseEntity.ok(health);
} catch (Exception e) {
health.put("status", "DOWN");
health.put("error", e.getMessage());
return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).body(health);
}
}
@GetMapping("/ready")
public ResponseEntity<String> readiness() {
// 检查应用是否就绪
try {
// 检查必要的依赖服务
checkDependencies();
return ResponseEntity.ok("READY");
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).body("NOT_READY");
}
}
@GetMapping("/live")
public ResponseEntity<String> liveness() {
// 检查应用是否存活
return ResponseEntity.ok("ALIVE");
}
private void checkDependencies() throws Exception {
// 检查数据库
try (Connection conn = dataSource.getConnection()) {
conn.isValid(3);
}
// 检查其他依赖服务
// ...
}
}
服务网格
Consul Connect
Consul Connect提供服务间的安全通信:
java
# 启用Connect
consul agent -config-file=consul.json
# consul.json
{
"connect": {
"enabled": true
},
"ports": {
"grpc": 8502
}
}
服务网格配置
java
{
"service": {
"name": "user-service",
"port": 8080,
"connect": {
"sidecar_service": {
"proxy": {
"upstreams": [
{
"destination_name": "database",
"local_bind_port": 5432
},
{
"destination_name": "cache",
"local_bind_port": 6379
}
]
}
}
}
}
}
Envoy代理集成
java
# 生成Envoy配置
consul connect envoy -sidecar-for user-service -admin-bind 0.0.0.0:19000
# 启动Envoy代理
consul connect envoy -sidecar-for user-service &
# 或者使用docker
docker run --rm -d \
--name user-service-sidecar \
--network consul_default \
-p 19000:19000 \
envoyproxy/envoy:v1.23-latest \
/usr/local/bin/envoy --config-yaml "$(consul connect envoy -sidecar-for user-service)"
服务意图配置
java
# 创建服务意图
curl -X PUT http://localhost:8500/v1/connect/intentions \
-d '{
"SourceName": "order-service",
"DestinationName": "user-service",
"Action": "allow",
"Description": "Allow order service to call user service"
}'
# 或者使用配置文件
# intentions.json
{
"Kind": "service-intentions",
"Name": "user-service",
"Sources": [
{
"Name": "order-service",
"Action": "allow"
},
{
"Name": "payment-service",
"Action": "allow"
},
{
"Name": "*",
"Action": "deny"
}
]
}
# 应用配置
consul config write intentions.json
ACL权限控制
ACL系统配置
java
{
"acl": {
"enabled": true,
"default_policy": "deny",
"enable_token_persistence": true,
"tokens": {
"master": "bootstrap-token-here",
"agent": "agent-token-here"
}
}
}
Bootstrap ACL系统
java
# 1. Bootstrap ACL系统
consul acl bootstrap
# 返回示例:
# AccessorID: 12345678-1234-1234-1234-123456789012
# SecretID: abcdefgh-1234-1234-1234-123456789012
# Description: Bootstrap Token (Global Management)
# Local: false
# Create Time: 2023-01-01 12:00:00 +0000 UTC
# Policies:
# 00000000-0000-0000-0000-000000000001 - global-management
# 2. 设置环境变量
export CONSUL_HTTP_TOKEN=abcdefgh-1234-1234-1234-123456789012
创建策略和令牌
java
# 1. 创建策略
consul acl policy create \
-name "service-policy" \
-description "Policy for service operations" \
-rules @service-policy.hcl
# service-policy.hcl
service_prefix "" {
policy = "write"
}
node_prefix "" {
policy = "read"
}
key_prefix "config/" {
policy = "write"
}
session_prefix "" {
policy = "write"
}
# 2. 创建令牌
consul acl token create \
-description "Service token" \
-policy-name "service-policy"
# 3. 为特定服务创建策略
consul acl policy create \
-name "user-service-policy" \
-description "Policy for user service" \
-rules @user-service-policy.hcl
# user-service-policy.hcl
service "user-service" {
policy = "write"
}
service_prefix "" {
policy = "read"
}
node_prefix "" {
policy = "read"
}
key_prefix "config/user-service/" {
policy = "write"
}
key_prefix "config/shared/" {
policy = "read"
}
使用令牌
java
# 1. 使用HTTP头
curl -H "X-Consul-Token: your-token-here" \
http://localhost:8500/v1/kv/config/database/url
# 2. 使用查询参数
curl http://localhost:8500/v1/kv/config/database/url?token=your-token-here
# 3. 在客户端配置中使用
# Spring Boot配置
spring:
cloud:
consul:
discovery:
acl-token: your-service-token-here
config:
acl-token: your-config-token-here
java
// Java客户端配置
@Configuration
public class ConsulConfig {
@Value("${consul.token}")
private String consulToken;
@Bean
public ConsulClient consulClient() {
return new ConsulClient("localhost", 8500, consulToken);
}
}
集群配置
多节点集群
java
# 服务器节点配置
# server1.json
{
"datacenter": "dc1",
"data_dir": "/opt/consul/data",
"log_level": "INFO",
"node_name": "server1",
"bind_addr": "192.168.1.10",
"client_addr": "0.0.0.0",
"retry_join": ["192.168.1.10", "192.168.1.11", "192.168.1.12"],
"server": true,
"bootstrap_expect": 3,
"ui_config": {
"enabled": true
},
"connect": {
"enabled": true
},
"ports": {
"grpc": 8502
},
"encrypt": "pUqJrVyVRj5jsiYEkM/tFQYfWyJIv4s3XkvDwy7Cu5s=",
"acl": {
"enabled": true,
"default_policy": "deny",
"enable_token_persistence": true
},
"performance": {
"raft_multiplier": 1
}
}
# 客户端节点配置
# client1.json
{
"datacenter": "dc1",
"data_dir": "/opt/consul/data",
"log_level": "INFO",
"node_name": "client1",
"bind_addr": "192.168.1.100",
"client_addr": "127.0.0.1",
"retry_join": ["192.168.1.10", "192.168.1.11", "192.168.1.12"],
"server": false,
"encrypt": "pUqJrVyVRj5jsiYEkM/tFQYfWyJIv4s3XkvDwy7Cu5s=",
"acl": {
"enabled": true,
"default_policy": "deny",
"tokens": {
"agent": "agent-token-here"
}
}
}
多数据中心
java
# DC1配置
# dc1-server.json
{
"datacenter": "dc1",
"primary_datacenter": "dc1",
"data_dir": "/opt/consul/data",
"node_name": "dc1-server1",
"bind_addr": "192.168.1.10",
"server": true,
"bootstrap_expect": 3,
"retry_join": ["192.168.1.10", "192.168.1.11", "192.168.1.12"],
"acl": {
"enabled": true,
"default_policy": "deny",
"enable_token_persistence": true,
"enable_token_replication": true
},
"connect": {
"enabled": true,
"enable_mesh_gateway_wan_federation": true
}
}
# DC2配置
# dc2-server.json
{
"datacenter": "dc2",
"primary_datacenter": "dc1",
"data_dir": "/opt/consul/data",
"node_name": "dc2-server1",
"bind_addr": "192.168.2.10",
"server": true,
"bootstrap_expect": 3,
"retry_join": ["192.168.2.10", "192.168.2.11", "192.168.2.12"],
"retry_join_wan": ["192.168.1.10", "192.168.1.11", "192.168.1.12"],
"acl": {
"enabled": true,
"default_policy": "deny",
"enable_token_persistence": true,
"enable_token_replication": true
},
"connect": {
"enabled": true,
"enable_mesh_gateway_wan_federation": true
}
}
# 连接数据中心
consul join -wan 192.168.1.10
网格网关配置
java
{
"service": {
"name": "mesh-gateway",
"kind": "mesh-gateway",
"port": 8443,
"meta": {
"version": "1.0"
},
"proxy": {
"config": {
"envoy_gateway_bind_addresses": {
"all-interfaces": "0.0.0.0:8443"
}
}
}
}
}
API使用
Catalog API
java
# 1. 节点相关
# 获取所有节点
curl http://localhost:8500/v1/catalog/nodes
# 获取特定节点信息
curl http://localhost:8500/v1/catalog/node/node1
# 2. 服务相关
# 获取所有服务
curl http://localhost:8500/v1/catalog/services
# 获取特定服务的所有实例
curl http://localhost:8500/v1/catalog/service/user-service
# 根据标签过滤
curl http://localhost:8500/v1/catalog/service/user-service?tag=production
# 3. 数据中心
# 获取所有数据中心
curl http://localhost:8500/v1/catalog/datacenters
Health API
java
# 1. 节点健康检查
curl http://localhost:8500/v1/health/node/node1
# 2. 服务健康检查
# 获取所有实例(包括不健康的)
curl http://localhost:8500/v1/health/service/user-service
# 只获取健康的实例
curl http://localhost:8500/v1/health/service/user-service?passing
# 获取特定数据中心的服务
curl http://localhost:8500/v1/health/service/user-service?dc=dc1
# 3. 检查特定健康检查
curl http://localhost:8500/v1/health/checks/user-service
KV API
java
# 1. 基本操作
# 存储键值
curl -X PUT http://localhost:8500/v1/kv/config/app/name -d "My Application"
# 获取键值
curl http://localhost:8500/v1/kv/config/app/name
# 获取原始值(不含元数据)
curl http://localhost:8500/v1/kv/config/app/name?raw
# 2. 目录操作
# 递归获取
curl http://localhost:8500/v1/kv/config/?recurse
# 只获取键名
curl http://localhost:8500/v1/kv/config/?keys
# 3. 原子操作
# CAS操作
curl -X PUT http://localhost:8500/v1/kv/config/counter?cas=123 -d "456"
# 获取并包含索引
curl http://localhost:8500/v1/kv/config/counter
# 4. 事务操作
curl -X PUT http://localhost:8500/v1/txn -d '[
{
"KV": {
"Verb": "set",
"Key": "config/app/name",
"Value": "TXN Application"
}
},
{
"KV": {
"Verb": "get",
"Key": "config/app/version"
}
}
]'
Agent API
java
# 1. 节点信息
curl http://localhost:8500/v1/agent/self
# 2. 成员信息
curl http://localhost:8500/v1/agent/members
# 3. 服务管理
# 注册服务
curl -X PUT http://localhost:8500/v1/agent/service/register -d '{
"id": "user-service-1",
"name": "user-service",
"address": "192.168.1.100",
"port": 8080
}'
# 注销服务
curl -X PUT http://localhost:8500/v1/agent/service/deregister/user-service-1
# 获取本地服务
curl http://localhost:8500/v1/agent/services
# 4. 健康检查管理
# 注册检查
curl -X PUT http://localhost:8500/v1/agent/check/register -d '{
"id": "service-check",
"name": "Service Health Check",
"http": "http://localhost:8080/health",
"interval": "10s"
}'
# 手动设置检查状态
curl -X PUT http://localhost:8500/v1/agent/check/pass/service-check
curl -X PUT http://localhost:8500/v1/agent/check/warn/service-check
curl -X PUT http://localhost:8500/v1/agent/check/fail/service-check
# 注销检查
curl -X PUT http://localhost:8500/v1/agent/check/deregister/service-check
客户端集成
Java客户端
java
<dependency>
<groupId>com.ecwid.consul</groupId>
<artifactId>consul-api</artifactId>
<version>1.4.5</version>
</dependency>
java
@Service
@Slf4j
publicclass ConsulService {
privatefinal ConsulClient consulClient;
public ConsulService() {
this.consulClient = new ConsulClient("localhost", 8500);
}
// 服务注册
public void registerService(String serviceId, String serviceName,
String address, int port) {
NewService newService = new NewService();
newService.setId(serviceId);
newService.setName(serviceName);
newService.setAddress(address);
newService.setPort(port);
// 添加健康检查
NewService.Check check = new NewService.Check();
check.setHttp("http://" + address + ":" + port + "/health");
check.setInterval("10s");
check.setTimeout("3s");
newService.setCheck(check);
// 添加标签
newService.setTags(Arrays.asList("v1.0", "production"));
consulClient.agentServiceRegister(newService);
log.info("服务注册成功:{}:{}", serviceName, serviceId);
}
// 服务发现
public List<ServiceHealth> discoverService(String serviceName) {
HealthServicesRequest request = HealthServicesRequest.newBuilder()
.setPassing(true)
.build();
Response<List<ServiceHealth>> response = consulClient.getHealthServices(serviceName, request);
return response.getValue();
}
// 负载均衡选择
public ServiceHealth selectService(String serviceName) {
List<ServiceHealth> services = discoverService(serviceName);
if (services.isEmpty()) {
thrownew ServiceUnavailableException("No available instances for " + serviceName);
}
// 简单的随机负载均衡
return services.get(ThreadLocalRandom.current().nextInt(services.size()));
}
// KV操作
public void putKV(String key, String value) {
consulClient.setKVValue(key, value);
log.info("存储KV:{}={}", key, value);
}
public String getKV(String key) {
Response<GetValue> response = consulClient.getKVValue(key);
if (response.getValue() != null) {
return response.getValue().getDecodedValue();
}
returnnull;
}
public Map<String, String> getKVRecursive(String keyPrefix) {
Response<List<GetValue>> response = consulClient.getKVValues(keyPrefix);
Map<String, String> result = new HashMap<>();
if (response.getValue() != null) {
for (GetValue value : response.getValue()) {
result.put(value.getKey(), value.getDecodedValue());
}
}
return result;
}
// 监听KV变化
@Async
public void watchKV(String key, Consumer<String> callback) {
long lastIndex = 0;
while (true) {
try {
QueryParams queryParams = QueryParams.Builder.builder()
.setIndex(lastIndex)
.setWaitTime(30)
.build();
Response<GetValue> response = consulClient.getKVValue(key, queryParams);
if (response.getConsulIndex() > lastIndex) {
lastIndex = response.getConsulIndex();
if (response.getValue() != null) {
String value = response.getValue().getDecodedValue();
callback.accept(value);
}
}
} catch (Exception e) {
log.error("监听KV变化失败:{}", key, e);
try {
Thread.sleep(5000);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
break;
}
}
}
}
}
Go客户端
java
package main
import (
"fmt"
"github.com/hashicorp/consul/api"
"log"
)
func main() {
// 创建客户端
config := api.DefaultConfig()
config.Address = "localhost:8500"
client, err := api.NewClient(config)
if err != nil {
log.Fatal(err)
}
// 服务注册
registration := &api.AgentServiceRegistration{
ID: "user-service-1",
Name: "user-service",
Address: "localhost",
Port: 8080,
Tags: []string{"v1.0", "production"},
Check: &api.AgentServiceCheck{
HTTP: "http://localhost:8080/health",
Interval: "10s",
Timeout: "3s",
},
}
err = client.Agent().ServiceRegister(registration)
if err != nil {
log.Fatal(err)
}
fmt.Println("服务注册成功")
// 服务发现
services, _, err := client.Health().Service("user-service", "", true, nil)
if err != nil {
log.Fatal(err)
}
for _, service := range services {
fmt.Printf("服务实例:%s:%d\n", service.Service.Address, service.Service.Port)
}
// KV操作
kv := client.KV()
// 存储
p := &api.KVPair{Key: "config/app/name", Value: []byte("My Go App")}
_, err = kv.Put(p, nil)
if err != nil {
log.Fatal(err)
}
// 获取
pair, _, err := kv.Get("config/app/name", nil)
if err != nil {
log.Fatal(err)
}
if pair != nil {
fmt.Printf("配置值:%s\n", string(pair.Value))
}
}
Python客户端
java
import consul
import json
import time
from typing import List, Dict, Optional
class ConsulService:
def __init__(self, host='localhost', port=8500, token=None):
self.consul = consul.Consul(host=host, port=port, token=token)
def register_service(self, service_id: str, service_name: str,
address: str, port: int, tags: List[str] = None):
"""注册服务"""
check = consul.Check.http(f"http://{address}:{port}/health", interval="10s")
self.consul.agent.service.register(
name=service_name,
service_id=service_id,
address=address,
port=port,
tags=tags or [],
check=check
)
print(f"服务注册成功:{service_name}:{service_id}")
def discover_services(self, service_name: str) -> List[Dict]:
"""服务发现"""
index, services = self.consul.health.service(service_name, passing=True)
return services
def select_service(self, service_name: str) -> Optional[Dict]:
"""负载均衡选择服务"""
services = self.discover_services(service_name)
ifnot services:
returnNone
import random
return random.choice(services)
def put_kv(self, key: str, value: str):
"""存储KV"""
self.consul.kv.put(key, value)
print(f"存储KV:{key}={value}")
def get_kv(self, key: str) -> Optional[str]:
"""获取KV"""
index, data = self.consul.kv.get(key)
if data:
return data['Value'].decode('utf-8')
returnNone
def get_kv_recursive(self, key_prefix: str) -> Dict[str, str]:
"""递归获取KV"""
index, data = self.consul.kv.get(key_prefix, recurse=True)
result = {}
if data:
for item in data:
key = item['Key']
value = item['Value'].decode('utf-8') if item['Value'] else''
result[key] = value
return result
def watch_kv(self, key: str, callback):
"""监听KV变化"""
index = None
whileTrue:
try:
index, data = self.consul.kv.get(key, index=index)
if data:
value = data['Value'].decode('utf-8')
callback(value)
time.sleep(1)
except Exception as e:
print(f"监听KV变化失败:{e}")
time.sleep(5)
# 使用示例
if __name__ == "__main__":
consul_service = ConsulService()
# 注册服务
consul_service.register_service(
service_id="python-service-1",
service_name="python-service",
address="localhost",
port=8080,
tags=["python", "v1.0"]
)
# 服务发现
services = consul_service.discover_services("user-service")
for service in services:
print(f"发现服务:{service['Service']['Address']}:{service['Service']['Port']}")
# KV操作
consul_service.put_kv("config/python/app_name", "Python Application")
app_name = consul_service.get_kv("config/python/app_name")
print(f"应用名称:{app_name}")
监控运维
Prometheus监控
java
# prometheus.yml
global:
scrape_interval:15s
scrape_configs:
-job_name:'consul'
static_configs:
-targets:['localhost:8500']
metrics_path:/v1/agent/metrics
params:
format:['prometheus']
scrape_interval:30s
-job_name:'consul-services'
consul_sd_configs:
-server:'localhost:8500'
services:[]
relabel_configs:
-source_labels:[__meta_consul_service]
target_label:service
-source_labels:[__meta_consul_node]
target_label:node
-source_labels:[__meta_consul_tags]
target_label:tags
Grafana仪表板
java
{
"dashboard": {
"title": "Consul Monitoring",
"panels": [
{
"title": "Consul Cluster Health",
"type": "stat",
"targets": [
{
"expr": "consul_up",
"legendFormat": "Consul Nodes Up"
}
]
},
{
"title": "Service Count",
"type": "graph",
"targets": [
{
"expr": "consul_catalog_services",
"legendFormat": "Total Services"
}
]
},
{
"title": "Raft Transactions",
"type": "graph",
"targets": [
{
"expr": "rate(consul_raft_apply[5m])",
"legendFormat": "Raft Apply Rate"
}
]
},
{
"title": "KV Operations",
"type": "graph",
"targets": [
{
"expr": "rate(consul_kvs_apply[5m])",
"legendFormat": "KV Apply Rate"
}
]
}
]
}
}
健康检查监控
java
#!/bin/bash
# consul_health_monitor.sh
CONSUL_HOST="localhost:8500"
ALERT_WEBHOOK="https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK"
check_consul_health() {
# 检查Consul leader
LEADER=$(curl -s http://${CONSUL_HOST}/v1/status/leader)
if [ "$LEADER" == '""' ]; then
send_alert "Consul集群没有leader"
return 1
fi
# 检查节点数量
NODE_COUNT=$(curl -s http://${CONSUL_HOST}/v1/catalog/nodes | jq length)
if [ "$NODE_COUNT" -lt 3 ]; then
send_alert "Consul节点数量不足:$NODE_COUNT"
fi
# 检查关键服务
CRITICAL_SERVICES=("user-service""order-service""payment-service")
for service in"${CRITICAL_SERVICES[@]}"; do
HEALTHY_COUNT=$(curl -s "http://${CONSUL_HOST}/v1/health/service/${service}?passing" | jq length)
if [ "$HEALTHY_COUNT" -eq 0 ]; then
send_alert "关键服务不可用:$service"
fi
done
}
send_alert() {
local message="$1"
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
curl -X POST -H 'Content-type: application/json' \
--data "{\"text\":\"⚠️ Consul Alert [$timestamp]: $message\"}" \
"$ALERT_WEBHOOK"
echo"[$timestamp] ALERT: $message"
}
# 主循环
whiletrue; do
check_consul_health
sleep 60
done
日志监控
java
# filebeat.yml
filebeat.inputs:
-type:log
enabled:true
paths:
-/opt/consul/logs/*.log
fields:
service:consul
datacenter:dc1
multiline.pattern:'^\d{4}/\d{2}/\d{2}'
multiline.negate:true
multiline.match:after
output.logstash:
hosts:["logstash:5044"]
processors:
-add_host_metadata:
when.not.contains.tags:forwarded
# logstash.conf
input {
beats {
port => 5044
}
}
filter {
if [fields][service] == "consul" {
grok {
match => {
"message" => "%{TIMESTAMP_ISO8601:timestamp} \[%{LOGLEVEL:level}\] %{GREEDYDATA:message}"
}
}
date {
match => [ "timestamp", "ISO8601" ]
}
if [level] in ["ERROR", "WARN"] {
mutate {
add_tag => [ "alert" ]
}
}
}
}
output {
elasticsearch {
hosts => ["elasticsearch:9200"]
index => "consul-logs-%{+YYYY.MM.dd}"
}
if"alert"in [tags] {
email {
to => ["admin@example.com"]
subject => "Consul Alert: %{level}"
body => "Message: %{message}\nTimestamp: %{timestamp}"
}
}
}
故障排查
常见问题诊断
java
# 1. 检查Consul状态
consul info
consul members
consul operator raft list-peers
# 2. 检查日志
tail -f /opt/consul/logs/consul.log
journalctl -u consul -f
# 3. 检查网络连接
netstat -tulpn | grep consul
ss -tulpn | grep :8500
# 4. 检查磁盘空间
df -h /opt/consul/data
du -sh /opt/consul/data/*
# 5. 检查性能指标
curl http://localhost:8500/v1/agent/metrics?format=prometheus
集群脑裂处理
java
# 1. 识别脑裂
# 检查每个节点的leader
for server in server1 server2 server3; do
echo"=== $server ==="
ssh $server"consul info | grep leader"
done
# 2. 停止所有节点
for server in server1 server2 server3; do
ssh $server"systemctl stop consul"
done
# 3. 清理状态(谨慎操作)
# 在主节点上保留数据,其他节点清理
ssh server2 "rm -rf /opt/consul/data/raft/*"
ssh server3 "rm -rf /opt/consul/data/raft/*"
# 4. 重新启动集群
# 先启动主节点
ssh server1 "systemctl start consul"
# 等待主节点稳定后启动其他节点
sleep 10
ssh server2 "systemctl start consul"
ssh server3 "systemctl start consul"
# 5. 验证集群状态
consul members
consul operator raft list-peers
数据恢复
java
# 1. 创建快照
consul snapshot save backup.snap
# 2. 恢复快照
consul snapshot restore backup.snap
# 3. 自动化备份脚本
#!/bin/bash
# backup_consul.sh
BACKUP_DIR="/opt/consul/backups"
DATE=$(date +%Y%m%d_%H%M%S)
SNAPSHOT_FILE="${BACKUP_DIR}/consul_${DATE}.snap"
# 创建备份目录
mkdir -p $BACKUP_DIR
# 创建快照
consul snapshot save $SNAPSHOT_FILE
# 验证快照
consul snapshot inspect $SNAPSHOT_FILE
# 保留最近7天的备份
find $BACKUP_DIR -name "consul_*.snap" -mtime +7 -delete
echo"备份完成:$SNAPSHOT_FILE"
# 添加到crontab
# 0 2 * * * /opt/consul/scripts/backup_consul.sh
性能调优
java
{
"performance": {
"raft_multiplier": 1,
"rpc_hold_timeout": "7s"
},
"limits": {
"http_max_conns_per_client": 200,
"https_handshake_timeout": "5s",
"rpc_handshake_timeout": "5s",
"rpc_max_burst": 1000,
"rpc_max_conns_per_client": 100,
"rpc_rate": 100,
"kv_max_value_size": 1048576
},
"ports": {
"server": 8300,
"serf_lan": 8301,
"serf_wan": 8302,
"http": 8500,
"https": -1,
"grpc": 8502,
"dns": 8600
}
}
实战案例
微服务电商平台
java
# 服务架构设计
services:
# 用户服务
user-service:
instances:3
tags:[user,core]
health_check:http://localhost:8080/health
# 订单服务
order-service:
instances:2
tags:[order,business]
health_check:http://localhost:8081/health
# 支付服务
payment-service:
instances:2
tags:[payment,critical]
health_check:http://localhost:8082/health
# 通知服务
notification-service:
instances:1
tags:[notification,support]
health_check:http://localhost:8083/health
# 配置管理
configurations:
database:
user_db_url:mysql://user-db:3306/users
order_db_url:mysql://order-db:3306/orders
payment_db_url:mysql://payment-db:3306/payments
redis:
cache_url:redis://redis-cluster:6379
session_url:redis://redis-session:6379
messaging:
rabbitmq_url:amqp://rabbitmq:5672
kafka_brokers:kafka1:9092,kafka2:9092,kafka3:9092
服务注册配置
java
// 统一服务注册配置
@Configuration
publicclass ConsulServiceRegistration {
@Value("${spring.application.name}")
private String serviceName;
@Value("${server.port}")
privateint port;
@Autowired
private ConsulClient consulClient;
@PostConstruct
public void registerService() {
String serviceId = serviceName + "-" + getHostname() + "-" + port;
NewService service = new NewService();
service.setId(serviceId);
service.setName(serviceName);
service.setAddress(getLocalIpAddress());
service.setPort(port);
// 添加标签
service.setTags(Arrays.asList(
"version=" + getVersion(),
"environment=" + getEnvironment(),
"zone=" + getZone()
));
// 添加元数据
Map<String, String> meta = new HashMap<>();
meta.put("version", getVersion());
meta.put("git-commit", getGitCommit());
meta.put("build-time", getBuildTime());
service.setMeta(meta);
// 健康检查
NewService.Check check = new NewService.Check();
check.setHttp("http://" + getLocalIpAddress() + ":" + port + "/actuator/health");
check.setInterval("10s");
check.setTimeout("3s");
check.setDeregisterCriticalServiceAfter("30s");
service.setCheck(check);
// 注册服务
consulClient.agentServiceRegister(service);
log.info("服务注册成功:{}", serviceId);
// 添加优雅关闭钩子
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
consulClient.agentServiceDeregister(serviceId);
log.info("服务注销成功:{}", serviceId);
}));
}
private String getHostname() {
try {
return InetAddress.getLocalHost().getHostName();
} catch (Exception e) {
return"unknown";
}
}
private String getLocalIpAddress() {
try {
return InetAddress.getLocalHost().getHostAddress();
} catch (Exception e) {
return"127.0.0.1";
}
}
private String getVersion() {
return getClass().getPackage().getImplementationVersion() != null ?
getClass().getPackage().getImplementationVersion() : "unknown";
}
private String getEnvironment() {
return System.getProperty("spring.profiles.active", "default");
}
private String getZone() {
return System.getProperty("deployment.zone", "default");
}
private String getGitCommit() {
try {
InputStream is = getClass().getResourceAsStream("/META-INF/build-info.properties");
Properties props = new Properties();
props.load(is);
return props.getProperty("build.revision", "unknown");
} catch (Exception e) {
return"unknown";
}
}
private String getBuildTime() {
try {
InputStream is = getClass().getResourceAsStream("/META-INF/build-info.properties");
Properties props = new Properties();
props.load(is);
return props.getProperty("build.time", "unknown");
} catch (Exception e) {
return"unknown";
}
}
}
配置中心实现
java
@Component
@Slf4j
publicclass ConsulConfigManager {
@Autowired
private ConsulClient consulClient;
@Autowired
private ApplicationEventPublisher eventPublisher;
privatefinal Map<String, String> configCache = new ConcurrentHashMap<>();
privatefinal Map<String, Long> watchIndexes = new ConcurrentHashMap<>();
@PostConstruct
public void init() {
// 初始化配置监听
startConfigWatcher("config/common/");
startConfigWatcher("config/" + getServiceName() + "/");
}
@Async
public void startConfigWatcher(String keyPrefix) {
while (true) {
try {
Long lastIndex = watchIndexes.getOrDefault(keyPrefix, 0L);
QueryParams queryParams = QueryParams.Builder.builder()
.setIndex(lastIndex)
.setWaitTime(30)
.build();
Response<List<GetValue>> response = consulClient.getKVValues(keyPrefix, queryParams);
if (response.getConsulIndex() > lastIndex) {
watchIndexes.put(keyPrefix, response.getConsulIndex());
if (response.getValue() != null) {
updateConfigs(response.getValue());
}
}
} catch (Exception e) {
log.error("配置监听失败:{}", keyPrefix, e);
try {
Thread.sleep(5000);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
break;
}
}
}
}
private void updateConfigs(List<GetValue> values) {
Map<String, String> changes = new HashMap<>();
for (GetValue value : values) {
String key = value.getKey();
String newValue = value.getDecodedValue();
String oldValue = configCache.put(key, newValue);
if (!Objects.equals(oldValue, newValue)) {
changes.put(key, newValue);
log.info("配置更新:{} = {}", key, newValue);
}
}
if (!changes.isEmpty()) {
eventPublisher.publishEvent(new ConfigChangeEvent(this, changes));
}
}
public String getConfig(String key) {
String value = configCache.get(key);
if (value == null) {
// 从Consul获取
Response<GetValue> response = consulClient.getKVValue(key);
if (response.getValue() != null) {
value = response.getValue().getDecodedValue();
configCache.put(key, value);
}
}
return value;
}
public String getConfig(String key, String defaultValue) {
String value = getConfig(key);
return value != null ? value : defaultValue;
}
public void putConfig(String key, String value) {
consulClient.setKVValue(key, value);
configCache.put(key, value);
log.info("配置设置:{} = {}", key, value);
}
private String getServiceName() {
return System.getProperty("spring.application.name", "unknown");
}
}
java
@Component
@Slf4j
publicclass ConfigChangeHandler {
@Autowired
private DataSource dataSource;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@EventListener
public void handleConfigChange(ConfigChangeEvent event) {
Map<String, String> changes = event.getChanges();
for (Map.Entry<String, String> entry : changes.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
if (key.startsWith("config/database/")) {
handleDatabaseConfigChange(key, value);
} elseif (key.startsWith("config/redis/")) {
handleRedisConfigChange(key, value);
} elseif (key.startsWith("config/feature/")) {
handleFeatureToggleChange(key, value);
}
}
}
private void handleDatabaseConfigChange(String key, String value) {
log.info("处理数据库配置变更:{} = {}", key, value);
if (dataSource instanceof HikariDataSource) {
HikariDataSource hikariDs = (HikariDataSource) dataSource;
if (key.endsWith("/max-pool-size")) {
int maxPoolSize = Integer.parseInt(value);
hikariDs.setMaximumPoolSize(maxPoolSize);
log.info("更新数据库连接池大小:{}", maxPoolSize);
} elseif (key.endsWith("/connection-timeout")) {
long timeout = Long.parseLong(value);
hikariDs.setConnectionTimeout(timeout);
log.info("更新数据库连接超时:{}", timeout);
}
}
}
private void handleRedisConfigChange(String key, String value) {
log.info("处理Redis配置变更:{} = {}", key, value);
// 这里可以实现Redis连接池的动态更新
// 由于Spring Redis的限制,通常需要重新创建连接工厂
}
private void handleFeatureToggleChange(String key, String value) {
log.info("处理功能开关变更:{} = {}", key, value);
// 更新功能开关缓存
String featureName = key.substring(key.lastIndexOf("/") + 1);
boolean enabled = Boolean.parseBoolean(value);
FeatureToggleManager.updateFeature(featureName, enabled);
}
}
最佳实践
1. 服务命名规范
java
# 服务命名规范
naming_convention:
service_name:
pattern:"^[a-z][a-z0-9-]*[a-z0-9]$"
examples:
-user-service
-order-service
-payment-gateway
-notification-service
service_id:
pattern:"{service-name}-{hostname}-{port}"
examples:
-user-service-node1-8080
-order-service-node2-8081
tags:
required:[version,environment]
optional:[zone,cluster,team]
examples:
-version=1.0.0
-environment=production
-zone=us-east-1a
-cluster=main
-team=backend
2. 健康检查策略
java
health_check_strategy:
http_check:
path:/actuator/health
interval:10s
timeout:3s
deregister_critical_service_after:30s
tcp_check:
interval:30s
timeout:5s
script_check:
interval:60s
timeout:10s
grpc_check:
interval:15s
timeout:5s
3. 配置管理规范
java
config_structure:
global:
prefix:"config/global/"
keys:
-logging/level
-monitoring/enabled
-security/jwt/secret
service_specific:
prefix:"config/{service-name}/"
keys:
-database/url
-database/pool-size
-redis/host
-features/enabled
environment_specific:
prefix:"config/{service-name}/{environment}/"
keys:
-database/password
-external-api/endpoints
sensitive_data:
encryption:required
access_control:strict
audit:enabled
4. 安全加固
java
security_hardening:
acl:
enabled:true
default_policy:deny
token_ttl:24h
encryption:
gossip:enabled
rpc:enabled
network:
bind_addr:private_ip
client_addr:"127.0.0.1"
authentication:
method:jwt
provider:external_oauth
authorization:
service_policies:granular
kv_policies:path_based
audit:
enabled:true
log_level:info
retention:90d
5. 监控告警
java
monitoring:
metrics:
-consul_up
-consul_raft_leader
-consul_catalog_services
-consul_catalog_nodes
-consul_health_service_status
-consul_kvs_apply_rate
alerts:
-name:ConsulDown
condition:consul_up==0
severity:critical
-name:NoLeader
condition:consul_raft_leader==0
severity:critical
-name:ServiceDown
condition:consul_health_service_status{status="critical"}>0
severity:warning
-name:HighKVLoad
condition:rate(consul_kvs_apply[5m])>100
severity:warning
logging:
level:INFO
format:json
destinations:
-file:/var/log/consul/consul.log
-syslog:enabled
-stdout:enabled
6. 高可用部署
java
high_availability:
cluster_size:3# 奇数个节点
server_distribution:
availability_zones:3
nodes_per_zone:1
client_distribution:
co_located:true# 与应用服务同节点
backup_strategy:
frequency:daily
retention:30d
verification:automatic
disaster_recovery:
rpo:1h# Recovery Point Objective
rto:15m# Recovery Time Objective
network_partitioning:
handling:quorum_based
split_brain_prevention:enabled
总结
Consul作为HashiCorp开源的服务网格解决方案,为现代微服务架构提供了完整的服务发现、配置管理和安全通信能力。通过本文的学习,您应该掌握:
核心概念:Agent、Service、Node、Datacenter等基本概念
服务治理:服务注册发现、健康检查、负载均衡
配置管理:基于KV存储的动态配置管理
安全控制:ACL权限控制、服务间安全通信
集群管理:多节点集群、多数据中心部署
运维监控:健康监控、性能指标、故障排查
进阶学习建议
深入架构:学习Raft共识算法和分布式系统原理
服务网格:深入了解Consul Connect和Envoy集成
企业特性:学习Consul Enterprise的高级功能
云原生集成:与Kubernetes、Istio等平台集成
性能调优:大规模集群的性能优化和调优
Consul是构建可靠微服务架构的重要基础设施,掌握它对于现代应用开发和运维至关重要。