Eureka Server注册表存储结构与多级缓存机制

目录

1、Eureka Server工作流程

2、Eureka Server注册表存储结构

2.1、基本存储结构

2.2、Eureka Server集群之间的注册表同步

3、Eureka Server多级缓存机制

3.1、多级缓存介绍

3.1.1、拉取注册表流程

3.1.2、更新注册表流程

3.2、多级缓存优点


1、Eureka Server工作流程

  1. Eureka Server,负责服务注册与发现,负责记录微服务集群下的服务信息,包括服务ip,port等服务基础信息;
  2. Eureka Client服务在启动时,会将自身机器信息,注册到Eureka Server中;
  3. Eureka Client服务,每隔30秒,会向Eureka Server发送请求,获取最新的服务注册信息,然后将注册中心上的所有服务信息,更新到本地注册表中;
  4. Eureka Client服务,每隔30秒,会向Eureka Server发送一次心跳请求,告诉Eureka Server自己还活着,否则Eureka Server会将该服务下线。

2、Eureka Server注册表存储结构

        其中,registry变量的key = 服务名称,value = 一个服务的多个实例。

        而value = Map<String, Lease<InstanceInfo>>, key为服务实例id,InstanceInfo为服务实例的具体信息(机器ip,port等),Lease则维护了每个服务最近一次发送心跳的时间。

2.1、基本存储结构

        Eureka Server的注册表直接基于纯内存,如上图所示,Eureka Server直接在内存中维护了一个ConcurrentHasnMap数据结构,用于保存Eureka Server服务注册信息。

        每个Eureka Client服务的注册、服务下线、服务故障,都会再内存里维护和更新这个registry这个注册表。

        客户端每隔30秒拉取服务的时候,Eureka Server就会直接提供内存中有变化的注册表数据。

        客户端每隔30秒发送心跳请求时,Eureka Server就会直接在内存中更新服务的心跳时间。

2.2、Eureka Server集群之间的注册表同步

  1. Eureka Client在进行注册、发送心跳、下线、更改状态等操作时,会向Eureka Server集群中的任意一个Eureka Server发送请求;
  2. 收到请求的Eureka Server更新自身的注册表信息;
  3. 收到请求的Eureka Server将Client操作数据发送给集群中除自己以外的所有节点。

以Eureka Client注册为例:

a、Eureka Server接收到客户端注册请求,进行实例数据检查,并通过registry注册实例。

@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
	if (isBlank(info.getId())) {
		return Response.status(400).entity("Missing instanceId").build();
	} else if (isBlank(info.getHostName())) {
		return Response.status(400).entity("Missing hostname").build();
	} else if (isBlank(info.getIPAddr())) {
		return Response.status(400).entity("Missing ip address").build();
	} else if (isBlank(info.getAppName())) {
		return Response.status(400).entity("Missing appName").build();
	} else if (!appName.equals(info.getAppName())) {
		return Response.status(400).entity("Mismatched appName, expecting " + appName + " but was " + info.getAppName()).build();
	} else if (info.getDataCenterInfo() == null) {
		return Response.status(400).entity("Missing dataCenterInfo").build();
	} else if (info.getDataCenterInfo().getName() == null) {
		return Response.status(400).entity("Missing dataCenterInfo Name").build();
	}

	// handle cases where clients may be registering with bad DataCenterInfo with missing data
	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());
			}
		}
	}

    // 注册服务实例
	registry.register(info, "true".equals(isReplication));
	return Response.status(204).build();  // 204 to be backwards compatible
}

b、registry方法,将注册信息存储到本地,并在集群间进行同步

@Override
public void register(final InstanceInfo info, final boolean isReplication) {
	int leaseDuration = Lease.DEFAULT_DURATION_IN_SECS;
	if (info.getLeaseInfo() != null && info.getLeaseInfo().getDurationInSecs() > 0) {
		leaseDuration = info.getLeaseInfo().getDurationInSecs();
	}
    // 将实例注册到本地注册表
	super.register(info, leaseDuration, isReplication);

	// 注册 下线 故障 心跳 同步到其他eureka server
	replicateToPeers(Action.Register, info.getAppName(), info.getId(), info, null, isReplication);
}

c、将Eureka Client的注册操作复制到集群中除自己以外的所有节点

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()) {
			// 如果遍历到的Eureka Server节点是当前节点,则跳过
			if (peerEurekaNodes.isThisMyUrl(node.getServiceUrl())) {
				continue;
			}
			// 复制实例操作到集群中其他节点
			replicateInstanceActionsToPeers(action, appName, id, info, newStatus, node);
		}
	} finally {
		tracer.stop();
	}
}

d、replicateInstanceActionsToPeers执行具体操作

 private void replicateInstanceActionsToPeers(Action action, String appName,
											 String id, InstanceInfo info, InstanceStatus newStatus,
											 PeerEurekaNode node) {
	try {
		InstanceInfo infoFromRegistry = null;
		CurrentRequestVersion.set(Version.V2);
		switch (action) {
			case Cancel:
				node.cancel(appName, id);
				break;
			case Heartbeat:
				InstanceStatus overriddenStatus = overriddenInstanceStatusMap.get(id);
				infoFromRegistry = getInstanceByAppAndId(appName, id, false);
				node.heartbeat(appName, id, infoFromRegistry, overriddenStatus, false);
				break;
            // 注册操作
			case Register:
				node.register(info);
				break;
			case StatusUpdate:
				infoFromRegistry = getInstanceByAppAndId(appName, id, false);
				node.statusUpdate(appName, id, newStatus, infoFromRegistry);
				break;
			case DeleteStatusOverride:
				infoFromRegistry = getInstanceByAppAndId(appName, id, false);
				node.deleteStatusOverride(appName, id, infoFromRegistry);
				break;
		}
	} catch (Throwable t) {
		logger.error("Cannot replicate information to {} for action {}", node.getServiceUrl(), action.name(), t);
	}
}

e、以实例注册 case Registry为例

 public void register(final InstanceInfo info) throws Exception {
	long expiryTime = System.currentTimeMillis() + getLeaseRenewalOf(info);
	batchingDispatcher.process(
			//将服务的信息转化为字符串
			taskId("register", info),
			//对复制任务这个实体进行赋值
			new InstanceReplicationTask(targetHost, Action.Register, info, null, true) {
				
				public EurekaHttpResponse<Void> execute() {
					//向其他eureka server发送注册信息
					return replicationClient.register(info);
				}
			},
			expiryTime
	);
}

@Override
public EurekaHttpResponse<Void> register(InstanceInfo info) {
	String urlPath = "apps/" + info.getAppName();
	Response response = null;
	try {
		Builder resourceBuilder = jerseyClient.target(serviceUrl).path(urlPath).request();
		addExtraProperties(resourceBuilder);
		addExtraHeaders(resourceBuilder);
		response = resourceBuilder
				.accept(MediaType.APPLICATION_JSON)
				.acceptEncoding("gzip")
				.post(Entity.json(info));
		return anEurekaHttpResponse(response.getStatus()).headers(headersOf(response)).build();
	} finally {
		if (logger.isDebugEnabled()) {
			logger.debug("Jersey2 HTTP POST {}/{} with instance {}; statusCode={}", serviceUrl, urlPath, info.getId(),
					response == null ? "N/A" : response.getStatus());
		}
		if (response != null) {
			response.close();
		}
	}
}

3、Eureka Server多级缓存机制

3.1、多级缓存介绍

目的:为了避免同时读写内存数据结构造成的并发冲突问题。

三级缓存:ReadOnlyCacheMap、ReadWriteCacheMap、registry三级缓存。

3.1.1、拉取注册表流程

  1. 首先从ReadOnlyCacheMap里查缓存的注册表。
  2. 若没有,就找ReadWriteCacheMap里缓存的注册表。
  3. 如果还没有,就从内存registry中获取实际的注册表数据。

3.1.2、更新注册表流程

  1. 在内存registry中更新变更的注册表信息,同时清除ReadWriteCacheMap;
  2. 一段时间内(30秒),客户端还能从ReadOnlyCacheMap中获取旧的注册表信息;
  3. 30秒过后,Eureka Server发现ReadWriteCacheMap被清空了,会去清空ReadOnlyCacheMap中旧的注册表信息;
  4. 下次有新的拉取请求,由于ReadOnlyCacheMap和ReadWriteCacheMap已被清空,此时会直接从内存registry中拉取最新的注册表信息,同时填充各缓存数据。

3.2、多级缓存优点

  • 尽可能保证了内存注册表数据不会出现频繁的读写冲突问题。

  • 进一步保证对Eureka Server的大量请求,都是快速从纯内存走,性能极高。

以上内容为个人学习汇总,仅供学习参考,如有问题,欢迎在评论区指出,谢谢!

部分图片来源于网络,如有侵权,请联系作者删除。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值