Spring Cloud之Eureka服务端实现原理分析

SpringCloud总体介绍

Eureka使用
Eureka客户端实现原理分析
Eureka服务端实现原理分析

本文主要内容:

  1. 服务端如何存储应用服务信息
  2. 服务端如何处理client请求
    • 服务端如何处理服务注册请求
    • 服务端如何处理heartbeat请求
    • 服务端如何处理服务列表拉取请求
  3. 服务端如何实现自我保护机制

spring-cloud-netflix-eureka-client不同版本之间,代码会不一样,本文分析的是spring-cloud-netflix-eureka-server-2.2.2.RELEASE.jar版

一、Eureka如何存储应用信息

首先,我们先了解Eureka Server是如何保存实例信息的,Eureka Server通过三级缓存的机制保存应用实例信息。即:在Eureka Server存在三个地方存储实例信息

  • registry注册缓存: 用于处理客户端服务更新实例信息,类型ConcurrentHashMap<String, Map<String, Lease>>
  • readWriteCacheMap读写缓存: 定时同步registry中的应用信息,如果禁用只读缓存readOnlyCacheMap,也可以对客户端提供服务列表查询,类型LoadingCache<Key, Value>
  • readOnlyCacheMap只读缓存: 定时同步readWriteCacheMap中的应用信息,同时用于提供客户端读取应用列表,ConcurrentMap<Key, Value>

采用三级缓存的意义:当存在大量的服务注册和更新时,如果只是修改一个ConcurrentHashMap数据,会因为锁的竞争,影响性能。通过多级缓存实现读写分离,提供服务端性能

  • 默认情况下定时任务每30s将readWriteCacheMap同步至readOnlyCacheMap,每60s清理超过90s未续约的节点,
    • responseCacheUpdateIntervalMs参数值 : readOnlyCacheMap 缓存更新的定时器时间间隔,默认为30秒
    • responseCacheAutoExpirationInSeconds : readWriteCacheMap 缓存过期时间,默认为 180 秒
  • Eureka Client每30s从readOnlyCacheMap更新服务注册信息

三级缓存示意图:
在这里插入图片描述

二、服务端如何处理client请求

Eureka服务端对外提供了很多服务地址:在eureka-core-1.9.17.jar包的com.netflix.eureka.resources中
在这里插入图片描述

2.1、Eureka服务处理注册请求

2.1.1、ApplicationResource.addInstance():用于处理服务注册请求

@POST
@Consumes({
   "application/json", "application/xml"})
public Response addInstance(InstanceInfo info,@HeaderParam(PeerEurekaNode.HEADER_REPLICATION) String isReplication) {
   
    logger.debug("Registering instance {} (replication={})", info.getId(), isReplication);
    // 参数验证:validate that the instanceinfo contains all the necessary required fields
    ......
    // 省略

    // handle cases where clients may be registering with bad DataCenterInfo with missing data
    // 不能提供服务返回400
     DataCenterInfo dataCenterInfo = info.getDataCenterInfo();
    if (dataCenterInfo instanceof UniqueIdentifier) {
   
        String dataCenterInfoId = ((UniqueIdentifier) dataCenterInfo).getId();
        if (isBlank(dataCenterInfoId)) {
   
            boolean experimental =	"true".equalsIgnoreCase(serverConfig.getExperimental("registration.validation.dataCenterInfoId"));
            if (experimental) {
   
                String entity = "DataCenterInfo of type " + dataCenterInfo.getClass() + " must contain a valid id";
                return Response.status(400).entity(entity).build();
            } else if (dataCenterInfo instanceof AmazonInfo) {
   
                AmazonInfo amazonInfo = (AmazonInfo) dataCenterInfo;
                String effectiveId = amazonInfo.get(AmazonInfo.MetaDataKey.instanceId);
                if (effectiveId == null) {
   
                    amazonInfo.getMetadata().put(AmazonInfo.MetaDataKey.instanceId.getName(), info.getId());
                }
            } else {
   
                logger.warn("Registering DataCenterInfo of type {} without an appropriate id", dataCenterInfo.getClass());
            }
        }
    }
    // 进行服务注册
    // 最终调用PeerAwareInstanceRegistry.register(info, "true".equals(isReplication));
    registry.register(info, "true".equals(isReplication));
    return Response.status(204).build();  // 204 to be backwards compatible
}

2.1.2、PeerAwareInstanceRegistry.register()

注册方法会调用到父类AbstractInstanceRegistry.register()方法

@Override
public void register(final InstanceInfo info, final boolean isReplication) {
   
    // 租约过期时间,默认90s,如果超过90s没有收到客户端的心跳,则剔除该节点
	int leaseDuration = Lease.DEFAULT_DURATION_IN_SECS;
	if (info.getLeaseInfo() != null && info.getLeaseInfo().getDurationInSecs() > 0) {
   
		leaseDuration = info.getLeaseInfo().getDurationInSecs();
	}
    // 调用super.register发起节点注册
	super.register(info, leaseDuration, isReplication);
    // 复制信息到Eureka Server集群中其他机器上,循环集群中的所有节点,逐一发起注册
	replicateToPeers(Action.Register, info.getAppName(), info.getId(), info, null, isReplication);
}

PeerAwareInstanceRegistry类图:
在这里插入图片描述

2.1.3、AbstractInstanceRegistry.register完成服务注册

Eureka Server端,会把客户端的地址信息保存到一个ConcurrentHashMap中,即:registry注册缓存。并通过心跳检测机制进行服务续约。

public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {
   
	try {
   
        // 获取读锁
		read.lock();
        // 根据appName,从registry中获得当前实例信息
		Map<String, Lease<InstanceInfo>> gMap = registry.get(registrant.getAppName());
        // 增加注册次数到监控信息中
		REGISTER.increment(isReplication);
        // 如果当前appName是第一次注册,则初始化一个ConcurrentHashMap
		if (gMap == null) {
   
			final ConcurrentHashMap<String, Lease<InstanceInfo>> gNewMap = new ConcurrentHashMap<String, Lease<InstanceInfo>>();
			gMap = registry.putIfAbsent(registrant.getAppName(), gNewMap);
			if (gMap == null) {
   
				gMap = gNewMap;
			}
		}
        // 从gMap中查询已经存在的Lease信息,(Lease信息,包括:服务提供者实例信息,以及对服务实例的租约管理)
		Lease<InstanceInfo> existingLease = gMap.get(registrant.getId());
		// Retain the last dirty timestamp without overwriting it, if there is already a lease
        // 当instance已经存在,则和客户端的instance进行比较,时间最新的为有效instance信息
		if (existingLease != null 
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值