服务注册与发现机制:构建动态微服务网络

发布时间:2026/5/16 0:41:22

服务注册与发现机制:构建动态微服务网络 服务注册与发现机制构建动态微服务网络一、服务注册与发现概述1.1 为什么需要服务发现在微服务架构中服务实例的网络位置是动态变化的服务发现解决了服务定位客户端如何找到服务提供者负载均衡如何将请求分发到多个实例故障处理实例不可用时如何处理动态扩缩容如何感知服务实例变化1.2 服务发现模式┌─────────────────────────────────────────────────────────────────────┐ │ 客户端发现模式 │ ├─────────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────┐ ┌─────────────────┐ ┌─────────┐ │ │ │ 客户端 │────────▶│ 服务注册中心 │◀────────│ 服务A │ │ │ │ │◀────────│ (Eureka/Nacos) │────────▶│ 实例1 │ │ │ │ │ │ │ └─────────┘ │ │ │ │────────▶│ │ ┌─────────┐ │ │ │ │◀────────│ │◀────────│ 服务A │ │ │ └─────────┘ └─────────────────┘ │ 实例2 │ │ │ │ └─────────┘ │ │ │ 1. 查询注册中心获取服务地址 │ │ └─▶ 2. 直接调用服务实例 │ │ │ └─────────────────────────────────────────────────────────────────────┘ ┌─────────────────────────────────────────────────────────────────────┐ │ 服务端发现模式 │ ├─────────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────┐ ┌─────────────────┐ ┌─────────┐ │ │ │ 客户端 │────────▶│ API网关 │────────▶│ 服务A │ │ │ │ │ │ (路由负载均衡) │ │ 实例1 │ │ │ └─────────┘ │ │ └─────────┘ │ │ │ │ ┌─────────┐ │ │ │ │────────▶│ 服务A │ │ │ └─────────────────┘ │ 实例2 │ │ │ │ └─────────┘ │ │ │ 1. 查询注册中心获取实例列表 │ │ └─▶ 2. 负载均衡选择实例 │ │ 3. 转发请求 │ └─────────────────────────────────────────────────────────────────────┘1.3 核心组件对比组件ConsulEurekaNacosZookeeper一致性Raft最终一致CP/AP可选ZAB健康检查HTTP/TCP/TTL心跳TCP/HTTP/MySQLKeepAlive多数据中心支持有限支持有限配置管理支持不支持支持有限社区活跃度高低停止维护高高二、Eureka服务注册与发现2.1 Eureka Server配置# application.yml server: port: 8761 spring: application: name: eureka-server eureka: instance: hostname: localhost client: register-with-eureka: false fetch-registry: false service-url: default-zone: http://${eureka.instance.hostname}:${server.port}/eureka/ # 高可用配置 server: enable-self-preservation: true eviction-interval-timer-in-ms: 5000 renewal-percent-threshold: 0.85 # 控制台配置 dashboard: enabled: true2.2 Eureka Client配置# application.yml spring: application: name: user-service eureka: instance: prefer-ip-address: true instance-id: ${spring.application.name}:${spring.application.instance_id:${server.port}} lease-renewal-interval-in-seconds: 10 lease-expiration-duration-in-seconds: 30 client: register-with-eureka: true fetch-registry: true registry-fetch-interval-seconds: 10 service-url: default-zone: http://localhost:8761/eureka/2.3 Eureka高可用配置# eureka-server-1配置 server: port: 8761 eureka: instance: hostname: eureka1.example.com client: register-with-eureka: true fetch-registry: true service-url: default-zone: http://eureka2.example.com:8762/eureka/,http://eureka3.example.com:8763/eureka/ --- # eureka-server-2配置 server: port: 8762 eureka: instance: hostname: eureka2.example.com client: register-with-eureka: true fetch-registry: true service-url: default-zone: http://eureka1.example.com:8761/eureka/,http://eureka3.example.com:8763/eureka/ --- # eureka-server-3配置 server: port: 8763 eureka: instance: hostname: eureka3.example.com client: register-with-eureka: true fetch-registry: true service-url: default-zone: http://eureka1.example.com:8761/eureka/,http://eureka2.example.com:8762/eureka/三、Consul服务注册与发现3.1 Consul Server配置# config.json { datacenter: dc1, data_dir: /opt/consul/data, server: true, bootstrap_expect: 3, ui_config: { enabled: true }, client_addr: 0.0.0.0, advertise_addr: 10.0.1.10, retry_join: [10.0.1.10, 10.0.1.11, 10.0.1.12], encrypt: your-encryption-key, ca_file: /opt/consul/consul-agent-ca.pem, cert_file: /opt/consul/dc1-server-consul-0.pem, key_file: /opt/consul/dc1-server-consul-0-key.pem }3.2 Spring Cloud Consul配置spring: cloud: consul: host: localhost port: 8500 discovery: enabled: true service-name: ${spring.application.name} instance-id: ${spring.application.name}:${random.value} prefer-ip-address: true health-check-path: /actuator/health health-check-interval: 10s health-check-timeout: 5s health-check-critical-timeout: 30s register: true deregister: true catalog-services-watch-delay: 10s3.3 手动服务注册Configuration public class ConsulRegistrationConfig { Autowired private ConsulClient consulClient; Bean public ServiceRegistryConsulRegistration consulServiceRegistry() { return new ConsulServiceRegistry(consulClient); } Bean public ConsulRegistration consulRegistration() { return new ConsulRegistration( createServiceDefinition(), createMetadata() ); } private MapString, String createMetadata() { MapString, String metadata new HashMap(); metadata.put(version, 1.0.0); metadata.put(environment, production); metadata.put(region, us-east-1); return metadata; } }四、Nacos服务注册与发现4.1 Nacos Server配置# application.properties server.port8848 spring.datasource.platformmysql db.num1 db.url.0jdbc:mysql://localhost:3306/nacos?characterEncodingutf8connectTimeout1000socketTimeout3000autoReconnecttrue db.usernacos db.passwordnacos # 单机模式 nacos.standalonetrue # 集群模式 # nacos.integrate.open.raft3 # server.addresses10.0.1.10:8848,10.0.1.11:8848,10.0.1.12:88484.2 Spring Cloud Nacos配置spring: cloud: nacos: discovery: enabled: true server-addr: localhost:8848 namespace: ${NACOS_NAMESPACE:public} group: ${NACOS_GROUP:DEFAULT_GROUP} cluster-name: ${NACOS_CLUSTER:DEFAULT} weight: 100 enabled: true ephemeral: false # 持久化实例 metadata: version: v1 env: production4.3 动态服务发现Service Slf4j public class NacosServiceDiscovery { Autowired private NamingService namingService; public ListInstance getInstances(String serviceName) { try { return namingService.selectInstances(serviceName, true); } catch (NacosException e) { log.error(Failed to get instances, e); return Collections.emptyList(); } } public Instance getOneHealthyInstance(String serviceName) { try { return namingService.selectOneHealthyInstance(serviceName); } catch (NacosException e) { log.error(Failed to get healthy instance, e); return null; } } public ListServiceInstance getServicesByGroup(String groupName) { try { ListInstance instances namingService.selectInstances(user-service, true); return instances.stream() .map(this::toServiceInstance) .collect(Collectors.toList()); } catch (NacosException e) { log.error(Failed to get services, e); return Collections.emptyList(); } } private ServiceInstance toServiceInstance(Instance instance) { return ServiceInstance.builder() .instanceId(instance.getInstanceId()) .host(instance.getIp()) .port(instance.getPort()) .metadata(instance.getMetadata()) .build(); } }五、服务调用5.1 RestTemplate服务调用Configuration public class RestTemplateConfig { Bean LoadBalanced public RestTemplate restTemplate() { return new RestTemplate(); } } Service public class UserServiceClient { Autowired private RestTemplate restTemplate; public User getUserById(Long userId) { return restTemplate.getForObject( http://user-service/api/users/{id}, User.class, userId ); } public ListUser getUsersByIds(ListLong userIds) { String ids String.join(,, userIds.stream() .map(String::valueOf) .collect(Collectors.toList())); return restTemplate.getForObject( http://user-service/api/users?ids{ids}, new ParameterizedTypeReferenceListUser() {}, ids ); } }5.2 WebClient服务调用Configuration public class WebClientConfig { Bean LoadBalanced public WebClient.Builder loadBalancedWebClientBuilder() { return WebClient.builder() .baseUrl(http://user-service) .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE); } } Service public class UserWebClientService { private final WebClient webClient; public UserWebClientService(WebClient.Builder builder) { this.webClient builder.build(); } public MonoUser getUserById(Long userId) { return webClient.get() .uri(/api/users/{id}, userId) .retrieve() .bodyToMono(User.class) .timeout(Duration.ofSeconds(5)) .onErrorResume(WebClientResponseException.class, e - Mono.empty()); } public FluxUser getUsersByIds(ListLong userIds) { return webClient.post() .uri(/api/users/batch) .bodyValue(userIds) .retrieve() .bodyToFlux(User.class); } }5.3 OpenFeign服务调用FeignClient(name user-service, fallback UserServiceFallback.class) public interface UserServiceClient { GetMapping(/api/users/{id}) User getUserById(PathVariable(id) Long id); PostMapping(/api/users/batch) ListUser getUsersByIds(RequestBody ListLong ids); GetMapping(/api/users) PageUser getUsers(RequestParam int page, RequestParam int size); } Component public class UserServiceFallback implements UserServiceClient { Override public User getUserById(Long id) { return User.builder() .id(id) .username(default) .build(); } Override public ListUser getUsersByIds(ListLong ids) { return Collections.emptyList(); } Override public PageUser getUsers(int page, int size) { return Page.empty(); } }六、负载均衡6.1 Ribbon配置user-service: ribbon: NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule NFLoadBalancerPingClassName: com.netflix.loadbalancer.NoOpPing NFLoadBalancerPingInterval: 30 ConnectTimeout: 5000 ReadTimeout: 5000 OkToRetryOnAllOperations: true MaxAutoRetries: 3 MaxAutoRetriesNextServer: 16.2 自定义负载均衡规则Configuration public class CustomLoadBalancerConfig { Bean public IRule customRule() { return new WeightedResponseTimeRule(); } } public class CustomLoadBalancerRule extends AbstractLoadBalancerRule { Override public Server choose(Object key) { return choose(key, LoadBalancerBuilder.newBuilder() .buildFixedServerListWithWeights(getServersFromConfig())); } Bean public ReactorLoadBalancerServiceInstance randomServiceInstance() { return new RandomLoadBalancer(serviceInstanceListSupplier()); } }6.3 Spring Cloud LoadBalancerConfiguration public class LoadBalancerConfig { Bean public ReactorLoadBalancerServiceInstance randomLoadBalancer( ServiceInstanceListSupplier supplier) { return new RandomLoadBalancer(supplier); } Bean public ServiceInstanceListSupplier serviceInstanceListSupplier( ConfigurableApplicationContext context) { return ServiceInstanceListSupplier.builder() .withDiscoveryClient() .withCaching() .build(context); } } Service public class CustomLoadBalancerService { Autowired private ReactorLoadBalancerServiceInstance loadBalancer; public MonoServiceInstance choose(String serviceId) { return loadBalancer.choose() .map(LBResponse::getServer) .switchIfEmpty(Mono.error(new ServiceNotFoundException(serviceId))); } }七、健康检查7.1 Actuator健康检查端点management: endpoints: web: exposure: include: health,info,metrics,prometheus endpoint: health: show-details: always probes: enabled: true health: livenessState: enabled: true readinessState: enabled: true consul: enabled: true nacos: enabled: true7.2 自定义健康检查Component public class CustomHealthIndicator implements ReactiveHealthIndicator { Autowired private DatabaseService databaseService; Override public MonoHealth health() { return checkDatabase() .map(dbHealth - Health.up().withDetails(dbHealth).build()) .onErrorResume(e - Mono.just( Health.down().withException(e).build())); } private MonoMapString, Object checkDatabase() { return Mono.fromCallable(() - { MapString, Object status new HashMap(); status.put(connection, databaseService.isConnected()); status.put(activeConnections, databaseService.getActiveCount()); return status; }); } }7.3 Eureka健康检查Component public class EurekaHealthCheckHandler implements HealthCheckHandler { private final MapString, HealthCheck healthChecks new ConcurrentHashMap(); Autowired private DatabaseHealthCheck databaseCheck; Autowired private RedisHealthCheck redisCheck; PostConstruct public void init() { healthChecks.put(database, databaseCheck); healthChecks.put(redis, redisCheck); } Override public InstanceStatus getStatus(InstanceStatus currentStatus) { for (HealthCheck check : healthChecks.values()) { if (!check.isHealthy()) { return InstanceStatus.DOWN; } } return InstanceStatus.UP; } }八、故障处理8.1 熔断降级Configuration public class Resilience4jConfig { Bean public CircuitBreakerRegistry circuitBreakerRegistry() { return CircuitBreakerRegistry.ofDefaults(); } } Service public class ResilientUserService { private final UserServiceClient userClient; public ResilientUserService(UserServiceClient userClient) { this.userClient userClient; // 配置熔断器 CircuitBreakerConfig config CircuitBreakerConfig.custom() .failureRateThreshold(50) .waitDurationInOpenState(Duration.ofSeconds(30)) .slidingWindowType(SlidingWindowType.COUNT_BASED) .slidingWindowSize(10) .build(); CircuitBreaker circuitBreaker CircuitBreaker.of(userService, config); this.userClient Decorators .ofSupplier(() - userClient.getUserById(1L)) .withCircuitBreaker(circuitBreaker) .withFallback(List.of(Exception.class), e - getDefaultUser()) .decorate(); } private User getDefaultUser() { return User.builder().id(0L).username(default).build(); } }8.2 重试机制Configuration public class RetryConfig { Bean public RetryRegistry retryRegistry() { return RetryRegistry.ofDefaults(); } } Service public class RetryableUserService { private final UserServiceClient userClient; public RetryableUserService(UserServiceClient userClient) { RetryConfig config RetryConfig.custom() .maxAttempts(3) .waitDuration(Duration.ofMillis(500)) .retryExceptions(IOException.class, TimeoutException.class) .ignoreExceptions(BusinessException.class) .build(); Retry retry Retry.of(userService, config); this.userClient Decorators .ofSupplier(() - userClient.getUserById(1L)) .withRetry(retry) .decorate(); } }8.3 超时处理Configuration public class TimeoutConfig { Bean public TimeoutRegistry timeoutRegistry() { return TimeoutRegistry.ofDefaults(); } } Service public class TimeoutAwareService { private final WebClient webClient; public TimeoutAwareService(WebClient.Builder builder) { this.webClient builder .filter((request, next) - next.exchange(request) .timeout(Duration.ofSeconds(3)) .onErrorResume(TimeoutException.class, e - Mono.error(new ServiceTimeoutException(e))) ) .build(); } }九、最佳实践9.1 服务注册清单配置项推荐值说明心跳间隔10-30秒太短增加网络负载太长发现慢实例过期时间90秒通常为心跳间隔的3倍拉取间隔30秒客户端缓存刷新间隔最大重试3次注册失败重试次数9.2 高可用部署# Kubernetes部署Eureka apiVersion: apps/v1 kind: StatefulSet metadata: name: eureka spec: serviceName: eureka replicas: 3 selector: matchLabels: app: eureka template: spec: affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: labelSelector: matchExpressions: - key: app operator: In values: - eureka topologyKey: kubernetes.io/hostname containers: - name: eureka image: eureka-server:latest ports: - containerPort: 8761 env: - name: EUREKA_CLIENT_SERVICEURL_DEFAULTZONE value: http://eureka-0.eureka:8761/eureka/,http://eureka-1.eureka:8761/eureka/,http://eureka-2.eureka:8761/eureka/十、总结服务注册与发现是微服务架构的基础组件通过本文的介绍你可以服务发现模式客户端发现和服务端发现EurekaNetflix开源的服务注册中心已停止维护ConsulHashiCorp的多功能服务网格解决方案Nacos阿里巴巴开源的服务发现和配置管理平台服务调用RestTemplate、WebClient、OpenFeign负载均衡Ribbon、Spring Cloud LoadBalancer健康检查Actuator、自定义健康检查故障处理熔断、降级、重试、超时选择合适的服务注册与发现方案需要综合考虑一致性要求、功能特性、运维成本等因素。

相关新闻