spring cloud 源码追踪
Eureka
① 客户端配置属性:EurekaClientConfigBean(初始化相关线程,是否需要和eurekaserver沟通)
② 配置心跳的间隔时间:org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean#leaseRenewalIntervalInSeconds
③ eureka server端配置:org.springframework.cloud.netflix.eureka.server.EurekaServerConfigBean
④ 配置初始化:EurekaClientAutoConfiguration
⑤ client端发送心跳:com.netflix.discovery.shared.transport.jersey.AbstractJerseyEurekaHttpClient#sendHeartBeat
⑥ eureka启动的springboot类上要加上@EnableDiscoveryClient
⑦ 在发送心跳时,如果指定server端请求异常就会尝试下一个server
com.netflix.discovery.shared.transport.decorator.RetryableEurekaHttpClient#execute
假设client端配置server如下
eureka.client.serviceUrl.defaultZone=http://localhost:8889/eureka/,http://localhost:8888/eureka/
也就是如果8889一直正常,就只会向它发送心跳
⑧ com.netflix.discovery.DiscoveryClient 用于发送心跳(com.netflix.discovery.shared.transport.jersey.AbstractJerseyEurekaHttpClient#sendHeartBeat),定时拉取最新eureka server 上得serverList并更新(com.netflix.discovery.DiscoveryClient.CacheRefreshThread 这个才是真正地向eureka拉取最新地服务IP,然后更新到相应的map中,供其它地方从map中取,比如下面的PollingServerListUpdater)
eureka client发送心跳走JerseyApplicationClient, 里面的addExtraHeaders表示isReplication 不为true
@Override
public EurekaHttpResponse<InstanceInfo> sendHeartBeat(String appName, String id, InstanceInfo info, InstanceStatus overriddenStatus) {
String urlPath = "apps/" + appName + '/' + id;
ClientResponse response = null;
try {
WebResource webResource = jerseyClient.resource(serviceUrl)
.path(urlPath)
.queryParam("status", info.getStatus().toString())
.queryParam("lastDirtyTimestamp", info.getLastDirtyTimestamp().toString());
if (overriddenStatus != null) {
webResource = webResource.queryParam("overriddenstatus", overriddenStatus.name());
}
Builder requestBuilder = webResource.getRequestBuilder();
//client发送心跳和eureka server 互相同步心跳都会走这个方法
//eureka server会在这里走子类JerseyReplicationClient,设置isReplication为true
//client走子类JerseyApplicationClient,就没有设置
addExtraHeaders(requestBuilder);
response = requestBuilder.put(ClientResponse.class);
EurekaHttpResponseBuilder<InstanceInfo> eurekaResponseBuilder = anEurekaHttpResponse(response.getStatus(), InstanceInfo.class).headers(headersOf(response));
if (response.hasEntity()) {
eurekaResponseBuilder.entity(response.getEntity(InstanceInfo.class));
}
return eurekaResponseBuilder.build();
} finally {
if (logger.isDebugEnabled()) {
logger.debug("Jersey HTTP PUT {}/{}; statusCode={}", serviceUrl, urlPath, response == null ? "N/A" : response.getStatus());
}
if (response != null) {
response.close();
}
}
}
⑨ eureka server集群同步,实现最终一致性
发送同步请求
com.netflix.eureka.cluster.ReplicationTaskProcessor#process(java.util.List<com.netflix.eureka.cluster.ReplicationTask>)
==>com.netflix.eureka.transport.JerseyReplicationClient#submitBatchUpdates
单server node接收其它server同步过来的请求,这个时候会将isRelication置为true,
接收同步请求
com.netflix.eureka.resources.PeerReplicationResource#batchReplication
===>com.netflix.eureka.resources.InstanceResource#renewLease(最终会调用到这里)
⑩com.netflix.loadbalancer.BaseLoadBalancer.Pinger用于校验服务是否仍在服务,防止网络异常,但是服务列表没有及时刷新
心跳请求,请求方式为put:http://域名:端口/eureka/apps/test/test:f26bf974da597329f6f7a63e689c8ed6?status=UP&lastDirtyTimestamp=1555913417898
eureka server接收eureka client端的心跳请求(实时更新 eureka server端得服务列表上服务的状态,具体值参考InstanceStatus)
eureka server端如何下线服务(这个避免不了,client端接着发送心跳,又注册上去):
http://localhost:8888/eureka/apps/SERVICE-DISCOVERY/service-discovery:c0c8f42019fadcfb7f189204f4fb06cc
eureka server接收client的请求:com.netflix.eureka.resources.InstanceResource
Ribbon
初始化
初始化
org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration(由org.springframework.cloud.netflix.ribbon.RibbonClientConfigurationRegistrar导入)
org.springframework.cloud.netflix.ribbon.eureka.EurekaRibbonClientConfiguration
org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration
饥饿模式初始化ribbon client(ribbon.eager-load.enabled=true并且配置ribbon.eager-load.clients指定需要启动初始化的ribbonClient)
org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration#ribbonApplicationContextInitializer
在这会给每个ribbon client 创建一个AnnotationConfigApplicationContext作为独立容器
==>
org.springframework.cloud.context.named.NamedContextFactory#createContext
在每个ribbon client的context中注册:RibbonAutoConfiguration、RibbonClientConfiguration(defaultConfigType)、RibbonEurekaAutoConfiguration、EurekaRibbonClientConfiguration
ribbon client拉取eureka注册中心服务
初始化PollingServerListUpdater
org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration#ribbonServerListUpdater
==>
com.netflix.loadbalancer.DynamicServerListLoadBalancer#restOfInit
==>
com.netflix.loadbalancer.DynamicServerListLoadBalancer#enableAndInitLearnNewServersFeature(开启pollingServer线程)
==>
com.netflix.loadbalancer.ServerListUpdater.UpdateAction#doUpdate
==>
com.netflix.loadbalancer.DynamicServerListLoadBalancer#updateListOfServers
==>
com.netflix.niws.loadbalancer.DiscoveryEnabledNIWSServerList#obtainServersViaDiscovery
==>
com.netflix.loadbalancer.DynamicServerListLoadBalancer#updateAllServerList
刷新了服务列表缓存
@RibbonClient注解原理,源码解释
作用:用于自定义ribbon client相关配置,比如:ILoadBalancer、ServerListFilter、IRule,可以参考RibbonClientConfiguration,替换RibbonClientConfiguration中某些bean
示例:
@SpringBootApplication
@RibbonClients(value = {
@RibbonClient(name = "productServer", configuration = ProductRibbonConfiguration.class)})
public class ProductCenterApplication {
public static void main(String[] args) {
SpringApplication.run(ProductCenterApplication.class, args);
}
}
@Configuration
@ExcudeAnnotation
public class ProductRibbonConfiguration{
@Bean
public ServerList<Server> ribbonServerList() {
// 实例列表
String listOfServers = "http://localhost:8080";
String[] splits = listOfServers.split(",");
int len = splits.length;
if (len == 0) {
return new StaticServerList<Server>();
}
Server[] servers = new Server[len];
for (int i = 0; i < len; i++) {
servers[i] = new Server(splits[i].trim());
}
return new StaticServerList<Server>(servers);
}
}
原理:就是把@RibbonClient配置的Configuration注入到每个ribbon client的ApplicationContext中,然后每个Ribbon client的IOC容器在初始化RibbonClientConfiguration时候,里面相应ILoadBalancer、ServerListFilter、ServerList等能够被替换掉,走自定义得。
RibbonClientConfigurationRegistrar
==>
org.springframework.cloud.netflix.ribbon.RibbonClientConfigurationRegistrar#registerClientConfiguration 注入RibbonClientSpecification(里面包含了我们定义的configuration)到当前容器中
==>
org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration#springClientFactory
将上面所有的RibbonClientSpecification 作为springClientFactory.configurations即NamedContextFactory.configurations
每个ribbon client初始化ApplicationContext时候会调用
org.springframework.cloud.context.named.NamedContextFactory#createContext
==>
将RibbonClientSpecification中的configuration也就是@RibbonClient配置的Configuration注入到client的ApplicationContext中
==>
ribbon client 的IOC中RibbonClientConfiguration初始化
==>
org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration#ribbonServerList(拿这个举例,上面示例就是替换这个),就不会被初始化,因为当前IOC中的ServerList为:ProductRibbonConfiguration.ribbonServerList
==>
org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration#ribbonLoadBalancer初始化,就包含了自定义的ServerList,比如这个不去eureka拉取服务,走写死的服务
默认的负载均衡器:
org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient
Hystrix
hystrix配置大集合:com.netflix.hystrix.HystrixCommandProperties、com.netflix.hystrix.HystrixThreadPoolProperties
获取fallBack回调:com.netflix.hystrix.contrib.javanica.utils.MethodProvider.FallbackMethodFinder#doFind
获取原始业务异常:com.netflix.hystrix.AbstractCommand#getFailedExecutionException
具体执行异常fallBack回调函数:com.netflix.hystrix.contrib.javanica.command.GenericCommand#getFallback
hystrix线程:com.netflix.hystrix.HystrixThreadPool
使用示例:
@Service
public class HystrixTestSevice {
@HystrixCommand(fallbackMethod = "testCircuitBreakerFallback", commandProperties = {
//errorThresholdPercentage 断路器错误请求百分比
//是否开启熔断器
@HystrixProperty(name = "circuitBreaker.enabled", value = "true"),
// @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "0"),//失败的比例
//@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value ="1"), //滑动窗口内允许失败的次数
// @HystrixProperty(name = "circuitBreaker.executionTimeoutInMilliseconds", value = "3000"),
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000"),
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "1000")
})
public String testCircuitBreaker(int id) {
if (id % 2 == 0 && id < 40) { // 直接返回
return "consumer testCircuitBreaker " + id;
} else { // 无限循环模拟超时
int j = 0;
/*try {
Thread.currentThread().sleep(6000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}*/
System.out.println(1 / 0);
return "";
}
}
public String testCircuitBreakerFallback(int id, Throwable e) {
e.printStackTrace(); //打印完整的错误堆栈
return "fallback:failure";
}
}
Zuul
zuul:用作网关,服务鉴权和限流,服务分发
主要核心流转类:com.netflix.zuul.http.ZuulServlet
配置初始化类:org.springframework.cloud.netflix.zuul.ZuulConfiguration
服务分发:com.netflix.zuul.FilterProcessor#route
ribbon分发调用请求并返回数据:org.springframework.cloud.netflix.zuul.filters.route.RibbonRoutingFilter
对于Ribbon,Zuul默认封装了org.springframework.cloud.netflix.zuul.filters.route.apache.HttpClientRibbonCommand利用Hystrix去调用底层服务,实际调用:org.springframework.cloud.netflix.zuul.filters.route.RibbonRoutingFilter#forward