学习目标
通过本章学习,你将能够: - 理解 Nacos 与 Spring Cloud 的集成原理 - 掌握 Spring Cloud Alibaba 的使用 - 实现服务注册与发现的集成 - 配置动态配置管理 - 实现负载均衡和服务调用 - 掌握配置热更新和服务治理
1. 集成概述
1.1 Spring Cloud Alibaba 简介
from enum import Enum
from dataclasses import dataclass, field
from typing import List, Dict, Optional, Any, Callable
import time
import threading
import json
import random
from datetime import datetime
class ServiceStatus(Enum):
"""服务状态枚举"""
UP = "UP" # 服务正常
DOWN = "DOWN" # 服务下线
OUT_OF_SERVICE = "OUT_OF_SERVICE" # 服务停止
UNKNOWN = "UNKNOWN" # 状态未知
class LoadBalanceStrategy(Enum):
"""负载均衡策略枚举"""
ROUND_ROBIN = "ROUND_ROBIN" # 轮询
RANDOM = "RANDOM" # 随机
WEIGHTED_RANDOM = "WEIGHTED_RANDOM" # 加权随机
LEAST_ACTIVE = "LEAST_ACTIVE" # 最少活跃
CONSISTENT_HASH = "CONSISTENT_HASH" # 一致性哈希
class ConfigFormat(Enum):
"""配置格式枚举"""
PROPERTIES = "properties" # Properties 格式
YAML = "yaml" # YAML 格式
JSON = "json" # JSON 格式
XML = "xml" # XML 格式
TEXT = "text" # 纯文本格式
class RefreshScope(Enum):
"""刷新范围枚举"""
SINGLETON = "singleton" # 单例刷新
PROTOTYPE = "prototype" # 原型刷新
REQUEST = "request" # 请求范围刷新
SESSION = "session" # 会话范围刷新
@dataclass
class ServiceInstance:
"""服务实例信息"""
service_id: str # 服务 ID
instance_id: str # 实例 ID
host: str # 主机地址
port: int # 端口号
secure: bool = False # 是否使用 HTTPS
metadata: Dict[str, str] = field(default_factory=dict)
status: ServiceStatus = ServiceStatus.UP
weight: float = 1.0 # 权重
cluster_name: str = "DEFAULT" # 集群名称
namespace_id: str = "public" # 命名空间
group_name: str = "DEFAULT_GROUP" # 分组名称
ephemeral: bool = True # 是否临时实例
enabled: bool = True # 是否启用
healthy: bool = True # 是否健康
def get_uri(self) -> str:
"""获取服务 URI"""
scheme = "https" if self.secure else "http"
return f"{scheme}://{self.host}:{self.port}"
def get_service_url(self, path: str = "") -> str:
"""获取服务完整 URL"""
base_uri = self.get_uri()
if path.startswith("/"):
return f"{base_uri}{path}"
return f"{base_uri}/{path}" if path else base_uri
@dataclass
class ConfigProperty:
"""配置属性"""
data_id: str # 配置 ID
group: str = "DEFAULT_GROUP" # 配置分组
namespace_id: str = "public" # 命名空间
content: str = "" # 配置内容
config_type: ConfigFormat = ConfigFormat.PROPERTIES
timeout: int = 3000 # 超时时间
auto_refresh: bool = True # 自动刷新
refresh_scope: RefreshScope = RefreshScope.SINGLETON
listeners: List[Callable[[str], None]] = field(default_factory=list)
last_modified: float = field(default_factory=time.time)
def add_listener(self, listener: Callable[[str], None]):
"""添加配置变更监听器"""
self.listeners.append(listener)
def notify_change(self, new_content: str):
"""通知配置变更"""
old_content = self.content
self.content = new_content
self.last_modified = time.time()
for listener in self.listeners:
try:
listener(new_content)
except Exception as e:
print(f"配置变更监听器执行失败: {e}")
@dataclass
class CircuitBreakerConfig:
"""熔断器配置"""
failure_threshold: int = 5 # 失败阈值
timeout: int = 60000 # 超时时间(毫秒)
retry_timeout: int = 10000 # 重试超时(毫秒)
success_threshold: int = 3 # 成功阈值
enabled: bool = True # 是否启用
class SpringCloudNacosIntegration:
"""Spring Cloud Nacos 集成管理器"""
def __init__(self, server_addr: str = "localhost:8848",
namespace: str = "public"):
self.server_addr = server_addr
self.namespace = namespace
self.services: Dict[str, List[ServiceInstance]] = {}
self.configs: Dict[str, ConfigProperty] = {}
self.load_balancer_strategy = LoadBalanceStrategy.ROUND_ROBIN
self.circuit_breaker_config = CircuitBreakerConfig()
self._lock = threading.RLock()
self._running = False
self._threads = []
def start(self):
"""启动集成服务"""
with self._lock:
if self._running:
print("⚠️ Spring Cloud Nacos 集成已经在运行中")
return
self._running = True
# 启动服务发现线程
discovery_thread = threading.Thread(target=self._service_discovery_worker, daemon=True)
discovery_thread.start()
self._threads.append(discovery_thread)
# 启动配置监听线程
config_thread = threading.Thread(target=self._config_listener_worker, daemon=True)
config_thread.start()
self._threads.append(config_thread)
# 启动健康检查线程
health_thread = threading.Thread(target=self._health_check_worker, daemon=True)
health_thread.start()
self._threads.append(health_thread)
print(f"✅ Spring Cloud Nacos 集成启动成功")
def stop(self):
"""停止集成服务"""
with self._lock:
if not self._running:
print("⚠️ Spring Cloud Nacos 集成未在运行")
return
self._running = False
# 等待线程结束
for thread in self._threads:
if thread.is_alive():
thread.join(timeout=5)
print(f"✅ Spring Cloud Nacos 集成已停止")
def register_service(self, service_instance: ServiceInstance) -> bool:
"""注册服务实例"""
with self._lock:
service_id = service_instance.service_id
if service_id not in self.services:
self.services[service_id] = []
# 检查实例是否已存在
for instance in self.services[service_id]:
if (instance.host == service_instance.host and
instance.port == service_instance.port):
print(f"⚠️ 服务实例已存在: {service_instance.instance_id}")
return False
self.services[service_id].append(service_instance)
print(f"✅ 服务注册成功: {service_instance.service_id} - {service_instance.get_uri()}")
return True
def deregister_service(self, service_id: str, instance_id: str) -> bool:
"""注销服务实例"""
with self._lock:
if service_id not in self.services:
print(f"❌ 服务不存在: {service_id}")
return False
instances = self.services[service_id]
for i, instance in enumerate(instances):
if instance.instance_id == instance_id:
instances.pop(i)
print(f"✅ 服务注销成功: {instance_id}")
# 如果没有实例了,删除服务
if not instances:
del self.services[service_id]
return True
print(f"❌ 服务实例不存在: {instance_id}")
return False
def discover_services(self, service_id: str) -> List[ServiceInstance]:
"""发现服务实例"""
with self._lock:
if service_id not in self.services:
return []
# 返回健康的实例
healthy_instances = []
for instance in self.services[service_id]:
if instance.enabled and instance.healthy and instance.status == ServiceStatus.UP:
healthy_instances.append(instance)
return healthy_instances
def get_service_instance(self, service_id: str) -> Optional[ServiceInstance]:
"""根据负载均衡策略获取服务实例"""
instances = self.discover_services(service_id)
if not instances:
print(f"❌ 没有可用的服务实例: {service_id}")
return None
return self._select_instance(instances)
def _select_instance(self, instances: List[ServiceInstance]) -> ServiceInstance:
"""根据负载均衡策略选择实例"""
if self.load_balancer_strategy == LoadBalanceStrategy.ROUND_ROBIN:
return self._round_robin_select(instances)
elif self.load_balancer_strategy == LoadBalanceStrategy.RANDOM:
return random.choice(instances)
elif self.load_balancer_strategy == LoadBalanceStrategy.WEIGHTED_RANDOM:
return self._weighted_random_select(instances)
else:
return instances[0]
def _round_robin_select(self, instances: List[ServiceInstance]) -> ServiceInstance:
"""轮询选择"""
if not hasattr(self, '_round_robin_index'):
self._round_robin_index = 0
instance = instances[self._round_robin_index % len(instances)]
self._round_robin_index += 1
return instance
def _weighted_random_select(self, instances: List[ServiceInstance]) -> ServiceInstance:
"""加权随机选择"""
total_weight = sum(instance.weight for instance in instances)
if total_weight == 0:
return random.choice(instances)
random_weight = random.uniform(0, total_weight)
current_weight = 0
for instance in instances:
current_weight += instance.weight
if current_weight >= random_weight:
return instance
return instances[-1]
def register_config(self, config_property: ConfigProperty):
"""注册配置"""
with self._lock:
config_key = f"{config_property.data_id}#{config_property.group}#{config_property.namespace_id}"
self.configs[config_key] = config_property
print(f"✅ 配置注册成功: {config_property.data_id}")
def get_config(self, data_id: str, group: str = "DEFAULT_GROUP",
namespace_id: str = "public") -> Optional[str]:
"""获取配置"""
with self._lock:
config_key = f"{data_id}#{group}#{namespace_id}"
if config_key in self.configs:
return self.configs[config_key].content
return None
def update_config(self, data_id: str, content: str, group: str = "DEFAULT_GROUP",
namespace_id: str = "public") -> bool:
"""更新配置"""
with self._lock:
config_key = f"{data_id}#{group}#{namespace_id}"
if config_key in self.configs:
config_property = self.configs[config_key]
config_property.notify_change(content)
print(f"✅ 配置更新成功: {data_id}")
return True
print(f"❌ 配置不存在: {data_id}")
return False
def add_config_listener(self, data_id: str, listener: Callable[[str], None],
group: str = "DEFAULT_GROUP", namespace_id: str = "public"):
"""添加配置监听器"""
with self._lock:
config_key = f"{data_id}#{group}#{namespace_id}"
if config_key in self.configs:
self.configs[config_key].add_listener(listener)
print(f"✅ 配置监听器添加成功: {data_id}")
else:
print(f"❌ 配置不存在: {data_id}")
def _service_discovery_worker(self):
"""服务发现工作线程"""
while self._running:
try:
# 模拟服务发现更新
with self._lock:
for service_id, instances in self.services.items():
for instance in instances:
# 模拟实例状态变化
if random.random() < 0.05: # 5% 概率状态变化
if instance.status == ServiceStatus.UP:
instance.status = ServiceStatus.DOWN
instance.healthy = False
print(f"⚠️ 服务实例状态变为 DOWN: {instance.instance_id}")
else:
instance.status = ServiceStatus.UP
instance.healthy = True
print(f"✅ 服务实例状态恢复 UP: {instance.instance_id}")
time.sleep(10) # 每10秒检查一次
except Exception as e:
print(f"❌ 服务发现工作线程异常: {e}")
time.sleep(1)
def _config_listener_worker(self):
"""配置监听工作线程"""
while self._running:
try:
# 模拟配置变更
with self._lock:
for config_key, config_property in self.configs.items():
if config_property.auto_refresh and random.random() < 0.02: # 2% 概率配置变更
new_content = f"updated_content_{int(time.time())}"
config_property.notify_change(new_content)
print(f"🔄 配置自动更新: {config_property.data_id}")
time.sleep(30) # 每30秒检查一次
except Exception as e:
print(f"❌ 配置监听工作线程异常: {e}")
time.sleep(1)
def _health_check_worker(self):
"""健康检查工作线程"""
while self._running:
try:
with self._lock:
for service_id, instances in self.services.items():
for instance in instances:
# 模拟健康检查
if random.random() < 0.1: # 10% 概率健康状态变化
instance.healthy = not instance.healthy
status = "健康" if instance.healthy else "不健康"
print(f"🏥 健康检查: {instance.instance_id} - {status}")
time.sleep(15) # 每15秒检查一次
except Exception as e:
print(f"❌ 健康检查工作线程异常: {e}")
time.sleep(1)
def get_service_statistics(self) -> Dict[str, Any]:
"""获取服务统计信息"""
with self._lock:
stats = {
"total_services": len(self.services),
"total_instances": sum(len(instances) for instances in self.services.values()),
"healthy_instances": 0,
"total_configs": len(self.configs),
"services": {},
"load_balancer_strategy": self.load_balancer_strategy.value
}
for service_id, instances in self.services.items():
healthy_count = sum(1 for instance in instances if instance.healthy)
stats["healthy_instances"] += healthy_count
stats["services"][service_id] = {
"total_instances": len(instances),
"healthy_instances": healthy_count,
"instances": [
{
"instance_id": instance.instance_id,
"uri": instance.get_uri(),
"status": instance.status.value,
"healthy": instance.healthy,
"weight": instance.weight
}
for instance in instances
]
}
return stats
# 使用示例
if __name__ == "__main__":
# 创建 Spring Cloud Nacos 集成
integration = SpringCloudNacosIntegration("localhost:8848", "public")
print("=== Spring Cloud Nacos 集成示例 ===")
# 启动集成服务
integration.start()
# 注册服务实例
user_service_1 = ServiceInstance(
service_id="user-service",
instance_id="user-service-1",
host="192.168.1.101",
port=8080,
weight=1.0,
metadata={"version": "1.0.0", "zone": "zone-a"}
)
user_service_2 = ServiceInstance(
service_id="user-service",
instance_id="user-service-2",
host="192.168.1.102",
port=8080,
weight=1.5,
metadata={"version": "1.0.0", "zone": "zone-b"}
)
order_service = ServiceInstance(
service_id="order-service",
instance_id="order-service-1",
host="192.168.1.103",
port=8081,
weight=1.0,
metadata={"version": "2.0.0", "zone": "zone-a"}
)
integration.register_service(user_service_1)
integration.register_service(user_service_2)
integration.register_service(order_service)
# 注册配置
database_config = ConfigProperty(
data_id="database.properties",
group="DEFAULT_GROUP",
namespace_id="public",
content="spring.datasource.url=jdbc:mysql://localhost:3306/test\nspring.datasource.username=root",
config_type=ConfigFormat.PROPERTIES,
auto_refresh=True
)
redis_config = ConfigProperty(
data_id="redis.yaml",
group="DEFAULT_GROUP",
namespace_id="public",
content="redis:\n host: localhost\n port: 6379\n timeout: 3000",
config_type=ConfigFormat.YAML,
auto_refresh=True
)
integration.register_config(database_config)
integration.register_config(redis_config)
# 添加配置监听器
def on_database_config_change(new_content: str):
print(f"📝 数据库配置变更: {new_content[:50]}...")
def on_redis_config_change(new_content: str):
print(f"📝 Redis 配置变更: {new_content[:50]}...")
integration.add_config_listener("database.properties", on_database_config_change)
integration.add_config_listener("redis.yaml", on_redis_config_change)
# 服务发现测试
print("\n=== 服务发现测试 ===")
for i in range(5):
instance = integration.get_service_instance("user-service")
if instance:
print(f"请求 {i+1}: {instance.instance_id} - {instance.get_uri()}")
time.sleep(1)
# 配置获取测试
print("\n=== 配置管理测试 ===")
db_config = integration.get_config("database.properties")
print(f"数据库配置: {db_config[:50]}...")
redis_config_content = integration.get_config("redis.yaml")
print(f"Redis 配置: {redis_config_content[:50]}...")
# 配置更新测试
print("\n=== 配置更新测试 ===")
new_db_config = "spring.datasource.url=jdbc:mysql://localhost:3306/prod\nspring.datasource.username=admin"
integration.update_config("database.properties", new_db_config)
# 等待一段时间观察自动更新
print("\n=== 等待自动更新 ===")
time.sleep(35)
# 显示统计信息
print("\n=== 服务统计信息 ===")
stats = integration.get_service_statistics()
print(f"总服务数: {stats['total_services']}")
print(f"总实例数: {stats['total_instances']}")
print(f"健康实例数: {stats['healthy_instances']}")
print(f"总配置数: {stats['total_configs']}")
print(f"负载均衡策略: {stats['load_balancer_strategy']}")
for service_id, service_info in stats['services'].items():
print(f"\n服务 {service_id}:")
print(f" 实例数: {service_info['total_instances']}")
print(f" 健康实例数: {service_info['healthy_instances']}")
for instance_info in service_info['instances']:
print(f" - {instance_info['instance_id']}: {instance_info['uri']} ({instance_info['status']})")
# 停止集成服务
time.sleep(5)
integration.stop()
2. 项目配置
2.1 Maven 依赖配置
<!-- pom.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>nacos-spring-cloud-demo</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<name>Nacos Spring Cloud Demo</name>
<description>Nacos Spring Cloud Integration Demo</description>
<properties>
<java.version>11</java.version>
<spring-boot.version>2.7.8</spring-boot.version>
<spring-cloud.version>2021.0.5</spring-cloud.version>
<spring-cloud-alibaba.version>2021.0.4.0</spring-cloud-alibaba.version>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencyManagement>
<dependencies>
<!-- Spring Boot BOM -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Spring Cloud BOM -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Spring Cloud Alibaba BOM -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- Spring Boot Starter Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Boot Starter Actuator -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- Spring Cloud Starter Bootstrap -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<!-- Nacos Discovery -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- Nacos Config -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- Spring Cloud Loadbalancer -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<!-- Spring Cloud OpenFeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- Spring Cloud Circuit Breaker -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
</dependency>
<!-- Spring Cloud Gateway (可选) -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- Spring Boot Configuration Processor -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!-- Validation -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- JSON Processing -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<!-- Test Dependencies -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-contract-stub-runner</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<configuration>
<excludes>
<exclude>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>${project.build.sourceEncoding}</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M8</version>
<configuration>
<skipTests>false</skipTests>
</configuration>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<releases>
<enabled>false</enabled>
</releases>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
<pluginRepository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<releases>
<enabled>false</enabled>
</releases>
</pluginRepository>
</pluginRepositories>
</project>
2.2 应用配置文件
# bootstrap.yml
# Bootstrap 配置文件,优先级高于 application.yml
spring:
application:
name: nacos-spring-cloud-demo
profiles:
active: dev
cloud:
nacos:
# 服务发现配置
discovery:
server-addr: localhost:8848
namespace: public
group: DEFAULT_GROUP
cluster-name: DEFAULT
service: ${spring.application.name}
weight: 1
network-interface: eth0
ip: 192.168.1.100
port: ${server.port:8080}
secure: false
access-key:
secret-key:
log-name: nacos-discovery.log
endpoint:
metadata:
version: 1.0.0
zone: zone-a
cluster: default
watch-delay: 30000
heart-beat-interval: 5000
heart-beat-timeout: 15000
ip-delete-timeout: 30000
instance-enabled: true
ephemeral: true
# 配置管理配置
config:
server-addr: localhost:8848
namespace: public
group: DEFAULT_GROUP
prefix: ${spring.application.name}
file-extension: yml
timeout: 3000
max-retry: 3
config-retry-time: 2000
config-long-poll-timeout: 46000
enable-remote-sync-config: true
access-key:
secret-key:
endpoint:
# 扩展配置
extension-configs:
- data-id: database.yml
group: DEFAULT_GROUP
refresh: true
- data-id: redis.yml
group: DEFAULT_GROUP
refresh: true
- data-id: mq.yml
group: DEFAULT_GROUP
refresh: true
# 共享配置
shared-configs:
- data-id: common.yml
group: DEFAULT_GROUP
refresh: true
- data-id: logging.yml
group: DEFAULT_GROUP
refresh: true
# 日志配置
logging:
level:
com.alibaba.nacos: DEBUG
org.springframework.cloud.alibaba: DEBUG
com.example: DEBUG
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"
file: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"
file:
name: logs/nacos-spring-cloud-demo.log
max-size: 100MB
max-history: 30
# application.yml
# 应用主配置文件
server:
port: 8080
servlet:
context-path: /api
tomcat:
max-threads: 200
min-spare-threads: 10
max-connections: 8192
accept-count: 100
connection-timeout: 20000
spring:
application:
name: nacos-spring-cloud-demo
# 数据源配置(从 Nacos 配置中心获取)
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
username: root
password: password
hikari:
minimum-idle: 5
maximum-pool-size: 20
auto-commit: true
idle-timeout: 30000
pool-name: DatebookHikariCP
max-lifetime: 1800000
connection-timeout: 30000
connection-test-query: SELECT 1
# Redis 配置(从 Nacos 配置中心获取)
redis:
host: localhost
port: 6379
password:
timeout: 3000ms
database: 0
lettuce:
pool:
max-active: 8
max-wait: -1ms
max-idle: 8
min-idle: 0
# Jackson 配置
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
default-property-inclusion: non_null
serialization:
write-dates-as-timestamps: false
fail-on-empty-beans: false
deserialization:
fail-on-unknown-properties: false
# Cloud 配置
cloud:
# 负载均衡配置
loadbalancer:
ribbon:
enabled: false
cache:
enabled: true
ttl: 35s
capacity: 256
health-check:
initial-delay: 0
interval: 25s
# OpenFeign 配置
openfeign:
client:
config:
default:
connect-timeout: 5000
read-timeout: 10000
logger-level: basic
user-service:
connect-timeout: 3000
read-timeout: 8000
logger-level: full
compression:
request:
enabled: true
mime-types: text/xml,application/xml,application/json
min-request-size: 2048
response:
enabled: true
metrics:
enabled: true
# 熔断器配置
circuitbreaker:
resilience4j:
enabled: true
instances:
user-service:
failure-rate-threshold: 50
wait-duration-in-open-state: 30s
sliding-window-size: 10
minimum-number-of-calls: 5
permitted-number-of-calls-in-half-open-state: 3
automatic-transition-from-open-to-half-open-enabled: true
# Gateway 配置(如果使用网关)
gateway:
discovery:
locator:
enabled: true
lower-case-service-id: true
routes:
- id: user-service
uri: lb://user-service
predicates:
- Path=/user/**
filters:
- StripPrefix=1
- id: order-service
uri: lb://order-service
predicates:
- Path=/order/**
filters:
- StripPrefix=1
# 监控配置
management:
endpoints:
web:
exposure:
include: '*'
endpoint:
health:
show-details: always
metrics:
enabled: true
metrics:
export:
prometheus:
enabled: true
distribution:
percentiles-histogram:
http.server.requests: true
percentiles:
http.server.requests: 0.5, 0.9, 0.95, 0.99
health:
nacos:
enabled: true
# Feign 日志配置
feign:
client:
config:
default:
logger-level: BASIC
httpclient:
enabled: true
max-connections: 200
max-connections-per-route: 50
connection-timeout: 2000
connection-timer-repeat: 3000
compression:
request:
enabled: true
response:
enabled: true
# 自定义配置
app:
name: ${spring.application.name}
version: 1.0.0
description: Nacos Spring Cloud Integration Demo
author: Developer
# 业务配置
business:
user:
default-page-size: 20
max-page-size: 100
cache-ttl: 300
order:
timeout: 30
retry-times: 3
# 安全配置
security:
jwt:
secret: mySecretKey
expiration: 86400
cors:
allowed-origins: "*"
allowed-methods: GET,POST,PUT,DELETE,OPTIONS
allowed-headers: "*"
max-age: 3600
2.3 环境特定配置
# application-dev.yml
# 开发环境配置
spring:
cloud:
nacos:
discovery:
server-addr: localhost:8848
namespace: dev
config:
server-addr: localhost:8848
namespace: dev
logging:
level:
root: INFO
com.example: DEBUG
com.alibaba.nacos: DEBUG
app:
business:
user:
cache-ttl: 60 # 开发环境缓存时间短
# application-test.yml
# 测试环境配置
spring:
cloud:
nacos:
discovery:
server-addr: nacos-test.example.com:8848
namespace: test
config:
server-addr: nacos-test.example.com:8848
namespace: test
logging:
level:
root: WARN
com.example: INFO
app:
business:
user:
cache-ttl: 180 # 测试环境中等缓存时间
# application-prod.yml
# 生产环境配置
spring:
cloud:
nacos:
discovery:
server-addr: nacos-prod.example.com:8848
namespace: prod
cluster-name: PROD
config:
server-addr: nacos-prod.example.com:8848
namespace: prod
logging:
level:
root: WARN
com.example: INFO
app:
business:
user:
cache-ttl: 600 # 生产环境缓存时间长
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
3. 服务注册与发现
3.1 服务提供者
// UserServiceApplication.java
// 用户服务应用主类
package com.example.userservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class UserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceApplication.class, args);
}
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
// User.java
// 用户实体类
package com.example.userservice.entity;
import com.fasterxml.jackson.annotation.JsonFormat;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
import java.time.LocalDateTime;
public class User {
private Long id;
@NotBlank(message = "用户名不能为空")
@Size(min = 3, max = 20, message = "用户名长度必须在3-20之间")
private String username;
@NotBlank(message = "邮箱不能为空")
@Email(message = "邮箱格式不正确")
private String email;
private String phone;
private Integer status;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updateTime;
// 构造函数
public User() {}
public User(Long id, String username, String email) {
this.id = id;
this.username = username;
this.email = email;
this.status = 1;
this.createTime = LocalDateTime.now();
this.updateTime = LocalDateTime.now();
}
// Getter 和 Setter 方法
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public LocalDateTime getCreateTime() {
return createTime;
}
public void setCreateTime(LocalDateTime createTime) {
this.createTime = createTime;
}
public LocalDateTime getUpdateTime() {
return updateTime;
}
public void setUpdateTime(LocalDateTime updateTime) {
this.updateTime = updateTime;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", email='" + email + '\'' +
", phone='" + phone + '\'' +
", status=" + status +
", createTime=" + createTime +
", updateTime=" + updateTime +
'}';
}
}
// UserService.java
// 用户服务接口
package com.example.userservice.service;
import com.example.userservice.entity.User;
import java.util.List;
public interface UserService {
/**
* 根据 ID 获取用户
*/
User getUserById(Long id);
/**
* 根据用户名获取用户
*/
User getUserByUsername(String username);
/**
* 获取所有用户
*/
List<User> getAllUsers();
/**
* 创建用户
*/
User createUser(User user);
/**
* 更新用户
*/
User updateUser(Long id, User user);
/**
* 删除用户
*/
boolean deleteUser(Long id);
/**
* 分页查询用户
*/
List<User> getUsersByPage(int page, int size);
}
// UserServiceImpl.java
// 用户服务实现类
package com.example.userservice.service.impl;
import com.example.userservice.entity.User;
import com.example.userservice.service.UserService;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
@Service
public class UserServiceImpl implements UserService {
// 模拟数据库存储
private final Map<Long, User> userStorage = new ConcurrentHashMap<>();
private Long nextId = 1L;
public UserServiceImpl() {
// 初始化一些测试数据
initTestData();
}
private void initTestData() {
createUser(new User(null, "admin", "admin@example.com"));
createUser(new User(null, "user1", "user1@example.com"));
createUser(new User(null, "user2", "user2@example.com"));
}
@Override
public User getUserById(Long id) {
return userStorage.get(id);
}
@Override
public User getUserByUsername(String username) {
return userStorage.values().stream()
.filter(user -> user.getUsername().equals(username))
.findFirst()
.orElse(null);
}
@Override
public List<User> getAllUsers() {
return new ArrayList<>(userStorage.values());
}
@Override
public User createUser(User user) {
user.setId(nextId++);
user.setCreateTime(LocalDateTime.now());
user.setUpdateTime(LocalDateTime.now());
user.setStatus(1);
userStorage.put(user.getId(), user);
return user;
}
@Override
public User updateUser(Long id, User user) {
User existingUser = userStorage.get(id);
if (existingUser != null) {
user.setId(id);
user.setCreateTime(existingUser.getCreateTime());
user.setUpdateTime(LocalDateTime.now());
userStorage.put(id, user);
return user;
}
return null;
}
@Override
public boolean deleteUser(Long id) {
return userStorage.remove(id) != null;
}
@Override
public List<User> getUsersByPage(int page, int size) {
List<User> allUsers = getAllUsers();
int start = page * size;
int end = Math.min(start + size, allUsers.size());
if (start >= allUsers.size()) {
return Collections.emptyList();
}
return allUsers.subList(start, end);
}
}
// UserController.java
// 用户控制器
package com.example.userservice.controller;
import com.example.userservice.entity.User;
import com.example.userservice.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import javax.validation.constraints.Min;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/users")
@RefreshScope
@Validated
public class UserController {
@Autowired
private UserService userService;
@Value("${app.business.user.default-page-size:20}")
private int defaultPageSize;
@Value("${app.business.user.max-page-size:100}")
private int maxPageSize;
@Value("${spring.application.name}")
private String serviceName;
@Value("${server.port}")
private String serverPort;
/**
* 获取服务信息
*/
@GetMapping("/info")
public ResponseEntity<Map<String, Object>> getServiceInfo() {
Map<String, Object> info = new HashMap<>();
info.put("serviceName", serviceName);
info.put("serverPort", serverPort);
info.put("timestamp", System.currentTimeMillis());
info.put("defaultPageSize", defaultPageSize);
info.put("maxPageSize", maxPageSize);
return ResponseEntity.ok(info);
}
/**
* 根据 ID 获取用户
*/
@GetMapping("/{id}")
public ResponseEntity<User> getUserById(@PathVariable @Min(1) Long id) {
User user = userService.getUserById(id);
if (user != null) {
return ResponseEntity.ok(user);
}
return ResponseEntity.notFound().build();
}
/**
* 根据用户名获取用户
*/
@GetMapping("/username/{username}")
public ResponseEntity<User> getUserByUsername(@PathVariable String username) {
User user = userService.getUserByUsername(username);
if (user != null) {
return ResponseEntity.ok(user);
}
return ResponseEntity.notFound().build();
}
/**
* 获取所有用户
*/
@GetMapping
public ResponseEntity<List<User>> getAllUsers() {
List<User> users = userService.getAllUsers();
return ResponseEntity.ok(users);
}
/**
* 分页查询用户
*/
@GetMapping("/page")
public ResponseEntity<Map<String, Object>> getUsersByPage(
@RequestParam(defaultValue = "0") @Min(0) int page,
@RequestParam(defaultValue = "20") @Min(1) int size) {
// 限制页面大小
if (size > maxPageSize) {
size = maxPageSize;
}
List<User> users = userService.getUsersByPage(page, size);
int total = userService.getAllUsers().size();
Map<String, Object> result = new HashMap<>();
result.put("content", users);
result.put("page", page);
result.put("size", size);
result.put("total", total);
result.put("totalPages", (total + size - 1) / size);
result.put("serviceName", serviceName);
result.put("serverPort", serverPort);
return ResponseEntity.ok(result);
}
/**
* 创建用户
*/
@PostMapping
public ResponseEntity<User> createUser(@Valid @RequestBody User user) {
User createdUser = userService.createUser(user);
return ResponseEntity.ok(createdUser);
}
/**
* 更新用户
*/
@PutMapping("/{id}")
public ResponseEntity<User> updateUser(@PathVariable @Min(1) Long id,
@Valid @RequestBody User user) {
User updatedUser = userService.updateUser(id, user);
if (updatedUser != null) {
return ResponseEntity.ok(updatedUser);
}
return ResponseEntity.notFound().build();
}
/**
* 删除用户
*/
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteUser(@PathVariable @Min(1) Long id) {
boolean deleted = userService.deleteUser(id);
if (deleted) {
return ResponseEntity.ok().build();
}
return ResponseEntity.notFound().build();
}
/**
* 健康检查
*/
@GetMapping("/health")
public ResponseEntity<Map<String, Object>> health() {
Map<String, Object> health = new HashMap<>();
health.put("status", "UP");
health.put("serviceName", serviceName);
health.put("serverPort", serverPort);
health.put("timestamp", System.currentTimeMillis());
health.put("userCount", userService.getAllUsers().size());
return ResponseEntity.ok(health);
}
}
3.2 服务消费者
// OrderServiceApplication.java
// 订单服务应用主类
package com.example.orderservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class OrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
// UserServiceClient.java
// 用户服务 Feign 客户端
package com.example.orderservice.client;
import com.example.orderservice.entity.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@FeignClient(name = "user-service", path = "/api/users")
public interface UserServiceClient {
@GetMapping("/info")
Map<String, Object> getServiceInfo();
@GetMapping("/{id}")
User getUserById(@PathVariable("id") Long id);
@GetMapping("/username/{username}")
User getUserByUsername(@PathVariable("username") String username);
@GetMapping
List<User> getAllUsers();
@GetMapping("/page")
Map<String, Object> getUsersByPage(@RequestParam("page") int page,
@RequestParam("size") int size);
@PostMapping
User createUser(@RequestBody User user);
@PutMapping("/{id}")
User updateUser(@PathVariable("id") Long id, @RequestBody User user);
@DeleteMapping("/{id}")
void deleteUser(@PathVariable("id") Long id);
@GetMapping("/health")
Map<String, Object> health();
}
// Order.java
// 订单实体类
package com.example.orderservice.entity;
import com.fasterxml.jackson.annotation.JsonFormat;
import javax.validation.constraints.DecimalMin;
import javax.validation.constraints.NotNull;
import java.math.BigDecimal;
import java.time.LocalDateTime;
public class Order {
private Long id;
@NotNull(message = "用户ID不能为空")
private Long userId;
private String orderNo;
@DecimalMin(value = "0.01", message = "订单金额必须大于0")
private BigDecimal amount;
private Integer status; // 0-待支付, 1-已支付, 2-已取消
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updateTime;
// 关联用户信息(非持久化字段)
private User user;
// 构造函数
public Order() {}
public Order(Long userId, BigDecimal amount) {
this.userId = userId;
this.amount = amount;
this.status = 0;
this.createTime = LocalDateTime.now();
this.updateTime = LocalDateTime.now();
this.orderNo = generateOrderNo();
}
private String generateOrderNo() {
return "ORD" + System.currentTimeMillis() + (int)(Math.random() * 1000);
}
// Getter 和 Setter 方法
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public String getOrderNo() {
return orderNo;
}
public void setOrderNo(String orderNo) {
this.orderNo = orderNo;
}
public BigDecimal getAmount() {
return amount;
}
public void setAmount(BigDecimal amount) {
this.amount = amount;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public LocalDateTime getCreateTime() {
return createTime;
}
public void setCreateTime(LocalDateTime createTime) {
this.createTime = createTime;
}
public LocalDateTime getUpdateTime() {
return updateTime;
}
public void setUpdateTime(LocalDateTime updateTime) {
this.updateTime = updateTime;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
@Override
public String toString() {
return "Order{" +
"id=" + id +
", userId=" + userId +
", orderNo='" + orderNo + '\'' +
", amount=" + amount +
", status=" + status +
", createTime=" + createTime +
", updateTime=" + updateTime +
'}';
}
}
// User.java
// 用户实体类(订单服务中的副本)
package com.example.orderservice.entity;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.time.LocalDateTime;
public class User {
private Long id;
private String username;
private String email;
private String phone;
private Integer status;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updateTime;
// 构造函数
public User() {}
public User(Long id, String username, String email) {
this.id = id;
this.username = username;
this.email = email;
}
// Getter 和 Setter 方法
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public LocalDateTime getCreateTime() {
return createTime;
}
public void setCreateTime(LocalDateTime createTime) {
this.createTime = createTime;
}
public LocalDateTime getUpdateTime() {
return updateTime;
}
public void setUpdateTime(LocalDateTime updateTime) {
this.updateTime = updateTime;
}
}
// OrderService.java
// 订单服务接口
package com.example.orderservice.service;
import com.example.orderservice.entity.Order;
import java.util.List;
public interface OrderService {
Order createOrder(Order order);
Order getOrderById(Long id);
List<Order> getOrdersByUserId(Long userId);
List<Order> getAllOrders();
Order updateOrderStatus(Long id, Integer status);
boolean deleteOrder(Long id);
}
// OrderServiceImpl.java
// 订单服务实现类
package com.example.orderservice.service.impl;
import com.example.orderservice.client.UserServiceClient;
import com.example.orderservice.entity.Order;
import com.example.orderservice.entity.User;
import com.example.orderservice.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private UserServiceClient userServiceClient;
// 模拟数据库存储
private final Map<Long, Order> orderStorage = new ConcurrentHashMap<>();
private Long nextId = 1L;
@Override
public Order createOrder(Order order) {
// 验证用户是否存在
try {
User user = userServiceClient.getUserById(order.getUserId());
if (user == null) {
throw new RuntimeException("用户不存在: " + order.getUserId());
}
order.setId(nextId++);
order.setCreateTime(LocalDateTime.now());
order.setUpdateTime(LocalDateTime.now());
order.setUser(user);
orderStorage.put(order.getId(), order);
return order;
} catch (Exception e) {
throw new RuntimeException("创建订单失败: " + e.getMessage(), e);
}
}
@Override
public Order getOrderById(Long id) {
Order order = orderStorage.get(id);
if (order != null) {
// 填充用户信息
try {
User user = userServiceClient.getUserById(order.getUserId());
order.setUser(user);
} catch (Exception e) {
// 用户服务不可用时,不影响订单查询
System.err.println("获取用户信息失败: " + e.getMessage());
}
}
return order;
}
@Override
public List<Order> getOrdersByUserId(Long userId) {
return orderStorage.values().stream()
.filter(order -> order.getUserId().equals(userId))
.collect(Collectors.toList());
}
@Override
public List<Order> getAllOrders() {
return new ArrayList<>(orderStorage.values());
}
@Override
public Order updateOrderStatus(Long id, Integer status) {
Order order = orderStorage.get(id);
if (order != null) {
order.setStatus(status);
order.setUpdateTime(LocalDateTime.now());
return order;
}
return null;
}
@Override
public boolean deleteOrder(Long id) {
return orderStorage.remove(id) != null;
}
}
// OrderController.java
// 订单控制器
package com.example.orderservice.controller;
import com.example.orderservice.client.UserServiceClient;
import com.example.orderservice.entity.Order;
import com.example.orderservice.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import javax.validation.constraints.Min;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/orders")
@RefreshScope
@Validated
public class OrderController {
@Autowired
private OrderService orderService;
@Autowired
private UserServiceClient userServiceClient;
@Value("${app.business.order.timeout:30}")
private int orderTimeout;
@Value("${app.business.order.retry-times:3}")
private int retryTimes;
@Value("${spring.application.name}")
private String serviceName;
@Value("${server.port}")
private String serverPort;
/**
* 获取服务信息
*/
@GetMapping("/info")
public ResponseEntity<Map<String, Object>> getServiceInfo() {
Map<String, Object> info = new HashMap<>();
info.put("serviceName", serviceName);
info.put("serverPort", serverPort);
info.put("timestamp", System.currentTimeMillis());
info.put("orderTimeout", orderTimeout);
info.put("retryTimes", retryTimes);
// 调用用户服务获取信息
try {
Map<String, Object> userServiceInfo = userServiceClient.getServiceInfo();
info.put("userServiceInfo", userServiceInfo);
} catch (Exception e) {
info.put("userServiceError", e.getMessage());
}
return ResponseEntity.ok(info);
}
/**
* 创建订单
*/
@PostMapping
public ResponseEntity<Order> createOrder(@Valid @RequestBody Order order) {
try {
Order createdOrder = orderService.createOrder(order);
return ResponseEntity.ok(createdOrder);
} catch (Exception e) {
return ResponseEntity.badRequest().build();
}
}
/**
* 根据 ID 获取订单
*/
@GetMapping("/{id}")
public ResponseEntity<Order> getOrderById(@PathVariable @Min(1) Long id) {
Order order = orderService.getOrderById(id);
if (order != null) {
return ResponseEntity.ok(order);
}
return ResponseEntity.notFound().build();
}
/**
* 根据用户 ID 获取订单
*/
@GetMapping("/user/{userId}")
public ResponseEntity<List<Order>> getOrdersByUserId(@PathVariable @Min(1) Long userId) {
List<Order> orders = orderService.getOrdersByUserId(userId);
return ResponseEntity.ok(orders);
}
/**
* 获取所有订单
*/
@GetMapping
public ResponseEntity<List<Order>> getAllOrders() {
List<Order> orders = orderService.getAllOrders();
return ResponseEntity.ok(orders);
}
/**
* 更新订单状态
*/
@PutMapping("/{id}/status")
public ResponseEntity<Order> updateOrderStatus(@PathVariable @Min(1) Long id,
@RequestParam Integer status) {
Order updatedOrder = orderService.updateOrderStatus(id, status);
if (updatedOrder != null) {
return ResponseEntity.ok(updatedOrder);
}
return ResponseEntity.notFound().build();
}
/**
* 删除订单
*/
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteOrder(@PathVariable @Min(1) Long id) {
boolean deleted = orderService.deleteOrder(id);
if (deleted) {
return ResponseEntity.ok().build();
}
return ResponseEntity.notFound().build();
}
/**
* 健康检查
*/
@GetMapping("/health")
public ResponseEntity<Map<String, Object>> health() {
Map<String, Object> health = new HashMap<>();
health.put("status", "UP");
health.put("serviceName", serviceName);
health.put("serverPort", serverPort);
health.put("timestamp", System.currentTimeMillis());
health.put("orderCount", orderService.getAllOrders().size());
// 检查用户服务健康状态
try {
Map<String, Object> userHealth = userServiceClient.health();
health.put("userServiceHealth", userHealth);
} catch (Exception e) {
health.put("userServiceHealth", "DOWN");
health.put("userServiceError", e.getMessage());
}
return ResponseEntity.ok(health);
}
}
4. 配置热更新
4.1 配置属性类
// AppProperties.java
// 应用配置属性类
package com.example.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "app")
@RefreshScope
public class AppProperties {
private String name;
private String version;
private String description;
private String author;
private Business business = new Business();
private Security security = new Security();
public static class Business {
private User user = new User();
private Order order = new Order();
public static class User {
private int defaultPageSize = 20;
private int maxPageSize = 100;
private int cacheTtl = 300;
// Getter 和 Setter
public int getDefaultPageSize() {
return defaultPageSize;
}
public void setDefaultPageSize(int defaultPageSize) {
this.defaultPageSize = defaultPageSize;
}
public int getMaxPageSize() {
return maxPageSize;
}
public void setMaxPageSize(int maxPageSize) {
this.maxPageSize = maxPageSize;
}
public int getCacheTtl() {
return cacheTtl;
}
public void setCacheTtl(int cacheTtl) {
this.cacheTtl = cacheTtl;
}
}
public static class Order {
private int timeout = 30;
private int retryTimes = 3;
// Getter 和 Setter
public int getTimeout() {
return timeout;
}
public void setTimeout(int timeout) {
this.timeout = timeout;
}
public int getRetryTimes() {
return retryTimes;
}
public void setRetryTimes(int retryTimes) {
this.retryTimes = retryTimes;
}
}
// Getter 和 Setter
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public Order getOrder() {
return order;
}
public void setOrder(Order order) {
this.order = order;
}
}
public static class Security {
private Jwt jwt = new Jwt();
private Cors cors = new Cors();
public static class Jwt {
private String secret = "mySecretKey";
private long expiration = 86400;
// Getter 和 Setter
public String getSecret() {
return secret;
}
public void setSecret(String secret) {
this.secret = secret;
}
public long getExpiration() {
return expiration;
}
public void setExpiration(long expiration) {
this.expiration = expiration;
}
}
public static class Cors {
private String allowedOrigins = "*";
private String allowedMethods = "GET,POST,PUT,DELETE,OPTIONS";
private String allowedHeaders = "*";
private long maxAge = 3600;
// Getter 和 Setter
public String getAllowedOrigins() {
return allowedOrigins;
}
public void setAllowedOrigins(String allowedOrigins) {
this.allowedOrigins = allowedOrigins;
}
public String getAllowedMethods() {
return allowedMethods;
}
public void setAllowedMethods(String allowedMethods) {
this.allowedMethods = allowedMethods;
}
public String getAllowedHeaders() {
return allowedHeaders;
}
public void setAllowedHeaders(String allowedHeaders) {
this.allowedHeaders = allowedHeaders;
}
public long getMaxAge() {
return maxAge;
}
public void setMaxAge(long maxAge) {
this.maxAge = maxAge;
}
}
// Getter 和 Setter
public Jwt getJwt() {
return jwt;
}
public void setJwt(Jwt jwt) {
this.jwt = jwt;
}
public Cors getCors() {
return cors;
}
public void setCors(Cors cors) {
this.cors = cors;
}
}
// 主类的 Getter 和 Setter
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public Business getBusiness() {
return business;
}
public void setBusiness(Business business) {
this.business = business;
}
public Security getSecurity() {
return security;
}
public void setSecurity(Security security) {
this.security = security;
}
}
4.2 配置监听器
// ConfigRefreshListener.java
// 配置刷新监听器
package com.example.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.context.environment.EnvironmentChangeEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.util.Set;
@Component
public class ConfigRefreshListener implements ApplicationListener<EnvironmentChangeEvent> {
@Autowired
private AppProperties appProperties;
@Override
public void onApplicationEvent(EnvironmentChangeEvent event) {
Set<String> keys = event.getKeys();
System.out.println("配置刷新事件触发,时间: " + LocalDateTime.now());
System.out.println("变更的配置项: " + keys);
// 处理特定配置变更
for (String key : keys) {
if (key.startsWith("app.business.order")) {
System.out.println("订单业务配置发生变更: " + key);
System.out.println("当前订单超时时间: " + appProperties.getBusiness().getOrder().getTimeout());
System.out.println("当前重试次数: " + appProperties.getBusiness().getOrder().getRetryTimes());
} else if (key.startsWith("app.business.user")) {
System.out.println("用户业务配置发生变更: " + key);
System.out.println("当前默认页面大小: " + appProperties.getBusiness().getUser().getDefaultPageSize());
System.out.println("当前最大页面大小: " + appProperties.getBusiness().getUser().getMaxPageSize());
} else if (key.startsWith("app.security")) {
System.out.println("安全配置发生变更: " + key);
System.out.println("当前JWT密钥: " + appProperties.getSecurity().getJwt().getSecret());
System.out.println("当前JWT过期时间: " + appProperties.getSecurity().getJwt().getExpiration());
}
}
}
}
// ConfigController.java
// 配置管理控制器
package com.example.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.endpoint.RefreshEndpoint;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/config")
public class ConfigController {
@Autowired
private AppProperties appProperties;
@Autowired
private RefreshEndpoint refreshEndpoint;
/**
* 获取当前配置
*/
@GetMapping("/current")
public ResponseEntity<AppProperties> getCurrentConfig() {
return ResponseEntity.ok(appProperties);
}
/**
* 手动刷新配置
*/
@PostMapping("/refresh")
public ResponseEntity<Map<String, Object>> refreshConfig() {
Collection<String> keys = refreshEndpoint.refresh();
Map<String, Object> result = new HashMap<>();
result.put("refreshedKeys", keys);
result.put("timestamp", System.currentTimeMillis());
result.put("message", "配置刷新成功");
return ResponseEntity.ok(result);
}
/**
* 获取业务配置
*/
@GetMapping("/business")
public ResponseEntity<AppProperties.Business> getBusinessConfig() {
return ResponseEntity.ok(appProperties.getBusiness());
}
/**
* 获取安全配置
*/
@GetMapping("/security")
public ResponseEntity<AppProperties.Security> getSecurityConfig() {
return ResponseEntity.ok(appProperties.getSecurity());
}
}
5. 负载均衡配置
5.1 Ribbon 配置
# application.yml - Ribbon 负载均衡配置
user-service:
ribbon:
# 负载均衡策略
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.WeightedResponseTimeRule
# 连接超时时间
ConnectTimeout: 3000
# 读取超时时间
ReadTimeout: 10000
# 重试次数
MaxAutoRetries: 1
# 切换实例的重试次数
MaxAutoRetriesNextServer: 2
# 是否对所有操作重试
OkToRetryOnAllOperations: false
# 服务列表刷新间隔
ServerListRefreshInterval: 2000
# 实例检查间隔
NFLoadBalancerPingInterval: 30
# 实例检查超时时间
NFLoadBalancerPingTimeout: 5
# Feign 配置
feign:
hystrix:
enabled: true
compression:
request:
enabled: true
mime-types: text/xml,application/xml,application/json
min-request-size: 2048
response:
enabled: true
client:
config:
default:
connectTimeout: 5000
readTimeout: 10000
loggerLevel: basic
user-service:
connectTimeout: 3000
readTimeout: 8000
loggerLevel: full
# Hystrix 配置
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 10000
circuitBreaker:
enabled: true
requestVolumeThreshold: 20
sleepWindowInMilliseconds: 5000
errorThresholdPercentage: 50
fallback:
enabled: true
UserServiceClient#getUserById(Long):
execution:
isolation:
thread:
timeoutInMilliseconds: 5000
circuitBreaker:
requestVolumeThreshold: 10
errorThresholdPercentage: 30
5.2 自定义负载均衡配置
// LoadBalancerConfig.java
// 负载均衡配置类
package com.example.config;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import com.netflix.loadbalancer.RoundRobinRule;
import com.netflix.loadbalancer.WeightedResponseTimeRule;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@RibbonClient(name = "user-service", configuration = LoadBalancerConfig.class)
public class LoadBalancerConfig {
@Value("${app.loadbalancer.strategy:round-robin}")
private String strategy;
@Bean
public IRule ribbonRule() {
switch (strategy.toLowerCase()) {
case "random":
return new RandomRule();
case "weighted":
return new WeightedResponseTimeRule();
case "round-robin":
default:
return new RoundRobinRule();
}
}
}
// CustomLoadBalancerRule.java
// 自定义负载均衡规则
package com.example.config;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
public class CustomLoadBalancerRule extends AbstractLoadBalancerRule {
@Override
public void initWithNiwsConfig(IClientConfig clientConfig) {
// 初始化配置
}
@Override
public Server choose(Object key) {
return choose(getLoadBalancer(), key);
}
public Server choose(ILoadBalancer lb, Object key) {
if (lb == null) {
return null;
}
Server server = null;
while (server == null) {
if (Thread.interrupted()) {
return null;
}
List<Server> upList = lb.getReachableServers();
List<Server> allList = lb.getAllServers();
int serverCount = allList.size();
if (serverCount == 0) {
return null;
}
// 自定义负载均衡逻辑:优先选择响应时间短的服务器
if (upList.size() > 0) {
// 简单的随机选择,实际可以根据服务器性能指标选择
int index = ThreadLocalRandom.current().nextInt(upList.size());
server = upList.get(index);
}
if (server == null) {
Thread.yield();
continue;
}
if (server.isAlive()) {
return server;
}
server = null;
Thread.yield();
}
return server;
}
}
6. 服务网关集成
6.1 Gateway 配置
# gateway-application.yml - Spring Cloud Gateway 配置
spring:
application:
name: api-gateway
cloud:
nacos:
discovery:
server-addr: localhost:8848
namespace: public
group: DEFAULT_GROUP
config:
server-addr: localhost:8848
file-extension: yml
namespace: public
group: DEFAULT_GROUP
gateway:
discovery:
locator:
enabled: true
lower-case-service-id: true
routes:
- id: user-service
uri: lb://user-service
predicates:
- Path=/api/users/**
filters:
- StripPrefix=1
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 10
redis-rate-limiter.burstCapacity: 20
key-resolver: "#{@ipKeyResolver}"
- id: order-service
uri: lb://order-service
predicates:
- Path=/api/orders/**
filters:
- StripPrefix=1
- name: CircuitBreaker
args:
name: order-service-cb
fallbackUri: forward:/fallback/orders
default-filters:
- name: GlobalFilter
- name: Retry
args:
retries: 3
statuses: BAD_GATEWAY,GATEWAY_TIMEOUT
methods: GET,POST
server:
port: 8080
management:
endpoints:
web:
exposure:
include: "*"
endpoint:
health:
show-details: always
6.2 Gateway 过滤器
// GlobalAuthFilter.java
// 全局认证过滤器
package com.example.gateway.filter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component
public class GlobalAuthFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
String path = request.getURI().getPath();
// 跳过认证的路径
if (isSkipAuth(path)) {
return chain.filter(exchange);
}
// 检查认证头
String token = request.getHeaders().getFirst("Authorization");
if (token == null || !token.startsWith("Bearer ")) {
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
// 验证 token(简化版本)
if (!validateToken(token)) {
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
// 添加用户信息到请求头
ServerHttpRequest modifiedRequest = request.mutate()
.header("X-User-Id", extractUserId(token))
.header("X-User-Name", extractUserName(token))
.build();
return chain.filter(exchange.mutate().request(modifiedRequest).build());
}
@Override
public int getOrder() {
return -100;
}
private boolean isSkipAuth(String path) {
return path.startsWith("/api/auth/") ||
path.startsWith("/api/public/") ||
path.equals("/health") ||
path.equals("/info");
}
private boolean validateToken(String token) {
// 简化的 token 验证逻辑
return token.length() > 10;
}
private String extractUserId(String token) {
// 从 token 中提取用户 ID
return "12345";
}
private String extractUserName(String token) {
// 从 token 中提取用户名
return "testuser";
}
}
// IpKeyResolver.java
// IP 限流键解析器
package com.example.gateway.config;
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component
public class IpKeyResolver implements KeyResolver {
@Override
public Mono<String> resolve(ServerWebExchange exchange) {
return Mono.just(getIpAddress(exchange));
}
private String getIpAddress(ServerWebExchange exchange) {
String xForwardedFor = exchange.getRequest().getHeaders().getFirst("X-Forwarded-For");
if (xForwardedFor != null && !xForwardedFor.isEmpty()) {
return xForwardedFor.split(",")[0].trim();
}
String xRealIp = exchange.getRequest().getHeaders().getFirst("X-Real-IP");
if (xRealIp != null && !xRealIp.isEmpty()) {
return xRealIp;
}
return exchange.getRequest().getRemoteAddress() != null ?
exchange.getRequest().getRemoteAddress().getAddress().getHostAddress() : "unknown";
}
}
7. 测试与验证
7.1 单元测试
// UserServiceTest.java
// 用户服务测试
package com.example.userservice.service;
import com.example.userservice.entity.User;
import com.example.userservice.service.impl.UserServiceImpl;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.TestPropertySource;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
@TestPropertySource(properties = {
"spring.cloud.nacos.discovery.server-addr=localhost:8848",
"spring.cloud.nacos.config.server-addr=localhost:8848"
})
class UserServiceTest {
private UserServiceImpl userService;
@BeforeEach
void setUp() {
userService = new UserServiceImpl();
}
@Test
void testCreateUser() {
User user = new User();
user.setUsername("testuser");
user.setEmail("test@example.com");
user.setPhone("13800138000");
User createdUser = userService.createUser(user);
assertNotNull(createdUser);
assertNotNull(createdUser.getId());
assertEquals("testuser", createdUser.getUsername());
assertEquals("test@example.com", createdUser.getEmail());
assertNotNull(createdUser.getCreateTime());
}
@Test
void testGetUserById() {
// 先创建用户
User user = new User();
user.setUsername("testuser2");
user.setEmail("test2@example.com");
User createdUser = userService.createUser(user);
// 查询用户
User foundUser = userService.getUserById(createdUser.getId());
assertNotNull(foundUser);
assertEquals(createdUser.getId(), foundUser.getId());
assertEquals("testuser2", foundUser.getUsername());
}
@Test
void testGetUserByIdNotFound() {
User user = userService.getUserById(999L);
assertNull(user);
}
@Test
void testUpdateUser() {
// 先创建用户
User user = new User();
user.setUsername("testuser3");
user.setEmail("test3@example.com");
User createdUser = userService.createUser(user);
// 更新用户
createdUser.setEmail("updated@example.com");
User updatedUser = userService.updateUser(createdUser);
assertNotNull(updatedUser);
assertEquals("updated@example.com", updatedUser.getEmail());
assertNotNull(updatedUser.getUpdateTime());
}
@Test
void testDeleteUser() {
// 先创建用户
User user = new User();
user.setUsername("testuser4");
user.setEmail("test4@example.com");
User createdUser = userService.createUser(user);
// 删除用户
boolean deleted = userService.deleteUser(createdUser.getId());
assertTrue(deleted);
// 验证用户已删除
User deletedUser = userService.getUserById(createdUser.getId());
assertNull(deletedUser);
}
}
7.2 集成测试
// OrderServiceIntegrationTest.java
// 订单服务集成测试
package com.example.orderservice.integration;
import com.example.orderservice.entity.Order;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureWebMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureWebMvc
@TestPropertySource(properties = {
"spring.cloud.nacos.discovery.server-addr=localhost:8848",
"spring.cloud.nacos.config.server-addr=localhost:8848"
})
class OrderServiceIntegrationTest {
@Autowired
private WebApplicationContext webApplicationContext;
@Autowired
private ObjectMapper objectMapper;
private MockMvc mockMvc;
@Test
void testCreateOrder() throws Exception {
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
Order order = new Order();
order.setUserId(1L);
order.setProductName("测试商品");
order.setQuantity(2);
order.setPrice(99.99);
order.setStatus(0);
mockMvc.perform(post("/orders")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(order)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id").exists())
.andExpect(jsonPath("$.userId").value(1L))
.andExpect(jsonPath("$.productName").value("测试商品"))
.andExpect(jsonPath("$.quantity").value(2))
.andExpect(jsonPath("$.price").value(99.99));
}
@Test
void testGetOrderById() throws Exception {
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
mockMvc.perform(get("/orders/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id").value(1L));
}
@Test
void testGetServiceInfo() throws Exception {
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
mockMvc.perform(get("/orders/info"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.serviceName").exists())
.andExpect(jsonPath("$.serverPort").exists())
.andExpect(jsonPath("$.timestamp").exists());
}
@Test
void testHealthCheck() throws Exception {
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
mockMvc.perform(get("/orders/health"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.status").value("UP"))
.andExpect(jsonPath("$.serviceName").exists());
}
}
7.3 性能测试脚本
#!/bin/bash
# performance-test.sh
# 性能测试脚本
echo "开始性能测试..."
# 测试用户服务
echo "测试用户服务性能..."
ab -n 1000 -c 10 http://localhost:8081/users/info
# 测试订单服务
echo "测试订单服务性能..."
ab -n 1000 -c 10 http://localhost:8082/orders/info
# 测试网关
echo "测试网关性能..."
ab -n 1000 -c 10 http://localhost:8080/api/users/info
ab -n 1000 -c 10 http://localhost:8080/api/orders/info
# 测试配置刷新
echo "测试配置刷新..."
for i in {1..10}; do
curl -X POST http://localhost:8081/config/refresh
curl -X POST http://localhost:8082/config/refresh
sleep 1
done
echo "性能测试完成!"
8. 监控与运维
8.1 健康检查配置
# application.yml - 健康检查配置
management:
endpoints:
web:
exposure:
include: "health,info,metrics,prometheus,nacos-discovery,nacos-config"
base-path: /actuator
endpoint:
health:
show-details: always
show-components: always
info:
enabled: true
metrics:
enabled: true
health:
nacos:
enabled: true
diskspace:
enabled: true
db:
enabled: true
metrics:
export:
prometheus:
enabled: true
distribution:
percentiles-histogram:
http.server.requests: true
percentiles:
http.server.requests: 0.5, 0.9, 0.95, 0.99
info:
app:
name: ${spring.application.name}
version: 1.0.0
description: Spring Cloud Nacos 集成示例
build:
time: ${maven.build.timestamp}
git:
branch: ${git.branch}
commit: ${git.commit.id}
8.2 日志配置
<!-- logback-spring.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<springProfile name="!prod">
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<include resource="org/springframework/boot/logging/logback/console-appender.xml"/>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/${spring.application.name}.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/${spring.application.name}.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%X{traceId}] %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE"/>
</root>
<logger name="com.example" level="DEBUG"/>
<logger name="com.alibaba.nacos" level="INFO"/>
<logger name="org.springframework.cloud" level="DEBUG"/>
</springProfile>
<springProfile name="prod">
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/${spring.application.name}.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/${spring.application.name}.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>500MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<maxHistory>60</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%X{traceId}] %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<file>logs/${spring.application.name}-error.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/${spring.application.name}-error.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<maxHistory>90</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%X{traceId}] %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="FILE"/>
<appender-ref ref="ERROR_FILE"/>
</root>
<logger name="com.example" level="INFO"/>
<logger name="com.alibaba.nacos" level="WARN"/>
<logger name="org.springframework.cloud" level="WARN"/>
</springProfile>
</configuration>
9. 核心要点
9.1 架构设计
- 服务拆分: 按业务领域拆分服务,保持服务的单一职责
- 配置管理: 使用 Nacos 统一管理配置,支持多环境和热更新
- 服务发现: 利用 Nacos 实现服务的自动注册与发现
- 负载均衡: 配置合适的负载均衡策略,提高系统可用性
- 网关集成: 使用 Spring Cloud Gateway 统一入口和路由
9.2 最佳实践
- 配置分层: 按环境、应用、模块分层管理配置
- 健康检查: 配置完善的健康检查机制
- 监控告警: 集成 Prometheus 和 Grafana 监控
- 日志管理: 统一日志格式和收集策略
- 安全防护: 实现认证、授权和限流机制
9.3 性能优化
- 连接池优化: 合理配置 HTTP 连接池参数
- 缓存策略: 使用本地缓存和分布式缓存
- 异步处理: 使用异步调用提高响应速度
- 批量操作: 减少网络调用次数
- 资源隔离: 使用线程池隔离不同业务
10. 下一步学习
- Spring Cloud Alibaba Sentinel: 学习熔断降级和流量控制
- Spring Cloud Alibaba Seata: 学习分布式事务处理
- Spring Cloud Sleuth: 学习分布式链路追踪
- Spring Cloud Stream: 学习消息驱动的微服务
- Kubernetes 部署: 学习容器化部署和编排
通过本教程,你已经掌握了 Nacos 与 Spring Cloud 的集成方法,包括服务注册发现、配置管理、负载均衡、网关集成等核心功能。继续深入学习相关技术,构建更加完善的微服务架构。