Spring Cloud Alibaba系列


第一節 項目簡介

  Spring Cloud Alibaba 致力於提供微服務開發的一站式解決方案。此項目包含開發分布式應用微服務的必需組件,方便開發者通過 Spring Cloud 編程模型輕松使用這些組件來開發分布式應用服務。

  依托 Spring Cloud Alibaba,您只需要添加一些注解和少量配置,就可以將 Spring Cloud 應用接入阿里微服務解決方案,通過阿里中間件來迅速搭建分布式應用系統。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

第二章 依賴管理

Spring Cloud Alibaba BOM 包含了它所使用的所有依賴的版本。
 

一、版本管理規范

 
項目的版本號格式為 x.x.x 的形式,其中 x 的數值類型為數字,從 0 開始取值,且不
限於 0~9 這個范圍。項目處於孵化器階段時,第一位版本號固定使用 0,即版本號為 0.x.x
的格式。
由於 Spring Boot 1 和 Spring Boot 2 在 Actuator 模塊的接口和注解有很大的變更,且
spring-cloud-commons 從 1.x.x 版本升級到 2.0.0 版本也有較大的變更,因此 Spring Cloud
Alibaba 采取跟 SpringBoot 版本號一致的版本:
 1.5.x 版本適用於 Spring Boot 1.5.x
 2.0.x 版本適用於 Spring Boot 2.0.x
 2.1.x 版本適用於 Spring Boot 2.1.x
 2.2.x 版本適用於 Spring Boot 2.2.x
下表展示了 Spring Cloud Alibaba & Spring Cloud & Spring Boot 兼容關系:
The Spring Cloud Alibaba & Spring Cloud & Spring Boot compatibility table

 

 

 

 

 

以后我們的項目選擇的版本為:
 Spring Boot 2.2.3.RELEASE
 Spring Cloud Hoxton.SR3
 Spring Cloud Alibaba 2.2.0.RELEASE
 

二、依賴管理

  Spring Cloud Alibaba BOM 包含了它所使用的所有依賴的版本。如果您是 Maven Central
用戶,請將我們的 BOM 添加到您的 pom.xml 中的 <dependencyManagement> 部分。 這
將允許您省略任何 Maven 依賴項的版本,而是將版本控制委派給 BOM。
 
<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-alibaba-dependencies</artifactId>
      <version>2.2.0.RELEASE</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement> 

 

 

 
在下面的章節中,假設您使用的是 Spring Cloud Alibaba bom,相關 starter 依賴將不包
含版本號。
 
 

三、父項目的創建

 
  使用父項目能控制所有子項目依賴的全局版本,能有效的去除子項目里面重復的依賴,
減少出錯的幾率。在此,我們將創建一個父 pom,讓所有的子模塊都繼承該父 pom。
 

3.1 spring-cloud-alibaba-examples 項目的創建

 
  spring-cloud-alibaba-examples 將作為我們子項目的最大父 pom 項目!
注意:在使用 IDEA 之前我們假設您已經設置了相應的 JDK 配置和 Maven 配置
 

3.1.1 使用 IDEA 創建一個 Maven 項目

 
創建一個新的 Maven 項目 

 

 

選擇 Next: 

 

 

 

填寫下面的相關信息:
Name:spring-cloud-alibaba-examples
Location:D:\workspace\spring-cloud-alibaba-examples(不要隨便放)
GroupId:com.bjsxt
Version:1.0
 
最后,點擊 finish 完成創建的過程!
 
 
 

3.1.2 Spring Boot 版本的控制

 
  我們采用<parent> 的方式來導入 Spriing Boot 的版本號。
打開項目的 pom.xml 文件,添加依賴內容: 
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

 

 
這樣,我們的項目就已經規定了 Spring Boot 的版本為 2.2.3.RELEASE 了。
 
 

3.1.3 Spring Cloud 版本的控制

 
我們使用依賴管理的方式來添加 Spring Cloud 的版本信息,在<properties> 里面定義版
本信息,這里面我們選擇 Hoxton.SR3 這個版本!
 
<properties>
<spring-cloud.version>Hoxton.SR3</spring-cloud.version>
</properties>

 

 
  之后在 <dependencyManagement> 里面添加 spring-cloud 的 bom 信息,這將允許您省略任何 Maven 依賴項的版本,而是將版本控制委派給 BOM。
 
 <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

 

 
 

3.1.4 Spring Cloud Alibaba 版本的控制

 
  同樣,我們使用依賴管理的方式來添加 Spring Cloud Alibaba 的版本信息。在<properties>里面定義版本信息,這里面我們選擇 2.2.0.RELEASE 這個版本!
 
<properties>
...
<com-alibaba-cloud.version>2.2.0.RELEASE</com-alibaba-cloud.version>
...
</properties>

 

 
  之后在 <dependencyManagement> 里面添加 spring-cloud 的 bom 信息,這將允許您省略任何 Maven 依賴項的版本,而是將版本控制委派給 BOM。 
 
 
<dependencyManagement>
    <dependencies>
    ...
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-dependencies</artifactId>
            <version>${com-alibaba-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
....
    </dependencies>
</dependencyManagement>                

 

 

3.1.4 設置為 pom 的版本方式

添加項目的打包方式: 
<packaging>pom</packaging>

 

這將保證我們的項目是以 pom 打包的。 
 

3.1.5 完整的 pom.xml

<?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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent> 
 
<groupId>com.bjsxt</groupId>
<artifactId>spring-cloud-alibaba-examples</artifactId>
<version>1.0</version>
<properties>
<spring-cloud.version>Hoxton.SR3</spring-cloud.version>
<com-alibaba-cloud.version>2.2.0.RELEASE</com-alibaba-cloud.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${com-alibaba-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project> 

 

 

3.2 項目的打包

3.2.1 刪除項目里面多余的文件夾

  我們的項目僅僅是一個父 pom 文件,因此,項目只需要保留 pom.xml 即可,我們在此
可以刪除 src 目錄。
 

 

 

3.2.2 執行打包

 
使用 Maven 打包我們的項目。
 

 

 

 

 

 

3.2.3 觀察打包后的效果

 
我們打開我們 Maven 設置的本地倉庫地址,如圖所示:
打開 Maven 里面的 settings.xml 文件,找到該標簽

 

 

發現我們本地倉庫的地址位於 D 盤的 lib\jar 文件夾里面:
打開該文件夾: 

 

 

第三章 Nacos

 
一、什么是 Nacos?

 

 

  Nacos 致力於發現、配置和管理微服務。它提供了一組簡單易用的特性集,幫助快速實
現動態服務發現、服務配置、服務元數據及流量管理。使用 Nacos 可以更敏捷和容易地構
建、交付和管理微服務平台。 Nacos 是構建以“服務”為中心的現代應用架構 (例如微服務
范式、雲原生范式) 的服務基礎設施。
服務(Service)是 Nacos 世界的一等公民。Nacos 支持幾乎所有主流類型的“服務”的發現、
配置和管理: 
 Kubernetes Service.
 gRPC & Dubbo RPC Service.
 Spring Cloud RESTful Service.

 

 

二、Nacos 的關鍵特性

 
1.服務發現和服務健康監測 ,支持基於 DNS 和 RPC 的服務發現,支持基於傳輸層和
應用層的監控檢查;
2.動態配置服務 ,可以以中心化、外部化和動態化的方式管理所有環境的應用配置和
服務配置,同時支持版本追蹤和一鍵回滾等功能;
3.動態 DNS 服務 ,動態 DNS 服務支持權重路由,讓您更容易地實現中間層負載均
衡、更靈活的路由策略、流量控制以及數據中心內網的簡單 DNS 解析服務;
4.服務及其元數據管理 ,管理數據中心的所有服務及元數據,包括管理服務的描述、
生命周期、服務的靜態依賴分析、服務的健康狀態、服務的流量管理、路由及安全策略、服
務的 SLA 以及最首要的 metrics 統計數據。
 

三、Nacos 的核心概念

 

 

 服務 (Service)
  服務是指一個或一組軟件功能(例如特定信息的檢索或一組操作的執行),其目的是不同的
客戶端可以為不同的目的重用(例如通過跨進程的網絡調用)。Nacos 支持主流的服務生態,
如 Kubernetes Service、gRPC|Dubbo RPC Service 或者 Spring Cloud RESTful Service.
 服務注冊中心 (Service Registry)
  服務注冊中心,它是服務實例及元數據的數據庫。服務實例在啟動時注冊到服務注冊表,並
在關閉時注銷。服務和路由器的客戶端查詢服務注冊表以查找服務的可用實例。服務注冊中
心可能會調用服務實例的健康檢查 API 來驗證它是否能夠處理請求。
 服務元數據 (Service Metadata)
  服務元數據是指包括服務端點(endpoints)、服務標簽、服務版本號、服務實例權重、路由規
則、安全策略等描述服務的數據
 服務提供方 (Service Provider)
  是指提供可復用和可調用服務的應用方
 服務消費方 (Service Consumer)
  是指會發起對某個服務調用的應用方
 配置 (Configuration)
  在系統開發過程中通常會將一些需要變更的參數、變量等從代碼中分離出來獨立管理,以獨
立的配置文件的形式存在。目的是讓靜態的系統工件或者交付物(如 WAR,JAR 包等)更
好地和實際的物理運行環境進行適配。配置管理一般包含在系統部署的過程中,由系統管理
員或者運維人員完成這個步驟。配置變更是調整系統運行時的行為的有效手段之一。
 配置管理 (Configuration Management)
  在數據中心中,系統中所有配置的編輯、存儲、分發、變更管理、歷史版本管理、變更審計
等所有與配置相關的活動統稱為配置管理。
 名字服務 (Naming Service)
  提供分布式系統中所有對象(Object)、實體(Entity)的“名字”到關聯的元數據之間的映射管理
服務,例如 ServiceName -> Endpoints Info, Distributed Lock Name -> Lock Owner/Status Info,
DNS Domain Name -> IP List, 服務發現和 DNS 就是名字服務的 2 大場景。
 配置服務 (Configuration Service)
  在服務或者應用運行過程中,提供動態配置或者元數據以及配置管理的服務提供者。
 
 
 

第三章 Nacos 注冊中心

 

一、Nacos Discovery 簡介

 
  使用 Spring Cloud Alibaba Nacos Discovery,可基於 Spring Cloud 的編程模型快速接入 Nacos
服務注冊功能。
  服務發現是微服務架構體系中最關鍵的組件之一。如果嘗試着用手動的方式來給每一個客戶
端來配置所有服務提供者的服務列表是一件非常困難的事,而且也不利於服務的動態擴縮
容。Nacos Discovery 可以幫助您將服務自動注冊到 Nacos 服務端並且能夠動態感知和刷新
某個服務實例的服務列表。除此之外,Nacos Discovery 也將服務實例自身的一些元數據信
息-例如 host,port, 健康檢查 URL,主頁等內容注冊到 Nacos。
 
 

二、Nacos Server 安裝

 nacos2.0.3:處理:
 
在使用 Nacos 之前我們首先要獲取和安裝 Nacos。
 

2.1 Nacos Server 的下載

 
因為 Spring Cloud Alibaba 2.2.0.RELEASE 內置的 Nacos client 版本為 1.1.4,所以我們使用這個
版本的 Nacos。Nacos 下載地址:https://github.com/alibaba/nacos/releases:
 

 

 

我們找到 1.1.4 版本后點擊:
 

 

 

找到相關系統的壓縮包,這里面我使用的是 window 系統,所以我們選擇
nacos-server-1.1.4.zip 該文件,來點擊下載他。
 
 

 

 

2.2 Nacos Server 目錄的說明

 
將下載的 nacos-server-1.1.4 復制到我們的開發工具的文件夾里面。(沒有的同學可以新建
一個,養成一個良好的習慣)
 

 

 

 

 

 

 

 

 

2.3 配置 Nacos Server

 
進入${Nacos}/conf 目錄里面,使用文件編輯器打開 application.properties 文件,這里面我使
用的是 sublime text:

 

 

 

 

  Nacos 默認使用嵌入式數據庫實現數據的存儲,並不方便觀察數據存儲的基本情況,這
里面我們修改為使用 Mysql 數據庫做數據的存儲,方便我們觀察數據的結構。
在配置文件末尾添加如下配置: 
spring.datasource.platform=mysql
db.num=1
db.url.0=jdbc:mysql://localhost:3306/nacos?characterEncoding=utf8&connectTi
meout=1000&socketTimeout=3000&autoReconnect=true
db.user=root
db.password=123456 
 
 
注意:
  •  上面的 url 地址是我的服務器地址,如果你的 mysql 安裝在虛擬機或雲服務器上面,就
填寫真實的地址。
  •  db.user 用戶名
  •  db.password 密碼

2.4 Mysql 表的導入

  提示:Nacos 建議使用 5.7 以上的 Mysql 數據庫,版本較低可能存儲兼容性問題
我使用的 Mysql 數據庫版本為 5.7
 
打開${Nacos}/conf/文件夾: 
 

 

 

新建數據庫: 

 

 

新建 Nacos 的的數據庫:

 

 

 

 

 

 

 

 

具體表的細節和設計,我們將在源碼分析的階段講解。
 

2.5 Nacos Server 的啟動

 
上面工作都完成后,現在我們來啟動一個單機版的 Nacos 服務器。
進入到${Nacos}/bin 目錄里面:
 

 

 

 

 

 

 

 

 

 

 

三、框架的搭建

 

 

我們將使用搭建所示的測試案例。
 

3.1 創建 nacos-examples 的父項目

 
我們將為 Nacos 創建一個單獨項目,方便我們來學習和管理 nacos 的相關功能。
使用 IDEA 創建一個 Maven 模塊:

 

 

 

 

 

點擊 next:

 

 

Name:nacos-examples
其他項將會自動的填充進去。
點擊 Finish 完成創建。
 
修改   nacos-examples 里面的 pom.xml 文件,在此我們引入:
  •  spring-boot-starter-web (spring-boot-web 開啟的最基礎的依賴)
  • spring-cloud-alibaba-nacos-discovery(nacos 做服務發現的最基礎的依賴)
 
在<dependencies>添加這 2 個依賴:
 <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-nacos-discovery</artifactId>
        </dependency>
    </dependencies>

 

 
 
修改 nacos-examples 項目的打包方式:
<packaging>pom</packaging>
 
最后,完整的 pom.xml 文件如下所示:
 
 
<?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">
    <parent>
        <artifactId>spring-cloud-alibaba-examples</artifactId>
        <groupId>com.bjsxt</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <packaging>pom</packaging>
    <modules>
        <module>provider</module>
        <module>consumer</module>
        <module>config-client</module>
    </modules>
    <artifactId>nacos-examples</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-nacos-discovery</artifactId>
        </dependency>
    </dependencies>
</project>

 

 
 
 

3.2 創建服務的提供者 provider

 
服務提供者將為以后的服務消費者提供一些接口的供遠程調用。
使用 IDEA 為 nacos-examples 創建一個子模塊:

 

 

將 Next 進入下一步操作:
 

 

 

注意: 我們的 parent 必須選擇為 nacos-examples。
 
Name:provider(服務的提供者)
其他的項將自動的填充,不需要我們修改。
Provider 的依賴分析:
 

 

 

我們的 provider 已經自動的從 nacos-exmaples 里面把這 2 個依賴繼承過來了。
 

3.3 創建服務的消費者

服務消費者將遠程調用服務提供者提供的接口。
使用 IDEA 為 nacos-examples 創建一個子模塊:
 

 

 

將 Next 進入下一步操作:

 

 

注意:我們的 parent 必須選擇為 nacos-examples。
Name:consumer(服務的提供者)
其他的項將自動的填充,不需要我們修改。
Consumer 的依賴分析:

 

 

我們的 consumer 已經自動的從 nacos-exmaples 里面把這 2 個依賴繼承過來了。
至此,我們項目的骨架已經搭建完成。如下圖所示:

 

 

四、使用 Nacos 做注冊中心

 

4.1 provider 項目的完善

Provider 現在還是一個空的項目,里面沒有任何的數據接口。
 

4.1.1 添加一個 application.yml 配置文件

 
我們給 provider 添加一個配置文件:

 

 

文件的名稱為:

 

 

完成創建后,IDEA 能自動的識別該 yml 文件。

 

 

編輯該配置文件,添加如下的配置:
 
server:
    port: 8080
spring:
    application:
        name: provider-service
    cloud:
        nacos:
            discovery:
                server-addr: localhost:8848

 

 

 
 
配置說明:
 
  • server.port provider 服務端口為 8001 ;
  • spring.application.name 服務名稱為 provider-service;
  • spring.cloud.nacos.server-addr ,指定 Nacos 注冊中心的地址;
 

4.1.2 添加一個啟動類

 
Provider 還沒有啟動類,我們需要給他添加一個。

 

 

com.bjsxt 為包名,ProviderApplicaton 為類的名稱,創建完成后,自動的出現:

 

 

在該類里面添加如下的代碼:
 
 
@SpringBootApplication // 標記為 SpringBoot 的應用
@EnableDiscoveryClient // 啟用服務發現的功能
public class ProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderApplication.class,args) ;
}
}

 

 

4.1.3 添加一個 API 接口

 
提供者通過提供 API 接口供服務提供者調用。
在 provider 里面創建一個類:

 

 

名稱為 controller.EchoController

 

 

成功后,如下圖所示:
添加一個接口:
 
@RestController
public class EchoController {
/**
* echo message
* @param message
* @return reply content
*/
@GetMapping("/echo/{message}")
public ResponseEntity<String> echo(@PathVariable("message")
String message){
return
ResponseEntity.ok(String.format("hello,%s",message)) ;
}
}

 

 
該接口將會對訪問的人輸入一個 hello,xxx。
 

4.1.4 啟動 provider 測試

 
現在我們的 provider 已經准備就緒,啟動看能否注冊到 Nacos 上面 。

 

 

發現 provider 已經啟動成功了。
打開 Nacos 頁面觀察 provider 是否上線:

 

 

服務的提供者已經成功的上線了。
 

4.2 consumer 項目的完成

 
Consumer 現在還是一個空的項目,里面沒有任何的數據接口,也沒有對服務的提供者進行
調用。
 

4.2.1 添加一個 application.yml 配置文件


我們給 consumer 添加一個配置文件

 

 

名稱為:application.yml

 

 

 
IDEA 能自動的識別該配置文件的:
 
編輯該文件,給該文件添加如下的配置信息:
 
server:
    port: 8090
spring:
    application:
        name: consumer-service
    cloud:
       nacos:
        discovery:
            server-addr: localhost:8848               

 

 
4.2.2 添加一個啟動類
 
Consumer 現在還沒有任何的啟動類,我們需要給他添加一個。

 

 

創建成功后,如下圖所示:
 

 

 

在該類里面添加如下代碼:
 
@SpringBootApplication // 標記為 SpringBoot 的應用
@EnableDiscoveryClient // 開啟服務的發現功能
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class,args) ;
}
}

 

 
4.2.3 服務發現的測試
繼續改造啟動類,在里面添加如下代碼:
 
@Autowired
private DiscoveryClient discoveryClient ;// 快速服務的發現
@GetMapping("/discovery/{serviceName}")
public ResponseEntity<List<String>> discovery(@PathVariable("serviceName")
String serviceName){
/**
* 通過服務的 ID / 名稱得到服務的實例列表
*/
List<ServiceInstance> instances =
discoveryClient.getInstances(serviceName);
if(instances==null || instances.isEmpty()){
return ResponseEntity.notFound().build() ;
}
List<String> services = new ArrayList<>(instances.size());
instances.forEach(instance->{
services.add(instance.getHost()+":"+instance.getPort());
});
return ResponseEntity.ok(services) ;
}

 

 
  因為我們在啟動類里面添加了接口,所以要必須添加@RestController 該注解來標記該類:
最后,我們啟動 consumer-service 項目:

 

 

Consumer-service 已經成功了上線了。
在瀏覽器輸入:
http://localhost:8090/discovery/provider-service

 

其中:provider-service 代表服務的名稱
服務器給我們響應:
 

 

 

代表服務已經發現成功。
輸入一個不存在的服務名稱:
 
http://localhost:8090/discovery/provider
 
服務器給我們響應:

 

 

代表,該服務還沒有服務的提供者。
 
4.2.4 遠程調用的測試
在 consumer,我們調用 provider 里面的 echo 接口
繼續改造我們的啟動類,注冊一個 RestTemplate 對象:
 
/**
* 在容器里面注入一個 RestTempalte 對象
* @return
*/
@Bean
public RestTemplate restTemplate(){
return new RestTemplate() ;
}

 

 
調用測試:
 
@Autowired
27private RestTemplate restTemplate ;
@GetMapping("/rpcv1/{message}")
public ResponseEntity<String> rpcV1(@PathVariable("message") String message){
ResponseEntity<String> responseEntity = restTemplate.getForEntity(
"http://localhost:8081/echo/{message}",
String.class,
message
);
if(responseEntity.getStatusCode()==HttpStatus.OK){
return ResponseEntity.ok(String.format("遠程調用成功,結果
為%s",responseEntity.getBody())) ;
}
return ResponseEntity.badRequest().body("遠程調用失敗") ;
}
@GetMapping("/rpcv2/{message}")
public ResponseEntity<String> rpcV2(@PathVariable("message") String message){
List<ServiceInstance> instances =
discoveryClient.getInstances("nacos-provider");
if(instances==null || instances.isEmpty()){
return ResponseEntity.badRequest().body("當前服務沒有服務的提供者") ;
}
ServiceInstance serviceInstance = instances.get(0);
String instance =
serviceInstance.getHost()+":"+serviceInstance.getPort() ;
ResponseEntity<String> responseEntity = restTemplate.getForEntity(
String.format("http://%s/echo/{message}",instance),
String.class,
message
);
if(responseEntity.getStatusCode()==HttpStatus.OK){
return ResponseEntity.ok(String.format("遠程調用成功,結果
為%s",responseEntity.getBody())) ;
}
return ResponseEntity.badRequest().body("遠程調用失敗") ;
}
@GetMapping("/rpcv3/{message}")
public ResponseEntity<String> rpcV3(@PathVariable("message") String message){
List<ServiceInstance> instances =
28discoveryClient.getInstances("nacos-provider");
if(instances==null || instances.isEmpty()){
return ResponseEntity.badRequest().body("當前服務沒有服務的提供者") ;
}
ServiceInstance serviceInstance = loadbalance(instances);
System.out.println(serviceInstance);
String instance =
serviceInstance.getHost()+":"+serviceInstance.getPort() ;
ResponseEntity<String> responseEntity = restTemplate.getForEntity(
String.format("http://%s/echo/{message}",instance),
String.class,
message
);
if(responseEntity.getStatusCode()==HttpStatus.OK){
return ResponseEntity.ok(String.format("遠程調用成功,結果
為%s",responseEntity.getBody())) ;
}
return ResponseEntity.badRequest().body("遠程調用失敗") ;
}
 
 
其中,loadBalance 的實現邏輯為:
 
/**
* 從一個服務的實例列表里面選擇一個服務的實例
* @param instances
* 實例列表
* @return
* 具體的實例
*/
private ServiceInstance loadbalance(List<ServiceInstance> instances) {
Random random = new Random(System.currentTimeMillis());
return instances.get(random.nextInt(instances.size())) ;
}

 

 
 
重啟 Consummer-service:

 

 

 
在瀏覽器里面輸入:
 
http://localhost:8090/rpc/world

 

 
服務器給我們相應的數據為:

 

 

遠程調用已經成功!
 
 
五、負載均衡測試
當服務壓力突然變大時,我們需要通過對服務進行彈性伸縮和動態的擴容,讓服務展示更強
的並發能力和容錯能力。
 
5.1 修改服務提供者的數據接口
修改服務提供者的接口,可以讓我們很清晰的看到,我們當前調用的是那一台服務提供者。
讓我們來修改 provider 里面 EchoController 這個類
 

 

 

代碼如下:
@RestController
public class EchoController { 
 
@Value("${server.port}")
private Integer port ;
/**
* echo message
* @param message
* @return reply content
*/
@GetMapping("/echo/{message}")
public ResponseEntity<String> echo(@PathVariable("message") String
message){
return ResponseEntity.ok(String.format("hello,%s,我是服務提供
者:%s",message,port)) ;
}
}

 

因為我們在同一台機器上啟動多個服務提供者,那服務提供者的端口肯定不相同,我們
在此使用 port 作為服務提供者的唯一標示。
@Value : 從 env 里面注入這個值
 
5.2 啟動多個服務的提供者
先停止所有的服務:

 

 

在啟動一個服務的提供者:

 

 

啟動成功后:
 

 

 

啟動其他 1 個:

 

 

進入編輯頁面:

 

 

2 個操作:
Name:不能重復,這里我修改為 ProviderApplication-2
Program arguments:--server.port=8081 這句話的意思是,在啟動該服務時,添加一個
變量: server.port=8081 ,類似我們使用這樣的命令啟動:
 
java -jar xxx.jar --server.port=8081

 

 
--server.port 該 值 將 會 被 添 加 到 spring 的 env 里 面 , 並 且 該 方 式 的 優 先 級 高 於
application.yml 里面的配置值。所以,系統會使用該 port 來啟動服務。
修改完畢后,點擊 ok。
現在啟動我們的復制的 ProviderApplicaton-2:

 

 

觀察 Nacos 里面的數據:

 

 

服務:provider-service 已經有 2 個實例了,點擊詳情:

 

 

 
可以手動的下線該服務。
 

5.3 測試負載均衡

 
修改 consumer 里面的 ConsumerApplication ,在里面的末尾添加如下的代碼:
 
@Autowired
private LoadBalancerClient loadBalancerClient ;
@GetMapping("/choose/{serviceName}")
public ResponseEntity<String> choose(@PathVariable("serviceName") String serviceName){
ServiceInstance instance = loadBalancerClient.choose(serviceName);
System.out.println(String.format("本次選擇的實例
為:%s:%s",instance.getHost(),instance.getPort()));
return ResponseEntity.ok(instance.getHost()+":"+instance.getPort()) ;
}

 

 
其中:
 LoadBalancerClient :Ribbon 提供給我們最外層的接口,使用它我們可以很輕易的實現
負載均衡。
 LoadBalancerClient.choose(serviceName):通過給定的服務名稱,Ribbon 底層通過負載
均衡的算法得到一個可以進行遠程的實例。
現在,啟動 Consumer 進行測試:
在瀏覽器里面輸入:
 
http://localhost:8090/choose/provider-service

 

得到:

 

 刷新頁面:

 

 

並且后台輸出:

 

 

發現負載均衡已經完成。
 

5.4 更簡單的遠程調用測試

 
在之前,我們在 consumer 使用 RestTemplate 調用 provider 時,手動實現了負載均衡,url
的拼接:

 

 

代碼相當的冗余。現在,我們使用 Ribbon 的方式來實現:
在注入 RestTemplate 的方法上添加注解:

 

 

遠程調用的代碼變為:
@GetMapping("/rpc/ribbon/{message}")
public ResponseEntity<String> rpcByRibbon(@PathVariable("message") String
message){
ResponseEntity<String> responseEntity =
restTemplate.getForEntity("http://provider-service/echo/" + message,
String.class);
if(responseEntity.getStatusCode()==HttpStatus.OK){
return ResponseEntity.ok("調用成功,provider-service 相應給我們的數
據為:"+responseEntity.getBody()) ;
}
return ResponseEntity.badRequest().body("調用失敗,provider-service 的
相應碼為:"+responseEntity.getStatusCode()) ;
}

 

 

重啟 consumer 測試:
 
在瀏覽器里面輸入:
http://localhost:8090/rpc/ribbon/{message}

 

 

刷新測試:

 

 

負載均衡已經完成了。
 
 

六、Nacos Discovery 對外暴露的 Endpoint

 
Nacos Discovery 內部提供了一個 Endpoint, 對應的 endpoint id 為 nacos-discovery。我們通
過該 Endpoint,能獲取到:
 當前服務有哪些服務訂閱者 ;
 當前應用 Nacos 的基礎配置信息 ;
 

6.1 給項目添加依賴

 
假設我們想看服務提供者(provider)有那些訂閱者,以及 Nacos 的基礎配置信息。
我們就需要給 provider 項目的 pom.xml 文件里面添加:
 
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>

 

 

6.2 修改配置文件

 
Endpoint 本身對外界隱藏顯示,我們需要在配置里面開啟對 Endponit 的顯示支持。
修改 application.yml 配置文件,在里面添加如下的配置:
 
management:
38endpoints:
web:
exposure:
include: "*"

 

 
說明:
 exposure.include:對外界保留那些 Endpoint,若是所有則使用* ;
 

6.3 查詢效果

 
重啟項目,瀏覽器訪問:
http://localhost:8081/actuator/nacos-discovery
效果為:
 
 

 

 

七、Nacos Discovery Starter 更多的配置項

 

 

 

 

 

第三章 Nacos 配置中心

一、Nacos Config 簡介
 
使用 Spring Cloud Alibaba Nacos Config,可基於 Spring Cloud 的編程模型快速接入 Nacos 配
置管理功能。
 
上一節 Spring Cloud Alibaba Nacos 注冊中心記錄了 Nacos 作為注冊中心的使用方式,這節繼
續記錄下Nacos作為配置中心的使用方式。本節使用的Spring Cloud版本為Hoxton.SR3,
Spring
Cloud Alibaba 版本為 2.2.0.RELEASE,Spring Boot 版本為 2.2.3.RELEASE。
 
二、項目的搭建

 

 

  我們將在我們 nacos-examples 的基礎上,搭建一個 config-client,用來今天 nacos 配置中
心的案例測試 
 
 

2.1 創建 config-client 項目

 
使用 IDEA 創建一個 Maven 模塊:

 

 

 

選擇 Maven:

 

點擊下一步:

 

 

Parent:選擇 nacos-examples
Name:config-client
其他的項,保持默認。
點擊 FINISH 完成創建的過程:
 

 

 

2.2 添加依賴

 
我們的項目繼承自 nacos-examples,它里面已經包含 2 個基本的依賴:
 服務注冊和發現:spring-cloud-alibaba-nacos-discovery 這個是微服務里面必不可缺的組
 Web 開發相關:spring-boot-starter-web 開發 web 項目最基礎的依賴
現在,我們在里面添加今天我們要學習的配置獲取的組件:
 
spring-cloud-alibaba-nacos-config

 

編輯 config-client 里面的 pom.xml 文件,添加以下的內容:
 
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-nacos-config</artifactId>
</dependency>
</dependencies> 
 
在添加一個 mavne 的打包插件:
 
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
 
查看項目所有的依賴:

 

 

現在,項目里面已經包含上面的 3 個依賴了。
 

2.3 完整的 pom.xml 文件

<?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">
<parent>
<artifactId>nacos-examples</artifactId>
<groupId>com.bjsxt</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>config-client</artifactId>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-nacos-config</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project> 

 

 
至此,項目的搭建完成了。
 

三、在 nacos-server 里面添加配置

 
相關的工作圖,如圖: 
 

 

 

Nacos-client 會從 Nacos-Server 里面獲取配置文件,首先,Nacos-Server 里面需要有配
置文件才能獲取。
 
新建如下所示的配置信息:
 
Data ID:
nacos-config.properties
Group :
DEFAULT_GROUP
配置格式:
Properties
配置內容:
user.name=nacos-config-properties
user.age=90 
 
打開 Nacos 的管理頁面:

 

 

現在我們看見,里面還沒有任何的配置信息。
點擊:

 

 

 

 

 

 

點擊發布,完成配置文件的發表任務: 

 

 

 

返回后,配置信息已經發布成功: 

 

 

四、獲取配置信息 

config-client項目的pom.xml

<?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">
    <parent>
        <artifactId>nacos-examples</artifactId>
        <groupId>com.bjsxt</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>config-client</artifactId>

    <dependencies>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-nacos-config</artifactId>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

 

我們將演示在 config-client 里面獲取上面我們寫的 2 個配置信息

 

4.1 添加一個配置文件

 

 

文件名稱為: 

 

 

注意:不是 application.yml ,bootstrap.yml 比 application 有更高的優先級。
Idea 能自動的識別該文件的格式:
 

 

 

編輯該文件,添加以下內容:
 
server:
  port: 8070
spring:
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
      config:
        server-addr: localhost:8848
        file-extension: properties
  application:
    name: config-client

 

 
 
 
配置說明:
 server.port: 服務的運行端口
spring.cloud.nacos.discovery.server-addr: 注冊中心地址
spring.cloud.nacos.config.server-addr: 配置中心地址
spring.cloud.nacos.config.file-extension: 配置中心的配置屬性的數據格式
spring.application.name: 應用的名稱 
 

4.2 新建一個啟動類 

 

 

名稱為:com.bjsxt.ConfigClientApplication 
 

 

編輯該類:
@SpringBootApplication
@EnableDiscoveryClient
@RefreshScope
@RestController
public class ConfigClientApplication {
@Value("${user.name}")
private String userName ;
@Value("${user.age}")
private Integer userAge ;
public static void main(String[] args) {
SpringApplication.run(ConfigClientApplication.class,args) ;
}
/**
* 獲取配置文件里面用戶的信息
* @return
*/
@GetMapping("/user/info")
public ResponseEntity<String> getUser(){
return ResponseEntity.ok(
String.format("從配置中心獲取的信息為:user:%s,
age:%s",userName,userAge)) ;
}
}

 

說明:
@RefreshScope:代表配置文件是可以被刷新的
@Value:從 env 里面獲取配置信息
 

4.3 啟動測試 

 

 

在瀏覽器里面輸入:
 
http://localhost:8070/user/info

 

 

效果已經完成了。
 
 

五、獲取配置規則

nacos 配置中心通過 namespace、dataId 和 group 來唯一確定一條配置。
 
Namespace:即命名空間。默認的命名空間為 public,我們可以在 Nacos 控制台中新建
命名空間;
dataId:即配置文件名稱
Group:即配置分組,默認為 DEFAULT_GROUP,可以通過 spring.cloud.nacos.config.group
配置。
其中:dataId 是最關鍵的配置字段:格式如下:

 

 

說明:
 prefix 默 認 為 spring.application.name 的 值 , 也 可 以 通 過 配 置 項
spring.cloud.nacos.config.prefix 來配置;
 spring.profiles.active 即為當前環境對應的 profile。注意,當 spring.profiles.active 為空時,
對應的連接符-也將不存在,dataId 的拼接格式變成${prefix}.${file-extension};
 file-extension 為 配 置 內 容 的 數 據 格 式 , 可 以 通 過 配 置 項 spring.cloud.nacos.config.file-extension 來配置。
這就是上面我們為什么能獲得到配置的原因了。
 
 

六、配置划分實戰 

 

 

Nacos 配置中心的 namespace、dataId 和 group 可以方便靈活地划分配置。比如,我們現在
有一個項目需要開發,項目名稱為 bjsxt,項目開發人員分為兩個組:GROUP_A 和 GROUP_B,
項目分為三個環境:開發環境 dev、測試環境 test 和生產環境 prod。
Bjsxt->GRUOR_A->dev
 
6.1 在 Nacos 控制台中新建一個名稱為 bjsxt 的命名空間

 

 

點擊新建命令空間:
填寫以下的內容: 
 

 

 

點擊確定,完成創建。
完成后,如圖所示:

 

 

新建完成后:我們看見它自動幫我們生產了一個 ID:
 
8defab18-df88-49e5-b13e-526f89da87ad

 

記錄該 ID 值。
 

6.2 在 Nacos 新建配置 

 

 

切換完成后,點擊 +:

 

 

填寫以上的信息。點擊發布:

 

 

完成后:

 

 

已經完成創建。
 
 

6.3 獲取配置文件

 
修改 config-client 里的 bootstrap.yml 文件:
 
 
server:
    port: 8070
spring:
    cloud:
        nacos:
            discovery:
                server-addr: localhost:8848
            config: # 指定配置中心的地址和配置中心使用的數據格式
                server-addr: localhost:8848
                file-extension: yml  #properties
                group: GROUP_A  # 獲取 GROUP_A 里面的配置
                namespace: 8defab18-df88-49e5-b13e-526f89da87ad # 命名空間,寫 id的值
#               prefix: ${spring.application.name} # 前綴,默認為應用的名稱,不需要
修改
    application:
        name: config-client
    profiles:
        active: dev # 使用的 dev 環境的配置    

 

 

 

 

6.4 重啟 config-client 測試 

 

 

瀏覽器訪問: 
 
 
http://localhost:8070/user/info 
 
得到: 

 

 

配置信息已經獲取成功。
 

七、配置回滾

 
Nacos 中,修改配置點擊發布后會創建一個對應的歷史版本快照,我們可以在 Nacos 控制台
的歷史版本列表中找到這些快照。
 

7.1 動態刷新

 

 

該注解,可以刷新配置。故無需啟動就能看見最新的配置信息。
 

7.2 修改配置文件 

 

 

點擊發布。
出現對比頁面:
 

 

 

點擊確認發布:
 

 

 

然后點擊返回。
 
 

7.3 歷史版本的查詢

 

 

 

 

可以看見修改的日期。
 

7.4 回滾

將配置信息回滾為之前的版本:
 

 

 

根據修改時間,我們將 12:38:53 的數據回滾為 12:25:12 的數據:
 

 

 

已經自動的展示回滾的內容了,點擊回滾配置: 

 

 

點擊確認,完成回滾:

 

 

再次測試:
瀏覽器輸入:
 
http://localhost:8070/user/info

 

 
已經回滾成功了: 

 

八、獲取多個配置

 
除了通過上面的方式指定一個唯一配置外,我們還可以同時獲取多個配置文件的內容。
 
 

8.1 修改 config-client 里面的配置文件

server:
  port: 8070
spring:
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
config:
  extension-configs: # 多個配置
      - dataId: test-a.yml
       group: DEFAULT_GROUP
      refresh: true
     - dataId: test-b.yml
      group: DEFAULT_GROUP
      refresh: false
# 指定配置中心的地址和配置中心使用的數據格式
#server-addr: localhost:8848
#file-extension: yml
#properties
#group: GROUP_A # 獲取 GROUP_A 里面的配置
#namespace: 8defab18-df88-49e5-b13e-526f89da87ad # 命名空間,寫 id 的值
##prefix: ${spring.application.name} # 前綴,默認為應用的名稱,不需要修改
application:
  name: config-client
profiles:
  active: dev # 使用的 dev 環境的配置

 

說明:
  •  spring.cloud.nacos.config.extension-configs[n].dataId,指定多個配置的 dataId,必須包含
文件格式,支持 properties、yaml 或 yml;
  •  spring.cloud.nacos.config.extension-configs[n].group,指定分組;
  •  spring.cloud.nacos.config.extension-configs[n].refresh,是否支持刷新。
  上 面 的 配 置 中 , 我 們 分 別 從 DEFAULT_GROUP 中 獲 取 了 config-client-a.yml 和
config-client-b.yml 配置內容,並且 config-client-a.yml 支持刷新,config-client-b.yml 不支持刷新。
 
注意
沒有 namespace 的配置,言外之意就是 Nacos 目前還不支持多個配置指定不同的命名空間。
 

8.2 在 Nacos 里面完成這 2 個配置文件的創建

config-client-a.yml:
 
切換 namespace:

 

 

點擊 + 完成創建: 

 

 

點擊發布。
config-client-b.yml
 

 

 

 

 

8.3 獲取配置信息

 
重啟 config-client,測試:
瀏覽器訪問: 
 
 

九、Spring Cloud Alibaba Nacos Config 常用的配置.

 

 

 

 

 

 

第四章 Spring Cloud Alibaba Sentinel

一、Sentinel 簡介

 

 

隨着微服務的流行,服務和服務之間的穩定性變得越來越重要。 Sentinel 以流量為切入點,
從流量控制、熔斷降級、系統負載保護等多個維度保護服務的穩定性。
 

Sentinel 具有以下特征:

 
  •  豐富的應用場景: Sentinel 承接了阿里巴巴近 10 年的雙十一大促流量的核心場景,
例如秒殺(即突發流量控制在系統容量可以承受的范圍)、消息削峰填谷、實時熔斷下
游不可用應用等。
  •  完備的實時監控: Sentinel 同時提供實時的監控功能。您可以在控制台中看到接入應
用的單台機器秒級數據,甚至 500 台以下規模的集群的匯總運行情況。
  •  廣泛的開源生態: Sentinel 提供開箱即用的與其它開源框架/庫的整合模塊,例如與
Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相應的依賴並進行簡單的配置即可
快速地接入 Sentinel。
  •  完善的 SPI 擴展點: Sentinel 提供簡單易用、完善的 SPI 擴展點。您可以通過實現擴
展點,快速的定制邏輯。例如定制規則管理、適配數據源等。
 

二、Sentinel 控制台安裝

 
  Sentinel 提供一個輕量級的開源控制台,它提供機器發現以及健康情況管理、監控(單機和
 
集群),規則管理和推送的功能。本節將詳細記錄何如通過 Sentinel 控制台控制 Sentinel 客
 
戶端的各種行為。Sentinel 控制台的功能主要包括:流量控制、降級控制、熱點配置、系統規則 和 授權規則等。
 

2.1 下載 Sentinel

 

 

找到:1.7.1 版本: 
 

 

 

 

 

點擊 sentinel-dashboard-1.7.1.jar 完成下載:
 
提示:
 
直接在 github 下載,速度非常的慢,大家可以從今天的軟件目錄里面去下載: 

 

 

2.2 啟動 sentinel-dashboard

 
將下載好的 sentinel-dashboard-1.7.1.jar 復制到安裝軟件的目錄里面。

 

 

使用:
    java -jar sentinel-dashboard-1.7.1.jar
 
來啟動一個 sentinel-dashboard 的實例。
 
啟動成功后:

 

 

我們可以通過瀏覽器訪問:
  http://localhost:8080/ 

 

 

其中,用戶名:sentinel
密碼: sentinel
更多可用的啟動參數配置:
 
java -D 參數名=參數值 -jar xx.jar
java -jar xx.jar --參數名=參數值
 
 
  • -Dsentinel.dashboard.auth.username=sentinel 用於指定控制台的登錄用戶名為 sentinel;
  •  -Dsentinel.dashboard.auth.password=123456 用於指定控制台的登錄密碼為 123456,如果省略這兩個參數,默認用戶和密碼均為 sentinel;
  •  -Dserver.servlet.session.timeout=7200 用於指定 Spring Boot 服務端 session 的過期時間,如 7200 表示 7200 秒;60m 表示 60 分鍾,默認為 30 分鍾;
  •  -Dcsp.sentinel.dashboard.server=consoleIp:port 指定控制台地址和端口
 
 

三、搭建客戶端

 

 

剛才我們搭建了 sentinel-dashboard,我們還需要搭建一個客戶端,用於測試 sentinel 的各種
功能。
我們將搭建如圖所示的 Maven 項目結構:

 

 

3.1 使用 IDEA 創建子模塊

 

 

選擇 Maven 項目:
 

 

 

點擊 Next:
 

 

 

 

 

Parent:選擇 spring-cloud-alibaba-examples
Name:命名為 sentinel-example-client
其他的項保持默認值即可。
點擊 Finish 完成創建。 
 

 

 

3.2 添加依賴

 
修改 sentinel-example-client 里面的 pom.xml 文件:
添加以下的內容:
 
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
</dependencies> 

 

 
 
有 2 個依賴:
  •  spring-cloud-starter-alibaba-sentinel 這是 spring cloud 和 sentinel 集成的項目
  •  spring-boot-starter-web 開啟 web 最基礎的依賴
添加 spring boot 的打包插件:
 
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build> 

 

 
 
這樣,我們的項目打包好了后,可以使用 java -jar 來直接運行了。
 

3.3 完整的 pom.xml 

<?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">
<parent>
<artifactId>spring-cloud-alibaba-examples</artifactId>
<groupId>com.bjsxt</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>sentinel-example-client</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project> 

3.4 添加一個配置文件

 

命名為: 

 

 

修改該配置文件,添加以下的配置:
 
server:
    port: 8085
spring:
    application:
        name: sentinel-client
    cloud:
        sentinel:
            transport:
                dashboard: localhost:8080
                port: 8719 

 

 
 
其中:
  •  spring.cloud.sentinel.transport.dashboard 指定了 sentinel 控制台的 ip 和端口地址;
  •  spring.cloud.sentinel.transport.port 代表 sentinel 客戶端和控制台通信的端口,默認為
8719,如果這個端口已經被占用,那么 sentinel 會自動從 8719 開始依次+1 掃描,直到
找到未被占用的端口。
 

3.5 添加一個啟動類 

 

 

名稱為:com.bjsxt.SentinelClientApplication

 

 

添加如下的代碼: 
 
@SpringBootApplication
public class SentinelClientApplication {
  public static void main(String[] args) {
    SpringApplication.run(SentinelClientApplication.class ,args) ;
  }
}

 

 

3.6 添加一個 Controller

 

 

 

名稱為:controller.TestController

 

 

在 TestContrller 里面添加如下接口: 
 
@RestController
public class TestController {
    @GetMapping("/hello")
    public ResponseEntity<String> hello(){
        return ResponseEntity.ok("hello,sentinel") ;
    }
}
 

 

 

3.7 啟動項目

 

在瀏覽器訪問:
 
http://localhost:8080/#/dashboard/home
 
出現: 

 

 

發現並沒有任何的功能。
 
此時,我們訪問一下我們寫的 hello 接口: 
 

 

 

多訪問幾次。
 
再次訪問:

 

 

控制台已經顯示正常了。
 
並且,在簇點鏈路中可以看到剛剛那筆請求,我們可以對它進行流控、降級、授權、熱點等
 
配置(控制台是懶加載的,如果沒有任何請求,那么控制台也不會有任何內容)。 
 

 

 

四、流控規則

 
流量的控制規則。
 
在簇點鏈路列表中,點擊/hello 后面的流控按鈕:

 

 

出現: 

 

 

  •  資 源 名 : 標 識 資 源 的 唯 一 名 稱 , 默 認 為 請 求 路 徑 , 也 可 以 在 客 戶 端 中 使 用
@SentinelResource 配置;
  •  針 對 來 源 : Sentinel 可 以 針 對 服 務 調 用 者 進 行 限 流 , 填 寫 微 服 務 名 稱 即
spring.application.name,默認為 default,不區分來源;
  •  閾值類型、單機閾值:
    •    QPS(Queries-per-second,每秒鍾的請求數量):當調用該 api 的 QPS 達到閾值的時候,進行限流;
    •    線程數:當調用該 api 的線程數達到閾值的時候,進行限流。
    •    是否集群:默認不集群;
  •    流控模式:
    •    直接:當 api 調用達到限流條件的時,直接限流;
    •    關聯:當關聯的資源請求達到閾值的時候,限流自己;
    •    鏈路:只記錄指定鏈路上的流量(指定資源從入口資源進來的流量,如果達到閾值,則進行限流)。
  •    流控效果:
    •    快速失敗:直接失敗;
    •    Warm Up:根據 codeFactor(冷加載因子,默認值為 3)的值,從閾值/codeFactor,經過預熱時長,才達到設置的 QPS 閾值;
    •    排隊等待:勻速排隊,讓請求勻速通過,閾值類型必須設置為 QPS,否則無效。
 
 

4.1 QPS 直接失敗

 
演示下 QPS 直接失敗設置及效果。點擊簇點鏈路列表中/hello 請求后面的流控按鈕:
 

 

 

  上面設置的效果是,1 秒鍾內請求/hello 資源的次數達到 2 次以上的時候,進行限流。
點擊新增完成該規則的設置。

 

 

現在,在瀏覽器訪問:

 

 

  當手速快點的時候(1 秒超過 2 次),頁面返回 Blocked by Sentinel (flow limiting)。並且響應
碼為 429。
 
 

4.2 線程數直接失敗

 

4.2.1 添加接口

 
在 TestController 里面新建一個接口。 
/**
* 線程直接失敗
* @return
* @throws InterruptedException
*/
@GetMapping("/thread")
public ResponseEntity<String> threadMode() throws InterruptedException {
TimeUnit.SECONDS.sleep(1);
return ResponseEntity.ok("hello,sentinel!") ;
}

 

 
其中,我們添加了:
 
TimeUnit.SECONDS.sleep(1);
 
讓線程睡 1s ,這樣,更容易觸發規則。
重啟項目。
訪問我們添加的 thread 的接口: 
 
http://localhost:8085/thread

 

 

 

發現,頁面需要等待 1s 左右才會響應,這是線程 sleep 的效果。

4.2.2 新增流控規則

 

 

 

 

點擊新增,完成創建。

 

 

4.2.3 測試該規則 

 
瀏覽器快速的訪問:
http://localhost:8085/thread 

 

 

 

4.3 關聯

 
訪問某個接口達到一定的流控規則后,開始限制本接口。
 

4.3.1 在 TestController 里面添加 api 接口 

 
@GetMapping("/test1")
public ResponseEntity<String> test1(){
return ResponseEntity.ok("hello,test1") ;
}
@GetMapping("/test2")
public ResponseEntity<String> test2(){
return ResponseEntity.ok("hello,test2") ;
}

 

 
重啟項目,正常的訪問 test1,test2 測試:

 

 

4.3.2 添加規則

我們想讓 test1 關聯 test2,也就是說,訪問 test2 接口達到某種規則后,開始限流 test1 。

 

 

上述流控規則表示:當 1 秒內訪問/test2 的次數大於 2 的時候,限流/test1。

 

 

 

 

4.3.3 測試規則

我們使用打開 2 個網頁,密集訪問/test2,然后我們手動瀏覽器請求/test1,看看效果。
 

 

 

 

 

發現已經開始限流了。
 

4.4 鏈路

 
程序的調用可以看成為一條鏈路。當觸發鏈路的某條規則后,該鏈路被限流。

 

 

上圖從 API 出發,可以發現有 2 條鏈路,分別為:
Link1-->hello-->other
Link2-->hello-->other
2 條鏈路在調用上,限流規則互補影響。
 

 

 

 

 

4.4.2 添加接口 

 

@Autowired
private TestService testService ;
@GetMapping("/link1")
public ResponseEntity<String> link1(){
return ResponseEntity.ok(
String.format("link1,調用 test,結果
為%s",testService.hello())) ;
}
@GetMapping("/link2")
public ResponseEntity<String> lin2(){
return ResponseEntity.ok(
String.format("link2,調用 test,結果為%s",testService.hello())) ;
}
 

 

4.4.3 聲明資源

 
我們現在把 TestService 里面的 hello 方法變成一個資源:

 

 

注意:
@SentinelResource("hello")將該方法標識為一個 sentinel 資源,名稱為 hello。
然后重啟測試:

 

 

4.4.4 添加鏈路規則

點擊簇點鏈路: 

 

 

 

 

在入口資源,我們使用的是 link1,點擊新增,完成對規則的添加。
上述配置的意思是,當通過/link1 訪問 hello 的時候,QPS 大於 2 則進行限流;言外之意就是
/link 訪問 hello 請求並不受影響。
 

 

 

4.4.5 測試該規則

 
打開 link1 ,link2 接口。

 

 

 

 

訪問測試 n 次,發現還是不能成功。難受!
具體的錯誤: 

 

 

4.5 預熱 Warm Up

 
流控效果除了直接失敗外,我們也可以選擇預熱 Warm Up。

 

 

sentinel 客戶端的默認冷加載因子 coldFactor 為 3,即請求 QPS 從 threshold / 3 開始,經預
熱時長逐漸升至設定的 QPS 閾值。
比如:
我們給 hello 資源設置該規則。

 

 

 根據codeFactor(冷加載因子,默認為3)的值,即請求 QPS 從 threshold / 3 開始,經預熱時長逐漸升至設定的 QPS 閾值

 

上面的配置意思是:對於/hello 資源,一開始的 QPS 閾值為 3,經過 10 秒后,QPS 閾值
達到 10。
新增完成后: 

 

 

 

快速訪問/hello
http://localhost:8085/hello 

 

 

 

 

 

 

 

 

最快的手速點刷新,一開始會常看到 Blocked by Sentinel (flow limiting)的提示,10 秒后
幾乎不再出現(因為你的手速很難達到 1 秒 10 下)。

4.6 排隊等待

 
排隊等待方式不會拒絕請求,而是嚴格控制請求通過的間隔時間,也即是讓請求以均勻的速
度通過。
在 TestController 里面添加接口:
private static Logger logger = LoggerFactory.getLogger(TestController.class) ;
@GetMapping("/queue")
public ResponseEntity<String> queue(){
logger.info("開始處理請求");
return ResponseEntity.ok("ok") ;
} 

 

 
重啟項目並且訪問 queue 接口: 

 

 

給 queue 添加一個限流規則:
 

 

 

 

 

五、降級規則

 
Sentinel 除了流量控制以外,對調用鏈路中不穩定的資源進行熔斷降級也是保障高可用的重
 
要措施之一。由於調用關系的復雜性,如果調用鏈路中的某個資源不穩定,最終會導致請求
 
發生堆積。Sentinel 熔斷降級會在調用鏈路中某個資源出現不穩定狀態時(例如調用超時或
 
異常比例升高),對這個資源的調用進行限制,讓請求快速失敗,避免影響到其它的資源而
 
導致級聯錯誤。當資源被降級后,在接下來的降級時間窗口之內,對該資源的調用都自動熔
 
斷(默認行為是拋出 DegradeException)。
 
當訪問系統失敗超過一定的次數后,對該接口進行熔斷的操作。
 

 

 

我們可以發現,降級策略分為 3 種:
  •  RT,平均響應時間 (DEGRADE_GRADE_RT):當 1s 內持續進入 5 個請求,對應時刻的
平均響應時間(秒級)均超過閾值(count,以 ms 為單位),那么在接下的時間窗口
 
(DegradeRule 中的 timeWindow,以 s 為單位)之內,對這個方法的調用都會自動地
 
熔斷(拋出 DegradeException)。注意 Sentinel 默認統計的 RT 上限是 4900 ms,超
 
出 此 閾 值 的 都 會 算 作 4900 ms , 若 需 要 變 更 此 上 限 可 以 通 過 啟 動 配 置 項
 
-Dcsp.sentinel.statistic.max.rt=xxx 來配置。
  •  異常比例 (DEGRADE_GRADE_EXCEPTION_RATIO):當資源的每秒請求量 >= 5,並且每秒
異常總數占通過量的比值超過閾值(DegradeRule 中的 count)之后,資源進入降級狀
 
態,即在接下的時間窗口(DegradeRule 中的 timeWindow,以 s 為單位)之內,對這
 
個方法的調用都會自動地返回。異常比率的閾值范圍是 [0.0, 1.0],代表 0% - 100%。
  •  異常數 (DEGRADE_GRADE_EXCEPTION_COUNT):當資源近 1 分鍾的異常數目超過閾值
之后會進行熔斷。注意由於統計時間窗口是分鍾級別的,若 timeWindow 小於 60s,
 
則結束熔斷狀態后仍可能再進入熔斷狀態。
 

5.1 RT

 
  當 1s 內持續進入 5 個請求,對應時刻的平均響應時間(秒級)均超過閾值(count,以 ms
為單位),那么在接下的時間窗口(DegradeRule 中的 timeWindow,以 s 為單位)之內,
對這個方法的調用都會自動地熔斷(拋出 DegradeException) 
 

5.1.1 添加測試接口

 
@GetMapping("/rt")
public ResponseEntity<String> rt() throws InterruptedException {
TimeUnit.SECONDS.sleep(1);
return ResponseEntity.ok("ok") ;
}

 

 

5.1.2 添加降級的規則 

 

 

5.1.3 測試

 
打開 Apache jmeter 。
添加一個線程組: 

 

 

1 s 10 個線程同時發請求。
 

 

 

 

 

 

 

 

 

 

 

5.2 異常比例

 
當資源的每秒請求量 >= 5,並且每秒異常總數占通過量的比值超過閾值之后,資源進入降
級狀態,在接下來的時間窗口內,程序都會快速失敗。
 

5.2.1 添加接口 

@GetMapping("/exception")
public ResponseEntity<String> exception() throws InterruptedException {
throw new RuntimeException("就是不想成功!") ;
}

 

 

5.2.2 添加降級規則

 

 

上面的配置含義是:如果/exception 的 QPS 大於 5,並且每秒鍾的請求異常比例大於 0.5
的話,那么在未來的 3 秒鍾(時間窗口)內,sentinel 斷路器打開,該 api 接口不可用。
也就是說,如果一秒內有 10 個請求進來,超過 5 個以上都出錯,那么會觸發熔斷,1
秒鍾內這個接口不可用。
 
 

5.2.3 測試

 
打開 Jmeter,修改請求的地址:

 

 

開始測試

 
在瀏覽器打開:
 
http://localhost:8086/exception 
 

 

 

直接被熔斷,停止 jemter,等待 3s,在此訪問: 

 

 

5.3 異常數

 
當策略為異常數時表示:當指定時間窗口內,請求異常數大於等於某個值時,觸發降級。繼
續使用上面的接口測試。
 

5.3.1 添加規則 

 

 

上面的規則表示:在 60 秒內,訪問/exception 請求異常的次數大於等於 5,則觸發降級。
 

5.3.2 測試該規則

 
可以看到,當第 5 次訪問的時候成功觸發了降級。
 

六、熱點規則

 
熱點即經常訪問的數據。很多時候我們希望統計某個熱點數據中訪問頻次最高的數據,並對
其訪問進行限制。
 

6.1 添加一個接口 

 

@GetMapping("/buy")
@SentinelResource("buy")
public ResponseEntity<String> buy(String prodName,Integer prodCount){
return ResponseEntity.ok("買" + prodCount + "份" + prodName );
} 

 

 

6.2 添加熱點的規則

 
對這個資源添加熱點規則: 

 

 

上面的配置含義是:對 buy 資源添加熱點規則,當第 0 個參數的值為華為的時候 QPS
閾值為 3,否則為 1。此外,如果第 0 個參數不傳,那么這筆請求不受該熱點規則限制。
 
 

6.3 測試效果

 
不是華為: 

 

 

買 1 次后,里面限流。
 

 

 

七、系統規則

 
系統規則則是針對整個系統設置限流規則,並不針對某個資源,設置頁面如下: 

 

 

閾值類型包含以下五種:

 
 Load 自適應(僅對 Linux/Unix-like 機器生效):系統的 load1 作為啟發指標,進行自
適應系統保護。當系統 load1 超過設定的啟發值,且系統當前的並發線程數超過估算
的系統容量時才會觸發系統保護(BBR 階段)。系統容量由系統的 maxQps minRt 估
算得出。設定參考值一般是 CPU cores 2.5。
 CPU usage(1.5.0+ 版本):當系統 CPU 使用率超過閾值即觸發系統保護(取值范圍
0.0-1.0),比較靈敏。
 平均 RT:當單台機器上所有入口流量的平均 RT 達到閾值即觸發系統保護,單位是毫
秒。
 並發線程數:當單台機器上所有入口流量的並發線程數達到閾值即觸發系統保護。
 入口 QPS:當單台機器上所有入口流量的 QPS 達到閾值即觸發系統保護。
 

八、授權規則

 
授權規則用於配置資源的黑白名單: 
 

 

 

上述配置表示:只有 appA 和 appB 才能訪問 test1 資源。
 

第四章 Spring Cloud Alibaba Sentinel

 

一、@SentinelResource 簡介

Sentinel 提供了@SentinelResource 注解用於定義資源,並提供可選的異常回退和 Block 回退。
 
異常回退指的是@SentinelResource 注解標注的方法發生 Java 異常時的回退處理;Block 回退
 
指的是當@SentinelResource 資源訪問不符合 Sentinel 控制台定義的規則時的回退(默認返回
 
Blocked by Sentinel (flow limiting))。這節簡單記錄下該注解的用法。
 

 

 

2.1 搭建 sentinel-example

 
我們將在 sentinel-example 里面演示所有@SentinelResource 的的功能。
 
2.1.4 完整的 pom.xml 文件如下
<?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">
<parent>
<artifactId>spring-cloud-alibaba-examples</artifactId>
<groupId>com.bjsxt</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<packaging>pom</packaging>
<artifactId>sentinel-examples</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
</project>

 

 
2.1.1 使用 IDEA 創建一個 Maven 項目

 

 

 

 

2.1.2 添加依賴 
 
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies> 

 

 
 Spring-boot-stater-web 是開發 web 最基礎的依賴;
 spring-cloud-alibaba-nacos-discovery 是服務的發現組件
 

2.1.3 修改項目的打包方式

 
<packaging>pom</packaging>

 

 
 

2.2 搭建 sentinel-provider

 
Provide 是一個普通的服務的提供者。
 

2.2.1 使用 IDEA 創建一個 Maven 項目

 

 

選擇 Maven 項目:
 

 

 

 

 

Parent:選擇 sentinel-example
Name:sentinel-provider
 
點擊 Finish,完成創建。
2.2.2 修改項目的打包方式
我們修改項目的打包方式,以后我們可以使用 jar 來發布項目。
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build> 

 

 

2.2.3 完整的 pom.xml 文件

<?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">
<parent>
<artifactId>sentinel-examples</artifactId>
<groupId>com.bjsxt</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>sentinel-provider</artifactId>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project> 

 

 
2.3 搭建 sentinel-consumer
 
Provide 是一個普通的服務的消費者。
 
2.3.1 使用 IDEA 創建一個 Maven 項目

 

 

點擊 Next,填寫以下的內容:

 

 

Parent:選擇 sentinel-example
Name:sentinel-consumer
點擊 Finish,完成創建。
 

2.3.2 修改 pom.xml 文件

 
我們將在該項目里面演示@SentinelResource 的功能。
 
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
</dependencies>

 

 
我們修改項目的打包方式,以后我們可以使用 jar 來發布項目。
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build> 

 

 

2.3.3 完整的 pom.xml 文件

 
<?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">
<parent>
<artifactId>sentinel-examples</artifactId>
<groupId>com.bjsxt</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>sentinel-consumer</artifactId>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

 

 
 

三、完善 sentinel-provider

我們在 provider 里面添加一個模擬數據接口。
 
3.1 添加一個數據接口
 

 

 

代碼如下:
@RestController
public class GoodsController {
@GetMapping("/goods/buy/{name}/{count}")
public ResponseEntity<String> buy(
@PathVariable("name") String name,
@PathVariable("count") Integer count) {
return ResponseEntity.ok(String.format("購買%d 份%s", count, name));
}
}

 

 

3.2 添加配置文件

server:
    port: 8081
spring:
    application:
        name: sentinel-provider
cloud:
    nacos:
        discovery:
            server-addr: localhost:8848    

 

 

 

僅僅是為了讓服務能注冊到注冊中心而已。
 
 

3.3 添加一個啟動類 

 

 

代碼如下:
 
@SpringBootApplication
@EnableDiscoveryClient
public class SentinelProviderApplication {
public static void main(String[] args) {
SpringApplication.run(SentinelProviderApplication.class ,args) ;
}
}

 

 

3.4 啟動測試

 
在啟動之前,我們必須保證 Nacos 已經啟動成功。

 

 

測試接口:
 
http://localhost:8081/goods/buy/huawei/1

 

 

四、完善 sentinel-consumer

 
4.1 調用服務提供者 

 

 

代碼如下:
 
@RestController
public class BuyController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("buy/{name}/{count}")
@SentinelResource(value = "buy", fallback = "buyFallback", blockHandler =
"buyBlock")
public ResponseEntity<String> buy(@PathVariable String name, @PathVariable
Integer count) {
if (count >= 20) {
throw new IllegalArgumentException("購買數量過多");
}
if ("miband".equalsIgnoreCase(name)) {
throw new NullPointerException("已售罄");
}
Map<String, Object> params = new HashMap<>(2);
params.put("name", name);
params.put("count", count);
return ResponseEntity.ok(
this.restTemplate.getForEntity("http://sentinel-provider/goods/buy/{name}/{
count}", String.class, params).getBody());
}
// 異常回退
public ResponseEntity<String> buyFallback(@PathVariable String name,
@PathVariable Integer count, Throwable throwable) {
return ResponseEntity.ok(
String.format("【進入 fallback 方法】購買%d 份%s 失敗,%s", count,
name, throwable.getMessage()));
}
// sentinel 回退
public ResponseEntity<String> buyBlock(@PathVariable String name,
@PathVariable Integer count, BlockException e) {
return ResponseEntity.ok(String.format("【進入 blockHandler 方法】購買%d
份%s 失敗,當前購買人數過多,請稍后再試", count, name));
}
}

 

 
4.2 添加配置文件

 

 

內容如下:
server :
  port : 8083
spring :
  application :
     name : sentinel-consumer
cloud :
  nacos :
  discovery :
  server-addr : localhost:8848
sentinel :
  transport :
  dashboard : localhost:8080
  port : 8719
 
 
4.3 添加啟動類
@SpringBootApplication
@EnableDiscoveryClient
public class SentinelConsumerApplication {
public static void main ( String [] args ) {
SpringApplication . run ( SentinelConsumerApplication . class ,args ) ;
}
@LoadBalanced
@Bean
public RestTemplate restTemplate (){
return new RestTemplate () ;
}
}
4.4 啟動測試
4.4.1 啟動軟件
在啟動之前,必須保證這些軟件已經啟動:
Nacos-Server
Sentinel-Dashboard
Sentinel-provider
准備就緒后,啟動 sentine-consumer
 

 

 4.4.2 添加流控的規則

 

 

 

 

 當訪問該資源,QPS 超過 2 時,拋出異常。

測試:
http://192.168.1.11:8083/buy/huawei/1

 

 

五、其他用法
5.1 說明
在當前類中編寫回退方法會使得代碼變得冗余耦合度高,我們可以將回退方法抽取出來到一
個指定類中.
 
5.2 新增 BuyBlockHandler 
 

 

 代碼如下:

public class BuyBlockHandler {
// sentinel 回退
public static String buyBlock ( @PathVariable String name, @PathVariable
Integer count, BlockException e ) {
return String . format ( " 【進入 blockHandler 方法】購買 %d %s 失敗,當前購買
人數過多,請稍后再試 " , count, name ) ;
}
}
 
5.3 新增 BuyFallBack
 

 

 代碼如下:

public class BuyBlockHandler {
// sentinel 回退
public static ResponseEntity < String > buyBlock ( @PathVariable String name,
@PathVariable Integer count, BlockException e ) {
return ResponseEntity . ok ( String . format ( " 【進入 blockHandler 方法】購買 %d
%s 失敗,當前購買人數過多,請稍后再試 " , count, name )) ;
}
}
 
5.4 改造 BuyController
 
@RestController
public class BuyController {
@Autowired
private RestTemplate restTemplate ;
@GetMapping ( "buy/{name}/{count}" )
@SentinelResource (
value = "buy" ,
fallback = "buyFallback" ,
fallbackClass = BuyFallBack . class ,
blockHandler = "buyBlock" ,
blockHandlerClass = BuyBlockHandler . class ,
)
public ResponseEntity < String > buy ( @PathVariable String name, @PathVariable
Integer count ) {
if ( count >= 20 ) {
throw new IllegalArgumentException ( " 購買數量過多 " ) ;
}
if ( "miband" .equalsIgnoreCase ( name )) {
throw new NullPointerException ( " 已售罄 " ) ;
}
Map < String , Object > params = new HashMap <>( 2 ) ;
params .put ( "name" , name ) ;
params .put ( "count" , count ) ;
return ResponseEntity . ok (
this . restTemplate .getForEntity ( "http://sentinel-provider/goods/buy/{name}/{
count}" , String . class , params ) .getBody ())
}
 
 
 
 
 
 
 

第五章 Spring Cloud Alibaba Seata

 

一、Seata 簡介

 
  Seata 是一款開源的 分布式事務解決方案,致力於在微服務架構下提供高性能和簡單易
 
用的分布式事務服務。在 Seata 開源之前,Seata 對應的內部版本在阿里經濟體內部一直扮
 
演着分布式一致性中間件的角色,幫助經濟體平穩的度過歷年的雙 11,對各 BU 業務進行了
 
有力的支撐。經過多年沉淀與積累,商業化產品先后在阿里雲、金融雲進行售賣。2019.1 為
 
了打造更加完善的技術生態和普惠技術成果,Seata 正式宣布對外開源,未來 Seata 將以社
 
區共建的形式幫助其技術更加可靠與完備。
 

二、Seata-Server 的安裝

 
在使用 Seata 之前,我們首先要安裝 Seata-Server 服務器。

2.1 下載 Seata

由於我們使用的 spring cloud alibaba 版本為 2.2.0.RELEASE,他里面控制了 seata 的版本為
1.0.0,故我們在此下載 1.0.0 版本的 seata。
訪問:
https://github.com/seata/seata/releases/tag/v1.0.0

 

 

由於我使用的是 windows 的電腦,故選擇 seata-server-1.0.0.zip 該版本。
點擊該文件下載。
當下載速度較慢時,大家可以從今天的軟件目錄里面去下載。
 

2.2 Seata-Server 目錄分析

 

 

 

Bin:可執行文件目錄
Conf:配置文件目錄lib:依賴的 jar
LICENSE:授權文件
 

2.3 Seata 啟動

 
進入{seata}/bin 目錄里面,雙擊:

 

 

代表 seata-server 已經啟動成功。
 

三、框架的搭建

 

 

 

在本示例中,我們模擬了一個用戶購買貨物的場景:
 StorageService 負責扣減庫存數量;
 OrderService 負責保存訂單;
 AccountService 負責扣減用戶賬戶余額;
 Business 負責用戶下單的整個流程處理;
 

3.1 搭建 seata-examples 項目

 
seata-examples 用來控制所有項目的依賴版本號,以及去除公共的依賴 seata。
 

3.1.1 使用 IDEA 創建 Maven 項目

選擇 Maven 項目:

 

 

點擊 Next,添加以下的信息:

 

 

Parent:選擇 spring-cloud-alibaba-examples
Name:seata-examples
其他的信息保持默認即可。然后點擊 Finish 即可完成創建。
 

3.1.2 添加依賴

 
打開項目的 pom.xml 文件,添加以下的依賴。
<dependencies>
<!--
服務注冊-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-nacos-discovery</artifactId>
</dependency>
<!--seata-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
<!--web 項目的基礎依賴-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId></dependency>
</dependencies>

 

 

3.1.3 完整的 pom.xml

<?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">
<parent>
<artifactId>spring-cloud-alibaba-examples</artifactId>
<groupId>com.bjsxt</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>seata-examples</artifactId>
<dependencies>
<!--
服務注冊-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-nacos-discovery</artifactId>
</dependency>
<!--
seata-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
<!--
web 項目的基礎依賴-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>

 

 
最后,我們的項目的依賴關系如下:可以看見,我們的 seata 版本為 1.0.0 。

 

 

3.2 搭建 account-service 項目

 
Account-service 項目將負責扣減用戶賬戶余額
3.2.1 使用 IDEA 創建一個 Maven 項目

 

 

選擇 Maven 項目:

 

 

點擊 Next 后,填寫以下的信息:
 

 

 

Parent:seata-examples
Name:account-service
其他的值保持默認即可。
 

3.2.2 添加依賴

 
我們需要使用 ORM 框架來完成對數據庫的操作。在次我們需要使用 Mybatis-Plus 來操作數據庫。
打開 pom.xml ,在里面添加如下內容:
<dependencies>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.0</version>
</dependency>
<!--MySQL 依賴 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>

 

為了以后我們打包發布我們的項目,在此我們添加 boot-maven 的打包插件:
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

 

為了以后我們打包發布我們的項目,在此我們添加 boot-maven 的打包插件:
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

 

3.2.3 完整的 pom.xml 文件如下
<?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">
<parent>
<artifactId>seata-examples</artifactId>
<groupId>com.bjsxt</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>account-service</artifactId>
<dependencies>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>2.3</version>
</dependency>
<!--MySQL 依賴 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

 

項目的依賴關系如下:
 

3.3 搭建 business-service 項目

 
在 business 將主要完成下單邏輯,包含庫存的扣減,訂單的創建。
 

3.3.1 使用 IDEA 創建一個 Maven 項目

 
選擇 Maven 項目:
點擊 Next 后,填寫以下的信息:Parent:seata-examples
Name:business-service
其他的值保持默認即可。
 

3.3.2 添加依賴

 

為了以后我們打包發布我們的項目,在此我們添加 boot-maven 的打包插件:
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

 

3.2.3 完整的 pom.xml 文件如下
<?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">
<parent>
<artifactId>seata-examples</artifactId><groupId>com.bjsxt</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>account-service</artifactId>
<dependencies>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>2.3</version>
</dependency>
<!--MySQL 依賴 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

 

項目的依賴關系如下:

 

 

3.3 搭建 business-service 項目

 
在 business 將主要完成下單邏輯,包含庫存的扣減,訂單的創建。
 

3.3.1 使用 IDEA 創建一個 Maven 項目

 

 

選擇 Maven 項目:

 

 

點擊 Next 后,填寫以下的信息:

 

 

Parent:seata-examples
Name:business-service
其他的值保持默認即可。
 

3.3.2 添加依賴

為了以后我們打包發布我們的項目,在此我們添加 boot-maven 的打包插件:
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

 

3.2.3 完整的 pom.xml 文件如下
<?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">
<parent>
<artifactId>seata-examples</artifactId><groupId>com.bjsxt</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>account-service</artifactId>
<dependencies>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>2.3</version>
</dependency>
<!--MySQL 依賴 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

 

項目的依賴關系如下:

 

 

3.4 搭建 order-service 項目

 
order-service 項目將負責保存用戶訂單。
 

3.4.1 使用 IDEA 創建一個 Maven 項目

 

 

選擇 Maven 項目:

 

 

點擊 Next 后,填寫以下的信息:Parent:seata-examples
Name:order-service
其他的值保持默認即可。
 

3.4.2 添加依賴

 
我們需要使用 ORM 框架來完成對數據庫的操作。在次我們需要使用 Mybatis-Plus 來操作數
據庫。
打開 pom.xml ,在里面添加如下內容:
<dependencies>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.0</version>
</dependency>
<!--MySQL 依賴 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>

 

 
為了以后我們打包發布我們的項目,在此我們添加 boot-maven 的打包插件:
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

 

 
3.4.3 完整的 pom.xml 文件如下
<?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">
<parent>
<artifactId>seata-examples</artifactId>
<groupId>com.bjsxt</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>order-service</artifactId>
<dependencies>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>2.3</version>
</dependency>
<!--MySQL 依賴 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

 

項目的依賴關系如下:

 

 

3.5 搭建 storage-service 項目

 
storage-service 將負責扣除商品的庫存。
 

3.5.1 使用 IDEA 創建一個 Maven 項目

 

 

 
點擊 Next 后,填寫以下的信息:

 

 

Parent:seata-examples
Name:storage-service
 
其他的值保持默認即可。
 

3.5.2 添加依賴

 
我們需要使用 ORM 框架來完成對數據庫的操作。在次我們需要使用 Mybatis-Plus 來操作數
據庫。
打開 pom.xml ,在里面添加如下內容:
<dependencies>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.0</version>
</dependency>
<!--MySQL 依賴 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency></dependencies>
為了以后我們打包發布我們的項目,在此我們添加 boot-maven 的打包插件:
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

 

3.5.3 完整的 pom.xml 文件如下

<?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">
<parent>
<artifactId>seata-examples</artifactId>
<groupId>com.bjsxt</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>order-service</artifactId>
<dependencies>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>2.3</version>
</dependency>
<!--MySQL 依賴 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies><build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

 

 
項目的依賴關系如下:
 

3.6 完整的項目的

 

 

案例為四、代碼的完善
 

4.1 數據庫表導入

 
在測試分布式事務之前,我們需要先設計數據庫,以及准備測試數據。
新建數據庫,命名為:seata

 

 

導入 Sql:
Sql 文件在今天的軟件文件夾里面:
導入該 sql:點擊開始,進行導入。
成功后,發現成功的條數為:
表有如下:account :用戶的賬號表
Order:訂單表;
Stoage:商品的庫存表;
undo_log:回滾事務表,SEATA AT 模式需要 UNDO_LOG 表。
4.2 模型對象和 Mapper 對象生成
使用 IDEA 連接數據庫:

 

 

成功后,如圖所示:

 

 

提示:
若大家沒有安裝 mybatis 的代碼生成插件,可以在今天的軟件文件夾里面下載安裝。
 Account_tbl:

 

 

 Order_tbl:

 

 

 Storage_tbl:
代碼生成完畢后:
4.3 storage-service 代碼的完善
4.3.1 接口設計
在 storage-service 里面,主要完成對庫存的扣減。
新建一個接口:命名為:StorageService,代碼如下:
代碼如下:
public interface StorageService {
/**
* 扣減商品的庫存
* @param commodityCode
* 商品的編碼
* @param count
* 扣減商品的數量
*/
void deduct(String commodityCode, int count);
}
 

4.3.2 實現該接口

 

 

名稱為:impl.StorageService,代碼的實現如下:
 
@Service
public class StorageServiceImpl implements StorageService {
private static Logger logger = LoggerFactory.getLogger(StorageServiceImpl.class);
@Autowired
private StorageTblMapper storageTblMapper;
@Override
public void deduct(String commodityCode, int count) {
logger.info("開始扣減庫存,商品編碼:{},數量:{}", commodityCode, count);
StorageTbl storageTbl = storageTblMapper.selectOne(
new LambdaQueryWrapper<StorageTbl>()
.eq(StorageTbl::getCommodityCode, commodityCode));
int idleCount = storageTbl.getCount() - count;
if (idleCount < 0) {
throw new RuntimeException("庫存不足");
}
storageTbl.setCount(idleCount);
storageTblMapper.updateById(storageTbl);
logger.info("庫存扣減成功,商品編碼:{},剩余數量:{}", commodityCode, idleCount);
}
}

 

 
4.3.3 使用 Restful 暴露此接口

 

 

添加一個 Controller
代碼如下:
@RestController
public class StorageController {
private static Logger logger = LoggerFactory.getLogger(StorageController.class) ;
@Autowired
private StorageService storageService ;
/**
* 扣減商品的庫存
* @param commodityCode 商品的編碼
* @param count 商品的數量
* @return
*/
@GetMapping("/deduct/{commodityCode}/{count}")
public ResponseEntity<Void> deduct(
@PathVariable("commodityCode") String commodityCode,
@PathVariable("count") Integer count){
logger.info("Account Service ... xid: " + RootContext.getXID());
// 開始扣減庫存
storageService.deduct(commodityCode , count);
return ResponseEntity.ok().build() ;
}
}

 

4.3.4 添加配置文件

在 resource 目錄里面新建配置文件:

 

 

內容如下:
server:
    port: 18084
spring:
    application:
        name: storage-service
    cloud:
        alibaba:
            seata:
                tx-service-group: storage-service
            nacos:
                discovery:
                    server-addr: localhost:8848
    datasource:
        name: storageDataSource
        type: com.alibaba.druid.pool.DruidDataSource
        username: root
        password: 123456
        driver-class-name: com.mysql.cj.jdbc.Driver
        url:jdbc:mysql://localhost:3306/seata?useSSL=false&serverTimezone=UTC
        druid:
            max-active: 20
            min-idle: 2
            initial-size: 2
seata:
    service:
        vgroup-mapping:
            account-service: default
    grouplist:
        default: 127.0.0.1:8091
    disable-global-transaction: false
    enabled: true
mybatis-plus:
    mapper-locations: classpath:/mapper/*.xml    

 

 

4.3.5 添加啟動類

 

 

@SpringBootApplication
@EnableDiscoveryClient
@MapperScan("com.bjsxt.mapper")
public class StorageServiceApplication {
public static void main(String[] args) {
SpringApplication.run(StorageServiceApplication.class ,args) ;
}
}

 

4.3.6 啟動項目測試
啟動項目后,打印該日志,說明連接 seata-server 成功。4.4 account-service 代碼的完善
4.4.1 接口設計
在 account-service 里面,主要完成對用戶余扣減。
新建一個接口:
命名為:AccountService,代碼如下:
代碼如下:
 
public interface AccountService {
/**
* 從用戶的賬號扣減金額
* @param userId
* 用戶的 Id
* @param money
* 金額
*/
void debit(String userId, int money);
}
 

4.4.2 實現該接口

 
名稱為:impl.StorageService,代碼的實現如下:
 
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountTblMapper accountTblMapper;
private static Logger logger = LoggerFactory.getLogger(AccountServiceImpl.class);
@Override
public void debit(String userId, int money) {
logger.info("准備扣減用戶:{} 余額,扣減的數目為:{}", userId, money);
AccountTbl accountTbl = accountTblMapper.selectOne(
new LambdaQueryWrapper<AccountTbl>()
.eq(AccountTbl::getUserId, userId));
int idleMoney = accountTbl.getMoney() - money;
if (idleMoney < 0) {
throw new RuntimeException("用戶余額不足");
}
accountTbl.setMoney(idleMoney);
accountTblMapper.updateById(accountTbl);
logger.info("扣減用戶{}金額成功,剩余金額為{}", userId, money);
}
}

 

 

4.4.3 使用 Restful 暴露此接口

 
添加一個 Controller
名稱為:
代碼如下:
@RestController
public class AccountController {
@Autowired
private AccountService accountService ;
private static Logger logger =
LoggerFactory.getLogger(AccountController.class) ;
@GetMapping("/debit/{userId}/{money}")
public ResponseEntity<Void> debit(
@PathVariable("userId") String userId,
@PathVariable("money") Integer money){
logger.info("Account Service ... xid: " + RootContext.getXID());
// 開始扣減余額
accountService.debit(userId , money);
return ResponseEntity.ok().build() ;
}
}

 

4.4.4 添加配置文件

 
在 resource 目錄里面新建配置文件:
內容如下:
server:
    port: 18085
spring:
    application:
        name: account-service
    cloud:
        alibaba:
            seata:
                tx-service-group: account-service
        nacos:
            discovery:
                server-addr: localhost:8848
datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/seata?useSSL=false&serverTimezone=UTC
    druid:
        max-active: 20
        min-idle: 2
        initial-size: 2
seata:
    service:
        vgroup-mapping:
            account-service: default
        grouplist:
            default: 127.0.0.1:8091
            disable-global-transaction: false
        enabled: true
mybatis-plus:
mapper-locations: classpath:/mapper/*.xml

 

 
 

4.4.5 添加啟動類

命名為 AccountServiceApplication ,代碼如下:
 
@SpringBootApplication
@EnableDiscoveryClient
@MapperScan("com.bjsxt.mapper")
public class AccoutServiceApplication {
public static void main(String[] args) {
SpringApplication.run(AccoutServiceApplication.class ,args) ;
}
}

 

 

4.4.6 啟動項目測試

啟動項目后,打印該日志,說明連接 seata-server 成功。4.5 order-service 代碼的完善

4.5.1 接口設計

在 order-service 里面,主要完成保存用戶訂單的操作。
新建一個接口:
命名為:OrderService,代碼如下:
代碼如下:
public interface OrderService {
/**
* 創建一個訂單
* @param userId 用戶 id
* @param commodityCode 商品的編號
* @param orderCount 商品的數量
* @return OrderTbl
*/OrderTbl create(String userId, String commodityCode, int orderCount) ;
}

 

 
 

4.5.4 Ribbon 集成

 

 

創建一個配置類:
代碼如下:
@Configuration
public class HttpUtilConfig {
@LoadBalanced
@Bean
public RestTemplate restTemplate(){
return new RestTemplate() ;
}
}

 

 

4.5.2 實現該接口

 

 

名稱為:impl.OrderService,代碼的實現如下:
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderTblMapper orderTblMapper;
@Autowired
private AccountService accountService;
private static Logger logger =
LoggerFactory.getLogger(OrderServiceImpl.class);
@Override
public OrderTbl create(String userId, String commodityCode, int orderCount)
{
logger.info("准備為{}創建一個訂單,商品編號為{},數量為{}", userId,
commodityCode, orderCount);
// 1 計算總金額
int orderMoney = calculate(commodityCode, orderCount);
accountService.debit(userId, orderMoney);
OrderTbl order = new OrderTbl();
order.setUserId(userId);
order.setCommodityCode(commodityCode);
order.setCount(orderCount);order.setMoney(orderMoney);
orderTblMapper.insert(order);
// INSERT INTO orders ...
return order;
}
private int calculate(String commodityCode, int orderCount) {
// 我們現在沒有商品的表,在此我們把商品的價格定死
int prodPrice = 0 ;
if("HUAWEI_0001".equals(commodityCode)){ // 華為時 100
prodPrice = 100;
}else if ("XIAOMI_002".equals(commodityCode)){ // 小米時 200
prodPrice = 200 ;
}else {
prodPrice = 1000 ; // 其他為 1000
}
return orderCount * prodPrice ;
}
}

 

 

4.5.3 遠程調用 account-service 的實現

 

 

創建一個 AccountService 的類,該類里面主要完成對 accout-servic 的遠程調用。
名稱為:

 

 

/**
* 實現對賬號服務的遠程調用
*/
@Service
public class AccountService {
private static Logger logger = LoggerFactory.getLogger(AccountService.class) ;
/**
* 1 ribbon 的方式
*/
@Autowired
private RestTemplate restTemplate ;
/**
* 2 feign 的方式
*/
public void debit(String userId, int orderMoney) {
ResponseEntity<Void> entity = restTemplate.
getForEntity(
"http://accout-service/debit/{userId}/{orderMoney}",
Void.class,
userId,
orderMoney
);
if(entity.getStatusCode()== HttpStatus.OK){
logger.info("扣減用戶{}金額成功,本次扣減的數目為{}",userId,orderMoney);
return ;
}
logger.info("扣減用戶{}金額失敗",userId);
throw new RuntimeException("扣減金額失敗") ;
}
}

 

 
 
我們在此使用的時 Ribbon 做遠程調用,下面的章節我們也會測試 Feign 。

4.5.5 使用 Restful 暴露此接口

 

 

代碼如下:
@RestController
public class OrderController {
private static Logger logger = LoggerFactory.getLogger(OrderController.class) ;
@Autowired
private OrderService orderService ;
/**
* 創建訂單
* @param userId
* 用戶 Id
* @param commodityCode
* 商品的編號* @param orderCount
* 商品的數量
* @return
*/
@GetMapping("/create/{userId}/{commodityCode}/{orderCount}")
public ResponseEntity<Void> create(
@PathVariable("userId") String userId,
@PathVariable("commodityCode") String commodityCode,
@PathVariable("orderCount") int orderCount){
logger.info("Order Service ... xid: " + RootContext.getXID());
orderService.create(userId, commodityCode, orderCount) ;
return ResponseEntity.ok().build() ;
}
}

 

 

4.5.6 添加配置文件

在 resource 目錄里面新建配置文件:

 

 

命名為:application.yml
內容如下:
server:
    port: 18086
spring:
    application:
        name: order-service
    cloud:
        alibaba:
            seata:
                tx-service-group: order-service
        nacos:
            discovery:
                server-addr: localhost:8848
    datasource:
        name: orderDataSource
        type: com.alibaba.druid.pool.DruidDataSource
        username: root
        password: 123456
        driver-class-name: com.mysql.cj.jdbc.Driver
        url:
jdbc:mysql://localhost:3306/seata?useSSL=false&serverTimezone=UTC
    druid:
        max-active: 20
        min-idle: 2
        initial-size: 2
seata:
    service:
        vgroup-mapping:
            order-service: default
        grouplist:
            default: 127.0.0.1:8091
        disable-global-transaction: false
    enabled: true
mybatis-plus:
mapper-locations: classpath:/mapper/*.xml    

 

 
4.5.7 添加啟動類

 

 

命名為:OrderServiceApplication
代碼如下:
@SpringBootApplication@EnableDiscoveryClient
@MapperScan("com.bjsxt.mapper")
public class OrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class ,args) ;
}
}

 

4.58 啟動項目測試

 

 

啟動項目后,打印該日志,說明連接 seata-server 成功。
 

4.6 business-service 代碼的完善

 

4.6.1 接口設計

在 business-service 里面,主要完成下單的邏輯,包含 2 個主要的步驟,就是對庫存服務和
訂單服務的遠程調用。
新建一個接口:

 

 

命名為:com.bjsxt.service.BusinessService
代碼如下:
 
public interface BusinessService {
/**
* 采購/下單的過程
* @param userId
* 用戶的 Id
* @param commodityCode
* 商品的編碼
* @param orderCount
* 商品的數量
*/
void purchase(String userId, String commodityCode, int orderCount) ;
}

 

 

4.6.2 實現該接口

 
名稱為:impl.BusinessServiceImpl,代碼的實現如下:
@Service
public class BusinessServiceImpl implements BusinessService {
private static Logger logger = LoggerFactory.getLogger(BusinessServiceImpl.class) ;
@Autowired
private StorageService storageService;
@Autowired
private OrderService orderService;
@Override
public void purchase(String userId, String commodityCode, int orderCount) {
logger.info("准備下單,用戶:{},商品:{},數量:
{}",userId,commodityCode,orderCount);
storageService.deduct(commodityCode, orderCount); orderService.create(userId, commodityCode, orderCount) ;
logger.info("下單完成");
}
}

 

 

4.6.3 遠程調用 storage-service 的實現

 

 

創建一個 StorageService 的類,該類里面主要完成對 storage-servic 的遠程調用。名稱為:
@Service
public class StorageService {
private static Logger logger = LoggerFactory.getLogger(StorageService.class) ;
/**
* 1 采用 Ribbon 的形式
*/
@Autowired
private RestTemplate restTemplate ;
/**
* 2 采用 Feign 的形式
*/
public void deduct(String commodityCode, int orderCount) {
ResponseEntity<Void> entity = restTemplate.
getForEntity(
"http://storage-service/debut/{commodityCode}/{orderCount}",
Void.class,
commodityCode,
orderCount
);
if (entity.getStatusCode()== HttpStatus.OK){
logger.info("扣減庫存成功,商品編號為{},本次扣減的數量為{}",commodityCode,orderCount);
return;
}
throw new RuntimeException("扣減庫存失敗") ;}
}

 

 
 
我們在此使用的時 Ribbon 做遠程調用,下面的章節我們也會測試 Feign 。
 

4.6.4 遠程調用 order-service 的實現

 

 

新建一個類:
 
代碼如下:
 
@Service
public class OrderService {
private static Logger logger = LoggerFactory.getLogger(StorageService.class) ;
/**
* 1 采用 Ribbon 的形式
*/
@Autowired
private RestTemplate restTemplate ;
/**
* 2 采用 Feign 的形式
*/
public void create(String userId, String commodityCode, int orderCount) {
ResponseEntity<Void> entity = restTemplate.
getForEntity(
"http://order-service/create/{userId}/{commodityCode}/{orderCount}",
Void.class,
userId ,
commodityCode,
orderCount
);
if (entity.getStatusCode()== HttpStatus.OK){
logger.info("訂單創建成功,用戶為{} ,商品編號為{},本次扣減的數量為{}",userId ,
commodityCode,orderCount);
return;}
throw new RuntimeException("訂單創建失敗") ;
}
}

 

 

4.6.5 集成 Ribbon

 
添加一個 HttpUtilConfig 的配置類:

 

 

代碼如下:
 
@Configuration
public class HttpUtilConfig {
@LoadBalanced
@Bean
public RestTemplate restTemplate(){
return new RestTemplate() ;
}
}

 

 

4.6.6 添加配置文件

 
在 resource 目錄里面新建配置文件:

 

 

命名為:application.yml
內容如下:
server:
port: 18087
    spring:
        application:
            name: business-service
        cloud:
            alibaba:
                seata:
                    tx-service-group: business-service
            nacos:
                discovery:
                server-addr: localhost:8848
seata:
    service:
        vgroup-mapping:
            business-service: default
        grouplist:
            default: 127.0.0.1:8091
        disable-global-transaction: false
    enabled: true    

 

 

4.6.7 添加啟動類

 
命名為:BusinessServiceApplication
代碼如下:
 
@SpringBootApplication
@EnableDiscoveryClient
public class BusinessServiceApplication {
public static void main(String[] args) {
SpringApplication.run(BusinessServiceApplication.class ,args) ;
}
}

 

 
4.6.8 暴露下單接口
繼續改造啟動類:
@SpringBootApplication
@EnableDiscoveryClient
@RestController
public class BusinessServiceApplication {
@Autowired
private BusinessService businessService ;
public static void main(String[] args) {
SpringApplication.run(BusinessServiceApplication.class ,args) ;
}
/*** 開始下單
* @param userId
* 用戶的 Id
* @param commodityCode
* 商品的編號
* @param orderCount
* 商品的數量
* @return
*/
@GetMapping("/purchase/{userId}/{commodityCode}/{orderCount}")
public ResponseEntity<Void> purchase(
@PathVariable("userId") String userId,
@PathVariable("commodityCode")String commodityCode,
@PathVariable("orderCount")Integer orderCount){
businessService.purchase(userId,commodityCode,orderCount);
return ResponseEntity.ok().build() ;
}
}

 

4.6.9 啟動項目測試

 
啟動項目后,打印該日志,說明連接 seata-server 成功。
 

4.7 總體的調用流程如下

 
都啟動完成后:

 

 

Nacos-Server:

 

 

 

 

4.8 正常下單測試

 
在瀏覽器里面訪問:
 
http://localhost:18087/purchase/SXT_USER_1/HUAWEI_0001/1
 
代表 SXT_USER_1 購買 HUAWEI_0001 產品 1 件。
 
數據庫里面:
  •  Accout_tbl 里面,SXT_USER_1 用戶的金額減少 100; Storage_tbl 里面,HUAWEI_0001 的庫存減少了 1;
  •  Order_Tbl 里面,創建了一條訂單記錄;
說明,此時遠程調用時正常的。
 

 

 

4.9 分布式事務的演示

我們演示如圖的異常:
 

 

 

我們可以發現,遠程調用共有 3 處。
 

4.9.1 在 accout-service 服務扣減余額觸發異常

 

 

 

4.9.2 重啟 accout-service

 

4.9.3 還原數據庫里面的數據

 
Account_Tbl:Storage_Tbl:

 

 

4.9.4 重新下單測試
 
http://localhost:18087/purchase/SXT_USER_1/HUAWEI_0001/1

 

 

數據庫的數據:

 

 

Account_Tbl:Storage_Tbl:

 

 

我們發現,分布式事務產生了,accout-service 內部的異常,導致 accout_tbl 表數據回滾了。
但是,在 storage_tbl :位於 stoage-service 的事務卻沒有回滾。
 

4.10 使用 Seata 解決分布式問題

 

4.10.1 改造 accout-service 里面的 AccountServiceImpl

 

 

當用戶的 ID 為:SXT_USER_2 時,我們拋出異常,當為其他用戶時,我們正常的下單。
 

4.10.2 改造 BusinessServiceImpl

 

 

 

添加一個注解,看他是否能解決分布式事務的問題
4.10.3 重啟測試
重啟 accout-service,business-service 測試
使用 SXT_USER_1 正常的下單測試:

 

 

Stoage_tbl:庫存正常
Accout_Tbl:余額正常
使用 SXT_USER_2 下單測試:

 

 

 

 

發現發生異常后,
stoage_tbl 里面的沒有發生改變,數據正常
Accout_tbl 里面的數據也沒有發生改變,數據正常
分布式事務測試成功了
 

五、集成 Feign 測試 Seata

 
在上面的章節中,我們使用的時 Ribbon + RestTemplate 的形式做的遠程調用。下面我們來演
示 Feign 的調用方式。
 

5.1 改造 business-service

5.1.1 添加依賴

修改 business-service 項目里面的 pom.xml 文件,在里面添加依賴。
 
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId></dependency>
</dependencies>

 

 
5.1.2 添加 OrderServiceFeign

 

 

里面的代碼如下:
 
@FeignClient("order-service")
public interface OrderServiceFeign {
@GetMapping("/create/{userId}/{commodityCode}/{orderCount}")
ResponseEntity<Void> create(
@PathVariable("userId") String userId,
@PathVariable("commodityCode") String commodityCode,
@PathVariable("orderCount") Integer orderCount);
}

 

5.1.3 添加 StorageServiceFeign

 

 

@FeignClient("storage-service")
public interface StorageServiceFeign {
@GetMapping("/deduct/{commodityCode}/{orderCount}")
ResponseEntity<Void> deduct(
@PathVariable("commodityCode") String commodityCode,
@PathVariable("orderCount") Integer orderCount
) ;
}

 

 
5.1.5 改造 OrderService

 

 

@Service
public class OrderService {
private static Logger logger = LoggerFactory.getLogger(StorageService.class) ;
/**
* 1 采用 Ribbon 的形式
*/
@Autowired
private RestTemplate restTemplate ;
@Autowired
private OrderServiceFeign orderServiceFeign ;
/**
* 2 采用 Feign 的形式
*/
public void create(String userId, String commodityCode, int orderCount){

// Ribbon // ResponseEntity<Void> entity = restTemplate. // getForEntity( // "http://order-service/create/{userId}/{commodityCode}/{orderCount}", // Void.class, // userId , // commodityCode, // orderCount // ); //Feign ResponseEntity<Void> entity = orderServiceFeign.create(userId, commodityCode, orderCount); if (entity.getStatusCode()== HttpStatus.OK){ logger.info("訂單創建成功,用戶為{} ,商品編號為{},本次扣減的數量為{}",userId , commodityCode,orderCount); return; } throw new RuntimeException("訂單創建失敗") ; } }

 

 

5.1.7 改造 StorageService

 

 

代碼如下:
 
@Service
public class StorageService {
private static Logger logger = LoggerFactory.getLogger(StorageService.class);
/*** 1 采用 Ribbon 的形式
*/
@Autowired
private RestTemplate restTemplate;
@Autowired
private StorageServiceFeign storageServiceFeign;
/**
* 2 采用 Feign 的形式
*/
public void deduct(String commodityCode, int orderCount) {
// Ribbon
//
ResponseEntity<Void> entity = restTemplate.
//
getForEntity(
//
"http://storage-service/deduct/{commodityCode}/{orderCount}",
//
Void.class,
//
commodityCode,
//
orderCount
//
);
//Feign
ResponseEntity<Void> entity = storageServiceFeign.deduct(commodityCode,
orderCount);
if (entity.getStatusCode() == HttpStatus.OK) {
logger.info("扣減庫存成功,商品編號為{},本次扣減的數量為{}", commodityCode,
orderCount);
return;
}
throw new RuntimeException("扣減庫存失敗");
}
}

 

 

5.1.6 在啟動類里面開啟對 Feign 的支持

 

 

5.2 改造 order-service

 

5.2.1 添加依賴

在 dependencies 添加:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
 

 

 
5.2.2 添加接口

 

 

里面的代碼如下:
 
@FeignClient("account-service")
public interface AccountServiceFeign {
@GetMapping("/debit/{userId}/{orderMoney}")
ResponseEntity<Void> debit(
@PathVariable("userId") String userId,
@PathVariable("orderMoney") Integer orderMoney
) ;
}

 

5.2.3 修改 AccoutService

 

 

/**
* 實現對賬號服務的遠程調用
*/
@Service
public class AccountService {
private static Logger logger = LoggerFactory.getLogger(AccountService.class) ;
/**
* 1 ribbon 的方式
*/
@Autowired
private RestTemplate restTemplate ;
@Autowired
private AccountServiceFeign accountServiceFeign ;
/**
* 2 feign 的方式*/
public void debit(String userId, int orderMoney) {
//Ribbon
//
ResponseEntity<Void> entity = restTemplate.
//
getForEntity(
//
"http://accout-service/debit/{userId}/{orderMoney}",
//
Void.class,
//
userId,
//
orderMoney
//
);
ResponseEntity<Void> entity = accountServiceFeign.debit(userId, orderMoney);
if(entity.getStatusCode()== HttpStatus.OK){
logger.info("扣減用戶{}金額成功,本次扣減的數目為{}",userId,orderMoney);
return ;
}
logger.info("扣減用戶{}金額失敗",userId);
throw new RuntimeException("扣減金額失敗") ;
}
}

 

 

5.2.4 在啟動類里面添加對 Feign 的支持

 

 

5.3 重啟測試

 
重啟 order-service ,business-service
還原數據庫數據,開始測試。
正常下單測試:

 

 

使用 SXT_USER_2 下單:

 

 

出錯了,但是數據庫的各個表都正常。
Seata 測試成功了。
 

第六章 Spring Cloud Alibaba Dubbo

 

一、項目簡介

 
Dubbo Spring Cloud 基於 Dubbo Spring Boot 2.7.1 和 Spring Cloud 2.x 開發,無論開發人
員是 Dubbo 用戶還是 Spring Cloud 用戶,都能輕松地駕馭,並以接近“零”成本的代價使應
用向上遷移。Dubbo Spring Cloud 致力於簡化 Cloud Native 開發成本,提高研發效能以及提
升應用性能等目的。
Dubbo Spring Cloud 首個 Preview Release,隨同 Spring Cloud Alibaba `0.2.2.RELEASE` 和
0.9.0.RELEASE 一同發布,分別對應 Spring Cloud Finchley 與 Greenwich(下文分別簡稱為 “F”
版 和 “G” 版)
 

二、功能的完成度

 
由於 Dubbo Spring Cloud 構建在原生的 Spring Cloud 之上,其服務治理方面的能力可
認為是 Spring Cloud Plus,不僅完全覆蓋 Spring Cloud 原生特性,而且提供更為穩定和成熟
的實現,特性比對如下表所示:

 

 

 

 

三、框架的搭建

 
我們將搭建如圖所示的項目框架

 

 

3.1 搭建 spring-cloud-dubbo-examples

 
spring-cloud-dubbo-exmaples 是一個父項目,用來給子項目控制版本和去除公共的依賴。
 

3.1.1 創建項目

 
使用 IDEA 創建一個模塊:

 

 

選擇 Maven:

 

 

點擊 Next,進行下一步操作:

 

 

Parent:必須選擇之前我們創建的 spring-cloud-alibaba-examples。
Name:spring-cloud-dubbo-examples 項目的名稱
點擊 Finish,完成項目的創建。

 

 

至此,spring-cloud-dubbo-examples 項目已經完成創建了。
 

3.1.2 添加依賴

 
打開該項目的 pom.xml,添加以下內容: 
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-dubbo</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies> 

 

 
 

3.1.3 修改項目的打包方式

 
<packaging>pom</packaging>
 

3.1.4 完整的 pom.xml 文件如下

 
<?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">
<parent>
<artifactId>spring-cloud-alibaba-examples</artifactId>
<groupId>com.bjsxt</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<packaging>pom</packaging>
<artifactId>spring-cloud-dubbo-examples</artifactId>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-dubbo</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
</project>

 

 
 

3.2 搭建 dubbo-api

dubbo-api 里面將存放用於發表服務的接口。
 
3.2.1 創建 dubbo-api 項目
使用 IDEA 創建一個子模塊。 

 

 

選擇 Maven 項目: 

 

 

點擊 Next 進行下一步操作:

 

 

Parent:選擇 spring-cloud-dubbo-examples
Name:名稱為 dubbo-api
點擊 Finish 完成項目的創建:

 

 

3.2.2 完整的 pom.xml 文件如下

 
dubbo-api 的 pom.xml 文件如下:
<?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">
<parent>
<artifactId>spring-cloud-dubbo-examples</artifactId>
<groupId>com.bjsxt</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>dubbo-api</artifactId>
</project> 

 

 
3.3 搭建 dubbo-provider
3.3.1 創建 dubbo-provider 項目
搭建 dubbo-provider 用來做一個服務的提供者。
使用 IDEA 創建一個模塊:

 

 

點擊 Next,進行下一步操作:

 

 

Parent:選擇 spring-cloud-alibaba-examples
Name:dubbo-provider
點擊 Finish,完成項目的創建。
 
3.3.2 修改 Maven 的打包方式
Maven 項目默認會被 target 目錄下的 class 文件打包在一個 jar 里面,該 jar 並不能直接運行,
我們需要修改它的打包方式為 spring-boot 的打包,這樣,打包后的項目將能直接被運行。
修改 pom.xml ,添加如下的內容: 
 
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

 

 
這樣,該項目將最終被打包成為一個 jar,能直接通過 java -jar 來運行
 

3.3.3 完整的 pom.xml 文件如下

 
<?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">
<parent>
<artifactId>spring-cloud-dubbo-examples</artifactId>
<groupId>com.bjsxt</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>dubbo-provider</artifactId>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project> 

 

 

3.4 搭建 dubbo-consumer

 

3.4.1 創建 dubbo-provider-consumer 項目

 
搭建 dubbo-provider 用來做一個服務的提供者。
使用 IDEA 創建一個模塊: 
 

 

 

選擇 Maven 項目:

 

 

點擊 Next,進行下一步操作:

 

 

Parent:選擇 spring-cloud-alibaba-examples
Name:dubbo-consumer
點擊 Finish,完成項目的創建。
 

3.4.2 修改 Maven 的打包方式

 
Maven 項目默認會被 target 目錄下的 class 文件打包在一個 jar 里面,該 jar 並不能直接運行,
我們需要修改它的打包方式為 spring-boot 的打包,這樣,打包后的項目將能直接被運行。
修改 pom.xml ,添加如下的內容:
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

 

這樣,該項目將最終被打包成為一個 jar,能直接通過 java -jar 來運行
 

3.4.3 完整的 pom.xml 文件如下

 
<?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">
<parent>
<artifactId>spring-cloud-dubbo-examples</artifactId>
<groupId>com.bjsxt</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>dubbo-consumer</artifactId>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

 

 
 
3.4.4 完整的項目結構

 

 

四、代碼的完善

 

4.1 dubbo-api 代碼的完善.

 

4.1.1 定義 Dubbo 服務接口

Dubbo 服務接口是服務提供方與消費方的遠程通訊契約,通常由普通的 Java 接口
(interface)來聲明。 

 

 

代碼如下:
 
public interface EchoService {
String echo(String message);
}
 

4.1.2 項目的打包

 
Api 項目主要是為了把 rpc 中定義的接口發布出去。
我們可以使用 Maven 的普通打包方式把編譯后的 class 文件打包為 jar。 

 

 

打包成功后,項目的 jar 位於:

 

 

4.2 dubbo-provider 代碼的完善
4.2.1 添加依賴
在 dubbo-provider 的 pom.xml 的 dependencies 添加以下的依賴
<dependencies>
<dependency>
<groupId>com.bjsxt</groupId>
<artifactId>dubbo-api</artifactId>
<version>1.0</version>
</dependency>
</dependencies>
 

4.2.2 實現 dubbo-api 里面定義的接口

 

 

代碼的內容如下:
@Service
public class EchoServiceImpl implements EchoService {
@Override
public String echo(String message) {
return "[echo] Hello, " + message;
}
 

4.2.3 添加配置文件 

 

 

內容如下:
 
dubbo:
    scan:
# dubbo 服務掃描基准包
        base-packages: com.bjsxt.service.impl
    cloud:
        subscribed-services: dubbo-provider
    protocol:
# dubbo 協議
        name: dubbo
# dubbo 協議端口( -1 表示自增端口,從 20880 開始)
        port: -1
    registry:
# 掛載到 Spring Cloud 注冊中心
        address: spring-cloud://localhost
spring:
    application:
# Dubbo 應用名稱
        name: dubbo-provider
    main:
# Spring Boot 2.1 需要設定
        allow-bean-definition-overriding: true
    cloud:
        nacos:
# Nacos 服務發現與注冊配置
            discovery:
                server-addr: localhost:8848 

 

 

4.2.4 啟動類

 

 

代碼如下:
 
@SpringBootApplication
@EnableDiscoveryClient
public class ProviderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderServiceApplication.class, args) ;
}
}

 

 

4.3 dubbo-consumer 代碼的完善

 

4.3.1 添加依賴

 
在 dubbo-consumer 的 pom.xml 的 dependencies 添加以下的依賴
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.bjsxt</groupId>
<artifactId>dubbo-api</artifactId>
<version>1.0</version>
</dependency>
<!-- Dubbo Spring Cloud Starter -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-dubbo</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>

 

 

4.3.2 添加配置文件 

 

 

內容如下:
 
dubbo:
    registry:
# 掛載到 Spring Cloud 注冊中心
        address: nacos://127.0.0.1:8848
    cloud:
        subscribed-services: dubbo-provider
server:
    port: 8080
spring:
    application:
# Dubbo 應用名稱
        name: dubbo-consumer
    main:
# Spring Boot 2.1 需要設定
        allow-bean-definition-overriding: true
    cloud:
        nacos:
# Nacos 服務發現與注冊配置
            discovery:
                server-addr: 127.0.0.1:8848 

 

 

4.3.3 啟動類 

 

 

代碼如下: 
 
@EnableDiscoveryClient
@SpringBootApplication
@RestController
public class ConsumerServiceApplication {
@Reference
private EchoService echoService ;
public static void main(String[] args) {
SpringApplication.run(ConsumerServiceApplication.class,args) ;
}
@GetMapping("/rpc")
public ResponseEntity<String> rpc(){
return ResponseEntity.ok(String.format("調用結果
為%s",echoService.echo("info")));
}
}

 

 

4.4 遠程調用測試

 啟動 Nacos-Server
 啟動 dubbo-provider
 啟動 dubbo-consumer
 
查看 Nacos 控制台:
http://localhost:8848/nacos/ 
 

 

 

瀏覽器訪問: 

 

 

調用已經成功;
 

五、負載均衡調用測試

 

5.1 啟動多個服務的提供者

修改服務提供者里面實現類的代碼:

 

 

 

 

 

 

 

 

 

5.2 使用消費者負載均衡調用測試

訪問:
http://localhost:8080/rpc
 

 

 

 

 


免責聲明!

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



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