Spring Cloud 服务发现
1. 服务发现概述
1.1 什么是服务发现
服务发现是微服务架构中用于动态发现服务实例的机制,服务启动时注册,调用时发现。
1.2 服务发现模式
| 模式 | 说明 | 代表 |
|---|---|---|
| 客户端发现 | 客户端查询注册中心,自行负载均衡 | Eureka、Nacos |
| 服务端发现 | 通过负载均衡器/网关转发 | Kubernetes Service、AWS ELB |
2. Nacos 服务发现
2.1 添加依赖
xml
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2023.0.1.0</version>
</dependency>2.2 配置 Nacos
yaml
spring:
application:
name: user-service
cloud:
nacos:
discovery:
server-addr: localhost:8848
namespace: dev
group: DEFAULT_GROUP
service: ${spring.application.name}
weight: 1
metadata:
version: 1.0.0
region: cn-east2.3 启用服务发现
java
@SpringBootApplication
@EnableDiscoveryClient
public class UserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceApplication.class, args);
}
}2.4 服务注册配置
yaml
spring:
cloud:
nacos:
discovery:
server-addr: localhost:8848
heart-beat-interval: 5000
heart-beat-timeout: 15000
ip-delete-timeout: 30000
register-enabled: true
instance-enabled: true
ephemeral: true3. 服务调用
3.1 使用 DiscoveryClient
java
@Service
public class OrderClient {
private final DiscoveryClient discoveryClient;
private final RestTemplate restTemplate;
public Order getOrder(Long id) {
List<ServiceInstance> instances =
discoveryClient.getInstances("order-service");
if (instances.isEmpty()) {
throw new RuntimeException("No available order service");
}
ServiceInstance instance = instances.get(0);
String url = instance.getUri() + "/api/orders/" + id;
return restTemplate.getForObject(url, Order.class);
}
}3.2 使用 LoadBalancer
java
@Configuration
public class RestTemplateConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
@Service
public class OrderClient {
private final RestTemplate restTemplate;
public Order getOrder(Long id) {
return restTemplate.getForObject(
"http://order-service/api/orders/" + id,
Order.class
);
}
}3.3 使用 OpenFeign
xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>java
@SpringBootApplication
@EnableFeignClients
public class UserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceApplication.class, args);
}
}
@FeignClient(name = "order-service", path = "/api/orders")
public interface OrderClient {
@GetMapping("/{id}")
Order getOrder(@PathVariable Long id);
@GetMapping
List<Order> getOrdersByUserId(@RequestParam Long userId);
@PostMapping
Order createOrder(@RequestBody OrderRequest request);
}
@Service
public class UserService {
private final OrderClient orderClient;
public UserWithOrders getUserWithOrders(Long userId) {
User user = userRepository.findById(userId).orElse(null);
List<Order> orders = orderClient.getOrdersByUserId(userId);
return new UserWithOrders(user, orders);
}
}4. 负载均衡
4.1 负载均衡策略
java
@Configuration
public class LoadBalancerConfig {
@Bean
ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(
Environment environment,
LoadBalancerClientFactory factory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new RandomLoadBalancer(
factory.getLazyProvider(name, ServiceInstanceListSupplier.class),
name
);
}
}
@Configuration
@LoadBalancerClients(defaultConfiguration = LoadBalancerConfig.class)
public class LoadBalancerClientConfig {
}4.2 自定义负载均衡
java
public class CustomLoadBalancer implements ReactorServiceInstanceLoadBalancer {
private final ServiceInstanceListSupplier serviceInstanceListSupplier;
private final String serviceId;
private AtomicInteger position;
public CustomLoadBalancer(ServiceInstanceListSupplier supplier, String serviceId) {
this.serviceInstanceListSupplier = supplier;
this.serviceId = serviceId;
this.position = new AtomicInteger(0);
}
@Override
public Mono<Response<ServiceInstance>> choose(Request request) {
return serviceInstanceListSupplier.get()
.next()
.map(instances -> {
if (instances.isEmpty()) {
return new EmptyResponse();
}
int pos = Math.abs(position.incrementAndGet());
ServiceInstance instance = instances.get(pos % instances.size());
return new DefaultResponse(instance);
});
}
}4.3 基于权重的负载均衡
java
public class WeightedLoadBalancer implements ReactorServiceInstanceLoadBalancer {
private final ServiceInstanceListSupplier supplier;
@Override
public Mono<Response<ServiceInstance>> choose(Request request) {
return supplier.get()
.next()
.map(instances -> {
if (instances.isEmpty()) {
return new EmptyResponse();
}
List<ServiceInstance> weightedInstances = new ArrayList<>();
for (ServiceInstance instance : instances) {
int weight = Integer.parseInt(
instance.getMetadata().getOrDefault("weight", "1")
);
for (int i = 0; i < weight; i++) {
weightedInstances.add(instance);
}
}
int index = ThreadLocalRandom.current().nextInt(weightedInstances.size());
return new DefaultResponse(weightedInstances.get(index));
});
}
}5. Nacos 配置中心
5.1 添加依赖
xml
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>5.2 配置加载
yaml
# bootstrap.yml
spring:
application:
name: user-service
profiles:
active: dev
cloud:
nacos:
config:
server-addr: localhost:8848
namespace: dev
group: DEFAULT_GROUP
file-extension: yaml
shared-configs:
- data-id: common.yaml
group: DEFAULT_GROUP
refresh: true
extension-configs:
- data-id: redis.yaml
group: DEFAULT_GROUP
refresh: true5.3 动态配置刷新
java
@RestController
@RefreshScope
public class ConfigController {
@Value("${app.config.name}")
private String configName;
@Value("${app.config.max-connections:100}")
private int maxConnections;
@GetMapping("/config")
public Map<String, Object> getConfig() {
return Map.of(
"configName", configName,
"maxConnections", maxConnections
);
}
}
@Configuration
@RefreshScope
public class DynamicConfig {
@Value("${app.feature.enabled:false}")
private boolean featureEnabled;
@Bean
public FeatureService featureService() {
return new FeatureService(featureEnabled);
}
}6. 服务健康检查
6.1 健康检查配置
yaml
spring:
cloud:
nacos:
discovery:
heart-beat-interval: 5000
heart-beat-timeout: 15000
ip-delete-timeout: 30000
management:
endpoints:
web:
exposure:
include: health
endpoint:
health:
show-details: always6.2 自定义健康检查
java
@Component
public class CustomHealthIndicator implements HealthIndicator {
@Override
public Health health() {
boolean healthy = checkHealth();
if (healthy) {
return Health.up()
.withDetail("service", "user-service")
.withDetail("timestamp", System.currentTimeMillis())
.build();
}
return Health.down()
.withDetail("error", "Service unavailable")
.build();
}
private boolean checkHealth() {
return true;
}
}7. 服务元数据
7.1 配置元数据
yaml
spring:
cloud:
nacos:
discovery:
metadata:
version: 1.0.0
region: cn-east
zone: zone-a
weight: 100
preserved.register.source: SPRING_CLOUD7.2 使用元数据
java
@Service
public class MetadataService {
private final DiscoveryClient discoveryClient;
public List<ServiceInstance> getInstancesByVersion(String version) {
return discoveryClient.getInstances("user-service")
.stream()
.filter(instance ->
version.equals(instance.getMetadata().get("version")))
.toList();
}
public List<ServiceInstance> getInstancesByRegion(String region) {
return discoveryClient.getInstances("user-service")
.stream()
.filter(instance ->
region.equals(instance.getMetadata().get("region")))
.toList();
}
}8. 集群配置
8.1 Nacos 集群
yaml
spring:
cloud:
nacos:
discovery:
server-addr: nacos1:8848,nacos2:8848,nacos3:8848
namespace: prod
cluster-name: DEFAULT8.2 服务分组
yaml
spring:
cloud:
nacos:
discovery:
group: USER_GROUP
namespace: prod9. 小结
本章学习了 Spring Cloud 服务发现的核心内容:
| 内容 | 要点 |
|---|---|
| Nacos 服务发现 | 注册、发现、配置 |
| 服务调用 | DiscoveryClient、LoadBalancer、OpenFeign |
| 负载均衡 | 轮询、随机、权重 |
| 配置中心 | 动态配置、配置刷新 |
| 健康检查 | 心跳、自定义健康检查 |
| 服务元数据 | 版本、区域、权重 |
下一章将学习 Spring Cloud 网关。