Zuul 网关是具体核心业务服务的看门神,相比具体实现业务的系统服务来说它是一个边缘服务,主要提供动态路由,监控,弹性,安全性等功能。在分布式的微服务系统中,系统被拆为了多套系统,通过zuul网关来对用户的请求进行路由,转发到具体的后台服务系统中。
本 Chat 主要内容如下:
网关是具体核心业务服务的看门神,相比具体实现业务的系统服务来说它是一个边缘服务,主要提供动态路由,监控,弹性,安全性等功能,下面我们从单体应用到多体应用的演化过程来讲解网关的演化历程。
一般业务系统发展历程都是基本相似的,从单体应用到多应用,从本地调用到远程调用。对应单体应用架构模式(如下图1),由于只需一个应用,所有业务模块的功能都打包为了一个 War 包进行部署,这样可以减少机器资源和部署的繁琐。
图1 单体应用
在单体应用中,网关模块是和应用部署到同一个jvm进程里面的,当外部移动设备或者web站点访问单体应用的功能时候,请求是先被应用的网关模块拦截的,网关模块对请求进行鉴权、限流等动作后在把具体的请求转发到当前应用对应的模块进行处理。
随着业务的发展,网站的流量会越来越大,在单体应用中简单的通过加机器的方式可以带来的承受流量冲击的能力也越来越低,这时候就会考虑根据业务将单体应用拆成若干个功能独立的应用,单体应用拆为多个应用后,由于不同的应用开发对应的功能,所以多应用开发之间可以独立开发而不用去理解对方的业务,另外不同的应用模块只承受对应业务流量的压力,不会对其他应用模块造成影响,这时候多体的分布式系统就出现了,如下图2。
图2 多体应用
如上图在多体应用中业务模块A和B单独起了个应用,每个应用里面有自己的网关模块,如果业务模块多了,那么每个应用都有自己的网关模块,这样复用性不好,所以可以考虑把网关模块提起出来,单独作为一个应用来做服务路由,如下图3:
如上图当移动设备发起请求时候是具体发送到网关应用的,经过鉴权后请求会被转发到具体的后端服务应用上,对应前端移动设备来说他们不在乎也不知道后端服务器应用是一个还是多个,他们只能感知到网关应用的存在。
Zuul是Netflix开源的一个网关组件,在Netflix内部系统中Zuul被用来作为内部系统的门面,如下图是Zuul在Netflix内部使用的一个架构图:
如上图最上层的移动设备或者网站首先通过aws负载均衡器把请求路由到zuul网关上,zuul网关则负责把请求路由到具体的后端service上。
Zuul开源地址 https://github.com/Netflix/zuul
Zuul网关的核心是一系列的过滤器,这些过滤器可以对请求或者响应结果做一系列过滤,Zuul 提供了一个框架可以支持动态加载,编译,运行这些过滤器,这些过滤器是使用责任链方式顺序对请求或者响应结果进行处理的,这些过滤器直接不会直接进行通信,但是通过责任链传递的RequestContext参数可以共享一些东西。
虽然Zuul 支持任何可以在jvm上跑的语言,但是目前zuul的过滤器只能使用Groovy脚本来编写。编写好的过滤器脚本一般放在zuul服务器的固定目录,zuul服务器会开启一个线程定时去轮询被修改或者新增的过滤器,然后动态进行编译,加载到内存,然后等后续有请求进来,新增或者修改后的过滤器就会生效了。
在zuul中过滤器分为四种:
如下图为zuul1.0的工作原理:
如上图,当zuul接受到请求后,首先会由前置过滤器进行处理,然后在由路由过滤器具体把请求转发到后端应用,然后在执行后置过滤器把执行结果写会到请求方,当上面任何一个类型过滤器执行出错时候执行该过滤器。
本节作者使用zuul的版本:
...
....
总结:zuul1.0时候当zuul接受到一个请求后会同步执行前置过滤器、路由过滤器、后置过滤器,等执行完毕后在同步把结果返回为调用方,调用方在整个过程中是阻塞的。其实SpringBoot集成的zuul就是自己实现了个前置过滤器做选择路由,然后自己实现了个路由过滤器根据前置过滤器选择的路由具体做路由转发。
Netty作为高性能异步网络通讯框架,在dubbo,rocketmq,sofa等知名开源框架中都有使用,如下图zuul2.0使用netty server作为网关监听服务器监听客户端发来的请求,然后把请求转发到前置过滤器(inbound filters)进行处理,处理完毕后在把请求使用netty client代理到具体的后端服务器进行处理,处理完毕后在把结果交给后者过滤器(outbound filters)进行处理,然后把处理结果通过nettyServer写回客户端。
...
总: 在zuul1.0时候客户端发起的请求后需要同步等待zuul网关返回,zuul网关这边对每个请求会分派一个线程来进行处理,这会导致并发请求数量有限。而zuul2.0使用netty作为异步通讯,可以大大加大并发请求量。
一起来学SpringCloud之整合Zuul网关服务(二)
俗话说,人生如水。水是生命之源,水质决定生命质量。水是人体的重要组成部分。饮用安全的饮用水越来越受到公众的关注。这种现象也滋生了过滤水质的机器,叫做净水器。净水器靠的是滤芯过滤水中肉眼看不见的铁锈、泥沙、细菌,有效的过滤水质才能赢得大众的喜爱。那么家用净水器什么牌子好呢?家用净水器品牌排名有哪些?
1.本诗恩净水器
冰净水器优点:技术强,资质老,效果好。21世纪的今天,冰尊净水器、冰尊果蔬清洗机、冰尊空气净化器、冰尊扫地机器人、冰尊吸尘器、冰尊美容仪已经全面投放市场,堪称行业标杆。
冰尊是至高无上的贵族身份象征,一直是皇室贵族和高端人士的专属日用品。冰尊推出的每一款产品都代表着行业的权威,是“高端、大气、高档”的代名词。
尊冰产品采用弱碱净化、等离子净化、超声波消毒灭菌、HEPA过滤、双核内胆、无缝设计、多层分体压缩、纯天然物理清洁芯、高氧活化等先进技术,净化更干净、更健康!2.美的净水器
美的净水器比外观好,美的品牌是后盾。遍布全国的漂亮售后网络可以弥补质量的不足。但是在普通人眼里,美貌绝对是最有魅力的。美是一种凝聚力,一种信仰,一种价值认同。很多时候,我们不用知道净水器是什么,只要知道什么是美就行了。卖点:大品牌,购买趋势上升。
前言
大家好,一直以来我都本着用最通俗的话理解核心的知识点, 我认为所有的难点都离不开 基础知识 的铺垫。目前正在出一个SpringCloud长期系列教程,从入门到进阶, 篇幅会较多~
适合人群有一定的Java基础
想尝试微服务开发
有SpringBoot开发基础
想学习或了解SpringCloud
想提高自己的同学
大佬可以绕过 ~
背景如果你是一路看过来的,很高兴你能够耐心看完。之前带大家学了Springboot这门框架,熟练掌握了单体应用的开发,如今微服务开发盛行,对我们的技术要求也是越来越高,薪资也是令人兴奋。这个系列将会带大家学习SpringCloud微服务开发,我会带大家一步一步的入门,耐心看完你一定会有收获~
情景回顾上期带大家一起认识了Zuul微服务网关以及带大家体验了它的常用配置,本期学习Zuul网关过滤器,我们一起来看一下吧~
PRE过滤器本期代码依然沿用上期的模块。在实现之前,先给大家简单普及一下它的生命周期的各个阶段,一个有四个阶段 pre、post、route和error
PRE:PRE过滤器用于将请求路径与配置的路由规则进行匹配,以找到需要转发的目标地址,并做一些前置加工,比如请求的校验等;
ROUTING:ROUTING过滤器用于将外部请求转发到具体服务实例上去;
POST:POST过滤器用于将微服务的响应信息返回到客户端,这个过程种可以对返回数据进行加工处理;
ERROR:上述的过程发生异常后将调用ERROR过滤器。ERROR过滤器捕获到异常后需要将异常信息返回给客户端,所以最终还是会调用POST过滤器。
下面我们就手动实现一个pre过滤器
@Componentpublic class PreZuulFilter extends ZuulFilter {private final Logger log = LoggerFactory.getLogger(this.getClass());@Overridepublic String filterType() {return "pre";}@Overridepublic int filterOrder() {return 1;}@Overridepublic boolean shouldFilter() {// 进行跨域请求的时候,并且请求头中有额外参数,比如token,客户端会先发送一个OPTIONS请求来探测后续需要发起的跨域POST请求是否安全可接受// 所以这个请求就不需要拦截,下面是处理方式RequestContext requestContext = RequestContext.getCurrentContext();HttpServletRequest request = requestContext.getRequest();if (request.getMethod().equals(RequestMethod.OPTIONS.name())) {log.info("OPTIONS请求不做拦截操作");return false;}return true;}@Overridepublic Object run() {RequestContext requestContext = RequestContext.getCurrentContext();HttpServletRequest request = requestContext.getRequest();String userToken = request.getHeader("apikey");if (StringUtils.isBlank(userToken)) {log.warn("apikey为空");sendError(requestContext, 99001, "请传输参数apikey", null);return null;}String host = request.getRemoteHost();String method = request.getMethod();String uri = request.getRequestURI();log.info("请求URI:{},HTTP Method:{},请求IP:{}", uri, method, host);return null;}private void sendError(RequestContext requestContext, int status, String msg, String userToken) {requestContext.setSendZuulResponse(false); //不对请求进行路由requestContext.setResponseStatusCode(status);//设置返回状态码requestContext.setResponseBody(JSONObject.toJSonString(msg));//设置返回响应体requestContext.getResponse().setContentType("application/json;charset=UTF-8");//设置返回响应体格式,可能会乱码}}我们要实现的逻辑是run()部分,这个是不是很像我们之前给大家讲的Shiro拦截器,我们可以借助这一过滤器实现自己的特定功能
POST 过滤器post过滤器可以在请求转发后获取请求信息和响应,这里给大家写一个例子,可以用来记录请求日志
@Componentpublic class PostZuulFilter extends ZuulFilter {private final Logger log = LoggerFactory.getLogger(this.getClass());@Overridepublic String filterType() {return "post";}@Overridepublic int filterOrder() {return 2;}@Overridepublic boolean shouldFilter() {// 进行跨域请求的时候,并且请求头中有额外参数,比如token,客户端会先发送一个OPTIONS请求来探测后续需要发起的跨域POST请求是否安全可接受// 所以这个请求就不需要拦截,下面是处理方式RequestContext requestContext = RequestContext.getCurrentContext();HttpServletRequest request = requestContext.getRequest();if (request.getMethod().equals(RequestMethod.OPTIONS.name())) {log.info("OPTIONS请求不做拦截操作");return false;}// 如果前面的拦截器不进行路由,那么后面的过滤器就没必要执行if (!requestContext.sendZuulResponse()) {return false;}return true;}@Overridepublic Object run() throws ZuulException {RequestContext requestContext = RequestContext.getCurrentContext();InputStream stream = requestContext.getResponseDataStream();if (stream == null) {return null;}HttpServletRequest request = requestContext.getRequest();String requestParams = getRequestParams(requestContext, request);log.warn(requestParams);try {String responseBody = IOUtils.toString(stream);RequestContext.getCurrentContext().setResponseBody(responseBody);} catch (IOException e) {e.printStackTrace();}return null;}//获取请求参数,适用于POST请求/GET请求,以及参数拼接在URL后面的POST请求private String getRequestParams(RequestContext requestContext, HttpServletRequest request) {String requestParams = null;String requestMethod = request.getMethod();StringBuilder params = new StringBuilder();Enumeration<String> names = request.getParameterNames();if (requestMethod.equals("GET")) {while (names.hasMoreElements()) {String name = (String) names.nextElement();params.append(name);params.append("=");params.append(request.getParameter(name));params.append("&");}requestParams = params.delete(params.length() - 1, params.length()).toString();} else {Map<String, String> res = new HashMap<>();Enumeration<?> temp = request.getParameterNames();if (null != temp) {while (temp.hasMoreElements()) {String en = (String) temp.nextElement();String value = request.getParameter(en);res.put(en, value);}requestParams = JSON.toJSonString(res);}if (StringUtils.isBlank(requestParams) || "{}".equals(requestParams)) {BufferedReader br = null;StringBuilder sb = new StringBuilder("");try {br = request.getReader();String str;while ((str = br.readLine()) != null) {sb.append(str);}br.close();} catch (IOException e) {e.printStackTrace();} finally {if (null != br) {try {br.close();} catch (IOException e) {e.printStackTrace();}}}requestParams = sb.toString();}}return requestParams;}}Error 过滤器error过滤器是在服务网关出现异常的时候起作用的
@Componentpublic class ErrorZuulFilter extends ZuulFilter {private final Logger log = LoggerFactory.getLogger(this.getClass());@Overridepublic String filterType() {return "error";}@Overridepublic int filterOrder() {//需要在默认的 SendErrorFilter 之前return 5;}@Overridepublic boolean shouldFilter() {// 只有在抛出异常时才会进行拦截return RequestContext.getCurrentContext().containsKey("throwable");}@Overridepublic Object run() {try {RequestContext requestContext = RequestContext.getCurrentContext();Object e = requestContext.get("throwable");if (e instanceof ZuulException) {ZuulException zuulException = (ZuulException) e;// 删除该异常信息,不然在下一个过滤器中还会被执行处理requestContext.remove("throwable");// 响应给客户端信息HttpServletResponse response = requestContext.getResponse();response.setHeader("Content-type", "application/json;charset=UTF-8");response.setCharacterEncoding("UTF-8");PrintWriter pw = null;pw = response.getWriter();pw.write(JSONObject.toJSonString("系统出现异常"));pw.close();}} catch (Exception ex) {log.error("Exception filtering in custom error filter", ex);ReflectionUtils.rethrowRuntimeException(ex);}return null;}}上面就是常用的过滤器,大家可以启动服务请求一下试试, 也可以借助它实现一些自己想到的小功能,或者整合一些其它的工具库
结束语本期到这里就结束了, 总结一下,本节主要讲了常用的Zuul网关过滤器, 以及带大家实现了自定义的过滤器, 建议大家自己多去尝试 ~
下期预告俗话说,没有仪表盘的车不敢开, 微服务也是如此,这么多的服务,我怎么知道它的链路调用关系,下期就给大家介绍一下它的链路追踪方案, 其实开源的产品比较多,比如ZipKin,jaeger, 这里给大家介绍jeager, 它是uber开源的,go语言写的,很轻量,方便集成,下期就带大家集成一下。 关注我,不迷路, 下期不见不散 ~
更文时间工作日(周一 ? 周五)
周末不更 ?
节假日不定时更
往期内容我的博客(阅读体验较佳)
SpringBoot系列教程合集
一起来学SpringCloud之微服务概述
一起来学SpringCloud之整合Nacos
一起来学SpringCloud之服务间调用
一起来学SpringCloud之整合hystrix服务熔断和降级
一起来学SpringCloud之整合Ribbon
一起来学SpringCloud之整合Open Feign实现远程调用
一起来学SpringCloud之整合Alibabasentinel
一起来学SpringCloud之整合Zuul网关服务(一)
项目源码(源码已更新 欢迎star?)springboot-all
地址: https://github.com/qiuChengleiy/springboot-all.git
原文:https://juejin.cn/post/7099627466334928926