首页 > 其他分享 >eureka注册中心知识点剖析

eureka注册中心知识点剖析

时间:2024-01-17 22:45:56浏览次数:32  
标签:知识点 spring server 剖析 client 注册 registration eureka

背景说明

本文针对eureka的源码分析,基于的版本号:

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.14</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
 <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            <version>3.1.7</version>
        </dependency>

知识点

SmartLifecycle接口

在spring启动后,或者停止前,需要做一些操作,就可以实现该接口。
在spring启动后,需要把应用注册到eureka server,服务停止前,需要从eureka server剔除该应用。实现方式就是实现了SmartLifecycle接口。

public class EurekaAutoServiceRegistration
		implements AutoServiceRegistration, SmartLifecycle, Ordered, SmartApplicationListener {
	@Override
	public void start() {
		// only set the port if the nonSecurePort or securePort is 0 and this.port != 0
		if (this.port.get() != 0) {
			if (this.registration.getNonSecurePort() == 0) {
				this.registration.setNonSecurePort(this.port.get());
			}

			if (this.registration.getSecurePort() == 0 && this.registration.isSecure()) {
				this.registration.setSecurePort(this.port.get());
			}
		}

		// only initialize if nonSecurePort is greater than 0 and it isn't already running
		// because of containerPortInitializer below
		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);
		}
	}

	@Override
	public void stop() {
		this.serviceRegistry.deregister(this.registration);
		this.running.set(false);
	}
}

SmartLifecycle 和ApplicationListener功能类似。

@import注解

作用:可以用来动态加载一个Configuration文件,或者动态加载一个Bean
使用场景:当项目启动后,还不确定要加载哪个bean时,就可以使用这种方式。
https://zhuanlan.zhihu.com/p/147025312

spring.factories 文件

image
文件中,定义的是接口和接口的实现类。
在Spring启动过程中,不同时机会加载不同接口的实现类。
如当添加了@SpringBootApplication注解之后,就会自动加载EnableAutoConfiguration接口的实现类,spring boot的自动配置功能就是这么实现的。

@EnableConfigurationProperties注解

https://www.jianshu.com/p/7f54da1cb2eb
当我们定义一个类,这个类会映射到application.yaml中的配置,但是又不想把这个类声明成一个Bean,就可以通过@EnableConfigurationProperties把Properties类声明成一个bean

在spring boot2.7版本中,@EnableDiscoveryClient 注解已经没用了

image

即使不使用这个注解,classpath中有了eureka,也会进行服务注册。
如果classpath中已经有了eureka,又想禁用服务注册,怎么办?

spring.cloud.service-registry.auto-registration.enabled = false

image

spring框架自定义事件通知机制

场景:spring启动时,当应用程序注册到eureka server之后,需要通知DiscoveryClientHealthIndicator(健康指示器),告诉他已经注册完成。实现方式:
(1)监听自定义事件

public class DiscoveryClientHealthIndicator
		implements DiscoveryHealthIndicator, Ordered, ApplicationListener<InstanceRegisteredEvent<?>> {
	@Override
	public void onApplicationEvent(InstanceRegisteredEvent<?> event) {
		if (this.discoveryInitialized.compareAndSet(false, true)) {
			this.log.debug("Discovery Client has been initialized");
		}
	}
}

(2)发送事件:

this.context.publishEvent(new InstanceRegisteredEvent<>(this, this.registration.getInstanceConfig()));

疑问:事件监听程序,运行在哪个线程中?

eureka client 服务注册源码解析

1、/spring-cloud-netflix-eureka-client/3.1.7/spring-cloud-netflix-eureka-client-3.1.7-sources.jar!/META-INF/spring.factories 中定义自动配置类

org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration

2、接着就会注入EurekaClientAutoConfiguration中的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);
	}

3、在EurekaAutoServiceRegistration中,实现了SmartLifecycle的start、stop接口。因此,应用启动、停止过程中,就会自动进行服务的注册、注销。

	@Override
	public void start() {
		// only initialize if nonSecurePort is greater than 0 and it isn't already running
		// because of containerPortInitializer below
		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);
		}
	}

4、关键代码this.serviceRegistry.register,继续追踪,会执行com.netflix.appinfo.ApplicationInfoManager中的setInstanceStatus()方法:
image

5、通过线程池,调用InstanceInfoReplicator.run()方法:
image

6、查找可用的eureka server,发送请求,进行实例注册
image

自问自答

eureka client注册到server时,负载均衡机制是怎样的?如果一个eureka server挂了,会发生什么?

1、选择server instance的逻辑:

	@Override
    public List<AwsEndpoint> getClusterEndpoints() {
        List<AwsEndpoint>[] parts = ResolverUtils.splitByZone(delegate.getClusterEndpoints(), myZone);
        List<AwsEndpoint> myZoneEndpoints = parts[0];
        List<AwsEndpoint> remainingEndpoints = parts[1];
        List<AwsEndpoint> randomizedList = randomizeAndMerge(myZoneEndpoints, remainingEndpoints);
        if (!zoneAffinity) {
            Collections.reverse(randomizedList);
        }

        logger.debug("Local zone={}; resolved to: {}", myZone, randomizedList);

        return randomizedList;
    }

会选择和client同区域的server

2、如果有多个待选instance,当需要向server发送请求时,如果遇到server响应错误(500),会重试,请求下一台server

private final AtomicReference<EurekaHttpClient> delegate = new AtomicReference<>();

int endpointIdx = 0;
for (int retry = 0; retry < numberOfRetries; retry++) {
    // 记录当前使用的http client(代表当前正在使用的eureka server)
    EurekaHttpClient currentHttpClient = delegate.get();
    EurekaEndpoint currentEndpoint = null;
    // 如果没有可用的server,就进行查询。如果已经找到了,下次就直接使用
    if (currentHttpClient == null) {
        if (candidateHosts == null) {
            candidateHosts = getHostCandidates();
            if (candidateHosts.isEmpty()) {
                throw new TransportException("There is no known eureka server; cluster server list is empty");
            }
        }
        if (endpointIdx >= candidateHosts.size()) {
            throw new TransportException("Cannot execute request on any known server");
        }
        // 如果当前没有可用实例(或者当前实例请求失败了),就取下一个server,进行尝试
        currentEndpoint = candidateHosts.get(endpointIdx++);
        currentHttpClient = clientFactory.newClient(currentEndpoint);
    }

    try {
        EurekaHttpResponse<R> response = requestExecutor.execute(currentHttpClient);
        if (serverStatusEvaluator.accept(response.getStatusCode(), requestExecutor.getRequestType())) {
            delegate.set(currentHttpClient);
            if (retry > 0) {
                logger.info("Request execution succeeded on retry #{}", retry);
            }
            return response;
        }
        logger.warn("Request execution failure with status code {}; retrying on another server if available", response.getStatusCode());
    } catch (Exception e) {
        logger.warn("Request execution failed with message: {}", e.getMessage());  // just log message as the underlying client should log the stacktrace
    }

    // Connection error or 5xx from the server that must be retried on another server
    // 如果使用当前server请求失败了,就把currentHttpClient置为null,下次循环时,重新查找可用server
    delegate.compareAndSet(currentHttpClient, null);
    if (currentEndpoint != null) {
        quarantineSet.add(currentEndpoint);
    }
}
throw new TransportException("Retry limit reached; giving up on completing the request");

eureka集群是点对点架构(没有master),会存在数据写入冲突的问题吗?

eureka架构,满足分布式原理cap中的ap,不满足一致性。理由:
1、往eureka中写入的数据(注册一个服务),要过一段时间才能查询到。写入之后不能立即查询到,所以不是一致性。是最终一致性
2、在多实例eureka集中中,当client注册到一个节点之后,它会异步地把数据发送到其他所有节点。由于是异步的,如果数据写入A节点,从B节点查询时肯定是有延迟的。

不会存在写入冲突。理由:
1、同一个key(client实例信息),正常情况下只会写入第一个instance (eureka.client.serviceUrl.defaultZone中第一个server)。
2、如果遇到第一个server挂了,会尝试请求下一个server。此时,同一个key会写入不同的instance。由于服务注册的数据写入都是幂等的(多次insert效果一样、没有递增操作),因为不存在数据冲突的问题。

如何快速阅读java源码?

带着问题:“eureka server不同实例之间如何复制数据?”,来分享下如果快速找到相关的源码?

1、看源码最开始最懵逼的是,不知道相关逻辑的代码在哪里?

途径一:把spring日志改为debug模式,通过关键日志,推测相关代码逻辑在哪个文件

途径二:通过wireshark抓包,查看服务间都调用了哪些http接口,然后在代码中搜索相关接口。

比如客户端注册的是eureka 8761端口的实例,从结果看,很快8762、8763两个实例都有该app的信息了。
那么,我就通过wireshark监听和端口8762之间的通信:
image

看到在请求8762端口上的POST /eureka/peerreplication/batch/ HTTP/1.1接口,参数是:
image
看起来,8761实例通过调用上面的接口,发app信息同步到了8762

搜索接口名称,找到了相关代码之后,继续分析:

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();
            }
            // If it is a replication already, do not replicate again as this will create a poison replication
            if (peerEurekaNodes == Collections.EMPTY_LIST || isReplication) {
                return;
            }

            for (final PeerEurekaNode node : peerEurekaNodes.getPeerEurekaNodes()) {
                // If the url represents this host, do not replicate to yourself.
                if (peerEurekaNodes.isThisMyUrl(node.getServiceUrl())) {
                    continue;
                }
                replicateInstanceActionsToPeers(action, appName, id, info, newStatus, node);
            }
        } finally {
            tracer.stop();
        }
    }

另外,这里的复制是异步进行的。具体操作方式(记得不太清了):
1、建一个阻塞队列。当有新客户端注册时,把实例信息发往队列中。
2、另外一个线程,尝试从阻塞队列中读取数据,并设置一个超时时间。如果超时未读到数据,就继续循环等到,直到有新数据。

详细代码就不再分析了。

参考

https://www.cnblogs.com/rickiyang/p/11802413.html

https://blog.wangqi.love/articles/Spring-Cloud/Eureka(五)——高可用.html

https://cloud.spring.io/spring-cloud-netflix/reference/html/#netflix-eureka-server-starter

https://github.com/cfregly/fluxcapacitor/wiki/NetflixOSS-FAQ#eureka-service-discovery-load-balancer

https://groups.google.com/g/eureka_netflix

https://github.com/doocs/advanced-java/blob/main/docs/micro-services/how-eureka-enable-service-discovery-and-service-registration.md

标签:知识点,spring,server,剖析,client,注册,registration,eureka
From: https://www.cnblogs.com/xushengbin/p/17966538

相关文章

  • sql注入知识点总结
    mysql基础知识删除数据库dropdatabase库名;创建数据库createdatabase库名;php中使用sql语句来创建$sql='createdatabasedatabase_name';$retval=mysqli_query($coon,$sql);选择数据库usedatabase_name;php中使用mysqli_select_db($conn,'database_name')......
  • Java期末知识点总结(不全)
    Java知识点Lesson1认识javaLesson2-3认识对象:封装数据为类1.类中有成员变量(属性/字段)+成员函数(方法),类是对象的模板/缔造者/抽象/类型2.局部变量和成员变量的区别:(1)定义位置不同:定义在类中的变量成员变量,定义在方法或{}中的是局部变量(2)内存中位置不同:成员变量在堆......
  • 入门Linux运维工程师需要掌握的知识点和工具以及技能
    Linux系统的学习,可以选用redhat或centos,特别是centos在企业中用得最多,当然还会有其它版本的,比如Ubuntu等,根据自己的工作情况和兴趣来定。当然不同发行版本主要是包上的区别以及一些命令的差异,其他内核上的东西都大同小异。对于刚入门或准备入门Linux运维的来说,整理总结了以下10个......
  • 全面剖析文件外发管控——为企业数据安全保驾护航
    文件外发控制是一个企业数据安全保护措施中的重要环节。在企业的日常运营中,文件外发控制可以在很大程度上保护企业核心数据的安全,防止因文件外发而导致的敏感信息泄露。在企业涉及合同、研发、财务等多个敏感领域的情况下,文件外发控制的重要性不言而喻。此外,文件外发控制还可以有......
  • Eureka----服务搭建,服务注册,服务发现
    Eureka----服务搭建3步走: Eureka----服务注册  Eureka----服务发现 总结:@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ......
  • Spring IoC 原理剖析
    目录IoC容器系列的设计与实现:BeanFactory和ApplicationContextIOC容器接口设计图BeanFactory容器设计原理编程式使用IOC容器ApplicationContext设计原理AbstractRefreshableApplicationContextIoC容器的初始化创建Bean容器前的准备工作创建Bean容器,加载并注册Bean刷新Bean......
  • Kepware OPC UA Gateway之技术深度剖析与分享
    KepwareOPCUAGateway之技术深度剖析与分享随着客户对Kepware产品功能需求的进一步深入,Kepware适时推出了全功能套件(Premium Connectivity Suite)。使用一个授权即可解锁KepwareServer的所有功能,包含所有标准/高级驱动,IoTGateway,Datalogger,AdvancedTags等高级插件以及OPCU......
  • C++零碎知识点
    目录RTTI运行时类型信息RTTI运行时类型信息在C++中,RTTI(Run-TimeTypeInformation,运行时类型信息)是一种机制,允许在程序执行期间确定对象的类型。RTTI是为了解决许多类库供应商自行实现此功能而导致的不兼容性问题而添加到C++语言中的。RTTI的主要目的是允许在运行时获取对象......
  • 精彩推荐 |【Java技术专题】「重塑技术功底」攻破Java技术盲点之剖析动态代理的实现原
    背景介绍在Java编程中,动态代理的应用非常广泛。它被广泛应用于SpringAOP框架、Hibernate数据查询、测试框架的后端mock、RPC以及Java注解对象获取等领域。静态代理和动态代理与静态代理不同,动态代理的代理关系是在运行时确定的,这使得它在灵活性上更胜一筹。相比之下,静态代理的代理......
  • 并发重要知识点—线程池详解
    https://blog.csdn.net/qq_40270751/article/details/78843226 创建线程的另一种方法是实现Runnable接口。Runnable接口中只有一个run()方法,它非Thread类子类的类提供的一种激活方式。一个类实现Runnable接口后,并不代表该类是一个“线程”类,不能直接运行,必须通过Thread实例才......