eureka原理
客户端是如何注册到注册中心的
在eureka的client与server通讯过程中,client需要把自己的信息发送到server,那么client具体是通过什么方式发送的,又发送了些什么东西呢。
通过eureka的启动日志可以看到这么一段信息,上面记录了作为client的时候向服务发送注册信息的注册状态。那么我们可以通过这段信息查找到DiscoveryClient类打上断点来看下客户端是如何注册的。
通过断点我们可以看到该方法的调用链,在这里面我们可以清晰的看到,在最初始还是一段spring的容器的初始化,然后通过DefaultLifecycleProcessor来初始化实现了SmartLifecycle接口的EurekaServiceRegistry类,在注册前置操作完毕之后通过通知listener方式初始化一个定时任务线程池来重复执行DiscoveryClient的register记录状态的变更。
我们现在可以根据下面的方法逻辑来看下client是如何向服务器发送注册信息的,并且可以知道具体发送了一些什么东西。
通过DiscoveryClient中的register方法我们可以清楚的看到客户端向服务端发送的实例信息,我们可以看一下在运行时的时候客户端中有些什么信息。
由上图我们可以看出发送的信息主要有服务的名称、IP、实例名称及端口信息。具体是通过什么方式发送的,我们可以通过对象的名称看出来是通过http的方式发送。
注册中心又是如何接收客户端的注册请求
在我们弄清楚客户端发送的注册信息之后,我们可以继续看下注册中心又是如何接收从客户端发送过来的
通过该日志信息我们可以查到AbstractInstanceRegistry类中有个register输出了这条日志。那么同样的方式我们通过断点来查看调用链。
通过调用链我们可以清晰的看到客户端发送注册信息到注册中心,在ApplicationResource#addInstance
对发送过来信息必要参数进行验证之后,最终调用到AbstractInstanceRegistry#register中进行信息注册。该请求接收的方式主要是通过NIO的方式来接收。
注册中心是如何存储注册信息的
在我们接收到注册信息之后我们需要对注册信息进行存储,在注册中心中是通过一个ConcurrentHashMap来存储的。在第一次注册的时候会判断map是否存在如果不存在的话则会创建一个key为String、value为Lease的ConcurrentHashMap集合对象,并将注册信息存储到该Map中,在之后的注册信息中则刷新该对象中的信息。具体操作逻辑可看下图:
注册中心的高可用机制
在微服务体系中,注册中心是一个非常重要的服务,如果注册中心宕机了,那么就会造成服务之间的调用无法拿到当时时间点有效的服务器地址,这样就会造成我们各个服务的调用失败,为了保证注册中心的高可用,eureka的注册机制中提供了注册中心的相互注册。在这种情况下如果有一台eureka宕机了,我们可以通过控台来看观察eureka的状态,及时通知相关人员来修复。
那么具体在eureka中如何实现高可用呢,我们只需要在server端的配置上加上其他server端的注册地址,将该服务器的信息发送到其他server端,这样eureka就形成了一个集群。
既然我们已经知道是通过这条配置来形成集群的那么我们可以在日志中查找一下,是否有类似的信息。通过下图的日志打印,我们可以找到有这么一个方法PeerEurekaNodes#start打印出了节点信息,那么我们同样通过断点的方式来查看一下调用链及运行逻辑。
通过调用链我们我们查找到这样的一段逻辑,从字面上的意思是初始化eureka节点。在eureka的启动方法中我们可以看到他启动了一个单线程定时任务来做eureka集群节点更新,至于任务执行使用的参数信息这个在后面讲配置的时候会说到。
通过源码的追踪我们知道在第一次更新节点信息的时候创建用于server通讯的JerseyReplicationClient
。在创建的时候会根据配置的属性信息选择是否需要使用ssl,并且将这个通讯客户端封装成一个PeerEurekaNode对象用来指代eureka的远程server服务。
。
在PeerEurekaNode创建的时候创建数据同步任务处理器ReplicationTaskProcessor、创建批量任务调度器batchingDispatcher及创建单任务调度器nonBatchingDispatcher。
Eureka集群信息同步机制
现在我们知道在注册中心集群的各个节点中会存储PeerEurekaNode对象,我们可以通过这些对象来同步各个节点的信息,那么在这些集群节点信息之间又同步了些什么信息呢。我们可以在PeerAwareInstanceRegistryImpl#register中看到在注册之后有个replicateToPeers方法来同步节点信息,在这个方法中我们就看到了PeerEurekaNode对象。
在该方法中我们可以看到他通过一个isReplication参数来表示实例是否为复制的实例,通过这样的方式来防止循环传播。如果不是复制的实例并且存在eureka集群的话则会同步应用名称、id、实例信息。
注册中心剔除服务的机制什么
在eureka中如果有些服务发生了错误,未正常在注册中心中注销,如果有服务调用到了这些服务就会导致业务无法进行,这时该怎么办。eureka为我们提供了服务剔除机制,主要用于剔除这些异常未正常调用cannel的服务,主要通过EvictionTask中的evict方法来实现。
上图就是evict的具体代码,我们来详细说下这里主要做了些什么操作:
- 首先他会判断是否开启了自我保护机制,该机制在配置文件中配置。
- 如果最后[更新时间 + lease的存活时间 + 补偿时间 < 当前时间]就加入到过期列表,补偿时间的计算如下图,计算方式[当前时间点 - task最近一次执行的时间点 - 清除间隔,小于0则取0]。
- 计算要剔除的注册服务最大剔除数量,计算方式为[注册服务总数-注册服务总数*自我保护续约百分比阀值],阀值来自配置文件默认0.85。
- 通过洗牌算法(Knuth shuffle algorithm)算法来公平剔除服务。
- 服务剔除的方法如下图,从服务容器中获取该服务的实例,移除他。将当前服务剔除的操作时间添加到最近关闭服务队列、最近状态更改队列。最后清除缓存。
因为自我保护机制的存在,会延迟才剔除服务,所以我们需要对服务进行降级和熔断才能保证该服务尽量少调用或不调用。
服务消费者如何获取服务信息
注册中心的作用我们清楚了,那么在服务调用的时候我们需要知道被调用服务器的信息,我们是如何在client中下载到其他服务器的信息呢。
为了查找client的执行痕迹,我们需要把日志的级别修改debug,我们在client配置文件加上一条配置使日志打印出来[logging.level.root=DEBUG],加上之后我们再运行的时候就可以在注册之后的信息看到如下图的日志信息。
根据打印的信息我们可以在对应的日志信息中加上断点查看调用链,通过调用链我们可以看到具体的刷新服务信息的逻辑。通过addInstance方法将服务信息添加到ConcurrentHashMap中。
至于如何初始化该刷新任务我们可以通过调用链之一向上可以找到下图方法,在DiscoveryClient#initScheduledTasks方法中可以看到通过配置参数来设置定时执行任务。
eureka的作用总结
注册中心主要用于服务的注册发现,可以及时通过admin页面来查看服务状态,方便运维人员及时处理异常服务器。