Spring Cloud Zuul 网关

1. 网关

网关是为于服务最边缘的服务,直接暴露给用户,作为连接用户与服务之间的桥梁
zuul转为微服务设计的一个网关
它的功能有

  1. 路由到指定的服务
  2. 负载均衡
  3. 拦截
  4. 断路器
    在这里插入图片描述

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. 创建一个网关

  1. 新建springboot项目
    在这里插入图片描述
  2. 添加依赖
    Ribbon 和Hystix 已经默认集成在zuul 里面了
    在这里插入图片描述
  3. 在启动类上 添加注解
    @EnableZuulProxy
    在这里插入图片描述
  4. 修改配置文件 application.yml
server:
  port: 80
spring:
  application:
    name: gateway-server
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka
  1. 启动eureka-server,eureka-client
  2. 通过网关访问调用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做啥?

  1. 路由机制
  2. 限流机制
  3. 熔断
  4. 权限验证和登录拦截(有一点点,我们以后会使用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来实现,具体的架构图如下图所示
在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值