网关
- Kong
- Zuul
- Traefik
- Spring Cloud Gateway
- Nginx+lua
一,Nginx
简单配置一下,
二,Zuul
1.介绍
Zuul是Netflix开源的微服务网关,他可以和Eureka,Ribbon,Hystrix等组件配合使用。Zuul组件的核心是一系列的过滤器,这些过滤器可以完成以下功能:
- 身份认证和安全:识别每一个资源的验证要求,并拒绝那些不符的请求
- 审查与监控:在服务边界追踪并统计数据,提供精确的生产视图
- 动态路由:动态将请求路由到不同后端集群
- 压力测试:逐渐增加指向集群的流量,以了解性能
- 负载分配:为每一种负载类型分配对应容量,并弃用超出限定值的请求
- 静态响应处理:边缘位置进行响应,避免转发到内部集群
- 多区域弹性:跨域AWS Region进行请求路由,旨在实现ELB(ElasticLoad Balancing)使用多样化
2.搭建Zuul网关服务器
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
<version>2.0.7.RELEASE</version>
</dependency>
//开启zuul网关功能
@EnableZuulProxy
server:
port: 8080
spring:
application:
name: api_zuul_server
3.路由
zuul:
prefix: /yyong
ignored-services: "*"
routes:
mydept: #路由id,随便写
serviceId: mydept-service #指定Eureka注册中心中的服务id
path: /mydept/**#映射路径
- 添加eureka的依赖
- 开启服务发现
- 相当于大门
4.过滤器
/**
* 自定义的zuul过滤器
* 继承抽象父类
*/
@Component
public class LoginFilter extends ZuulFilter {
/**
* 定义过滤器类型
* pre:转发到微服务之前执行的过滤器
* routing:在路由请求是执行的过滤器
* post:执行微服务获取返回值之后执行的过滤器
* error:在整个阶段抛出异常的时候执行的过滤器
* @return
*/
@Override
public String filterType() {
return "pre";
}
/**
* 指定过滤器的执行顺序
* 返回值越少,执行顺寻越高
* @return
*/
@Override
public int filterOrder() {
return 1;
}
/**
* 当前过滤器是否生效
* true:使用此过滤器
* flase:不使用此过滤器
* @return
*/
@Override
public boolean shouldFilter() {
return true;
}
/**
* 指定过滤器中的业务逻辑
* @return
* @throws ZuulException
*/
@Override
public Object run() throws ZuulException {
System.out.println("执行了过滤器");
return null;
}
}
小小的案例
/**
* 指定过滤器中的业务逻辑
* 身份认证:
* 1.所有的请求需要携带一个参数:access-token
* 2.获取request请求
* 3.通过request获取参数access-token
* 4.判断token是否为空
* 4.1 token==null:身份验证失败
* 4.2 token!==null:执行后续操作
* 在zuul网关中,通过RequestContext的上下文对象,可以获取对象request对象
* @return
* @throws ZuulException
*/
@Override
public Object run() throws ZuulException {
System.out.println("执行了过滤器");
//1.获取zuul提供的上下文对象RequestContext
RequestContext ctx = RequestContext.getCurrentContext();
//2.从RequestContext中获取request
HttpServletRequest request = ctx.getRequest();
//3.获取请求参数access-token
String token = request.getParameter("access-token");
//4.判断
if (token == null){
//4.1 token==null:身份验证失败
ctx.setSendZuulResponse(false);//拦截请求
ctx.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
}
//4.2 token!==null:执行后续操作
return null;
}
5.源码
三,Spring Cloud Gateway
在实际使用中我们会发现直接使用Zuul会存在诸多问题,包括:
- 性能问题
- 不支持任何长连接,如websocket
Zuul网关的替换方案
- zuul2.x
- Spring Cloud Gateway
1.路由配置
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
#配置SpringCloudGateway的路由
spring:
cloud:
gateway:
routes:
#配置路由:路由id,路由到微服务的uri,断言(判断条件)
- id: xxxx-dept #保持唯一
uri: http://127.0.0.1 #目标微服务请求地址
predicates:
- Path=/product/** #路由匹配条件
断言条件
2)动态路由(面向服务的路由):
#配置SpringCloudGateway的路由
spring:
cloud:
gateway:
routes:
#配置路由:路由id,路由到微服务的uri,断言(判断条件)
- id: xxxx-dept #保持唯一
# uri: http://127.0.0.1 #目标微服务请求地址
uri: lb://service-product #lb:// 根据微服务名称从注册中心中拉取服务请求路径
predicates:
- Path=/product-service/** #路由匹配条件
filters: #配置路由过滤器,http://127.0.0.1:8080/product-service/product/1 ---> http://127.0.0.1:9001/product/1
- RewritePath=/product-service/(?<segment>.*),/$\{segment} #路径重写的过滤器
#配置自动的根据微服务名称进行路由转发
discovery:
locator:
enabled: true #开启根据服务名称自动转发
lower-case-service-id: true #微服务名称已小写形式呈现
2.过滤器
1)过滤器的生命周期
- PRE,之前
- POST,之后
2)过滤器类型
- GatewayFilter:应用到单个路由或者一个分组的路由上
- GlobalFilter:应用到所有的路由上
/**
* 自定义一个全局过滤器
* 实现 globalfilter,ordered
*/
public class LoginGlobalFilter 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 1;
}
}
/**
* 执行过滤器的业务逻辑
* 对请求参数中的access-token进行判断
* 如果存在此参数:代表已经认证成功
* 如果不存在此参数:认证失败
* ServerWebExchange:相当于请求和响应的上下文(zuul中的RequestContext)
* @param exchange
* @param chain
* @return
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("执行了自定义的全局过滤器");
//1.获取请求参数
String first = exchange.getRequest().getQueryParams().getFirst("access-token");
//2.判断是否存在:
if(first == null){
//3.如果不存在:认证失败
System.out.println("没有登录");
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
//4.如果存在:继续执行
return chain.filter(exchange);//继续向下执行
}
3.统一鉴权
4.网关限流
1)常见的限流算法
(1)计数器:不平滑
(2)漏桶算法
(3)令牌桶算法
2)基于 Filter 的限流
(1)准备工作
redis
在工程中引入redis相应的依赖
<!--actuator监控信息完善-->
<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>
(2)修改网关中的application.yml配置
spring:
application: api-server #服务名称
redis:
host: 127.0.0.1
pool: 6379
database: 0
cloud: #配置SpringCloudGateway的路由
gateway:
routes:
- id: payment-router
uri: lb://payment-service
predicates:
- Path=/pay/**
filters:
# RequestRateLimiter:使用限流过滤器,是springcloud gateway提供的
# 参数 replenishRate:向令牌桶中填充的速率
# burstCapacity :令牌桶的容量
- name: RequestRateLimiter
args:
# 令牌桶每秒填充平均速率
redis-rate-limiter.replenishRate: 1
# 令牌桶的上限
redis-rate-limiter.burstCapacity: 5
# 使用SpEL从容器中获取对象
key-resolver: '#{@pathKeyResolver}'
(3)配置redis中解析器keySesolver
@Configuration
public class KeyResolverConfiguration {
/**
* 编写基于请求路径的限流规则
* //abc
* //基于请求ip 127.0.0.1
* //基于参数
* @return
*/
@Bean
public KeyResolver pathKeyResolver(){
//自定义的KeyResolver
return new KeyResolver(){
/**
* ServerWebExchange:
* 上下文参数
* @param exchange
* @return
*/
@Override
public Mono<String> resolve(ServerWebExchange exchange) {
return Mono.just(exchange.getRequest().getPath().toString());
}
};
}
/**
* 基于请求参数的限流
* 请求 abc ? userId=1
* @return
*/
@Bean
public KeyResolver userKeyResolver(){
return new KeyResolver(){
@Override
public Mono<String> resolve(ServerWebExchange exchange) {
return Mono.just(exchange.getRequest().getQueryParams().getFirst("userId"));
}
};
}
/**
* 基于请求ip的限流
* 请求 abc ? userId=1
* @return
*/
@Bean
public KeyResolver userKeyResolver(){
return new KeyResolver(){
@Override
public Mono<String> resolve(ServerWebExchange exchange) {
return Mono.just(exchange.getRequest().getHeaders().getFirst("X-Forwarded-For"));
}
};
}
}
3)基于 Sentinel 的限流
<!--sentinel限流-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
<version>1.6.3</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;
}
/**
* 配置限流的异常处理器:SentinelGatewayBlockExceptionHandler
* @return
*/
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler(){
return new SentinelGatewayBlockExceptionHandler(viewResolvers,serverCodecConfigurer);
}
/**
* 配置限流过滤器
* @return
*/
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public GlobalFilter sentinelGlobalFilter() {
return new SentinelGatewayFilter();
}
/**
* 配置初始化的限流参数
* 用户指定资源的限流的规则
* 1.资源名称(路由id)
* 2.配置统计时间
* 3.配置限流阈值
*/
@PostConstruct
public void initGatewayRules(){
Set<GatewayFlowRule> rules = new HashSet<>();
/* rules.add(
new GatewayFlowRule("order-service")//资源名称
.setCount(1)//限流阈值
.setIntervalSec(1)//统计事件窗口,单位秒,默认是一秒
);
rules.add(
new GatewayFlowRule("order-service")//资源名称
.setCount(1)//限流阈值
.setIntervalSec(1)//统计事件窗口,单位秒,默认是一秒
.setParamItem(new GatewayParamFlowItem()
.setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM).setFieldName("id"))
);*/
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(){
@Override
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
public 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);
}*/
}