spring cloud面向開發人員,對分佈式系統從編程模型上提供了強大的支持。能夠說是分佈式系統解決方案的全家桶,極大地下降了開發與構建分佈式系統的門檻。java
包括了諸以下列功能:mysql
Netflix是一家互聯網流媒體播放商,美國視頻巨頭,最近買下《流浪地球》,並在190多個國家播出的就是它了。隨着Netflix轉型爲一家雲計算公司,也開始積極參與開源項目。Netflix OSS(Open Source)就是由Netflix公司開發的框架,解決上了規模以後的分佈式系統可能出現的一些問題。spring cloud基於spring boot,爲spring boot提供Netflix OSS的集成。git
這段時間對spring cloud進行了學習總結,留下點什麼,好讓網友上手spring cloud時少躺坑。github
我用的版本以下:web
spring boot不一樣版本之間在配置和依賴上會有差別,強烈建議建立工程後先把spring boot和spring cloud的版本依賴調整成與本篇一致,快速上手少躺坑。算法
上手完後,你能夠嘗試升到最新版本,官方spring-cloud頁(進去後拉到最下面)提供了spring-cloud與spring-boot版本匹配表。升級完後建議從新測試一下相關功能。spring
IntelliJ IDEA(我是2018.2.5的版本),建立工程eureka-server:sql
File -> New->Product... -> 選擇Spring Initializr -> Project SDK用1.8 -> Next -> 輸入Product Metadata -> Next -> 選擇Cloud Discovery -> 選擇Eureka Server(springboot版本選個2.0以上的)
注意建立工程後調整spring boot和spring cloud的版本:docker
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.2.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> ... <properties> <java.version>1.8</java.version> <spring-cloud.version>Finchley.RELEASE</spring-cloud.version> </properties>
因爲選擇了Eureka Server,建立成功後pom.xml裏已經幫你引入瞭如下依賴:shell
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency>
咱們給啓動類加上註解@EnableEurekaServer:
package com.hicoview.eureka; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; @SpringBootApplication @EnableEurekaServer public class EurekaApplication { public static void main(String[] args) { SpringApplication.run(EurekaApplication.class, args); } }
配置application.yml(習慣yml風格的同窗,把application.properties直接改過來):
eureka: client: # 默認eureka服務註冊中心會將自身做爲客戶端來嘗試註冊,因此咱們須要禁用它的客戶端註冊行爲 register-with-eureka: false # 默認30秒會更新客戶端註冊上來的服務清單,啓動時就不獲取了,否則啓動會有報錯,雖然不影響 fetch-registry: false server: # 關閉註冊中心自我保護(默認是true,生產環境不建議關閉,去掉該配置項或改爲true) enable-self-preservation: false spring: application: name: eureka server: port: 8761
服務註冊中心搞定,啓動成功後訪問http://localhost:8761,能夠看到註冊中心頁面:
Eureka Server高可用
只要eureka-server以不一樣端口啓動多個實例便可,多個實例兩兩註冊到對方。好比,
以8761端口啓動一個Eureka Server端,註冊到8762:
eureka: client: service-url: # 提供其餘註冊中心的地址,註冊中心將自身以客戶端註冊的方式註冊到其餘註冊中心去 defaultZone: http://localhost:8762/eureka/ register-with-eureka: false fetch-registry: false server: enable-self-preservation: false spring: application: name: eureka server: port: 8761
以8762端口啓動一個Eureka Server端,註冊到8761:
eureka: client: service-url: # 提供其餘註冊中心的地址,註冊中心將自身以客戶端註冊的方式註冊到其餘註冊中心去 defaultZone: http://localhost:8761/eureka/ register-with-eureka: false fetch-registry: false server: enable-self-preservation: false spring: application: name: eureka server: port: 8762
訪問http://localhost:8761,能夠看到有8762的副本:
訪問http://localhost:8762,也同樣有8761的副本。
接下來咱們用Eureka Client來驗證下服務的註冊。
實際上充當Eureka Client角色應該是各類業務的微服務工程了,這裏爲了快速演示一下服務註冊,臨時先搞個無心義的eureka-client工程示範。
建立Eureka Client工程eureka-client:
File -> New->Product... -> 選擇Spring Initializr -> Project SDK用1.8 -> Next -> 輸入Product Metadata -> Next -> 選擇Cloud Discovery -> 選擇Eureka Discovery(springboot版本選2.0以上)
注意每次建立工程後的第一件事,改spring-boot和spring-cloud的版本,再也不贅述
因爲選擇了Eureka Discovery,建立成功後pom.xml裏已經幫你引入瞭如下依賴:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
修改pom.xml,還要加入web依賴,否則沒法啓動成功:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
啓動類加註解@EnableDiscoveryClient:
package com.hicoview.client; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; @SpringBootApplication @EnableDiscoveryClient public class ClientApplication { public static void main(String[] args) { SpringApplication.run(ClientApplication.class, args); } }
配置application.yml:
eureka: client: service-url: # 註冊中心地址,若是註冊中心是高可用,那麼這裏後面能夠添加多個地址,逗號分開 defaultZone: http://localhost:8761/eureka/ #defaultZone: http://localhost:8761/eureka/,http://localhost:8762/eureka/ spring: application: name: client
啓動成後,訪問註冊中心http://localhost:8761:
完成了Eureka服務註冊示例,接下來咱們簡單模擬一個業務場景,示範微服務之間的服務調用。
以商品下單爲例,好比將業務拆分爲商品服務和訂單服務,訂單服務會調用商品服務的庫存扣減。
單個微服務工程,統一按如下目錄編排:
-product --product-common 商品服務公用對象 --product-client 商品服務客戶端,以jar包方式被訂單服務依賴 --product-server 商品服務,要註冊到Eureka Server,外部經過product-client來調用
咱們的微服務工程之間,依賴關係以下:
咱們從商品微服務工程開始
外層product初始pom.xml:
<modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.2.RELEASE</version> <relativePath/> </parent> <groupId>com.hicoview</groupId> <artifactId>product</artifactId> <version>0.0.1-SNAPSHOT</version> <name>product</name> <description>Demo project for Spring Boot</description> <modules> <module>product-common</module> <module>product-client</module> <module>product-server</module> </modules> <properties> <java.version>1.8</java.version> <spring-cloud.version>Finchley.RELEASE</spring-cloud.version> <product-common.version>0.0.1-SNAPSHOT</product-common.version> </properties> <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> <dependency> <groupId>com.hicoview</groupId> <artifactId>product-common</artifactId> <version>${product-common.version}</version> </dependency> </dependencies> </dependencyManagement>
product-common初始pom.xml:
<modelVersion>4.0.0</modelVersion> <parent> <groupId>com.hicoview</groupId> <artifactId>product</artifactId> <version>0.0.1-SNAPSHOT</version> </parent> <artifactId>product-common</artifactId> <dependencies> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> </dependencies>
product-client初始pom.xml:
<modelVersion>4.0.0</modelVersion> <parent> <groupId>com.hicoview</groupId> <artifactId>product</artifactId> <version>0.0.1-SNAPSHOT</version> </parent> <artifactId>product-client</artifactId> <dependencies> <dependency> <groupId>com.hicoview</groupId> <artifactId>product-common</artifactId> </dependency> </dependencies>
product-server的初始pom.xml:
<modelVersion>4.0.0</modelVersion> <parent> <groupId>com.hicoview</groupId> <artifactId>product</artifactId> <version>0.0.1-SNAPSHOT</version> </parent> <artifactId>product-server</artifactId> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>com.hicoview</groupId> <artifactId>product-common</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
接下來咱們調整product-server工程
商品服務需註冊到Eureka Server,添加Eureka Client相關依賴:
<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>
啓動類加註解@EnableDiscoveryClient:
package com.hicoview.product; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; @SpringBootApplication @EnableDiscoveryClient public class ProductApplication { public static void main(String[] args) { SpringApplication.run(ProductApplication.class, args); } }
配置application.yml:
eureka: client: service-url: defaultZone: http://localhost:8761/eureka/ spring: application: name: product server: port: 8080
啓動product-server,訪問註冊中心http://localhost:8761,PRODUCT註冊上去了,再繼續往下看。
咱們寫個簡單的服務調用示例一下,訂單服務下單邏輯調用商品服務進行扣減庫存。
繼續修改product-server工程
package com.hicoview.product.service; import java.util.List; public interface ProductService { // 扣減庫存 void decreaseStock(); }
package com.hicoview.product.service.impl; import com.hicoview.product.service.ProductService; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @Service @Slf4j public class ProductServiceImpl implements ProductService { @Override public void decreaseStock() { log.info("------扣減庫存-----"); } }
spring cloud的RPC服務是使用HTTP方式調用的,因此還要建立ProductController:
package com.hicoview.product.controller; import com.hicoview.product.service.ProductService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/product") public class ProductController { @Autowired private ProductService productService; @PostMapping("/decreaseStock") public void decreaseStock() { productService.decreaseStock(); } }
接下來修改product-client工程
pom.xml增長Feign依賴:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
建立ProductClient:
package com.hicoview.product.client; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @FeignClient(name="product") public interface ProductClient { // 經過Feign來代理對PRODUCT服務的HTTP請求 @PostMapping("/product/decreaseStock") void decreaseStock(); }
再次啓動product-server,沒問題的話,把product上傳到本地maven倉庫:
mvn -Dmaven.test.skip=true -U clean install
而後初始化訂單微服務工程後繼續。
修改order-server工程
order-server的服務可能會被User等其餘服務調用,也是個Eureka client,添加依賴:
<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>
啓動類加註解@EnableDiscoveryClient
package com.hicoview.product; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; @SpringBootApplication @EnableDiscoveryClient public class ProductApplication { public static void main(String[] args) { SpringApplication.run(ProductApplication.class, args); } }
配置application.yml:
eureka: client: service-url: defaultZone: http://localhost:8761/eureka/ spring: application: name: order server: port: 8081
啓動order-server,訪問註冊中心http://localhost:8761/:
ORDER服務註冊成功後,繼續調整order-server工程
因爲訂單服務要調用商品服務,需添加對product-client依。修改pom.xml:
<dependency> <groupId>com.hicoview</groupId> <artifactId>product-client</artifactId> </dependency>
對版本的管理統一交給上層,修改上層order的pom.xml,增長如下配置:
<properties> ... <product-client.version>0.0.1-SNAPSHOT</product-client.version> </properties> <dependencyManagement> <dependencies> ... <dependency> <groupId>com.hicoview</groupId> <artifactId>product-client</artifactId> <version>${product-client.version}</version> </dependency> </dependencies> </dependencyManagement>
回到order-server,修改啓動類,添加Feign掃描路徑:
package com.hicoview.order; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.openfeign.EnableFeignClients; @SpringBootApplication @EnableDiscoveryClient @EnableFeignClients(basePackages = "com.hicoview.product.client") public class OrderApplication { public static void main(String[] args) { SpringApplication.run(OrderApplication.class, args); } }
而後按順序建立Controller、Service:
package com.hicoview.order.controller; import com.hicoview.order.service.OrderService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/order") public class OrderController { @Autowired private OrderService orderService; // 建立訂單 @PostMapping("/create") public void create() { orderService.createOrder(); } }
public interface OrderService { void createOrder(); }
@Service @Slf4j public class OrderServiceImpl implements OrderService { @Autowired private ProductClient productClient; @Override public void createOrder() { log.info("------建立訂單-----"); // 調用商品扣減服務 productClient.decreaseStock(); } }
啓動order-server,發起下單請求:
curl -X POST http://localhost:8081/order/create
服務調用成功,order-server和product-server輸出:
2019-02-26 11:09:58.408 INFO 3021 --- [nio-8081-exec-3] c.h.order.service.impl.OrderServiceImpl : ------建立訂單-----
2019-02-26 11:09:58.430 INFO 3015 --- [nio-8080-exec-3] c.h.p.service.impl.ProductServiceImpl : ------扣減庫存-----
爲了示例的極簡,上面服務調用沒有涉及到入參和返回值。若是須要定義參數或返回值,考慮到內外部都會用到,需將參數bean定義在product-common中做爲公共bean。
Rest服務的調用,可使用Feign或RestTemplate來完成,上面示例咱們使用了Feign。
Feign(推薦)
Feign是一個聲明式的Web Service客戶端,它的目的就是讓Web Service調用更加簡單。Feign提供了HTTP請求的模板,經過編寫簡單的接口和插入註解,就能夠定義好HTTP請求的參數、格式、地址等信息。
Feign會徹底代理HTTP請求,咱們只須要像調用方法同樣調用它就能夠完成服務請求及相關處理。Feign整合了Ribbon和Hystrix(關於Hystrix咱們後面再講),可讓咱們再也不須要顯式地使用這兩個組件。
RestTemplate
RestTemplate提供了多種便捷訪問遠程Http服務的方法,能夠了解下。
第一種方式,直接使用RestTemplate,URL寫死:
// 1. RestTemplate restTemplate = new RestTemplate(); restTemplate.getForObject("http://localhost:8080/product/decreaseStock", String.class);
這種方式直接使用目標的IP,而線上部署的IP地址可能會切換,同一個服務還會啓多個進程,因此有弊端。
第二種方式,利用LoadBalancerClient,經過應用名獲取URL,而後再使用RestTemplate:
@Autowired private LoadBalancerClient loadBalancerClient;
// 1. 第二種方式,經過應用名字拿到其中任意一個host和port ServiceInstance serviceInstance = loadBalancerClient.choose("PRODUCT"); String url = String.format("http://%s:%s", serviceInstance.getHost(), serviceInstance.getPort() + "/product/decreaseStock"); RestTemplate restTemplate = new RestTemplate(); restTemplate.getForObject(url, String.class);
第三種方式,寫一個config把RestTemplate做爲一個bean配置上去:
@Component public class RestTemplateConfig { @Bean @LoadBalanced public RestTemplate restTemplate() { return new RestTemplate(); } }
@Autowired private RestTemplate restTemplate;
// 使用時url裏直接用應用名PRODUCT便可 restTemplate.getForObject("http://PRODUCT/product/decreaseStock", String.class);
Ribbon客戶端負載均衡器
Spring Cloud Ribbon是一個基於HTTP和TCP的客戶端負載均衡工具,它基於Netflix Ribbon實現。經過Spring Cloud的封裝,可讓咱們輕鬆地將面向服務的REST模版請求自動轉換成客戶端負載均衡的服務調用。Spring Cloud Ribbon雖然只是一個工具類框架,它不像服務註冊中心、配置中心、API網關那樣須要獨立部署,可是它幾乎存在於每個Spring Cloud構建的微服務和基礎設施中。
在Spring Cloud中,Ribbon可自動從Eureka Server獲取服務提供者地址列表,並基於負載均衡算法,請求其中一個服務提供者實例。還有微服務之間的調用,經過Feign或RestTemplate找到一個目標服務。以及API網關Zuul的請求轉發等內容,實際上都是經過Ribbon來實現的。
MQ經常使用於分佈式系統應用解耦,流量銷峯、異步處理的場景。spring-cloud集成了RabbitMQ,使用AmqpTemplate對消息進行發送。前面介紹Spring Cloud Bus時已安裝過RabbitMQ了。
咱們嘗試在product-server扣減庫存時發送消息,由order-server訂閱。
修改product-server,引入依賴:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency>
調整productServiceIml:
@Service @Slf4j public class ProductServiceImpl implements ProductService { @Autowired private AmqpTemplate amqpTemplate; @Override public void decreaseStock() { log.info("------扣減庫存-----"); // 使用AmqpTemplate發送異步消息 amqpTemplate.convertAndSend("productInfo", "庫存扣減消息"); } }
接下來是訂閱方order-server,引入依賴:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency>
package com.hicoview.order.message; import lombok.extern.slf4j.Slf4j; import org.springframework.amqp.rabbit.annotation.Queue; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.stereotype.Component; @Component @Slf4j public class MessageReceiver { @RabbitListener(queuesToDeclare = @Queue("productInfo")) public void process(String message) { log.info("MqReceiver: {}", message); } }
下單測試下:
curl -X POST http://localhost:8081/order/create
order-server控制檯輸出:
2019-02-27 11:27:25.777 INFO 7816 --- [cTaskExecutor-1] c.h.order.message.MessageReceiver : MqReceiver: 庫存扣減消息
各類各樣的消息隊列的產生和更新,使MQ組件學習成本愈來愈高,String Cloud Stream爲一些供應商的消息中間件產品(目前支持rabbit和kafka)提供了個性化的自動化配置,引入發佈訂閱、消費組、以及分區這3個概念,有效的簡化了上層研發人員對MQ使用的複雜度,讓開發人員更多的精力投入到核心業務的處理。
Spring Cloud Config爲各應用環境提供了一箇中心化的外部配置。配置服務器默認採用git來存儲配置信息,這樣就有助於對配置進行版本管理,而且能夠經過git客戶端工具來方便維護配置內容。固然它也提供本地化文件系統的存儲方式。
使用集中式配置管理,在配置變動時,能夠通知到各應用程序,應用程序不須要重啓。
建立Config Server端工程config-server:
File -> New->Product... -> 選擇Spring Initializr -> Project SDK用1.8 -> Next -> 輸入Product Metadata -> Next
(springboot選擇2.0以上) 選擇Cloud Discovery -> 選擇Eureka Discovery 選擇Cloud Config -> 選擇Config Server
因爲選擇了Eureka Discovery和Config Server,建立成功後pom.xml裏已經幫你引入瞭如下依賴:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-server</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
Config Server也是要註冊到Eureka,做爲Eureka Client,咱們還要加入以下依賴:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- 避免後面的數據庫配置出錯,mysql依賴也加了 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency>
再給啓動類加上註解@EnableDiscoveryClient和@EnableConfigServer:
package com.hicoview.config; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.config.server.EnableConfigServer; @SpringBootApplication @EnableDiscoveryClient @EnableConfigServer public class EurekaApplication { public static void main(String[] args) { SpringApplication.run(EurekaApplication.class, args); } }
配置application.yml:
eureka: client: service-url: defaultZone: http://localhost:8761/eureka/ spring: application: name: config cloud: config: server: git: uri: http://code.hicoview.com:8000/backend/config.git username: root password: 8ggf9afd6g9gj # 配置文件下載後存儲的本地目錄 basedir: /Users/zhutx/springcloud/config-server/basedir server: port: 9999
而後按照配置的git倉庫地址,在github或gitlab上建立config倉庫。
以商品微服務的配置來演示,在倉庫建立product-dev.yml:
server: port: 8080 spring: datasource: driver-class-name: com.mysql.jdbc.Driver username: root password: passwd_1986 url: jdbc:mysql://127.0.0.1:3306/SpringCloud_Sell?characterEncoding=utf-8&useSSL=false
啓動做爲Config Server的config-server工程,查看註冊中心http://localhost:8761:
訪問config-server的如下任一地址,均可以顯示出對應格式的配置內容:
http://localhost:9999/product-dev.yml
http://localhost:9999/product-dev.properties
http://localhost:9999/product-dev.json
接下來咱們看看配置客戶端Config Client(即product-server)怎麼引用配置。
咱們給product-server加入配置客戶端的依賴:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-client</artifactId> </dependency>
修改application.yml配置:
spring: application: name: product cloud: config: discovery: enabled: true service-id: CONFIG profile: dev eureka: client: service-url: defaultZone: http://localhost:8761/eureka/
這樣子就能夠從Eureka服務註冊中心找到CONFIG服務,並拿到product-dev.yml了。
啓動product-server,查看Eurake註冊中心,PRODUCT服務成功註冊:
若是代碼裏有操做數據庫,那麼啓動時會初始化數據庫鏈接,這種狀況下啓動其實會出錯,由於spring boot不知道配置加載順序。
咱們指望是先拿到CONFIG的配置,再初始化數據庫。
解決辦法是把product-server的application.yml改爲bootstrap.yml就好,讓本地配置項優先級更高。
微服務工程使用配置服務的狀況下,注意將application.yml都改爲bootstrap.yml。而且,讓bootstrap.yml文件只保留Eureka Client的配置和獲取CONFIG的配置;
另外,若是生產環境要使用統一配置中心,能夠啓動多個Config Server進程,保持高可用。
一樣的操做,把order-server配置也抽取到外部
下圖是當前的配置工做機制,config-server拉取遠端git配置,並在本地存一份。而後config-server經過把自身註冊到Eureka從而提供了拉取配置的服務,而配置客戶端(product和order)經過引入config-client依賴,在啓動時便能獲取加載到了配置。
咱們須要作到修改遠程配置,應用程序不重啓,還須要藉助Spring Cloud Bus。Spring Cloud Bus集成了RabbitMQ,併爲config-server自動引入配置刷新服務(bus-refresh)。
以下圖所示,作法是遠端git修改配置後,主動調用config-server的/bus-refresh服務,發佈RabbitMQ消息,config-client接收消息並更新配置。
咱們先安裝RabbitMQ:
# docker安裝rabbitmq docker run -d --hostname my-rabbit -p 5672:5672 -p 15672:15672 rabbitmq:3.7.9-management # 驗證下 docker ps | grep 'rabbitmq'
能成功訪問RabbitMQ控制檯http://localhost:15671,繼續。
修改config-server,增長依賴:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bus-amqp</artifactId> </dependency>
修改application.yml,增長如下配置,把包括bus-refresh在內的全部config server的服務都暴露出來:
management: endpoints: web: exposure: include: "*"
咱們拿product-server來演示,修改product-server的pom增長依賴:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bus-amqp</artifactId> </dependency>
而後爲product-server提供一個WEB接口,方便咱們在配置變化時驗證是否已加載新的配置:
@RestController @RequestMapping("/env") @RefreshScope public class EnvController { @Value("${env}") private String env; @GetMapping("/print") public String print() { return env; } }
測試下,咱們修改遠端git配置,先增長env配置:
server: port: 8080 spring: datasource: driver-class-name: com.mysql.jdbc.Driver username: root password: passwd_1986 url: jdbc:mysql://127.0.0.1:3306/SpringCloud_Sell?characterEncoding=utf-8&useSSL=false # 增長了該配置 env: dev
重啓下config-server和product-server。
訪問 product-server http://localhost:8080/env/print,顯示dev
而後咱們把遠端env配置項改爲test,調用config-server的配置刷新服務 bus-refresh:
curl -v -X POST http://localhost:9999/actuator/bus-refresh
再次訪問 product-server http://localhost:8080/env/print,顯示test
至此,已經作到了變動配置不重啓應用。咱們再借助Git倉庫的webhook功能,在push指令發生後幫咱們發個bus-refresh請求就完美了。Gitlab的話在倉庫的這個位置:
Repository -> Settings -> Integrations -> Add webhook
Zuul 是在雲平臺上提供動態路由,監控,彈性,安全等邊緣服務的框架。Zuul 至關因而設備和 Netflix 流應用的 Web網站後端全部請求的前門。以下圖:
Filter是Zuul的核心,用來實現對外服務的控制。Filter的生命週期有4個,分別是「PRE」、「ROUTING」、「POST」、「ERROR」,整個生命週期能夠用下圖來表示:
Zuul大部分功能都是經過過濾器來實現的,這些過濾器類型對應於請求的典型生命週期。
Zuul中默認實現的Filter
順序值越小的Filter,優先級越高
類型 | 順序 | 過濾器 | 功能 |
---|---|---|---|
pre | -3 | ServletDetectionFilter | 標記處理Servlet的類型 |
pre | -2 | Servlet30WrapperFilter | 包裝HttpServletRequest請求 |
pre | -1 | FormBodyWrapperFilter | 包裝請求體 |
route | 1 | DebugFilter | 標記調試標誌 |
route | 5 | PreDecorationFilter | 處理請求上下文供後續使用 |
route | 10 | RibbonRoutingFilter | serviceId請求轉發 |
route | 100 | SimpleHostRoutingFilter | url請求轉發 |
route | 500 | SendForwardFilter | forward請求轉發 |
post | 0 | SendErrorFilter | 處理有錯誤的請求響應 |
post | 1000 | SendResponseFilter | 處理正常的請求響應 |
Zuul高併發性能不如Nginx,咱們可使用Zuu微服務網關實現統一鑑權、限流、參數校驗、請求參數和返回結果包裝,結合Nginx做爲前置負載均衡服務器,構建以下圖所示的微服務架構:
建立網關工程api-gateway:
File -> New->Product... -> 選擇Spring Initializr -> Project SDK用1.8 -> Next -> 輸入Product Metadata -> Next
(springboot選擇2.0以上) 選擇Cloud Discovery -> 選擇Eureka Discovery 選擇Cloud Config -> 選擇Config Client 選擇Cloud Routing -> 選擇Zuul
按以上配置,已經幫咱們引入瞭如下依賴:
<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-zuul</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-client</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
增長依賴:
<!-- 做爲Eureka Client,需增長web依賴 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- 做爲Config Client,需添加spring cloud bus依賴 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bus-amqp</artifactId> </dependency>
啓動類添加@EnableZuulProxy註解:
@SpringBootApplication @EnableZuulProxy public class ApiGatewayApplication { public static void main(String[] args) { SpringApplication.run(ApiGatewayApplication.class, args); } // 遠程Zull配置變動後,自動載入新配置 @ConfigurationProperties("zuul") @RefreshScope public ZuulProperties zuulProperties() { return new ZuulProperties(); } }
Git中央倉庫添加api-gateway-dev.yml
server: port: 9000 zuul: # 所有服務忽略敏感頭(可傳遞cookie) sensitive-headers:
默認路由規則,好比訪問product微服務的/product/list,須要請求/product/product/list
server: port: 9000 zuul: # 所有服務忽略敏感頭(可傳遞cookie) sensitive-headers: management: endpoints: web: exposure: # 經過http://localhost:9000/actuator/routes 能夠查看路由規則 include: "*"
修改路由規則:
server: port: 9000 zuul: # 所有服務忽略敏感頭(所有服務均可傳遞cookie) sensitive-headers: # 修改網關路由規則 routes: # /myProduct/product/list -> /product/product/list product: /myProduct/** # 禁用特定的一些路由請求 ignored-patterns: - /order/order/list - /order/order/create # - /**/order/list management: endpoints: web: exposure: include: "*"
自定義Filter實現鑑權:
@Component public class AuthFilter extends ZuulFilter { @Autowired private StringRedisTemplate stringRedisTemplate; // 鑑權是前置過濾器 @Override public String filterType() { return PRE_TYPE; } @Override public int filterOrder() { return PRE_DECORATION_FILTER_ORDER - 1; } @Override public boolean shouldFilter() { RequestContext requestContext = RequestContext.getCurrentContext(); HttpServletRequest request = requestContext.getRequest(); // 除登陸外的其餘請求,都要進行權限校驗 if (!"/login".equals(request.getRequestURI())) { return true; } return false; } @Override public Object run() { RequestContext requestContext = RequestContext.getCurrentContext(); HttpServletRequest request = requestContext.getRequest(); // 假設請求url裏不攜帶cookie就看作未登陸 Cookie cookie = CookieUtil.get(request, "token"); if (cookie == null || StringUtils.isEmpty(cookie.getValue())) { requestContext.setSendZuulResponse(false); requestContext.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value()); } return null; } }
自定義Filter實現限流
直接用google的guava組件的令牌桶實現限流
package com.hicoview.apigateway.filter; import com.google.common.util.concurrent.RateLimiter; import com.hicoview.apigateway.exception.RateLimitException; import com.netflix.zuul.ZuulFilter; import org.springframework.stereotype.Component; import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE; import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.SERVLET_DETECTION_FILTER_ORDER; @Component public class RateLimitFilter extends ZuulFilter { // 每秒限流1000 private static final RateLimiter RATE_LIMITER = RateLimiter.create(1000); // 限流做爲前置過濾器 @Override public String filterType() { return PRE_TYPE; } // 做爲限流,優先級要比最高的還要高 @Override public int filterOrder() { return SERVLET_DETECTION_FILTER_ORDER - 1; } @Override public boolean shouldFilter() { return true; } @Override public Object run() { if (!RATE_LIMITER.tryAcquire()) { throw new RateLimitException(); } return null; } }
配置容許域訪問
@Configuration public class CorsConfig { @Bean public CorsFilter corsFilter() { final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); final CorsConfiguration config = new CorsConfiguration(); config.setAllowCredentials(true); config.setAllowedOrigins(Arrays.asList("*")); config.setAllowedHeaders(Arrays.asList("*")); config.setAllowedMethods(Arrays.asList("*")); config.setMaxAge(300l); source.registerCorsConfiguration("/**", config); return new CorsFilter(source); } }