Spring Cloud技术分析(4)- spring cloud zuul

扫码关注公众号:Java 技术驿站

发送:vip
将链接复制到本浏览器,永久解锁本站全部文章

【公众号:Java 技术驿站】 【加作者微信交流技术,拉技术群】
免费领取10G资料包与项目实战视频资料

地址:http://tech.lede.com/

spring cloud zuul是netflix提供的一个组件,功能类似于nginx,用于反向代理,可以提供动态路由、监控、授权、安全、调度等边缘服务。

1. zuul是什么

微服务场景下,每一个微服务对外暴露了一组细粒度的服务。客户端的请求可能会涉及到一串的服务调用,如果将这些微服务都暴露给客户端,那么会增加客户端代码的复杂度。

参考GOF设计模式中的Facade模式,将细粒度的服务组合起来提供一个粗粒度的服务,所有请求都导入一个统一的入口,那么整个服务只需要暴露一个api,对外屏蔽了服务端的实现细节,也减少了客户端与服务器的网络调用次数。这就是api gateway。

20191123100105\_1.png

有了api gateway之后,一些与业务关系并不大的通用处理逻辑可以从api gateway中剥离出来,api gateway仅仅负责服务的编排与结果的组装。

Spring Cloud Netflix的Zuul组件可以做反向代理的功能,通过路由寻址将请求转发到后端的粗粒度服务上,并做一些通用的逻辑处理。

20191123100105\_2.png

总结Zuul的作用:

  • 动态路由
  • 监控
  • 安全
  • 认证鉴权
  • 压力测试
  • 金丝雀测试
  • 审查
  • 服务迁移
  • 负载剪裁
  • 静态应答处理

2. 怎么使用zuul

maven配置

在pom.xml中添加以下配置

12345678910111213141516171819202122232425262728`|`<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>1.5.3.RELEASE</version></parent><dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-eureka</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-zuul</artifactId></dependency></dependencies><dependencyManagement><dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>Dalston.RELEASE</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement>
12345678910111213141516171819202122232425262728` | `<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>1.5.3.RELEASE</version></parent><dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-eureka</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-zuul</artifactId></dependency></dependencies><dependencyManagement><dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>Dalston.RELEASE</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement>

引入eureka的目的是为了后续与eureka做整合,使用serviceId做路由。

java编程

在启动类添加@EnableZuulProxy注解

123456789`|`@EnableZuulProxy@SpringCloudApplicationpublicclassGatewayServer{publicstaticvoidmain(String[]args){SpringApplication.run(GatewayServer.class,args);}}
123456789` | `@EnableZuulProxy@SpringCloudApplicationpublicclassGatewayServer{publicstaticvoidmain(String[]args){SpringApplication.run(GatewayServer.class,args);}}

3. zuul对路由的配置

Zuul对路由跳转的配置是在application.yml文件中,定义了两种映射方式:

  • url映射
  • serviceId映射

1. url直接映射

  • 单实例url直连
12345`|`zuul:routes:wap:path:/wap/**url:http://192.168.1.10:8081
12345` | `zuul:routes:wap:path:/wap/**url:http://192.168.1.10:8081
  • 多实例路由
1234567891011`|`zuul:routes:wap:path:/wap/**serviceId:wapribbon:eureka:enabled:falsewap:ribbon:listOfServers:http://192.168.1.10:8081,http://192.168.1.11:8081
1234567891011` | `zuul:routes:wap:path:/wap/**serviceId:wapribbon:eureka:enabled:falsewap:ribbon:listOfServers:http://192.168.1.10:8081,http://192.168.1.11:8081
  • forward跳转到本地url
12345`|`zuul:routes:wap:path:/wap/**url:forward:/wap
12345` | `zuul:routes:wap:path:/wap/**url:forward:/wap

2. serviceId映射

  • 默认serviceId,serviceId:activity,路由规则:/activity/101 -> /101
123`|`zuul:routes:activity:/activity/**
123` | `zuul:routes:activity:/activity/**
  • 指定serviceId,serviceId:micro-activity,路由规则:/activity/101 -> /activity/101
123456`|`zuul:routes:activity:path:/activity/**#指定serviceId:micro-activity#指定路由的serviceIdstripPrefix:false
123456` | `zuul:routes:activity:path:/activity/**#指定serviceId:micro-activity#指定路由的serviceIdstripPrefix:false

4. Cookie与头信息

默认情况下,Zuul在请求路由时,会过滤HTTP请求头信息中的一些敏感信息,默认的敏感头信息通过zuul.sensitiveHeaders定义,包括Cookie、Set-Cookie、Authorization。

  • 设置全局参数覆盖默认值
12`|`zuul:sensitiveHeaders:#使用空来覆盖默认值
12` | `zuul:sensitiveHeaders:#使用空来覆盖默认值
  • 指定路由的参数配置
1234`|`zuul:routes:[route]:customSensitiveHeaders:true#对指定路由开启自定义敏感头
1234` | `zuul:routes:[route]:customSensitiveHeaders:true#对指定路由开启自定义敏感头
1234`|`zuul:routes:[route]:sensitiveHeaders:#对指定路由的敏感头设置为空
1234` | `zuul:routes:[route]:sensitiveHeaders:#对指定路由的敏感头设置为空

5. zuul的关键知识点

filter是Zuul的核心,用来实现对外服务的控制。filter的生命周期有4个,分别是”pre”、”route”、”post”、”error”,整个生命周期可以用下图来表示。

20191123100105\_3.png

具体的代码是在ZuulServletFilter类中,从实现可以看出,error可以在所有阶段捕获异常后执行,但是当post阶段处理中出现异常不会再回到post阶段,那么这就需要保证在post阶段不要有异常,因为一旦有异常后就不会走post执行SendErrorFilter了。

12345678910111213141516171819202122232425262728293031323334353637383940`|`publicclassZuulServletFilterimplementsFilter{@OverridepublicvoiddoFilter(ServletRequestservletRequest,ServletResponseservletResponse,FilterChainfilterChain)throwsIOException,ServletException{try{init((HttpServletRequest)servletRequest,(HttpServletResponse)servletResponse);try{preRouting();}catch(ZuulExceptione){error(e);postRouting();return;}//Onlyforwardontotothechainifazuulresponseisnotbeingsentif(!RequestContext.getCurrentContext().sendZuulResponse()){filterChain.doFilter(servletRequest,servletResponse);return;}try{routing();}catch(ZuulExceptione){error(e);postRouting();return;}try{postRouting();}catch(ZuulExceptione){error(e);return;}}catch(Throwablee){error(newZuulException(e,500,"UNCAUGHT_EXCEPTION_FROM_FILTER_"+e.getClass().getName()));}finally{RequestContext.getCurrentContext().unset();}}}
12345678910111213141516171819202122232425262728293031323334353637383940` | `publicclassZuulServletFilterimplementsFilter{@OverridepublicvoiddoFilter(ServletRequestservletRequest,ServletResponseservletResponse,FilterChainfilterChain)throwsIOException,ServletException{try{init((HttpServletRequest)servletRequest,(HttpServletResponse)servletResponse);try{preRouting();}catch(ZuulExceptione){error(e);postRouting();return;}//Onlyforwardontotothechainifazuulresponseisnotbeingsentif(!RequestContext.getCurrentContext().sendZuulResponse()){filterChain.doFilter(servletRequest,servletResponse);return;}try{routing();}catch(ZuulExceptione){error(e);postRouting();return;}try{postRouting();}catch(ZuulExceptione){error(e);return;}}catch(Throwablee){error(newZuulException(e,500,"UNCAUGHT_EXCEPTION_FROM_FILTER_"+e.getClass().getName()));}finally{RequestContext.getCurrentContext().unset();}}}

zuul中默认实现的filter

类型 顺序 过滤器 功能
pre -3 ServletDetectionFilter 标记处理Servlet的类型
pre -2 Servlet30WrapperFilter 包装HttpServletRequest请求
pre -1 FormBodyWrapperFilter 包装请求体
route 1 DebugFilter 标记调试标志
route 5 PreDecorationFilter 处理请求上下文供后续使用
route 10 RibbonRoutingFilter serviceId请求转发
route 100 SimpleHostRoutingFilter url请求转发
route 500 SendForwardFilter forward请求转发
post 0 SendErrorFilter 处理有错误的请求响应
post 1000 SendResponseFilter 处理正常的请求响应

禁用指定的filter

可以在application.yml中配置需要禁用的filter,格式:zuul:[filter-name]:[filter-type]:disable:true。

1234`|`zuul:FormBodyWrapperFilter:pre:disable:true
1234` | `zuul:FormBodyWrapperFilter:pre:disable:true

自定义filter

使用java实现自定义filter,需要添加继承于ZuulFilter的类,覆盖其中的4个方法。

123456789101112131415161718192021`|`publicclassCustomerPreFilterextendsZuulFilter{@OverrideStringfilterType(){return"pre";//定义filter的类型,有pre、route、post、error四种}@OverrideintfilterOrder(){return10;//定义filter的顺序,数字越小表示顺序越高,越先执行}@OverridebooleanshouldFilter(){returntrue;//表示是否需要执行该filter,true表示执行,false表示不执行}@OverrideObjectrun(){returnnull;//filter需要执行的具体操作}}
123456789101112131415161718192021` | `publicclassCustomerPreFilterextendsZuulFilter{@OverrideStringfilterType(){return"pre";//定义filter的类型,有pre、route、post、error四种}@OverrideintfilterOrder(){return10;//定义filter的顺序,数字越小表示顺序越高,越先执行}@OverridebooleanshouldFilter(){returntrue;//表示是否需要执行该filter,true表示执行,false表示不执行}@OverrideObjectrun(){returnnull;//filter需要执行的具体操作}}

6. zuul对动态语言的支持

zuul支持使用groovy语言来动态修改filter,它是基于jvm的语言,语法简单并且很多与java类似。

需要先将groovy的jar包引入

12345`|`<dependency><groupId>org.codehaus.groovy</groupId><artifactId>groovy-all</artifactId><version>2.4.9</version></dependency>
12345` | `<dependency><groupId>org.codehaus.groovy</groupId><artifactId>groovy-all</artifactId><version>2.4.9</version></dependency>

然后扫描groovy文件

123`|`FilterLoader.getInstance().setCompiler(newGroovyCompiler());FilterFileManager.setFilenameFilter(newGroovyFileFilter());FilterFileManager.init(internal,path);
123` | `FilterLoader.getInstance().setCompiler(newGroovyCompiler());FilterFileManager.setFilenameFilter(newGroovyFileFilter());FilterFileManager.init(internal,path);

FilterFileManager会对groovy文件变更加载如内存,其实现是开启了一个后台线程,以指定间隔的时间长度管理文件,具体的代码如下。

1234567891011121314151617181920212223242526`|`publicclassFilterFileManager{publicstaticvoidinit(intpollingIntervalSeconds,String...directories)throwsException,IllegalAccessException,InstantiationException{if(INSTANCE==null)INSTANCE=newFilterFileManager();INSTANCE.aDirectories=directories;INSTANCE.pollingIntervalSeconds=pollingIntervalSeconds;INSTANCE.manageFiles();INSTANCE.startPoller();}voidstartPoller(){poller=newThread("GroovyFilterFileManagerPoller"){publicvoidrun(){while(bRunning){try{sleep(pollingIntervalSeconds*1000);manageFiles();}catch(Exceptione){e.printStackTrace();}}}};poller.setDaemon(true);poller.start();}}
1234567891011121314151617181920212223242526` | `publicclassFilterFileManager{publicstaticvoidinit(intpollingIntervalSeconds,String...directories)throwsException,IllegalAccessException,InstantiationException{if(INSTANCE==null)INSTANCE=newFilterFileManager();INSTANCE.aDirectories=directories;INSTANCE.pollingIntervalSeconds=pollingIntervalSeconds;INSTANCE.manageFiles();INSTANCE.startPoller();}voidstartPoller(){poller=newThread("GroovyFilterFileManagerPoller"){publicvoidrun(){while(bRunning){try{sleep(pollingIntervalSeconds*1000);manageFiles();}catch(Exceptione){e.printStackTrace();}}}};poller.setDaemon(true);poller.start();}}

最终调用的是FilterLoader的putFilter方法,是否需要重新加载文件,判断依据是看文件是否已经加载到内存中,同时文件是否被修改过。

12345678910111213141516171819202122232425`|`publicclassFilterLoader{publicbooleanputFilter(Filefile)throwsException{StringsName=file.getAbsolutePath()+file.getName();if(filterClassLastModified.get(sName)!=null&&(file.lastModified()!=filterClassLastModified.get(sName))){LOG.debug("reloadingfilter"+sName);filterRegistry.remove(sName);}ZuulFilterfilter=filterRegistry.get(sName);if(filter==null){Classclazz=COMPILER.compile(file);if(!Modifier.isAbstract(clazz.getModifiers())){filter=(ZuulFilter)FILTER_FACTORY.newInstance(clazz);List<ZuulFilter>list=hashFiltersByType.get(filter.filterType());if(list!=null){hashFiltersByType.remove(filter.filterType());//rebuildthislist}filterRegistry.put(file.getAbsolutePath()+file.getName(),filter);filterClassLastModified.put(sName,file.lastModified());returntrue;}}returnfalse;}}
12345678910111213141516171819202122232425` | `publicclassFilterLoader{publicbooleanputFilter(Filefile)throwsException{StringsName=file.getAbsolutePath()+file.getName();if(filterClassLastModified.get(sName)!=null&&(file.lastModified()!=filterClassLastModified.get(sName))){LOG.debug("reloadingfilter"+sName);filterRegistry.remove(sName);}ZuulFilterfilter=filterRegistry.get(sName);if(filter==null){Classclazz=COMPILER.compile(file);if(!Modifier.isAbstract(clazz.getModifiers())){filter=(ZuulFilter)FILTER_FACTORY.newInstance(clazz);List<ZuulFilter>list=hashFiltersByType.get(filter.filterType());if(list!=null){hashFiltersByType.remove(filter.filterType());//rebuildthislist}filterRegistry.put(file.getAbsolutePath()+file.getName(),filter);filterClassLastModified.put(sName,file.lastModified());returntrue;}}returnfalse;}}

groovy实现的filter,groovy语法格式与java相近:

123456789101112131415161718192021`|`classPreFilterextendsZuulFilter{@OverrideStringfilterType(){return"pre"}@OverrideintfilterOrder(){return1000}@OverridebooleanshouldFilter(){returntrue}@OverrideObjectrun(){returnnull}}
123456789101112131415161718192021` | `classPreFilterextendsZuulFilter{@OverrideStringfilterType(){return"pre"}@OverrideintfilterOrder(){return1000}@OverridebooleanshouldFilter(){returntrue}@OverrideObjectrun(){returnnull}}

7. 全局异常处理

介绍一下Zuul的全局异常处理的一种方式:添加一个类型为”error”的filter,将错误信息写入RequestContext,这样SendErrorFilter就可以获取错误信息了。

1234567891011121314151617181920212223242526`|`classErrorFilterextendsZuulFilter{@OverrideStringfilterType(){returnFilterConstants.ERROR_TYPE}@OverrideintfilterOrder(){return10}@OverridebooleanshouldFilter(){returntrue}@OverrideObjectrun(){RequestContextcontext=getRequestContext()Throwablethrowable=context.getThrowable()LOGGER.error("[ErrorFilter]errormessage:{}",throwable.getCause().getMessage())ctx.set("error.status_code",HttpServletResponse.SC_INTERNAL_SERVER_ERROR)ctx.set("error.exception",throwable.getCause())returnnull}}
1234567891011121314151617181920212223242526` | `classErrorFilterextendsZuulFilter{@OverrideStringfilterType(){returnFilterConstants.ERROR_TYPE}@OverrideintfilterOrder(){return10}@OverridebooleanshouldFilter(){returntrue}@OverrideObjectrun(){RequestContextcontext=getRequestContext()Throwablethrowable=context.getThrowable()LOGGER.error("[ErrorFilter]errormessage:{}",throwable.getCause().getMessage())ctx.set("error.status_code",HttpServletResponse.SC_INTERNAL_SERVER_ERROR)ctx.set("error.exception",throwable.getCause())returnnull}}

8. 性能优化参考

1. 在application.yml文件中配置线程数、缓冲大小

12345678910`|`server:tomcat:max-threads:128#最大worker线程min-spare-threads:64#最小worker线程undertow:io-threads:8#IO线程数,默认为CPU核心数,最小为2worker-threads:40#阻塞任务线程池,值设置取决于系统的负载,默认为io-threads*8buffer-size:512#每块buffer的空间大小buffers-per-region:10#每个区分配的buffer数量direct-buffers:512#是否分配的直接内存
12345678910` | `server:tomcat:max-threads:128#最大worker线程min-spare-threads:64#最小worker线程undertow:io-threads:8#IO线程数,默认为CPU核心数,最小为2worker-threads:40#阻塞任务线程池,值设置取决于系统的负载,默认为io-threads*8buffer-size:512#每块buffer的空间大小buffers-per-region:10#每个区分配的buffer数量direct-buffers:512#是否分配的直接内存

2. 在application.yml文件中配置zuul和ribbon

12345`|`zuul:host:max-total-connections:500#每个服务的http客户端连接池最大连接,默认值是200max-per-route-connections:50#每个route可用的最大连接数,默认值是20ribbon-isolation-strategy:THREAD#可选:SEMAPHORETHREAD
12345` | `zuul:host:max-total-connections:500#每个服务的http客户端连接池最大连接,默认值是200max-per-route-connections:50#每个route可用的最大连接数,默认值是20ribbon-isolation-strategy:THREAD#可选:SEMAPHORETHREAD

20191123100105\_4.png

3. 在application.yml文件中配置hystrix

hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds 设置thread的默认超时时间,默认值是10000。

hystrix.command.[CommandKey].execution.isolation.thread.timeoutInMilliseconds 设置不同微服务的超时时间。

123456789101112`|`hystrix:command:default:execution:isolation:thread:timeoutInMilliseconds:5000activity:execution:isolation:thread:timeoutInMilliseconds:3000
123456789101112` | `hystrix:command:default:execution:isolation:thread:timeoutInMilliseconds:5000activity:execution:isolation:thread:timeoutInMilliseconds:3000

下一篇:
Spring Cloud技术分析(5)- spring cloud config

参考资料


来源:http://ddrv.cn/a/88268

赞(0) 打赏
版权归原创作者所有,任何形式的转载请联系博主:daming_90:Java 技术驿站 » Spring Cloud技术分析(4)- spring cloud zuul

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏