1、簡介
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<EurekaRegistration>接口)調用register方法注冊服務。
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 }
待完善!