Eureka深入

作者: 二月_春风 | 来源:发表于2017-07-17 17:49 被阅读306次

基础架构

  • 服务注册中心:Eureka提供的服务端,提供服务注册与发现的功能,也就是上一节的eurekaservice-server服务。
  • 服务提供者:提供服务的应用,可以是springboot应用,也可以是其他技术平台且遵循Eureka通信机制的应用。它将自己提供的服务注册到Eureka,以供其它应用发现,也就是上一节中我们实现的user-service
  • 服务消费者:消费者应用从服务注册中心获取服务列表,从而使消费者可以知道去何处调用其所需要的服务,上一节中使用了Ribbon来实现服务消费,后续还会使用fegin的消费方式

很多时候,客户端即是服务提供者也是服务消费者,因为一个服务即要给别的服务调用本身也要调用其他的服务。

服务治理机制

服务提供者

服务注册
“服务提供者”在启动的时候会通过发送Rest请求的方式将自己注册搭配Eureka Server上,同时带上了自身服务的一些元数据信息。Eureka Server接收到这个Rest请求之后,将元数据信息存储到一个双层结构的Map中,其中第一层的key是服务名,第二层的key是具体服务的实例名。在服务注册时候,需要确认一下 eureka.client.register-with-eureka=true参数是否正确,该值默认为true。若设置为false将不会启动注册操作。

服务同步
二个服务提供者分别注册到了两个不同的服务注册中心上,也就是说,它们的信息分别被两个服务注册中心所维护。此时,由于服务注册中心之间因为互相注册为服务,当服务提供者发送注册请求到一个服务注册中心时,它会将请求转发给集群中相连的其它注册中心,从而实现注册中心之间的服务同步。通过服务同步,两个服务提供者的服务信息就可以通过这两台服务注册中心的任意一台获取到。

服务续约
在注册服务之后,服务提供者会维护一个心跳用来告诉Eureka Server:“我还活着”,以防止Eureka Server的“剔除任务”将该服务实例从服务列表中排出出去,我们称该操作为服务续约(Renew)。
关于服务续约有两个重要属性,我们可以关注并根据需要来进行调整:

eureka.instance.lease-renewal-interval-in-seconds=30
eureka.instance.lease-expiration-duration-in-seconds=90

eureka.instance.lease-renewal-interval-in-seconds参数用于定义服务续约任务的调用间隔时间,默认为30s。
eureka.instance.lease-expiration-duration-in-seconds参数用于定义服务失效的时间,默认为90s。

官网对这一段的解释:
链接地址

Why is it so Slow to Register a Service?
Being an instance also involves a periodic heartbeat to the registry (via the client’s serviceUrl) with default duration 30 seconds. A service is not available for discovery by clients until the instance, the server and the client all have the same metadata in their local cache (so it could take 3 heartbeats). You can change the period using eureka.instance.leaseRenewalIntervalInSeconds and this will speed up the process of getting clients connected to other services. In production it’s probably better to stick with the default because there are some computations internally in the server that make assumptions about the lease renewal period.

稍微翻译一下:
作为一个实例还要定期(一般默认是30s)发送心跳到注册中心(通过client serviceUrl)。当服务端和客户端都包含相同的本地缓存(一般是3个心跳)都没有接收心跳即说明这个服务不可用了。我们可以通过eureka.instance.leaseRenewalIntervalInSeconds来修改这个发送心跳的间隔去改变客户端连接其他服务端的过程。但是在生产中一般不建议去修改这个默认值,因为这个30s是通过一系列的复杂计算得到的,也就是说这个30s是最佳实践。

服务消费者

  • 获取服务
    我们启动服务消费者的时候,会发送一个Rest请求给服务注册中心,来获取上面注册的服务清单。为了性能考虑。Eureka Server会维护一份只读的服务清单来返回给客户端,同时该缓存清单会每隔30s更新一次。
    获取服务是服务消费者的基础,必须保证eureka.client.fetch-registry=true。若希望修改缓存清单的更新时间,可以通过eureka.client.registry-fetch-interval-secords=30参数进行修改,该参数默认值为30s。

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

对于访问实例的选择,Eureka中有Region和Zone的概念,一个Region中可以包含多个Zone,每个服务客户端需要被注册到一个Zone中,所以每个客户端对应一个Region和一个Zone。在进行服务调用的时候,优先访问同一个Zone中的服务提供方,若访问不到,就访问其他的Zone。

  • 服务下线
    在系统运行过程中必然会面临关闭或重启服务的某个实例的情况,在服务关闭期间,我们自然不希望客户端会继续调用关闭了的实例,所以在客户端程序中,当服务实例进行正常的关闭操作时,他会触发一个服务下线的Rest请求给Eureka Server,告诉服务注册中心:”我要下线了"。服务端在接收到请求之后,将该服务状态置为下线(Down),并把该下线事件传播出去。

这一步骤我使用kill -9 进程并没有去告诉客户端这个事件。怎样处理?
因为不要使用kill -9命令,直接使用kill 进程号来关闭服务。

服务注册中心

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

  • 自我保护
    当我们在本地调试的基于Eureka的程序时,基本上都会碰到这样一个问题,在服务注册中心的信息面板上出现类似下面的红色警告信息:

自我保护

实际上,该警告就是触发了Eureka Server的自我保护机制。服务注册到Eureka Server之后,会维护一个心跳连接,告诉Eureka Server自己还活着。Eureka Server在运行期间,会统计心跳失败的比例在15分钟之内是否低于85%,如果出现低于的情况(在单机调试的时候很容器满足,实际在生产环境上通常是由于网络不稳定导致),Eureka Server会将当前的实例注册信息保护起来,让这些实例不会过期,尽可能保护这些注册信息。但是,在这段保护器件内实例若出现问题,那么客户端很容器拿到实际已经不存在的服务实例,会出现调用失败的情况,所以客户端必须要有容错机制,比如可以使用请求重试,断路器等机制。

由于本地调试很容易触发注册中心的保护机制,这会使得注册中心维护的服务实例不那么准确。所以,我们在本地进行开发的时候,可以使用eureka.server.enable-self-preservation=false参数来关闭保护机制,以确保注册中心可以将不可用的实例正确剔除。

配置详解

在Eureka 的服务治理体系中,主要分为服务端和客户端两个不同的角色、,服务端为服务注册中心,客户端为各个接口的微服务应用。
Eureka客户端的配置主要分为以下两个方面:

  • 服务注册相关的配置信息,包含服务注册中心的地址,服务获取的间隔时间,可用区域等。
  • 服务实例相关的配置信息,包括服务实例的名称,ip地址,端口号,健康检查路径等。

Eureka服务端更多的类似于一个现成产品,大多数情况下,我们不需要修改它的配置信息。有兴趣自己可以查看org.springframework.cloud.netflix.eureka.server.EurekaServerConfigBean进一步学习,这些配置都以eureka.server作为前缀。自己学习的时候也可以关闭Eureka的自我保护模式(eureka.server.enable-self-preservation=false),以防止关闭的实例无法被服务注册中心剔除的问题。

服务注册配置

关于服务注册类的配置信息,我们可以通过查询org.springframework.cloud.netflix.eureka.server.EurekaServerConfigBean进一步学习。

指定注册中心
将一个springboot服务纳入到Eureka的服务治理体系中,除了引入Eureka的依赖之外,就是在配置文件中指定注册中心,主要通过eureka.client.serviceUrl参数实现。该参数址存储在HashMap类型中,并且设置有一组默认值。默认的key为defaultZone,value是http://localhost:8761/eureka/

private Map<String, String> serviceUrl = new HashMap();

构造函数

public EurekaClientConfigBean() {
     this.serviceUrl.put("defaultZone", "http://localhost:8761/eureka/");
     this.gZipContent = true;
     this.useDnsForFetchingServiceUrls = false;
     this.registerWithEureka = true;
     this.preferSameZoneEureka = true;
     this.availabilityZones = new HashMap();
     this.filterOnlyUpInstances = true;
     this.fetchRegistry = true;
     this.dollarReplacement = "_-";
     this.escapeCharReplacement = "__";
     this.allowRedirects = false;
     this.onDemandUpdateStatusChange = true;
      this.clientDataAccept = EurekaAccept.full.name();
}
    
    
//serviceUrls如果我们没有指定的话会默认的使用defaultZone,并且其默认值是http://localhost:8761/eureka/
public List<String> getEurekaServerServiceUrls(String myZone) {
        String serviceUrls = (String)this.serviceUrl.get(myZone);
        if(serviceUrls == null || serviceUrls.isEmpty()) {
            serviceUrls = (String)this.serviceUrl.get("defaultZone");
}

        if(!StringUtils.isEmpty(serviceUrls)) {
            String[] serviceUrlsSplit = StringUtils.commaDelimitedListToStringArray(serviceUrls);
            List<String> eurekaServiceUrls = new ArrayList(serviceUrlsSplit.length);
            String[] var5 = serviceUrlsSplit;
            int var6 = serviceUrlsSplit.length;

      for(int var7 = 0; var7 < var6; ++var7) {
                String eurekaServiceUrl = var5[var7];
                if(!this.endsWithSlash(eurekaServiceUrl)) {
                    eurekaServiceUrl = eurekaServiceUrl + "/";
        }

      eurekaServiceUrls.add(eurekaServiceUrl);
 }

     return eurekaServiceUrls;
} else {
     return new ArrayList();
}
}

下面整理了org.springframework.cloud.netflix.eureka.server.EurekaClientConfigBean中常用配置参数以及对应的说明和默认值,这些参数均以eureka.client为前缀。

@ConfigurationProperties("eureka.client")
public class EurekaClientConfigBean implements EurekaClientConfig, EurekaConstants {
    public static final String PREFIX = "eureka.client";
    @Autowired(
        required = false
    )
    PropertyResolver propertyResolver;
    public static final String DEFAULT_URL = "http://localhost:8761/eureka/";
    public static final String DEFAULT_ZONE = "defaultZone";
    private static final int MINUTES = 60;
    //启用Eureka客户端,默认值是true
    private boolean enabled = true;
    private EurekaTransportConfig transport = new CloudEurekaTransportConfig();
    //从Eureka服务端获取注册信息的间隔时间,间隔为s
    private int registryFetchIntervalSeconds = 30;
    //更新实例信息的变化到Eureka服务端的间隔时间,单位为s
    private int instanceInfoReplicationIntervalSeconds = 30;
    //初始化实例信息到Eureka服务端的间隔时间,单位为s
    private int initialInstanceInfoReplicationIntervalSeconds = 40;
    //轮询Eureka服务端地址更改的间隔时间,单位为s。当我们与Springcloud Config配合,动态刷新Eureka的serviceURL地址时需要关注此参数
    private int eurekaServiceUrlPollIntervalSeconds = 300;
    private String proxyPort;
    private String proxyHost;
    private String proxyUserName;
    private String proxyPassword;
    //读取Eureka Server信息的超时时间,单位是s
    private int eurekaServerReadTimeoutSeconds = 8;
    //连接EurekaServer的超时时间,单位是s
    private int eurekaServerConnectTimeoutSeconds = 5;
    private String backupRegistryImpl;
    //从Eureka客户端到所有Eureja服务端的连接总数
    private int eurekaServerTotalConnections = 200;
    //从Eureka客户端到每隔Eureka服务端主机的连接总数
    private int eurekaServerTotalConnectionsPerHost = 50;
    private String eurekaServerURLContext;
    private String eurekaServerPort;
    private String eurekaServerDNSName;
    private String region = "us-east-1";
    //Eureka服务端连接的空闲关闭时间,单位是s
    private int eurekaConnectionIdleTimeoutSeconds = 30;
    private String registryRefreshSingleVipAddress;
    //心跳连接池的初始化线程数
    private int heartbeatExecutorThreadPoolSize = 2;
    //心跳超时重试延迟时间的最大乘数值
    private int heartbeatExecutorExponentialBackOffBound = 10;
    //缓存刷新线程池的初始化线程数
    private int cacheRefreshExecutorThreadPoolSize = 2;
    //缓存刷新重试延迟时间的最大乘数值
    private int cacheRefreshExecutorExponentialBackOffBound = 10;
    private Map<String, String> serviceUrl = new HashMap();
    private boolean gZipContent;
    //使用dns来获取Eureka服务端的serviceUrl,默认是false
    private boolean useDnsForFetchingServiceUrls;
    //是否要将自身的实例信息注册到Eureka服务端,默认值是true,在构造函数中会赋值为true
    private boolean registerWithEureka;
    //是否偏好使用处于相同Zone的Eureka服务端,默认值是true
    private boolean preferSameZoneEureka;
    private boolean logDeltaDiff;
    private boolean disableDelta;
    private String fetchRemoteRegionsRegistry;
    private Map<String, String> availabilityZones;
    //获取实例时是否过滤,仅保留UP状态的实例
    private boolean filterOnlyUpInstances;
    //是否从Eureka服务端获取注册信息
    private boolean fetchRegistry;
    private String dollarReplacement;
    private String escapeCharReplacement;
    private boolean allowRedirects;
    private boolean onDemandUpdateStatusChange;
    private String encoderName;
    private String decoderName;
    private String clientDataAccept;

服务实例类配置

关于服务实例类的配置信息,可以通过查看org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean的源码来获取详细内容,这些配置信息都以eureka.instance为前缀。

元数据
org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean的配置信息中,有一大部分内容都是对服务实例元数据的配置,什么是服务实例元数据呢?
它是Eureka客户端在向服务注册中心发送注册请求时,用来描述自身服务信息的对象,其中包含了一些标准的元数据,比如服务名称,实例名称,实例ip,实例端口等用于服务治理的重要信息,以及一些用于负载均衡策略或是其他特殊用途的自定义元数据信息。

在使用spring cloud Eureka的时候,所有的配置信息都通过org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean进行加载,但在真正进行服务注册的时候,还是会包装成com.netflix.appinfo.InstanceInfo对象发送给Eureka服务端。这二个类非常类似,我们可以直接查看com.netflix.appinfo.InstanceInfo类详细了解原生Eureka对元数据的定义。

其中

@XStreamAlias("metadata")
private volatile Map<String, String> metadata = new ConcurrentHashMap<String, String>();

是自定义的元数据信息,而其他成员变量则是标准化的元数据信息。

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

eureka.instance.metadataMap.zone=miaozhihao

实例名配置
实例名,即com.netflix.appinfo.InstanceInfo中的instanceId参数,它是区分同一服务中不同实例的唯一标识。
Netfilx Eureka 的原生实现中,实例名采用主机名作为默认值,这样的设置使得在同一主机上无法启动多个相同的服务实例。所以,在spring cloud Eureka的配置中,针对同一个主机中启动多个实例的情况,对实例名的默认命名做了更多合理的扩展,它采用了如下的默认规则:

${spring.cloud.client.hostname}:${spring.application.name}:${spring.application.instance_id:${server.port}

对于实例名的命名规则,我们可以通过eureka.instance.instance-id参数进行设置。

小技能
同一个服务以不同的端口启动,可以指定

server.port=${random.int[10000,19999]}。

设置实例名:

eureka.instance.instance-id=${spring.application.name}:${spring.cloud.client.ipAddress}:${spring.application.instance_id:${random.int}}

端点配置
InstanceInfo中,我们可以看到一些URL的配置信息,比如homePageUrlstatusPageUrlhealthCheckUrl,它们分别代表了应用主页的url,状态页的url,健康检查的url。其中,状态页和健康检查的url在springcloud Eureka中默认使用了spring-boot-actuator模块提供的/info端点和/health端点。为确保服务的正常运行,我们必须确保Eureka客户端的/health端点在发送元数据的时候,是一个能够被注册中心访问的地址,否则服务注册中心不会根据应用的健康检查来更改状态(仅当开启了healthcheck功能时,以该端点信息作为健康检查标准)。而/info端点如果不正确的话,会导致Eureka面板中单击服务实例时,无法访问到服务实例提高的信息接口。

大多数情况下,我们不需要修改这几个url的配置,但是在一些特殊情况下,比如为应用设置了context-path,这时,所有spring-boot-actuator模块的监控端点都会增加一个前缀。所以我们就需要做如下的配置,为/info/health端点也加上类似的前缀信息:

management.context-path=/hello
eureka.instance.statusPageUrlPath=${management.context-path}/info
eureka.instance.healthCheckUrlPath=${management.context-path}/health

另外,为了安全考虑,也有可能会修改/info/health端点的原始路径。这个时候,我们也需要做一些特殊的配置:

endpoints.info.path=/appInfo
endpoints.health.path=/checkHealth

eureka.instance.statusPageUrlPath=/${endpoints.info.path}
eureka.instance.healthCheckUrlPath=/${endpoints.health.path}

上面都是使用相对路径进行配置的,由于Eureka的服务注册中心默认会以http的方式访问和暴露这些端点,因此客户端应用以https的方式来暴露服务和监控端点的时候,相对路径无法满足需求了,还可以使用下面的绝对路径:

eureka.instance.statusPageUrl=https://${eureka.instance.hostname}/info
eureka.instance.healthCheckUrl=https://${eureka.instance.hostname}/health
eureka.instance.homePageUrl=https://${eureka.instance.hostname}/

健康检查
默认情况下,Eureka中各个服务实例的健康检测并不是通过spring-boot-starter-actuator模块的/health端点来实现的,而是通过客户端心跳的方式来保持服务实例的存活。在Eureka的服务续约与剔除机制下,客户端的健康状态从注册到注册中心开始都会处于UP状态,心跳终止一段时间后,服务注册中心将其剔除。

默认的心跳方式可以有效的检测客户端进程是否正常运行,但是无法保证客户端应用能够正常提供服务。由于大多数微服务应用都会有一些其他的外部资源依赖,比如数据库,缓存,消息代理,如果这些服务宕掉的话,实际上微服务是无法对外提供服务接口的,但是心跳检测无法检测到。

在spring cloud Eureka中,我们可以将其交给各个微服务的spring-boot-actuator模块的/health端点来进行健康检测。
在各个客户端的/health中没有加特殊处理,如果加入特殊处理,Eureka也要加入特殊的处理,参考上面的所讲。

8761
8762

org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean中定义的配置参数以及对应的说明和默认值,这些参数均以eureka.instance为前缀。

参数名 说明 默认值
preferIpAddress 是否优先使用ip地址作为主机名的标识 false
leaseRenewalIntervalInSeconds Eureka客户端向服务端发送心跳的时间间隔,单位为s 30
leaseExpirationDurationInSeconds Eureka服务端在收到最后一次心跳之后等待的时间上限,单位为s,超过该时间剔除该服务 90
nonSecurePort 非安全的通信端口号 80
securePort 安全的通信端口号 443
nonSecurePortEnabled 是否启用非安全的通信端口号 true
securePortEnabled 是否启用安全的通信端口号
appname 服务名称,默认取spring.application.name的配置值,如果没有则去unkown
hostname 主机名,不配置的时候将根据操作系统的主机名来获取

上面的参数除了前三个需要配置,其他的大多数不需要配置,默认值即可。

相关文章

网友评论

    本文标题:Eureka深入

    本文链接:https://www.haomeiwen.com/subject/dhdqkxtx.html