0.学习目标
1.学会配置Hystrix熔断
2.学会使用Feign进行远程调用
3.能独立搭建Zuul网关
4.能编写Zuul的拦截器
4.Zuul网关
4.1.简介
官网:https://github.com/Netflix/zuul
Zuul:维基百科:
电影《捉鬼敢死队》中的怪兽,Zuul,在纽约引发了巨大骚乱。
事实上,在微服务架构中,Zuul就是守门的大Boss!一夫当关,万夫莫开!
4.2 Zuul加入后的架构
网关类似关卡,会拦住请求
在没有网关的时候,每个服务可以被任意调用,就不安全,这时候网关就可以控制权限,并且做一个路由的功能(进行转发到指定的服务),第三就是做负载均衡(因为服务有集群,有很多的实例,至于要调用哪个实例,就要负载均衡),网关还可以起到限流的作用
不管是来自于客户端(PC或移动端)的请求,还是服务内部调用。一切对服务的请求都会经过Zuul这个网关,然后再由网关来实现 鉴权、动态路由等等操作。Zuul就是我们服务的统一入口。
4.3 快速入门
4.3.1 新建工程
创建项目
添加Zuul依赖:
只需要引入这个依赖,里面已经配置了其他依赖
4.3.2 编写启动类(加注解,即配置类)
注意这里添加的是@EnableZuulProxy这个注解,而不是@EnableZuulServer(因为@EnableZuulServer的功能单一,在zuul里面有很多过滤器,而配置@EnableZuulServer这个很多功能都实现不了)
在启动SpringBoot即可
4.3.3.编写配置文件
1.端口号
server:
port:10010
2.规则(路由功能:请求到网关的时候,网关要将它的请求转发到指定的服务上,那么就要知道哪个请求对应哪个服务,这就是我们要配置的(类似一个规则 )
zuul:
routes:
hehe:(路由id,可以随便取,只要不相同)
path:(匹配路径,只要访问路径与path匹配,就被网关拦截,再转发)
url:(转发目的路径,改变了域名+端口号,方法名就不照旧因为就是要把他送到他要的去访问的方法上 )
原访问路径
改成先到网关,再有网关转发到访问服务路径
先user-service/user和user-service/**(拦截所有服务)匹配到了,那么网关就会拦截到,把他转发到url(即把域名+端口号+path改成url)所以后面的访问路径就是localhost:8081/user/9(也就是原访问路径,所以可以成功转发到指定的服务上)
而这种配置方法存在严重的问题
1.把路径写死了(如果服务路径改了,那么配置文件这里的url也要改)
2.没有实现负载均衡(如果服务中有多个实例,集群,那么没有实现负载均衡)
所以我们再回头看架构,我们不能让网关一拦截到就直接进行转发,我们应该借助Eureka注册中心,拉取服务列表(即告诉他有什么服务),并作负载均衡,然后再一一转发
所以我们配置的时候就要借助Eureka
那么就要引入Eureka依赖,注意是引入客户端(因为我们是看服务列表,然后去访问服务的,注意怎么区分客户端还是服务端,如果是服务Eureka的服务就是服务端,如果是Eureka取访问别的服务的就是客户端)
引入Eureka依赖,并启动
(这时候不应该给网关配置url(转发路径,因为不需要写死),那么我们配置了Eureka还需要什么呢,需要告诉Eureka要访问的服务id,所以我们只需要配置服务idserviceId,这样就可以网卡先进行负载均衡,再通过Eureka取服务对应的服务
然后这里的配置方式有点小问题
就是主要的属性就是path和serviceId,反而上面的路由id(haha)是无所谓的
所以可以通过Key:Value的形式简化配置(serviceId为Key,path为Value
实际上zuul已经会帮我们默认给每个服务配置这两个属性
例如,我们这个项目还有个微服务consumer-demo,我们以他为例,即我们根本没有给他配置任何的path和serviceId,但我们还是类比刚刚的访问路径,运行后发现居然可以访问到
(理论上我们没有配置serviceId和path,那么网关匹配不到不会拦截后而且不会转发,可实际上,zuul已经给我们注册在eureka上的服务都给配置了默认的serviceId(默认是xxx-service),path(xxx-service/**)
手动配置就能符合自己的访问习惯(比如user-service太长,改成user),还有就是有些服务并不想给外部调用,只有自己的服务内部可以调用他,就要配置一个ignored-service
路由前缀
比如我们path是user/**,然而我们的原访问路径是user/9
那么我们的现访问路径是user/user/9
前面的user是path的user用来匹配,给网关扫描
后面的user是访问服务的user,用来找服务的
那么如果我们想配置的path里的前缀就是后面的服务路径就只需配置一个属性strip-prefix(前缀去除,我们把他改成false,那么匹配完user/**后,就不会去除掉前缀user,而继续使用他为访问服务路径)
可以把上面这个前缀去除属性加在全局上,那么每个路由都要满足
还有一个配置属性:就是全局前缀,即每次匹配时都要加上
4.3.5 启动测试:
访问的路径中需要加上配置规则的映射路径
5.过滤器
5.1 过滤器
5.1.1 ZuulFilter
Zuul作为网关的其中一个重要功能,就是实现请求的鉴权。而这个动作我们往往是通过Zuul提供的过滤器来实现的。
(权限控制通过过滤器实现通过接口zuulFilter,里面有四个方法)
ZuulFilter是过滤器的顶级父类。在这里我们看一下其中定义的4个最重要的方法:
分别是filterType/filterOrder/shouldFilter/run
filterType:过滤器类型(有四种:pre/post/routing/error)
filterOrder:过滤器顺序(越大优先级越低)
shouldFilter:是否需要过滤(返回true,过滤生效,false失效)
run:过滤逻辑(自己编写过滤的逻辑)
过滤器类型
pre:请求在被路由之前执行
routing:在路由请求时调用
post:在routing和errror过滤器之后调用
error:处理请求时发生错误调用
filterOrder:通过返回的int值来定义过滤器的执行顺序,数字越小优先级越高。
5.1.2 过滤器执行生命周期:
这张是Zuul官网提供的请求生命周期图,清晰的表现了一个请求在各个过滤器的执行顺序。
解析:
(一次请求先经过前置过滤器(内置的,会对请求的参数,请求头等进行处理解析),这时候request就准备好了,那么zuul就要把这些请求转发给对应的微服务,而这个就是routing(路由过滤器,通过路由规则转发给具体的微服务,微服务完成后再把结果转发给routing,得到结果后routing还会对结果做检测,再转发给后置,custom是自定义过滤器,可以再任何一个位置,还有一个过滤器是error过滤器,当前置、路由、后置过滤器任意一个发生异常的时候,就会进入error过滤器,当运行完error会转发会post后置过滤器(当然如果就是这个post过滤器发生错误就转发回下一个)
前置过滤器作用:权限控制(进入routing前要先检测权限),限流(类似限制只能多少秒访问一次)
后置过滤器和error过滤器一般就是处理异常
还有很多过滤器就等用到时再讲解
正常流程:
请求到达首先会经过pre类型过滤器,而后到达routing类型,进行路由,请求就到达真正的服务提供者,执行请求,返回结果后,会到达post过滤器。而后返回响应。
异常流程:
整个过程中,pre或者routing过滤器出现异常,都会直接进入error过滤器,再error处理完毕后,会将请求交给POST过滤器,最后返回给用户。
如果是error过滤器自己出现异常,最终也会进入POST过滤器,而后返回。
如果是POST过滤器出现异常,会跳转到error过滤器,但是与pre和routing不同的时,请求不会再到达POST过滤器了。
所有内置过滤器列表:
5.1.3.使用场景
场景非常多:
请求鉴权:一般放在pre类型,如果发现没有访问权限,直接就拦截了
异常处理:一般会在error类型和post类型过滤器中结合来处理。
服务调用时长统计:pre和post结合使用。
类似登录界面(检测是否登录)
如果登入过,信息就存在Session,然后把他对应的SessionId存到cookie,以后登录校验的时候就得到cookie通过SessionId查询这个登录信息(原理就是通过过滤器校验变量,那么我们就来模拟一下,自定义一个过滤器来校验变量是否等于某值 或 是否存在某个变量)
5.2 自定义过滤器
接下来我们来自定义一个过滤器,模拟一个登录的校验。基本逻辑:如果请求中有access-token参数,则认为请求有效,放行。
5.2.1 定义过滤器类
实现四个方法
1.filterType():返回过滤器类型,可以直接写过滤器类型的字符串,如"pre"(前置),但专业点写法可以通过配置了的变量名代替(类似宏名的作用),即FilterConstans.PRE_TYPE(其实这种只是编写习惯的问题,这就是一个好的编写代码习惯,希望学习)
2.filterOrder():通过找到这些已经定义好的过滤器顺序来判断你的过滤器应该处在那个阶段,比如我们是验证表单提交的账号信息,那么我们的顺序可以再处理请求头之前,即PRE_DECORATION_FILTER_ORDER(处理表单),之前可以通过-1,那么顺序就在前面了
3.shouldFilter():打开过滤器,true;关闭,false
4.run():过滤的业务逻辑
比如我们要检测表单提交的数据中是否存在xxx变量(来代表是否登陆过,登录过就有,就放行)
但我们如果不会获得这个request(只知道再servlet和jsp里面)
我们也要学会一个技巧,查阅类似功能的源代码
比如我们要自定义一个过滤器,那么可能zuulFilter已经定义好的过滤器也有调用request的过程,我们翻阅一下就能找到并使用
获取request分两步,第一步得到请求上下文,其实就是类似之前学的request的全局域,通过这个域可以获得request,而这个请求上下文的生命周期是从进入zuulFilter,一直到返回服务到客户端,所以再路由前路由后都可以获得,但微服务就无法调用了
在获取了request,在获得xxx变量来判断是否存在,但这里有个判断的注意事项
我们如果用以上这种方式判断有两个问题
1.麻烦,要写两个判断,还要记得调用trim()
2.trim()方法本身效率并不高,是通过截取原字符串产生新的的字符串,以及特别容易引起内存的溢出(因为要截取多次)
所以我们可以借助其他工具,比如apache提供的lang3,那么就要引入依赖
引入了这个依赖后调用
要学会用别人的工具(站在巨人的肩膀上)
然后判断完后就可以执行放行和拦截了
其中,不管是放行还是拦截都是通过上下文里的一个属性(ZuulResponse)来决定的
然后拦截后,我们也可以设置一下状态码(404:NotFind找不到 403Forbidden禁止…)
5.2.2 测试
没有带xxx变量,结果为403,不让通过
加上后就可以正常执行了
3.10.负载均衡和熔断
Zuul中默认就已经集成了Ribbon负载均衡和Hystix熔断机制。但是所有的超时策略都是走的默认值,比如熔断超时时间只有1S,很容易就触发了。因此建议我们手动进行配置:
(之前有讲过,Feign内部有负载均衡,zuul内部也有配置负载均衡和熔断依赖)
那么我们只需要配置一些他们两个的配置属性
1.负载均衡的连接超时时长
2.负载均衡的读取超市时长
3.熔断的超时时长
其中这里有个小小的注意事项:
如果(ConnectionTimeout+ReadTimeout)* 2 > timeoutInMilliseconds
那么就会报错,其中这个2是默认值 也是可以修改的
至于为什么呢 我们可以通过查看源码,在AbstractRibbonCommand里面配置的总超时时长
我们可以看到有四个变量
1.ribbonReadTimeout:就是我们自己配置的,默认是1000
2.ribbonConnectTimeout:也是自己配置的,默认是1000
3.maxAutoRtries:是自动重试,在请求失败了后,不会说马上就返回错误的提示信息(熔断),而会先重试一次(这个默认次数是0)
4.maxAutoRetriesNextServer:不同服务器的重试,因为一个服务中有多个实例,如果这个实例失败了,那么就会换一个服务实例(这个的默认次数是1次)
最后总超时时长=(变量1+变量2)* (变量3 + 1) * (变量4 + 1)
所以才会有之前的那个判断 因为变量3=0,变量4=1 ,所以才会有个*2的计算
(ConnectionTimeout+ReadTimeout)* 2 > timeoutInMilliseconds