SpringCloud学习1-服务注册与发现(Eureka)

前言

Oracle转让Java,各种动态语言的曝光率上升,Java工程师的未来在哪里?我觉得Spring Cloud让未来有无限可能。拖了半年之久的Spring Cloud学习就从今天开始了。中文教材不多,而且大多都是简单的离散的信息,想要找到企业级的一体化解决方案很少。不过,对于入门来说,简单就够了,等到用的时候自然而然的汇总起来。

目标是把springcloud的子项目过一遍。

Component

Edgware.SR2

Finchley.M7

Finchley.BUILD-SNAPSHOT

spring-cloud-aws

1.2.2.RELEASE

2.0.0.M4

2.0.0.BUILD-SNAPSHOT

spring-cloud-bus

1.3.2.RELEASE

2.0.0.M6

2.0.0.BUILD-SNAPSHOT

spring-cloud-cli

1.4.1.RELEASE

2.0.0.M1

2.0.0.BUILD-SNAPSHOT

spring-cloud-commons

1.3.2.RELEASE

2.0.0.M7

2.0.0.BUILD-SNAPSHOT

spring-cloud-contract

1.2.3.RELEASE

2.0.0.M7

2.0.0.BUILD-SNAPSHOT

spring-cloud-config

1.4.2.RELEASE

2.0.0.M7

2.0.0.BUILD-SNAPSHOT

spring-cloud-netflix

1.4.3.RELEASE

2.0.0.M7

2.0.0.BUILD-SNAPSHOT

spring-cloud-security

1.2.2.RELEASE

2.0.0.M2

2.0.0.BUILD-SNAPSHOT

spring-cloud-cloudfoundry

1.1.1.RELEASE

2.0.0.M3

2.0.0.BUILD-SNAPSHOT

spring-cloud-consul

1.3.2.RELEASE

2.0.0.M6

2.0.0.BUILD-SNAPSHOT

spring-cloud-sleuth

1.3.2.RELEASE

2.0.0.M7

2.0.0.BUILD-SNAPSHOT

spring-cloud-stream

Ditmars.SR3

Elmhurst.RC1

Elmhurst.BUILD-SNAPSHOT

spring-cloud-zookeeper

1.2.0.RELEASE

2.0.0.M6

2.0.0.BUILD-SNAPSHOT

spring-boot

1.5.10.RELEASE

2.0.0.RC2

2.0.0.BUILD-SNAPSHOT

spring-cloud-task

1.2.2.RELEASE

2.0.0.M3

2.0.0.RELEASE

spring-cloud-vault

1.1.0.RELEASE

2.0.0.M6

2.0.0.BUILD-SNAPSHOT

spring-cloud-gateway

1.0.1.RELEASE

2.0.0.M7

2.0.0.BUILD-SNAPSHOT

spring-cloud-openfeign

2.0.0.M1

2.0.0.BUILD-SNAPSHOT

本次学习服务注册与发现, Eureka。

Eureka介绍

Eureka是一个基于REST(Representational State Transfer)的服务,主要用于AWS cloud, 提供服务定位(locating services)、负载均衡(load balancing)、故障转移(failover of middle-tier servers)。我们把它叫做Eureka Server. Eureka也提供了基于Java的客户端组件,Eureka Client,内置的负载均衡器可以实现基本的round-robin负载均衡能力。在Netflix,一个基于Eureka的更复杂的负载均衡器针对多种因素(如流量、资源利用率、错误状态等)提供加权负载均衡,以实现高可用(superior resiliency).

为什么需要Eureka

在AWS Cloud,由于其天生的特性,服务器经常变换。我们知道每个EC2挂掉后,重启又是一个新的。不像传统的固定IP,AWS的服务器是变化的。因此需要更复杂的负载均衡方案来动态注册和注销。由于AWS并没有提供中间层负载均衡解决方案,Eureka填补了这个领域的巨大空白。

Eureka和AWS ELB有什么不同

AWS ELB(Elastic Load Balancer)是面向最终用户Web流量的边缘服务的负载均衡解决方案。Eureka填补了对中间层负载均衡的需求。理论上,你可以把中间层服务放在AWS ELB之后,但在EC2模型中,你将会把他们直接暴露到外网,从而失去了AWS security groups的好处。(这里有疑问,我现实使用的时候ELB也有区分VPC的,所以不会暴露到外网,不知道是不是本文发布的时候AWS还没这功能,所以感觉Eureka和ELB区别不大啊)。

AWS ELB也是一种传统的基于代理的负载平衡解决方案,而Eureka则不同之处在于负载平衡发生在实例/服务器/主机级别。客户端实例知道他们需要与哪些服务器交互的所有信息。这样的好坏取决于你怎么看待它。如果你想要AWS现在提供的基于粘滞用户session的负载均衡,Eureka没有开箱即用的解决方案。在Netflix,我们更喜欢我们的服务是无状态的(非粘性)。这有利于提供更好的扩展性,Eureka非常适合解决这个问题。(感觉这段也是吹水,现在的web交互大都是无状态的,状态通过redis,message queue等第三方维护,ELB照样可以提供)。

使用Eureka区分基于代理的负载平衡和负载平衡的另一个重要方面是,您的应用程序可以灵活地处理负载平衡器的中断,因为有关可用服务器的信息会缓存在客户端上。这确实需要少量的内存,但换得更好的弹性。

Eureka和Route 53有什么不同

Route 53是一个域名服务,就像Eureka可以为中层服务器提供相同的服务一样,但仅此而已。 Route 53是一项DNS服务,即使对于非AWS数据中心,也可以托管您的DNS记录。 Route 53还可以在AWS区域间执行基于延迟的路由。Eureka类似于内部DNS,与全世界的DNS服务器无关。Eureka也是区域隔离的,因为它不知道其他AWS区域中的服务器。保存信息的主要目的是在区域内进行负载平衡。

虽然你可以在Route 53中注册你的中间层服务器,并依赖AWS安全组保护你的服务器不受外网访问,但你的中间层服务器身份仍然暴露于外网环境。它同样带有传统基于DNS的负载均衡方案的缺点,其中流量仍然会被路由到已经不健康或已经不存在的服务器上(在AWS云中,服务器随时可能消失)。

Eureka如何使用?

在Netflix,Eureka不仅是中间层负载均衡关键部分,还有以下功能:

与Netflix Asgard一起提供红/黑部署服务, Asgard是一个让云部署更方便的开源服务。Eureka会与Asgard搭配,让应用在新/老版本部署切换,让故障处理更快速和无缝,尤其是当启动100个实例部署时要花费很长时间的时候。

当我们的cassandra需要维护时,停止Cassandra实例。

为我们的memcached缓存服务提供识别环上实例列表功能。

为特定的应用提供因意外导致故障保存元信息的服务。

Eureka使用时机?

当你的服务运行在AWS云上并且你不希望使用AWS ELB注册或暴露给外网。你要么需要使用类似round-robin这种简单的负载均衡方案或者想要写一个基于Eureka包装过的符合要求的负载均衡器。你没有session粘性,没有session绑定机制和在外部缓存(例如 memcached)载入会话数据的需要。更重要的是,如果你的架构风格适合一个基于客户端的负载均衡模型,Eureka相当适合这个场景。

应用客户端和应用服务端如何通信?

通信技术可以是任何你喜欢的。Eureka帮你找到你需要通信的服务信息但没有引入任何通信协议或方法的限制。比如,你可以用Eureka获取目标服务器地址并使用thrift,http(s)或其他RPC机制的协议。

Eureka架构

上面的架构图描述了Eureka是如何在Netflix部署的,这也是Eureka集群的运行方式。在每个区域(region)都有一个eureka集群,它只知道该区域内的实例信息。每个分区(zone)至少有一个eureka服务器来处理本分区故障。

服务注册在Eureka上并且每30秒发送心跳来续租。如果一个客户端在几次内没有刷新心跳,它将在大约90秒内被移出服务器注册表。注册信息和更新信息会在整个eureka集群的节点进行复制。任何分区的客户端都可查找注册中心信息(每30秒发生一次)来定位他们的服务(可能会在任何分区)并进行远程调用。

非Java服务和客户端

对于非Java的服务,你可以用其他语言实现eureka的客户端部分。基于REST的服务也暴露给了所有操作给Eureka客户端。非Java客户端也可以使用REST服务来查询其他服务的信息。

可配置

有了Eureka,你可以动态添加删除集群节点。你可以调整内部配置,从超时到线程池。Eureka使用archaius并且如果你有一个配置源的实现,那么很多配置可以动态调优。

弹性

在AWS云中,构建弹性伸缩必不可少。Eureka是我们经验的结晶,并且在客户端和服务端都内置了弹性能力。

Eureka客户端设计成可以处理一个或多个Eureka服务端的失败场景。由于Eureka客户端有注册表缓存信息,即使所有的eureka服务器都挂了,服务也能正常运行。

Eureka服务器对于其他eureka节点挂了也提供了足够的弹性。即使服务端和客户端之间产生了网络分区,服务器也由内置的弹性策略来防止大规模的停机。

多区域

在多个AWS区域部署Eureka是一个很简单的工作。不同区域之间Eureka集群并不通信。

监控

Eureka使用servo来跟踪服务端和客户端的信息,包括性能,监控和报警。数据保存在JMX中并暴露给Amazon Cloud Watch。


Eureka服务治理体系

以下参考《Spring Cloud Eureka详解》, 作者 大道化简, 然而,后面发现是程序员dd的书籍里的内容。在此正名,以下出自《Spring Cloud 微服务实战》,作者翟永超。

大概读完Eureka的简介,应该可以知道Eureka是负责微服务架构中服务治理的功能,负责各个微服务实例的自动注册和发现。

服务注册

在服务治理框架中,通常都会构建一个注册中心,每个服务单元向注册中心登记自己提供的服务,包括服务的主机与端口号、服务版本号、通讯协议等一些附加信息。注册中心按照服务名分类组织服务清单,同时还需要以心跳检测的方式去监测清单中的服务是否可用,若不可用需要从服务清单中剔除,以达到排除故障服务的效果。

服务发现

在服务治理框架下,服务间的调用不再通过指定具体的实例地址来实现,而是通过服务名发起请求调用实现。服务调用方通过服务名从服务注册中心的服务清单中获取服务实例的列表清单,通过指定的负载均衡策略取出一个服务实例位置来进行服务调用。

Eureka服务端

Eureka服务端,即服务注册中心。它同其他服务注册中心一样,支持高可用配置。依托于强一致性提供良好的服务实例可用性,可以应对多种不同的故障场景。

Eureka服务端支持集群模式部署,当集群中有分片发生故障的时候,Eureka会自动转入自我保护模式。它允许在分片发生故障的时候继续提供服务的发现和注册,当故障分配恢复时,集群中的其他分片会把他们的状态再次同步回来。集群中的的不同服务注册中心通过异步模式互相复制各自的状态,这也意味着在给定的时间点每个实例关于所有服务的状态可能存在不一致的现象。

Eureka客户端

Eureka客户端,主要处理服务的注册和发现。客户端服务通过注册和参数配置的方式,嵌入在客户端应用程序的代码中。在应用程序启动时,Eureka客户端向服务注册中心注册自身提供的服务,并周期性的发送心跳来更新它的服务租约。同时,他也能从服务端查询当前注册的服务信息并把它们缓存到本地并周期行的刷新服务状态。


注册中心

在服务治理框架中,通常都会构建一个注册中心,每个服务单元向注册中心登记自己提供的服务,包括服务的主机与端口号、服务版本号、通讯协议等一些附加信息。注册中心按照服务名分类组织服务清单,同时还需要以心跳检测的方式去监测清单中的服务是否可用,若不可用需要从服务清单中剔除,以达到排除故障服务的效果。

创建Eureka Server

测试代码: https://github.com/Ryan-Miao/eureka-server

Eureka Server是基于springboot的,只要启动一个springboot就可以了。start.spring.io提供了一系列启动模板,而且Spring又和Idea比较暧昧,所以使用Idea可以超级简单的搭建和集成Spring项目。

在Idea里,新建项目,选择Spring initializer.

然后,勾选你想要的组件就行了。这里搜索Eureka Server, 选择

当然,创建好项目后记得先修改编码为UTF8, 不然万恶的GBK...

我的pom如下:

<?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.test</groupId>
    <artifactId>eureka-server</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>eureka-server</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.10.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <spring-cloud.version>Edgware.SR2</spring-cloud.version>
    </properties>

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

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </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>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>


</project>

然后,在application.properties中加入配置信息:

spring.application.name=eureka-server

#服务注册中心端口号
server.port=1110

#服务注册中心实例的主机名
eureka.instance.hostname=localhost

#是否向服务注册中心注册自己
eureka.client.register-with-eureka=false

#是否检索服务
eureka.client.fetch-registry=false

#服务注册中心的配置内容,指定服务注册中心的位置
eureka.client.serviceUrl.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka/

修改启动类,添加@EnableEurekaServer

@EnableEurekaServer
@SpringBootApplication
public class EurekaServerApplication {

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

然后,启动main方法即可。如果端口有冲突,修改合适的端口重启。demo中端口为1110,启动后,访问http://localhost:1110/ 可以看到状态控制台。

前文也说了,上述demo是注册中心,所有的微服务要向本server注册以实现负载均衡。那么,首先就要保证注册中心的稳定,于是就必须搭建Eureka集群的高可用方案。

高可用服务注册中心

考虑到发生故障的情况,服务注册中心发生故障必将会造成整个系统的瘫痪,因此需要保证服务注册中心的高可用。

Eureka Server在设计的时候就考虑了高可用设计,在Eureka服务治理设计中,所有节点既是服务的提供方,也是服务的消费方,服务注册中心也不例外。

Eureka Server的高可用实际上就是将自己做为服务向其他服务注册中心注册自己,这样就可以形成一组互相注册的服务注册中心,以实现服务清单的互相同步,达到高可用的效果。

构建服务注册中心集群

Eureka Server的同步遵循着一个非常简单的原则:只要有一条边将节点连接,就可以进行信息传播与同步。可以采用两两注册的方式实现集群中节点完全对等的效果,实现最高可用性集群,任何一台注册中心故障都不会影响服务的注册与发现.

所以,下面创建3个Eureka Server两两互相注册,形成集群。由于核心代码一样,我们只要将其部署在不同的机器上即可。因此,我需要3个不同的配置文件。

为了本地模拟,修改host,虚拟3个域名

windows host 位置C:\Windows\System32\drivers\etc

127.0.0.1 master
127.0.0.1 backup1
127.0.0.1 backup2

然后,给我们的Eureka Server增加3个配置文件。此时,应该将application.properties里除了spring.application.name之外的配置注释掉,我们后面3个配置暂时不用上面几个开关。

application-peer1.properties

server.port=1111

eureka.instance.hostname=master

eureka.client.serviceUrl.defaultZone=http://backup1:1112/eureka/,http://backup2:1113/eureka/

application-peer2.properties

server.port=1112

eureka.instance.hostname=backup1

eureka.client.serviceUrl.defaultZone=http://master:1111/eureka/,http://backup2:1113/eureka/

application-peer3.properties

server.port=1113

eureka.instance.hostname=backup2


eureka.client.serviceUrl.defaultZone=http://master:1111/eureka/,http://backup1:1112/eureka/

由于是本地开发环境,我们直接以maven启动。当然,也可以选择jar启动。

分别打开3个命令行

mvn spring-boot:run -Dspring.profiles.active=peer1
mvn spring-boot:run -Dspring.profiles.active=peer2
mvn spring-boot:run -Dspring.profiles.active=peer3

注意,由于启动的时候会去指定zone注册,而另外的server还没启动,这时候会报错,Cannot execute request on any known server, 不用理会,接着启动后两个即可。

全部启动成功后,访问http://master:1111/

可以看到2个备份,在线的server有3个Availability Zones。Availability Zones在AWS中是指可用区,是在不同region里的不同机房。

注册中心高可用集群demo搭建完毕。

失效剔除

有些时候,我们的服务实例并不一定会正常下线,可能由于内存溢出、网络故障等原因使服务不能正常运作。而服务注册中心并未收到“服务下线”的请求,为了从服务列表中将这些无法提供服务的实例剔除,Eureka Server在启动的时候会创建一个定时任务,默认每隔一段时间(默认为60秒)将当前清单中超时(默认为90秒)没有续约的服务剔除出去。

自我保护

服务注册到Eureka Server后,会维护一个心跳连接,告诉Eureka Server自己还活着。Eureka Server在运行期间会统计心跳失败的比例在15分钟以之内是否低于85%,如果出现低于的情况,Eureka Server会将当前实例注册信息保护起来,让这些实例不会过期。这样做会使客户端很容易拿到实际已经不存在的服务实例,会出现调用失败的情况。因此客户端要有容错机制,比如请求重试、断路器。

以下是自我保护相关的属性:

eureka.server.enableSelfPreservation=true. 可以设置改参数值为false,以确保注册中心将不可用的实例删除

region(地域)与zone(可用区)

region和zone(或者Availability Zone)均是AWS的概念。在非AWS环境下,我们可以简单地将region理解为地域,zone理解成机房。一个region可以包含多个zone,可以理解为一个地域内的多个不同的机房。不同地域的距离很远,一个地域的不同zone间距离往往较近,也可能在同一个机房内。

region可以通过配置文件进行配置,如果不配置,会默认使用us-east-1。同样Zone也可以配置,如果不配置,会默认使用defaultZone。

Eureka Server通过eureka.client.serviceUrl.defaultZone属性设置Eureka的服务注册中心的位置。

指定region和zone的属性如下:

(1)eureka.client.availabilityZones.myregion=myzone# myregion是region

(2)eureka.client.region=myregion

Ribbon的默认策略会优先访问通客户端处于同一个region中的服务端实例,只有当同一个zone中没有可用服务端实例的时候才会访问其他zone中的实例。所以通过zone属性的定义,配合实际部署的物理结构,我们就可以设计出应对区域性故障的容错集群。

安全验证

刚才的demo中,我们注册中心的面板是公开访问的。这里可以简单加入用户名密码,让访问更安全。当然,你可以自己实现sso。 pom添加

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

然后,为了简单演示,事实上也应该这样。我们将3个server的用户名密码设置一致。即,在application.properties里添加:

security.user.name=admin
security.user.password=123456

然后,在我们其他三个配置文件中的eureka.client.serviceUrl.defaultZone添加自己的url并加入用户名密码, 以peer1为例子

eureka.client.serviceUrl.defaultZone=http://${security.user.name}:${security.user.password}@${eureka.instance.hostname}:${server.port}/eureka/,http://backup1:1112/eureka/,http://backup2:1113/eureka/

服务提供者 微服务集群

服务注册

服务提供者在启动的时候会通过REST请求的方式将自己注册到Eureka Server上,同时带上自身服务的一些元数据信息。Eureka Server接收到这个Rest请求之后,将元数据信息存储在一个双层结构的Map中,其中第一层的key是服务名。第二层的key 是具体服务的实例名。

在服务注册时,需要确认一下eureka.client.register-with-eureka=true参数是否正确,该值默认为true。若设置为fasle将不会启动注册操作。

服务同步

从eureka服务治理体系架构图中可以看到,不同的服务提供者可以注册在不同的服务注册中心上,它们的信息被不同的服务注册中心维护。

此时,由于多个服务注册中心互相注册为服务,当服务提供者发送注册请求到一个服务注册中心时,它会将该请求转发给集群中相连的其他注册中心,从而实现服务注册中心之间的服务同步。通过服务同步,提供者的服务信息就可以通过集群中的任意一个服务注册中心获得。

服务续约

在注册服务之后,服务提供者会维护一个心跳用来持续高速Eureka Server,“我还在持续提供服务”,否则Eureka Server的剔除任务会将该服务实例从服务列表中排除出去。我们称之为服务续约。

下面是服务续约的两个重要属性:

(1)eureka.instance.lease-expiration-duration-in-seconds

leaseExpirationDurationInSeconds,表示eureka server至上一次收到client的心跳之后,等待下一次心跳的超时时间,在这个时间内若没收到下一次心跳,则将移除该instance。

默认为90秒

如果该值太大,则很可能将流量转发过去的时候,该instance已经不存活了。 如果该值设置太小了,则instance则很可能因为临时的网络抖动而被摘除掉。 该值至少应该大于leaseRenewalIntervalInSeconds

(2)eureka.instance.lease-renewal-interval-in-seconds

leaseRenewalIntervalInSeconds,表示eureka client发送心跳给server端的频率。如果在leaseExpirationDurationInSeconds后,server端没有收到client的心跳,则将摘除该instance。除此之外,如果该instance实现了HealthCheckCallback,并决定让自己unavailable的话,则该instance也不会接收到流量。

默认30秒

创建并注册服务提供者 Eureka Client

项目代码: https://github.com/Ryan-Miao/eureka-client

Eureka Server是注册中心,我们的客户端也要集成Eureka client来自我注册。我们client项目也是基于Springboot的。同样创建一个新的项目 eureka-client. 这次,要引入Eureka Discovery以及健康检查Actuator。最终pom如下:

<?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.test</groupId>
    <artifactId>eureka-client</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>eureka-client</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.10.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <spring-cloud.version>Edgware.SR2</spring-cloud.version>
    </properties>

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

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </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>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>


</project>

然后,启动类添加@EnableDiscoveryClient

@EnableDiscoveryClient
@SpringBootApplication
public class EurekaClientApplication {

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

最后,添加配置信息:

spring.application.name=eureka-client-service-provider
server.port=2001
eureka.client.serviceUrl.defaultZone=http://admin:123456@master:1111/eureka/,http://admin:123456@backup1:1112/eureka/,http://admin:123456@backup2:1113/eureka/

这里,我们把三个注册中心的地址都加上。然后,为保证高可用,我们的服务提供者也需要集群部署。真实生产环境中肯定是部署到不同的机器上,在本地模拟的话,我们只好以不同端口来模拟了。

启动端口2001

mvn spring-boot:run -Dserver.port=2001

启动端口2002

mvn spring-boot:run -Dserver.port=2002

然后,查看注册中心面板。


服务消费者 另一个微服务集群

获取服务

消费者服务启动时,会发送一个Rest请求给服务注册中心,来获取上面注册的服务清单。为了性能考虑,Eureka Server会维护一份只读的服务注册清单来返回给客户端,同时该缓存清单默认会每隔30秒更新一次。

下面是获取服务的两个重要的属性:

(1) eureka.client.fetch-registry

是否需要去检索寻找服务,默认是true

(2)eureka.client.registry-fetch-interval-seconds

表示eureka client间隔多久去拉取服务注册信息,默认为30秒,对于api-gateway,如果要迅速获取服务注册状态,可以缩小该值,比如5秒

服务调用

服务消费者在获取服务清单后,通过服务名可以获取具体提供服务的实例名和该实例的元数据信息。因为有这些服务实例的详细信息,所以客户端可以根据自己的需要决定具体调用哪个实例,在Ribbon中会默认采用轮询的方式进行调用,从而实现客户端的负载均衡。

等学到Ribbon之后再继续服务消费。

服务下线

在系统运行过程中必然会面临关闭或重启服务的某个实例的情况,在服务关闭操作时,会触发一个服务下线的Rest服务请求给Eureka Server,告诉服务注册中心:“我要下线了。”服务端在接收到该请求后,将该服务状态置位下线(DOWN),并把该下线事件传播出去。

配置详解

服务实例类配置

端点配置

eureka实例的状态页面和健康监控的url默认为spring boot actuator提供的/info端点和/health端点。我们必须确保Eureka客户端的/health端点在发送元数据的时候,是一个能够被注册中心访问到的地址,否则服务注册中心不会根据应用的健康检查来更改状态(仅当开启了healthcheck功能时,以该端点信息作为健康检查标准)。而如果/info端点不正确的话,会导致在Eureka面板中单击服务时,无法访问到服务实例提供的信息接口。

大多数情况下,我们不需要修改这个几个url配置。但是当应用不使用默认的上下文(context path或servlet path,比如配置server.servletPath=/test),或者管理终端路径(比如配置management.contextPath=/admin)时,我们需要修改健康检查和状态页的url地址信息。

application.yml配置文件如下:

server.context-path=/helloeureka

//下面配置为相对路径,也支持配置成绝对路径,例如需要支持https

eureka.instance.health-check-url-path=${server.context-path}/health

eureka.instance.status-page-url-path=${server.context-path}/info

元数据

元数据是Eureka客户端在向服务注册中心发送注册请求时,用来描述自身服务信息的对象,其中包含了一些标准化的元数据,比如服务名称、实例名称、实例IP、实例端口等用于服务治理的重要信息;以及一些用于负载均衡策略或是其他特殊用途的自定义元数据信息。

我们可以通过eureka.instance.<properties>=<value>的格式对标准化元数据直接进行配置,其中<properties>就是EurekaInstanceConfigBean对象中的成员变量。而对于自定义元数据,可以通过eureka.instance.metadataMap.<key>=<value>的格式来进行配置。比如:

eureka.instance.metadataMap.zone=tianjin

//随机生成实例名

eureka.instance.metadataMap.instanceId=${spring.application.name}:${random.value}

健康检测

默认情况下,Eureka中各个服务实例的健康检测并不是通过spring-boot-acturator模块的/health端点来实现的,而是依靠客户端心跳的方式来保持服务实例的存活。在Eureka的服务续约与剔除机制下,客户端的健康状态从注册到注册中心开始都会处于UP状态,除非心跳终止一段时间之后,服务注册中心将其剔除。默认的心跳实现方式可以有效检查客户端进程是否正常运作,但却无法保证客户端应用能够正常提供服务。

在Spring Cloud Eureka中,可以把Eureka客户端的健康检测交给spring-boot-actuator模块的health端点,以实现更加全面的健康状态维护,设置方式如下:

(1) 在pom.xml中引入spring-boot-starter-actuator模块的依赖

(2) 在application.properties中增加参数配置eureka.client.healthcheck.enabled=true

这里,Idea里并没有提示eureka.client.healthcheck.enabled这个属性,并且还显示黄色,让以为是不是哪里错了,根本不敢尝试。不过百度后,发现有人做了类似实验,成功了。好吧,可能对这个的学习还不够,或者就应该给Idea提一个issue。下面给出自定义health check来替换Eureka自带心跳测试。

自己实现HealthChecker

根据自己health的定义,自己实现一个HealthChecker。这里简单模拟。 在eureka-client创建MyHealthChecker,

@Component
public class MyHealthChecker implements HealthIndicator {


    private volatile boolean up = true;

    @Override
    public Health health() {
        if (up) {
            return new Health.Builder().withDetail("aaa_cnt", 10) //自定义监控内容
                    .withDetail("bbb_status", "up").up().build();
        } else {
            return new Health.Builder().withDetail("error", "client is down").down().build();
        }

    }

    public boolean isUp() {
        return up;
    }

    public void setUp(boolean up) {
        this.up = up;
    }
}

然后,暴露出一个接口来控制up状态。

@RestController
public class HealthSettingController {

    @Autowired
    MyHealthChecker myHealthChecker;

    @RequestMapping("/health/{status}")
    public String up(@PathVariable("status") Boolean status) {
        myHealthChecker.setUp(status);

        return status.toString();
    }

}

然后在我们的客户端eureka-clientp配置文件里新增eureka.client.healthcheck.enabled=true

重新启动,访问http://localhost:2001/health/false来把我们client端中一个instance设置为down。然后,刷新eureka server面板,即访问http://master:1111/, 可以看到,我们的client确实下线了。

其他配置

除了上述配置参数外,下面整理了一些EurekaInstanceConfigBean中定义的配置参数以及对应的说明和默认值,这些参数均以eureka.instance为前缀。

通讯协议

默认情况下,Eureka使用Jersey和XStream配合JSON作为Server与Client之间的通讯协议。也可以选择实现自己的协议来代替。

参考

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏JAVA高级架构

如何实现大型网站架构设计的负载均衡

负载均衡 (Load Balancing) 负载均衡建立在现有网络结构之上,它提供了一种廉价有效透明的方法扩展网络设备和服务器的带宽、增加吞吐量、加强网络数据处...

35810
来自专栏james大数据架构

我是如何处理大并发量订单处理的 KafKa部署总结

  今天要介绍的是消息中间件KafKa,应该说是一个很牛的中间件吧,背靠Apache 与很多有名的中间件搭配起来用效果更好哦 ,为什么不用RabbitMQ,因为...

2749
来自专栏企鹅号快讯

浅谈几种SLB技术的实现

今天小普和大家分享下,在最近的学习过程中,关于几个负载均衡技术的理解,以及几个实现的原理和关键点,希望对各位读者朋友有收获。 1 http重定向协议实现负载均衡...

3905
来自专栏Web项目聚集地

Dubbo入门-搭建一个最简单的Demo框架

1. 单一应用框架(ORM) 当网站流量很小时,只需一个应用,将所有功能如下单支付等都部署在一起,以减少部署节点和成本。 缺点:单一的系统架构,使得在开发过...

1052
来自专栏IT笔记

Nginx + Shiro + Redis 实现负载均衡集群(成绩报告查询系统升级篇)

写在开始 上一篇讲到使用Ehcache实现分布式缓存,尽管其直接操作JVM内存,速度快,效率高,但是缓存同步麻烦,分布式集群配置不方便,如果应用服务器重启会丢失...

2777
来自专栏aoho求索

NSQ深入与实践

1. 介绍 最近在研究一些消息中间件,常用的MQ如RabbitMQ,ActiveMQ,Kafka等。NSQ是一个基于Go语言的分布式实时消息平台,它基于MIT开...

3919
来自专栏携程技术中心

干货 | 深度剖析服务发现组件Netflix Eureka

作者简介 宋顺,携程框架研发部技术专家。2016年初加入携程,主要负责中间件产品的相关研发工作。毕业于复旦大学软件工程系,曾就职于大众点评,担任后台系统技术负责...

3485
来自专栏大魏分享(微信公众号:david-share)

微服务架构模式解析 | 大魏学微服务系列第一篇

微服务架构( Microservice Architecture MSA)环境的定义特征是:模块化服务是单独部署的、每个模块化服务可以独立于其他服务,或同一服务...

933
来自专栏Golang语言社区

Web负载均衡

序: 对Web站点扩展一开始不宜过早,除非是基于高可用性和就近部署的考虑。但对于架构师而言,在架构设计之初就要有扩展的计划,关键是要清楚何时进行扩展。这...

3287
来自专栏Rainbond开源「容器云平台」

云帮(ACP)7月升级:重构负载均衡,优化后端组件功能

1243

扫码关注云+社区