學習一下 SpringCloud (二)– 服務註冊中心 Eureka、Zookeeper、Consul、Nacos
- 2020 年 12 月 26 日
- 筆記
- SpringCloud
(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 子模組的方式創建項目
基本操作詳見上一篇://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="//maven.apache.org/POM/4.0.0" xmlns:xsi="//www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="//maven.apache.org/POM/4.0.0 //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="//maven.apache.org/POM/4.0.0" xmlns:xsi="//www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="//maven.apache.org/POM/4.0.0 //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 程式碼生成器生成相關程式碼,也可以手動創建。
程式碼生成器相關操作詳見://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:統一結果處理
為了統一返回的數據格式,自定義一個包裝類,用於包裝並返回數據。
詳見://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 可以參考://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 = "//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)熱部署
詳見://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", "//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", "//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 註解。
【訪問流程:】 訪問 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,//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,//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,//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,//eureka.server.7002.com:7002/eureka,//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,//eureka.server.7002.com:7002/eureka,//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,//eureka.server.7002.com:7002/eureka,//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,//eureka.server.7002.com:7002/eureka,//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.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.web.bind.annotation.*; import java.util.HashMap; import java.util.List; import java.util.Map; @RestController @RequestMapping("/producer/user") public class UserController { @Autowired private UserService userService; @Value("${eureka.instance.hostname}") private String hostname; @Value("${server.port}") private String port; @Autowired private DiscoveryClient discoveryClient; @GetMapping("/discovery") public Result discovery() { // 獲取服務名列表 List<String> servicesList = discoveryClient.getServices(); // 根據服務名 獲取 每個服務名下的 各個服務的資訊 Map<String, List<ServiceInstance>> map = new HashMap<>(); servicesList.stream().forEach(service -> { map.put(service, discoveryClient.getInstances(service)); }); return Result.ok(true, 200, "discovery services success").data("services", map); } @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)); } }
啟動服務 eureka_server_7001、eureka_client_producer_8002、eureka_client_producer_8003、eureka_client_consumer_9002,並通過 postman 測試一下。
【測試 url:】 http://localhost:8002/producer/user/discovery 【測試結果:】 { "success": true, "code": 200, "message": "create data success", "data": { "services": { "eureka-client-producer": [ { "scheme": "http", "host": "eureka.client.producer.8003", "port": 8003, "metadata": { "management.port": "8003" }, "secure": false, "uri": "//eureka.client.producer.8003:8003", "serviceId": "EUREKA-CLIENT-PRODUCER", "instanceId": "eureka-client-producer.instance2", "instanceInfo": { "instanceId": "eureka-client-producer.instance2", "app": "EUREKA-CLIENT-PRODUCER", "appGroupName": null, "ipAddr": "192.168.217.1", "sid": "na", "homePageUrl": "//eureka.client.producer.8003:8003/", "statusPageUrl": "//eureka.client.producer.8003:8003/actuator/info", "healthCheckUrl": "//eureka.client.producer.8003:8003/actuator/health", "secureHealthCheckUrl": null, "vipAddress": "eureka-client-producer", "secureVipAddress": "eureka-client-producer", "countryId": 1, "dataCenterInfo": { "@class": "com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo", "name": "MyOwn" }, "hostName": "eureka.client.producer.8003", "status": "UP", "overriddenStatus": "UNKNOWN", "leaseInfo": { "renewalIntervalInSecs": 30, "durationInSecs": 90, "registrationTimestamp": 1608171423023, "lastRenewalTimestamp": 1608172078443, "evictionTimestamp": 0, "serviceUpTimestamp": 1608171423023 }, "isCoordinatingDiscoveryServer": false, "metadata": { "management.port": "8003" }, "lastUpdatedTimestamp": 1608171423023, "lastDirtyTimestamp": 1608171418248, "actionType": "ADDED", "asgName": null } }, { "scheme": "http", "host": "eureka.client.producer.8002", "port": 8002, "metadata": { "management.port": "8002" }, "secure": false, "uri": "//eureka.client.producer.8002:8002", "serviceId": "EUREKA-CLIENT-PRODUCER", "instanceId": "eureka-client-producer.instance1", "instanceInfo": { "instanceId": "eureka-client-producer.instance1", "app": "EUREKA-CLIENT-PRODUCER", "appGroupName": null, "ipAddr": "192.168.217.1", "sid": "na", "homePageUrl": "//eureka.client.producer.8002:8002/", "statusPageUrl": "//eureka.client.producer.8002:8002/actuator/info", "healthCheckUrl": "//eureka.client.producer.8002:8002/actuator/health", "secureHealthCheckUrl": null, "vipAddress": "eureka-client-producer", "secureVipAddress": "eureka-client-producer", "countryId": 1, "dataCenterInfo": { "@class": "com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo", "name": "MyOwn" }, "hostName": "eureka.client.producer.8002", "status": "UP", "overriddenStatus": "UNKNOWN", "leaseInfo": { "renewalIntervalInSecs": 30, "durationInSecs": 90, "registrationTimestamp": 1608172121773, "lastRenewalTimestamp": 1608172121773, "evictionTimestamp": 0, "serviceUpTimestamp": 1608170460627 }, "isCoordinatingDiscoveryServer": false, "metadata": { "management.port": "8002" }, "lastUpdatedTimestamp": 1608172121773, "lastDirtyTimestamp": 1608172117716, "actionType": "ADDED", "asgName": null } } ], "eureka-client-consumer": [ { "scheme": "http", "host": "eureka.client.consumer.9002", "port": 9002, "metadata": { "management.port": "9002" }, "secure": false, "uri": "//eureka.client.consumer.9002:9002", "serviceId": "EUREKA-CLIENT-CONSUMER", "instanceId": "eureka-client-consumer-instance1", "instanceInfo": { "instanceId": "eureka-client-consumer-instance1", "app": "EUREKA-CLIENT-CONSUMER", "appGroupName": null, "ipAddr": "192.168.217.1", "sid": "na", "homePageUrl": "//eureka.client.consumer.9002:9002/", "statusPageUrl": "//eureka.client.consumer.9002:9002/actuator/info", "healthCheckUrl": "//eureka.client.consumer.9002:9002/actuator/health", "secureHealthCheckUrl": null, "vipAddress": "eureka-client-consumer", "secureVipAddress": "eureka-client-consumer", "countryId": 1, "dataCenterInfo": { "@class": "com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo", "name": "MyOwn" }, "hostName": "eureka.client.consumer.9002", "status": "UP", "overriddenStatus": "UNKNOWN", "leaseInfo": { "renewalIntervalInSecs": 30, "durationInSecs": 90, "registrationTimestamp": 1608171433871, "lastRenewalTimestamp": 1608172060016, "evictionTimestamp": 0, "serviceUpTimestamp": 1608171433872 }, "isCoordinatingDiscoveryServer": false, "metadata": { "management.port": "9002" }, "lastUpdatedTimestamp": 1608171433872, "lastDirtyTimestamp": 1608171429809, "actionType": "ADDED", "asgName": null } } ] } } }
(3)自我保護機制
【自我保護機制:】 自我保護機制主要用於 Eureka Client 與 Eureka Server 之間存在 網路分區(中斷了連接)時 對服務註冊表資訊的保護。 當自我保護機制開啟時,Eureka Server 不再刪除 服務註冊表中的數據,即不會註銷、剔除 任何服務(即使 Eureka Client 宕機了)。 註: Eureka Server 出現如下提示時,即表示進入了保護模式。 EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY'RE NOT. RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUST TO BE SAFE. 【為什麼產生 自我保護機制:】 自我保護機制屬於 CAP 原則里的 AP(即在 網路分區時,保證服務的可用性)。 一般情況下,Eureka Server 在一定時間內沒有收到 某個服務的心跳(默認 30 秒發一次心跳),Eureka Server 將會註銷該實例(默認 90 秒收不到心跳就剔除)。 但是存在特殊情況:發生網路分區故障(比如:延時、擁堵、卡頓)等情況時,服務 與 Eureka Server 之間無法正常通訊,此時若直接 剔除服務,那就可能造成很大的影響(此時的服務 本身並沒有問題,註銷服務 是不合理的)。 為了解決上面的特殊情況,引入了 自我保護 的概念,當 Eureka Server 短時間內丟失過多服務時,將會開啟自我保護模式。 自我保護模式一旦開啟,將不會註銷任何服務實例(寧願保留錯誤的服務資訊,也不刪除正常的服務)。 而自我保護模式一開,客戶端訪問時就容易訪問到 已經不存在的服務資訊,將會出現服務調用失敗的情況,所以客戶端必須進行容錯處理(比如:請求重試、斷路器等)。 【自我保護機制觸發條件:】 經過一分鐘,Renews(last min) < Renews threshold * 0.85,就會觸發自我保護機制。 註: Renews(last min) 表示 Eureka 最後一分鐘接收的心跳數。 Renews threshold 表示 Eureka 最後一分鐘應該接收的心跳數。
關閉自我保護模式:
【舉例:】 以 eureka_server_7001、eureka_client_producer_8002 為例。 eureka_server_7001 為 Eureka Server。 eureka_client_producer_8002 為 Eureka Client。 當 eureka_server_7001 在一定時間內沒有接收到 eureka_client_producer_8002 的心跳,將會從服務列表中 剔除 eureka_client_producer_8002 服務。 在 Eureka Server 端配置 關閉自我保護模式。 eureka: server: enable-self-preservation: false # 關閉自我保護模式 eviction-interval-timer-in-ms: 2000 # 清理無效服務的間隔 在 Eureka Client 端配置 心跳發送時間間隔、以及超時等待時間。 eureka: instance: lease-renewal-interval-in-seconds: 1 # 客戶端向 註冊中心 發送心跳的時間間隔,默認 30 秒 lease-expiration-duration-in-seconds: 5 # 註冊中心 等待心跳最長時間,超時剔除服務,默認 90 秒 【在 eureka_server_7001 中 配置關閉自我保護模式:】 server: port: 7001 eureka: server: enable-self-preservation: false # 關閉自我保護模式 eviction-interval-timer-in-ms: 2000 # 清理無效服務的間隔 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,//eureka.server.7003.com:7003/eureka 【在 eureka_client_producer_8002 中配置 心跳發送時間間隔、以及超時等待時間:】 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 # 設置主機名 lease-renewal-interval-in-seconds: 1 # 客戶端向 註冊中心 發送心跳的時間間隔,默認 30 秒 lease-expiration-duration-in-seconds: 5 # 註冊中心 等待心跳最長時間,超時剔除服務,默認 90 秒 client: register-with-eureka: true # 默認為 true,註冊到 註冊中心 fetch-registry: true # 默認為 true,從註冊中心 獲取 註冊資訊 service-url: # 指向 註冊中心 地址,註冊到 集群所有的 註冊中心。 defaultZone: http://eureka.server.7001.com:7001/eureka,//eureka.server.7002.com:7002/eureka,//eureka.server.7003.com:7003/eureka
6、Eureka 保證 AP、基本工作流程
(1)Eureka 保證 AP
前面搭建集群版 Eureka 時,存在多個 Eureka Server 節點,這些節點不分 主、從 節點(所有節點平等),節點之間 相互註冊,每個節點通過 service-url 指向其他的 Eureka Server。
當一個 Eureka Server 節點宕機後,會自動切換到其他可用的 Eureka Server 節點,也就意味著 只要有一台 Eureka Server 正常工作,那麼系統就不會崩潰(提高了可用性)。
但是 Eureka Server 各節點間採用非同步方式進行數據同步,不保證節點間數據強一致性,也即各個 Server 保存的服務列表資訊可能不一致,但是數據最終是一致的。也即 保證 AP。
(2)基本工作流程
通過前面一系列操作,應該大致理解了 Eureka 工作流程,此處總結一下。
【基本工作流程:】 Step1: Eureka Server 啟動後,會等待 Eureka Client 註冊,並將其保存在 服務列表中。 如果配置了 Eureka Server 集群,那麼集群各節點之間會同步服務列表資訊。 Step2: Eureka Client 啟動後,會根據配置去 Eureka Server 註冊中心進行 服務註冊。 Step3: Eureka Client 默認每隔 30 秒向 Eureka Server 發送一次心跳請求,保持與註冊中心的連接(保證 Client 是正常的)。 Step4: Eureka Server 默認 90 秒沒有收到 Eureka Client 心跳請求,則視其為 失效服務,會從 註冊中心將 Client 服務剔除。 Step5: 單位時間內,若 Eureka Server 統計到大量 Eureka Client 心跳丟失,則認定出現了 網路異常, 將會開啟 自動保護模式,此時不會剔除 Client 服務(即使服務宕機 也會將其保留)。 Step6: Eureka Client 心跳恢復正常後,Eureka Server 將會自動退出 自動保護模式。 Step7: Eureka Client 默認 30 秒從註冊中心 獲取 服務列表資訊,並將其資訊快取在本地。 Step8: Eureka Client 進行服務調用時,先從本地快取查詢 服務,獲取不到時服務時 會去 註冊中心 獲取最新的 服務列表資訊 並保存在本地。 Step9: Eureka Client 獲取到 目標服務資訊後,通過 遠程調用、負載均衡(默認輪詢)的方式 發起服務調用。 Step10: Eureka Client 正常關閉時,會向 Eureka Server 發送取消請求,Eureka Server 將其從服務列表中剔除。
四、服務註冊與發現 — Zookeeper
1、什麼是 Zookeeper?
(1)什麼是 Zookeeper ?
【Zookeeper:】 Zookeeper 是一個開源、分散式的服務管理框架,屬於 Apache Hadoop 的一個子項目, 為分散式應用提供協調服務的(比如:狀態同步、集群管理、分散式應用配置管理 等)。 【官網地址:】 https://zookeeper.apache.org/ https://github.com/apache/zookeeper
(2)本質
【本質:】 Zookeeper 可以看成一個基於 觀察者模式 設計的分散式服務管理框架。 在伺服器端可以 存儲、管理 數據,並接受 客戶端(觀察者)的註冊,一旦數據變化,將通知這些 觀察者 作出相應的動作。 簡單的理解:Zookeeper 就是 文件系統 加上 監聽通知機制 來工作。 【文件系統:】 Zookeeper 維護一個類似 Linux 文件目錄 的數據結構。 每個子目錄稱為一個 znode 節點(可以通過唯一路徑進行標識),znode 可用於存儲數據(不宜存放大數據,一般存儲上限 1 M),相同層級的 znode 不能重名。 【監聽通知機制:】 客戶端註冊後,會監聽 znode 節點 是否變化,一旦節點變化(數據改變、節點刪除、增加子節點 等),Zookeeper 將會通知客戶端。 【znode 類型:】 持久節點(persistent): 客戶端與 zookeeper 斷開連接後,節點仍然存在。 持久有序節點(persistent sequential): 客戶端與 Zookeeper 建立連接後,會給節點按照順序進行編號(比如:/znode 變為 /znode0000000001)。 客戶端與 zookeeper 斷開連接後,節點仍然存在。 臨時節點(ephemeral): 客戶端與 zookeeper 斷開連接後,節點會被刪除。 臨時有序節點(ephemeral sequential): 客戶端與 Zookeeper 建立連接後,會給節點按照順序進行編號。 客戶端與 zookeeper 斷開連接後,節點會被刪除。 註: 編號由父節點維護,是一個單調遞增的計數器,可用於全局事件的排序(便於推斷分散式系統中事件的執行先後順序)。
(3)Zookeeper 功能舉例 — 分散式應用配置管理
在實際工作中,一個服務經常以集群的方式進行部署,如果此時需要修改服務的配置,
若不對配置進行管理,那麼將需要 逐個服務 進行修改,非常麻煩、易出錯。
通過 Zookeeper 可以對配置進行管理,將配置放在 Zookeeper 某個目錄節點中,然後讓這些服務 去監聽 該節點,
此時修改節點中的數據(配置資訊),那麼每個服務都會監聽到數據的變化,獲取到最新的配置資訊。
(4)zookeeper 選舉機制
選舉發生在 zookeeper 集群中,單機版不存在選舉。
註:
此處僅簡單介紹一下,篇幅有限,後續再補充,詳情可自行查閱相關文檔。
常見概念有 選舉機制、ZAB 協議、兩階段提交、寫操作流程等。
【zookeeper 集群特點:】 Zookeeper 集群由一個 Leader 以及 多個 Follower 組成。 集群中只要有半數以上的節點正常工作,那麼集群將能正常提供服務,zookeeper 集群一般為奇數節點。 註: 對於寫請求,請求會同時發送給其他 zookeeper 伺服器,達成一致後,請求才會返回成功。 所以提高集群的機器數量,雖然提高了讀效率,但是降低了寫效率。 【選舉發生場合:】 場合一:zookeeper 伺服器集群初始化啟動時會選取 Leader。 場合二:集群中 Leader 故障(宕機)時從剩餘節點中選取新的 Leader。 【zxid、myid】 myid 全局唯一的數字(一般 1-255),每個數字表示 zookeeper 伺服器集群中的一個 伺服器。 zxid 指的是 zookeeper transaction id,zookeeper 每一次狀態改變(增加、刪除節點,修改節點數據等),都將對應一個遞增的 transaction id,即 zxid。 註: zxid 越大,表示當前數據越新。 【選舉基本原則:】 每個節點先投自己一票。 然後與其他節點進行比較,如果有其他節點 A 被更多人選擇,那麼跟隨大部隊(將票投給節點 A)。 若節點 A 被超過一半節點選擇,那麼將結束選舉過程,並將節點 A 視為 Leader。 節點比較規則: 兩個節點 zxid 進行比較,zxid 大的節點作為 leader。 若 zxid 相同時,根據 myid 進行比較,myid 大的節點作為 leader。
2、使用 Docker 安裝、使用 Zookeeper
(1)安裝
此處使用 Docker-compose 進行鏡像下載 以及 啟動容器。
註:
Docker 以及 Docker-compose 使用可參考:
//www.cnblogs.com/l-y-h/p/12622730.html
//www.cnblogs.com/l-y-h/p/12622730.html#_label8_2
【docker-compose.yml】 # 指定 compose 文件版本,與 docker 兼容,高版本的 docker 一般使用 3.x。 version: '3.7' services: # 設置服務名 zookeeper_service1: # 配置所使用的鏡像 image: zookeeper # 容器總是重啟 restart: always # 容器名稱 container_name: zookeeper_service1 #與宿主機的埠映射 ports: - 2181:2181 #容器目錄映射 volumes: - /usr/mydata/zookeeper/zookeeper_service1/data:/data - /usr/mydata/zookeeper/zookeeper_service1/datalog:/datalog
(2)常用命令
可以直接進入 容器內部 進行相關操作。
進入 zookeeper_service1 的 bin 目錄,並執行 zkCli.sh 命令,可以開啟客戶端。
【進入 zookeeper_service1 容器的命令:】 docker exec -it zookeeper_service1 /bin/bash 【退出 zookeeper_service1 容器的命令:】 exit
客戶端常用命令:
不同版本的命令可能稍微不同,但大體還是一致的。
【查詢當前節點下的全部子節點:】 ls 節點名稱 註: 節點名稱 就是 節點路徑,比如:/、/zookeeper、/test/zookeeper 等 比如: ls / 查詢根目錄下全部子節點 【查詢當前節點下的數據:】 get 節點名稱 比如: get /zookeeper 獲取 /zookeeper 節點的數據 【查詢節點資訊:】 stat 節點名稱 比如: stat /zookeeper 獲取 /zookeeper 節點的資訊 【創建節點:】 create [-s] [-e] 節點名稱 節點數據 註: -s 為 sequential,即當前節點類型為 有序節點。 -e 為 ephemeral,即當前節點類型為 臨時節點。 若不存在 -s 、-e 參數,則默認為 持久節點。 比如: create -e /ephemeralZnode ephemeralData 創建一個臨時節點,名為 ephemeralZnode。 create -s -e /ephemeralSequentialZnode ephemeralSequentialData 創建一個臨時有序的節點,系統會自動編號,比如: ephemeralSequentialZnode0000000003 【修改節點數據:】 set 節點名稱 節點新數據 比如: set /ephemeralZnode newEphemeralData 修改 /ephemeralZnode 數據為 newEphemeralData 【刪除節點數據:】 delete 節點名稱 或者 deleteall 節點名稱 註: delete 刪除的是沒有子節點的節點。 deleteall 刪除的是當前節點 以及 其全部子節點 【離開客戶端:】 quit
3、SpringCloud 整合 Zookeeper 單機版
(1)說明
在之前學習 Eureka 時,SpringCloud 整合了 Eureka 服務端 以及 客戶端的實現,
而此處 Zookeeper 已經提供了服務端的實現,所以只需要使用 SpringCloud 整合 Zookeeper 客戶端即可。
與 Eureka 類似,客戶端也可分為 服務提供者、服務消費者。
創建與 eureka_client_producer_8001 類似的 zookeeper_client_producer_8005 作為 服務提供者。
創建與 eureka_client_consumer_9001 類似的 zookeeper_client_consumer_9003 作為 服務消費者。
(2)創建 zookeeper_client_producer_8005 子模組。
修改子模組 與 父模組 pom.xml 文件(與前面創建模組類似,此處省略)。
引入 zookeeper_discovery 依賴。
修改 application.yml 配置文件。
在啟動類上添加 @EnableDiscoveryClient 註解(不添加好像也可以正常註冊)。
【pom.xml:】 <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId> </dependency> 【application.yml】 server: port: 8005 spring: application: name: zookeeper-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 cloud: # zookeeper 配置 zookeeper: # 配置連接 zookeeper 伺服器的地址 connect-string: 120.26.184.41:2181
啟動服務,在 zookeeper 伺服器端,執行 zkCli.sh 進入客戶端,可以查看到 註冊節點 的資訊。
註:
默認是臨時節點,服務一旦宕機,zookeeper 將會將該節點移除。
(3)同理創建 zookeeper_client_consumer_9003 模組
修改子模組 與 父模組 pom.xml 文件(與前面創建模組類似,此處省略)。
引入 zookeeper_discovery 依賴。
修改 application.yml 配置文件。
在啟動類上添加 @EnableDiscoveryClient 註解(不添加好像也可以正常註冊)。
引入 RestTemplate 時,需要添加 @LoadBalanced,並修改訪問地址為 服務名。
【依賴:】 <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId> </dependency> 【application.yml】 server: port: 9003 spring: application: name: zookeeper-client-consumer cloud: zookeeper: connect-string: 120.26.184.41:2181
啟動服務,可以在註冊中心看到已經註冊的服務。
4、Zookeeper 偽集群版(docker-compose 啟動)
(1)集群角色說明
Zookeeper Server 集群不同於 Eureka,其節點之間存在主從之分,其一個 Server 斷開後,將會在剩餘節點中 重新選舉出一個 Leader。
【Server 節點角色:】
Leader 主節點
Follower 從節點(參與選舉主節點)
Observer 從節點(不參與選舉主節點)
進入 zookeeper 容器的 bin 目錄後,可以通過 zkServer.sh status 查看當前服務的角色。
(2)項目說明:
【項目說明:】 與 Eureka 類似,需要對 Server 以及 producer 作出集群處理。 此處使用 docker-compose 進行 Server 集群(偽集群)處理。 創建 zookeeper_client_producer_8006、zookeeper_client_producer_8007、zookeeper_client_producer_8008 作為 producer 集群。 創建 zookeeper_client_consumer_9004 作為服務消費者,進行服務調用。
(3) 通過 docker-compose 啟動 Zookeeper 集群
【docker-compose.yml】 # 指定 compose 文件版本,與 docker 兼容,高版本的 docker 一般使用 3.x。 version: '3.7' services: # 設置服務名 zookeeper_service2: # 配置所使用的鏡像 image: zookeeper # 容器總是重啟 restart: always # 容器名稱 container_name: zookeeper_service2 #與宿主機的埠映射 ports: - 2182:2181 #容器目錄映射 volumes: - /usr/mydata/zookeeper/zookeeper_service2/data:/data - /usr/mydata/zookeeper/zookeeper_service2/datalog:/datalog # 設置環境變數 environment: # Server 唯一標識(1 - 255) ZOO_MY_ID: 2 # 指定服務資訊,格式: server.A=B:C:D;E # 其中: A 表示伺服器標識,B 是伺服器 ip(服務名),C 是伺服器與集群中 Leader 進行交互的埠,D 是用來選取新 Leader 進行交互的埠, E 為埠號 ZOO_SERVERS: server.2=zookeeper_service2:2888:3888;2181 server.3=zookeeper_service3:2888:3888;2181 server.4=zookeeper_service4:2888:3888;2181 # 設置服務名 zookeeper_service3: # 配置所使用的鏡像 image: zookeeper # 容器總是重啟 restart: always # 容器名稱 container_name: zookeeper_service3 #與宿主機的埠映射 ports: - 2183:2181 #容器目錄映射 volumes: - /usr/mydata/zookeeper/zookeeper_service3/data:/data - /usr/mydata/zookeeper/zookeeper_service3/datalog:/datalog # 設置環境變數 environment: # Server 唯一標識(自然數) ZOO_MY_ID: 3 # 指定服務資訊,格式: server.A=B:C:D;E # 其中: A 表示伺服器標識,B 是伺服器 ip(服務名),C 是伺服器與集群中 Leader 進行交互的埠,D 是用來選取新 Leader 進行交互的埠, E 為埠號 ZOO_SERVERS: server.2=zookeeper_service2:2888:3888;2181 server.3=zookeeper_service3:2888:3888;2181 server.4=zookeeper_service4:2888:3888;2181 # 設置服務名 zookeeper_service4: # 配置所使用的鏡像 image: zookeeper # 容器總是重啟 restart: always # 容器名稱 container_name: zookeeper_service4 #與宿主機的埠映射 ports: - 2184:2181 #容器目錄映射 volumes: - /usr/mydata/zookeeper/zookeeper_service4/data:/data - /usr/mydata/zookeeper/zookeeper_service4/datalog:/datalog # 設置環境變數 environment: # Server 唯一標識(自然數) ZOO_MY_ID: 4 # 指定服務資訊,格式: server.A=B:C:D;E # 其中: A 表示伺服器標識,B 是伺服器 ip(服務名),C 是伺服器與集群中 Leader 進行交互的埠,D 是用來選取新 Leader 進行交互的埠, E 為埠號 ZOO_SERVERS: server.2=zookeeper_service2:2888:3888;2181 server.3=zookeeper_service3:2888:3888;2181 server.4=zookeeper_service4:2888:3888;2181
(4)創建 producer 集群。
與單機版創建 zookeeper_client_producer_8005 同樣的流程創建 zookeeper_client_producer_8006、zookeeper_client_producer_8007、zookeeper_client_producer_8008。
此處省略創建過程。
唯一區別在於,配置註冊中心地址時,配置集群上所有的 server 地址(以逗號隔開)。
【zookeeper_client_producer_8006 的 application.yml】 server: port: 8006 spring: application: name: zookeeper-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 cloud: # zookeeper 配置 zookeeper: # 配置連接 zookeeper 伺服器的地址 connect-string: 120.26.184.41:2181, 120.26.184.41:2182, 120.26.184.41:2183 【zookeeper_client_producer_8007 的 application.yml】 server: port: 8007 spring: application: name: zookeeper-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 cloud: # zookeeper 配置 zookeeper: # 配置連接 zookeeper 伺服器的地址 connect-string: 120.26.184.41:2181, 120.26.184.41:2182, 120.26.184.41:2183 【zookeeper_client_producer_8008 的 application.yml】 server: port: 8008 spring: application: name: zookeeper-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 cloud: # zookeeper 配置 zookeeper: # 配置連接 zookeeper 伺服器的地址 connect-string: 120.26.184.41:2181, 120.26.184.41:2182, 120.26.184.41:2183
啟動三個服務,可以在 伺服器查看到 三個服務的 節點。
(5)創建 consumer
與創建 zookeeper_client_consumer_9003 同樣流程創建 zookeeper_client_consumer_9004,
此處創建流程省略。
唯一區別在於,配置註冊中心地址時,配置集群上所有的 server 地址(以逗號隔開)。
【application.yml】 server: port: 9004 spring: application: name: zookeeper-client-consumer cloud: zookeeper: connect-string: 120.26.184.41:2181, 120.26.184.41:2182, 120.26.184.41:2183
(6)實現 Discovery
Discovery 程式碼與 Eureka 程式碼一致(同樣通過 DiscoveryClient 進行操作)。
【ConsumerController:】 package com.lyh.springcloud.zookeeper_client_consumer_9004.controller; import com.lyh.springcloud.common.tools.Result; import com.lyh.springcloud.zookeeper_client_consumer_9004.entity.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.web.bind.annotation.*; import org.springframework.web.client.RestTemplate; import java.util.HashMap; import java.util.List; import java.util.Map; @RestController @RequestMapping("/consumer/user") public class ConsumerController { // 注意,此處 url 寫死的,僅用於演示,實際項目中不能這麼干。 // public static final String PRODUCER_URL = "//localhost:8001/producer/"; // 通過服務名 找到 zookeeper 註冊中心真實訪問的 地址 public static final String PRODUCER_URL = "//zookeeper-client-producer"; @Autowired private RestTemplate restTemplate; @Autowired private DiscoveryClient discoveryClient; @GetMapping("/discovery") public Result discovery() { // 獲取服務名列表 List<String> servicesList = discoveryClient.getServices(); // 根據服務名 獲取 每個服務名下的 各個服務的資訊 Map<String, List<ServiceInstance>> map = new HashMap<>(); servicesList.stream().forEach(service -> { map.put(service, discoveryClient.getInstances(service)); }); return Result.ok(true, 200, "discovery services success").data("services", map); } @GetMapping("/get/{id}") public Result getUser(@PathVariable Integer id) { return restTemplate.getForObject(PRODUCER_URL + "/producer/user/get/" + id, Result.class); } @PostMapping("/create") public Result createUser(@RequestBody User user) { return restTemplate.postForObject(PRODUCER_URL + "/producer/user/create", user, Result.class); } }
5、Zookeeper 真集群版(docker-compose 啟動)
(1)說明
【說明:】 真集群版 與 偽集群版 基本操作還是一致的,除了 docker-compose.yml 有些許區別。 此處僅搭建出 server 真集群環境(以及解決搭建環境遇到的坑),其餘操作均與偽集群類似,不在重複講述。 【環境:】 zookeeper 集群節點數量一般為 奇數。 此處使用的是 阿里雲兩台伺服器(財力有限,湊合一下)。 雲伺服器: 120.26.184.41 182.92.120.184
(2)前提條件(開放埠)
【伺服器 通過 埠 進行 數據交互:】 由於 zookeeper server 部署在不同的伺服器上,而伺服器 選舉 以及 數據同步 需要進行交互, 之前搭建偽集群時也提到了三個埠 2181、2888、3888,想要伺服器之間正常交互,就需要開放這三個埠、並對其進行映射。 註: 2181 是對客戶端提供服務的埠。 2888 是伺服器與集群中 Leader 進行數據交互的埠。 3888 是用來選取新 Leader 進行交互的埠。 若需要開放埠,進入 阿里雲 官網,找到相應的阿里雲伺服器,並添加 安全組 規則(在入方向中開放指定的埠)。
(3)搭建環境遇到的坑 以及 解決
【問題一:】 docker-compose up -d 啟動容器後, 通過 docker-compose logs -f 查看日誌發現,無法通過 3888 或者 2888 進行通訊。 在 182.92.120.184 查看日誌報錯資訊: Cannot open channel to 3 at election address /120.26.184.41:3888 在 120.26.184.41 查看日誌報錯資訊: Cannot open channel to 3 at election address /182.92.120.184:3888 註: 極大的可能是因為 埠未開放 或者 埠未做映射。 【問題二:】 在 182.92.120.184 查看日誌報錯資訊: /182.92.120.184:3888:QuorumCnxManager$Listener$ListenerHandler@1093] - Exception while listening java.net.BindException: Cannot assign requested address (Bind failed) 註: 極大的可能是配置 自身伺服器 ip 時,使用了公網 ip.
【如何解決(標準步驟、親測有效):】 對於某個伺服器配置步驟如下: Step1: 進入阿里雲官網,找到對應的伺服器,並配置安全組規則,開放 2182、2888、3888 埠。 Step2: 指定埠映射,將開放的埠與 2181、2888、3888 進行映射。 註: 伺服器埠開放後,如果不進行映射,埠效果等同於未開放。 Step3: 指定伺服器 ip 時(配置), 對於本身伺服器地址,可以使用 服務名 或者 0.0.0.0 進行替代,不要使用 公網 ip(會出錯)。 對於其他伺服器,直接使用公網 ip 即可。
(4)在 120.26.184.41 伺服器上通過 docker-compose.yml 啟動 zookeeper。
此處使用 服務名(容器名)替代 自身 ip 地址。
【docker-compose.yml】 # 指定 compose 文件版本,與 docker 兼容,高版本的 docker 一般使用 3.x。 version: '3.7' services: # 設置服務名 zookeeper_service3: # 配置所使用的鏡像 image: zookeeper # 容器總是重啟 restart: always # 容器名稱 container_name: zookeeper_service3 #與宿主機的埠映射 ports: - 2182:2181 - 2888:2888 - 3888:3888 #容器目錄映射 volumes: - /usr/mydata/zookeeper/zookeeper_service3/data:/data - /usr/mydata/zookeeper/zookeeper_service3/datalog:/datalog # 設置環境變數 environment: # Server 唯一標識(自然數) ZOO_MY_ID: 3 # 指定服務資訊,格式: server.A=B:C:D;E # 其中: A 表示伺服器標識,B 是伺服器 ip(服務名),C 是伺服器與集群中 Leader 進行交互的埠,D 是用來選取新 Leader 進行交互的埠, E 為埠號 ZOO_SERVERS: server.2=182.92.120.184:2888:3888;2182 server.3=zookeeper_service3:2888:3888;2182 #ZOO_SERVERS: server.2=182.92.120.184:2888:3888;2182 server.3=0.0.0.0:2888:3888;2182
(5)在 182.92.120.184 伺服器上通過 docker-compose.yml 啟動 zookeeper。
此處使用 0.0.0.0 替代 自身 ip 地址。
【docker-compose.yml】 # 指定 compose 文件版本,與 docker 兼容,高版本的 docker 一般使用 3.x。 version: '3.7' services: # 設置服務名 zookeeper_service2: # 配置所使用的鏡像 image: zookeeper # 容器總是重啟 restart: always # 容器名稱 container_name: zookeeper_service2 #與宿主機的埠映射 ports: - 2182:2181 - 2888:2888 - 3888:3888 #容器目錄映射 volumes: - /usr/mydata/zookeeper/zookeeper_service2/data:/data - /usr/mydata/zookeeper/zookeeper_service2/datalog:/datalog # 設置環境變數 environment: # Server 唯一標識(1 - 255) ZOO_MY_ID: 2 # 指定服務資訊,格式: server.A=B:C:D;E # 其中: A 表示伺服器標識,B 是伺服器 ip(服務名),C 是伺服器與集群中 Leader 進行交互的埠,D 是用來選取新 Leader 進行交互的埠, E 為埠號 ZOO_SERVERS: server.2=0.0.0.0:2888:3888;2182 server.3=120.26.184.41:2888:3888;2182 #ZOO_SERVERS: server.2=zookeeper_service2:2888:3888;2182 server.3=120.26.184.41:2888:3888;2182
(6)分別在兩個伺服器上通過 docker-compose up -d 啟動。
先在 182.92.120.184 上啟動,然後在 120.26.184.41 上啟動。
正常搭建後,server 角色 如下所示。
同理,若需要增加節點,按照上面三步操作即可。
五、服務註冊與發現 — Consul
1、什麼是 Consul ?
(1)什麼是 Consul ?
【Consul:】 Consul 是一套開源的分散式服務發現和配置管理系統,由 HashiCorp 公司使用 Go 語言開發。 提供了微服務系統中 服務發現、配置中心 等功能。 【官網地址:】 https://www.consul.io/docs/intro https://learn.hashicorp.com/consul https://github.com/hashicorp/consul 註: 官網解釋還是挺詳細的,請自行查閱。 此處僅簡單使用一下 Consul,具體原理沒有仔細研究,後續有時間再補充。
(2)consul 角色
consul 與 zookeeper 類似,提供了可執行程式作為 服務端。
但其可以細分為兩種角色(client、server)。
【角色:】 client: 客戶端, 無狀態。 consul agent --client 將 HTTP 和 DNS 介面請求轉發給區域網內的服務端集群。 server: 服務端, 保存配置資訊。 可作為高可用集群, 在區域網內與本地客戶端 client 通訊, 通過廣域網與其他數據中心通訊。 每個數據中心的 server 數量推薦為 3 個或是 5 個(奇數個)。
(3)常用埠說明:
【常用埠說明:】 8300:通常用於 server 節點,處理集群內部的請求(數據讀寫、複製)。 8301:通常用於單個數據中心的所有節點間相互通訊(區域網 LAN 內資訊同步)。 8302:通常用於單個或多個數據中心之間節點的相互通訊(廣域網 WAN 內資訊同步)。 8500:通常用於提供 UI 服務、獲取服務列表、註冊服務、註銷服務 等 HTTP 介面。 8600:通常作為 DNS 伺服器,提供服務發現功能(通過節點名查詢節點資訊)。
2、官網下載、安裝 consul — windows
(1)從官網下載。
官網提供了各種版本的可執行程式,下載相應版本即可。
此處下載 windows 版本的 consul 為例,並使用。
【官網下載地址:】 https://www.consul.io/downloads
(2)安裝 consul
下載之後,可以得到一個可執行文件(consul.exe),雙擊即可運行(會閃一下彈窗)。
判斷是否安裝成功,可以進入命令行模式,輸入 consul -version。
若正常輸出版本號,則安裝成功。
(3)啟動 consul
命令行啟動,默認通過 8500 埠可以訪問 ui 介面。
【命令行輸入:】 consul agent -dev -client=0.0.0.0 -bootstrap-expect=1 -ui -node=consul_server1 參數說明: -client 指定客戶端可以訪問的 ip,默認為 127.0.0.1(不對外提供服務),設置成 0.0.0.0 表示不對客戶端 ip 進行限制(對外提供服務)。 -dev 以開發模式啟動。 -ui 可以使用 web 介面訪問。 -bootstrap-expect 表示集群中 server 節點個數,一般為奇數。 -node 表示節點在 web ui 介面中顯示的名稱。
3、SpringCloud 整合 consul — 單機版
(1)說明:
【說明:】
SpringCloud 整合 consul,與 SpringCloud 整合 Zookeeper 是非常類似的。
通過上面步驟已經成功啟動了 Server 節點,現在只需要將服務註冊進 Server 即可。
與 Zookeeper 類似,客戶端也可分為 服務提供者、服務消費者。
創建與 zookeeper_client_producer_8005 類似的 consul_client_producer_8009 作為 服務提供者。
創建與 zookeeper_client_consumer_9003 類似的 consul_client_consumer_9005 作為 服務消費者。
(2)創建 consul_client_producer_8009 子模組。
修改子模組 與 父模組 pom.xml 文件(與前面創建模組類似,此處省略)。
引入 consul_discovery 依賴 以及 actuator 依賴。
修改 application.yml 配置文件。
在啟動類上添加 @EnableDiscoveryClient 註解(不添加好像也可以正常註冊)。
【依賴:】 <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-consul-discovery</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> 【application.yml】 server: port: 8009 spring: application: name: consul_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 cloud: # 配置 consul 資訊 consul: # 配置註冊中心地址 host: localhost # 配置訪問埠號 port: 8500 # 配置服務提供者資訊(非必須配置) discovery: # 是否需要註冊 register: true # 配置實例 ID instance-id: ${spring.application.name}-${server.port} # 配置服務名 service-name: ${spring.application.name} # 配置服務埠 port: ${server.port} # 使用 ip 地址而非主機名 prefer-ip-address: true # 設置健康檢查路徑 health-check-url: http://${spring.cloud.client.ip-address}:${server.port}/actuator/health # 健康檢查失敗後,自動剔除服務(單位 s 表示秒,m 表示分鐘) health-check-critical-timeout: 10s
(3)創建 consul_client_consumer_9005 模組。
修改子模組 與 父模組 pom.xml 文件(與前面創建模組類似,此處省略)。
引入 consul_discovery 依賴 以及 actuator 依賴。
修改 application.yml 配置文件。
在啟動類上添加 @EnableDiscoveryClient 註解(不添加好像也可以正常註冊)。
引入 RestTemplate 時,需要添加 @LoadBalanced,並修改訪問地址為 服務名。
【依賴:】 <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-consul-discovery</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> 【application.yml】 server: port: 9005 spring: application: name: consul-client-consumer cloud: # 配置 consul 資訊 consul: # 配置註冊中心地址 host: localhost # 配置訪問埠號 port: 8500 # 配置服務提供者資訊(非必須配置) discovery: # 是否需要註冊 register: true # 配置實例 ID instance-id: ${spring.application.name}-${server.port} # 配置服務名 service-name: ${spring.application.name} # 配置服務埠 port: ${server.port} # 使用 ip 地址而非主機名 prefer-ip-address: true # 設置健康檢查路徑 health-check-url: http://${spring.cloud.client.ip-address}:${server.port}/actuator/health # 健康檢查失敗後,自動剔除服務(單位 s 表示秒,m 表示分鐘) health-check-critical-timeout: 10s
(4)分別啟動兩個服務
可以在 consul UI 介面看到服務資訊。
4、健康檢查出錯問題
(1)健康檢查出錯分析
分析一:
一般是 actuator 依賴未添加(能解決大部分問題)。
分析二:
如果添加 actuator 依賴後仍出錯,可能是伺服器的問題。
一般是通過健康檢查的 URL 無法訪問服務。
比如:
若使用 雲伺服器安裝並部署 consul,而在本地啟動 服務時,此時 健康檢查 可能會出錯,伺服器調用健康檢查請求 被拒絕(因為此時 健康檢查 URL 非公網 IP 地址,無法訪問到服務)。將服務同樣部署在 伺服器上,將伺服器公網 IP 作為健康檢查 URL 地址(並開放相關埠),此時通過公網 IP 可以訪問到服務,從而健康檢查成功。
詳見後面 docker-compose 啟動 consul 單機版。
(2)添加 actuator 依賴
大多數情況下,添加上 actuator 依賴即可解決問題。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
5、docker-compose 啟動 consul 單機版
(1)docker-compose.yml 文件如下:
【docker-compose.yml:】 # 指定 compose 文件版本,與 docker 兼容,高版本的 docker 一般使用 3.x。 version: '3.7' services: # 設置服務名 consul_server1: # 配置所使用的鏡像 image: consul # 容器總是重啟 restart: always # 容器名稱 container_name: consul_server1 #與宿主機的埠映射 ports: - 8500:8500 - 8300:8300 - 8301:8301 - 8302:8302 - 8600:8600 #容器目錄映射 volumes: - /usr/mydata/consul/consul_server1/data:/consul/data - /usr/mydata/consul/consul_server1/config:/consul/config # 覆蓋容器默認啟動命令 command: agent -server -bind=0.0.0.0 -client=0.0.0.0 -bootstrap-expect=1 -ui -node=consul_server1 【command 參數解釋:】 -server 以服務端角色啟動。 -client 指定客戶端可以訪問的 ip,默認為 127.0.0.1(不對外提供服務),設置成 0.0.0.0 表示不對客戶端 ip 進行限制(對外提供服務)。 -dev 以開發模式啟動。 -bind 表示綁定到指定 ip。 -ui 可以使用 web 介面訪問。 -bootstrap-expect 表示集群中 server 節點個數,一般為奇數。 -node 表示節點在 web ui 介面中顯示的名稱。 -retry-join 表示加入集群中去,加入失敗後可以重新加入
(2)查看集群中成員
通過 consul members 可以查看集群中成員狀態。
比如:
docker exec -t consul_server1 consul members
註:
consul_server1 是容器名稱。
(3)訪問 UI 介面,可以查看到節點情況。
啟動正常情況,訪問介面如下:
啟動失敗情況,訪問頁面出現 500 錯誤,如下:
(4)演示健康檢查出錯
由於此處在 雲伺服器上啟動 consul 伺服器,而服務還是在本地啟動。
修改服務的 配置文件,將其註冊中心地址改為雲伺服器公網 IP 地址: 120.26.184.41。
以 consul_client_consumer_9005 為例,修改如下圖所示:
server: port: 9005 spring: application: name: consul-client-consumer cloud: # 配置 consul 資訊 consul: # 配置註冊中心地址 host: 120.26.184.41 # 配置訪問埠號 port: 8500 # 配置服務提供者資訊(非必須配置) discovery: # 是否需要註冊 register: true # 配置實例 ID instance-id: ${spring.application.name}-${server.port} # 配置服務名 service-name: ${spring.application.name} # 配置服務埠 port: ${server.port} # 使用 ip 地址而非主機名 prefer-ip-address: true # 設置健康檢查路徑 health-check-url: http://${spring.cloud.client.ip-address}:${server.port}/actuator/health # 健康檢查失敗後,自動剔除服務(單位 s 表示秒,m 表示分鐘) health-check-critical-timeout: 10s management: endpoint: health: #顯示健康具體資訊,默認不會顯示詳細資訊 show-details: always
重新啟動服務後,發現健康檢查出錯。
註:
服務本地啟動是沒問題的,但是伺服器 通過 健康檢查 URL 訪問不到服務。
修改服務配置文件,將健康檢查 URL 設置成 伺服器公網 IP 地址,並將服務打包部署到 伺服器上(需要配置安全組,開放埠),此時健康檢查通過公網 IP 可以訪問到 服務。
6、docker-compose 啟動 consul 偽集群
(1)說明
篇幅有限,此處僅演示 docker-compose 啟動偽集群,不創建服務模組進行演示。
(2)偽集群
docker-compose.yml 文件如下。
啟動三個容器,consul_server1、consul_server2、consul_server3 均以 server 模式啟動。
【docker-compose.yml】 # 指定 compose 文件版本,與 docker 兼容,高版本的 docker 一般使用 3.x。 version: '3.7' services: # 設置服務名 consul_server1: # 配置所使用的鏡像 image: consul # 容器總是重啟 restart: always # 容器名稱 container_name: consul_server1 #與宿主機的埠映射 ports: - 8500:8500 #容器目錄映射 volumes: - /usr/mydata/consul/consul_server1/data:/consul/data - /usr/mydata/consul/consul_server1/config:/consul/config # 覆蓋容器默認啟動命令 command: agent -server -bind=0.0.0.0 -client=0.0.0.0 -ui -bootstrap-expect=3 -ui -node=consul_server1 # 設置服務名 consul_server2: # 配置所使用的鏡像 image: consul # 容器總是重啟 restart: always # 容器名稱 container_name: consul_server2 #與宿主機的埠映射 ports: - 8501:8500 #容器目錄映射 volumes: - /usr/mydata/consul/consul_server2/data:/consul/data - /usr/mydata/consul/consul_server2/config:/consul/config # 覆蓋容器默認啟動命令 command: agent -server -bind=0.0.0.0 -client=0.0.0.0 -ui -bootstrap-expect=3 -ui -node=consul_server2 -join=consul_server1 # 設置服務名 consul_server3: # 配置所使用的鏡像 image: consul # 容器總是重啟 restart: always # 容器名稱 container_name: consul_server3 #與宿主機的埠映射 ports: - 8502:8500 #容器目錄映射 volumes: - /usr/mydata/consul/consul_server3/data:/consul/data - /usr/mydata/consul/consul_server3/config:/consul/config # 覆蓋容器默認啟動命令 command: agent -server -bind=0.0.0.0 -client=0.0.0.0 -ui -bootstrap-expect=3 -ui -node=consul_server3 -join=consul_server1 【command 參數解釋:】 -server 以服務端角色啟動。 -client 指定客戶端可以訪問的 ip,默認為 127.0.0.1(不對外提供服務),設置成 0.0.0.0 表示不對客戶端 ip 進行限制(對外提供服務)。 -dev 以開發模式啟動。 -bind 表示綁定到指定 ip。 -ui 可以使用 web 介面訪問。 -bootstrap-expect 表示集群中 server 節點個數,一般為奇數。 -node 表示節點在 web ui 介面中顯示的名稱。 -join 表示加入集群中去。 -retry-join 表示加入集群中去,加入失敗後可以重新加入
consul 就簡單介紹到這了,沒有在實際工作中使用過,用到的時候再去研究一下。
有什麼不對的地方,希望不吝賜教。