带你读《极简Spring Cloud实战》之二:服务发现:Eureka

简介: 本书从实战、进阶、全面配置三个层次展开介绍,分为三篇。基础服务篇介绍构建一个核心微服务架构不可缺少的部分。任务与消息篇则着重介绍Spring Cloud 针对消息、任务、调用依赖等方面的支持方案。微服务实战篇基于Spring Cloud+Docker构建一个精简而又五脏俱全的小项目。

点击查看第一章
点击查看第三章
第2章

服务发现:Eureka

服务发现是微服务架构中的一个重要概念。试想当系统服务之间的依赖越来越多,A服务可能需要调用B、C、D等服务,同时被调用方也就是服务提供方可能为了保证自身高可用,还需要同时以集群的模式部署B1、B2、C1、C2等,想象一下A的配置文件该有多复杂。将服务提供方的地址写死在配置文件中,那么服务提供方如果横向扩展增加实例,是不是还需要修改作为服务调用方的A的配置文件?这时我们就迫切需要一种服务发现的机制。所有的服务提供方启动时向注册中心报告自身的信息,包括自己的地址、端口,以及提供哪些服务等相关信息。当服务调用方需要调用服务时,只需要问注册中心是谁提供了相关的服务,注册中心返回哪些提供方提供了这些服务,调用方就可以自己根据注册中心返回的信息去请求了。Eureka就提供了这样一种能力,同时自身作为注册中心的同时也提供了高可用的支持,支持集群部署时各个节点之间的注册数据同步复制。
Eureka是Netflix开源的一款提供服务注册和发现的产品,提供了完整的服务注册和服务发现实现,也是Spring Cloud体系中最重要、最核心的组件之一。
通俗讲,Eureka就是一个服务中心,将所有可以提供的服务都注册到它这里来管理,其他各调用者需要的时候去注册中心获取,然后服务调用方再向服务提供方发起调用,避免了服务之间的直接调用,方便后续的水平扩展、故障转移等。
所以,服务中心这么重要的组件一旦宕机将会影响全部服务,因此需要搭建Eureka集群来保持高可用性,建议生产中最少配备两台。随着系统流量的不断增加,需要根据情况来扩展某个服务,Eureka内部提供均衡负载的功能,只需要增加相应的服务端实例即可。那么在系统的运行期间某个实例宕机怎么办?Eureka提供心跳检测机制,如果某个实例在规定的时间内没有进行通信则会被自动剔除掉,避免了某个实例挂掉而影响服务。
因此,使用Eureka就自动具有了注册中心、负载均衡、故障转移的功能。
它主要包括两个组件。

  • Eureka Client:一个Java客户端,用于简化与Eureka Server的交互(通常就是微服务中的客户端和服务端)。
  • Eureka Server:提供服务注册和发现的能力(通常就是微服务中的注册中心)。

各个微服务启动时,会通过Eureka Client向Eureka Server注册自己,Eureka Server会存储该服务的信息,如图2-1所示。
image.png

也就是说,每个微服务的客户端和服务端都会注册到Eureka Server,这就衍生出了微服务相互识别的话题。

  • 同步:每个Eureka Server同时是Eureka Client(逻辑上的),多个Eureka Server之间通过复制的方式完成服务注册表的同步,从而实现Eureka的高可用。
  • 识别:Eureka Client会在本地缓存Eureka Server中的信息。
    即使所有Eureka Server节点都宕掉,服务消费方仍可使用本地缓存中的信息找到服务提供方。
  • 续约:微服务会周期性(默认30s)地向Eureka Server发送心跳以续约(Renew)自己的信息(类似于heartbeat机制)。
  • 续期:Eureka Server会定期(默认60s)执行一次失效服务检测功能,它会检查超过一定时间(默认90s)没有续约的微服务,发现则会注销该微服务节点。
    当一个注册器客户端通过Eureka进行注册时,它会带上一些描述自己情况的元数据,如地址、端口、健康指示器地址、主页等。Eureka会接收每一个服务实例发送的心跳包。如果心跳包超过配置的间隔时间,则这个服务实例就会被移除。

2.1 使用Eureka

接下来尝试一个Eureka的示例。
新建一个Maven项目,在pom.xml中添加对Eureka服务端的依赖:

<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>


新建EurekaServerApplication.java,添加@EnableEurekaServer注解:
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {

public static void main(String[] args) {
    SpringApplication.run(EurekaServerApplication.class, args);
}

}
启动程序后,打开http://localhost:8080/ 就可以看到如图2-2所示的监控页面,图中展示了向Eureka注册的所有客户端。
image.png

2.1.1 Eureka服务提供方

启动一个服务提供方并注册到Eureka。新建一个项目并在pom.xml中添加依赖:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

新建EurekaProviderApplication.java,并对外暴露一个sayHello的HTTP接口。
@SpringBootApplication
@EnableEurekaClient
@RestController
public class EurekaProviderApplication {

@RequestMapping("/sayHello")
public  String sayHello(String name){
    return "hello "+name;
}
public static void main(String[] args) {
    new SpringApplicationBuilder(EurekaProviderApplication.class).web(true).run(args);
}

}
在application.yml配置Eureka服务端地址以及自身服务名称:
eureka:

client:
    serviceUrl:
        defaultZone: http://localhost:8761/eureka/ #在注册中心配置Eureka地址

spring:

application:
    name: myprovider #自身名字

启动EurekaProviderApplication后,再去看Eureka的监控页面,应该能看到如图2-3所示信息,表明服务已经注册到Eureka。
image.png
还有一种方法就是直接使用原生的com.netflix.discovery.EurekaClient(对应Spring Cloud的DiscoveryClient)。通常,尽量不要直接使用原始的Netflix的Eureka Client,因为Spring已经对其进行封装抽象,应尽可能使用DiscoveryClient。

2.1.2 Eureka服务调用方

一旦在应用中使用了@EnableDiscoveryClient或者@EnableEurekaClient,就可以从Eureka Server中使用服务发现功能。
利用如下代码,通过DiscoveryClient可以从Eureka服务端获得所有提供myprovider服务的实例列表,并根据获得的ServiceInstance对象获取每个提供方的相关信息,如端口IP等,从中选取第一个提供方,通过Spring的RestTemplate发起HTTP请求进行调用。对于获取到的提供方集合,我们根据什么规则去选取哪个提供方?能否做到负载均衡呢?这将在第5章详细介绍。
@SpringBootApplication
@EnableDiscoveryClient
@Slf4j
@RestController
public class EurekaClientApplication {

@Bean
public RestTemplate restTemplate(){
    return new RestTemplate();
}
@Autowired
private DiscoveryClient client;
@Autowired
private RestTemplate restTemplate;
@RequestMapping(value = "/sayHello" ,method = RequestMethod.GET)
public String sayHello(String name) {
    List<ServiceInstance> instances = client.getInstances("myprovider");
    if (!instances.isEmpty()) {
        ServiceInstance instance = instances.get(0);
        log.info(instance.getUri().toString());
        String result=restTemplate.getForObject(instance.getUri().toString()+ "/sayHello?name="+name,String.class);
        return  result;
    }
    return  "failed";
}
public static void main(String[] args) {
    SpringApplication.run(EurekaClientApplication.class, args);
}

}
此时,我们通过HTTP请求调用/sayHello接口,则可以看到服务调用方从Eureka获取服务提供方信息,并进行调用的日志信息了。
不要在@PostConstruct方法以及@Scheduled中(或者任何ApplicationContext还没初始完成的地方)使用EurekaClient。其需要等待SmartLifecycle(phase=0)初始化完成才可以。

2.2 进阶场景

尝试过Eureka的基本使用场景后,虽然功能能够实现,但是落实到具体的企业级应用场景时,必然会有许多需要自定义的配置以及一些生产环境可能需要用到的参数。下面列出一些场景供读者参考。
(1)Eureka的健康检查
默认情况下,Eureka通过客户端心跳包来检测客户端状态,并不是通过spring-boot-actuator模块的/health端点来实现的。默认的心跳实现方式可以有效地检查Eureka客户端进程是否正常运作,但是无法保证客户端应用能够正常提供服务。由于大多数微服务应用都会有一些外部资源依赖,比如数据库、Redis缓存等,如果应用与这些外部资源无法连通时,实际上已经不能提供正常的对外服务了,但因为客户端心跳依然在运行,所以它还是会被服务消费者调用,而这样的调用实际上并不能获得预期的效果。当然,我们可以开启Eureka的健康检查,这样应用状态就可以同步给Eureka了。在application.yml中添加如下配置即可:
eureka:

client:
    healthcheck:
        enabled:true

Eureka中的实例一共有如下几种状态:UP、DOWN、STARTING、OUT_OF_SERVICE、UNKNOWN。如果需要更多的健康检查控制,可以实现com.netflix.appinfo.Health-CheckHandler接口,根据自己的场景进行操作。
eureka.client.healthcheck.enabled=true只能在application.yml中设置,如果在bootstrap.yml中设置,会导致Eureka注册为UNKNOWN的状态。
(2)自我保护模式
Eureka在设计时,认为分布式环境的网络是不可靠的,可能会因网络问题导致Eureka Server没有收到实例的心跳,但是这并不能说明实例宕了,所以Eureka Server默认会打开保护模式,它主要是网络分区场景下的一种保护。
一旦进入保护模式,Eureka Server将会尝试保护其服务注册表中的信息,不再删除里面的数据(即不会注销任何微服务)。在这种机制下,它仍然鼓励客户端再去尝试调用这个所谓down状态的实例,当网络故障恢复后,该Eureka Server节点会自动退出自我保护模式。若确实调用失败,熔断器就派上用场了。
关于熔断器,第6章会详细介绍并演示。
通过修改注册中心的配置文件application.yml,即可打开或关闭注册中心的自我保护模式:
eureka:

server:
    enable-self-presaervation: false           #关闭自我保护模式(默认为打开)

综上,自我保护模式是一种应对网络异常的安全保护措施。它的理念是宁可同时保留所有实例(健康实例和不健康实例都会保留),也不盲目注销任何健康的实例。使用自我保护模式,可以让Eureka集群更加健壮、稳定。
(3)踢出宕机节点
自我保护模式打开时,已关停节点是一直显示在Eureka首页的。
关闭自我保护模式后,由于其默认的心跳周期比较长等原因,要过一会儿才会发现已关停节点被自动踢出了。若想尽快踢出,就只能修改默认的心跳周期参数了。
注册中心的配置文件application.yml需要修改的地方如下所示:
eureka:

server:
    enable-self-preservation: false       # 关闭自我保护模式(默认为打开)
    eviction-interval-timer-in-ms: 1000   # 续期时间,即扫描失效服务的间隔时间
                                            (默认为60*1000ms)

客户端的配置文件application.yml需要修改的地方为:
eureka:

instance:
    lease-renewal-interval-in-seconds: 5   # 心跳时间,即服务续约间隔时间(默认为30s)
    lease-expiration-duration-in-seconds: 15  # 发呆时间,即服务续约到期时间(默认为90s)
client:
    healthcheck:
        enabled: true          #开启健康检查(依赖spring-boot-starter-actuator)

更改Eureka Server的更新频率将打破注册中心的自我保护功能,不建议生产环境自定义这些配置。
(4)注册服务慢的问题
客户端去注册中心默认持续30s,直到实例自身、服务端、客户端各自元数据本地缓存同步完成后服务才可用(至少需要3次心跳周期)。可以通过eureka.instance.leaseRenewalIntervalInSeconds修改这个周期,改善客户端链接到服务的速度。不过,考虑短期的网络波动以及服务续期等情况,在生产环境最好用默认设定。
(5)服务状态显示UNKNOWN
如果在Eureka监控页面发现服务状态显示UNKNOWN,则很大可能是把微服务的eureka.client.healthcheck.enabled属性配置在bootstrap.yml里面的问题。比如,实际测试发现,Eureka首页显示的服务状态,本应是UP(1),却变成大红色的粗体UNKNOWN(1)。
(6)自定义InstanceId
两个相同的服务(端口不同),如果注册时设置的都是${spring.application.name},那么Eureka首页只会看到一个服务名字,而无法区分有几个实例注册上来了。于是,可以自定义生成InstanceId的规则。
Eureka服务名默认如下:
${spring.cloud.client.hostname}:${spring.application.name}:${spring.application.instance_id:${server.port}}
可以在配置文件中通过eureka.intance.intance_id来自定义:
eureka:

instance:
    #修改显示的微服务名为:IP:端口
    instance-id: ${spring.cloud.client.ipAddress}:${server.port}  

(7)自定义Eureka控制台服务的链接
既然微服务显示的名称允许修改,那么其对应的点击链接也是可以修改的。
同样,还是修改微服务的配置文件,如下所示:
eureka:

instance:
    # ip-address: 192.168.6.66  #只有prefer-ip-address=true时才会生效
    prefer-ip-address: true     #设置微服务调用地址为IP优先(默认为false)

Eureka首页显示的微服务调用地址,默认是http://hostName:port/info
而在设置prefer-ip-address=true之后,调用地址会变成http://ip:port/info
这时若再设置ip-address=192.168.6.66,则调用地址会变成http://192.168.6.66:2100/info
(8)健康度指示器
一个Eurake实例的状态页和健康指示器默认为/info和/health,这两个是由Spring Boot Actuator应用提供的访问端点。可以通过以下方式进行修改:
application.yml
eureka:

instance:
    statusPageUrlPath: ${management.context-path}/info
    healthCheckUrlPath: ${management.context-path}/health

这些地址会被用于Eureka对客户端元数据的获取,以及健康检测。
(9)就近原则
用户量比较大或者用户地理位置分布范围很广的项目,一般都会有多个机房。这个时候如果上线服务的话,我们希望一个机房内的服务优先调用同一个机房内的服务,当同一个机房的服务不可用时,再去调用其他机房的服务,以达到减少延时的作用。Eureka有Region和Zone的概念,可以理解为现实中的大区(Region)和机房(Zone)。Eureka Client在启动时需要指定Zone,它会优先请求自己Zone的Eureka Server获取注册列表。同样,Eureka Server在启动时也需要指定Zone。如果没有指定,其会默认使用defaultZone。
(10)高可用配置
Eureka Server也支持运行多实例,并以互相注册的方式(即伙伴机制)来实现高可用的部署,即每一台Eureka都在配置中指定另一个Eureka地址作为伙伴,它在启动时会向伙伴节点获取注册列表。如此一来,Eureka集群新加机器时,就不用担心注册列表的完整性。所以,只需要在Eureka Server里面配置其他可用的serviceUrl,就实现了注册中心的高可用。
我们新建两个Eureka服务端项。第1个服务端项为EurekaServer1项目中的配置文件
/src/main/resources/application.yml。
server:

port: 8989

eureka

serviceUrl: 
    defaultZone: 

第2个服务端项为EurekaServer2项目中的配置文件/src/main/resources/application.yml。
server:

port: 9898

eureka

serviceUrl: 
    defaultZone: 

启动Server1、Server2后,分别访问http://127.0.0.1:8989/eureka/http://127.0.0.1:9898/eureka/ ,发现DS Replicas、General Info模块出现了对方的信息。读者可以自行测试,分别单独向Server1或者Server2进行服务注册时,都会自动同步给另外一个注册中心。
在生产环境中大于两台注册中心的条件下,可以同理将其配置成如图2-4所示的双向环形。
image.png

2.3 小结

本章详细讲解了Eureka组件的工作原理,并结合示例介绍了Eureka的服务提供方和服务调用方的使用步骤。在进阶场景章节中我们深入探究了各个场景下使用Eureka的各种解决方案与自定义场景的配置。下个章开始学习如何使用Config组件来对Eureka以及其他组件进行动态化配置。

相关文章
|
1月前
|
XML Java 测试技术
Spring5入门到实战------17、Spring5新功能 --Nullable注解和函数式注册对象。整合JUnit5单元测试框架
这篇文章介绍了Spring5框架的三个新特性:支持@Nullable注解以明确方法返回、参数和属性值可以为空;引入函数式风格的GenericApplicationContext进行对象注册和管理;以及如何整合JUnit5进行单元测试,同时讨论了JUnit4与JUnit5的整合方法,并提出了关于配置文件加载的疑问。
Spring5入门到实战------17、Spring5新功能 --Nullable注解和函数式注册对象。整合JUnit5单元测试框架
|
22天前
|
Cloud Native Java Nacos
微服务时代的新宠儿!Spring Cloud Nacos实战指南,带你玩转服务发现与配置管理,拥抱云原生潮流!
【8月更文挑战第29天】Spring Cloud Nacos作为微服务架构中的新兴之星,凭借其轻量、高效的特点,迅速成为服务发现、配置管理和治理的首选方案。Nacos(命名和配置服务)由阿里巴巴开源,为云原生应用提供了动态服务发现及配置管理等功能,简化了服务间的调用与依赖管理。本文将指导你通过五个步骤在Spring Boot项目中集成Nacos,实现服务注册、发现及配置动态管理,从而轻松搭建出高效的微服务环境。
93 0
|
7天前
|
负载均衡 Java Nacos
SpringCloud基础1——远程调用、Eureka,Nacos注册中心、Ribbon负载均衡
微服务介绍、SpringCloud、服务拆分和远程调用、Eureka注册中心、Ribbon负载均衡、Nacos注册中心
SpringCloud基础1——远程调用、Eureka,Nacos注册中心、Ribbon负载均衡
|
1月前
|
NoSQL Java Redis
Redis6入门到实战------ 八、Redis与Spring Boot整合
这篇文章详细介绍了如何在Spring Boot项目中整合Redis,包括在`pom.xml`中添加依赖、配置`application.properties`文件、创建配置类以及编写测试类来验证Redis的连接和基本操作。
Redis6入门到实战------ 八、Redis与Spring Boot整合
|
1月前
|
SQL 数据库
Spring5入门到实战------13、使用JdbcTemplate操作数据库(批量增删改)。具体代码+讲解 【下篇】
这篇文章是Spring5框架的实战教程,深入讲解了如何使用JdbcTemplate进行数据库的批量操作,包括批量添加、批量修改和批量删除的具体代码实现和测试过程,并通过完整的项目案例展示了如何在实际开发中应用这些技术。
Spring5入门到实战------13、使用JdbcTemplate操作数据库(批量增删改)。具体代码+讲解 【下篇】
|
1月前
|
XML Java 数据格式
Spring5入门到实战------11、使用XML方式实现AOP切面编程。具体代码+讲解
这篇文章是Spring5框架的AOP切面编程教程,通过XML配置方式,详细讲解了如何创建被增强类和增强类,如何在Spring配置文件中定义切入点和切面,以及如何将增强逻辑应用到具体方法上。文章通过具体的代码示例和测试结果,展示了使用XML配置实现AOP的过程,并强调了虽然注解开发更为便捷,但掌握XML配置也是非常重要的。
Spring5入门到实战------11、使用XML方式实现AOP切面编程。具体代码+讲解
|
22天前
|
Java API UED
【实战秘籍】Spring Boot开发者的福音:掌握网络防抖动,告别无效请求,提升用户体验!
【8月更文挑战第29天】网络防抖动技术能有效处理频繁触发的事件或请求,避免资源浪费,提升系统响应速度与用户体验。本文介绍如何在Spring Boot中实现防抖动,并提供代码示例。通过使用ScheduledExecutorService,可轻松实现延迟执行功能,确保仅在用户停止输入后才触发操作,大幅减少服务器负载。此外,还可利用`@Async`注解简化异步处理逻辑。防抖动是优化应用性能的关键策略,有助于打造高效稳定的软件系统。
31 2
|
1月前
|
XML Java Maven
Spring5入门到实战------16、Spring5新功能 --整合日志框架(Log4j2)
这篇文章是Spring5框架的入门到实战教程,介绍了Spring5的新功能——整合日志框架Log4j2,包括Spring5对日志框架的通用封装、如何在项目中引入Log4j2、编写Log4j2的XML配置文件,并通过测试类展示了如何使用Log4j2进行日志记录。
Spring5入门到实战------16、Spring5新功能 --整合日志框架(Log4j2)
|
1月前
|
XML Java 数据库
Spring5入门到实战------15、事务操作---概念--场景---声明式事务管理---事务参数--注解方式---xml方式
这篇文章是Spring5框架的实战教程,详细介绍了事务的概念、ACID特性、事务操作的场景,并通过实际的银行转账示例,演示了Spring框架中声明式事务管理的实现,包括使用注解和XML配置两种方式,以及如何配置事务参数来控制事务的行为。
Spring5入门到实战------15、事务操作---概念--场景---声明式事务管理---事务参数--注解方式---xml方式
|
1月前
|
XML 数据库 数据格式
Spring5入门到实战------14、完全注解开发形式 ----JdbcTemplate操作数据库(增删改查、批量增删改)。具体代码+讲解 【终结篇】
这篇文章是Spring5框架的实战教程的终结篇,介绍了如何使用注解而非XML配置文件来实现JdbcTemplate的数据库操作,包括增删改查和批量操作,通过创建配置类来注入数据库连接池和JdbcTemplate对象,并展示了完全注解开发形式的项目结构和代码实现。
Spring5入门到实战------14、完全注解开发形式 ----JdbcTemplate操作数据库(增删改查、批量增删改)。具体代码+讲解 【终结篇】