微服務當前這么火爆的程度,如果不能學會一種微服務框架技術。怎么能升職加薪,增加簡歷的籌碼?spring cloud 和 Dubbo 需要單獨學習。說沒有時間?沒有精力?要學倆個框架?而Spring Cloud alibaba只需要你學會一個就會擁有倆種微服務治理框架技術。何樂而不為呢?加油吧!騷猿年
在之前的 zuul 我們講了。怎么去動態的獲取路由。作為Spring Cloud 親兒子的存在 gateway 不可能不支持動態路由。今天我們初探一下gateway 的動態路由。
需求前置。在了解動態路由。可能會能想到,我們的數據要存儲在數據庫中或者redis 當中。還要有存儲的實體類。
思考:
配置中心刷新routes配置信息。路由信息刷新改變。利用事件發布,利用配置中心完成動態刷新路由。本次改造我們使用自定義存儲方式達到手動觸發動態路由
InMemoryRouteDefinitionRepository 默認使用。這個類就是把握們當前的所有的路由routes 存儲在內存當中。當服務重啟或者刷新,內存就不復存在。ps: 因為本項目是用nacos 注冊中心也是配置中心。可以存儲在nacos 配置中心里面。
RouteDefinitionRepository 接口是InMemoryRouteDefinitionRepository 是它的接口類 繼承了 RouteDefinitionLocator 、RouteDefinitionWriter倆個接口。
RouteDefinitionLocator 接口
RouteDefinitionWriter 用來實現路由的添加與刪除。
思考:
基於這個原則我們在動態添加或者刪除路由的時候,就可以根據這個接口實現去滿足我們動態的控制路由規則。
RouteDefinitionRepository 這個接口成為了關鍵點我們來重新動態路由其實也是基於這個接口來實現
實體類
package com.xian.cloud.model;
import lombok.Data;
/**
* @Author: xlr
* @Date: Created in 5:10 PM 2019/9/29
*/
@Data
public class GatewayRoutesEntity {
private Long id;
private String serviceId;
private String uri;
private String predicates;
private String filters;
}
GatewayRoutesService 接口
package com.xian.cloud.service;
import com.xian.cloud.model.GatewayRoutesEntity;
import org.springframework.cloud.gateway.filter.FilterDefinition;
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
import java.util.List;
/**
* <Description>
*
* @author xianliru@100tal.com
* @version 1.0
* @createDate 2019/11/08 16:08
*/
public interface GatewayRoutesService {
List<GatewayRoutesEntity> findAll() throws Exception;
String loadRouteDefinition() throws Exception;
GatewayRoutesEntity save(GatewayRoutesEntity gatewayDefine) throws Exception;
void deleteById(String id) throws Exception;
boolean existsById(String id)throws Exception;
List<PredicateDefinition> getPredicateDefinition(String predicates) ;
List<FilterDefinition> getFilterDefinition(String filters) ;
}
GatewayRoutesService 實現類
package com.xian.cloud.service.impl;
import com.alibaba.fastjson.JSON;
import com.xian.cloud.model.GatewayRoutesEntity;
import com.xian.cloud.service.GatewayRoutesService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.filter.FilterDefinition;
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;
import java.net.URI;
import java.util.List;
/**
* <Description>
*
* @author xianliru@100tal.com
* @version 1.0
* @createDate 2019/11/08 16:09
*/
@Service
@Slf4j
public class GatewayRoutesServiceImpl implements GatewayRoutesService {
public static final String GATEWAY_DEFINE_LIST_KEY = "gateway_routes_list_key";
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private RouteDefinitionWriter routeDefinitionWriter;
private ApplicationEventPublisher publisher;
@Override
public List <GatewayRoutesEntity> findAll() throws Exception {
Long size = redisTemplate.opsForList().size( GATEWAY_DEFINE_LIST_KEY );
List<GatewayRoutesEntity> list = redisTemplate.opsForList().range( GATEWAY_DEFINE_LIST_KEY, 0, size );
return list;
}
@Override
public String loadRouteDefinition() {
try {
List <GatewayRoutesEntity> gatewayDefineServiceAll = findAll();
if (gatewayDefineServiceAll == null) {
return "none route defined";
}
for (GatewayRoutesEntity gatewayDefine : gatewayDefineServiceAll) {
RouteDefinition definition = new RouteDefinition();
definition.setId( gatewayDefine.getServiceId() );
definition.setUri( new URI( gatewayDefine.getUri() ) );
List <PredicateDefinition> predicateDefinitions = getPredicateDefinition(gatewayDefine.getPredicates());
if (predicateDefinitions != null) {
definition.setPredicates( predicateDefinitions );
}
List <FilterDefinition> filterDefinitions = getFilterDefinition(gatewayDefine.getFilters());
if (filterDefinitions != null) {
definition.setFilters( filterDefinitions );
}
routeDefinitionWriter.save( Mono.just( definition ) ).subscribe();
publisher.publishEvent( new RefreshRoutesEvent( this ) );
}
return "success";
} catch (Exception e) {
e.printStackTrace();
return "failure";
}
}
/**
* 獲取所有的 自定義路由規則
* @param gatewayDefine
* @return
* @throws Exception
*/
@Override
public GatewayRoutesEntity save(GatewayRoutesEntity gatewayDefine) throws Exception {
log.info( "save RouteDefinition : {}",gatewayDefine );
redisTemplate.opsForList().rightPush( GATEWAY_DEFINE_LIST_KEY, gatewayDefine );
return gatewayDefine;
}
@Override
public void deleteById(String id) throws Exception {
List <GatewayRoutesEntity> all = findAll();
for (GatewayRoutesEntity gatewayDefine : all) {
if(gatewayDefine.getServiceId().equals( id )){
redisTemplate.opsForList().remove( GATEWAY_DEFINE_LIST_KEY,0, gatewayDefine);
}
}
}
@Override
public boolean existsById(String id) throws Exception {
List <GatewayRoutesEntity> all = findAll();
for (GatewayRoutesEntity gatewayDefine : all) {
if(gatewayDefine.getServiceId().equals( id )){
return true;
}
}
return false;
}
@Override
public List<PredicateDefinition> getPredicateDefinition(String predicates) {
if ( StringUtils.isNotBlank( predicates )) {
List<PredicateDefinition> predicateDefinitionList = JSON.parseArray(predicates, PredicateDefinition.class);
return predicateDefinitionList;
} else {
return null;
}
}
@Override
public List<FilterDefinition> getFilterDefinition(String filters) {
if (StringUtils.isNotBlank( filters )) {
List<FilterDefinition> filterDefinitionList = JSON.parseArray(filters, FilterDefinition.class);
return filterDefinitionList;
} else {
return null;
}
}
}
然后我們在進行 RouteDefinitionLocator 、RouteDefinitionWriter 倆個接口的聲明 在配置文件中
package com.xian.cloud.config;
import com.xian.cloud.repository.GatewayRoutesRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.route.RouteDefinitionLocator;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
/**
* <Description> 動態更新路由
*
* @author xianliru@100tal.com
* @version 1.0
* @createDate 2019/11/08 17:12
*/
@Configuration
@Slf4j
public class GatewayRoutesDefinitionConfig {
@Bean
RouteDefinitionLocator routeDefinitionLocator(){
return new GatewayRoutesRepository();
}
@Bean
@Primary
RouteDefinitionWriter routeDefinitionWriter(){
return new GatewayRoutesRepository();
}
}
RefreshRoutesEvent 事件發布刷新routes事件,通知網關。 這個事件是gateway 的事件。
package com.xian.cloud.event;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Component;
/**
* <Description>
*
* @author xianliru@100tal.com
* @version 1.0
* @createDate 2019/11/08 17:20
*/
@Component
@Slf4j
public class RefreshRouteService implements ApplicationEventPublisherAware {
private ApplicationEventPublisher publisher;
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
/**
* 刷新路由表
*/
public void refreshRoutes() {
publisher.publishEvent(new RefreshRoutesEvent(this));
}
}
然后我們還差一個手動觸發的接口,創建GatewayRoutesController
package com.xian.cloud.controller;
import com.xian.cloud.event.RefreshRouteService;
import com.xian.cloud.model.RestResult;
import com.xian.cloud.model.RestResultBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* <Description>
*
* @author xianliru@100tal.com
* @version 1.0
* @createDate 2019/11/08 17:18
*/
@RestController
@RequestMapping("/gateway")
public class GatewayRoutesController {
@Autowired
private RefreshRouteService refreshRouteService;
@GetMapping("/refreshRoutes")
public RestResult refreshRoutes(){
refreshRouteService.refreshRoutes();
return RestResultBuilder.builder().success().build();
}
}
到此為止,就完成了所有的代碼。
啟動服務
其實還有一種很簡單直接的寫法。參考重新定義 Spring Cloud 實戰中的寫法。
創建事件增加類DynamicRouteServiceImpl
package com.xian.cloud.event;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;
/**
* <Description>
*
* @author xianliru@100tal.com
* @version 1.0
* @createDate 2019/11/09 10:40
*/
@Slf4j
@Service
public class DynamicRouteServiceImpl implements ApplicationEventPublisherAware {
@Qualifier("routeDefinitionRepositor")
@Autowired
private RouteDefinitionWriter routeDefinitionWriter;
private ApplicationEventPublisher publisher;
/**
* 添加路由實體類
* @param definition
* @return
*/
public boolean add(RouteDefinition definition){
routeDefinitionWriter.save((Mono<RouteDefinition>) Mono.just(definition).subscribe());
this.publisher.publishEvent(new RefreshRoutesEvent(this));
return true;
}
/**
*
* @param definition 路由實體類
* @return
*/
public boolean update(RouteDefinition definition){
try {
routeDefinitionWriter.delete(Mono.just(definition.getId()));
}catch (Exception e){
log.error("update 失敗。沒有找到對應的路由ID :{}",definition.getId());
}
routeDefinitionWriter.save((Mono<RouteDefinition>) (Mono.just(definition)).subscribe());
this.publisher.publishEvent(new RefreshRoutesEvent(this));
return true;
}
/**
* serviceId
* @param id
* @return
*/
public boolean del(String id){
routeDefinitionWriter.delete(Mono.just(id));
this.publisher.publishEvent(new RefreshRoutesEvent(this));
return true;
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.publisher = applicationEventPublisher;
}
}
修改controller 添加三個方法
package com.xian.cloud.controller;
import com.xian.cloud.event.DynamicRouteServiceImpl;
import com.xian.cloud.event.RefreshRouteService;
import com.xian.cloud.model.RestResult;
import com.xian.cloud.model.RestResultBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.web.bind.annotation.*;
/**
* <Description>
*
* @author xianliru@100tal.com
* @version 1.0
* @createDate 2019/11/08 17:18
*/
@RestController
@RequestMapping("/gateway")
public class GatewayRoutesController {
@Autowired
private RefreshRouteService refreshRouteService;
@Autowired
private DynamicRouteServiceImpl dynamicRouteService;
@GetMapping("/refreshRoutes")
public RestResult refreshRoutes(){
refreshRouteService.refreshRoutes();
return RestResultBuilder.builder().success().build();
}
/**
*
* @param definition
* @return
*/
@RequestMapping(value = "routes/add",method = RequestMethod.POST)
public RestResult add(@RequestBody RouteDefinition definition){
boolean flag = dynamicRouteService.add(definition);
if(flag){
return RestResultBuilder.builder().success().build();
}
return RestResultBuilder.builder().failure().build();
}
/**
*
* @param definition
* @return
*/
@RequestMapping(value = "routes/update",method = RequestMethod.POST)
public RestResult update(@RequestBody RouteDefinition definition){
boolean flag = dynamicRouteService.add(definition);
if(flag){
return RestResultBuilder.builder().success().build();
}
return RestResultBuilder.builder().failure().build();
}
/**
*
* @param serviceId
* @return
*/
@RequestMapping(value = "routes/del",method = RequestMethod.POST)
public RestResult update(@RequestParam("serviceId") String serviceId){
boolean flag = dynamicRouteService.del(serviceId);
if(flag){
return RestResultBuilder.builder().success().build();
}
return RestResultBuilder.builder().failure().build();
}
}
增刪改。三個接口對外暴露。
以上就是gateway 的動態刷新路由。
思考
動態刷新能滿足我們在服務運維上的管理方便。但是因為這個整體使用的nacos 配置中心做的。這樣做的成本考慮。動態刷新是不是真的有必要這么做。完全可以在配置中心配置然后刷新。就能觸發路由的整體刷新。如果需要自己搭建運維管理中心。有自己的管理體系可以實現動態路由。
摘自參考 spring cloud 官方文檔
服務器nacos 地址 http://47.99.209.72:8848/nacos
往期地址 spring cloud 文章地址
Spring Cloud Alibaba (nacos 注冊中心搭建)
Spring Cloud Alibaba 使用nacos 注冊中心
Spring Cloud Alibaba nacos 配置中心使用
Spring Cloud alibaba網關 sentinel zuul 四 限流熔斷
Spring Cloud gateway 網關服務二 斷言、過濾器
Spring Cloud gateway 三 自定義過濾器GatewayFilter
如何喜歡可以關注分享本公眾號。
版權聲明:本文為博主原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接和本聲明。轉載請附帶公眾號二維碼