Skip to content

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-east

2.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: true

3. 服务调用

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: true

5.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: always

6.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_CLOUD

7.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: DEFAULT

8.2 服务分组

yaml
spring:
  cloud:
    nacos:
      discovery:
        group: USER_GROUP
        namespace: prod

9. 小结

本章学习了 Spring Cloud 服务发现的核心内容:

内容要点
Nacos 服务发现注册、发现、配置
服务调用DiscoveryClient、LoadBalancer、OpenFeign
负载均衡轮询、随机、权重
配置中心动态配置、配置刷新
健康检查心跳、自定义健康检查
服务元数据版本、区域、权重

下一章将学习 Spring Cloud 网关。