通常我們如果有一個服務,會部署到多台伺服器上,這些微服務如果都暴露給客戶,是非常難以管理的,我們系統需要有一個唯一的出口,API網關是一個服務,是系統的唯一出口。API網關封裝了系統内部的微服務,為用戶端提供一個定制的API。用戶端隻需要調用網關接口,就可以調用到實際的微服務,實際的服務對客戶不可見,并且容易擴充服務。
API網關可以結合ribbon完成負載均衡的功能,可以自動檢查微服務的狀況,及時剔除或者加入某個微服務到可用服務清單。此外網關可以完成權限檢查、限流、統計等功能。下面我們将一一完成上面的功能。注意微服務隻是提供rest的接口,不會有額外的元件依賴,不需要eureka等。隻需要兩個工程,一個是微服務,我們可以部署到多台伺服器,那麼隻是通路的ip不同,在示範的時候,我們在本機示範,修改端口,達到啟動多個微服務的目的,另一個就是網關,主要是spring cloud gateway 和 ribbon兩大元件來實作網關和負載均衡等功能。

GitHub代碼
1|01、rest服務建構
1、建立一個父工程
删除src目錄
pom檔案增加如下内容:
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.3.RELEASE</version> </parent>
2、建立一個module
3、修改pom.xml檔案,增加如下内容:
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies>
4、在resources檔案夾下面增加一個application.yml檔案,内容為:
server: port: 1001
5、新增一個主入口類Provider1001Application,内容如下
package com.yefengyu.cloud; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class Provider1001Application { public static void main(String[] args) { SpringApplication.run(Provider1001Application.class, args); } }
6、新增一個rest接口HelloWorldController,内容如下:
package com.yefengyu.cloud.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; @RestController public class HelloWorldController { @GetMapping("/hello") @ResponseBody public String hello() { return "hello spring cloud, 1001"; } }
7、啟動main方法,在浏覽器輸入http://localhost:1001/hello可以看到:
8、同理,我們再建立2個rest服務,步驟如下
(1)新增module,參考第二小節,主要把名稱改為:
provider1002
provider1003
(2)pom.xml檔案參考第三小節,加上即可。
(3)參考第四小節,建立application.yml檔案,注意端口改為
1002
1003
(4)參考第五小節,建立一個主啟動類,名稱為如下,内容都一樣。
Provider1002Application
Provider1003Application
(5)參考第六小節,新增一個HelloWorldController接口,其中隻有下面這句中的 1001 改為 1002 或 1003,友善測試觀察結果
return "hello spring cloud, 1001";
(6)參考第7小節測試成功新增的兩個服務即可。
(7)現在的工程如下:
2|02、spring cloud gateway
Spring Cloud Gateway是Spring官方基于Spring 5.0,Spring Boot 2.0和Project Reactor等技術開發的網關,Spring Cloud Gateway旨在為微服務架構提供一種簡單而有效的統一的API路由管理方式。
路由:Gateway的基礎構模組化塊。它包括一個ID,一個目标URL,一個斷言集合和一個過濾器集合。如果斷言判斷為真,則路由比對。
斷言:這是Java8的新增功能,輸入的類型為Spring架構的ServerWebExchange。它可以比對HTTP請求中的任何東西,比如:請求頭或者參數。
過濾器:是Spring架構的GatewayFilter,請求和響應都可以被Filter修改。
1、建立一個module,按照上面的方式,名稱叫: gateway
2、添加依賴
<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> <version>2.1.0.RELEASE</version> </dependency> </dependencies>
3、建立一個主啟動類 GatewayApplication
package com.yefengyu.gateway; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class GatewayApplication { public static void main(String[] args) { SpringApplication.run(GatewayApplication.class, args); } }
4、新增一個application.yml檔案,内容如下:
server: port: 8080 spring: application: name: gateway_server cloud: gateway: default-filters: routes: - id: my_route uri: http://localhost:1001/ predicates: - Path=/gateway/** filters: - StripPrefix=1
5、測試
通路 http://localhost:8080/gateway/hello
上面隻是簡單的路由轉發,可以先了解下工作原理:
url 中的 http://localhost:8080/ 會通路到gateway這個服務,spring cloud gateway會在配置的路由中做謂詞比對,也就是url中的gateway比對到了id為my_route的路由,就會把http://localhost:8080/替換為http://localhost:1001/,并且filters中的規則(StripPrefix)會把http://localhost:8080/gateway/hello中的gateway去掉,那麼http://localhost:8080/gateway/hello實際就會去通路http://localhost:1001/hello,也就是通路到了provider1001服務。
疑問?
上面的uri隻配置了provider1001服務,如何使用geteway通路三個服務呢?需要使用負載均衡ribbon
3|03、ribbon負載均衡
下面的操作都是在gateway這個服務操作的:
1、添加依賴
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-ribbon</artifactId> <version>2.1.0.RELEASE</version> </dependency>
2、修改配置檔案,增加或者修改見紅色部分
server: port: 8080 spring: application: name: gateway_server cloud: gateway: default-filters: routes: - id: my_route uri: lb://my-load-balanced-service predicates: - Path=/gateway/** filters: - StripPrefix=1 my-load-balanced-service: ribbon: listOfServers: localhost:1001, localhost:1002,localhost:1003 NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule
3、重新開機gateway服務,不斷通路 http://localhost:8080/gateway/hello,發現可以不斷通路的時候在三個服務中來回切換,達到了負載均衡的目的。上面我們使用了輪詢的負載均衡政策。
注意:
listOfServers:配置的微服務的伺服器ip端口
NFLoadBalancerRuleClassName:使用的負載均衡政策,預設提供了幾種,也可以自己實作(後續講解),預設提供的如下:
疑問:
如果上面listOfServers中的任何一個服務關閉了,然後使用gateway通路,會出現什麼情況?
事實是這樣的:
比如provider1003服務當機。那麼使用輪詢算法的時候,不斷通路gateway,會出現: provider1001 正常,provider1002 正常,provider1003 異常,provider1001 正常,provider1002 正常,provider1003 異常。。。 provider1003 已經當機,但是請求還是不斷轉發到該服務上,導緻服務通路的過程中,部分請求異常。
我們需要有這樣的功能,如果某個服務當機,那麼請求就不會發送到該伺服器上,如果當機的伺服器恢複了,那麼請求又可以發送到該伺服器上,要實作這個功能,需要ribbon對服務進行健康檢查。
(1)首先微服務需要有個rest接口,就叫做heath接口吧,調用heath接口傳回ok表明服務正常。
(2)gateway需要有調用heath接口的功能,并且配置到ribbon可以不斷調用該接口,時刻檢查服務的狀态,如果有伺服器挂掉,可以很快感覺到,并把該服務剔除可用服務清單。
4|04、健康檢查
1、provider1001,provider1002,provider1003增加一個rest接口HealthController
package com.yefengyu.cloud.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; @RestController public class HealthController { @GetMapping("/heath") @ResponseBody public String heath() { return "ok"; } }
2、在gateway工程裡面做如下修改
(1)建立一個Config類
package com.yefengyu.gateway.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; @Configuration public class MainConfig { @Bean public RestTemplate restTemplate() { return new RestTemplate(); } }
(2)建立一個健康檢查的類,主要是調用heath接口。
package com.yefengyu.gateway.loadbanlance; import com.netflix.loadbalancer.IPing; import com.netflix.loadbalancer.Server; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; import org.springframework.web.client.RestTemplate; @Component public class HealthExamination implements IPing { @Autowired private RestTemplate restTemplate; @Override public boolean isAlive(Server server) { String url = "http://" + server.getId() + "/heath"; try { ResponseEntity<String> heath = restTemplate.getForEntity(url, String.class); if (heath.getStatusCode() == HttpStatus.OK) { System.out.println("ping " + url + " success and response is " + heath.getBody()); return true; } System.out.println("ping " + url + " error and response is " + heath.getBody()); return false; } catch (Exception e) { System.out.println("ping " + url + " failed"); return false; } } }
上面代碼繼承IPing接口,判斷服務是否可用。我們在微服務中增加heath接口,在gateway中調用該接口,如果傳回正常則認為微服務可用。
(3)修改配置檔案,注意最後一行,這是唯一增加的。
server: port: 8080 spring: application: name: gateway_server cloud: gateway: default-filters: routes: - id: my_route uri: lb://my-load-balanced-service predicates: - Path=/gateway/** filters: - StripPrefix=1 my-load-balanced-service: ribbon: listOfServers: localhost:1001,localhost:1002,localhost:1003 NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule NFLoadBalancerPingClassName: com.yefengyu.gateway.loadbanlance.HealthExamination
3、重新啟動微服務和gateway服務,然後通過網關通路,可以看到可以正常通路,如果此時把某一台微服務停掉,通路的時候開始可能會報錯,但是随着健康檢查的運作,檢測到該服務不可用,則會把該服務剔除,以後隻會通路正常運作的服務。當當機的服務重新開機,健康檢查還會把該服務加入到可用服務清單,下次請求就會再次發送到該服務上。
5|05、自定義負載均衡政策
上面示範了随機、輪詢等負載均衡算法,我們可以自定義負載均衡算法。需求是:每個伺服器通路三次再跳轉到下一個伺服器。
隻需要在gateway項目中實作AbstractLoadBalancerRule抽象類,然後配置到下面即可
1、負載均衡算法(參考RandomRule)
package com.yefengyu.gateway.loadbanlance; import com.netflix.client.config.IClientConfig; import com.netflix.loadbalancer.AbstractLoadBalancerRule; import com.netflix.loadbalancer.ILoadBalancer; import com.netflix.loadbalancer.Server; import java.util.ArrayList; import java.util.List; public class MyRule extends AbstractLoadBalancerRule { private volatile int total; private volatile int index; List<Server> upList = new ArrayList<>(); public MyRule() { } public Server choose(ILoadBalancer lb, Object key) { if (lb == null) { return null; } else { Server server = null; while (server == null) { if (Thread.interrupted()) { return null; } List<Server> allList = lb.getAllServers(); int serverCount = allList.size(); if (serverCount == 0) { return null; } if (total == 0) { upList = lb.getReachableServers(); } if (total < 3) { if (upList.size() != lb.getReachableServers().size()) { index = 0; } server = lb.getReachableServers().get(index); total++; } else { total = 0; index++; if (index >= lb.getReachableServers().size()) { index = 0; } } if (server == null) { Thread.yield(); } else { if (server.isAlive()) { return server; } server = null; Thread.yield(); } } return server; } } public Server choose(Object key) { return this.choose(this.getLoadBalancer(), key); } public void initWithNiwsConfig(IClientConfig clientConfig) { } }
2、修改配置
3、重新開機gateway,然後通路http://localhost:8080/gateway/hello不斷點選,發現點選三次就會調整到下一個服務。
