SpringCloud Zuul 网关
网关的概念
API Gateway 网关,是系统的唯一入口,处理非业务功能、统一过滤请求,提供路由、权限验证、监控、缓存、限流等功能。
统一接入
-
路由转发 /api/v1/user,/api/v1/order,....路由到不同的服务
-
AB测试、灰度测试
-
负载均衡 网关自带负载均衡器均衡功能,可根据负载均衡算法转发该服务的某个节点,因为要从服务中心拿节点列表,所以网关也要注册到注册中心上
-
容灾处理 某个下级服务集群不可用时,可以直接切断对该服务的请求
-
日志记录
流量监控
-
限流处理
-
服务降级
安全防护
-
权限验证
-
服务监控
-
网络隔离 网关可以将内网、外网隔开,服务节点在内网中,通过内网调用服务,速度快;用户通过外网(公网ip)访问网关
网关可以调用服务,是一个特殊的消费者,消费者的负载均衡、容错保护、服务监控、限流降级它都能做到。
如果网关进行了集群,需要Nginx进行负载均衡,来确定调用哪个网关节点。
主流网关
- zuul 和eureka、ribbon、hystrix一样,都是Netflix旗下的项目
- kong 基于nginx的网关
- nginx+lua nginx是一个高性能的HTTP和反向代理服务器,lua是脚本语言,让Nginx执行Lua脚本实现网关
使用Zuul进行路由转发
(1)新建子模块api-gateway作为网关服务,创建时只需勾选Spring Cloud Discovery -> Eureka Discovery Client ,Spring Cloud Routing -> Routing[Maintenance]
也可以手动加依赖:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-zuul</artifactId> </dependency>
网关要从Eureka Server获取服务节点列表,使用Eureka Client内置的Ribbon进行负载均衡,所以要添加Eureka Client的依赖,并进行Eureka Client相关配置
Zuul的依赖中已经包含了Hystrix的依赖。
(2)引导类
@SpringBootApplication @EnableEurekaClient @EnableZuulProxy //也可以使用@EnableZuulServer public class ApiGatewayApplication { public static void main(String[] args) { SpringApplication.run(ApiGatewayApplication.class, args); } }
@EnableZuulProxy中已经包含了Hystrix的注解@EnableCircuitBreaker。
默认使用轮询,如果要使用其它负载均衡策略,设置方式和Eureka Client的完全相同。
(3)配置文件
server:
port: 9000
spring:
application:
#服务名称
name: api-gateway
eureka:
client:
#注册中心地址
serviceUrl:
defaultZone: http://127.0.0.1:8761/eureka/
127.0.0.1:9000/user-service/api/v1/user/order/1 通过网关来访问服务,后面是服务名、服务接口
需要指定路由规则:
zuul:
routes:
#指定路由规则,key是服务名,value是映射地址
user-service: /api-gateway/user-service/**
#取消原来的映射
ignored-patterns: /*-service/**
127.0.0.1:9000/api-gateway/user-service/api/v1/user/order/1 使用网关作为唯一入口,127.0.0.1:9000/api-gateway可以统一拦截处理所有的请求。
根据服务接口/api/v1/user/order/{user_id}来识别服务,所以api-gateway后面不一定要是服务名,比如可以是user。
如果不取消原来的映射方式,原来的映射方式也可以访问。
也可以使用下面的方式指定:
zuul:
prefix: /api-gateway/
这种方式只是加一个前缀,127.0.0.1:9000/api-gateway/user-service/api/v1/user/order/1 ,后面依然是服务名。
Zuul 过滤请求
新建一个包filter,包下新建一个类,继承ZuulFilter
package com.chy.mall.apigateway.filter; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; import java.io.IOException; @Component //放到spring容器中 public class ValidateFilter extends ZuulFilter { //ZuulFilter是抽象类,需要实现里面的方法 /* 指定过滤类型(时机),"pre"是路由转发之前,"routing"是路由转发之时,"post"是返回响应给浏览器之前,"error"是发生错误时 前处理用pre,后处理用post */ @Override public String filterType() { return "pre"; } /* 要执行多个拦截器,需要指定这些拦截器调用的先后顺序,数值越小,优先级越高、越先执行 可以在FilterConstants类中查看Zuul自带的拦截器的Order,自定义拦截器的Order要参考它们进行设置,不能想设置多大就设置多大 前处理一般设置为0~9 */ @Override public int filterOrder() { return 0; } /* 是否要使用此过滤器,默认为false,需要改为true 如果需要关闭此拦截器,改为false即可 */ @Override public boolean shouldFilter() { return true; } /* 核心方法,用来过滤请求 */ @Override public Object run() { RequestContext currentContext = RequestContext.getCurrentContext(); //前处理,获取request HttpServletRequest request = currentContext.getRequest(); //获取参数 String token = request.getParameter("token"); //token要和数据库查到的进行比较,此处随便写一个代替 if (token==null || !token.equals("123456")) { //如果token错误,直接返回响应,不转发给服务。只是不转发给服务,此方法后面部分的代码还是会执行 currentContext.setSendZuulResponse(false); currentContext.setResponseStatusCode(400); //输出信息到浏览器 try { currentContext.getResponse().getWriter().write("token is invalid"); } catch (IOException e) { e.printStackTrace(); } } // 有时候可能要对指定的url进行拦截处理 // ip:port后面的部分,示例 /api-gateway/user-service/api/v1/user/order/1 String requestURI = request.getRequestURI(); if (requestURI.contains("/api-gateway/user-service/......")){ //...... } if (requestURI.equals("/api-gateway/user-service/.....")){ //...... } return null; } }
不建议使用大量的过滤器,因为会加大时间开销,拉低性能。
网关中一般写通用的过滤,比如校验用户是否登录;如果是对部分服务进行过滤,最好在shouldFilter()中就判断请求地址。
如果只是个别服务的过滤,写在服务自身中。
zuul 限流
系统性能都是有限的,过多的请求|访问量会冲垮应用,所以一般都要做限流。
hystrix是服务层的限流,zuul可以使用谷歌的guava框架对请求进行限流,zuul的限流是网关层的限流,也可以在nginx上进行限流。
示例:zuul -> 服务A -> 服务B ,zuul使用guava对服务A进行限流,服务A使用hystrix限流A本身对服务B的调用请求
zuul本身已经集成了guava,直接用即可
package com.chy.mall.apigateway.filter; import com.google.common.util.concurrent.RateLimiter; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; @Component public class UserServiceLimitFilter extends ZuulFilter { //RateLimiter是com.google.common.util.concurrent.RateLimiter,不要导错了 //指定最大访问量,数值一般是压测得到的qps上限,此处设置为1000 private static final RateLimiter RATE_LIMITER = RateLimiter.create(1000); @Override public String filterType() { return "pre"; } @Override public int filterOrder() { //限流的过滤器放在自定义前处理的最前面 return -4; } @Override public boolean shouldFilter() { RequestContext currentContext = RequestContext.getCurrentContext(); HttpServletRequest request = currentContext.getRequest(); String requestURI = request.getRequestURI(); //设置要要限流的服务,此处限流的服务是user-service,通过zuul请求user-service的qps不能超过1000
//如果多个服务的qps上限都一样,可以一起设置 if (requestURI.contains("/api-gateway/user-service/")){ return true; } return false; } @Override public Object run() { RequestContext currentContext = RequestContext.getCurrentContext(); //如果超过设置的上限,直接返回响应,不转发给服务 if (!RATE_LIMITER.tryAcquire()){ //直接返回响应,不转发给服务 currentContext.setSendZuulResponse(false); //返回给浏览器的状态码是429 过多请求,可以使用常量,也可以直接写int型的状态码 currentContext.setResponseStatusCode(HttpStatus.TOO_MANY_REQUESTS.value()); } return null; } }
zuul 请求头过滤
Zuul默认会对请求头进行过滤处理:
private Set<String> sensitiveHeaders = new LinkedHashSet(Arrays.asList("Cookie", "Set-Cookie", "Authorization"));
默认会将请求头中的这3部分剔除,再将请求转发给服务,所有在服务中是取不到cookie的。
需要设置一下:
zuul:
routes:
#指定路由规则,前面是服务名,后面是映射地址
user-service: /api-gateway/user/**
#取消原来的映射
ignored-patterns: /*-service/**
#取消对请求头的默认处理
sensitive-headers:
配置文件中设置了此字段,会调用setter方法设置此字段。
不设置值,即为null,用null覆盖默认值。
zuul 上传文件限制
zuul默认对请求大小、上传文件的大小有限制,需要设置一下
spring:
application:
#服务名称
name: api-gateway
servlet:
multipart:
#设置请求的最大尺寸,单位M,默认10
max-request-size: 1024
#上传文件允许的单个文件的最大尺寸,单位M,默认1
max-file-size: 1024
zuul 集群
使用网关之后,ajax的url、<a>链接的href、<form>的action、服务调用地址,都要写成网关地址,通过网关统一访问,不再直接访问服务。
zuul集群之后,使用nginx做负载均衡,由nginx确定将请求转发到哪个网关节点,所有的请求都向nginx发起,上面的那些地址也都要写成nginx的地址。
如果只有一台nginx服务器,这台nginx挂了之后系统就不可用了,所以往往要用 nginx+lvs+keepalive 实现nginx的高可用。