一、服务网关
1.什么是服务网关?
服务网关相当于路由转发加过滤器。
(1)路由转发:接收一切外界请求,转发到后端的微服务上去;
(2)过滤器:在服务网关中可以完成一系列的横切功能,例如权限校验、限流以及监控等,这些都可以通过过滤器完成(其实路由转发也是通过过滤器实现的)。
2.为什么使用网关:
服务网关相当于路由转发加过滤器;由于每个服务引入了这个公共服务,那么相当于在每个服务中都引入了相同的权限校验的代码,使得每个服务的jar包大小无故增加。
由于每个服务都引入了这个公共服务,那么我们后续升级这个服务可能就比较困难,而且公共服务的功能越多,升级就越难,而且假设我们改变了公共服务中的权限校验的方式,想让所有的服务都去使用新的权限校验方式,我们就需要将之前所有的服务都重新引包,编译部署。
3.网关解决了什么问题:
网关的职责:
(1)统一入口:为全部微服务提供唯一入口,网关起到外部和内部的隔离,保障了后台服务的安全性。
(2)鉴权校验:识别每个请求的权限,拒绝不复合要求的请求。
(3)动态路由:动态的将请求路由到不同的后端集群中。
(4)减少客户端与服务的耦合,服务何以独立发展,通过网关层来做映射。
4.创建网关服务简单案例:
-
创建项目:
- 修改POM文件添加Zuul的启动依赖:
org.springframework.boot
spring-boot-starter-web
org.springframework.cloud
spring-cloud-starter-eureka
org.springframework.cloud
spring-cloud-starter-zuul
- 修改配置文件:
spring.application.name=zuul-gateway
server.port=9010
#设置服务注册中心地址
eureka.client.serviceUrl.defaultZone=http://admin:123456@eureka1:8761/eureka/,http://admin:123456@eureka2:8761/eureka/
- 修改启动类添加@EnableZuulProxy注解:
@EnableZuulProxy
@SpringBootApplication
public class ZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class, args);
}
}
-
调用的服务为设计模式中Product-Provider服务:
- 使用服务网关访问服务的URL格式:
http://网关服务地址:网关服务端口/访问的服务的名称/访问的服务中的接口的地址;
二、路由器的四种路由规则
(1)使用URL指定路由方式;
(2)使用服务名称指定路由方式;
(3)路由的排除方法;
(4)路由的添加前缀方法;
1.创建项目:
调用的服务还是Product-Provider;配置路由规则主要是修改配置文件;
- 配置文件:
spring.application.name=zuul-gateway-route
server.port=9011
#设置服务注册中心地址
eureka.client.serviceUrl.defaultZone=http://admin:123456@eureka1:8761/eureka/,http://admin:123456@eureka2:8761/eureka/
2.采用URL指定:
##################### 路由指定:URL 指定 ##################
# URL 匹配关键字,如果包含关键字就跳转到指定的 URL 中
zuul.routes.e-book-product-provider.path=/aaa/**
zuul.routes.e-book-product-provider.url=http://127.0.0.1:9001/
3.使用服务名称指定:
#################### 路由指定:服务指定 1 ##############
#将路径的/suibian/引到 eureka 的 e-book-product-provider 服务 上
#规则:zuul.routes.路径名.path ##规则:zuul.routes.路径名.serviceId=eureka 的服务名
zuul.routes.e-book-product-provider.path=/bbb/**
zuul.routes.e-book-product-provider.serviceId=e-book-product-provider
###############路由指定:服务指定 2#############
#zuul.routes 后面跟着的是服务名,服务名后面跟着的是路径规则,这种 配置方式更简单。
zuul.routes.e-book-product-provider.path=/ccc/**
4.路由排除:
###############路由排除:排除某几个服务##############
#排除后,这个地址将为空 http://127.0.0.1:9030/e-book-product-provider/product/findAll
#多个服务逗号隔开
zuul.ignored-services=e-book-product-provider
###############路由排除:排除所有服务 ###################
#由于服务太多,不可能手工一个个加,故路由排除所有服务,然后针对要 路由的服务进行手工加
zuul.ignored-services=*
zuul.routes.e-book-order-provider.path=/e-book-order-provider/**
################路由排除:排除指定关键字的路径 ###############
#排除所有包括/finaAll/的路径
zuul.ignored-patterns=/**/findAll/**
zuul.routes.e-book-order-provider.path=/aaa/**
5.添加前缀:
## http://127.0.0.1:9030/suibian/product-provider/product/findAll
zuul.prefix=/aaa
zuul.routes.e-book-product-provider.path=/product-provider/ **
三、网关过滤器
1.自定义网关过滤器:
-
创建项目:
- 修改POM文件:
org.springframework.boot
spring-boot-starter-web
org.springframework.cloud
spring-cloud-starter-eureka
org.springframework.cloud
spring-cloud-starter-zuul
- 修改配置文件:
spring.application.name=zuul-gateway-filter
server.port=9011
#设置服务注册中心地址
eureka.client.serviceUrl.defaultZone=http://admin:123456@eureka1:8761/eureka/,http://admin:123456@eureka2:8761/eureka/
- 修改启动类:
@EnableZuulProxy
@SpringBootApplication
public class ZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class, args);
}
}
- 创建Filter继承ZuulFilter:
@Component
public class LogFilter extends ZuulFilter {
private static final Logger logger = LoggerFactory.getLogger(LoggerFactory.class);
// 是否开启过滤器,默认是false关闭
@Override
public boolean shouldFilter() {
return true;
}
// 过滤的内容,在run方法中编写过滤逻辑
@Override
public Object run() {
// 获取请求上下文
RequestContext currentContext = RequestContext.getCurrentContext();
HttpServletRequest request = currentContext.getRequest();
logger.info("LogFilter....method={},url={}", request.getMethod(), request.getRequestURL().toString());
return null;
}
// 过滤器类型,通过过滤器类型决定了过滤器执行的时间
@Override
public String filterType() {
return "pre";
}
// 过滤器的执行顺序:通过整数表示顺序,数值越小,优先级越高
@Override
public int filterOrder() {
return 0;
}
}
-
测试:
2.过滤器的类型:
Zuul的中心是一系列过滤器,能够在HTTP请求和响应的路由过程中执行一系列操作。
方法 | 作用 |
---|---|
FilterType | 表示过滤器的类型,使用字符串表示,在Zuul中默认定义了四种不同生命周期的过滤器类型 |
FilterOrder | 使用int值定义过滤器的执行顺序,数值越小优先级越高 |
ShouldFilter | 返回一个boolean类型来判断该过滤器是否执行 |
run | 逻辑处理,两个用途;第一,请求拦截,对请求进行验证判断,如果请求无效就直接断路,如果有效再加工处理;第二,请求结果后处理,对结果做一些加工处理 |
FilterType类型 | 作用 |
---|---|
Pre | 可以在请求被路由之前调用。一般用于身份权限验证、记录调用日志 |
Routing | 在路由请求时候被调用 |
Post | 在route和error过滤器之后被调用。用于信息手机、统计信息 |
Error | 处理请求时发生错误时被调用。用于异常处理封装 |
-
Zuul请求的生命周期:
3.采用网关过滤器实现权限验证:
-
创建项目:
- 修改POM文件:
org.springframework.boot
spring-boot-starter-web
org.springframework.cloud
spring-cloud-starter-eureka
org.springframework.cloud
spring-cloud-starter-zuul
- 修改配置文件:
spring.application.name=zuul-gateway-example
server.port=9012
#设置服务注册中心地址
eureka.client.serviceUrl.defaultZone=http://admin:123456@eureka1:8761/eureka/,http://admin:123456@eureka2:8761/eureka/
- 创建Filter继承ZuulFilter:
@Component
public class AccessFilter extends ZuulFilter{
private static final Logger logger = LoggerFactory.getLogger(AccessFilter.class);
@Override
public boolean shouldFilter() {
System.out.println("shouldFilter!!!");
return true;
}
@Override
public Object run() {
//获取请求信息
RequestContext context = RequestContext.getCurrentContext();
HttpServletRequest request = context.getRequest();
logger.info("---------pre---------");
//获取表单中的token
String token = request.getParameter("token");
//判断
if(token==null) {
logger.warn("token is null");
context.setSendZuulResponse(false);//表示请求结束。不在继 续向下请求
context.setResponseStatusCode(400);//响应的状态码
context.setResponseBody("{'result':'token is null'}");//响应的内容
context.getResponse().setContentType("text/html;charset=utf-8");
}else {
//访问redis服务进行验证
logger.info("token not null");
}
return null;
}
@Override
public String filterType() {
System.out.println("filterType!!!");
return "pre";
}
@Override
public int filterOrder() {
System.out.println("filterOrder!!!");
return 0;
}
}
- 修改启动类:
@EnableZuulProxy
@SpringBootApplication
public class ZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class, args);
}
}
-
测试:
4.网关过滤器执行的顺序和Post类型:
同一类型的过滤执行顺序和 filterOrder有关。
- 创建AccessFilter2:
@Component
public class AccessFilter2 extends ZuulFilter {
private static final Logger logger = LoggerFactory.getLogger(AccessFilter2.class);
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() {
// 获取请求信息
RequestContext context = RequestContext.getCurrentContext();
HttpServletRequest request = context.getRequest();
logger.info("---------pre2---------");
// 添加异常信息
throw new RuntimeException();
}
@Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return 2;
}
}
-
测试:
- 创建Post类型:
@Component
public class PostFilter extends ZuulFilter {
private static final Logger logger = LoggerFactory.getLogger(PostFilter.class);
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() {
// 获取请求信息
RequestContext context = RequestContext.getCurrentContext();
HttpServletRequest request = context.getRequest();
logger.info("---------Post---------");
return null;
}
@Override
public String filterType() {
return "post";
}
@Override
public int filterOrder() {
return 0;
}
}
-
测试:
5.使用网关过滤器对系统异常统一处理:
SpringBoot的默认异常处理映射为“/error”。BasicErrorController已经默认实现了“text/html”的处理,如果想返回自定义JSON格式信息,则实现“ErrorController ”接口。
- 创建ErrorFilter:
@Component
public class ErrorFilter extends ZuulFilter {
private static final Logger logger = LoggerFactory.getLogger(ErrorFilter.class);
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() {
// 获取请求信息
RequestContext context = RequestContext.getCurrentContext();
HttpServletRequest request = context.getRequest();
logger.info("---------Error---------");
return null;
}
@Override
public String filterType() {
return "error";
}
@Override
public int filterOrder() {
return 0;
}
}
- 创建ExceptionHandler实现ErrorController接口:
@RestController
public class ExceptionHandler implements ErrorController{
@Override
public String getErrorPath() {
return "/error";
}
@RequestMapping("/error")
public String error() {
return "{'error':'数据错误'}";
}
}
-
测试:
四、网关容错
1.Zuul和Hystrix无缝整合:
在 zuul 的 jar 包中包含了 hystrix 的 jar 包。所以我们不需要在项目中添加 Hystrix 的坐标。
- 访问网关服务的数据监控流:
http://ip:端口号/hystrix.stream;
-
使用dashboard服务通过可视化的界面监控数据 :
2.在网关中实现服务的降级处理:
-
创建项目:
- 修改POM文件:
org.springframework.cloud
spring-cloud-dependencies
Dalston.SR5
pom
import
org.springframework.boot
spring-boot-starter-web
org.springframework.cloud
spring-cloud-starter-eureka
org.springframework.cloud
spring-cloud-starter-zuul
- 修改配置文件:
spring.application.name=zuul-gateway
server.port=9011
#设置服务注册中心地址
eureka.client.serviceUrl.defaultZone=http://admin:123456@eureka1:8761/eureka/,http://admin:123456@eureka2:8761/eureka/
- 添加Fallback类实现ZuulFallbackProvider接口:
@Component
public class ProductFallback implements ZuulFallbackProvider {
// 给定的服务名称,为服务做降级处理
@Override
public String getRoute() {
return "e-book-product-provider";
}
// 当服务无法执行时,该方法返回拖地数据
@Override
public ClientHttpResponse fallbackResponse() {
return new ClientHttpResponse() {
// 设置响应的头信息
@Override
public HttpHeaders getHeaders() {
HttpHeaders httpHeaders = new HttpHeaders();
MediaType type = new MediaType("application", "json", Charset.forName("utf-8"));
httpHeaders.setContentType(type);// 设置编码
return httpHeaders;
}
// 设置响应体
@Override
public InputStream getBody() throws IOException {
String content = "Product服务不可用,请联系管理员!";
return new ByteArrayInputStream(content.getBytes());
}
// fallback的状态码,返回的String类型
@Override
public String getStatusText() throws IOException {
return getStatusCode().getReasonPhrase();
}
// fallback的状态码,返回的HttpStatus类型
@Override
public HttpStatus getStatusCode() throws IOException {
return HttpStatus.OK;
}
// fallback的状态码,返回的int类型
@Override
public int getRawStatusCode() throws IOException {
return getStatusCode().value();
}
@Override
public void close() {
}
};
}
}
- 修改启动类:
@EnableZuulProxy
@SpringBootApplication
public class ZuulFallBackApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulFallBackApplication.class, args);
}
}
-
测试:
3.在高并发情况下,网关实现限流达到自我保护:
在规定时间内限制对服务端的请求次数,如果超过限制次数服务端会抛出异常,从而实现限流的效果;主要通过修改配置文件中的配置信息实现。
- 修改POM文件,添加zuul-ratelimit坐标:
org.springframework.cloud
spring-cloud-dependencies
Dalston.SR5
pom
import
org.springframework.boot
spring-boot-starter-web
org.springframework.cloud
spring-cloud-starter-eureka
org.springframework.cloud
spring-cloud-starter-zuul
com.marcosbarbero.cloud
spring-cloud-zuul-ratelimit
1.3.4.RELEASE
- 添加配置信息:
spring.application.name=zuul-gateway-ratelimt
server.port=9012
#设置服务注册中心地址
eureka.client.serviceUrl.defaultZone=http://admin:123456@eureka1:8761/eureka/,http://admin:123456@eureka2:8761/eureka/
#路由规则
zuul.routes.e-book-product-provider.path=/product/**
zuul.routes.e-book-product-provider.serviceId=e-book-product-provider
#路由规则
zuul.routes.e-book-order-provider.path=/order/**
zuul.routes.e-book-order-provider.serviceId=e-book-order-provider
- 修改启动类:
@EnableZuulProxy
@SpringBootApplication
public class ZuulFallBackApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulFallBackApplication.class, args);
}
}
- 实现全局限流修改配置文件:
#全局配置限流
zuul.ratelimit.enabled=true
#60s 内请求超过 3 次,服务端就抛出异常,60s 后可以恢复正常请求
zuul.ratelimit.default-policy.limit=3
zuul.ratelimit.default-policy.refresh-interval=60
#针对 IP 进行限流,不影响其他 IP
zuul.ratelimit.default-policy.type=origin
-
测试:
- 实现局部限流,修改配置文件:
#局部限流:针对某个服务进行限流
##开启限流
zuul.ratelimit.enabled=true
##60s 内请求超过 3 次,服务端就抛出异常,60s 后可以恢复正常请求
zuul.ratelimit.policies.e-book-product-provider.limit=3
zuul.ratelimit.policies.e-book-product-provider.refresh-int erval=60
##针对某个 IP 进行限流,不影响其他 IP
zuul.ratelimit.policies.e-book-product-provider.type=origin
-
网关限流参数:
4.Zuul性能调优,网关的两层超时调优:
主要是对Hystrix和Ribbon超时问题造成请求连接超时;设置两层超时调优Ribbon的超时时间必须要小于Hystrix的超时时间。
-
创建项目:
修改POM文件,添加Zuul依赖:
org.springframework.cloud
spring-cloud-dependencies
Dalston.SR5
pom
import
org.springframework.boot
spring-boot-starter-web
org.springframework.cloud
spring-cloud-starter-eureka
org.springframework.cloud
spring-cloud-starter-zuul
- 修改配置文件:
spring.application.name=zuul-gateway
server.port=9014
#设置服务注册中心地址
eureka.client.serviceUrl.defaultZone=http://admin:123456@eureka1:8761/eureka/,http://admin:123456@eureka2:8761/eureka/
#第一层 hystrix 超时时间设置
#默认情况下是线程池隔离,超时时间 1000ms
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=8000
#第二层 ribbon超时时间设置:设置比第一层小
#请求连接的超时时间: 默认 5s
ribbon.ConnectTimeout=5000
# 请求处理的超时时间: 默认 5s
ribbon.ReadTimeout=5000
- 修改启动类:
@EnableZuulProxy
@SpringBootApplication
public class ZuulTimeoutApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulTimeoutApplication.class, args);
}
}
- 将服务的线程睡两秒模拟服务超时:
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
-
测试: