服务网关实践

news2025/1/21 10:25:18

概述

微服务架构由很多个微小的服务(微服务)组成系统,每个微服务有自己的主机和端口号。如果直接让客户端与各个微服务通信,带来的问题有:

  • 客户端需要维护多个请求地址(主机和端口号)
  • 每个微服务都需要独立认证
  • 某些场景存在跨域问题

在客户端和各个微服务之间添加微服务网关可以解决如上问题,客户端的请求都先经过微服务网关,客户端就只需要知道网关的请求地址即可,添加网关的优点有:

  • 统一入口,只需要维护一个请求地址(主机和端口号)
  • 易于监控
  • 统一认证

本文介绍的微服务网关技术是Spring Cloud GateWay。该项目提供了一个用于在 Spring WebFlux 或 Spring WebMVC 之上构建 API 网关的库。 Spring Cloud Gateway 旨在提供一种简单而有效的方法来路由到 API 并为其提供横切关注点,例如:安全性、监控。

本文的操作是在服务熔断保护实践--Sentinal的基础上进行。

环境说明

jdk1.8

maven3.6.3

mysql8

spring cloud2021.0.8

spring boot2.7.12

idea2022

步骤

创建子模块

在父工程下,创建子模块:api_gateway_server

添加依赖

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

启动类

package org.example.gateway;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

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

路由配置

配置文件application.yml

server:
  port: 8080
spring:
  application:
    name: api-gateway-server
  cloud:
    gateway:
      routes:
        - id: product-service
          uri: http://127.0.0.1:9001
          predicates:
            - Path=/product/**

配置的作用:通过网关访问路径符合/product/开头时,网关会将请求转发到uri(http://127.0.0.1:9001)的对应的路径下。例如:访问localhost:8080/product/1,网关会将请求转发到http://127.0.0.1:9001/product/1,其中127.0.0.1localhost可以互相替换。

启动网关服务报错如下

Spring MVC found on classpath, which is incompatible with Spring Cloud Gateway.

Action:

Please set spring.main.web-application-type=reactive or remove spring-boot-starter-web dependency.

启动报错原因:

SpringCloud Gateway使用的web框架为webflux,和SpringMVC不兼容,所以gateway不需要引入web依赖,否则造成依赖冲突。

解决启动报错:

父工程不全局引入web依赖,需要的子工程单独引入web依赖,不需要则不引入(例如:gateway工程不需要则不引入)

父工程,删除web依赖

在需要的子工程(order-service、order-service-feign_hystrix、product-service)里添加Web依赖

		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

刷新依赖

测试

启动eureka服务、启动product服务(确认product服务的端口号为9001)

浏览器访问

直接访问微服务

http://localhost:9001/product/1

 通过网关访问

http://localhost:8080/product/1

可以看到通过网关访问和直接访问微服务效果一样。

路由规则

Spring Cloud Gateway 的功能很强大,前面只是使用了 Predicates(断言) 进行了简单的条件匹配,其实Spring Cloud Gataway 帮我们内置了很多 Predicates 功能。在 Spring Cloud Gateway 中 Spring 利用Predicate 的特性实现了各种路由匹配规则,如:通过 Header、请求参数等不同的条件来进行作为条件匹配到对应的路由。

路由匹配示例

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

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

之前我们配置路由uri是直接配置了微服务的主机和端口号:uri: http://127.0.0.1:9001

如果微服务地址有变化,需要修改网关配置uri,耦合度高。

Spring Cloud GateWay支持动态路由:即自动的从注册中心中获取服务列表并访问。

api_gateway_server服务(网关服务)引入eureka依赖

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

修改网关服务配置文件application.yml

  • 路由uri的值改为 lb://service-product

       lb代表从注册中心获取服务,同时具备赋值均衡功能,//后面接的是服务名称

  • 添加配置eureka的注册中心相关信息
server:
  port: 8080
spring:
  application:
    name: api-gateway-server
  cloud:
    gateway:
      routes:
        - id: product-service
          uri: lb://service-product
          predicates:
            - Path=/product/**
# 配置eureka
eureka:
  client:
    service-url:
      defaultZone: http://localhost:9000/eureka/
  instance:
    prefer-ip-address: true

配置的作用:通过网关访问,如果路径包含/product开头,会转发到uri(lb://service-product) 对应的微服务接口。例如:访问localhost:8080/product/1符合断言路径的条件,网关将请求会转发到service-product微服务,微服务名称映射的主机和端口号从eureka注册中心得到,转发对应的接口为localhost:9001/product/1

测试

重启gateway服务

查看eureka服务

访问正常

http://localhost:8080/product/1

过滤器重写请求路径

为了让更好识别访问路径将转发到哪个服务,在网关访问路径中带有具有识别作用的特定路径,例如:用带有product-service的路径表示请求将转发到product服务。

完整的访问路径如下

http://localhost:8080/product-service/product/1

响应错误页面

要访问成功,可以使用过滤器重写请求路径,

application.yml配置如下:

          predicates:
            #- Path=/product/**
            - Path=/product-service/**
          filters:
            - RewritePath=/product-service/(?<segment>),/$\{segment} #路径重写的过滤器,把/product-service/xxx的路径重写为/xxx

重启网关服务

再次访问

http://localhost:8080/product-service/product/1

成功访问到数据

过滤器

过滤器类型:局部过滤器(GatewayFilter)和全局过滤器(GlobalFilter)。

局部过滤器(GatewayFilter):应用到单个路由或者一个分组的路由上

全局过滤器(GlobalFilter):应用到所有路由上。

局部过滤器

部分局部过滤器如下

过滤器工厂作用参数
AddRequestHeader为原始请求添加HeaderHeader的名称及值
AddRequestParameter为原始请求添加请求参数参数名称及值
AddResponseHeader为原始响应添加HeaderHeader的名称及值
DedupeResponseHeader剔除响应头中重复的值需要去重的Header名称及去重策略
Hystrix为路由引入Hystrix的断路器保护HystrixCommand 的名称
FallbackHeaders为fallbackUri的请求头中添加具体的异常信息Header的名称
PrefixPath为原始请求路径添加前缀前缀路径
PreserveHostHeader为请求添加一个preserveHostHeader=true的属性,路由过滤器会检查该属性以决定是否要发送原始的Host
RequestRateLimiter用于对请求限流,限流算法为令牌桶keyResolver、rateLimiter、statusCode、denyEmptyKey、emptyKeyStatus
RedirectTo将原始请求重定向到指定的URLhttp状态码及重定向的url
RemoveHopByHopHeadersFilter为原始请求删除IETF组织规定的一系列Header默认就会启用,可以通过配置指定仅删除哪些Header
RemoveRequestHeader为原始请求删除某个HeaderHeader名称
RemoveResponseHeader为原始响应删除某个HeaderHeader名称
RewritePath重写原始的请求路径原始路径正则表达式以及重写后路径的正则表达式
RewriteResponseHeader重写原始响应中的某个HeaderHeader名称,值的正则表达式,重写后的值
SaveSession在转发请求之前,强制执行WebSession::save 操作
secureHeaders为原始响应添加一系列起安全作用的响应头无,支持修改这些安全响应头的值
SetPath修改原始的请求路径修改后的路径
SetResponseHeader修改原始响应中某个Header的值Header名称,修改后的值
SetStatus修改原始响应的状态码HTTP 状态码,可以是数字,也可以是字符串
StripPrefix用于截断原始请求的路径使用数字表示要截断的路径的数量
Retry针对不同的响应进行重试retries、statuses、methods、series
RequestSize设置允许接收最大请求包的大 小。如果请求包大小超过设置的值,则返回 413 Payload TooLarge请求包大小,单位为字节,默认值为5M
ModifyRequestBody在转发请求之前修改原始请求体内容修改后的请求体内容
ModifyResponseBody修改原始响应体的内容修改后的响应体内容

每个过滤器工厂都对应一个实现类,并且这些类的名称必须以GatewayFilterFactory 结尾,这是Spring Cloud Gateway的一个约定,例如 AddRequestHeader 对应的实现类为AddRequestHeaderGatewayFilterFactory

全局过滤器

全局过滤器(GlobalFilter)作用于所有路由。通过全局过滤器可以实现对权限的统一校验,安全性验证等功能。

GloabalFilter接口源码如下:

过滤器应用
网关统一鉴权

编写一个全局过滤器

package org.example.gateway.filter;

import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
 * 自定义全局过滤器
 */
@Component
public class LoginFilter implements GlobalFilter, Ordered {
    /**
     * 过滤器逻辑
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        System.out.println("执行了自定义的全局过滤器");
        return chain.filter(exchange);//放行,继续向下执行
    }

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

重启网关服务

 访问

http://localhost:8080/product-service/product/1

看到数据如下

查看网关服务IDEA控制台的输出,看到执行了自定义的全局过滤器。

说明过滤器正常工作了。

鉴权逻辑:

  • 当客户端第一次请求服务时,服务端对用户进行信息认证(登录)

  • 认证通过,将用户信息进行加密形成token,返回给客户端,作为登录凭证

  • 以后每次请求,客户端都携带认证的token

  • 服务端对token进行解密,判断是否有效

修改LoginFilter的filter方法

/**
 * 过滤器逻辑
 *  对请求参数中的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){
        System.out.println("没有登录");
        exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
        return exchange.getResponse().setComplete();//请求结束
    }
    return chain.filter(exchange);//放行,继续向下执行
}

重启网关服务

测试

  • 未携带access-token测试

    http://localhost:8080/product-service/product/1

IDEA控制台提示没有登录  

  • 携带access-token测试

    http://localhost:8080/product-service/product/1?access-token=1

网关限流

可以在网关阶段对服务调用进行限流,达到保护服务的作用。

限流算法

常见的限流算法有:计数器限流算法、滑动窗口算法、漏桶算法、令牌桶算法。

每种算法原理、优缺点、使用场景也各不一样,具体可参考 常见限流算法

限流后的处理方式
  • 拒绝服务

  • 排队等待

  • 服务降级

基于filter的限流

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

安装好redis

启动redis服务端

启动redis客户端,使用monitor命令监控redis数据变化信息

127.0.0.1:6379> monitor

在api_gateway_server服务中引入redis相关依赖(基于reactive的redis依赖)

        <!--监控依赖-->
        <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>

 修改application.yml,配置redis和网关过滤器

server:
  port: 8080 #端口
spring:
  application:
    name: api-gateway-server #服务名称
  redis:
    host: localhost
    port: 6379
    database: 0
  cloud: #配置SpringCloudGateway的路由
    gateway:
      routes:
        - id: product-service
          uri: lb://service-product
          predicates:
            - Path=/product-service/**
          filters:
            - name: RequestRateLimiter
              args:
                # 使用SpEL从bean容器中获取对象
                key-resolver: '#{@pathKeyResolver}'
                # 令牌桶每秒填充平均速率
                redis-rate-limiter.replenishRate: 1
                # 令牌桶的上限
                redis-rate-limiter.burstCapacity: 3
            - RewritePath=/product-service/(?<segment>.*), /$\{segment}
#eureka注册中心
eureka:
  client:
    service-url:
      defaultZone: http://localhost:9000/eureka/
  instance:
    prefer-ip-address: true #使用ip地址注册

虽然args相关配置存在报错如下,但是经验证,不影响运行。

org.example.gateway包下,新建config包,并在config包下编写KeyResolverConfiguration配置

package org.example.gateway.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;

@Configuration
public class KeyResolverConfiguration {
    /**
     * 基于请求路径的限流
     */
    @Bean
    public KeyResolver pathKeyResolver() {
        return exchange -> Mono.just(
                exchange.getRequest().getPath().toString()
        );
    }
}

启动eureka、product、gateway服务

浏览器访问

http://localhost:8080/product-service/product/1?access-token=1

连续多次访问,每秒超过3次访问后,响应如下:

查看redis客户端监控的输出如下:

以上实现了基于请求路径的限流,其他方式限流,参考代码如下:

package org.example.gateway.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;

@Configuration
public class KeyResolverConfiguration {
    /**
     * 基于请求路径的限流
     *  方法名称与yml中key-resolver的值对应
     */
    @Bean
    public KeyResolver pathKeyResolver() {
        return exchange -> Mono.just(
                exchange.getRequest().getPath().toString()
        );
    }
    
    // 如果是其他的方式限流,代码如下,同时注意修改yml的key-resolver的值。 注意只能有一个@Bean生效,否则启动出错

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

    /**
     * 基于用户的限流(参数)
     */
//    @Bean
//    public KeyResolver userKeyResolver() {
//        return exchange -> Mono.just(
//                exchange.getRequest().getQueryParams().getFirst("user")
//        );
//    }
}

测试其他的限流,例如:基于用户参数的限流

注释其他Bean代码,让如下Bean生效

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

修改application.yml

                key-resolver: '#{@userKeyResolver}'

测试

http://localhost:8080/product-service/product/1?userId=1&access-token=1

访问达不到限流条件时

访问达到限流条件时

基于sentinel的限流

api_gateway_server服务添加如下依赖

		<!--sentinel限流-->
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
        </dependency>

添加配置类

package org.example.gateway.config;

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.GatewayParamFlowItem;
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.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.http.server.reactive.ServerHttpResponse;
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.nio.charset.StandardCharsets;
import java.util.*;

@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
	public void initGatewayRules() {
		Set<GatewayFlowRule> rules = new HashSet<>();
//		rules.add(
//			new GatewayFlowRule("order-service") //资源名称
//					.setCount(1) // 限流阈值
//					.setIntervalSec(1) // 统计时间窗口,单位是秒,默认是 1 秒
//		);
//		rules.add(new GatewayFlowRule("order-service")
//				.setCount(1)
//				.setIntervalSec(1)
//				.setParamItem(new GatewayParamFlowItem()
//						.setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM).setFieldName("id")
//				)
//		);
		//限流规则对访问路径匹配product-service开头的路径生效
		rules.add(new GatewayFlowRule("product-service")
				.setCount(1)
				.setIntervalSec(1)
		);
//		rules.add(new GatewayFlowRule("product_api")
//				.setCount(1)
//				.setIntervalSec(1)
//		);
//		rules.add(new GatewayFlowRule("order_api")
//				.setCount(1)
//				.setIntervalSec(1)
//		);
		GatewayRuleManager.loadRules(rules);
	}

//	@PostConstruct
//	public void initBlockHandlers() {
//		BlockRequestHandler blockRequestHandler = 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(blockRequestHandler);
//	}

//	@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/**").
//							setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
//				}});
//		ApiDefinition api2 = new ApiDefinition("order_api")
//				.setPredicateItems(new HashSet<ApiPredicateItem>() {{
//					add(new ApiPathPredicateItem().setPattern("/order-service/order"));
//				}});
//		definitions.add(api1);
//		definitions.add(api2);
//		GatewayApiDefinitionManager.loadApiDefinitions(definitions);
//	}
}

以上配置,根据路由(访问路径)进行限流,限流规则对访问路径匹配product-service开头的路径生效。

修改application.yml

  • 注释RequestRateLimiter filter相关配置
          filters:
#            - name: RequestRateLimiter
#              args:
#                # 以下上个配置项目报错,不能解析,但不影响使用
#                # 使用SpEL从容器中获取对象
#                #key-resolver: '#{@pathKeyResolver}'
#                key-resolver: '#{@userKeyResolver}'
#                # 令牌桶每秒填充平均速率
#                redis-rate-limiter.replenishRate: 1
#                # 令牌桶的上限
#                redis-rate-limiter.burstCapacity: 3
            - RewritePath=/product-service/(?<segment>.*), /$\{segment}
  •  注释掉redis相关配置
#  redis:
#    host: localhost
#    port: 6379
#    database: 0

为了防止启动服务报redis连接拒绝,注释掉redis依赖,同时注意刷新依赖生效

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

测试

启动eureka、product、gateway服务

访问

http://localhost:8080/product-service/product/1?access-token=1

访问超过阈值后,响应

Blocked by Sentinel: ParamFlowException

因为路径中包含product-service开头,符合限流规则,所以当访问超出哦阈值后,执行限流操作,看到如上默认的响应信息。

为了更加友好的限流提示,可以自定义响应信息,如下:

取消initBlockHandlers方法的注释

	/**
	 * 自定义限流提示
	 */
	@PostConstruct
	public void initBlockHandlers() {
		BlockRequestHandler blockRequestHandler = 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(blockRequestHandler);
	}

重启gateway服务

测试,访问超过限流阈值时,响应如下

以上是基于路由的限流规则,这个路由下的所有资源都受到这个限流规则限制,sentinel也支持自定义API限流规则,去掉initCustomizedApis()方法注释

/**
 * 自定义API限流规则
 *        1.定义分组,例如:product_api和order_api
 *        2.在initGatewayRules方法对小组配置限流规则
 */
@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/**").
                  setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
         }});
   ApiDefinition api2 = new ApiDefinition("order_api")
         .setPredicateItems(new HashSet<ApiPredicateItem>() {{
            add(new ApiPathPredicateItem().setPattern("/order-service/order"));
         }});
   definitions.add(api1);
   definitions.add(api2);
   GatewayApiDefinitionManager.loadApiDefinitions(definitions);
}

在initGatewayRules方法对小组配置限流规则

@PostConstruct
	public void initGatewayRules() {
		Set<GatewayFlowRule> rules = new HashSet<>();

		rules.add(new GatewayFlowRule("product_api")
				.setCount(1)
				.setIntervalSec(1)
		);
//		rules.add(new GatewayFlowRule("order_api")
//				.setCount(1)
//				.setIntervalSec(1)
//		);
		GatewayRuleManager.loadRules(rules);
	}

重启gateway服务

测试

访问超过阈值后,同样能够起到限流效果

网关高可用

所有请求都需要先经过网关才到具体的微服务,网关只有一个节点会存在单点故障,解决办法是使用多个网关服务构成网关集群,实现网关高可用,当一个网关服务不可用,还有其他网关服务可用。实现架构如下图所示:

为了方便测试,取消网关限流配置,注释@Configuration注解

//@Configuration
public class GatewayConfiguration {

重启网关服务(8080端口)

复制配置得到另一个网关服务

修改api_gateway_server网关服务的配置文件 application.yml,将端口号改为8081,通过选中运行中的GatewayServerApplication,右键-->Copy Configuration复制配置得到另一个网关服务运行实例

运行复制得到的网关服务(8081端口)

访问8080和8081均能访问到服务

http://localhost:8080/product-service/product/1?access-token=1

http://localhost:8081/product-service/product/1?access-token=1

官网下载并解压nginx安装包

修改nginx配置,进入conf目录,修改nginx.conf文件

注释掉原来location /的配置,添加如下配置

http {
   upstream gateway{
        server 127.0.0.1:8080;
        server 127.0.0.1:8081;
    }
    server {
        ...
	    location / {
            	proxy_pass http://gateway; //proxy_pass请求转发
        }
        ...
    }  
}

配置后,如图 

启动nginx

浏览器访问

http://localhost/product-service/product/1?access-token=1

如果需要改策略,可以修改nginx配置,配置案例如下

IP哈希(IP Hash)策略

upstream gateway {
        ip_hash;
        server 127.0.0.1:8080;
        server 127.0.0.1:8081;
    }

重启nginx,同一机器访问服务三次,均是8080网关提供服务

最少连接(Least Connections)策略

upstream gateway {
        least_conn;
        server 127.0.0.1:8080;
        server 127.0.0.1:8081;
    }

加权轮询(Weighted Round Robin)策略

upstream gateway {
        server 127.0.0.1:8080 weight=3;
        server 127.0.0.1:8081 weight=1;
    }

模拟故障,停掉一个网关服务

依然能访问,实现了网关的高可用。

完成!enjoy it!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1215063.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

解决requests库中UnicodeError异常的问题

摘要&#xff1a;本文介绍了使用requests库时可能遇到的UnicodeError异常&#xff0c;并提供了两种解决方法&#xff0c;以确保你的代码能够正常处理URL。 问题背景 在使用requests库时&#xff0c;当尝试获取类似’http://.example.com’这样的URL时&#xff0c;可能会遇到Un…

1、24 个常见的 Docker 疑难杂症处理技巧(一)

1Docker 迁移存储目录 默认情况系统会将 Docker 容器存放在 /var/lib/docker 目录下 [问题起因] 今天通过监控系统&#xff0c;发现公司其中一台服务器的磁盘快慢&#xff0c;随即上去看了下&#xff0c;发现 /var/lib/docker 这个目录特别大。由上述原因&#xff0c;我们都知…

草图一键生成静态网页,看看这个开源项目

借助GPT-4V视觉模型&#xff0c;可以轻松的将一张草图生成一个静态页面。现在这已经不是什么稀奇事了。主要是分享一下它的Prompt&#xff0c;很简单&#xff0c;用户画好草图后&#xff0c;将草图保存成png图片&#xff0c;传给GPT-4V&#xff0c;然后GPT返回一个标准的HTML&a…

解决 requests 2.28.x 版本 SSL 错误

最近&#xff0c;在使用requests 2.28.1版本进行HTTP post传输时&#xff0c;您可能遇到了一个问题&#xff0c;即SSL验证失败并显示错误消息(Caused by SSLError(SSLCertVerificationError(1, [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get loc…

了解七大经典排序算法,看这一篇就足够了!!!

✏️✏️✏️好&#xff0c;那么今天给大家分享一下七大经典排序算法&#xff01; 清风的CSDN博客 &#x1f61b;&#x1f61b;&#x1f61b;希望我的文章能对你有所帮助&#xff0c;有不足的地方还请各位看官多多指教&#xff0c;大家一起学习交流&#xff01; 动动你们发财的…

代码随想录算法训练营第五十五天 | LeetCode 583. 两个字符串的删除操作、72. 编辑距离、编辑距离总结

代码随想录算法训练营第五十五天 | LeetCode 583. 两个字符串的删除操作、72. 编辑距离、编辑距离总结 文章链接&#xff1a;两个字符串的删除操作、编辑距离、编辑距离总结 视频链接&#xff1a;两个字符串的删除操作、编辑距离 1. LeetCode 583. 两个字符串的删除操作 1.1 思…

算法通关村第九关-白银挑战二分查找与高频搜索树

大家好我是苏麟,今天看看二分查找相关的题目 . 大纲 二分查找拓展问题山脉数组的峰顶索引 中序与搜索树二叉搜索树中的搜索验证二叉搜索树 二分查找拓展问题 山脉数组的峰顶索引 描述 : 符合下列属性的数组 arr 称为 山脉数组 &#xff1a; arr.length > 3存在 i&#…

【halcon】外观检测总结之灰度操作

1 灰度操作之 滞后延时 *滞后阈值 hysteresis_threshold (ImageInvert, RegionHysteresis, 190, 220, 3)这句话的意思就是&#xff0c;逐个判断这个图片区域里像素的灰度值&#xff0c;如果这个值小于190就不考虑了pass掉&#xff0c;如果大于220就直接入选。值在190和220之间…

企业商标信息查询API的优势和应用实例分析

前言 企业商标是企业在市场中的重要标识和竞争力的体现&#xff0c;而商标信息查询API则成为了企业品牌管理的重要工具。那么&#xff0c;这篇文章将详细阐述企业商标信息查询API的优势和应用实例分析。 企业商标信息API的优势 企业商标信息查询API的优势在于它可以快速、准…

通过Python脚本支持OC代码重构实践(三):数据项使用模块接入数据通路的适配

作者 | 刘俊启 导读 在软件开发中&#xff0c;经常会遇到一些代码问题&#xff0c;例如逻辑结构复杂、依赖关系混乱、代码冗余、不易读懂的命名等。这些问题可能导致代码的可维护性下降&#xff0c;增加维护成本&#xff0c;同时也会影响到开发效率。这时通常通过重构的方式对已…

还在为忘记BIOS密码担心?至少有五种方法可以重置或删除BIOS密码

忘记密码是一个我们都非常熟悉的问题。虽然在大多数情况下,只需单击“忘记密码”选项,然后按照几个简单的步骤即可恢复访问权限,但情况并非总是如此。忘记BIOS密码(通常为避免进入BIOS设置或避免个人计算机启动而设置的密码)意味着你将无法完全启动系统。 幸运的是,就像…

非对口专业测试人,婉拒猎头、放弃6份高薪offer,你敢信?

从非对口的国贸专业&#xff0c;步入测试之路&#xff1b;从红色旅游小城湘潭&#xff0c;迈入国际化都市上海。“明确方向-及时实践-谨慎选择-踏实扎根-计划未来”。她的每一步&#xff0c;都走得格外坚定有力......话不多说&#xff0c;让我们一起来看看这位小姐姐的成长故事…

揭秘2023年最热门的跨境电商源码趋势,你不能错过的关键信息

随着全球市场的不断扩大和国际贸易的加速&#xff0c;跨境电商源码正成为越来越多企业的首选。在本文中&#xff0c;我们将揭秘2023年跨境电商源码的最新趋势&#xff0c;为您带来关键信息&#xff0c;帮助您抓住机遇&#xff0c;实现商业成功。 2023年跨境电商源码趋势解析 …

JavaWeb-CSS

一、什么是CSS CSS&#xff08;Cascading Style Sheets&#xff0c;层叠样式表&#xff09;能够对网页中元素的位置排版进行精确的控制&#xff0c;拥有对网页对象和模型样式的编辑能力&#xff0c;简单来说就是页面美化。 CSS样式代码中的注释需要使用/**/。 二、CSS的引入方…

未来服务器操作系统的趋势与展望

摘要&#xff1a; 随着云计算、大数据和人工智能不断的发展&#xff0c;服务器操作系统也需要随之进行新一轮的升级。本文通过分析当前服务器操作系统的现状&#xff0c;探讨了未来服务器操作系统的趋势和展望&#xff0c;并针对一些关键问题提出了解决方案。 一、引言 服务器…

最全100种副业汇总,如果你没一份稳定的副业,那么要仔细看看了

为什么一定要有一份副业&#xff1f;三种人一定要有一份自己的副业&#xff0c;宝妈、大学生、死工资的上班族。因为有了一份优质的副业&#xff0c;您将不用依靠老公生活&#xff0c;您将不用向父母伸手要钱&#xff0c;您将不用时时刻刻看领导的颜色。 今天为大家总结了各类副…

折爱心教程(简单版本)

文章目录 1.折出双三角形2.向中心折叠3.形成正方形4.对折正反面相同5.向中心折6.外侧角向中心折7.顶部三角形向下折叠注意参考资料 我怎么也没有想到&#xff0c;身为混迹职场多年的老油子&#xff0c;竟然还能遇到折纸这种硬性task。 可是给的教程步骤省略太多了&#xff0c;看…

自动驾驶大模型,是怎么学习「世界知识」的?

近期&#xff0c;科技产业大佬不约而同地发出一个非常强烈的信号&#xff1a;自动驾驶走向完全的成熟&#xff0c;必须要被AI大模型重构。 中国工程院院士、清华大学教授、清华智能产业研究院&#xff08;AIR&#xff09;院长张亚勤认为&#xff0c;「自动驾驶是高度复杂的、最…

《QT从基础到进阶·二十九》QT,opencv源码调试

有时候我们在使用VS调试程序的bug&#xff0c;但发现程序崩溃的地方并不在我们写的程序中&#xff0c;我们通过调用堆栈发现程序崩溃的地方出现在QT或者opencv等源码中&#xff0c;那么我们怎么能把断点打到这些开源库中&#xff0c;下面提供一种办法&#xff1a; 解决方案–右…

老哥们平日是怎么排查线上问题的?

1、做好监控告警 如果线上出现了问题&#xff0c;我们更多的是希望由监控告警发现我们出了线上问题&#xff0c;而不是等到业务侧反馈。所以&#xff0c;我们需要对核心接口做好监控告警的功能。 2、定位报警层面 如果是业务代码层面的监控报警&#xff0c;那我们应该是可以…