一、为什么要用服务网关
乐队,每个人演奏自己的乐曲是一个服务,多个人使用同一种乐器就是一个微服务集群
指挥家是服务网关,做全局的统筹处理

二、它解决了什么问题

java 网关框架 java实现网关_java 网关框架

网关的职责:
1、统一入口:为全部微服务提供唯一入口点,网关起到外部和内部隔离,保障了后台服务的安全性
2、鉴权校验:识别每个请求的权限,拒绝不符合要求的请求
3、动态路由:动态的将请求路由到不同的后端集群中
4、减少客户端与服务的耦合:服务可以独立发展,通过网关层来做映射

三、网关的服务访问
比如直接访问服务是:
http://127.0.0.1:8011/InstanceInfo 访问网关:
http://127.0.0.1:8012/service-provider1/InstanceInfo
返回结果是一样的

四、网关的路由器4种规则

1、采用URL指定路由方法
URL匹配关键字,如果URL包含指定关键字就跳转到指定的URL中

zuul.routes.service-provider.path=/service-provider/**
zuul.routes.service-provider.url=http://127.0.0.1:8011/

访问:http://127.0.0.1:8012/service-provider/InstanceInfo

参数说明:
(1)
通配符:?
含义:匹配任意单个字符
举例:/service-provider/?
解释:匹配/service-provider/a,/service-provider/b,/service-provider/c等

(2)
通配符:*
含义:匹配任意数量的字符
举例:/service-provider/*
解释:匹配/service-provider/aaa,/service-provider/bbb,/service-provider/ccc等,无法匹配/service-provider/a/b/c

(3)
通配符:**
含义:匹配任意数量的字符
举例:/service-provider/**
解释:匹配/service-provider/aaa,/service-provider/bbb,/service-provider/ccc等,可以匹配/service-provider/a/b/c

2、采用服务指定路由方法
方式一
将路径的/service-provider/引到eureka的service-provider1服务上
规则:
zuul.routes.路径名.path=路径
zuul.routes.路径名.serviceId=eureka的服务名

zuul.routes.service-provider.path=/service-provider/**
zuul.routes.service-provider.serviceId=service-provider1

方式二
zuul.routes后面跟着的是服务名,服务名后面跟着的是路径规则,这种配置方式更简单

zuul.routes.service-provider1.path=/service-provider/**

3、路由的排除方法
方式一
排除某几个服务,多个服务逗号隔开

zuul.ignored-services=service-provider1

排除后这个地址为空http://127.0.0.1:8012/service-provider1/InstanceInfo

方式二
排除所有服务
由于服务太多,不可能手工一个个加,故路由排除所有服务,然后针对要路由的服务进行手工添加

zuul.ignored-services=*
zuul.routes.服务名.path=路径

方式三
排除指定关键字的路径
排除所有包含/list/的路径

zuul.ignored-patterns=/**/list/**
zuul.routes.service-provider1.path=/service-provider/**

4、路由的添加前缀方法

zuul.prefix=/api
zuul.routes.service-provider1.path=/service-provider/**

访问时必须改成http://127.0.0.1:8012/api/service-provider1/InstanceInfo

五、过滤器

1、如何定义自己的过滤器
添加LogFilter.java

package com.example.mycloud.run;

import javax.servlet.http.HttpServletRequest;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;

@Component
public class LogFilter extends ZuulFilter {

	private static final Logger logger = LoggerFactory.getLogger(LogFilter.class);
	
	@Override
	public boolean shouldFilter() {
		//开启过滤器,设置为true
		return true;
	}

	@Override
	public Object run() throws ZuulException {
		//过滤器的内容,打印请求的信息
		RequestContext rc = RequestContext.getCurrentContext();
		HttpServletRequest request = rc.getRequest();
		logger.info("method={},url={}", request.getMethod(), request.getRequestURL().toString());
		return null;
	}

	@Override
	public String filterType() {
		//过滤器的类型
		return "pre";
	}

	@Override
	public int filterOrder() {
		// TODO Auto-generated method stub
		return 0;
	}

}

日志: 

2022-06-09 17:01:42.086  INFO 22424 --- [nio-8012-exec-1] com.example.mycloud.run.LogFilter        : method=GET,url=http://127.0.0.1:8012/service-provider1/InstanceInfo

2、过滤器有哪些类型

2-1、filterType
过滤器的类型,用字符串来表示。在zuul中默认定义了四种不同生命周期的过滤器类型:
(1)pre
可以在请求被路由之前调用。一般用于身份权限验证、记录调用日志等等。
(2)routing
在路由执行之后被调用。
(3)post
在routing和error过滤器之后被调用。可用于信息收集、统计信息。例如性能指标,对response的结构做些特殊处理。
(4)error
处理请求时发生错误时被调用。用于异常处理封装。

2-2、filterOrder
用int值来定义过滤器的执行顺序,数值越小优先级越高。

2-3、shouldFilter
返回一个boolean类型来判断该过滤器是否要执行。

2-4、run
逻辑处理。一般2个用途:
第一,请求前拦截,对请求进行验证判断,如果请求无效就直接断路;如果有效可再加工处理。
第二,请求结果后处理,即对结果做一些加工处理。

3、http请求在过滤器的生命周期表

java 网关框架 java实现网关_ide_02

4、权限校验例子

@Override
	public Object run() throws ZuulException {
		//过滤器的内容,打印请求的信息
		RequestContext rc = RequestContext.getCurrentContext();
		HttpServletRequest request = rc.getRequest();
		logger.info("method={},url={}", request.getMethod(), request.getRequestURL().toString());
		
		//权限过滤例子
//		String token = request.getParameter("token");
//		if (token == null) {
//			logger.warn("token is null..........");
//			rc.setSendZuulResponse(false); //代表结束请求,不在继续下级传递
//			rc.setResponseStatusCode(401);
//			rc.setResponseBody("{\"result\":\"token is null\"}");
//			rc.getResponse().setContentType("text/html;charset=utf-8");
//		} else {
//			//校验token,redis验证
//			logger.info("token is ok");
//		}
		
		return null;
	}

5、采用网关过滤器对系统异常统一处理
添加异常类ErrorFilter.java

package com.example.mycloud.run;

import javax.servlet.http.HttpServletRequest;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;

@Component
public class ErrorFilter extends ZuulFilter {

	private static final Logger logger = LoggerFactory.getLogger(ErrorFilter.class);
	
	@Override
	public boolean shouldFilter() {
		//开启过滤器,设置为true
		return true;
	}

	@Override
	public Object run() throws ZuulException {
		//过滤器的内容,打印请求的信息
		RequestContext rc = RequestContext.getCurrentContext();
		HttpServletRequest request = rc.getRequest();
		logger.info("--------------------error--------------------");
		
		return null;
	}

	@Override
	public String filterType() {
		//过滤器的类型
		return "error";
	}

	@Override
	public int filterOrder() {
		// TODO Auto-generated method stub
		return 0;
	}

}

抛出异常:

throw new RuntimeException();

触发异常过滤器

会跳转到/error路径上,添加错误提示类:

package com.example.mycloud.run;

import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ErrorGatewayController implements ErrorController {

	@Override
	public String getErrorPath() {
		return "/error";
	}
	
	@RequestMapping("/error")
	public String error() {
		return "{\"result\":\"500 error!\"}";
	}

}

六、网关容错

1、zuul和hystrix无缝衔接

zuul默认集成了hystrix,访问:http://127.0.0.1:8012/actuator/hystrix.stream

java 网关框架 java实现网关_ide_03

2、网关如何实现服务降级
springboot2.1需要实现FallbackProvider接口
添加服务降级类ServiceProvider1Fallback.java

package com.example.mycloud.run;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;

import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Component;

@Component
public class ServiceProvider1Fallback implements FallbackProvider {

	@Override
	public String getRoute() {
		//代表为哪个服务fallback
		return "service-provider1";
	}

	@Override
	public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
		return new ClientHttpResponse() {

			@Override
			public InputStream getBody() throws IOException {
				//返回的文本信息
				String input = "服务不可用,请联系管理员!";
				return new ByteArrayInputStream(input.getBytes());
			}

			@Override
			public HttpHeaders getHeaders() {
				//http头部类型
				HttpHeaders header = new HttpHeaders();
				MediaType mt = new MediaType("application","json",Charset.forName("utf-8"));
				header.setContentType(mt);
				return header;
			}

			@Override
			public HttpStatus getStatusCode() throws IOException {
				//http状态码
				return HttpStatus.OK;
			}

			@Override
			public int getRawStatusCode() throws IOException {
				//http状态值
				return this.getStatusCode().value();
			}

			@Override
			public String getStatusText() throws IOException {
				//http状态文本
				return this.getStatusCode().getReasonPhrase();
			}

			@Override
			public void close() {
				// TODO Auto-generated method stub
				
			}
			
		};
	}

}

停止service-provider1服务,访问:http://127.0.0.1:8012/api/sp1/InstanceInfo

java 网关框架 java实现网关_spring_04

七、网关限流
限制某个IP请求流量

1、pom文件添加,zuul限流的jar包

<!-- zuul限流 -->
<dependency>
	<groupId>com.marcosbarbero.cloud</groupId>
	<artifactId>spring-cloud-zuul-ratelimit</artifactId>
	<version>2.4.2.RELEASE</version>
</dependency>

2、添加限流的配置

#开启限流
zuul.ratelimit.enabled=true
#60s内请求超过3次,服务端就抛出异常,60s后可以恢复正常请求
zuul.ratelimit.policies.service-provider.limit=3
zuul.ratelimit.policies.service-provider.refresh-interval=60
#针对某个IP进行限流,不影响其他IP
zuul.ratelimit.policies.service-provider.type=origin

3、限流配置说明
(1)zuul.ratelimit.enabled
默认值:
false
备注:
开启限流

(2)zuul.ratelimit.repository
默认值:
in_memory
备注:
支持的存储方式={redis, consul, jpa, in_memory}
in_memory=使用ConcurrentHashMap作为数据存储
consul=使用consul作为数据存储
redis=使用redis作为数据存储
jpa=使用数据库作为数据存储

(3)zuul.ratelimit.policies.ServiceId.refresh-interval
默认值:
60
备注:
刷新时间窗口的时间,默认值(秒)

(4)zuul.ratelimit.policies.ServiceId.limit
默认值:

备注:
每个刷新时间窗口对应的请求数量限制

(5)zuul.ratelimit.policies.ServiceId.quota
默认值:

备注:
单位时间内允许访问的总时间(统计每次请求的时间总和)
例子:refresh-interval=60、limit=10、quota=30,60秒内允许10个访问,并且要求总请求时间小于30秒

(6)zuul.ratelimit.policies.ServiceId.type
默认值:

备注:
type={origin, user, url}
origin=对访问IP进行限流(例如:某个IP每分钟只允许请求多少次)
url=对请求的url进行限流(例如:某个url每分钟只允许调用多少次)
user=对某些特定用户或者用户组进行限流(例如:非VIP用户限制每分钟只允许调用100次某个API等)

4、添加限流全局配置

zuul.ratelimit.enabled=true
zuul.ratelimit.default-policy.limit=3
zuul.ratelimit.default-policy.refresh-interval=60
zuul.ratelimit.default-policy.type=origin

八、网关的2层超时调优

1、原因

java 网关框架 java实现网关_限流_05

2、修改配置

#第一层hystirx超时时间设置
#默认情况下是线程池隔离,超时时间1000ms
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=60*1000
#第二层ribbon超时时间设置,设置比第一层小
#请求连接的超时时间,默认5s
ribbon.ConnectTimeout=10*1000
#请求处理的超时时间,默认5s
ribbon.ReadTimeout=10*1000