09-微服务网关(API网关)之GateWay

GateWay 网关

路由配置

基础路由

引入坐标

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

注意:

注意SpringCloud Gateway使用的web框架为webflux,和SpringMVC不兼容,解决方法是将父工程中的web依赖删除,并添加至各个需要web依赖的子工程中

  • 引入的限流组件是hystrix
  • redis底层不再使用jedis,而是lettuce

编写启动类

只编写启动类即可,不用做多余配置

@SpringBootApplication
public class GatewayServerApplication {

	public static void main(String[] args) {
		SpringApplication.run(GatewayServerApplication.class, args);
	}
}

yml配置

以调用 生产者(商品微服务)为例

#====================
#路由配置方式 1 (基础配置)
#基于服务提供者的ip,pom中只需要添加gateway依赖即可,不需要Eureka
#====================
server:
  port: 8080 #端口
spring:
  application:
    name: api-gateway-server #服务名称
  #路由配置
  cloud:
    gateway:
      #配置路由: 路由id,路由到微服务的uri,断言(判断条件)
      routes:
        - id: product-service #保持唯一id即可
          uri: http://127.0.0.1:9001  #目标微服务请求地址
          predicates:
            - Path=/product/**  #路由条件 path :路由匹配条件

#浏览器访问:http://localhost:8080/product/1

路由规则

断言:路由条件

以下是不同种方式的yml配置:

#路由断言之后匹配
cloud:
  gateway:
   routes:
     - id: after_route
       uri: https://xxxx.com
       predicates:
       - After=xxxxx
#------------------------------
#路由断言之前匹配
cloud:
  gateway:
    routes:
      - id: before_route
        uri: https://xxxx.com
        predicates:
        - Before=xxxxx
#------------------------------
#路由断言之间
cloud:
  gateway:
    routes:
      - id: between_route
        uri: https://xxxx.com
        predicates:
        -  Between=xxxx,xxxx
#------------------------------
#路由断言Cookie匹配,此predicate匹配给定名称(chocolate)和正则表达式(ch.p)
cloud:
  gateway:
    routes:
      - id: cookie_route
        uri: https://xxxx.com
        predicates:
        - Cookie=chocolate, ch.p
#------------------------------
#路由断言Header匹配,header名称匹配X-Request-Id,且正则表达式匹配\d+
cloud:
  gateway:
    routes:
      - id: header_route
        uri: https://xxxx.com
        predicates:
        - Header=X-Request-Id, \d+
#------------------------------
#路由断言匹配Host匹配,匹配下面Host主机列表,**代表可变参数
cloud:
  gateway:
    routes:
      - id: host_route
        uri: https://xxxx.com
        predicates:
          - Host=**.somehost.org,**.anotherhost.org
#------------------------------
#路由断言Method匹配,匹配的是请求的HTTP方法
cloud:
  gateway:
    routes:
      - id: method_route
        uri: https://xxxx.com
        predicates:
        - Method=GET
#------------------------------
#路由断言匹配,{segment}为可变参数
cloud:
  gateway:
    routes:
      - id: host_route
        uri: https://xxxx.com
        predicates:
        - Path=/foo/{segment},/bar/{segment}
#------------------------------
#路由断言Query匹配,将请求的参数param(baz)进行匹配,也可以进行regexp正则表达式匹配 (参数包含 foo,并且foo的值匹配ba.)
cloud:
  gateway:
    routes:
      - id: query_route
        uri: https://xxxx.com
        predicates:
        - Query=baz 或 Query=foo,ba.
#------------------------------
#路由断言RemoteAddr匹配, 将匹配192.168.1.1~192.168.1.254之间的ip地址,其中24为子网掩码位数即255.255.255.0
cloud:
  gateway:
   routes:
      - id: remoteaddr_route
        uri: https://example.org
        predicates:
        - RemoteAddr=192.168.1.1/24
       

动态路由(面向服务的路由)

引入坐标

<!--引入EurekaClient-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

配置启动类

只编写启动类即可,不用做多余配置

yml配置

  • 配置eureka
  • uri 从服务中心拉取
server:
  port: 8080 #端口
spring:
  application:
    name: api-gateway-server #服务名称
  #路由配置方式2(面向服务的动态路由,pom中引入Eureka依赖,yml配置eureka)
  cloud:
    gateway:
      #配置路由: 路由id,路由到微服务的uri,断言(判断条件)
      routes:
        - id: product-service #保持唯一id即可
          uri: lb://service-product #lb://根据微服务名称从往册中心中拉取服务请求路径
          predicates:
            - Path=/product/**  #路由条件 path :路由匹配条件。
#配置Eureka
eureka:
  client:
    service-url:
      defaultZone: http://localhost:9000/eureka/
  instance:
    prefer-ip-address: true #使用ip地址注册
    
#浏览器访问:http://localhost:8080/product/1

参数解释:

lb:// 根据微服务名称从往册中心中拉取服务请求路径

路径转发(局部路由)

filters 过滤

引入坐标

<!--引入EurekaClient-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

配置启动类

只编写启动类即可,不用做多余配置

yml配置

  • 修改yml,加入过滤器
  • 配置eureka
  • 开启服务名称转发
#-------------------路由配置方式3(路径转发,基于filters过滤器,yml配置eureka)
server:
  port: 8080 #端口
spring:
  application:
    name: api-gateway-server #服务名称
  #路由配置方式3
  cloud:
    gateway:
      #配置路由:路由id,路由到徽服务的uri ,断言(判断条件)
      routes:
        - id: product-service #保持唯一id即可
          uri: lb://service-product #lb://根据微服务名称从往册中心中拉取服务请求路径
          predicates:
            - Path=/product-service/**  #将当前请求转发到http://127.0.0. 1: 9001/product/1
          filters: #配置路由过滤器
            - RewritePath=/service-product/(?<segment>.*), /$\{segment} #路径重写的过滤器,在yml中$应写为$\
      #配置自动的根据微服务名称进行路由转发
      discovery:
        locator:
          enabled: true #开启根据微服务名称自动转发
          lower-case-service-id: true #微服务名称以小写形式呈现
#配置Eureka
eureka:
  client:
    service-url:
      defaultZone: http://localhost:9000/eureka/
      registry-fetch-interval-seconds: 5 # 获取服务列表的周期:5s
  instance:
    prefer-ip-address: true #使用ip地址注册.

#浏览器访问:http://localhost:8080/service-product/product/1
#或 http://localhost:9001/product/1

参数解释:

  • discovery-locator-enabled
    • 是否与服务注册于发现组件进行结合,通过 serviceId 转发到具体的服务实例。默认为 false,设为 true 便开启通过服务中心自动的根据 serviceId 创建路由的功能
  • discovery-locator-lower-case-service-id
    • 表示将请求路径的服务名配置改成小写,因为服务注册的时候,向注册中心注册时将服务名转成大写的了

过滤器

基础概念

(1)过滤器的生命周期
Spring Cloud GatewayFilter 的生命周期不像 Zuul 的那么丰富,它只有两个:“pre” 和 “post”。

  • PRE: 在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。
  • POST:在路由到微服务以后执行。这种过滤器可用来为响应添加标准的 HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。

(2)过滤器类型
Spring Cloud Gateway 的 Filter 从作用范围可分为另外两种 GatewayFilterGlobalFilter

  • GatewayFilter:应用到单个路由或者一个分组的路由上。
  • GlobalFilter:应用到所有的路由上。

局部过滤器

局部过滤器(GatewayFilter),是针对单个路由的过滤器。可以对访问的URL过滤,进行切面处理。在Spring Cloud Gateway中通过GatewayFilter的形式内置了很多不同类型的局部过滤器。如上述的路径重写:

 #路由配置
  cloud:
    gateway:
      #配置路由: 路由id,路由到微服务的uri,断言(判断条件)
      routes:
        - id: product-service #保持唯一id即可
          uri: lb://service-product #lb://根据微服务名称从往册中心中拉取服务请求路径
          predicates:
#            - Path=/product/**  #路由条件 path :路由匹配条件
            - Path=/product-service/**  #将当前请求转发到http://127.0.0. 1: 9001/product/1
          filters: #配置路由过滤器
            - RewritePath=/product-service/(?<segment>.*), /$\{segment} #路径重写的过滤器,在yml中$应写为$\

全局过滤器

全局过滤器(GlobalFilter)作用于所有路由,Spring Cloud Gateway 定义了Global Filter接口,用户可以自定义实现自己的Global Filter。通过全局过滤器可以实现对权限的统一校验,安全性验证等功能,并且全局过滤器也是程序员使用比较多的过滤器。
Spring Cloud Gateway内部也是通过一系列的内置全局过滤器对整个路由转发进行处理如下:

9-1

自定义全局过滤器

/**
 * 自定义一个全局过滤器
 *      实现 globalfilter , ordered接口
 **/
@Component
public class LoginFilter implements GlobalFilter, Ordered {

	/**
	 * 执行过滤器中的业务逻辑
	 * @param exchange
	 * @param chain
	 * @return
	 */
	@Override
	public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
		System.out.println("执行了自定义全局过滤器");
		return chain.filter(exchange);
	}

	/**
	 * 指定过滤器的执行顺序,返回值越小,执行优先级越高
	 * @return
	 */
	@Override
	public int getOrder() {
		return 0;
	}
}

统一授权(基于全局过滤器)

开发中的鉴权逻辑:

  • 当客户端第一次请求服务时,服务端对用户进行信息认证(登录)
  • 认证通过,将用户信息进行加密形成token,返回给客户端,作为登录凭证
  • 以后每次请求,客户端都携带认证的token
  • 服务端对token进行解密,判断是否有效。

9-2

编写测试代码

需要搭配 【路径转发(局部路由)yml】配置使用

/**
 * 自定义一个全局过滤器
 *      实现 globalfilter , ordered接口
 **/
@Component
public class LoginFilter implements GlobalFilter, Ordered {

	/**
	 * 执行过滤器中的业务逻辑
	 *     对请求参数中的access-token进行判断
	 *      如果存在此参数:代表已经认证成功
	 *      如果不存在此参数 : 认证失败.
	 *  ServerWebExchange : 相当于请求和响应的上下文(zuul中的RequestContext)
	 */
	@Override
	public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
		System.out.println("执行了自定义的全局过滤器");
		//1.获取请求参数access-token
		String token = exchange.getRequest().getQueryParams().getFirst("access-token");
		//2.判断是否存在
		if(token == null) {
			//3.如果不存在 : 认证失败
			System.out.println("没有登录");
			exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
			return exchange.getResponse().setComplete(); //请求结束
		}
		//4.如果存在,继续执行
		return chain.filter(exchange); //继续向下执行
	}

	/**
	 * 指定过滤器的执行顺序,返回值越小,执行优先级越高
	 * @return
	 */
	@Override
	public int getOrder() {
		return 0;
	}
}
  • 自定义全局过滤器需要实现GlobalFilter和Ordered接口。
  • 在filter方法中完成过滤器的逻辑判断处理
  • 在getOrder方法指定此过滤器的优先级,返回值越大级别越低
  • ServerWebExchange 就相当于当前请求和响应的上下文,存放着重要的请求-响应属性、请求实
    例和响应实例等等。一个请求中的request,response都可以通过 ServerWebExchange 获取
  • 调用 chain.filter 继续向下游执行
  • 浏览器访问测试:

网关限流

常见算法

计数器算法

计数器限流算法是最简单的一种限流实现方式。其本质是通过维护一个单位时间内的计数器,每次请求计数器加1,当单位时间内计数器累加到大于设定的阈值,则之后的请求都被拒绝,直到单位时间已经过去,再将计数器重置为零。

9-3

漏桶算法

漏桶算法可以很好地限制容量池的大小,从而防止流量暴增。漏桶可以看作是一个带有常量服务时间的单服务器队列,如果漏桶(包缓存)溢出,那么数据包会被丢弃。 在网络中,漏桶算法可以控制端口的流量输出速率,平滑网络上的突发流量,实现流量整形,从而为网络提供一个稳定的流量。

9-4

为了更好的控制流量,漏桶算法需要通过两个变量进行控制:一个是桶的大小,支持流量突发增多时可以存多少的水(burst),另一个是水桶漏洞的大小(rate)。

令牌桶算法

令牌桶算法是对漏桶算法的一种改进,桶算法能够限制请求调用的速率,而令牌桶算法能够在限制调用的平均速率的同时还允许一定程度的突发调用。在令牌桶算法中,存在一个桶,用来存放固定数量的令牌。算法中存在一种机制,以一定的速率往桶中放令牌。每次请求调用需要先获取令牌,只有拿到令牌,才有机会继续执行,否则选择选择等待可用的令牌、或者直接拒绝。放令牌这个动作是持续不断的进行,如果桶中令牌数达到上限,就丢弃令牌,所以就存在这种情况,桶中一直有大量的可用令牌,这时进来的请求就可以直接拿到令牌执行,比如设置qps为100,那么限流器初始化完成一秒后,桶中就已经有100个令牌了,这时服务还没完全启动好,等启动完成对外提供服务时,该限流器可以抵挡瞬时的100个请求。所以,只有桶中没有令牌时,请求才会进行等待,最后相当于以一定的速率执行。

9-5

基于Filter的限流

SpringCloudGateway官方就提供了基于令牌桶的限流支持。基于其内置的过滤器工厂
RequestRateLimiterGatewayFilterFactory 实现。在过滤器工厂中是通过Redis和lua脚本结合的方式进行流量控制。

环境搭建

启动redis,cli命令窗口中输入Redis Monitor 命令

Redis Monitor 命令用于实时打印出 Redis 服务器接收到的命令,调试用。

输入:monitor 即可

引入坐标
<!--健康监控-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--redis-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
yml配置
#====================
#路由配置方式 4 (令牌桶限流)
#基于filters过滤器的令牌桶限流,需配置KeyResolverConfiguration类
#需引入 健康监控、redis依赖
#====================
server:
  port: 8080 #端口
spring:
  application:
    name: api-gateway-server #服务名称
  redis:
    host: localhost
    pool: 6379
    database: 0
  #路由配置
  cloud:
    gateway:
      #配置路由:路由id,路由到徽服务的uri ,断言(判断条件)
      routes:
        - id: product-service #保持唯一id即可
          uri: lb://service-product #lb://根据微服务名称从往册中心中拉取服务请求路径
          predicates:
            - Path=/product-service/**  #将当前请求转发到http://127.0.0. 1: 9001/product/1
          filters: #配置路由过滤器
            - RewritePath=/product-service/(?<segment>.*), /$\{segment} #路径重写的过滤器,在yml中$应写为$\
            - name: RequestRateLimiter
              args:
                key-resolver: '#{@pathKeyResolver}' # 使用SpringEL表达式从容器中获取对象
                redis-rate-limiter.replenishRate: 1 #令牌桶每秒填充平均速率
                redis-rate-limiter.burstCapacity: 3 #令牌桶的上限
                #注释
                # RequestRateLimiter : 使用限流过滤器,是springcloud gateway提供的
                # 参数  replenishRate : 向令牌桶中填充的速率
                #     burstCapacity :令牌桶的容量
#配置Eureka
eureka:
  client:
    service-url:
      defaultZone: http://localhost:9000/eureka/
  instance:
    prefer-ip-address: true #使用ip地址注册
配置KeyResolver
  • 为了达到不同的限流效果和规则,可以通过实现 KeyResolver 接口,定义不同请求类型的限流键。

  • 编写 KeyResolverConfiguration 配置类

package com.hwx.gateway.conf;

import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
 *基于令牌桶的限流
 **/
@Configuration    //不使用此配置类时,注释掉该注解即可
public class KeyResolverConfiguration {

	/**
	 * 编写基于请求路径的限流规则
	 *  //abc
	 *  //基于请求ip 127.0.0.1
	 *  //基于参数
	 */
	@Bean
	public KeyResolver pathKeyResolver() {
		//自定义的KeyResolver
		return new KeyResolver() {
			/**
			 * ServerWebExchange :
			 *      上下文参数
			 */
			public Mono<String> resolve(ServerWebExchange exchange) {
				return Mono.just(exchange.getRequest().getPath().toString());
			}
		};
	}

	/**
	 * 基于请求参数的限流
	 *
	 *  请求 abc ? userId=1
	 */
	//@Bean
	public KeyResolver userKeyResolver() {
		return exchange -> Mono.just(
				exchange.getRequest().getQueryParams().getFirst("userId")
		);
	}

	/**
	 * 基于请求ip的限流
	 */
	//@Bean
	public KeyResolver ipKeyResolver() {
		return exchange -> Mono.just(
				exchange.getRequest().getHeaders().getFirst("X-Forwarded-For")
		);
	}


}

测试

#浏览器访问 http://localhost:8080/product-service/product/1
#  1秒3次成功
#  1秒5次限流

基于Sentinel的限流

环境搭建

引入坐标
<!--gateway对sentinel的支持-->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
    <version>1.6.3</version>
</dependency>
编写配置类,并配置
/**
 * sentinel限流的配置
 */
@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;
	}

	/**
	 * 配置限流的异常处理器:SentinelGatewayBlockExceptionHandler
	 */
	@Bean
	@Order(Ordered.HIGHEST_PRECEDENCE)
	public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
		return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
	}

	/**
	 * 配置限流过滤器
	 */
	@Bean
	@Order(Ordered.HIGHEST_PRECEDENCE)
	public GlobalFilter sentinelGatewayFilter() {
		return new SentinelGatewayFilter();
	}

	/**
	 * @PostConstruct定义初始化的加载方法,用于指定资源的限流规则。这里资源的名称为orderservice,统计时间是1秒内,限流阈值是1。表示每秒只能访问一个请求。
	 *
	 * 配置初始化的限流参数
	 *  用于指定资源的限流规则.
	 *      1.资源名称 (路由id)
	 *      2.配置统计时间
	 *      3.配置限流阈值
	 */
	@PostConstruct
	public void initGatewayRules() {
		Set<GatewayFlowRule> rules = new HashSet<>();
		rules.add(new GatewayFlowRule("product-service")
				.setCount(1)
				.setIntervalSec(1)
		);
		GatewayRuleManager.loadRules(rules);
	}
}
修改yml配置,移除filters下的RequestRateLimiter配置
server:
  port: 8080 #端口
spring:
  application:
    name: api-gateway-server #服务名称
  redis:
    host: localhost
    pool: 6379
    database: 0
  #路由配置
  cloud:
    gateway:
      #配置路由:路由id,路由到徽服务的uri ,断言(判断条件)
      routes:
        - id: product-service #保持唯一id即可
          uri: lb://service-product #lb://根据微服务名称从往册中心中拉取服务请求路径
          predicates:
            - Path=/product-service/**  #将当前请求转发到http://127.0.0. 1: 9001/product/1
          filters: #配置路由过滤器
            - RewritePath=/product-service/(?<segment>.*), /$\{segment} #路径重写的过滤器,在


#配置Eureka
eureka:
  client:
    service-url:
      defaultZone: http://localhost:9000/eureka/
  instance:
    prefer-ip-address: true #使用ip地址注册


测试

自定义异常

当触发限流后页面显示的是 Blocked by Sentinel: FlowException。为了展示更加友好的限流提示,Sentinel支持自定义异常处理。

可以在 GatewayCallbackManager 注册回调进行定制:

  • setBlockHandler :注册函数用于实现自定义的逻辑处理被限流的请求,对应接口为BlockRequestHandler 默认实现为 DefaultBlockRequestHandler ,当被限流时会返回类似于下面的错误信息: Blocked by Sentinel: FlowException
	/**
	 * 自定义限流处理器
	 */
	@PostConstruct
	public void initBlockHandlers() {
		BlockRequestHandler blockHandler = new BlockRequestHandler() {
			public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {
				Map map = new HashMap();
				map.put("code", 001);
				map.put("message", "不好意思,限流啦");
				return ServerResponse.status(HttpStatus.OK)
						.contentType(MediaType.APPLICATION_JSON_UTF8)
						.body(BodyInserters.fromObject(map));
			}
		};
		GatewayCallbackManager.setBlockHandler(blockHandler);
	}
  • 结果如下图

9-6

自定义API分组

修改初始化限流配置,并配置自定义方法

@PostConstruct
	public void initGatewayRules() {
		Set<GatewayFlowRule> rules = new HashSet<>();
		//这个是结合 【自定义API限流分组】方法 所使用
		rules.add(new GatewayFlowRule("product_api")
				.setCount(1).setIntervalSec(1)
		);
		GatewayRuleManager.loadRules(rules);
	}

	/**
	 * 自定义API限流分组
	 *      1.定义分组
	 *      2.对小组配置限流规则
	 */
	@PostConstruct
	private void initCustomizedApis() {
		Set<ApiDefinition> definitions = new HashSet<>();
		ApiDefinition api1 = new ApiDefinition("product_api")
				.setPredicateItems(new HashSet<ApiPredicateItem>() {{
					add(new ApiPathPredicateItem().setPattern("/product-service/product/**"). //以/product-service/product/开都的所有url
							setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
				}});
		ApiDefinition api2 = new ApiDefinition("order_api")
				.setPredicateItems(new HashSet<ApiPredicateItem>() {{
					add(new ApiPathPredicateItem().setPattern("/order-service/order")); //完全匹配/order-service/order 的url
				}});
		definitions.add(api1);
		definitions.add(api2);
		GatewayApiDefinitionManager.loadApiDefinitions(definitions);
	}

整体配置类

package com.hwx.gateway.conf;

import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPathPredicateItem;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPredicateItem;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.GatewayApiDefinitionManager;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;
import com.alibaba.csp.sentinel.adapter.gateway.sc.SentinelGatewayFilter;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.BlockRequestHandler;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
import com.alibaba.csp.sentinel.adapter.gateway.sc.exception.SentinelGatewayBlockExceptionHandler;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.reactive.result.view.ViewResolver;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import javax.annotation.PostConstruct;
import java.util.*;

/**
 * sentinel限流的配置
 */
@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;
	}

	/**
	 * 配置限流的异常处理器:SentinelGatewayBlockExceptionHandler
	 */
	@Bean
	@Order(Ordered.HIGHEST_PRECEDENCE)
	public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
		return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
	}

	/**
	 * 配置限流过滤器
	 */
	@Bean
	@Order(Ordered.HIGHEST_PRECEDENCE)
	public GlobalFilter sentinelGatewayFilter() {
		return new SentinelGatewayFilter();
	}

	/**
	 * @PostConstruct
	 * 定义初始化的加载方法,
	 * 用于指定资源的限流规则。这里资源的名称为orderservice,统计时间是1秒内,限流阈值是1。表示每秒只能访问一个请求。
	 *
	 * 配置初始化的限流参数
	 *  用于指定资源的限流规则.
	 *      1.资源名称 (路由id)
	 *      2.配置统计时间
	 *      3.配置限流阈值
	 */
	@PostConstruct
	public void initGatewayRules() {
		Set<GatewayFlowRule> rules = new HashSet<>();
//		rules.add(new GatewayFlowRule("product-service")
//				.setCount(1)
//				.setIntervalSec(1)
//		);

		//这个是结合 【自定义API限流分组】方法 所使用
		rules.add(new GatewayFlowRule("product_api")
				.setCount(1).setIntervalSec(1)
		);
		rules.add(new GatewayFlowRule("order_api")
				.setCount(1).setIntervalSec(1)
		);
		GatewayRuleManager.loadRules(rules);
	}

	/**
	 * 自定义API限流分组
	 *      1.定义分组
	 *      2.对小组配置限流规则
	 */
	@PostConstruct
	private void initCustomizedApis() {
		Set<ApiDefinition> definitions = new HashSet<>();
		ApiDefinition api1 = new ApiDefinition("product_api")
				.setPredicateItems(new HashSet<ApiPredicateItem>() {{
					add(new ApiPathPredicateItem().setPattern("/product-service/product/**"). //以/product-service/product/下的所有url
							setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
				}});
		ApiDefinition api2 = new ApiDefinition("order_api")
				.setPredicateItems(new HashSet<ApiPredicateItem>() {{
					add(new ApiPathPredicateItem().setPattern("/order-service/order/**"). //以/product-service/product/下的所有url
							setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
				}});
//		ApiDefinition api2 = new ApiDefinition("order_api")
//				.setPredicateItems(new HashSet<ApiPredicateItem>() {{
//					add(new ApiPathPredicateItem().setPattern("/order-service/order")); //完全匹配/order-service/order 的url
//				}});
		definitions.add(api1);
		definitions.add(api2);
		GatewayApiDefinitionManager.loadApiDefinitions(definitions);
	}

	/**
	 * 自定义限流处理器
	 */
	@PostConstruct
	public void initBlockHandlers() {
		BlockRequestHandler blockHandler = new BlockRequestHandler() {
			public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {
				Map map = new HashMap();
				map.put("code", 001);
				map.put("message", "不好意思,限流啦");
				return ServerResponse.status(HttpStatus.OK)
						.contentType(MediaType.APPLICATION_JSON_UTF8)
						.body(BodyInserters.fromObject(map));
			}
		};
		GatewayCallbackManager.setBlockHandler(blockHandler);
	}

}

网关高可用

简述

高可用HA(High Availability)是分布式系统架构设计中必须考虑的因素之一,它通常是指,通过设计减少系统不能提供服务的时间。我们都知道,单点是系统高可用的大敌,单点往往是系统高可用最大的风险和敌人,应该尽量在系统设计的过程中避免单点。方法论上,高可用保证的原则是“集群化”,或者叫“冗余”:只有一个单点,挂了服务会受影响;如果有冗余备份,挂了还有其他backup能够顶上。

9-7

我们实际使用 Spring Cloud Gateway 的方式如上图,不同的客户端使用不同的负载将请求分发到后端的 Gateway,Gateway 再通过HTTP调用后端服务,最后对外输出。因此为了保证 Gateway 的高可用性,前端可以同时启动多个 Gateway 实例进行负载,在 Gateway 的前端使用 Nginx 或者 F5 进行负载转发以达到高可用性。

搭建环境

IDEA中操作

准备两个gateway,端口分别设置为8080,8081(可以在idea的services中copy一个服务,并修改端口号)

配置nginx

#配置多台服务器(这里只在一台服务器上的不同端口)
upstream gateway {
    server 127.0.0.1:8081;
    server 127.0.0.1:8080;
}
#请求转向mysvr 定义的服务器列表
    location / {
    proxy_pass http://gateway;
}

访问测试

在浏览器上通过访问 http://localhost/order-service/order/buy/1 请求的效果和之前是一样的。

这次关闭一台网关服务器,还是可以支持部分请求的访问。

posted @ 2021-07-19 15:48  爱码士很优秀  阅读(885)  评论(0)    收藏  举报