作者:禅与计算机程序设计艺术
1.简介
在 Spring Cloud 的世界里,分布式系统经历了开发、测试、运维三个阶段。而在开发阶段,通常采用集中式配置方式,将所有配置文件统一管理在一台服务器上。随着业务系统的不断扩张,各个微服务模块都需要配置自己的属性值。因此,需要一个分布式配置中心来解决这一问题。Spring Cloud 提供了基于 Netflix Eureka 和 Spring Cloud Config 的分布式配置中心解决方案,本文主要以 Eureka 为例进行分析。
Spring Cloud 是一系列框架的集合,它利用 Spring Boot 技术栈来简化分布式系统的开发。其中,分布式配置中心就是 Spring Cloud 提供的一个用来解决配置文件管理的服务。在 Spring Cloud 中,有一个叫做 Spring Cloud Config 的子项目用来解决这个问题,Spring Cloud Config 可以帮助应用程序从远程 Git 或 Subversion 存储库加载配置数据并集中管理。
本文主要内容:
1.介绍 Spring Cloud 分布式配置中心Eureka
2.简单了解 Spring Cloud Eureka 功能特性
3.通过 Demo 学习 Eureka 的基本用法
4.深入学习 Spring Cloud Eureka 内部机制
5.结合实际案例分析 Eureka 配置中心优缺点及改进方向
5.结论:
1.Eureka 是 Spring Cloud 中的一个独立的配置中心,它具备服务注册和发现等功能。
2.作为一个独立的服务,Eureka 对资源消耗比较小,并且其自身也提供了负载均衡的能力。
3.但 Eureka 缺少权限管理、审计等功能,因此对公司的 IT 安全管控可能存在一定障碍。
4.另外,Eureka 自身不支持动态刷新,如果配置发生变化,需要重启应用才能生效,适用于静态配置。
5.总结:由于 Eureka 自身的限制,使得其只能用于某些特定场景下的配置中心。对于其他类型的分布式系统,比如消息队列、服务发现等,Eureka 并不能很好的发挥作用。
如果要实现真正意义上的配置中心,目前最佳的选择还是开源社区中的 Apollo(携程开源)或 Consul(HashiCorp 开源)。对于个人开发者来说,建议尽量采用 Spring Cloud Config 来代替,虽然它不是一个独立的配置中心,但是集成非常方便。同时,也可以基于 Eureka 自己开发一套权限管理和审计系统。
2.Eureka 简介
Spring Cloud Eureka 是 Spring Cloud 构建的一套基于 RESTful HTTP 服务的服务治理组件。其本质是一个高可用的服务注册中心,能够让客户端发现服务,并根据负载均衡算法,向提供者发送请求。通过配置不同的策略,Eureka 可以实现自动化的失效检测和剔除,从而保证服务的可用性。Eureka 支持多种协议,包括 HTTP、DNS 等。
3.Eureka 功能特性
Eureka 主要具有以下几个方面的特点:
(1)服务注册与发现
Eureka 是分布式协调服务,它负责服务的注册与查找。当一个微服务启动时,会注册到 Eureka Server 上,并保持心跳,表明自身可用。其它微服务通过调用服务名,请求相应的服务接口,并获取服务端响应结果。
(2)健康检查
Eureka 通过组合不同监测方式,可以实现对服务节点的健康检查。当服务节点出现故障时,它立即通知 Eureka Server 注销,并且剔除该节点。通过定义好的策略,Eureka 可以识别出服务节点故障之后的恢复时间,然后调整负载均衡策略,避免因节点过载而导致的服务雪崩效应。
(3)负载均衡
Eureka 使用基于客户端的负载均衡算法,通过向注册到 Eureka 的服务提供者发送请求,可以实现服务的动态分配。当某个服务节点不可用时,Eureka 将路由请求转移到另一台可用的节点上。
(4)容错与冗余
当多个服务节点之间存在网络波动或者连接失败时,Eureka 会自动屏蔽故障节点,保障服务的可用性。此外,Eureka 还会提供容错机制,可以通过部署多个 Eureka Server 集群来提升可用性。
(5)客户端容错处理
Eureka 提供了客户端容错处理机制,客户端首先会轮询 Eureka Server 获取当前实例列表,然后根据负载均衡策略选择一个实例,进行服务调用。当某个实例出现连接失败时,Eureka 会移除该实例,从而保证服务的可用性。
4.Eureka 安装运行
准备环境:
1.下载并安装 JDK 和 Maven
2.创建本地仓库:mkdir /usr/local/maven
3.编辑 Maven 配置文件 settings.xml:
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
<!-- localRepository
| The path to the local repository for caching artifacts.
| Default: ${user.home}/.m2/repository
| user.home system property.
-->
<localRepository>/usr/local/maven</localRepository>
</settings>
4.配置 JAVA_HOME:export JAVA_HOME=/path/to/jdk/bin/java
Eureka 安装
1.克隆项目源码到本地:git clone https://github.com/Netflix/eureka.git
2.编译安装:cd eureka && mvn clean install -DskipTests
3.启动 Eureka:cd eureka-server && java -jar target/eureka-server-1.0-SNAPSHOT.jar
注意:若没有配置JAVA_HOME环境变量,则需在命令前面指定JDK安装路径。
java -Djava.net.preferIPv4Stack=true -jar target/eureka-server-1.0-SNAPSHOT.jar
这样就可以使用 IPv4 地址绑定服务器。
访问验证
浏览器访问 http://localhost:8761 ,会看到如下界面表示成功安装并启动 Eureka:
5.通过Demo学习Eureka的基本用法
下面通过一个简单的 Demo 学习一下 Eureka 的基本用法。
工程结构
pom.xml 文件
添加 Eureka Client 和 Eureka Server 的依赖:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>springcloudconfigdemo</artifactId>
<packaging>jar</packaging>
<version>0.0.1-SNAPSHOT</version>
<name>springcloudconfigdemo</name>
<url>http://www.example.com/</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Eureka Server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<!-- Eureka Client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<repositories>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<snapshots><enabled>true</enabled></snapshots>
</repository>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<snapshots><enabled>true</enabled></snapshots>
</pluginRepository>
<pluginRepository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
</pluginRepository>
</pluginRepositories>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.yml 文件
在 resources 文件夹下添加配置文件 application.yml ,内容如下:
server:port:8081spring:application:name: service-provider
cloud:config:uri: http://localhost:8888eureka:client:serviceUrl:defaultZone: http://localhost:8761/eureka/
logging:level:root: INFO
spring.application.name
: 服务名称,这里是
service-provider
。
spring.cloud.config.uri
: 配置中心地址,这里是配置中心地址。
eureka.client.serviceUrl.defaultZone
: Eureka Server 地址。
ServiceProviderApplication.java
创建 ServiceProviderApplication.java 文件:
packagecom.example.service;importorg.springframework.boot.SpringApplication;importorg.springframework.boot.autoconfigure.SpringBootApplication;importorg.springframework.cloud.netflix.eureka.EnableEurekaClient;@SpringBootApplication@EnableEurekaClient// 开启 Eureka Client 注解publicclassServiceProviderApplication{publicstaticvoidmain(String[] args){SpringApplication.run(ServiceProviderApplication.class, args);}}
HelloController.java
在 src/main/java/com.example/controller 包下创建 HelloController.java 文件,内容如下:
packagecom.example.controller;importorg.springframework.beans.factory.annotation.Value;importorg.springframework.web.bind.annotation.GetMapping;importorg.springframework.web.bind.annotation.RestController;@RestControllerpublicclassHelloController{@Value("${hello.msg}")privateString helloMsg;@GetMapping("/hello")publicStringsayHello(){return"Hello, "+ helloMsg;}}
@Value("${hello.msg}")
,读取配置文件中的 hello.msg 属性的值。
创建配置文件
在配置中心创建一个配置文件 hello.properties,内容如下:
hello.msg=world!
启动项目
在启动类 Application 中,添加
@EnableConfigServer
注解启用配置中心:
packagecom.example;importorg.springframework.boot.SpringApplication;importorg.springframework.boot.autoconfigure.SpringBootApplication;importorg.springframework.cloud.config.server.EnableConfigServer;@SpringBootApplication@EnableConfigServerpublicclassApplication{publicstaticvoidmain(String[] args){SpringApplication.run(Application.class, args);}}
执行测试
启动 ServiceProviderApplication ,再启动 Eureka Server ,分别执行以下两个命令:
服务注册
POST http://localhost:8761/eureka/apps
请求参数:
{"applications":{"versions__delta":"1","apps__hashcode":"UP_1_","application":[{"name":"SERVICE-PROVIDER","instance":[{"instanceId":"service-provider-1","hostName":"DESKTOP-DNKJRTV","app":"SERVICE-PROVIDER","ipAddr":"192.168.0.117","status":"STARTING","overriddenstatus":"UNKNOWN","port":{"$":8081,"@enabled":"true"},"securePort":{"$":443,"@enabled":"false"},"countryId":1,"dataCenterInfo":{"@class":"com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo","name":"MyOwn"},"leaseInfo":{"renewalIntervalInSecs":30,"durationInSecs":90,"registrationTimestamp":1606695900227,"lastRenewalTimestamp":1606696200232,"evictionTimestamp":0,"serviceUpTimestamp":0},"metadata":{"management.port":"8081"},"homePageUrl":"http://DESKTOP-DNKJRTV:8081/","healthCheckUrl":"http://DESKTOP-DNKJRTV:8081/actuator/health","secureHealthCheckUrl":"https://DESKTOP-DNKJRTV:8081/actuator/health","vipAddress":"service-provider","secureVipAddress":"service-provider","isCoordinatingDiscoveryServer":"false","lastUpdatedTimestamp":"1606695900227","lastDirtyTimestamp":"1606695885202","actionType":"ADDED"}}]}]}}
返回结果:
{"statusCode":204,"message":"No Content"}
服务消费
GET http://localhost:8762/hello
请求结果:
Hello, world!
此时可以看到 Eureka Server 可以正常注册服务,并使得服务消费者可以获得服务提供者的相关信息。
6.Eureka 内部机制
Eureka 的内部机制比较复杂,需要了解一些核心算法原理和具体操作步骤才能更好的理解它的工作原理。下面通过几个小节来逐一介绍 Eureka 的内部机制。
Eureka 自我保护模式
Eureka 本身提供了一个自我保护模式,当遇到特殊情况(比如网络分区、隔离带等)导致 Eureka 不可用的时候,会自动进入自我保护模式,防止 Eureka 集群中出现单点故障。当 Eureka 处于自我保护模式时,它会保护其服务注册表中所有微服务的信息,只接受新注册的微服务,而不会接收已存在微服务的信息更新。为了防止由于局部网络拥塞等原因导致客户端超时,Eureka 在自我保护模式下会减慢服务的刷新频率,每隔一段时间才会启动一次服务的心跳检测。
Eureka 默认情况下,自我保护模式关闭,通过设置
eureka.client.enableSelfPreservationMode = true
来打开自我保护模式。
Peer-to-Peer 传输
Eureka 使用了 Apache ZooKeeper 作为其数据存储,Eureka 集群中的每个节点都是完全平等的,彼此之间相互竞争地储存服务注册信息。这种去中心化的数据存储模式最大限度地降低了服务注册中心的单点故障风险。Eureka 使用 Paxos 协议来保证 Eureka Server 数据的一致性。
每次 Eureka 服务器节点之间需要交换信息的时候,都会先使用 Thrift 框架进行序列化传输,Thrift 是 Apache 基金会旗下的开源 RPC 框架。Thrift 传输性能比 JSON 快很多。
服务续约
Eureka 客户端定时向 Eureka 服务器发送心跳,表明自己仍然活跃,若 Eureka 服务器长时间没收到心跳,会将服务标记为失效,Eureka 服务器会将失效服务剔除出集群。同时,Eureka 客户端会周期性地对每个服务节点发送请求,探测是否还有活跃的服务提供者。若服务节点一直没有回应,则会把节点标记为“无响应”,开始进入自我保护模式。
集群状态改变
Eureka 集群中的任何节点都可以触发集群状态变更事件,包括新增节点、丢失节点、网络分区等。
当一个新的 Eureka 节点加入到集群中时,它会首先和现有的节点同步服务注册表。然后,它会把新加入的节点视作一个心跳,带着自己的服务信息和有效期,注册到所有的其他节点上。
当一个节点长期无法连通时,可能会导致整个集群不可用。所以,当检测到网络故障时,Eureka 会停止向失联的节点发送心跳。不过,Eureka 不会立刻将失联节点清除掉,而是等待一定时间,确保网络恢复后,失联节点能够重新联系到其他节点并重新同步服务注册表。
当一个节点超过设定的失联时间后,Eureka 会将它标记为“失效”,并通知其他节点。其他节点收到失效通知后,会尝试恢复失效节点上的服务,然后把它从自己的服务注册表中删除。
负载均衡
Eureka 使用客户端负载均衡策略,客户端从 Eureka 服务器中获取服务实例,并实现了几种负载均衡算法,如轮询、加权轮训等。
Eureka 客户端会优先选择在同一个 zone(可用区)内的节点进行负载均衡,然后才会选择跨 zone 节点。
当某个服务节点不可用时,Eureka 会将请求转移到另一台可用的节点上。Eureka 默认提供了一种基于 Round Robin 的负载均衡算法,当然用户也可以自定义自己的负载均衡算法。
7.结论
本文通过学习 Spring Cloud 分布式配置中心 Eureka 的基本知识和特性,阐述了 Eureka 的基本原理、特性、安装和使用方法。同时,还通过一个 Demo 学习了 Eureka 的基本用法,并对 Eureka 的内部机制进行了深入剖析。最后,通过简短的总结,给读者们呈现了一幅全景图,看完这篇文章,大家应该对 Spring Cloud 分布式配置中心 Eureka 有了深刻的认识和理解。
版权归原作者 禅与计算机程序设计艺术 所有, 如有侵权,请联系我们删除。