eureka浅析

netfix eureka 浅析

摘要: 网上关于eureka 的介绍很多,也有很多优秀的文章可以作为参考,在文章末尾我会贴上自己看过觉得有收获或者总结性强的文章。主要内容会简单介绍 eureka 整体的架构和原理,部分代码实现,以及配置参数列表和一些注意事项。

什么是eureka?

Eureka是一种基于REST(Representational State Transfer)的服务,主要用于AWS云,用于定位服务,以实现中间层服务器的负载平衡和故障转移。我们将此服务称为Eureka Server。Eureka还附带了一个基于Java的客户端组件Eureka Client,它使与服务的交互变得更加容易。客户端还有一个内置的负载均衡器,可以进行基本的循环负载均衡。在Netflix,一个更复杂的负载均衡器包含Eureka基于流量,资源使用,错误条件等多种因素提供加权负载平衡,以提供卓越的弹性。

本文以官方wiki为主要参考,wiki: https://github.com/Netflix/eureka/wiki/Eureka-at-a-glance

这段是官方的翻译,说白了,就是我们现在使用的微服务架构中的注册中心,server端是一个服务注册信息中心,client端则作为信息的消费者和提供者。类似的注册中心 还有consul,zookeeper,etcd 等都可以作为注册中心,官方宣布 eureka2.x disconnected 之后,consul 或称为主流的替代方案。

eureka 原理及架构

官方架构图:https://github.com/Netflix/eureka/raw/master/images/eureka_architecture.png

服务在Eureka 注册,然后发送心跳每30秒更新一次租约。如果客户端无法续订租约几次,则会在大约90秒内将其从服务器注册表中删除。注册信息和续订将复制到群集中的所有eureka节点。来自任何区域的客户端都可以查找注册表信息(每30秒发生一次)以查找其服务(可能位于任何区域中)并进行远程调用。

eureka 一些概念及部分源码分析

在架构中可以得知,分为以下几个部分:

高可用框架使用的是三个server,两两注册的方式。

  • register 注册 :client 启动后发起注册,netflix源码是从第一次发起心跳(30s后)发生注册,springcloud 更改过设置,在client启动时就发起注册。 在 eureka-client 里的discoveryclient类中有一个register()法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    boolean register() throws Throwable {
    logger.info(PREFIX + "{}: registering service...", appPathIdentifier);
    EurekaHttpResponse<Void> httpResponse;
    try {
    httpResponse = eurekaTransport.registrationClient.register(instanceInfo);
    } catch (Exception e) {
    logger.warn(PREFIX + "{} - registration failed {}", appPathIdentifier, e.getMessage(), e);
    throw e;
    }
    if (logger.isInfoEnabled()) {
    logger.info(PREFIX + "{} - registration status: {}", appPathIdentifier, httpResponse.getStatusCode());
    }
    return httpResponse.getStatusCode() == Status.NO_CONTENT.getStatusCode();
    }

    这里小小吐槽一下,logger.info 竟然 用了 isInfoEnabled() 判断,有些多余,这个原则是为了 减少 字符串 + 操作可能带来多余的性能复合,但现在用了通配符 {} ,在logger.info 的实现中已经使用 isInfoEnabled 方法来避免了。但是源码里经常这样写,可能是为了保险?

    往下追踪eurekaTransport.registrationClient.register 方法可以发现,底层是使用 Jersey 实现了自己的eurekahttp 封装,jersey (一个 Java restful 框架) 还没有使用过,有空去研究一下。

    往上追踪呢发现 register 方法是在一个 InstanceInfoReplicator类 ,继承了runnale 是一个定时任务,二这个定时任务在@inject DiscoveryClient 的时候就执行了initScheduledTasks() 方法,在这个方法内开启了这个定时任务。

  • renew 续约 :client 每30s 发生一次http请求,及上面提到的定时任务,来告诉server端,我依旧存活着。renew() 方法 与 register() 类似,都在 discoveryclient类中。服务端的续约接口自,eureka-core中的 com.netflix.eureka包下的InstanceResource类下,接口方法为renewLease(),追踪源码可以看到 心跳间隔是30s,剔除服务心跳超时时间是90s

    可以通过设置参数来改变时间间隔,官方建议是谨慎改变这个时间

    1
    2
    eureka.instance.leaseRenewalIntervalInSeconds
    eureka.instance.leaseExpirationDurationInSeconds
  • getRegistery 得到服务 : client 获得server端的 实例信息,同上,有一个fetchRegistry() 和 getApplications() 的方法,客户端想要获得应用可以使用getApplication方法,会从本地缓存中获得应用,本地缓存使用,AtomicReference localRegionApps 来保存对象,保证线程安全。大量使用了这个。而 fetchRegistry() 方法则是 重新获得server端 register信息,并且更新本地缓存和远端服务端中该服务的状态。

  • cancel 服务下线 : 类似上面,使用的unregister() 方法。在@PreDestroy shutdown方法中调用,则是在服务关闭或者意外崩溃时,调用注销方法,包括关闭定时任务,更改自身服务状态,再向server端发送最后一次http请求。

  • replicate:这里我理解为实例信息在服务端间的同步,在eureka-core 中PeerEurekaNodes 类有写,每隔10分钟更新集群节点,每隔一分钟统计最近一分钟内所有Client的续约次数,也就是接收到的心跳次数,以此来作为是否触发服务信息回收的依据之一,EurekaServer每隔60S执行一次服务信息的回收,每隔30S执行一次,更新只读响应缓存,具体代码分析参考下面的文章。

    eureka-server peer同步: https://blog.csdn.net/qq_27529917/article/details/80934523

eureka 的相关注意事项和问题

  • Eureka的几处缓存 (三处缓存,一处延时)

    服务启动后最长可能需要2分钟时间才能被其它服务感知到,

    1. eureka-server http缓存,server端相应getApplication 从缓存中拿取

      eureka-server 的缓存:

      对于client的http请求,server端利用两个map做缓存,一个是readWriteCacheMap 一个是readOnlyCacheMap ,先请求readOnlyCacheMap ,若数据与 readWriteCacheMap ,则同步readWriteCacheMap 的数据,若readWriteCacheMap 还是没有,则直接从内存 registry 中获取,readOnlyCacheMap 缓存30s 定时器更新,readWriteCacheMap 缓存180s 定时器更新

      源码分析参考: https://www.jianshu.com/p/ae4f0c8b8135

    2. eureka-client 对获取到的服务(30s更新)做了本地缓存,默认从本地缓存中读取 ,

    3. 负载均衡的ribbon 30s缓存,缓存 eureka-client 服务列表

    4. 启动延迟注册,需要等30s 后,发起心跳请求才注册

  • 服务注册信息不会被二次传播

    如果Eureka A的peer指向了B, B的peer指向了C,那么当服务向A注册时,B中会有该服务的注册信息,但是C中没有。需要全部写在 peer 里

  • 多网卡环境下的IP选择问题

    如果服务部署的机器上安装了多块网卡,它们分别对应IP地址A, B, C,此时:
    Eureka会选择IP合法(标准ipv4地址)、索引值最小(eth0, eth1中eth0优先)且不在忽略列表中(可在application.properites中配置忽略哪些网卡)的网卡地址作为服务IP。
    这个坑的详细分析见:http://blog.csdn.net/neosmith/article/details/53126924

  • Eureka 的自我保护模式

    如果有任何时间,Eureka Serve接收到的续约低于为该值配置的百分比(默认为15分钟内低于85%),则服务器开启自我保护模式,即不再剔除注册列表的信息。

以上问题的答案,在我参考的文章底部有,也是常见的一些问题。

https://blog.csdn.net/fanhenghui/article/details/77008733

https://blog.csdn.net/forezp/article/details/73017664

生产上的eureka配置(仅参考)

server端配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
## 中小规模下,自我保护模式坑比好处多,所以关闭它
eureka.server.enableSelfPreservation=false
## 心跳阈值计算周期,如果开启自我保护模式,可以改一下这个配置
## eureka.server.renewalThresholdUpdateIntervalMs=120000

## 主动失效检测间隔,配置成5秒
eureka.server.evictionIntervalTimerInMs=5000

## 心跳间隔,5秒
eureka.instance.leaseRenewalIntervalInSeconds=5
## 没有心跳的淘汰时间,10秒
eureka.instance.leaseExpirationDurationInSeconds=10

## 禁用readOnlyCacheMap
eureka.server. useReadOnlyResponseCache=false

client 端配置

1
2
3
4
5
6
7
8
9
## 心跳间隔,5秒
eureka.instance.leaseRenewalIntervalInSeconds=5
## 没有心跳的淘汰时间,10秒
eureka.instance.leaseExpirationDurationInSeconds=10

# 定时刷新本地缓存时间
eureka.client.registryFetchIntervalSeconds=5
# ribbon缓存时间
ribbon.ServerListRefreshInterval=2000

主要针对缓存时间的配置,心跳缩短的弊端就是,在自我保护机制阈值的计算时候,按照每分钟计算,如果时间缩短为一倍,失败时候就会多一倍的计算,需要调整阈值,根据实际场景调试等,还有就是server的缓存机制太多层,详情看以下博客。

https://www.jianshu.com/p/6ac4810f6f42

坚持原创技术分享,您的支持将鼓励我继续创作!