Spring Cloud項目(三): 服務註冊與發現(Eureka)、原理及其常見問題

好不容易能空出時間整理一下這幾個月的所得了

Eureka

一、 簡介

Eureka是Netflix開發的服務發現框架,本身是一個基於REST的服務,主要用於定位運行在AWS域中的中間層服務,以達到負載均衡和中間層服務故障轉移的目的。SpringCloud將它集成在其子項目spring-cloud-netflix中,以實現SpringCloud的服務發現功能。

注:AWS(亞馬遜雲計算服務),其中重要組成部分有Region(區域)和Available Zone(可用區),可以把AWS理解爲一個書店,有很多個書架(region),書架相對獨立,每個書架都有都有很多層(Available Zone),每個層的書要保持一致就需要用管理員(通過高帶寬、低延遲網絡與完全冗餘的專用城域光纖互連,爲可用區之間提供高吞吐量和低延遲的網絡。網絡性能足以完成可用區之間的同步複製)。

二、 組成部分

Eureka包含兩個組件:Eureka Server和Eureka Client

  1. Eureka Server(多個server可以互相註冊)
    Eureka Server提供服務註冊服務,各個節點啓動後,會在Eureka Server中進行註冊,這樣Eureka Server中的服務註冊表中將會存儲所有可用服務節點的信息,服務節點的信息可以在界面中直觀的看到。
    Eureka Server本身也是一個服務,默認情況下會自動註冊到Eureka註冊中心。
    如果搭建單機版的Eureka Server註冊中心,則需要配置取消Eureka Server的自動註冊邏輯。畢竟當前服務註冊到當前服務代表的註冊中心中是一個說不通的邏輯。
    Eureka Server通過Register、Get、Renew等接口提供服務的註冊、發現和心跳檢測等服務。
  2. Eureka Client(註冊在server上)
    Eureka Client是一個java客戶端,用於簡化與Eureka Server的交互,客戶端同時也具備一個內置的、使用輪詢(round-robin)負載算法的負載均衡器。在應用啓動後,將會向Eureka Server發送心跳,默認週期爲30秒,如果Eureka Server在多個心跳週期內沒有接收到某個節點的心跳,Eureka Server將會從服務註冊表中把這個服務節點移除(默認90秒)。
    Eureka Client分爲兩個角色,分別是:Application Service(Service Provider 服務提供方)Application Client(Service Consumer服務消費方)

三、Eureka Server架構圖

在這裏插入圖片描述

  • Register(服務註冊):把自己的IP和端口註冊給Eureka。
  • Renew(服務續約):發送心跳包,每30秒發送一次。告訴Eureka自己還活着。
  • Cancel(服務下線):當provider關閉時會向Eureka發送消息,把自己從服務列表中刪除。防止consumer調用到不存在的服務。
  • Get Registry(獲取服務註冊列表):獲取其他服務列表。
  • Replicate(集羣中數據同步):eureka集羣中服務端之間的數據複製與同步。
  • Make Remote Call(遠程調用):完成服務的遠程調用。

四、單機版本的Eureka server 和 Eureka client(客戶端提供方)和 Eureka client(客戶端消費方)

Eureka server項目

pom.xml

<!-- 新增eureka 服務端依賴-->
<dependency>
	 <groupId>org.springframework.cloud</groupId>
     <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>

application.yml

#設置eureka server 服務的端口號
server:
  port : 8761
spring:
  application:
    name: eurekazz
eureka:
  client:
    fetch-registry: false #是否從Eureka服務中獲取註冊信息,由於是單機,所以不需要同步
    register-with-eureka: false #由於是單機版本就不向註冊中心註冊自己
  service-url:
     defaultZone: http://127.0.0.1:8761/eureka #註冊到註冊中心,如果不是單機,則需要將其他的Eureka server地址用逗號隔開

啓動類

@SpringBootApplication
@EnableEurekaServer  //開啓eureka服務
public class EurekazzApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekazzApplication.class, args);
    }
}

當控制檯出現如下提示,則可以打開http://localhost:8761/,就能看到eureka服務頁面
在這裏插入圖片描述

Eureka client(客戶端提供方)

pom.xml

		<dependency>
 			<!-- eureka依賴   -->   
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>
        <dependency>
        	<!-- 客戶端依賴,註冊在eureka上   -->   
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
        	<!-- 由於客戶端會提供web訪問,所以添加此依賴  -->  
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

bootstrap.yml(一般將重要參數放入該文件裏,優先級高,bootstrap一定會被讀取(覆蓋application.yml的相同配置))

server:
  port: 6666 #端口號
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:8761/eureka #註冊到註冊中心
  instance:
    prefer-ip-address: true #顯示ip

啓動類

//@EnableDiscoverClient 與@EnableEurekaClient相同效果
@EnableEurekaClient //加入註冊中心 也可以不需要寫,Springcloud Edgware以上,引入依賴就會自動加載配置文件,自動註冊。
@SpringCloudApplication
/**
 * author : ww
 */

public class ConfigseverApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConfigseverApplication.class, args);
    }
}
Eureka client(客戶端消費方)

與Eureka client(客戶端提供方)配置一樣。

五、集羣的Eureka server

Eureka client 會定期的連接Eureka server獲取該server下注冊信息,並緩存在本地,單機情況下,如果Eureka server宕機了,client端還是會根據緩存信息去訪問,但無法更新緩存信息,很容易造成問題,所以一般都會使用高可用的Eureka server集羣。

集羣的Eureka server

新建兩個Eureka server項目(eurekaone,eurekatwo),作爲集羣。除了配置文件不同,其他都和上面Eureka server項目相同
1.如果是windows,則修改一下本地映射地址(C:\Windows\System32\drivers\etc\hosts),目的是爲了方便在註冊中心中辨別
在這裏插入圖片描述
2.application.yml(Eureka server 01項目)

#設置eureka server 服務的端口號
server:
  port : 8761
spring:
  application:
    name: eurekaone
eureka:
  client:
    fetch-registry: true #同步註冊中心的信息
    register-with-eureka: true #註冊中心註冊自己
    service-url:
      #基本上把集羣的所有的註冊中心地址都放進來,避免當前服務地址宕機後,其他Eureka client項目註冊無法同步
      defaultZone: http://peer2:8762/eureka,http://127.0.0.1:8761/eureka 
  instance:
    hostname: peer1
    instance-id: peer1

3.application.yml(Eureka server 02項目)

#設置eureka server 服務的端口號
server:
  port : 8762
spring:
  application:
    name: eurekatwo
eureka:
  service-url:
     defaultZone: http://peer2:8762/eureka,http://127.0.0.1:8761/eureka 
  client:
    fetch-registry: true 
    register-with-eureka: true 

4.運行兩個項目,分別訪問:http://peer1:8761/http://peer2:8762/
DS Replicas 爲同步註冊信息的列表
在這裏插入圖片描述
在這裏插入圖片描述
5.開啓一個Eureka client項目任意註冊 一個Eureka server項目,關閉一個Eureka server項目,依然能訪問到它的註冊信息

eureka啓動執行邏輯:

EurekaServerMarkerConfiguration(創建maker)–>EurekaServerAutoConfiguration(加載配置文件)–>EurekaServerInitializerConfiguration(初始化eureka)–>eurekaServerBootstrap(啓動eureka加載,相鄰節點註冊信息)

  1. 從spring-cloud-netflix-eureka-server包中,可以看到在@EurekaServerMarkerConfiguration裝載Marker後臺,依賴中會先自動加載EurekaServerAutoConfiguration的bean,主要功能是將eureka-server中的功能bean加載在beanfactory(實體工廠),比如說通過jersey向外提供獲取server信息的接口配置節點信息啓動eureka
@Configuration //表明這是一個配置類
@Import(EurekaServerInitializerConfiguration.class) //導入啓動EurekaServer的bean
@ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class) //標識只有在EurekaServerMarkerConfiguration.Marker.class加載完纔會加載當前這個bean,然後開啓eureka
@EnableConfigurationProperties({ EurekaDashboardProperties.class,
      InstanceRegistryProperties.class })
@PropertySource("classpath:/eureka/server.properties") //加載配置文件
public class EurekaServerAutoConfiguration extends WebMvcConfigurerAdapter {

    // 此處省略大部分代碼,僅抽取一些關鍵的代碼片段

    // 加載EurekaController, spring-cloud 提供了一些額外的接口,用來獲取eurekaServer的信息
    @Bean
    @ConditionalOnProperty(prefix = "eureka.dashboard", name = "enabled", matchIfMissing = true)
    public EurekaController eurekaController() {
       return new EurekaController(this.applicationInfoManager);
    }
    // 配置服務節點信息,這裏的作用主要是爲了配置Eureka的peer節點,也就是說當有收到有節點註冊上來
    //的時候,需要通知給那些服務節點, (互爲一個集羣)
    @Bean
    @ConditionalOnMissingBean
    public PeerEurekaNodes peerEurekaNodes(PeerAwareInstanceRegistry registry,
        ServerCodecs serverCodecs) {
        return new PeerEurekaNodes(registry, this.eurekaServerConfig,
         this.eurekaClientConfig, serverCodecs, this.applicationInfoManager);
    }
    // EurekaServer的上下文
    @Bean
    public EurekaServerContext eurekaServerContext(ServerCodecs serverCodecs,
      PeerAwareInstanceRegistry registry, PeerEurekaNodes peerEurekaNodes) {
        return new DefaultEurekaServerContext(this.eurekaServerConfig, serverCodecs,
         registry, peerEurekaNodes, this.applicationInfoManager);
    }
    // 這個類的作用是spring-cloud和原生eureka的膠水代碼,通過這個類來啓動EurekaSever
    // 後面這個類會在EurekaServerInitializerConfiguration被調用,進行eureka啓動
    // EurekaServerBootstrap類會初始化Eureka的環境變量,並且也會初始化eureka的上下文(複製節點,狀態修改爲up,開啓定時任務清理心跳失效60s的客戶端)
    @Bean
    public EurekaServerBootstrap eurekaServerBootstrap(PeerAwareInstanceRegistry registry,
      EurekaServerContext serverContext) {
    return new EurekaServerBootstrap(this.applicationInfoManager,
         this.eurekaClientConfig, this.eurekaServerConfig, registry,
         serverContext);
    }
// 配置攔截器,ServletContainer裏面實現了jersey框架,通過他來實現eurekaServer對外的restFull接口
@Bean
public FilterRegistrationBean jerseyFilterRegistration(
      javax.ws.rs.core.Application eurekaJerseyApp) {
   FilterRegistrationBean bean = new FilterRegistrationBean();
   bean.setFilter(new ServletContainer(eurekaJerseyApp));
   bean.setOrder(Ordered.LOWEST_PRECEDENCE);
   bean.setUrlPatterns(
         Collections.singletonList(EurekaConstants.DEFAULT_PREFIX + "/*"));

   return bean;
}

// 攔截器實例
@Bean
public javax.ws.rs.core.Application jerseyApplication(Environment environment,
      ResourceLoader resourceLoader) {

   ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(
         false, environment);

   // Filter to include only classes that have a particular annotation.
   //
   provider.addIncludeFilter(new AnnotationTypeFilter(Path.class));
   provider.addIncludeFilter(new AnnotationTypeFilter(Provider.class));

   // Find classes in Eureka packages (or subpackages)
   //
   Set<Class<?>> classes = new HashSet<Class<?>>();
   for (String basePackage : EUREKA_PACKAGES) {
      Set<BeanDefinition> beans = provider.findCandidateComponents(basePackage);
      for (BeanDefinition bd : beans) {
         Class<?> cls = ClassUtils.resolveClassName(bd.getBeanClassName(),
               resourceLoader.getClassLoader());
         classes.add(cls);
      }
   }

   // Construct the Jersey ResourceConfig
   //
   Map<String, Object> propsAndFeatures = new HashMap<String, Object>();
   propsAndFeatures.put(
         // Skip static content used by the webapp
         ServletContainer.PROPERTY_WEB_PAGE_CONTENT_REGEX,
         EurekaConstants.DEFAULT_PREFIX + "/(fonts|images|css|js)/.*");

   DefaultResourceConfig rc = new DefaultResourceConfig(classes);
   rc.setPropertiesAndFeatures(propsAndFeatures);

   return rc;
}
}

  1. 加載@EurekaServerInitializerConfiguration標籤,會開啓一個線程去初始化EurekaServer,同時啓動Eureka Server
/**
 * @author Dave Syer
 */
@Configuration
@CommonsLog
public class EurekaServerInitializerConfiguration
      implements ServletContextAware, SmartLifecycle, Ordered {

   @Autowired
   private EurekaServerConfig eurekaServerConfig;

   private ServletContext servletContext;

   @Autowired
   private ApplicationContext applicationContext;

   @Autowired
   private EurekaServerBootstrap eurekaServerBootstrap;

   private boolean running;

   private int order = 1;

   @Override
   public void setServletContext(ServletContext servletContext) {
      this.servletContext = servletContext;
   }
   @Override
   public void start() {
      // 啓動一個線程
      new Thread(new Runnable() {
         @Override
         public void run() {
            try {
               //初始化EurekaServer,同時啓動Eureka Server ,
               eurekaServerBootstrap.contextInitialized(EurekaServerInitializerConfiguration.this.servletContext);
               log.info("Started Eureka Server");
                // 發佈EurekaServer的註冊事件
               publish(new EurekaRegistryAvailableEvent(getEurekaServerConfig()));
                // 設置啓動的狀態爲true
               EurekaServerInitializerConfiguration.this.running = true;
                // 發送Eureka Start 事件 , 其他還有各種事件,我們可以監聽這種時間,然後做一些特定的業務需求,後面會講到。
               publish(new EurekaServerStartedEvent(getEurekaServerConfig()));
            }
            catch (Exception ex) {
               // Help!
               log.error("Could not initialize Eureka servlet context", ex);
            }
         }
      }).start();
   }

  
}

六、常見問題

  1. 註冊服務慢
    原因:由於心跳機制,默認30s,只有當實例、服務器、客戶端的本地緩存數據一致時才能被其他服務發現(3次心跳),可以修改心跳時間間隔(eureka.instance.leaseRenewal-IntervalInSeconds)
  2. 已經停止的微服務節點註銷慢或者不住校
    關閉自我保護、修改清理間隔時間(修改配置文件)
  3. 如何主動關閉
    postman調用:localhost:8761/eureka/apps/{application.name}/
    寫接口
@RestController
public class HelloController {
    @Autowired
    private DiscoveryClient client;
    
    @RequestMapping(value = "/offline", method = RequestMethod.GET)
    public void offLine(){
    	DiscoveryManager.getInstance().shutdownComponent();
    }   
}

注:如果集羣中有一個eureka server宕機。不會有類似選舉的過程,客戶端請求會自動切換到新的eureka server節點,維持強一致性和高可用性,對網絡分區等問題採用自我保護機制,保留信息不過期。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章