Ribbon 可以通过配置文件制定负载均衡的规则 默认使用轮训算法来实现负载均衡 根据响应时间做权重
两个自动装配类 RibbonAutoConfiguration 和 LoadBalancerAutoConfiguration
装配类会让 加了LoadBalanced 注解的 RestTemplate 加上一个拦截器 LoadBalancerInterceptor
关键类 LoadBalancerInterceptor RibbonClientConfiguration ZoneAwareLoadBalancer DynamicServerListLoadBalancer
LoadBalancerInterceptor 通过 LoadBalancerClient 会生成一个 ZoneAwareLoadBalancer 这个里面包含了 Irule Iping 一些信息
LoadBalancerInterceptor 继承自 DynamicServerListLoadBalancer
DynamicServerListLoadBalancer 的构造方法里面加载了几个定时任务去更新实例表
LoadBalancerAutoConfiguration 会加载 SpringClientFactory
SpringClientFactory 继承 NamedContextFactory 上下文 会在 getInstance 也就是加载上下文的时候加载配置类 RibbonClientConfiguration
RibbonClientConfiguration 是利用spring的容器进行构建的 在 RibbonLoadBalancerClient 的execute 执行 getLoadBalancer 最后执行 getInstance 通过spring容器 获取 RibbonClientConfiguration
应用初始化的时候会自动装配 RibbonAutoConfiguration 和 LoadBalancerAutoConfiguration
RibbonAutoConfiguration 先自动装配于 LoadBalancerAutoConfiguration
LoadBalancerAutoConfiguration
加载 LoadBalancerClient 类 然后还会把加了 LoadBalanced 注解的RestTemplate 加上一个拦截器
加载 LoadBalancerInterceptor
RibbonAutoConfiguration
加载 RibbonLoadBalancerClient 类 然后在 LoadBalancerInterceptor.intercept 进入 RibbonLoadBalancerClient 的 execute 方法 然后在 RibbonLoadBalancerClient 获取 ILoadBalancer 均衡器和 Server
加载 BaseLoadBalancer
客户端的负载均衡 LoadBalanced LoadBalancerInterceptor
@LoadBalanced 注解 加了Qualifier注解作为标记
LoadBalancerAutoConfiguration 自动装配 RestTemplateCustomizer、 LoadBalancerInterceptor
LoadBalancerInterceptor 为每一个RestTemplate设置一个拦截器 LoadBalancerInterceptor
RestTemplateCustomizer 对修饰了LoadBalanced注解的RestTemplate的实例进行添加
LoadBalancerInterceptor 拦截器
intercept 方法 执行loadBalancer.execute
execute
先获得一个负载均衡器 getLoadBalancer 从 RibbonClientConfiguration 得到 继承IClientConfig的 DefaultClientConfigImpl 里面获取 IClientConfig 根据 IClientConfig 返回一个 PollingServerListUpdater
getInstance 根据spring容器本身实现工厂得到 RibbonClientConfiguration 然后得到 ZoneAwareLoadBalancer
调用 ZoneAwareLoadBalancer 会执行 DynamicServerListLoadBalancer 构造方法 里面有一个 restOfInit 方法
restOfInit 进入
执行 updateListOfServers
会执行 enableAndInitLearnNewServersFeature 方法 会开启一个执行 UpdateAction 的doUpdate的线程 并把这个线程放入 ScheduledFuture 里面定时执行 获取服务地址列表
获取服务地址 getServer
ZoneAwareLoadBalancer.chooseServer 会根据负载均衡器的规则进行地址的选择 ( 默认使用轮训算法)
InterceptingClientHttpRequest -> AbstractBufferingClientHttpRequest -> AbstractClientHttpRequest -> ClientHttpRequest 接口实现关系路径
RestTemplate 在 InterceptingClientHttpRequest 中进入拦截器 LoadBalancerInterceptor 的 intercept 方法
BaseLoadBalancer
allServerList 服务列表 循环更新列表 cas操作枷锁
LoadBalancerInterceptor 是拿到allServerList列表然后进行负载均衡
DynamicServerListLoadBalancer 是在执行远程调用的时候加一个scheduledFuture线程去动态从本地或者服务中心获取allServerList 服务列表
setPingInterval 设置时间不断的ping 服务列表的地址 确认服务有没有挂 通讯时间长度
setupPingTask 起了一个任务 ping注册中心检查服务存活,然后刷新本地可用服务的缓存
RibbonClientConfiguration 是利用spring的容器进行构建的 在 RibbonLoadBalancerClient 的execute 执行 getLoadBalancer 最后执行 getInstance 通过spring容器 获取 RibbonClientConfiguration debug链路追踪
接口 认实现类 描述
IClientConfig efaultClientConfigImpl 管理配置接口
IRule ZoneAvoidanceRule 均衡策略接口
IPing DummyPing 检查服务可用性接口
ServerList
ILoadBalancer ZoneAwareLoadBalancer 负载均衡接口
ServerListUpdater PollingServerListUpdater 定时更新服务列表接口
ServerIntrospector DefaultServerIntrospector 安全端口接口
OpenFegin :
关键类 FeignClientFactoryBean ReflectiveFeign FeignInvocationHandler
FeignAutoConfiguration 自动装配 把 FeignContext、Targeter 注入到IOC容器中
FeignRibbonClientAutoConfiguration -> DefaultFeignLoadBalancedConfiguration 返回一个 LoadBalancerFeignClient 的client 解析 Feign 客户端
EnableFeignClients 注解和 FeignClient 注解
1: 自动装配期间把 FeignClient注册成一个 FeignClientFactoryBean
EnableFeignClients 放在springboot的启动类 EnableFeignClients注解里面会Import FeignClientsRegistrar 类
FeignClientsRegistrar 会扫描有 FeignClient 的注解 得到一个 FeignClientFactoryBean 注入到IOC容器中
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class); registerFeignClient 方法
FeignClientFactoryBean.getObject 方法里在 targeter.target 方法里面 build.newInstance 获得一个 ReflectiveFeign 类 ReflectiveFeign会生成一个代理对象
最终调用 FeignInvocationHandler.invoke (FeignInvocationHandler 是针对方法级别的Handle)
2:执行handle方法
在 ReflectiveFeign 的静态内部类 FeignInvocationHandler 里面执行逻辑 执行 SynchronousMethodHandler.invoke
由该 Handler 完成后续的 HTTP 转换, 发送, 接收, 翻译HTTP响应的工作
NamedContextFactory 上下文概念也就是NamedContextFactory
子context或者叫子容器,子context维护自身的所有bean 例如 FeignContext 就是 feign客户端的上下文 通过名称获取相对应的上下文来进行 客户端对服务端的访问
继承NamedContextFactory 类的构造方法 例如 FeignContext 需要添加 super(FeignClientsConfiguration.class, "feign", "feign.client.name");
FeignClientsConfiguration context 需要加载的自动化配置类
Eureka AP特性
服务注册的顶层接口为 ServiceRegistry -> Registration 只要是springcloud的服务注册组件 都会实现这个类 例如 nacos eureka
EurekaClient 客户端
1:EurekaClientAutoConfiguration 自动装配类
EurekaAutoServiceRegistration 在自动装配类里面 继承了 SmartLifecycle 会执行 onApplicationEvent 最后调用start方法
SmartLifecycle 在容器所有bean加载和初始化完毕执行
继承了 SmartLifecycle 的类会在springboot的run方法里面的 refresh 调用到 AbstractApplicationContext 的 finishRefresh getLifecycleProcessor().onRefresh();
在这里进行调用 继承了 SmartLifecycle 的类的start方法
AbstractApplicationContext.finishRefresh 调用 this.getLifecycleProcessor().onRefresh(); 会执行实现了 SmartLifecycle 接口的类的 start 方法
EurekaAutoServiceRegistration 会实现 SmartLifecycle 智能生命周期接口 然后在bean 加载完之后执行 EurekaAutoServiceRegistration.start 方法
最后在start方法里面 this.serviceRegistry.register(this.registration); 更改实例状态 方法最终会调用 EurekaServiceRegistry 类中的register方法 监听 notify 方法实现 注册监听事件
start 方法
更改实例状态
reg.getApplicationInfoManager().setInstanceStatus(reg.getInstanceConfig().getInitialStatus());
ApplicationInfoManager ApplicationInfoManager.StatusChangeListener.notify()
注册健康机制
reg.getHealthCheckHandler().ifAvailable((healthCheckHandler) -> {reg.getEurekaClient().registerHealthCheck(healthCheckHandler);}); HealthCheckHandler
EurekaClientConfiguration内部类里面撞在了一个 EurekaClient = new CloudEurekaClient
装载的时候CloudEurekaClient的父类 DiscoveryClient 会初始化
2:DiscoveryClient
本地服务缓存为 AtomicReference
如果集成了ribbon的话 ribbon从 localRegionApps 获取服务地址信息
DiscoveryClient 在 initScheduledTasks 方法会开启多个线程池
scheduler 线程池 用来定时执行心跳的线程池任务和刷新缓存的线程池任务
heartbeatExecutor 心跳线程池 想服务端发送请求 向服务端表示客户端的应用正常运行 30秒发送一次
检测服务的心跳的线程池
cacheRefreshExecutor 缓存线程池 刷新需要调用的服务地址的缓存 CacheRefreshThread 执行run方法里面的 fetchRegistry 方法刷新缓存
定时同步服务端的实例列表信息
statusChangeListener = new ApplicationInfoManager.StatusChangeListener() 匿名内部类 实现 EurekaServiceRegistry 的 notify方法
applicationInfoManager.registerStatusChangeListener(statusChangeListener) 然后发布事件
instanceInfoReplicator.start 会执行 InstanceInfoReplicator 类的 run 方法
在run方法里面 执行 discoveryClient.register(); 进行服务的注册
AbstractJerseyEurekaHttpClient 进行Eureka客户端到服务端的通信
statusChangeListener = new ApplicationInfoManager.StatusChangeListener() { 监听上面更改实例状态
注册时取全量数据 然后就交给缓存线程池获取
getAndStoreFullRegistry(); 全量获取服务接口 AbstractJerseyEurekaHttpClient
getAndUpdateDelta 增量获取服务接口
EurekaServer 服务端
ApplicationResource ApplicationsResource 类接收请求
ApplicationResource.addInstance 里面有一个 registry.register(info, "true".equals(isReplication));
进入到 PeerAwareInstanceRegistryImpl.register
super.register(info, leaseDuration, isReplication); 调用了 AbstractInstanceRegistry.register 先进行注册
最终所有的服务都会注册到 registry 对象里面 这就是Eureka的一级缓存 如下:
ConcurrentHashMap
invalidateCache() 添加服务之后进行缓存的失效 在 ResponseCacheImpl 的invalidate 方法 执行对应key的缓存失效
ResponseCacheImpl
readWriteCacheMap 二级缓存(写缓存) LoadingCache
readOnlyCacheMap 一级缓存(读缓存) ConcurrentMap
replicateToPeers(Action.Register, info.getAppName(), info.getId(), info, null, isReplication); 然后同步到其他的服务端节点
衰减重试 和Eureka自我保护机制相关
客户端 DiscoveryClient类 会维护一个 TimedSupervisorTask的定时心跳任务 向注册中心去发送心跳请求
如果遇到网络抖动的话 会延长心跳的访问时间 是用初始时间乘以2 来延长心跳时间
这个时间当访问正常的时候会回置为30秒
Eureka自我保护机制
默认如果在15分钟内超过85%的客户端节点都没有正常的心跳,那么Eureka就认为客户端与注册中心出现了网络故障,Eureka Server自动进入自我保护机制
Eureka Server不再从注册列表中移除因为长时间没收到心跳而应该过期的服务。
Eureka Server仍然能够接受新服务的注册和查询请求,但是不会被同步到其它节点上,保证当前节点依然可用。
当网络稳定时,当前Eureka Server新的注册信息会被同步到其它节点中。
EurekaServer存在3个变量:registry、readWriteCacheMap、readOnlyCacheMap,用来保存服务注册信息。
默认情况下,定时任务每30s将readWriteCacheMap中的服务信息同步到readOnlyCacheMap。
每60s,通过EvictTask将超过90s未续约的服务从registry、readWriteCacheMap中剔除。
Eureka多级缓存的意义 为了读写分离
当大规模服务注册、更新时,如果只是修改一个ConcurrenHashMap(registry),势必存在大量的锁竞争,影响性能。
Eureka是AP模型,设计之初就是为了满足高可用,只需要满足最终可用。所以用到多级缓存来实现读写分离,注册时直接写入registry,同时失效readWriteCacheMap中对应的key,
获取注册信息时,如果使用use-read-only-response-cache开启了读缓存,则先从readOnlyCacheMap中读取,如果readOnlyCacheMap中不存在,则先用readWriteCacheMap中获取,将其放入readOnlyCacheMap,
若readWriteCacheMap中不存在,则返回空
Spring-cloud-config
MutablePropertySources 属性资源类
List
http://e58e836c5a68.ngrok.io/actuator/bus-refresh
http://e58e836c5a68.ngrok.io/monitor?path="*" gitee 的WebHooks 不起作用 postman可以刷新
负责解析文件的组件 ConfigurableEnvironment 关键在于 PropertySource
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); 加载spring的配置
1:ConfigurableEnvironment environment = getOrCreateEnvironment(); 在SpringApplication.run 方法中 在 prepareEnvironment方法 会生成一个 StandardServletEnvironment 的 ConfigurableEnvironment 环境类
在 ConfigurableEnvironment 里面配置 configurePropertySources 和 configureProfiles 放入 MutablePropertySources
ConfigurableEnvironment = StandardServletEnvironment -> 在StandardServletEnvironment会加载下列配置
因为父类 AbstractEnvironment 的this.customizePropertySources(this.propertySources); 会直接调用StandardServletEnvironment的 customizePropertySources
StandardServletEnvironment(基于servlet环境的):customizePropertySources
SERVLET_CONFIG_PROPERTY_SOURCE_NAME:servlet的配置信息,也就是在中配置的 例如 DispatcherServlet ServletConfigPropertySource
SERVLET_CONTEXT_PROPERTY_SOURCE_NAME: 这个是servlet初始化的上下文,也就是以前我们在web.xml中配置的 context-param。 ServletContextPropertySource
JNDI_PROPERTY_SOURCE_NAME: 加载jndi.properties配置信息。
StandardEnvironment(标准的配置源):customizePropertySources
SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME: 系统变量,通过System.setProperty设置的变量,默认可以看到java.version、os.name等。 PropertiesPropertySource
SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME: 系统环境变量,也就是我们配置JAVA_HOME的地方。 SystemEnvironmentPropertySource
2:configureEnvironment(environment, applicationArguments.getSourceArgs());
addConversionService 添加一个类型转换器
configurePropertySources (可选项)
defaultProperties 加载默认的配置文件信息 有就放在 MutablePropertySources 最后面
CompositePropertySource 加载命令行参数 复合的PropertySource
没有addCommandLineProperties 就在 MutablePropertySources 首位放一个 SimpleCommandLinePropertySource
configureProfiles
把环境设置的profiles 加载至环境里面 例如yml文件中的 profiles.active
3:listeners.environmentPrepared(environment); 加载yml文件(springboot的功能)
listener.environmentPrepared 方法 进入到 SimpleApplicationEventMulticaster.multicastEvent -> listener.onApplicationEvent(event);
进入到 ConfigFileApplicationListener.onApplicationEvent 方法
onApplicationEvent -> postProcessEnvironment -> addPropertySources方法 new Loader(environment, resourceLoader).load();
onApplicationEnvironmentPreparedEvent:循环 EnvironmentPostProcessor
进入 ConfigFileApplicationListener 的 postProcessEnvironment
new Loader(environment, resourceLoader).load();
new Loader()
加载文件路径
this.resourceLoader = (resourceLoader != null) ? resourceLoader: new DefaultResourceLoader(getClass().getClassLoader());
spi机制 加载文件类型
this.propertySourceLoaders = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class,getClass().getClassLoader());
load()方法
FilteredPropertySource.apply
进入 load方法 没有配置会默认从下面目录便利某些文件
DEFAULT_SEARCH_LOCATIONS = "classpath:/,classpath:/config/,file:./,file:./config/*/,file:./config/";
最终调用到 YamlPropertySourceLoader 执行类的 load 方法来解析yml文件信息 放入 MutablePropertySources 的 List
onApplicationPreparedEvent
总结 遍历指定目录/默认目录下的文件 生成PropertySource 加载到 MutablePropertySources 里面 可以通过 environment.getProperty
springcloud提供了
PropertySourceLocator接口支持扩展自定义配置加载到spring Environment中。
也可以实现 PropertySourceLoader 然后加载配置文件 在ConfigFileApplicationListener 里面通过spi机制循环实现PropertySourceLoader类的 load 方法
弊端 因为是file 加载类 所以需要有相对应的file 才会进行load方法
所以 Springcloud 是用 ConfigServicePropertySourceLocator 在准备环境时 加载 远程配置信息
在run方法里面 prepareContext 方法 applyInitializers 里面 调用 PropertySourceBootstrapConfiguration 的initialize方法时候会遍历实现了 PropertySourceLocator 类
然后 locateCollection 方法里面调用 locate 方法 加载配置信息
ConfigServicePropertySourceLocator Spring-cloud-config 远程加载配置信息的类
spring-cloud-Hystrix
继承 HystrixCommand 类
使用 HystrixCommand 注解
HystrixCommandProperties 配置信息
HystrixCommand
getExecutionObservable
getFallbackObservable
executeCommandAndObserve
HystrixCommandAspect 使用注解 HystrixCommand/HystrixCollapser 的时候 会经过 HystrixCommandAspect 拦截
实现 HystrixCommand 注解 最终都是调用 execute 方法 通过 toObservable().toBlocking().toFuture(); 获取一个 Future 然后get 获取结果
Observable
Observable.defer(applyHystrixSemantics)
.map(wrapWithAllOnNextHooks);
AbstractCommand 的 toObservable 方法 返回 handleSemaphoreRejectionViaFallback / handleShortCircuitViaFallback(默认)
executeCommandWithSpecifiedIsolation 熔断和信号量判断的 Observable
getUserExecutionObservable -> HystrixCommand.getExecutionObservable 执行继承了 GenericCommand 的 run 方法
Observable
执行 GenericCommand 的run方法 和 getFallback 方法
滑动窗口: 流量控制技术
信号量隔离
如果使用了开启了信号量隔离进行限流熔断的话
executionSemaphore.tryAcquire() 使用 TryableSemaphoreActual 然后用 AtomicInteger count 去进行累加 直到大于设置的请求数量
小于等于请求数量 :执行 executeCommandAndObserve -> executeCommandWithSpecifiedIsolation
进去 getUserExecutionObservable 然后是真正调用run方法的方法 getExecutionObservable
然后调用 GenericCommand 的方法 执行对应的方法
大于请求数量 执行 handleSemaphoreRejectionViaFallback 直接报错并拒绝请求
ratelimiter的令牌桶算法和漏桶算法,都是直接对请求量来计数。只是令牌桶算法可以将前面一段时间没有用掉的请求量允许余额拿过继续用。而漏桶算法一段时间就允许这么多,前面没用掉的也不能用了。
hystrix信号量隔离限制的是tomcat等Web容器线程数,一段时间仅仅能支持这么多。多余的请求再来请求线程资源,就被拒绝了。
所以是一种“曲径通幽”的限流方式。因为实际是通过隔离了部分容器线程资源,也算是一种隔离方式。
信号量隔离模式:使用一个原子计数器(或信号量)记录当前有多少个线程在运行。
当有新的请求时,先判断计数器的数值,若超过了设置的最大线程数,则丢弃该请求,反之,计数器+1,
请求返回时计数器-1.这种方式是严格的控制线程且立即返回模式,无法应对突发流量,好处是没有线程切换带来的资源消耗。
1、不会使用Hystrix管理的线程池处理请求。使用容器(Tomcat)的线程处理请求逻辑
2、不涉及线程切换,资源调度,上下文的转换等,相对效率高
3、信号量隔离也会启动熔断机制。如果请求并发数超标,则触发熔断,返回fallback数据。
4、设置信号量隔离后,线程池相关配置失效
线程池隔离
Hystrix默认使用了线程池模式
executionSemaphore.tryAcquire() 使用 TryableSemaphoreNoOp 直接返回true
执行 executeCommandAndObserve -> executeCommandWithSpecifiedIsolation
executeCommandWithSpecifiedIsolation 判断 executionIsolationStrategy 属性是线程池还是信号量熔断
进去 getUserExecutionObservable 然后是真正调用run方法的方法 getExecutionObservable
然后调用 GenericCommand 的方法 执行对应的方法
使用一个线程池来存储当前的请求,线程池对请求做处理,设置任务的超时时间,堆积的请求先进入队列。
这种方式需要对每一个依赖服务申请线程池资源,有一定的资源消耗,线程切换,好处是可以应对突发流量。
通过threadPoolKey指定依赖服务的线程池key,创建命令时,创建线程池并缓存到ConcurrentHashMap
这样操作,即使某个远程依赖服务出现异常,也不会影响其他服务调用。
hystrix请求合并如何实现?
微服务架构中通常需要依赖多个远程的微服务,而远程调用中最常见的问题就是通信消耗与连接数占用。在高并发的情况之下,因通信次数的增加,总的通信时间消耗将会变得越来越长。
同时,因为依赖服务的线程池资源有限,将出现排队等待与响应延迟的清况。
为了优化这两个问题,Hystrix 提供了HystrixCollapser来实现请求的合并,以减少通信消耗和线程数的占用。
HystrixCollapser 实现了在 HystrixCommand之前放置一个合并处理器,将处于一个很短的时间窗(默认10毫秒)内对同一依赖服务的多个请求进行整合,并以批量方式发起请求的功能(前提是服务提供方提供相应的批量接口)。
HystrixCollapser的封装多个请求合并发送的具体细节,开发者只需关注业务上将单次请求合并成多次请求即可。