前面分別對 Spring Cloud Zuul 與 Spring Cloud Gateway 進行了簡單的說明,它門是API網關,API網關負責服務請求路由、組合及協議轉換,客戶端的所有請求都首先經過API網關,然后由它將匹配的請求路由到合適的微服務,是系統流量的入口,在實際生產環境中為了保證高可靠和高可用,盡量避免重啟,如果有新的服務要上線時,可以通過動態路由配置功能上線。
本篇拿 Spring Cloud Gateway 為例,對網關的動態路由進行簡單分析,下一篇將分享動態路由的進階實現
1.gateway配置路由主要有兩種方式,1.用yml配置文件,2.寫在代碼里。而無論是 yml,還是代碼配置,啟動網關后將無法修改路由配置,如有新服務要上線,則需要先把網關下線,修改 yml 配置后,再重啟網關
- yml配置方式
- 代碼配置方式
2.gateway網關啟動時,路由信息默認會加載內存中,路由信息被封裝到 RouteDefinition 對象中,配置多個RouteDefinition組成gateway的路由系統,仔細的同學可能看到RouteDefinition中的字段與上面代碼配置方式比較對應
- RouteDefinition對象在 org.springframework.cloud.gateway.route包下,其定義如下:
- RouteDefinitionLocator是個接口,在org.springframework.cloud.gateway.route包下,如果想查看網關中所有的路由信息,調用此接口方法是一個辦法,需要從先注入到容器,后面還有另外一種查看方式,是Spring Cloud Gateway 的Endpoint端點提供的方法
3.Spring Cloud Gateway 提供了 Endpoint 端點,暴露路由信息,有獲取所有路由、刷新路由、查看單個路由、刪除路由等方法,源碼在 org.springframework.cloud.gateway.actuate.GatewayControllerEndpoint 中,想訪問端點中的方法需要添加 spring-boot-starter-actuator 注解,並在配置文件中暴露所有端點
# 暴露端點
management:
endpoints:
web:
exposure:
include: '*'
endpoint:
health:
show-details: always
4.動態路由代碼實現
前提:需要啟動 3個服務,eureka、gateway、consumer-service
- 1.eureka使用前面博客中的代碼
- 2.consumer-service是個web項目,提供一個hello方法,需注冊到eureka上
- 3.新建gateway,添加引用
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<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-actuator</artifactId>
</dependency>
- 4.添加基本配置和注冊到eureka,不要配置路由信息映射到consumer-service,由后面的動態路由功能路由過去
- 5.根據Spring Cloud Gateway的路由模型定義數據傳輸模型,分別是:路由模型、過濾器模型、斷言模型
//1.創建路由模型
public class GatewayRouteDefinition {
//路由的Id
private String id;
//路由斷言集合配置
private List<GatewayPredicateDefinition> predicates = new ArrayList<>();
//路由過濾器集合配置
private List<GatewayFilterDefinition> filters = new ArrayList<>();
//路由規則轉發的目標uri
private String uri;
//路由執行的順序
private int order = 0;
//此處省略get和set方法
}
//2.創建過濾器模型
public class GatewayFilterDefinition {
//Filter Name
private String name;
//對應的路由規則
private Map<String, String> args = new LinkedHashMap<>();
//此處省略Get和Set方法
}
//3.路由斷言模型
public class GatewayPredicateDefinition {
//斷言對應的Name
private String name;
//配置的斷言規則
private Map<String, String> args = new LinkedHashMap<>();
//此處省略Get和Set方法
}
- 6.編寫動態路由實現類,需實現ApplicationEventPublisherAware接口
/**
* 動態路由服務
*/
@Service
public class DynamicRouteServiceImpl implements ApplicationEventPublisherAware{
@Autowired
private RouteDefinitionWriter routeDefinitionWriter;
private ApplicationEventPublisher publisher;
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.publisher = applicationEventPublisher;
}
//增加路由
public String add(RouteDefinition definition) {
routeDefinitionWriter.save(Mono.just(definition)).subscribe();
this.publisher.publishEvent(new RefreshRoutesEvent(this));
return "success";
}
//更新路由
public String update(RouteDefinition definition) {
try {
delete(definition.getId());
} catch (Exception e) {
return "update fail,not find route routeId: "+definition.getId();
}
try {
routeDefinitionWriter.save(Mono.just(definition)).subscribe();
this.publisher.publishEvent(new RefreshRoutesEvent(this));
return "success";
} catch (Exception e) {
return "update route fail";
}
}
//刪除路由
public Mono<ResponseEntity<Object>> delete(String id) {
return this.routeDefinitionWriter.delete(Mono.just(id)).then(Mono.defer(() -> {
return Mono.just(ResponseEntity.ok().build());
})).onErrorResume((t) -> {
return t instanceof NotFoundException;
}, (t) -> {
return Mono.just(ResponseEntity.notFound().build());
});
}
}
- 7.編寫 Rest接口,通過這些接口實現動態路由功能,注意SpringCloudGateway使用的是WebFlux不要引用WebMvc
@RestController
@RequestMapping("/route")
public class RouteController {
@Autowired
private DynamicRouteServiceImpl dynamicRouteService;
//增加路由
@PostMapping("/add")
public String add(@RequestBody GatewayRouteDefinition gwdefinition) {
String flag = "fail";
try {
RouteDefinition definition = assembleRouteDefinition(gwdefinition);
flag = this.dynamicRouteService.add(definition);
} catch (Exception e) {
e.printStackTrace();
}
return flag;
}
//刪除路由
@DeleteMapping("/routes/{id}")
public Mono<ResponseEntity<Object>> delete(@PathVariable String id) {
try {
return this.dynamicRouteService.delete(id);
}catch (Exception e){
e.printStackTrace();
}
return null;
}
//更新路由
@PostMapping("/update")
public String update(@RequestBody GatewayRouteDefinition gwdefinition) {
RouteDefinition definition = assembleRouteDefinition(gwdefinition);
return this.dynamicRouteService.update(definition);
}
//把傳遞進來的參數轉換成路由對象
private RouteDefinition assembleRouteDefinition(GatewayRouteDefinition gwdefinition) {
RouteDefinition definition = new RouteDefinition();
definition.setId(gwdefinition.getId());
definition.setOrder(gwdefinition.getOrder());
//設置斷言
List<PredicateDefinition> pdList=new ArrayList<>();
List<GatewayPredicateDefinition> gatewayPredicateDefinitionList=gwdefinition.getPredicates();
for (GatewayPredicateDefinition gpDefinition: gatewayPredicateDefinitionList) {
PredicateDefinition predicate = new PredicateDefinition();
predicate.setArgs(gpDefinition.getArgs());
predicate.setName(gpDefinition.getName());
pdList.add(predicate);
}
definition.setPredicates(pdList);
//設置過濾器
List<FilterDefinition> filters = new ArrayList();
List<GatewayFilterDefinition> gatewayFilters = gwdefinition.getFilters();
for(GatewayFilterDefinition filterDefinition : gatewayFilters){
FilterDefinition filter = new FilterDefinition();
filter.setName(filterDefinition.getName());
filter.setArgs(filterDefinition.getArgs());
filters.add(filter);
}
definition.setFilters(filters);
URI uri = null;
if(gwdefinition.getUri().startsWith("http")){
uri = UriComponentsBuilder.fromHttpUrl(gwdefinition.getUri()).build().toUri();
}else{
// uri為 lb://consumer-service 時使用下面的方法
uri = URI.create(gwdefinition.getUri());
}
definition.setUri(uri);
return definition;
}
}
- 8.啟動項目,查看網關路由信息,訪問 localhost:9999/actuator/gateway/routes,因沒有配置路由信息,因此返回結果為空數組
- 9.通過Postman發一個 post 請求新增路由,接口地址:http://localhost:9999/route/update,路由到 consumer-service 上,然后通過網關訪問查看是否轉發請求了(這里直接調用的update,有就會覆蓋,沒有則新增)
- 10.再訪問 localhost:9999/actuator/gateway/routes ,可以看到新的路由信息已經配置進去了,這就是動態路由配置,還可以調用刪除、修改接口,操作動態操作路由信息
- 11.配置路由信息后,訪問consumer-service服務,正常返回,說明路由已經生效,請求轉發到consumer-service服務
好了,動態路由的簡單實現了,一般在生產環境不使用此方式,因為網關都是多實例部署,還可能隨時增加實例,需要已調用接口的方式一一調用網關所有的實例
本篇博客參考了許進的文章,地址:http://springcloud.cn/view/407 ,還有他寫的 《重新定義SpringCloud》比較不錯
代碼已上傳至碼雲,源碼,項目使用的版本信息如下:
- SpringBoot 2.0.6.RELEASE
- SpringCloud Finchley.SR2
下篇將介紹動態路由的進階實現,實現方式:
- 創建一個路由信息維護的項目
- 實現增刪改查路由信息到mysql
- 然后發布,發布后將路由信息與版本信息保存到redis中,對外提供 rest 接口
- 網關開啟定時任務,定時拉取 rest 接口中最新版本的路由信息,這樣網關發布多個實例后,都會單獨的去拉取維護的路由信息
參考了https://blog.csdn.net/tianyaleixiaowu/article/details/83412301
轉自:https://blog.csdn.net/zhuyu19911016520/article/details/86557165