1. 程式人生 > >學習一下 SpringCloud (二)-- 服務註冊中心 Eureka、Zookeeper、Consul、Nacos

學習一下 SpringCloud (二)-- 服務註冊中心 Eureka、Zookeeper、Consul、Nacos

(1) 相關博文地址:

學習一下 SpringCloud (一)-- 從單體架構到微服務架構、程式碼拆分(maven 聚合): https://www.cnblogs.com/l-y-h/p/14105682.html

(2)程式碼地址:

https://github.com/lyh-man/SpringCloudDemo

 

一、從零開始 搭建、優化 微服務

1、專案說明

【基本說明:】
    上一篇介紹了 架構演變 以及 程式碼拆分,詳見:https://www.cnblogs.com/l-y-h/p/14105682.html
    從這篇開始,將從零開始搭建微服務,逐步對 程式碼進行 優化,並選擇相關技術解決 微服務相關問題。
    
【基本環境:】
    開發工具:IDEA
    編碼環境:Java8 + MySQL 8
    框架:SpringBoot 2.3.5 + SpringCloud Hoxton.SR9 + MyBatisPlus 3.3.1
注:
    微服務相關技術此處不一一列舉出來了,有些技術僅會簡單使用、原理部分並沒有全弄懂(持續學習中,有不對的地方還望不吝賜教)。
    MyBatisPlus 基本使用可參考:https://www.cnblogs.com/l-y-h/p/12859477.html
    搭建 SpringBoot 專案可參考:https://www.cnblogs.com/l-y-h/p/13083375.html

 

2、基本專案建立

(1)專案簡介

【專案簡介:】
    上一篇介紹了 垂直拆分 程式碼,詳見:https://www.cnblogs.com/l-y-h/p/14105682.html#_label1_2
    此處以此為基礎,逐步優化、並使用相關微服務技術去搭建。

【專案基本模組:(從最簡單開始,後續模組視情況新增)】
    專案分為兩個模組:生產者模組(producer)、消費者模組(consumer)。
    生產者模組 用於 提供 各種服務。
    消費者模組 用於 訪問 各種服務。
注:
    生產者提供各種服務,其需要與資料庫進行互動(controller、service、mapper 都需要)。
    消費者訪問服務,只需要編寫 controller 即可,消費者 去 遠端訪問 生產者服務。
    可以使用 RestTemplate 進行遠端服務呼叫。

【專案命名約定:】
    為了便於區分各服務模組,各個模組服務名 命名規則為: 模組名 + _ + 埠號。
    比如:生產者模組為 producer_8000、消費者模組為 consumer_9000

 

(2)採用 maven 聚合 SpringBoot 子模組的方式建立專案
  基本操作詳見上一篇:https://www.cnblogs.com/l-y-h/p/14105682.html#_label1_2

Step1:建立 maven 聚合工程(SpringCloudDemo)。

 

 

 

Step2:建立 SpringBoot 子模組(producer_8000)。
  修改子模組配置檔案(埠號為 8000、服務名為 producer)。
  修改子模組 pom.xml 中 <parent> 標籤,指向父工程。
  修改父工程 pom.xml 中 <module> 標籤,指向子模組。.

 

 

 

Step3:引入 producer_8000 所需依賴。
  引入 MyBatisPlus 以及 MySQL 等依賴,在 父工程 進行版本控制。

【建一個表(producer_user),SQL 如下:】
DROP DATABASE IF EXISTS producer;

CREATE DATABASE producer;

USE producer;

CREATE TABLE producer_user(
    id BIGINT(20) AUTO_INCREMENT COMMENT 'ID',
    name VARCHAR(100) COMMENT 'Name',
    PRIMARY KEY (id)
) ENGINE=INNODB DEFAULT CHARSET=utf8mb4 COMMENT 'user';

【在父工程(SpringCloudDemo)中進行版本控制:】
<?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.lyh.springcloud</groupId>
  <artifactId>SpringCloudDemo</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>pom</packaging>

  <modules>
    <module>producer_8000</module>
    <module>consumer_9000</module>
  </modules>

  <name>SpringCloudDemo</name>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <mybatisplus.version>3.3.1.tmp</mybatisplus.version>
    <mysql.connector.version>8.0.18</mysql.connector.version>
    <httpcore.version>4.4.13</httpcore.version>
    <lombok.version>1.18.12</lombok.version>
    <java.version>1.8</java.version>
  </properties>

  <!-- springboot -->
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.5.RELEASE</version>
  </parent>

  <dependencyManagement>
    <dependencies>
      <!-- mybatis-plus -->
      <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>${mybatisplus.version}</version>
      </dependency>

      <!-- mybatis-plus 程式碼生成器相關依賴 -->
      <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-generator</artifactId>
        <version>${mybatisplus.version}</version>
      </dependency>
      <!-- 新增 mybatis-plus 模板引擎 依賴 -->
      <dependency>
        <groupId>org.apache.velocity</groupId>
        <artifactId>velocity-engine-core</artifactId>
        <version>2.2</version>
      </dependency>

      <!-- mysql-connector -->
      <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>${mysql.connector.version}</version>
      </dependency>

      <!-- 狀態碼參考地址:http://hc.apache.org/httpcomponents-core-ga/httpcore/apidocs/org/apache/http/HttpStatus.html -->
      <dependency>
        <groupId>org.apache.httpcomponents</groupId>
        <artifactId>httpcore</artifactId>
        <version>${httpcore.version}</version>
      </dependency>

      <!-- lombok -->
      <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>${lombok.version}</version>
        <scope>provided</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>
</project>

【在子工程(producer_8000)中進行依賴引入:】
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.lyh.springcloud</groupId>
        <artifactId>SpringCloudDemo</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <artifactId>producer</artifactId>
    <name>producer</name>

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

        <!-- mybatis-plus -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
        </dependency>

        <!-- mysql-connector -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <!-- 狀態碼參考地址:http://hc.apache.org/httpcomponents-core-ga/httpcore/apidocs/org/apache/http/HttpStatus.html -->
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpcore</artifactId>
        </dependency>

        <!-- lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
        </dependency>

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

        <!-- mybatis-plus 程式碼生成器相關依賴 -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
        </dependency>
        <!-- 新增 mybatis-plus 模板引擎 依賴 -->
        <dependency>
            <groupId>org.apache.velocity</groupId>
            <artifactId>velocity-engine-core</artifactId>
        </dependency>
    </dependencies>
</project>

 

 

 

Step4:編寫 producer_8000 所需基本程式碼。
  配置 MySQL 資料來源。

【配置 MySQL 資料來源:】
server:
  port: 8000

spring:
  application:
    name: producer
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: 123456
    url: jdbc:mysql://120.26.184.41:3306/producer?useUnicode=true&characterEncoding=utf8

 

 

 

編寫相關 bean、mapper、service、controller 等程式碼。
此處通過 mybatis-plus 程式碼生成器生成相關程式碼,也可以手動建立。
程式碼生成器相關操作詳見:https://www.cnblogs.com/l-y-h/p/12859477.html#_label1_2

【TestAutoGenerate:】
package com.lyh.springcloud.generateCode;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.GlobalConfig;
import com.baomidou.mybatisplus.generator.config.PackageConfig;
import com.baomidou.mybatisplus.generator.config.StrategyConfig;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import org.junit.jupiter.api.Test;

public class TestAutoGenerate {
    @Test
    public void autoGenerate() {
        // Step1:程式碼生成器
        AutoGenerator mpg = new AutoGenerator();

        // Step2:全域性配置
        GlobalConfig gc = new GlobalConfig();
        // 填寫程式碼生成的目錄(需要修改)
        String projectPath = "E:\\myProject\\SpringCloudDemo\\producer_8000";
        // 拼接出程式碼最終輸出的目錄
        gc.setOutputDir(projectPath + "/src/main/java");
        // 配置開發者資訊(可選)(需要修改)
        gc.setAuthor("lyh");
        // 配置是否開啟目錄,false 為不開啟(可選)
        gc.setOpen(false);
        // 實體屬性 Swagger2 註解,新增 Swagger 依賴,開啟 Swagger2 模式(可選)
        //gc.setSwagger2(true);
        // 重新生成檔案時是否覆蓋,false 表示不覆蓋(可選)
        gc.setFileOverride(false);
        // 配置主鍵生成策略,此處為 ASSIGN_ID(可選)
        gc.setIdType(IdType.ASSIGN_ID);
        // 配置日期型別,此處為 ONLY_DATE(可選)
        gc.setDateType(DateType.ONLY_DATE);
        // 預設生成的 service 會有 I 字首
        gc.setServiceName("%sService");
        mpg.setGlobalConfig(gc);

        // Step3:資料來源配置(需要修改)
        DataSourceConfig dsc = new DataSourceConfig();
        // 配置資料庫 url 地址
        dsc.setUrl("jdbc:mysql://120.26.184.41:3306/producer?useUnicode=true&characterEncoding=utf8");
        // dsc.setSchemaName("testMyBatisPlus"); // 可以直接在 url 中指定資料庫名
        // 配置資料庫驅動
        dsc.setDriverName("com.mysql.cj.jdbc.Driver");
        // 配置資料庫連線使用者名稱
        dsc.setUsername("root");
        // 配置資料庫連線密碼
        dsc.setPassword("123456");
        mpg.setDataSource(dsc);

        // Step:4:包配置
        PackageConfig pc = new PackageConfig();
        // 配置父包名(需要修改)
        pc.setParent("com.lyh.springcloud");
        // 配置模組名(需要修改)
        pc.setModuleName("producer");
        // 配置 entity 包名
        pc.setEntity("entity");
        // 配置 mapper 包名
        pc.setMapper("mapper");
        // 配置 service 包名
        pc.setService("service");
        // 配置 controller 包名
        pc.setController("controller");
        mpg.setPackageInfo(pc);

        // Step5:策略配置(資料庫表配置)
        StrategyConfig strategy = new StrategyConfig();
        // 指定表名(可以同時操作多個表,使用 , 隔開)(需要修改)
        strategy.setInclude("producer_user");
        // 配置資料表與實體類名之間對映的策略
        strategy.setNaming(NamingStrategy.underline_to_camel);
        // 配置資料表的欄位與實體類的屬性名之間對映的策略
        strategy.setColumnNaming(NamingStrategy.underline_to_camel);
        // 配置 lombok 模式
        strategy.setEntityLombokModel(true);
        // 配置 rest 風格的控制器(@RestController)
        strategy.setRestControllerStyle(true);
        // 配置駝峰轉連字元
        strategy.setControllerMappingHyphenStyle(true);
        // 配置表字首,生成實體時去除表字首
        // 此處的表名為 test_mybatis_plus_user,模組名為 test_mybatis_plus,去除字首後剩下為 user。
        strategy.setTablePrefix(pc.getModuleName() + "_");
        mpg.setStrategy(strategy);

        // Step6:執行程式碼生成操作
        mpg.execute();
    }
}

 

 

 

Step5:統一結果處理
  為了統一返回的資料格式,自定義一個包裝類,用於包裝並返回資料。
  詳見:https://www.cnblogs.com/l-y-h/p/13083375.html#_label1_1

【Result】
package com.lyh.springcloud.producer.common.tools;

import lombok.Data;
import org.apache.http.HttpStatus;

import java.util.HashMap;
import java.util.Map;

/**
 * 統一結果返回類。方法採用鏈式呼叫的寫法(即返回類本身 return this)。
 * 構造器私有,不允許進行例項化,但提供靜態方法 ok、error 返回一個例項。
 * 靜態方法說明:
 *      ok     返回一個 成功操作 的結果(例項物件)。
 *      error  返回一個 失敗操作 的結果(例項物件)。
 *
 * 普通方法說明:
 *      success      用於自定義響應是否成功
 *      code         用於自定義響應狀態碼
 *      message      用於自定義響應訊息
 *      data         用於自定義響應資料
 *
 * 依賴資訊說明:
 *      此處使用 @Data 註解,需匯入 lombok 相關依賴檔案。
 *      使用 HttpStatus 的常量表示 響應狀態碼,需匯入 httpcore 相關依賴檔案。
 */
@Data
public class Result {
    /**
     * 響應是否成功,true 為成功,false 為失敗
     */
    private Boolean success;

    /**
     * 響應狀態碼, 200 成功,500 系統異常
     */
    private Integer code;

    /**
     * 響應訊息
     */
    private String message;

    /**
     * 響應資料
     */
    private Map<String, Object> data = new HashMap<>();

    /**
     * 預設私有構造器
     */
    private Result(){}

    /**
     * 私有自定義構造器
     * @param success 響應是否成功
     * @param code 響應狀態碼
     * @param message 響應訊息
     */
    private Result(Boolean success, Integer code, String message){
        this.success = success;
        this.code = code;
        this.message = message;
    }

    /**
     * 返回一個預設的 成功操作 的結果,預設響應狀態碼 200
     * @return 成功操作的例項物件
     */
    public static Result ok() {
        return new Result(true, HttpStatus.SC_OK, "success");
    }

    /**
     * 返回一個自定義 成功操作 的結果
     * @param success 響應是否成功
     * @param code 響應狀態碼
     * @param message 響應訊息
     * @return 成功操作的例項物件
     */
    public static Result ok(Boolean success, Integer code, String message) {
        return new Result(success, code, message);
    }

    /**
     * 返回一個預設的 失敗操作 的結果,預設響應狀態碼為 500
     * @return 失敗操作的例項物件
     */
    public static Result error() {
        return new Result(false, HttpStatus.SC_INTERNAL_SERVER_ERROR, "error");
    }

    /**
     * 返回一個自定義 失敗操作 的結果
     * @param success 響應是否成功
     * @param code 響應狀態碼
     * @param message 相應訊息
     * @return 失敗操作的例項物件
     */
    public static Result error(Boolean success, Integer code, String message) {
        return new Result(success, code, message);
    }

    /**
     * 自定義響應是否成功
     * @param success 響應是否成功
     * @return 當前例項物件
     */
    public Result success(Boolean success) {
        this.setSuccess(success);
        return this;
    }

    /**
     * 自定義響應狀態碼
     * @param code 響應狀態碼
     * @return 當前例項物件
     */
    public Result code(Integer code) {
        this.setCode(code);
        return this;
    }

    /**
     * 自定義響應訊息
     * @param message 響應訊息
     * @return 當前例項物件
     */
    public Result message(String message) {
        this.setMessage(message);
        return this;
    }

    /**
     * 自定義響應資料,一次設定一個 map 集合
     * @param map 響應資料
     * @return 當前例項物件
     */
    public Result data(Map<String, Object> map) {
        this.data.putAll(map);
        return this;
    }

    /**
     * 通用設定響應資料,一次設定一個 key - value 鍵值對
     * @param key 鍵
     * @param value 資料
     * @return 當前例項物件
     */
    public Result data(String key, Object value) {
        this.data.put(key, value);
        return this;
    }
}

 

 

 

Step6:編寫兩個介面,也即 生產者 對外提供的功能。
  此處定義一個 查詢介面(根據 id 返回資料),一個新增介面(向資料庫中新增資料)。
  使用程式碼生成器生成的 UserService 中實現了 IService 介面,其內部定義了許多方法,此處可以直接使用,而不用 通過 xml 編寫 SQL 語句。
注:
  想要使用 MyBatisPlus,不要忘記使用 @Mapper 或者 @MapperScan 指定 mapper 的位置。

【controller:】
package com.lyh.springcloud.producer.controller;


import com.lyh.springcloud.producer.common.tools.Result;
import com.lyh.springcloud.producer.entity.User;
import com.lyh.springcloud.producer.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/producer/user")
public class UserController {
    @Autowired
    private UserService userService;

    @GetMapping("/get/{id}")
    public Result getUser(@PathVariable Integer id) {
        User user = userService.getById(id);
        if (user == null) {
            return Result.error(false, 404, "data not found");
        }
        return Result.ok(true, 200, "query data success").data("user", user);
    }

    @PostMapping("/create")
    public Result createUser(@RequestBody User user) {
        boolean result = userService.save(user);
        if (!result) {
            return Result.error(false, 404, "create data error");
        }
        return Result.ok(true, 200, "create data success");
    }
}

 

 

 

此處暫時使用 postman 測試一下兩個介面的功能,也可以 整合 Swagger 進行測試。
SpringBoot 整合 Swagger 可以參考:https://www.cnblogs.com/l-y-h/p/13083375.html#_label2_0

 

 

 

通過上面操作,producer 已經能基本調通了(細節並沒有過多處理),能夠對外提供服務了。
接下來就是對 consumer 進行操作了(建立流程與 producer 類似)。

 

Step7:建立 SpringBoot 子模組(consumer_9000)。
  修改子模組配置檔案(埠號為 9000、服務名為 consumer)。
  修改子模組 pom.xml 中 <parent> 標籤,指向父工程。
  修改父工程 pom.xml 中 <module> 標籤,指向子模組。
注意:
  consumer 也屬於 web 工程,所以得新增 web 相關依賴。

 

 

 

Step8:編寫 consumer 基本程式碼。
  由於 consumer 只用於訪問 producer 的服務,所以只需編寫 controller 程式碼即可。
  此處通過 RestTemplate 進行遠端呼叫(見下一小節)。

 

3、使用 RestTemplate 進行遠端呼叫

(1)什麼是 RestTemplate?

【RestTemplate:】
    RestTemplate 是 Spring 提供的用於訪問 Rest 服務的客戶端模板工具集,提供一種簡單、便捷的模板類 來訪問 restful 服務。

簡單的理解:
    RestTemplate 提供了多種 簡單便捷的 訪問遠端 Http 服務的方法。 
注:
    需要引入 Spring-web 依賴。 
    
【文件地址:(Spring 5.2.8)】
    https://docs.spring.io/spring-framework/docs/5.2.8.RELEASE/javadoc-api/org/springframework/web/client/RestTemplate.html

 

(2)RestTemplate 常用方法

【傳送 POST 請求:】
    postForObject(URI url, Object args, Class<T> class)
注:
    url 指的是 遠端呼叫 地址,即 需要訪問的介面的 請求地址。
    args 指的是 請求引數。
    class 指的是 HTTP 響應結果 被轉換的 物件型別(即 對返回結果進行 包裝)。
    
【傳送 GET 請求:】
    getForObject(String url, Class<T> class)
注:
    引數同上。 

 

(3)使用 RestTemplate?
  Step1:先得宣告一下 RestTemplate(在配置類中通過 @Bean 建立並交給 Spring 容器管理)

【ApplicationConfig】
package com.lyh.springcloud.consumer.config;

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

@Configuration
public class ApplicationConfig {

    @Bean
    public RestTemplate getRestTemplate() {
        return new RestTemplate();
    }
}

 

 

 

Step2:編寫相關程式碼。
  由於 consumer 呼叫 Producer 服務,且為了 返回結果的統一,所以在 consumer 中還需要引入 Result 以及 User 兩個類 以及 這兩個類所需的依賴。

【ConsumerController】
package com.lyh.springcloud.consumer.controller;

import com.lyh.springcloud.consumer.entity.User;
import com.lyh.springcloud.consumer.tools.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;

@RestController
@RequestMapping("/consumer/user")
public class ConsumerController {

    // 注意,此處 url 寫死的,僅用於演示,實際專案中不能這麼幹。
    public static final String PRODUCER_URL = "http://localhost:8000/producer/";

    @Autowired
    private RestTemplate restTemplate;

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

    @PostMapping("/create")
    public Result createUser(@RequestBody User user) {
        return restTemplate.postForObject(PRODUCER_URL + "/user/create", user, Result.class);
    }
}

 

 

 

Step3:使用 postman 測試一下。

producer 埠號為 8000,consumer 埠號為 9000,

【訪問流程舉例:】
    通過 POST 請求 訪問地址 http://localhost:9000/consumer/user/create,
    經過 consumer 內部轉換,會通過 RestTemplate 訪問 http://localhost:8000/producer/user/create 。

 

 

 

4、熱部署、IDEA 開啟 Run Dashboard 視窗(提高開發效率)

(1)熱部署
  詳見:https://www.cnblogs.com/l-y-h/p/13083375.html#_label2_3

(2)IDEA 開啟 Run Dashboard 視窗
  一般專案啟動後,可以在 run 視窗中看到 專案情況,但專案啟動的越多,關閉、停止等控制就很麻煩,可以通過開啟 Run Dashboard 視窗,簡化專案 Run、Debug 等操作。

 

 

 

在專案目錄下找到 .idea 資料夾,開啟 workspace.xml 檔案,並新增如下配置,然後啟動專案即可。

<component name="RunDashboard">
  <option name="configurationTypes">
    <set>
      <option value="SpringBootApplicationConfigurationType" />
    </set>
  </option>
</component>

 

 

 

5、專案優化(提取能複用的公共程式碼)

(1)相關程式碼
  上面演示的相關程式碼可以在 GitHub 中獲取到。

【git 地址:】
    https://github.com/lyh-man/SpringCloudDemo.git

 

(2)抽取公共程式碼,使其變成一個公共模組
  通過上面兩個模組的編寫,可以發現 會出現相同的 程式碼,比如:Result、User。若模組過多時,每個模組都寫一遍這些程式碼,則程式碼冗餘。若程式碼需要修改時,還得一個一個模組進行修改,增加了不必要的工作量。
  可以將這些相同程式碼抽取出來,形成一個 公共模組,此時只需要引入這個公共模組即可。修改程式碼時,只需要針對 公共模組 進行修改、新增即可。

 

Step1:建立一個子模組 common。

 

 

 

Step2:抽取 其他子模組 公共程式碼 放入 公共模組 中(比如:Result)。相關依賴也需要引入。

 

 

 

Step3:剔除 其他子模組 中的公共程式碼,並在 pom.xml 檔案中引入 公共模組。
  若啟動報錯,先 mvn install 執行一下 common 模組。

 

 

 

  通過上面一系列步驟,已經簡單的搭建了一個專案。現在就是考慮 微服務 的問題了,逐步引入 微服務 的各種技術 來解決問題。

 

二、引入服務註冊 與 發現

1、問題 與 解決

【問題:】
    首先要解決的就是 服務 註冊 與 發現的問題。
    現在專案中存在 兩個模組 consumer_9000 與 producer_8000,實際工作環境中,這兩個模組 一般都是以 叢集 方式進行部署。
比如:
    consumer_9000、consumer_9001、consumer_9002 構成叢集來提供 消費者服務。
    producer_8000、producer_8001、producer_8002 構成叢集來提供 生產者服務。
    
    那麼如何去管理這些服務?叢集的各服務之間通訊怎麼處理?這些服務掛掉了怎麼辦?哪些服務可用 與 不可用? ... 一系列問題
    
    由於 服務與服務 之間依賴關係複雜、管理難度大,所以提出 服務註冊與發現 的概念。

【常見服務註冊與發現的 技術實現:】
    Eureka     停止維護了,不推薦使用。
    ZooKeeper
    Consul
    Nacos      阿里開源的產品,功能還是很強悍的,推薦使用

【服務註冊與發現:】
    服務註冊與發現,顧名思義,就是 服務如何註冊,以及 服務如何發現。
服務註冊:
    存在一個註冊中心,當服務啟動時,會將當前服務 的元資料資訊 以別名的方式 註冊到 註冊中心中(比如:主機號、埠號等),使用心跳檢測的方式判斷當前服務是否可用、剔除。
服務發現:
    獲取服務時,會向註冊中心查詢服務別名,用來獲取真實服務資訊(主機、埠號),再通過 遠端呼叫 的方式訪問真實服務。

 

2、CAP 原則、BASE 理論

(1)什麼是 CAP?

【CAP:】
    CAP 原則指的是一個分散式系統中,無法同時滿足 C、A、P 三點,最多隻能滿足兩點(AP、CP、AC)。

【C(Consistency 一致性)】
    指的是 資料的一致性,即執行某個操作後,保證所有節點上的資料 同步更新。
比如:
    分散式系統中,某個服務執行了更新資料的操作後,那麼所有取得該資料的使用者 應該獲取的是 最新的值。即所有節點訪問 同一份最新的資料副本。

【A(Availability 可用性)】
    指的是 服務的高可用性。即一個操作能在一定的時間內返回結果(不管結果是成功還是失敗)。
比如:
    分散式系統中,某個服務掛掉了(宕機),系統整體 應保證 還能正常執行、響應請求(不會整體崩潰)。

【P(Partition tolerance 分割槽容錯性)】
    指的是 網路分割槽 情況下,仍能正常對外提供服務。
比如:
    分散式系統中,各個節點組成的網路應該是連通的,
    若因 軟體、硬體 故障導致 某些節點之間不連通了,即 網路分為幾個區域(網路分割槽)。
    此時節點服務沒有掛掉(宕機),但是不能正常通訊,系統整體 應保證 還能正常執行、響應請求。
    
分割槽容錯:
    網路分割槽出現時,資料分佈在 這些不連通的區域中,即 節點之間不能相互通訊、資料不能同步。
    而容錯解決的問題 就是 即使兩個節點不能通訊,仍要對外提供服務,不能因為分割槽而使整個系統癱瘓。

 

(2)CAP 選擇

【CAP 選擇:】
    在分散式系統中,分割槽是不可避免的。
    提高分割槽容錯性的方式一般為 服務部署在多個節點上(即 資料放置在多個節點上),當一個節點斷開後,可以從其他節點獲取到資料,保證系統正常執行。
    
    但是一個服務存在多個節點後,多個節點之間的資料 為了保證資料一致,就會帶來 資料一致性問題(C)。
    
    要保證資料一致性,則 每次操作資料後 均得等待 所有資料同步一致後 才能正常返回結果,
    而在 資料同步的過程中,節點之間可能出現 網路阻塞、故障等 導致響應超時(服務呼叫失敗),這又帶來了 可用性問題(A)。

    若要保證 可用性,即 不管資料是否同步成功,直接返回結果,那就有可能導致 多個節點之間資料不一致,即 資料一致性問題。
    
    當然若一定要保證 資料一致性,可以不做分割槽(每個服務都是單節點),此時也不用擔心資料同步問題(可用性也解決了),但服務一旦掛了,系統就崩潰了(容錯性低),不適用於 高可用的分散式系統。

綜上所述:
    分散式系統中,服務部署節點越多,分割槽容錯性越高,但資料同步操作也就更復雜、耗時(一致性難保證)。
    若想保證一致性,就需要犧牲可用性。
    若想保證可用性,就需要犧牲一致性(只是犧牲強一致性,資料最終還是一致的)。
  
【CAP 組合方式:】
    CAP 組合方式有 AP、CP、CA。
    CA 不適用於 分散式系統。
    AP 常見元件:Eureka。
    CP 常見元件:Zookeeper、Consul。
    Nacos 可以實現 AP 與 CP 的切換。

 

(3)BASE 理論

【BASE 理論:】
    BASE 理論基於 CAP 演變而來,權衡 A 與 C 對系統的影響(理解為對 AP 的補充),對系統要求降低。
    在無法做到 強一致性 的情況下,應該使系統基本可用、資料最終一致。
    BASE 是 BA、S、E 縮寫。

【BA(Basically Available 基本可用):】
    指的是  系統 發生不可預知的故障時,允許損失部分可用性,但是系統整體是可用的。
注:
    損失部分可用性(舉例:)
        時間上的損失:正常情況下,系統處理請求可能需要 0.5 秒,但由於系統故障,可能需要 3 秒才能處理完請求,保證請求能正常處理完成。
        非系統核心功能的損失:正常情況下,使用者可以訪問系統所有功能,但是訪問量突然變大時,可以減少非核心功能的使用 保證 核心功能的正常執行。

【S(Soft state 軟狀態):】
    指的是 允許系統中資料存在中間狀態(各節點間的資料不一致),但資料中間狀態不會影響到系統的整體可用性。
    即允許節點之間 資料同步 可以存在 延時的過程。
    
【E(Eventually consistent 最終一致性):】
    指的是 系統各節點經過一段時間 資料同步後,最終的資料都是一致的。
注:
    強一致性:某個節點執行寫操作後,則各個節點執行 讀操作 讀取的結果都是一致的、且是最新的資料。
    弱一致性:讀操作執行後,讀取的 不一定是 最新的資料。
    最終一致性:系統在一定時間內 肯定會 達到資料一致的狀態。

 

三、服務註冊與發現 -- Eureka

1、什麼是 Eureka ?

  Eureka 是 NetFlix 公司開發的 實現服務註冊與發現的 技術框架,遵循 AP 原則。
  SpringCloud 已將其整合到 spring-cloud-netflix 中,實現 SpringCloud 的服務註冊與發現。
  官方已經停止維護 Eureka,雖然不推薦使用,但還是可以學習一下基本思想、以及使用。

【官方文件:】
    https://github.com/Netflix/eureka/wiki

 

 

 

(2)Eureka Server、Eureka Client。
  Eureka 採用 C/S 架構設計,分為 Eureka Server、Eureka Client。

【Eureka Server:】
    Eureka Server 作為服務註冊的伺服器(即 註冊中心),當服務啟動後,會在註冊中心註冊。
    也即通過 Eureka Server 中的服務登錄檔 可以知道所有可用的 服務節點資訊。
注:
    Eureka Server 本身也是一個服務,預設會自動註冊進 註冊中心。
    若是單機版的 Eureka Server,一般取消自動註冊自身的邏輯(自己註冊自己,沒啥意義)。

【Eureka Client:】
    Eureka Client 作為客戶端,簡化與 Eureka Server 的互動,擁有一個內建的、輪詢的負載均衡器(提供基本的負載均衡)。
    Eureka Client 既可以作為 服務提供者,又可以是 服務的消費者。
    作為服務提供者時,服務啟動後,會在 Eureka Server 註冊中心進行註冊。
    作為服務消費者時,即 呼叫服務提供者提供的服務,會從註冊中心 獲取到 服務提供者的真實地址,將地址快取在本地,向 Eureka Server 傳送心跳(預設週期 30s)。
    如果 Eureka Server 在多個心跳週期內沒有接收到某個節點的心跳(預設 90s),Eureka Server 將會從註冊中心中移除 該服務節點。  

 

 

 

(3)Eureka 1.x 與 2.x 的依賴區別

【Eureka 1.x】
    Eureka Server 與 Eureka Client 引用的是同一個依賴。
如下:
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-eureka</artifactId>
    <version>1.4.7.RELEASE</version>
</dependency>
    
【Eureka 2.x】
    Eureka Server 與 Eureka Client 引用的是不同的依賴。
如下:
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
    <version>2.2.6.RELEASE</version>
</dependency>

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

 

(4)Eureka 常用配置引數

【字首為 eureka.instance 的引數:】
hostname:
    即 eureka.instance.hostname
    配置當前例項的主機名。
    
appname:
    即 eureka.instance.appname
    設定服務端例項名稱,優先順序高於 spring.application.name。
注:
    服務名 不要 使用下劃線 _ 作為連線符,可以使用 - 作為連線符。
    
instance-id:
    即 eureka.instance.instance-id
    設定當前例項 ID。
    
lease-expiration-duration-in-seconds:
    即 eureka.instance.lease-expiration-duration-in-seconds
    設定服務失效時間,預設 90 秒
    
lease-renewal-interval-in-seconds:
    即 eureka.instance.lease-renewal-interval-in-seconds
    設定心跳時間,預設 30 秒

ip-address:
    即 eureka.instance.ip-address
    設定當前例項 IP 地址。

prefer-ip-address:
    即 eureka.instance.prefer-ip-address
    預設為 false,設定為 true 時,則顯示在註冊中心的 是 IP 地址 而非 主機名。

【字首為 eureka.server 的引數:】
enable-self-preservation:
    即 eureka.server.enable-self-preservation
    預設為 true,設定 false 表示關閉自我保護模式(Eureka Server 短時間內丟失客戶端時,自我保護模式 使 Server 不刪除失去連線的客戶端)

eviction-interval-timer-in-ms:
    即 eureka.server.eviction-interval-timer-in-ms
    設定 Eureka Server 清理無效節點的時間間隔,單位:毫秒,預設為 60000 毫秒。

【字首為 eureka.client 的引數:】
register-with-eureka:
    即 eureka.client.register-with-eureka
    預設為 true,設定 false 表示不向註冊中心註冊自己(Eureka Server 一般設定為 false)。
    
fetch-registry:
    即 eureka.client.fetch-registry
    預設為 true,設定 false 表示不去註冊中心 獲取 註冊資訊(Eureka Server 一般設定為 false)。
    
service-url.defaultZone:
    即 eureka.client.service-url.defaultZone
    設定 Eureka 伺服器地址,型別為 HashMap,預設為:serviceUrl.put("defaultZone", "http://localhost:8761/eureka/");

 

 

 

2、Eureka 使用 -- 單機版

(1)基本說明

【基本說明:】
    Eureka 使用 分為 server 與 client。
    首先需要建立一個 Eureka Server 模組(eureka_server_7000),作為 服務註冊中心。
    前面建立的兩個模組 consumer_9000、producer_8000 可以作為 Eureka Client 模組。
注:
    producer_8000 作為 服務提供者,向 Eureka Server 中註冊。
    consumer_9000 作為 服務消費者,從 Eureka Server 中發現服務。
    建立與 consumer_9000 一樣的 eureka_client_consumer_9001 作為服務消費者進行演示。
    建立與 producer_8000 一樣的 eureka_client_producer_8001 作為服務提供者進行演示。 
    
    單機版沒使用價值,主要是為了由淺入深,為後面的叢集版做鋪墊。
也即:
    單機版需要建立三個子工程。
        eureka_server_7000                作為服務註冊中心
        eureka_client_producer_8001       作為服務提供者(提供服務)
        eureka_client_consumer_9001       作為服務消費者(呼叫服務)
    
    eureka_client_producer_8001 與 eureka_client_consumer_9001 都會註冊進 eureka_server_7000。
    eureka_client_consumer_9001 通過 eureka_client_producer_8001 配置的服務名,在 eureka_server_7000 註冊中心中找到 eureka_client_producer_8001 真實地址。
    然後再通過 RestTemplate 遠端呼叫該地址,從而完成 服務之間的互動。

 

 

 

(2)建立一個 Eureka Server 子模組(eureka_server_7000)
  建立一個 Eureka Server 子模組 eureka_server_7000,作為服務註冊中心。
Step1:引入 Eureka Servers 依賴
  在父工程中管理 springcloud 版本。
  在子模組中引入 eureka-server 依賴。

【父工程管理 springcloud 版本:】
<properties>
  <springcloud.version>Hoxton.SR9</springcloud.version>
</properties>

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

【子工程引入 eureka-server 依賴】
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>

 

 

 

Step2:配置 Eureka Server
  編寫 Eureka Server 配置檔案。

【application.yml】
server:
  port: 7000

eureka:
  instance:
    hostname: localhost
    appname: Eureka-Server # 設定服務端例項名稱,優先順序高於 spring.application.name
    instance-id: eureka-server-instance1 # 設定例項 ID
  client:
    register-with-eureka: false # 預設為 true,設定 false 表示不向註冊中心註冊自己
    fetch-registry: false # 預設為 true,設定 false 表示不去註冊中心 獲取 註冊資訊
    # 設定 Eureka 伺服器地址,型別為 HashMap,預設為:serviceUrl.put("defaultZone", "http://localhost:8761/eureka/");
    service-url:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka

 

 

 

Step3:啟動 Eureka Server 服務
  在啟動類上,新增 @EnableEurekaServer 註解,用於開啟 EurekaServer。

 

 

 

 

 

 

(3)建立子工程 eureka_client_producer_8001
  建立一個與 producer_8000 相同的子工程 eureka_client_producer_8001 。
  作為 Eureka Client,並註冊到 註冊中心中。

Step1:建立子工程 eureka_client_producer_8001,並引入 eureka-client 依賴。
  與 producer_8000 流程相同,直接 copy 然後修改亦可(此處不再重複截圖)。

【eureka_client_producer_8001 引入 eureka-client 依賴:】
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

 

 

 

Step2:配置 Eureka Client。

【application.yml】
server:
  port: 8001

spring:
  application:
    name: eureka-client-producer-8001
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: 123456
    url: jdbc:mysql://120.26.184.41:3306/producer?useUnicode=true&characterEncoding=utf8

eureka:
  instance:
    appname: eureka-client-producer-8001 # 優先順序比 spring.application.name 高
    instance-id: ${eureka.instance.appname} # 設定當前例項 ID
  client:
    register-with-eureka: true # 預設為 true,註冊到 註冊中心
    fetch-registry: true # 預設為 true,從註冊中心 獲取 註冊資訊
    service-url:
      # 指向 註冊中心 地址,也即 eureka_server_7000 的地址。
      defaultZone: http://localhost:7000/eureka

 

 

 

Step3:啟動 Eureka Client 服務
  在啟動類上,新增 @EnableEurekaClient 註解,用於開啟 EurekaClient(不新增也能正常註冊到 註冊中心)。

 

 

 

 

 

 

(4)建立子工程 eureka_client_consumer_9001
  建立一個與 consumer_9000 相同的子工程 eureka_client_consumer_9001。
  作為 Eureka Client,並註冊到 註冊中心中。

Step1:建立 eureka_client_consumer_9001
  與建立 eureka_client_producer_8001 類似,此處不重複截圖。
  可以直接 copy 一份 consumer_9000 程式碼進行修改。
  引入 Eureka Client 依賴。
  配置 Eureka Client,然後在啟動類上新增 @EnableEurekaClient 註解。

【eureka_client_consumer_9001 引入 eureka-client 依賴:】
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

【application.yml:】
server:
  port: 9001
spring:
  application:
    name: eureka-client-consumer-9001

eureka:
  instance:
    appname: eureka-client-consumer-9001 # 優先順序比 spring.application.name 高
    instance-id: ${eureka.instance.appname} # 設定當前例項 ID
  client:
    register-with-eureka: true # 預設為 true,註冊到 註冊中心
    fetch-registry: true # 預設為 true,從註冊中心 獲取 註冊資訊
    service-url:
      # 指向 註冊中心 地址,也即 eureka_server_7000 的地址。
      defaultZone: http://localhost:7000/eureka

 

 

 

Step2:更換 RestTemplate 訪問的 URL。
  配置了 Eureka 後,consumer 呼叫 producer 不能直接寫死了,應該在 Eureka Server 註冊中心通過 服務名 找到 真實對應的 地址後 再去 遠端訪問。
  此處需要更換 RestTemplate 的訪問地址(為 Eureka Client 註冊時的 服務名)。
  在配置 RestTemplate 時需要新增上 @LoadBalanced 註解。

 

 

 

Step3:簡單測試一下。

【訪問流程:】
    訪問 http://localhost:9001/consumer/user/get/2
    內部通過 EUREKA-CLIENT-PRODUCER-8001 服務名找到對應的 地址 localhost:8001。
    然後轉為遠端呼叫 http://localhost:8001/producer/user/get/2
即
    consumer 根據 服務註冊中心 找到 producer 的地址,
    然後通過 遠端呼叫 該地址,達到 訪問 producer 服務的目的。

 

 

 

 

 

 

3、Eureka 填坑(通過服務名訪問 服務遇到的坑)

(1)說明

【場景:】
    沒有配置 Eureka 時,consumer 通過 RestTemplate 呼叫 producer 服務。
    此時呼叫地址是寫死的,比如:http://localhost:8001/
    
    配置了 Eureka 後,consumer、producer 已經註冊到 Eureka Server 中。
    此時 consumer 應該從 Eureka 中通過 服務名 獲取到 producer 的真實地址,然後再通過 RestTemplate 去呼叫。
    此時呼叫地址寫的是 被呼叫的服務名,比如:http://EUREKA_CLIENT_PRODUCER_8001/
注:
    此處 替換地址後 遇到的三個坑(通過註冊中心 服務名 訪問真實服務遇到的坑)。

 

(2)錯誤一:(未新增 @LoadBalanced 註解)

【錯誤資訊:】
    java.net.UnknownHostException: EUREKA_CLIENT_PRODUCER_8001
    
【解決:】
    使用 @Bean 配置 RestTemplate 時,同時新增上 @LoadBalanced 註解即可。

 

 

 

(3)錯誤二:(服務名使用了 下劃線 _ 作為連線符 )

【錯誤資訊:】
    java.lang.IllegalStateException: Request URI does not contain a valid hostname:http://EUREKA_CLIENT_PRODUCER_8001/
    
【解決:】
    配置服務名時,將下劃線 _ 改為 - 作為連線符。

 

 

 

(4)錯誤三:(解析主機號、域名失敗)

【錯誤資訊:】
    java.net.UnknownHostException: eureka.client.producer.8002

【解決:】
    開啟 hosts 檔案,並配置域名對映(在後面構建叢集版 Eureka 時可能遇到)。

 

 

 

4、Eureka 偽叢集版

(1)基本說明

【為什麼使用叢集:】
    遠端服務呼叫 最重要的一個問題 就是 高可用,如果只有一個服務,那麼當服務掛掉了,整個系統將會崩潰,
    所以需要部署多個服務(叢集),除非所有服務都掛掉了,整個系統才會崩潰。
    同樣的,註冊中心也需要部署多個(叢集)。
    採用叢集方式部署、並實現負載均衡以及故障容錯 從而提高 可用性。
    
【叢集搭建基本說明:】
    前面單機版建立了 eureka_server_7000、eureka_client_producer_8001、eureka_client_consumer_9001 三個工程。
    此處為了區分,並演示叢集的操作,
        建立與 eureka_server_7000 一樣的 eureka_server_7001、eureka_server_7002、eureka_server_7003 作為 註冊中心 叢集。
        建立與 eureka_client_producer_8001 一樣的 eureka_client_producer_8002、eureka_client_producer_8003、eureka_client_producer_8004 作為 服務提供者 叢集。
        建立與 eureka_client_consumer_9001  一樣的 eureka_client_consumer_9002 作為 服務消費者(可以不做叢集)。
注:
    建立流程基本一致,但是配置檔案有些許差別。

 

 

 

(2)建立 Eureka Server 叢集。
Step1:建立與 eureka_server_7000 相同的 eureka_server_7001。
  修改 pom.xml 引入 eureka-server 依賴。
  配置 eureka-server。
  在啟動類上新增 @EnableEurekaServer 註解。

【引入依賴:】
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>

【配置 Eureka Server:】
server:
  port: 7001

eureka:
  instance:
    hostname: eureka.server.7001.com # 定義主機名
    appname: Eureka-Server # 設定服務端例項名稱,優先順序高於 spring.application.name
    instance-id: eureka-server-instance2 # 設定例項 ID
  client:
    register-with-eureka: false # 預設為 true,設定 false 表示不向註冊中心註冊自己
    fetch-registry: false # 預設為 true,設定 false 表示不去註冊中心 獲取 註冊資訊
    # 指向叢集中 其他的 註冊中心
    service-url:
      defaultZone: http://eureka.server.7002.com:7002/eureka,http://eureka.server.7003.com:7003/eureka

 

 

 

Step2:同理建立 eureka_server_7002、eureka_server_7003

【eureka_server_7002 的 application.yml:】
server:
  port: 7002

eureka:
  instance:
    hostname: eureka.server.7002.com # 定義主機名
    appname: Eureka-Server # 設定服務端例項名稱,優先順序高於 spring.application.name
    instance-id: eureka-server-instance3 # 設定例項 ID
  client:
    register-with-eureka: false # 預設為 true,設定 false 表示不向註冊中心註冊自己
    fetch-registry: false # 預設為 true,設定 false 表示不去註冊中心 獲取 註冊資訊
    # 指向叢集中 其他的 註冊中心
    service-url:
      defaultZone: http://eureka.server.7001.com:7001/eureka,http://eureka.server.7003.com:7003/eureka

【eureka_server_7003 的 application.yml:】
server:
  port: 7003

eureka:
  instance:
    hostname: eureka.server.7003.com # 定義主機名
    appname: Eureka-Server # 設定服務端例項名稱,優先順序高於 spring.application.name
    instance-id: eureka-server-instance4 # 設定例項 ID
  client:
    register-with-eureka: false # 預設為 true,設定 false 表示不向註冊中心註冊自己
    fetch-registry: false # 預設為 true,設定 false 表示不去註冊中心 獲取 註冊資訊
    # 指向叢集中 其他的 註冊中心
    service-url:
      defaultZone: http://eureka.server.7001.com:7001/eureka,http://eureka.server.7002.com:7002/eureka

 

 

 

Step3:修改 hosts 檔案,進行域名對映。
  若服務啟動後,各個服務無法正常顯示在 Eureka 頁面中,可以配置域名對映試試。
  若未配置對映,則 Eureka Client 註冊時可能會出現問題。

【hosts 檔案位置:】
    windows 的 hosts 檔案位置:C:\Windows\System32\drivers\etc\hosts
    linux 的 hosts 檔案位置:/etc/hosts
    
【新增埠對映:】
127.0.0.1       eureka.server.7001.com
127.0.0.1       eureka.server.7002.com
127.0.0.1       eureka.server.7003.com

 

 

 

 

 

 

(3)建立 eureka_client_producer 叢集。
Step1:建立與 eureka_client_producer_8001 相同的 eureka_client_producer_8002。
  引入 eureka_client 依賴。
  修改 application.yml 配置檔案。
  在啟動類上新增 @EnableEurekaClient 註解。

【引入依賴:】
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

【application.yml】
server:
  port: 8002

spring:
  application:
    name: eureka-client-producer
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: 123456
    url: jdbc:mysql://120.26.184.41:3306/producer?useUnicode=true&characterEncoding=utf8

eureka:
  instance:
    appname: eureka-client-producer # 優先順序比 spring.application.name 高
    instance-id: eureka-client-producer.instance1 # 設定當前例項 ID
    hostname: eureka.client.producer.8002 # 設定主機名
  client:
    register-with-eureka: true # 預設為 true,註冊到 註冊中心
    fetch-registry: true # 預設為 true,從註冊中心 獲取 註冊資訊
    service-url:
      # 指向 註冊中心 地址,註冊到 叢集所有的 註冊中心。
      defaultZone: http://eureka.server.7001.com:7001/eureka,http://eureka.server.7002.com:7002/eureka,http://eureka.server.7003.com:7003/eureka 

 

 

 

Step2:同理建立 eureka_client_producer_8003、eureka_client_producer_8004

【eureka_client_producer_8003 的 application.yml:】
server:
  port: 8003

spring:
  application:
    name: eureka-client-producer
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: 123456
    url: jdbc:mysql://120.26.184.41:3306/producer?useUnicode=true&characterEncoding=utf8

eureka:
  instance:
    appname: eureka-client-producer # 優先順序比 spring.application.name 高
    instance-id: eureka-client-producer.instance2 # 設定當前例項 ID
    hostname: eureka.client.producer.8003 # 設定主機名
  client:
    register-with-eureka: true # 預設為 true,註冊到 註冊中心
    fetch-registry: true # 預設為 true,從註冊中心 獲取 註冊資訊
    service-url:
      # 指向 註冊中心 地址,註冊到 叢集所有的 註冊中心。
      defaultZone: http://eureka.server.7001.com:7001/eureka,http://eureka.server.7002.com:7002/eureka,http://eureka.server.7003.com:7003/eureka
      
【eureka_client_producer_8004 的 application.yml:】
server:
  port: 8004

spring:
  application:
    name: eureka-client-producer
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: 123456
    url: jdbc:mysql://120.26.184.41:3306/producer?useUnicode=true&characterEncoding=utf8

eureka:
  instance:
    appname: eureka-client-producer # 優先順序比 spring.application.name 高
    instance-id: eureka-client-producer.instance3 # 設定當前例項 ID
    hostname: eureka.client.producer.8004 # 設定主機名
  client:
    register-with-eureka: true # 預設為 true,註冊到 註冊中心
    fetch-registry: true # 預設為 true,從註冊中心 獲取 註冊資訊
    service-url:
      # 指向 註冊中心 地址,註冊到 叢集所有的 註冊中心。
      defaultZone: http://eureka.server.7001.com:7001/eureka,http://eureka.server.7002.com:7002/eureka,http://eureka.server.7003.com:7003/eureka

 

 

 

Step3:為了防止服務訪問失敗,修改 hosts 檔案,新增域名對映。

【域名對映:】
127.0.0.1       eureka.client.producer.8002
127.0.0.1       eureka.client.producer.8003
127.0.0.1       eureka.client.producer.8004

 

 

 

(4)建立 eureka_client_consumer_9002
Step1:建立與 eureka_client_consumer_9001 相同的 eureka_client_consumer_9002。
  引入 eureka_client 依賴。
  修改 application.yml 配置檔案。
  在啟動類上新增 @EnableEurekaClient 註解。

【引入依賴:】
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

【application.yml】
server:
  port: 9002
spring:
  application:
    name: eureka-client-consumer

eureka:
  instance:
    appname: eureka-client-consumer # 優先順序比 spring.application.name 高
    instance-id: eureka-client-consumer-instance1  # 設定當前例項 ID
    hostname: eureka.client.consumer.9002 # 設定主機名
  client:
    register-with-eureka: true # 預設為 true,註冊到 註冊中心
    fetch-registry: true # 預設為 true,從註冊中心 獲取 註冊資訊
    service-url:
      # 指向 註冊中心 地址,註冊到 叢集所有的 註冊中心。
      defaultZone: http://eureka.server.7001.com:7001/eureka,http://eureka.server.7002.com:7002/eureka,http://eureka.server.7003.com:7003/eureka

 

 

 

Step2:修改 RestTemplate 傳送的 URL 地址。

 

 

 

Step3:
  為了區分究竟呼叫的是 哪一個 producer 服務,在 producer 服務介面返回時,返回埠號以及主機名,對 三個 producer 服務進行如下修改。

package com.lyh.springcloud.eureka_client_producer_8002.controller;


import com.lyh.springcloud.common.tools.Result;
import com.lyh.springcloud.eureka_client_producer_8002.entity.User;
import com.lyh.springcloud.eureka_client_producer_8002.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/producer/user")
public class UserController {
    @Autowired
    private UserService userService;

    @Value("${eureka.instance.hostname}")
    private String hostname;

    @Value("${server.port}")
    private String port;

    @GetMapping("/get/{id}")
    public Result getUser(@PathVariable Integer id) {
        User user = userService.getById(id);
        if (user == null) {
            return Result.error(false, 404, "data not found").data("ip", (hostname + ":" + port));
        }
        return Result.ok(true, 200, "query data success").data("user", user).data("ip", (hostname + ":" + port));
    }

    @PostMapping("/create")
    public Result createUser(@RequestBody User user) {
        boolean result = userService.save(user);
        if (!result) {
            return Result.error(false, 404, "create data error").data("ip", (hostname + ":" + port));
        }
        return Result.ok(true, 200, "create data success").data("ip", (hostname + ":" + port));
    }
}

 

 

 

(5)啟動專案 並訪問。

【訪問流程:】
    訪問 http://localhost:9002/consumer/user/get/2 時,
    根據服務名 EUREKA-CLIENT-PRODUCER 會得到三個 producer 服務。
    會根據負載均衡,輪詢三個服務中的某個進行遠端呼叫。
注:
    若訪問出錯為 java.net.UnknownHostException: eureka.client.producer.8002 時,
    可以修改 hosts 檔案,進行 域名對映。
    比如:
        127.0.0.1       eureka.client.producer.8002

 

 

 

 

 

 

 

 

 

5、配置 actuator、服務發現、自我保護機制

(1)配置 actuator
  用於監控 springboot 應用,比如:檢視狀態、健康檢查等。

【引入 actuator 依賴:】
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>    

 

 

 

未配置 actuator 時,出現如下圖所示錯誤。

 

 

 

配置 actuator 後,再次訪問如下圖所示。

 

 

 

(2)服務發現
  對於註冊進 註冊中心 的服務,可以通過服務發現來獲取 服務列表的資訊。
  以 eureka_client_producer_8002 為例,在其中編寫一個 介面,用於返回 服務資訊。
  在啟動類上新增 @EnableDiscoveryClient 註解(不新增好像也可以獲取服務資訊)。

【編寫一個介面:】
package com.lyh.springcloud.eureka_client_producer_8002.controller;

import com.lyh.springcloud.common.tools.Result;
import com.lyh.springcloud.eureka_client_producer_8002.entity.User;
import com.lyh.springcloud.eureka_client_producer_8002.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.ServiceIn