前提
阅读和分析源码的意义再哪儿?
- 技术功底
- hold全场
- 架构设计能力
- 职场竞争力
源码实际上是码农技术水平的分水岭
1. 准备调试环境
- IDEA
- GRADLE
- Spring Cloud Edgware.SR3 对应 Netflix Eureka源码:1.7.2
基于Netflix Eureka的Eureka源码调试 git clone https://github.com/Netflix/eureka.git
Spring Cloud Netlix Eureka 只不过对netflix的eureka进行了封装,加了一些注解,对spring boot进行支持
https://github.com/spring-cloud/spring-cloud-netflix
https://github.com/Netflix/eureka
2.Netflix Eureka 项目结构
- eureka-client:eureka 的客户端,注册到eureka server上去的一个服务,就是一个客户端,无论是注册、还是发现服务,无论是提供者还是服务消费者,都是一个eureka 客户端
- eureka-client-archaius2
- eureka-client-jersey2:jersey框架类比于spring web mvc框架,支持mvc模式,支持restful http请求
- eureka-core:这个指eureka 客户端,注册中心
- eureka-core-jersey2
- eureka-examples:eureka 提供的例子,单元测试
- eureka-resources:基于jsp开发的eureka 控制台,web页面,可以看到服务的注册信息
- eureka-server:这里把eureka-client、eureka-server、eureka-resources 打包成一个war包,eureka server本身也是一个eureka client 同时也是注册中心,同时也是eureka控制台,真正的使用注册中心
- eureka-server-governator
- eureka-test-utils:单元测试工具类
3.eureka server 初探
3.1 eureka server的gradle分析
-
eureka运行的核心的流程,eureka client往eureka server注册的过程,服务注册;服务发现,eureka client从eureka server获取注册表的过程;服务心跳,eureka client定时往eureka server发送续约通知(心跳);服务实例摘除;通信,限流,自我保护,server集群
-
eureka server是依赖eureka client的,eureka server也是一个eureka client,因为server集群模式的时候,eureka server也要扮演eureka client的角色,往其他的eureka server上去注册。
-
eureka core,扮演了核心的注册中心的角色,接收别人的服务注册请求,提供服务发现的功能,保持心跳(续约请求),摘除故障服务实例。eureka server依赖eureka core的,基于eureka core的功能对外暴露接口,提供注册中心的功能
-
jersey框架,eureka server依赖jersey框架,可以认为jersey框架类比于spring web mvc框架,支持mvc模式,支持restful http请求。jersey在国内几乎没什么公司使用,很少很少,在国外有一些公司用,netflix就会用,jersey去开发eureka。eureka里面,服务通信,都是基于http请求的,restful接口来通信的。
-
eureka client和eureka server之间进行通信,都是基于jersey框架实现http restful接口请求和调用的。eureka-client-jersey2,eureka-core-jersey2,其实这两个工程,就是eureka为了方便自己,对jersey框架的一个封装,提供更多的功能,方便自己使用。
![](https://img-blog.csdnimg.cn/img_convert/87641780eaec0b36c0a75c8ebaf6a5bf.png)
eureka server 本质就是一个web应用,工程里并没有什么源码
依赖 eureka-client 本身也可以作为客户端进行服务注册
依赖 eureka-core eureka-server的全部功能都在eureka-core工程中
依赖 jersey 通过jersey 封装http请求 restful接口通信
打包会将eureka-resorces工程下的resources 给搞到这个war包里
3.2 eureka-server web工程的web.xml分析
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<!-- listener 是在web应用启动的时候就会执行的,负责对这个web应用进行初始化的事-->
<!-- 在eureka-core里 负责eureka-server的初始化-->
<listener>
<listener-class>com.netflix.eureka.EurekaBootStrap</listener-class>
</listener>
<!-- 负责状态相关的处理逻辑-->
<filter>
<filter-name>statusFilter</filter-name>
<filter-class>com.netflix.eureka.StatusFilter</filter-class>
</filter>
<!-- 对请求进行认证处理-->
<filter>
<filter-name>requestAuthFilter</filter-name>
<filter-class>com.netflix.eureka.ServerRequestAuthFilter</filter-class>
</filter>
<!-- 负责限流相关的逻辑 -->
<filter>
<filter-name>rateLimitingFilter</filter-name>
<filter-class>com.netflix.eureka.RateLimitingFilter</filter-class>
</filter>
<!-- 压缩相关的,encoding编码相关-->
<filter>
<filter-name>gzipEncodingEnforcingFilter</filter-name>
<filter-class>com.netflix.eureka.GzipEncodingEnforcingFilter</filter-class>
</filter>
<!-- 类似于mvc框架-->
<filter>
<filter-name>jersey</filter-name>
<filter-class>com.sun.jersey.spi.container.servlet.ServletContainer</filter-class>
<init-param>
<param-name>com.sun.jersey.config.property.WebPageContentRegex</param-name>
<param-value>/(flex|images|js|css|jsp)/.*</param-value>
</init-param>
<init-param>
<param-name>com.sun.jersey.config.property.packages</param-name>
<param-value>com.sun.jersey;com.netflix</param-value>
</init-param>
<!-- GZIP content encoding/decoding -->
<init-param>
<param-name>com.sun.jersey.spi.container.ContainerRequestFilters</param-name>
<param-value>com.sun.jersey.api.container.filter.GZIPContentEncodingFilter</param-value>
</init-param>
<init-param>
<param-name>com.sun.jersey.spi.container.ContainerResponseFilters</param-name>
<param-value>com.sun.jersey.api.container.filter.GZIPContentEncodingFilter</param-value>
</init-param>
</filter>
<!-- statusFilter requestAuthFilter通用逻辑,对所用请求生效-->
<filter-mapping>
<filter-name>statusFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>requestAuthFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 默认是不开启的,如果要打开,需要把注释打开 让filter生效-->
<!-- Uncomment this to enable rate limiter filter.
<filter-mapping>
<filter-name>rateLimitingFilter</filter-name>
<url-pattern>/v2/apps</url-pattern>
<url-pattern>/v2/apps/*</url-pattern>
</filter-mapping>
-->
<!-- 拦截 /v2/apps相关的请求-->
<filter-mapping>
<filter-name>gzipEncodingEnforcingFilter</filter-name>
<url-pattern>/v2/apps</url-pattern>
<url-pattern>/v2/apps/*</url-pattern>
</filter-mapping>
<!-- 拦截所有请求-->
<filter-mapping>
<filter-name>jersey</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 欢迎页面-->
<welcome-file-list>
<welcome-file>jsp/status.jsp</welcome-file>
</welcome-file-list>
</web-app>
- listener:监听web应用启动的时候就会执行,负责对这个web应用进行初始化的事儿,最重要的就是listener-com.netflix.eureka.EurekaBootStrap
- filter:
- StatusFilter:负责状态相关的处理逻辑
- ServerRequestAuthFilter:对请求进行授权认证的处理的
- RateLimitingFilter:负责限流相关的逻辑的
- GzipEncodingEnforcingFilter:gzip,压缩相关的;encoding,编码相关
- jersey:是拦截所有的请求的
- welcome-file-list:配置了status.jsp是欢迎页面,首页,eureka-server的控制台页面,展示注册服务的信息
eureka-server -> build.gradle中的依赖和构建的配置
eureka-server -> web应用 -> war包 -> tomcat就可以启动
web.xml -> listener -> 4个filter -> jersy filter -> filter mapping -> welcome file
3.3 eureka server启动之环境初始化
当eureka-server打成war包 放在容器中被启动,监听器会监听到web应用被启动
EurekaBootStrap 实现了 ServletContextListener 重写contextInitialized方法
web应用启动会触发contextInitialized方法执行,也就是eureka server启动初始化的入口
3.3.1 初始化Eureka-server环境-initEurekaEnvironment
protected void initEurekaEnvironment() throws Exception {
logger.info("Setting the eureka configuration..");
//ConfigurationManager 管理Eureka自己的所有配置 读取配置文件中配置到内存中,
//供后续的eureka-server运行来使用 单例
//获取数据中心
String dataCenter = ConfigurationManager.getConfigInstance().getString(EUREKA_DATACENTER);
// EUREKA_DATACENTER 如果没有配置 就默认default
if (dataCenter == null) {
//用默认的
logger.info("Eureka data center value eureka.datacenter is not set, defaulting to default");
ConfigurationManager.getConfigInstance().setProperty(ARCHAIUS_DEPLOYMENT_DATACENTER, DEFAULT);
} else {
ConfigurationManager.getConfigInstance().setProperty(ARCHAIUS_DEPLOYMENT_DATACENTER, dataCenter);
}
//EUREKA_ENVIRONMENT = eureka.environment 拿环境
String environment = ConfigurationManager.getConfigInstance().getString(EUREKA_ENVIRONMENT);
if (environment == null) {
//如果没配默认就是test环境
ConfigurationManager.getConfigInstance().setProperty(ARCHAIUS_DEPLOYMENT_ENVIRONMENT, TEST);
logger.info("Eureka environment value eureka.environment is not set, defaulting to test");
}
}
public static AbstractConfiguration getConfigInstance() {
if (instance == null) {
synchronized (ConfigurationManager.class) {
if (instance == null) {
instance = getConfigInstance(Boolean.getBoolean(DynamicPropertyFactory.DISABLE_DEFAULT_CONFIG));
}
}
}
return instance;
}
加载eureka-server.properties文件中的配置
ConfigurationManager.getConfigInstance()
:初始化配置管理器管理所有的配置(ConfigurationManager是属于netfilx config开源项目的),读取配置文件中配置到内存中,供后续的eureka-server运行来使用- getConfigInstance double check + volatile 单例 实现线程安全
- 初始化eurueka运行的环境,如果你没有配置的话,默认就给你设置为test环境,初始化完成
3.3.2 初始化Eureka-Server上下文-initEurekaServerContext
1.加载eureka-server.properties文件中的配置
EurekaServerConfig eurekaServerConfig = new DefaultEurekaServerConfig()
-
创建
DefaultEurekaServerConfig
对象,面向接口编程,eurekaServerConfig 是一个接口,会执行一个init()
方法,DefaultEurekaServerConfig
会将eureka-server.properties
文件中的配置加载出来 都放到ConfigurationManager
中去 -
DefaultEurekaServerConfig
提供各个获取配置项的各个方法,都是通过硬编码的配置项的名称,从DynamicPropertyFactory中获取配置项的值 -
在获取配置项的时候,如果没有配置,那么就会有默认的值,全部属性都有默认值
2.构建ApplicationInfoManager和eurekaClient
ApplicationInfoManager applicationInfoManager = null;
if (eurekaClient == null) {
EurekaInstanceConfig instanceConfig = isCloud(ConfigurationManager.getDeploymentContext())
? new CloudInstanceConfig()
: new MyDataCenterInstanceConfig();
applicationInfoManager = new ApplicationInfoManager(
instanceConfig, new EurekaConfigBasedInstanceInfoProvider(instanceConfig).get());
EurekaClientConfig eurekaClientConfig = new DefaultEurekaClientConfig();
eurekaClient = new DiscoveryClient(applicationInfoManager, eurekaClientConfig);
} else {
applicationInfoManager = eurekaClient.getApplicationInfoManager();
}
EurekaInstanceConfig:基于EurekaInstanceConfig对外暴露接口来获取这个eureka-client.properties文件中的一些配置的读取,判断是否云环境,如果不是从子类MyDataCenterInstanceConfig 创建配置类,面向接口编程
CommonConstants.CONFIG_FILE_NAME=eureka-client
初始化applicationInfoManager
:服务实例管理器 如果你是集群模式,eureka server
扮演的角色其实就是一个服务,作为一个服务会向其他eureka Server
注册 eureka Client->Application
,applicationInfoManager管理当前Server作为Client的InstanceInfo 实例信息
4
EurekaClientConfig eurekaClientConfig = new DefaultEurekaClientConfig() 包含了eureka client相关的一些配置项,去读eureka-client.properties里的一些配置
这里跟上面的EurekaInstanceConfig关注点是不一样的 EurekaInstanceConfig代表一些服务实例InstanceInfo的配置项,
EurekaClientConfig 关联的是eureka client的一些配置项,基于EurekaClientConfig 接口对外暴露 获取eureka client的一些配置项
DiscoveryClient:基于applicationInfoManager(包含了服务实例的信息、配置,作为服务实例管理的一个组件)和eurekaClientConfig(eureka client 相关的配置) 构建一个eureka client
- eureka client实例信息读取存储
- 是否从其他eureka服务器获取注册表获取抓取信息(eureka server集群状态)
- 支持调度的线程池
- 支持心跳的线程池
- 支持缓存刷新的线程池
- 支持eureka client和 eureka server 进行网络通信的组件
- 注册表的抓取
- 初始化调度任务
DiscoveryClient(ApplicationInfoManager applicationInfoManager, EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args,
Provider<BackupRegistry> backupRegistryProvider) {
if (args != null) {
this.healthCheckHandlerProvider = args.healthCheckHandlerProvider;
this.healthCheckCallbackProvider = args.healthCheckCallbackProvider;
this.eventListeners.addAll(args.getEventListeners());
this.preRegistrationHandler = args.preRegistrationHandler;
} else {
this.healthCheckCallbackProvider = null;
this.healthCheckHandlerProvider = null;
this.preRegistrationHandler = null;
}
this.applicationInfoManager = applicationInfoManager;
InstanceInfo myInfo = applicationInfoManager.getInfo();
clientConfig = config;
staticClientConfig = clientConfig;
transportConfig = config.getTransportConfig();
instanceInfo = myInfo;
if (myInfo != null) {
//appName代表一个服务名称,可能呢服务部署了多台机器,每台机器上部署就是一个服务实例
appPathIdentifier = instanceInfo.getAppName() + "/" + instanceInfo.getId();
} else {
logger.warn("Setting instanceInfo to a passed in null value");
}
this.backupRegistryProvider = backupRegistryProvider;
this.urlRandomizer = new EndpointUtils.InstanceInfoBasedUrlRandomizer(instanceInfo);
localRegionApps.set(new Applications());
//抓取注册表相关的东西
fetchRegistryGeneration = new AtomicLong(0);
remoteRegionsToFetch = new AtomicReference<String>(clientConfig.fetchRegistryForRemoteRegions());
remoteRegionsRef = new AtomicReference<>(remoteRegionsToFetch.get() == null ? null : remoteRegionsToFetch.get().split(","));
/**
* 指示此客户端是否应从eureka服务器获取eureka注册表信息
* 如果是eureka server的话 会将这个fetchRegistry给手动设置为false 默认是true
* 如果是eureka server集群的话就要保持为true
*/
if (config.shouldFetchRegistry()) {
this.registryStalenessMonitor = new ThresholdLevelsMetric(this, METRIC_REGISTRY_PREFIX + "lastUpdateSec_", new long[]{15L, 30L, 60L, 120L, 240L, 480L});
} else {
this.registryStalenessMonitor = ThresholdLevelsMetric.NO_OP_METRIC;
}
/**
* 指示此实例是否应向eureka服务器注册其信息以供其他人发现
* 如果是单个eureka server 就设置成false 否则反之
*/
if (config.shouldRegisterWithEureka()) {
this.heartbeatStalenessMonitor = new ThresholdLevelsMetric(this, METRIC_REGISTRATION_PREFIX + "lastHeartbeatSec_", new long[]{15L, 30L, 60L, 120L, 240L, 480L});
} else {
this.heartbeatStalenessMonitor = ThresholdLevelsMetric.NO_OP_METRIC;
}
logger.info("Initializing Eureka in region {}", clientConfig.getRegion());
//单机节点 不要注册也不要抓取
if (!config.shouldRegisterWithEureka() && !config.shouldFetchRegistry()) {
logger.info("Client configured to neither register nor query for data.");
scheduler = null;
heartbeatExecutor = null;
cacheRefreshExecutor = null;
eurekaTransport = null;
instanceRegionChecker = new InstanceRegionChecker(new PropertyBasedAzToRegionMapper(config), clientConfig.getRegion());
// This is a bit of hack to allow for existing code using DiscoveryManager.getInstance()
// to work with DI'd DiscoveryClient
DiscoveryManager.getInstance().setDiscoveryClient(this);
DiscoveryManager.getInstance().setEurekaClientConfig(config);
initTimestampMs = System.currentTimeMillis();
logger.info("Discovery Client initialized at timestamp {} with initial instances count: {}",
initTimestampMs, this.getApplications().size());
return; // no need to setup up an network tasks and we are done
}
try {
// default size of 2 - 1 each for heartbeat and cacheRefresh
//支持调度的线程池
scheduler = Executors.newScheduledThreadPool(2,
new ThreadFactoryBuilder()
.setNameFormat("DiscoveryClient-%d")
.setDaemon(true)
.build());
//支持心跳线程池
heartbeatExecutor = new ThreadPoolExecutor(
1, clientConfig.getHeartbeatExecutorThreadPoolSize(), 0, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
new ThreadFactoryBuilder()
.setNameFormat("DiscoveryClient-HeartbeatExecutor-%d")
.setDaemon(true)
.build()
); // use direct handoff
//支持缓存刷新的线程池
cacheRefreshExecutor = new ThreadPoolExecutor(
1, clientConfig.getCacheRefreshExecutorThreadPoolSize(), 0, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
new ThreadFactoryBuilder()
.setNameFormat("DiscoveryClient-CacheRefreshExecutor-%d")
.setDaemon(true)
.build()
); // use direct handoff
//支持底层eureka client 和eureka server 进行网络通信的组件
eurekaTransport = new EurekaTransport();
scheduleServerEndpointTask(eurekaTransport, args);
AzToRegionMapper azToRegionMapper;
if (clientConfig.shouldUseDnsForFetchingServiceUrls()) {
azToRegionMapper = new DNSBasedAzToRegionMapper(clientConfig);
} else {
azToRegionMapper = new PropertyBasedAzToRegionMapper(clientConfig);
}
if (null != remoteRegionsToFetch.get()) {
azToRegionMapper.setRegionsToFetch(remoteRegionsToFetch.get().split(","));
}
instanceRegionChecker = new InstanceRegionChecker(azToRegionMapper, clientConfig.getRegion());
} catch (Throwable e) {
throw new RuntimeException("Failed to initialize DiscoveryClient!", e);
}
//抓取注册表
//如果你配置 应该要抓取注册表的信息,那么就会在启动的时候来一次全量的注册表的抓取
if (clientConfig.shouldFetchRegistry() && !fetchRegistry(false)) {
//如果抓取失败 从备份哪里拿
fetchRegistryFromBackup();
}
// call and execute the pre registration handler before all background tasks (inc registration) is started
if (this.preRegistrationHandler != null) {
this.preRegistrationHandler.beforeRegistration();
}
//初始化调度任务
initScheduledTasks();
try {
Monitors.registerObject(this);
} catch (Throwable e) {
logger.warn("Cannot register timers", e);
}
// This is a bit of hack to allow for existing code using DiscoveryManager.getInstance()
// to work with DI'd DiscoveryClient
DiscoveryManager.getInstance().setDiscoveryClient(this);
DiscoveryManager.getInstance().setEurekaClientConfig(config);
initTimestampMs = System.currentTimeMillis();
logger.info("Discovery Client initialized at timestamp {} with initial instances count: {}",
initTimestampMs, this.getApplications().size());
}
PeerAwareInstanceRegistry:处理注册相关的事情
PeerAwareInstanceRegistry registry;
if (isAws(applicationInfoManager.getInfo())) {
registry = new AwsInstanceRegistry(
eurekaServerConfig,
eurekaClient.getEurekaClientConfig(),
serverCodecs,
eurekaClient
);
awsBinder = new AwsBinderDelegate(eurekaServerConfig, eurekaClient.getEurekaClientConfig(), registry, applicationInfoManager);
awsBinder.start();
} else {
//创建一个注册表
registry = new PeerAwareInstanceRegistryImpl(
eurekaServerConfig,
eurekaClient.getEurekaClientConfig(),
serverCodecs,
eurekaClient
);
}
/**
* Create a new, empty instance registry.
*/
protected AbstractInstanceRegistry(EurekaServerConfig serverConfig, EurekaClientConfig clientConfig, ServerCodecs serverCodecs) {
this.serverConfig = serverConfig;
this.clientConfig = clientConfig;
this.serverCodecs = serverCodecs;
this.recentCanceledQueue = new CircularQueue<Pair<Long, String>>(1000);
this.recentRegisteredQueue = new CircularQueue<Pair<Long, String>>(1000);
this.renewsLastMin = new MeasuredRate(1000 * 60 * 1);
//增量的任务调度 //默认30秒一次
//默认30秒一次 ,看一下服务实例的变更记录,是否在队列里停留超过180秒 如果超过3分钟,
//就会从队列里将这个服务实例给移除掉,这个recentlyChangedQueue 只保留最近三分钟的服务实例变更
this.deltaRetentionTimer.schedule(getDeltaRetentionTask(),
serverConfig.getDeltaRetentionTimerIntervalInMs(),
serverConfig.getDeltaRetentionTimerIntervalInMs());
}
/**
* 默认30秒一次 ,看一下服务实例的变更记录,是否在队列里停留超过180秒 如果超过3分钟,
* 就会从队列里将这个服务实例给移除掉,这个recentlyChangedQueue 只保留最近三分钟的服务实例变更
* @return
*/
private TimerTask getDeltaRetentionTask() {
return new TimerTask() {
@Override
public void run() {
Iterator<RecentlyChangedItem> it = recentlyChangedQueue.iterator();
while (it.hasNext()) {
//说明这个记录进入队列的时间超过3分钟
if (it.next().getLastUpdateTime() <
System.currentTimeMillis() - serverConfig.getRetentionTimeInMSInDeltaQueue()) {
//就把这条记录从队列移除
it.remove();
} else {
break;
}
}
}
};
}
PeerEurekaNodes
//处理PeerEurekaNodes节点相关的事情
//代表一个eureka server集群
PeerEurekaNodes peerEurekaNodes = getPeerEurekaNodes(
registry,
eurekaServerConfig,
eurekaClient.getEurekaClientConfig(),
serverCodecs,
applicationInfoManager
);
eureka server上下文(context)的构建及初始化
完成eureka server上下文(context)的构建及初始化,EurekaServerContext代表了当前这个eureka server的一个服务器上下文,包含了服务器需要的所有的东西
serverContext = new DefaultEurekaServerContext(
eurekaServerConfig,
serverCodecs,
registry,
peerEurekaNodes,
applicationInfoManager
);
//将serverContext 放入EurekaServerContextHolder 以后谁要是要用这个上下文,直接从holder里面去
EurekaServerContextHolder.initialize(serverContext);
// 启动eureka server
serverContext.initialize();
logger.info("Initialized server context");
从相邻的eureka节点拷贝注册信息
int registryCount = registry.syncUp();
//自动检查服务服务实例是否宕机
registry.openForTraffic(applicationInfoManager, registryCount);
eureka监控相关的事情
EurekaMonitors.registerAllStats();