1. 网关
网关是为于服务最边缘的服务,直接暴露给用户,作为连接用户与服务之间的桥梁
zuul转为微服务设计的一个网关
它的功能有
- 路由到指定的服务
- 负载均衡
- 拦截
- 断路器
2. Nginx 和zuul的区别
他们都是网关
2.1 相同点
用户访问,也是先访问nginx/zuul ,不能直接访问里面的服务,他们都能做路由,负载均衡,以及限流
2.2 不同点
Nginx在做路由,负载均衡,限流之前,都有修改nginx.conf的配置文件,把需要负载均衡,路由,限流的规则加在里面
Eg:使用nginx 做tomcat的负载均衡
Nginx.conf
Upstream www.car.com {
Server ip:port;
Server ip:port;
}
Location / {
Proxy_pass http://www.car.com
}
但是zuul 不同,zuul 自动的负载均衡和路由,zuul 和eureka 高度集成,实现自动的路由,和Ribbon 结合,实现了负载均衡,zuul 也能轻易的实现限流和权限验证。
可以看出,zuul 就是为了spring cloud来专门设计的!
Nginx是C语言写的,zuul是Java语言写的,所以Nginx的性能比Zuul 高一点
3. 创建一个网关
- 新建springboot项目
- 添加依赖
Ribbon 和Hystix 已经默认集成在zuul 里面了
- 在启动类上 添加注解
@EnableZuulProxy
- 修改配置文件 application.yml
server:
port: 80
spring:
application:
name: gateway-server
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka
- 启动eureka-server,eureka-client
- 通过网关访问调用eureka-client的info方法
地址:http://localhost/provider/info;
provider是eureka-client的名称,因为网关的端口是80,所以端口可以省略
4. zuul的特殊的路由规则
zuul的路由规则默认是
http://zuul的地址:zuul的端口号/服务的名称/访问的接口;
我们可以自定义路由规则,只需修改配置文件
4.1 自定义路由的规则
zuul:
routes:
provider-router: # 随便写的,是一个唯一的,代表一个微服务的路由机制
service-id: provider # 该路由机制针对的是那个微服务
path: /pro/** # 访问怎么样的url ,才能被我匹配过来// http://localhost /pro/info
consumer-router:
service-id: consumer
path: /con/**
4.2 统一加一个前缀
prefix的值开头不要忘了加一个斜杠,否则会报错
zuul:
routes:
provider-router: # 随便写的,是一个唯一的,代表一个微服务的路由机制
service-id: provider # 该路由机制针对的是那个微服务
path: /pro/** # 访问怎么样的url ,才能被我匹配过来// http://localhost /pro/info
consumer-router:
service-id: consumer
path: /con/**
prefix: /v1
4.3 动态的路由机制
使用的较少,因为在zuul的内部,已经内置了默认的访问规则
/**
* 需要实现动态的路由机制,需要在容器里面注入一个对象
*/
@Bean
public PatternServiceRouteMapper patternServiceRouteMapper() {
// 第一个参数: 你进入那个微服务里面 (?<name>.*)-(?<version>v.*$)
// 第二个参数:你访问怎么样的url //使用正则表达式写 ${version}/${name}
return new PatternServiceRouteMapper("(?<name>.*)-(?<version>v.*$)", "${version}/${name}");
// v1/provider(访问的url地址) -> provider-v1(进该微服务里面)
}
5. zuul的全局拦截
Zuul是一个前端访问的唯一入口,我们可以在zuul 实现一个token的拦截验证,但是实际上我们不会使用zuul 来做权限的验证和拦截!
shouldFilter();方法的作用是拦截器是否失效,反回true生效,返回false不生效;
run();是拦截后做的需要做的事情;
filterType();它的返回值定义这个Filter是哪一种Filter,有种类型,分别为pre:调用服务之前拦截
router:调用服务时拦截
post:调用服务后拦截
error:调用服务出错时拦截
@Component
public class GateFilter extends ZuulFilter {
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
RequestContext currentContext = RequestContext.getCurrentContext();
HttpServletRequest request = currentContext.getRequest();
String token = request.getHeader("TOKEN");
if(token == null) {
currentContext.setResponseBody("token is null");
currentContext.setResponseStatusCode(401);
currentContext.setSendZuulResponse(false);
return null;
}
if(!"12345".equals(token)) {
currentContext.setResponseBody("token is error");
currentContext.setResponseStatusCode(401);
currentContext.setSendZuulResponse(false);
return null;
}
currentContext.setSendZuulResponse(true);
return null;
}
@Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return 0;
}
}
测试
6. zuul的限流
当有个恶意用户每min访问超过一定的次数后,我们可以对他实现限制,让它在一分钟内,只能访问n的次数!
比如频繁地访问github的页面就会返回如下页面,这就是一个限流的效果
限流的本质就是一个Filter,使用redis记录访问次数,使用滑动窗口的算法实现统计一段时间内访问的次数
为什么不使用map而使用redis来统计访问次数,因为在使用zuul集群的时候,如果使用map,多台zuul服务器之间不能共享访问次数,假设限流设置为60秒不能超过10次,那么一个用户在60秒内在一台服务器访问几次,在另一台服务器访问几次,又在那台服务器访问几次,这个用户在60秒内可能访问了不止10次,但是任然没有限流。
把访问次数放到redis中是zuul服务器之间是可以共享数据的,一个用户在60秒内访问次数达到了10次是一定会被限流的
需要依赖一个限流的包
<dependency>
<groupId>com.marcosbarbero.cloud</groupId>
<artifactId>spring-cloud-zuul-ratelimit</artifactId>
<version>LATEST</version>
</dependency>
因为要使用到redis来统计访问次数,所以还要依赖redis
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
修改配置文件application.yml
server:
port: 80
spring:
application:
name: gateway-server
redis:
host: 101.37.24.227
port: 6379
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka
zuul:
# routes:
# provider-router: # 随便写的,是一个唯一的,代表一个微服务的路由机制
# service-id: provider # 该路由机制针对的是那个微服务
# path: /pro/** # 访问怎么样的url ,才能被我匹配过来// http://localhost /pro/info
# consumer-router:
# service-id: consumer
# path: /con/**
# prefix: /v1
ratelimit:
enabled: true
repository: REDIS
behind-proxy: true
add-response-headers: true
default-policy-list: # 针对全局的配置,该配置对所有的微服务都有效果
- limit: 10 #optional - 次数的限制
quota: 1000 #optional - 时间的限制
refresh-interval: 60 # 60s
type: #optional
- origin # 针对某个ip地址(也就是一个客户端)
policy-list: # 对某些服务做特定的配置
provider: # 微服务的id
- limit: 5 #optional - request number limit per refresh interval window
quota: 1000 #optional - request time limit per refresh interval window (in seconds)
refresh-interval: 60 #default value (in seconds)
type: #optional
- origin # 针对某个ip地址(也就是一个客户端)
测试:
滑动窗口,在一定时间内(一个窗口)访问一定次数,对它实现限流
滑动窗口的算法实现,和Hystix 里面的统计断路器是否要打开是一个思路
7. zuul断路器
要实现断路器,需要实现一个接口:
之前实现:ZuulFallbackProvider
现在实现:FallbackProvider
@Component
public class ZuulFallBackProvider implements FallbackProvider{
@Override
public String getRoute() {
return "provider";
}
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
System.out.println("微服务为:" + route);
System.out.println("原因为:" + cause);
return new ClientHttpResponse() {
@Override
public InputStream getBody() throws IOException {
byte[] buf = "fall back error".getBytes();
return new ByteArrayInputStream(buf);
}
@Override
public HttpHeaders getHeaders() {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.set("reason", "server is down");
return httpHeaders;
}
@Override
public HttpStatus getStatusCode() throws IOException {
return HttpStatus.BAD_REQUEST;
}
@Override
public int getRawStatusCode() throws IOException {
return 400;
}
@Override
public String getStatusText() throws IOException {
return "断开连接";
}
@Override
public void close() {
}
};
}
}
熔断的测试
先正常调用provider ,然后突然把provider 挂断,它就会有熔断的效果
错误的类型为400
Fallback has started
8.我们在项目里面使用zuul做啥?
- 路由机制
- 限流机制
- 熔断
- 权限验证和登录拦截(有一点点,我们以后会使用OAuth2.0 +JWT 来实现权限验证和登录)
9. zuul源码分析
9.1 zuul在一次请求中做了哪些事?
用户将请求发送给zuul,zuul将请求路由到服务,服务处理请求将数据相应给zuul,zuul在将数据响应给用户
9.2 我们怎么实现zuul的效果
用户的访问进来->PreFilter->RouterFilter->PostFilter->用户
Zuul的核心本质就是Filter,Zuul的所有功能都在Filter 里面实现
Zuul 解析我们的url 来决定我们去访问那个微服务,是Filter做的
Zuul 去发请求访问微服务,也是Filter做的
Zuul 去给用户响应数据,也是Filter 做的
9.3 和spring的ServletFilter相比
Spring的Filter拦截后需要执行的代码在doFilter方法中
public void doFilter(ServletRequest request, ServletResponse response,FilterChain chain) throws IOException, ServletException; |
zuul的Filter拦截后需要执行的代码在run方法中
public Object run() throws ZuulException{} |
它们获取当前请求对象的方式不同!
在SevletFilter 里面非常简单,因为在doFilter 里面request 和response 都传递进来了response
ZuulFilter 通过RequestContext 共享访问的变量!在ZuulFilter 的获取方式如下
RequestContext currentContext = RequestContext.getCurrentContext(); |
9.4 用户怎么访问zuul
用户访问zuul 会首先进入zuul的什么东西里面?
用户访问SpringMvc 会先进哪里?DispatchSevlet 里面
在zuul里面也有一个请求转发器,叫ZuulServlet
9.5 源码分析
@Override
public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException {
try {
init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);
// 所有的请求都有建立
RequestContext context = RequestContext.getCurrentContext();
// 给当前的访问,设置了一个zuul的处理引擎
context.setZuulEngineRan();
try {
// 会进入pre的Route里面,执行里面所有的Filter
// preRoute主要做参数的校验,权限的验证,限流等
preRoute();
} catch (ZuulException e) {
// 在执行preRoute时出现异常则会进入error里面
error(e);
//在执行preRoute时出现异常则会进入error里面,然后再进入postRoute里面,是因为只有postRoute方法才能给用户响应数据
postRoute();
return;
}
try {
// zuul将用户的访问,转为zuul构造请求对微服务访问
route();
} catch (ZuulException e) {
error(e);
postRoute();
return;
}
try {
// zuul将微服务响应给它的数据响应给客户
postRoute();
} catch (ZuulException e) {
error(e);
return;
}
} catch (Throwable e) {
error(new ZuulException(e, 500, "UNHANDLED_EXCEPTION_" + e.getClass().getName()));
} finally {
RequestContext.getCurrentContext().unset();
}
}
9.6 zuul的执行流程的总结
ZuulServlet->ZuulServlet.xxxRouter->ZuulRunner.xxRouter-> FilterProcessor.getInstance().xxxRoute()
主要执行Filter的代码在FilterProcessor里面完成了
1 加载本质执行类型的Filter的列表
2 执行每一个Filter
FilterProcessor.processZuulFilter(Filter)->Filter.runFilter->Filter.run();
我们可以看出,filter的执行在FilterProcessor 里面完成的
9.6 zuul的几个比较重要的filter
Zuul里面的所有的功能都是通过Filter 来实现的?
现在我们考虑,哪些Filter比较重要:
Zuul通过那个Filter 去访问微服务了的?
Zuul是通过那个Filter 去给用户响应数据的?
Zuul在Route(类型) 阶段去访问微服务了
Zuul的Post(类型)阶段去给用户响应数据了
Filter在启动类中构造出来的,所以应找到一个配置类
在ZuulProxyAutoConfiguration 类中,我们来看一些关键的Filter
Pre类型的(不重要):
PreDecorationFilter
Route类型的:
RibbonRoutingFilter
Post类型的:
在ZuulServerAutoConfiguration里面注入了
SendResponseFilter
0 PreDecorationFilter(执行url解析,决定使用哪种路由的类型)/provider/
1 RibbonRoutingFilter(给微服务发请求)
2 SendResponseFilter(给用户响应数据)
9.6.1 PreDecorationFilter(不是很重要)
解析URL 地址来获取的当前要使用的是那个路由器:
9.6.2 RibbonRoutingFilter
9.6.3 SendResponseFilter
9.6.4 发送请求的细节
我们只知道它在RibbonRouting 里面发起请求,但是我们没有看见到底是怎么发请求?
HttpURLConnection
HttpClient
OkHttp
未来模式
Callable与Runnable的区别
1.Callable能接受一个泛型,然后在call方法中返回一个这个类型的值。而Runnable的run方法没有返回值
2.Callable的call方法可以抛出异常,而Runnable的run方法不会抛出异常。
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService threadPool = Executors.newFixedThreadPool(1);
// Callable 不仅在线程里面运行代码块,并且后面线程的执行结果我也需要
/**
* 你未来将有女朋友
*/
Future<String> submit = threadPool.submit(new Callable<String>() {
@Override
public String call() throws Exception {
Thread.sleep(5000); // 在子线程里面sleep了
return "我是翠花";
}
});
// Runnable 只是在线程里面运行代码,不关注该线程的返回值
// Future<?> submit2 = threadPool.submit(new Runnable() {
//
// @Override
// public void run() {
// System.out.println("我在执行");
// }
// });
// int i = 1 ;
// while(true) { // 在主线程里面天天看,有没有
// 中间可以学习
// Thread.sleep(1000);
// System.out.println("在这期间,我还学习了java"+(i++));
// if(submit.isDone()) {
// System.out.println("女朋友她来了");
// String string = submit.get();
// System.out.println("她是"+string);
// return ;
// }else {
// System.out.println("她还没有来,继续等");
// }
// }
/**
* 在Future ,主线程可以一直不要阻塞:因为我们可以通过 idDone来判断子线程是否执行完毕!若返回为true,代表它执行完毕了
* Future,还有一个方法:get方法,回去
*/
// 中间不能学习
String string = submit.get(); // 主线程直接等待子线程的完成 ,在等待的期间,主线程不做任何事情
System.out.println(string);
}
思考SpringMVC
它的本质是servlet,思考servlet的一部模式
前端发送请求到后端要经过DispathServlet,一个线程要按顺序先走处理器映射器,再走处理器适配器,最后走视图解析器,假设走适配器的时间较长,那么该线程在未得到结果之前无法回收,请求一旦多了,线程池的线程不够用了,别的请求就没办法进来了;
SpringMVC做了一个异步的设计,具体过程如下图,因为适配器消耗的时间长任务会在适配器的容器中堆积,那么使用多线程来处理
https://github.com/NymphWeb/nymph/查看springMVC的异步的实现
zuul的源码总结
zuul的功能的实现都是由Filter来实现的
ZuulServlet—>ZuulRunner—>FilterProcessor
实际上是由FilterProcessor来调用Filter的
RibbonRoutingFilter,给服务发送请求
SendResponseFilter,给用户响应数据
zuul集群的实现
zuul的集群的实现使用nginx来实现,具体的架构图如下图所示