网关是什么
是一个网络整体系统中的前置门户入口。请求首先通过网关,进行路径的路由,定位到具体的服务节点上。是进入服务的统一大门。
网关分类
api开放网关
开放api(openApi) 企业需要将自身数据、能力等作为开发平台向外开放,通常会以rest的方式向外提供,最好的例子就是淘宝开放平台、腾讯公司的QQ开发平台、微信开放平台。 Open API开放平台必然涉及到客户应用的接入、API权限的管理、调用次数管理等,必然会有一个统一的入口进行管理,这正是API网关可以发挥作用的时候。
微服务网关
微服务的概念最早在2012年提出,在Martin Fowler的大力推广下,微服务在2014年后得到了大力发展。 在微服务架构中,有一个组件可以说是必不可少的,那就是微服务网关,微服务网关处理了负载均衡,缓存,路由,访问控制,服务代理,监控,日志等。API网关在微服务架构中正是以微服务网关的身份存在。
API服务管理平台
上述的微服务架构对企业来说有可能实施上是困难的,企业有很多遗留系统,要全部抽取为微服务器改动太大,对企业来说成本太高。但是由于不同系统间存在大量的API服务互相调用,因此需要对系统间服务调用进行管理,清晰地看到各系统调用关系,对系统间调用进行监控等。 API网关可以解决这些问题,我们可以认为如果没有大规模的实施微服务架构,那么对企业来说微服务网关就是企业的API服务管理平台。
网关作用
- 统一入口:未全部为服务提供一个唯一的入口,网关起到外部和内部隔离的作用,保障了后台服务的安全性。
- 鉴权校验:识别每个请求的权限,拒绝不符合要求的请求。
- 动态路由:动态的将请求路由到不同的后端集群中。
- 减少客户端与服务端的耦合:服务可以独立发展,通过网关层来做映射。
- 限流控制等
网关与过滤器区别
网关是拦截所有服务器请求进行控制
过滤器拦截某单个服务器请求进行控制
Nginx与Zuul的区别
Nginx是采用服务器负载均衡进行转发
Zuul依赖Ribbon和eureka实现本地负载均衡转发
相对来说Nginx功能比Zuul功能更加强大,能够整合其他语言比如lua脚本实现强大的功能,同时Nginx可以更好的抗高并发,Zuul网关适用于请求过滤和拦截等。
Zuul 网关的原理
springcloud 整合了 zuul,其实现是类似 过滤器和aop 的整合,一系列Filter展开的,这些Filter在整个HTTP请求过程中执行一连串的操作,zuul 整合了ribbon 和hystrix 提供服务。
Zuul 网关使用
- 启动 eureka 服务 : http://localhost:1008;
- 启动product 服务: http://localhost:3008
product 服务是注册到 eureka 的名称
product 服务接口: http://localhost:3008/product/test1
@RequestMapping("/product/test1")
public String test1(){
System.out.println("------test1 接口被调用啦,正在执行");
return "success return test1";
}
作为我们的测试服务,接下来是网关服务
maven 依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
<version>2.0.1.RELEASE</version>
</dependency>
<!-- SpringBoot整合eureka客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<version>2.0.1.RELEASE</version>
</dependency>
<!-- zuul网关的重试机制,不是使用ribbon内置的重试机制
是借助spring-retry组件实现的重试
开启zuul网关重试机制需要增加下述依赖
-->
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
<!-- 集成lombok 框架 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
启动类
/**
*
* @EnableZuulProxy - 开启Zuul网关。
* 当前应用是一个Zuul微服务网关。会在Eureka注册中心中注册当前服务。并发现其他的服务,不用添加其他的eureka注解了
*/
@SpringBootApplication
@EnableZuulProxy
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
配置路由
首先端口和eureka配置
server.port=4008
spring.application.name=zuul
eureka.client.serviceUrl.defaultZone = http://localhost:1008/eureka/
增加路由,先采用名称路由
zuul.routes.webshop-product.path=/api-product/**
zuul.routes.webshop-product.service-id=product
访问的方式是这样
http://zuulHostIp:port/要访问的服务名称/服务中的URL,
例如: http://localhost:3008/product/test1
访问: http://localhost:4008/api-product/product/test1
过滤器
- 前置过滤:是请求进入Zuul之后,立刻执行的过滤逻辑。
- 路由后过滤:是请求进入Zuul之后,并Zuul实现了请求路由后执行的过滤逻辑,
- 路由后过滤,是在远程服务调用之前过滤的逻辑。
- 后置过滤:远程服务调用结束后执行的过滤逻辑。
- 异常过滤:是任意一个过滤器发生异常或远程服务调用无结果反馈的时候执行的过滤逻辑。无结果反馈,就是远程服务调用超时。
实现父类来定义,具体如下
@Component
@Slf4j
public class PreFilter extends ZuulFilter {
/**
* 过滤器的类型。可选值有:
* pre - 前置过滤
* route - 路由后过滤
* error - 异常过滤
* post - 远程服务调用后过滤
*/
@Override
public String filterType() {
return "pre";
}
/**
* 同种类的过滤器的执行顺序。
* 按照返回值的自然升序执行。
*/
@Override
public int filterOrder() {
return 0;
}
/**
* 返回boolean类型。代表当前filter是否生效。
* 默认值为false。
* 返回true代表开启filter。
*/
@Override
public boolean shouldFilter() {
return true;
}
/**
* run方法就是过滤器的具体逻辑。
* return 可以返回任意的对象,当前实现忽略。(spring-cloud-zuul官方解释)
* 直接返回null即可。
*
* 使用过滤器验证客户端是否有登陆
*/
@Override
public Object run() throws ZuulException {
System.out.println("Pre 网关过滤执行");
// 通过zuul,获取请求上下文
RequestContext rc = RequestContext.getCurrentContext();
HttpServletRequest request = rc.getRequest();
log.info("LogFilter1.....method={},url={}",
request.getMethod(),request.getRequestURL().toString());
// 可以记录日志、鉴权,给维护人员记录提供定位协助、统计性能
String userToken = request.getParameter("userToken");
// if (StringUtils.isEmpty(userToken)) {
// rc.setSendZuulResponse(false);
// rc.setResponseStatusCode(401);
// rc.setResponseBody("userToken is null");
// return null;
// }
// 否则正常执行业务逻辑.....
return null;
}
}
现在启动 zuul 服务。访问 http://localhost:4008/api-product/product/test1,返回正常,已经路由到了product 服务。
容错机制
路由到目标服务有时候会出现网络不通等原因,造成失败,需要注意的是,只要有返回,就不会触发容错机制,不论是不是异常。
跳转 product服务,延时3秒返回
@RestController
public class TestController1 {
@RequestMapping("/product/test1")
public String test1(){
try {
Thread.sleep(3000); // 模拟故障,超时 3 秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("------test1 接口被调用啦,正在执行");
return "success return test1";
}
}
现在设置zuul 路由到product 时间是 2秒,增加配置
zuul.host.connect-timeout-millis=2000
增加容错代码
@Component
public class TestFallbackProvider implements FallbackProvider {
/**
* return - 返回fallback处理哪一个服务。返回的是服务的名称,
* 推荐 - 为指定的服务定义特性化的fallback逻辑。
* 推荐 - 提供一个处理所有服务的fallback逻辑。
* 好处 - 服务某个服务发生超时,那么指定的fallback逻辑执行。如果有新服务上线,未提供fallback逻辑,有一个通用的。
* 可以使用通配符‘*’代表为全部的服务提供容错处理。
*/
@Override
public String getRoute() {
return "product";
}
// 降级处理
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
System.out.println("进入 fallbackResponse");
// 设置降级给客户端返回的信息
return new ClientHttpResponse(){
@Override
public HttpHeaders getHeaders() {
HttpHeaders header = new HttpHeaders();
// 返回json数据
MediaType mt = new MediaType("application","json", Charset.forName("utf-8"));
header.setContentType(mt);
return header;
}
// 设置响应的内容
@Override
public InputStream getBody() throws IOException {
return new ByteArrayInputStream("product服务不可用,请于管理员联系!".getBytes("utf-8"));
}
@Override
public HttpStatus getStatusCode() throws IOException {
return HttpStatus.BAD_REQUEST;
}
@Override
public int getRawStatusCode() throws IOException {
return HttpStatus.BAD_REQUEST.value();
}
// 返回状态码
@Override
public String getStatusText() throws IOException {
return HttpStatus.BAD_REQUEST.getReasonPhrase();
}
@Override
public void close() {
}
};
}
}
访问 http://localhost:4008/api-product/product/test1,
前台返回 友好提示
product服务不可用,请于管理员联系!
zuul 限流
首先简单说一下 spring cloud zuul-ratelimit,他是外国人专门针对 zuul 编写的限流库,提供来4种限流策略,如下。
限流粒度/类型 | 说明 |
---|---|
User | 针对请求的用户进行限流 |
Origin | 针对请求的Origin进行限流 |
URL | 针对请求的接口限流 |
ServerId | 针对服务限流,默认的 |
多种粒度临时变量储存方式
存储方式 | 说明 |
---|---|
IN_MEMORY | 基于本地内存,底层是ConcurrentHashMap,默认的 |
REDIS | 基于redis存储,使用时必须搭建redis |
JPA | spring data jpa,基于数据库 |
BUKET4J | 使用一个Java编写的基于令牌桶算法的限流库 |
CONSUL | consul 的kv存储 |
如果 zuul 需要多节点部署,那就不能用 IN_MEMORY 存储方式,比较常用的就是用Redis。
增加依赖
<dependency>
<groupId>com.marcosbarbero.cloud</groupId>
<artifactId>spring-cloud-zuul-ratelimit</artifactId>
<version>1.3.4.RELEASE</version>
</dependency>
配置文件增加
# 全局限流配置
# 开启限流保护
zuul.ratelimit.enabled=true
# 60s内请求超过3次,服务端就抛出异常,60s后可以恢复正常请求
zuul.ratelimit.default-policy.limit=3
zuul.ratelimit.default-policy.refresh-interval=60
简单的设置了全局的限流
然后访问服务,当第四次的时候,返回
Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.
Sat May 09 15:35:08 CST 2020
There was an unexpected error (type=Too Many Requests, status=429).
429
这个地方,可以统一处理下异常,返回友好提示。
- 补充:局部限流
针对product 服务限流
# 开启限流保护
zuul.ratelimit.enabled=true
# hystrix-application-client服务60s内请求超过3次,服务抛出异常。
zuul.ratelimit.policies.product.limit=3
zuul.ratelimit.policies.product.refresh-interval=60
# 针对IP限流。
zuul.ratelimit.policies.product.type=origin