前面的 3 章我们都在说 Eureka 是如何使用的,但是在面试中很少有人会问到你具体的如何配置和使用,而是深入原理问你 Eureka 是如何实现的?那么本文就结合前面的一些章节,来一起讨论讨论 Eureka 的实现原理与机制。
先来张图:
上面这张图,可能大家都看过。在实际的应用中,Eureka 是以集群的方式存在的。然后 Service Provider:服务提供方,将自身服务注册到 Eureka,从而使服务消费方能够找到。Service Consumer:服务消费方,从 Eureka 获取注册服务列表,从而能够消费服务。Eureka Server:提供服务注册和发现。消费方会缓存一些服务提供方,消费方获得服务列表后,直接调用服务提供方,而不是通过 Eureka Server 去调用。服务提供方和 Eureka Server 会实时通过心跳来保活,保证每个服务的可用性。
注意,上面只是一个逻辑图。实际的使用中,一个服务可能即是消费方又是提供方,同时可能还具备服务的发现功能。
下面再来看第二张图:
上面的这张图显示,Eureka 集群之间会进行数据同步。服务提供方会进行Register(服务注册)、Renew(服务续约)、Cancel(服务下线)等操作。Service Consumer 会向 Eureka Server 获取注册服务列表,并通过 HTTP Rest 远程调用的方式消费服务。
看懂这几张图后,我们可以深入到 Eureka 源码中去。Eureka 其实就是一个简单的 Servlet 应用,它使用 Jersey 框架实现自身的 RESTful HTTP接口,peer之间的同步与服务的注册全部通过 HTTP 协议实现,定时任务(发送心跳、定时清理过期服务、节点同步等)通过 JDK 自带的 Timer 实现,内存缓存使用 Google 的 guava 包实现。
大家可以自己到 github 上下载 Eureka 源码,自己编译测试测试。看我下载的其中一个版本的核心代码结构如下:
eureka-core 模块包含了功能的核心实现:
- com.netflix.eureka.cluster – 与peer节点复制(replication)相关的功能
- com.netflix.eureka.lease – 即”租约”, 用来控制注册信息的生命周期(添加、清除、续约)
- com.netflix.eureka.registry – 存储、查询服务注册信息
- com.netflix.eureka.resources – RESTful风格中的”R”, 即资源。相当于SpringMVC中的Controller
- com.netflix.eureka.transport – 发送HTTP请求的客户端,如发送心跳
- com.netflix.eureka.aws – 与amazon AWS服务相关的类
通过 Octotree 查看 github 上的源码结构,Eureka 的源代码并没有多少。
由于是 Servlet 应用,所以 Eureka 需要通过 Servlet 的相关监听器 ServletContextListener 嵌入到 Servlet 的生命周期中。EurekaServerBootStrap 类实现了该接口,在 servlet 标准的 contextInitialized() 方法中完成了初始化工作:
@Override public void contextInitialized(ServletContextEvent event) { try { // 读取配置信息 initEurekaEnvironment(); // 初始化Eureka Client(用来与其它节点进行同步) // 初始化server initEurekaServerContext(); ServletContext sc = event.getServletContext(); sc.setAttribute(EurekaServerContext.class.getName(), serverContext); } catch (Throwable e) { logger.error("Cannot bootstrap eureka server :", e); throw new RuntimeException("Cannot bootstrap eureka server :", e); } }
由于讲解源码需要花费大量的篇幅,我就不在细讲。后面整个 Spring Cloud 教程做完之后,如果有时间我在单独来讲。这里大家找到入口后,先尝试着自己去分析吧。
Register(服务注册)
服务注册一般是发生在服务启动的时候,后面如果服务自身检测认为 Down,也会来更新服务状态的。
注册的服务列表保存在一个嵌套的 hash map中:
private final ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>> registry = new ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>>();
- 第一层hash map的key是app name,也就是应用名字
- 第二层hash map的key是instance name,也就是实例名字
ApplicationResource 类接收 Http 服务请求,调用 PeerAwareInstanceRegistryImpl 的 register 方法。
PeerAwareInstanceRegistryImpl 完成服务注册后,调用 replicateToPeers 向其它 Eureka Server 节点(Peer)做状态同步(异步操作)。
Renew(服务续约)
Renew(服务续约)操作由 Service Provider 定期调用,类似于 heartbeat。主要是用来告诉 Eureka Server Service Provider 还活着,避免服务被剔除掉。
服务续约的过程与服务注册的过程基本一致:首先更新自身状态,再同步到其它Peer。
Cancel(服务下线)
Cancel(服务下线)一般在 Service Provider shut down 的时候调用,用来把自身的服务从 Eureka Server 中删除,以防客户端调用不存在的服务。
同样的先从主节点下线,然后向其它节点异步操作做状态同步。
说完这三个主要的流程后,相信大家对 Eureka 有了更深的认识。其实 Eureka 还有很多其它的知识点,我们没讲到。如某一个节点故障将发生哪些操作?再比如消费方的缓存更新策略等。这些限于时间关系,我们在整个 Spring Cloud 案例写完了后,再回过头来整理。
参考资料
- Spring Cloud Netflix Eureka源码导读与原理分析
- 深度剖析服务发现组件Netflix Eureka
: » Spring Cloud 教程第四章 Eureka 的原理与实现机制
原创文章,作者:carmelaweatherly,如若转载,请注明出处:https://blog.ytso.com/251823.html