一、Eureka
1、Register:服务注册
当Eureka客户端向Eureka Server注册时,它提供自身的元数据,比如IP地址、端口,运行状况指示符URL,主页等。
2、Renew:服务续约
Eureka客户会每隔30秒发送一次心跳来续约。 通过续约来告知Eureka Server该Eureka客户仍然存在,没有出现问题。 正常情况下,如果Eureka Server在90秒没有收到Eureka客户的续约,它会将实例从其注册表中删除。 建议不要更改续约间隔。
3、Fetch Registries:获取注册列表信息
Eureka客户端从服务器获取注册表信息,并将其缓存在本地。客户端会使用该信息查找其他服务,从而进行远程调用。该注册列表信息定期(每30秒钟)更新一次。每次返回注册列表信息可能与Eureka客户端的缓存信息不同, Eureka客户端自动处理。如果由于某种原因导致注册列表信息不能及时匹配,Eureka客户端则会重新获取整个注册表信息。 Eureka服务器缓存注册列表信息,整个注册表以及每个应用程序的信息进行了压缩,压缩内容和没有压缩的内容完全相同。Eureka客户端和Eureka 服务器可以使用JSON / XML格式进行通讯。在默认的情况下Eureka客户端使用压缩JSON格式来获取注册列表的信息。
4、Cancel:服务下线
Eureka客户端在程序关闭时向Eureka服务器发送取消请求。 发送请求后,该客户端实例信息将从服务器的实例注册表中删除。该下线请求不会自动完成,它需要调用以下内容:
DiscoveryManager.getInstance().shutdownComponent();
5、Eviction 服务剔除
在默认的情况下,当Eureka客户端连续90秒没有向Eureka服务器发送服务续约,即心跳,Eureka服务器会将该服务实例从服务注册列表删除,即服务剔除。
二、Feign
1、首先通过@EnableFeignClients,开启FeignClient
2、根据Feign的规则实现接口,并添加FeignClient注解
3、程序启动时,会进行包扫描,扫描所有的@FeginClient注解类,并将这些信息注入到IOC容器中
4、当接口被调用时,通过jdk动态代理来生成具体的RequestTemplate
5、RequestTemplate在生成Request
6、Request交给Client去处理,其中客户端可以使httpUrlConnetion,httpClient,okhttp
7、最后Client被封在到loadBalanceClient类,这个类结合Ribbon做到了负载均衡
三、Ribbon
负载均衡器是从EurekaClient获取服务信息,并根据IRule去路由,并且根据IPing去判断服务的可用性。
负载均衡器多久一次去获取一次从Eureka Client获取注册信息呢,即每10秒钟,向EurekaClient发送一次"ping"。
由此可见,LoadBalancerClient是在初始化的时候,会向Eureka回去服务注册列表,并且向通过10s一次向EurekaClient发送“ping”,
来判断服务的可用性,如果服务的可用性发生了改变或者服务数量和之前的不一致,
则更新或者重新拉取。LoadBalancerClient有了这些服务注册列表,就可以根据具体的IRule来进行负载均衡。
综上所述:
1、Ribbon的负载均衡,主要通过LoadBalancerClient来实现的
2、而LoadBalancerClient具体交给了ILoadBalancer来处理
3、ILoadBalancer通过配置IRule、IPing等信息,并向EurekaClient获取注册列表的信息,并默认10秒一次向EurekaClient发送“ping”,
进而检查是否更新服务列表。
4、最后,得到注册列表后,ILoadBalancer根据IRule的策略进行负载均衡。
5、而RestTemplate 被@LoadBalance注解后,能过用负载均衡,主要是维护了一个被@LoadBalance注解的RestTemplate列表,
并给列表中的RestTemplate添加拦截器,进而交给负载均衡器去处理。
private static Map<String, LoadBalancerHttpClient> instanceMap = Maps.newConcurrentMap();
public static LoadBalancerHttpClient getInstance(String clientName) {
clientName = EnvironmentHolder.get().resolvePlaceholders(clientName);
LoadBalancerHttpClient instance = instanceMap.get(clientName);
if (instance == null) {
synchronized (instanceMap) {
instance = instanceMap.get(clientName);
if (instance == null) {
instance = new LoadBalancerHttpClient();
instance.clientName = clientName;
instanceMap.put(clientName, instance);
}
}
}
instance.initRequestContext();
return instance;
}
四、Hystrix
1、Hystrix的设计原则
a、防止单个服务的故障,耗尽整个系统服务的容器(比如tomcat)的线程资源。
b、减少负载并快速失败,而不是排队。
c、在可行的情况下提供回退以保护用户免受故障。
d、使用隔离技术(如隔板,泳道和断路器模式)来限制任何一个依赖的影响。
e、通过近乎实时的指标,监控和警报来优化发现故障的时间。
f、通过配置更改的低延迟传播优化恢复时间,并支持Hystrix大多数方面的动态属性更改,
从而允许您使用低延迟反馈循环进行实时操作修改。
g、保护整个依赖客户端执行中的故障,而不仅仅是在网络流量上进行保护降级、限流。
2、Hystrix是怎么工作的?
1)、构建一个HystrixCommand或者HystrixObservableCommand 对象。
2)、执行Command
通过使用Hystrix命令对象的以下四种方法之一,可以执行该命令有四种方法(前两种方法仅适用于简单的HystrixCommand对象
并不适用于HystrixObservableCommand):
a、execute()–阻塞,然后返回从依赖关系接收到的单个响应(或者在发生错误时抛出异常)
b、queue()–返回一个可以从依赖关系获得单个响应的future 对象
c、observe()–订阅Observable代表依赖关系的响应,并返回一个Observable,该Observable会复制该来源Observable
d、toObservable() --返回一个Observable,当您订阅它时,将执行Hystrix命令并发出其响应
3.响应是否有缓存?
如果为该命令启用请求缓存,并且如果缓存中对该请求的响应可用,则此缓存响应将立即以“可观察”的形式返回。
4.断路器是否打开?
当您执行该命令时,Hystrix将检查断路器以查看电路是否打开。
如果电路打开(或“跳闸”),则Hystrix将不会执行该命令,但会将流程路由到(8)获取回退。
如果电路关闭,则流程进行到(5)以检查是否有可用于运行命令的容量。
5.线程池/队列/信号量是否已经满负载?
如果与命令相关联的线程池和队列(或信号量,如果不在线程中运行)已满,则Hystrix将不会执行该命令,但将立即将流程路由到(8)获取回退。
6.HystrixObservableCommand.construct() 或者 HystrixCommand.run()
在这里,Hystrix通过您为此目的编写的方法调用对依赖关系的请求,其中之一是:
HystrixCommand.run() - 返回单个响应或者引发异常
HystrixObservableCommand.construct() - 返回一个发出响应的Observable或者发送一个onError通知
7.计算Circuit 的健康
Hystrix向断路器报告成功,失败,拒绝和超时,该断路器维护了一系列的计算统计数据组。
它使用这些统计信息来确定电路何时“跳闸”,此时短路任何后续请求直到恢复时间过去,在首次检查某些健康检查之后,它再次关闭电路。
8.获取Fallback
当命令执行失败时,Hystrix试图恢复到你的回退:当construct()或run()
(6.)抛出异常时,当命令由于电路断开而短路时(4.),
当 命令的线程池和队列或信号量处于容量(5.),或者当命令超过其超时长度时。
编写Fallback ,它不一依赖于任何的网络依赖,从内存中获取获取通过其他的静态逻辑。
如果你非要通过网络去获取Fallback,你可能需要些在获取服务的接口的逻辑上写一个HystrixCommand。
9.返回成功的响应
如果 Hystrix command成功,如果Hystrix命令成功,它将以Observable的形式返回对呼叫者的响应或响应。
根据您在上述步骤2中调用命令的方式,此Observable可能会在返回给您之前进行转换:
execute() - 以与.queue()相同的方式获取Future,然后在此Future上调用get()来获取Observable发出的单个值
queue() - 将Observable转换为BlockingObservable,以便将其转换为Future,然后返回此未来
observe() - 立即订阅Observable并启动执行命令的流程; 返回一个Observable,当您订阅它时,重播排放和通知
toObservable() - 返回Observable不变; 您必须订阅它才能实际开始导致命令执行的流程
五、zuul
在zuul中, 整个请求的过程如下:
1、首先将请求httpRequest给zuulservlet处理,zuulservlet中有一个zuulRunner对象。
2、该对象中初始化了RequestContext:作为存储整个请求的一些数据,并被所有的zuulfilter共享。
3、zuulRunner中还有 FilterProcessor,FilterProcessor作为执行所有的zuulfilter的管理器。
4、FilterProcessor从filterloader 中获取zuulfilter,而zuulfilter是被filterFileManager所加载,并支持groovy热加载,采用了轮询的方式热加载。
5、有了这些filter之后,zuulservelet首先执行的Pre类型的过滤器,再执行route类型的过滤器,最后执行的是post 类型的过滤器
6、如果在执行这些过滤器有错误的时候则会执行error类型的过滤器。执行完这些过滤器,最终将请求的结果返回给客户端。
不同类型过滤器:
过滤器的order值越小,就越先执行,并且在执行过滤器的过程中,
它们共享了一个RequestContext对象,该对象的生命周期贯穿于请求,
可以看出优先执行了pre类型的过滤器,并将执行后的结果放在RequestContext中,供后续的filter使用,
比如在执行PreDecorationFilter的时候,决定使用哪一个route,它的结果的是放在RequestContext对象中,
后续会执行所有的route的过滤器,如果不满足条件就不执行该过滤器的run方法。最终达到了就执行一个route过滤器的run()方法。
而error类型的过滤器,是在程序发生异常的时候执行的。
post类型的过滤,在默认的情况下,只注入了SendResponseFilter,该类型的过滤器是将最终的请求结果以流的形式输出给客户单。