注册中心原理

image-20230219145829240

分布式微服务架构中,服务注册中⼼⽤于存储服务提供者地址信息、服务发布相关 的属性信息,消费者通过主动查询和被动通知的⽅式获取服务提供者的地址信息, ⽽不再需要通过硬编码⽅式得到提供者的地址信息。消费者只需要知道当前系统发 布了那些服务,⽽不需要知道服务具体存在于什么位置,这就是透明化路由。

  • pull模式: 服务消费者可以主动拉取可用的服务提供者清单

  • push模式: 服务消费者订阅服务,当服务提供者有变化时,注册中心也会主动推送更新后的服务清单给消费者

  • 注册中心需要完成对服务提供者的健康监控,当发现服务提供者失效时需要及时剔除

主流服务中心对比

  • Zookeeper

​ Zookeeper它是⼀个分布式服务框架,是Apache Hadoop 的⼀个⼦项⽬,它主 要是⽤来解决分布式应 ⽤中经常遇到的⼀些数据管理问题,如:统⼀命名服务、状态同步服务、集群管理、分布式应⽤配置项的管理等。

​ Zookeeper本质=存储+监听通知,主要是通过znode监听实现服务注册的,只要服务节点发生变动,就会通知到监听端,另外,Zookeeper有选举机制,保证了可用性,只要有一半以上的选举节点存活即可.

  • Eureka

​ 由Netflix开源,并被Pivatal集成到SpringCloud体系中,它是基于 RestfulAPI ⻛格开发的服务注册与发现组件

  • Consul

​ Consul是由HashiCorp基于Go语⾔开发的⽀持多数据中⼼分布式⾼可⽤的服务 发布和注册服务软件, 采⽤Raft算法保证服务的⼀致性,且⽀持健康检查

  • Nacos

​ Nacos是⼀个更易于构建云原⽣应⽤的动态服务发现、配置管理和服务管理平 台。简单来说 Nacos 就是 注册中⼼ + 配置中⼼的组合,帮助我们解决微服务开 发必会涉及到的服务注册 与发现,服务配置,服务管理等问题。Nacos 是 Spring Cloud Alibaba 核⼼组件之⼀,负责服务注册与发现,还有配置。

image-20230219150655582

P:分区容错性(⼀定的要满⾜的)

C:数据⼀致性

A:⾼可⽤

CAP不可能同时满⾜三个,要么是AP,要么是CP

Eureka组件

基础架构

image-20230219150904416

交互流程

这个是官方的一个描述图

image-20230219151004848

  • 从图中可以看出,Eureka Server支持集群同步,异地同步.高可用性
  • 微服务会定期向Eureka Server发送心跳,进行存活续约
  • Eureka Server同时也是Eureka Client,多个Eureka Server通过复制的方式完成服务注册列表同步
  • Eureka Client会缓存Eureka Server中的信息,即使所有Eureka Server节点宕机,服务消费者依然可以使用缓存查找到服务提供者

Eureka源码剖析

1.Eureka Server启动过程

SpringBoot启动时,会加载EurekaServerAutoConfiguration配置类

image-20230219152926084

  • EurekaServerAutoConfiguration类

image-20230219153111389

  1. 只有添加了@EnableEurekaServer注解,才会引入EurekaServerMarkerConfiguration类

image-20230219153412783

image-20230219153442058

  1. EurekaServerAutoConfiguration做了哪些事

image-20230219153833349

针对PeerEurekaNodes是如何实现节点更新的呢?可以查看类中的start方法

image-20230219154124895

上面的updatePeerEurekaNodes是什么时候执行的呢?

image-20230219154217808

可以看到在构建DefaultEurekaServerContext对象时,会执行start方法

image-20230219154315496

在配置类中,我们还需要关注如下两个bea:

image-20230219154414391

  1. EurekaServerInitializerConfiguration做了哪些事?

image-20230219154513593

查看contextInitialized中都初始化了哪些内容?

image-20230219154600102

继续查看initEurekaServerContext中都做了啥?

image-20230219154703520

查看syncUp方法中,如何实现的注册信息同步?

image-20230219154819412

再看一看同步过来的信息是如何注册到EurekaServer中的

image-20230219154926051

我们再看一下如何实现的服务剔除

com.netflix.eureka.registry.PeerAwareInstanceRegistryImpl#openForTraffic

image-20230219155149289

进入postInit方法查看

image-20230219155207263

2.Eureka服务接口暴露策略

在第一步中,我们看到自动配置类中注册了jerseyFilterRegistration

image-20230219155413455

注入Jersey的细节

image-20230219155556689

具体要扫描那些包呢?

image-20230219155725305

查看这些包下包含哪些接口服务

image-20230219155745185

这些就是使⽤Jersey发布的供Eureka Client调⽤的Restful⻛格服务接⼝(完成服务 注册、⼼跳续约等接⼝

3.Eureka Server服务注册接口(接受客户端注册服务)

查看ApplicationResource类的addInstance()⽅法中代码

image-20230219155916706

点击register方法:注册服务信息并且同步到其他Eureka节点,查看实现

image-20230219155949756

点击super.register方法:实例信息存储到注册表是一个ConcurrentHashMap.查看实现

/**
* Registers a new instance with a given duration.
*
* @see
com.netflix.eureka.lease.LeaseManager#register(java.lang.Object,
int, boolean)
*/
public void register(InstanceInfo registrant, int leaseDuration,
boolean isReplication) {
try {
read.lock(); //读锁

// registry是保存所有应⽤实例信息的Map:
ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>>
// 从registry中获取当前appName的所有实例信息
Map<String, Lease<InstanceInfo>> gMap =
registry.get(registrant.getAppName());

REGISTER.increment(isReplication); //注册统计+1

// 如果当前appName实例信息为空,新建Map
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;
}
}

// 获取实例的Lease租约信息
Lease<InstanceInfo> existingLease =
gMap.get(registrant.getId());
// Retain the last dirty timestamp without overwriting it,
if there is already a lease
// 如果已经有租约,则保留最后⼀个脏时间戳⽽不覆盖它
// (⽐较当前请求实例租约 和 已有租约 的LastDirtyTimestamp,选择靠
后的)
if (existingLease != null && (existingLease.getHolder() !=
null)) {
Long existingLastDirtyTimestamp =
existingLease.getHolder().getLastDirtyTimestamp();
Long registrationLastDirtyTimestamp =
registrant.getLastDirtyTimestamp();
logger.debug("Existing lease found (existing={},
provided={}", existingLastDirtyTimestamp,
registrationLastDirtyTimestamp);
if (existingLastDirtyTimestamp >
registrationLastDirtyTimestamp) {
logger.warn("There is an existing lease and the
existing lease's dirty timestamp {} is greater" +
" than the one that is being registered
{}", existingLastDirtyTimestamp, registrationLastDirtyTimestamp);
logger.warn("Using the existing instanceInfo
instead of the new instanceInfo as the registrant");
registrant = existingLease.getHolder();
}
}
else {
// The lease does not exist and hence it is a new
registration
// 如果之前不存在实例的租约,说明是新实例注册
// expectedNumberOfRenewsPerMin期待的每分钟续约数+2(因为
30s⼀个)
// 并更新numberOfRenewsPerMinThreshold每分钟续约阀值(85%)
synchronized (lock) {
if (this.expectedNumberOfRenewsPerMin > 0) {
// Since the client wants to cancel it, reduce
the threshold
// (1
// for 30 seconds, 2 for a minute)
this.expectedNumberOfRenewsPerMin =
this.expectedNumberOfRenewsPerMin + 2;
this.numberOfRenewsPerMinThreshold =
(int)
(this.expectedNumberOfRenewsPerMin *
serverConfig.getRenewalPercentThreshold());
}
}
logger.debug("No previous lease information found; it
is new registration");
}

Lease<InstanceInfo> lease = new Lease<InstanceInfo>
(registrant, leaseDuration);
if (existingLease != null) {

lease.setServiceUpTimestamp(existingLease.getServiceUpTimestamp())
;
}
gMap.put(registrant.getId(), lease); //当前实例信息放到维护注册
信息的Map

// 同步维护最近注册队列
synchronized (recentRegisteredQueue) {
recentRegisteredQueue.add(new Pair<Long, String>(
System.currentTimeMillis(),
registrant.getAppName() + "(" +
registrant.getId() + ")"));
}

// This is where the initial state transfer of overridden
status happens
// 如果当前实例已经维护了OverriddenStatus,将其也放到此Eureka
Server的overriddenInstanceStatusMap中
if
(!InstanceStatus.UNKNOWN.equals(registrant.getOverriddenStatus()))
{
logger.debug("Found overridden status {} for instance
{}. Checking to see if needs to be add to the "
+ "overrides",
registrant.getOverriddenStatus(), registrant.getId());
if
(!overriddenInstanceStatusMap.containsKey(registrant.getId())) {
logger.info("Not found overridden id {} and hence
adding it", registrant.getId());
overriddenInstanceStatusMap.put(registrant.getId(),
registrant.getOverriddenStatus());
}
}
InstanceStatus overriddenStatusFromMap =
overriddenInstanceStatusMap.get(registrant.getId());
if (overriddenStatusFromMap != null) {
logger.info("Storing overridden status {} from map",
overriddenStatusFromMap);

registrant.setOverriddenStatus(overriddenStatusFromMap);
}
// Set the status based on the overridden status rules
// 根据overridden status规则,设置状态
InstanceStatus overriddenInstanceStatus
= getOverriddenInstanceStatus(registrant,
existingLease, isReplication);
registrant.setStatusWithoutDirty(overriddenInstanceStatus);
// If the lease is registered with UP status, set lease
service up timestamp
// 如果租约以UP状态注册,设置租赁服务时间戳
if (InstanceStatus.UP.equals(registrant.getStatus())) {
lease.serviceUp();
}

registrant.setActionType(ActionType.ADDED); //ActionType为
ADD
recentlyChangedQueue.add(new RecentlyChangedItem(lease));
//维护recentlyChangedQueue
registrant.setLastUpdatedTimestamp(); //更新最后更新时间

// 使当前应⽤的ResponseCache失效
invalidateCache(registrant.getAppName(),
registrant.getVIPAddress(), registrant.getSecureVipAddress());
logger.info("Registered instance {}/{} with status {}
(replication={})",
registrant.getAppName(), registrant.getId(),
registrant.getStatus(), isReplication);
} finally {
read.unlock(); //读锁
}
}

我们再看一下如何复制的对等节点,PeerAwareInstanceRegistryImpl#replicateToPeers() :复制到Eureka对等节点

private void replicateToPeers(Action action, String appName, String
id,
InstanceInfo info /* optional */,
InstanceStatus newStatus /* optional
*/, boolean isReplication) {
Stopwatch tracer = action.getTimer().start();
try {
// 如果是复制操作(针对当前节点,false)
if (isReplication) {
numberOfReplicationsLastMin.increment();
}
// 如果它已经是复制,请不要再次复制,直接return
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;
}
// 复制Instance实例操作到某个node节点
replicateInstanceActionsToPeers(action, appName, id,
info, newStatus, node);
}
}
finally {
tracer.stop();
}
}

查看PeerAwareInstanceRegistryImpl#replicateInstanceActionsToPeers的具体实现

image-20230219160459321

4.Eureka Server服务续约接口(接受客户端续约)

InstanceResource的renewLease⽅法中完成客户端的⼼跳(续约)处理,关键代码:registry.renew(app.getName(), id, isFromReplicaNode)

image-20230219160617705

image-20230219160626025

image-20230219160632274

replicateInstanceActionsToPeers() 复制Instance实例操作到其它节点

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: //删除OverrideStatus
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);
}
}

5.Eureka Client注册服务

SpringBoot启动时,会加载EurekaClientAutoConfiguration配置类

image-20230219160906757

分析EurekaClientAutoConfiguration类的注解

image-20230219160938819

image-20230219160945740

1.读取配置文件

image-20230219161022219

2.启动时从EurekaServer获取服务实例信息

image-20230219161035803

观察父类DiscoveryClient()

image-20230219161111648

image-20230219161122044

3.注册自己到EurekaServer中

image-20230219161140157

image-20230219161159258

4.开启一些定时任务(心跳,刷新缓存)

刷新缓存

image-20230219161221754

image-20230219161230730

image-20230219161241016

心跳续约

image-20230219161253230

image-20230219161309324

6. Eureka Client下架服务

查看com.netflix.discovery.DiscoveryClient#shutdown

image-20230219161523458

image-20230219161538743