本文编写于 244 天前,最后修改于 244 天前,其中某些信息可能已经过时。

接上篇《4.服务发现与注册》

上一篇我们讲解了服务发现组件(注册中心)的相关知识,下面我们来着重讲解Spring Cloud生态下的比较常用的服务发现组件Eureka。

一、Eureka简介

Eureka是Netflix开发的服务发现矿建,本身是一个基于REST的服务,主要用于定位运行在AWS(亚马逊服务器)域中的中间层服务,以达到负载均衡和中间层服务故障转移的目的。Spring Cloud将它继承在其子项目spring-cloud-netflix中,以实现Spring Cloud的发现功能。

Eureka项目相当活跃,代码更新相当频繁,截止发博前最新的RELEASE稳定版本为1.9.3。Eureka 2.0的版本之前一直在开发中,旨在使用细粒度的订阅模式去取代基于拉取的模型,以此带来更好的拓展性。但是由于在开发过程中出现了一些问题,可能是代码复杂度过高或者性能提升也并没有预期中的那么好,总之Eureka 2.0的开源工作宣告停止,其团队还宣布将Eureka 2.0分支用在生产环境将后果自负(来自https://github.com/Netflix/eureka/wiki):


所以这里我们下面主要以Eureka 1.9.3以下的版本来进行讲解。

二、Eureka原理

下面是一张Netflix提供的Eureka在微服务运行环境中的工作模式(出自:https://github.com/Netflix/eureka/wiki/Eureka-at-a-glance)


先解释一下图中的一个要点(region和zone),以方便我们理解这张图:
我们可以观察到,图的上面有“us-east-1c”等信息的字样,这些实际上标识了服务所在的“区域(region)”以及“可用区(zone)”。region的概念最初由亚马逊提出,AWS云服务在全球的不同地区都有数据中心,所以根据地理位置会将某个地区的基础设施服务集合成为一个区域。在他们的硬软件服务节点中,需要选择不同的区域,这些区域的数据时相互独立的。例如下面是一些区域的编码:

附图出自亚马逊AWS在CSDN的官博:https://blog.csdn.net/awschina/article/details/17639191
而每一个区域(region)又分为可用区(zone),一个可用区一般是由多个数据中心组成,可用区与可用区之间在设计上是相互独立的,在一个区域内,可用区与可用区之间是通过高速网络连接:

附图和部分解释出自亚马逊AWS在CSDN的官博:https://blog.csdn.net/awschina/article/details/17639191
所以上图中的“us-east-1c”、“us-east-1c”和“us-east-1c”都是“us-east-1”这个region的zone。

下面我们来看上面这张图,首先图上面有3个Eureka Server服务,在Eureka中提供了服务注册表,对外提供的API,可以让服务消费者通过API进行服务注册列表的获取,也可以让服务提供者通过API进行自我服务的注册。
在表中,“Application Service”代表的是服务提供者,“Application Client”代表的是服务消费者;而“Make Remote Call”就是远程服务调用(REST接口调用)的意思;

在“Application Service”和“Application Client”中都有Eureka Client,即Eureka客户端,用来与Eureka Server即Eureka服务端进行交互的。

所以由上图可知,Eureka包含两个组件:Eureka Client和Eureka Server。

Eureka Server提供服务注册功能,各个节点启动后,会在Eureka Server中进行注册,这样Eureka Server中的服务注册列表中会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观的看到。

Eureka Client是一个Java客户端,用于简化与Eureka Server的交互,客户端同时也具备一个内置的、使用轮询(round-robin)负载算法的负载均衡器。

在配置注册的应用启动后,将会向Eureka Server发送心跳(默认周期为30秒)。如果Eureka Server在多个心跳周期内没有收到某个节点的心跳,Eureka Server将会从服务注册表中把这个服务节点移除(默认90秒)。

同时,Eureka Server之间将会通过复制的方式完成数据的同步(Eureka高可用)。

Eureka还提供了客户端缓存的机制,即使所有的Eureka Server都挂掉,客户端依然可以利用缓存中的消息消费其他服务的API。

综上,Eureka通过心跳检测、健康检查、客户端缓存等机制,确保了系统的高可用性、灵活性和可伸缩性。

综上所述。在上图中,当“Application Service”和“Application Client”启动后,它通过Eureka Client与Eureka Server交互,将自己的服务信息注册到Eureka Server中的服务注册表中去。而服务注册表中存放了所有服务节点的ip和端口号。由于服务注册中心需要进行高可用配置,所以这里进行了Eureka Server的复制(Server间进行了相互注册)。

三、实现一个Eureka Server

下面我们来编码实现一个Eureka Server。
1、准备工作
在编写Eureka Server之前,还记得我们将之前编写的服务提供者microserver-simple-provider-user和服务消费者microserver-simple-consumer-movie吗:


我们拷贝这两个工程,然后做一些改造,使其通过父级工程来管理版本。
首先说明一下,之前编写服务提供者和服务消费者时,使用的Greenwich.SR1的2.2.0.BUILD-SNAPSHOT版本,虽然版本比较新,但是由于该版本是快照版,不是RELEASE稳定正式释出版,所以在下面的过程中,笔者遇到了很多问题,所以改为使用Finchley.SR2的2.0.6.RELEASE版本。(也告诫大家在正式环境中一定要使用RELEASE版本)
因为想使用Spring Cloud,那么所有服务都要继承一个Spring Cloud的父类工程,所以我们打开工程所在源目录,创建一个文件夹为code,将这两个工程放在文件夹下,

在进行改造之前,先将这两个工程打成压缩包,放在bak备份文件夹下,备份一下,便于以后对比。
然后各自复制两个工程,重命名为去掉“simple”的工程:

新复制的这两个名为“microserver-provider-user”和“microserver-consumer-movie”的工程,是要进行父工程整合,和进行后面的Eureka的测试的。
分别进入“microserver-provider-user”和“microserver-consumer-movie”的文件夹,删除多余的文件,只留src和pom(src下的test文件夹删除,因为后面把junit的依赖去掉了):

然后修改其pom文件,将artifactId和name修改为各自的文件夹名称:

<artifactId>microserver-provider-user</artifactId>
<name>microserver-provider-user</name>

<artifactId>microserver-consumer-movie</artifactId>
<name>microserver-consumer-movie</name>

然后在code文件夹下创建一个pom.xml,准备将该文件夹编译为为其父级工程,用来做依赖jar包的版本控制:


然后在pom.xml文件中添加artifactId等工程定义信息(这里总父级工程名为microserver-spring-cloud),引入两个子工程,以及Spring boot start的父级工程依赖,然后定义两个子工程的所有dependency依赖版本,还有编码规则以及插件:

<?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.microserver.cloud</groupId>
    <artifactId>microserver-spring-cloud</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>pom</packaging>
    
    <modules>
        <module>microserver-provider-user</module>
        <module>microserver-consumer-movie</module>
    </modules>
    
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>
 
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Finchley.SR2</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
 
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

上面的dependencyManagement的作用是来管理jar包的版本,让子项目中引用一个依赖而不用显示的列出版本号。
这里有两种写法,一种是列出所有需要使用的dependency依赖以及其具体的版本,还有一种优雅的写法,就是上面的写法,在Spring Cloud官方的API上推荐用这种写法(这里是Finchley.SR2版本):


该写法会引入spring-cloud-dependencies依赖工程,该工程定义了该Spring Cloud对应版本所需要的所有依赖的版本信息,所以不用再一个一个写dependency依赖以及其具体的版本了。
详见https://cloud.spring.io/spring-cloud-static/Finchley.SR2/single/spring-cloud.html(Ctrl+F搜索“dependencyManagement”)

然后修改两个子工程(microserver-consumer-movie和microserver-provider-user)的pom.xml,将parent信息改为继承microserver-spring-cloud父工程,然后去除多余的properties、build配置(父级已经配了):
microserver-provider-user:

<?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>
    <parent>
        <groupId>com.microserver.cloud</groupId>
        <artifactId>microserver-spring-cloud</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <artifactId>microserver-provider-user</artifactId>
    <name>microserver-provider-user</name>
    <description>Demo project for Spring Boot</description>
 
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
 
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>
    </dependencies>
 
</project>

microserver-consumer-movie:

<?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>
    <parent>
        <groupId>com.microserver.cloud</groupId>
        <artifactId>microserver-spring-cloud</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <artifactId>microserver-consumer-movie</artifactId>
    <name>microserver-consumer-movie</name>
    <description>Demo project for Spring Boot</description>
 
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
</project>

然后我们回到编译器,将code所在文件夹以maven形式引入编译器,选择主pom文件:



这样我们的父、子工程结构就编写完了:


上面将原来的microserver-simple-provider-user和microserver-simple-consumer-movie工程关闭(close)掉,不再使用了。
打开“Project Explorer”视图,更能看清楚父子工程的级别关系:

注意:如果有报错,别忘记右键工程Maven->update project工程。

2、编写Eureka
新建一个Maven工程,定义artifactId等工程信息:




修改pom.xml文件,添加编码格式和eureka的依赖:

<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>
  <parent>
    <groupId>com.microserver.cloud</groupId>
    <artifactId>microserver-spring-cloud</artifactId>
    <version>0.0.1-SNAPSHOT</version>
  </parent>
  <artifactId>microserver-discovery-eureka</artifactId>
  <name>microserver-discovery-eureka</name>
  
 <dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
    </dependency>
  </dependencies>
  
</project>

注意:eureka的依赖根据版本的不同名称是有区别的
Spring Cloud 2.0 之前 spring-cloud-starter-eureka-server
Spring Cloud 2.0 之后 spring-cloud-starter-netflix-eureka-server

然后给之前的父工程microserver-spring-cloud的module在添加一个eureka成员:

<modules>
    <module>microserver-provider-user</module>
    <module>microserver-consumer-movie</module>
    <module>microserver-discovery-eureka</module>
</modules>

然后新建一个启动类EurekaServerApplicationRun.java:

package com.microserver.cloud.eureka;
 
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
 
@SpringBootApplication
@EnableEurekaServer  //表示是一个服务发现者
public class EurekaServerApplicationRun {
    public static void main(String[] args) {
        SpringApplication.run(EurekaServerApplicationRun.class, args);
    }
}

其中@EnableEurekaServer注解就是声明该应用为服务发现服务端,会启动一个服务注册中心提供给其它应用进行对话。

然后在resource下创建配置文件application.yml:

server:
  port: 8761
eureka:
  client:
    register-with-eureka: false
    fetch-registry: false
    service-url:
      defaultZone: http://eureka1:8761/eureka

其中:
server.port声明了服务端口为8761;
下面的配置,是因为在默认配置下,该注册中心也会将自己作为客户端来尝试注册它自己,所以我们需要禁用它的客户端注册行为。
eureka.client.register-with-eureka:由于该应用为注册中心,所以设置为false,代表不向注册中心注册自己。
eureka.client.fetch-registry:由于注册中心的职责就是维护服务实例,它并不需要去检索服务,所以也设置为false。
eureka.client.service-url.defaultZone:设置注册中心对客户端暴露的默认访问路径。

最后在windows的C:WindowsSystem32driversetchosts文件中配置eureka1对应的ip(127.0.0.1):
127.0.0.1 eureka1

启动EurekaServerApplicationRun类:


当控制台出现以下字样,证明Eureka Server启动成功:
Started EurekaServerApplicationRun in 18.012 seconds (JVM running for 18.986)

我们去浏览器中访问http://localhost:8761/:


可以看到我们打开了Eureka的控制台首页,里面有几个看板:
(1)System Status
“System Status”是当前的Eureka的系统状态,包括:
Environment:当前的运行环境。
Data center:数据中心。
Current time:当前时间。
Uptime:服务已经运行了多少时间。
Lease expiration enabled:自我保护模式,是否启用租约过期(false禁止/true可剔除)。
Renews threshold:Eureka Server 期望每分钟收到客户端实例续约的总数。
Renews (last min):Eureka Server 最后 1 分钟收到客户端实例续约的总数。

(2)DS Replicas
DS Replicas是指在Eureka集群下该服务从哪里同步数据。

(3)Instances currently registered with Eureka
这里就是指注册到该Eureka Server的服务实例列表。因为现在没有注册,所以列表为空。

(4)General Info
General Info是列出当前应用的一些参数:
total-avail-memory:可用内存。
environment:当前的运行环境。
num-of-cpus:cpu的核数。
current-memory-usage:当前使用的内存。
server-uptime:服务已经运行了多少时间。
registered-replicas:当前注册的Eureka Server的节点。
unavailable-replicas:当前不可用的节点(一般会显示自己)。
available-replicas:当前可用的Eureka Server的节点。

(5)Instance Info
Instance Info是值当前实例的信息:
ipAddr:eureka服务端IP
status:eureka服务端状态

左上方还有一个选项:


点开后可以看到:

该选项“Last 1000 cancelled leases”是指“最后1000个取消的租约”,点击页面可以看到,还有一个“Last 1000 newly registered leases”,意思是“最后1000个新注册的租约”。

至此,我们搭建了一个单机版的Eureka。下一篇我们将改造后的microserver-provider-user和microserver-consumer-movie注册到服务中心去,并且能通过注册中心获取对方的服务实例调用地址。

本部分参考源码下载:https://download.csdn.net/download/u013517797/11259868