Spring Cloud 快速入門(一)簡介、與Dubbo對比、建立基礎工程

FixBugIdou1995發表於2020-10-11

1. Spring Cloud 簡介

1.1 簡介

我們從多個角度看什麼是Spring Cloud:

1.1.1 官網簡介

在這裡插入圖片描述
開啟 Spring 官網 http://spring.io 首頁的中部,可以看到 Spring Cloud 的簡介。

【原文】Building distributed systems doesn’t need to be complex and error-prone(易錯). Spring Cloud offers a simple and accessible(易接受的) programming model to the most common distributed system patterns(模式), helping developers build resilient(有彈性的), reliable(可靠的), and coordinated(協調的) applications. Spring Cloud is built on top of Spring Boot, making it easy for developers to get started and become productive quickly.

【翻譯】構建分散式系統不需要複雜和容易出錯。Spring Cloud 為最常見的分散式系統模式提供了一種簡單且易於接受的程式設計模型,幫助開發人員構建有彈性的、可靠的、協調的應用程式。Spring Cloud 構建於 Spring Boot 之上,使得開發者很容易入手並快速應用於生產中。

SpringCloud架構圖:
在這裡插入圖片描述

1.1.2 百度百科

Spring Cloud 是一系列框架的有序集合。它利用 Spring Boot 的開發便利性巧妙地簡化了分散式系統基礎設施的開發,如服務發現註冊、配置中心、訊息匯流排、負載均衡、斷路器、資料監控等,都可以用 Spring Boot 的開發風格做到一鍵啟動和部署。Spring Cloud 並沒有重複製造輪子,它只是將目前各家公司開發的比較成熟、經得起實際考驗的服務框架組合起來,通過 Spring Boot 風格進行再封裝遮蔽掉了複雜的配置和實現原理,最終給開發者提供了一套簡單易懂、易部署和易維護的分散式系統開發工具包。

1.1.3 總結

Spring Cloud 是什麼?阿里高階框架師、Dubbo 專案的負責人劉軍說,Spring Cloud 是微服務系統架構的一站式解決方案。

Spring Cloud 與 Spring Boot 是什麼關係呢?Spring Boot 為 Spring Cloud 提供了程式碼實現環境,使用 Spring Boot將其它元件有機融合到了 Spring Cloud 的體系架構中了。所以說,Spring Cloud 是基於 Spring Boot 的、微服務系統架構的一站式解決方案

1.2 Spring Cloud 的國內使用情況

在這裡插入圖片描述

1.3 Spring Cloud 線上資源

1.4 Spring Cloud 版本

1.4.1 版本號來源

在這裡插入圖片描述

Spring Cloud 的版本號並不是我們通常見的數字版本號,而是一些很奇怪的單詞。這些單詞均為英國倫敦地鐵站的站名。同時根據字母表的順序來對應版本時間順序,比如:最早的 Release 版本 Angel(天使),第二個 Release 版本 Brixton(英國地名),然後是 Camden、Dalston、Edgware,目前使用較多的是 Finchley(英國地名)版本,而最新版本為 Hoxton(英國地名),後續的快速入門篇,我們使用的都是目前最新版本 Hoxton。

除了版本號外還有一些其他的版本標記:

  • SNAPSHOP:快照版,可以使用,但其仍處理連續不斷的開發改進中,不建議使用。
  • M:里程碑版。其也會標註上 PRE,preview,預覽版,內測版,不建議使用。
  • RC:Release Candidate,發行候選版,主要是用於修復 BUG,一般該版本中不會再新增大的功能修改了。正式發行前的版本。
  • SR:Service Release,服務發行版,正式發行版。一般還會被標註上 GA,General Available

1.4.2 Spring Cloud 與 Spring Boot 版本

某一版本的 Spring Cloud 要求必須要執行在某一特定 Spring Boot 版本下。它們的對應關係在 Spring Cloud 官網可以看到版本對應說明。
在這裡插入圖片描述
右邊表示的是SpringBoot最低版本

2. Spring Cloud 與 Dubbo之間對比

2.1 Spring Cloud 與 Dubbo對比

首先給了一個常見的分散式架構圖:
在這裡插入圖片描述

Dubbo 與 Spring Cloud 在不同節點上的技術實現、知識點對比:

路由閘道器

  • Dubbo
    反向代理Nginx:靜態代理、動靜分離、負載均衡、虛擬主機
  • Spring Cloud
    Spring Cloud Zuul:服務路由、請求過濾、令牌桶限流、多維請求限流、灰度釋出、Zuul的高可用、負載均衡、服務降級
    Gateway:暫時沒研究

消費者叢集

  • 服務容錯/熔斷、降級

    • Dubbo
      Dubbo自身實現:叢集容錯Cluster、Mock機制、宣告式快取
    • Spring Cloud
      Hystrix: 執行隔離、fallbackMethod降級、fallbackFactory降級、Dashboard儀表盤、服務降級報警機制
  • 服務路由

    • Dubbo
      Dubbo自身實現的:Router
    • Spring Cloud
      消費者好像沒有路由功能
  • 負載均衡

    • Dubbo
      Dubbo自身實現的:LoadBalance
    • Spring Cloud
      Ribbon:IRule

服務註冊中心

  • Dubbo
    Zookeeper(CP強一致性):Paxos演算法、ZAB演算法、Leader選舉、Znode、Wather、ZkClient、Curator
    Redis等其他中間間也可以做註冊中心
  • SpringCloud
    Eureka(AP高可用):服務發現Discovery、自我保護機制、Eureka叢集

提供者叢集

  • Dubbo
    預設用Zookeeper作為配置中心
  • SpringCloud
    Spring Cloud Config:配置檔案的自動更新、訊息匯流排系統Spring Cloud Bus

快取資料層

  • 記憶體資料庫Redis:Redis主從叢集、Sentinel叢集、快取穿透、快取雪崩、熱點快取、雙重檢測鎖機制

DBMS

  • MySQL主備叢集:讀寫分離叢集、MySQL- Proxy、MyCat分庫分表

2.2 Spring Cloud 與 Dubbo 技術選型

Spring Cloud 與 Dubbo 均為微服務框架,開發團隊在進行技術選型時,總會將它們進行對比,考慮應該選擇哪一個。可以從以下幾方面考慮:

架構完整度

  • Dubbo 僅提供了服務註冊與服務治理兩個模組。Spring Cloud實現的功能Dubbo也都能實現,只不過Dubbo整合第三方框架時,部分框架Dubbo已經提供了整合機制,沒有提供的整合的程式碼就需要自己實現。
  • 而SpringCloud就不需要自己實現,沒有相容性問題。(可以類比自己組裝桌上型電腦,自己組裝,一個是比較麻煩,另一個問題是硬體之間存在相容性問題,如果買品牌機,開發商已經幫你全裝好了,並且不會有相容問題都是經過測試的)

社群活躍度

  • SpringCloug相對來說活躍度更高,維護成本就低,但是是英文的

通訊協議

  • Dubbo 通訊使用的是 RPC,屬於傳輸層,通訊效率更高
  • Spring Cloud 是 HTTP REST,屬於應用層,比Dubbo通訊效率低,但是現在也不是問題了,因為有Spring Cloud gRPC(谷歌的)
  • 現在Dubbo想做成SpringCloud裡面的一個模組:Spring Cloud Dubbo

技術改造與微服務開發

  • 傳統專案改成Dubbo專案,相對來說成本低
  • 改成SpringCloud,基本等於重做,但是如果新專案建議用SpringCloud

3. 建立基礎工程

加下來我們會建立一個服務提供者工程和一個服務消費者工程,作為後續快速入門篇知識講解演示的基礎工程。

本例實現了消費者對提供者的呼叫,但並未使用到Spring Cloud,但其為後續Spring Cloud的執行測試環境。使用 MySQL 資料庫,使用 Spring Data JPA 作為持久層技術。

3.1 建立提供者工程 01-provider-8081

(1) 建立工程
建立一個 Spring Initializr 工程,並命名為 01-provider-8081。匯入 Lombok、Web、JPA 及 MySQL 驅動依賴。

在這裡插入圖片描述

(2) 匯入 Druid 依賴

<dependencies>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.1.10</version>
    </dependency>

    <!--修改MySQL驅動版本-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.47</version>
        <scope>runtime</scope>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

(3) 定義實體類

@Data
@Entity  // springdatajpa預設用的hibernate,可以自動建表
@JsonIgnoreProperties({"hibernateLazyInitializer", "handler", "fieldHandler"})
// 客戶端與伺服器之間的資料互動,是由SpringMVC的HttpMessageConverter處理的
// 其中一個實現是處理  Jackson資料 -> 完成Java物件與JSON資料間的轉換工作
// JPA的預設實現是Hibernate,而Hibernate預設對於物件的查詢是基於延遲載入的
// Depart depart = service.findById(5);   這裡的depart實際是一個javasist動態代理物件
// String name = depart.getName();
// 對於HttpMessageConverter,它拿到物件後會立即轉成json,這個時候物件中有些資料是空的會報錯
// 基於這種情況我們需要忽略掉一些屬性就不會報錯了
public class Depart {
    @Id  // 表示當前屬性為自動建的表的主鍵
    @GeneratedValue(strategy = GenerationType.IDENTITY)  // 主鍵自動遞增
    private Integer id;
    private String name;
}

(4) 定義 Repository 介面

// 第一個泛型是,當前Repository所操作的物件的型別
// 第二個泛型是,當前Repository所操作的物件的id型別
public interface DepartRepository extends JpaRepository<Depart, Integer> {
}

(5) 定義 Service 介面

public interface DepartService {
    boolean saveDepart(Depart depart);
    boolean removeDepartById(Integer id);
    boolean modifyDepart(Depart depart);
    Depart getDepartById(int id);
    List<Depart> listAllDeparts();
}

(6) 定義 Service 實現類

@Service
public class DepartServiceImpl implements DepartService {

    @Autowired
    private DepartRepository repository;
	//A、新增資料
    // 插入
    @Override
    public boolean saveDepart(Depart depart) {
        // 對於save()的引數,根據其id的不同,有以下三種情況:
        // depart的id為null:save()執行的是插入操作
        // depart的id不為null,且DB中該id存在:save()執行的是修改操作
        // depart的id不為null,但DB中該id不存在:save()執行的是插入操作,
        //      但其播入後的記錄id值並不是這裡指定的id,而是其根據指定的id生成策略所生成的id
        Depart obj = repository.save(depart);
        return obj != null ? true : false;
    }
	//B、 刪除資料
    @Override
    public boolean removeDepartById(Integer id) {
        if(repository.existsById(id)) {
            // 在DB中指定的id若不存在,該方法會丟擲異常
            repository.deleteById(id);
            return true;
        }
        return false;
    }
	//C、 修改資料
    @Override
    public boolean modifyDepart(Depart depart) {
        Depart obj = repository.save(depart);
        return obj != null ? true : false;
    }
	//D、根據 id 查詢
    @Override
    public Depart getDepartById(int id) {
        if(repository.existsById(id)) {
            // 在DB中指定的id若不存在,該方法會丟擲異常
            return repository.getOne(id);
        }
        Depart depart = new Depart();
        depart.setName("no this depart");
        return depart;
    }
	//E、 查詢所有
    @Override
    public List<Depart> listAllDeparts() {
        return repository.findAll();
    }
}

(7) 定義處理器

@RestController
@RequestMapping("/provider/depart")
public class DepartController {
    @Autowired
    private DepartService service;

    @PostMapping("/save")
    public boolean saveHandler(@RequestBody Depart depart) {
        return service.saveDepart(depart);
    }

    @DeleteMapping("/del/{id}")
    public boolean delHandler(@PathVariable("id") Integer id) {
        return service.removeDepartById(id);
    }

    @PutMapping("/update")
    public boolean updateHandler(@RequestBody Depart depart) {
        return service.modifyDepart(depart);
    }

    @GetMapping("/get/{id}")
    public Depart getHandler(@PathVariable("id") Integer id) {
        return service.getDepartById(id);
    }

    @GetMapping("/list")
    public List<Depart> listHandler() {
        return service.listAllDeparts();
    }
}

(8) 修改配置檔案

server:
  port: 8081

spring:
  # 配置spring data jpa
  jpa:
    # 指定是否在spring容器啟動時建立表,預設false
    generate-ddl: true
    # 指定在控制檯是否顯示SQL語句,預設false
    show-sql: true
    # 指定應用重啟後不重新更新表內容
    hibernate:
      ddl-auto: none
  # 配置資料來源
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql:///test?useUnicode=true&amp;characterEncoding=utf8
    username: root
    password: 111

# 配置日誌
logging:
  # 控制日誌在控制檯的輸出
  pattern:
    console: level-%level %msg%n
  # 控制日誌的顯示級別
  level:
    # 控制Spring Boot啟動時顯示的日誌級別
    root: info
    # 控制Hibernate執行時的日誌級別
    org.hibernate: info
    # 在show-sql為true時顯示SQL中的動態引數值
    org.hibernate.type.descriptor.sql.BasicBinder: trace
    # 在show-sql為true時顯示查詢結果
    org.hibernate.hql.internal.ast.exec.BasicExecutor: trace
    # 控制自己程式碼執行時顯示的日誌級別
    com.abc: debug

(9) 啟動類更名

@SpringBootApplication
public class ApplicationProvider8081 {//方便和消費者區分

    public static void main(String[] args) {
        SpringApplication.run(ApplicationProvider8081.class, args);
    }

}

(10) 測試
在這裡插入圖片描述

在這裡插入圖片描述

3.2 建立消費者工程 01-consumer-8080

(1) 建立工程
建立一個 Spring Initializr 工程,並命名為 01-consumer-8080,匯入 Lombok 與 Web 依賴。
在這裡插入圖片描述

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

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
        <exclusions>
            <exclusion>
                <groupId>org.junit.vintage</groupId>
                <artifactId>junit-vintage-engine</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

(2) 定義實體類

//消費者端,不需要持久層相關注解了
@Data
public class Depart {
    private Integer id;
    private String name;
}

(3) 定義 JavaConfig 配置類

@Configuration
public class DepartCodeConfigure {

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

(4) 定義處理器類

@RestController
@RequestMapping("/consumer/depart")
public class SomeController {
    @Autowired
    private RestTemplate restTemplate;

    private static final String SERVICE_PROVIDER = "http://localhost:8081";
    
    //A、新增資料
    @PostMapping("/save")
    public boolean saveHandler(@RequestBody Depart depart) {

        String url = SERVICE_PROVIDER + "/provider/depart/save";
        return restTemplate.postForObject(url, depart, Boolean.class);
    }
    
	//B、 刪除與修改資料
    @DeleteMapping("/del/{id}")
    public void deleteHandler(@PathVariable("id") int id) {
        String url = SERVICE_PROVIDER + "/provider/depart/del/" + id;
        //RestTemplate的delete方法是沒有返回值的
        restTemplate.delete(url);
    }

    @PutMapping("/update")
    public void updateHandler(@RequestBody Depart depart) {
        String url = SERVICE_PROVIDER + "/provider/depart/update";
        //RestTemplate的put也是沒有返回值的
        restTemplate.put(url, depart);
    }
    
	//C、 兩個查詢
    @GetMapping("/get/{id}")
    public Depart getByIdHandler(@PathVariable("id") int id) {
        String url = SERVICE_PROVIDER + "/provider/depart/get/" + id;
        return restTemplate.getForObject(url, Depart.class);
    }

    @GetMapping("/list")
    public List<Depart> listHandler() {
        String url = SERVICE_PROVIDER + "/provider/depart/list";
        return restTemplate.getForObject(url, List.class);
    }
}

(5) 啟動類更名

@SpringBootApplication
public class ApplicationConsumer8080 {

    public static void main(String[] args) {
        SpringApplication.run(ApplicationConsumer8080.class, args);
    }

}

(6) 測試
在這裡插入圖片描述

在這裡插入圖片描述

相關文章