文章目录
-
- 说明
-
-
- 遇到的问题
- 相关知识点
- 单节点模式
-
- 项目结构
- 依赖管理pom配置
- 子模块pom配置
- application.yml
- 结果:访问localhost:8761
- 多节点模式(高可用)
-
- 项目结构
- application-eureka1.yml
- application-eureka2
- 结果:访问localhost:8761
- 结果:访问localhost:8762
- Eureka自我保护机制追踪
-
说明
IDE: Idea
SpringBoot版本:2.3.4.RELEASE
SpringCloud版本: Hoxton.SR8
遇到的问题
- 如果没有添加
节点,则继承了当前pom配置的子模块不会加载Maven依赖<modules>
相关知识点
- Eureka 自我保护机制。
eureka自我保护机制,如果微服务向注册中心注册了自己,但是因为网络原因导致,注册中心和微服务的通信失败,这个时候,注册中心如果没有开启自我保护机制,这个微服务会在90S的心跳检测后背注销。如果开启了保护机制,则是会保持这个微服务的注册信息,后续网络问题回复后,注册中心会退出保护机制。
- Spring active profiles 详情1,
单节点模式
项目结构
依赖管理pom配置
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.xerbeauty</groupId>
<artifactId>spring-cloud</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-cloud-dependencies-manage</name>
<description>spring cloud 项目依赖管理</description>
<packaging>pom</packaging>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.SR8</spring-cloud.version>
<spring-boot.version>2.3.4.RELEASE</spring-boot.version>
<project.version>0.0.1-SNAPSHOT</project.version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
</parent>
<!-- 多模块项目,如果子项目继承了父项目的配置,而没有添加modules配置,Idea 则不会加载Maven依赖-->
<modules>
<module>spring-cloud-eureka-server</module>
</modules>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<showWarnings>true</showWarnings>
</configuration>
</plugin>
</plugins>
</build>
</project>
子模块pom配置
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.xerbeauty</groupId>
<artifactId>spring-cloud</artifactId>
<version>0.0.1-SNAPSHOT</version>
<!-- <relativePath>../pom.xml</relativePath> <!– lookup parent from repository –>-->
</parent>
<packaging>jar</packaging>
<artifactId>spring-cloud-eureka-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-cloud-eureka-server</name>
<description>eureka 服务端</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.SR8</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.yml
server:
port: 8761 # eureka 默认端口
eureka:
instance:
hostname: localhost
client:
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
fetch-registry: false # 自己就是服务端
register-with-eureka: false # 不注册自己
server:
enable-self-preservation: false # 禁用自我保护机制,保护机制: 尽可能的保存服务注册信息,等待心跳阈值重启微服务. 关闭保护机制,通信失败的微服务会被注销
结果:访问localhost:8761
多节点模式(高可用)
项目结构
application-eureka1.yml
server:
port: 8761 # eureka 默认端口
spring:
profiles:
active: eureka1
application:
name: Eureka1
eureka:
instance:
hostname: localhost
appname: eureka1
client:
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/,http://localhost:8762/eureka/
# fetch-registry: false # 自己就是服务端
# register-with-eureka: false # 不注册自己
server:
enable-self-preservation: false # 禁用自我保护机制,保护机制: 尽可能的保存服务注册信息,等待心跳阈值重启微服务. 关闭保护机制,通信失败的微服务会被注销
application-eureka2
server:
port: 8762
spring:
profiles:
active: eureka2
application:
name: Eureka2
eureka:
instance:
hostname: localhost
appname: eureka2
client:
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/,http://localhost:8761/eureka/
# fetch-registry: false # 自己就是服务端
# register-with-eureka: false # 不注册自己
server:
enable-self-preservation: false # 禁用自我保护机制,保护机制: 尽可能的保存服务注册信息,等待心跳阈值重启微服务. 关闭保护机制,通信失败的微服务会被注销
结果:访问localhost:8761
结果:访问localhost:8762
Eureka自我保护机制追踪
- 先从@EnableEurekaServer注解开始,就是导入了一个EurekaServerMarkerConfiguration.class
- Idea
追踪进去,又没有具体的实现,心态有点小蹦,这些人写码的思路也太野了。查找一下@ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class),条件加载Go to implemention
- Find in Path 查找一下
- 那就进去看一下吧,看来这里面应该能找到点东西,大部分都是@Bean 4.同样也有配置类导入
- 摘抄一段代码,瞬间就能明白Eureka的自我保护机制是什么东西了
@Configuration(proxyBeanMethods = false) public class EurekaServerInitializerConfiguration implements ServletContextAware, SmartLifecycle, Ordered { // ... 省略属性 @Override public void start() { new Thread(() -> { try { // 这里可以看到一个被启动的线程,同时结合控制台的输出内容,AbstractInstanceRegistry的内容 eurekaServerBootstrap.contextInitialized( EurekaServerInitializerConfiguration.this.servletContext); log.info("Started Eureka Server"); publish(new EurekaRegistryAvailableEvent(getEurekaServerConfig())); EurekaServerInitializerConfiguration.this.running = true; publish(new EurekaServerStartedEvent(getEurekaServerConfig())); } catch (Exception ex) { // Help! log.error("Could not initialize Eureka servlet context", ex); } }).start(); } // ... 省略部分方法 } ---- {@link EurekaServerBootstrap} public class EurekaServerBootstrap { // ... 省略属性 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); } } //... protected void initEurekaEnvironment() throws Exception { log.info("Setting the eureka configuration.."); String dataCenter = ConfigurationManager.getConfigInstance() .getString(EUREKA_DATACENTER); if (dataCenter == null) { log.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); } String environment = ConfigurationManager.getConfigInstance() .getString(EUREKA_ENVIRONMENT); if (environment == null) { ConfigurationManager.getConfigInstance() .setProperty(ARCHAIUS_DEPLOYMENT_ENVIRONMENT, TEST); log.info( "Eureka environment value eureka.environment is not set, defaulting to test"); } else { ConfigurationManager.getConfigInstance() .setProperty(ARCHAIUS_DEPLOYMENT_ENVIRONMENT, environment); } } protected void initEurekaServerContext() throws Exception { // For backward compatibility JsonXStream.getInstance().registerConverter(new V1AwareInstanceInfoConverter(), XStream.PRIORITY_VERY_HIGH); XmlXStream.getInstance().registerConverter(new V1AwareInstanceInfoConverter(), XStream.PRIORITY_VERY_HIGH); if (isAws(this.applicationInfoManager.getInfo())) { this.awsBinder = new AwsBinderDelegate(this.eurekaServerConfig, this.eurekaClientConfig, this.registry, this.applicationInfoManager); this.awsBinder.start(); } EurekaServerContextHolder.initialize(this.serverContext); log.info("Initialized server context"); // 这个注释最Nice了 // Copy registry from neighboring eureka node int registryCount = this.registry.syncUp(); //PeerAwareInstanceRegistry registry; 属性声明 this.registry.openForTraffic(this.applicationInfoManager, registryCount); // Register all monitoring statistics. EurekaMonitors.registerAllStats(); } //... } ---- @PeerAwareInstanceRegistryImpl#openForTraffic public void openForTraffic(ApplicationInfoManager applicationInfoManager, int count) { this.expectedNumberOfClientsSendingRenews = count; this.updateRenewsPerMinThreshold(); logger.info("Got {} instances from neighboring DS node", count); logger.info("Renew threshold is: {}", this.numberOfRenewsPerMinThreshold); this.startupTime = System.currentTimeMillis(); if (count > 0) { this.peerInstancesTransferEmptyOnStartup = false; } Name selfName = applicationInfoManager.getInfo().getDataCenterInfo().getName(); boolean isAws = Name.Amazon == selfName; if (isAws && this.serverConfig.shouldPrimeAwsReplicaConnections()) { logger.info("Priming AWS connections for all replicas.."); this.primeAwsReplicas(applicationInfoManager); } logger.info("Changing status to UP"); applicationInfoManager.setInstanceStatus(InstanceStatus.UP); super.postInit(); } ---- @AbstractInstanceRegistry#postInit protected void postInit() { this.renewsLastMin.start(); if (this.evictionTaskRef.get() != null) { ((AbstractInstanceRegistry.EvictionTask)this.evictionTaskRef.get()).cancel(); } this.evictionTaskRef.set(new AbstractInstanceRegistry.EvictionTask()); this.evictionTimer.schedule((TimerTask)this.evictionTaskRef.get(), this.serverConfig.getEvictionIntervalTimerInMs(), this.serverConfig.getEvictionIntervalTimerInMs()); }
通过这段代码追踪,最后是发现了保护机制的启动地方了,启动的是
private final AtomicReference<EvictionTask> evictionTaskRef = new AtomicReference<EvictionTask>();
这个类型的任务
@AbstractInstanceRegistry.TimerTask
class EvictionTask extends TimerTask {
private final AtomicLong lastExecutionNanosRef = new AtomicLong(0l);
@Override
public void run() {
try {
long compensationTimeMs = getCompensationTimeMs();
logger.info("Running the evict task with compensationTime {}ms", compensationTimeMs);
evict(compensationTimeMs);
} catch (Throwable e) {
logger.error("Could not run the evict task", e);
}
}
long getCompensationTimeMs() {
long currNanos = getCurrentTimeNano();
long lastNanos = lastExecutionNanosRef.getAndSet(currNanos);
if (lastNanos == 0l) {
return 0l;
}
long elapsedMs = TimeUnit.NANOSECONDS.toMillis(currNanos - lastNanos);
long compensationTime = elapsedMs - serverConfig.getEvictionIntervalTimerInMs();
return compensationTime <= 0l ? 0l : compensationTime;
}
long getCurrentTimeNano() { // for testing
return System.nanoTime();
}
}
----
@AbstractInstanceRegistry#evict
public void evict(long additionalLeaseMs) {
logger.debug("Running the evict task");
if (!isLeaseExpirationEnabled()) {
logger.debug("DS: lease expiration is currently disabled.");
return;
}
// We collect first all expired items, to evict them in random order. For large eviction sets,
// if we do not that, we might wipe out whole apps before self preservation kicks in. By randomizing it,
// the impact should be evenly distributed across all applications.
List<Lease<InstanceInfo>> expiredLeases = new ArrayList<>();
for (Entry<String, Map<String, Lease<InstanceInfo>>> groupEntry : registry.entrySet()) {
Map<String, Lease<InstanceInfo>> leaseMap = groupEntry.getValue();
if (leaseMap != null) {
for (Entry<String, Lease<InstanceInfo>> leaseEntry : leaseMap.entrySet()) {
Lease<InstanceInfo> lease = leaseEntry.getValue();
if (lease.isExpired(additionalLeaseMs) && lease.getHolder() != null) {
expiredLeases.add(lease);
}
}
}
}
// To compensate for GC pauses or drifting local time, we need to use current registry size as a base for
// triggering self-preservation. Without that we would wipe out full registry.
int registrySize = (int) getLocalRegistrySize();
int registrySizeThreshold = (int) (registrySize * serverConfig.getRenewalPercentThreshold());
int evictionLimit = registrySize - registrySizeThreshold;
int toEvict = Math.min(expiredLeases.size(), evictionLimit);
if (toEvict > 0) {
logger.info("Evicting {} items (expired={}, evictionLimit={})", toEvict, expiredLeases.size(), evictionLimit);
Random random = new Random(System.currentTimeMillis());
for (int i = 0; i < toEvict; i++) {
// Pick a random item (Knuth shuffle algorithm)
int next = i + random.nextInt(expiredLeases.size() - i);
Collections.swap(expiredLeases, i, next);
Lease<InstanceInfo> lease = expiredLeases.get(i);
String appName = lease.getHolder().getAppName();
String id = lease.getHolder().getId();
EXPIRED.increment();
logger.warn("DS: Registry: expired lease for {}/{}", appName, id);
internalCancel(appName, id, false);
}
}
}
关于保护机制的追踪暂时在这里打住,我觉得还有一些东西没整明白。这里的代码先保留,等待微服务调用的时候 再来追踪服务的调用。欢迎各位大佬留言交流。