文章首发:Spring Cloud Neflix之Eureka入门和实战

spring cloud组件列表

组件名称所属醒目组件分类
Eurekaspring-cloud-neflix注册核心
Zuulspring-cloud-nexflix第一代网关
Sidecarspring-cloud-nexflix多语言
Ribbonspring-cloud-nexflix负载平衡
Hystrixspring-cloud-nexflix熔断器
Turbinespring-cloud-nexflix集群监控
Feignspring-cloud-openfeign申明式HTTP客户端
Consulspring-cloud-consul注册核心
Gatewayspring-cloud-gateway第二代网关
Sleuthspring-cloud-sleuth链路追踪
Configspring-cloud-config配置核心
Busspring-cloud-bus总线
Pipelinespring-cloud-pipelines部署管道
Dataflowspring-cloud-dataflow数据处理
Nacosspring-cloud-alibaba注册核心、配置核心
Sentinelspring-cloud-alibaba流量管制和服务降级
Seataspring-cloud-alibaba分布式事务
Dubbo RPCspring-cloud-alibabaRPC

spring cloud和spring boot的版本对应

Spring Cloud版本Spring Boot版本
Hoxton2.2.x, 2.3.x (Starting with SR5)
Greenwich2.1.x
Finchley2.0.x
Edgware1.5.x
Dalston1.5.x

Eureka入门案例

  1. 创立Maven父级pom工程
    <parent>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-parent</artifactId>        <version>2.2.3.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>Hoxton.SR3</spring.cloud-version>    </properties>    <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>    <dependencies>        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-web</artifactId>        </dependency>        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-test</artifactId>            <scope>test</scope>        </dependency>    </dependencies>    <build>        <plugins>            <plugin>                <groupId>org.springframework.boot</groupId>                <artifactId>spring-boot-maven-plugin</artifactId>            </plugin>        </plugins>    </build>

2.在父级工程下创立Eureka Server子模块

           <dependencies>           <dependency>               <groupId>org.springframework.cloud</groupId>               <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>           </dependency>       </dependencies>          <build>           <plugins>               <plugin>                   <groupId>org.springframework.boot</groupId>                   <artifactId>spring-boot-maven-plugin</artifactId>               </plugin>           </plugins>       </build>

启动类

package com.msr.better.registry;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;@EnableEurekaServer@SpringBootApplicationpublic class EurekaServerApplication {    public static void main(String[] args) {        SpringApplication.run(EurekaServerApplication.class, args);    }}

配置文件

application.yml(单机版)

server:  port: 8761eureka:  instance:    hostname: localhost  client:    registerWithEureka: false    fetchRegistry: false    serviceUrl:      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/  server:    waitTimeInMsWhenSyncEmpty: 0    enableSelfPreservation: false

3.在父级工程下创立Eureka Client子模块

           <dependencies>           <dependency>            <groupId>org.springframework.cloud</groupId>               <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>            </dependency>       </dependencies>          <build>           <plugins>               <plugin>                   <groupId>org.springframework.boot</groupId>                   <artifactId>spring-boot-maven-plugin</artifactId>               </plugin>           </plugins>        </build>

启动类

package com.msr.better.client;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.client.discovery.EnableDiscoveryClient;@EnableDiscoveryClient@SpringBootApplicationpublic class ServiceApplication {    public static void main(String[] args) {        SpringApplication.run(ServiceApplication.class, args);    }}

配置文件:resources/application.yml

server:  port: 8081spring:  application:    name: cloud-eureka-client1eureka:  client:    serviceUrl:      defaultZone: http://localhost:8761/eureka/ # 一个Eureka Server

4.演示成果

启动Eureka Server和Eureka Client。在浏览器拜访http://localhost:8761。能够看... Client胜利注册到Eureka Server中来了。

拜访Eureka Server的REST API接口:http://localhost:8761/eureka/...

<applications>   <versions__delta>1</versions__delta>    <apps__hashcode>UP_1_</apps__hashcode>    <application>     <name>CLOUD-EUREKA-CLIENT1</name>      <instance>       <instanceId>DESTOP-IUCSN852:cloud-eureka-client1:8081</instanceId>        <hostName>DESTOP-IUCSN852</hostName>        <app>CLOUD-EUREKA-CLIENT1</app>        <ipAddr>192.168.3.2</ipAddr>        <status>UP</status>        <overriddenstatus>UNKNOWN</overriddenstatus>        <port enabled="true">8081</port>        <securePort enabled="false">443</securePort>        <countryId>1</countryId>        <dataCenterInfo class="com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo">         <name>MyOwn</name>       </dataCenterInfo>        <leaseInfo>         <renewalIntervalInSecs>30</renewalIntervalInSecs>          <durationInSecs>90</durationInSecs>          <registrationTimestamp>1612541898071</registrationTimestamp>          <lastRenewalTimestamp>1612541898071</lastRenewalTimestamp>          <evictionTimestamp>0</evictionTimestamp>          <serviceUpTimestamp>1612541898071</serviceUpTimestamp>       </leaseInfo>        <metadata>         <management.port>8081</management.port>       </metadata>        <homePageUrl>http://DESTOP-IUCSN852:8081/</homePageUrl>        <statusPageUrl>http://DESTOP-IUCSN852:8081/actuator/info</statusPageUrl>        <healthCheckUrl>http://DESTOP-IUCSN852:8081/actuator/health</healthCheckUrl>        <vipAddress>cloud-eureka-client1</vipAddress>        <secureVipAddress>cloud-eureka-client1</secureVipAddress>        <isCoordinatingDiscoveryServer>false</isCoordinatingDiscoveryServer>        <lastUpdatedTimestamp>1612541898071</lastUpdatedTimestamp>        <lastDirtyTimestamp>1612541897993</lastDirtyTimestamp>        <actionType>ADDED</actionType>     </instance>   </application> </applications>

Eureka的Rest API简介

Eureka是Netflix公司开源的一款服务发现组件,该组件提供的服务发现能够为负载平衡、failover等提供反对。Eureka包含Eureka Server和Eureka Client。Eureka Server提供REST服务,Eureka Client则是Java编写的客户端。

REST API列表

操作http动作形容
注册新的实例POST /eureka/apps/{appId}能够输出json或xml格局的body
登记利用实例DELETE /eureka/apps/{appId}/{instanceId}胜利返回200
向利用实例发送心跳PUT /eureka/apps/{appId}/{instanceId}胜利返回200,如果instanceId不存在返回404
查问所有实例GET /eureka/apps胜利返回200,输入json或xml格局
查问指定appId的实例GET /eureka/apps/{appId}胜利返回200,输入json或xml格局
依据指定appId和instanceId查问GET /eureka/apps/{appId}/{instanceId}胜利返回200,输入json或xml格局
依据指定instanceId查问GET /eureka/instances/{instanceId}胜利返回200,输入json或xml格局
暂停利用实例PUT /eureka/apps/{appId}/{instanceId}/status?value=OUT_OF_SERVICE胜利返回200,失败返回500
复原利用实例DELETE /eureka/apps/{appId}/{instanceId}/status?value=UP(value参数能够不传)胜利返回200,失败返回500
更新元数据PUT /eureka/apps/{appId}/{instanceId}/metadata?key=value胜利返回200,失败返回500
依据vip地址查问GET /eureka/vips/{vipAddress}胜利返回200,输入json或xml格局
依据svip地址查问GET /eureka/svips/{svipAddress}胜利返回200,输入json或xml格局

Eureka外围类

InstanceInfo(com.netflix.appinfo.InstanceInfo)

public class InstanceInfo {    private static final String VERSION_UNKNOWN = "unknown";    // 日志    private static final Logger logger = LoggerFactory.getLogger(InstanceInfo.class);    // 默认端口    public static final int DEFAULT_PORT = 7001;    // https默认端口    public static final int DEFAULT_SECURE_PORT = 7002;    public static final int DEFAULT_COUNTRY_ID = 1;    // 实例id    private volatile String instanceId;    // 利用名    private volatile String appName;    // 利用所属群组    @Auto    private volatile String appGroupName;    // IP地址    private volatile String ipAddr;    private static final String SID_DEFAULT = "na";    // 被废除的属性,默认为na    /** @deprecated */    @Deprecated    private volatile String sid;    // 端口号    private volatile int port;    // https端口号    private volatile int securePort;    // 利用实例的首页url    @Auto    private volatile String homePageUrl;    // 利用实例的状态页的url    @Auto    private volatile String statusPageUrl;    // 利用实例健康检查的url    @Auto    private volatile String healthCheckUrl;    // 利用实例健康检查https的url    @Auto    private volatile String secureHealthCheckUrl;    // 虚构ip地址    @Auto    private volatile String vipAddress;    // https的虚构ip地址    @Auto    private volatile String secureVipAddress;    //     @XStreamOmitField    private String statusPageRelativeUrl;    //     @XStreamOmitField    private String statusPageExplicitUrl;    //     @XStreamOmitField    private String healthCheckRelativeUrl;    //     @XStreamOmitField    private String healthCheckSecureExplicitUrl;    //     @XStreamOmitField    private String vipAddressUnresolved;    //     @XStreamOmitField    private String secureVipAddressUnresolved;    //     @XStreamOmitField    private String healthCheckExplicitUrl;    //     /** @deprecated */    @Deprecated    private volatile int countryId;    // 是否开启https端口    private volatile boolean isSecurePortEnabled;    // 是否开启http端口    private volatile boolean isUnsecurePortEnabled;    // dataCenter信息,如Neflix或者Amazon或者MyOwn    private volatile DataCenterInfo dataCenterInfo;    // 主机名称    private volatile String hostName;    // 实例状态,如:UP,DOWN、STARTING、OUT_OFSERVICE、UNKNOWN    private volatile InstanceInfo.InstanceStatus status;    // 外界须要强制笼罩的状态值,默认为UNKNOWN    private volatile InstanceInfo.InstanceStatus overriddenStatus;    @XStreamOmitField    private volatile boolean isInstanceInfoDirty;    // 租约信息    private volatile LeaseInfo leaseInfo;    // 首先标识是否discoveryServer,其实标识改discoveryServer是否是响应你申请的实例    @Auto    private volatile Boolean isCoordinatingDiscoveryServer;    // 利用实例的元数据信息    @XStreamAlias("metadata")    private volatile Map<String, String> metadata;    // 状态信息最初更新工夫    @Auto    private volatile Long lastUpdatedTimestamp;    // 实例信息最新的过期工夫,在Client端用于示意给实例信息是否与Eureka Server统一,在Server端用于多个Eureka Server的信息同步解决    @Auto    private volatile Long lastDirtyTimestamp;    // 示意Eureka Server对改实例执行的操作,包含ADDED、MODIFIED、DELETED这三类    @Auto    private volatile InstanceInfo.ActionType actionType;    // 在AWS的autoscaling group的名称    @Auto    private volatile String asgName;    // 版本    private String version;        // 无参构造函数    private InstanceInfo() {        this.sid = "na";        this.port = 7001;        this.securePort = 7002;        this.countryId = 1;        this.isSecurePortEnabled = false;        this.isUnsecurePortEnabled = true;        this.status = InstanceInfo.InstanceStatus.UP;        this.overriddenStatus = InstanceInfo.InstanceStatus.UNKNOWN;        this.isInstanceInfoDirty = false;        this.isCoordinatingDiscoveryServer = Boolean.FALSE;        this.version = "unknown";        this.metadata = new ConcurrentHashMap();        this.lastUpdatedTimestamp = System.currentTimeMillis();        this.lastDirtyTimestamp = this.lastUpdatedTimestamp;    }        // 其余省略...    }

租约信息类:LeaseInfo(com.netflix.appinfo.LeaseInfo)

public class LeaseInfo {    // 默认的续约周期时间,单位秒    public static final int DEFAULT_LEASE_RENEWAL_INTERVAL = 30;    // 默认的租约无效时长,单位秒    public static final int DEFAULT_LEASE_DURATION = 90;    // Client端续约的距离周期    private int renewalIntervalInSecs;    // Client端须要设置的租约的无效时长    private int durationInSecs;    // Server端设置该租约的第一次注册工夫    private long registrationTimestamp;    // Server端设置的该租约的最初一次续约工夫    private long lastRenewalTimestamp;    // Server端设置的该租约被剔除的工夫    private long evictionTimestamp;    // Server端设置的该服务实例标记为UP的工夫    private long serviceUpTimestamp;    // 无参构造函数    private LeaseInfo() {        this.renewalIntervalInSecs = 30;        this.durationInSecs = 90;    }}

service discovery的实例信息的形象接口,约定了服务发现的实例利用有哪些通用的信息。

public interface ServiceInstance {    default String getInstanceId() {        return null;    }    // 实例的id    String getServiceId();    // 实例的host    String getHost();    // 实例端口    int getPort();    // 实例是否开启了https    boolean isSecure();    // 实例uri地址    URI getUri();    // 元数据信息    Map<String, String> getMetadata();    // 实例的scheme    default String getScheme() {        return null;    }}

因为Spring Cloud Discovery适配了Zookeeper、Consul、Netflix Eureka、Nacos等注册核心,因而其ServiceInstance定义更为形象和通用,而且采取的是定义方法的形式。Spring Cloud对该接口的实现类为EurekaRegistration(org.springframework.cloud.netflix.eureka.serviceregistry.EurekaRegistration),EurekaRegistration实现了ServiceInstance接口。

InstanceStatus是InstanceInfo内的枚举类,用于标识服务实例的状态。

public static enum InstanceStatus {        UP,        DOWN,        STARTING,        OUT_OF_SERVICE,        UNKNOWN;        private InstanceStatus() {        }        public static InstanceInfo.InstanceStatus toEnum(String s) {            if (s != null) {                try {                    return valueOf(s.toUpperCase());                } catch (IllegalArgumentException var2) {                    InstanceInfo.logger.debug("illegal argument supplied to InstanceStatus.valueOf: {}, defaulting to {}", s, UNKNOWN);                }            }            return UNKNOWN;        }    }

从定义能够看得出来,服务实例次要有UP、DOWN、STARTING、OUT_OFSERVICE、UNKNOWN这几个状态。其中OUT_OF_SERVICE标识进行服务,即进行接管申请,处于这个状态的服务实例将不会被路由到,常常用于降级部署的场景。

服务的外围操作

对于服务发现来说,围绕服务实例次要有一下几个重要的操作:

  • 服务注册(register)
  • 服务下线(cancel)
  • 服务租约(renew)
  • 服务剔除(evict)

围绕这几个性能,Eureka设计了几个外围操作类:

  • com.netflix.eureka.lease.LeaseManager
  • com.netflix.discovery.shared.LookupService
  • com.netflix.eureka.registry.InstanceRegistry
  • com.netflix.eureka.registry.AbstractInstanceRegistry
  • com.netflix.eureka.registry.PeerAwareInstanceRegistryImpl

Spring CloudEureka在netflix Eureka的根底上,形象或定义了如下外围类:

  • org.springframework.cloud.netflix.eureka.server.InstanceRegistry
  • org.springframework.cloud.client.serviceregistry.ServiceRegistry
  • org.springframework.cloud.netflix.eureka.serviceregistry.EurekaServiceRegistry
  • org.springframework.cloud.netflix.eureka.serviceregistry.EurekaRegistration
  • org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration
  • org.springframework.cloud.netflix.eureka.EurekaClientConfigBean
  • org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean

其中LeaseManager以及LookupService是Eureka对于服务发现相干的操作定义的接口。前者定义了服务写操作相干的办法,后者定义了查问操作相干的办法。

LeaseManager(com.netflix.eureka.lease.LeaseManager)

该接口定义了应用服务实例在服务中心的几个操作方法:register、cansel、renew、evict。

public interface LeaseManager<T> {    // 用于注册服务实例信息    void register(T var1, int var2, boolean var3);    // 用于删除服务实例信息    boolean cancel(String var1, String var2, boolean var3);    // 用于与Eureka Server进行心跳操作,维持租约    boolean renew(String var1, String var2, boolean var3);    // 服务端的一个办法,用于剔除租约过期的服务实例信息    void evict();}

LookupService(com.netflix.discovery.shared.LookupService)

该接口定义了Eureka Client从服务中心获取服务实例的查询方法。

public interface LookupService<T> {    Application getApplication(String var1);    Applications getApplications();    List<InstanceInfo> getInstancesById(String var1);    InstanceInfo getNextServerFromEureka(String var1, boolean var2);}

这个接口次要是给Client端应用的,其中定义了获取所有利用信息、依据利用id获取所有服务实例,以及依据visualHostname应用round-robin形式获取下一个服务实例。

Eureka的设计理念

作为一个服务注册于发现核心,次要解决如下几个问题:

  1. 服务实例如何注册到服务中心

实质上就是在服务启动的时候,须要调用Eureka Server的REST API的register办法,去注册利用实例的信息。对于Java利用则是应用Eureka Client封装好的API去调用;对于Spring Cloud利用更加简略,能够应用spring-cloud-starter-netflix-eureka-client,基于Spring Boot的主动配置,主动帮你实现服务信息的注册。

  1. 服务实例如何从服务中心剔除

失常状况下服务实例在敞开利用的时候,应该通过狗子办法或其余生命周期回调办法去调用Eureka Server的REST API的de-register办法,来删除本身服务实例。另外对于服务实例挂掉或者其余异常情况没有及时删除本身信息的问题,Eureka Server要求Client端定时进行续约,也就是发送心跳,来证实服务实例是衰弱的,是能够调用的。如果租约超过肯定工夫没有进行续约操作,Eureka Server端会被动剔除。这一点Eureka Server采纳的就是分布式应用外面经典的心跳模式。

  1. 服务实例信息的一致性问题

因为服务注册以及发现核心可能是单点的,其本身必有个集群。上面次要分为AP优于CP、Peer to Peer架构、Zone以及Region设计,SELF PRESERVATION设计四个方面。

AP优于CP

分布式系统的CAP三个个性:

C(Consistency):数据一致性,即数据在存在多个正本的状况下,可能因为网络、机器故障、软件系统等问题导致数据写入局部正本胜利,局部正本失败,进而造成正本之间数据呈现不统一的,存在抵触。满足一致性则要求对数据进行更新操作之后,多正本的数据保持一致。

A(Availability):可靠性,在任何时候客户端对集群进行读写操作,求情都能失常响应,即在肯定的延时延时内实现。

P(Partition Tolerance):分区容错性,即产生通信故障的时候,整个集群被宰割为多个无奈相互通信的分区是,集群依然可用。

对于分布式系统来说,个别网络条件绝对不可控,呈现网络分区是不可避免的,因而零碎必须具备分区容错性。在这个前提下,分布式系统的设计则在CP和AP之间进行抉择。不能了解成CAP三者之间必须三选二,他们三者之间不是对等和能够相互替换的。在分布式畛域中,P是一个客观存在的事实,不可绕过,所以P与AC之间不是对等关系。

对于ZooKeeper,它就是CP的,但又不是严格的强一致性,比方客户端A提交了一个写操作,ZooKeeper在板书节点操作胜利之后返回,此时假如客户端B的读操作申请到的是A写操作尚未同步到的节点,那么读取到的就不是客户端A歇菜做胜利之后的数据。如果在应用的时候须要强一致性,则须要在读取数据的应用执行以下sync操作,即leader节点先同步下数据,这样能力保障强统一。在极其的状况下产生网络分区的时候,如果leader节点不在non-quorum分区,那么对这个分区上节点的读写操作申请将会报错,无奈满足Availability个性。

Eureka是在部署AWS的背景下设计的,其设计者认为,在云端特地是在大规模部署的状况下,失败是不可避免的,有可能是Eureka本身部署失败,注册的服务不可用,或者因为呈现网络分区导致服务不可用,因而不能回避这个问题。所以要在呈现了这些问题了,还要能持续提供服务注册以及发现性能,Eureka才抉择满足Availability(可靠性)。所以Eureka会保留可用及过期的数据,总比丢掉可用的数据好。这样的话,利用实例的注册信息在集群中的所有节点并不是强统一的,这就须要客户端可能反对负载平衡以及失败重试。在Netflix的生态外面,有ribbon提供这一个性能。

Peer to Peer架构

在分布式系统中数据存在过个正本之间的复制,复制形式可分为主从复制和对等复制。

  1. 主从复制

    主从复制也就是广为人知的Master-Slave模式,即有一个主正本,其余正本是从正本。所有数据的写操作都提交到主正本中,最初由主正本更新到其余从正本。有同步更新、异步更新、同步及异步混合。

  2. 对等复制

    即Peer to Peer模式,因为任何正本都能够接管写操作,不存在写操作的压力瓶颈。然而因为每个正本都能够进行写操作解决,个正本之间的数据同步以及抵触解决是一个比拟辣手的问题。

    Eureka采纳的就是Peer to Peer的复制模式。上面分为Client端和Server端两个角度。

    (1)Client端

    Client端个别通过上面的配置Eureka Server的peer节点:

    eureka:  client:    serviceUrl:      defaultZone: http://localhost:8761/eureka/,http://localhost:8762/eureka/

实际上代码里反对preferSameZoneEureka,即有多个分区的话,优先选择与利用实例所在分区一样的其余服务实例,如果没有找到默认应用defaultZone。客户端应用quarantineSet保护一个不可用的Eureka Server列表,进行申请的时候,优先从可用的列表中进行抉择,如果申请失败,则切换到下一Eureka Server实例进行空虚,重试次数默认是3。

另外为了避免每个Client都依照下面配置的Server列表指定的程序来拜访,而造成Erueka Server节点申请散布不均的状况,Client端有一个定时工作(默认5分钟执行一次)来刷洗并随机化Eureka Server的列表。

(2)Server端

Eureka Server自身就依赖了Eureka Client,因为每个Eureka Server是作为其余Eureka Server的Client。在单个Eureka Server启动的时候,会有一个syncUp的操作,通过Eureka Client申请其余Eureka Server节点中的一个节点获取注册的利用实例信息,而后复制到其余peer节点。

Eureka Server在执行复制操作的时候,应用HEADER_REPLICATION的http header来讲这个申请操作与一般的利用实例的失常操作辨别开来。通过HEADER_REPLICATION来标识是复制申请,这样其余peer节点接管到申请的时候,就不会对它的peer节点进行复制操作,从而防止死循环。

Eureka Server应用peer ot peer的复制模式,重点解决的是另外一个问题:数据复制抵触。解决办法:

  • lastDirtyTimestamp标识
  • heartbeat

针对数据的不统一,个别是通过版本号机制来解决的,最初在不同的正本之间判断申请复制数据的版本号与本地数据的版本号的高下就行了。Eureka没有间接应用版本号的属性,而是应用lastDirtyTimestamp的字段来比照。

如果开启了SyncWhenTimestampDiffers配置(默认开启),当lastDirtyTimestamp不为空的时候,就会做相应的解决:

  • 如果申请参数的lastDirtyTimestamp值大于Server本地该实例的lastDirtyTimstamp值,则示意EurekaServer之间的数据呈现抵触,这个时候就返回404,要求利用实例从新进行register操作。
  • 如果申请参数的lastDirtyTimestamp值小于Server本地该实例的lstDirtyTimestamp值,如果是peer节点的复制申请,则示意数据呈现抵触,返回409给peer节点,要求其同步本人最新的数据信息。

peer节点之间的互相复制不能保障所有操作都胜利,所以Eureka还要通过利用实例与Server之间的heartbeat也就是renewLease操作来进行数据的最终修复,如果发现利用实例数据与某个Server的数据呈现不统一,则Server返回404,利用实例从新进行register操作。

Zone及Region设计

Netflix的服务大部分在Amazon上,因而Eureka的设计有一部分也是基于Amazon的Zone及Region的基础设施之上。在AmazonEC2托管在寰球的各个中央,它用Region来代表一个独立的天文区域,比方Eureka Server默认设置了个Region : us-east-I、us - west-I、us-west-2、eu-west-1。

在每个Region上面,还分了多个AvailabilityZone,一个Region对应多个AvailabilityZone。每一个Region之间是互相独立以及隔离的,默认状况下资源只在单个Region之间的AvailabilityZone进行复制,跨Regin之间不会进行资源复制。Region和AvailabilityZone之间的关系如下:

SELF PRESERVATION设计

在分布式系统设计外面,通常须要对利用实例的存活进行健康检查,比拟要害的问题是要解决好网络抖动或短暂不可用时造成的误判。另外Eureka Server端与Client端之间如果呈现网络分区问题,在极其状况下会使得Eureka Server清空局部服务的实例列表,这个将重大影响到Eureka Server的Availability属性。因而Erueka Server引入了SELF PRESERVATION机制。
Eureka Client端与Server端之间有个租约,Client要定时发送心跳来维持这个租约,示意本人还存活着。Eureka通过以后注册的实倒数,去计算每分钟应该从利用实例接管到的心跳数,如果最近一分钟接管到的续约的次数小于等于指定阀值的话,则敞开租约生效剔除,禁止定时工作剔除生效的实例,从而爱护注册信息。

Eureka参数调优以及监控

上面次要分为Client端和Server端两大类进行简述,Eureka的几个外围参数

客户端参数

Client端的外围参数

参数默认值阐明
eureka.client.availability-zones告知Client有哪些region以及availability-zones,反对配置批改运行时失效
eureka.client.filter-only-up-instancestrue是否过滤出InstanceStatus为UP的实例
eureka.client.regionus-east-1指定该利用实例所在的region,AWS datacenters实用
eureka.client.register-with-eurekatrue是否将该利用实例注册到Eureka Server
eureka.client.prefer-szme-zone-eurekatrue是否优先应用与该实例处于雷同zone的Eureka Server
eureka.client.on-demand-update-status-changetrue是够将本地实例状态更新通过ApplicationInfoManager实时触发同步到Eureka Server
eureka.instance.metadata-map指定利用实例的元数据信息
eureka.instance.prefer-ip-addressfalse指定优先应用ip地址代替host name作为实例的hostName字段值
eureka.instance.lease-expiration-duration-in-seconds90指定Eureka Client距离多久须要向Eureka Server发送心跳来告知Eureka Server该实例还存活

定时工作参数

参数默认值阐明
eureka.client.cache-refresh-executor-thread-pool-size2刷新缓存的CacheRefreshThread的线程池大小
eureka.client.cache-refresh-executor-exponential-back-off-bound10(刷新缓存)调度工作执行超时时下次的调度的延迟时间
reka.client.heartbeat-executor-thread-pool-size2心跳线程HeartBeatThread的线程池大小
eureka.client.heartbeat-executor-exponential-back-off-bound10(心跳执行)调度工作超时时下次的调度的延时工夫
eureka.client.registry-fetch-interval-seconds30CacheRefreshThread线程调度频率
eureka.client.eureka-service-url-poll-interval-seconds5*60AsyncResolver.updateTask刷新Eureka Server地址的工夫距离
eureka.client.initial-instance-info-replication-interval-seconds40InstanceInfoReplicator将实例信息变更同步到Eureka Server的初始延时工夫
eureka.client.instance-info-replication-interval-seconds30InstanceInfoReplicator将实例信息变更同步到Eureka Server的工夫距离
ureka.instance.lease-renewal-interval-in-seconds30Eureka Client向Eureka Server发送心跳的工夫距离

http参数

Eureka Client底层httpClient与Eureka Server通信,提供的先关参数

参数默认值阐明
eureka.client.eureka-server-connect-timeout-seconds5连贯超时工夫
eureka.client.eureka-server-read-timeout-seconds8读超时工夫
eureka.client.eureka-server-total-connections200连接池最大流动连接数
eureka.client.eureka-server-total-connections-per-host50每个host能应用的最大链接数
eureka.client.eureka-connection-idle-timeout-seconds30连接池中链接的闲暇工夫

服务端端参数

次要蕴含这几类:基本参数、response cache参数、peer相参数、http参数

基本参数

参数默认值阐明
eureka.server.enable-self-perservationtrue是否开启自我保护模式
eureka.server.renewal-percent-threshold0.85指定每分钟须要收到续约次数的阈值
eureka.instance.registry.expected-number-of-renews-per-min1指定每分钟须要接管到的续约次数值,理论该值在其中被写死为count*2,另外也会被更新
eureka.server.renewal-threshold-update-interval-ms15分钟指定updateRenewalThreshold定时工作的调度频率,来动静更新expectedNumberOfRenewsPerMin及numberOfRenewsPerminThreshold值
eureka.server.eviction-interval-timer-in-ms60*1000指定EvictionTask定时工作的调度频率,用于剔除过期的实例

response cache参数

Eureka Server为了晋升本身REST API接口的性能,提供了两个缓存:一个是基于ConcurrentMap的readOnlyCacheMap,一个是基于Guava Cache的readWriteCacheMap。其相干参数如下:

参数默认值阐明
eureka.server.use-read-only-response-cachetrue是否应用只读的response-cache
eureka.server.response-cache-update-interval-ms30*1000设置CacheUpdateTask的调度工夫距离,用于从readWriteCacheMap更新数据到readOnlyCacheMap。仅仅在eureka.server.use-read-only-response-cache为true的时候失效
eureka.server.response-cache-auto-expiration-in-seconds180设置readWriteCacheMap的expireAfterWrite参数,指定写入多长时间过过期

peer相干参数

参数默认值阐明
eureka.server.peer-eureka-nodes-update-interval-ms10分钟指定peersUpdateTask调度的工夫距离,用于从配置文件刷新peerEurekaNodes节点的配置信息('eureka.client.serviceUrl相干zone的配置')
eureka.server.peer-eureka-status-refresh-time-interval-ms30*1000指定更新peer node状态信息的工夫距离

http参数

Eureka Server须要与其余peer节点进行通信,复制实例信息,其底层应用httpClient,提供相干的参数

参数默认值阐明
eureka.server.peer-node-connect-timeout-ms200连贯超时工夫
eureka.server.peer-node-read-timeout-ms200读超时工夫
eureka.server.peer-node-total-connections1000连接池最大流动连接数
eureka.server.peer-node-total-connections-per-host500每个host能应用的最大连接数
eureka.server.peer-node-connection-idle-timeout-seconds30连接池中连贯的闲暇工夫

参数调优

常见问题

1.为什么服务下线了,Eureka Server接口返回的信息还会存在?

2.为什么服务上线了,Eureka Client不能及时获取到?

3.为什么会有一下提醒:

EMERGENCY!EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY’RE NOT. RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUST TO BE SAFE

解决办法:

1.Eureka Server并不是强统一的,因而registry中会议保留过期的实例信息。起因如下:

  • 利用实例异样挂掉,没能在挂掉之前告知Eureka Server要下线掉该服务实例信息。这个就须要依赖Eureka Server的EvictionTask去剔除。
  • 利用实例下线是有告知Eureka Server下线,然而因为Eureka Server的REST API有response cache,因而须要期待缓存过期能力更新。
  • 因为Eureka Server开启并以入了SELF PRESERVATION(自我爱护)模式,导致registry的信息不会因为过期而被剔除掉,直到退出SELF PRESERVATION(自我爱护)模式。

针对Client下线而没有告诉Eureka Server的问题,能够调整EvictionTask的调度频率,比方把默认的工夫距离60s,调整为5s:

eureka:  server:    eviction-interval-timer-in-ms: 5000

针对response cache的问题,能够依据状况思考敞开readOnlyCacheMap:

eureka:  server:    use-read-only-response-cache: false

或者调整readWriteCacheMap的过期工夫:

eureka:  server:    response-cache-auto-expiration-in-seconds: 60

针对SELF PRESERVATION(自我爱护)的问题,在测试环境能够将enable-self-preservation设置为false:

eureka:  server:    enable-self-preservation: false

敞开之后会提醒:

THE SELF PRESER VAT ION MODE IS TURNED OFF.  THIS MAY NOT PRO TECT INSTANCE EXPIRY IN CASE OF NETWORK/OTHER PROBLEMS.

或者:

RENEWALS ARE LESSER THAN THE THRESHOLD.THE SELF PRESERVATION MODE IS TURNED OFF.THIS MAY  NOT PROTECT INSTANCE EXPIRY IN CASE OF NETWORK/OTHER PROBLEMS.

2.针对新服务上线,Eureka Client获取不及时的问题,在测试环境,能够适当进步client端拉取Server注册信息的频率,例如上面将默认的30s改为5s:

eureka:  client:    registry-fetch-interval-seconds: 5

3.在理论生产过程中,常常会有网络抖动等问题造成服务实例与Eureka Server的心跳未能如期放弃,然而服务实例自身是衰弱的,这个时候如果依照租约剔除机制剔除的话,会造成误判无果大范畴误判的话,可能导致整个服务注册列表的大部分注册信息被删除,从而没有可用服务。Eureka为了解决这个问题引入了SELF PRESERVATION机制,当最近一分钟接管到的租约次数小于等于指定阈值的话,则敞开租约生效剔除,禁止定时工作生效的实例,从而爱护注册信息。

在生产环境下,能够吧renewwalPercentThreshold及leaseRenewalIntervalInSeconds参数调小一点,从而进步触发SELF PRESERVATION机制的阈值。

eureka:  instance:    lease-renewal-interval-in-seconds: 10 #默认是30    renewal-percent-threshold: 0.49       #默认是0.85

监控指标

Eureka内置了基于servo的指标统计,具体在com.netflix.eureka.util.EurekaMonitors。Spring Boot 2.x版本改为应用Micrometer,不再反对Neflix Servo,转而反对Neflix Servo的替代品Neflix Spectator。不过对于Servo,能够通过DefaultMonitorRegistry.getInstance().getRegisteredMonitors来获取所有注册了的Monitor,进而获取其指标值。

//// Source code recreated from a .class file by IntelliJ IDEA// (powered by Fernflower decompiler)//package com.netflix.eureka.util;import com.netflix.appinfo.AmazonInfo;import com.netflix.appinfo.ApplicationInfoManager;import com.netflix.appinfo.DataCenterInfo;import com.netflix.appinfo.AmazonInfo.MetaDataKey;import com.netflix.appinfo.DataCenterInfo.Name;import com.netflix.servo.DefaultMonitorRegistry;import com.netflix.servo.annotations.DataSourceType;import com.netflix.servo.annotations.Monitor;import com.netflix.servo.monitor.Monitors;import java.util.concurrent.atomic.AtomicLong;public enum EurekaMonitors {    // 自启动以来收到的总续约次数    RENEW("renewCounter", "Number of total renews seen since startup"),    // 自启动以来收到的总勾销租约次数    CANCEL("cancelCounter", "Number of total cancels seen since startup"),    // 自启动以来查问registry的总次数    GET_ALL_CACHE_MISS("getAllCacheMissCounter", "Number of total registery queries seen since startup"),    // 自启动以来delta查问registry的总次数    GET_ALL_CACHE_MISS_DELTA("getAllCacheMissDeltaCounter", "Number of total registery queries for delta seen since startup"),    // 自启动以来应用remote region查问registry的总次数    GET_ALL_WITH_REMOTE_REGIONS_CACHE_MISS("getAllWithRemoteRegionCacheMissCounter", "Number of total registry with remote region queries seen since startup"),  // 自启动以来应用remote region及delta形式查问registry的总次数  GET_ALL_WITH_REMOTE_REGIONS_CACHE_MISS_DELTA("getAllWithRemoteRegionCacheMissDeltaCounter", "Number of total registry queries for delta with remote region seen since startup"),    // 自启动以来查问delta的总次数    GET_ALL_DELTA("getAllDeltaCounter", "Number of total deltas since startup"),    // 自启动以来传递regions查问delta的总次数    GET_ALL_DELTA_WITH_REMOTE_REGIONS("getAllDeltaWithRemoteRegionCounter", "Number of total deltas with remote regions since startup"),    // 自启动以来查问'/{version}/apps'的次数    GET_ALL("getAllCounter", "Number of total registry queries seen since startup"),    // 自启动以来传递regions参数查问'/{version}/apps'的次数    GET_ALL_WITH_REMOTE_REGIONS("getAllWithRemoteRegionCounter", "Number of total registry queries with remote regions, seen since startup"),    // 自启动以来申请/{version}/apps/{appId}的总次数    GET_APPLICATION("getApplicationCounter", "Number of total application queries seen since startup"),    // 自启动以来register的总次数    REGISTER("registerCounter", "Number of total registers seen since startup"),    // 自启动以来剔除过期实例的总次数    EXPIRED("expiredCounter", "Number of total expired leases since startup"),    // 自启动以来statusUpdate的总次数    STATUS_UPDATE("statusUpdateCounter", "Number of total admin status updates since startup"),    // 自启动以来deleteStatusOverride的总次数    STATUS_OVERRIDE_DELETE("statusOverrideDeleteCounter", "Number of status override removals"),    // 自启动以来收到cancel申请时对应实例找不到的次数    CANCEL_NOT_FOUND("cancelNotFoundCounter", "Number of total cancel requests on non-existing instance since startup"),    // 自启动以来收到renew申请时对应实例找不到的次数    RENEW_NOT_FOUND("renewNotFoundexpiredCounter", "Number of total renew on non-existing instance since startup"),    REJECTED_REPLICATIONS("numOfRejectedReplications", "Number of replications rejected because of full queue"),    FAILED_REPLICATIONS("numOfFailedReplications", "Number of failed replications - likely from timeouts"),    // 因为开启rate limiter被抛弃的申请数量    RATE_LIMITED("numOfRateLimitedRequests", "Number of requests discarded by the rate limiter"),    // 如果开启rate limiter的话,将被抛弃的申请数    RATE_LIMITED_CANDIDATES("numOfRateLimitedRequestCandidates", "Number of requests that would be discarded if the rate limiter's throttling is activated"),    // 开启rate limiter时申请全量registry被抛弃的申请数    RATE_LIMITED_FULL_FETCH("numOfRateLimitedFullFetchRequests", "Number of full registry fetch requests discarded by the rate limiter"),    // 如果开启rate limiter时申请全量registry将被抛弃的申请数    RATE_LIMITED_FULL_FETCH_CANDIDATES("numOfRateLimitedFullFetchRequestCandidates", "Number of full registry fetch requests that would be discarded if the rate limiter's throttling is activated");    private final String name;    private final String myZoneCounterName;    private final String description;    @Monitor(        name = "count",        type = DataSourceType.COUNTER    )    private final AtomicLong counter = new AtomicLong();    @Monitor(        name = "count-minus-replication",        type = DataSourceType.COUNTER    )    private final AtomicLong myZoneCounter = new AtomicLong();    private EurekaMonitors(String name, String description) {        this.name = name;        this.description = description;        DataCenterInfo dcInfo = ApplicationInfoManager.getInstance().getInfo().getDataCenterInfo();        if (dcInfo.getName() == Name.Amazon) {            this.myZoneCounterName = ((AmazonInfo)dcInfo).get(MetaDataKey.availabilityZone) + "." + name;        } else {            this.myZoneCounterName = "dcmaster." + name;        }    }    public void increment() {        this.increment(false);    }    public void increment(boolean isReplication) {        this.counter.incrementAndGet();        if (!isReplication) {            this.myZoneCounter.incrementAndGet();        }    }    public String getName() {        return this.name;    }    public String getZoneSpecificName() {        return this.myZoneCounterName;    }    public String getDescription() {        return this.description;    }    public long getCount() {        return this.counter.get();    }    public long getZoneSpecificCount() {        return this.myZoneCounter.get();    }    public static void registerAllStats() {        EurekaMonitors[] var0 = values();        int var1 = var0.length;        for(int var2 = 0; var2 < var1; ++var2) {            EurekaMonitors c = var0[var2];            Monitors.registerObject(c.getName(), c);        }    }    public static void shutdown() {        EurekaMonitors[] var0 = values();        int var1 = var0.length;        for(int var2 = 0; var2 < var1; ++var2) {            EurekaMonitors c = var0[var2];            DefaultMonitorRegistry.getInstance().unregister(Monitors.newObjectMonitor(c.getName(), c));        }    }}

Eureka实战

Eureka Server在线扩容

筹备工作:创立cloud工程

创立cloud-config-server子模块。

    <dependencies>        <dependency>            <groupId>org.springframework.cloud</groupId>            <artifactId>spring-cloud-config-server</artifactId>        </dependency>    </dependencies>

启动类

package com.msr.batter.config;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.config.server.EnableConfigServer;@SpringBootApplication@EnableConfigServerpublic class ConfigApplication {    public static void main(String[] args) {        SpringApplication.run(ConfigApplication.class, args);    }}

配置文件:resources/bootstrap.yml

spring:  profiles:    active: native  application:    name: config-serverserver:  port: 8888

这里不是次要解说Spring Cloud Config内容。所以对于配置文件应用native的profile,也就是应用文件来存储配置,默认是放在resources/config目录下。在Eureka Server和Eureka Client模块别离引入spring-cloud-starter-config。别离都创立一个ServerControllerClientController

Eureka Client的配置文件:resources/config/eureka-client.yml

server:  port: 8081spring:  application:    name: cloud-eureka-client1eureka:  client:    serviceUrl:      defaultZone: http://localhost:8761/eureka/ # 一个Eureka Server

Eureka Server配置文件:resources/config/eureka-server-peer1.yml

server:  port: 8761spring:  application:    name: cloud-eureka-servereureka:  instance:    hostname: localhost    preferIpAddress: true  client:    registerWithEureka: true    fetchRegistry: true    serviceUrl:      defaultZone: http://localhost:8761/eureka/ # 一个Eureka Server  server:    waitTimeInMsWhenSyncEmpty: 0    enableSelfPreservation: false

cloud-eureka-server模块

        <!--    增加    -->        <dependency>            <groupId>org.springframework.cloud</groupId>            <artifactId>spring-cloud-starter-config</artifactId>        </dependency>

ServerController

package com.msr.better.registry.controller;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.cloud.netflix.eureka.EurekaClientConfigBean;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RestController;@RestController@RequestMapping("query")public class ServerController {    @Autowired    EurekaClientConfigBean eurekaClientConfigBean;    @GetMapping("eureka-server")    public Object getEurekaServerUrl() {        return eurekaClientConfigBean.getServiceUrl();    }}

配置文件:resources/bootstrap.yml

spring:  application:    name: cloud-eureka-server  cloud:    config:      uri: http://localhost:8888management:  endpoints:    web:      exposure:        include: '*'

配置文件:resources/application.yml

eureka:  server:    peer-eureka-nodes-update-interval-ms: 10000 #默认是10分钟即600000,这里为了验证改为10秒

cloud-erueka-client子模块

        <!--    增加    --><dependency>    <groupId>org.springframework.cloud</groupId>    <artifactId>spring-cloud-starter-config</artifactId></dependency>

ClientController

package com.msr.better.client.controller;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.cloud.client.ServiceInstance;import org.springframework.cloud.client.discovery.DiscoveryClient;import org.springframework.cloud.netflix.eureka.EurekaClientConfigBean;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PathVariable;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import java.util.HashMap;import java.util.List;import java.util.Map;/** * @author MaiShuRen * @date 2020-10-23 */@RestController@RequestMapping("query")public class ClientController {    @Autowired    private DiscoveryClient discoveryClient;    @Autowired    private EurekaClientConfigBean clientConfigBean;    /**     * 服务实例信息     *     * @return     */    @GetMapping("info")    public Map<String, Object> info() {        Map<String, Object> map = new HashMap<>(16);        List<String> services = discoveryClient.getServices();        String description = discoveryClient.description();        int order = discoveryClient.getOrder();        map.put("services", services);        map.put("description", description);        map.put("order", order);        return map;    }    /**     * 依据服务名称获取服务实例列表     *     * @param name 服务名     * @return 返回后果     */    @GetMapping("instance/{name}")    public List<ServiceInstance> getIns(@PathVariable String name) {        return discoveryClient.getInstances(name);    }    /**     * 获取Eureka Server的url     *     * @return     */    @GetMapping("eureka-server")    public Object getServiceUrl() {        return clientConfigBean.getServiceUrl();    }}

配置文件:resources/bootstrap.yml

spring:  application:    name: cloud-eureka-client  cloud:    config:      uri: http://localhost:8888management:  endpoints:    web:      exposure:        include: '*'

费别启动cloud-config-servercloud-eureka-server(应用peer1的profile)、cloud-eureka-client

mvn spring-boot:run 在cloud-config-server目录下执行,启动cloud-config-server服务

mvn spring-boot:run -Dspring-boot.run.profiles=peer1 在cloud-eureka-server目录下执行,启动cloud-eureka-server

mvn spring-boot:run 在cloud-eureka-client目录下执行,启动cloud-eureka-client

扩容两个Eureka Server

cloud-config-server下新增配置cloud-eureka-server-peer2.ymlcloud-eureka-server-peer3.yml

cloud-eureka-server-peer2.yml

server:  port: 8762eureka:  instance:    hostname: localhost    preferIpAddress: true  client:    registerWithEureka: true    fetchRegistry: true    serviceUrl:      defaultZone: http://localhost:8761/eureka/,http://localhost:8763/eureka/  server:    waitTimeInMsWhenSyncEmpty: 0    enableSelfPreservation: false

启动:mvn spring-boot:run -Dspring-boot.run.profiles=peer2

cloud-eureka-server-peer3.yml

server:  port: 8763eureka:  instance:    hostname: localhost    preferIpAddress: true  client:    registerWithEureka: true    fetchRegistry: true    serviceUrl:      defaultZone: http://localhost:8761/eureka/,http://localhost:8762/eureka/  server:    waitTimeInMsWhenSyncEmpty: 0    enableSelfPreservation: false

启动:mvn spring-boot:run -Dspring.profiles.active=peer3

批改一下cloud-eureka-client.ymlcloud-eureka-server-peer1.yml的配置文件

cloud-eureka-server-peer1.yml的批改

server:  port: 8761eureka:  instance:    hostname: localhost    preferIpAddress: true  client:    registerWithEureka: true    fetchRegistry: false    serviceUrl:      defaultZone: http://localhost:8762/eureka/,http://localhost:8763/eureka/  server:    waitTimeInMsWhenSyncEmpty: 0    enableSelfPreservation: false

cloud-eureka-client.yml的批改

server:  port: 8081spring:  application:    name: cloud-eureka-client1eureka:  client:    serviceUrl:      efaultZone: http://localhost:8761/eureka/,http://localhost:8762/eureka/,http://localhost:8763/eureka/

而后重新启动cloud-config-server,使批改的配置失效。而后应用Postman这类型工具通过POST申请去拜访http://localhost:8081/actuato...cloud-eureka-clientcloud-eureka-server-peer1。或者应用上面的命令

curl -i -X POST localhost:8081/actuator/refresh

curl -i -X POST localhost:8761/actuator/refresh

而后在浏览器或者应用curl命令去申请,之前cloud-config-server写好的一个接口:http://localhost:8081/query/e...

{"defaultZone": "http://localhost:8761/eureka/,