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



這是一個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的安全認證,這是為了防止任何人都可以檢索服務。這里不講這個知識點。我會單獨寫出來(但暫時咕咕咕),有興趣自查。


免責聲明!

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



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