一、網站架構演變過程
從傳統架構(單體應用) 到 分布式架構(以項目進行拆分) 到 SOA架構(面向服務架構) 到 微服務架構
1) 傳統架構:
其實就是SSH或者SSM,屬於單點應用,把整個業務模塊都會在一個項目中進行開發,分為MVC架構,會拆分成業務邏輯層、業務邏輯層、數據庫訪問層
缺點:一般只適合於一個人或者適合小團隊開發,耦合度太高,一旦某個模塊導致服務不可用,可能會影響到項目
2) 分布式架構
其實是基於傳統架構演變過來的
分布式架構基於傳統架構演變過來的,將傳統的項目以項目模塊進行拆分成n多個子項目,每個項目都有自己獨立的數據庫等
總結:分布式架構與傳統架構區別:項目粒度分的更加細,耦合度降低
區分是否是分布式架構在於打的jar包或者war是否是多個jvm項目通訊
3) SOA架構與微服務架構
SOA架構也是基於分布式架構演變過來的。SOA架構代表面向服務架構,俗稱服務化,可以理解為面向於業務邏輯層開發。將共同的業務代碼進行抽取出來,提供給其他接口進行調用。服務與服務之間通訊采用rpc遠程調用技術。
服務概念:將共同的業務邏輯進行拆分,拆分成獨立的項目進行部署,沒有視圖層,當然也可以理解為接口
SOA架構特點:底層基於SOAP(Http協議+XML:如webservice)或者ESB(消息總線)實現,底層使用HTTP或者Https協議+重量級XML數據交換格式進行通訊
在后面微服務中,以json格式代替xml
rpc遠程調用技術框架:如httpClient、springCloud、dubbo、grpc
核心底層socket技術或者netty實現
4) 微服務架構產生的原因
4.1) 首先微服務架構基於SOA架構演變過來的
SOA架構缺點:
1、依賴於中心化服務發現機制
2、因為SOA架構采用SOAP協議(Http+XM),因為XML傳輸協議比較占用寬帶,整個XML報文中有非常大冗余數據,所以在微服務架構中以json輕量級方式代替xml報文傳輸。
3、服務管理非常混亂,缺少服務管理和治理設施不完善
4.2) 微服務架構模式
微服務架構是從SOA架構演變過來的,比SOA架構上粒度更加精細,讓專業的人做專業的事,目的是提高效率。每個服務與服務之間是互不影響,每個服務必須獨立部署(獨立數據庫、獨立redis等),微服務架構更加體現輕量級,采用restful風格提高API,也就是Http協議+JSON格式進行傳輸,更加輕巧更加適合於互聯網公司敏捷開發、快速迭代產品。
詳細分析:
微服務架構從SOA架構演變過來
服務化功能本身從SOA這層已經實現,只不過微服務架構在單獨服務層有進行細分服務服務層有進行細分服務
如會員服務:會員服務在微服務有進行細分為:
4.3) 微服務架構與SOA架構區別
1、為服務架構基於SOA架構演變過來,繼承SOA架構的優點,在微服務架構中去除了SOA架構中的ESB消息總線,采用http+json(restful)進行傳輸
2、微服務架構比SOA架構粒度會更加精細,讓專業的人去做專業的事,目的提高效率,每個服務於服務之間互不影響,微服務架構中,每個服務必須獨立部署,微服務架構更加輕巧,輕量級
3、SOA架構中可能數據庫存儲會發生共享,微服務強調每個服務都是單獨數據庫,保證每個服務與服務之間互不影響
4、項目體現特征服務架構比SOA架構更加適合於互聯網公司敏捷,快速迭代版本,因為粒度非常精細
二、springCloud簡介
1、為什么要使用springCloud
springCloud是目前來說,是一套比較完善的為微服務解決方案框架。它不像其他rpc遠程調用框架,只是解決了某個微服務中的問題。可以把springCloud理解為一條龍微服務解決方案。微服務全家桶--SpringCloud比較完善
微服務中:
分布式配置中心
分布式鎖
分布式服務治理
分布式任務調用平台
總結:
因為SpringCloud出現,對微服務 技術提供了非常大的幫助,以往內SpringCloud提供了一套完整的微服務解決方案,不像其他框架只是解決了微服務中某個問題
服務治理:阿里巴巴開源的Dubbo和當當網在其基礎上擴展的Dubbox、Eureka、Apache的Consul等
分布式配置中心:百度的disconf、Netfix的Archaius、360的Qconf、SpringCloud、攜程的阿波羅等
分布式任務:xxl-job、elastic-job、springCloud的task等
服務跟蹤:京東的hyra、springCloud的sleuth等
2、什么是SpringCloud
SpringCloud是基於SpringBoot基礎上開發的微服務框架,SpringCloud是一套目前非常完整的微服務解決方案框架,其內容包含服務治理、注冊中心、配置管理、斷路器、智能路由、微代理、控制總線、全局鎖、分布式會話等
springCloud包含眾多的子項目
SpringCloud config 分布式配置中心
SpringCloud netflix 核心組件
Hystrix:服務保護框架
Ribbon:客戶端負載均衡器
feign:基於ribbo和hystrix的聲明式服務調用組件
Zuul:網關組件,提供智能路由 、訪問過濾等功能
三、SpringCloud服務發現與注冊
SpringCloud曾經采用Eureka,現在已經閉源了
1、服務治理SpringCloud eureka
1) 什么是服務治理
在傳統的rpc遠程調用框架中,管理每個服務與服務之間依賴關系比較復雜,管理比較復雜,所以需要使用服務治理,管理服務於服務之間依賴關系,可以實現服務調用、負載均衡、容錯等,實現服務發現與注冊。
下圖可以解釋使用注冊中心的理由:
2)什么是服務注冊與發現
在服務注冊與發現中,有一個注冊中心,當服務器啟動的時候,會把當前自己服務器的信息 比如 服務地址通訊地址等以別名方式注冊到注冊中心上。另一方(消費者|服務提供者),以該別名的方式去注冊中心上獲取到實際的服務通訊地址,然后再實現本地rpc調用
RPC遠程調用框架核心設計思想:在於注冊中心,因為使用注冊中心管理每個服務與服務之間的一個依賴關系(服務治理概念)
在任何rpc遠程框架中,都會有一個注冊中心(存放服務地址相關信息(接口地址))
以下是搭建一套基於Eureka作為注冊中心的demo,項目層次圖如下
3) 搭建注冊中心
3.1) 新建maven工程
3.2) 引入maven
注意2.0版本區別1版本在於eureka命名更加規范(1是spring-cloud-starter-eureka-server)
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.1.RELEASE</version> </parent> <!-- 管理依賴 --> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Finchley.M7</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <!--SpringCloud eureka-server --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency> </dependencies> <!-- 注意: 這里必須要添加, 否者各種依賴有問題 --> <repositories> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/libs-milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories>
3.3) 新建配置文件application.yml
###服務端口號 server: port: 8100 ###eureka 基本信息配置 eureka: instance: ###注冊到eurekaip地址(注冊中心ip) hostname: 127.0.0.1 client: serviceUrl: defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ #多個用,號隔開 ###因為自己是為注冊中心,不需要自己注冊自己(集群需要設置為true) register-with-eureka: false ###因為自己是為注冊中心,不需要檢索服務 fetch-registry: false
3.4) 編寫啟動類
@SpringBootApplication @EnableEurekaServer //開啟EurekaServer服務 開啟注冊中心 public class AppEureka { public static void main(String[] args) { SpringApplication.run(AppEureka.class,args); } }
3.5) 訪問Eureka注冊中心管理平台,通過本機IP+ 配置的端口號:這里是http://localhost:8100
4) 搭建服務提供者
4.1) 新建生產者服務(service層)子項目
4.2) 添加pom依賴
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.1.RELEASE</version> </parent> <!-- 管理依賴 --> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Finchley.M7</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <!-- SpringBoot整合Web組件 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- SpringBoot整合eureka客戶端 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> </dependencies> <!-- 注意: 這里必須要添加, 否者各種依賴有問題 --> <repositories> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/libs-milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories>
4.3) 添加application.yml配置文件
###會員項目服務啟動端口號 server: port: 8000 ###服務名稱(服務注冊到eureka名稱,如serviceId) spring: application: name: app-producer ###服務注冊到eureka地址,8100為注冊中心端口號 eureka: client: service-url: defaultZone: http://localhost:8100/eureka
4.3) 編輯需要注冊的服務接口
@RestController public class MemberAPIController { @Value("${server.port}") private String serverPort; //用於區分集群 @RequestMapping("/getMember") public String getMember() { return "this is Mr_佳先森的會員服務,端口號為:"+serverPort; } }
4.4) 編寫啟動類
@SpringBootApplication @EnableEurekaClient //將當前服務注冊到eureka上 public class AppMember { public static void main(String[] args) { SpringApplication.run(AppMember.class, args); } }
4.5)運行注冊中心,再運行生成者,此時通過訪問控制台,能看到注冊后的信息
注意:這里的生產者名是根據生產者配置文件的配置的別名而定(這里是app-producer)
5) 編寫消費者
5.1) 新建消費者子項目
5.2) 配置pom依賴
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.1.RELEASE</version> </parent> <!-- 管理依賴 --> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Finchley.M7</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <!-- SpringBoot整合Web組件 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- SpringBoot整合eureka客戶端 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> </dependencies> <!-- 注意: 這里必須要添加, 否者各種依賴有問題 --> <repositories> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/libs-milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories>
5.3) 編寫消費者配置文件和消費方法
###訂單服務(消費者)啟動端口號 server: port: 8001 ###服務名稱(服務注冊到eureka名稱) spring: application: name: app-order ###服務注冊到eureka地址 eureka: client: service-url: defaultZone: http://localhost:8100/eureka,
@RestController public class OrderController { /** * RestTemplate由SpringBoot web組件提供 默認整合ribbo負載均衡器 * rest方式底層是采用httpClient技術 */ @Autowired private RestTemplate restTemplate; /** * 訂單服務調用會員服務: * 在springCloud當中,有兩種方式調用生產的消息 * 1.rest方式 2、fegin(SpringCloud方式) * @return */ @RequestMapping("/getOrder") public String getOrderInfo() { //采用服務別名(生產者)+方法映射id: String url = "http://app-producer/getMember"; String result = restTemplate.getForObject(url, String.class); System.out.println("訂單服務調用會員服務是_:"+result); return result; } }
5.4) 編寫啟動類
基於這里采用的是ribbon的RestTemplate方式調用生產者,需要啟動類中注冊該類到容器中,並添加@LoadBalanced注解(注意區別於nginx:nginx負載均衡主要
基於服務器(配合一些服務(如mysql,tomcat等)),而ribbon主要基於本地(主要用於微服務),我們可以通過更改生產者端口號構建集群環境,不斷調用消費者@LoadBalanced 注解能實現負載均衡)
@SpringBootApplication @EnableEurekaClient public class AppOrder { public static void main(String[] args) { SpringApplication.run(AppOrder.class, args); } //解決RestTemplate找不到問題:應該把restTemplate注冊到springboot容器中 @Bean @LoadBalanced //開啟負載均衡功能,並且支持別名方式調用生產者(否則無法使用別名) RestTemplate restTemplage() { return new RestTemplate(); } }
5.5) 在控制台可以看到消費者信息並調用服務
6) Eureka集群高可用環境搭建
6.1)、微服務rpc遠程服務調用最核心的是什么?
注冊中心。如果注冊中心因為某種原因出現故障,有可能導致整個為服務環境不可用
解決辦法:搭建注冊中心集群--大型互聯網公司注冊中心都是集群版本。
6.2) 搭建eureka集群環境
思路:Eureka高可用實際上是將自己作為服務向其他服務注冊中心注冊自己,這樣就可以形成一組相互作用的服務注冊中心,從而實現服務清單 的互相調用,達到高可用的效果
6.3) 新建兩個注冊中心項目,參考以上的做法,只是配置文件會發生一點變化
這里的環境是擁有一台8100的注冊中心和9100的注冊中心,他們相互注冊
8100:application.yml
server: port: 8100 ##定義服務名稱:集群環境服務名稱得相同 spring: application: name: springCloud-eureka eureka: instance: hostname: 127.0.0.1 client: serviceUrl: defaultZone: http://${eureka.instance.hostname}:9100/eureka/ register-with-eureka: true fetch-registry: true
9100:application.yml
server: port: 9100 ##定義服務名稱:集群環境服務名稱得相同 spring: application: name: springCloud-eureka eureka: instance: hostname: 127.0.0.1 client: serviceUrl: defaultZone: http://${eureka.instance.hostname}:8100/eureka/ register-with-eureka: true fetch-registry: true
6.4) 啟動兩個啟動類,第一個可能會報如下錯誤,是因為第二個注冊中心未啟動,從而報無法識別服務器
6.5) 同時訪問兩個管理平台可以看到相互注冊的信息
6.6) 客戶端連接注冊中心
此時生成者和消費者只需要分別將服務注冊到兩個注冊中心中去和從兩個注冊中心拿服務
如生成者
###會員項目服務啟動端口號 server: port: 8000 ###服務名稱(服務注冊到eureka名稱,如serviceId) spring: application: name: app-producer ###服務注冊到eureka地址 eureka: client: service-url: defaultZone: http://localhost:8100/eureka,http://localhost:9100/eureka ###因為該應用為注冊中心,不會注冊自己 register-with-eureka: true ###是否需要從eureka上獲取注冊信息 fetch-registry: true
6.6) 啟動
從管理平台可以看出,8100為主,9100為備
在注冊過程中,只會保證有一台注冊中心服務有對應服務信息數據,當主(8100)注冊中心宕機后,自動轉移數據到備(9100)注冊中心上去
6.7) 驗證高可用
如果此時將8100注冊中心關閉,那么數據會轉移到9100注冊中心上去(8100宕機后,默認是30秒左右數據轉移到備機上)
四、Eureka自我保護機制
注冊中心目的為了做什么?服務治理,服務注冊與發現能夠實現負載均衡,管理服務與服務之間的依賴關系
1、引入話題:
分為兩種角色:EurekaClient(注冊中心) 和 EurekaServer(注冊中心服務端,即生產者),如果將兩個服務端(端口號不同)注冊到注冊中心(集群),利用消費者從注冊中心中拿取消費。然后將其中一個服務端關閉,會出現如下圖情況:剛開始服務端能不斷從兩個服務中進行消費(負載均衡),當其中一個生產者(客戶端)宕機,刷新消費時因為負載均衡時而訪問不到(因為宕機)時而能訪問的到(另外一台沒有宕機),但是注冊中心在一定時間內還會存在宕機后的客戶端服務
2、為什么會產生Eureka自我保護機制?
為了防止EurekaClient可以正常運行,但是 與 EurekaServer網絡不通情況下,EurekaServer不會將EurekaClient服務剔除
自我保護機制
3、在什么情況下開啟自我保護機制
本地環境:建議在本地環境禁止自我保護機制
生成環境:建議開啟,不能誤刪存活的服務
4、怎么禁止自我保護
4.1) 注冊中心客戶端增加配置
server: port: 8100 spring: application: name: springCloud-eureka eureka: instance: hostname: 127.0.0.1 client: serviceUrl: defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ register-with-eureka: false server: ###開發時關閉自我保護機制,保證不可用服務及時踢除 enable-self-preservation: false eviction-interval-timer-in-ms: 2000
4.2) 生產者客戶端拿取消費
###會員項目服務啟動端口號 server: port: 8002 ###服務名稱(服務注冊到eureka名稱,如serviceId) spring: application: name: app-producer ###服務注冊到eureka地址 eureka: client: service-url: defaultZone: http://localhost:8100/eureka #心跳檢測與續約時間 #開發時設置小些,保證服務關閉后注冊中心能即使剔除服務 instance: ###Eureka客戶端向服務端發送心跳的時間間隔,單位為秒(客戶端告訴服務端自己會按照該規則) lease-renewal-interval-in-seconds: 1 ###Eureka服務端在收到最后一次心跳后等待時間上限,單位為秒,超過將剔除(客戶端告訴服務端自己會按照該規則) lease-expiration-duration-in-seconds: 2
五、springCloud整合Zookeeper作為注冊中心
因為Eureka已經閉源,但是不影響它作為注冊中心,當然也可以利用zookeeper進行代替
zookeeper是一個分布式協調工具,可以實現注冊中心功能,采用Zookeeper節點類型--臨時節點
整個系統層次結構如下
1、編寫生產者:
1.1)建立maven項目,並配置pom依賴
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.1.RELEASE</version> </parent> <!-- 管理依賴 --> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Finchley.M7</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <!-- SpringBoot整合Web組件 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- SpringBoot整合eureka客戶端 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId> </dependency> </dependencies> <!-- 注意: 這里必須要添加, 否者各種依賴有問題 --> <repositories> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/libs-milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories>
1.2) 建立application.yml配置文件
###服務端口號 server: port: 8002 ###服務名稱 spring: application: name: zk-producer cloud: zookeeper: ###注冊到zookeeper地址 connect-string: 192.168.174.128:2181
1.3) 建立邏輯代碼(需要注冊的方法)
/** * @EnableDiscoveryClient * 作用:如果服務使用consul或者zookeeper,該注解用於向注冊中心注冊服務 * @author Administrator * */ @RestController @EnableDiscoveryClient public class ZkProducerController { @Value("${server.port}") private String serverPort; @RequestMapping("/getProduce") public String getProduce() { return "生產者生產服務:端口號為:"+serverPort; } }
1.4) 建立啟動類
@SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
1.5) 啟動zookeeper(這里我采用的是docker啟動zookeeper)
[root@bogon ~]# systemctl start docker [root@bogon ~]# docker run --privileged=true -d --name zookeeper --publish 2181:2181 -d zookeeper:latest ffc45f9fb92ae045c205407d39375a31c639bbf66d1f9b42339cf5e167a7c467
2、編寫消費者
2.1) 創建maven工程,注入依賴,和生成這依賴保持一致
2.2) 編寫application.yml配置文件
###服務端口號 server: port: 8060 ###服務名稱 spring: application: name: zk-consumer cloud: zookeeper: ###注冊到zookeeper地址 connect-string: 192.168.174.128:2181
2.3) 編寫消費服務方法
@RestController @EnableDiscoveryClient public class ZkConsumerController { @Autowired private RestTemplate restTemplate; @RequestMapping("/getOrder") public String getOrderInfo() { //采用服務別名(生產者)+方法映射id: String url = "http://zk-producer/getProduce"; String result = restTemplate.getForObject(url, String.class); System.out.println("訂單服務調用會員服務是_:"+result); return result; } }
2.4) 編寫啟動類,並運行該類
@SpringBootApplication public class ApplicationForConsumer { public static void main(String[] args) { SpringApplication.run(ApplicationForConsumer.class, args); } //解決RestTemplate找不到問題:應該把restTemplate注冊到springboot容器中 @Bean @LoadBalanced //開啟負載均衡功能,並且支持別名方式調用生產者(否則無法使用別名) RestTemplate restTemplage() { return new RestTemplate(); } }
2.5) 訪問 http://localhost:8060/getOrder 如果能打印指定的返回結果,說明搭建成功
3、關於DiscoveryClient的使用
DiscoveryClient可以獲取注冊中心中注冊的信息列表,只需要通過該引用的getInstances()方法就可以獲取指定的列表信息,注意之所以返回值是集合是因為考慮到集群
@RestController @EnableDiscoveryClient public class ZkConsumerController { @Autowired private RestTemplate restTemplate; @Autowired private DiscoveryClient discoveryClient; @RequestMapping("/getOrder") public String getOrderInfo() { //采用服務別名(生產者)+方法映射id: String url = "http://zk-producer/getProduce"; String result = restTemplate.getForObject(url, String.class); System.out.println("訂單服務調用會員服務是_:"+result); return result; } //如何獲取到注冊中心上服務列表信息 @Bean @LoadBalanced public RestTemplate restTemplate() { return new RestTemplate(); } @RequestMapping("/discoveryClientMember") public List<ServiceInstance> discoveryClientMember() { List<ServiceInstance> instances = discoveryClient.getInstances("zk-producer");//參數來源於生產者配置的服務名 for(ServiceInstance temp:instances) { System.out.println("url:"+temp.getUri()); } return instances; } }
六、使用Consul代替Eureka作注冊中心
Consul 是一套開源的分布式服務發現和配置管理系統,由 HashiCorp 公司用 Go 語言開發。
它具有很多優點。包括: 基於 raft 協議,比較簡潔; 支持健康檢查, 同時支持 HTTP 和 DNS 協議 支持跨數據中心的 WAN 集群 提供圖形界面 跨平台,支持 Linux、Mac、Windows
Consul 整合SpringCloud 學習網站:https://springcloud.cc/spring-cloud-consul.html
Consul下載地址https://www.consul.io/downloads.html
1、搭建consul環境
1.1) 下載consul:下載
1.2) 啟動
進入到存放下載好的執行文件,利用命令co
啟動consul命令
consul agent -dev -ui -node=cy
-dev開發服務器模式啟動,-node結點名為cy,-ui可以用界面訪問,默認能訪問。
測試訪問地址:http://localhost:8500
nsul agent-dev-ui-node=cy啟動
一、編寫注冊者
1、pom依賴
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.3.RELEASE</version> </parent> <!-- 管理依賴 --> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Finchley.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <!-- SpringBoot整合Web組件 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--SpringCloud consul-server --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-consul-discovery</artifactId> </dependency> </dependencies> <!-- 注意: 這里必須要添加, 否者各種依賴有問題 --> <repositories> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/libs-milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories>
2、編寫配置application.yml
###服務端口號 server: port: 8502 spring: application: name: consul-producer ####consul注冊中心地址 cloud: consul: host: localhost port: 8500 discovery: ###服務地址直接為ip地址,這個自定義,它將成為生產者ip hostname: 192.168.174.128 ###默認情況下服務注冊到注冊中心,地址隨機生成英文
3、編寫邏輯類
@RestController @SpringBootApplication @EnableDiscoveryClient public class Productor { @Value("${server.port}") private String serverPort; @RequestMapping("getMember") public String getMember() { return "服務提供者:端口號為:"+serverPort; } public static void main(String[] args) { SpringApplication.run(Productor.class, args); } }
4、啟動訪問:http://localhost:8500
七、SpringCloud之負載均衡
1、springCloud之本地緩存Ribbon
1.1、Ribbon是Springcloud(本地)客戶端負載均衡器,當消費者從注冊中心獲取到serviceId以及多以的服務地址后,會緩存到本地(JVM客戶端)。然后在本地實現遠程的rpc調用,就如同繞過注冊中心直接調用生產者拿取服務
只要加上@LoadBalanced注解即可
@Autowired private RestTemplate restTemplate;
@Bean @LoadBalanced public RestTemplate restTemplate() { return new RestTemplate(); }
2.2、關於負載均衡算法
消費者以服務別名獲取到對應的服務接口地址的時候,可能會多個,存放采用list接口。當實現本地負載均衡時,此時涉及到負載均衡算法
負載均衡算法:接口總請求數%服務器數量 = 實際調用服務器位置下標
如:List [0] value = 127.0.0.1:8080
List [1] value = 127.0.0.1:8081
這里是兩個相同服務的集群
當總請求數為1時: 1 % 2 =1 對應下標位置為1 ,則獲得服務地址為127.0.0.1:8081
當總請求數位2時: 2 % 2 =0 對應下標位置為0 ,則獲得服務地址為127.0.0.1:8080
當總請求數位2時: 3 % 2 =1 對應下標位置為1 ,則獲得服務地址為127.0.0.1:8081
如此類推。。。
2.3、手寫本地負載均衡器
1)編寫簡單的eureka注冊中心,生產者,消費者,最好關閉服務保護
2) 在消費者子模塊中編寫自定義本地緩存器
@RestController public class ExtRibbonController { //可以獲取注冊中心上的服務列表 @Autowired private DiscoveryClient discoveryClient; @Autowired private RestTemplate restTemplate; //接口的請求總數 private int requestCount; @RequestMapping("/ribbonMember") public String ribbonMember() { //1、獲取對應服務器遠程調用地址 String instanceUrl = getInstance() + "/getMember"; System.out.println("instanceUrl" + instanceUrl); //2.可以直接使用httpClient技術實現遠程調用 String result = restTemplate.getForObject(instanceUrl, String.class); return result; } private String getInstance() { List<ServiceInstance> instances = discoveryClient.getInstances("app-producer"); if(instances == null || instances.size()<=0) { return null; } //獲取服務器集群個數 int instanceSize = instances.size(); int index = requestCount % instanceSize; requestCount++; return instances.get(index).getUri().toString(); } }
3) 啟動eureka注冊中心,以集群方式啟動兩次生產者(這里端口號為8002和8003),啟動消費者
4)項目目錄層次如下
5) 輸入請求地址http://localhost:8001/ribbonMember,這里是調用自定義的負載均衡器請求方法,通過它來調用生產者的服務,通過刷新請求可以看到端口號的變化
2.4)Ribbon本地負載均衡客戶端與Nginx服務端負載均衡區別
Ribbon本地負載均衡,原理:在調用接口時候,會在eureka注冊中心上獲取注冊信息服務列表之后,會緩存到jvm本地,從而在本地實現rpc遠程服務調用技術。
Nginx是服務器負載均衡,客戶端所有請求都會交給nginx,然后由nginx實現轉發請求。即負載均衡器室友服務端實現的。
應用場景:
本地負載均衡器適用在微服務rpc遠程調用,比如dubbo、springCloud
nginx是服務器負載均衡適用於針對於服務端 比如tomcat、jetty
2.5) SpringCloud 聲明式Feign客戶端
2.5.1)在springCloud中,它支持兩種客戶端調用工具
1、Rest方式,但是它基本不用
@Autowired private RestTemplate restTemplate;
2、Feign客戶端
其以后實際開發中用的最多,它是一個聲明式的Http客戶端調用工具,采用接口+注解方式實現,可讀性比較強
2.5.2)搭建環境
1)編寫eureka注冊中心,生產者,消費者(只需要編寫配置和啟動類)
2)消費者引入feign依賴
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
3)編寫feign接口
注意:feign客戶端是采用接口形式調用服務,其客戶端名與生產端配置的服務名保持一致,需要調用的服務名映射與服務端的映射保持一致
@FeignClient(name="app-producer") //此參數取決 於生產者的服務名 public interface ApiFeign { @RequestMapping("/getMember") //此參數取決於調用生產者的那個方法映射(和生產者保持一致) public String getMember(); }
4)編寫controller層
@RestController public class FeignController { @Autowired private ApiFeign apiFeign; @RequestMapping("/feignMember") public String feighMember() { return apiFeign.getMember(); } }
5)訪問服務http://localhost:8001/feignMember,如果能成功調用說明環境搭建成功
八、feign處理超時
1、什么是服務雪崩效應
默認情況下tomcat只有一個線程池去處理客戶端發送的所有的服務請求,這樣在高並發情況下,如果客戶端所有請求堆積在同一個服務接口上,就會產生tomcat所有線程去處理該服務接口,可能會導致其他服務接口訪問產生延遲和等待無法訪問。
tomcat有個線程池,每個一個線程去處理客戶端發送請求。假設Tomcat最大請求數(同時)20,客戶端發送100個請求。會發生80個請求產生延遲等待。
2、環境:
當利用feign客戶端調用服務時,如果生產者中的一個消息有1.5秒的延遲,那么在調用服務時會報時間超時
生產者實現類中的一個方法
@RequestMapping("/getUserInfo") public ResponseBase getUserInfo() { try { //服務接口產生1.5秒的延遲 Thread.sleep(1500); }catch(Exception e) { } return setResultSuccess("消費消息成功"); }
異常:
java.net.SocketTimeoutException: Read timed out
3、feign在網絡延遲情況下的超時時間解決思路
只需在消費者配置中配置延長超時時間即可
###訂單服務(消費者)啟動端口號 server: port: 8001 ###服務名稱(服務注冊到eureka名稱) spring: application: name: app-order ###服務注冊到eureka地址 eureka: client: service-url: defaultZone: http://localhost:8100/eureka ###是否注冊自己 register-with-eureka: true ###是否從eureka上獲取注冊上信息 fetch-registry: true ###設置feign客戶端超時時間 ###springCloud默認開啟支持ribbon ribbon: ###指的是建立連接所用的時間,適用於網絡狀況正常的情況下,兩端連接所用的時間 ReadTimeout: 5000 ###指的是建立連接后從服務器讀取到可用資源所用的時間 ConnectTimeout: 5000
具體代碼參考鏈接:git@gitee.com:MR_JiaXianSen/spring_cloud_template.git
九、服務保護Hystrix
1、微服務高可用技術
大型復雜的分布式系統中,高可用相關的技術架構非常重要。
高可用架構非常重要的一個環節,就是如何將分布式系統中的各個服務打造成高可用的服務,從而足以應對分布式系統環境中的各種各樣的問題,,避免整個分布式系統被某個服務的故障給拖垮。
比如:
服務間的調用超時
服務間的調用失敗
要解決這些棘手的分布式系統可用性問題,就涉及到了高可用分布式系統中的很多重要的技術,包括:
資源隔離
限流與過載保護
熔斷
優雅降級
容錯
超時控制
監控運維
服務雪崩效應
服務雪崩效應產生與服務堆積在同一個線程池中,因為所有的請求都是同一個線程池進行處理,這時候如果在高並發情況下,所有的請求全部訪問同一個接口,
這時候可能會導致其他服務沒有線程進行接受請求,這就是服務雪崩效應效應。
服務降級
在高並發情況下,防止用戶一直等待,使用服務降級方式(直接返回一個友好的提示給客戶端,調用fallBack方法)
服務熔斷
熔斷機制目的為了保護服務,在高並發情況下,如果請求達到一定極限(可以自己設置闊值)如果流量超出了設置閾值,讓后直接拒絕訪問,保護當前服務。使用服務降級方式返回一個友好提示,服務熔斷和服務降級一起使用)
服務隔離
因為默認情況下,只有一個線程池會維護所有的服務接口,如果大量的請求訪問同意接口,達到tomcat線程池默認極限,可能會導致其他服務無法訪問。
解決服務雪崩效應:
1)線程池隔離
使用服務隔離機制(線程池方式和信號量),使用線程池方式實現隔離的原理: 相當於每個接口(服務)都有自己獨立的線程池,因為每個線程池互不影響,這樣的話就可以解決服務雪崩效應。
線程池隔離:
2) 信號量隔離
使用一個原子計數器(或信號量)來記錄當前有多少個線程在運行,當請求進來時先判斷技術器的數值,若超過設置的最大線程個數則拒絕該請求,若不超過則通過,這時候計數器+1,請求返回成功后技術器-1
服務限流
服務限流就是對接口訪問進行限制。技術器也可以進行粗暴限流實現
2、Hystrix簡介
Hystrix是國外知名的視頻網站Netflix所開源的非常流行的高可用架構框架。Hystrix能夠完美的解決分布式系統架構中打造高可用服務面臨的一系列技術難題。
Hystrix “豪豬”,具有自我保護的能力。hystrix 通過如下機制來解決雪崩效應問題。
在微服務架構中,我們把每個業務都拆分成單個服務模塊。然后當有業務需求是,服務間課互相調用,但是,由於網絡原因等因素,有可能出現服務不可用的情況,當某個服務出現問題時,其他服務如果繼續調用這個服務,就是可能出現線程阻塞,但如果同時又大量的請求,就會造成線程資源被用完,這樣就可能會導致服務癱瘓,由於服務間會相互調用,很容易造成蝴蝶效應導致整個系統宕機。而斷路器可以解決這點。
資源隔離:包括線程池隔離和信號量隔離,限制調用分布式服務的資源使用,某一個調用的服務出現問題不會影響其他服務調用。
緩存:提供了請求緩存、請求合並實現。
3、基於Hystrix解決服務雪崩效應原理
1)、服務降級
在高並發情況下,防止用戶一直等待,使用服務降級方式(返回一個友好的提示直接給客戶端,不會去處理請求,調用fallBack本地方法),在tomcat中沒有線程進行處理客戶端請求時,不應該讓客戶一直在轉圈等待。目的是為了用戶體驗
如:秒殺服務降級處理-----提示當前請求人數過多,請稍后重試
2)、服務熔斷
服務熔斷的目的是為了保護服務,在高並發情況下,如果請求達到了一定的極限(可以自己設置一個閾值),如果流量超出了設置的閾值情況下,會自動開啟服務保護功能,使用服務降級方式返回一個友好的提示,服務熔斷機制和服務降級是一起使用的。注意Hystrix默認閾值為10個,超過十個則開啟服務保護,服務降級
3)、服務隔離
隔離方式分為線程池隔離和信號量隔離。
4、Hystrix環境搭建
1)在父級pom中引入hystrix依賴
<!-- hystrix斷路器 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>
2)在消費者配置中開啟hystrix斷路器
###訂單服務(消費者)啟動端口號 server: port: 8001 ###服務名稱(服務注冊到eureka名稱) spring: application: name: app-order ###服務注冊到eureka地址 eureka: client: service-url: defaultZone: http://localhost:8100/eureka ###是否注冊自己 register-with-eureka: true ###是否從eureka上獲取注冊上信息 fetch-registry: true ###設置feign客戶端超時時間 ###springCloud默認開啟支持ribbon ribbon: ###指的是建立連接所用的時間,適用於網絡狀況正常的情況下,兩端連接所用的時間 ReadTimeout: 5000 ###指的是建立連接后從服務器讀取到可用資源所用的時間 ConnectTimeout: 5000 ###開啟Hystrix斷路器 feign: hystrix: enabled: true
2)在消費者端定義三個接口及實現類
其中兩個方法中一個方法一個有處理雪崩效應方案,另一個沒有,還有一個方法時配合前兩個方法查看效果的
首先,要使得某個方法有服務保護,得加上@HystrixCommand注解並有處理時回調的提示方法fallbackMethod
Hystrix有兩種請求方式去配置服務保護
方式一:通過注解和接口形式:@HystrixCommand(fallbackMethod=""),其中fallbackMethod作用是服務降級
@HystrixCommand默認開啟線程池隔離方式、開啟服務降級和服務熔斷機制
@HystrixCommand(fallbackMethod="consumerInfoHystrixFallback") @RequestMapping("/consumerUserInfoHystrix") public ResponseBase consumerUserInfoHystrix() { System.out.println("consumerUserInfoHystrix:線程池名稱:"+Thread.currentThread().getName()); return comsumerFeignService.getUserInfo(); } public ResponseBase consumerInfoHystrixFallback() { return setResultSuccess("返回一個友好提示:服務降級,服務器忙,請稍后重試!"); }
全部代碼如下:
接口層:
public interface ConsumerService {
@RequestMapping("/consumerInfo")
public String consumerInfo(String name);
@RequestMapping("/consumerUserInfo")
public ResponseBase consumerUserInfo();
@RequestMapping("/consumerInfoForHystrix")
public ResponseBase consumerInfoForHystrix();
}
實現層:
方式一:通過注解和接口形式
@RestController public class ComsumerServiceImpl extends BaseApiService implements ConsumerService{ @Autowired private ComsumerFeignService comsumerFeignService; @Override @RequestMapping("/consumerInfo") public String consumerInfo(String name) { UserEntity user = comsumerFeignService.getProduceInfo(name); return user==null?"沒有找到用戶信息":user.toString(); } //該方法時沒有解決服務雪崩效應 @Override @RequestMapping("/consumerUserInfo") public ResponseBase consumerUserInfo() { return comsumerFeignService.getUserInfo(); } //Hystrix有兩種請求方式去配置服務保護 //方式一:通過注解和接口形式:@HystrixCommand(fallbackMethod=""),其中fallbackMethod作用是服務降級 //@HystrixCommand默認開啟線程池隔離方式、開啟服務降級和服務熔斷機制 @HystrixCommand(fallbackMethod="consumerInfoHystrixFallback") @RequestMapping("/consumerUserInfoHystrix") public ResponseBase consumerUserInfoHystrix() { System.out.println("consumerUserInfoHystrix:線程池名稱:"+Thread.currentThread().getName()); return comsumerFeignService.getUserInfo(); } public ResponseBase consumerInfoHystrixFallback() { return setResultSuccess("返回一個友好提示:服務降級,服務器忙,請稍后重試!"); } /** * 用於做對比:查看服務保護效果 */ @Override @RequestMapping("/consumerInfoForHystrix") public ResponseBase consumerInfoForHystrix() { System.out.println("consumerInfoHystrix:線程池名稱:"+Thread.currentThread().getName()); return setResultSuccess(); } }
3) 在消費者啟動類上添加啟動Htstrix注解
@SpringBootApplication @EnableEurekaClient @EnableFeignClients @EnableHystrix public class AppComsuApplication { public static void main(String[] args) { SpringApplication.run(AppComsuApplication.class,args); } }
4)此時通過測壓工具以200並發量測試consumerInfoForHystrix方法,再在瀏覽器中調用consumerUserInfoHystrix方法時,會出現
當然:通過控制台可以看到兩個方法調用的線程id是不同的
方式二:通過類的方式
方式一是在每個需要調用的方法上添加注解,這樣代碼既冗余,並且我們只是針對getUserInfo的方法進行降級,開放獨立線程池,但是用方法一是其功能包含可整個方法,這樣是不可取的
方式二是通過類的形式實現的
5) 首先新增一個類,這個類需要實現feign客戶端接口,因為feign客戶端接口實現了生產者接口,這樣就間接取得到了需要降級的方法,這里只針對getUserInfo作演示
@Component public class ConsumerFallback extends BaseApiService implements ComsumerFeignService{ @Override public UserEntity getProduceInfo(String name) { // TODO Auto-generated method stub return null; } //服務降級的友好提示 @Override public ResponseBase getUserInfo() { return setResultError("服務器忙,請稍后重試!以類的方式進行服務降級"); } }
6) 在feign客戶端中引入回調類
@FeignClient(value="app-producer",fallback = ConsumerFallback.class) public interface ComsumerFeignService extends ProducerService{ }
7) 此時我們隨便請求一個包含需要調用getUserInfo服務的方法,能起到降級作用(注意:因為getUserInfo方法的實現層采用了失眠1.5秒,而Htstrix超時時間默認為1秒,所以才會開啟服務保護進行降級給出友好提示)
如請求方法為
//方式二:通過類的方式 @RequestMapping("/consumerUserInfoHystrixForClass") public ResponseBase consumerUserInfoHystrixForClass() { System.out.println("consumerUserInfoHystrix:線程池名稱:"+Thread.currentThread().getName()); return comsumerFeignService.getUserInfo(); }
5、Hystrix設置超時時間
如果調用其他接口超時的時候(默認是1秒時間),如果在一秒中沒有及時響應的話(如調用服務時,服務接口有1.5秒的睡眠),默認情況下業務邏輯是可以執行的,但是直接直接執行服務降級方法(即執行fallbackMethod)
@Override @RequestMapping("/getUserInfo") public ResponseBase getUserInfo() { try { //服務接口產生1.5秒的延遲 Thread.sleep(1500); }catch(Exception e) { } return setResultSuccess("消費消息成功"); }
當然我們可以禁掉Hystrix超時設置,在消費者配置中配置,去掉后除非是網絡蹦了或者延遲嚴重,才會走fallbackMethod方法,正常情況下會成功調用服務
###訂單服務(消費者)啟動端口號 server: port: 8001 ###服務名稱(服務注冊到eureka名稱) spring: application: name: app-order ###服務注冊到eureka地址 eureka: client: service-url: defaultZone: http://localhost:8100/eureka ###是否注冊自己 register-with-eureka: true ###是否從eureka上獲取注冊上信息 fetch-registry: true ###設置feign客戶端超時時間 ###springCloud默認開啟支持ribbon ribbon: ###指的是建立連接所用的時間,適用於網絡狀況正常的情況下,兩端連接所用的時間 ReadTimeout: 5000 ###指的是建立連接后從服務器讀取到可用資源所用的時間 ConnectTimeout: 5000 ###開啟Hystrix斷路器 feign: hystrix: enabled: true ###hystrix禁止服務超時時間 hystrix: command: default: execution: timeout: enabled: false
此時我們再訪問consumerUserInfoHystrix方法(注意該方法調用的 服務就是含有1.5秒睡眠的服務),會正常執行,且返回結果也不是rollbackMethod返回的數據,說明沒有調用
十、SpringCloud config分布式配置中心
1、為什么使用分布式的配置中心
在微服務如果使用傳統的方式管理配置文件,配置文件管理非常復雜,如果生產環境配置文件,可能需要發生改變時,需要重新打成war包,重新讀取配置信息在jvm內存中
2、什么是分布式配置中心
在微服務當中使用同一個服務管理所有服務配置文件信息,能夠實現后台可管理,當服務器正在運行的時候,如果配置文件需要發生改變,可以實現不需要重啟服務器實時更改配置文件信息
注意:熱部署其實底層還是會重啟服務器,不適合生產環境只適合本地開發測試
3、分布式框架配置中心框架
1) 阿波羅 攜程寫的分布式配置中心,擁有圖形界面可管理配置文件信息,配置文件信息存放在數據庫里面
2) springCloud config:沒有后台可管理分布式配置中心,配置文件按信息存放在版本控制器里面(git / svn)
3) 唯品會:使用Zookeeper實現分布式配置中心,將配置存放在zookeeper,持久節點+事件通知
4、springCloud Config分布式配置中心原理
首先分析:分布式配置中心需要哪些組件:
1) web管理系統----后台可以使用圖形界面管理配置文件 SpringCloud config沒有圖形管理配置文件
2) 存放分布式配置文件按服務器(持久化存儲服務器)----springCloud config使用版本控制器存放配置文件信息(git/svn)
3) ConfigServer:緩存配置文件服務器(臨時緩存存放)
4) ConfigClient:讀取ConfigServer配置文件信息
5、搭建環境
1) 搭建git環境
目的:持久化存儲配置文件信息,git環境上文件夾以項目進行區分
1.2) 公司項目中環境是如何區分的
dev 開發環境
sit 測試環境
pre 預發布環境
prd 准生產環境
此演練兩個環境sit環境和prd環境,注意在git環境是私有的需要配置密碼,否則無法訪問
2) 新建maven聚合工程
2.1) 新建eureka 注冊中心
2.2) 新建配置子項目springCloud2.0_config_server
2.2.1)引入依賴
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.1.RELEASE</version> </parent> <!-- 管理依賴 --> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Finchley.M7</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <!--spring-cloud 整合 config-server --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-server</artifactId> </dependency> <!-- SpringBoot整合eureka客戶端 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> </dependencies> <!-- 注意: 這里必須要添加, 否者各種依賴有問題 --> <repositories> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/libs-milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories>
2.2.2) 編寫配置
###注冊中心服務地址 eureka: client: service-url: defaultZone: http://localhost:8100/eureka ###服務注冊名稱 spring: application: name: config-server cloud: config: server: git: ###config-server讀取git環境地址(注意:這里是在git項目中新建的一個config文件夾內的HTTPS地址) uri: https://gitee.com/MR_JiaXianSen/springCloud-config.git search-paths: ###存放的文件目錄服務地址,即文件夾名,"-"表示多個,用"-"區分 - config ###讀取的分支 label: master server: port: 8888
2.2.3) 編寫啟動類
@SpringBootApplication @EnableEurekaClient @EnableConfigServer //開啟config Server服務器 public class AppConfigApplication { public static void main(String[] args) { SpringApplication.run(AppConfigApplication.class, args); } }
2.3) 在git config目錄中創建配置文件,並測試連接
注意:配置文件夾的命名規范:服務名稱-環境.properties(如producer-dev.properties),這里新建兩個配置文件,內容采用key-value形式,且key保持一致
2.4) 此時啟動注冊中心,啟動springCloud2.0_config_server,直接訪問http://localhost:8888/test-configClient-prd.properties或者http://localhost:8888/test-configClient-sit.properties都可直接訪問配置里的內容(注意如果git環境私有並未配置密碼會報404無法訪問)
2.5) 新建客戶端子項目springCloud2.0_config_client
該子項目是通過程序訪問config_server服務端的配置信息(即git版本庫中的配置文件信息)
2.5.1)引入依賴
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.1.RELEASE</version> </parent> <!-- 管理依賴 --> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Finchley.M7</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <!--spring-cloud 整合 config-client --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-client</artifactId> </dependency> <!-- SpringBoot整合eureka客戶端 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <!-- 注意:一定要添加此依賴,否則會啟動不久自動關閉 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> <!-- 注意: 這里必須要添加, 否者各種依賴有問題 --> <repositories> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/libs-milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories>
2.5.2) 添加配置bootstrap.yml
###服務名稱:注意:這里的服務名要與git配置的文件名(配置名+環境)中的配置名保持一致,項目啟動時他是根據 ###該服務名稱去從git項目目錄中找與之配對的配置文件 spring: application: name: test-configClient cloud: config: ###表示讀取的版本環境 profile: sit discovery: ###表示讀取的config-server環境,此要與config-server子項目中 的服務別名要保持一致 service-id: config-server ###表示開啟讀取權限 enabled: true ###注冊中心服務地址 eureka: client: service-url: defaultZone: http://localhost:8100/eureka ###表示服務端口號 server: port: 8882
2.5.3)新增訪問controller即啟動類
@RestController public class TestClientController { @Value("${springCloudLearning}") //此要與git中配置文件的key保持一致 private String key; @RequestMapping("/getInfo") public String getInfo() { return key; } }
@SpringBootApplication @EnableEurekaClient public class AppClientAppliction { public static void main(String[] args) { SpringApplication.run(AppClientAppliction.class, args); } }
2.5.4)訪問:
2.6) 實時刷新配置
當git配置文件中的內容更改后,因為本地緩存原因,客戶端不能實時獲得更改后的配置信息,平常做法是重啟項目
springCloud分布式配置中心可以采用手動刷新或者自動刷新實時更新配置文件更改后的內容,手動刷新和自動刷新都不要重啟項目
1) 手動刷新--需要人工調用接口讀取最新配置(監控中心)
2) 自動刷新---消息總線進行實時通知(不建議:對性能不好)
以下演示手動刷新方式
2.6.1) 配置actuator監控中心,先在springCloud_config_client子項目中新增依賴
<!-- actuator監控中心 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
2.6.2) 新增配置
###服務名稱:注意:這里的服務名要與git配置的文件名(配置名+環境)中的配置名保持一致,項目啟動時他是根據 ###該服務名稱去從git項目目錄中找與之配對的配置文件 spring: application: name: test-configClient cloud: config: ###表示讀取的版本環境 profile: sit discovery: ###表示讀取的config-server環境,此要與config-server子項目中 的服務別名要保持一致 service-id: config-server ###表示開啟讀取權限 enabled: true ###注冊中心服務地址 eureka: client: service-url: defaultZone: http://localhost:8100/eureka ###表示服務端口號 server: port: 8882 ###開啟所有的監控端點 management: endpoints: web: exposure: include: "*"
2.6.3) 在controller類中新增@RefreshScope注解
@RestController @RefreshScope public class TestClientController { @Value("${springCloudLearning}") //此要與git中配置文件的key保持一致 private String key; @RequestMapping("/getInfo") public String getInfo() { return key; } }
2.6.4) 此時如果git配置文件信息發生改變,需要以post方式手動調用actuator/refresh接口進行刷新本地緩存,如這里是http://localhost:8882/actuator/refresh,從而每次調用請求時(http://localhost:8882/getInfo)都可以獲得配置文件中最新的信息
配置中心的源碼:git@gitee.com:MR_JiaXianSen/spring_cloud_template.git
十一、微服務網關技術
1、網關API(接口) Gateway(網關) --接口網關注意:接口沒有界面
2、接口什么背景下產生的?
在面向服務架構和微服務背景下產生的,目的是為了解耦,rpc遠程調用中產生的
3、接口如何分類
開發接口----提供給其他機構合作伙伴進行調用(必須在外網訪問) 如螞蟻開放平台,微信公眾號開發
需要通過appId(目的授權一些接口授權) +appsecret生成accessToken進行通訊
內部接口----一般只能在居於網中進行訪問,服務與服務調用之間關系都在同一個微服務系統中,目的是為了保證安全
4、網關概念
1) 相當於客戶端請求統一先請求到網關服務器上,然后由網關服務器進行轉發到實際服務器地址上。功能類似於nginx
2) 過濾器與網關區別是什么?
過濾器是攔截單個tomcat服務器進行攔截請求,網關是攔截整個微服務所有請求
3) Nginx與Zuul的區別
相同點: Zuul和Nginx都可以實現負載均衡、反向代理(掩飾真實IP)、過濾請求,實現網關效果
不同點:Nginx采用C語言編寫,Zuul采用java語言編寫
Zuul負載均衡實現:采用ribbon+eruka實現本地負載均衡;Nginx負載均衡實現:采用服務器端實現負載均衡
性能:Nginx相對於Zuul功能更加強大,因為Nginx整合一些腳本語言(如Lua),Nginx適合於服務器端負載均衡
Zuul更適合微服務中,最好是nginx+zuul實現網關(nginx作反向代理,Zuul對微服務實現網關攔截)
注意:Zuul默認開啟Ribbon本地負載均衡
5、環境搭建
5.1) 新建項目,引入依賴
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.1.RELEASE</version> </parent> <!-- 管理依賴 --> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Finchley.M7</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-zuul</artifactId> </dependency> <!--SpringCloud eureka-server --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> </dependencies> <!-- 注意: 這里必須要添加, 否者各種依賴有問題 --> <repositories> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/libs-milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories>
5.2) 新建配置application.yml
###服務注冊地址 eureka: client: serviceUrl: defaultZone: http://localhost:8100/eureka/ ###api網關端口號 server: port: 80 ###網關名稱 spring: application: name: service-zuul zuul: routes: ###定義的服務規則,可以自定義 api-a: ###當客戶端發送請求127.0.0.1:80/api-producer開頭的 都會轉發到生產者服務 path: /api-producer/** ###生產者服務別名,zuul網關默認整合ribbon自動實現負載均衡輪詢效果 serviceId: app-producer api-b: ###當客戶端發送請求127.0.0.1:80/api-consumer開頭的 都會轉發到消費者服務 path: /api-consumer/** serviceId: app-consumer
5.3) 新增啟動類,開啟Zuul功能
@EnableZuulProxy @EnableEurekaClient @SpringBootApplication public class ZuulApplication { public static void main(String[] args) { SpringApplication.run(ZuulApplication.class, args); } }
5.4) 此時可以測試Zuul的方向代理過程,先啟動Zuul子項目,再啟動eureka注冊中心,再啟動生產者,此時可以通過網關代理訪問生產者,顯示內容一樣
http://localhost:80/api-producer/getProduce?name=rose
http://localhost:8003/getProduce?name=rose
6、利用Zuul作過濾器
6.1) 編寫過濾器
此過濾功能時請求未傳userToken參數,會提示報錯進行攔截
/** * 網關過濾器 * @author 佳先森 * */ @Component public class TokenFilter extends ZuulFilter{ /** * 編寫過濾器攔截業務邏輯代碼 * 環境:攔截所有服務接口,判斷是否有傳userToken */ public Object run() throws ZuulException { //攔截所有的服務接口,判斷服務接口是否有傳遞UserToken參數 //1.首先要獲取上下文 RequestContext currentContext = RequestContext.getCurrentContext(); //2.獲取request對象 HttpServletRequest request = currentContext.getRequest(); //3.一般獲取token的時候 從請求頭中獲取token參數,這里只是演示方便 String userToken = request.getParameter("userToken"); if(StringUtils.isEmpty(userToken)) { //不會繼續執行...不會調用服務接口,網關服務直接響應給客戶端 //返回一個錯誤提示 currentContext.setSendZuulResponse(false); currentContext.setResponseBody("userToken is null"); currentContext.setResponseStatusCode(401); return null; } //正行執行調用其他服務接口... return null; } /** * 判斷過濾器是否生效 */ public boolean shouldFilter() { return true; } /** * 表示過濾器執行順序,當一個請求在同一階段時存在 * 多個過濾器時,多個過濾器執行順序問題 */ public int filterOrder() { return 0; } /** * 表示過濾器類型 "pre"表示請求之前進行 */ public String filterType() { return "pre"; } }
6.2) 繼續啟動項目
7、網關之集群
7.1) 先配置nginx環境
7.1.1) 找到電腦hosts文件,win7 所在目錄為C:\Windows\System32\drivers\etc
目的是對虛擬機ip進行映射
192.168.174.128 www.ibo.com
7.1.2) 在nginx配置文件中添加如下代碼
###上游服務器 集群輪詢機制
upstream backServer{ server 192.168.2.175:81; server 192.168.2.175:82; } server { listen 80; server_name www.ibo.com; location / {
###指定上游服務器負載均衡服務器 proxy_pass http://backServer/; index index.html index.htm; }
此時通過分別開啟以端口號為81和82的網關配合nginx可以實現集群后的負載均衡
十二、Swagger
1、引入
隨着微服務架構體系的發展和應用,為了前后端能夠更好的集成與對接,為了項目方便交互,每個項目都需要提供相應的API文檔
2、為什么需要swagger
傳統:
1) 如前后端分離那些事,如果后端編寫的接口文檔更新沒及時通知前端;API接口返回信息不明確
2) 接口文檔太多,不便於管理
swagger:
1)、功能豐富;支持多種注解,自動生成接口文檔,支持在界面測試API接口功能
2)、及時更新
3)、整合簡單。通過添加pom依賴和簡單配置,內嵌與應用中就可以同時發布API接口文檔界面,不需要部署獨立服務
3、搭建環境
1)、引入依賴
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.1.RELEASE</version> </parent> <!-- 管理依賴 --> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Finchley.M7</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--swagger2--> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.8.0</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.8.0</version> </dependency> </dependencies> <!-- 注意: 這里必須要添加, 否者各種依賴有問題 --> <repositories> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/libs-milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories>
2) 新增配置application.yml
###服務啟動端口號 server: port: 8000 ###服務名稱(服務注冊到eureka名稱) spring: application: name: springboot-swagger
3) 新增配置類
/** * 注意配置的信息可以存放在分布式配置中心里面 * @author 佳先森 * */ @Configuration @EnableSwagger2 //開啟swagger2功能 public class SwaggerConfig { @Bean public Docket createRestApi() { return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).select() //生成API掃包范圍(指定哪個包需要生成api) .apis(RequestHandlerSelectors.basePackage("com.ibo.api")).paths(PathSelectors.any()).build(); } //創建api文檔信息 private ApiInfo apiInfo() { return new ApiInfoBuilder().title("XX項目對接文檔").description("平台導入功能") .termsOfServiceUrl("http://www.ibo.com") .version("1.0").build(); } }
4) 新增需要生成api的指定controller,注意該controller需要在指定的掃描包中,這里根據上個配置應該是com.ibo.api
@Api("SwaggerDemo控制器") @RestController public class SwaggerController { @ApiOperation("swagger演示接口")//接口描述 //@RequestMapping("/swaggerIndex")//注意在微服務項目中最好用get/post等請求規范寫法 @GetMapping("/swaggerIndex") public String swaggerIndex() { System.out.println("swaggerIndex"); return "swaggerIndex"; } @ApiOperation("獲取用戶信息") //接口描述 @ApiImplicitParam(name="username",value="用戶信息參數",required=true,dataType="String")//傳參參數描述 @PostMapping("/getUserInfo") public String getUserInfo(String username) { System.out.println("用戶名為:"+username); return "userName:"+username; } }
5) 新建啟動類
@SpringBootApplication public class SwaggerApplication { public static void main(String[] args) { SpringApplication.run(SwaggerApplication.class, args); } }
6)啟動項目, 訪問平台管理界面http://localhost:8000/swagger-ui.html#/swagger-controller
4、swagger集群
在微服務中,swagger是每個服務集成的,那么如何將整個微服務中的swagger進行合成。
1、我們可以使用Zuul+Swagger實現管理每個微服務API文檔
2、可以使用Nginx + Swagger以項目不同區分跳轉不同的接口文檔
SpringBoot支持Swagger管理,只需要Zuul網關添加對應服務Sawgger文檔即可
集群環境搭建:
4.1) 生產者和消費者和網關子項目中同時引入swagger依賴,此依賴就等同於上面兩個,是spring對其兩個依賴進行了整合
<!-- 引入swagger依賴 --> <dependency> <groupId>com.spring4all</groupId> <artifactId>swagger-spring-boot-starter</artifactId> <version>1.7.0.RELEASE</version> </dependency>
4.2) 分別在生產者和消費者配置中添加swagger配置
###定義swagger掃包范圍 swagger: base-package: com.ibo.serviceImpl
4.3) 在生產者和消費者調用類上添加@Api注解並添加方法描述
如生產者:
@RestController @Api("生產者服務") public class ProduceServiceImpl extends BaseApiService implements ProducerService{ @Value("${server.port}") private String serverPort; @Override @ApiOperation("獲得服務信息") @ApiImplicitParam(name="name",value="用戶名",required=true,dataType="String") @GetMapping("/getProduce") public UserEntity getProduceInfo(@RequestParam("name")String name) { UserEntity userEntity = new UserEntity(); userEntity.setName(name+"端口號:"+serverPort); userEntity.setAge(24); return userEntity; }
4.4) 在啟動類中開啟swagger注解
@EnableSwagger2Doc //生成swagger注解文檔 @SpringBootApplication public class AppComsuApplication { public static void main(String[] args) { SpringApplication.run(AppComsuApplication.class,args); } }
4.5) 在網關啟動類中增加配置與開啟swagger功能
@EnableZuulProxy @EnableEurekaClient @EnableSwagger2Doc @SpringBootApplication public class ZuulApplication { public static void main(String[] args) { SpringApplication.run(ZuulApplication.class, args); } //zuul配置能夠使用config實現實時更新 @RefreshScope @ConfigurationProperties("zuul") public ZuulProperties zuulProperties() { return new ZuulProperties(); } //添加文檔來源 @Component @Primary class DocumentationConfig implements SwaggerResourcesProvider{ @Override public List<SwaggerResource> get() { List<SwaggerResource> resource = new ArrayList<>(); resource.add(swaggerResource("api-producer","/api-producer/v2/api-docs","2.0")); resource.add(swaggerResource("api-consumer","/api-consumer/v2/api-docs","2.0")); return resource; } /** * * @param name 此參數可以隨意定義,用於查詢接口模塊名稱 * @param location /api-producer/v2/api-docs 中api-producer取自於網關的配置,這是對所有生產者訪問時的域名前綴 * @param version 版本號 * @return */ private SwaggerResource swaggerResource(String name, String location,String version) { SwaggerResource swaggerResource = new SwaggerResource(); swaggerResource.setName(name); swaggerResource.setLocation(location); swaggerResource.setSwaggerVersion(version); return swaggerResource; } } }
4.6) 此時如果訪問http://localhost:82/swagger-ui.html#/swagger-controller 通過select a spec可以查看生產者和消費者各自的接口信息
十三、服務鏈路追蹤
微服務架構是一個分布式架構,它按業務划分服務單元,一個分布式系統往往有很多個服務單元。由於服務單元數量眾多,業務的復雜性,如果出現了錯誤和異常,很難去定位。主要體現在,一個請求可能需要調用很多個服務,而內部服務的調用復雜性,決定了問題難以定位。所以微服務架構中,必須實現分布式鏈路追蹤,去跟進一個請求到底有哪些服務參與,參與的順序又是怎樣的,從而達到每個請求的步驟清晰可見,出了問題,很快定位。而Zipkin能解決此問題
1、環境搭建
1)下載zkpkin jar包:https://dl.bintray.com/openzipkin/maven/io/zipkin/java/zipkin-server/
2)通過java -jar 命令運行jar包
輸入http://localhost:9411進入首界面
3)創建兩個子項目,maven配置保持差不多一致,只是端口號不一樣
3.1) 引入maven
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.3.RELEASE</version> <relativePath/> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> <spring-cloud.version>Finchley.RELEASE</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zipkin</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
3.2) 引入配置
主意運行的jar包端口號為9411
server: port: 10013 spring: zipkin: ##對應的運行jar包域名 base-url: http://localhost:9411 application: name: zipkinServiceOne
3.3) 創年啟動類
實例1:
@SpringBootApplication
@RestController public class ZipKinServiceOneApplication { private static Logger logger = Logger.getLogger(ZipKinServiceOneApplication.class); @Autowired RestTemplate restTemplate; public static void main(String[] args) { SpringApplication.run(ZipKinServiceOneApplication.class, args); } @Bean public RestTemplate getRestTemplate() { return new RestTemplate(); } @GetMapping("/getInfoOne") public String callHome() { logger.log(Level.INFO,"calling trace service-one"); return restTemplate.getForObject("http://localhost:10014/getInfoTwo", String.class); } @GetMapping("/info") public String info() { logger.log(Level.INFO,"calling trace service-one"); return "i am service-one"; } @Bean public Sampler defaultSampler() { return Sampler.ALWAYS_SAMPLE; } }
實例2:
@SpringBootApplication @RestController public class ZipKinServiceTwoApplication { private static Logger logger = Logger.getLogger(ZipKinServiceTwoApplication.class); @Autowired RestTemplate restTemplate; public static void main(String[] args) { SpringApplication.run(ZipKinServiceTwoApplication.class, args); } @Bean public RestTemplate getRestTemplate() { return new RestTemplate(); } @GetMapping("/getInfoTwo") public String callHome() { logger.log(Level.INFO,"calling trace service-two"); return restTemplate.getForObject("http://localhost:10013/info", String.class); } @GetMapping("/info") public String info() { logger.log(Level.INFO,"calling trace service-two"); return "i am service-two"; } @Bean public Sampler defaultSampler() { return Sampler.ALWAYS_SAMPLE; } }
3.4) 訪問10013端口號的getInfoOne服務:http://localhost:10013/getInfoOne
3.4)此時查看zipkin控制太的依賴分析,可以看到鏈路調用情況