SpringCloud使用Gateway组件作为路由器

SpringCloud使用Gateway组件作为路由器

一、说明:

参考:https://windmt.com/2018/05/07/spring-cloud-13-spring-cloud-gateway-router/

官网:https://cloud.spring.io/spring-cloud-gateway/reference/html/#shortcut-configuration

参考:https://www.cnblogs.com/crazymakercircle/p/11704077.html

https://www.cnblogs.com/crazymakercircle/p/11704077.html

1、getway系统架构

image

2、getway分成以下几部分
  1. Route(路由):这是网关的基本构建块。它由一个 ID,一个目标 URI,一组断言和一组过滤器定义。如果断言为真,则路由匹配

  2. Predicate(断言):这是一个 Java 8 的 Predicate。输入类型是一个 ServerWebExchange。我们可以使用它来匹配来自 HTTP 请求的任何内容,例如 headers 或参数

  3. Filter(过滤器):这是 GatewayFilter 的实例,我们可以使用它修改请求和响应。

    在这里插å
¥å›¾ç‰‡æè¿°

3、getaway的请求流程

在这里插å
¥å›¾ç‰‡æè¿°

二、gateway接入注册中心
1、添加依赖
<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>
2、添加配置
spring:
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true   		#开启gateway的客户端发现
      routes:					#路由规则
        - id: course      		#路由规则
          uri: /v1/api/course  	#映射地址
          order: 1    			#排序
          predicates:			#配置预处理器
            - Path=/**
          filters:				#配置过滤器
            - SetPath=
            - RewritePath=/CONSUMER/(?<segment>.*), /$\{segment}
3、启动类设置为Eureka的客户端
@EnableDiscoveryClient
@SpringBootApplication
public class GateWayApplication {
	public static void main(String[] args) {
		SpringApplication.run(GateWayApplication.class, args);
	}
}
二、gateway在项目中的使用
1、gateway的Predicate规则

(1)内置规则说明和实例

# datetime :请求时间限制在之前,之间,之后
- After=2018-01-20T06:06:06+08:00[Asia/Shanghai]
- Before=2018-01-20T06:06:06+08:00[Asia/Shanghai]
- Between=2018-01-20T06:06:06+08:00[Asia/Shanghai], 2019-01-20T06:06:06+08:00[Asia/Shanghai]
# cookie :请求指定的Cookie
- Cookie=ityouknow, kee.e
# header :请求指定的Header的正则配置和指定名称
- Header=X-Request-Id, \d+
# host :请求指定的Host
- Host=**.ityouknow.com
# method :请求指定的方法
- Method=GET,POST,OPTIONS
# path :请求指定的路径
- Path=/foo/{segment}
# QueryParam :请求指定的参数值
- Query=channel_web
# RemoteAddr :请求指定的远程地址
- RemoteAddr=192.168.1.1/24
spring:
  cloud:
    gateway:
      routes:
       - id: host_foo_path_headers_to_httpbin
        uri: http://ityouknow.com
        predicates:
        - Host=**.foo.org
        - Path=/headers
        - Method=GET
        - Header=X-Request-Id, \d+
        - Query=foo, ba.
        - Query=baz
        - Cookie=chocolate, ch.p
        - After=2018-01-20T06:06:06+08:00[Asia/Shanghai]
2、gateway的Filter规则
spring:
  cloud:
    gateway:
      routes:
      - id: nameRoot
        uri: http://nameservice
        predicates:
        - Path=/name/** # 当请求路径匹配到/name/**会将包含name和后边的字符串接去掉转发,
        filters:
        - StripPrefix=2 # 请求路径修改过滤器 StripPrefix=2就代表截取路径的个数,
        - PrefixPath=/mypath # 跟StripPrefix相反,会添加后转发
3、自定义拦截器
spring:
  cloud:
    gateway:
      routes:
      - id: nameRoot
        uri: http://nameservice
        predicates:
        - Path=/name/** # 当请求路径匹配到/name/**会将包含name和后边的字符串接去掉转发,
        filters:
        - StripPrefix=2 # 请求路径修改过滤器 StripPrefix=2就代表截取路径的个数,
        - PrefixPath=/mypath # 跟StripPrefix相反,会添加后转发
        - Auth     # 自定义的权限过滤器
        - IPForbid=0:0:0:0:0:0:0:1 #自定义的IP拦截器
        - ResponseBody  #自定义的响应结果拦截器
(1)自定义全局限流过滤器
<dependency>
    <groupId>com.github.vladimir-bukhtoyarov</groupId>
    <artifactId>bucket4j-core</artifactId>
    <version>4.4.1</version>
</dependency>

通过token桶的方式设置时间段内限流

//=======全局限流过滤器=========//
//全局过滤器,实现GlobalFilter接口,和Ordered接口即可。
@Component
public class FluidControlGlobalGatewayFilter implements GlobalFilter, Ordered{
    int capacity = 5;//桶的最大容量,即能装载 Token 的最大数量

    int refillTokens = 1; //每次 Token 补充量

    Duration duration = Duration.ofSeconds(1); //补充 Token 的时间间隔

    private static final Map<String, Bucket> BUCKET_CACHE = new ConcurrentHashMap<>();

    private Bucket createNewBucket(){
        Refill refill = Refill.greedy(refillTokens, duration);
        Bandwidth limit = Bandwidth.classic(capacity, refill);
        return Bucket4j.builder().addLimit(limit).build();
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain){
        String ip = exchange.getRequest().getRemoteAddress().getAddress().getHostAddress();
        Bucket bucket = BUCKET_CACHE.computeIfAbsent(ip, k -> createNewBucket());

        System.out.println("IP: " + ip + ",has Tokens: " + bucket.getAvailableTokens());
        if (bucket.tryConsume(1)){
            return chain.filter(exchange);
        }else{
            exchange.getResponse().setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
            return exchange.getResponse().setComplete();
        }
    }

    @Override
    public int getOrder(){
        return -100;
    }
}
//==========================
//全局过滤器,使用配置类形式,直接构造bean,使用注解完成Ordered接口功能,统计接口调用时间
@Configuration
public class GlobalGatewayFilterConfig{
    //直接将简单的统计耗时加入过滤器
    @Bean
    @Order(-100)
    public GlobalFilter elapsedGlobalFilter(){
        return new FluidControlGlobalGatewayFilter();
    }
}
(2)自定义统计耗时
/全局过滤器,使用配置类形式,直接构造bean,使用注解完成Ordered接口功能,统计接口调用时间
@Configuration
public class GlobalGatewayFilterConfig{
    //直接将简单的统计耗时加入过滤器
    @Bean
    @Order(-100)
    public GlobalFilter elapsedGlobalFilter(){
        return (exchange, chain) -> {
            //调用请求之前统计时间
            Long startTime = System.currentTimeMillis();
            return chain.filter(exchange).then().then(Mono.fromRunnable(() -> {
                //调用请求之后统计时间
                Long endTime = System.currentTimeMillis();
                System.out.println(
                    exchange.getRequest().getURI().getRawPath() 
                    + ", cost time : " + (endTime - startTime) + "ms");
            }));
        };
    }
    
}
(3)自定义简单权限过滤器
//全局权限过滤器
public class AuthGatewayFilter implements GatewayFilter, Ordered{
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain){
        //获取header的参数
        String name = exchange.getRequest().getHeaders().getFirst("name");
        String password = exchange.getRequest().getHeaders().getFirst("password");
        boolean permitted = AuthUtil.isPermitted(name, password);//权限比较
        if (permitted){
            return chain.filter(exchange);
        }else{
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }
    }
    @Override
    public int getOrder(){
        return 10;
    }
}
//简单权限
public final class AuthUtil{
    private static Map<String, String> map = new HashMap<>();

    private AuthUtil(){}

    //程序启动的时候加载权限的信息,比如从文件、数据库中加载
    public static void init(){
        map.put("tom", "123456");
    }

    //简单判断
    public static boolean isPermitted(String name, String password){
        return map.containsKey(name) && map.get(name).equals(password);
    }
}
//启动类初始化权限
@SpringBootApplication
public class GatewayApplication{
    public static void main(String[] args){
        SpringApplication springApplication = new SpringApplication(GatewayApplication.class);
        springApplication.addListeners(new ApplicationListenerStarted());//增加监听器
        springApplication.run(args);
    }

    private static class ApplicationListenerStarted
        implements ApplicationListener<ApplicationStartedEvent>{
        @Override
        public void onApplicationEvent(ApplicationStartedEvent applicationStartedEvent)
        {
            //权限初始化数据
            AuthUtil.init();
        }
    }
}
//===========
@Component
public class AuthGatewayFilterFactory extends AbstractGatewayFilterFactory<Object>{
    @Override
    public GatewayFilter apply(Object config){
        return new AuthGatewayFilter();
    }
}
(4)自定义IP地址禁止过滤器
@Component
@Order(99)
public class IPForbidGatewayFilterFactory
    extends AbstractGatewayFilterFactory<IPForbidGatewayFilterFactory.Config>
{

    public IPForbidGatewayFilterFactory()
    {
        super(Config.class);
    }

    @Override
    public List<String> shortcutFieldOrder()
    {
        return Arrays.asList("forbidIp");
    }

    @Override
    public GatewayFilter apply(Config config)
    {
        return (exchange, chain) -> {
            String ip = exchange.getRequest().getRemoteAddress().getAddress().getHostAddress();
            if (config.getForbidIp().equals(ip))
            {
                return chain.filter(exchange);
            }
            exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
            return exchange.getResponse().setComplete();

        };
    }

    static public class Config
    {
        private String forbidIp;

        public String getForbidIp()
        {
            return forbidIp;
        }

        public void setForbidIp(String forbidIp)
        {
            this.forbidIp = forbidIp;
        }
    }
}
(5)自定义返回值过滤器

这个过滤器主要是处理如果服务端的接口返回的数据过长,或者有某些敏感字段导致响应的报文问题,乱码问题,等等


@Component
public class ResponseBodyGatewayFilterFactory extends AbstractGatewayFilterFactory<Object> {

    @Override
    public GatewayFilter apply(Object config) {

        return new ModifyResponseGatewayFilter();
    }


    public class ModifyResponseGatewayFilter implements GatewayFilter, Ordered {

        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            return chain.filter(exchange.mutate().response(decorate(exchange)).build());
        }

        @SuppressWarnings("unchecked")
        ServerHttpResponse decorate(ServerWebExchange exchange) {
            return new ServerHttpResponseDecorator(exchange.getResponse()) {

                @Override
                public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {

                    Class inClass = String.class;
                    Class outClass = String.class;

                    String originalResponseContentType = exchange
                            .getAttribute(ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR);
                    HttpHeaders httpHeaders = new HttpHeaders();

                    httpHeaders.add(HttpHeaders.CONTENT_TYPE,
                            originalResponseContentType);

                    ClientResponse clientResponse = ClientResponse
                            .create(exchange.getResponse().getStatusCode())
                            .headers(headers -> headers.putAll(httpHeaders))
                            .body(Flux.from(body)).build();

                    Mono modifiedBody = clientResponse.bodyToMono(inClass)
                            .flatMap(originalBody -> {
                                //TODO:此次可以对返回的body进行操作
                                System.out.println(originalBody);
                                return Mono.just(originalBody);
                            });

                    BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody,
                            outClass);
                    CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(
                            exchange, exchange.getResponse().getHeaders());
                    return bodyInserter.insert(outputMessage, new BodyInserterContext())
                            .then(Mono.defer(() -> {
                                Flux<DataBuffer> messageBody = outputMessage.getBody();
                                HttpHeaders headers = getDelegate().getHeaders();
                                if (!headers.containsKey(HttpHeaders.TRANSFER_ENCODING)) {
                                    messageBody = messageBody.doOnNext(data -> headers
                                            .setContentLength(data.readableByteCount()));
                                }
                                return getDelegate().writeWith(messageBody);
                            }));
                }

                @Override
                public Mono<Void> writeAndFlushWith(
                        Publisher<? extends Publisher<? extends DataBuffer>> body) {
                    return writeWith(Flux.from(body).flatMapSequential(p -> p));
                }
            };
        }

        @Override
        public int getOrder() {
            return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER - 1;
        }
    }
}
(6)定义局部过滤器
//@Component
@Slf4j
public class UserIdCheckGateWayFilter implements GatewayFilter, Ordered
{
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain)
    {
        String url = exchange.getRequest().getPath().pathWithinApplication().value();
        log.info("请求URL:" + url);
        log.info("method:" + exchange.getRequest().getMethod());
       /*   String secret = exchange.getRequest().getHeaders().getFirst("secret");
        if (StringUtils.isBlank(secret))
        {
            return chain.filter(exchange);
        }*/
         //获取param 请求参数
        String uname = exchange.getRequest().getQueryParams().getFirst("uname");
        //获取header
        String userId = exchange.getRequest().getHeaders().getFirst("user-id");
        log.info("userId:" + userId);

        if (StringUtils.isBlank(userId))
        {
            log.info("*****头部验证不通过,请在头部输入  user-id");
            //终止请求,直接回应
            exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
            return exchange.getResponse().setComplete();
        }
        return chain.filter(exchange);
    }

    //   值越小,优先级越高
//    int HIGHEST_PRECEDENCE = -2147483648;
//    int LOWEST_PRECEDENCE = 2147483647;
    @Override
    public int getOrder()
    {
        return HIGHEST_PRECEDENCE;
    }
}

//===========添加到过滤器
@Component
public class UserIdCheckGatewayFilterFactory extends AbstractGatewayFilterFactory<Object>
{
    @Override
    public GatewayFilter apply(Object config)
    {
        return new UserIdCheckGateWayFilter();
    }
}

在配置文件中配置

	- id: service_provider_demo_route_filter
          uri: lb://service-provider-demo
          predicates:
            - Path=/filter/**
          filters:
            - RewritePath=/filter/(?<segment>.*), /provider/$\{segment}
            - UserIdCheck
4、统一配置跨域请求
spring:
  cloud:
    gateway:
      globalcors:
        cors-configurations:
          '[/**]':
            allowed-origins: "*"
            allowed-headers: "*"
            allow-credentials: true
            allowed-methods:
              - GET
              - POST
三、gateway的高级应用
1、结合redis实现网关限流

(1)添加依赖

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>

(2)添加相关的配置

spring:
  application:
    name: cloud-gateway
  redis:  		# redis的相关配置
    host: localhost
    password:
    port: 6379
  cloud: 
    gateway:
     discovery:
        locator:
         enabled: true
     routes: 		# 路由配置
     - id: rate_limit_route
       uri: /v1/api/course
       filters:  		# 限速路由相关配置
       - name: RequestRateLimiter
         args:
           redis-rate-limiter.replenishRate: 10 # 允许用户每秒处理多少个请求
           redis-rate-limiter.burstCapacity: 20 #令牌桶的容量,允许在一秒钟内完成的最大请求数
           key-resolver: "#{@userKeyResolver}" #使用 SpEL 按名称引用 bean
       predicates:
         - Method=GET 

(3)添加配置类

public class Config {
    
    // 根据请求参数中的user字段来作为路由
    @Bean
    public KeyResolver userKeyResolver() {
        return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("user"));
    }
    // 根据远程主机名称来作为路由
    @Bean  
    public KeyResolver ipKeyResolver() {
        return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
    }
    //直接根据URL进行限流
    @Bean
    public KeyResolver apiKeyResolver() {
      return exchange -> Mono.just(exchange.getRequest().getPath().value());
    }
}
2、结合hystrix断路器
spring:
  cloud:
    gateway:
      routes:
      - id: ingredients
        uri: lb://ingredients
        predicates:
        - Path=//ingredients/**
        filters:
        - name: Hystrix
          args:
            name: fetchIngredients
            fallbackUri: forward:/fallback   # 服务降级回调地址
      - id: ingredients-fallback  # 定义回调服务地址
        uri: http://localhost:9994
        predicates:
        - Path=/fallback 
(1)使用resilience4j方式
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-circuitbreaker-reactor-resilience4j</artifactId>
    <version>1.0.0.RELEASE</version>
</dependency>

引入ReactiveResilience4JCircuitBreakerFactory

@Bean
public ReactiveResilience4JCircuitBreakerFactory defaultCustomizer() {
 
	CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom() //
			.slidingWindowType(SlidingWindowType.TIME_BASED) // 滑动窗口的类型为时间窗口
			.slidingWindowSize(60) // 时间窗口的大小为60秒
			.minimumNumberOfCalls(5) // 在单位时间窗口内最少需要5次调用才能开始进行统计计算
			.failureRateThreshold(50) // 在单位时间窗口内调用失败率达到50%后会启动断路器
			.enableAutomaticTransitionFromOpenToHalfOpen() // 允许断路器自动由打开状态转换为半开状态
			.permittedNumberOfCallsInHalfOpenState(5) // 在半开状态下允许进行正常调用的次数
			.waitDurationInOpenState(Duration.ofSeconds(60)) // 断路器打开状态转换为半开状态需要等待60秒
			.recordExceptions(Throwable.class) // 所有异常都当作失败来处理
			.build();
 
	ReactiveResilience4JCircuitBreakerFactory factory = new ReactiveResilience4JCircuitBreakerFactory();
	factory.configureDefault(id -> new Resilience4JConfigBuilder(id)
			.timeLimiterConfig(TimeLimiterConfig.custom().timeoutDuration(Duration.ofMillis(500)).build())
			.circuitBreakerConfig(circuitBreakerConfig).build());
 
	return factory;
}
(2)使用hystrix实现
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    <version>2.2.1.RELEASE</version>
</dependency>

引入Hystrix工厂

@Bean
public HystrixCircuitBreakerFactory defaultConfig() {
	HystrixCircuitBreakerFactory circuitBreakerFactory = new HystrixCircuitBreakerFactory();
	circuitBreakerFactory.configureDefault(id -> HystrixCommand.Setter
			.withGroupKey(HystrixCommandGroupKey.Factory.asKey(id)).andCommandPropertiesDefaults(
					HystrixCommandProperties.Setter().withExecutionTimeoutInMilliseconds(4000)));
 
	return circuitBreakerFactory;
}
3、配置重试RetryGatewayFilter
spring:
  cloud:
    gateway:
      routes:
      - id: retry_test
        uri: lb://spring-cloud-producer
        predicates:
        - Path=/retry
        filters:
        - name: Retry
          args:
            retries: 3   		#重试次数,默认值是 3 次
            statuses: BAD_GATEWAY #HTTP 的状态返回码,取值请参考:org.springframework.http.HttpStatus
            methods: GET #指定哪些方法的请求需要进行重试逻辑,默认值是 GET 方法
            series: 5 #状态码配置,默认值是 SERVER_ERROR,值是 5,也就是 5XX(5 开头的状态码),共有5 个值
4、结合Ribbon实现负载均衡
  • uri: lb://my-load-balanced-service 注意lb即表示使用了负载均衡
  • NFLoadBalancerRuleClassName 采用Ribbon的负载均衡策略
    • RandomRule 随机策略
    • RetryRule 重试策略
    • RoundRule 轮询策略
    • WeightedResponseTimeRule 响应权重策略等等
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

健康检查

//=====客户端定义一个健康检查接口
@RestController
public class HealthController
{
    @GetMapping("/heath")
    @ResponseBody
    public String heath()
    {
        return "ok";
    }
}
//====gateway工程增加一个config类
@Configuration
public class MainConfig
{
    @Bean
    public RestTemplate restTemplate()
    {
        return new 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;
        }
    }
}

自定义负载均衡策略

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){}
}
//将自定义策略配置到配置文件中即可
5、Gateway结合Swagger文档
@Component
public class MySwaggerResourceProvider implements SwaggerResourcesProvider {

    /**
     * swagger2默认的url后缀
     */
    private static final String SWAGGER2URL = "/v2/api-docs";

    /**
     * 网关路由
     */
    private final RouteLocator routeLocator;

    /**
     * 网关应用名称
     */
    @Value("${spring.application.name}")
    private String self;

    @Autowired
    public MySwaggerResourceProvider(RouteLocator routeLocator) {
        this.routeLocator = routeLocator;
    }

    @Override
    public List<SwaggerResource> get() {
        List<SwaggerResource> resources = new ArrayList<>();
        List<String> routeHosts = new ArrayList<>();
        // 获取所有可用的host:serviceId
        routeLocator.getRoutes().filter(route -> route.getUri().getHost() != null)
            .filter(route -> !self.equals(route.getUri().getHost()))
            .subscribe(route -> routeHosts.add(route.getUri().getHost()));

        // 记录已经添加过的server,存在同一个应用注册了多个服务在nacos上
        Set<String> dealed = new HashSet<>();
        routeHosts.forEach(instance -> {
            // 拼接url,样式为/serviceId/v2/api-info,当网关调用这个接口时,会自动通过负载均衡寻找对应的主机
            String url = "/" + instance.toLowerCase() + SWAGGER2URL;
            // 无group时 使用默认
            if (!dealed.contains(url)) {
                dealed.add(url);
                SwaggerResource swaggerResource = new SwaggerResource();
                swaggerResource.setUrl(url);
                swaggerResource.setName(instance);
                // 服务下拉列表读取映射
                resources.add(swaggerResource);
            }
        });
        return resources;
    }
}

6、Gateway结合redis实现集群限流

内部原理是令牌桶算法,通过内置的redis的lua脚本,给每个访问的请求派发令牌,从而实现限流

(1)添加依赖
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifatId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
(2)配置类
@Configuration
public class GatewayConfiguration {

	//构建RequestRateLimiterGatewayFilterFactory
	@Bean
	@ConditionalOnBean({RedisRateLimiter.class, IpKeyResolver.class})
	public RequestRateLimiterGatewayFilterFactory requestRateLimiterGatewayFilterFactory(RedisRateLimiter rateLimiter, IpKeyResolver keyResolver) {
		return new RequestRateLimiterGatewayFilterFactory(rateLimiter, keyResolver);
	}

}
(3)IpKeyResolver通过IP进行限流
@Component
public class IpKeyResolver implements KeyResolver {

  @Override
  public Mono<String> resolve(ServerWebExchange exchange) {
        return Mono.just(exchange.getRequest()
                         .getRemoteAddress()
                         .getAddress()
                         .getHostAddress());
  }
  @Bean
  public HostAddrKeyResolver hostAddrKeyResolver() {
    return new HostAddrKeyResolver();
  }

}
(4)UriKeyResolver通过URL路径进行限流
public class UriKeyResolver implements KeyResolver {
 
  @Override
  public Mono<String> resolve(ServerWebExchange exchange) {
    return Mono.just(exchange.getRequest().getURI().getPath());
  }
 
 @Bean
  public UriKeyResolver uriKeyResolver() {
    return new UriKeyResolver();
  }
}
(5)UserIdKeyResolver通过UserId进行限流
public class UserIdKeyResolver implements KeyResolver {
 
  @Bean
  KeyResolver userKeyResolver() {
    return exchange -> Mono.just(exchange.getRequest()
                 .getQueryParams().getFirst("userid"));
  }
 @Bean
  public UserIdKeyResolver userIdKeyResolver() {
    return new UserIdKeyResolver();
  }
}
(6)添加配置项
server:
 port: 8081
spring:
  application:
  	name: gateway-limiter	
 cloud:
  gateway:
   routes:
   - id: limit_route
    uri: http://httpbin.org:80/get
    predicates:
    - After=2017-01-20T17:42:47.789-07:00[America/Denver]
    filters:
    - name: RequestRateLimiter
     args:
      key-resolver: '#{@IpKeyResolver}' #令牌桶解析对象名称
      redis-rate-limiter.replenishRate: 1 # 令牌填充速率
      redis-rate-limiter.burstCapacity: 3  #令牌桶的总量
 redis:
  host: localhost
  port: 6379
  database: 0
7、gateway跨域配置
(1)第一种方式,配置方式
spring:
  cloud:
    gateway:
      globalcors:
        cors-configurations:
          '[/**]': 
            # 允许携带认证信息
            # 允许跨域的源(网站域名/ip),设置*为全部
            # 允许跨域请求里的head字段,设置*为全部
            # 允许跨域的method, 默认为GET和OPTIONS,设置*为全部
            # 跨域允许的有效期
            allow-credentials: true
            allowed-origins: 
            - "http://localhost:13009"
            - "http://localhost:13010"
            allowed-headers: "*"
            allowed-methods: 
            - OPTIONS
            - GET
            - POST
            max-age: 3600
            # 允许response的head信息
            # 默认仅允许如下6个:
            #     Cache-Control
            #     Content-Language
            #     Content-Type
            #     Expires
            #     Last-Modified
            #     Pragma
            #exposed-headers:
(2)第二种方式,配置类方式
@Configuration
@ConditionalOnBean(GlobalCorsProperties.class)
public class CorsAutoConfiguration {
    @Autowired
    private  GlobalCorsProperties globalCorsProperties;
    
    @Bean
    public CorsWebFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
        globalCorsProperties.getCorsConfigurations().forEach((path,corsConfiguration)->source.registerCorsConfiguration(path, corsConfiguration));
        return new CorsWebFilter(source);
    }
}
8、Gateway的全局异常处理
public class JsonExceptionHandler extends DefaultErrorWebExceptionHandler {

	public JsonExceptionHandler(ErrorAttributes errorAttributes, ResourceProperties resourceProperties,
			ErrorProperties errorProperties, ApplicationContext applicationContext) {
		super(errorAttributes, resourceProperties, errorProperties, applicationContext);
	}

	/**
	 * 获取异常属性
	 */
	@Override
	protected Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) {
		int code = 500;
		Throwable error = super.getError(request);
		if (error instanceof org.springframework.cloud.gateway.support.NotFoundException) {
			code = 404;
		}
		return response(code, this.buildMessage(request, error));
	}

	/**
	 * 指定响应处理方法为JSON处理的方法
	 * @param errorAttributes
	 */
	@Override
	protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
		return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
	}

	/**
	 * 根据code获取对应的HttpStatus
	 * @param errorAttributes
	 */
	@Override
	protected HttpStatus getHttpStatus(Map<String, Object> errorAttributes) {
		int statusCode = (int) errorAttributes.get("code");
		return HttpStatus.valueOf(statusCode);
	}

	/**
	 * 构建异常信息
	 * @param request
	 * @param ex
	 * @return
	 */
	private String buildMessage(ServerRequest request, Throwable ex) {
		StringBuilder message = new StringBuilder("Failed to handle request [");
		message.append(request.methodName());
		message.append(" ");
		message.append(request.uri());
		message.append("]");
		if (ex != null) {
			message.append(": ");
			message.append(ex.getMessage());
		}
		return message.toString();
	}

	/**
	 * 构建返回的JSON数据格式
	 * @param status		状态码
	 * @param errorMessage  异常信息
	 * @return
	 */
	public static Map<String, Object> response(int status, String errorMessage) {
		Map<String, Object> map = new HashMap<>();
		map.put("code", status);
		map.put("message", errorMessage);
		map.put("data", null);
		return map;
	}

}
9、 自定义异常处理
public class JsonExceptionHandler extends DefaultErrorWebExceptionHandler {

	public JsonExceptionHandler(ErrorAttributes errorAttributes, ResourceProperties resourceProperties,
			ErrorProperties errorProperties, ApplicationContext applicationContext) {
		super(errorAttributes, resourceProperties, errorProperties, applicationContext);
	}

	/**
	 * 获取异常属性
	 */
	@Override
	protected Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) {
		int code = 500;
		Throwable error = super.getError(request);
		if (error instanceof org.springframework.cloud.gateway.support.NotFoundException) {
			code = 404;
		}
		return response(code, this.buildMessage(request, error));
	}

	/**
	 * 指定响应处理方法为JSON处理的方法
	 * @param errorAttributes
	 */
	@Override
	protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
		return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
	}

	/**
	 * 根据code获取对应的HttpStatus
	 * @param errorAttributes
	 */
	@Override
	protected HttpStatus getHttpStatus(Map<String, Object> errorAttributes) {
		int statusCode = (int) errorAttributes.get("code");
		return HttpStatus.valueOf(statusCode);
	}

	/**
	 * 构建异常信息
	 * @param request
	 * @param ex
	 * @return
	 */
	private String buildMessage(ServerRequest request, Throwable ex) {
		StringBuilder message = new StringBuilder("Failed to handle request [");
		message.append(request.methodName());
		message.append(" ");
		message.append(request.uri());
		message.append("]");
		if (ex != null) {
			message.append(": ");
			message.append(ex.getMessage());
		}
		return message.toString();
	}

	/**
	 * 构建返回的JSON数据格式
	 * @param status		状态码
	 * @param errorMessage  异常信息
	 * @return
	 */
	public static Map<String, Object> response(int status, String errorMessage) {
		Map<String, Object> map = new HashMap<>();
		map.put("code", status);
		map.put("message", errorMessage);
		map.put("data", null);
		return map;
	}

}


@Configuration
@EnableConfigurationProperties({ServerProperties.class, ResourceProperties.class})
public class ErrorHandlerConfiguration {

    private final ServerProperties serverProperties;

    private final ApplicationContext applicationContext;

    private final ResourceProperties resourceProperties;

    private final List<ViewResolver> viewResolvers;

    private final ServerCodecConfigurer serverCodecConfigurer;

    public ErrorHandlerConfiguration(ServerProperties serverProperties,
                                     ResourceProperties resourceProperties,
                                     ObjectProvider<List<ViewResolver>> viewResolversProvider,
                                     ServerCodecConfigurer serverCodecConfigurer,
                                     ApplicationContext applicationContext) {
        this.serverProperties = serverProperties;
        this.applicationContext = applicationContext;
        this.resourceProperties = resourceProperties;
        this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
        this.serverCodecConfigurer = serverCodecConfigurer;
    }

    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public ErrorWebExceptionHandler errorWebExceptionHandler(ErrorAttributes errorAttributes) {
        JsonExceptionHandler exceptionHandler = new JsonExceptionHandler(
                errorAttributes, 
                this.resourceProperties,
                this.serverProperties.getError(), 
                this.applicationContext);
        exceptionHandler.setViewResolvers(this.viewResolvers);
        exceptionHandler.setMessageWriters(this.serverCodecConfigurer.getWriters());
        exceptionHandler.setMessageReaders(this.serverCodecConfigurer.getReaders());
        return exceptionHandler;
    }
    
}


10、整合Sentinel实现限流
<!--添加sentinel和gateway适配-->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
    <version>1.7.1</version>
</dependency>
//配置限流拦截器
@Configuration
public class GatewayConfiguration {

    private final List<ViewResolver> viewResolvers;
    private final ServerCodecConfigurer serverCodecConfigurer;
	
    public GatewayConfiguration(ObjectProvider<List<ViewResolver>> viewResolversProvider,
                                ServerCodecConfigurer serverCodecConfigurer) {
        this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
        this.serverCodecConfigurer = serverCodecConfigurer;
    }
	
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
        // Register the block exception handler for Spring Cloud Gateway.
        return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
    }
	//添加sentinel拦截器
    @Bean
    @Order(-1)
    public GlobalFilter sentinelGatewayFilter() {
        return new SentinelGatewayFilter();
    }
	//设置限流规则
    @PostConstruct
    public void doInit() {
        initGatewayRules();
    }

    private void initGatewayRules() {
        Set<GatewayFlowRule> rules = new HashSet<>();
        rules.add(new GatewayFlowRule("demo2")
                .setCount(2)
                .setIntervalSec(2)
                .setBurst(2)
                .setParamItem(new GatewayParamFlowItem()
                        .setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_CLIENT_IP)
                )
        );

        GatewayRuleManager.loadRules(rules);
    }
}


//配置自定义返回消息
public class JsonSentinelGatewayBlockExceptionHandler implements WebExceptionHandler {
    public JsonSentinelGatewayBlockExceptionHandler(List<ViewResolver> viewResolvers, ServerCodecConfigurer serverCodecConfigurer) {
    }

    @Override
    public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
        ServerHttpResponse serverHttpResponse = exchange.getResponse();
        serverHttpResponse.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        byte[] datas = "{\"code\":403,\"msg\":\"API接口被限流\"}".getBytes(StandardCharsets.UTF_8);
        DataBuffer buffer = serverHttpResponse.bufferFactory().wrap(datas);
        return serverHttpResponse.writeWith(Mono.just(buffer));
    }

}
11、结合Nacos实现动态路由

https://www.cnblogs.com/jian0110/p/12862569.html

(1)Nacos控制台添加一个gateway-route配置
  • 选择json结构
[{
    "id": "consumer-router",
    "order": 0,
    "predicates": [{
        "args": {
            "pattern": "/consume/**"
        },
        "name": "Path"
    }],
    "uri": "lb://nacos-consumer"
},{
    "id": "provider-router",
    "order": 2,
    "predicates": [{
        "args": {
            "pattern": "/provide/**"
        },
        "name": "Path"
    }],
    "uri": "lb://nacos-provider"
}]
(2)gateway连接配置中心
  • 添加nacos配置以及启动类增加服务发现
  • 添加相关配置
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
spring.cloud.nacos.discovery.namespace=08ecd1e5-c042-410a-84d5-b0a8fbeed8ea
nacos.gateway.route.config.data-id=gateway-router
nacos.gateway.route.config.group=DEFAULT_GROUP
@SpringBootApplication
@EnableDiscoveryClient
public class GatewayApplication
{
    public static void main( String[] args )
    {
        SpringApplication.run(GatewayApplication.class, args);
    }
}
/**
 * 路由类配置
 */
@Configuration
public class GatewayConfig {
    public static final long DEFAULT_TIMEOUT = 30000;

    public static String NACOS_SERVER_ADDR;

    public static String NACOS_NAMESPACE;

    public static String NACOS_ROUTE_DATA_ID;

    public static String NACOS_ROUTE_GROUP;

    @Value("${spring.cloud.nacos.discovery.server-addr}")
    public void setNacosServerAddr(String nacosServerAddr){
        NACOS_SERVER_ADDR = nacosServerAddr;
    }

    @Value("${spring.cloud.nacos.discovery.namespace}")
    public void setNacosNamespace(String nacosNamespace){
        NACOS_NAMESPACE = nacosNamespace;
    }

    @Value("${nacos.gateway.route.config.data-id}")
    public void setNacosRouteDataId(String nacosRouteDataId){
        NACOS_ROUTE_DATA_ID = nacosRouteDataId;
    }

    @Value("${nacos.gateway.route.config.group}")
    public void setNacosRouteGroup(String nacosRouteGroup){
        NACOS_ROUTE_GROUP = nacosRouteGroup;
    }

}

(3)初始化路由

/**
 *
 * 通过nacos下发动态路由配置,监听Nacos中gateway-route配置
 *
 */
@Component
@Slf4j
@DependsOn({"gatewayConfig"}) // 依赖于gatewayConfig bean
public class DynamicRouteServiceImplByNacos {

    @Autowired
    private DynamicRouteServiceImpl dynamicRouteService;


    private ConfigService configService;

    @PostConstruct
    public void init() {
        log.info("gateway route init...");
        try{
            configService = initConfigService();
            if(configService == null){
                log.warn("initConfigService fail");
                return;
            }
            String configInfo = configService.getConfig(GatewayConfig.NACOS_ROUTE_DATA_ID, GatewayConfig.NACOS_ROUTE_GROUP, GatewayConfig.DEFAULT_TIMEOUT);
            log.info("获取网关当前配置:\r\n{}",configInfo);
            List<RouteDefinition> definitionList = JSON.parseArray(configInfo, RouteDefinition.class);
            for(RouteDefinition definition : definitionList){
                log.info("update route : {}",definition.toString());
                dynamicRouteService.add(definition);
            }
        } catch (Exception e) {
            log.error("初始化网关路由时发生错误",e);
        }
        dynamicRouteByNacosListener(GatewayConfig.NACOS_ROUTE_DATA_ID,GatewayConfig.NACOS_ROUTE_GROUP);
    }

    /**
     * 监听Nacos下发的动态路由配置
     * @param dataId
     * @param group
     */
    public void dynamicRouteByNacosListener (String dataId, String group){
        try {
            configService.addListener(dataId, group, new Listener()  {
                @Override
                public void receiveConfigInfo(String configInfo) {
                    log.info("进行网关更新:\n\r{}",configInfo);
                    List<RouteDefinition> definitionList = JSON.parseArray(configInfo, RouteDefinition.class);
                    log.info("update route : {}",definitionList.toString());
                    dynamicRouteService.updateList(definitionList);
                }
                @Override
                public Executor getExecutor() {
                    log.info("getExecutor\n\r");
                    return null;
                }
            });
        } catch (NacosException e) {
            log.error("从nacos接收动态路由配置出错!!!",e);
        }
    }

    /**
     * 初始化网关路由 nacos config
     * @return
     */
    private ConfigService initConfigService(){
        try{
            Properties properties = new Properties();
            properties.setProperty("serverAddr",GatewayConfig.NACOS_SERVER_ADDR);
            properties.setProperty("namespace",GatewayConfig.NACOS_NAMESPACE);
            return configService= NacosFactory.createConfigService(properties);
        } catch (Exception e) {
            log.error("初始化网关路由时发生错误",e);
            return null;
        }
    }
}

(4)实现动态路由规则
/**
 * 动态更新路由网关service
 * 1)实现一个Spring提供的事件推送接口ApplicationEventPublisherAware
 * 2)提供动态路由的基础方法,可通过获取bean操作该类的方法。该类提供新增路由、更新路由、删除路由,然后实现发布的功能。
 */
@Slf4j
@Service
public class DynamicRouteServiceImpl implements ApplicationEventPublisherAware {
    @Autowired
    private RouteDefinitionWriter routeDefinitionWriter;
    @Autowired
    private RouteDefinitionLocator routeDefinitionLocator;

    /**
     * 发布事件
     */
    @Autowired
    private ApplicationEventPublisher publisher;

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.publisher = applicationEventPublisher;
    }

    /**
     * 删除路由
     * @param id
     * @return
     */
    public String delete(String id) {
        try {
            log.info("gateway delete route id {}",id);
            this.routeDefinitionWriter.delete(Mono.just(id)).subscribe();
            this.publisher.publishEvent(new RefreshRoutesEvent(this));
            return "delete success";
        } catch (Exception e) {
            return "delete fail";
        }
    }

    /**
     * 更新路由
     * @param definitions
     * @return
     */
    public String updateList(List<RouteDefinition> definitions) {
        log.info("gateway update route {}",definitions);
        // 删除缓存routerDefinition
        List<RouteDefinition> routeDefinitionsExits =  routeDefinitionLocator.getRouteDefinitions().buffer().blockFirst();
        if (!CollectionUtils.isEmpty(routeDefinitionsExits)) {
            routeDefinitionsExits.forEach(routeDefinition -> {
                log.info("delete routeDefinition:{}", routeDefinition);
                delete(routeDefinition.getId());
            });
        }
        definitions.forEach(definition -> {
            updateById(definition);
        });
        return "success";
    }

    /**
     * 更新路由
     * @param definition
     * @return
     */
    public String updateById(RouteDefinition definition) {
        try {
            log.info("gateway update route {}",definition);
            this.routeDefinitionWriter.delete(Mono.just(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";
        }
    }

    /**
     * 增加路由
     * @param definition
     * @return
     */
    public String add(RouteDefinition definition) {
        log.info("gateway add route {}",definition);
        routeDefinitionWriter.save(Mono.just(definition)).subscribe();
        this.publisher.publishEvent(new RefreshRoutesEvent(this));
        return "success";
    }
}

四、Gateway CQRS 解决方案
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值