SpringCloud
這個階段該如何學?
三層架構 + MVC
框架:
Spring IOC AOP
SpringBoot,新一代的JavaEE開發標準,自動裝配
模組化~ all in one,程式碼沒變化~
微服務架構四個核心問題?
1.服務很多,客戶端該怎麼訪問
2.這麼多服務?服務之間如何通訊?
3.這麼多服務?如何治理?
4.服務掛了怎麼辦?
解決方案:
SpringCloud 生態! SpringBoot
1.SpringCloud NetFlix 一站式解決方案!
api閘道器,zuul元件
Feign --- HttpClient --- Http通訊方式,同步,阻塞
服務註冊發現:Eureka
熔斷機制:Hystrix
。。。。。。
2.Apache Dubbo Zookeeper 半自動,需要整合別人的!
API:沒有,找第三方元件,或者自己實現
Dubbo
Zookeeper
沒有:藉助Hystrix
Dubbo這個方案並不完善~
3.SpringCloud Alibaba 一站式解決方案!更簡單
新概念:服務網格~Server Mesh
istio
萬變不離其宗
1.API
2.HTTP,RPC
3.註冊與發現
4.熔斷機制
注意:這部分內容是已經停止維護的SpringCloud Netflix,可與SpringCloud Alibaba進行對比,使用方法類似。
1、常見面試題
1.1、什麼是微服務?
1.2、微服務之間是如何獨立通訊的?
1.3、SpringCloud和Dubbo有哪些區別?
1.4、SpringBoot和SpringCloud,請你談談對他們的理解
1.5、什麼是服務熔斷?什麼是服務降級
1.6、微服務的優缺點分別是什麼?說下你在專案開發中遇到的坑(微服務缺點)
1.7、你所知道的微服務技術棧有哪些?請列舉一二
1.8、eureka和zookeeper都可以提供服務註冊與發現的功能,請說說兩個的區別?
...
2、微服務概述
2.1、什麼是微服務
什麼是微服務?微服務(Microservice Architecture)是近幾年流行的一種架構思想,關於它的概念很難一言以蔽之。
究竟什麼是微服務呢?我們在此引用ThoughtWorks公司的首席科學家Martin Fowler於2014年提出的一段話:
通常而言,微服務架構是一種架構模式,或者說是一種架構風格,他提倡將單一的應用程式劃分成一組小的服務,每個服務執行在其獨立的自己的程式內,服務之間互相協調,互相配置,為使用者提供最終價值。服務之間採用輕量級的通訊機制互相溝通,每個服務都圍繞著具體的業務進行構建,並且能夠被獨立的部署到生產環境中,另外,應儘量避免統一的、集中式的服務管理體制,對具體的一個服務而言,應根據業務上下文,選擇合適的語言,工具對其進行構建,可以有一個非常輕量級的集中式管理來協調這些服務,可以使用不同的語言來編寫服務,也可以使用不同的資料儲存;
可能有人覺得官方的話太過生澀,我們從技術維度來理解下:
微服務化的核心就是將傳統的一站式應用,根據業務拆分成一個一個的服務,徹底地去耦合,每一個微服務提供單個業務功能的服務,一個業務做一件事情,從技術角度看就是一種小而獨立的處理過程,類似程式的概念,能夠自行單獨啟動或銷燬,擁有自己獨立的資料庫。
2.2、微服務與微服務架構
微服務
強調的是服務的大小,他關注的事件某一個點,是具體解決某一個問題/提供落地對應服務的一個服務應用,狹義的看,可以看作是IDEA中的一個個微服務工程,或者Module
IDEA 工具裡面使用Maven開發的一個個獨立的小Module,它具體是使用SpringBoot開發的一個小模組,專業的事情交給專業的模組來做,一個模組就做這一件事情
強調的是一個個的個體,每個個體完成一個具體的任務或者功能!
微服務架構
一種新的架構形式,Martin Fowler,2014年提出
微服務架構是一種架構模式,它提倡將單一應用程式劃分成一組小的服務,服務之間互相協調,互相配合,為使用者提供最終價值。每個服務執行在其獨立的程式中,服務與服務間採用輕量級的通訊機制互相協作,每個服務都圍繞著具體的業務進行構建,並且能夠被獨立的部署到生產環境中,另外,應儘量避免統一的、集中式的服務管理機制,對具體的一個服務而言,應根據業務上下文,選擇合適的語言、工具對其進行構建。
2.3、微服務優缺點
優點
- 單一職責原則
- 每個服務足夠內聚,足夠小,程式碼容易理解,這樣能聚焦一個指定的業務功能或業務需求;
- 開發簡單,開發效率提高,一個服務可能就是專一的只幹一件事;
- 微服務能夠被小團隊單獨開發,這個小團隊是2~5人的開發人員組成;
- 微服務是鬆耦合的,是有功能意義的服務,無論是在開發階段或部署階段都是獨立的;
- 微服務能使用不同的語言開發;
- 易於和第三方整合,微服務允許容易且靈活的方式整合自動部署,通過持續整合工具,如jenkins,Hudson,bamboo;
- 微服務易於被一個開發人員理解、修改和維護,這樣小團隊能夠更關注自己的工作成果。無需通過合作才能體現價值;
- 微服務允許你利用融合最新技術
- 微服務只是業務邏輯的程式碼,不會和HTML、CSS或其他介面混合
- 每個微服務都有自己的儲存能力,可以有自己的資料庫,也可以有統一的資料庫
缺點:
- 開發人員要處理分散式系統的複雜性
- 多服務運維難度,隨著服務的增加,運維的壓力也在增大
- 系統部署依賴
- 服務間通訊成本
- 資料一致性
- 系統整合測試
- 效能監控...
2.4、微服務技術棧有哪些?
微服務條目 | 落地技術 |
---|---|
服務開發 | SpringBoot,Spring,SpringMVC |
服務配置與管理 | Netflix公司的Archaius、阿里的Diamond |
服務註冊與發現 | Eureka、Consul、Zookeeper等 |
服務呼叫 | Rest、RPC、gRPC |
服務熔斷器 | Hystrix、Envoy等 |
負載均衡 | Ribbon、Nginx等 |
服務介面呼叫(客戶端呼叫服務的簡化工具) | Feign等 |
訊息佇列 | Kafka、RabbitMQ、ActiveMQ等 |
服務配置中心管理 | SpringCloudConfig、Chef等 |
服務路由(API閘道器) | Zuul等 |
服務監控 | Zabbix、Nagios、Metrics、Specatator等 |
全鏈路追蹤 | Zipkin、Brave、Dapper等 |
服務部署 | Docker、OpenStack、Kubernetes等 |
資料流操作開發包 | SpringCloud Stream(封裝與Redis,Rabbit,Kafka等傳送接收訊息) |
事件訊息匯流排 | SpringCloud Bus |
2.5、為什麼選擇SpringCloud作為微服務架構
1、選型依據
- 整體解決方案和框架成熟度
- 社群熱度
- 可維護性
- 學習曲線
2、當前各大IT公司用的微服務架構有哪些?
- 阿里:dubbo + HFS
- 京東:JSF
- 新浪:Motan
- 噹噹網 DubboX
- ......
3、各微服務框架對比
功能點/服務框架 | Netflix/SpringCloud | Motan | gRPC | Thrift | Dubbo/DubboX |
---|---|---|---|---|---|
功能定位 | 完整的微服務框架 | RPC框架,但整合了ZK或Consul,實現叢集環境的基本服務註冊/發現 | RPC框架 | RPC框架 | 服務框架 |
支援Rest | 是,Ribbon支援多種可插拔的序列化選擇 | 否 | 否 | 否 | 否 |
支援RPC | 否 | 是(Hession2) | 是 | 是 | 是 |
支援多語言 | 是(Rest形式)? | 否 | 是 | 是 | 否 |
負載均衡 | 是(服務端zuul+客戶端Ribbon),zuul-服務,動態路由,雲端負載均衡,Eureka(針對中間層伺服器) | 是(客戶端) | 否 | 否 | 是(客戶端) |
配置服務 | Netflix Archaius,SpringCloudConfig Server集中配置 | 是(zookeeper提供) | 否 | 否 | 否 |
服務呼叫鏈監控 | 是(zuul),zuul提供邊緣服務,API閘道器 | 否 | 否 | 否 | 否 |
高可用/容錯 | 是(服務端Hystrix+客戶端Ribbon) | 是(客戶端) | 否 | 否 | 是(客戶端) |
典型應用案例 | Netflix | Sina | |||
社群活躍程度 | 高 | 一般 | 高 | 一般 | 2017年後重新開始維護,之前中斷了5年 |
學習難度 | 中等 | 低 | 高 | 高 | 低 |
文件豐富 | 高 | 一般 | 一般 | 一般 | 高 |
其他 | SpringCloud Bus為我們的應用程式帶來了更多管理端點 | 支援降級 | Netflix內部在開發整合gRPC | IDL定義 | 實踐的公司比較多 |
3、SpringCloud入門概述
3.1、SpringCloud是什麼
SpringCloud官網:https://spring.io/
SpringCloud,基於SpringBoot提供了一套微服務解決方案,包括服務註冊與發現、配置中心、全鏈路監控、服務閘道器、負載均衡、熔斷器等元件,除了基於NetFlix的開源元件做高度抽象封裝之外,還有一些選型中立的開源元件。
SpringCloud利用SpringBoot的開發便利性,巧妙地簡化了分散式系統基礎設施的開發,SpringCloud為開發人員提供了快速構建分散式系統的一些工具,包括配置管理、服務發現、熔斷器、路由、微代理、事件匯流排、全域性鎖、決策競選、分散式會話等等,他們都可以用SpringBoot的開發風格做到一鍵啟動和部署。
SpringCloud並沒有重複造輪子,它只是將目前各家公司開發的比較成熟,經得起實際考驗的服務框架組合起來,通過SpringBoot風格進行再封裝,遮蔽掉了複雜的配置和實現原理,最終給開發者留出了一套簡單易懂,易部署和易維護的分散式系統開發工具包。
SpringCloud是分散式微服務架構下的一站式解決方案,是各個微服務架構落地技術的集合體,俗稱微服務全家桶。
3.2、SpringCloud和SpringBoot關係
- SpringBoot專注於快速方便的開發單個個體微服務。
- SpringCloud是關注全域性的微服務協調整理治理框架,它將SpringBoot開發的一個個單體微服務整合並管理起來,為各個微服務之間提供:配置管理、服務發現、熔斷器、路由、微代理、事件匯流排、全域性鎖、決策競選、分散式會話等等整合服務。
- SpringBoot可以離開SpringCloud獨立使用,開發專案,但是SpringCloud離不開SpringBoot,屬於依賴關係
- SpringBoot專注於快速、方便的開發單個個體微服務,SpringCloud關注全域性的服務治理框架
3.3、Dubbo和SpringCloud技術選型
1、分散式+服務治理Dubbo
目前成熟的網際網路架構:應用服務化拆分 + 訊息中介軟體
2、Dubbo和SpringCloud對比
可以看一下社群活躍度
https://github.com/spring-cloud
結果:
Dubbo | Spring | |
---|---|---|
服務註冊中心 | Zookeeper | Spring Cloud NetFlix Eureka |
服務呼叫方式 | RPC | REST API |
服務監控 | Dubbo-monitor | Spring Boot Admin |
熔斷器 | 不完善 | Spring Cloud Netflix Hystrix |
服務閘道器 | 無 | Spring Cloud Netflix Zuul |
分散式配置 | 無 | Spring Cloud Config |
服務跟蹤 | 無 | Spring Cloud Sleuth |
訊息匯流排 | 無 | Spring Cloud Bus |
資料流 | 無 | Spring Cloud Stream |
批量任務 | 無 | Spring Cloud Task |
最大區別:SpringCloud拋棄了Dubbo的RPC通訊,採用的是基於HTTP的REST方式。
嚴格來說,這兩種方式各有優劣。雖然從一定程度上來說,後者犧牲了服務呼叫的效能,但也避免了上面提到的原生RPC帶來的問題。而且REST相比RPC更為靈活,服務提供方和呼叫方的依賴只依靠一紙契約,不存在程式碼級別的強依賴,這在強調快速演化的微服務環境下,顯得更加合適。
品牌機與組裝機的區別
很明顯,SpringCloud的功能比Dubbo更加強大,涵蓋面更廣,而且作為Spring的拳頭專案,它也能夠與Spring Framework、Spring Boot、Spring Data、Spring Batch等其他Spring專案完美融合,這些對於微服務而言是至關重要的。使用Dubbo構建的微服務架構就像組裝電腦,各環節我們的選擇自由度很高,但是最終結果很有可能因為一條記憶體質量不行就點不亮了,總是讓人不怎麼放心,但是如果你是一名高手,那這些都不是問題;而SpringCloud就像品牌機,在Spring Source的整合下,做了大量的相容性測試,保證了機器擁有更高的穩定性,但是如果要再使用非原裝元件外的東西,就需要對其基礎有足夠的瞭解。
社群支援與更新力度
最為重要的是,Dubbo停止了5年左右的更新,雖然2017.7重啟了。對於技術發展的新需求,需要由開發者自行擴充升級(比如噹噹網弄出了DubboX),這對於很多想要採用微服務架構的中小軟體組織,顯然是不太合適的,中小公司沒有這麼強大的技術能力去修改Dubbo原始碼+周邊的一整套解決方案,並不是每一個公司都有阿里的大牛+真實的線上生產環境測試過。
設計模式 + 微服務拆分思想
總結:
曾風靡國內的開源RPC服務框架Dubbo在重啟維護後,令許多使用者為之雀躍,但同時,也迎來了一些質疑的聲音。網際網路技術發展迅速,Dubbo是否還能跟上時代?Dubbo與SpringCloud相比又有何優勢和差異?是否會有相關舉措保證Dubbo的後續更新頻率?
人物:Dubbo重啟維護開發的劉軍,主要負責人之一
劉軍,阿里巴巴中介軟體高階研發工程師,主導了Dubbo重啟維護以後的幾個發版計劃,專注於高效能RPC框架和微服務相關領域。曾負責網易考拉RPC框架的研發及指導在內部使用,參與了服務治理平臺、分散式跟蹤系統、分散式一致性框架等從無到有的設計與開發過程。
解決的問題域不一樣:Dubbo的定位是一款RPC框架,SpringCloud的目標是微服務架構下的一站式解決方案。
3.4、SpringCloud能幹嘛
- Distributed/versioned configuration (分散式/版本控制配置)
- Service registration and discovery (服務註冊與發現)
- Routing (路由)
- Service-to-service calls (服務到服務的呼叫)
- Load balancing (負載均衡配置)
- Circuit Breakers (熔斷器)
- Distributed messaging (分散式訊息管理)
- ......
3.5、SpringCloud在哪下
官網:https://spring.io/projects/spring-cloud
這玩意的版本號有點特別
Spring Cloud是一個由眾多子專案組成的大型綜合專案,每個子專案有不同的發行節奏,都維護著自己的釋出版本號。Spring Cloud通過一個資源清單BOM(Bill of Materials)來管理每個版本的子專案清單。為避免與子專案的釋出號混淆,所有沒有采用版本號的方式,而是通過命名的方式。
這些版本名稱的命名方式採用了倫敦地鐵站的名稱,同時根據字母表的順序來對應版本時間順序,比如:最早的Release版本:Angel,第二個Release版本:Brixton,然後是Camden、Dalston、Edgware,目前是按時間來對應版本。
參考書:
- https://springcloud.cc/spring-cloud-netflix.html
- 中文API文件:https://springcloud.cc/spring-cloud-dalston.html
- SpringCloud中國社群 http://springcloud.cn/
- SpringCloud中文網 https://springcloud.cc
4、SpringCloud專案學習
4.1、總體介紹
- 我們會使用一個Dept部門模組做一個微服務通用案例Consumer消費者(Client)通過REST呼叫Provider提供者(Server)提供的服務。
- 回憶Spring,SpringMVC,MyBatis等以往學習的知識。。。
- Maven的分包分模組架構複習
一個簡單的Maven模組結構是這樣的:
--- app-parent:一個父專案(app-parent)聚合很多子專案(app-util,app-dao,app-web...)
|-- pom.xml
|
|-- app-core
||----pom.xml
|
|-- app-web
||----pom.xml
......
一個父工程帶著多個子Module子模組
MicroServiceCloud父工程(Project)下初次帶著三個子模組(Module)
- microservicecloud-api 【封裝的整體entity/介面/公共配置等】
- microservicecloud-provider-dept-8001 【服務提供者】
- microservicecloud-consumer-dept-80 【服務消費者】
動手開幹!
4.2、SpringCloud版本選擇
https://start.spring.io/actuator/info
SpringCloud中各種元件技術的選擇:
紅叉表示停更,值得學習但不推薦使用的技術,本學習文件主要還是學習SpringCloud Netflix
越下邊是使用人數最多的元件技術
圖片資料是2020年1月份
4.3、Rest學習環境搭建:服務提供者
-
新建一個普通maven父專案springcloud,匯入pom依賴
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.kuang</groupId> <artifactId>springcloud</artifactId> <version>1.0-SNAPSHOT</version> <modules> <module>springcloud-api</module> <module>springcloud-provider-dept-8001</module> </modules> <!--打包方式 pom--> <packaging>pom</packaging> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <junit.version>4.13.2</junit.version> <lombok.version>1.18.22</lombok.version> </properties> <dependencyManagement> <dependencies> <!--SpringCloud的依賴--> <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-dependencies --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>2021.0.2</version> <type>pom</type> <scope>import</scope> </dependency> <!--SpringBoot--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>2.6.7</version> <type>pom</type> <scope>import</scope> </dependency> <!--資料庫--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.28</version> </dependency> <!--資料來源--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.9</version> </dependency> <!--SpringBoot啟動器--> <!--mybatis啟動器--> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.2</version> </dependency> <!--日誌和測試--> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> <version>1.2.11</version> </dependency> <!--junit--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>${junit.version}</version> </dependency> <!--lombok--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${lombok.version}</version> </dependency> <!--Log4j--> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> </dependencies> </dependencyManagement> </project>
-
在父專案中新建一個子模組springcloud-api,匯入子模組依賴
<?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>springcloud</artifactId> <groupId>com.kuang</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>springcloud-api</artifactId> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties> <!--當前的Module自己需要的依賴,如果父依賴中已經配置了版本,這裡就不用寫了--> <dependencies> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> </dependencies> </project>
-
新建資料庫db01,新建表dept如下圖
-
新建實體類Dept與資料庫對應
@Data @NoArgsConstructor @Accessors(chain = true) //鏈式寫法 public class Dept implements Serializable { //Dept 實體類, orm mysql--dept 類表關係對映 private Long deptno; //主鍵 private String dname; //這個資料是存在哪個資料庫的欄位~ 微服務,一個服務對應一個資料庫,同一個資訊可能存在不同的資料庫 private String db_source; public Dept(String dname){ //構造器 this.dname = dname; } /* 鏈式寫法: Dept dept = new Dept(); dept.setDeptNo(11).setDname('ssss').setDb_source('001'); */ }
-
在父專案中新建一個子模組springcloud-provider-dept-8001,匯入依賴
<?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>springcloud</artifactId> <groupId>com.kuang</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>springcloud-provider-dept-8001</artifactId> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties> <dependencies> <!--我們需要拿到實體類,所以要配置api module--> <dependency> <groupId>com.kuang</groupId> <artifactId>springcloud-api</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <!--junit--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> </dependency> <!--test--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-test</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--jetty--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jetty</artifactId> </dependency> <!--熱部署工具--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> </dependency> </dependencies> </project>
-
新建spring配置檔案,mybaits配置檔案,mapper配置檔案結構如下
-
配置檔案內容分別如下
application.yml
server: port: 8001 #mybatis配置 mybatis: type-aliases-package: com.kuang.springcloud.pojo config-location: classpath:mybatis/mybatis-config.xml mapper-locations: classpath:mybatis/mapper/*.xml #spring的配置 spring: application: name: springcloud-provider-dept datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/db01?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT username: root password: 123456
mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <settings> <!--開啟二級快取--> <setting name="cacheEnabled" value="true"/> </settings> </configuration>
DeptMapper.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.kuang.springcloud.dao.DeptDao"> <insert id="addDept" parameterType="Dept"> insert into db01.dept (dname, db_source) values (#{dname},DATABASE()); </insert> <select id="queryDeptById" resultType="Dept" parameterType="Long"> select * from db01.dept where deptno = #{deptno}; </select> <select id="queryAll" resultType="Dept"> select * from db01.dept; </select> </mapper>
-
新建mapper(dao)層,service層,controller層結構如下
-
介面及class檔案內容分別如下
DeptDao
@Mapper @Repository public interface DeptDao { public boolean addDept(Dept dept); public Dept queryDeptById(Long id); public List<Dept> queryAll(); }
DeptService
public interface DeptService { public boolean addDept(Dept dept); public Dept queryDeptById(Long id); public List<Dept> queryAll(); }
DeptServiceImpl
@Service public class DeptServiceImpl implements DeptService{ @Autowired private DeptDao deptDao; @Override public boolean addDept(Dept dept) { return deptDao.addDept(dept); } @Override public Dept queryDeptById(Long id) { return deptDao.queryDeptById(id); } @Override public List<Dept> queryAll() { return deptDao.queryAll(); } }
DeptController
//提供Restful服務! @RestController public class DeptController { @Autowired private DeptService deptService; @PostMapping("/dept/add") public boolean addDept(Dept dept){ return deptService.addDept(dept); } @GetMapping("/dept/get/{id}") public Dept get(@PathVariable("id") Long id){ return deptService.queryDeptById(id); } @GetMapping("/dept/list") public List<Dept> queryAll(){ return deptService.queryAll(); } }
-
編寫啟動類,與controller,service,dao資料夾位於同一級
//啟動類 @SpringBootApplication public class DeptProvider_8001 { public static void main(String[] args) { SpringApplication.run(DeptProvider_8001.class,args); } }
4.4、Rest學習環境搭建:服務消費者
-
在父專案中新建一個子模組springcloud-consumer-dept-80,匯入依賴
<?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>springcloud</artifactId> <groupId>com.kuang</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>springcloud-consumer-dept-80</artifactId> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties> <!--實體類+web--> <dependencies> <dependency> <groupId>com.kuang</groupId> <artifactId>springcloud-api</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--熱部署工具--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> </dependency> </dependencies> </project>
-
新建配置檔案application.yml
server: port: 80
-
新建一個配置類ConfigBean.java,獲取RestTemplate並注入Bean中
@Configuration public class ConfigBean { //@Configuration -- spring applicationContext.xml @Bean public RestTemplate getRestTemplate(){ return new RestTemplate(); } }
-
新建一個消費者介面DeptConsumerController.java
@RestController public class DeptConsumerController { //理解:消費者,不應該有service層~ //RestFul RestTemplate... 供我們直接呼叫就可以了!註冊到Spring中 //(url, 實體: Map, Class<T> responseType) @Autowired private RestTemplate restTemplate;//提供多種便捷訪問遠端http服務的方法,簡單的restful服務模板~ private static final String REST_URL_PREFIX = "http://localhost:8001"; @RequestMapping("/consumer/dept/add") public boolean add(Dept dept){ return restTemplate.postForObject(REST_URL_PREFIX+"/dept/add",dept,Boolean.class); } @RequestMapping("/consumer/dept/get/{id}") public Dept get(@PathVariable("id") Long id){ return restTemplate.getForObject(REST_URL_PREFIX+"/dept/get/"+id,Dept.class); } @RequestMapping("/consumer/dept/list") public List<Dept> list(){ return restTemplate.getForObject(REST_URL_PREFIX+"/dept/list",List.class); } }
-
新建一個消費者啟動類DeptConsumer_80.java
@SpringBootApplication public class DeptConsumer_80 { public static void main(String[] args) { SpringApplication.run(DeptConsumer_80.class,args); } }
4.5、SpringCloud中不同技術使用的大體流程
1、匯入依賴
2、編寫配置檔案
3、開啟這個功能 @Enable...
4、配置類
5、Eureka服務註冊與發現
5.1、什麼是Eureka
- Eureka:怎麼讀?
- Netflix在設計Eureka時,遵循的就是AP原則
- Eureka是Netflix的一個子模組,也就是核心模組之一。Eureka是一個基於REST的服務,用於定位服務,以實現雲端中間層服務發現和故障轉移,服務註冊與發現對於微服務來說是非常重要的,有了服務註冊與發現,只需要使用服務的識別符號,就可以訪問到服務,而不需要修改服務呼叫的配置檔案了,功能類似於Dubbo的註冊中心,比如Zookeeper;
5.2、原理講解
- Eureka的基本結構
- SpringCloud封裝了NetFlix公司開發的Eureka模組來實現服務註冊和發現(對比Zookeeper)
- Eureka採用了C-S的架構設計,EurekaServer作為服務註冊功能的伺服器,他是服務註冊中心
- 而系統中的其他微服務。使用Eureka的客戶端連線到EurekaServer並維持心跳連線。這樣系統的維護人員就可以通過EurekaServer來監控系統中各個微服務是否正常執行,SpringCloud的一些其他模組(比如Zuul)就可以通過EurekaServer來發現系統中的其他微服務,並執行相關的邏輯操作;
- 和Dubbo架構對比
- Eureka包含兩個元件:Eureka Server和Eureka Client。
- Eureka Server提供服務註冊服務,各個節點啟動後,會在Eureka Server中進行註冊,這樣Eureka Server中的服務登錄檔中將會儲存所有可用服務節點的資訊,服務節點的資訊可以在介面中直觀的看到。
- Eureka Client是一個Java客戶端,用於簡化Eureka Server的互動,客戶端同時也具備一個內建的,使用輪詢負載演算法的負載均衡器。在應用啟動後,將會向Eureka Server傳送心跳(預設週期為30秒)。如果Eureka Server在多個心跳週期內沒有接收到某個節點的心跳,Eureka Server將會從服務登錄檔中把這個服務節點移除掉(預設週期為90秒)
- 三大角色
- Eureka Server:提供服務的註冊與發現。zookeeper
- Service Provider:將自身服務註冊到Eureka中,從而使消費方能夠找到。
- Service Consumer:服務消費方從Eureka中獲取註冊服務列表,從而找到消費服務。
自我保護機制:好死不如賴活著
- 當EurekaServer節點在短時間內丟失過多客戶端時(可能發生了網路分割槽故障),那麼這個節點就會進入自我保護模式。一旦進入該模式,EurekaServer就會保護服務登錄檔中的資訊,不再刪除服務登錄檔中的資料(也就是不會登出任何微服務)。當網路故障恢復後,EurekaServer節點會自動退出自我保護模式。
5.3、Eureka實現程式碼
-
新建子模組springcloud-eureka-7001,匯入依賴
<!--EUREKA依賴--> <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> <version>1.4.7.RELEASE</version> </dependency> <!--actuator完善監控資訊--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
-
新建配置檔案application.yml
server: port: 7001 #Eureka配置 eureka: instance: hostname: localhost #Eureka服務端的例項名稱 client: register-with-eureka: false #表示是否向eureka註冊中心註冊自己 fetch-registry: false #fetch-registry如果為false,則表示自己為註冊中心 service-url: #監控頁面~ defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
-
新建啟動類EurekaServer_7001.java,設定EurakeServer註解
//啟動之後,訪問http://localhost:7001/ @SpringBootApplication @EnableEurekaServer //EnableEurekaServer 服務端的啟動類,可以接受別人註冊進來~ public class EurekaServer_7001 { public static void main(String[] args) { SpringApplication.run(EurekaServer_7001.class,args); } }
-
在父依賴中新增build程式碼,解決info無效的問題
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <executions> <execution> <goals> <goal>build-info</goal> </goals> </execution> </executions> </plugin> </plugins> </build>
-
在服務者模組springcloud-provider-dept-8001中匯入Eureka依賴
<!--EUREKA依賴--> <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> <version>1.4.7.RELEASE</version> </dependency> <!--actuator完善監控資訊--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
-
新建配置檔案application.yml
server: port: 8001 #mybatis配置 mybatis: type-aliases-package: com.kuang.springcloud.pojo config-location: classpath:mybatis/mybatis-config.xml mapper-locations: classpath:mybatis/mapper/*.xml #spring的配置 spring: application: name: springcloud-provider-dept datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/db01?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT username: root password: 123456 #Eureka的配置,服務註冊到哪裡 eureka: client: service-url: defaultZone: http://localhost:7001/eureka/ instance: instance-id: springcloud-provider-dept-8001 #修改eureka上的預設描述資訊! hostname: localhost #修改主機名稱 #解決url過濾問題 management: endpoints: web: exposure: include: "*" #info配置,無效,建立單獨的build-info.properties解決,注意格式 info: app-name: kuangshen-springcloud company-name: blog.kuangstudy.com
-
新建info配置檔案META-INF/build-info.properties
build.app_name=kuangshen-springcloud build.company_name=blog.kuangstudy.com
-
在服務者的controller檔案DeptController.java中配置新的方法獲取註冊的微服務的具體資訊
//提供Restful服務! @RestController public class DeptController { @Autowired private DeptService deptService; //獲取一些配置的資訊,得到具體的微服務! @Autowired private DiscoveryClient client; @PostMapping("/dept/add") public boolean addDept(Dept dept){ return deptService.addDept(dept); } @GetMapping("/dept/get/{id}") public Dept get(@PathVariable("id") Long id){ return deptService.queryDeptById(id); } @GetMapping("/dept/list") public List<Dept> queryAll(){ return deptService.queryAll(); } //註冊進來的微服務,獲取一些訊息 @GetMapping("/dept/discovery") public Object Discovery(){ //獲取微服務列表的清單 List<String> services = client.getServices(); System.out.println("discovery=>services:"+services); //得到一個具體的微服務資訊,通過具體的微服務id,applicationName List<ServiceInstance> instances = client.getInstances("SPRINGCLOUD-PROVIDER-DEPT"); for (ServiceInstance instance : instances) { System.out.println( instance.getHost()+"\t"+ instance.getPort()+"\t"+ instance.getUri()+"\t"+ instance.getServiceId() ); } return this.client; } }
-
在服務者的啟動類中新增EurekaClient及DiscoveryClient註解
//啟動類 @SpringBootApplication @EnableEurekaClient //在服務啟動後自動註冊到Eureka中! @EnableDiscoveryClient //服務發現~ public class DeptProvider_8001 { public static void main(String[] args) { SpringApplication.run(DeptProvider_8001.class,args); } }
-
先執行springcloud-eureka-7001,再執行springcloud-provider-dept-8001,主介面如下,可點選檢視服務者相關資訊
-
訪問http://localhost:7001/dept/discovery可檢視微服務的詳細資訊,終端會輸出詳細資訊
5.3、Eureka叢集環境配置
結構示意圖:
程式碼演示:
-
在C盤下修改介面配置,進入資料夾C:\Windows\System32\drivers\etc\hosts,在檔案末尾新增不同url對映到localhost介面
-
新建兩個子模組springcloud-eureka-7002,springcloud-eureka-7003,結構與springcloud-eureka-7001類似
啟動類
//啟動之後,訪問http://localhost:7001/ @SpringBootApplication @EnableEurekaServer //EnableEurekaServer 服務端的啟動類,可以接受別人註冊進來~ public class EurekaServer_7001 { public static void main(String[] args) { SpringApplication.run(EurekaServer_7001.class,args); } } ======================================= //啟動之後,訪問http://localhost:7002/ @SpringBootApplication @EnableEurekaServer //EnableEurekaServer 服務端的啟動類,可以接受別人註冊進來~ public class EurekaServer_7002 { public static void main(String[] args) { SpringApplication.run(EurekaServer_7002.class,args); } } ======================================= //啟動之後,訪問http://localhost:7003/ @SpringBootApplication @EnableEurekaServer //EnableEurekaServer 服務端的啟動類,可以接受別人註冊進來~ public class EurekaServer_7003 { public static void main(String[] args) { SpringApplication.run(EurekaServer_7003.class,args); } }
配置檔案
server: port: 7001 #Eureka配置 eureka: instance: hostname: eureka7001.com #Eureka服務端的例項名稱 client: register-with-eureka: false #表示是否向eureka註冊中心註冊自己 fetch-registry: false #fetch-registry如果為false,則表示自己為註冊中心 service-url: #監控頁面~ defaultZone: http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/ ####################################### server: port: 7002 #Eureka配置 eureka: instance: hostname: eureka7002.com #Eureka服務端的例項名稱 client: register-with-eureka: false #表示是否向eureka註冊中心註冊自己 fetch-registry: false #fetch-registry如果為false,則表示自己為註冊中心 service-url: #監控頁面~ defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7003.com:7003/eureka/ ####################################### server: port: 7003 #Eureka配置 eureka: instance: hostname: eureka7003.com #Eureka服務端的例項名稱 client: register-with-eureka: false #表示是否向eureka註冊中心註冊自己 fetch-registry: false #fetch-registry如果為false,則表示自己為註冊中心 service-url: #監控頁面~ defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
依賴與springcloud-eureka-7001相同
<!--導包--> <dependencies> <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka-server --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka-server</artifactId> <version>1.4.7.RELEASE</version> </dependency> </dependencies>
-
修改服務提供者的配置檔案,使其向三個Eureka釋出
server: port: 8001 #mybatis配置 mybatis: type-aliases-package: com.kuang.springcloud.pojo config-location: classpath:mybatis/mybatis-config.xml mapper-locations: classpath:mybatis/mapper/*.xml #spring的配置 spring: application: name: springcloud-provider-dept datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/db01?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT username: root password: 123456 #Eureka的配置,服務註冊到哪裡 eureka: client: service-url: defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/ instance: instance-id: springcloud-provider-dept-8001 #修改eureka上的預設描述資訊! hostname: localhost #修改主機名稱 #解決url過濾問題 management: endpoints: web: exposure: include: "*" #info配置,無效,建立單獨的build-info.properties解決,注意格式 info: app-name: kuangshen-springcloud company-name: blog.kuangstudy.com
-
執行三個Eureka及一個服務提供者,其中兩個Eureka介面如下,可見每個Eureka都可見其他兩個Eureka
5.4、對比Zookeeper
回顧CAP原則
RDBMS(Mysql、Oracle、sqlServer)===> ACID
NoSQL(redis、mongdb)===> CAP
ACID是什麼?
- A(Atomicity)原子性
- C(Consistency)一致性
- I(Isolation)隔離性
- D(Durability)永續性
CAP是什麼?
- C(Consistency)強一致性
- A(Availability)可用性
- P(Partition tolerance)分割槽容錯性
CAP的三進二:CA、AP、CP(無法保證三個都滿足)
CAP理論的核心
- 一個分散式系統不可能同時很好的滿足一致性、可用性和分割槽容錯性這三個需求
- 根據CAP原理,將NoSQL資料庫分成了滿足CA原則,滿足CP原則和滿足AP原則三大類:
- CA:單點叢集,滿足一致性,可用的系統,通常可擴充套件性較差(P分割槽容錯性差)
- CP:滿足一致性,分割槽容錯性的系統,通常效能不是特別高(A可用性差)
- AP:滿足可用性,分割槽容錯性的系統,通常可能對一致性要求低一些(C一致性差)
作為服務註冊中心,Eureka比Zookeeper好在哪裡?
著名的CAP理論指出,一個分散式系統不可能同時滿足C(一致性)、A(可用性)、P(容錯性)。
由於分割槽容錯性P在分散式系統中是必須要保證的,因此我們只能在A和C之間進行權衡。
- Zookeeper保證的是CP;
- Eureka保證的是AP;
Zookeeper保證的是CP
當向註冊中心查詢服務列表時,我們可以容忍註冊中心返回的是幾分鐘以前的註冊資訊,但不能接受服務直接down掉不可用。也就是說,服務註冊功能對可用性的要求是高於一致性的。但是zk會出現這樣一種情況,當master節點因為網路故障與其他節點失去聯絡時,剩餘節點會重新進行leader選舉。問題在於,選舉leader的時間太長,30~120s,且選舉期間整個zk叢集都是不可用的,這就導致在選舉期間註冊服務癱瘓。在雲部署的環境下,因為網路問題使得zk叢集失去master節點是較大概率會發生的事件,雖然服務最終能夠恢復,但是漫長的選舉時間導致的註冊長期不可用是不能容忍的。
Eureka保證的是AP
Eureka看明白了這一點,因此在設計時就優先保證可用性。Eureka各個節點都是平等的,幾個節點掛掉不會影響正常節點的工作,剩餘的節點依然可以提供註冊和查詢服務。而Eureka的客戶端在向某個Eureka註冊時,如果發現連線失敗,則會自動切換至其他節點,只要有一臺Eureka還在,就能保證註冊服務的可用性,只不過查到的
資訊可能不是最新的,除此之外,Eureka還有一種自我保護機制,如果在15分鐘內超過85%的節點都沒有正常的心跳,那麼Eureka就認為客戶端與註冊中心出現了網路故障,此時會出現以下幾種情況:
- Eureka不再從註冊列表中移除因為長時間沒收到心跳而應該過期的服務
- Eureka仍然能夠接受新服務的註冊和查詢請求,但是不會被同步到其他節點上(即保證當前節點依然可用)
- 當網路穩定時,當前例項新的註冊資訊會被同步到其他節點中
因此,Eureka可以很好地應對因網路故障導致部分節點失去聯絡的情況,而不會像zookeeper那樣使整個註冊服務癱瘓。
6、Ribbon
spring-cloud-starter-netflix-eureka-client中已經內建了Ribbon,再匯入Ribbon依賴會衝突。
6.1、Ribbon簡介
Spring Cloud Ribbon是基於Netflix Ribbon實現的一套客戶端負載均衡的工具。
簡單地說,Ribbon是Netflix釋出的開源專案,主要功能是提供客戶端的軟體負載均衡演算法和服務呼叫。Ribbon客戶端元件提供一系列完善的配置項如連線超時,重試等。簡單地說,就是在配置檔案中列出Load Balancer(簡稱LB)後面所有機器,Ribbon會自動的幫助你基於某種規則(如簡單輪詢,隨機連線等)去連線這些機器。我們很容易使用Ribbon實現自定義的負載均衡演算法
GitHub文件地址:https://github.com/Netflix/ribbon
注意:Ribbon現在已經進入了維護模式
Ribbon的職能:LB負載均衡,簡單地說就是將使用者的請求平均分攤到多個服務上,從而達到系統的HA(高可用)。
常見的負載均衡軟體有Nginx,LVS,硬體F5等。
Ribbon本地負載均衡客戶端和Nginx服務端負載均衡區別:
Nginx是伺服器負載均衡,客戶端所有請求都會交給Nginx,然後由Nginx實現轉發請求。即負載均衡是由服務端實現的。
Ribbon是本地(客戶端)負載均衡,在呼叫微服務介面時候,會在註冊中心上獲取註冊資訊服務列表之後快取到JVM本地,從而在本地實現RPC遠端服務呼叫技術
集中式LB
即在服務的消費方和提供方之間使用獨立的LB設施(可以是硬體,如F5,也可以是軟體,如Nginx),由該設施負責把訪問請求通過某種策略轉發至服務提供方
程式內LB
將LB邏輯整合到消費方,消費方從服務註冊中心獲知有哪些地址可用,然後自己再從這些地址中選擇一個合適的伺服器。
Ribbon就屬於程式內LB,它只是一個類庫,整合於消費方程式,消費方通過它來獲取到服務提供方的地址。
Ribbon其實就是一個軟體實現負載均衡的客戶端元件,他可以和其他所需請求的客戶端結合使用,和Eureka結合只是其中的一個例項
示意圖:
6.2、Ribbon初體驗
-
在消費者模組springcloud-consumer-dept-80中匯入Eureka依賴,不要再匯入Ribbon依賴,否則會導致衝突
<!--Eureka--> <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> <version>1.4.7.RELEASE</version> </dependency>
-
在該模組中增加Eureka的配置
server: port: 80 #Eureka配置 eureka: client: register-with-eureka: false #不向Eureka註冊自己 service-url: defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
-
在配置類中,增加@LoadBalanced註解,以使用配置負載均衡實現RestTemplate
@Configuration public class ConfigBean { //@Configuration -- spring applicationContext.xml //配置負載均衡實現RestTemplate @Bean @LoadBalanced //Ribbon public RestTemplate getRestTemplate(){ return new RestTemplate(); } }
-
在controller層中DeptConsumerController.java中將訪問地址改為服務名
@RestController public class DeptConsumerController { //理解:消費者,不應該有service層~ //RestFul RestTemplate... 供我們直接呼叫就可以了!註冊到Spring中 //(url, 實體: Map, Class<T> responseType) @Autowired private RestTemplate restTemplate;//提供多種便捷訪問遠端http服務的方法,簡單的restful服務模板~ //Ribbon 我們這裡的地址,應該是一個變數,通過服務名來訪問 //private static final String REST_URL_PREFIX = "http://localhost:8001"; private static final String REST_URL_PREFIX = "http://SPRINGCLOUD-PROVIDER-DEPT"; @RequestMapping("/consumer/dept/add") public boolean add(Dept dept){ return restTemplate.postForObject(REST_URL_PREFIX+"/dept/add",dept,Boolean.class); } @RequestMapping("/consumer/dept/get/{id}") public Dept get(@PathVariable("id") Long id){ return restTemplate.getForObject(REST_URL_PREFIX+"/dept/get/"+id,Dept.class); } @RequestMapping("/consumer/dept/list") public List<Dept> list(){ return restTemplate.getForObject(REST_URL_PREFIX+"/dept/list",List.class); } }
-
執行三個Eureka叢集,服務提供者與消費者進行測試
6.3、使用Ribbon實現負載均衡
-
新建兩個資料庫,db02,db03,與db01相同
CREATE DATABASE /*!32312 IF NOT EXISTS*/`db02` /*!40100 DEFAULT CHARACTER SET utf8 */; USE `db02`; /*Table structure for table `dept` */ DROP TABLE IF EXISTS `dept`; CREATE TABLE `dept` ( `deptno` bigint(20) NOT NULL AUTO_INCREMENT, `dname` varchar(60) DEFAULT NULL, `db_source` varchar(60) DEFAULT NULL, PRIMARY KEY (`deptno`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='部門表'; /*Data for the table `dept` */ insert into `dept`(`deptno`,`dname`,`db_source`) values (1,'開發部','db02'),(2,'人事部','db02'),(3,'財務部','db02'),(4,'市場部','db02'),(5,'運維部','db02'); ####################################### CREATE DATABASE /*!32312 IF NOT EXISTS*/`db03` /*!40100 DEFAULT CHARACTER SET utf8 */; USE `db03`; /*Table structure for table `dept` */ DROP TABLE IF EXISTS `dept`; CREATE TABLE `dept` ( `deptno` bigint(20) NOT NULL AUTO_INCREMENT, `dname` varchar(60) DEFAULT NULL, `db_source` varchar(60) DEFAULT NULL, PRIMARY KEY (`deptno`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='部門表'; /*Data for the table `dept` */ insert into `dept`(`deptno`,`dname`,`db_source`) values (1,'開發部','db03'),(2,'人事部','db03'),(3,'財務部','db03'),(4,'市場部','db03'),(5,'運維部','db03');
-
新疆兩個服務提供者模組,springcloud-eureka-8002,springcloud-eureka-8003,與springcloud-eureka-8001相同,匯入相同的依賴,並修改配置檔案為對應的資料庫及介面
pom.xml
<dependencies> <!--EUREKA依賴--> <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> <version>1.4.7.RELEASE</version> </dependency> <!--actuator完善監控資訊--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!--我們需要拿到實體類,所以要配置api module--> <dependency> <groupId>com.kuang</groupId> <artifactId>springcloud-api</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <!--junit--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> </dependency> <!--test--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-test</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--jetty--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jetty</artifactId> </dependency> <!--熱部署工具--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> </dependency> </dependencies>
application.yml
server: port: 8002 #mybatis配置 mybatis: type-aliases-package: com.kuang.springcloud.pojo config-location: classpath:mybatis/mybatis-config.xml mapper-locations: classpath:mybatis/mapper/*.xml #spring的配置 spring: application: name: springcloud-provider-dept #三個服務名稱一致 datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/db02?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT username: root password: 123456 #Eureka的配置,服務註冊到哪裡 eureka: client: service-url: defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/ instance: instance-id: springcloud-provider-dept-8002 #修改eureka上的預設描述資訊! hostname: localhost #修改主機名稱 #解決url過濾問題 management: endpoints: web: exposure: include: "*" #info配置,無效,建立單獨的build-info.properties解決,注意格式 info: app-name: kuangshen-springcloud company-name: blog.kuangstudy.com
DeptMapper.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.kuang.springcloud.dao.DeptDao"> <insert id="addDept" parameterType="Dept"> insert into db02.dept (dname, db_source) values (#{dname},DATABASE()); </insert> <select id="queryDeptById" resultType="Dept" parameterType="Long"> select * from db02.dept where deptno = #{deptno}; </select> <select id="queryAll" resultType="Dept"> select * from db02.dept; </select> </mapper>
其餘檔案與springcloud-provider-dept-8001模組相同,名字不同
-
執行三個eureka叢集及三個服務提供者模組及一個消費者模組,訪問eureka7001主介面,發現有兩個備用eureka頁面及一個服務,服務後有三個服務提供者可供選擇
-
執行服務消費者,多次執行後,發現服務消費者按輪詢演算法(預設在活躍的服務提供者中選擇)依次訪問三個服務提供者(這裡使用三個不同的資料庫進行模擬以便觀察)
-
我們還可以使用不同的演算法訪問服務提供者,秩序在configuration類中新增@Bean重寫相應的方法並返回即可;也可以在啟動類的上一級目錄中建立編寫自定義的類,並在啟動類中新增@RibbonClient註解,並在註解後指定對應的自定義類即可,這裡不再演示。
7、Feign負載均衡
7.1、簡介
feign是宣告式的web service客戶端,它讓微服務之間的呼叫變得更簡單了,類似controller呼叫service。SpringCloud繼承了Ribbon和Eureka,可在使用Feign時提供負載均衡的http客戶端。
只需要建立一個介面,然後新增註解即可!
feign,主要是社群,大家都習慣面向介面程式設計。這個是很多開發人員的規範。呼叫微服務訪問兩種方法
- 微服務名字 【ribbon】
- 介面和註解 【feign】
Feign能幹什麼?
- Feign旨在使編寫Java Http客戶端變得更容易
- 前面在使用Ribbon + RestTemplate時,利用RestTemplate對Http請求的封裝處理,形成了一套模板化的呼叫方法。但是在實際開發中,由於對服務依賴的呼叫可能不止一處,往往一個介面會被多處呼叫,所以通常都會針對每個微服務自行封裝一些客戶端類來包裝這些依賴服務的呼叫。所以,Feign在此基礎上做了進一步封裝,由他來幫助我們定義和實現依賴服務介面的定義,在Feign的實現下,我們只需要建立一個介面並使用註解的方式來配置它(類似於以前Dao介面上標註Mapper註解,現在是一個微服務介面上面標註一個Feign註解即可。)即可完成對服務提供方的介面繫結,簡化了使用SpringCloud Ribbon時,自動封裝服務呼叫客戶端的開發量。
Feign整合了Ribbon
- 利用Ribbon維護了MicroServiceCloud-Dept的服務列表資訊,並且通過輪詢實現了客戶端的負載均衡,而與Ribbon不同的是,通過Feign只需要定義服務繫結介面且以宣告式的方法,優雅而且簡單的實現了服務呼叫。
7.2、程式碼實現
-
在實體類模組匯入feign依賴
<!--feign--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-feign</artifactId> <version>1.4.7.RELEASE</version> </dependency>
-
在實體類模組springcloud-api新建一個service層介面DeptClientService,新增FeignClient註解,其值為服務提供者
@Component @FeignClient(value = "SPRINGCLOUD-PROVIDER-DEPT") public interface DeptClientService { @GetMapping("/dept/get/{id}") public Dept queryById(@PathVariable("id") Long id); @GetMapping("/dept/list") public List<Dept> queryAll(); @PostMapping("/dept/add") public boolean addDept(Dept dept); }
-
新建feign消費者模組springcloud-consumer-dept-feign,與消費者模組springcloud-consumer-dept-80模組類似,僅改動controller層及啟動類。
匯入Feign依賴
<!--feign--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-feign</artifactId> <version>1.4.7.RELEASE</version> </dependency>
建立controller檔案DeptConsumerController.java
@RestController public class DeptConsumerController { @Autowired private DeptClientService deptClientService; @RequestMapping("/consumer/dept/add") public boolean add(Dept dept){ return this.deptClientService.addDept(dept); } @RequestMapping("/consumer/dept/get/{id}") public Dept get(@PathVariable("id") Long id){ return this.deptClientService.queryById(id); } @RequestMapping("/consumer/dept/list") public List<Dept> list(){ return this.deptClientService.queryAll(); } }
啟動類FeignDeptConsumer_80.java
@SpringBootApplication @EnableEurekaClient @EnableFeignClients(basePackages = {"com.kuang.springcloud"}) public class FeignDeptConsumer_80 { public static void main(String[] args) { SpringApplication.run(FeignDeptConsumer_80.class,args); } }
-
啟動eureka叢集,服務提供者及feign消費者進行測試,同樣可以實現查詢
8、Hystrix
分散式系統面臨的問題
複雜的分散式體系結構中的應用程式有數十個依賴關係,每個依賴關係在某些時候將不可避免地失敗!
服務雪崩
多個微服務之間呼叫的時候,假設微服務A呼叫微服務B和微服務C,微服務B和微服務C又呼叫其他的微服務,這就是所謂的“扇出”,如果“扇出”的鏈路上某個微服務的呼叫響應時間過長或者不可用,對微服務A的呼叫就會佔用越來越多的系統資源,進而引起系統崩潰,所謂的“雪崩效應”。
對於高流量的應用來說,單一的後端依賴可能會導致所有伺服器上的所有資源都在幾秒鐘內飽和。比失敗更糟糕的是,這些應用程式還可能導致服務之間的延遲增加,備份佇列、執行緒和其他系統資源緊張,導致整個系統發生更多的級聯故障,這些都表示需要對故障和延遲進行隔離和管理,以便單個依賴關係的失敗,不能取消整個應用程式或系統。
我們需要 ’棄車保帥‘
什麼是Hystrix
Hystrix是一個用於處理分散式系統的延遲和容錯的開源庫,在分散式系統裡,許多依賴不可避免的會呼叫失敗,比如超時、異常等,Hystrix能夠保證在一個依賴出問題的情況下,不會導致整體服務失敗,避免級聯故障,以提高分散式系統的彈性。
“斷路器”本身是一種開關裝置,當某個服務單元發生故障之後,通過斷路器的故障監控(類似熔斷保險絲),向呼叫方返回一個服務預期的,可處理的備選響應(FallBack),而不是長時間的等待或者丟擲呼叫方法無法處理的異常,這樣就可以保證了服務呼叫方的執行緒不會被長時間,不必要的佔用,從而避免了故障在分散式系統中的蔓延,乃至雪崩。
能幹嘛
- 服務降級
- 服務熔斷
- 服務限流
- 接近實時的監控
- ......
官網資料
http://github.com/Netflix/Hystrix/wiki
示意圖:
8.1、服務熔斷
是什麼
熔斷機制是對雪崩效應的一種微服務鏈路保護機制。
當扇出鏈路的某個微服務不可用或者響應時間太長,會進行服務的降級,進而熔斷該節點微服務的呼叫,快速返回錯誤的響應資訊。當檢測到該節點微服務呼叫響應正常後恢復呼叫鏈路。在SpringCloud框架裡熔斷機制通過Hystrix實現。Hystrix會監控微服務間呼叫的情況,當失敗的呼叫到一定閾值,預設是5秒內20次呼叫失敗就會啟動熔斷機制。熔斷機制的註解是@HystrixCommand。
程式碼實現:
-
新建一個服務提供者,springcloud-provider-dept-hystrix-8001,與springcloud-provider-dept-8001相同,新增hystrix依賴
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-netflix-hystrix --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> <version>2.2.10.RELEASE</version> </dependency>
修改配置檔案
#Eureka的配置,服務註冊到哪裡 eureka: client: service-url: defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/ instance: instance-id: springcloud-provider-dept-hystrix-8001 #修改eureka上的預設描述資訊! hostname: localhost #修改主機名稱 prefer-ip-address: true # true,可以顯示服務的ip地址~
-
重寫controller層DeptController.java實現Hystrix服務熔斷
//提供Restful服務! @RestController public class DeptController { @Autowired private DeptService deptService; @GetMapping("/dept/get/{id}") @HystrixCommand(fallbackMethod = "hystrixGet") public Dept get(@PathVariable("id") Long id){ Dept dept = deptService.queryDeptById(id); if (dept==null){ throw new RuntimeException("id=>"+id+",不存在該使用者,或者資訊無法找到~"); } return dept; } //備選方法 public Dept hystrixGet(@PathVariable("id") Long id){ return new Dept() .setDeptno(id) .setDname("id=>"+id+"沒有對應的資訊,null--@Hystrix") .setDb_source("no this database in MySQL"); } }
-
啟動類新增對Hystrix的支援@EnableHystrix
//啟動類 @SpringBootApplication @EnableEurekaClient //在服務啟動後自動註冊到Eureka中! @EnableDiscoveryClient //服務發現~ @EnableHystrix //新增對熔斷的支援,新版包含EnableCircuitBreaker public class DeptProviderHystrix_8001 { public static void main(String[] args) { SpringApplication.run(DeptProviderHystrix_8001.class,args); } }
-
啟動Eureka叢集,hystrix服務提供者及消費者,先訪問Eureka,可以指定服務提供者顯示ip地址
-
使用消費者查詢資料庫不存在的資訊,發現未報錯,頁面顯示指定的資訊
8.2、服務降級
服務降級是當伺服器壓力劇增的情況下,根據當前業務情況及流量對一些服務和頁面有策略的降級,以此釋放伺服器資源以保證核心任務的正常執行。
什麼是服務降級
服務降級,就是對不怎麼重要的服務進行低優先順序的處理。說白了,就是儘可能的把系統資源讓給優先順序高的服務。資源有限,而請求是無限的。如果在併發高峰期,不做服務降級處理,一方面肯定會影響整體服務的效能,嚴重的話可能會導致當機某些重要的服務不可用。所以,一般在高峰期,為了保證網站核心功能服務的可用性,都要對某些服務降級處理。
是消費者使用的一種應對服務提供者無響應的策略。
程式碼實現:
-
在實體類模組springcloud-api中新建一個service類DeptClientServiceFallbackFactory.java,實現FallbackFactory方法,返回DeptClientService並重寫方法,並將該類注入Spring中@Component
//降級~ @Component public class DeptClientServiceFallbackFactory implements FallbackFactory { @Override public DeptClientService create(Throwable cause) { return new DeptClientService() { @Override public Dept queryById(Long id) { return new Dept() .setDeptno(id) .setDname("id=>"+id+"沒有對應的資訊,客戶端提供了降級的資訊,這個服務現在已經被關閉了") .setDb_source("沒有資料~"); } @Override public List<Dept> queryAll() { return null; } @Override public boolean addDept(Dept dept) { return false; } }; } }
-
在實體類模組springcloud-api中service層的DeptClientService介面中指定fallbackFactory所指向的類
@Component @FeignClient(value = "SPRINGCLOUD-PROVIDER-DEPT",fallbackFactory = DeptClientServiceFallbackFactory.class) public interface DeptClientService { @GetMapping("/dept/get/{id}") public Dept queryById(@PathVariable("id") Long id); @GetMapping("/dept/list") public List<Dept> queryAll(); @PostMapping("/dept/add") public boolean addDept(Dept dept); }
-
在消費者springcloud-consumer-dept-feign中匯入依賴
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-netflix-hystrix --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> <version>2.2.10.RELEASE</version> </dependency>
-
在配置檔案application.yml中開啟降級feign.hystrix(新版配置不同)
server: port: 80 #開啟降級feign.hystrix(新版配置不同) feign: circuitbreaker: enabled: true #Eureka配置 eureka: client: register-with-eureka: false #不向Eureka註冊自己 service-url: defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
-
啟動Eureka叢集及消費者springcloud-consumer-dept-feign,使用消費者進行訪問,找不到服務則顯示預定內容不會報錯。
8.3、服務熔斷與服務降級簡單對比
服務熔斷:服務端,某個服務超時或者異常,引起熔斷,保險絲~。
服務降級:客戶端~,從整個網站請求負載考慮,當某個服務熔斷或者關閉之後,服務將不再被呼叫,此時在客戶端,我們可以準備一個FallbackFactory,返回一個預設的值(預設值),整體的服務水平下降了,但是好歹能用。
8.4、Dashboard流監控
控制皮膚資訊:
-
如何看
-
七色
-
一圈
實心圈:共有兩種含義,它通過顏色的變化代表了例項的健康程度
它的健康程度從綠色<黃色<橙色<紅色 遞減
該實心圓除了顏色的變化之外,它的大小也會根據例項的請求流量發生變化,流量越大,該實心圓就越大,所以通過該實心圓的展示,就可以在大量的例項中快速發現故障例項和高壓力例項。
-
一線
曲線:用來記錄2分鐘內流量的相對變化,可以通過它來觀察到流量的上升和下降趨勢!
-
程式碼實現:
-
建立一個監控頁面springcloud-consumer-hystrix-dashboard,匯入hystrix和dashboard依賴
<!--Hystrix依賴--> <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-netflix-hystrix --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> <version>2.2.10.RELEASE</version> </dependency> <!--Dashboard依賴--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId> <version>2.2.10.RELEASE</version> </dependency>
-
新建配置檔案application.yml
server: port: 9001 hystrix: dashboard: proxy-stream-allow-list: "localhost"
-
新建一個啟動類DeptConsumerDashboard_9001.java
@SpringBootApplication @EnableHystrixDashboard //開啟監控 public class DeptConsumerDashboard_9001 { public static void main(String[] args) { SpringApplication.run(DeptConsumerDashboard_9001.class,args); } }
-
將服務提供者springcloud-provider-dept-hystrix-8001放入監控皮膚,匯入hystrix和監控依賴,在啟動類增加一個Servlet
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-netflix-hystrix --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> <version>2.2.10.RELEASE</version> </dependency> <!--actuator完善監控資訊--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
//啟動類 @SpringBootApplication @EnableEurekaClient //在服務啟動後自動註冊到Eureka中! @EnableDiscoveryClient //服務發現~ @EnableHystrix //新增對熔斷的支援,新版包含EnableCircuitBreaker public class DeptProviderHystrix_8001 { public static void main(String[] args) { SpringApplication.run(DeptProviderHystrix_8001.class,args); } //增加一個Servlet public ServletRegistrationBean hystrixMetricsStreamServlet(){ ServletRegistrationBean registrationBean = new ServletRegistrationBean(new HystrixMetricsStreamServlet()); registrationBean.addUrlMappings("/actuator/hystrix.stream"); return registrationBean; } }
-
啟動EurekaServer_7001,DeptProviderHystrix_8001及DeptConsumerDashboard_9001進行測試
- 訪問Hystrix Dashboard皮膚
- 使用服務提供者進行查詢後,檢視/actuator/hystrix頁面
- 根據查詢頁面填寫Dashboard頁面資訊進行監視
- 最後檢視監視頁面
9、Zuul路由閘道器
9.1、概述
什麼是Zuul?
Zuul包含了對請求的路由和過濾兩個最主要的功能:
其中路由功能負責將外部請求轉發到具體的微服務例項上,是實現外部訪問統一入口的基礎,而過濾器功能則負責對請求的處理過程進行干預,是實現請求校驗、服務聚合等功能的基礎。Zuul和Eureka進行整合,將Zuul自身註冊為Eureka服務治理下的應用,同時從Eureka中獲得其他微服務的訊息,也即以後的訪問微服務都是通過Zuul跳轉後獲得。
注意:Zuul服務最終還是會註冊進Eureka
提供:代理 + 路由 + 過濾 三大功能!
Zuul能幹嘛?
- 路由
- 過濾
官網文件:https://github.com/Netflix/zuul
Zuul示意圖:
9.2、程式碼實現:
-
新建springcloud-zuul-9527模組,增加依賴
<!--實體類+web--> <dependencies> <!--zuul依賴--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-zuul</artifactId> <version>2.2.10.RELEASE</version> </dependency> <!--Hystrix依賴--> <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-netflix-hystrix --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> <version>2.2.10.RELEASE</version> </dependency> <!--Dashboard依賴--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId> <version>2.2.10.RELEASE</version> </dependency> <!--feign--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-feign</artifactId> <version>1.4.7.RELEASE</version> </dependency> <!--Eureka--> <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> <version>1.4.7.RELEASE</version> </dependency> <dependency> <groupId>com.kuang</groupId> <artifactId>springcloud-api</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--熱部署工具--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> </dependency> </dependencies>
-
新建配置檔案application.yml,對Zuul進行配置
server: port: 9527 spring: application: name: springcloud-zuul #Eureka的配置,服務註冊到哪裡 eureka: client: service-url: defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/ instance: instance-id: zuul9527.com #修改eureka上的預設描述資訊! hostname: "localhost" #修改主機名稱 prefer-ip-address: true # true,可以顯示服務的ip地址~ #解決url過濾問題 management: endpoints: web: exposure: include: "*" #info配置,無效,建立單獨的build-info.properties解決,注意格式 info: app-name: kuangshen-springcloud company-name: blog.kuangstudy.com zuul: routes: mydept: serviceId: springcloud-provider-dept path: /mydept/** ignored-services: "*" #不能再使用這個路徑訪問了,ignored : 忽略,隱藏全部的 prefix: /kuang #設定訪問時統一的字首
-
新建啟動類,開啟Zuul
@SpringBootApplication @EnableEurekaClient @EnableZuulProxy //開啟Zuul public class ZuulApplication_9527 { public static void main(String[] args) { SpringApplication.run(ZuulApplication_9527.class,args); } }
-
啟動Eureka,provider8001,及zuul9527進行測試
警告:該部分經反覆嘗試debug,修改springboot及springcloud版本、加攔截器配置類、增加ribbon時間、修改配置檔案等操作,均不能解決不能通過Zuul訪問其他服務的問題,只能實現將Zuul加入eureka並且訪問Zuul服務。在該版本中尚未找到可用的解決方案,目前只能使用其他方法實現閘道器的功能。
10、SpringCloud config分散式配置(Git)
10.1、概述
分散式系統面臨的--配置檔案的問題
微服務意味著要將單體應用中的業務拆分成一個個子服務,每個服務的粒度相對較小,因此係統中會出現大量的服務,由於每個服務都需要必要的配置資訊才能執行,所以一套集中式的、動態的配置管理設施是必不可少的。SpringCloud提供了ConfigServer來解決這個問題,我們每一個微服務自己帶著一個application.yml,那上百的配置檔案要修改起來,豈不是要瘋!
什麼是SpringCloud config分散式配置中心
SpringCloud Config為微服務架構中的微服務提供集中化的外部配置支援,配置伺服器為各個不同微服務應用的所有環節提供了一個中心化的外部配置。
SpringCloud Config分為服務端和客戶端兩部分;
服務端也稱為分散式配置中心,它是一個獨立的微服務應用,用來連線配置伺服器併為客戶端提供獲取配置資訊、加密、解密資訊等訪問介面。
客戶端則是通過指定的配置中心來管理應用資源,以及與業務相關的配置內容,並在啟動的時候從配置中心獲取和載入配置資訊。配置伺服器預設採用git來儲存配置資訊,這樣就有助於對環境配置進行版本管理。並且可以通過git客戶端工具來方便的管理和訪問配置內容。
SpringCloud Config分散式配置中心能幹嘛
- 集中管理配置檔案
- 不同環境,不同配置,動態化的配置更新,分環境部署,比如 /dev /test /prod /beta /release
- 執行期間動態調整配置,不再需要在每個服務部署的機器上編寫配置檔案,服務會向配置中心統一拉取配置自己的資訊。
- 當配置發生變動時,服務不需要重啟,即可感知到配置的變化,並應用新的配置
- 將配置資訊以REST介面的形式暴露
SpringCloud Config分散式配置中心與github整合
由於SpringCloud Config預設使用Git來儲存配置檔案(也有其他方式,比如支援SVN和本地檔案),但是最推薦的還是Git,而且使用的是http/https訪問的形式;
10.2、Git環境搭建
-
註冊碼雲Gitee賬號,登入,新建倉庫springcloud-config,設定如下圖
-
下載並安裝最新版Git,在Git本地倉庫資料夾下右鍵開啟git bash here
-
在git中輸入以下命令檢視是否存在.ssh檔案,如果存在則d到該檔案目錄中備份後刪除,保證不存在該檔案
~/.ssh
-
在git中輸入以下命令並連續三次回車,以生成ssh公開金鑰(郵箱可隨意輸入,只是金鑰名稱,習慣輸入郵箱)
ssh-keygen -t ed25519 -C "406623380@qq.com"
-
輸入以下指令檢視ssh金鑰,在碼雲中
設定/安全設定/SSH公鑰
中新增公鑰,將公鑰全部資訊複製到這裡並設定標題cat ~/.ssh/id_ed25519.pub
-
在碼雲該專案的首頁點選
克隆/下載
複製ssh地址,然後在git中輸入以下指令克隆該專案git clone git@gitee.com:wydilearn/springcloud-config.git
輸入yes繼續clone
-
輸入以下四個指令,將本地倉庫中新建的application.yml檔案上傳到Gitee倉庫中並在master分支中顯示
git add . git status #可以檢視到新建的檔案 git commit -m "first commit" #提交新檔案,本次提交的名字可以隨便取 git push origin master #將檔案上傳至master分支
-
在碼雲的該專案中可檢視到新上傳的檔案
10.3、服務端連線Git配置
-
新建一個服務端配置模組springcloud-config-server-3344,匯入依賴
<dependencies> <!--config--> <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-config-server --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-server</artifactId> <version>3.1.2</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--Eureka--> <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> <version>1.4.7.RELEASE</version> </dependency> </dependencies>
-
新建配置檔案application.yml連線遠端倉庫,獲取Gitee中倉庫的https地址作為uri
server: port: 3344 spring: application: name: springcloud-config-server # 連線遠端倉庫 cloud: config: server: git: uri: https://gitee.com/wydilearn/springcloud-config.git # https,不是git
-
新建一個啟動類Config_Server_3344.java,啟用服務端配置
@SpringBootApplication @EnableConfigServer // public class Config_Server_3344 { public static void main(String[] args) { SpringApplication.run(Config_Server_3344.class,args); } }
-
啟動該模組進行測試,訪問以下三個路徑可訪問到Gitee倉庫的檔案
10.4、客戶端連線服務端訪問遠端
-
在本地springcloud-config專案下新建配置檔案config-client.yml
spring: profiles: active: dev --- server: port: 8201 #spring的配置 spring: profiles: dev application: name: springcloud-provider-dept #Eureka的配置,服務註冊到哪裡 eureka: client: service-url: defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/ --- server: port: 8202 #spring的配置 spring: profiles: test application: name: springcloud-provider-dept #Eureka的配置,服務註冊到哪裡 eureka: client: service-url: defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
-
使用Git上傳該配置檔案到Gitee專案中,與之前檔案上傳操作類似
-
在IDEA中新建客戶端配置模組springcloud-config-client-3355,匯入依賴,與服務端模組依賴略有不同
<dependencies> <!--config--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bootstrap</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--Eureka--> <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> <version>1.4.7.RELEASE</version> </dependency> </dependencies>
-
新建兩個配置檔案application.yml與bootstrap.yml
bootstrap.yml
# 系統級的配置 spring: cloud: config: uri: http://localhost:3344 #連線本地服務,通過本地服務到Gitee獲取 name: config-client #需要從git上讀取的資源名稱,不要字尾 profile: dev #拿到檔案中的哪個生產環境 label: master #哪個分支 http://localhost:3344/master/config-client-dev.yml
application.yml
# 使用者級的配置 spring: application: name: springcloud-config-client-3355
此時未配置埠,預設為dev埠8201,可自行設定埠
-
新建一個controller層檔案ConfigClientController.java獲取Gitee中的配置檔案並展示
@RestController public class ConfigClientController { @Value("${spring.application.name}") private String applicationName; @Value("${eureka.client.service-url.defaultZone}") private String eurekaServer; @Value("${server.port}") private String port; @RequestMapping("/config") public String getConfig(){ return "applicationName:" + applicationName + "eurekaServer:" + eurekaServer + "port:" + port; } }
-
新建啟動類ConfigClient_3355.java
@SpringBootApplication public class ConfigClient_3355 { public static void main(String[] args) { SpringApplication.run(ConfigClient_3355.class,args); } }
-
同時啟動服務端配置模組與客戶端配置模組進行測試,可通過客戶端模組讀取配置資訊
10.5、遠端配置實戰測試
-
在本地Git專案中新建連個配置檔案config-eureka.yml及config-dept.yml並上傳,git上傳操作與之前相同
config-dept.yml
spring: profiles: active: dev --- server: port: 8001 #mybatis配置 mybatis: type-aliases-package: com.kuang.springcloud.pojo config-location: classpath:mybatis/mybatis-config.xml mapper-locations: classpath:mybatis/mapper/*.xml #spring的配置 spring: profiles: dev application: name: springcloud-config-dept datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/db02?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT username: root password: 123456 #Eureka的配置,服務註冊到哪裡 eureka: client: service-url: defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/ instance: instance-id: springcloud-provider-dept-8001 #修改eureka上的預設描述資訊! hostname: localhost #修改主機名稱 #解決url過濾問題 management: endpoints: web: exposure: include: "*" --- server: port: 8001 #mybatis配置 mybatis: type-aliases-package: com.kuang.springcloud.pojo config-location: classpath:mybatis/mybatis-config.xml mapper-locations: classpath:mybatis/mapper/*.xml #spring的配置 spring: profiles: test application: name: springcloud-config-dept datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/db01?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT username: root password: 123456 #Eureka的配置,服務註冊到哪裡 eureka: client: service-url: defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/ instance: instance-id: springcloud-provider-dept-8001 #修改eureka上的預設描述資訊! hostname: localhost #修改主機名稱 #解決url過濾問題 management: endpoints: web: exposure: include: "*"
config-eureka.yml
spring: profiles: active: dev --- server: port: 7001 #spring的配置 spring: profiles: dev application: name: springcloud-config-eureka #Eureka配置 eureka: instance: hostname: eureka7001.com #Eureka服務端的例項名稱 client: register-with-eureka: false #表示是否向eureka註冊中心註冊自己 fetch-registry: false #fetch-registry如果為false,則表示自己為註冊中心 service-url: #監控頁面~ # 單機: defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ # 叢集(關聯): defaultZone: http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/ --- server: port: 7001 #spring的配置 spring: profiles: test application: name: springcloud-config-eureka #Eureka配置 eureka: instance: hostname: eureka7001.com #Eureka服務端的例項名稱 client: register-with-eureka: false #表示是否向eureka註冊中心註冊自己 fetch-registry: false #fetch-registry如果為false,則表示自己為註冊中心 service-url: #監控頁面~ # 單機: defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ # 叢集(關聯): defaultZone: http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/ --- server: port: 7001 #Eureka配置 eureka: instance: hostname: eureka7001.com #Eureka服務端的例項名稱 client: register-with-eureka: false #表示是否向eureka註冊中心註冊自己 fetch-registry: false #fetch-registry如果為false,則表示自己為註冊中心 service-url: #監控頁面~ # 單機: defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ # 叢集(關聯): defaultZone: http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
-
新建springcloud-config-eureka-7001模組,內容與springcloud-eureka-7001相同,匯入依賴,新建配置檔案application.yml及bootstrap.yml
<!--導包--> <dependencies> <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka-server --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka-server</artifactId> <version>1.4.7.RELEASE</version> </dependency> <!--熱部署工具--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> </dependency> <!--config--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bootstrap</artifactId> </dependency> </dependencies>
application.yml
spring: application: name: springcloud-config-eureka-7001
bootstrap.yml
spring: cloud: config: name: config-eureka label: master profile: dev uri: http://localhost:3344
-
新建springcloud-config-dept-8001模組,內容與springcloud-provider-dept-8001相同,匯入依賴,新建application.yml及bootstrap.yml
<dependencies> <!--Hystrix依賴--> <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-netflix-hystrix --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> <version>2.2.10.RELEASE</version> </dependency> <!--EUREKA依賴--> <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> <version>1.4.7.RELEASE</version> </dependency> <!--actuator完善監控資訊--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!--我們需要拿到實體類,所以要配置api module--> <dependency> <groupId>com.kuang</groupId> <artifactId>springcloud-api</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <!--junit--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> </dependency> <!--test--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-test</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--jetty--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jetty</artifactId> </dependency> <!--熱部署工具--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> </dependency> <!--config--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bootstrap</artifactId> </dependency> </dependencies>
application.yml
spring: application: name: springcloud-config-dept-8001
bootstrap.yml
spring: cloud: config: name: config-dept label: master profile: test uri: http://localhost:3344
-
啟動springcloud-config-server-3344,springcloud-config-eureka-7001,springcloud-config-dept-8001三個模組進行測試,發現服務在eureka中註冊,並且可以通過服務查詢資料庫