OCP open source project: Spring Cloud Gateway module to achieve dynamic routing

OCP open source project: Spring Cloud Gateway module to achieve dynamic routing

1 Introduction

This chapter describes the OCP open source project: dynamic routing of Spring Cloud Gateway module.

2. Spring Cloud Gateway

Spring Cloud Gateway is designed to provide a simple and effective way to route to the API, and provide them with crosscutting concerns, such as: security, monitoring / indicators and elasticity.

2.1 Spring Cloud Gateway feature

  • 2.0 built on Spring Framework 5, Project Reactor and Spring Boot
  • Any routes can be matched request attribute.
  • And a filter predicate specific route.
  • Hystrix integrated circuit breaker.
  • Spring Cloud DiscoveryClient集成
  • Easy to write predicates and filters
  • Request rate limit
  • Path Rewrite

2.2 project combat

Next, we begin our journey Spring Cloud Gateway limiting of it!

2.2.1 Spring Cloud Gateway 限流

new-api-gateway

2.2.2 OCP sub-project new-api-gateway

pom.xml

<!--基于 reactive stream 的redis -->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency><!--spring cloud gateway 相关依赖-->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency><dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-actuator</artifactId>
</dependency><dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>复制代码

2.2.3 Configuration HostName rules limiting

Currently only the user-center User Center limiting


spring:
  cloud:
     gateway:
       discovery:
         locator:
           lowerCaseServiceId: true
           enabled: true
       routes:
        # =====================================
        - id: api-eureka
          uri: lb://eureka-server
          order: 8000
          predicates:
          - Path=/api-eureka/**
          filters:
          - StripPrefix=1   
          - name: Hystrix
            args:
              name : default
              fallbackUri: 'forward:/defaultfallback'
        - id: api-user
          uri: lb://user-center
          order: 8001
          predicates:
          - Path=/api-user/**   
          filters:
          - GwSwaggerHeaderFilter
          - StripPrefix=1 
          - name: Hystrix
            args:
              name : default
              fallbackUri: 'forward:/defaultfallback'
          - name: RequestRateLimiter                #对应 RequestRateLimiterGatewayFilterFactory
            args:
              redis-rate-limiter.replenishRate: 1  # 令牌桶的容积 放入令牌桶的容积每次一个
              redis-rate-limiter.burstCapacity: 3  # 流速 每秒      
              key-resolver: "#{@ipAddressKeyResolver}" # SPEL表达式去的对应的bean


复制代码

2.2.4 New configuration class RequestRateLimiterConfig

New classes disposed in com.open.capacity.client.config the path

package com.open.capacity.client.config;
​
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import reactor.core.publisher.Mono;
​
/**
 * 定义spring cloud gateway中的  key-resolver: "#{@ipAddressKeyResolver}" #SPEL表达式去的对应的bean
 *  ipAddressKeyResolver 要取bean的名字
 *
 */
@Configuration
public class RequestRateLimiterConfig {
​
    /**
     * 根据 HostName 进行限流
     * @return
     */
    @Bean("ipAddressKeyResolver")
    public KeyResolver ipAddressKeyResolver() {
        return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
    }
​
    /**
     * 根据api接口来限流
     * @return
     */
    @Bean(name="apiKeyResolver")
    public KeyResolver apiKeyResolver() {
        return exchange ->  Mono.just(exchange.getRequest().getPath().value());
    }
​
    /**
     * 用户限流
     * 使用这种方式限流,请求路径中必须携带userId参数。
     *  提供第三种方式
     * @return
     */
    @Bean("userKeyResolver")
    KeyResolver userKeyResolver() {
        return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("userId"));
    }
}
​
复制代码

2.2.5 stress test

Then things get that done after the configuration we started stress tests, before the pressure test, due to the new-api-gateway global presence interceptor AccessFilter, and if you do not log on to test. First "/ api-auth / **" in the determination commented. Next, we tested with open postman

@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
	// TODO Auto-generated method stub
	
	String accessToken = extractToken(exchange.getRequest());
	
	
	if(pathMatcher.match("/**/v2/api-docs/**",exchange.getRequest().getPath().value())){
		return chain.filter(exchange);
	}
	
	if(!pathMatcher.match("/api-auth/**",exchange.getRequest().getPath().value())){
//          if (accessToken == null) {
//              exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
//              return exchange.getResponse().setComplete();
//          }else{
//              try {
//                  Map<String, Object> params =  (Map<String, Object>) redisTemplate.opsForValue().get("token:" + accessToken) ;
//                  if(params.isEmpty()){
//                      exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
//                      return exchange.getResponse().setComplete();
//                  }
//              } catch (Exception e) {
//                  exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
//                  return exchange.getResponse().setComplete();
//              }
//          }
	}
	return chain.filter(exchange);
}
​
​
复制代码
  • 1. Open the postman select Collections click the New button

New
Enter a name and description

  • 2. Click New to create a new request of Collection

    Add request
    Add request2

    1. Enter the address: 127.0.0.1: 9200 / api-user / users-anon / login username = admin?

Fill in the URL

  • 4. Tests of the tab is switched to the lower right is selected Status code is 200 option, where other methods may be selected according to their definitions api.

Select Status code is 200

  • 5. It should be noted: the data just finished, you must click save to save, otherwise it could not be effective;
    Storage
    Request configuration

Request configuration

    1. Click Run, you can view the results, there are five successful, five failed; limiting the successful return of 429

operation result
operation result

In the previous chapter, we have done a simple spring cloud gateway and limiting introduction, the next most important, and most crucial spring cloud gateway dynamic routing , first of all, API gateway is responsible for the service request routing, protocol conversion and combination, customers All requests are terminated after the first gateway API, then it will match a routing request to the appropriate micro-services, the system is the inlet flow in a production environment to ensure a highly reliable and in order to avoid restarting, if there are new when the service to on-line, you can configure the function line through dynamic routing.

3. Spring Cloud Gateway to achieve dynamic routing

First, springcloudgateway configure routing in two ways:

  • yml profile
  • Object Configuration (configuration mode codes) for

3.1 yml Configuration

yml Configuration

3.2 Code Mode Configuration

Code Mode Configuration

3.3 Routing initialization

When srping cloud gateway gateway startup, default routing information loaded in the memory, the routing information is encapsulated into RouteDefinition object,

org.springframework.cloud.gateway.route.RouteDefinition
复制代码

RouteDefinition

Some of such properties are:

@NotEmpty
private String id = UUID.randomUUID().toString();
​
//路由断言定义
@NotEmpty
@Valid
private List<PredicateDefinition> predicates = new ArrayList<>();
​
//路由过滤定义
@Valid
private List<FilterDefinition> filters = new ArrayList<>();
​
//对应的URI
@NotNull
private URI uri;
​
private int order = 0;
复制代码

A RouteDefinition has a unique ID, if not specified, the default is the UUID, more RouteDefinition formed a gateway routing system, all routing information was assembled a loaded at system startup, and saved them to memory.

3.4 Gateway automatically configures

org.springframework.cloud.gateway.config.GatewayAutoConfiguration
复制代码

4

//RouteLocatorBuilder 采用代码的方式注入路由
@Bean
public RouteLocatorBuilder routeLocatorBuilder(ConfigurableApplicationContext context) {
  return new RouteLocatorBuilder(context);
}
​
//PropertiesRouteDefinitionLocator 配置文件路由定义
@Bean
@ConditionalOnMissingBean
public PropertiesRouteDefinitionLocator propertiesRouteDefinitionLocator(GatewayProperties properties) {
  return new PropertiesRouteDefinitionLocator(properties);
}
​
//InMemoryRouteDefinitionRepository 内存路由定义
@Bean
@ConditionalOnMissingBean(RouteDefinitionRepository.class)
public InMemoryRouteDefinitionRepository inMemoryRouteDefinitionRepository() {
  return new InMemoryRouteDefinitionRepository();
}
​
//CompositeRouteDefinitionLocator 组合多种模式,为RouteDefinition统一入口
@Bean
@Primary
public RouteDefinitionLocator routeDefinitionLocator(List<RouteDefinitionLocator> routeDefinitionLocators) {
  return new CompositeRouteDefinitionLocator(Flux.fromIterable(routeDefinitionLocators));
}
​
​
@Bean
public RouteLocator routeDefinitionRouteLocator(GatewayProperties properties,
                                                List<GatewayFilterFactory> GatewayFilters,
                                                List<RoutePredicateFactory> predicates,
                                                RouteDefinitionLocator routeDefinitionLocator) {
  return new RouteDefinitionRouteLocator(routeDefinitionLocator, predicates, GatewayFilters, properties);
}
​
//CachingRouteLocator 为RouteDefinition提供缓存功能
@Bean
@Primary
//TODO: property to disable composite?
public RouteLocator cachedCompositeRouteLocator(List<RouteLocator> routeLocators) {
  return new CachingRouteLocator(new CompositeRouteLocator(Flux.fromIterable(routeLocators)));
}
​
复制代码

Yml assembly file, it returns PropertiesRouteDefinitionLocator, the class inherits RouteDefinitionLocator, RouteDefinitionLocator is routing loader, there is only one way is to get routing information.

org.springframework.cloud.gateway.route.RouteDefinitionLocator
复制代码

5

RouteDefinitionLocator class diagram as follows:

RouteDefinitionLocator 类图

Subclasses Function Description:

  • CachingRouteDefinitionLocator: RouteDefinitionLocator packaging, cache target RouteDefinitionLocator provide caching functionality for routeDefinitions
  • CompositeRouteDefinitionLocator -RouteDefinitionLocator packaging, various combinations to achieve RouteDefinitionLocator provide a unified entrance routeDefinitions
  • PropertiesRouteDefinitionLocator- RouteDefinition read from the configuration file (GatewayProperties e.g., YML / Properties, etc.)
  • RouteDefinitionRepository- RouteDefinition read from memory (e.g., memory / Redis / MySQL, etc.)
  • DiscoveryClientRouteDefinitionLocator- from the registry (e.g., Eureka / Consul / Zookeeper / Etcd etc.

Recommended Reference article: www.jianshu.com/p/b02c7495e...

Write dynamic routing 3.5

New Data script in SQL 02.oauth-center.sql directory


​
#
# Structure for table "sys_gateway_routes"
#
​
DROP TABLE IF EXISTS sys_gateway_routes;
CREATE TABLE sys_gateway_routes
(
  `id`            char(32) NOT NULL COMMENT 'id',
  `uri`           VARCHAR(100) NOT NULL COMMENT 'uri路径',
  `predicates`    VARCHAR(1000) COMMENT '判定器',
  `filters`       VARCHAR(1000) COMMENT '过滤器',
  `order`         INT COMMENT '排序',
  `description`   VARCHAR(500) COMMENT '描述',
  `delFlag`       int(11) DEFAULT '0' COMMENT '删除标志 0 不删除 1 删除',
  `createTime`    datetime NOT NULL,
  `updateTime`    datetime NOT NULL,
   PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=38 DEFAULT CHARSET=utf8mb4 COMMENT '服务网关路由表';
​

复制代码

/**
 * 路由实体类
 */
public class GatewayRoutes {
    private String id;
    private String uri;
    private String predicates;
    private String filters;
    private Integer order;
    private String description;
    private Integer delFlag;
    private Date createTime;
    private Date updateTime;   
    //省略getter,setter
}
复制代码

/**
 *  路由的Service类
 */
@Service
public class DynamicRouteServiceImpl implements ApplicationEventPublisherAware, IDynamicRouteService {
  
  /**
     * 新增路由
     *
     * @param gatewayRouteDefinition
     * @return
     */
  @Override
  public String add(GatewayRouteDefinition gatewayRouteDefinition) {
    GatewayRoutes gatewayRoutes = transformToGatewayRoutes(gatewayRouteDefinition);
    gatewayRoutes.setDelFlag(0);
    gatewayRoutes.setCreateTime(new Date());
    gatewayRoutes.setUpdateTime(new Date());
    gatewayRoutesMapper.insertSelective(gatewayRoutes);
​
    gatewayRouteDefinition.setId(gatewayRoutes.getId());
    redisTemplate.opsForValue().set(GATEWAY_ROUTES_PREFIX + gatewayRouteDefinition.getId(), JSONObject.toJSONString(gatewayRouteDefinition));
    return gatewayRoutes.getId();
  }
​
  /**
     * 修改路由
     *
     * @param gatewayRouteDefinition
     * @return
     */
  @Override
  public String update(GatewayRouteDefinition gatewayRouteDefinition) {
    GatewayRoutes gatewayRoutes = transformToGatewayRoutes(gatewayRouteDefinition);
    gatewayRoutes.setCreateTime(new Date());
    gatewayRoutes.setUpdateTime(new Date());
    gatewayRoutesMapper.updateByPrimaryKeySelective(gatewayRoutes);
​
    redisTemplate.delete(GATEWAY_ROUTES_PREFIX + gatewayRouteDefinition.getId());
    redisTemplate.opsForValue().set(GATEWAY_ROUTES_PREFIX + gatewayRouteDefinition.getId(), JSONObject.toJSONString(gatewayRouteDefinition));
    return gatewayRouteDefinition.getId();
  }
​
​
  /**
     * 删除路由
     * @param id
     * @return
     */
  @Override
  public String delete(String id) {
    gatewayRoutesMapper.deleteByPrimaryKey(id);
    redisTemplate.delete(GATEWAY_ROUTES_PREFIX + id);
    return "success";
  }
  
  
}
​
复制代码

/**
 *  核心类
 *      getRouteDefinitions() 通过该方法获取到全部路由,每次有request过来请求的时候,都会往该方法过。
 *
 */
@Component
public class RedisRouteDefinitionRepository implements RouteDefinitionRepository {
​
    public static final String GATEWAY_ROUTES_PREFIX = "geteway_routes_";
​
    @Autowired
    private StringRedisTemplate redisTemplate;
​
    private Set<RouteDefinition> routeDefinitions = new HashSet<>();
​
    /**
     * 获取全部路由
     * @return
     */
    @Override
    public Flux<RouteDefinition> getRouteDefinitions() {
        /**
         * 从redis 中 获取 全部路由,因为保存在redis ,mysql 中 频繁读取mysql 有可能会带来不必要的问题
         */
        Set<String> gatewayKeys = redisTemplate.keys(GATEWAY_ROUTES_PREFIX + "*");
        if (!CollectionUtils.isEmpty(gatewayKeys)) {
            List<String> gatewayRoutes = Optional.ofNullable(redisTemplate.opsForValue().multiGet(gatewayKeys)).orElse(Lists.newArrayList());
            gatewayRoutes
                    .forEach(routeDefinition -> routeDefinitions.add(JSON.parseObject(routeDefinition, RouteDefinition.class)));
        }
        return Flux.fromIterable(routeDefinitions);
    }
​
    /**
     * 添加路由方法
     * @param route
     * @return
     */
    @Override
    public Mono<Void> save(Mono<RouteDefinition> route) {
        return route.flatMap(routeDefinition -> {
            routeDefinitions.add( routeDefinition );
            return Mono.empty();
        });
    }
​
    /**
     * 删除路由
     * @param routeId
     * @return
     */
    @Override
    public Mono<Void> delete(Mono<String> routeId) {
        return routeId.flatMap(id -> {
            List<RouteDefinition> collect = routeDefinitions.stream().filter(
                    routeDefinition -> StringUtils.equals(routeDefinition.getId(), id)
            ).collect(Collectors.toList());
            routeDefinitions.removeAll(collect);
            return Mono.empty();
        });
    }
}
​
​
复制代码

​
​
/**
 *  编写Rest接口
 */
@RestController
@RequestMapping("/route")
public class RouteController {
​
    @Autowired
    private IDynamicRouteService dynamicRouteService;
​
    //增加路由
    @PostMapping("/add")
    public Result add(@RequestBody GatewayRouteDefinition gatewayRouteDefinition) {
        return Result.succeed(dynamicRouteService.add(gatewayRouteDefinition));
    }
​
    //更新路由
    @PostMapping("/update")
    public Result update(@RequestBody GatewayRouteDefinition gatewayRouteDefinition) {
        return Result.succeed(dynamicRouteService.update(gatewayRouteDefinition));
    }
​
    //删除路由
    @DeleteMapping("/{id}")
    public Result delete(@PathVariable String id) {
        return Result.succeed(dynamicRouteService.delete(id));
    }
​
}
​
复制代码

Written test 3.6 Dynamic Routing

GET localhost:9200/actuator/gateway/routes
复制代码

1. Using this interface, all the routes, the route under test to see Gateway / JD / * * did not find

7

POST 127.0.0.1:9200/route/add
复制代码

Json constructed from parameters corresponding to the format type com.open.capacity.client.dto.GatewayRouteDefinition


{
  "id": "",
  "uri": "lb://user-center",
  "order": 1111,
  "filters": [
    {
      "name": "StripPrefix",
      "args": {
        "_genkey_0": "1"
      }
    }
  ],
  "predicates": [
    {
      "name": "Path",
      "args": {
        "_genkey_0": "/jd/**"
      }
    }
  ],
  "description": "测试路由新增"
}
​
复制代码

Added successfully, return the corresponding id, see mysql, redis have been successfully saved

8

9

Here Insert Picture Description

In just get access to all of the routing interface, find our ** / jd / **** already registered on our gateway

10

GET localhost:9200/jd/users-anon/login?username=admin
复制代码

This time, we do not have to restart the project, you can still access route our custom, to this, we have completed the addition operation, subsequent delete, update, it is to simply call the API under complete!

11

Plan focuses

More from open source projects OCP : gitee.com/owenwangwen...

Project Demo http://59.110.164.254:8066/login.html username / password: admin / admin

Project Monitoring http://106.13.3.200:3000 username / password: admin / 1q2w3e4r

Item code address gitee.com/owenwangwen...

Group number: 483 725 710 (NOTE: Coder programming) are welcome to join ~

The end of the sentence

Welcome attention to personal micro-channel public number: Coder program for the latest original technical articles and free learning materials, a lot more boutique mind maps, interview data, PMP preparation materials waiting for you to lead, allowing you to learn technical knowledge anytime, anywhere!

Welcome attention and star ~

Micro-channel public number

Guess you like

Origin juejin.im/post/5d748f4fe51d45621479ad53