服务注册与发现详解
在微服务架构中,服务实例的数量和位置是动态变化的。服务注册与发现机制解决了服务间如何相互找到并通信的核心问题,它是微服务架构的基础设施之一。
服务发现 = 动态注册 + 自动发现 + 负载均衡 + 健康检查 + 故障转移
服务发现架构
1. 服务发现基础概念
1.1 什么是服务发现?
服务发现是微服务架构中的一个重要组件,它负责管理服务实例的注册、发现和健康检查。当服务启动时,它会向注册中心注册自己的信息;当服务需要调用其他服务时,它会从注册中心获取目标服务的实例列表。
服务发现的核心组件
1public class ServiceDiscoveryComponents {2 /*3 * 服务发现的核心组件4 * 1. 服务注册中心:存储服务实例信息的中心化存储5 * 2. 服务提供者:向注册中心注册自己的服务实例6 * 3. 服务消费者:从注册中心获取服务实例列表7 * 4. 负载均衡器:在多个服务实例间分发请求8 * 5. 健康检查器:监控服务实例的健康状态9 */10 11 // 服务实例信息12 public class ServiceInstance {13 private String instanceId; // 实例ID14 private String serviceId; // 服务ID15 private String host; // 主机地址16 private int port; // 端口17 private boolean secure; // 是否使用HTTPS18 private String uri; // 服务URI19 private Map<String, String> metadata; // 元数据20 private String zone; // 可用区21 private boolean enabled; // 是否启用22 private long registrationTime; // 注册时间23 private long lastUpdatedTime; // 最后更新时间24 25 // 构造函数、getter、setter方法26 }27 28 // 服务注册接口29 public interface ServiceRegistry {30 // 注册服务实例31 void register(ServiceInstance serviceInstance);32 33 // 注销服务实例34 void deregister(ServiceInstance serviceInstance);35 36 // 更新服务实例37 void update(ServiceInstance serviceInstance);38 39 // 设置服务实例状态40 void setStatus(ServiceInstance serviceInstance, String status);41 }42 43 // 服务发现接口44 public interface ServiceDiscovery {45 // 获取服务实例列表46 List<ServiceInstance> getInstances(String serviceId);47 48 // 获取所有服务ID49 List<String> getServiceIds();50 51 // 获取服务实例52 ServiceInstance getInstance(String serviceId, String instanceId);53 54 // 添加服务实例监听器55 void addInstanceChangeListener(String serviceId, InstanceChangeListener listener);56 }57 58 // 负载均衡器接口59 public interface LoadBalancer {60 // 选择服务实例61 ServiceInstance choose(String serviceId, List<ServiceInstance> instances);62 63 // 获取负载均衡策略64 String getLoadBalancerStrategy();65 }66 67 // 健康检查器接口68 public interface HealthChecker {69 // 检查服务实例健康状态70 boolean isHealthy(ServiceInstance instance);71 72 // 执行健康检查73 HealthCheckResult check(ServiceInstance instance);74 75 // 获取健康检查配置76 HealthCheckConfig getConfig();77 }78}1.2 服务发现模式
客户端发现模式
1public class ClientSideDiscovery {2 /*3 * 客户端发现模式4 * 客户端负责从服务注册中心获取服务实例列表,并实现负载均衡5 * 6 * 优点:7 * 1. 客户端可以灵活选择负载均衡策略8 * 2. 减少网络跳数,提高性能9 * 3. 客户端可以缓存服务实例列表10 * 11 * 缺点:12 * 1. 客户端需要实现服务发现逻辑13 * 2. 客户端需要处理服务注册中心的故障14 * 3. 不同语言的客户端需要重复实现15 */16 17 // 客户端发现实现18 public class ClientSideDiscoveryImpl {19 private final ServiceDiscovery serviceDiscovery;20 private final LoadBalancer loadBalancer;21 private final Map<String, List<ServiceInstance>> instanceCache = new ConcurrentHashMap<>();22 23 public ClientSideDiscoveryImpl(ServiceDiscovery serviceDiscovery, LoadBalancer loadBalancer) {24 this.serviceDiscovery = serviceDiscovery;25 this.loadBalancer = loadBalancer;26 }27 28 // 获取服务实例29 public ServiceInstance getInstance(String serviceId) {30 List<ServiceInstance> instances = getInstances(serviceId);31 if (instances.isEmpty()) {32 throw new ServiceNotFoundException("No instances found for service: " + serviceId);33 }34 return loadBalancer.choose(serviceId, instances);35 }36 37 // 获取服务实例列表38 public List<ServiceInstance> getInstances(String serviceId) {39 // 先从缓存获取40 List<ServiceInstance> instances = instanceCache.get(serviceId);41 if (instances == null) {42 // 从注册中心获取43 instances = serviceDiscovery.getInstances(serviceId);44 // 更新缓存45 instanceCache.put(serviceId, instances);46 }47 return instances;48 }49 50 // 刷新服务实例列表51 public void refreshInstances(String serviceId) {52 List<ServiceInstance> instances = serviceDiscovery.getInstances(serviceId);53 instanceCache.put(serviceId, instances);54 }55 56 // 添加服务实例变化监听器57 public void addInstanceChangeListener(String serviceId) {58 serviceDiscovery.addInstanceChangeListener(serviceId, event -> {59 // 服务实例发生变化时,刷新缓存60 refreshInstances(serviceId);61 });62 }63 }64 65 // 负载均衡策略实现66 public class LoadBalancerStrategies {67 68 // 轮询负载均衡69 public class RoundRobinLoadBalancer implements LoadBalancer {70 private final AtomicInteger counter = new AtomicInteger(0);71 72 @Override73 public ServiceInstance choose(String serviceId, List<ServiceInstance> instances) {74 if (instances.isEmpty()) {75 return null;76 }77 int index = counter.incrementAndGet() % instances.size();78 return instances.get(index);79 }80 81 @Override82 public String getLoadBalancerStrategy() {83 return "RoundRobin";84 }85 }86 87 // 随机负载均衡88 public class RandomLoadBalancer implements LoadBalancer {89 private final Random random = new Random();90 91 @Override92 public ServiceInstance choose(String serviceId, List<ServiceInstance> instances) {93 if (instances.isEmpty()) {94 return null;95 }96 int index = random.nextInt(instances.size());97 return instances.get(index);98 }99 100 @Override101 public String getLoadBalancerStrategy() {102 return "Random";103 }104 }105 106 // 权重负载均衡107 public class WeightedLoadBalancer implements LoadBalancer {108 109 @Override110 public ServiceInstance choose(String serviceId, List<ServiceInstance> instances) {111 if (instances.isEmpty()) {112 return null;113 }114 115 // 计算总权重116 int totalWeight = instances.stream()117 .mapToInt(instance -> getWeight(instance))118 .sum();119 120 // 随机选择121 int randomWeight = new Random().nextInt(totalWeight);122 int currentWeight = 0;123 124 for (ServiceInstance instance : instances) {125 currentWeight += getWeight(instance);126 if (randomWeight < currentWeight) {127 return instance;128 }129 }130 131 return instances.get(0);132 }133 134 private int getWeight(ServiceInstance instance) {135 String weightStr = instance.getMetadata().get("weight");136 return weightStr != null ? Integer.parseInt(weightStr) : 1;137 }138 139 @Override140 public String getLoadBalancerStrategy() {141 return "Weighted";142 }143 }144 145 // 最小连接数负载均衡146 public class LeastConnectionLoadBalancer implements LoadBalancer {147 private final Map<String, AtomicInteger> connectionCounts = new ConcurrentHashMap<>();148 149 @Override150 public ServiceInstance choose(String serviceId, List<ServiceInstance> instances) {151 if (instances.isEmpty()) {152 return null;153 }154 155 return instances.stream()156 .min(Comparator.comparingInt(instance -> 157 connectionCounts.getOrDefault(instance.getInstanceId(), new AtomicInteger(0)).get()))158 .orElse(instances.get(0));159 }160 161 public void incrementConnection(String instanceId) {162 connectionCounts.computeIfAbsent(instanceId, k -> new AtomicInteger(0)).incrementAndGet();163 }164 165 public void decrementConnection(String instanceId) {166 AtomicInteger count = connectionCounts.get(instanceId);167 if (count != null) {168 count.decrementAndGet();169 }170 }171 172 @Override173 public String getLoadBalancerStrategy() {174 return "LeastConnection";175 }176 }177 }178}服务端发现模式
1public class ServerSideDiscovery {2 /*3 * 服务端发现模式4 * 客户端通过负载均衡器(如API网关)访问服务,负载均衡器负责服务发现5 * 6 * 优点:7 * 1. 客户端不需要实现服务发现逻辑8 * 2. 服务发现逻辑集中管理9 * 3. 支持多种协议和语言10 * 11 * 缺点:12 * 1. 增加网络跳数13 * 2. 负载均衡器可能成为瓶颈14 * 3. 需要额外的基础设施15 */16 17 // API网关实现18 public class ApiGateway {19 private final ServiceDiscovery serviceDiscovery;20 private final LoadBalancer loadBalancer;21 private final HttpClient httpClient;22 23 public ApiGateway(ServiceDiscovery serviceDiscovery, LoadBalancer loadBalancer) {24 this.serviceDiscovery = serviceDiscovery;25 this.loadBalancer = loadBalancer;26 this.httpClient = HttpClient.create();27 }28 29 // 路由请求到目标服务30 public Mono<HttpResponse> route(HttpRequest request) {31 String serviceId = extractServiceId(request);32 ServiceInstance instance = getInstance(serviceId);33 34 if (instance == null) {35 return Mono.error(new ServiceNotFoundException("Service not found: " + serviceId));36 }37 38 String targetUrl = buildTargetUrl(instance, request);39 return forwardRequest(targetUrl, request);40 }41 42 // 获取服务实例43 private ServiceInstance getInstance(String serviceId) {44 List<ServiceInstance> instances = serviceDiscovery.getInstances(serviceId);45 if (instances.isEmpty()) {46 return null;47 }48 return loadBalancer.choose(serviceId, instances);49 }50 51 // 提取服务ID52 private String extractServiceId(HttpRequest request) {53 String path = request.getPath();54 // 从路径中提取服务ID,例如 /api/users/123 -> users55 String[] parts = path.split("/");56 if (parts.length >= 3 && "api".equals(parts[1])) {57 return parts[2];58 }59 throw new IllegalArgumentException("Invalid service path: " + path);60 }61 62 // 构建目标URL63 private String buildTargetUrl(ServiceInstance instance, HttpRequest request) {64 String protocol = instance.isSecure() ? "https" : "http";65 String baseUrl = protocol + "://" + instance.getHost() + ":" + instance.getPort();66 67 // 移除服务前缀,例如 /api/users/123 -> /users/12368 String path = request.getPath().replaceFirst("/api/" + extractServiceId(request), "");69 return baseUrl + path;70 }71 72 // 转发请求73 private Mono<HttpResponse> forwardRequest(String targetUrl, HttpRequest request) {74 return httpClient.request(request.getMethod())75 .uri(targetUrl)76 .send(request.getBody())77 .response();78 }79 }80 81 // 负载均衡器实现82 public class GatewayLoadBalancer {83 84 // 健康检查过滤器85 public class HealthCheckFilter {86 private final HealthChecker healthChecker;87 88 public HealthCheckFilter(HealthChecker healthChecker) {89 this.healthChecker = healthChecker;90 }91 92 public List<ServiceInstance> filterHealthyInstances(List<ServiceInstance> instances) {93 return instances.stream()94 .filter(instance -> healthChecker.isHealthy(instance))95 .collect(Collectors.toList());96 }97 }98 99 // 区域感知负载均衡100 public class ZoneAwareLoadBalancer implements LoadBalancer {101 private final String currentZone;102 103 public ZoneAwareLoadBalancer(String currentZone) {104 this.currentZone = currentZone;105 }106 107 @Override108 public ServiceInstance choose(String serviceId, List<ServiceInstance> instances) {109 if (instances.isEmpty()) {110 return null;111 }112 113 // 优先选择同区域的实例114 List<ServiceInstance> sameZoneInstances = instances.stream()115 .filter(instance -> currentZone.equals(instance.getZone()))116 .collect(Collectors.toList());117 118 if (!sameZoneInstances.isEmpty()) {119 return new RoundRobinLoadBalancer().choose(serviceId, sameZoneInstances);120 }121 122 // 如果没有同区域实例,选择其他区域的实例123 return new RoundRobinLoadBalancer().choose(serviceId, instances);124 }125 126 @Override127 public String getLoadBalancerStrategy() {128 return "ZoneAware";129 }130 }131 }132}1.3 服务发现架构对比
架构模式对比表
| 特性 | 客户端发现 | 服务端发现 |
|---|---|---|
| 实现复杂度 | 客户端需要实现发现逻辑 | 客户端简单,服务端复杂 |
| 性能 | 直接调用,性能好 | 多一次网络跳转 |
| 可用性 | 客户端需要处理注册中心故障 | 负载均衡器可能成为单点 |
| 语言支持 | 每种语言都需要实现 | 支持所有语言 |
| 负载均衡 | 客户端实现,策略灵活 | 服务端实现,策略统一 |
| 缓存 | 客户端可以缓存实例列表 | 服务端缓存,客户端无感知 |
架构图
1客户端发现模式:2Client → Service Discovery → Service Registry3 ↓4Client → Service Instance (直接调用)56服务端发现模式:7Client → Load Balancer → Service Discovery → Service Registry8 ↓9Load Balancer → Service Instance (代理调用)- 客户端发现:适合对性能要求高、客户端可控的场景
- 服务端发现:适合多语言、客户端不可控的场景
- 混合模式:可以结合两种模式的优点
2. Eureka 详解
2.1 Eureka 架构原理
Eureka是Netflix开源的服务发现组件,采用客户端发现模式。它由Eureka Server(服务端)和Eureka Client(客户端)组成。
Eureka 核心架构
1public class EurekaArchitecture {2 /*3 * Eureka核心架构4 * 1. Eureka Server:服务注册中心,存储服务实例信息5 * 2. Eureka Client:服务客户端,负责注册和发现服务6 * 3. Application Service:应用服务,向Eureka注册自己7 * 4. Application Client:应用客户端,从Eureka发现服务8 */9 10 // Eureka Server配置11 public class EurekaServerConfig {12 private final int port = 8761; // 默认端口13 private final boolean enableSelfPreservation = true; // 启用自我保护14 private final int evictionIntervalTimerInMs = 60000; // 清理间隔15 private final int renewalThresholdUpdateIntervalMs = 900000; // 更新阈值间隔16 private final int peerEurekaNodesUpdateIntervalMs = 600000; // 节点更新间隔17 18 // 集群配置19 private final List<String> peerEurekaNodes = Arrays.asList(20 "http://eureka-server-1:8761/eureka/",21 "http://eureka-server-2:8761/eureka/",22 "http://eureka-server-3:8761/eureka/"23 );24 }25 26 // Eureka Client配置27 public class EurekaClientConfig {28 private final String serviceUrl = "http://localhost:8761/eureka/"; // 服务地址29 private final int registryFetchIntervalSeconds = 30; // 获取注册表间隔30 private final int instanceInfoReplicationIntervalSeconds = 30; // 实例信息复制间隔31 private final int initialInstanceInfoReplicationIntervalSeconds = 40; // 初始复制间隔32 private final int eurekaServiceUrlPollIntervalSeconds = 300; // 服务地址轮询间隔33 private final int eurekaServerConnectTimeoutSeconds = 5; // 连接超时34 private final int eurekaServerReadTimeoutSeconds = 8; // 读取超时35 private final int eurekaServerTotalConnections = 200; // 总连接数36 private final int eurekaServerTotalConnectionsPerHost = 50; // 每主机连接数37 }38 39 // 服务实例信息40 public class EurekaInstanceInfo {41 private String instanceId; // 实例ID42 private String appName; // 应用名称43 private String appGroupName; // 应用组名称44 private String ipAddr; // IP地址45 private String sid; // 安全ID46 private int port; // 端口47 private int securePort; // 安全端口48 private String homePageUrl; // 首页URL49 private String statusPageUrl; // 状态页URL50 private String healthCheckUrl; // 健康检查URL51 private String secureHealthCheckUrl; // 安全健康检查URL52 private String vipAddress; // VIP地址53 private String secureVipAddress; // 安全VIP地址54 private String countryId; // 国家ID55 private String dataCenterInfo; // 数据中心信息56 private String hostName; // 主机名57 private String status; // 状态58 private String overriddenStatus; // 覆盖状态59 private boolean isCoordinatingDiscoveryServer; // 是否协调发现服务器60 private long lastUpdatedTimestamp; // 最后更新时间戳61 private long lastDirtyTimestamp; // 最后脏时间戳62 private String actionType; // 操作类型63 private String asgName; // ASG名称64 private Map<String, String> metadata; // 元数据65 }66}2.2 Eureka Server 配置与部署
基础配置
1spring:2 application:3 name: eureka-server4server:5 port: 876167eureka:8 instance:9 hostname: localhost10 client:11 # 是否向Eureka注册自己12 register-with-eureka: false13 # 是否从Eureka获取注册信息14 fetch-registry: false15 # 服务地址16 service-url:17 defaultZone: http://localhost:8761/eureka/18 server:19 # 关闭自我保护模式20 enable-self-preservation: false21 # 清理间隔(毫秒)22 eviction-interval-timer-in-ms: 6000023 # 响应缓存更新间隔24 response-cache-update-interval-ms: 300025 # 响应缓存自动过期时间26 response-cache-auto-expiration-in-seconds: 18027 # 启用压缩28 enable-compression: true29 # 最大压缩大小30 max-compression-size: 8192集群配置
1# eureka-server-1.yml2spring:3 application:4 name: eureka-server5server:6 port: 876178eureka:9 instance:10 hostname: eureka-server-111 client:12 register-with-eureka: false13 fetch-registry: false14 service-url:15 defaultZone: http://eureka-server-2:8762/eureka/,http://eureka-server-3:8763/eureka/16 server:17 enable-self-preservation: true18 eviction-interval-timer-in-ms: 600001920---21# eureka-server-2.yml22spring:23 application:24 name: eureka-server25server:26 port: 87622728eureka:29 instance:30 hostname: eureka-server-231 client:32 register-with-eureka: false33 fetch-registry: false34 service-url:35 defaultZone: http://eureka-server-1:8761/eureka/,http://eureka-server-3:8763/eureka/36 server:37 enable-self-preservation: true38 eviction-interval-timer-in-ms: 600003940---41# eureka-server-3.yml42spring:43 application:44 name: eureka-server45server:46 port: 87634748eureka:49 instance:50 hostname: eureka-server-351 client:52 register-with-eureka: false53 fetch-registry: false54 service-url:55 defaultZone: http://eureka-server-1:8761/eureka/,http://eureka-server-2:8762/eureka/56 server:57 enable-self-preservation: true58 eviction-interval-timer-in-ms: 60000启动类
1@EnableEurekaServer2@SpringBootApplication3public class EurekaServerApplication {4 5 public static void main(String[] args) {6 SpringApplication.run(EurekaServerApplication.class, args);7 }8 9 @Bean10 @Primary11 public EurekaClientConfigBean eurekaClientConfigBean() {12 EurekaClientConfigBean config = new EurekaClientConfigBean();13 config.setServiceUrl(new HashMap<String, String>() {{14 put("defaultZone", "http://localhost:8761/eureka/");15 }});16 config.setRegisterWithEureka(false);17 config.setFetchRegistry(false);18 return config;19 }20 21 @Bean22 public EurekaServerConfigBean eurekaServerConfigBean() {23 EurekaServerConfigBean config = new EurekaServerConfigBean();24 config.setEnableSelfPreservation(false);25 config.setEvictionIntervalTimerInMs(60000);26 return config;27 }28}2.3 Eureka Client 配置与使用
客户端配置
1spring:2 application:3 name: user-service4server:5 port: 808167eureka:8 instance:9 # 实例ID10 instance-id: ${spring.application.name}:${server.port}11 # 主机名12 hostname: localhost13 # 端口14 port: ${server.port}15 # 是否使用IP地址16 prefer-ip-address: true17 # IP地址18 ip-address: ${spring.cloud.client.ip-address}19 # 健康检查路径20 health-check-url-path: /actuator/health21 # 状态页路径22 status-page-url-path: /actuator/info23 # 首页路径24 home-page-url-path: /25 # 元数据26 metadata-map:27 zone: zone128 weight: 129 version: v1.0.030 # 租约续约间隔31 lease-renewal-interval-in-seconds: 3032 # 租约过期时间33 lease-expiration-duration-in-seconds: 9034 # 应用名称35 appname: ${spring.application.name}36 # 应用组名称37 app-group-name: ${spring.application.name}38 client:39 # 服务地址40 service-url:41 defaultZone: http://localhost:8761/eureka/42 # 是否注册到Eureka43 register-with-eureka: true44 # 是否从Eureka获取注册信息45 fetch-registry: true46 # 获取注册表间隔47 registry-fetch-interval-seconds: 3048 # 实例信息复制间隔49 instance-info-replication-interval-seconds: 3050 # 初始实例信息复制间隔51 initial-instance-info-replication-interval-seconds: 4052 # 服务地址轮询间隔53 eureka-service-url-poll-interval-seconds: 30054 # 连接超时55 eureka-server-connect-timeout-seconds: 556 # 读取超时57 eureka-server-read-timeout-seconds: 858 # 总连接数59 eureka-server-total-connections: 20060 # 每主机连接数61 eureka-server-total-connections-per-host: 5062 # 是否启用压缩63 g-zip-content: true64 # 压缩大小65 g-zip-content-size: 8192客户端启动类
1@EnableDiscoveryClient2@SpringBootApplication3public class UserServiceApplication {4 5 public static void main(String[] args) {6 SpringApplication.run(UserServiceApplication.class, args);7 }8 9 @Bean10 @LoadBalanced11 public RestTemplate restTemplate() {12 return new RestTemplate();13 }14 15 @Bean16 public EurekaClientConfigBean eurekaClientConfigBean() {17 EurekaClientConfigBean config = new EurekaClientConfigBean();18 config.setServiceUrl(new HashMap<String, String>() {{19 put("defaultZone", "http://localhost:8761/eureka/");20 }});21 config.setRegisterWithEureka(true);22 config.setFetchRegistry(true);23 config.setRegistryFetchIntervalSeconds(30);24 return config;25 }26 27 @Bean28 public EurekaInstanceConfigBean eurekaInstanceConfigBean() {29 EurekaInstanceConfigBean config = new EurekaInstanceConfigBean();30 config.setInstanceId(applicationName + ":" + serverPort);31 config.setPreferIpAddress(true);32 config.setLeaseRenewalIntervalInSeconds(30);33 config.setLeaseExpirationDurationInSeconds(90);34 return config;35 }36}服务调用示例
1@Service2public class UserService {3 4 @Autowired5 private RestTemplate restTemplate;6 7 @Autowired8 private DiscoveryClient discoveryClient;9 10 // 使用RestTemplate调用服务11 public User getUserById(String id) {12 // 通过服务名调用,自动负载均衡13 String url = "http://user-service/api/users/" + id;14 ResponseEntity<User> response = restTemplate.getForEntity(url, User.class);15 return response.getBody();16 }17 18 // 使用DiscoveryClient获取服务实例19 public List<ServiceInstance> getServiceInstances(String serviceId) {20 return discoveryClient.getInstances(serviceId);21 }22 23 // 手动选择服务实例24 public User getUserByIdWithManualSelection(String id) {25 List<ServiceInstance> instances = discoveryClient.getInstances("user-service");26 if (instances.isEmpty()) {27 throw new ServiceNotFoundException("No instances found for user-service");28 }29 30 // 选择第一个实例31 ServiceInstance instance = instances.get(0);32 String url = "http://" + instance.getHost() + ":" + instance.getPort() + "/api/users/" + id;33 ResponseEntity<User> response = restTemplate.getForEntity(url, User.class);34 return response.getBody();35 }36 37 // 使用负载均衡器38 @Autowired39 private LoadBalancerClient loadBalancerClient;40 41 public User getUserByIdWithLoadBalancer(String id) {42 ServiceInstance instance = loadBalancerClient.choose("user-service");43 if (instance == null) {44 throw new ServiceNotFoundException("No instances found for user-service");45 }46 47 String url = "http://" + instance.getHost() + ":" + instance.getPort() + "/api/users/" + id;48 ResponseEntity<User> response = restTemplate.getForEntity(url, User.class);49 return response.getBody();50 }51}- 集群部署:生产环境建议至少3个Eureka Server实例
- 自我保护:根据网络环境决定是否启用自我保护
- 健康检查:配置合适的健康检查路径和间隔
- 负载均衡:使用Spring Cloud LoadBalancer实现客户端负载均衡
- 监控告警:监控Eureka Server和Client的状态
3. 其他服务发现方案
3.1 Consul 服务发现
Consul是HashiCorp开发的服务发现和配置管理工具,支持多数据中心、KV存储、健康检查等功能。
Consul 特点
1public class ConsulFeatures {2 /*3 * Consul核心特点4 * 1. 服务发现:支持服务注册和发现5 * 2. 健康检查:支持多种健康检查方式6 * 3. KV存储:提供键值对存储功能7 * 4. 多数据中心:支持跨数据中心部署8 * 5. ACL:访问控制列表9 * 6. DNS:支持DNS查询10 */11}Consul 配置
1spring:2 application:3 name: order-service4 cloud:5 consul:6 host: localhost7 port: 85008 discovery:9 # 是否注册10 register: true11 # 注册的服务ID12 instance-id: ${spring.application.name}:${server.port}13 # 服务名称14 service-name: ${spring.application.name}15 # 服务端口16 port: ${server.port}17 # 健康检查路径18 health-check-path: /actuator/health19 # 健康检查间隔20 health-check-interval: 15s21 # 健康检查超时22 health-check-timeout: 5s23 # 心跳检查24 heartbeat:25 enabled: true26 interval: 15s27 ttl: 30s28 # 元数据29 metadata:30 version: v1.0.031 zone: zone132 weight: 133 # 标签34 tags:35 - version=v1.0.036 - zone=zone137 # 数据中心38 datacenter: dc139 # 命名空间40 namespace: default3.2 Nacos 服务发现
Nacos是阿里巴巴开源的服务发现和配置管理平台,支持服务注册、配置管理、动态DNS等功能。
Nacos 特点
1public class NacosFeatures {2 /*3 * Nacos核心特点4 * 1. 服务注册发现:支持服务注册和发现5 * 2. 配置管理:支持配置的动态更新6 * 3. 命名空间:支持多租户隔离7 * 4. 分组管理:支持服务分组8 * 5. 权重路由:支持基于权重的负载均衡9 * 6. 集群管理:支持集群部署10 */11}Nacos 配置
1spring:2 application:3 name: inventory-service4 cloud:5 nacos:6 discovery:7 # 服务器地址8 server-addr: localhost:88489 # 命名空间10 namespace: public11 # 分组12 group: DEFAULT_GROUP13 # 集群名称14 cluster-name: DEFAULT15 # 实例ID16 instance-id: ${spring.application.name}:${server.port}17 # 服务名称18 service: ${spring.application.name}19 # 端口20 port: ${server.port}21 # 权重22 weight: 123 # 是否启用24 enabled: true25 # 是否临时实例26 ephemeral: true27 # 元数据28 metadata:29 version: v1.0.030 zone: zone131 # 健康检查32 heart-beat-interval: 500033 heart-beat-timeout: 1500034 ip-delete-timeout: 30000负载均衡策略对比
- 轮询算法
- 权重算法
- 最少连接
1public class RoundRobinLoadBalancer implements LoadBalancer {2 private final AtomicInteger counter = new AtomicInteger(0);3 4 @Override5 public ServiceInstance choose(String serviceId, List<ServiceInstance> instances) {6 if (instances.isEmpty()) {7 return null;8 }9 int index = counter.incrementAndGet() % instances.size();10 return instances.get(index);11 }12}特点:
- ✅ 实现简单
- ✅ 请求分布均匀
- ❌ 不考虑服务器性能差异
1public class WeightedLoadBalancer implements LoadBalancer {2 @Override3 public ServiceInstance choose(String serviceId, List<ServiceInstance> instances) {4 int totalWeight = instances.stream()5 .mapToInt(this::getWeight)6 .sum();7 8 int randomWeight = new Random().nextInt(totalWeight);9 int currentWeight = 0;10 11 for (ServiceInstance instance : instances) {12 currentWeight += getWeight(instance);13 if (randomWeight < currentWeight) {14 return instance;15 }16 }17 return instances.get(0);18 }19 20 private int getWeight(ServiceInstance instance) {21 String weightStr = instance.getMetadata().get("weight");22 return weightStr != null ? Integer.parseInt(weightStr) : 1;23 }24}特点:
- ✅ 考虑服务器性能差异
- ✅ 灵活配置权重
- ❌ 配置相对复杂
1public class LeastConnectionLoadBalancer implements LoadBalancer {2 private final Map<String, AtomicInteger> connectionCounts = new ConcurrentHashMap<>();3 4 @Override5 public ServiceInstance choose(String serviceId, List<ServiceInstance> instances) {6 return instances.stream()7 .min(Comparator.comparingInt(instance -> 8 connectionCounts.getOrDefault(instance.getInstanceId(), 9 new AtomicInteger(0)).get()))10 .orElse(instances.get(0));11 }12 13 public void incrementConnection(String instanceId) {14 connectionCounts.computeIfAbsent(instanceId, k -> new AtomicInteger(0))15 .incrementAndGet();16 }17 18 public void decrementConnection(String instanceId) {19 AtomicInteger count = connectionCounts.get(instanceId);20 if (count != null) {21 count.decrementAndGet();22 }23 }24}特点:
- ✅ 考虑实际负载情况
- ✅ 适合长连接场景
- ❌ 需要维护连接状态
- Eureka:适合Spring Cloud生态,简单易用
- Consul:功能丰富,支持多数据中心
- Nacos:阿里生态,功能全面,中文文档丰富
4. 健康检查与监控
4.1 健康检查机制
健康检查类型对比
- HTTP检查
- TCP检查
- 心跳检查
1public class HttpHealthCheck {2 private final String healthCheckUrl;3 private final int timeout;4 private final RestTemplate restTemplate;5 6 public HttpHealthCheck(String healthCheckUrl, int timeout) {7 this.healthCheckUrl = healthCheckUrl;8 this.timeout = timeout;9 this.restTemplate = createRestTemplate();10 }11 12 public boolean check(String serviceUrl) {13 try {14 ResponseEntity<String> response = restTemplate.getForEntity(15 serviceUrl + healthCheckUrl, String.class);16 return response.getStatusCode().is2xxSuccessful();17 } catch (Exception e) {18 return false;19 }20 }21 22 private RestTemplate createRestTemplate() {23 RestTemplate template = new RestTemplate();24 HttpComponentsClientHttpRequestFactory factory = 25 new HttpComponentsClientHttpRequestFactory();26 factory.setConnectTimeout(timeout);27 factory.setReadTimeout(timeout);28 template.setRequestFactory(factory);29 return template;30 }31}适用场景:Web服务、REST API
1public class TcpHealthCheck {2 private final int port;3 private final int timeout;4 5 public TcpHealthCheck(int port, int timeout) {6 this.port = port;7 this.timeout = timeout;8 }9 10 public boolean check(String host) {11 try (Socket socket = new Socket()) {12 socket.connect(new InetSocketAddress(host, port), timeout);13 return socket.isConnected();14 } catch (Exception e) {15 return false;16 }17 }18}适用场景:数据库、缓存服务
1public class HeartbeatHealthCheck {2 private final Map<String, Long> lastHeartbeat = new ConcurrentHashMap<>();3 private final long heartbeatTimeout;4 5 public HeartbeatHealthCheck(long heartbeatTimeout) {6 this.heartbeatTimeout = heartbeatTimeout;7 }8 9 public void recordHeartbeat(String instanceId) {10 lastHeartbeat.put(instanceId, System.currentTimeMillis());11 }12 13 public boolean isHealthy(String instanceId) {14 Long lastBeat = lastHeartbeat.get(instanceId);15 if (lastBeat == null) {16 return false;17 }18 return System.currentTimeMillis() - lastBeat < heartbeatTimeout;19 }20}适用场景:轻量级服务、高频检查
4.2 监控指标
关键监控指标
1@Component2public class ServiceDiscoveryMetrics {3 4 private final MeterRegistry meterRegistry;5 private final Counter registeredServices;6 private final Counter deregisteredServices;7 private final Gauge activeServices;8 private final Timer discoveryLatency;9 10 public ServiceDiscoveryMetrics(MeterRegistry meterRegistry) {11 this.meterRegistry = meterRegistry;12 this.registeredServices = Counter.builder("service_discovery_registered_total")13 .description("Total number of registered services")14 .register(meterRegistry);15 this.deregisteredServices = Counter.builder("service_discovery_deregistered_total")16 .description("Total number of deregistered services")17 .register(meterRegistry);18 this.activeServices = Gauge.builder("service_discovery_active_services")19 .description("Number of active services")20 .register(meterRegistry, this, ServiceDiscoveryMetrics::getActiveServicesCount);21 this.discoveryLatency = Timer.builder("service_discovery_latency")22 .description("Service discovery latency")23 .register(meterRegistry);24 }25 26 public void recordServiceRegistration(String serviceId) {27 registeredServices.increment(Tags.of("service", serviceId));28 }29 30 public void recordServiceDeregistration(String serviceId) {31 deregisteredServices.increment(Tags.of("service", serviceId));32 }33 34 public void recordDiscoveryLatency(long duration) {35 discoveryLatency.record(duration, TimeUnit.MILLISECONDS);36 }37 38 private double getActiveServicesCount() {39 // 获取活跃服务数量的实际实现40 return 0.0;41 }42}5. 面试题精选
5.1 基础概念题
Q1: 什么是服务发现?它的作用是什么?
答: 服务发现是微服务架构中的一个重要组件,它解决了服务间如何相互找到并通信的问题。
主要作用:
- 动态注册:服务启动时自动向注册中心注册
- 自动发现:服务调用时自动从注册中心获取目标服务实例
- 负载均衡:在多个服务实例间分发请求
- 健康检查:监控服务实例的健康状态
- 故障转移:自动剔除故障实例
Q2: 客户端发现和服务端发现的区别是什么?
答: 主要区别如下:
- 客户端发现
- 服务端发现
优势:
- 客户端可以灵活选择负载均衡策略
- 减少网络跳数,提高性能
- 客户端可以缓存服务实例列表
劣势:
- 客户端需要实现服务发现逻辑
- 客户端需要处理注册中心故障
- 不同语言的客户端需要重复实现
优势:
- 客户端不需要实现服务发现逻辑
- 服务发现逻辑集中管理
- 支持多种协议和语言
劣势:
- 增加网络跳数
- 负载均衡器可能成为瓶颈
- 需要额外的基础设施
5.2 实践题
Q3: 如何配置Eureka集群?
答: Eureka集群配置步骤如下:
1# eureka-server-1.yml2eureka:3 client:4 service-url:5 defaultZone: http://eureka-server-2:8762/eureka/,http://eureka-server-3:8763/eureka/67# eureka-server-2.yml8eureka:9 client:10 service-url:11 defaultZone: http://eureka-server-1:8761/eureka/,http://eureka-server-3:8763/eureka/1213# eureka-server-3.yml14eureka:15 client:16 service-url:17 defaultZone: http://eureka-server-1:8761/eureka/,http://eureka-server-2:8762/eureka/Q4: Eureka的自我保护机制是什么?
答: Eureka自我保护机制是为了防止网络分区故障时服务被错误剔除。
工作原理:
- 当Eureka Server在短时间内丢失过多客户端时,会进入自我保护模式
- 在自我保护模式下,Eureka Server不会删除任何服务实例
- 当网络恢复后,Eureka Server会自动退出自我保护模式
1eureka:2 server:3 enable-self-preservation: true # 启用自我保护4 renewal-percent-threshold: 0.85 # 续约百分比阈值5.3 架构设计题
Q5: 如何设计一个高可用的服务发现系统?
答: 高可用服务发现系统设计包括以下几个方面:
- 多注册中心:部署多个注册中心实例,相互同步数据
- 客户端缓存:客户端缓存服务实例列表,减少对注册中心的依赖
- 健康检查:定期检查服务实例健康状态,及时剔除故障实例
- 监控告警:监控注册中心和服务实例状态,及时发现问题
- 理解原理:掌握服务发现的核心概念和工作原理
- 掌握配置:熟悉各种服务发现工具的配置方法
- 实践应用:通过实际项目练习服务发现的使用
- 监控运维:学会服务发现的监控和运维管理
- 高可用设计:了解服务发现的高可用设计方法
通过本章的学习,你应该已经深入理解了服务发现的核心概念、实现方案和最佳实践。服务发现是微服务架构的基础设施,合理使用服务发现可以显著提高系统的可用性和可维护性。在实际项目中,要根据业务需求选择合适的服务发现方案,并注重高可用设计和监控运维。
负载均衡策略对比
- 轮询算法
- 权重算法
- 最少连接
1public class RoundRobinLoadBalancer implements LoadBalancer {2 private final AtomicInteger counter = new AtomicInteger(0);3 4 @Override5 public ServiceInstance choose(String serviceId, List<ServiceInstance> instances) {6 if (instances.isEmpty()) {7 return null;8 }9 int index = counter.incrementAndGet() % instances.size();10 return instances.get(index);11 }12}特点:
- ✅ 实现简单
- ✅ 请求分布均匀
- ❌ 不考虑服务器性能差异
1public class WeightedLoadBalancer implements LoadBalancer {2 @Override3 public ServiceInstance choose(String serviceId, List<ServiceInstance> instances) {4 int totalWeight = instances.stream()5 .mapToInt(this::getWeight)6 .sum();7 8 int randomWeight = new Random().nextInt(totalWeight);9 int currentWeight = 0;10 11 for (ServiceInstance instance : instances) {12 currentWeight += getWeight(instance);13 if (randomWeight < currentWeight) {14 return instance;15 }16 }17 return instances.get(0);18 }19 20 private int getWeight(ServiceInstance instance) {21 String weightStr = instance.getMetadata().get("weight");22 return weightStr != null ? Integer.parseInt(weightStr) : 1;23 }24}特点:
- ✅ 考虑服务器性能差异
- ✅ 灵活配置权重
- ❌ 配置相对复杂
1public class LeastConnectionLoadBalancer implements LoadBalancer {2 private final Map<String, AtomicInteger> connectionCounts = new ConcurrentHashMap<>();3 4 @Override5 public ServiceInstance choose(String serviceId, List<ServiceInstance> instances) {6 return instances.stream()7 .min(Comparator.comparingInt(instance -> 8 connectionCounts.getOrDefault(instance.getInstanceId(), 9 new AtomicInteger(0)).get()))10 .orElse(instances.get(0));11 }12 13 public void incrementConnection(String instanceId) {14 connectionCounts.computeIfAbsent(instanceId, k -> new AtomicInteger(0))15 .incrementAndGet();16 }17 18 public void decrementConnection(String instanceId) {19 AtomicInteger count = connectionCounts.get(instanceId);20 if (count != null) {21 count.decrementAndGet();22 }23 }24}特点:
- ✅ 考虑实际负载情况
- ✅ 适合长连接场景
- ❌ 需要维护连接状态
- Eureka:适合Spring Cloud生态,简单易用
- Consul:功能丰富,支持多数据中心
- Nacos:阿里生态,功能全面,中文文档丰富
4. 健康检查与监控
4.1 健康检查机制
健康检查类型对比
- HTTP检查
- TCP检查
- 心跳检查
1public class HttpHealthCheck {2 private final String healthCheckUrl;3 private final int timeout;4 private final RestTemplate restTemplate;5 6 public HttpHealthCheck(String healthCheckUrl, int timeout) {7 this.healthCheckUrl = healthCheckUrl;8 this.timeout = timeout;9 this.restTemplate = createRestTemplate();10 }11 12 public boolean check(String serviceUrl) {13 try {14 ResponseEntity<String> response = restTemplate.getForEntity(15 serviceUrl + healthCheckUrl, String.class);16 return response.getStatusCode().is2xxSuccessful();17 } catch (Exception e) {18 return false;19 }20 }21 22 private RestTemplate createRestTemplate() {23 RestTemplate template = new RestTemplate();24 HttpComponentsClientHttpRequestFactory factory = 25 new HttpComponentsClientHttpRequestFactory();26 factory.setConnectTimeout(timeout);27 factory.setReadTimeout(timeout);28 template.setRequestFactory(factory);29 return template;30 }31}适用场景:Web服务、REST API
1public class TcpHealthCheck {2 private final int port;3 private final int timeout;4 5 public TcpHealthCheck(int port, int timeout) {6 this.port = port;7 this.timeout = timeout;8 }9 10 public boolean check(String host) {11 try (Socket socket = new Socket()) {12 socket.connect(new InetSocketAddress(host, port), timeout);13 return socket.isConnected();14 } catch (Exception e) {15 return false;16 }17 }18}适用场景:数据库、缓存服务
1public class HeartbeatHealthCheck {2 private final Map<String, Long> lastHeartbeat = new ConcurrentHashMap<>();3 private final long heartbeatTimeout;4 5 public HeartbeatHealthCheck(long heartbeatTimeout) {6 this.heartbeatTimeout = heartbeatTimeout;7 }8 9 public void recordHeartbeat(String instanceId) {10 lastHeartbeat.put(instanceId, System.currentTimeMillis());11 }12 13 public boolean isHealthy(String instanceId) {14 Long lastBeat = lastHeartbeat.get(instanceId);15 if (lastBeat == null) {16 return false;17 }18 return System.currentTimeMillis() - lastBeat < heartbeatTimeout;19 }20}适用场景:轻量级服务、高频检查
4.2 监控指标
关键监控指标
1@Component2public class ServiceDiscoveryMetrics {3 4 private final MeterRegistry meterRegistry;5 private final Counter registeredServices;6 private final Counter deregisteredServices;7 private final Gauge activeServices;8 private final Timer discoveryLatency;9 10 public ServiceDiscoveryMetrics(MeterRegistry meterRegistry) {11 this.meterRegistry = meterRegistry;12 this.registeredServices = Counter.builder("service_discovery_registered_total")13 .description("Total number of registered services")14 .register(meterRegistry);15 this.deregisteredServices = Counter.builder("service_discovery_deregistered_total")16 .description("Total number of deregistered services")17 .register(meterRegistry);18 this.activeServices = Gauge.builder("service_discovery_active_services")19 .description("Number of active services")20 .register(meterRegistry, this, ServiceDiscoveryMetrics::getActiveServicesCount);21 this.discoveryLatency = Timer.builder("service_discovery_latency")22 .description("Service discovery latency")23 .register(meterRegistry);24 }25 26 public void recordServiceRegistration(String serviceId) {27 registeredServices.increment(Tags.of("service", serviceId));28 }29 30 public void recordServiceDeregistration(String serviceId) {31 deregisteredServices.increment(Tags.of("service", serviceId));32 }33 34 public void recordDiscoveryLatency(long duration) {35 discoveryLatency.record(duration, TimeUnit.MILLISECONDS);36 }37 38 private double getActiveServicesCount() {39 // 获取活跃服务数量的实际实现40 return 0.0;41 }42}5. 面试题精选
5.1 基础概念题
Q1: 什么是服务发现?它的作用是什么?
答: 服务发现是微服务架构中的一个重要组件,它解决了服务间如何相互找到并通信的问题。
主要作用:
- 动态注册:服务启动时自动向注册中心注册
- 自动发现:服务调用时自动从注册中心获取目标服务实例
- 负载均衡:在多个服务实例间分发请求
- 健康检查:监控服务实例的健康状态
- 故障转移:自动剔除故障实例
Q2: 客户端发现和服务端发现的区别是什么?
答: 主要区别如下:
- 客户端发现
- 服务端发现
优势:
- 客户端可以灵活选择负载均衡策略
- 减少网络跳数,提高性能
- 客户端可以缓存服务实例列表
劣势:
- 客户端需要实现服务发现逻辑
- 客户端需要处理注册中心故障
- 不同语言的客户端需要重复实现
优势:
- 客户端不需要实现服务发现逻辑
- 服务发现逻辑集中管理
- 支持多种协议和语言
劣势:
- 增加网络跳数
- 负载均衡器可能成为瓶颈
- 需要额外的基础设施
5.2 实践题
Q3: 如何配置Eureka集群?
答: Eureka集群配置步骤如下:
1# eureka-server-1.yml2eureka:3 client:4 service-url:5 defaultZone: http://eureka-server-2:8762/eureka/,http://eureka-server-3:8763/eureka/67# eureka-server-2.yml8eureka:9 client:10 service-url:11 defaultZone: http://eureka-server-1:8761/eureka/,http://eureka-server-3:8763/eureka/1213# eureka-server-3.yml14eureka:15 client:16 service-url:17 defaultZone: http://eureka-server-1:8761/eureka/,http://eureka-server-2:8762/eureka/Q4: Eureka的自我保护机制是什么?
答: Eureka自我保护机制是为了防止网络分区故障时服务被错误剔除。
工作原理:
- 当Eureka Server在短时间内丢失过多客户端时,会进入自我保护模式
- 在自我保护模式下,Eureka Server不会删除任何服务实例
- 当网络恢复后,Eureka Server会自动退出自我保护模式
1eureka:2 server:3 enable-self-preservation: true # 启用自我保护4 renewal-percent-threshold: 0.85 # 续约百分比阈值5.3 架构设计题
Q5: 如何设计一个高可用的服务发现系统?
答: 高可用服务发现系统设计包括以下几个方面:
- 多注册中心:部署多个注册中心实例,相互同步数据
- 客户端缓存:客户端缓存服务实例列表,减少对注册中心的依赖
- 健康检查:定期检查服务实例健康状态,及时剔除故障实例
- 监控告警:监控注册中心和服务实例状态,及时发现问题
- 理解原理:掌握服务发现的核心概念和工作原理
- 掌握配置:熟悉各种服务发现工具的配置方法
- 实践应用:通过实际项目练习服务发现的使用
- 监控运维:学会服务发现的监控和运维管理
- 高可用设计:了解服务发现的高可用设计方法
通过本章的学习,你应该已经深入理解了服务发现的核心概念、实现方案和最佳实践。服务发现是微服务架构的基础设施,合理使用服务发现可以显著提高系统的可用性和可维护性。在实际项目中,要根据业务需求选择合适的服务发现方案,并注重高可用设计和监控运维。态,功能全面,中文文档丰富 :::
4. 健康检查与监控
4.1 健康检查机制
健康检查类型
1public class HealthCheckTypes {2 /*3 * 健康检查类型4 * 1. HTTP检查:通过HTTP请求检查服务状态5 * 2. TCP检查:通过TCP连接检查服务状态6 * 3. 脚本检查:通过执行脚本检查服务状态7 * 4. 心跳检查:通过心跳机制检查服务状态8 */9 10 // HTTP健康检查11 public class HttpHealthCheck {12 private final String healthCheckUrl;13 private final int timeout;14 private final int interval;15 private final int unhealthyThreshold;16 private final int healthyThreshold;17 18 public boolean check(String serviceUrl) {19 try {20 RestTemplate restTemplate = new RestTemplate();21 restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory());22 ((HttpComponentsClientHttpRequestFactory) restTemplate.getRequestFactory())23 .setConnectTimeout(timeout);24 ((HttpComponentsClientHttpRequestFactory) restTemplate.getRequestFactory())25 .setReadTimeout(timeout);26 27 ResponseEntity<String> response = restTemplate.getForEntity(28 serviceUrl + healthCheckUrl, String.class);29 return response.getStatusCode().is2xxSuccessful();30 } catch (Exception e) {31 return false;32 }33 }34 }35 36 // TCP健康检查37 public class TcpHealthCheck {38 private final int port;39 private final int timeout;40 41 public boolean check(String host) {42 try (Socket socket = new Socket()) {43 socket.connect(new InetSocketAddress(host, port), timeout);44 return socket.isConnected();45 } catch (Exception e) {46 return false;47 }48 }49 }50 51 // 心跳健康检查52 public class HeartbeatHealthCheck {53 private final Map<String, Long> lastHeartbeat = new ConcurrentHashMap<>();54 private final long heartbeatTimeout;55 56 public void recordHeartbeat(String instanceId) {57 lastHeartbeat.put(instanceId, System.currentTimeMillis());58 }59 60 public boolean isHealthy(String instanceId) {61 Long lastBeat = lastHeartbeat.get(instanceId);62 if (lastBeat == null) {63 return false;64 }65 return System.currentTimeMillis() - lastBeat < heartbeatTimeout;66 }67 }68}4.2 监控指标
关键监控指标
1public class MonitoringMetrics {2 3 @Component4 public class ServiceDiscoveryMetrics {5 6 private final MeterRegistry meterRegistry;7 private final Counter registeredServices;8 private final Counter deregisteredServices;9 private final Gauge activeServices;10 private final Timer discoveryLatency;11 12 public ServiceDiscoveryMetrics(MeterRegistry meterRegistry) {13 this.meterRegistry = meterRegistry;14 this.registeredServices = Counter.builder("service_discovery_registered_total")15 .description("Total number of registered services")16 .register(meterRegistry);17 this.deregisteredServices = Counter.builder("service_discovery_deregistered_total")18 .description("Total number of deregistered services")19 .register(meterRegistry);20 this.activeServices = Gauge.builder("service_discovery_active_services")21 .description("Number of active services")22 .register(meterRegistry, this, ServiceDiscoveryMetrics::getActiveServicesCount);23 this.discoveryLatency = Timer.builder("service_discovery_latency")24 .description("Service discovery latency")25 .register(meterRegistry);26 }27 28 public void recordServiceRegistration(String serviceId) {29 registeredServices.increment(Tags.of("service", serviceId));30 }31 32 public void recordServiceDeregistration(String serviceId) {33 deregisteredServices.increment(Tags.of("service", serviceId));34 }35 36 public void recordDiscoveryLatency(long duration) {37 discoveryLatency.record(duration, TimeUnit.MILLISECONDS);38 }39 40 private double getActiveServicesCount() {41 // 获取活跃服务数量42 return 0.0; // 实际实现中需要计算43 }44 }45}5. 面试题精选
5.1 基础概念题
Q1: 什么是服务发现?它的作用是什么?
答: 服务发现是微服务架构中的一个重要组件,它解决了服务间如何相互找到并通信的问题。
主要作用:
- 动态注册:服务启动时自动向注册中心注册
- 自动发现:服务调用时自动从注册中心获取目标服务实例
- 负载均衡:在多个服务实例间分发请求
- 健康检查:监控服务实例的健康状态
- 故障转移:自动剔除故障实例
Q2: 客户端发现和服务端发现的区别是什么?
答: 主要区别如下:
客户端发现:
- 客户端负责从注册中心获取服务实例列表
- 客户端实现负载均衡逻辑
- 性能好,减少网络跳数
- 客户端需要处理注册中心故障
服务端发现:
- 负载均衡器负责服务发现
- 客户端通过负载均衡器访问服务
- 支持多种语言和协议
- 增加网络跳数,负载均衡器可能成为瓶颈
5.2 实践题
Q3: 如何配置Eureka集群?
答: Eureka集群配置步骤如下:
- 配置多个Eureka Server:
1# eureka-server-1.yml2eureka:3 client:4 service-url:5 defaultZone: http://eureka-server-2:8762/eureka/,http://eureka-server-3:8763/eureka/67# eureka-server-2.yml8eureka:9 client:10 service-url:11 defaultZone: http://eureka-server-1:8761/eureka/,http://eureka-server-3:8763/eureka/1213# eureka-server-3.yml14eureka:15 client:16 service-url:17 defaultZone: http://eureka-server-1:8761/eureka/,http://eureka-server-2:8762/eureka/- 客户端配置:
1eureka:2 client:3 service-url:4 defaultZone: http://eureka-server-1:8761/eureka/,http://eureka-server-2:8762/eureka/,http://eureka-server-3:8763/eureka/Q4: Eureka的自我保护机制是什么?
答: Eureka自我保护机制是为了防止网络分区故障时服务被错误剔除。
工作原理:
- 当Eureka Server在短时间内丢失过多客户端时,会进入自我保护模式
- 在自我保护模式下,Eureka Server不会删除任何服务实例
- 当网络恢复后,Eureka Server会自动退出自我保护模式
配置:
1eureka:2 server:3 enable-self-preservation: true # 启用自我保护4 renewal-percent-threshold: 0.85 # 续约百分比阈值5.3 架构设计题
Q5: 如何设计一个高可用的服务发现系统?
答: 高可用服务发现系统设计包括以下几个方面:
- 多注册中心:
1@Configuration2public class MultiRegistryConfig {3 4 @Bean5 @Primary6 public ServiceRegistry primaryRegistry() {7 return new EurekaServiceRegistry();8 }9 10 @Bean11 public ServiceRegistry backupRegistry() {12 return new ConsulServiceRegistry();13 }14 15 @Bean16 public ServiceDiscovery multiRegistryDiscovery() {17 return new MultiRegistryServiceDiscovery(18 Arrays.asList(primaryRegistry(), backupRegistry())19 );20 }21}- 客户端缓存:
1@Component2public class CachedServiceDiscovery {3 4 private final Map<String, List<ServiceInstance>> instanceCache = new ConcurrentHashMap<>();5 private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);6 7 public CachedServiceDiscovery() {8 // 定期刷新缓存9 scheduler.scheduleAtFixedRate(this::refreshCache, 30, 30, TimeUnit.SECONDS);10 }11 12 public List<ServiceInstance> getInstances(String serviceId) {13 return instanceCache.computeIfAbsent(serviceId, this::fetchInstances);14 }15 16 private List<ServiceInstance> fetchInstances(String serviceId) {17 // 从注册中心获取实例列表18 return serviceDiscovery.getInstances(serviceId);19 }20}- 健康检查:
1@Component2public class HealthCheckService {3 4 private final Map<String, HealthStatus> healthStatus = new ConcurrentHashMap<>();5 6 public boolean isHealthy(String instanceId) {7 HealthStatus status = healthStatus.get(instanceId);8 return status != null && status.isHealthy();9 }10 11 @Scheduled(fixedRate = 30000)12 public void performHealthCheck() {13 // 执行健康检查14 for (ServiceInstance instance : getAllInstances()) {15 boolean healthy = checkHealth(instance);16 healthStatus.put(instance.getInstanceId(), new HealthStatus(healthy));17 }18 }19}- 理解原理:掌握服务发现的核心概念和工作原理
- 掌握配置:熟悉各种服务发现工具的配置方法
- 实践应用:通过实际项目练习服务发现的使用
- 监控运维:学会服务发现的监控和运维管理
- 高可用设计:了解服务发现的高可用设计方法
通过本章的学习,你应该已经深入理解了服务发现的核心概念、实现方案和最佳实践。服务发现是微服务架构的基础设施,合理使用服务发现可以显著提高系统的可用性和可维护性。在实际项目中,要根据业务需求选择合适的服务发现方案,并注重高可用设计和监控运维。
参与讨论