Eureka源码剖析
@EnableEurekaServer
@EnableEurekaServer
@SpringBootApplication
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
启动EurekaServer只需要@EnableEurekaServer注解就可以了,所以配置和初始化都在这个注解之中操作。
@EnableDiscoveryClient
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({EurekaServerMarkerConfiguration.class})
public @interface EnableEurekaServer {
}
@Configuration
public class EurekaServerMarkerConfiguration {
public EurekaServerMarkerConfiguration() {
}
// 只是把一个空的Marker类变成了spring中的Bean
@Bean
public EurekaServerMarkerConfiguration.Marker eurekaServerMarkerBean() {
return new EurekaServerMarkerConfiguration.Marker();
}
class Marker {
Marker() {
}
}
}
这里用到了Springboot自动配置的功能,那就来看下Springboot的自动配置原理,参考这篇blog:https://blog.csdn.net/u014745069/article/details/83820511
EurekaServerAutoConfiguration
这里可以看到在spring-cloud-netfix-eureka-server-{version}.jar中, META-INF/spring.factories之中配置了自动装配类:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.netflix.eureka.server.EurekaServerAutoConfiguration
@Configuration
@Import({EurekaServerInitializerConfiguration.class})
@ConditionalOnBean({Marker.class})
@EnableConfigurationProperties({EurekaDashboardProperties.class, InstanceRegistryProperties.class})
@PropertySource({"classpath:/eureka/server.properties"})
public class EurekaServerAutoConfiguration extends WebMvcConfigurerAdapter {
- @ConditionalOnBean判断EurekaServerMarkerConfiguration.Marker这个Bean是否存在。如果存在才会解析这个自动配置类,从而呼应了@EnableEurekaServer这个注解的功能。
这种都是Springboot的基本操作了,再Saga Omega的代码中也出现过@ConditionalOnProperty基于配置文件来选择是否启用配置:
@Configuration
@Import({OmegaSpringConfig.class,TransactionAspectConfig.class})
@ConditionalOnProperty(value = {"omega.enabled"}, matchIfMissing = true)
public class OmegaSpringAutoConfiguration {
}
-
@EnableConfigurationProperties注解和@PropertySource注解都加载了一些键值对的属性。
-
@Import导入了一个初始化类EurekaServerInitializerConfiguration
EurekaServerAutoConfiguration作为自动配置类,我们看看它主要配置了哪些东西(有所忽略)
看板
@Bean
@ConditionalOnProperty(prefix = "eureka.dashboard", name = "enabled", matchIfMissing = true)
public EurekaController eurekaController() {
return new EurekaController(this.applicationInfoManager);
}
发现注册
@Bean
public PeerAwareInstanceRegistry peerAwareInstanceRegistry(
ServerCodecs serverCodecs) {
this.eurekaClient.getApplications(); // force initialization
return new InstanceRegistry(this.eurekaServerConfig, this.eurekaClientConfig,
serverCodecs, this.eurekaClient,
this.instanceRegistryProperties.getExpectedNumberOfRenewsPerMin(),
this.instanceRegistryProperties.getDefaultOpenForTrafficCount());
}
启动引导
@Bean
public EurekaServerBootstrap eurekaServerBootstrap(PeerAwareInstanceRegistry registry,
EurekaServerContext serverContext) {
return new EurekaServerBootstrap(this.applicationInfoManager,
this.eurekaClientConfig, this.eurekaServerConfig, registry,
serverContext);
}
Jersey提供rpc调用
/**
* Construct a Jersey {@link javax.ws.rs.core.Application} with all the resources
* required by the Eureka server.
*/
@Bean
public javax.ws.rs.core.Application jerseyApplication(Environment environment,
ResourceLoader resourceLoader) {
ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(
false, environment);
// Filter to include only classes that have a particular annotation.
//
provider.addIncludeFilter(new AnnotationTypeFilter(Path.class));
provider.addIncludeFilter(new AnnotationTypeFilter(Provider.class));
// Find classes in Eureka packages (or subpackages)
//
Set<Class<?>> classes = new HashSet<Class<?>>();
for (String basePackage : EUREKA_PACKAGES) {
Set<BeanDefinition> beans = provider.findCandidateComponents(basePackage);
for (BeanDefinition bd : beans) {
Class<?> cls = ClassUtils.resolveClassName(bd.getBeanClassName(),
resourceLoader.getClassLoader());
classes.add(cls);
}
}
// Construct the Jersey ResourceConfig
//
Map<String, Object> propsAndFeatures = new HashMap<String, Object>();
propsAndFeatures.put(
// Skip static content used by the webapp
ServletContainer.PROPERTY_WEB_PAGE_CONTENT_REGEX,
EurekaConstants.DEFAULT_PREFIX + "/(fonts|images|css|js)/.*");
DefaultResourceConfig rc = new DefaultResourceConfig(classes);
rc.setPropertiesAndFeatures(propsAndFeatures);
return rc;
}
Jersey是一个RESTFUL请求服务JAVA框架,与常规的JAVA编程使用的struts框架类似,它主要用于处理业务逻辑层。和SpringMVC有区别,反正是个Controller层的框架,不用详细学习,只要知道就可以了。
现在看下之前@EnableEurekaServer注解导入的EurekaServerInitializerConfiguration类:
@Configuration
public class EurekaServerInitializerConfiguration implements ServletContextAware, SmartLifecycle, Ordered {
// ...
@Override
public void start() {
new Thread(() -> {
try {
eurekaServerBootstrap.contextInitialized(EurekaServerInitializerConfiguration.this.servletContext);
publish(new EurekaRegistryAvailableEvent(getEurekaServerConfig()));
EurekaServerInitializerConfiguration.this.running = true;
publish(new EurekaServerStartedEvent(getEurekaServerConfig()));
} catch (Exception ex) {
//
}
}).start();
}
}
初始化过程调用了EurekaServerBootstrap的contextInitialized方法,我们跟进看看
public void contextInitialized(ServletContext context) {
try {
initEurekaEnvironment();
initEurekaServerContext();
context.setAttribute(EurekaServerContext.class.getName(), this.serverContext);
}
catch (Throwable e) {
log.error("Cannot bootstrap eureka server :", e);
throw new RuntimeException("Cannot bootstrap eureka server :", e);
}
}
这里初始化了EurekaEnvironment和EurekaServerContext,EurekaEnvironment无非就是设置了各种配置之类的东西。我们打开initEurekaServerContext看看
protected void initEurekaServerContext() throws Exception {
// ......
// Copy registry from neighboring eureka node
int registryCount = this.registry.syncUp();
this.registry.openForTraffic(this.applicationInfoManager, registryCount);
// Register all monitoring statistics.
EurekaMonitors.registerAllStats();
}
这里主要做了两件事:
1)从相邻的集群节点当中同步注册信息(EurekaServer是集群)
2)注册一个统计器
总结
@EnableEurekaServer注解开启了EurekaServerAutoConfiguration这个配置类的解析,EurekaServerAutoConfiguration这个配置了主要准备了看板、注册发现、启动引导、Jersey等,EurekaServerInitializerConfigration将会触发启动引导,引导过程会从其它Eureka集群节点当中同步注册信息。
eureka服务端注册服务
eureka是使用jersey来对外提供restful风格的rpc调用的。我们得找到注册服务的Resource(jersey中称为resource,对应springmvc的controller)
ApplicationResource类中的addInstance方法将作为服务端注册服务的入口
private final PeerAwareInstanceRegistry registry;
@POST
@Consumes({"application/json", "application/xml"})
public Response addInstance(InstanceInfo info, @HeaderParam(PeerEurekaNode.HEADER_REPLICATION) String isReplication) {
//...
registry.register(info, "true".equals(isReplication));
return Response.status(204).build();
}
public void register(InstanceInfo info, boolean isReplication) {
int leaseDuration = 90;
if (info.getLeaseInfo() != null && info.getLeaseInfo().getDurationInSecs() > 0) {
leaseDuration = info.getLeaseInfo().getDurationInSecs();
}
// 注册信息
super.register(info, leaseDuration, isReplication);
// 复制到其他节点
this.replicateToPeers(PeerAwareInstanceRegistryImpl.Action.Register, info.getAppName(), info.getId(), info, (InstanceStatus)null, isReplication);
}
register方法主要做了两件事
1)注册实例信息
2)复制到其它节点
我们关注注册实例,跟进super.register方法
register方法很长,看看核心的流程
public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {
try {
this.read.lock();
Map<String, Lease<InstanceInfo>> gMap = (Map)this.registry.get(registrant.getAppName());
EurekaMonitors.REGISTER.increment(isReplication);
if (gMap == null) {
ConcurrentHashMap<String, Lease<InstanceInfo>> gNewMap = new ConcurrentHashMap();
// 只是将InstanceInfo添加到registry集合之中
gMap = (Map)this.registry.putIfAbsent(registrant.getAppName(), gNewMap);
if (gMap == null) {
gMap = gNewMap;
}
} Lease<InstanceInfo> lease = new Lease<InstanceInfo>(registrant, leaseDuration);
gMap.put(registrant.getId(), lease);
// 省略
} finally {
//
}
ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>> registry
很容易理解服务名称是唯一的,但是服务是集群式的服务,所以服务中是有很多实例的。
这个是registry集合的数据结构
{
"商品服务": { // 服务名
"实例的唯一ID": { // 实例标识符
"lease": { // 持有实例信息
"registrationTimestamp":yyy, // 注册时间
"lastUpdateTimestamp":xxx, // 最后修改时间
.....
"instanceInfo": { // 实例信息
"appName": "商品服务",
"instanceId": "实例的唯一ID",
"ipAddr": "IP地址",
"port": "调用端口"
.......
}
}
}
}
}
public class Lease<T> {
enum Action {
Register, Cancel, Renew
};
public static final int DEFAULT_DURATION_IN_SECS = 90;
private T holder;
private long evictionTimestamp;
private long registrationTimestamp;
private long serviceUpTimestamp;
// Make it volatile so that the expiration task would see this quicker
private volatile long lastUpdateTimestamp;
private long duration;
......
public class InstanceInfo {
/**
* {@link InstanceInfo} JSON and XML format for port information does not follow the usual conventions, which
* makes its mapping complicated. This class represents the wire format for port information.
*/
private volatile String instanceId;
private volatile String appName;
@Auto
private volatile String appGroupName;
private volatile String ipAddr;
private static final String SID_DEFAULT = "na";
@Deprecated
private volatile String sid = SID_DEFAULT;
private volatile int port = DEFAULT_PORT;
private volatile int securePort = DEFAULT_SECURE_PORT;
@Auto
private volatile String homePageUrl;
@Auto
private volatile String statusPageUrl;
@Auto
private volatile String healthCheckUrl;
@Auto
private volatile String secureHealthCheckUrl;
@Auto
private volatile String vipAddress;
@Auto
private volatile String secureVipAddress;
......
eureka采用jersey来提供rpc服务,注册服务实际上就是向registry添加了一份实例信息。不过我们可以看到Eureka并没有对实例数据进行持久化,所以实例数据都是瞬时态的,这与zookeeper的做法存在区别。
所以每一个EurekaServer都是存储了服务的信息,并且EurekaServer也相互之间进行注册,并同步。
所以看下复制到其他节点的逻辑,看下replicateToPeers方法
private void replicateToPeers(Action action, String appName, String id,
InstanceInfo info /* optional */,
InstanceStatus newStatus /* optional */, boolean isReplication) {
Stopwatch tracer = action.getTimer().start();
try {
if (isReplication) {
numberOfReplicationsLastMin.increment();
}
// 如果本次register操作本身就是复制,就不再复制到其它节点了
if (peerEurekaNodes == Collections.EMPTY_LIST || isReplication) {
return;
}
// 遍历左右节点
for (final PeerEurekaNode node : peerEurekaNodes.getPeerEurekaNodes()) {
// 如果是当前节点,直接跳过
if (peerEurekaNodes.isThisMyUrl(node.getServiceUrl())) {
continue;
}
// 复制操作触发, 其实就是向其他节点发送注册请求
replicateInstanceActionsToPeers(action, appName, id, info, newStatus, node);
}
} finally {
tracer.stop();
}
}
private void replicateInstanceActionsToPeers(Action action, String appName,
String id, InstanceInfo info, InstanceStatus newStatus,
PeerEurekaNode node) {
try {
InstanceInfo infoFromRegistry = null;
CurrentRequestVersion.set(Version.V2);
switch (action) {
case Cancel:
node.cancel(appName, id);
break;
case Heartbeat:
InstanceStatus overriddenStatus = overriddenInstanceStatusMap.get(id);
infoFromRegistry = getInstanceByAppAndId(appName, id, false);
node.heartbeat(appName, id, infoFromRegistry, overriddenStatus, false);
break;
// 注册
case Register:
node.register(info);
break;
case StatusUpdate:
infoFromRegistry = getInstanceByAppAndId(appName, id, false);
node.statusUpdate(appName, id, newStatus, infoFromRegistry);
break;
case DeleteStatusOverride:
infoFromRegistry = getInstanceByAppAndId(appName, id, false);
node.deleteStatusOverride(appName, id, infoFromRegistry);
break;
}
} catch (Throwable t) {
// ...
}
}
// 复制实例信息被构造成了一个任务丢给了batchingDispatcher去异步执行,如果失败将会重试。
public void register(final InstanceInfo info) throws Exception {
long expiryTime = System.currentTimeMillis() + getLeaseRenewalOf(info);
// 异步执行任务
batchingDispatcher.process(
taskId("register", info),
// 构造了一个复制实例信息的任务
new InstanceReplicationTask(targetHost, Action.Register, info, null, true) {
public EurekaHttpResponse<Void> execute() {
return replicationClient.register(info);
}
},
expiryTime
);
}
// replicationClient.register()
// Jersey实现,就是发送http请求到其他Eureka Server
public EurekaHttpResponse<Void> register(InstanceInfo info) {
String urlPath = "apps/" + info.getAppName();
ClientResponse response = null;
try {
Builder resourceBuilder = jerseyClient.resource(serviceUrl).path(urlPath).getRequestBuilder();
addExtraHeaders(resourceBuilder);
response = resourceBuilder
.header("Accept-Encoding", "gzip")
.type(MediaType.APPLICATION_JSON_TYPE)
.accept(MediaType.APPLICATION_JSON)
.post(ClientResponse.class, info);
return anEurekaHttpResponse(response.getStatus()).headers(headersOf(response)).build();
} finally {
// ...
}
}
eureka Server会将register、cancel、heartbeat等操作从一个节点同步发送到其它节点,从而实现了复制的功能。eureka和zookeeper不一样,它是遵循AP的,所以采用了最终一致性,并没有像zookeeper一样选择强一致(CP)。eureka Server之间的维持最终一致性的细节点还是很多的,比如失败重试、超时、心跳、实例的版本号、同一个节点的锁控制等等。有兴趣的话可以详细了解它。
eureka获取服务列表
eureka服务端维护了一个服务信息的列表,服务端节点之间相互复制服务信息。而作为eureka的客户端将会从eureka服务端请求这个服务信息列表,选择对应的实例。
eureka服务端基于jersey来提供http服务调用,所以我们先找到它的Resource。
ApplicationsResource
@GET
public Response getContainers(@PathParam("version") String version,
@HeaderParam(HEADER_ACCEPT) String acceptHeader,
@HeaderParam(HEADER_ACCEPT_ENCODING) String acceptEncoding,
@HeaderParam(EurekaAccept.HTTP_X_EUREKA_ACCEPT) String eurekaAccept,
@Context UriInfo uriInfo,
@Nullable @QueryParam("regions") String regionsStr) {
// ...
Key cacheKey = new Key(Key.EntityType.Application,
ResponseCacheImpl.ALL_APPS,
keyType, CurrentRequestVersion.get(), EurekaAccept.fromString(eurekaAccept), regions
);
Response response;
if (acceptEncoding != null && acceptEncoding.contains(HEADER_GZIP_VALUE)) {
response = Response.ok(responseCache.getGZIP(cacheKey))
.header(HEADER_CONTENT_ENCODING, HEADER_GZIP_VALUE)
.header(HEADER_CONTENT_TYPE, returnMediaType)
.build();
} else {
// 从responseCache当中获取了Applications的序列号结果直接返回了
response = Response.ok(responseCache.get(cacheKey))
.build();
}
return response;
}
ResponseCache是在ApplicationsResource构造的时候从Registry中获取的
@Inject
ApplicationsResource(EurekaServerContext eurekaServer) {
this.serverConfig = eurekaServer.getServerConfig();
this.registry = eurekaServer.getRegistry();
this.responseCache = registry.getResponseCache();
}
看看ResponseCache的get方法做了什么
String get(final Key key, boolean useReadOnlyCache) {
Value payload = getValue(key, useReadOnlyCache);
if (payload == null || payload.getPayload().equals(EMPTY_PAYLOAD)) {
return null;
} else {
return payload.getPayload();
}
}
继续跟进getValue
Value getValue(final Key key, boolean useReadOnlyCache) {
Value payload = null;
try {
if (useReadOnlyCache) {
final Value currentPayload = readOnlyCacheMap.get(key);
if (currentPayload != null) {
payload = currentPayload;
} else {
payload = readWriteCacheMap.get(key);
readOnlyCacheMap.put(key, payload);
}
} else {
payload = readWriteCacheMap.get(key);
}
} catch (Throwable t) {
logger.error("Cannot get value for key : {}", key, t);
}
return payload;
}
可以看到,其实只是从ReadWriteCacheMap当中获取对应的值,那么我们再看看ReadWriteCacheMap是怎么被构造的
this.readWriteCacheMap = CacheBuilder.newBuilder().initialCapacity(serverConfig.getInitialCapacityOfResponseCache())
.expireAfterWrite(serverConfig.getResponseCacheAutoExpirationInSeconds(), TimeUnit.SECONDS)
.removalListener(new RemovalListener<Key, Value>() {
@Override
public void onRemoval(RemovalNotification<Key, Value> notification) {
Key removedKey = notification.getKey();
if (removedKey.hasRegions()) {
Key cloneWithNoRegions = removedKey.cloneWithoutRegions();
regionSpecificKeys.remove(cloneWithNoRegions, removedKey);
}
}
})
.build(new CacheLoader<Key, Value>() {
@Override
public Value load(Key key) throws Exception {
if (key.hasRegions()) {
Key cloneWithNoRegions = key.cloneWithoutRegions();
regionSpecificKeys.put(cloneWithNoRegions, key);
}
// 调用ReadWriteCacheMap的get方法,将会generatePayload方法
Value value = generatePayload(key);
return value;
}
});
private Value generatePayload(Key key) {
Stopwatch tracer = null;
try {
String payload;
switch (key.getEntityType()) {
case Application:
boolean isRemoteRegionRequested = key.hasRegions();
// 获取所有Application
if (ALL_APPS.equals(key.getName())) {
if (isRemoteRegionRequested) {
tracer = serializeAllAppsWithRemoteRegionTimer.start();
payload = getPayLoad(key, registry.getApplicationsFromMultipleRegions(key.getRegions()));
} else {
tracer = serializeAllAppsTimer.start();
payload = getPayLoad(key, registry.getApplications());
}
} else if (ALL_APPS_DELTA.equals(key.getName())) {
// ...
} else {
tracer = serializeOneApptimer.start();
// 获取某个Application
payload = getPayLoad(key, registry.getApplication(key.getName()));
}
break;
// ...
}
return new Value(payload);
} finally {
}
}
我们看到这里payload主要构成元素是Application,也就是我们需要的服务列表信息。
最后,我们跟进getPayLoad方法,看看这些服务列表信息是怎么被序列号成payload的
private String getPayLoad(Key key, Applications apps) {
// 编码器
EncoderWrapper encoderWrapper = serverCodecs.getEncoder(key.getType(), key.getEurekaAccept());
String result;
try {
result = encoderWrapper.encode(apps);
} catch (Exception e) {
return "";
}
return result;
}
总结:
获取服务信息列表其实就是从registry当中获取Applications,然后做一次序列化,最后通过http响应回去。总体来说还是比较简单的。
eureka客户端自动配置
@EnableDiscoveryClient
@EnableDiscoveryClient
@SpringBootApplication
public class EurekaClientApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaClientApplication.class, args);
}
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(EnableDiscoveryClientImportSelector.class)
public @interface EnableDiscoveryClient {
/**
* If true, the ServiceRegistry will automatically register the local server.
*/
boolean autoRegister() default true;
}
给了个参数autoRegister,默认为true,如果设置为false就不会去注册了。
@EnableDiscoveryClient(autoRegister = false)
不同于Server端的是,client端不是直接引入一个Configuration而是通过一个ImportSelector来导入类。跟进EnableDiscoveryClientImportSelector类
@Order(Ordered.LOWEST_PRECEDENCE - 100)
public class EnableDiscoveryClientImportSelector
extends SpringFactoryImportSelector<EnableDiscoveryClient> {
@Override
public String[] selectImports(AnnotationMetadata metadata) {
String[] imports = super.selectImports(metadata);
AnnotationAttributes attributes = AnnotationAttributes.fromMap(
metadata.getAnnotationAttributes(getAnnotationClass().getName(), true));
boolean autoRegister = attributes.getBoolean("autoRegister");
if (autoRegister) {
List<String> importsList = new ArrayList<>(Arrays.asList(imports));
// 返回AutoServiceRegistrationConfiguration,之后被导入
importsList.add("org.springframework.cloud.client.serviceregistry."+
"AutoServiceRegistrationConfiguration");
imports = importsList.toArray(new String[0]);
}
return imports;
}
@Override
protected boolean isEnabled() {
return new RelaxedPropertyResolver(getEnvironment()).getProperty(
"spring.cloud.discovery.enabled", Boolean.class, Boolean.TRUE);
}
@Override
protected boolean hasDefaultFactory() {
return true;
}
}
selectImports方法主要就是导入了一个类AutoServiceRegistrationCofiguration,我们看看导入的这个类干了啥
@Configuration
@EnableConfigurationProperties(AutoServiceRegistrationProperties.class)
public class AutoServiceRegistrationConfiguration {
}
@ConfigurationProperties("spring.cloud.service-registry.auto-registration")
public class AutoServiceRegistrationProperties {
/** If Auto-Service Registration is enabled, default to true. */
private boolean enabled = true;
/** Should startup fail if there is no AutoServiceRegistration, default to false. */
private boolean failFast = false;
public boolean isFailFast() {
return failFast;
}
public void setFailFast(boolean failFast) {
this.failFast = failFast;
}
}
只是简单的加载了一些属性,如果没有配置该属性那么将会是默认值(eureka启动的大部分默认值都是true,所以你会发现,即使没有添加@EnableDiscoveryClient注解,使用默认值也是可以的)。
EurekaClientAutoConfiguration自动配置客户端
@Configuration
@EnableConfigurationProperties
@ConditionalOnClass(EurekaClientConfig.class)
@Import(DiscoveryClientOptionalArgsConfiguration.class)
@ConditionalOnProperty(value = "eureka.client.enabled", matchIfMissing = true)
@ConditionalOnDiscoveryEnabled
@AutoConfigureBefore({ NoopDiscoveryClientAutoConfiguration.class,
CommonsClientAutoConfiguration.class, ServiceRegistryAutoConfiguration.class })
@AutoConfigureAfter(name = {
"org.springframework.cloud.autoconfigure.RefreshAutoConfiguration",
"org.springframework.cloud.netflix.eureka.EurekaDiscoveryClientConfiguration",
"org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationAutoConfiguration" })
public class EurekaClientAutoConfiguration {
// ...
}
注解比较多,但是没那么复杂。就是加载一些附加的配置,以及判断一下是否开始自动配置。下面,我们再看看内部添加了哪些东西(只关注几个核心的)
配置当前实例信息
配置实例信息包含很多,不过核心的无非就是名称、唯一标识、IP地址、端口等等
@Bean
@ConditionalOnMissingBean(value = EurekaInstanceConfig.class,
search = SearchStrategy.CURRENT)
public EurekaInstanceConfigBean eurekaInstanceConfigBean(InetUtils inetUtils,
ManagementMetadataProvider managementMetadataProvider) {
// ...
EurekaInstanceConfigBean instance = new EurekaInstanceConfigBean(inetUtils);
instance.setNonSecurePort(serverPort);
instance.setInstanceId(getDefaultInstanceId(env));
instance.setPreferIpAddress(preferIpAddress);
instance.setSecurePortEnabled(isSecurePortEnabled);
if (StringUtils.hasText(ipAddress)) {
instance.setIpAddress(ipAddress);
}
// ...
setupJmxPort(instance, jmxPort);
return instance;
}
负责注册的Bean
@Bean
public EurekaServiceRegistry eurekaServiceRegistry() {
return new EurekaServiceRegistry();
}
自动注册调用的Bean
@Bean
@ConditionalOnBean(AutoServiceRegistrationProperties.class)
@ConditionalOnProperty(
value = "spring.cloud.service-registry.auto-registration.enabled",
matchIfMissing = true)
public EurekaAutoServiceRegistration eurekaAutoServiceRegistration(
ApplicationContext context, EurekaServiceRegistry registry,
EurekaRegistration registration) {
return new EurekaAutoServiceRegistration(context, registry, registration);
}
Eureka待注册的对象
这个对象会包含上面的eurekaInstanceIConfigBean
@Bean
@ConditionalOnBean(AutoServiceRegistrationProperties.class)
@ConditionalOnProperty(
value = "spring.cloud.service-registry.auto-registration.enabled",
matchIfMissing = true)
public EurekaRegistration eurekaRegistration(EurekaClient eurekaClient,
CloudEurekaInstanceConfig instanceConfig,
ApplicationInfoManager applicationInfoManager, @Autowired(
required = false) ObjectProvider<HealthCheckHandler> healthCheckHandler) {
return EurekaRegistration.builder(instanceConfig).with(applicationInfoManager)
.with(eurekaClient).with(healthCheckHandler).build();
}
总结
eureka客户端的自动配置,其实就是做了一些注册等操作之前的准备。准备实例信息,配置eureka框架的东西。代码很多,但是逻辑并不复杂。下一篇文章,我们将看看如何自动注册。
eureka客户端自动注册服务
在自动配置的时候会产生一个负责自动注册的Bean,也就是EurekaAutoServiceRegistration,所以,我们打开EurekaAutoServiceRegistration看看
public class EurekaAutoServiceRegistration implements AutoServiceRegistration, SmartLifecycle, Ordered, SmartApplicationListener {
private ApplicationContext context;
private EurekaServiceRegistry serviceRegistry;
private EurekaRegistration registration;
public EurekaAutoServiceRegistration(ApplicationContext context, EurekaServiceRegistry serviceRegistry, EurekaRegistration registration) {
this.context = context;
this.serviceRegistry = serviceRegistry;
this.registration = registration;
}
@Override
public void start() {
// ...
if (!this.running.get() && this.registration.getNonSecurePort() > 0) {
// 调用注册
this.serviceRegistry.register(this.registration);
this.context.publishEvent(new InstanceRegisteredEvent<>(this, this.registration.getInstanceConfig()));
this.running.set(true);
}
}
}
注册任务被委托给了serviceRegistry来做,跟进register方法
@Override
public void register(EurekaRegistration reg) {
maybeInitializeClient(reg);
// ...
reg.getApplicationInfoManager().setInstanceStatus(reg.getInstanceConfig().getInitialStatus());
reg.getHealthCheckHandler().ifAvailable(healthCheckHandler -> reg.getEurekaClient().registerHealthCheck(healthCheckHandler));
}
调用了eurekaClient原始的registerhealthCheck方法,跟进它
@Override
public void registerHealthCheck(HealthCheckHandler healthCheckHandler) {
if (instanceInfo == null) {
logger.error("Cannot register a healthcheck handler when instance info is null!");
}
if (healthCheckHandler != null) {
// 注册心跳检查处理器
this.healthCheckHandlerRef.set(healthCheckHandler);
// schedule an onDemand update of the instanceInfo when a new healthcheck handler is registered
if (instanceInfoReplicator != null) {
instanceInfoReplicator.onDemandUpdate();
}
}
}
心跳检查处理器被设置为了成员变量,执行的核心逻辑被托付给了onDemandUpdate方法,跟进它
public boolean onDemandUpdate() {
if (rateLimiter.acquire(burstSize, allowedRatePerMinute)) {
if (!scheduler.isShutdown()) {
scheduler.submit(new Runnable() {
@Override
public void run() {
// ...
InstanceInfoReplicator.this.run();
}
});
return true;
} else {
// ...
}
} else {
// ...
}
}
单线程异步执行了当前类的run方法,进入run方法
public void run() {
try {
// 刷新实例信息
discoveryClient.refreshInstanceInfo();
Long dirtyTimestamp = instanceInfo.isDirtyWithTime();
if (dirtyTimestamp != null) {
// 注册
discoveryClient.register();
instanceInfo.unsetIsDirty(dirtyTimestamp);
}
} catch (Throwable t) {
// ...
} finally {
// 下一次延迟执行
Future next = scheduler.schedule(this, replicationIntervalSeconds, TimeUnit.SECONDS);
scheduledPeriodicRef.set(next);
}
}
run方法每次执行都会刷新实例信息,然后调用register注册新的实例信息,最后发出下一次执行的延迟任务
跟进register方法
boolean register() throws Throwable {
logger.info(PREFIX + "{}: registering service...", appPathIdentifier);
EurekaHttpResponse<Void> httpResponse;
try {
// 发出远程请求
httpResponse = eurekaTransport.registrationClient.register(instanceInfo);
} catch (Exception e) {
}
return httpResponse.getStatusCode() == Status.NO_CONTENT.getStatusCode();
}
继续跟进register方法,发出了http请求,这里以jersey为例
@Override
public EurekaHttpResponse<Void> register(InstanceInfo info) {
String urlPath = "apps/" + info.getAppName();
ClientResponse response = null;
try {
Builder resourceBuilder = jerseyClient.resource(serviceUrl).path(urlPath).getRequestBuilder();
addExtraHeaders(resourceBuilder);
response = resourceBuilder
.header("Accept-Encoding", "gzip")
.type(MediaType.APPLICATION_JSON_TYPE)
.accept(MediaType.APPLICATION_JSON)
// 发出http请求
.post(ClientResponse.class, info);
return anEurekaHttpResponse(response.getStatus()).headers(headersOf(response)).build();
} finally {
if (logger.isDebugEnabled()) {
logger.debug("Jersey HTTP POST {}/{} with instance {}; statusCode={}", serviceUrl, urlPath, info.getId(),
response == null ? "N/A" : response.getStatus());
}
if (response != null) {
response.close();
}
}
}
http请求将进入eureka服务端,
总结
eureka客户端自动注册服务主要是将自动配置的时候拿到的实例信息通过http请求发送给eureka服务端,默认30秒会执行一次。

浙公网安备 33010602011771号