干货分享:一文让你深入了解 Eureka
bigegpt 2024-10-20 04:27 2 浏览
前言
现在流行的微服务体系结构正在改变我们构建应用程序的方式,从单一的单体服务转变为越来越小的可单独部署的服务(称为微服务),共同构成了我们的应用程序。
当进行一个业务时不可避免就会存在多个服务之间调用,假如一个服务 A 要访问在另一台服务器部署的服务 B,那么前提是服务 A 要知道服务 B 所在机器的 IP 地址和服务对应的端口,最简单的方式就是让服务 A 自己去维护一份服务 B 的配置(包含 IP 地址和端口等信息)。
但是这种方式有几个明显的缺点:随着我们调用服务数量的增加,配置文件该如何维护;缺乏灵活性,如果服务 B 改变 IP 地址或者端口,服务 A 也要修改相应的文件配置;还有一个就是进行服务的动态扩容或缩小不方便。
一个比较好的解决方案就是 服务发现(Service Discovery)。它抽象出来了一个注册中心,当一个新的服务上线时,它会将自己的 IP 和端口注册到注册中心去,会对注册的服务进行定期的心跳检测,当发现服务状态异常时将其从注册中心剔除下线。服务 A 只要从注册中心中获取服务 B 的信息即可,即使当服务 B 的 IP 或者端口变更了,服务 A 也无需修改,从一定程度上解耦了服务。
服务发现目前业界有很多开源的实现,比如 apache 的 zookeeper、 Netflix 的 eureka、hashicorp 的 consul、 CoreOS 的 etcd。
Eureka 是什么
Eureka 在 github 上对其的定义为
Eureka is a REST (Representational State Transfer) based service that is primarily used in the AWS cloud for locating services for the purpose of load balancing and failover of middle-tier servers.
At Netflix, Eureka is used for the following purposes apart from playing a critical part in mid-tier load balancing.
Eureka 是由 Netflix 公司开源,采用的是 Client / Server 模式进行设计,基于 http 协议和使用 Restful Api 开发的服务注册与发现组件,提供了完整的服务注册和服务发现,可以和 Spring Cloud 无缝集成。
其中 Server 端扮演着服务注册中心的角色,主要是为 Client 端提供服务注册和发现等功能,维护着 Client 端的服务注册信息,同时定期心跳检测已注册的服务当不可用时将服务剔除下线,Client 端可以通过 Server 端获取自身所依赖服务的注册信息,从而完成服务间的调用。
遗憾的是从其官方的 github wiki 可以发现,2.0 版本已经不再开源。但是不影响我们对其进行深入了解,毕竟服务注册、服务发现相对来说还是比较基础和通用的,其它开源实现框架的思想也是相通的。
服务注册中心(Eureka Server)
我们在项目中引入 Eureka Server 的相关依赖,然后在启动类加上注解 @EnableEurekaServer,就可以将其作为注册中心,启动服务后访问页面如下:
我们继续添加两个模块 service-provider,service-consumer,然后在启动类加上注解 @EnableEurekaClient 并指定注册中心地址为我们刚刚启动的 Eureka Server,再次访问可以看到两个服务都已经注册进来了。
Demo 仓库地址:https://github.com/mghio/depth-in-springcloud
可以看到 Eureka 的使用非常简单,只需要添加几个注解和配置就实现了服务注册和服务发现,接下来我们看看它是如何实现这些功能的。
服务注册(Register)
注册中心提供了服务注册接口,用于当有新的服务启动后进行调用来实现服务注册,或者心跳检测到服务状态异常时,变更对应服务的状态。服务注册就是发送一个 POST 请求带上当前实例信息到类 ApplicationResource 的 addInstance 方法进行服务注册。
可以看到方法调用了类 PeerAwareInstanceRegistryImpl 的 register 方法,该方法主要分为两步:
- 调用父类 AbstractInstanceRegistry 的 register 方法把当前服务注册到注册中心
- 调用 replicateToPeers 方法使用异步的方式向其它的 Eureka Server 节点同步服务注册信息
服务注册信息保存在一个嵌套的 map 中,它的结构如下:
第一层 map 的 key 是应用名称(对应 Demo 里的 SERVICE-PROVIDER),第二层 map 的 key 是应用对应的实例名称(对应 Demo 里的 mghio-mbp:service-provider:9999),一个应用可以有多个实例,主要调用流程如下图所示:
服务续约(Renew)
服务续约会由服务提供者(比如 Demo 中的 service-provider)定期调用,类似于心跳,用来告知注册中心 Eureka Server 自己的状态,避免被 Eureka Server 认为服务时效将其剔除下线。服务续约就是发送一个 PUT 请求带上当前实例信息到类 InstanceResource 的 renewLease 方法进行服务续约操作。
进入到 PeerAwareInstanceRegistryImpl 的 renew 方法可以看到,服务续约步骤大体上和服务注册一致,先更新当前 Eureka Server 节点的状态,服务续约成功后再用异步的方式同步状态到其它 Eureka Server 节上,主要调用流程如下图所示:
服务下线(Cancel)
当服务提供者(比如 Demo 中的 service-provider)停止服务时,会发送请求告知注册中心 Eureka Server 进行服务剔除下线操作,防止服务消费者从注册中心调用到不存在的服务。服务下线就是发送一个 DELETE 请求带上当前实例信息到类 InstanceResource 的 cancelLease 方法进行服务剔除下线操作。
进入到 PeerAwareInstanceRegistryImpl 的 cancel 方法可以看到,服务续约步骤大体上和服务注册一致,先在当前 Eureka Server 节点剔除下线该服务,服务下线成功后再用异步的方式同步状态到其它 Eureka Server 节上,主要调用流程如下图所示:
服务剔除(Eviction)
服务剔除是注册中心 Eureka Server 在启动时就启动一个守护线程 evictionTimer 来定期(默认为 60 秒)执行检测服务的,判断标准就是超过一定时间没有进行 Renew 的服务,默认的失效时间是 90 秒,也就是说当一个已注册的服务在 90 秒内没有向注册中心 Eureka Server 进行服务续约(Renew),就会被从注册中心剔除下线。
失效时间可以通过配置 eureka.instance.leaseExpirationDurationInSeconds 进行修改,定期执行检测服务可以通过配置 eureka.server.evictionIntervalTimerInMs 进行修改,主要调用流程如下图所示:
服务提供者(Service Provider)
对于服务提供方(比如 Demo 中的 service-provider 服务)来说,主要有三大类操作,分别为 服务注册(Register)、服务续约(Renew)、服务下线(Cancel),接下来看看这三个操作是如何实现的。
服务注册(Register)
一个服务要对外提供服务,首先要在注册中心 Eureka Server 进行服务相关信息注册,能进行这一步的前提是你要配置 eureka.client.register-with-eureka=true,这个默认值为 true,注册中心不需要把自己注册到注册中心去,把这个配置设为 false,这个调用比较简单,主要调用流程如下图所示:
服务续约(Renew)
服务续约是由服务提供者方定期(默认为 30 秒)发起心跳的,主要是用来告知注册中心 Eureka Server 自己状态是正常的还活着,可以通过配置 eureka.instance.lease-renewal-interval-in-seconds 来修改,当然服务续约的前提是要配置 eureka.client.register-with-eureka=true,将该服务注册到注册中心中去,主要调用流程如下图所示:
服务下线(Cancel)
当服务提供者方服务停止时,要发送 DELETE 请求告知注册中心 Eureka Server 自己已经下线,好让注册中心将自己剔除下线,防止服务消费方从注册中心获取到不可用的服务。这个过程实现比较简单,在类 DiscoveryClient 的 shutdown 方法加上注解 @PreDestroy,当服务停止时会自动触发服务剔除下线,执行服务下线逻辑,主要调用流程如下图所示:
服务消费者(Service Consumer)
这里的服务消费者如果不需要被其它服务调用的话,其实只会涉及到两个操作,分别是从注册中心 获取服务列表(Fetch) 和 更新服务列表(Update)。如果同时也需要注册到注册中心对外提供服务的话,那么剩下的过程和上文提到的服务提供者是一致的,这里不再阐述,接下来看看这两个操作是如何实现的。
获取服务列表(Fetch)
服务消费者方启动之后首先肯定是要先从注册中心 Eureka Server 获取到可用的服务列表同时本地也会缓存一份。这个获取服务列表的操作是在服务启动后 DiscoverClient 类实例化的时候执行的。
可以看出,能发生这个获取服务列表的操作前提是要保证配置了 eureka.client.fetch-registry=true,该配置的默认值为 true,主要调用流程如下图所示:
更新服务列表(Update)
由上面的 获取服务列表(Fetch) 操作过程可知,本地也会缓存一份,所以这里需要定期的去到注册中心 Eureka Server 获取服务的最新配置,然后比较更新本地缓存,这个更新的间隔时间可以通过配置 eureka.client.registry-fetch-interval-seconds 修改,默认为 30 秒,能进行这一步更新服务列表的前提是你要配置 eureka.client.register-with-eureka=true,这个默认值为 true。主要调用流程如下图所示:
总结
工作中项目使用的是 Spring Cloud 技术栈,它有一套非常完善的开源代码来整合 Eureka,使用起来非常方便。之前都是直接加注解和修改几个配置属性一气呵成的,没有深入了解过源码实现,本文主要是阐述了服务注册、服务发现等相关过程和实现方式,对 Eureka 服务发现组件有了更近一步的了解。
相关推荐
- Java 泛型大揭秘:类型参数、通配符与最佳实践
-
引言在编程世界中,代码的可重用性和可维护性是至关重要的。为了实现这些目标,Java5引入了一种名为泛型(Generics)的强大功能。本文将详细介绍Java泛型的概念、优势和局限性,以及如何在...
- K8s 的标签与选择器:流畅运维的秘诀
-
在Kubernetes的世界里,**标签(Label)和选择器(Selector)**并不是最炫酷的技术,但却是贯穿整个集群管理与运维流程的核心机制。正是它们让复杂的资源调度、查询、自动化运维变得...
- 哈希Hash算法:原理、应用(哈希算法 知乎)
-
原作者:Linux教程,原文地址:「链接」什么是哈希算法?哈希算法(HashAlgorithm),又称为散列算法或杂凑算法,是一种将任意长度的数据输入转换为固定长度输出值的数学函数。其输出结果通常被...
- C#学习:基于LLM的简历评估程序(c# 简历)
-
前言在pocketflow的例子中看到了一个基于LLM的简历评估程序的例子,感觉还挺好玩的,为了练习一下C#,我最近使用C#重写了一个。准备不同的简历:image-20250528183949844查...
- 55顺位,砍41+14+3!季后赛也成得分王,难道他也是一名球星?
-
雷霆队最不可思议的新星:一个55号秀的疯狂逆袭!你是不是也觉得NBA最底层的55号秀,就只能当饮水机管理员?今年的55号秀阿龙·威金斯恐怕要打破你的认知了!常规赛阶段,这位二轮秀就像开了窍的天才,直接...
- 5分钟读懂C#字典对象(c# 字典获取值)
-
什么是字典对象在C#中,使用Dictionary类来管理由键值对组成的集合,这类集合被称为字典。字典最大的特点就是能够根据键来快速查找集合中的值,其键的定义不能重复,具有唯一性,相当于数组索引值,字典...
- c#窗体传值(c# 跨窗体传递数据)
-
在WinForm编程中我们经常需要进行俩个窗体间的传值。下面我给出了两种方法,来实现传值一、在输入数据的界面中定义一个属性,供接受数据的窗体使用1、子窗体usingSystem;usingSyst...
- C#入门篇章—委托(c#委托的理解)
-
C#委托1.委托的定义和使用委托的作用:如果要把方法作为函数来进行传递的话,就要用到委托。委托是一个类型,这个类型可以赋值一个方法的引用。C#的委托通过delegate关键字来声明。声明委托的...
- C#.NET in、out、ref详解(c#.net framework)
-
简介在C#中,in、ref和out是用于修改方法参数传递方式的关键字,它们决定了参数是按值传递还是按引用传递,以及参数是否必须在传递前初始化。基本语义对比修饰符传递方式可读写性必须初始化调用...
- C#广义表(广义表headtail)
-
在C#中,广义表(GeneralizedList)是一种特殊的数据结构,它是线性表的推广。广义表可以包含单个元素(称为原子),也可以包含另一个广义表(称为子表)。以下是一个简单的C#广义表示例代...
- 「C#.NET 拾遗补漏」04:你必须知道的反射
-
阅读本文大概需要3分钟。通常,反射用于动态获取对象的类型、属性和方法等信息。今天带你玩转反射,来汇总一下反射的各种常见操作,捡漏看看有没有你不知道的。获取类型的成员Type类的GetMembe...
- C#启动外部程序的问题(c#怎么启动)
-
IT&OT的深度融合是智能制造的基石。本公众号将聚焦于PLC编程与上位机开发。除理论知识外,也会结合我们团队在开发过程中遇到的具体问题介绍一些项目经验。在使用C#开发上位机时,有时会需要启动外部的一些...
- 全网最狠C#面试拷问:这20道题没答出来,别说你懂.NET!
-
在竞争激烈的C#开发岗位求职过程中,面试是必经的一道关卡。而一场高质量的面试,不仅能筛选出真正掌握C#和.NET技术精髓的人才,也能让求职者对自身技术水平有更清晰的认知。今天,就为大家精心准备了20道...
- C#匿名方法(c#匿名方法与匿名类)
-
C#中的匿名方法是一种没有名称只有主体的方法,它提供了一种传递代码块作为委托参数的技术。以下是关于C#匿名方法的一些重要特点和用法:特点省略参数列表:使用匿名方法可省略参数列表,这意味着匿名方法...
- C# Windows窗体(.Net Framework)知识总结
-
Windows窗体可大致分为Form窗体和MDI窗体,Form窗体没什么好细说的,知识点总结都在思维导图里面了,下文将围绕MDI窗体来讲述。MDI(MultipleDocumentInterfac...
- 一周热门
- 最近发表
- 标签列表
-
- mybatiscollection (79)
- mqtt服务器 (88)
- keyerror (78)
- c#map (65)
- resize函数 (64)
- xftp6 (83)
- bt搜索 (75)
- c#var (76)
- mybatis大于等于 (64)
- xcode-select (66)
- mysql授权 (74)
- 下载测试 (70)
- linuxlink (65)
- pythonwget (67)
- androidinclude (65)
- logstashinput (65)
- hadoop端口 (65)
- vue阻止冒泡 (67)
- oracle时间戳转换日期 (64)
- jquery跨域 (68)
- php写入文件 (73)
- kafkatools (66)
- mysql导出数据库 (66)
- jquery鼠标移入移出 (71)
- 取小数点后两位的函数 (73)