Eureka注册中心原理
1、简介
Eureka 是 Netflix 出品的用于实现服务注册和发现的工具,Spring Cloud 封装了 Netflix 公司开发的 Eureka 模块来实现服务注册和发现Eureka采用C-S的设计架构,包含Eureka Server 和Eureka Client两个组件。2、原理
(图片引用网上的)
2、一致性算法
CAP理论:C(一致性)、A(可用性)和P(分区容错性)。
- 一致性:它要求在同一时刻点,分布式系统中的所有数据备份都处于同一状态。
- 可用性:在系统集群的一部分节点宕机后,系统依然能够响应用户的请求。
- 分区容错性:在网络区间通信出现失败,系统能够容忍。
CAP理论指出,一个分布式系统不可能同时满足C、A和P。基于网络不稳定型,在分布式系统必须保证P在,因此我们只能在A和C之间进行权衡。
Zookeeper保证CP:
Zookeeper采用主从模式、paxos算法保证服务一致性,有leader节点和follow节点。当leader节点down掉之后,剩余节点会重新进行选举。选举过程中会导致服务不可用,丢掉了可用行。
Consul保证CP:
Consul采用主从模式、Raft算法保证服务一致性、基于Go语言开发的支持多数据中心分布式高可用的服务发布和注册服务软件;支持健康检查,集群间通过RPC的方式调用(HTTP和DNS)。
Eureka保证AP:
Eureka各个节点是平等的,部分节点挂掉不影响正常节点的工作,剩余节点依然可以提供注册和查询服务。Eureka客户端在向某个Eureka服务端注册或发现连接失败时,则会自动切换至其它Eureka服务端节点,只要有一个Eureka服务端节点正常,就能保证注册服务可用(保证可用性),只不过查到的信息可能不是最新的(不保证强一致性)。
常用服务发现产品对比:
3、架构图
Eureka注册中心未使用任何数据强一致性算法,仅通过注册中心之间注册服务数据复制方式保证的最终一致性。放弃数据强一致性,提升了注册的效率、降低了注册代价,同时提高了集群运行的健壮性。
4、健康检查
5、服务端启动过程分析
启动服务从注解开始:@EnableEurekaServer
1 @EnableDiscoveryClient //客户端发现与注册 2 @Target(ElementType.TYPE) 3 @Retention(RetentionPolicy.RUNTIME) 4 @Documented 5 @Import(EurekaServerMarkerConfiguration.class) //服务端通过注解方式引入,启动服务端 6 public @interface EnableEurekaServer {}
通过EurekaServerMarkerConfiguration类,找到EurekaServerAutoConfiguration自动配置类:
1 @Configuration 2 @Import(EurekaServerInitializerConfiguration.class) //注册中心初始化器配置 3 @ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class) //发现上面的注解中引入Bean 4 @EnableConfigurationProperties({ EurekaDashboardProperties.class, //注册中心可视化仪表板属性 5 InstanceRegistryProperties.class }) //实例注册属性 6 @PropertySource("classpath:/eureka/server.properties") 7 public class EurekaServerAutoConfiguration extends WebMvcConfigurerAdapter { 8 。。。。。。 9 }
类EurekaServerInitializerConfiguration初始化启动注册中心:
1 @Override 2 public void start() { 3 new Thread(new Runnable() { 4 @Override 5 public void run() { 6 try { 7 //TODO: is this class even needed now? 8 eurekaServerBootstrap.contextInitialized(EurekaServerInitializerConfiguration.this.servletContext); 9 log.info("Started Eureka Server"); 10 11 publish(new EurekaRegistryAvailableEvent(getEurekaServerConfig())); 12 EurekaServerInitializerConfiguration.this.running = true; 13 publish(new EurekaServerStartedEvent(getEurekaServerConfig())); 14 } 15 catch (Exception ex) { 16 // Help! 17 log.error("Could not initialize Eureka servlet context", ex); 18 } 19 } 20 }).start(); //启动一个新线程,在新线程中启动注册中心 21 }
1 public void contextInitialized(ServletContext context) { 2 try { 3 initEurekaEnvironment(); //初始化环境 4 initEurekaServerContext(); //初始化上下文 5 6 context.setAttribute(EurekaServerContext.class.getName(), this.serverContext); 7 } 8 catch (Throwable e) { 9 log.error("Cannot bootstrap eureka server :", e); 10 throw new RuntimeException("Cannot bootstrap eureka server :", e); 11 } 12 }
1 protected void initEurekaEnvironment() throws Exception { 2 log.info("Setting the eureka configuration.."); 3 4 String dataCenter = ConfigurationManager.getConfigInstance().getString(EUREKA_DATACENTER); 6 if (dataCenter == null) { 7 log.info( 8 "Eureka data center value eureka.datacenter is not set, defaulting to default"); 9 ConfigurationManager.getConfigInstance().setProperty(ARCHAIUS_DEPLOYMENT_DATACENTER, DEFAULT); //archaius.deployment.datacenter 11 } 12 else { 13 ConfigurationManager.getConfigInstance().setProperty(ARCHAIUS_DEPLOYMENT_DATACENTER, dataCenter);15 } 16 String environment = ConfigurationManager.getConfigInstance().getString(EUREKA_ENVIRONMENT); 18 if (environment == null) { 19 ConfigurationManager.getConfigInstance().setProperty(ARCHAIUS_DEPLOYMENT_ENVIRONMENT, TEST); //archaius.deployment.environment 21 log.info( 22 "Eureka environment value eureka.environment is not set, defaulting to test"); 23 } 24 else { 25 ConfigurationManager.getConfigInstance().setProperty(ARCHAIUS_DEPLOYMENT_ENVIRONMENT, environment); 27 } 28 }
1 protected void initEurekaServerContext() throws Exception { 2 // For backward compatibility 3 JsonXStream.getInstance().registerConverter(new V1AwareInstanceInfoConverter(), 4 XStream.PRIORITY_VERY_HIGH); 5 XmlXStream.getInstance().registerConverter(new V1AwareInstanceInfoConverter(), 6 XStream.PRIORITY_VERY_HIGH); 7 8 if (isAws(this.applicationInfoManager.getInfo())) { 9 this.awsBinder = new AwsBinderDelegate(this.eurekaServerConfig, this.eurekaClientConfig, this.registry, this.applicationInfoManager); 11 this.awsBinder.start(); 12 } 13 14 EurekaServerContextHolder.initialize(this.serverContext); 15 16 log.info("Initialized server context"); 17 18 // Copy registry from neighboring eureka node 19 int registryCount = this.registry.syncUp(); 20 this.registry.openForTraffic(this.applicationInfoManager, registryCount); 21 22 // Register all monitoring statistics. 23 EurekaMonitors.registerAllStats(); //注册监控中的统计数据 24 }
客户端将注册服务到注册中心,在org.springframework.cloud.netflix.eureka.server.InstanceRegistry类中,该类继承com.netflix.eureka.registry.PeerAwareInstanceRegistryImpl类;PeerAwareInstanceRegistryImpl类继承抽象类com.netflix.eureka.registry.AbstractInstanceRegistry(*),该类中的属性registry属性保存注册服务数据。
1 private final ConcurrentHashMap registry = new ConcurrentHashMap(); //抽闲类使用ConcurrentHashMap作为数据中心,将数据保存在内存中
7、客户端注册过程分析
1 @Target(ElementType.TYPE) 2 @Retention(RetentionPolicy.RUNTIME) 3 @Documented 4 @Inherited 5 @Import(EnableDiscoveryClientImportSelector.class) 6 public @interface EnableDiscoveryClient { 7 8 /** 9 * If true, the ServiceRegistry will automatically register the local server. 10 */ 11 boolean autoRegister() default true; 12 }
org.springframework.cloud.netflix.eureka.EurekaDiscoveryClientConfiguration类自动注册服务内属性serviceRegistry(org.springframework.cloud.netflix.eureka.serviceregistry.EurekaServiceRegistry类实例,实现org.springframework.cloud.client.serviceregistry.ServiceRegistry
1 @Override 2 public void start() { //实现org.springframework.context.Lifecycle生命周期接口start方法 3 // only set the port if the nonSecurePort is 0 and this.port != 0 4 if (this.port.get() != 0 && this.registration.getNonSecurePort() == 0) { 5 this.registration.setNonSecurePort(this.port.get()); 6 } 7 8 // only initialize if nonSecurePort is greater than 0 and it isn't already running 9 // because of containerPortInitializer below 10 if (!this.running.get() && this.registration.getNonSecurePort() > 0) { 11 12 this.serviceRegistry.register(this.registration); //注册服务 13 14 this.context.publishEvent(new InstanceRegisteredEvent<>(this, this.registration.getInstanceConfig())); 16 this.running.set(true); 17 } 18 }
注册服务客户端:
1 @Override 2 public void register(EurekaRegistration reg) { 3 maybeInitializeClient(reg); 4 5 if (log.isInfoEnabled()) { 6 log.info("Registering application " + reg.getInstanceConfig().getAppname() 7 + " with eureka with status " 8 + reg.getInstanceConfig().getInitialStatus()); 9 } 10 11 reg.getApplicationInfoManager() 12 .setInstanceStatus(reg.getInstanceConfig().getInitialStatus()); 13 14 if (reg.getHealthCheckHandler() != null) { 15 reg.getEurekaClient().registerHealthCheck(reg.getHealthCheckHandler()); 16 } 17 }
待完善!