springcloud读书笔记一 Eureka

如何使用

启动注册中心集群

启动服务提供者客户端注册

Eureka中的概念

自我保护机制

默认情况下,如果Eureka Server在一定时间内(默认90秒)没有接收到某个微服务实例的心跳,Eureka Server将会移除该实例。但是当网络分区故障发生时,微服务与Eureka Server之间无法正常通信,而微服务本身是正常运行的,此时不应该移除这个微服务,所以引入了自我保护机制。

自我保护模式正是一种针对网络异常波动的安全保护措施,使用自我保护模式能使Eureka集群更加的健壮、稳定的运行。

自我保护机制的工作机制是如果在15分钟内超过85%的客户端节点都没有正常的心跳,那么Eureka就认为客户端与注册中心出现了网络故障,Eureka Server自动进入自我保护机制,此时会出现以下几种情况:

1、Eureka Server不再从注册列表中移除因为长时间没收到心跳而应该过期的服务。
2、Eureka Server仍然能够接受新服务的注册和查询请求,但是不会被同步到其它节点上,保证当前节点依然可用。
3、当网络稳定时,当前Eureka Server新的注册信息会被同步到其它节点中。

因此Eureka Server可以很好的应对因网络故障导致部分节点失联的情况,而不会像ZK那样如果有一半不可用的情况会导致整个集群不可用而变成瘫痪。

自我保护模式被激活的条件是:在 1 分钟后,Renews (last min) < Renews threshold

这两个参数的意思:

  • Renews thresholdEureka Server 期望每分钟收到客户端实例续约的总数
  • Renews (last min)Eureka Server 最后 1 分钟收到客户端实例续约的总数

具体的值,我们可以在 Eureka Server 界面可以看到:

可以看到,我们部署了 3 个 Eureka Server(自注册模式),另外,又部署 7 个服务,注册到 Eureka Server 集群,参数值分别为:

  • Renews threshold:17
  • Renews (last min):20

下面说下Renews thresholdRenews threshold具体计算方式。

Renews threshold 计算代码:

this.expectedNumberOfRenewsPerMin = count * 2;
this.numberOfRenewsPerMinThreshold = (int) (this.expectedNumberOfRenewsPerMin * serverConfig.getRenewalPercentThreshold());

count表示服务的数量,如果 Eureka Server 开启自注册模式,也算一个服务,比如我们上面的示例,count的值就是 10(3 个自注册服务 + 7 个独立服务),serverConfig.getRenewalPercentThreshold()默认是 0.85(可以通过eureka.server.renewal-percent-threshold配置)。

所以,根据上面的分析,我们可以计算出Renews threshold的值:(int)(10 * 2 * 0.85) = (int)17 = 17

Renews (last min)计算方式:count * 2,数值 2 表示每 30 秒 1 个心跳,每分钟 2 个心跳的固定频率因子,所以具体值为:10 * 2 = 20

服务注册、服务续约(维持心跳)和服务查询

这三个过程在客户端启动的时候会分别启动一个定时任务不断执行相应的逻辑,都是通过http请求。

服务注册如果失败则会不断注册,直到注册成功。

服务查询默认配置每隔30s从服务端查询一次,然后更新本地服务清单缓存。

服务续约默认配置也是每隔30s发送一次心跳,默认配置是如果注册中心90s内没有收到某个服务的心跳就会清除这个服务。注册中心判断一个服务是否已经90s没有收到心跳也是每60s遍历循环一次服务列表。如果服务提供方是正常关闭的则注册中心会收到一个服务挂掉的请求,然后发布事件通知集群中的所有的eureka服务端。

Eureka和Nacos、zookeeper相比

由于eureka的很多逻辑都是通过轮询来实现的,比如心跳、判断服务是否挂掉,并且客户端和服务端都是通过http请求交互,所以当客户端机器比较多时会对注册中心造成比较大的压力,所以ureka在支持大量机器方面性能不好。

CP(一致性、可靠性)和AP(可用性、可靠性)不能兼得,Eureka支持AP,允许集群中的数据不一致。

深入理解Eureka Server集群同步

当Eureka服务端启动时会从集群中的其它机器上拉取服务列表到本地内存,这和普通客户端的行为一致,这样就实现了启动时的同步。

public enum Action {
    Heartbeat, Register, Cancel, StatusUpdate, DeleteStatusOverride;

    private com.netflix.servo.monitor.Timer timer = Monitors.newTimer(this.name());

    public com.netflix.servo.monitor.Timer getTimer() {
        return this.timer;
    }
}


Heartbeat : 心跳续约

Register : 注册

Cancel : 下线

StatusUpdate : 添加覆盖状态

DeleteStatusOverride : 删除覆盖状态

举个栗子,当有服务注册到这台机器时,注册完成后,这台机器会向注册中心集群中的其它server发送服务注册的请求。

@Override
public void register(final InstanceInfo info, final boolean isReplication) {
    int leaseDuration = Lease.DEFAULT_DURATION_IN_SECS;
    if (info.getLeaseInfo() != null && info.getLeaseInfo().getDurationInSecs() > 0) {
        leaseDuration = info.getLeaseInfo().getDurationInSecs();
    }
    // 发起注册
    super.register(info, leaseDuration, isReplication);
    // 注册完成后,在这里发起同步,同步类型为Register
    replicateToPeers(Action.Register, info.getAppName(), info.getId(), info, null, isReplication);
}
private void replicateToPeers(Action action, String appName, String id,
                              InstanceInfo info /* optional */,
                              InstanceStatus newStatus /* optional */, boolean isReplication) {
    Stopwatch tracer = action.getTimer().start();
    try {
        // 判断是否是集群同步请求,如果是,则记录最后一分钟的同步次数
        if (isReplication) {
            numberOfReplicationsLastMin.increment();
        }
        // If it is a replication already, do not replicate again as this will create a poison replication
        // 集群节点为空,或者这是一个Eureka Server 同步请求,直接return
        if (peerEurekaNodes == Collections.EMPTY_LIST || isReplication) {
            return;
        }
        // 循环相邻的Eureka Server Node, 分别发起请求同步
        for (final PeerEurekaNode node : peerEurekaNodes.getPeerEurekaNodes()) {
            // 判断是否是自身的URL,过滤掉
            if (peerEurekaNodes.isThisMyUrl(node.getServiceUrl())) {
                continue;
            }
            // 发起同步请求
            replicateInstanceActionsToPeers(action, appName, id, info, newStatus, node);
        }
    } finally {
        tracer.stop();
    }
}

Eureka注册的实例名配置

SpringCloud体系里的,服务实体向eureka注册时,注册名默认是“IP名:应用名:应用端口名”,即${spring.cloud.client.ipAddress}:${spring.application.name}:${spring.application.instance_id:${server.port}}。在eureka界面下显示如图:

      有些文章说默认的instance-id注册名是用机器主机名代替ip名,至少我在SpringCloud版本Dalston和Edgware下,无论eureka.instance.prefer-ip-address为true或者false,注册到eureka界面的都是以ip开头的,更高版本还没试过。

      那么如果我们想自定义服务在eureka上的实例名怎么弄呢?这个需求在服务多实例部署下很自然,因为多个实例下,可能不同的实例运行的是不同的版本,如果在eureka界面能看到每个实例运行的版本号,就更加方便问题的排查。尤其在开发和测试环境下,有些实例A运行的版本可能没有另外一些实例B运行版本的功能,但是ribbon又做了负载均衡,将对应这些功能的请求转发到A,就会造成错误的响应。如果eureka界面显示了版本号,那么我们就能先行定位问题的来源是不是版本的问题。

说了那么多,那么怎么给eureka界面上实例的显示名字加上版本号呢,或者说自定义格式。有如下步骤:

(1)要配置的属性为eureka.instance.instance-id。或者eureka.instance.instanceId也可以。好像目前SpringCloud对属性的中划线和驼峰型写法区分并不是很严。

(2)eureka.instance.instance-id必须写在application.properties或application.yml中。不能写在bootstrap.properties和bootstrap.yml中。可以去了解下springboot加载默认配置文件的顺序,bootstrap是在application之前的。写在bootstrap里的话,自定义的instance-id并不会在eureka界面生效,原理没研究过。

(3)取值如下,这里以写在application.properties为例:

eureka.instance.instance-id=${spring.cloud.client.ipAddress}:${spring.application.name}:${server.port}:@project.version@
这里注意project.version是引用maven里面的属性,因为springboot的parent包将maven中默认的${*}修改成了@*@,所以引用maven属性要用@@,而其他的比如server.port本来就是springboot的属性,直接${}。另一种方法可以参考博文http://blog.csdn.net/gj5266400/article/details/74914486 修改springboot对maven属性的引用符,统一为${}。

修改完后再次启动实例,eureka界面注册服务名已经加上了版本号:

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值