1. 程式人生 > >Spring Cloud 入門教程(一):服務治理(Eureka)

Spring Cloud 入門教程(一):服務治理(Eureka)

Spring Cloud是一系列框架的集合,其基於Spring Boot的開發便利性巧妙地簡化了分散式系統基礎設施的開發,構建了服務治理(發現註冊)、配置中心、訊息匯流排、負載均衡、斷路器、資料監控、分散式會話和叢集狀態管理等功能,為我們提供一整套企業級分散式雲應用的完美解決方案。

Spring Cloud包含了多個子專案(針對分散式系統中涉及的多個不同開源產品),比如:Spring Cloud Config、Spring Cloud Netflix、Spring Cloud CloudFoundry、Spring Cloud AWS、Spring Cloud Security、Spring Cloud Commons、Spring Cloud Zookeeper、Spring Cloud CLI等專案。這些專案是Spring將目前各家公司開發的比較成熟、經得起實際考驗的服務框架組合起來,通過Spring Boot風格進行再封裝遮蔽掉了複雜的配置和實現原理,最終給我們開發者留出了一套簡單易懂、易部署和易維護的分散式系統開發工具包。

Spring Cloud 具有特性,以及適用於哪些場景等包含:

  • 基於版本的分散式配置管理
  • 服務註冊與發現
  • 路由
  • 服務之間呼叫(依賴)
  • 負載均衡
  • 斷路器
  • 全域性鎖(分散式鎖)
  • 選主以及叢集狀態管理
  • 分散式訊息服務

Spring Cloud的核心是服務治理。而服務治理主要通過整合Netflix的相關產品來實現這方面的功能,也就是Spring Cloud Netflix,在該專案中包括用於服務註冊和發現的Eureka,呼叫斷路器Hystrix,呼叫端負載均衡Ribbon,Rest客戶端Feign,智慧服務路由Zuul,用於監控資料收集和展示的Spectator、Servo、Atlas,用於配置讀取的Archaius和提供Controller層Reactive封裝的RxJava。除此之外,針對Feign和RxJava並不是Netiflix的產品,但也被整合到了Spring Cloud Netflix中。

接下來的幾篇我將從Spring Cloud Netflix開始講解如何搭建我們的分散式開發架構。

1. Hello, Spring Cloud!示例工程

我們所要搭建的Hello, Spring Cloud!系統架構圖如下:

工程架構圖

從結構圖上可以看出有一下我們所構建的工程中有三種角色:

  • Eureka Server: 服務註冊中心,負責服務列表的註冊、維護和查詢等功能;
  • Service Provider: 服務提供方,同時也是一個Eureka Client,負責將所提供的服務向Eureka Server進行註冊、續約和登出等操作。註冊時所提供的主要資料包括服務名、機器ip、埠號、域名等,從而能夠使服務消費方能夠找到;
  • Service Consumer: 服務消費方,同時也是一個Eureka Client,同樣也會向Eureka Server註冊本身所提供的服務。但在本示例工程中更多的是從Eureka Server中獲取相應的服務列表,以便能夠發起服務呼叫。

Service Provider(服務提供方)和Service Consumer(服務消費方)並不是一個嚴格的概念,往往服務消費方也是一個服務提供方,同時服務提供方也可能會呼叫其它服務方所提供的服務。當然在我們進行微服務構建時還是需要遵守業務層級之間的劃分,儘量避免服務之間的迴圈依賴。

工程結構如下:

Hello Spring Cloud工程結構

Ok! 既然工程結構和系統架構都清楚了,下面讓我們開始擼起袖子寫程式碼。

2. 構建parent工程

筆者在構建專案的時候喜歡先構建一個parent工程,該工程僅用來定義一個pom檔案,後續工程的pom檔案的皆繼承該pom。在該pom中我們將定義各工程所共同使用的第三方依賴及相應版本定義,比如我們接下來的各工程中對Spring Cloud的依賴等。這樣我們就可以統一對第三方依賴及基礎資訊定義進行管理,後續當我們需要升級第三方依賴時,只需要修改一個地方就可以了。

parent pom檔案中的內容如下:

<?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/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.2.RELEASE</version>
    </parent>

    <groupId>twostepsfromjava.cloud</groupId>
    <artifactId>twostepsfromjava-cloud-parent</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>pom</packaging>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Dalston.SR1</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

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

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.5.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>    
</project>

在本系列文章中我們使用的Spring Cloud的版本為:Dalston.SR1Spring Boot則是1.5.2.RELEASE

關於Spring Cloud的命名:由於Spring Cloud是諸多子專案集合的綜合專案,原則上由其子專案維護自己的釋出版本號,也就是我們常用的版本號,如:1.2.3.RELEASE1.1.4.RELEASE等。因此Spring Cloud為了避免版本號與其子專案的版本號混淆,所以沒有采用版本號的方式,而是採用命名的方式。這些版本名稱採用了倫敦地鐵站的名字,根據字母表的順序來對應版本時間順序。比如,最早的Release版本名稱為Angel,第二個Release版本的名稱為Brixton,以此類推……。而我們在本系列文章所使用的版本名稱為:Dalston.SR1,也就是最新版本。後續版本名稱根據專案中公佈的分別為: EdgwareFinchley

另,Dalston.SR1中的SRservice releases的簡寫,而1則是該版本名稱中的第1個版本。

具體關於Spring Cloud版本的命名說明可以參考這裡.

3. 構建Eureka Server

3.1 編寫pom.xml檔案

我們將繼承parent專案的pom.xml,並把artifactId定義為:service-discovery

<?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/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>twostepsfromjava.cloud</groupId>
        <artifactId>twostepsfromjava-cloud-parent</artifactId>
        <version>1.0.0-SNAPSHOT</version>
        <relativePath>../parent</relativePath>
    </parent>

    <artifactId>service-discovery</artifactId>

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

這裡我們直接繼承parent專案中的pom,所以只需要宣告我們需要的新增的spring-cloud-starter-eureka-server依賴即可。

3.2 編寫啟動類

@EnableEurekaServer
@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        new SpringApplicationBuilder(Application.class).web(true).run(args);
    }

}

說明: 這裡核心就是在啟動類上新增@EnableEurekaServer,宣告這是一個Eureka伺服器。

3.3 編寫配置檔案

配置檔案在resources目錄下,預設名稱為:application.properties(本系列中將採用properties檔案格式,你也可以使用另外一種格式:yml)。

server.port=8260

eureka.instance.hostname=localhost
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
eureka.client.service-url.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka

這裡為什麼這麼配,暫時先不解釋,後續我會進行相關配置引數的解釋。

3.4 啟動伺服器

接下來你可以在你的IDE中啟動該服務。當然你也可以將該伺服器打包成一個Fat Jar,然後通過java -jar的命令啟動,如:

java -jar service-discovery-1.0.0-SNAPSHOT.jar

說明: 如果需要打包成一個Fat Jar你需要修改pom.xml中的配置,增加如下內容:

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

增加一個Spring Boot打包外掛。這樣編譯出來的Jar包就可以通過上述命令直接執行。

3.5 檢視伺服器

Eureka Server UI

Instance currently registered with Eureka部分可以看到現在尚未有任何例項註冊進來。

4. 構建Eureka Client

Eureka伺服器我們已經編寫好了,接下來我們就可以編寫一個Eureka的客戶端了。這個客戶端可能是一個服務提供者,也可能是一個服務消費者,甚至兩者都是。

我們先編寫一個簡單的Eureka Client,該客戶端提供一個簡單的服務,就是呼叫/hello服務端點(EndPoint)時返回一個字串Hello, Spring Cloud!

4.1 編寫pom.xml檔案

同樣,我們繼承自parent專案的pom.xml,這裡將artifactId定義為:service-hello,也就是提供Hello服務。

<?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/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>twostepsfromjava.cloud</groupId>
        <artifactId>twostepsfromjava-cloud-parent</artifactId>
        <version>1.0.0-SNAPSHOT</version>
        <relativePath>../parent</relativePath>
    </parent>

    <artifactId>service-hello</artifactId>

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

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

4.2 編寫啟動類

@EnableDiscoveryClient
@SpringBootApplication
public class Application {

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

}

說明: 這裡與service-discovery的唯一區別就是啟動類上註解變成了@EnableDiscoveryClient,宣告這是一個Eureka Client。

4.3 編寫一個簡單的API服務

@RestController
public class HelloEndpoint {
    protected Logger logger = LoggerFactory.getLogger(HelloEndpoint.class);

    @Autowired
    private EurekaInstanceConfig eurekaInstanceConfig;
    @Value("${server.port}")
    private int serverPort = 0;

    @RequestMapping(value = "/hello", method = RequestMethod.GET)
    public String hello() {
        this.logger.info("/hello, instanceId:{}, host:{}", eurekaInstanceConfig.getInstanceId(), eurekaInstanceConfig.getHostName(false));
        return "Hello, Spring Cloud! My port is " + String.valueOf(serverPort);
    }
}

該服務僅提供一個/hello服務端點,呼叫該服務後將返回一個字串Hello, Spring Cloud!

4.4 編寫配置檔案

server.port=2100

spring.application.name=SERVICE-HELLO

eureka.client.service-url.defaultZone=http://localhost:8260/eureka

說明: 這裡spring.application.name必須要設定,服務消費者將通過該名稱呼叫所提供的服務。 eureka.client.service-url也必須設定,表示我們要向那些Eureka伺服器進行服務註冊,這裡可以宣告多個Eureka伺服器,具體我們將在後面關於Eureka高可用相關章節中進行詳細說明。

4.5 啟動伺服器

同樣啟動該伺服器。啟動成功後,我們將在控制檯上看到這麼一句日誌:

[DiscoveryClient-InstanceInfoReplicator-0] INFO  c.netflix.discovery.DiscoveryClient - DiscoveryClient_SERVICE-HELLO/192.168.0.105:SERVICE-HELLO:2100 - registration status: 204

Eureka Client UI

說明我們的服務已經在Eureka伺服器上註冊成功。

5. 構建服務消費者

到上一小節其實一個最簡單的Eureka伺服器和客戶端就已經構建完畢了。為了讓我們更能夠體會到Eureka所發揮的作用,我們下面來構建一個服務消費者,該服務消費者將呼叫SERVICE-HELLO所提供的服務。

5.1 編寫pom.xml檔案

同樣,我們繼承自parent專案的pom.xml,這裡將artifactId定義為:consumer-hello,也就是Hello服務消費者。

<?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/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>twostepsfromjava.cloud</groupId>
        <artifactId>twostepsfromjava-cloud-parent</artifactId>
        <version>1.0.0-SNAPSHOT</version>
        <relativePath>../parent</relativePath>
    </parent>

    <artifactId>consumer-hello</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>        
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-ribbon</artifactId>
        </dependency>        
    </dependencies>    
</project>

這裡需要注意的是我們除了依賴spring-cloud-starter-eureka,還依賴了Spring Cloud中的另外一個子專案spring-cloud-starter-ribbon,該子專案提供客戶端負載均衡功能,可以自動從Eureka伺服器中獲取服務提供者的地址列表,從而能夠發起相應的呼叫。這個後面我們將詳細進行說明,這裡先引入進來就可以了。

5.2 編寫啟動類

@EnableDiscoveryClient
@SpringBootApplication
public class Application {

    @Bean
    @LoadBalanced
    RestTemplate restTemplate() {
        return new RestTemplate();
    }

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

}

Service-Hello一樣在啟動類上註解了@EnableDiscoveryClient,說明這也是一個Eureka Client。

5.3 編寫服務呼叫

@RestController
public class HelloController {
    @Autowired
    private RestTemplate restTemplate;

    @RequestMapping(value = "/hello", method = RequestMethod.GET)
    public String hello() {
        return restTemplate.getForEntity("http://SERVICE-HELLO/hello", String.class).getBody();
    }
}

該服務呼叫時一個標準的controllerhello()方法將通過restTemplate呼叫SERVICE-HELLO/hello服務並返回。

5.4 編寫配置檔案

server.port=8080

spring.application.name=consumer-hello

eureka.client.service-url.defaultZone=http://localhost:8260/eureka

5.5 啟動伺服器

啟動成功後,同樣我們將在控制檯上看到這麼一句日誌:

[DiscoveryClient-InstanceInfoReplicator-0] INFO  c.netflix.discovery.DiscoveryClient - DiscoveryClient_CONSUMER-HELLO/192.168.0.105:consumer-hello:8080 - registration status: 204

Eureka Client-CONSUMER UI

說明我們的兩個服務都已經在Eureka伺服器上註冊成功。

5.6 驗證服務呼叫

在瀏覽器中,我們輸入http://localhost:8080/hello,也就是該服務所定義的埠server.port=8080,將會看到如下介面:

Eureka CONSUMER UI

同時在Service-Hello的控制檯中會列印下面一句日誌:

[http-nio-2100-exec-1] INFO  i.t.c.s.hello.api.HelloEndpoint - /hello, instanceId:cd826dembp.lan:SERVICE-HELLO:2100, host:192.168.0.105

Ok,到這裡為止,我們的Hello, Spring Cloud!示例工程搭建完畢。