概述
SpringCloud对于注册中心提供了很多的选择,如下所示:
- 满足CAP中的Eureka-通过集群保证高可用,集群中每一个节点都是对等的,数据的一致通过节点相互通信来保证,所以无法保证强一致性,本身提供了自我保护机制(如果eureka服务在15分钟之内有超过85%的client无法通信,那么可能eureka与client出现来网络问题,eureka服务开启自我保护机制,不会再剔除90s内没有和eureka续约的client了。这个机制在开发阶段需要关闭),极大保证了服务的高可用。只要集群中存在一个节点都可以对外提供服务。
- 满足CAP中的CP的zookeeper-基于本身的ZAB协议保证数据的强一致性,集群中由master统一处理写请求,集群中服务器的个数必须满足n/2+1,否则服务不可用
- 满足CAP中的基于CP的Consul-基于raft共识算法来保证数据的强一致性,与zk类似集群中由master统一写请求,集群中服务器的个数必须满足n/2+1,否则服务不可用
- 既可以满足CAP中CP也可以使用AP的阿里新一代注册中心Nacos-其中CP也是通过raft算法实现,AP是通过自研的Distro算法实现。
基础准备
首先我们基于的springcloud的最新版本-Hoxton来介绍,这里使用的是springboot2.2.5,关于springboot与springCloud的版本对应关系可以参考下表:
SpirngCloud Version | Boot Version |
---|---|
Hoxton | 2.2.x |
Greenwich | 2.1.x |
Finchley | 2.0.x |
Edgware | 1.5.x |
Dalston | 1.5.x |
1:Eureka
1 创建一个简单的Eureka注册中心
1.1 创建一个maven父工程
我们在通过maven搭建springcloud脚手架工程的第一件事情就是创建一个maven父工程, 如下所示:
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.fc.springCloud</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<!-- 定义引用类库版本 -->
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-cloud.version>Hoxton.RELEASE</spring-cloud.version>
<java.version>1.8</java.version>
</properties>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<dependencies>
<!-- 引入公共jar -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- 应用监控-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- 应用测试-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<!--引用springcloud对应版本-->
<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>
<modules>
<module>eureka</module>
<module>eurekaClient</module>
</modules>
</project>
1.2 创建Eureka注册中心
在上述的父工程中创建一个子model,命名为eurekaServer,用以构建注册中心
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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>demo</artifactId>
<groupId>com.fc.springCloud</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>eureka-server</artifactId>
<packaging>jar</packaging>
<description>注册中心</description>
<dependencies>
<!-- SpringCloud Eureka 注册中心依赖 在不同版本此jar包是不同的 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<!-- actuator监控-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
<build>
<finalName>eureka-center</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
yml配置文件(这里也可以使用properties,个人喜欢使用yml,比较层次分明),可参考后续详细配置,自行删减。
上述为一个基础配置项,对于包含的其他项配置,后续通过源码分析介绍。
java启动类:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
/**
*
* 注册中心
* @author fangyuan
*
*/
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
最终通过http://localhost:18809/ 访问出现下面页面代表注册中心启动成功。
2: Eureka源码分析
2.1:Eureka服务端源码分析
Eureka server在整个微服务架构中的
对于Eureka与springcloud结合相关的代码在spring-cloud-starter-netflix-eureka-server包下
从上述的启动类中我们通过@EnableEurekaServer注解表明该服务注册中心,所以代码的研读的入口也在此,点开此注解的源码:
/**
*激活Eureka服务器相关配置的注释。
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(EurekaServerMarkerConfiguration.class)
public @interface EnableEurekaServer {
}
其中该注解通过@Import(该注解在这里的用于导入@Configuration注解的配置类,对于该注解不熟悉可自行百度下)导入EurekaServerMarkerConfiguration配置类,点开此类发现
继续向下寻找,点开EurekaServerAutoConfiguration此类,发现该类实现了WebMvcConfigurer接口(该接口提供很多自定义的拦截器,我们可以通过它实现跨域设置、类型转化器等等,详细用法可自行百度)
/**
*该类用于初始化eureka依赖的Bean对象
* @author Gunnar Hillert
* @author Biju Kunjummen
* @author Fahim Farook
*/
@Configuration(proxyBeanMethods = false)
//初始化EurekaServerInitializerConfiguration对象
@Import(EurekaServerInitializerConfiguration.class)
//标明当spring上下文中必须包含EurekaServerMarkerConfiguration.Marker类对象bean才会初始化,实际上也就代表类项目中必须包含@EnableEurekaServer注解
@ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class)
/**通过EnableConfigurationProperties注解将包含@ConfigurationProperties注解的两类
*EurekaDashboardProperties与InstanceRegistryProperties与对应的配置文件中的设置属性对应
*EurekaDashboardProperties是关于Eureka仪表板(UI)的配置属性,一般使用默认即可参考yml中eureka.dashboard
*InstanceRegistryProperties是关于实例注册相关配置属性,参考yml中eureka.instance.registry
*对应可参考后续yml中详细配置描述
*/
@EnableConfigurationProperties({ EurekaDashboardProperties.class,
InstanceRegistryProperties.class })
//加在server.properties配置文件
@PropertySource("classpath:/eureka/server.properties")
public class EurekaServerAutoConfiguration implements WebMvcConfigurer {
。。。
//是否开启
@Bean eureka界面
@ConditionalOnProperty(prefix = "eureka.dashboard", name = "enabled", matchIfMissing = true)
public EurekaController eurekaController() {
return new EurekaController(this.applicationInfoManager);
}
//初始化动作
@Bean
@ConditionalOnMissingBean
public ReplicationClientAdditionalFilters replicationClientAdditionalFilters() {
return new ReplicationClientAdditionalFilters(Collections.emptySet());
}
//初始化InstanceRegistry对象,该类用于eureka服务对于各client节点注册管理(包括对等节点处理-这里的对等节点指的就是eureka集群中的节点)这里springcloud对PeerAwareInstanceRegistry进行了包装处理,核心方法的执行本质还是使用PeerAwareInstanceRegistry在执行之前会进行handler拦截触发event事件发布
@Bean
public PeerAwareInstanceRegistry peerAwareInstanceRegistry(
ServerCodecs serverCodecs) {
this.eurekaClient.getApplications(); // force initialization
return new InstanceRegistry(this.eurekaServerConfig, this.eurekaClientConfig,
serverCodecs, this.eurekaClient,
this.instanceRegistryProperties.getExpectedNumberOfClientsSendingRenews(),
this.instanceRegistryProperties.getDefaultOpenForTrafficCount());
}
//用于管理包含Peererekanode(该节点是用于管理对等节点信息的管理)集合内生命周期的一个协助类-默认实现为RefreshablePeerEurekaNodes(该类其实是springcloud对PeerEurekaNodes的增强添加了监听EnvironmentChangeEvent事件-若该事件发生,那么代表当前服务环境发生变更了,需要进行校验是否重新修改所有的PeerEurekaNodes)Peererekanode中通过定时线程池定时(配置)来检测Peererekanode
@Bean
//从这个ConditionalOnMissingBean注解来看,我们可以在代码中自定义初始化该PeerEurekaNodes
@ConditionalOnMissingBean
public PeerEurekaNodes peerEurekaNodes(PeerAwareInstanceRegistry registry,
ServerCodecs serverCodecs,
ReplicationClientAdditionalFilters replicationClientAdditionalFilters) {
return new RefreshablePeerEurekaNodes(registry, this.eurekaServerConfig,
this.eurekaClientConfig, serverCodecs, this.applicationInfoManager,
replicationClientAdditionalFilters);
}
//初始化Eureka服务上下文对象,用于初始化和关闭eureka服务一些动作 实际对PeerEurekaNodes与PeerAwareInstanceRegistry管理 例如对peerEurekaNodes中定时任务启动和关闭与registry中任务的开启和关闭
@Bean
public EurekaServerContext eurekaServerContext(ServerCodecs serverCodecs,
PeerAwareInstanceRegistry registry, PeerEurekaNodes peerEurekaNodes) {
return new DefaultEurekaServerContext(this.eurekaServerConfig, serverCodecs,
registry, peerEurekaNodes, this.applicationInfoManager);
}
//初始化EurekaServerBootstrap对象用于初始化eureka基础环境
@Bean
public EurekaServerBootstrap eurekaServerBootstrap(PeerAwareInstanceRegistry registry,
EurekaServerContext serverContext) {
return new EurekaServerBootstrap(this.applicationInfoManager,
this.eurekaClientConfig, this.eurekaServerConfig, registry,
serverContext);
}
//注册jersey框架对外提供rest接口,提供与client与对等节点同步的接口
@Bean
public FilterRegistrationBean<?> jerseyFilterRegistration(
javax.ws.rs.core.Application eurekaJerseyApp) {
FilterRegistrationBean<Filter> bean = new FilterRegistrationBean<Filter>();
bean.setFilter(new ServletContainer(eurekaJerseyApp));
bean.setOrder(Ordered.LOWEST_PRECEDENCE);
bean.setUrlPatterns(
Collections.singletonList(EurekaConstants.DEFAULT_PREFIX + "/*"));
return bean;
}
。。。
}
/**
*该类用于eureka服务的一些参数
* @author Dave Syer
*/
@Configuration(proxyBeanMethods = false)
public class EurekaServerInitializerConfiguration implements
//通过ServletContextAware用以获取ServletContext对象
ServletContextAware,
//实现SmartLifecycle该接口,作用当Spring容器加载所有bean并完成初始化之后,会接着回调实现该接口的类中对应的方法(start()方法)
SmartLifecycle,
//作用用于spring初始化相同接口时顺序
Ordered {
private static final Log log = LogFactory
.getLog(EurekaServerInitializerConfiguration.class);
//包含服务注册中心有关的所有配置
@Autowired
private EurekaServerConfig eurekaServerConfig;
private ServletContext servletContext;
@Autowired
private ApplicationContext applicationContext;
@Autowired
private EurekaServerBootstrap eurekaServerBootstrap;
//当前eureka服务器状态
private boolean running;
//定义order等级
private int order = 1;
@Override
public void setServletContext(ServletContext servletContext) {
this.servletContext = servletContext;
}
@Override
public void start() {
//启动一个线程用于执行后续初始化动作减少主线程的压力
new Thread(() -> {
try {
// TODO: is this class even needed now?
eurekaServerBootstrap.contextInitialized(
EurekaServerInitializerConfiguration.this.servletContext);
log.info("Started Eureka Server");
//目前eureka服务种存在5种事件 该事件可以通过实现ApplicationListener接口来接受
/**
*EurekaRegistryAvailableEvent——eureka自身注册时触发内部通知事件
*EurekaServerStartedEvent——eureka服务启动时触发内部通知事件
*EurekaInstanceCanceledEvent——eureka客户端节点下线时触发内部通知事件
*EurekaInstanceRenewedEvent——eureka客户端节点更新时触发内部通知事件
*EurekaInstanceRegisteredEvent——eureka客户端节点注册时触发内部通知事件
*
*/
publish(new EurekaRegistryAvailableEvent(getEurekaServerConfig()));
//修改eureka服务器状态
EurekaServerInitializerConfiguration.this.running = true;
publish(new EurekaServerStartedEvent(getEurekaServerConfig()));
}
catch (Exception ex) {
// Help!
log.error("Could not initialize Eureka servlet context", ex);
}
}).start();
}
...
}
/**
*
*
*/
public class EurekaServerBootstrap {
private static final Log log = LogFactory.getLog(EurekaServerBootstrap.class);
private static final String TEST = "test";
private static final String ARCHAIUS_DEPLOYMENT_ENVIRONMENT = "archaius.deployment.environment";
private static final String EUREKA_ENVIRONMENT = "eureka.environment";
private static final String DEFAULT = "default";
private static final String ARCHAIUS_DEPLOYMENT_DATACENTER = "archaius.deployment.datacenter";
private static final String EUREKA_DATACENTER = "eureka.datacenter";
protected EurekaServerConfig eurekaServerConfig;
protected ApplicationInfoManager applicationInfoManager;
protected EurekaClientConfig eurekaClientConfig;
protected PeerAwareInstanceRegistry registry;
protected volatile EurekaServerContext serverContext;
protected volatile AwsBinder awsBinder;
public void contextInitialized(ServletContext context) {
try {
//初始化eureka环境
initEurekaEnvironment();
//初始化eureka服务上下文对象
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);
}
}
//销毁上下文中数据
public void contextDestroyed(ServletContext context) {
try {
log.info("Shutting down Eureka Server..");
context.removeAttribute(EurekaServerContext.class.getName());
destroyEurekaServerContext();
destroyEurekaEnvironment();
}
catch (Throwable e) {
log.error("Error shutting down eureka", e);
}
log.info("Eureka Service is now shutdown...");
}
//设置当前eureka基础环境
protected void initEurekaEnvironment() throws Exception {
log.info("Setting the eureka configuration..");
String dataCenter = ConfigurationManager.getConfigInstance()
.getString(EUREKA_DATACENTER);
//yml中可设置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);
}
//yml中可设置eureka.environment时 默认为空
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();
}
//该动作用于将serverContext对象EurekaServerContextHolder管理,该类提供了一个静态方法用于获取serverContext对象,这样就可以使一些不被ioc容器管理的对象也可以获取serverContext对象
EurekaServerContextHolder.initialize(this.serverContext);
log.info("Initialized server context");
// Copy registry from neighboring eureka node
//初始化同步eureka集群中其它服务中拥有的实例注册表信息
int registryCount = this.registry.syncUp();
//开启对外通道意思就是完成eureka服务初始化动作,修改服务状态为UP,表明可以对外提供服务
//同时通过InstanceRegistry进行初始化 并开启定时(通过eviction-interval-timer-in-ms设置)周期任务执行EvictionTask 代码在父抽象类AbstractInstanceRegistry中postInit()方法
this.registry.openForTraffic(this.applicationInfoManager, registryCount);
// Register all monitoring statistics.
EurekaMonitors.registerAllStats();
}
/**
* Server context shutdown hook. Override for custom logic
* @throws Exception - calling {@link AwsBinder#shutdown()} or
* {@link EurekaServerContext#shutdown()} may result in an exception
*/
protected void destroyEurekaServerContext() throws Exception {
EurekaMonitors.shutdown();
if (this.awsBinder != null) {
this.awsBinder.shutdown();
}
if (this.serverContext != null) {
this.serverContext.shutdown();
}
}
/**
* Users can override to clean up the environment themselves.
* @throws Exception - shutting down Eureka servers may result in an exception
*/
protected void destroyEurekaEnvironment() throws Exception {
}
protected boolean isAws(InstanceInfo selfInstanceInfo) {
boolean result = DataCenterInfo.Name.Amazon == selfInstanceInfo
.getDataCenterInfo().getName();
log.info("isAws returned " + result);
return result;
}
}
InstanceRegistry相关类图:
InstanceRegistry该类也是springcloud对于eureka的PeerAwareInstanceRegistry类的包装,对PeerAwareInstanceRegistry提供对各PeerNode对等节点的管理的功能(任何客户端发送到集群中的任意一台eureka中动作,都需要向该eureka服务保管的对等节点列表中任意一个函数都要进行相同的处理,其中包含注册register方法,删除cancel方法,续约等功能)的增强,并且开启各种不同的定时任务(例如驱逐失效服务节点,监控当前服务与各客户端连连接情况,失败率是否超过了一定阈值:默认是15min(renewal-threshold-update-interval-ms)内超过85%(通过renewal-percent-threshold设置)的,是否需要开启自我保护机制),本质是对操作进行拦截并推送Event事件,在应用中我们可以监控不同事件做些不同处理。其它都是使用PeerAwareInstanceRegistry的原装方法。
EurekaServerContext相关类图:
用于PeerAwareInstanceRegistry与PeerEurekaNodes相关任务的启动与关闭,默认实现为DefaultEurekaServerContext,通过@PostConstruct注解在DefaultEurekaServerContext构造函数初始化完成之后被调用,进行对各个任务(例如更新对等节点任务,节点错误阈值监控是否需要开启自我保护机制)的启动,以及通过@PreDestroy注解关闭对应任务。以及对如下所示:
RefreshablePeerEurekaNodes相关类图:
该类实际上是springcloud的构建对PeerEureakNodes(该类是对集群中对等节点的一个维护,包括增删改查等操作)的包装类,对PeerEureakNodes提供功能的增强,添加了 ApplicationListener监听器,监听了EnvironmentChangeEvent事件。一旦Environment发送了变更,需要判断PeerEureakNodes是否发送了变更,其它依旧使用PeerEureakNodes方法。PeerEureakNodes中维护了一个newSingleThreadScheduledExecutor的线程池,通过peer-eureka-nodes-update-interval-ms定时的调用(updatePeerEurekaNodes)去更新集群中对等节点的信息
PeerEurekaNode:
该类用于表示一个用于节点共享信息的对等节点。使用该类用于处理复制所有对等节点的更新操作,如注册、续订、取消、过期和状态更改等
上述为该类包含的的核心方法有:
register:注册
cancel:取消
heartbeat:心跳检测
等这些方法实际在服务端收到client请求时被调用,详细参考源码
EurekaServerBootstrap:
该类负载对EurekaServer中一些初始化动作,该类初始化动作的触发由EurekaServerInitializerConfiguration(该类实现了SmartLifecycle接口,在spring中bean初始化完成之后调用start()方法来做一些动作)中start()方法完成。该方法中就调用EurekaServerBootstrap中的contextInitialized()方法,该方法中由两个动作,initEurekaEnvironment()初始化化基础属性,比较重要的initEurekaServerContext()方法,该方法在初始化的时候需要同步其它的eureka中的节点注册表,InstanceRegistry调用syncUp()函数来做到。通过openForTraffic(),该函数由多个作用:
- 变更erurek服务状态为up对外提供服务
- 调用updateRenewsPerMinThreshold 计算每分钟续订的阈值并设置numberOfRenewsPerMinThreshold属性
- 调用AbstractInstanceRegistry中postInit()方法来做开启定时(通过eviction-interval-timer-in-ms设置)周期任务
EvictionTask:驱逐任务
该类用于驱逐一定时间内(默认是90s-该值由client自行设置)没有和eureka进行续约的客户端。
/**
*
*该类是抽象类AbstractInstanceRegistry中的一个内部类
*用以eureka服务对清理无效节点
*
*/
class EvictionTask extends TimerTask {
//存储上一次evictionTask执行纳秒时间
private final AtomicLong lastExecutionNanosRef = new AtomicLong(0l);
@Override
public void run() {
try {
//获取补偿时间(以ms为单位)-该时间是因为一些原因(例如由于时钟偏移或gc的stop allword)导致并不会按照设置的周期严格执行,实际上可能会出现evictionTask是根据配置的周期在之后执行。而作出的一定的补偿机制
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();
//通过atomic获取上一次任务执行时间 并将最新的时间写入
long lastNanos = lastExecutionNanosRef.getAndSet(currNanos);
if (lastNanos == 0l) {
return 0l;
}
//计算上一次迭代以来该任务的实际执行时间
long elapsedMs = TimeUnit.NANOSECONDS.toMillis(currNanos - lastNanos);
//获取最终补偿时间为上一次迭代以来该任务的实际执行时间(当前时间与上一次执行完成时间之差通过)与配置的执行时间(eviction-interval-timer-in-ms)之差
long compensationTime = elapsedMs - serverConfig.getEvictionIntervalTimerInMs();
return compensationTime <= 0l ? 0l : compensationTime;
}
long getCurrentTimeNano() { // for testing
return System.nanoTime();
}
}
yml文件
server:
port: 18809
##tomcat配置
tomcat:
##最大连接数 默认为8192
max-connections: 200
##最大线程数
max-threads: 300
min-spare-threads: 0
uri-encoding: UTF-8
accept-count: 100
spring:
application:
name: eureka-server
##eureka相关配置
eureka:
##自定义设置当前eureka环境 用于区别可能存在的多数据中心部署情况
##默认为test
environment: test
##默认为default
datacenter: default
##ui仪表盘相关配置
dashboard:
##仪表盘访问露肩 这一块有关代码在EurekaController类中 默认为http://ip:port/
path: /admin
##是否开启仪表盘 默认为true 若和admin或其他产品结合可关闭此选项
enabled: true
instance:
hostname: localhost
##实例注册
registry:
##用于确定何时取消租约的值,对于独立租约默认为1。对于对等复制的eurekas,应设置为0
default-open-for-traffic-count: 1
##预期发送续订的客户端数,默认为1 建议设置为非零,以确保即使是独立服务器也可以将其逐出策略调整为注册数
##如果为零,则即使成功注册也不会重置InstanceRegistry.register()中的速率阈值
expected-number-of-clients-sending-renews: 1
##设置多长时间对没有进行续约的服务进行剔除
##该值如果设置过大存在因实例不在时其它请求通过路由到该服务时导致服务不可用,设置过小时会出现网络波动时该服务被过早从服务列表中剔除
##该值应该大于lease-renewal-interval-in-seconds值 该值一般由client来设置
lease-expiration-duration-in-seconds: 90
client:
##指示此实例是否应向eureka服务器注册其信息以供其他服务发现。
##默认为他true 在某些情况下不希望发现该实例,而只希望发现其他实例时可以设置为false
registerWithEureka: false
##该客户端是否应从eureka服务器获取eureka注册表信息 默认为true
fetchRegistry: false
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
##服务端有关配置
server:
##驱逐时间定义
eviction-interval-timer-in-ms: 60000
##对等节点互相更新时间 定时器触发 默认10min
peer-eureka-nodes-update-interval-ms: 600000
##设置开启自我保护策略的失败比例阈值 默认为85%
renewal-percent-threshold: 0.85
##设置开启自我保护策略的时间阈值默认为15分钟
renewal-threshold-update-interval-ms: 900000
2.2:Eureka客户端源码分析
源码所在jar地址如下所示:
在META-INF中spring.factories中配置了
其中在eureka中会包含eureka客户端初始化入口类 EurekaClientAutoConfiguration与EurekaDiscoveryClientConfiguration两个配置类
eureka client主要有以下作用:
- 上线通知
- 启动时拉取
- 定期从server端pull最新的服务注册表信息
- 定时发送服务端心跳检测
- 下线通知
所以这里也针对上述功能做部分代码解析
EurekaClientAutoConfiguration类:该类初始化EurekaInstanceConfigBean(配置信息类) ,DiscoveryClient(cloud对DiscoveryClient的包装实现类CloudEurekaClient)
在DiscoveryClient中对应的构造函数中第一次全量拉取注册列表数据,如下所示
初始化了3个定时调度器,如下所示:
通过initScheduledTasks()函数构建:
其中CacheRefreshThread为具体更新注册表代码:
往下追踪下去发现与第一次pull公用一个fetchRegistry方法:根据条件来判断是否全量pull拉取
剩下的 getAndStoreFullRegistry与getAndUpdateDelta就是通过http访问服务端去pull数据
心跳检测:
初始化时注册:
当client进行关闭时,spring在bean被销毁之前执行shutdown动作
3:Eureka作为注册中心存在的问题
作为一个新手在使用eureka在生产环境中可能会遇到一些问题,例如新服务上线后,服务消费者不能访问到刚上线的新服务,需要过⼀段时间后才能访问?或是将服务下线后,服务还是会被调⽤到,⼀段时候后才彻底停⽌服务,访问前期会导致频繁报错?上线⼀个新的服务实例,但是服务消费者⽆感知,过了⼀段时间才知道某⼀个服务实例下线了,服务消费者⽆感知,仍然向这个服务实例在发起请求,这其实就是服务发现的⼀个问题,当我们需要调⽤服务实例时,信息是从注册中⼼Eureka获取的,然后通过Ribbon选择⼀个服务实例发起调⽤,如果出现调⽤不到或者下线后还可以调⽤的问题,原因肯定是服务实例的信息更新不及时导致的。这也是Eureka的client的主动pull更新模型导致(pull拉取时间过短对eureka服务端村砸性能压力,后续的阿里的Nacos提供了push与pull的双重保障)
Eureka服务发现慢的原因主要有两个,⼀部分是因为服务缓存导致的,另⼀部分是因为客户端缓存导致的。
- 服务端缓存 :服务注册到注册中⼼后,服务实例信息是存储在注册表中的,也就是内存中。但Eureka为了提⾼响应速 度,在内部做了优化,加⼊了两层的缓存结构,将Client需要的实例信息,直接缓存起来,获取的时候直接从缓存中拿数据然后响应给 Client。 第⼀层缓存是readOnlyCacheMap,readOnlyCacheMap是采⽤ConcurrentHashMap来存储数据的,主要负责定时与readWriteCacheMap进⾏数据同步,默认同步时间为 30 秒⼀次。 第⼆层缓存是readWriteCacheMap,readWriteCacheMap采⽤Guava来实现缓存。缓存过期时间默认为180秒,当服务下线、过期、注册、状态变更等操作都会清除此缓存中的数据。 Client获取服务实例数据时,会先从⼀级缓存中获取,如果⼀级缓存中不存在,再从⼆级缓存中获取, 如果⼆级缓存也不存在,会触发缓存的加载,从存储层拉取数据到缓存中,然后再返回给 Client。Eureka 之所以设计⼆级缓存机制,也是为了提⾼ Eureka Server 的响应速度,缺点是缓存会导致 Client 获取不到最新的服务实例信息,然后导致⽆法快速发现新的服务和已下线的服务。 了解了服务端的实现后,想要解决这个问题就变得很简单了,我们可以缩短只读缓存的更新时间 (eureka.server.response-cache-update-interval-ms)让服务发现变得更加及时,或者直接将只读缓 存关闭(eureka.server.use-read-only-response-cache=false),多级缓存也导致C层⾯(数据⼀致 性)很薄弱。 Eureka Server 中会有定时任务去检测失效的服务,将服务实例信息从注册表中移除,也可以将这个失 效检测的时间缩短,这样服务下线后就能够及时从注册表中清除。
- 客户端缓存: 客户端缓存主要分为两块内容,⼀块是 Eureka Client 缓存,⼀块是 Ribbon 缓存。 Eureka Client 缓存:EurekaClient负责跟EurekaServer进⾏交互,在EurekaClient中的com.netflflix.discovery.DiscoveryClient.initScheduledTasks() ⽅法中,初始化了⼀个 CacheRefreshThread 定时任务专⻔⽤来拉取 Eureka Server 的实例信息到本地。所以我们需要缩短这个定时拉取服务信息的时间间隔(eureka.client.registryFetchIntervalSeconds) 来快速发现新的服务。 Ribbon 缓存 Ribbon会EurekaClient中获取服务信息,ServerListUpdater是Ribbon中负责服务实例 更新的组件,默认的实现是PollingServerListUpdater,通过线程定时去更新实例信息。定时刷新的时间间隔默认是30秒,当服务停⽌或者上线后,这边最快也需要30秒才能将实例信息更新成最新的。我们 可以将这个时间调短⼀点,⽐如 3 秒。 刷新间隔的参数是通过 getRefreshIntervalMs ⽅法来获取的,⽅法中的逻辑也是从Ribbon 的配置中进 ⾏取值的。 将这些服务端缓存和客户端缓存的时间全部缩短后,跟默认的配置时间相⽐,快了很多。我们通过调整 参数的⽅式来尽量加快服务发现的速度,但是还是不能完全解决报错的问题,间隔时间设置为3秒,也还是会有间隔。所以我们⼀般都会开启重试功能,当路由的服务出现问题时,可以重试到另⼀个服务来 保证这次请求的成功。
最后
以上就是丰富泥猴桃为你收集整理的Springcloud-注册中心-Eureka源码分析的全部内容,希望文章能够帮你解决Springcloud-注册中心-Eureka源码分析所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复