【系統架構理論】一篇文章搞掂:微服務架構

本文篇幅較長,建議合理利用右上角目錄進行查看(若是沒有目錄請刷新)。git

本文基於《Spring 微服務實戰》一書進行總結和擴展,你們也能夠自行研讀此書。web

1、Spring、雲計算、微服務簡介

1.一、什麼是微服務

單體架構:

    • 在微服務概念逐步造成前,大部分Web應用都是基於單體架構風格進行構建。
    • 單體架構風格每每是幾個團隊維護一份共同代碼,生成一個單一程序進行運行。

    

 

    • 問題:當應用規模增加,各個團隊間的溝通和合做成本也會增大;而當其中一個團隊修改代碼時,整個應用程序須要從新構建、測試、部署。

微服務:

    • 微服務的概念在2014年左右在軟件開發社區中蔓延
    • 一個小的、鬆耦合的分佈式服務;分解和分離應用程序的功能,並使他們彼此獨立

    

微服務架構特徵

    • 邏輯業務分解爲明肯定義了職責範圍的細粒度組件,組件互相協調提供解決方案
    • 每一個組件有其職責領域,並能徹底獨立部署
    • 微服務應該對業務領域的單個部件負責
    • 一個微服務能夠跨多個應用程序複用
    • 微服務通訊基於一些基本原則,採用HTTP或JSON等輕量級通訊協議做,在服務的消費者和提供者間進行數據交換
    • 微服務小、獨立、分佈式的特性,使組織擁有明確責任領域的小型開發團隊;一樣是開發一個應用程序,這些團隊各自只須要負責本身作的服務。

1.二、Spring和微服務的關係

  • Spring的地位:Java應用的標準開發框架。由於Spring提供了依賴注入的功能,使程序中類之間的關係能夠外部管理,爲開發企業級應用提供了一個輕量級的解決方案,使其成爲了Java程序的一種必然使用的開發框架。
  • Spring的更新:Spring團隊是一個與時俱進的團隊,不斷地改造本身的代碼迎合時代需求;爲相應微服務架構的需求,Spring團隊開發出了2個項目:Spring Boot和Spring Cloud
    • Spring Boot:對Spring架構理念從新思考的結果:以默認配置的方式簡化了原來Spring大量的手工配置。
    • Spring Cloud:一個封裝了多個流行的雲管理微服務框架的公共框架。

1.三、爲何要改變應用架構和選擇微服務架構

當前局勢:

  互聯網鏈接了社會各個部分,鏈接了全球客戶羣,帶來了全球競爭算法

    • 複雜度上升:現在的應用程序不只要訪問本身的數據,更要和互聯網和外部服務提供商進行交互和通訊。
    • 客戶要求更快的交付:爲了應對瞬息萬變的市場,如今的客戶追求快速的變化,但願能在幾周或者幾天發佈新功能,而無需等待整個產品發佈。
    • 性能和伸縮性:全球性的應用程序沒法預測事務量的變化,但願能使應用程序跨多個服務器在事務高峯時進行擴大,在高峯後又能進行收縮。
    • 高可用性:客戶但願不能由於某個部分的故障致使整個系統的崩潰

微服務架構提供的解決方案:

  將單體的應用程序,分解爲互相獨立構建和部署的小型服務,帶來的好處是:spring

    • 高靈活性:將結構的服務進行組合和從新安排,可快速交付新功能;一個代碼單元越小,更容易修改代碼,也更容易測試。
    • 高彈性:解耦的服務使故障不會蔓延到整個應用程序,致使程序中斷。
    • 可伸縮性:解耦的服務能夠輕鬆地跨多個服務器進行水平分佈,從而適當地對功能/服務進行伸縮。若是是單體應用程序,即便只有小部分遇到瓶頸,都要整個程序進行擴展;而微服務的擴展是局部的,實現成本更低。

1.四、什麼是雲

如今「雲」的概念已經被過分使用。數據庫

雲:

  雲是網絡、互聯網的一種比喻說法。apache

雲計算:

  將數據的運算,放到互聯網中去。編程

雲計算的3種基本模式:

    • IaaS(Infrastructure as a Service)基礎設施即服務:直接使用互聯網中的硬件,如租用硬盤,租用內存,租用服務器;例如我在阿里雲上租用了一個服務器,在裏面安裝本身的數據庫,IIS,而後搭建Web應用程序。
    • Paas(Platform as a Service)平臺即服務:互聯網商提供一個完整的軟件研發和部署平臺,我無需購買硬件和軟件,直接在該平臺上開發和部署個人應用。
    • SaaS(Software as a Service)軟件即服務:互聯網商提供一套已經研發好,部署好的軟件,我直接經過Web來使用他的軟件。

  新型的模式json

    • Faas(Functions as a Service)函數即服務:將代碼塊交給互聯網商,該代碼會在互聯網商的計算設施上計算,對外暴露成一種服務,咱們直接調用該服務便可。
    • Caas(Container as a Service)容器即服務:互聯網商提供一些虛擬容器(如Docker),咱們把程序放到容器中啓動便可。

1.五、爲何選擇雲+微服務

微服務架構核心概念是:把每個服務打包和部署爲獨立的製品。後端

部署方式的可選項:

    • 物理服務器:雖然能夠,可是物理服務器沒法快速提高容量;且在多個物理服務器間水平伸縮微服務成本很是高。
    • 虛擬機鏡像:基於IaaS,能夠在租用的雲虛擬機中快速部署和啓動服務的多個實例。
    • 虛擬容器:基於IaaS,不是直接把服務部署到雲虛擬機,而是將Docker容器(或其餘容器)部署到虛擬機,再把服務放到虛擬容器中運行。這樣能夠把單個虛擬機隔離成共享相同虛擬機鏡像的一系列獨立進程。

雲的優點:

    • 快速:開發人員能夠在幾分鐘內快速啓動新的虛擬機和容器。
    • 高水平伸縮性:服務容量需求降低,能夠關閉虛擬服務器,減小費用;需求上升,能夠快速添加服務器和服務實例。
    • 高彈性:例如一臺微服務遇到問題,能夠啓動新的服務實例,讓程序繼續運行,給時間開發團隊解決問題。

IaaS+Docker:

  咱們採用IaaS+Docker做爲部署環境api

  爲何不是基於PaaS:IaaS與供應商無關,PaaS要考慮基於供應商提供的微服務抽象,因此考慮IaaS有更大靈活性,能夠跨供應商移植。

1.六、微服務架構的內容

編寫單個微服務的代碼很簡單,可是要開發、運行、支持一個健壯的微服務應用

須要考慮幾個主題:

  

    • 大小適當:如何正確劃分微服務大小,容許快速更改應用程序,下降整個應用程序中斷的風險?
    • 位置透明:微服務應用程序中,多個服務實例能夠快速啓動和關閉,如何管理服務調用的物理細節?
    • 有彈性:如何繞過失敗的服務,確保採用「快速失敗」來保護微服務消費者和應用程序的總體完整性?
    • 可重複:如何確保提供的每一個新服務實例與生產環境中的全部其餘服務實例具備相同的配置和代碼庫?
    • 可伸縮:如何使用一部處理和事件,最小化服務之間的直接依賴關係,確保能夠優雅地擴展微服務?

從基於模式的方法來考慮解決這些主題(詳細內容不是過重點,暫時不講解這些模式):

    • 核心微服務開發模式
    • 微服務路由模式
    • 微服務客戶端彈性模式
    • 微服務安全模式
    • 微服務日誌記錄和跟蹤模式
    • 微服務構建和部署模式

1.七、總結與實例

互聯網的發展與變化,要求如今的應用程序在高複雜性下仍然能保持快速交付、高彈性、高可用、高伸縮性,微服務架構就是爲了實現這個要求所出現的系統架構。

微服務架構,把系統的需求,分割成若干個能夠獨立運行的應用,並做爲服務暴露出來;而後經過服務的註冊與發現,使這些服務之間能夠互相協做,完成系統任務。

一個微服務框架的例子:Cloud-Admin,開源框架,https://gitee.com/minull/ace-security

架構圖:

這裏須要補充這個架構圖的意義

2、第一步:實現服務註冊與發現

服務發現:在分佈式計算架構中,咱們要調用某臺計算機提供的服務時,必須知道這臺機器的物理地址,這個稱爲服務發現。

微服務架構與服務發現:

  • 因爲應用被拆分爲多個微服務,咱們能夠將這些微服務實例數量進行水平伸縮,並經過服務發現讓這些服務參與運算;服務消費者不須要知道服務的真正地址,因此咱們能夠在服務池中添加和移除服務實例。而單體架構,只能考慮購買更大型、更好的硬件來擴大服務。
  • 當某些服務變得不健康或者不可用時,咱們能夠移除這些服務,服務發現引擎能夠繞過這些服務繼續運行,使移除服務形成的影響最小化。

咱們下面將經過Spring Cloud和Netflix技術,來實現這一個功能。

2.一、一種傳統的方式服務位置解析模型

DNS和負載均衡器的傳統服務位置解析模型:

缺點:

  • 單點故障——雖然負載均衡器能夠實現高可用,但這是整個基礎設施的單點故障。若是負載均衡器出現故障,那麼依賴它的每一個應用程序都會出現故障。儘管能夠使負載平衡器高度可用,但負載均衡器每每是應用程序基礎設施中的集中式阻塞點。
  • 有限的水平可伸縮性——在服務集中到單個負載均衡器集羣的狀況下,跨多個服務器水平伸縮負載均衡基礎設施的能力有限。許多商業負載均衡器受兩件事情的限制:冗餘模型和許可證成本。第一,大多數商業負載均衡器使用熱插拔模型實現冗餘,所以只能使用單個服務器來處理負載,而輔助負載均衡器僅在主負載均衡器中斷的狀況下,才能進行故障切換。這種架構本質上受到硬件的限制。第二,商業負載均衡器具備有限數量的許可證,它面向固定容量模型而不是更可變的模型。
  • 靜態管理——大多數傳統的負載均衡器不是爲快速註冊和註銷服務設計的。它們使用集中式數據庫來存儲規則的路由,添加新路由的惟一方法一般是經過供應商的專有API(Application Programming Interface,應用程序編程接口)來進行添加。
  • 複雜——因爲負載均衡器充當服務的代理,它必須將服務消費者的請求映射到物理服務。這個翻譯層一般會爲服務基礎設施增長一層複雜度,由於開發人員必須手動定義和部署服務的映射規則。在傳統的負載均衡器方案中,新服務實例的註冊是手動完成的,而不是在新服務實例啓動時完成的。

2.二、適合雲的服務發現架構

基於雲的微服務環境但願實現的服務發現架構有以下特色:

  • 高可用——服務發現須要可以支持「熱」集羣環境,在服務發現集羣中能夠跨多個節點共享服務查找。若是一個節點變得不可用,集羣中的其餘節點應該可以接管工做。
  • 點對點——服務發現集羣中的每一個節點共享服務實例的狀態。
  • 負載均衡——服務發現須要在全部服務實例之間動態地對請求進行負載均衡,以確保服務調用分佈在由它管理的全部服務實例上。在許多方面,服務發現取代了許多早期Web應用程序實現中使用的更靜態的、手動管理的負載均衡器。
  • 有彈性——服務發現的客戶端應該在本地「緩存」服務信息。本地緩存容許服務發現功能逐步降級,這樣,若是服務發現服務變得不可用,應用程序仍然能夠基於本地緩存中維護的信息來運行和定位服務。
  • 容錯——服務發現須要檢測出服務實例何時是不健康的,並從能夠接收客戶端請求的可用服務列表中移除該實例。服務發現應該在沒有人爲干預的狀況下,對這些故障進行檢測,並採起行動。

2.2.一、服務發現架構的共同流程

  

  • 服務註冊——當服務實例啓動時,它們將經過一個或多個服務發現實例來註冊它們能夠訪問的物理位置、路徑和端口。雖然每一個服務實例都具備惟一的IP地址和端口,可是每一個服務實例都將以相同的服務ID進行註冊。服務ID是惟一標識一組相同服務實例的鍵。
  • 服務地址的客戶端查找——客戶端經過服務發現代理獲取服務IP地址。
  • 信息共享——服務發現節點之間共享服務實例的健康信息。
  • 健康監測——服務向服務發現代理髮送心跳包。

2.2.二、增長客戶端負載均衡

上面的模型中,每次調用註冊的微服務實例時,服務發現引擎就會被調用。這種方法很脆弱,由於服務客戶端徹底依賴於服務發現引擎來查找和調用服務。

增長客戶端負載均衡的服務發現模型:

在這個模型中,當服務消費者須要調用一個服務時:

  • 它將聯繫服務發現服務,獲取它請求的全部服務實例,而後在服務消費者的機器上本地緩存數據。
  • 每當客戶端須要調用該服務時,服務消費者將從緩存中查找該服務的位置信息。一般,客戶端緩存將使用簡單的負載均衡算法,如「輪詢」負載均衡算法,以確保服務調用分佈在多個服務實例之間。
  • 而後,客戶端將按期與服務發現服務進行聯繫,並刷新服務實例的緩存。客戶端緩存最終是一致的,可是始終存在這樣的風險:在客戶端聯繫服務發現實例以進行刷新和調用時,調用可能會被定向到不健康的服務實例上。
  • 若是在調用服務的過程當中,服務調用失敗,那麼本地的服務發現緩存失效,服務發現客戶端將嘗試從服務發現代理刷新數據。

2.三、使用Spring Cloud和Netflix實現服務發現架構

使用Spring Cloud和Netflix實現的服務發現架構模型:

    • 隨着服務的啓動,許可證和組織服務將經過Eureka服務進行註冊。這個註冊過程將告訴Eureka每一個服務實例的物理位置和端口號,以及正在啓動的服務的服務ID。
    • 當許可證服務調用組織服務時,許可證服務將使用Netflix Ribbon庫來提供客戶端負載均衡。Ribbon將聯繫Eureka服務去檢索服務位置信息,而後在本地進行緩存。
    • Netflix Ribbon庫將按期對Eureka服務進行ping操做,並刷新服務位置的本地緩存。
    • 任何新的組織服務實例如今都將在本地對許可證服務可見,而任何不健康實例都將從本地緩存中移除。

2.四、代碼實現服務發現架構

2.4.一、構建Eureka服務

Eureka服務的做用是:

提供服務的註冊中心。

主POM文件:

添加Spring Cloud的版本管理,添加Spring Boot的Starter依賴和Test依賴。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>xxx</groupId>
    <artifactId>xxx</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>base-eureka</module>
    </modules>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.0.RELEASE</version>
    </parent>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Finchley.M8</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    
</project>
主POM文件代碼

Eureka模塊POM文件:

Eureka服務做爲項目的一個模塊進行添加,並在POM文件中添加Eureka服務器依賴以及Spring Cloud Starter依賴

<?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>XXX</artifactId>
        <groupId>XXX</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>base-eureka</artifactId>
    <packaging>jar</packaging>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-netflix-eureka-server</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter</artifactId>
        </dependency>
    </dependencies>

</project>
Eureka模塊POM文件

建立Eureka服務程序引導類:

經過@EnableEurekaServer註解,啓動Eureka服務器

@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaApplication.class, args);
    }
}
主程序引導類

添加配置文件:

經過application.yml文件對Eureka服務器進行配置

spring:
  application:
    name: eureka
    
server:
  port: 8761 #啓動端口

eureka:
  client:
    registerWithEureka: false #false:不做爲一個客戶端註冊到註冊中心
    fetchRegistry: false #爲true時,能夠啓動,但報異常:Cannot execute request on any known server
配置Eureka服務器

 啓動服務器:

根據配置的端口,經過引導類啓動程序後,訪問如http://localhost:8761/,便可看到以下畫面,說明Eureka服務器啓動成功

2.4.二、把RESTful服務註冊到Eureka註冊服務中去

模塊名爲:testrest

testrest模塊POM文件:

添加Spring Boot Web依賴和Eureka客戶端依賴

<?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>XXX</artifactId>
        <groupId>XXX</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>base-testrest</artifactId>

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

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

</project>
REST模塊POM文件

testrest服務程序引導類:

@SpringBootApplication
public class TestRestApplication {
    public static void main(String[] args) {
        SpringApplication.run(TestRestApplication.class, args);
    }
}
主程序引導類

testrest控制器:

@RestController
@RequestMapping("/test")
public class TestController {
    @RequestMapping(value = "/{id}", method = RequestMethod.GET)
    public String selectById(@PathVariable String id)  {
        return id;
    }
}
控制器

配置文件:

server:
  port: 8801 #啓動端口

spring:
  application:
    name: base-testrest #將使用Eureka註冊的服務的邏輯名稱

eureka:
  instance:
    preferIpAddress: true #註冊服務的IP,而不是服務器名稱
  client:
    registerWithEureka: true #向Eureka註冊服務
    fetchRegistry: true
    serviceUrl: 
      defaultZone: http://localhost:8761/eureka/ #Eureka服務的位置
配置文件

啓動客戶端:

先啓動Eureka服務器,而後再啓動testrest程序引導類,再訪問Eureka服務器界面,如http://localhost:8761/,便可看見此服務成功註冊到Eureka服務器中了

 2.4.三、建立另一個RESTful服務,對上面2中已註冊的服務進行發現和消費

對註冊到Eureka的服務進行發現和消費,有幾種方式:

  • Spring DiscoveryClient
  • 啓用了RestTemplate的Spring DiscoveryClient
  • Netflix Feign客戶端

這裏僅介紹Feign客戶端

模塊名爲:testrestconsume

testrestconsume模塊POM文件:

添加Spring Boot Web依賴、Eureka客戶端依賴及Feign依賴

<?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>XXX</artifactId>
        <groupId>XXX</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>base-testrestconsume</artifactId>

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

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

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
    </dependencies>

</project>
POM文件

testrestconsume服務程序引到類:

添加註解@EnableFeignClients

@SpringBootApplication
@EnableFeignClients
public class TestRestConsumeApplication {
    public static void main(String[] args) {
        SpringApplication.run(TestRestConsumeApplication.class, args);
    }
}
程序引導類

建立接口發現和消費微服務:

一個微服務的多個實例,使用其spring.application.name進行標識;

Feign客戶端利用接口和@FeignClient客戶端,使用name標識,能夠直接調用註冊到Eureka中的服務。

@FeignClient("base-testrest")
public interface ITestConsumeService {
  @RequestMapping(value="/test/{id}",method = RequestMethod.GET)
  public String selectById(@PathVariable("id") String id);
}
FeignClient接口

控制器調用接口:

Feign客戶端接口,直接經過@Autowired自動裝配便可使用

@RestController
@RequestMapping("/testconsume")
public class TestConsumeController {
    @Autowired
    private ITestConsumeService testConsumeService;

    @RequestMapping(value = "/{id}", method = RequestMethod.GET)
    public String selectById(@PathVariable String id)  {
        return testConsumeService.selectById(id);
    }
}
控制器

配置文件:

主要須要指明Eureka服務的地址

server:
  port: 8802 #啓動端口

spring:
  application:
    name: base-testrestconsume #將使用Eureka註冊的服務的邏輯名稱

eureka:
  instance:
    preferIpAddress: true #註冊服務的IP,而不是服務器名稱
  client:
    serviceUrl: #拉取註冊表的本地副本
      defaultZone: http://${EUREKA_HOST:localhost}:${EUREKA_PORT:8761}/eureka/ #Eureka服務的位置
配置文件

3、第二步:爲微服務提供認證和受權

一般一個系統的服務不會徹底暴露給全部人使用,而是根據用戶的身份、權力來決定是否容許其使用。

咱們下面將經過Spring Cloud Security、Spring Security Oauth2和Spring Security Jwt技術,來實現這一個功能。

3.一、OAuth2框架

OAuth2是一個基於令牌的安全驗證和受權框架,它將安全性分解爲4個部分:

  • 受保護資源——開發人員想要保護的資源(如一個微服務),確保只有已經過驗證而且具備適當受權的用戶才能訪問它。
  • 資源全部者——資源全部者定義哪些應用程序能夠調用其服務,哪些用戶能夠訪問該服務,以及他們能夠使用該服務完成哪些事情。資源全部者註冊的每一個應用程序都將得到一個應用程序名稱,該應用程序名稱與應用程序密鑰一塊兒標識應用程序。應用程序名稱和密鑰的組合是在驗證OAuth2令牌時傳遞的憑據的一部分。
  • 應用程序——直接和用戶解除的應用程序。一般用戶訪問應用程序,而後應用程序再調用各個微服務。
  • OAuth2驗證服務器——OAuth2驗證服務器是應用程序和正在使用的服務之間的中間人。OAuth2驗證服務器容許用戶對本身進行驗證,而沒必要將用戶憑據傳遞給由應用程序表明用戶調用的每一個服務。

 

OAuth2運做流程:

  • 一、用戶訪問應用程序,並提交其身份憑證(如賬號密碼);
  • 二、應用程序把身份憑證傳給OAuth2驗證服務器;
  • 三、OAuth2驗證服務器對其身份進行認證,認證經過則返回一個令牌給應用程序;
  • 四、應用程序調用服務時,把令牌傳給受保護服務;
  • 五、受保護服務把令牌傳給OAuth2驗證服務器,驗證令牌對應的用戶,是否擁有將要訪問資源的權力;
  • 六、驗證經過,則能夠使用對應資源。

OAuth2規範具備如下4種類型的受權,這裏僅討論密碼的方式:

  • 密碼(password);
  • 客戶端憑據(client credential);
  • 受權碼(authorization code);
  • 隱式(implicit)。

3.二、創建OAuth2驗證服務器

主POM文件:

添加Spring Boot Web依賴、Spring Cloud Security依賴及Spring Security Oauth2依賴

<?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>XXX</artifactId>
        <groupId>XXX</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>base-authentication</artifactId>
    <packaging>jar</packaging>

    <dependencies>

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

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-security</artifactId>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework.security.oauth/spring-security-oauth2 -->
        <dependency>
            <groupId>org.springframework.security.oauth</groupId>
            <artifactId>spring-security-oauth2</artifactId>
            <version>2.3.3.RELEASE</version>
        </dependency>

    </dependencies>

</project>
主POM文件

程序引導類:

添加驗證服務器註解@EnableAuthorizationServer代表這個是驗證服務器,添加資源服務器註解@EnableResourceServer代表這個程序提供的資源受到OAuth2保護

@SpringBootApplication
@EnableResourceServer
@EnableAuthorizationServer
public class AuthenticationApplication {
    public static void main(String[] args) {
        SpringApplication.run(AuthenticationApplication.class, args);
    }
}
程序引導類

OAuch2配置類:

配置一個AuthorizationServerConfigurerAdapter類,用於配置驗證服務註冊哪些應用程序

@Configuration
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private UserDetailsService userDetailsService;

    // 由於當前security版本,密碼須要以{0}XXXX的方式,增長密碼的編碼方式在花括號內進行傳輸
    // 因此若是想直接傳XXXX的密碼,須要加這段代碼
    @Bean
    public static NoOpPasswordEncoder passwordEncoder() {
        return (NoOpPasswordEncoder) NoOpPasswordEncoder.getInstance();
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("eagleeye")// 容許訪問的客戶端
                .secret("thisissecret")// 密碼
                .authorizedGrantTypes(// 容許的受權類型
                        "refresh_token",
                        "password",
                        "client_credentials")
                .scopes("webclient", "mobileclient");// 引用程序做用域
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints
                .authenticationManager(authenticationManager)
                .userDetailsService(userDetailsService);
    }
}
OAuth2配置類

WebSecurity配置類:

配置一個WebSecurityConfigurerAdapter類,用於配置系統有哪些用戶,分別是什麼角色

@Configuration
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    @Bean
    public UserDetailsService userDetailsServiceBean() throws Exception {
        return super.userDetailsServiceBean();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
                .inMemoryAuthentication()
                .withUser("john.carnell")
                .password("password1")
                .roles("USER")
                .and()
                .withUser("william.woodward")
                .password("password2")
                .roles("USER", "ADMIN");
    }
}
WebSecurity配置類

配置文件:

主要設置端口號和增長了一個url路徑前綴

spring:
  application:
    name: authenticationservice

server:
  servlet:
    context-path: /auth
  port: 8901
配置文件

令牌驗證端點:

建立一個REST端點,用於令牌驗證

@RestController
public class TokenController {
    @RequestMapping(value = {"/user"}, produces = "application/json")
    public Map<String, Object> user(OAuth2Authentication user) {
        Map<String, Object> userInfo = new HashMap<>();
        userInfo.put(
                "user",
                user.getUserAuthentication().getPrincipal());
        userInfo.put(
                "authorities",
                AuthorityUtils.authorityListToSet(
                        user.getUserAuthentication().getAuthorities()));
        return userInfo;
    }
}
令牌驗證端點控制器

運行:

啓動程序後,使用POST方式,能夠獲取到用戶對應令牌

而後使用令牌訪問令牌驗證端點,獲取令牌信息

3.三、設置獲取微服務資源須要認證和受權

要對其它微服務進行保護,只須要在微服務上進行一些設置便可;而後訪問微服務資源的時候就會根據要求進行令牌驗證。

主POM文件:

添加Spring Cloud Security依賴及Spring Security Oauth2依賴

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-security</artifactId>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework.security.oauth/spring-security-oauth2 -->
<dependency>
    <groupId>org.springframework.security.oauth</groupId>
    <artifactId>spring-security-oauth2</artifactId>
    <version>2.3.3.RELEASE</version>
</dependency>
POM文件增長的依賴

 主程序引導類:

添加資源服務器註解@EnableResourceServer代表這個程序提供的資源受到OAuth2保護

@EnableResourceServer
@SpringBootApplication
public class TestRestApplication {
    public static void main(String[] args) {
        SpringApplication.run(TestRestApplication.class, args);
    }
}
主程序引導類

配置文件:

配置OAuth2驗證服務器令牌驗證服務的地址

security:
  oauth2:
    resource:
       userInfoUri: http://localhost:8901/auth/user
配置文件

配置ResourceServer配置類:

配置一個ResourceServerConfigurerAdapter配置類,用於定義哪些資源須要什麼角色、什麼權限才能訪問

@Configuration
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception{
        // 只要用戶經過認證便可訪問
        http.authorizeRequests().anyRequest().authenticated();

        //用戶須要有相關角色和權限才能訪問
//        http
//        .authorizeRequests()
//          .antMatchers(HttpMethod.DELETE, "/v1/organizations/**")
//          .hasRole("ADMIN")
//          .anyRequest()
//          .authenticated();
    }
}
ResourceServerConfigurerAdapter配置類

運行:

把測試的微服務以及OAuth2驗證服務器都運行起來

訪問測試的微服務

當沒有令牌時,會提示:

當令牌不正確的時候,會提示:

咱們訪問驗證服務器,傳入相關數據,獲取一個新的token,並填入上面的Headers-Authorization中,才能正確訪問資源

3.四、使用JSON Web Token

OAuth2是一個基於令牌的驗證框架,但它並無爲如何定義其規範中的令牌提供任何標準。

爲了矯正OAuth2令牌標準的缺陷,一個名爲JSON Web Token(JWT)的新標準脫穎而出。

JWT是因特網工程任務組(Internet Engineering Task Force,IETF)提出的開放標準(RFC-7519),旨在爲OAuth2令牌提供標準結構。

JWT令牌具備以下特色:

  • 小巧——JWT令牌編碼爲Base64,能夠經過URL、HTTP首部或HTTP POST參數輕鬆傳遞。
  • 密碼簽名——JWT令牌由頒發它的驗證服務器簽名。這意味着能夠保證令牌沒有被篡改。
  • 自包含——因爲JWT令牌是密碼簽名的,接收該服務的微服務能夠保證令牌的內容是有效的,所以,不須要調用驗證服務來確認令牌的內容,由於令牌的簽名能夠被接收微服務確認,而且內容(如令牌和用戶信息的過時時間)能夠被接收微服務檢查。
  • 可擴展——當驗證服務生成一個令牌時,它能夠在令牌被密封以前在令牌中放置額外的信息。接收服務能夠解密令牌淨荷,並從它裏面檢索額外的上下文。

3.4.一、修改配置驗證服務器使用JWT

主POM文件:

原來已添加Spring Cloud Security依賴、Spring Security Oauth2依賴,再添加Spring Security JTW依賴

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-security</artifactId>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework.security.oauth/spring-security-oauth2 -->
<dependency>
    <groupId>org.springframework.security.oauth</groupId>
    <artifactId>spring-security-oauth2</artifactId>
    <version>2.3.3.RELEASE</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-jwt -->
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-jwt</artifactId>
    <version>1.0.9.RELEASE</version>
</dependency>
主POM文件依賴

JWTOAuch2配置類:

去掉原來的OAuch2Config配置類,配置一個新的AuthorizationServerConfigurerAdapter類,裏面設置使用JWT做爲令牌標準,用於配置驗證服務註冊哪些應用程序

@Component
@Configuration
public class ServiceConfig {
  @Value("${signing.key}")
  private String jwtSigningKey="";

  public String getJwtSigningKey() {
    return jwtSigningKey;
  }

}
一、經過配置文件讀取JWT密鑰
@Configuration
public class JWTTokenStoreConfig {

    @Autowired
    private ServiceConfig serviceConfig;

    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    @Bean
    @Primary // @Primary註解用於告訴Spring,若是有多個特定類型的bean(在本例中是DefaultTokenService),那麼就使用被@Primary標註的bean類型進行自動注入
    public DefaultTokenServices tokenServices() { // 用於從出示給服務的令牌中讀取數據
        DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
        defaultTokenServices.setTokenStore(tokenStore());
        defaultTokenServices.setSupportRefreshToken(true);
        return defaultTokenServices;
    }


    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {// 在JWT和OAuth2服務器之間充當翻譯
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey(serviceConfig.getJwtSigningKey());// 定義將用於簽署令牌的簽名密鑰
        return converter;
    }

    @Bean
    public TokenEnhancer jwtTokenEnhancer() {
        return new JWTTokenEnhancer();
    }
}
二、須要建立JWT令牌存儲配置類,用於定義Spring將如何管理JWT令牌的建立、簽名和翻譯
public class JWTTokenEnhancer implements TokenEnhancer {
//    @Autowired
//    private OrgUserRepository orgUserRepo;
//
//    private String getOrgId(String userName){
//        UserOrganization orgUser = orgUserRepo.findByUserName( userName );
//        return orgUser.getOrganizationId();
//    }

    @Override
    public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
        Map<String, Object> additionalInfo = new HashMap<>();
//        String orgId =  getOrgId(authentication.getName());

        String orgId = "id";

        additionalInfo.put("organizationId", orgId);

        ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
        return accessToken;
    }
}
(可選)三、配置Token擴展器,用於在token中擴展自定義字段
@Configuration
public class JWTOAuth2Config extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private TokenStore tokenStore;

    @Autowired
    private DefaultTokenServices tokenServices;

    @Autowired
    private JwtAccessTokenConverter jwtAccessTokenConverter;

    @Autowired
    private TokenEnhancer jwtTokenEnhancer;

    // 由於當前security版本,密碼須要以{0}XXXX的方式,增長密碼的編碼方式在花括號內進行傳輸
    // 因此若是想直接傳XXXX的密碼,須要加這段代碼
    @Bean
    public static NoOpPasswordEncoder passwordEncoder() {
        return (NoOpPasswordEncoder) NoOpPasswordEncoder.getInstance();
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        tokenEnhancerChain.setTokenEnhancers(Arrays.asList(jwtTokenEnhancer, jwtAccessTokenConverter));

        endpoints.tokenStore(tokenStore)                             // 注入令牌存儲
                .accessTokenConverter(jwtAccessTokenConverter)       // 這是鉤子,用於告訴Spring Security OAuth2代碼使用JWT
                .tokenEnhancer(tokenEnhancerChain)                   // 注入Token擴展器
                .authenticationManager(authenticationManager)
                .userDetailsService(userDetailsService);
    }



    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {

        clients.inMemory()
                .withClient("eagleeye")
                .secret("thisissecret")
                .authorizedGrantTypes("refresh_token", "password", "client_credentials")
                .scopes("webclient", "mobileclient");
    }
}
四、配置JWTOAuch2配置類

其它地方無需變動。

運行:

再次運行獲取token的服務,會獲得JWT形式的token:

 

3.4.二、配置微服務使用JWT進行驗證

此時,即便不修改微服務,也能夠經過驗證;由於驗證服務器的JWT是基於OAuth2的,因此支持客戶端使用OAuth2進行驗證。

固然,爲了使用一些JWT的特性,例如自包含等,咱們須要配置微服務使用JWT。

主POM文件:

原來已添加Spring Cloud Security依賴、Spring Security Oauth2依賴,再添加Spring Security JTW依賴

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-security</artifactId>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework.security.oauth/spring-security-oauth2 -->
<dependency>
    <groupId>org.springframework.security.oauth</groupId>
    <artifactId>spring-security-oauth2</artifactId>
    <version>2.3.3.RELEASE</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-jwt -->
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-jwt</artifactId>
    <version>1.0.9.RELEASE</version>
</dependency>
主POM文件依賴

建立令牌存儲配置類:

告訴微服務使用JWT做爲令牌,並須要設置密鑰和驗證服務端對應

@Configuration
public class JWTTokenStoreConfig {
    //JWT
    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    //JWT
    @Bean
    @Primary
    public DefaultTokenServices tokenServices() {
        DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
        defaultTokenServices.setTokenStore(tokenStore());
        defaultTokenServices.setSupportRefreshToken(true);
        return defaultTokenServices;
    }

    //JWT
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey("123456");
        return converter;
    }
}
JWT令牌存儲配置類

建立JWTRestTemplate Bean:

由於許可證服務調用組織服務,因此須要確保OAuth2令牌被傳播。這項工做一般是經過OAuth2RestTemplate類完成的,可是OAuth2RestTemplate類並不傳播基於JWT的令牌。爲了確保許可證服務可以作到這一點,須要添加一個自定義的RestTemplate bean來完成這個注入。 

@Component
public class UserContext {
    public static final String CORRELATION_ID = "tmx-correlation-id";
    public static final String AUTH_TOKEN     = "Authorization";
    public static final String USER_ID        = "tmx-user-id";
    public static final String ORG_ID         = "tmx-org-id";

    private static final ThreadLocal<String> correlationId= new ThreadLocal<String>();
    private static final ThreadLocal<String> authToken= new ThreadLocal<String>();
    private static final ThreadLocal<String> userId = new ThreadLocal<String>();
    private static final ThreadLocal<String> orgId = new ThreadLocal<String>();


    public static String getCorrelationId() { return correlationId.get(); }
    public static void setCorrelationId(String cid) {correlationId.set(cid);}

    public static String getAuthToken() { return authToken.get(); }
    public static void setAuthToken(String aToken) {authToken.set(aToken);}

    public static String getUserId() { return userId.get(); }
    public static void setUserId(String aUser) {userId.set(aUser);}

    public static String getOrgId() { return orgId.get(); }
    public static void setOrgId(String aOrg) {orgId.set(aOrg);}
}
輔助類UserContext
@Component
public class UserContextFilter implements Filter {
    private static final Logger logger = LoggerFactory.getLogger(UserContextFilter.class);

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
            throws IOException, ServletException {


        HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;

        logger.debug("I am entering the licensing service id with auth token: ", httpServletRequest.getHeader("Authorization"));


        UserContextHolder.getContext().setCorrelationId(httpServletRequest.getHeader(UserContext.CORRELATION_ID));
        UserContextHolder.getContext().setUserId(httpServletRequest.getHeader(UserContext.USER_ID));
        UserContextHolder.getContext().setAuthToken(httpServletRequest.getHeader(UserContext.AUTH_TOKEN));
        UserContextHolder.getContext().setOrgId(httpServletRequest.getHeader(UserContext.ORG_ID));

        filterChain.doFilter(httpServletRequest, servletResponse);
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {}

    @Override
    public void destroy() {}
}
輔助類UserContextFilter
public class UserContextHolder {
    private static final ThreadLocal<UserContext> userContext = new ThreadLocal<UserContext>();

    public static final UserContext getContext(){
        UserContext context = userContext.get();

        if (context == null) {
            context = createEmptyContext();
            userContext.set(context);

        }
        return userContext.get();
    }

    public static final void setContext(UserContext context) {
        Assert.notNull(context, "Only non-null UserContext instances are permitted");
        userContext.set(context);
    }

    public static final UserContext createEmptyContext(){
        return new UserContext();
    }
}
輔助類UserContextHolder
public class UserContextInterceptor implements ClientHttpRequestInterceptor {

    @Override
    public ClientHttpResponse intercept(
            HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
            throws IOException {

        HttpHeaders headers = request.getHeaders();
        headers.add(UserContext.CORRELATION_ID, UserContextHolder.getContext().getCorrelationId());
        headers.add(UserContext.AUTH_TOKEN, UserContextHolder.getContext().getAuthToken());


        return execution.execute(request, body);
    }
}
輔助類UserContextInterceptor
@Configuration
public class JWTRestTemplateConfig {
    @Primary
    @Bean
    public RestTemplate getCustomRestTemplate() {
        RestTemplate template = new RestTemplate();
        List interceptors = template.getInterceptors();
        if (interceptors == null) {
            template.setInterceptors(Collections.singletonList(new UserContextInterceptor()));
        } else {
            interceptors.add(new UserContextInterceptor());
            template.setInterceptors(interceptors);
        }

        return template;
    }
}
JWTRestTemplate配置類

 運行:

運行方式和OAuth2沒有區別,有一個有意思的區別是:

獲取到token後,把驗證服務器關掉,再使用token去訪問微服務,仍然能經過,這是由於JWT是自包含的,並不須要在每一個服務聯繫驗證服務器再驗證。

4、第三步:使用Spring Cloud和Zuul進行服務路由

需求:在微服務架構這種分佈式架構中,須要確保多個服務調用的關鍵行爲正常運做,如安全、日誌記錄、用戶跟蹤等。

問題:若是把這些工做分佈在各個微服務中實現,有時會忘記,有時須要修改則全部微服務都要修改,顯然不現實。

方案:

  將服務的這些橫切關注點抽象成一個獨立的,做爲全部微服務調用的過濾器和路由器的服務。這個橫切關注點稱爲服務網關(service gateway)。

  客戶端不在直接調用服務,而是由服務網管做爲單個策略執行點(Policy Enforcement Point,PEP),全部調用經過服務網關進行路由,而後被路由到目的地。

4.一、什麼是服務網關

使用服務網關前:難以實現安全性、日誌等橫切關注點

使用服務網關後:客戶端調用服務網關,全部服務的調用交給服務網關進行

這樣,微服務架構的橫切關注點能夠放在服務網關實現,如:

    • 靜態路由——服務網關將全部的服務調用放置在單個URL和API路由的後面。這簡化了開發,由於開發人員只須要知道全部服務的一個服務端點就能夠了。
    • 動態路由——服務網關能夠檢查傳入的服務請求,根據來自傳入請求的數據和服務調用者的身份執行智能路由。例如,可能會將參與測試版程序的客戶的全部調用路由到特定服務集羣的服務,這些服務運行的是不一樣版本的代碼,而不是其餘人使用的非測試版程序的代碼。
    • 驗證和受權——因爲全部服務調用都通過服務網關進行路由,因此服務網關是檢查服務調用者是否已經進行了驗證並被受權進行服務調用的天然場所。
    • 度量數據收集和日誌記錄——當服務調用經過服務網關時,能夠使用服務網關來收集數據和日誌信息,還能夠使用服務網關確保在用戶請求上提供關鍵信息以確保日誌統一。這並不意味着不該該從單個服務中收集度量數據,而是經過服務網關能夠集中收集許多基本度量數據,如服務調用次數和服務響應時間。

4.二、Spring Cloud和Netflix Zuul簡介

Spring Cloud集成了Netflix開源項目Zuul。

Zuul提供了許多功能,具體包括如下幾個:

  • 將應用程序中的全部服務的路由映射到一個URL——Zuul不侷限於一個URL。在Zuul中,開發人員能夠定義多個路由條目,使路由映射很是細粒度(每一個服務端點都有本身的路由映射)。然而,Zuul最多見的用例是構建一個單一的入口點,全部服務客戶端調用都將通過這個入口點。
  • 構建能夠對經過網關的請求進行檢查和操做的過濾器——這些過濾器容許開發人員在代碼中注入策略執行點,以一致的方式對全部服務調用執行大量操做。

使用Zuul的步驟:

  • 一、創建一個Spring Boot項目,並配置Zuul的Maven依賴項:
注意!在Spring Cloud的新版本中,依賴名改爲了spring-cloud-starter-netflix-zuul
<
dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zuul</artifactId> </dependency>
  • 二、使用Spring Cloud註解修改這個Spring Boot項目,將其聲明爲Zuul服務:
@SpringBootApplication
@EnableZuulProxy  ⇽--- 使服務成爲一個Zuul服務器
public class ZuulServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ZuulServerApplication.class, args);
    }
}

  @EnableZuulProxy與@EnableZuulServer

    @EnableZuulServer:使用此註解將建立一個Zuul服務器,它不會加載任何Zuul反向代理過濾器,也不會使用Netflix Eureka進行服務發現(咱們將很快進入Zuul和Eureka集成的主題)。

    本文只會使用@EnableZuulProxy註解。

  • 三、配置Zuul以便Eureka進行通訊(可選):

  Zuul將自動使用Eureka來經過服務ID查找服務,而後使用Netflix Ribbon對來自Zuul的請求進行客戶端負載均衡。

  配置src/main/resources/application.yml文件,與Eureka通訊

eureka:
  instance:
    preferIpAddress: true
  client:
    registerWithEureka: true
    fetchRegistry: true
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/

4.三、在Zuul中配置路由

Zuul的核心是一個反向代理中間服務器,負責捕獲客戶端的請求,而後表明客戶端調用遠程資源。

在微服務架構的狀況下,Zuul(反向代理)從客戶端接收微服務調用並將其轉發給下游服務。服務客戶端認爲它只與Zuul通訊。

Zuul要與下游服務進行溝通,Zuul必須知道如何將進來的調用映射到下游路由。Zuul有幾種機制來作到這一點,包括:

  • 4.3.一、經過服務發現自動映射路由;

  Zuul不須要配置,默認自動使用正在調用的服務的Eureka服務ID,並將其映射到下游服務實例。例如,若是要調用organizationservice並經過Zuul使用自動路由,則能夠使用如下URL做爲端點,讓客戶端調用Zuul服務實例:

http://localhost:5555/organizationservice/v1/organizations/e254f8c-c442-4ebe-

 

使用帶有Eureka的Zuul的優勢在於,開發人員不只能夠擁有一個能夠發出調用的單個端點,有了Eureka,開發人員還能夠添加和刪除服務的實例,而無須修改Zuul。例如,能夠向Eureka添加新服務,Zuul將自動路由到該服務,由於Zuul會與Eureka進行通訊,瞭解實際服務端點的位置。

  Zuul服務器上的/routes端點能夠查看服務中全部映射的列表。如http://localhost:5555/routes

  

經過zuul註冊的服務的映射展現在從/route調用返回的JSON體的左邊,路由映射到的實際Eureka服務ID展現在其右邊。

  • 4.3.二、使用服務發現手動映射路由;

Zuul容許開發人員更細粒度地明肯定義路由映射,而不是單純依賴服務的Eureka服務ID建立的自動路由。

能夠經過在zuulsvr/src/main/resources/application.yml中手動定義路由映射。

zuul:
  routes:
    organizationservice: /organization/**

經過添加上述配置,如今咱們就能夠經過訪問/organization/v1/organizations/ {organization-id}路由來訪問組織服務了。

查看route的結果

此時出現2條服務條目,一條是根據Eureka自動映射的,一條是咱們在配置文件中手動映射的。

能夠經過application.yml文件添加一個額外的Zuul參數ignored-services來排除Eureka自動映射的服務.

如下代碼片斷展現瞭如何使用ignored-services屬性從Zuul完成的自動映射中排除Eureka服務ID organizationservice。

zuul:
  ignored-services: 'organizationservice'
  routes:
    organizationservice: /organization/**

ignored-services屬性容許開發人員定義想要從註冊中排除的Eureka服務ID的列表,該列表以逗號進行分隔。

添加前綴標記

zuul:
  ignored-services: '*'  ⇽--- ignored-services被設置爲*,以排除全部基於Eureka服務ID的路由的註冊
  prefix: /api  ⇽--- 全部已定義的服務都將添加前綴/api
  routes:
    organizationservice: /organization/**  ⇽--- organizationservice和licensingservice分別映射到organization和licensing
    licensingservice: /licensing/**

如今,則須要經過/api/organization/v1/organization/ {organization-id}來訪問網關接口了

  • 4.3.三、使用靜態URL手動映射路由。

Zuul能夠用來路由那些不受Eureka管理的服務。在這種狀況下,能夠創建Zuul直接路由到一個靜態定義的URL。

zuul:
 routes:
   licensestatic:  ⇽--- Zuul用於在內部識別服務的關鍵字
     path: /licensestatic/**  ⇽--- 許可證服務的靜態路由
     url: http://licenseservice-static:8081  ⇽--- 已創建許可證服務的靜態實例,它將被直接調用,而不是由Zuul經過Eureka調用

如今,licensestatic端點再也不使用Eureka,而是直接將請求路由到http://licenseservice-static:8081端點。

這裏存在一個問題,那就是經過繞過Eureka,只有一條路徑能夠用來指向請求。

幸運的是,開發人員能夠手動配置Zuul來禁用Ribbon與Eureka集成,而後列出Ribbon將進行負載均衡的各個服務實例。

zuul:
  routes:
    licensestatic:
      path: /licensestatic/**
      serviceId: licensestatic  ⇽--- 定義一個服務ID,該服務ID將用於在Ribbon中查找服務
ribbon:
  eureka:
    enabled: false  ⇽--- 在Ribbon中禁用Eureka支持
licensestatic:
  ribbon:
    listOfServers: http://licenseservice-static1:8081,
      http://licenseservice-static2:8082  ⇽--- 指定請求會路由到的服務器列表

可是若是禁用了Ribbon與Eureka集成,Zuul沒法經過Ribbon來緩存服務的查找,那麼每次調用都會調用Eureka。

解決這些問題,能夠經過對非JVM應用程序創建單獨的Zuul服務器來處理這些路由。例如經過Spring Cloud Sidecar使用Eureka實例註冊非JVM服務,而後經過Zuul進行代理。(這裏不介紹Spring Cloud Sidecar)

  • 4.3.四、動態從新加載路由配置

動態從新加載路由的功能容許在不回收Zuul服務器的狀況下更改路由的映射。

Zuul公開了基於POST的端點路由/refresh,其做用是讓Zuul從新加載路由配置。在訪問完refresh端點以後,若是訪問/routes端點,就會看到路由被刷新了。

  • 4.3.五、Zuul和服務超時

Zuul使用Netflix的Hystrix和Ribbon庫,來幫助防止長時間運行的服務調用影響服務網關的性能。

在默認狀況下,對於任何須要用超過1 s的時間(這是Hystrix默認值)來處理請求的調用,Zuul將終止並返回一個HTTP 500錯誤。

能夠使用hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds屬性來爲全部經過Zuul運行的服務設置Hystrix超時。

zuul.prefix:  /api
zuul.routes.organizationservice: /organization/**
zuul.routes.licensingservice: /licensing/**
zuul.debug.request: true
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 2500

爲特定服務設置Hystrix超時,能夠使用須要覆蓋超時的服務的Eureka服務ID名稱來替換屬性的default部分。

hystrix.command.licensingservice.execution.isolation.thread.timeoutInMilliseconds:3000

最後,讀者須要知曉另一個超時屬性。雖然已經覆蓋了Hystrix的超時,Netflix Ribbon一樣會超時任何超過5 s的調用。儘管我強烈建議讀者從新審視調用時間超過5 s的調用的設計,但讀者能夠經過設置屬性servicename.ribbon.ReadTimeout來覆蓋Ribbon超時。

hystrix.command.licensingservice.execution.isolation.thread.timeoutInMilliseconds: 7000
licensingservice.ribbon.ReadTimeout: 7000

 4.四、Zuul的真正威力:過濾器

經過Zuul網關代理確實簡化了服務調用,可是Zuul的真正威力在於能夠爲全部流經網關的服務調用編寫自定義邏輯。

例如:安全性、日誌記錄和對全部服務的跟蹤。

Zuul實現這個功能的方式是:過濾器。

Zuul支持這3種類型過濾器:

  • 前置過濾器——前置過濾器在Zuul將實際請求發送到目的地以前被調用。前置過濾器一般執行確保服務具備一致的消息格式(例如,關鍵的HTTP首部是否設置穩當)的任務,或者充當看門人,確保調用該服務的用戶已經過驗證(他們的身份與他們聲稱的一致)和受權(他們能夠作他們請求作的)。
  • 後置過濾器——後置過濾器在目標服務被調用並將響應發送回客戶端後被調用。一般後置過濾器會用來記錄從目標服務返回的響應、處理錯誤或審覈對敏感信息的響應。
  • 路由過濾器——路由過濾器用於在調用目標服務以前攔截調用。一般使用路由過濾器來肯定是否須要進行某些級別的動態路由。例如,本章的後面將使用路由級別的過濾器,該過濾器將在同一服務的兩個不一樣版本之間進行路由,以便將一小部分的服務調用路由到服務的新版本,而不是路由到現有的服務。這樣就可以在不讓每一個人都使用新服務的狀況下,讓少許的用戶體驗新功能。

Zuul的運做過程:

  • (1)在請求進入Zuul網關時,Zuul調用全部在Zuul網關中定義的前置過濾器。前置過濾器能夠在HTTP請求到達實際服務以前對HTTP請求進行檢查和修改。前置過濾器不能將用戶重定向到不一樣的端點或服務。
  • (2)在針對Zuul的傳入請求執行前置過濾器以後,Zuul將執行已定義的路由過濾器。路由過濾器能夠更改服務所指向的目的地。
  • (3)路由過濾器能夠將服務調用重定向到Zuul服務器被配置的發送路由之外的位置。但Zuul路由過濾器不會執行HTTP重定向,而是會終止傳入的HTTP請求,而後表明原始調用者調用路由。這意味着路由過濾器必須徹底負責動態路由的調用,而且不能執行HTTP重定向。
  • (4)若是路由過濾器沒有動態地將調用者重定向到新路由,Zuul服務器將發送到最初的目標服務的路由。
  • (5)目標服務被調用後,Zuul後置過濾器將被調用。後置過濾器能夠檢查和修改來自被調用服務的響應。

 5、第四步:客戶端彈性模式

分佈式系統的傳統彈性實現:集羣關鍵服務器、服務間的負載均衡以及將基礎設施分離到多個位置

傳統彈性實現的缺點:只能解決致命的問題,當服務器奔潰,服務沒法調用時,應用程序纔會繞過它;而當服務變得緩慢或者性能不佳時,傳統方式則沒法繞過它

傳統彈性實現的危害:可能只是一個服務的問題,如執行緩慢等,致使線程池被佔用完,或數據庫鏈接池被佔用完,結果最終致使整個服務器資源被耗盡,致使服務器崩潰;甚至可能從一個服務器,蔓延往上游服務器去蔓延,致使整個生態系統崩潰。

解決方案:客戶端彈性模式

5.一、什麼是客戶端彈性模式

客戶端:是指調用遠程服務或遠程資源的應用程序

客戶端彈性模式:當調用的遠程服務或遠程資源,出現緩慢或其餘問題時,客戶端執行「快速失敗」,保證本身的線程池和數據庫鏈接不被佔用,防止遠程服務或資源的問題向上遊傳播

四種模式:

  • 客戶端負載均衡模式

在前面的服務發現中介紹到,客戶端從服務發現代理(如Netflix Eureka)查找服務的全部實例,而後緩存服務實例的物理位置。

每當服務消費者須要調用該服務實例時,客戶端負載均衡器將從它維護的服務位置池返回一個位置。

由於客戶端負載均衡器位於服務客戶端和服務消費者之間,因此負載均衡器能夠檢測服務實例是否拋出錯誤或表現不佳。

若是客戶端負載均衡器檢測到問題,它能夠從可用服務位置池中移除該服務實例,並防止未來的服務調用訪問該服務實例。

使用Netflix的Ribbon庫提供的開箱即用的功能,不須要額外的配置便可實現。

  • 斷路器模式

斷路器模式是模仿電路斷路器的客戶端彈性模式。

當遠程服務被調用時,斷路器將監視這個調用。

若是調用時間太長,斷路器將會介入並中斷調用。

此外,斷路器將監視全部對遠程資源的調用,若是對某一個遠程資源的調用失敗次數足夠多,那麼斷路器實現就會出現並採起快速失敗,阻止未來調用失敗的遠程資源。

  • 後備模式

後備模式中,當遠程服務調用失敗時,服務消費者將執行替代代碼路徑,並嘗試經過其餘方式執行操做,而不是生成一個異常。

這一般涉及從另外一數據源查找數據或將用戶的請求進行排隊以供未來處理。

用戶的調用結果不會顯示爲提示問題的異常,但用戶可能會被告知,他們的請求要在晚些時候被知足。

例如,假設咱們有一個電子商務網站,它能夠監控用戶的行爲,並嘗試向用戶推薦其餘能夠購買的產品。

一般來講,能夠調用微服務來對用戶過去的行爲進行分析,並返回針對特定用戶的推薦列表。

可是,若是這個偏好服務失敗,那麼後備策略多是檢索一個更通用的偏好列表,該列表基於全部用戶的購買記錄分析得出,而且更爲廣泛。

這些更通用的偏好列表數據可能來自徹底不一樣的服務和數據源。

  • 艙壁模式

艙壁模式是創建在造船的概念基礎上的。

經過使用艙壁模式,能夠把遠程資源的調用分到線程池中,並下降一個緩慢的遠程資源調用拖垮整個應用程序的風險。

線程池充當服務的「艙壁」。

每一個遠程資源都是隔離的,並分配給線程池。

若是一個服務響應緩慢,那麼這種服務調用的線程池就會飽和並中止處理請求,而對其餘服務的服務調用則不會變得飽和,由於它們被分配給了其餘線程池。

5.二、客戶端彈性的重要性

一個實現客戶端彈性的例子

第一種場景:

  • 愉快路徑,斷路器將維護一個定時器,若是在定時器的時間用完以前完成對遠程服務的調用,那麼一切都很是順利,服務B能夠繼續工做。

第二種場景:

  • 在部分降級的場景中,服務B將經過斷路器調用服務C。
  • 這一次服務C運行緩慢,在斷路器維護的線程上的定時器超時以前沒法完成對遠程服務的調用,斷路器就會切斷對遠程服務的鏈接。
  • 而後,服務B將從發出的調用中獲得一個錯誤,可是服務B不會佔用資源(也就是本身的線程池或鏈接池)來等待服務C完成調用。

第三種場景:

  • 若是對服務C的調用被斷路器超時中斷,斷路器將開始跟蹤已發生故障的數量。
  • 若是在必定時間內在服務C上發生了足夠多的錯誤,那麼斷路器就會電路「跳閘」,而且在不調用服務C的狀況下,就斷定全部對服務C的調用將會失敗。
  • 電路跳閘將會致使以下3種結果。
    1. 服務B如今當即知道服務C有問題,而沒必要等待斷路器超時。
    2. 服務B如今能夠選擇要麼完全失敗,要麼執行替代代碼(後備)來採起行動。
    3. 服務C將得到一個恢復的機會,由於在斷路器跳閘後,服務B不會調用它。這使得服務C有了喘息的空間,並有助於防止出現服務降級時發生的級聯死亡。
  • 最後,斷路器會讓少許的請求調用直達一個降級的服務,若是這些調用連續屢次成功,斷路器就會自動復位。

總結斷路器模式提供的關鍵能力:

  • 快速失敗:

當遠程服務處於降級狀態時,應用程序將會快速失敗,並防止一般會拖垮整個應用程序的資源耗盡問題的出現。在大多數中斷狀況下,最好是部分服務關閉而不是徹底關閉。

  • 優雅地失敗

經過超時和快速失敗,斷路器模式使應用程序開發人員有能力優雅地失敗,或尋求替代機制來執行用戶的意圖。

例如,若是用戶嘗試從一個數據源檢索數據,而且該數據源正在經歷服務降級,那麼應用程序開發人員能夠嘗試從其餘地方檢索該數據。

  • 無縫恢復

有了斷路器模式做爲中介,斷路器能夠按期檢查所請求的資源是否從新上線,並在沒有人爲干預的狀況下從新容許對該資源進行訪問。

對於有幾百個服務的系統,這種自恢復能力很重要,而不是靠人爲手工去恢復這些服務的狀態。

 5.三、Hystrix

構建斷路器模式、後備模式和艙壁模式的實現須要對線程和線程管理有深刻的理解,正確地作到這一點很困難。

咱們如今能夠藉助Spring Cloud和Netflix的Hystrix庫,輕鬆實現這些模式。

 一、引入依賴和使用註解開啓斷路器模式

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
@EnableCircuitBreaker
public class Application {
} 

斷路器咱們介紹2種類型:包裝數據庫調用、包裝服務調用

6、使用Spring Cloud Steam的事件驅動架構

基於消息,實現微服務間異步通訊

6.一、爲何使用消息傳遞、EDA和微服務

假設許可證服務須要調用組織服務獲取組織信息,而組織信息是較少修改的。

若是每次獲取組織信息,都調取組織服務對應的API,那麼網絡開銷較大。

一個可行方案是對組織信息進行緩存。

緩存方案實施時有如下3個核心要求:

(1)緩存的組織數據應該在許可證服務全部實例之間保持一致——表明不能在許可證服務本地緩存數據。

(2)不能將組織數據緩存在許可證服務的容器的內存中——許可證服務的運行時容器一般受到大小限制,而且能夠使用不一樣的訪問模式來對數據進行訪問。本地緩存可能會帶來複雜性,由於必須保證本地緩存與集羣中的全部其餘服務同步。

(3)在更新或刪除一個組織記錄時,開發人員但願許可證服務可以識別出組織服務中出現了狀態更改——許可證服務應該使該組織的全部緩存數據失效,並將它從緩存中刪除。

 

2種實現方法

6.1.一、使用同步請求——響應方式來傳遞狀態變化

一、用戶調用許可證服務,查詢許可證數據

二、許可證服務中須要組織信息,先檢查Redis緩存中是否有

三、若是沒有,則調用組織服務獲取,並寫入緩存中保存

四、用戶調用組織服務能夠更新組織數據

五、組織數據更新後,組織服務應該更新緩存中數據,能夠經過調用許可證端點或直接與緩存聯繫。

問題:

一、服務之間緊耦合

許可證服務始終依賴於組織服務來檢索數據。

若是經過調用許可證端點來更新緩存,則令組織服務又依賴於許可證服務。

若是組織服務直接聯繫Redis,也不合理,由於你直接與另一個服務的數據庫進行通訊,是不正確的,一個是權限問題,另外一個不清楚規則會破壞許可證服務的數據格式。

二、服務之間脆弱性

若是許可證服務出現問題,會影響甚至拖垮組織服務;而Redis出現問題,則會影響2個服務。

三、缺少靈活性

若是有新的服務對組織服務的變化感興趣,則須要修改組織服務,從而須要從新生成構建代碼部署代碼;並且整個網絡互相依賴,容易出現一個故障點拖垮整個網絡。

 

6.1.二、使用消息傳遞在服務之間傳達狀態更改

 

加入消息傳遞方式與上面一種方法的差異在於組織變動時如何

使用消息傳遞方式將會在許可證服務和組織服務之間注入隊列。該隊列不會用於從組織服務中讀取數據,而是由組織服務用於在組織服務管理的組織數據內發生狀態更改時發佈消息。圖8-2演示了這種方法。

在圖8-2所示的模型中,每次組織數據發生變化,組織服務都發布一條消息到隊列中。許可證服務正在監視消息隊列,並在消息進入時將相應的組織記錄從Redis緩存中清除。當涉及傳達狀態時,消息隊列充當許可證服務和組織服務之間的中介。這種方法提供瞭如下4個好處:

鬆耦合;
耐久性;
可伸縮性;
靈活性。
1.鬆耦合
微服務應用程序能夠由數十個小型的分佈式服務組成,這些服務彼此交互,並對彼此管理的數據感興趣。正如在前面提到的同步設計中所看到的,同步HTTP響應在許可證服務和組織服務之間產生一個強依賴關係。儘管咱們不能徹底消除這些依賴關係,可是經過僅公開直接管理服務所擁有的數據的端點,咱們能夠嘗試最小化依賴關係。消息傳遞的方法容許開發人員解耦兩個服務,由於在涉及傳達狀態更改時,兩個服務都不知道彼此。當組織服務須要發佈狀態更改時,它會將消息寫入隊列,而許可證服務只知道它獲得一條消息,殊不知道誰發佈了這條消息。

2.耐久性
隊列的存在讓開發人員能夠保證,即便服務的消費者已經關閉,也能夠發送消息。即便許可證服務不可用,組織服務也能夠繼續發佈消息。消息將存儲在隊列中,並將一直保存到許可證服務可用。另外一方面,經過將緩存和隊列方法結合在一塊兒,若是組織服務關閉,許可證服務能夠優雅地降級,由於至少有部分組織數據將位於其緩存中。有時候,舊數據比沒有數據好。

3.可伸縮性
由於消息存儲在隊列中,因此消息發送者沒必要等待來自消息消費者的響應,它們能夠繼續工做。一樣地,若是一個消息消費者沒有足夠的能力處理從消息隊列中讀取的消息,那麼啓動更多消息消費者,並讓它們處理從隊列中讀取的消息則是一項很是簡單的任務。這種可伸縮性方法適用於微服務模型,由於我經過本書強調的其中一件事情就是,啓動微服務的新實例應該是很簡單的,讓這些追加的微服務處理持有消息的消息隊列亦是如此。這就是水平伸縮的一個示例。從隊列中讀取消息的傳統伸縮機制涉及增長消息消費者能夠同時處理的線程數。遺憾的是,這種方法最終會受消息消費者可用的CPU數量的限制。微服務模型則沒有這樣的限制,由於它是經過增長託管消費消息的服務的機器數量來進行擴大的。

4.靈活性
消息的發送者不知道誰將會消費它。這意味着開發人員能夠輕鬆添加新的消息消費者(和新功能),而不影響原始發送服務。這是一個很是強大的概念,由於能夠在沒必要觸及現有服務的狀況下,將新功能添加到應用程序。新的代碼能夠監聽正在發佈的事件,並相應地對它們作出反應。

 

6.1.三、消息傳遞架構的缺點

與任何架構模型同樣,基於消息傳遞的架構也有折中。基於消息傳遞的架構多是複雜的,須要開發團隊密切關注一些關鍵的事情,包括:

消息處理語義;
消息可見性;
消息編排。
1.消息處理語義

開發人員不只須要瞭解如何發佈和消費消息

面對有序消息,開發人員須要考慮如何處理,沒有按順序處理會出現什麼狀況,而不是每條消息獨立地使用

還要考慮消息拋出異常或錯誤時,對當前消息的處理(重試or失敗?)以及對將來消息的處理

2.消息可見性

考慮使用關聯ID等,跟蹤Web服務調用以及消息的發佈,實現對用戶事務的跟蹤

3.消息編排

基於消息傳遞的應用程序很難按照順序進行業務邏輯推理,用戶事務可能也在不一樣時間不按順序執行,調試基於消息的應用程序會設計多個不一樣服務的日誌。

 

6.二、Spring Cloud Stream

簡介

Spring Cloud經過Spring Cloud Stream項目,輕鬆地將消息傳遞集成到基於Spring的微服務中。

Spring Cloud Stream是一個由註解驅動的框架,它容許開發人員在Spring應用程序中輕鬆地構建消息發佈者和消費者。

Spring Cloud Stream對消息傳遞平臺進行了抽象,實現消息發佈和消費是經過平臺無關的Spring接口實現的,而平臺的具體實現細節(如使用Kafka仍是RabbitMQ),則排除在應用程序以外。

架構

隨着Spring Cloud中消息的發佈和消費,有4個組件涉及發佈消息和消費消息,它們是:

發射器(source);
通道(channel);
綁定器(binder);
接收器(sink)。
1.發射器
當一個服務準備發佈消息時,它將使用一個發射器發佈消息。發射器是一個Spring註解接口,它接收一個普通Java對象(POJO),該對象表明要發佈的消息。發射器接收消息,而後序列化它(默認的序列化是JSON)並將消息發佈到通道。

2.通道
通道是對隊列的一個抽象,它將在消息生產者發佈消息或消息消費者消費消息後保留該消息。通道名稱始終與目標隊列名稱相關聯。然而,隊列名稱永遠不會直接公開給代碼,相反,通道名稱會在代碼中使用。這意味着開發人員能夠經過更改應用程序的配置而不是應用程序的代碼來切換通道讀取或寫入的隊列。

3.綁定器
綁定器是Spring Cloud Stream框架的一部分,它是與特定消息平臺對話的Spring代碼。Spring Cloud Stream框架的綁定器部分容許開發人員處理消息,而沒必要依賴於特定於平臺的庫和API來發布和消費消息。

4.接收器
在Spring Cloud Stream中,服務經過一個接收器從隊列中接收消息。接收器監聽傳入消息的通道,並將消息反序列化爲POJO。從這裏開始,消息就能夠按照Spring服務的業務邏輯來進行處理。

 

6.三、編寫簡單的消息發佈者和消費者

目的:A服務發佈消息,B服務打印到窗口

6.3.一、編寫消息發佈者

咱們首先修改組織服務,以便每次添加、更新或刪除組織數據時,組織服務將向Kafka主題(topic)發佈一條消息,指示組織更改事件已經發生。

 

 

 

 

 

 

2、使用Spring Cloud構建微服務

從3個關鍵角色的視角解析:

  • 架構師:負責大局,瞭解應用程序如何分解爲單個微服務,以及微服務如何交互以交付解決方案。
  • 軟件開發人員:編寫代碼並交付微服務。
  • DevOps工程師:負責服務部署和管理,保障每一個環境一致性和可重複性。

2.一、架構師:設計微服務架構

架構師負責提供解決問題的工做模型,提供腳手架供開發人員構建代碼,使應用程序全部部件組合在一塊兒。

關鍵任務:

  • 分解業務問題
  • 創建服務粒度
  • 定義服務接口

2.1.一、分解業務問題

!!!暫時不是重點,這裏須要補充

3、使用Spring Cloud配置服務器控制配置

由於微服務架構中,各個服務是獨立開發獨立部署的,配置文件不可能像單體架構同樣,給每個服務代碼中放置一份配置文件來控制。

這裏提出了一種方案:經過Spring Cloud配置服務器,將配置做爲一個微服務部署起來,其它微服務經過訪問這個配置服務器來獲取配置,從而實現統一管理。

3.一、構建一個基於文件系統的Spring Cloud配置服務器

目的:

  編寫對應的配置文件,放置在Spring Cloud配置服務的代碼下;其它微服務訪問獲取配置時,讀取這些文件,並返回給服務的消費者。

一、建立Maven工程

  

  爲項目加上resources文件夾

  

  下一步,添加排除的文件類型

  

二、加入POM文件

<?xml version="1.0"?>
<project
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
    xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.4.4.RELEASE</version>
    </parent>

    <groupId>com.ltmicro</groupId>
    <artifactId>test01</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>test01</name>
    <url>http://maven.apache.org</url>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Camden.SR5</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-server</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

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

</project>
View Code

主要代碼解析:

  • parent:spring-boot-starter-parent,使用Spring Boot
  • dependencyManagement-spring-cloud-dependencies:設定使用的Spring Cloud的版本
  • dependency-spring-cloud-config-server、spring-cloud-starter-config:引入Spring Cloud的服務器和配置組件,版本自動根據上面的來
  • 注意,Spring Boot和Spring Cloud是相對獨立的2個項目,他們之間的版本兼容性請查看官方文檔

三、添加幾個配置文件

  

四、配置Spring Cloud Config啓動類

使用註解:

  @SpringBootApplication:代表Spring Boot應用程序(Spring Cloud是Spring Boot應用)

  @EnableConfigServer:代表是Spring Cloud Config服務

@SpringBootApplication
@EnableConfigServer
public class ConfigServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConfigServiceApplication.class, args);
    }
}

五、 配置程序

在src/main/resources文件夾下,添加一個application.yml文件(也能夠使用properties文件,語法不一樣)

server:
  port: 8888
spring:
  profiles:
    active: native
  cloud:
    config:
      server:
        native:
          searchLocations: classpath:config/,classpath:config/licensingservice
#         searchLocations: c:/Louis/Projects/JavaDefaultWorkPlace/test01/src/main/resources/config/licensingservice,
#                          c:/Louis/Projects/JavaDefaultWorkPlace/test01/src/main/resources/config/organizationservice
#         searchLocations: file:///Louis/Projects/JavaDefaultWorkPlace/test01/src/main/resources/config/licensingservice,
#                          file:///Louis/Projects/JavaDefaultWorkPlace/test01/src/main/resources/config/organizationservice

主要設置了這個程序的端口,以native(本地)的方式運行,而後配置了對應查找配置的目錄(包含了3種寫法)

六、運行程序

我這裏使用的是Maven 的spring-boot:run命令運行,也能夠直接項目右鍵運行

 

訪問如下地址都可以獲得對應配置

  • http://localhost:8888/licensingservice/default
  • http://localhost:8888/licensingservice/dev
  • http://localhost:8888/licensingservice/prod

3.二、將配置服務改造爲基於Git

3.1中,若是部署這個配置服務,要求部署環境必須有對應文件系統,這個有時候很不方便,例如須要用容器來部署。

因此Spring Cloud Config支持其餘後端存儲庫,如Git等,咱們這裏介紹如何基於Git來構建這個配置服務。

一、Git的使用

Git是一個開源分佈式版本管理系統,咱們能夠使用一些廠商提供的Git服務,如GitHub、Gitee(碼雲)

下面咱們使用碼雲來實現這個配置服務。

在碼雲中建立一個項目,而後建立文件夾,放入咱們在3.1中設計好的配置文件

 

二、如今咱們能夠刪掉項目中resources文件夾下的配置文件

三、修改application.yml文件

注意訪問碼雲地址的時候,uri應該填項目地址下面這個

server:
  port: 8888
spring:
  cloud:
    config:
      server:
        git:
          uri: https://gitee.com/Louisyzh/LTMicro.git
          searchPaths: /**
          username: 你的碼雲賬號
          password: 你的碼雲密碼

四、同3.1運行程序,能獲得相同的結果

3.三、客戶端(其它微服務)獲取配置

咱們的配置服務器通過上面已經搭建好了,是一個Spring Boot程序,打包成jar或者war包便可放到生產環境中運行。

下面介紹客戶端(其它微服務)若是獲取這些配置

一、從新建立一個Maven項目,並添加resources文件夾

(步驟同上,略)

二、修改POM文件

<?xml version="1.0"?>
<project
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
    xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.4.4.RELEASE</version>
    </parent>

    <groupId>com.ltmicro</groupId>
    <artifactId>test01</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>test01</name>
    <url>http://maven.apache.org</url>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Camden.SR5</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

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

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

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

</project>
View Code

主要代碼解析:

  • dependency-spring-boot-starter-web:使用Spring Boot的Web依賴
  • dependency-spring-cloud-config-client:使用Spring Cloud Config的客戶端依賴

三、配置application.yml文件

spring:
  application:
    name: licensingservice
  profiles:
    active: default
  cloud:
    config:
      uri: http://localhost:8888
  • spring.application.name:應用程序名稱,必須直接映射到Spring Cloud配置服務器中的目錄名稱,即服務器上必須有此名的一個目錄。
  • spring.profiles.active:代表程序運行哪一個profile,對應就會讀取配置服務器中對應的profile的配置。
  • spring.cloud.config.uri:Spring Cloud Config服務器的地址。

四、構建一些測試代碼

配置好後,程序運行時,至關於把從服務器獲得的配置,做爲本身的配置文件進行初始化。

咱們這裏經過一個控制器+@Value註解讀取配置屬性來進行測試

建立一個組件用來讀取配置屬性

@Component
public class ServiceConfig {
    @Value("${example.property}")
    private String exampleProperty;

    public String getExampleProperty() {
        return exampleProperty;
    }
}

建立一個控制器用來顯示屬性

@RestController
public class LicenseServiceController {
    @Autowired
    private ServiceConfig serviceConfig;
    @RequestMapping(value = "/config", method = RequestMethod.GET)
    public String getLicenses() {
        return serviceConfig.getExampleProperty();
    }
}

五、運行程序

運行程序,訪問對應地址http://localhost:8080/config,便可顯示出獲取到的屬性。

3.四、配置文件的加密解密(待補充)

若是咱們使用像Gitee、GitHub這樣的第三方存儲庫存放咱們的配置文件,若是敏感的信息都明文存放,會容易暴露。

因此這裏提供一個對敏感信息進行加密解密的方案。

 

 

 

 

 

 

 

 

 

 

 

布式調用架構、SOA架構上面演進的一種架構,也不是一種徹底創新的架構。

是爲了適應互聯網行業需求而出現的一種架構。

包括詞彙、工做方式、開發方式的演進。

目前尚未很標準的一種定義,這裏介紹只是一種實現方式。

架構演進:服務的時代背景:

  • 互聯網行業的快速發展:爲快不破,快速套現,佔領用戶
  • 敏捷方法論的深刻人心:短迭代,仍是爲了實現快
  • 容器虛擬化等DevOps技術的成熟化:持續交付,爲快提供支持
  • 傳統分佈式技術面臨的挑戰:大規模服務化需求,服務粒度較小,數量較多

討論集中式架構:

  集中式單塊(Monolithic)架構:如MVC

  

  優點

    • 易於開發/測試/部署
    • 具備必定程度的水平伸縮性,如放多塊上去,作一些分流、負載均衡等,都是能夠的

  劣勢

    • 維護成本與業務複雜度:當業務變複雜,東西都放在單塊裏面,就不方便了,維護增長了成本
    • 團隊規模與結構
    • 交互週期與管理
    • 可擴展性

討論SOA架構:

  主要問題:

    • SOA只是一個思想,只提供指導思想和原則,難以達成共識
    • 服務的粒度/依賴關係/通訊協議等如何肯定:除非使用第三方框架
    • 服務實例數量相對有限,難以應對大規模服務化場景:這個只是相對微服務而言
    • 企業級實施方案,須要自頂向下推行
    • 交互方式偏重於功能性團隊模式:須要不一樣功能的部門都齊備且合做和諧

互聯網公司的需求:

  應對大規模服務化需求:(具體的含義,或者實際的示例是什麼?須要補充)

    • 如何肯定服務拆分粒度
    • 如何下降服務之間依賴
    • 若是儘可能下降技術/架構之間差別化
    • 若是讓團隊Owning服務
    • 如何進行快速的服務開發和交付

 

微服務概述:

定義:(來自Martin Fowler)

  • 微服務架構是一種架構模式,它提倡將單一應用程序劃分紅一組小的服務,服務之間互相協調和配合,爲用戶提供最終價值
  • 每一個服務運行在其獨立的進程中,服務於服務器間採用輕量級通訊機制相互溝通(一般是基於HTTP的RESTful API)
  • 每一個服務都圍繞着業務進行構建,而且可以被獨立部署到生產/類生產環境
  • 儘可能避免同一的、集中式的服務管理機制,對具體的一個服務而已,應該根據業務上下文,選擇合適的語言、工具進行構建

特性:(「微」的含義)

  • 業務獨立
  • 團隊自主
  • 技術無關輕量級通訊
  • 交付獨立性:

    

  • 進程隔離

    服務間並不須要組合成組建去發佈,而是每一個服務都是獨立的;同時下降服務之間的耦合。

    

微服務與SOA的區別和聯繫:

  

 

架構師演化:

快速演進過程當中的架構師:

  • 關注服務之間的交互,而不是服務內部實現細節
  • 作出符合團隊目標的技術選擇,提供代碼模版
  • 考慮系統容許多少可變性,並可以快速適應變化
  • 完成>完美
  • 建設團隊

 

咱們的思路:

  • 從核心原理到基本實現
  • 從開發到運維
  • 從技術體系到案例
    • 技術體系:Spring Boot、Spring Cloud
    • 案例:建模、開發、測試、部署
  • 穿插相關模式和架構風格

 

微服務架構

實現策略:

  • 採用鬆散服務體系
  • 圍繞業務組件團隊
  • 融合技術多樣性
  • 確保業務數據獨立
  • 基本設施自動化

採用鬆散服務體系

  • 獨立開發/部署/維護
  • 技術無關的集成接口

  

圍繞業務組件團隊

  創建Feature Team

  

融合技術多樣性

  由於面向服務,可能遇到不少系統

  使用http這些技術無關的技術進行鏈接

  

確保業務數據獨立

  使用服務集成而不是數據集成(微服務中一個頗有用的特色)

  

基礎設施自動化(DevOps)

  • 服務部署
  • 健康診斷
  • 錯誤回滾
  • 日誌分析
  • 服務治理

 

 

實施問題:

  • 分佈式系統固有的複雜性
    • 性能
    • 可靠性
    • 異步
    • 數據一致性
  • 部署自動化與運維成本
    • 配置
    • 監控和報警
    • 日誌收集
    • 部署流水線
  • 組織架構
    • 康威定律與企業文化
    • 開發/運維角色變換
      • 開發承擔整個生命週期
      • 運維儘早開來服務部署
  • 服務間的依賴管理
    • 開發進程同步和管理
    • 服務通訊與集成
    • 服務依賴測試

切入點:

一切從零開始:

  • 明確服務建模和服務集成的方法
  • 尋找一個構建微服務的基本框架
  • 探索麪向發佈的全流程微服務實現方案
    • 技術體系
    • 工具
    • 策略

 

 

第三節:

 

服務建模:

微服務中的如何實現鬆耦合

  獨立部署單個服務並不須要修改其它服務

  儘量使用輕量級通訊方式進行服務集成

微服務中的高內聚

  只改一個地方就能夠發佈

  儘量明確領域邊界

  

不該該是技術驅動建模,應該業務驅動建模

利用上下文劃分界限

 

 

服務集成:

 

 

RPC,Messaging分別表明了2中風格的通訊方式

API網關,一個統一入口

 

 

Spring Boot入門

Spring 的一個子項目

簡介:

 

 

 

基本示例:

 

Spring Boot消息傳遞

點對點,發佈訂閱

這2種方式都比較常見,因此基本的第三方消息中間件都支持這2種模型

消息傳遞的協議、規範

一、JMS(Java Message Service)協議

實現:ActiveMQ

Spring組件:spring-jms 只要實現了JMS的組件,均可以經過Spring的spring-jms抽象來操做

二、AMQP(Advanced Message Queuing Protocol)協議

實現:RabbitMQ

Spring組件:同上,有spring-rabbit

三、以上2個協議是業界標準;其它協議,如kafka,自定的規範,自爲體系

 

JMS規範(選修課裏面講到)

AMQP規範:

 

2個示例

講解了2個組件在Sping BOot中的簡單實用

 

Spring Boot部署與Docker

SPring Boot部署

 

Docker雲部署

 

部署建模:把部署提高到建模等級

有點相似Maven的倉庫,例如把一個jar包做爲鏡像放到註冊中心中,那麼其餘鏡像均可以訪問到他

 

羣關鍵服務器、服務間的負載均衡以及將基礎設施分離到多個位置的

Hystrix

相關文章
相關標籤/搜索