Spring Cloud認知學習(一):Spring Cloud介紹與Eureka使用

隨風行雲發表於2020-05-14


這是一個Spring Cloud系列文章,它並不會講解所有的知識點,它只是基於微服務的場景來逐步介紹常見元件的作用和意義,以及場景元件的整合。對於每個元件的知識並不會講解太多,只講常見的,目的是儘可能快速的對Spring Cloud的常用元件有一個基礎的認知,有了認知之後,你就可以基於你面對的場景來單獨學習某個元件,逐步豐滿自己Spring Cloud的知識。


Spring Cloud的介紹

  • Spring Cloud是一個微服務架構,他有多種元件來管理微服務的方方面面。Spring Cloud是用於構建微服務開發和治理的框架的集合。
  • Spring Cloud是最熱門的Java技術毋庸置疑。
  • 官網

微服務的介紹

  • 微服務是什麼這裡就不細化介紹了吧,應用服務化已經成為了趨勢,簡單的說就是把以前ALL-IN-ONE的一體應用的內部功能進行拆分,比如把簡訊功能單獨出來作為一個可以提供給外部呼叫的服務,這樣既提供了簡訊服務的複用性(其他的應用也能夠複用這個功能),也使得對某個功能進行單獨的負載能力提升稱為可能(All In One 的如果想提升搶購功能的負載能力的話,採用部署多個服務端來提升搶購功能的負載能力的時候也會順帶提升了使用者註冊等的負載能力,這就額外浪費了資源)。
  • 在微服務的理論中,為了解耦,每個微服務使用單獨的資料庫(當然了,可能有些人會覺得是同名服務使用同一個資料庫,微服務這東西概念其實還挺多爭論的。)。
  • 馬丁.福勒談微服務

Spring Cloud出現的原因:

  • 當你把原來的應用服務化了之後,那麼就會遇到這些服務的管理問題了,比如說檢測服務的可用性、檢視現在有什麼服務、多個同名(同功能)的服務怎麼做到負載均衡之類的問題。
  • Spring Cloud,基於Spring Boot提供了一套微服務解決方案,包括服務註冊與發現,配置中心,全鏈路監控,服務閘道器,負載均衡,熔斷器等元件。這些元件也不全是Spring 自己開發的,有一些是開源的元件,Spring進行了封裝了而已(Spring Cloud Netflix主要來自Netflix OSS的開源元件,Spring Cloud Alibaba由阿里提供)。Spring Cloud像Spirng Boot 的starter一樣遮蔽了複雜的配置,讓我們能夠通過簡單的配置來進行微服務開發

常見場景:

Spring Cloud可以解決以下的常見幾個場景(暫時只列舉幾個常見場景,其實微服務的方方面面基本都有解決方案)

  • 服務的開發:使用Spring Boot開發服務方便快速(Spring Boot其實不算Spring Cloud的內部元件,只能算一家人吧)
  • 服務的註冊與發現:主要是Eureka提供,用於把微服務註冊到Eureka中和讓服務消費者從Eureka中獲取可用微服務列表。(當然現在也有很多采用別的元件來做服務的註冊與發現)
  • 負載均衡:主要由Ribbon提供,用於在服務消費者端進行負載均衡,從而把請求均衡到各個同名服務上。
  • API閘道器:主要由Zuul提供,提供統一的服務呼叫入口,所有的服務呼叫都通過Zuul來呼叫,提供請求轉發、請求過濾等功能。
  • 服務的容錯的處理--斷路器:主要有Hystrix提供,用於解決微服務呼叫時發生服務熔斷的問題。
  • 分散式服務配置:主要由Spring Cloud Config提供,用於解決多個微服務的統一配置和分發配置問題。(一個服務的配置可以從Config配置中心中拉取)
  • 資料監控、訊息匯流排。。。。。。。

微服務的優劣勢:

優勢:

  • 微服務化之後,程式碼也偏向簡單模組化,會比較容易理解,就好比你搞一個正經的商城難,你搞一個註冊功能還不輕鬆嗎??而且程式碼模組化之後也方便管理,比如可以專門讓某個部門負責某個服務的開發;而且因為程式碼模組化解耦之後,新的需求可以僅僅針對某個服務來開發。
  • 一個服務就是一個獨立的服務端,可以獨立部署,與其他服務的耦合度低。(All In One中你改個註冊功能都要整個服務端重啟)
  • 服務可以動態擴容,由於是一個服務就是一個獨立的服務端,所以可以很自然的水平擴容,部署同名服務的多個服務端。
  • 。。。

劣勢:

  • 運維的難度提升了,以前的ALL IN ONE是一個服務端,現在由於微服務化了,導致了N多個服務端的產生,這些服務端的部署、監控都是問題。(所以這又引起了自動部署和監控的需求。)
    • 以一個之前我聽說過的事故來說一下:某個證券公司準備更新某個程式碼,結果漏更了一臺機上的服務端,然後這個服務端因為與其他服務的配合問題,導致了不斷得“自動收單”,可以理解成遊戲中商人對於拍賣行中的商品來自動低價掃貨。
  • 測試的難度提高了,如果你手動測試過自己的BUG的話,那麼你應該知道假如你的某一個方法修改了,那麼你應該檢測一下呼叫了這個方法的地方是否可能會發生BUG。服務的呼叫也是這樣的,如果某個服務修改了,安全起見的話你還是應該測試所有相關的服務的。(所以這又引起了自動化測試的需求)
    • 如果單單對一個端的測試來說,測試難度是降低了,但對於整體業務流程的測試難度就是加大了。
  • 當然了,因為分散式而導致的分散式事務問題也讓人頭疼。
  • (但如此熱門的技術,相信大家都心裡很清楚他的優點是遠大於缺點的。)

Spring Cloud版本問題

版本介紹

  • Spring Cloud 的版本名稱並不是像其他的專案那樣使用1.0之類的數字的,他使用了倫敦的地鐵名來作為版本的名稱,據說這是考慮到了Spring Cloud與子專案的依賴關係,為了避免版本名稱的衝突和誤解的。
  • 在版本的後面跟上SR(Service Release)代表這是一個穩定的版本,後面會跟一個數字,代表第幾次迭代,例如Hoxton.SR3

與Spring Boot版本對應關係

  • Spring Cloud的版本與Spring Boot要考慮版本的相容性,以下是Spring Cloud與Spring Boot版本的對應關係。
  • 請注意:Dalston和Edgware是對應1.5版本的Spring Boot,他不能使用在2.0上面。
Release版本(地鐵名) 對應的Spring Boot版本
Hoxton 2.2.x
Greenwich 2.1.x
Finchley 2.0.x
Edgware 1.5.x
Dalston 1.5.x

目前最新版本是Hoxton.SR3,但國內主流的應該還是Finchley或者Greenwich,所以下面的示例都將以Finchley版本為例,注意此版下的元件基本都是2.0.0版本的。


---分割線---學習的前提---分割線---

下面將對於Spring Cloud的常用元件來學習。如果你繼續向下學習,請確保你已經掌握了Spring Boot知識

注意:
?下面的學習只會貼出部分程式碼,其餘的程式碼將在github上儲存,會根據元件的學習來逐步commit程式碼所以可以根據程式碼的差異來比較每一版本程式碼的區別,瞭解增加新元件需要改動哪些程式碼,(從可以從歷史記錄中檢視每一次commit的程式碼更新,來了解新增的元件修改了哪些程式碼),從而加深印象。當然,自己動手也很重要。

【PS:下面的程式碼,我後期才發現我寫錯了一個單詞Service,有些地方寫對,有些地方寫錯了,但並不影響程式碼執行。?主要是因為大寫的時候沒檢查好】


基礎專案搭建

  • Spring Cloud 主要側重服務的治理,微服務主要由Spring Boot開發,所以我們首先基於Spring Boot構建一個簡單的微服務專案,後面通過逐步增加功能來學習Spring Cloud。

下面的示例程式碼請參考:微服務專案基礎搭建

1.建立一個Maven父工程:

父工程的建立方法在IDEA中和Eclipse中有區別,這裡給出IDEA的,Eclipse的可以自查(搜尋Eclipse建立父工程即可)

20200408145147


20200408145233


父工程的目錄結構如下: ![20200408145332](https://progor.oss-cn-shenzhen.aliyuncs.com/img/20200408145332.png)

在父工程的POM.XML中增加如下程式碼,鎖定後面的依賴版本:

    <!--使用dependencyManagement鎖定依賴的版本 start-->
    <dependencyManagement>
        <dependencies>
            <!--由於此時沒有了sping boot starter 作為parent工程,需要使用spring-boot-dependencies來達到相似效果-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>2.0.6.RELEASE</version>
                <!--但要注意此處版本可能與spring cloud衝突,由於我選擇了Finchley,所以這裡用了2.0.6-->
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>1.3.4</version>
            </dependency>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>5.1.47</version>
            </dependency>
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid</artifactId>
                <version>1.0.31</version>
            </dependency>
            <dependency>
                <groupId>ch.qos.logback</groupId>
                <artifactId>logback-core</artifactId>
                <version>1.2.3</version>
            </dependency>
            <dependency>
                <groupId>log4j</groupId>
                <artifactId>log4j</artifactId>
                <version>1.2.17</version>
            </dependency>

        </dependencies>

    </dependencyManagement>
    <!--使用dependencyManagement鎖定依賴的版本 end-->

注:當你在父工程下建立了新的module,那麼此時父工程的POM.xml就會增加內容:
在IDEA中,父工程下新增module的時候,父工程自動變packaing為pom。
20200408145859

2.建立一個共有依賴包:

如果你學過maven的分模組開發,你應該知道,一些被多個模組依賴的東西會被抽離到一個單獨模組中,然後其他模組依賴這個模組即可。下面建立的就是包含了User實體(與資料表對應)的共有依賴包。


在父工程上面右鍵`New`->`Module`來在父工程下新建模組`spring-cloud-common-data`,選擇模組為Maven方式,命名模組後,一路next(也可以在最後一步重新定義模組的儲存路徑): ![20200408152755](https://progor.oss-cn-shenzhen.aliyuncs.com/img/20200408152755.png)

建立一個User類:

package com.progor.study.entity;
// 請注意類放在哪個包裡面。

public class User {
    private Integer id;
    private String username;
    private String fullName;

    public User() {
    }

    public User(Integer id, String username, String fullName) {
        this.id = id;
        this.username = username;
        this.fullName = fullName;
    }

    // 篇幅考慮,省略setter,getter程式碼
}

執行一段SQL,我們的後面的測試建立資料:

DROP DATABASE IF EXISTS cloud01;
CREATE DATABASE cloud01 CHARACTER SET UTF8;
USE cloud01;
CREATE TABLE user
(
  id int PRIMARY KEY AUTO_INCREMENT,
  username VARCHAR(255),
  fullName  VARCHAR(255)
);

INSERT INTO user(username,fullName) VALUES('zhangsan','張三');
INSERT INTO user(username,fullName) VALUES('lisi','李四');
INSERT INTO user(username,fullName) VALUES('wangwu','王五');
INSERT INTO user(username,fullName) VALUES('zhaoliu','趙六');
INSERT INTO user(username,fullName) VALUES('lidazhuang','李大壯');

SELECT * FROM user;

3.建立一個服務提供者:


3.1 在父工程上面右鍵`New`->`Module`來在父工程下新建模組`spring-cloud-user-service-8001`。 ![20200408145419](https://progor.oss-cn-shenzhen.aliyuncs.com/img/20200408145419.png)
![20200408145450](https://progor.oss-cn-shenzhen.aliyuncs.com/img/20200408145450.png)

20200408145545

20200408145627

3.2 引入web開發相關依賴包:


    <dependencies>
        <!--引入公共依賴包 start-->
        <dependency>
            <groupId>com.progor.study</groupId>
            <artifactId>spring-cloud-common-data</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <!--引入公共依賴包 end-->
        <!--引入web開發相關包 start-->
        <!--web 模組-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--使用jettey作為預設的伺服器-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jetty</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
        </dependency>
        <!--引入web開發相關包 end-->
    </dependencies>

3.3,基於spring boot建立兩個介面(以及Service,Mapper之類的,前面說了需要Spring Boot基礎,那麼這些預設你都會了,就不解釋了):
20200408204506
Controller的核心程式碼如下:

// 由於返回json資料,懶得加註解@ResponseBody了,加個RestController
@RestController
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("/user/{id}")
    public User getUser(@PathVariable Integer id) {
        User user = userService.getUser(id);
        if (user == null) {
            throw new RuntimeException("該ID:" + id + "沒有對應的使用者資訊");
        }
        return user;
    }

    @GetMapping("/user/list")
    public List<User> listUser() {
        List<User> users = userService.listUser();
        return users;
    }

}

Mapper程式碼:

@Mapper
public interface UserMapper {
    List<User> listUser();

    User getUser(Integer id);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.progor.study.dao.UserMapper">
    <select id="listUser" resultType="com.progor.study.entity.User">
        SELECT * FROM user
    </select>
    <select id="getUser" parameterType="Integer" resultType="com.progor.study.entity.User">
        SELECT * FROM user WHERE id =#{id}
    </select>

</mapper>

application.yml:

server:
  port: 8001

spring:
  datasource:
    # 配置資料來源
    username: root
    password: 123456
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/cloud01
mybatis:
  # 全域性配置檔案位置:
  config-location: classpath:mybatis/mybatis-config.xml
  # 對映檔案位置:
  mapper-locations: classpath:mybatis/mapper/*.xml

注意,在上面有執行SQL,這裡的mybatis要查詢的資料從cloud01資料庫中獲取。

訪問http://localhost:8001/user/list,測試一下是否能呼叫到介面。

4.建立一個服務消費者

4.1:建立模組spring-cloud-user-consumer-80
20200408151908
4.2:匯入依賴:

    <dependencies>
        <!--引入公共依賴包 start-->
        <dependency>
            <groupId>com.progor.study</groupId>
            <artifactId>spring-cloud-common-data</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <!--引入公共依賴包 end-->
        <!--引入web開發相關包 start-->
        <!--web 模組-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--jettey作為預設的伺服器-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jetty</artifactId>
        </dependency>
    </dependencies>

4.3:建立程式碼:
20200408205519

application.yml程式碼:

server:
  port: 80

訪問http://localhost:80/user/list,測試一下是否能呼叫到8001服務的介面(80是沒有任何業務內容的,他呼叫的是8001的業務)。

小節總結

上面基礎專案搭建應該成功實現了服務消費者通過Http請求來呼叫服務提供者的服務了。
下面將使用Spring Cloud對這個簡單的微服務專案來增加功能,來講解各種元件在微服務中的作用。
如果你不瞭解上面的例子,那你最好再學習一下,然後再看下面的內容。


Eureka服務註冊與發現

介紹

  • Eureka,讀音尤里卡。
  • Eureka用於服務的註冊與發現。Eureka是Netflix開源的一個服務註冊與發現的元件,也被Spring Cloud整合到Spring Cloud Netflix模組中。
  • 服務的註冊與發現解決的問題:
    • 服務註冊:服務註冊使得多個同一服務的多個服務端使用同一個服務端名字註冊在了服務註冊中心中(比如簡訊功能有A1,A2,A3三個服務端,但他們在註冊中心的名字都是簡訊功能,那麼我需要簡訊功能的時候,我一查註冊中心就發現有三個服務端我可以呼叫),這樣使得可以在服務註冊中心中統一管理服務,檢視服務的各種狀態。
    • 服務發現:服務的發現首先要基於服務的註冊。在上面的簡單的呼叫服務的例子中,你是需要指定服務提供者的URL路徑的,這是非常耦合的行為,一個比較恰當的做法是讓他能夠變起來,而不是一個固定的值。怎麼變呢?當有了服務註冊之後,你可以從註冊中心中拉取到某個服務的多個服務例項資訊,然後獲取其中一個服務例項解析出你需要的服務提供者的URL,而實際上我們每一次使用的服務例項可以是不同的,這樣就讓服務提供者的URL成為了可變的,也使得後面的對同名服務的負載均衡也成為可能。
  • 類似Eureka的服務註冊與發現元件:consul,etcd,zookeeper。
  • 原理:Eureka由服務端Eureka Server和客戶端Eureka Client,服務端Eureka Server用於維護服務的列表(註冊中心),服務提供者通過客戶端Eureka Client把自己的服務資訊註冊到服務端Eureka Server中;服務消費者通過客戶端Eureka Client從服務端Eureka Server中拉取到服務端中註冊的服務。獲取到服務列表後,服務消費者就知道了服務的IP地址等資訊,就可以通過http來呼叫服務了。

有人說,好多人都開始放棄eureka了,為什麼這裡還要講?
雖然老舊,但作為曾經火過的,還是有一定的參考價值,而且你不知道你進的那家公司的技術是不是與時俱進的。或者說萬一讓你接手改造一個eureka的專案呢?
當然了,要不斷學習新的技術,consul目前來看應該是不錯的替代方案,我後面也會寫這個的。

簡單使用步驟

下面的程式碼可以參考:Eureka簡單使用步驟


1.父工程匯入依賴:

1.1 修改父工程的依賴:
現在開始spring cloud學習了,我們首先在父工程的pom.xml下面加入spring cloud的依賴鎖定,來鎖定我們元件的版本:
20200408223325

            <!--鎖定spring cloud版本 start-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Finchley.SR4</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!--鎖定spring cloud版本 end-->

1.2修改spring-cloud-eureka-server-7001模組依賴:
然後再配置spring-cloud-eureka-server-7001模組的pom.xml,由於前面父工程匯入了spring-cloud-dependencies,所以你這裡的eureka雖然沒指定版本,但繼承了之前鎖定的版本。

    <dependencies>
        <!--這裡貼一下舊版本的eureka-server依賴包,注意新版本的eureka位置變了-->
        <!--<dependency>-->
            <!--<groupId>org.springframework.cloud</groupId>-->
            <!--<artifactId>spring-cloud-starter-eureka-server</artifactId>-->
        <!--</dependency>-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
    </dependencies>


2.新建spring-cloud-eureka-server-7001模組:

2.1新建模組spring-cloud-eureka-server-7001
上面說了,Eureka是有服務端和客戶端的,客戶端整合在服務消費者和服務提供者上,服務端需要單獨建立,我們單獨建立一個Eureka Server出來。
20200408172154

2.2:建立主啟動類程式碼:

package com.progor.study;

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


@SpringBootApplication
@EnableEurekaServer //使用EnableEurekaServer來把當前服務端作為一個Eureka服務端
public class EurekaServer7001Application {
    public static void main(String[] args) {
        SpringApplication.run(EurekaServer7001Application.class, args);
    }
}

2.3修改application.yml:

# 配置服務埠
server:
  port: 7001

# 配置eureka相關
eureka:
  instance:
    hostname: localhost # eureka例項的名字
  client:
    register-with-eureka: false # 這個選項是“是否把自己註冊到eureka服務端”,由於它自己就是服務端,選false
    fetch-registry: false # 是否從註冊中心拉取服務,由於它自己就是服務端,選false
    service-url:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ # 設定Eureka Server的互動地址(註冊地址),用於服務檢索和服務註冊

2.4.測試訪問Eureka Server:
執行主程式類之後,訪問一下localhost:7001,如果有eureka介面的顯示就說明eureka服務端配置成功了。



3.修改服務提供者

在服務提供者spring-cloud-user-service-8001中配置eureka,把服務註冊到eureka中:
我們修改原來的spring-cloud-user-service-8001模組:

3.1修改pom.xml:

        <!--增加eureka 客戶端依賴 start-->
            <!--舊版本的依賴:-->
        <!--<dependency>-->
            <!--<groupId>org.springframework.cloud</groupId>-->
            <!--<artifactId>spring-cloud-starter-eureka</artifactId>-->
        <!--</dependency>-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!--增加eureka 客戶端依賴 end-->

3.2修改主程式類UserService8001Application:

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

@SpringBootApplication
@EnableEurekaClient // 啟用Eureka Client
public class UserService8001Application {
    public static void main(String[] args) {
        SpringApplication.run(UserService8001Application.class, args);
    }
}

3.3修改application.yml:

server:
  port: 8001

spring:
  datasource:
    # 配置資料來源
    username: root
    password: 123456
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/cloud01
  application:
    name: UserSerive # 多個同功能的服務使用應用名application.name來註冊,這個應用名你可以在eureka 中看到,它變成了服務名
mybatis:
  # 全域性配置檔案位置:
  config-location: classpath:mybatis/mybatis-config.xml
  # 對映檔案位置:
  mapper-locations: classpath:mybatis/mapper/*.xml

# eureka配置:
eureka:
  client:
    service-url:
      defaultZone: http://localhost:7001/eureka # 指定eureka 服務端互動地址
  instance:
    instance-id: UserService8001 # 當前服務例項名稱
    prefer-ip-address: true # 是否使用IP地址作為當前服務的標識,有些是會使用主機號,你可以嘗試註釋看看效果
    # 由於拉取服務和是否把自己註冊到eureka的都是預設true的,所以不需要配置

3.4執行主程式類,檢視http://localhost:7001,看是否有如下圖的資訊:
20200408231248



4.修改服務消費者

在服務消費者中配置eureka,使得能從eureka中獲取註冊的服務並且呼叫:
修改模組spring-cloud-user-consumer-80
4.1修改pom.xml:

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

4.2修改主程式類:

package com.progor.study;

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

@SpringBootApplication
@EnableEurekaClient
public class UserConsumer80Application {
    public static void main(String[] args) {
        SpringApplication.run(UserConsumer80Application.class, args);
    }
}

4.3 修改application.yml:

server:
  port: 80

eureka:
  client:
    service-url:
      defaultZone: http://localhost:7001/eureka
    register-with-eureka: false # 由於它不是一個服務提供者,不註冊到eureka

4.4修改AppConfig
修改Bean--RestTemplate,增加@LoadBalanced,讓restTemplate能夠把請求地址解析成服務名稱:

package com.progor.study.config;

import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class AppConfig {
    @Bean
    @LoadBalanced // eureka與這個配合,要使用LoadBalanced才會呼叫eureka中註冊的服務
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

4.5修改controller:
20200408233111

package com.progor.study.Controller;

import com.progor.study.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import java.util.List;

@RestController
public class UserController {
    // 注意這個restTemplate需要自己生成Bean,參考com.progor.study.config.AppConfig
    @Autowired
    private RestTemplate restTemplate;
    // 指定遠端訪問的URL,也就是服務提供者的URL
//    private static final String REST_URL_PREFIX = "http://localhost:8001";
    // 1.註釋直接使用URL來呼叫服務的程式碼,
    // 2.下面使用eureka來呼叫,下面的"http://USERSERIVE"的USERSERIVE是服務的名字,Eureka頁面中你看過的
    // 3.這樣就從eureka中拉取到名為USERSERIVE的服務的列表,並從中選擇一個服務例項呼叫
    private static final String REST_URL_PREFIX = "http://USERSERIVE";

    @GetMapping("/user/{id}")
    public User getUser(@PathVariable Integer id) {
        return restTemplate.getForObject(REST_URL_PREFIX + "/user/" + id, User.class);
    }

    @GetMapping("/user/list")
    public List<User> listUser() {
        return restTemplate.getForObject(REST_URL_PREFIX + "/user/list", List.class);
    }

}

4.6執行主程式類,訪問介面http://localhost/user/list,檢視是否能訪問。
?如果你程式碼正確了,那麼應該是整個正常訪問的,那麼注意了,我們上面並沒有寫固定的服務消費者的URL,那麼他是怎麼訪問的呢?他通過拉取eureka中的服務列表來解析出的。【由於有時候可能存在拉取的資料延遲問題,如果不相等的話,最好按順序啟動7001,8001,80】



?這裡提醒一個東西:上面都配了defaultZone,其實在單Eureka Server情況下,Eureka Server的defaultZone是可以不配的,因為沒有意義,(但消費者和生產者需要配),對於服務消費者和生產者來說,只要執行了起來,都可以根據IP來獲取(上面的就算不配,也可以通過http://localhost:7001/eureka來訪問),消費者和生產者並不關心Eureka Server的名字,他只關心地址。但在叢集中,defaultZone有獨特的意義。下面講。



Eureka叢集

Eureka裡面可能會註冊了很多服務,而服務消費者都從Eureka Server上拉取服務列表,這個負載壓力對於Eureka可能是很大的,而且由於服務列表都從Eureka Server中拉取,所以Eureka Server也是非常重要的。為了保證Eureka Server的健壯性,我們通常都會搭建Eureka叢集。



搭建步驟

下面的程式碼可以參考:Eureka簡單叢集實驗

1.新建三個eureka-server模組,

spring-cloud-eureka-cluster-server-7002
spring-cloud-eureka-cluster-server-7003
spring-cloud-eureka-cluster-server-7004

2.都匯入依賴包:

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

3.都對應修改主啟動類:

// spring-cloud-eureka-cluster-server-7002:
@SpringBootApplication
@EnableEurekaServer //使用EnableEurekaServer來把當前服務端作為一個Eureka服務端
public class EurekaClusterServer7002Application {
    public static void main(String[] args) {
        SpringApplication.run(EurekaClusterServer7002Application.class, args);
    }
}
// spring-cloud-eureka-cluster-server-7003:
@SpringBootApplication
@EnableEurekaServer //使用EnableEurekaServer來把當前服務端作為一個Eureka服務端
public class EurekaClusterServer7003Application {
    public static void main(String[] args) {
        SpringApplication.run(EurekaClusterServer7003Application.class, args);
    }
}
// spring-cloud-eureka-cluster-server-7004:
@SpringBootApplication
@EnableEurekaServer //使用EnableEurekaServer來把當前服務端作為一個Eureka服務端
public class EurekaClusterServer7004Application {
    public static void main(String[] args) {
        SpringApplication.run(EurekaClusterServer7004Application.class, args);
    }
}


4.修改host

❓這是因為eureka預設使用eureka.instance.name作為在eureka叢集中的標識名字。那麼不修改host的時候,會有一個問題:

  • 如果你使用host作為三個server的eureka.instance.name,那麼此時eureka怎麼區分這三個server呢?對於eureka是訪問不了的
  • 而且在配置defaultZone的時候也不可以配置多個同名的。你可以嘗試一下在defaultZone中寫下多個localhost但埠不一樣的URL。
  • 所以,在本地搭建叢集的時候,需要配置host。
127.0.0.1 eureka7001.com
127.0.0.1 eureka7002.com
127.0.0.1 eureka7003.com
127.0.0.1 eureka7004.com

5.修改application.yml:

7002:

# 配置服務埠
server:
  port: 7002

# 配置eureka相關
eureka:
  instance:
    hostname: eureka7002 # eureka例項的名字
  client:
    register-with-eureka: false # 這個選項是是否把自己註冊到eureka服務端,由於它自己就是服務端,選false
    fetch-registry: false # 是否從註冊中心拉取服務,由於它自己就是服務端,選false
    service-url:
      defaultZone: http://eureka7003.com:7003/eureka/,http://eureka7004.com:7004/eureka/ # 設定Eureka Server的互動地址(註冊地址),用於服務檢索和服務註冊

7003:

# 配置服務埠
server:
  port: 7003

# 配置eureka相關
eureka:
  instance:
    hostname: eureka7003 # eureka例項的名字
  client:
    register-with-eureka: false # 這個選項是是否把自己註冊到eureka服務端,由於它自己就是服務端,選false
    fetch-registry: false # 是否從註冊中心拉取服務,由於它自己就是服務端,選false
    service-url:
      defaultZone: http://eureka7002.com:7002/eureka/,http://eureka7004.com:7004/eureka/ # 設定Eureka Server的互動地址(註冊地址),用於服務檢索和服務註冊

7004:

# 配置服務埠
server:
  port: 7004

# 配置eureka相關
eureka:
  instance:
    hostname: eureka7004 # eureka例項的名字
  client:
    register-with-eureka: false # 這個選項是是否把自己註冊到eureka服務端,由於它自己就是服務端,選false
    fetch-registry: false # 是否從註冊中心拉取服務,由於它自己就是服務端,選false
    service-url:
      defaultZone: http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/ # 設定Eureka Server的互動地址(註冊地址),用於服務檢索和服務註冊

❓為什麼這裡是配另外的叢集節點的地址,不需要配自己的地址?
首先上面說了,其實自己配不配是不重要的,就算你不配,你的服務地址還是在的,消費者和生產者還是能夠通過埠來訪問eureka server。這裡配置的是與其他叢集節點的互動地址。



6.啟動三個server,檢視效果:

可以看到DS Replicas中有另外兩個節點的列表,下圖是7001的。
20200409230629


7.對消費者和生產者的處理

如果此時要註冊服務或拉取服務,那麼defaultZone要注意改成叢集的:
20200409230800
參考程式碼spring-cloud-user-service-8002-eureka-cluster

7.1.然後你就會在三個eureka中都可以看到你註冊的服務了:
20200409230839

當配置了叢集服務,結果某個節點掛掉的時候,會報錯,但並不影響服務。


知識補充:

  • 服務續約:每隔30s,eureka就會檢測服務的可用性。
  • 自我保護:你可以看到,如果當你把服務註冊到eureka之後,如果你停止這個服務,這個服務很長時間都不會把這個服務從eureka中移除,這是eureka的自我保護機制,樂觀的認為這個服務不久之後就會重新可用。
  • 服務剔除:如果Eureka Client(而且要是個服務提供者) 90s沒有向Eureka Server傳送心跳,那麼Eureka Server就會認為這個服務例項已經不可用了,把它從服務列表中刪除。【但基於自我保護後並不會刪除。】

補充:

  • Eureka可以開啟基於Spring Security的安全認證,這是為了防止任何人都可以檢索服務。這裡不講這個知識點。我會單獨寫出來(但暫時咕咕咕),有興趣自查。

相關文章