天天看点

SpringCloud从零开始-微服务注册中心-服务端创建-Eureka

文章目录

    • 说明
        • 遇到的问题
        • 相关知识点
      • 单节点模式
        • 项目结构
        • 依赖管理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

遇到的问题

  1. 如果没有添加

    <modules>

    节点,则继承了当前pom配置的子模块不会加载Maven依赖

相关知识点

  1. Eureka 自我保护机制。
    eureka自我保护机制,如果微服务向注册中心注册了自己,但是因为网络原因导致,注册中心和微服务的通信失败,这个时候,注册中心如果没有开启自我保护机制,这个微服务会在90S的心跳检测后背注销。如果开启了保护机制,则是会保持这个微服务的注册信息,后续网络问题回复后,注册中心会退出保护机制。
  2. Spring active profiles 详情1,

单节点模式

项目结构

SpringCloud从零开始-微服务注册中心-服务端创建-Eureka

依赖管理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> &lt;!&ndash; lookup parent from repository &ndash;&gt;-->
	</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

SpringCloud从零开始-微服务注册中心-服务端创建-Eureka

多节点模式(高可用)

项目结构

SpringCloud从零开始-微服务注册中心-服务端创建-Eureka

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

SpringCloud从零开始-微服务注册中心-服务端创建-Eureka

结果:访问localhost:8762

SpringCloud从零开始-微服务注册中心-服务端创建-Eureka

Eureka自我保护机制追踪

  1. 先从@EnableEurekaServer注解开始,就是导入了一个EurekaServerMarkerConfiguration.class
    SpringCloud从零开始-微服务注册中心-服务端创建-Eureka
  2. Idea

    Go to implemention

    追踪进去,又没有具体的实现,心态有点小蹦,这些人写码的思路也太野了。查找一下@ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class),条件加载
    SpringCloud从零开始-微服务注册中心-服务端创建-Eureka
  3. Find in Path 查找一下
    SpringCloud从零开始-微服务注册中心-服务端创建-Eureka
  4. 那就进去看一下吧,看来这里面应该能找到点东西,大部分都是@Bean
    SpringCloud从零开始-微服务注册中心-服务端创建-Eureka
    4.同样也有配置类导入
    SpringCloud从零开始-微服务注册中心-服务端创建-Eureka
  5. 摘抄一段代码,瞬间就能明白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);
        }
    }
}
           

关于保护机制的追踪暂时在这里打住,我觉得还有一些东西没整明白。这里的代码先保留,等待微服务调用的时候 再来追踪服务的调用。欢迎各位大佬留言交流。

继续阅读