spring cloudspringcloud学习

Eureka深入

2017-07-17  本文已影响306人  二月_春风

基础架构

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

服务治理机制

服务提供者

服务注册
“服务提供者”在启动的时候会通过发送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是最佳实践。

服务消费者

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

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

服务注册中心

自我保护

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

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

配置详解

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

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 主机名,不配置的时候将根据操作系统的主机名来获取

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

上一篇 下一篇

猜你喜欢

热点阅读