Spring-Cloud之服務的注冊與發現(Eureka)及其源碼分析


關於Spring cloud和微服務的概念:

https://www.cnblogs.com/xiaojunbo/p/7090742.html

什么是Eureka?

Eureka是Netflix開源的一個RESTful服務,主要用於服務的注冊發現。

有兩個組件組成:Eureka服務器和Eureka客戶端。

Eureka服務用以提供服務注冊、發現,以一個war的形式提供或者編譯源碼,將war拷貝進tomcat即可提供服務

Eureka服務器用作服務注冊服務器。相對於client端的服務端,為客戶端提供服務,通常情況下為一個集群

Eureka客戶端是一個java客戶端,通過向Eureka服務發現注冊的可用的eureka-server,向后端發送請求。用來簡化與服務器的交互、作為輪詢負載均衡器,並提供服務的故障切換支持。Netflix在其生產環境中使用的是另外的客戶端,它提供基於流量、資源利用率以及出錯狀態的加權負載均衡。(引用:http://blog.csdn.net/jek123456/article/details/74171055

 Spring Cloud Eureka

需要的是組件上Spring cloud netflix的Eureka,這是一個服務注冊和發現模塊

分為兩個部分:

@EnableEurekaClient:該注解表明應用既作為Eureka實例又作為Eureka client可以發現注冊的服務

@EnableEurekaServer:該注解表明應用為eureka服務,有可以聯合多個服務作為集群,對外提供服務注冊和發現功能

 服務注冊與發現對於微服務系統來說非常重要。有了服務發現與注冊,你就不需要整天改服務調用的配置文件了,你只需要使用服務的標識符,就可以訪問到服務。他的功能類似於dubbo的注冊中心(register)。

   服務發現:服務發現是微服務基礎架構的關鍵原則之一。試圖着手配置每個客戶端或某種格式的約定可以說是非常困難的和非常脆弱的。Eureka是Netflix服務發現的一種服務和客戶端。這種服務是可以被高可用性配置的和部署,並且在注冊的服務當中,每個服務的狀態可以互相復制給彼此。  

   服務注冊:當一個客戶端注冊到Eureka,它提供關於自己的元數據(諸如主機和端口,健康指標URL,首頁等)Eureka通過一個服務從各個實例接收心跳信息。如果心跳接收失敗超過配置的時間,實例將會正常從注冊里面移除

基本環境:

IDE: Intellij IDEA 2018.5

jdk:1.8

maven:4.0.0

 

一、創建eureka server

file -> new -> project -> 選擇spring initialzr

一直next直到這里:

創建完成的pom.xml文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>demo</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <spring-cloud.version>Finchley.RELEASE</spring-cloud.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-config-server</artifactId>
            </dependency>


        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>


</project>

啟動一個服務注冊中心,只需要一個注解@EnableEurekaServer,在這里加:

(每個人的類名可能都不一樣)

效果如下:

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;


@EnableEurekaServer
@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

eureka是一個高可用的組件,它沒有后端緩存,每一個實例注冊之后需要向注冊中心發送心跳(因此可以在內存中完成),在默認情況下erureka server也是一個eureka client ,必須要指定一個 server。eureka server的配置文件application.properties(或者application.yml):

server.port = 8761

eureka.instance.hostname = localhost
eureka.client.registerWithEureka = false
eureka.client.fetchRegistry = false
eureka.client.serviceUrl.defaultZone = http://${eureka.instance.hostname}:${server.port}/eureka/

如果是application.yml文件可以把它.properties文件改成.yml文件,變成樹的結構就行了:

server:
  port: 8761

eureka:
  instance:
    hostname: localhost
  client:
    registerWithEureka: false
    fetchRegistry: false
    serviceUrl:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

通過eureka.client.registerWithEureka:false和fetchRegistry:false來表明自己是一個eureka server.

run這個工程,打開http://localhost:8761

可以看到如下界面:

No application available:因為還沒有注冊服務當然不可能有服務被發現了。

二、創建eureka client

前面都是一樣的,但是pom.xml配置要改,改后如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>service-hi</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <spring-cloud.version>Finchley.RELEASE</spring-cloud.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            <version>RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>

    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-eureka</artifactId>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>


</project>

通過注解@EnableEurekaClient 表明自己是一個eurekaclient.

package com.example.demo;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@EnableEurekaClient
@RestController
@EnableAutoConfiguration
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
    @Value("${server.port}")
    String port;
    @RequestMapping("/hi")
    public String home(@RequestParam String name) {
        return "hi "+ name+",i am from port:" +port;
    }
}

還需要在配置文件中注明自己的服務注冊中心的地址,application.properties配置文件如下:

eureka.client.serviceUrl.defaultZone = http://localhost:8761/eureka/
server.port = 8762
spring.application.name = service-hi

同理,如果是application.yml文件,是這樣的:

eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/
server:
  port: 8762
spring:
  application:
    name: service-hi

需要指明spring.application.name,這個很重要,這在以后的服務與服務之間相互調用一般都是根據這個name 。 
啟動工程,打開http://localhost:8761 ,即eureka server 的網址:

這個服務已經注冊在服務中了,服務名為SERVICE-HI ,端口為8762

打開: http://localhost:8762/hi?name=mercy

可以看到:

Eureka client 到Server的調用過程源碼分析

首次從DiscoveryClient 構造函數中調用的initScheduledTasks方法說起。初始化了多個定時任務,其中一個定時任務就是間隔的獲取遠端的注冊信息。

private void initScheduledTasks() {
  scheduler.schedule(
                    new TimedSupervisorTask(
                            "cacheRefresh",
                            scheduler,
                            cacheRefreshExecutor,
                            registryFetchIntervalSeconds,
                            TimeUnit.SECONDS,
                            expBackOffBound,
                            new CacheRefreshThread()
                    ),
                    registryFetchIntervalSeconds, TimeUnit.SECONDS);
        }

CashRefreshThread:線程定時refresh:

class CacheRefreshThread implements Runnable {
        public void run() {
            refreshRegistry();
        }
    }
void refreshRegistry() {       
 boolean success = fetchRegistry(remoteRegionsModified);
            if (success) {
                registrySize = localRegionApps.get().size();
                lastSuccessfulRegistryFetchTimestamp = System.currentTimeMillis();
            }        
    }

獲取遠端注冊信息,根據參數會決定是調用getAndStoreFullRegistry()進去全部刷新,還是調用 getAndUpdateDelta(applications);只是更新。

不管哪種,維護的都是本地一個private final AtomicReference<Applications> localRegionApps = new AtomicReference<Applications>();對象。

前面說到,DiscoveryClient是netflix客戶端提供的服務注冊發現的句柄,Applications里包括了所有的注冊服務(wraps all the registry information returned by eureka server.),

其他接口,不管是查詢服務的,還是查詢服務實例的,也就是從在從這個wrapper里可以找到。

private boolean fetchRegistry(boolean forceFullRegistryFetch) {      
            Applications applications = getApplications();
            if (clientConfig.shouldDisableDelta()       
                getAndStoreFullRegistry();
            } else {
                getAndUpdateDelta(applications);
            }
            applications.setAppsHashCode(applications.getReconcileHashCode());
            logTotalInstances();       

        // Notify about cache refresh before updating the instance remote status
        onCacheRefreshed();
        // Update remote status based on refreshed data held in the cache
        updateInstanceRemoteStatus();
        // registry was fetched successfully, so return true
        return true;
    }

如何調用遠端eureka的注冊信息,是通過一個httpclient來完成的。eureka client訪問server的接口定義在EurekaHttpClient中。這也是eureka server向外提供的全部數據接口。

com.netflix.discovery.shared.transport.jersey.AbstractJerseyEurekaHttpClient.getApplications其實就是發起一個Rest請求

@Override
public EurekaHttpResponse<Applications> getApplications(String… regions) {
return getApplicationsInternal(“apps/”, regions);
}

 private EurekaHttpResponse<Applications> getApplicationsInternal(String urlPath, String[] regions) {
            WebResource webResource = jerseyClient.resource(serviceUrl).path(urlPath);
            if (regions != null && regions.length > 0) {
                regionsParamValue = StringUtil.join(regions);
                webResource = webResource.queryParam("regions", regionsParamValue);
            }
            Builder requestBuilder = webResource.getRequestBuilder();
            addExtraHeaders(requestBuilder);
            response = requestBuilder.accept(MediaType.APPLICATION_JSON_TYPE).get(ClientResponse.class);
}

服務端的一個rest 的resource來提供服務。提供的服務在這里可以找到:

https://github.com/Netflix/eureka/wiki/Eureka-REST-operations

resource通過ResponseCacheImpl來訪問到 AbstractInstanceRegistry中提供的接口來獲取數據。AbstractInstanceRegistry 是真正存儲和處理注冊信息的地方。

com . netflix . eureka . registry . AbstractInstanceRegistry .getApplication

public Application getApplication(String appName) {
        boolean disableTransparentFallback = serverConfig.disableTransparentFallbackToOtherRegion();
        return this.getApplication(appName, !disableTransparentFallback);
    }

public Application getApplication(String appName, boolean includeRemoteRegion) {
        Application app = null;
        Map<String, Lease<InstanceInfo>> leaseMap = registry.get(appName);
        if (leaseMap != null && leaseMap.size() > 0) {
            for (Entry<String, Lease<InstanceInfo>> entry : leaseMap.entrySet()) {
                if (app == null) {
                    app = new Application(appName);
                }
                app.addInstance(decorateInstanceInfo(entry.getValue()));
            }
        } else if (includeRemoteRegion) {
            for (RemoteRegionRegistry remoteRegistry : this.regionNameVSRemoteRegistry.values()) {
                Application application = remoteRegistry.getApplication(appName);
                if (application != null) {
                    return application;
                }
            }
        }
        return app;
    }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM