版權聲明:本文爲CSDN博主「niaobirdfly」的原創文章,遵循 CC 4.0 BY-SA 版權協議,
原文鏈接:https://blog.csdn.net/hellozpc/article/details/84144453
如果此教程對您有幫助,就請有錢的捧個錢場,沒錢的捧個人場(轉載分享)哦~
更多精彩的免費教程請訪問 https://blog.csdn.net/hellozpc
本教程代碼下載地址 https://download.csdn.net/download/zpcandzhj/10780804
Spring Cloud 微服務教程(二)
文章目錄
- Spring Cloud 微服務教程(二)
- 1.教程大綱
- 2.Feign客戶端-聲明式REST調用
- 2.1.分析
- 2.2.Feign的簡介
- 2.3.快速入門
- 2.3.1.導入依賴
- 2.3.2.創建一個ItemFeignClient接口
- 2.3.3.改造ItemService
- 2.3.4.在啓動類中添加 @EnableFeignClients 註解
- 2.3.5.重啓測試
- 2.4.到底發生了什麼?
- 2.5.Feign的多參數構造
- 2.6.設置統一的hystrix fallback接口
- 3.服務網關 Spring Cloud Zuul
- 3.1.分析
- 3.2.Zuul的簡介
- 3.3.使用Zuul之後的架構
- 3.4.快速入門
- 3.4.1.創建工程microsrvice-api-gateway
- 3.4.2.導入依賴
- 3.4.3.編寫啓動類ApiGatewayApplication
- 3.4.4.編寫application.yml文件
- 3.4.5.編寫路由規則
- 3.4.6.啓動測試
- 3.5.面向服務的路由
- 3.6.zuul配置詳解
- 3.6.1.指定服務id
- 3.6.2.忽略指定服務
- 3.6.3.忽略所有服務,只是有路由指定
- 3.6.4.同時配置path和url
- 3.6.5.面向服務配置,不破壞Hystrix、Ribbon特性
- 3.6.6.使用正則表達式指定路由規則
- 3.6.7.路由前綴
- 3.6.8.忽略某些路徑
- 3.7.過濾器
- 3.8.過濾器實戰
- 3.9.默認開啓ribbon
- 3.10.動態網關
- 3.11.網關集羣
- 4.分佈式配置中心 Spring Cloud Config
- 4.1.之前的配置文件用法存在什麼問題?
- 4.2.Spring Cloud Config 簡介
- 4.3.快速入門 – 搭建Config Server
- 4.3.3.編寫入口ConfigApplication
- 4.3.4.編寫application.yml
- 4.3.5.啓動測試
- 4.4.快速入門 – 搭建Config Client
- 4.5.手動更新運行中的配置文件
- 4.6.藉助與git的webhook(web鉤子)實現自動更新
- 4.7.Config Client配置優化-註冊到Eureka中
- 4.7.1.分析
- 4.7.2.在 microservice-config-server中添加Eureka的依賴
- 4.7.3.修改application.yml文件
- 4.7.4.ConfigApplication添加@EnableEurekaClient註解
- 4.7.5.在microservice-item中修改bootstrap.yml配置
- 4.7.6.測試
- 4.8.動態網關
- 5.消息總線 Spring Cloud Bus
- 5.1.分析
- 5.2.Spring Cloud Bus消息總線的簡介
- 5.3.RabbitMQ的安裝
- 5.4.使用Spring Cloud Bus的架構
- 5.5.實現
- 5.5.1.在 microservice-item添加依賴
- 5.5.2.在bootstrap.yml添加rabbitmq的配置
- 5.5.3.修改web hook的地址
- 5.5.4.啓動測試
- 5.5.5.流程總結
- 5.6.架構優化
- 6.整合consul&zookeeper作爲註冊中心
- 7.整合swagger API管理
1.教程大綱
1、Feign聲明式客戶端(REST調用)
2、服務網關 Spring Cloud Zuul
3、分佈式配置中心Spring Cloud Config
4、消息總線 Spring Cloud Bus
5、整合consul、zookeeper作爲註冊中心
6、整合swagger 統一管理API
2.Feign客戶端-聲明式REST調用
2.1.分析
之前《SpringCloud教程(一)》中我們通過RestTemplate調用REST服務,代碼是這樣的:
@HystrixCommand(fallbackMethod = "queryItemByIdFallbackMethod")
public Item queryItemById(Long id) {
String itemUrl = "http://app-item/item/{id}";
Item result = restTemplate.getForObject(itemUrl, Item.class, id);
return result;
}
- 1
- 2
- 3
- 4
- 5
- 6
雖然使用了Ribbon和Hystrix可以實現負載均衡和容錯處理,但是這個編碼在實現大量業務時會顯得太過於冗餘(如,多參數的URL拼接)。
思考:有沒有更加優雅的實現呢?
2.2.Feign的簡介
項目主頁:https://github.com/OpenFeign/feign
2.3.快速入門
在訂單微服務microservice order中增加對Feign的支持。
2.3.1.導入依賴
<!--springboot 整合fegnin客戶端-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
- 1
- 2
- 3
- 4
- 5
2.3.2.創建一個ItemFeignClient接口
package com.zpc.order.feign;
import com.zpc.order.entity.Item;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
/**
* 申明這是一個Feign客戶端,並且指明服務id
* @author Evan
*/
@FeignClient(value = "app-item")
public interface ItemFeignClient {
/**
* 這裏定義了類似於SpringMVC用法的方法,就可以進行RESTful方式的調用了
*
* @param id
* @return
*/
@RequestMapping(value = "/item/{id}", method = RequestMethod.GET)
Item queryItemById(@PathVariable("id") Long id);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
2.3.3.改造ItemService
@Autowired
private ItemFeignClient itemFeignClient;
@HystrixCommand(fallbackMethod = “queryItemByIdFallbackMethod”)
public Item queryItemById3(Long id) {
String itemUrl = “http://app-item/item/{id}”;
Item result = itemFeignClient.queryItemById(id);
System.out.println("===========HystrixCommand queryItemById-線程池名稱:" + Thread.currentThread().getName() + “訂單系統調用商品服務,result:” + result);
return result;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
2.3.4.在啓動類中添加 @EnableFeignClients 註解
package com.zpc.order.runner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication//申明這是一個Spring Boot項目
@EnableEurekaClient
@EnableHystrix
@EnableFeignClients(basePackages =“com.zpc.order.feign”)
@ComponentScan(basePackages = {“com.zpc.order.controller”, “com.zpc.order.service”,“com.zpc.order.properties”})//手動指定bean掃描範圍
public class OrderApp {
public static void main(String[] args) {
SpringApplication.run(OrderApp.class, args);
}
/**
* 向Spring容器中定義RestTemplate對象
* @return
*/
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate(new OkHttp3ClientHttpRequestFactory());
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
2.3.5.重啓測試
測試結果,一切正常。
2.4.到底發生了什麼?
@Autowired
private ItemFeignClient itemFeignClient;
@HystrixCommand(fallbackMethod = “queryItemByIdFallbackMethod”)
public Item queryItemById3(Long id) {
String itemUrl = “http://app-item/item/{id}”;
//Item result = restTemplate.getForObject(itemUrl, Item.class, id);
Item result = itemFeignClient.queryItemById(id);
return result;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
寫這樣的代碼,就可以訪問RESTful服務啦?
流程分析:
1、由於我們在入口程序使用了@EnableFeignClients
註解,Spring啓動後會掃描標註了@FeignClient
註解的接口,然後生成代理類。
2、我們在@FeignClient
接口中指定了value,其實就是指定了在Eureka
中的服務名稱。
3、在FeignClient
中的定義方法以及使用了SpringMVC
的註解,Feign
就會根據註解中的內容生成對應的URL,然後基於Ribbon
的負載均衡去調用REST
服務。
爲什麼使用的是SpringMVC
的註解?
i.其實,Feign是有自己的註解的@RequestLine
,是因爲Spring Cloud對Feign做了增強,兼容了SpringMVC的註解,使我們的學習成本更低
ii.專業的解釋是這樣的:
org.springframework.cloud.netflix.feign.FeignClientsConfiguration
設置的默認契約是SpringMVC契約。
2.5.Feign的多參數構造
服務方參數列表使用註解@RequestParam
、@PathVariable
修飾
2.6.設置統一的hystrix fallback接口
參考:http://www.ityouknow.com/springcloud/2017/05/16/springcloud-hystrix.html
一般在實際開發中fallback 方法不會直接寫在接口方法所在類裏,那樣太雜亂,例如之前訂單工程中的寫法:
@HystrixCommand(fallbackMethod = "queryItemByIdFallbackMethod")
public Item queryItemById3(Long id) {
Item result = itemFeignClient.queryItemById(id);
System.out.println("===========HystrixCommand queryItemById-線程池名稱:" + Thread.currentThread().getName() + "訂單系統調用商品服務,result:" + result);
return result;
}
public Item queryItemByIdFallbackMethod(Long id) {
return new Item(id, “查詢商品信息出錯!”, null, null, null);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
Order工程改進(將fallback方法放到類中):
1.不在方法上使用@HystrixCommand
註解
//@HystrixCommand(fallbackMethod = "queryItemByIdFallbackMethod")
public Item queryItemById3(Long id) {
Item result = itemFeignClient.queryItemById(id);
System.out.println("===========HystrixCommand queryItemById-線程池名稱:" + Thread.currentThread().getName() + "訂單系統調用商品服務,result:" + result);
return result;
}
- 1
- 2
- 3
- 4
- 5
- 6
2.創建回調類
package com.zpc.order.fallback;
import com.zpc.order.entity.Item;
import com.zpc.order.feign.ItemFeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.PathVariable;
/**
* 此類中的方法專門用於服務降級,該類一般要實現調用遠程服務的接口(這樣保證方法名一致)
*/
@Component
public class ItemServiceFallback implements ItemFeignClient {
/**
* 服務降級的方法要和原方法一致(名稱、參數列表)
* @param id
* @return
*/
@Override
public Item queryItemById(@PathVariable("id") Long id) {
return new Item(null, "服務降級方法queryItemById", null, "服務降級方法queryItemById", null);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
3.在Feign客戶端中添加fallback
屬性
package com.zpc.order.feign;
import com.zpc.order.entity.Item;
import com.zpc.order.fallback.ItemServiceFallback;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
/**
* 申明這是一個Feign客戶端,並且指明服務id
* 實際開發中ItemFeignClient一般直接繼承(extends)服務提供方的接口以避免代碼重複(例如Item工程會以jar包的形式提供ItemService接口)
* @author Evan
*/
@FeignClient(value = "app-item",fallback = ItemServiceFallback.class)
public interface ItemFeignClient{
/**
* 這裏定義了類似於SpringMVC用法的方法,就可以進行RESTful方式的調用了
*
* @param id
* @return
*/
@RequestMapping(value = "/item/{id}", method = RequestMethod.GET)
Item queryItemById(@PathVariable("id") Long id);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
4.配置文件中開啓hystrix
#開啓hystrix斷路器
feign:
hystrix:
enabled: true
- 1
- 2
- 3
- 4
5.修改啓動類,增加包掃描
@ComponentScan(basePackages = {"com.zpc.order.controller", "com.zpc.order.service","com.zpc.order.properties","com.zpc.order.fallback"})//手動指定bean掃描範圍
- 1
6.重新啓動應用測試
服務都正常的情況:
停止Item服務,訪問Order服務沒有配置hystrix熔斷的普通方法:
停止Item服務,訪問Order服務中配置了hystrix熔斷的方法:
配置fallback類OK!
3.服務網關 Spring Cloud Zuul
3.1.分析
通過前面的學習,使用Spring Cloud實現微服務的架構基本成型,大致是這樣的:
-
我們使用
Spring Cloud Netflix
中的Eureka作爲服務註冊中心完成服務註冊與發現;而服務間通過Feign
和Ribbon
實現服務的消費以及負載均衡;通過Spring Cloud Config
實現了應用多環境的外部化配置以及版本管理。爲了使得服務集羣更爲健壯,使用Hystrix
的融斷機制來避免在微服務架構中個別服務出現異常時引起的故障蔓延。 -
思考:在該架構中,我們的服務集羣包含:內部服務Service A和Service B,他們都會註冊與訂閱服務至
Eureka Server
,而Open Service是一個對外的服務,通過負載均衡公開至服務調用方。我們把焦點聚集在對外服務這塊,這樣的實現是否合理,或者是否有更好的實現方式呢?
先來說說這樣架構需要做的一些事兒以及存在的不足:
- 首先,破壞了服務無狀態特點。
爲了保證對外服務的安全性,我們需要實現對服務訪問的權限控制,而開放服務的權限控制機制將會貫穿並污染整個開放服務的業務邏輯,這會帶來的最直接問題是,破壞了服務集羣中REST API無狀態的特點。
從具體開發和測試的角度來說,在工作中除了要考慮實際的業務邏輯之外,還需要額外可續對接口訪問的控制處理。 - 其次,無法直接複用既有接口。
當我們需要對一個既有的集羣內的接口,實現外部訪問時,我們不得不通過在原有接口上增加校驗邏輯,或增加一個代理調用來實現權限控制,無法直接複用原有的接口。
面對類似上面的問題,我們要如何解決呢? 答案是:服務網關
!
-
爲了解決上面這些問題,我們需要將權限控制、日誌收集這樣的東西從我們的服務單元中抽離出去,而最適合這些邏輯的地方就是處於對外訪問最前端的地方,我們需要一個更強大一些的均衡負載器 服務網關。
-
服務網關是微服務架構中一個不可或缺的部分。通過服務網關統一向外系統提供REST API的過程中,除了具備服務路由、負載均衡功能之外,它還具備了權限控制等功能。Spring Cloud Netflix中的Zuul就擔任了這樣的一個角色,爲微服務架構提供了前門保護的作用,同時將權限控制這些較重的非業務邏輯內容遷移到服務路由層面,使得服務集羣主體能夠具備更高的可複用性和可測試性。
3.2.Zuul的簡介
官網:https://github.com/Netflix/zuul
3.3.使用Zuul之後的架構
從架構圖中可以看出,客戶端請求微服務時,先經過Zuul之後再請求,這樣就可以將一些類似於校驗的業務邏輯放到zuul中完成。
而微服務自身只需要關注自己的業務邏輯即可。當然在Zuul上層也可以搭建Nginx、F5
等負載均衡設施。
3.4.快速入門
3.4.1.創建工程microsrvice-api-gateway
3.4.2.導入依賴
<?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.zpc.microservice</groupId> <artifactId>microservice-api-gateway</artifactId> <version>1.0-SNAPSHOT</version> <packaging>jar</packaging> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.6.RELEASE</version> </parent> <dependencyManagement> <dependencies> <!-- 導入Spring Cloud的依賴管理 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Finchley.SR1</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies>
<!–整合zuul網關–>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
3.4.3.編寫啓動類ApiGatewayApplication
package com.zpc.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
@EnableZuulProxy
@SpringBootApplication
public class ApiGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ApiGatewayApplication.class, args);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
3.4.4.編寫application.yml文件
server:
port: 8087 #服務端口
spring:
application:
name: app-zuul-gateway #指定服務名
- 1
- 2
- 3
- 4
- 5
3.4.5.編寫路由規則
我們編寫路由規則:
server:
port: 8087 #服務端口
spring:
application:
name: app-zuul-gateway #指定服務名
zuul:
routes: #定義服務轉發規則
item-service: #item-service這個名字任意取的
path: /item-service/** #配置請求URL的請求規則
url: http://127.0.0.1:8081 #真正的微服務地址,path匹配的請求都轉發到這裏
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
3.4.6.啓動測試
同時啓動商品微服務工程、網關工程。
可以看到,已經通過zuul訪問到了商品微服務。
3.5.面向服務的路由
在快速入門中我們已經通過了URL映射,訪問到了商品微服務。這樣做會存在一個問題,就是,如果商品微服務的地址發生了改變怎麼辦?
很自然的能夠想到,不應該配置具體的url而是走Eureka註冊中心獲取地址。
3.5.1.添加Eureka服務的依賴
<!--整合eureka客戶端-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
- 1
- 2
- 3
- 4
- 5
3.5.2.修改application.yml配置文件
server:
port: 8087 #服務端口
spring:
application:
name: app-zuul-gateway #指定服務名
###服務註冊到eureka註冊中心的地址
eureka:
client:
service-url:
defaultZone: http://zpc:[email protected]:8100/eureka/,http://zpc:[email protected]:9100/eureka/
###因爲該應用爲服務提供者,是eureka的一個客戶端,需要註冊到註冊中心
register-with-eureka: true
###是否需要從eureka上檢索服務
fetch-registry: true
instance:
prefer-ip-address: true #將自己的ip地址註冊到Eureka服務中
ip-address: 127.0.0.1
instance-id: ${spring.application.name}###${server.port} #指定實例id
zuul:
routes: #定義服務轉發規則
item-service: #item-service這個名字是任意寫的
path: /item-service/** #匹配item-service的請求app-item服務
#url: http://127.0.0.1:8081 #真正的微服務地址
serviceid: app-item
order-service: #名字儘量和業務系統相關
path: /order-service/** #匹配order-service的請求app-order服務
serviceid: app-order
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
3.5.3.啓動測試
啓動Eureka註冊中心、Item服務、order服務、網管服務4個工程。
查看Eureka註冊中:
發現已經有gateway在註冊中心了。
接下來測試,功能是否正常:
發現一切正常。
3.6.zuul配置詳解
3.6.1.指定服務id
3.6.2.忽略指定服務
3.6.3.忽略所有服務,只是有路由指定
3.6.4.同時配置path和url
3.6.5.面向服務配置,不破壞Hystrix、Ribbon特性
zuul:
routes: #定義服務轉發規則
item-service: #item-service這個名字是任意寫的
path: /item-service/** #匹配item-service的url路徑請求app-item服務
#url: http://127.0.0.1:8081 #真正的微服務地址
serviceid: app-item
order-service: #名字儘量和業務系統相關
path: /order-service/** #匹配order-service的url路徑請求app-order服務
serviceid: app-order #指定Eureka註冊中心的服務id
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
3.6.6.使用正則表達式指定路由規則
3.6.7.路由前綴
3.6.8.忽略某些路徑
3.7.過濾器
過濾器是Zuul的重要組件。
3.7.1.過濾器ZuulFilter
ZuulFilter是一個抽象類,其實現類需要實現4個方法:
- shouldFilter:返回一個Boolean值,判斷該過濾器是否需要執行。返回true執行,返回false不執行。
- run:過濾器的具體業務邏輯。
- filterType:返回字符串代表過濾器的類型
a)pre:請求在被路由之前執行
b)routing:在路由請求時調用
c)post:在routing和errror過濾器之後調用
d)error:處理請求時發生錯誤調用 - filterOrder:通過返回的int值來定義過濾器的執行順序,數字越小優先級越高。
3.7.2.執行流程
3.8.過濾器實戰
需求:通過編寫過濾器實現用戶是否登錄的檢查。
實現:通過判斷請求中是否有token,如果有認爲就是已經登錄的,如果沒有就認爲是非法請求,響應401.
3.8.1.編寫UserLoginZuulFilter
package com.zpc.gateway.filter; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; import org.apache.commons.lang.StringUtils; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; /** * 網關過濾器 * 加入到Spring容器 * @author Evan */ @Component public class UserLoginZuulFilter extends ZuulFilter {
@Override public boolean shouldFilter() { return true; // 該過濾器需要執行 } @Override public Object run() { //編寫業務邏輯 RequestContext requestContext = RequestContext.getCurrentContext(); HttpServletRequest request = requestContext.getRequest(); String token = request.getParameter("token"); if(StringUtils.isEmpty(token)){ requestContext.setSendZuulResponse(false); // 過濾該請求,不對其進行路由 requestContext.setResponseStatusCode(401); // 設置響應狀態碼 requestContext.setResponseBody(" token is empty!!"); // 設置響應狀態碼 return null; } return null; } @Override public String filterType() { return "pre"; // 設置過濾器類型爲:pre } @Override public int filterOrder() { return 0;// 設置執行順序爲0 }
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
入口程序:
package com.zpc.gateway.runner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.context.annotation.ComponentScan;
@EnableZuulProxy
@SpringBootApplication
@ComponentScan(basePackages = “com.zpc.gateway.filter”)
public class ApiGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ApiGatewayApplication.class, args);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
3.8.2.啓動測試
帶token正常訪問:
可以看到過濾器已經生效。
3.9.默認開啓ribbon
Zuul網關默認開啓了 ribbon 負載均衡,可以修改端口,啓動多個Item服務進行測試,不斷刷新瀏覽器請求:
http://localhost:8087/item-service/item/2?token=1
發現多個Item服務被輪詢的訪問:
service port:8080
service port:8081
3.10.動態網關
將yml配置文件中的路由配置信息存在配置中心中可以實現網關的動態配置。請見4.8
節。
3.11.網關集羣
Zuul可以配合nginx搭建網關集羣。只要在nginx的配置文件nginx.conf
裏配置多個zuul地址:
http {
upstream myhttpServer{
#配置多個zuul地址
server localhost:8087;
server localhost:8086;
}
server {
listen 80;
server_name www.zpc.com;
location / {
proxy_pass http://myhttpServer;
index index.html index.htm;
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
架構圖:
瀏覽器訪問:http://www.zpc.com/item-service/item/2?token=1
4.分佈式配置中心 Spring Cloud Config
4.1.之前的配置文件用法存在什麼問題?
- 我們開發項目時,需要有很多的配置項需要寫在配置文件中,如:數據庫的連接信息等。
- 這樣看似沒有問題,但是我們想一想,如果我們的項目已經啓動運行,那麼數據庫服務器的ip地址發生了改變,我們該怎麼辦?
- 如果真是這樣,我們的應用需要重新修改配置文件,然後重新啓動,如果應用數量龐大,那麼這個維護成本就太大了!
- 有沒有好的辦法解決呢?當然是有的,
Spring Cloud Config
提供了這樣的功能,可以讓我們統一管理配置文件,以及實時同步更新,並不需要重新啓動應用程序。
4.2.Spring Cloud Config 簡介
Config Server是一個可橫向擴展、集中式的配置服務器,它用於集中管理應用程序各個環境下的配置,默認使用Git存儲配置文件內容,也可以使用SVN存儲,或者是本地文件存儲。
Config Client是Config Server的客戶端,用於操作存儲在Config Server中的配置內容。微服務在啓動時會請求Config Server獲取配置文件的內容,請求到後再啓動容器。
使用Spring Cloud Config的架構:
4.3.快速入門 – 搭建Config Server
4.3.1.準備3個配置文件,推送到Git服務器
準備3個文件(也可以使用yml文件):
microservice-dev.properties
microservice-prod.properties
microservice-test.properties
- 1
- 2
- 3
該文件的命名規則是:{application}-{profile}.properties
其內容是(另外2個文件內容稍有不同即可):
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true
jdbc.username=root
jdbc.password=123456
- 1
- 2
- 3
- 4
推送文件到git服務器,這裏使用的是碼雲,當然也可以使用github或者使用svn。使用碼雲創建一個項目(私有項目需要賬號密碼)
4.3.2.創建工程microservice-config-server
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.zpc.microservice</groupId> <artifactId>microservice-config-server</artifactId> <version>1.0-SNAPSHOT</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.6.RELEASE</version> </parent> <dependencyManagement> <dependencies> <!-- 導入Spring Cloud的依賴管理 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Finchley.SR1</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <!--整合配置中心--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-server</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
</project>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
4.3.3.編寫入口ConfigApplication
package com.zpc.configcenter;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;
/**
* 開啓配置服務
*/
@EnableConfigServer
@SpringBootApplication
public class ConfigApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigApplication.class, args);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
4.3.4.編寫application.yml
server:
port: 7788 #服務端口
spring:
application:
name: microservice-config-server #指定服務名
cloud:
config:
server:
git: #配置git倉庫地址
uri: https://gitee.com/hellpzpc/myspconfig.git
search-paths:
- myspringcloudconfig #配置文件目錄地址
username: [email protected] #碼雲賬號(公有項目不需要設置)
password: m86xxx #碼雲密碼(公有項目不需要設置)
label: master #分支名稱
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
4.3.5.啓動測試
測試已經看到了配置文件的內容。
請求配置文件的規則如下:
/{application}/{profile}/[label]
/{application}-{profile}.yml
/{label}/{application}-{profile}.yml
/{application}-{profile}.properties
/{label}/{application}-{profile}.properties
- 1
- 2
- 3
- 4
- 5
其中{label}是指分支,默認是master。
4.4.快速入門 – 搭建Config Client
我們在microservice-item項目中添加Config Client的支持。來讀取JDBC的配置文件的內容。
4.4.1.導入依賴
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-client</artifactId>
</dependency>
- 1
- 2
- 3
- 4
4.4.2.新增配置文件bootstrap.yml
spring:
cloud:
config:
uri: http://127.0.0.1:7788/ #配置中心的地址
profile: dev #對應配置服務中的{profile}
label: master #對應的分支
- 1
- 2
- 3
- 4
- 5
- 6
4.4.3.編寫JdbcConfigBean
編寫對象通過@Value
註解讀取Config Server中的值。
package com.zpc.item.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class JdbcConfigBean {
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Value("${jdbc.driverClassName}")
private String driverClassName;
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getDriverClassName() {
return driverClassName;
}
public void setDriverClassName(String driverClassName) {
this.driverClassName = driverClassName;
}
@Override
public String toString() {
return "JdbcConfigBean [url=" + url + ", username=" + username
+ ", password=" + password + ", driverClassName="
+ driverClassName + "]";
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
4.4.4.編寫測試方法進行測試
在ItemController中增加testconfig方法:
package com.zpc.item.controller; import com.zpc.item.config.JdbcConfigBean; import com.zpc.item.entity.Item; import com.zpc.item.service.ItemService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; /** * @author Evan */ @RestController public class ItemController { @Value("${server.port}") private String port; @Autowired private ItemService itemService; @Autowired private JdbcConfigBean jdbcConfigBean; /** * 對外提供接口服務,查詢商品信息 * * @param id * @return */ @GetMapping(value = "item/{id}") public Item queryItemById(@PathVariable("id") Long id) { System.out.println("service port:"+port); return this.itemService.queryItemById(id); }
@GetMapping(value = "testconfig") public String testconfig(){ return this.jdbcConfigBean.toString(); }
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
4.4.5.啓動測試
測試結果顯示,已經從Config Server中獲取到配置文件的內容。
4.5.手動更新運行中的配置文件
如果git服務器中的配置文件更新了怎麼辦?正在運行的應用中的配置內容如何更新?
4.5.1.測試
現在我們更改git服務器中的配置文件內容,端口改成8888:
然後刷新Config Server地址觀察:
可以看到這裏查詢到的是新的數據,端口已經更新爲8888。
但是在Config Client中測試:
看到依然是舊的數據。
如何才能在重啓應用的情況下,獲取到最新的配置文件內容呢? – 爲Config Client添加refresh支持。
4.5.2.加入依賴
使用actuator 監控中心完成刷新功能:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
- 1
- 2
- 3
- 4
4.5.3.爲JdbcConfigBean添加@RefreshScope註解
需要爲動態更新配置內容的bean添加@RefreshScope註解。
@Component
@RefreshScope
public class JdbcConfigBean {...//代碼省略}
- 1
- 2
- 3
4.5.4.修改application.yml文件
###服務端口號(本身是一個web項目)
server:
port: 8080
###起個名字作爲服務名稱(該服務註冊到eureka註冊中心的名稱,比如商品服務)
spring:
application:
name: app-item
###服務註冊到eureka註冊中心的地址
eureka:
client:
service-url:
defaultZone: http://zpc:[email protected]:8100/eureka/,http://zpc:[email protected]:9100/eureka/
###因爲該應用爲服務提供者,是eureka的一個客戶端,需要註冊到註冊中心
register-with-eureka: true
###是否需要從eureka上檢索服務
fetch-registry: true
instance:
prefer-ip-address: true #將自己的ip地址註冊到Eureka服務中
ip-address: 127.0.0.1
instance-id: ${spring.application.name}###${server.port} #指定實例id
#開啓所有端點
management:
endpoints:
web:
exposure:
include: "*"
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
4.5.5.重啓做測試
加了actuator依賴後,啓動項目控制檯輸出一行:
2018-11-04 21:17:19.749 INFO 187328 --- [main] s.b.a.e.w.s.WebMvcEndpointHandlerMapping : Mapped "{[/actuator/refresh],methods=[POST],produces=[application/vnd.spring-boot.actuator.v2+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping$OperationHandler.handle(javax.servlet.http.HttpServletRequest,java.util.Map<java.lang.String, java.lang.String>)
- 1
爲了測試,需要手動發起post請求actuator接口:
POST請求監控中心 http://localhost:8080/actuator/refresh
使用接口測試工具(例如postman)發送post請求actuator/refresh來更新配置內容:
響應:
刷新可以看到有配置文件內容更新了。
可以看到應該更新了最新的配置文件內容,我們也就實現了在未重啓項目的情況下實現了動態修改配置文件內容。
但是,項目已經發布上線了,不可能人爲的守在服務器前,發現有更新了就手動請求監控中心的refresh接口。
是否自動完成呢?
4.6.藉助與git的webhook(web鉤子)實現自動更新
碼雲、github等git服務器提供了web hook功能,意思是,在倉庫中的資源發生更新時會通知給誰,這裏的誰是一個url地址。
可以在命令行查看服務器ip地址,把外部(例如碼雲)能訪問到的你的服務器ip設置爲推送地址。
接下來進行測試,更新配置文件的內容。
測試的結果會發現,配置文件內容會動態更到Bean中。
(要使自己的服務能夠被外部訪問到需要做端口映射)
總結下流程:
4.7.Config Client配置優化-註冊到Eureka中
4.7.1.分析
在 microservice-item中作爲Config Client,在配置文件中了寫死了Config Server的地址:
spring:
cloud:
config:
name: microservice #對應配置中心的應用名稱,不寫默認是本應用名,即spring.application.name
uri: http://127.0.0.1:7788/
profile: dev #對應配置服務中的{profile}
label: master #對應的分支
- 1
- 2
- 3
- 4
- 5
- 6
- 7
這樣的硬編碼是不好的,如果配置中心的ip地址發生了改變,那麼久需要重新修改並且重啓應用了。
想想,有什麼好的辦法解決呢? 如果將Config Server作爲一個微服務,並且將其註冊的Eureka中,是不是就可以不用硬編碼了?
4.7.2.在 microservice-config-server中添加Eureka的依賴
<!--整合eureka客戶端-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
- 1
- 2
- 3
- 4
- 5
4.7.3.修改application.yml文件
server:
port: 7788 #服務端口
spring:
application:
name: microservice-config-server #指定服務名
cloud:
config:
server:
git: #配置git倉庫地址
uri: https://gitee.com/hellpzpc/myspconfig.git
search-paths:
- myspringcloudconfig #配置文件目錄地址
username: [email protected] #碼雲賬號(公有項目不需要設置)
password: mx8xxx2 #碼雲密碼(公有項目不需要設置)
label: master #分支
###服務註冊到eureka註冊中心的地址
eureka:
client:
service-url:
defaultZone: http://zpc:[email protected]:8100/eureka/,http://zpc:[email protected]:9100/eureka/
###因爲該應用爲服務提供者,是eureka的一個客戶端,需要註冊到註冊中心
register-with-eureka: true
###是否需要從eureka上檢索服務
fetch-registry: true
instance:
prefer-ip-address: true #將自己的ip地址註冊到Eureka服務中
instance-id: ${spring.application.name}###${server.port} #指定實例id
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
4.7.4.ConfigApplication添加@EnableEurekaClient註解
/**
* 開啓配置服務
*/
@EnableConfigServer
@SpringBootApplication
@EnableEurekaClient
public class ConfigApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigApplication.class, args);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
4.7.5.在microservice-item中修改bootstrap.yml配置
###服務註冊到eureka註冊中心的地址
eureka:
client:
service-url:
defaultZone: http://zpc:[email protected]:8100/eureka/,http://zpc:[email protected]:9100/eureka/
###因爲該應用爲服務提供者,是eureka的一個客戶端,需要註冊到註冊中心
register-with-eureka: true
###是否需要從eureka上檢索服務
fetch-registry: true
instance:
prefer-ip-address: true #將自己的ip地址註冊到Eureka服務中
ip-address: 127.0.0.1
instance-id: ${spring.application.name}###${server.port} #指定實例id
spring:
cloud:
config:
name: microservice #對應配置中心的應用名稱,默認是本應用名,即spring.application.name,該名稱要和git中的配置一致
#uri: http://127.0.0.1:7788/
profile: dev #對應配置服務中的{profile}
label: master #對應的分支
discovery:
enabled: true #啓用發現服務功能
service-id: microservice-config-server #指定配置中心工程的名稱
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
疑問:在application.yml中以及配置Eureka的信息,爲什麼在bootstrap.yml還需要配置?
因爲在Spring Boot中bootstrap.yml在application.yml之前加載,所以即使在application.yml中以及配置Eureka的信息,是使用不了的,所以需要在bootstrap.yml中配置Eureka的信息。
4.7.6.測試
端口改成9999:
發post請求手動刷新:
查看最新配置:
測試結果,一切正常。這就完美解決了硬編碼的問題。
4.8.動態網關
傳統方式將zuul路由規則配置在配置文件中,如果修改了路由規則,無疑需要重啓服務器。可以將配置文件存在配置中心,利用SpringCloud Config實現動態路由規則添加。
1.在git服務器上創建配置文件service-zuul-dev.yml,註釋掉網關工程yml文件中的zuul配置,把配置拷到service-zuul-dev.yml中。
註釋掉網關工程microservice-api-gateway中的配置,並開啓actuator:
2.在網關工程的pom中引入actuator依賴:
<!--開啓監控中心-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
- 1
- 2
- 3
- 4
- 5
3.在網關工程新建bootstrap.yml,啓用spring cloud config 功能:
###服務註冊到eureka註冊中心的地址
eureka:
client:
service-url:
defaultZone: http://zpc:[email protected]:8100/eureka/,http://zpc:[email protected]:9100/eureka/
###因爲該應用爲服務提供者,是eureka的一個客戶端,需要註冊到註冊中心
register-with-eureka: true
###是否需要從eureka上檢索服務
fetch-registry: true
instance:
prefer-ip-address: true #將自己的ip地址註冊到Eureka服務中
ip-address: 127.0.0.1
instance-id: ${spring.application.name}###${server.port} #指定實例id
spring:
cloud:
config:
name: service-zuul #對應配置中心的應用名稱,默認是本應用名,即spring.application.name,該名稱要和git中的配置一致
#uri: http://127.0.0.1:7788/
profile: dev #對應配置服務中的{profile}
label: master #對應的分支
discovery:
enabled: true #啓用發現服務功能
service-id: microservice-config-server #指定配置中心工程的名稱
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
4.在網關工程啓動類增加@RefreshScope註解,支持手動刷新
package com.zpc.gateway.runner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
import org.springframework.context.annotation.ComponentScan;
@EnableZuulProxy
@SpringBootApplication
@ComponentScan(basePackages = “com.zpc.gateway.filter”)
public class ApiGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ApiGatewayApplication.class, args);
}
@RefreshScope
@ConfigurationProperties("zuul")
public ZuulProperties zuulProperties(){
ZuulProperties properties = new ZuulProperties();
System.out.println("properties:"+properties);
return properties ;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
5.啓動配置中心(microservice-config-server)工程,查看能否訪問git上的配置文件:
6.啓動網關工程(microservice-api-gateway)、訂單工程(microservice-order)、商品工程(microservice-item)測試。
Eureka:
通過Zuul網關訪問訂單的服務OK:
通過Zuul網關訪問商品的服務OK:
7.修改git中的配置文件,手動發post請求刷新,看修改是否生效
修改了serviceid爲不存在的服務app-itemXX:
刷新:
再次通過zuul訪問item的服務:
8.把配置文件改回來,再發post請求http://localhost:8087/actuator/refresh,再次測試:
OK了,說明通過配置中心動態設置zuul配置生效:
5.消息總線 Spring Cloud Bus
5.1.分析
-
雖然通過Git服務器的web hook可以實現自動更新,但是,如果Config Client有很多的話,那麼需要在web hook中維護很多地址,如果是手動刷新的話還得一個一個地發送POST請求,這顯然是不現實的做法。
-
有沒有更好的方案呢? 我們非常容易想到消息隊列中的Topic,沒錯! 通過消息實現通知。
5.2.Spring Cloud Bus消息總線的簡介
目前Spring Cloud Bus消息總線只是實現了對RabbitMQ以及Kafka的支持。
5.3.RabbitMQ的安裝
參考《RabbitMQ教程》 https://blog.csdn.net/hellozpc/article/details/81436980
5.4.使用Spring Cloud Bus的架構
5.5.實現
5.5.1.在 microservice-item添加依賴
<!--整合Spring Cloud Bus-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
- 1
- 2
- 3
- 4
- 5
5.5.2.在bootstrap.yml添加rabbitmq的配置
###服務註冊到eureka註冊中心的地址
eureka:
client:
service-url:
defaultZone: http://zpc:[email protected]:8100/eureka/,http://zpc:[email protected]:9100/eureka/
###因爲該應用爲服務提供者,是eureka的一個客戶端,需要註冊到註冊中心
register-with-eureka: true
###是否需要從eureka上檢索服務
fetch-registry: true
instance:
prefer-ip-address: true #將自己的ip地址註冊到Eureka服務中
ip-address: 127.0.0.1
instance-id: ${spring.application.name}###${server.port} #指定實例id
spring:
cloud:
config:
name: microservice #對應配置中心的應用名稱,默認是本應用名,即spring.application.name,該名稱要和git中的配置一致
#uri: http://127.0.0.1:7788/
profile: dev #對應配置服務中的{profile}
label: master #對應的分支
discovery:
enabled: true #啓用發現服務功能
service-id: microservice-config-server #指定配置中心工程的名稱(在Eureka中的服務名)
rabbitmq: #RabbitMQ相關的配置
host: 127.0.0.1
port: 5672
username: guest
password: guest
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
5.5.3.修改web hook的地址
在啓動Item工程時會看到這樣的日誌:
2018-11-12 21:43:47.722 INFO 221032 --- [main] s.b.a.e.w.s.WebMvcEndpointHandlerMapping : Mapped "{[/actuator/bus-refresh],methods=[POST]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping$OperationHandler.handle(javax.servlet.http.HttpServletRequest,java.util.Map<java.lang.String, java.lang.String>)
- 1
說明刷新的地址/bus-refresh是由Spring Cloud Bus來處理,之前的/refresh依然是由以前的邏輯處理。
所以要修改Git服務器中的web hook的地址:(修改或者添加都可以)
5.5.4.啓動測試
首先需要保證Rabbit的服務開啓:
啓動rabbit管理工具:
查看是否啓動成功:
啓動Item工程後查看RabbitMQ中的交換機:
再看隊列:
接着,將 microservice-item的端口改成8082,再啓動一個 microservice-item實例,進行測試。
發現,有2個隊列,分別都綁定到springCloudBus的交換機。
接下里,修改配置文件的內容進行測試。
可以看到8081和8082這2個實例查詢到的信息都是一樣的。
接下來,修改配置文件內容將9999改成8888:
結果顯示,都是獲取到最新的數據。
在測試時,會發現,由於Git服務器的web鉤子推送到8181,所以8081的更新快一些,而8082更新就相對慢一些。
5.5.5.流程總結
更新文件到git服務器,Git服務器通過web鉤子通知到8081的/bus/refresh,8081的實例將消息發送到springCloudBus的交換機,由於8081的隊列頁綁定到交換機,所以8082也獲取到了更新的通知,然後去Config Server獲取最新的數據。
5.6.架構優化
在前面實現的架構中,發現8081這個實例不僅僅是提供了商品查詢的服務,還負責發送更新的消息到RabbitMQ。
這其實是違反了微服務架構中的職責單一的原則。
其實這個架構是可以改進的,就是將原有的Config Server不僅僅提供配置查詢的服務,而且還要負責更新消息的發送。
5.6.1.在microservice-config-server中導入依賴
<!--整合Spring Cloud Bus-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
- 1
- 2
- 3
- 4
- 5
5.6.2.修改application.yml配置文件
server:
port: 7788 #服務端口
spring:
application:
name: microservice-config-server #指定服務名
cloud:
config:
server:
git: #配置git倉庫地址
uri: https://gitee.com/hellpzpc/myspconfig.git
search-paths:
- myspringcloudconfig #配置文件目錄地址
username: [email protected] #碼雲賬號(公有項目不需要設置)
password: myxxxx #碼雲密碼(公有項目不需要設置)
label: master #分支
rabbitmq: #RabbitMQ相關的配置
host: 127.0.0.1
port: 5672
username: guest
password: guest
###服務註冊到eureka註冊中心的地址
eureka:
client:
service-url:
defaultZone: http://zpc:[email protected]:8100/eureka/,http://zpc:[email protected]:9100/eureka/
###因爲該應用爲服務提供者,是eureka的一個客戶端,需要註冊到註冊中心
register-with-eureka: true
###是否需要從eureka上檢索服務
fetch-registry: true
instance:
prefer-ip-address: true #將自己的ip地址註冊到Eureka服務中
instance-id: ${spring.application.name}###${server.port} #指定實例id
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
5.6.3.修改Git中的web鉤子
5.6.4.重啓測試
測試結果,和之前一樣,可以同步更新到8081和8082。
6.整合consul&zookeeper作爲註冊中心
由於Eureka 2.0已結宣稱閉源,因此可以考試使用其它註冊中心組件,例如zk、consul。
6.1.整合zookeeper作爲註冊中心
6.1.1.修改商品(Item)微服務項目
第一步 添加依賴(注意zk和Eureka不能共存,請先註釋掉Eureka的pom依賴)
<!--springboot 整合zookeeper客戶端(注意保證服務端和客戶端zk版本一致)-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
</dependency>
- 1
- 2
- 3
- 4
- 5
第二步 修改yml配置文件,添加zk作爲註冊中心(注意zk和Eureka不能共存,請先註釋掉Eureka的配置)
###服務端口號(本身是一個web項目)
server:
port: 8080
###起個名字作爲服務名稱(該服務註冊到eureka註冊中心的名稱,比如商品服務)
spring:
application:
name: app-item
###服務註冊到zookeeper註冊中心的地址
cloud:
zookeeper:
connect-string: 127.0.0.1:2181
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
第三步 修改入口程序
@EnableEurekaClient
改爲:
@EnableDiscoveryClient
6.1.2.修改訂單(order)微服務項目
第一步 添加依賴(注意zk和Eureka不能共存,請先註釋掉Eureka的pom依賴)
<!--springboot 整合zookeeper客戶端(注意保證服務端和客戶端zk版本一致)-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
</dependency>
- 1
- 2
- 3
- 4
- 5
第二步 修改yml配置文件,添加zk作爲註冊中心(注意zk和Eureka不能共存,請先註釋掉Eureka的配置)
###服務端口號(本身是一個web項目)
server:
port: 8080
###起個名字作爲服務名稱(該服務註冊到eureka註冊中心的名稱,比如商品服務)
spring:
application:
name: app-order
###服務註冊到zookeeper註冊中心的地址
cloud:
zookeeper:
connect-string: 127.0.0.1:2181
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
第三步 修改入口程序
@EnableEurekaClient
改爲:
@EnableDiscoveryClient
6.1.3.啓動zookeeper、商品工程、訂單工程
運行zookeeper目錄\bin\zkServer.cmd
注意項目中引入的zookeeper jar包版本要和你本地運行的zk服務器一致!!否則拒絕連接!筆者使用的是zookeeper-3.5.3-beta
通過ZooInspector查看註冊的服務(兩個臨時節點):
6.1.4. 測試功能
切換到zk註冊中心一切OK!
6.2.整合consul作爲註冊中心
6.2.1.Consul環境搭建
1)下載
https://www.consul.io/downloads.html
2)啓動
進入到consul目錄,命令行運行:
consul agent -dev -ui -node=test
-dev開發服務器模式啓動,-node設置節點名稱爲test,-ui可以用界面訪問(默認就能訪問)
3)訪問
http://localhost:8500
6.2.2.修改商品(Item)微服務項目
第一步 引入依賴 (務必註釋掉其他註冊中心的pom依賴)
<!--springboot 整合consul客戶端-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
- 1
- 2
- 3
- 4
- 5
第二步 修改yml配置文件(務必註釋掉其他註冊中心的配置項)
###服務端口號(本身是一個web項目)
server:
port: 8081
###起個名字作爲服務名稱(該服務註冊到eureka註冊中心的名稱,比如商品服務)
spring:
application:
name: app-item
###服務註冊到consul註冊中心的地址
###spring.cloud.consul.host/port配置的是Consul註冊中心(Consul服務器)的地址和端口(Server節點和Client節點都可以),Spring Cloud Consul調用 Consul HTTP REST 接口,進行服務註冊
###spring.cloud.consul.discovery.hostname配置 Spring Boot 服務的主機地址,也可以不進行配置,默認本機地址。
###healthCheckPath: /health 指定健康檢查的url地址(保證該接口地址返回http 2xx 返回碼,當然也可以忽略健康檢查register-health-check: false)
cloud:
consul:
host: 127.0.0.1
port: 8500
discovery:
healthCheckPath: /health
healthCheckInterval: 5s
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
第三步 ItemController增加健康檢查接口(即一個正常返回http 200的接口,如果consul健康檢查失敗,則服務不可調用)
/**
* 健康檢查
* @return
*/
@RequestMapping("/health")
public String healthCheck() {
return "OK";
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
第四步 入口程序中@EnableEurekaClient換成@EnableDiscoveryClient
6.2.3.修改訂單(order)微服務項目
第一步 引入依賴 (務必註釋掉其他註冊中心的pom依賴)
<!--springboot 整合consul客戶端-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
- 1
- 2
- 3
- 4
- 5
第二步 修改yml配置文件(務必註釋掉其他註冊中心的配置項)
###服務端口號(本身是一個web項目)
server:
port: 8082
###起個名字作爲服務名稱(該服務註冊到eureka註冊中心的名稱,比如商品服務)
spring:
application:
name: app-order
###服務註冊到consul註冊中心的地址
###spring.cloud.consul.host/port配置的是Consul註冊中心(Consul服務器)的地址和端口(Server節點和Client節點都可以),Spring Cloud Consul調用 Consul HTTP REST 接口,進行服務註冊
###spring.cloud.consul.discovery.hostname配置 Spring Boot 服務的主機地址,也可以不進行配置,默認本機地址。
###healthCheckPath: /health 指定健康檢查的url地址(保證該接口地址返回http 2xx 返回碼,當然也可以忽略健康檢查register-health-check: false)
cloud:
consul:
host: 127.0.0.1
port: 8500
discovery:
healthCheckPath: /health
healthCheckInterval: 5s
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
第三步 OrderController增加健康檢查接口(即一個正常返回http 200的接口,如果consul健康檢查失敗,則服務不可調用)
/**
* 健康檢查
* @return
*/
@RequestMapping("/health")
public String healthCheck() {
return "OK";
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
第四步 入口程序中@EnableEurekaClient
換成@EnableDiscoveryClient
6.2.4.啓動consul、商品工程、訂單工程
保證健康檢查全部OK,否則服務調不通!
6.2.5.測試功能
切換到consul註冊中心一切OK!
7.整合swagger API管理
Swagger是一款能夠讓你更好地書寫API文檔的框架。它可以生成一個具有互動性的API控制檯,方便前後端開發人員面向接口交流、接口測試。
7.1.快速入門
在商品系統(microservice-item)中做演示
1.引入依賴
<!--整合swagger -->
<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>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
2.配置Swagger
package com.zpc.item.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration
@EnableSwagger2//開啓swagger2
public class SwaggerConfig {
@Bean
public Docket createRestApi() {
//配置swagger 生成API的掃描範圍
return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).select().apis(RequestHandlerSelectors.basePackage("com.zpc.item")).paths(PathSelectors.any()).build();
}
/**
* 創建api文檔信息
* @return
*/
private ApiInfo apiInfo() {
return new ApiInfoBuilder().title("鳥鵬的專欄").description("SpringCloud教程").termsOfServiceUrl("https://blog.csdn.net/hellozpc").version("1.0").build();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
3.編寫Controller入口
package com.zpc.item.controller; import io.swagger.annotations.Api; import io.swagger.annotations.ApiImplicitParam; import io.swagger.annotations.ApiOperation; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; import java.math.BigDecimal; @Api("SwaggerDemo控制器") @RestController public class SwaggerController {
@ApiOperation("Swagger演示") @GetMapping("/swaggerIndex") public String swaggerIndex(String msg) { return "This is swaggerIndex!" + msg; } @ApiOperation("獲取商品詳情") @ApiImplicitParam(name = "itemName", value = "商品名稱", required = true, dataType = "String") @PostMapping("/getItemInfo") public String getItemInfo(String itemName) { return "商品名:" + itemName + " 商品價格:" + new BigDecimal(Math.random() * 100).setScale(2, BigDecimal.ROUND_HALF_UP); }
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
4.啓動工程,訪問http://localhost:8081/swagger-ui.html
在這個頁面可以執行交互式的測試:
7.2.Zuul整合Swagger統一管理微服務Api
在一個入口統一管理所有微服務(訂單服務、商品服務等)項目的API。實現方式:zuul
1.在訂單服務(microservice-order)中也引入swagger
這次直接使用springboot提供的starter,不使用7.1那種方式。
<!--引入swagger-->
<dependency>
<groupId>com.spring4all</groupId>
<artifactId>swagger-spring-boot-starter</artifactId>
<version>1.7.0.RELEASE</version>
</dependency>
- 1
- 2
- 3
- 4
- 5
- 6
創建Controller入口
package com.zpc.order.controller; import com.spring4all.swagger.EnableSwagger2Doc; import io.swagger.annotations.Api; import io.swagger.annotations.ApiImplicitParam; import io.swagger.annotations.ApiOperation; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; import java.math.BigDecimal; @EnableSwagger2Doc @Api("訂單服務接口") @RestController public class OrderApiController { @ApiOperation("提交訂單") @ApiImplicitParam(name = "orderNo", value = "訂單號", required = true, dataType = "String") @PostMapping("/putOrderInfo") public String putOrderInfo(String orderNo) { return "訂單:" + orderNo + " 已提交,商品總價:" + new BigDecimal(Math.random() * 1000).setScale(2, BigDecimal.ROUND_HALF_UP); }
@ApiOperation("獲取訂單詳情") @ApiImplicitParam(name = "orderNo", value = "訂單號", required = true, dataType = "String") @GetMapping("/getOrderInfo") public String getOrderInfo(String orderNo) { return "訂單:" + orderNo + " 商品總價格:" + new BigDecimal(Math.random() * 1000).setScale(2, BigDecimal.ROUND_HALF_UP); }
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
yml中配置掃描範圍
#配置Swagger接口掃描範圍
swagger:
base-package: com.zpc.order
- 1
- 2
- 3
2.在網關服務中也引入Swagger
<!--引入swagger-->
<dependency>
<groupId>com.spring4all</groupId>
<artifactId>swagger-spring-boot-starter</artifactId>
<version>1.7.0.RELEASE</version>
</dependency>
- 1
- 2
- 3
- 4
- 5
- 6
3.修改網關服務的啓動類
增加一個內部類DocumentationConfig
package com.zpc.gateway.runner;
import com.spring4all.swagger.EnableSwagger2Doc;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import springfox.documentation.swagger.web.SwaggerResource;
import springfox.documentation.swagger.web.SwaggerResourcesProvider;
import java.util.ArrayList;
import java.util.List;
@EnableZuulProxy
@EnableSwagger2Doc
@SpringBootApplication
@ComponentScan(basePackages = “com.zpc.gateway.filter”)
public class ApiGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ApiGatewayApplication.class, args);
}
@RefreshScope
@ConfigurationProperties("zuul")
public ZuulProperties zuulProperties(){
ZuulProperties properties = new ZuulProperties();
System.out.println("properties:"+properties);
return properties ;
}
/**
* 配置Swagger
*/
@Component
@Primary
class DocumentationConfig implements SwaggerResourcesProvider{
@Override
public List<SwaggerResource> get() {
List resource=new ArrayList<>();
//name可以隨便寫,location前綴要與zuul配置的path一致。zuul開了token驗證,要加上token,否則不用加?token=1
resource.add(swaggerResource("myapp-item","/item-service/v2/api-docs?token=1","2.0"));
resource.add(swaggerResource("myapp-order","/order-service/v2/api-docs?token=1","2.0"));
return resource;
}
//name可以隨便寫,location前綴要與zuul配置的path一致
private SwaggerResource swaggerResource(String name,String location,String version){
SwaggerResource swaggerResource=new SwaggerResource();
swaggerResource.setName(name);
swaggerResource.setLocation(location);
swaggerResource.setSwaggerVersion(version);
return swaggerResource;
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
4.測試
啓動Eureka註冊中心、item服務、order服務、網關服務開始測試
直接通過網關的入口地址http://localhost:8087/swagger-ui.html?token=1
統一訪問所有微服務的api,界面右上角可以選擇。
</div>
<link href="https://csdnimg.cn/release/phoenix/mdeditor/markdown_views-b6c3c6d139.css" rel="stylesheet">
<div class="more-toolbox">
<div class="left-toolbox">
<ul class="toolbox-list">
<li class="tool-item tool-active is-like "><a href="javascript:;"><svg class="icon" aria-hidden="true">
<use xlink:href="#csdnc-thumbsup"></use>
</svg><span class="name">點贊</span>
<span class="count">33</span>
</a></li>
<li class="tool-item tool-active is-collection "><a href="javascript:;" data-report-click="{"mod":"popu_824"}"><svg class="icon" aria-hidden="true">
<use xlink:href="#icon-csdnc-Collection-G"></use>
</svg><span class="name">收藏</span></a></li>
<li class="tool-item tool-active is-share"><a href="javascript:;" data-report-click="{"mod":"1582594662_002"}"><svg class="icon" aria-hidden="true">
<use xlink:href="#icon-csdnc-fenxiang"></use>
</svg>分享</a></li>
<!--打賞開始-->
<!--打賞結束-->
<li class="tool-item tool-more">
<a>
<svg t="1575545411852" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5717" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M179.176 499.222m-113.245 0a113.245 113.245 0 1 0 226.49 0 113.245 113.245 0 1 0-226.49 0Z" p-id="5718"></path><path d="M509.684 499.222m-113.245 0a113.245 113.245 0 1 0 226.49 0 113.245 113.245 0 1 0-226.49 0Z" p-id="5719"></path><path d="M846.175 499.222m-113.245 0a113.245 113.245 0 1 0 226.49 0 113.245 113.245 0 1 0-226.49 0Z" p-id="5720"></path></svg>
</a>
<ul class="more-box">
<li class="item"><a class="article-report">文章舉報</a></li>
</ul>
</li>
</ul>
</div>
</div>
<div class="person-messagebox">
<div class="left-message"><a href="https://blog.csdn.net/zpcandzhj">
<img src="https://profile.csdnimg.cn/5/C/E/3_zpcandzhj" class="avatar_pic" username="zpcandzhj">
<img src="https://g.csdnimg.cn/static/user-reg-year/1x/9.png" class="user-years">
</a></div>
<div class="middle-message">
<div class="title"><span class="tit"><a href="https://blog.csdn.net/zpcandzhj" data-report-click="{"mod":"popu_379"}" target="_blank">niaobirdfly</a></span>
</div>
<div class="text"><span>發佈了138 篇原創文章</span> · <span>獲贊 1055</span> · <span>訪問量 86萬+</span></div>
</div>
<div class="right-message">
<a href="https://bbs.csdn.net/topics/395527229" target="_blank" class="btn btn-sm btn-red-hollow bt-button personal-messageboard">他的留言板
</a>
<a class="btn btn-sm bt-button personal-watch" data-report-click="{"mod":"popu_379"}">關注</a>
</div>
</div>
</div>