eureka-server服务注册的实现、集群同步及eureka源码分析

eureka是基于jersey框架写的一个注册中心,主要功能提供了服务注册,服务下架,服务续约,集群同步等功能.

jersey是一个类似于springmvc的框架,只不过mvc是基于servlet的,jersey是基于filter的,二者在使用上也很类似,mvc发请求被servlet拦截到反射调用controller,而jersey是被filter拦截到调用resource, 二者的原理基本一致。
 

1、为什么加@EnableEurekaServer就能启动注册

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(EurekaServerMarkerConfiguration.class)
public @interface EnableEurekaServer {
}

————————————————————————————————————————————————————————————————————————————————————————
@Configuration
public class EurekaServerMarkerConfiguration {
	@Bean
	public Marker eurekaServerMarkerBean() {
		return new Marker();
	}
	class Marker {
	}
}

可以从EnableEurekaServer注解中可以看出,其@import导入一个EurekaServerMarkerConfiguration并实例了maker对象给spring容器管理,这个地方的目的就是为了在是为了在Spring容器中实例一个对象,并在后面的利用@Condition相关的条件注解来判断是否在spring容器中有maker对象.

关于@Condition的使用可以看这篇博客@Conditional注解 详细讲解及示例,及相应的源码分析

 

EurekaServerAutoConfiguration

eureka-server服务注册的实现、集群同步及eureka源码分析_第1张图片

上面这是springboot自动配置的原理,springboot在初始化的时候会把spring.factories定义的类也初始化

eureka-server服务注册的实现、集群同步及eureka源码分析_第2张图片

这里可以看出来@ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class)就是一个开关,根据是否有maker来判断是否加载配置类,来启动注册中心.

 

jersey过滤器的初始化

EurekaServerAutoConfiguration初始化一个filter,代码如下

//EurekaServerAutoConfiguration#
	/**
	 * Register the Jersey filter
	 */
	@Bean
	public FilterRegistrationBean jerseyFilterRegistration(
			javax.ws.rs.core.Application eurekaJerseyApp) {
		FilterRegistrationBean bean = new FilterRegistrationBean();
		bean.setFilter(new ServletContainer(eurekaJerseyApp));
		bean.setOrder(Ordered.LOWEST_PRECEDENCE);
		bean.setUrlPatterns(
				Collections.singletonList(EurekaConstants.DEFAULT_PREFIX + "/*"));

		return bean;
	}

过滤器的作用就是拦截所有的请求.

 

eureka服务注册Server端

源码在ApplicationResource的addInstance方法

eureka-server服务注册的实现、集群同步及eureka源码分析_第3张图片

看到上图是不是感觉jersey和我们的springmvc的controller层差不多呢?哈哈……

 

注册debug追踪到InstanceRegistry

ApplicationResource # addInstance
 registry.register(info, "true".equals(isReplication));------------------>

InstanceRegistry # 
	@Override
	public void register(final InstanceInfo info, final boolean isReplication) {
        //springcloud发布一个事件EurekaInstanceRegisteredEvent
		handleRegistration(info, resolveInstanceLeaseDuration(info), isReplication);

        //调用父类的register
		super.register(info, isReplication);
	}

---------------------------->>

看一下InstanceRegistry继承关系图:

eureka-server服务注册的实现、集群同步及eureka源码分析_第4张图片

 

PeerAwareInstanceRegistryImpl # 



    @Override
    public void register(final InstanceInfo info, final boolean isReplication) {
        // 服务过期时间默认90秒
        int leaseDuration = Lease.DEFAULT_DURATION_IN_SECS;
        if (info.getLeaseInfo() != null && info.getLeaseInfo().getDurationInSecs() > 0) {
            // 获取配置中的服务过期时间
            leaseDuration = info.getLeaseInfo().getDurationInSecs();
        }
        //服务注册
        super.register(info, leaseDuration, isReplication);
        //集群同步
        replicateToPeers(Action.Register, info.getAppName(), info.getId(), info, null, isReplication);
    }

 

AbstractInstanceRegistry.register 服务注册

我们知道spring容器底层就是一个ConcurrentHashMap,那么eureka的底层注册是什么样的数据结构呢?没错一定也是一个map.

ConcurrentHashMap>> registry = new ConcurrentHashMap();

registry的数据结构如上等价于>,为了进行相同服务的集群话,为上一层模块进行调用时方便负载均衡.

服务注册代码

/**
     * Registers a new instance with a given duration.
     *
     * @see com.netflix.eureka.lease.LeaseManager#register(java.lang.Object, int, boolean)
     */
    public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {
        try {
            read.lock();
           //根据应用名称获取对应的服务,因为微服务的application name可以相同,
           //服务实例instance id是不同的(方便集群,为负载均衡作准备),
            Map> gMap = registry.get(registrant.getAppName());
            REGISTER.increment(isReplication);
            if (gMap == null) {
                final ConcurrentHashMap> gNewMap = new ConcurrentHashMap>();
                gMap = registry.putIfAbsent(registrant.getAppName(), gNewMap);
                if (gMap == null) {
                    gMap = gNewMap;
                }
            }

            // 如果上面存在相同的服务的application name的微服务,那么就根据对应的服务的实例instance id来区分
            //尝试通过id拿到一个微服务实例,一般情况下都拿不到,除非以下两种情况
            //情况一:有两台 和instance id都一样微服务
            //情况二:断点调试,这种情况冲突是因为客户端注册有超时重试机制
            Lease existingLease = gMap.get(registrant.getId());
            //如果拿到了
            if (existingLease != null && (existingLease.getHolder() != null)) {
                //已经存在的微服务实例最后修改时间
                Long existingLastDirtyTimestamp = existingLease.getHolder().getLastDirtyTimestamp();
                // 要注册的微服务实例最后修改时间
                Long registrationLastDirtyTimestamp = registrant.getLastDirtyTimestamp();
                logger.debug("Existing lease found (existing={}, provided={}", existingLastDirtyTimestamp, registrationLastDirtyTimestamp);

                // this is a > instead of a >= because if the timestamps are equal, we still take the remote transmitted
                // InstanceInfo instead of the server local copy.
                //如果已存的微服务时间>要注册的(时间越大说明操作越新),用已存的覆盖要注册的
                //即如果出现冲突的话拿最新的微服务实例
                if (existingLastDirtyTimestamp > registrationLastDirtyTimestamp) {
                    logger.warn("There is an existing lease and the existing lease's dirty timestamp {} is greater" +
                            " than the one that is being registered {}", existingLastDirtyTimestamp, registrationLastDirtyTimestamp);
                    logger.warn("Using the existing instanceInfo instead of the new instanceInfo as the registrant");
                    registrant = existingLease.getHolder();
                }
            } else {            //没有拿到,
                // The lease does not exist and hence it is a new registration
                synchronized (lock) {
                    //期待发送心跳的客户端数量
                    if (this.expectedNumberOfRenewsPerMin > 0) {
                        // Since the client wants to cancel it, reduce the threshold
                        // (1
                        // for 30 seconds, 2 for a minute)
                        //要注册进来了,默认是30秒一次,一分钟2次所以加2
                        this.expectedNumberOfRenewsPerMin = this.expectedNumberOfRenewsPerMin + 2;
                        this.numberOfRenewsPerMinThreshold =
                                (int) (this.expectedNumberOfRenewsPerMin * serverConfig.getRenewalPercentThreshold());
                    }
                }
                logger.debug("No previous lease information found; it is new registration");
            }
            //不管如何都new一个Lease
            Lease lease = new Lease(registrant, leaseDuration);
            if (existingLease != null) {
                lease.setServiceUpTimestamp(existingLease.getServiceUpTimestamp());
            }
            //如果if(gMap == null)都没有进,说明微服务组内已经有微服务了,直接put(id,instance)即可
            gMap.put(registrant.getId(), lease);
            synchronized (recentRegisteredQueue) {
                //最近注册队列添加此微服务
                recentRegisteredQueue.add(new Pair(
                        System.currentTimeMillis(),
                        registrant.getAppName() + "(" + registrant.getId() + ")"));
            }
            // This is where the initial state transfer of overridden status happens
            if (!InstanceStatus.UNKNOWN.equals(registrant.getOverriddenStatus())) {
                logger.debug("Found overridden status {} for instance {}. Checking to see if needs to be add to the "
                                + "overrides", registrant.getOverriddenStatus(), registrant.getId());
                if (!overriddenInstanceStatusMap.containsKey(registrant.getId())) {
                    logger.info("Not found overridden id {} and hence adding it", registrant.getId());
                    overriddenInstanceStatusMap.put(registrant.getId(), registrant.getOverriddenStatus());
                }
            }
            InstanceStatus overriddenStatusFromMap = overriddenInstanceStatusMap.get(registrant.getId());
            if (overriddenStatusFromMap != null) {
                logger.info("Storing overridden status {} from map", overriddenStatusFromMap);
                registrant.setOverriddenStatus(overriddenStatusFromMap);
            }

            // Set the status based on the overridden status rules
            InstanceStatus overriddenInstanceStatus = getOverriddenInstanceStatus(registrant, existingLease, isReplication);
            registrant.setStatusWithoutDirty(overriddenInstanceStatus);

            // If the lease is registered with UP status, set lease service up timestamp
            if (InstanceStatus.UP.equals(registrant.getStatus())) {
                lease.serviceUp();
            }
            //标记微服务实例ADDED
            registrant.setActionType(ActionType.ADDED);
            // 最近改变队列添加此微服务,此队列会保存近三分钟有改动的微服务,用于增量更新
            recentlyChangedQueue.add(new RecentlyChangedItem(lease));
            // 设置最后更新的时间戳
            registrant.setLastUpdatedTimestamp();
            // 放入缓存中,
            invalidateCache(registrant.getAppName(), registrant.getVIPAddress(), registrant.getSecureVipAddress());
            logger.info("Registered instance {}/{} with status {} (replication={})",
                    registrant.getAppName(), registrant.getId(), registrant.getStatus(), isReplication);
        } finally {
            read.unlock();
        }
    }

updateRenewsPerMinThreshold()

	protected void updateRenewsPerMinThreshold() {
        this.numberOfRenewsPerMinThreshold = (int) (this.expectedNumberOfClientsSendingRenews
                * (60.0 / serverConfig.getExpectedClientRenewalIntervalSeconds())
                * serverConfig.getRenewalPercentThreshold());
    }

expectedNumberOfClientsSendingRenews:期待发送心跳的客户端数量
ExpectedClientRenewalIntervalSeconds:期待客户端发送心跳的间隔秒数
RenewalPercentThreshold:续期的百分比阈值85%
numberOfRenewsPerMinThreshold:客户端每分钟发送心跳数的阈值,如果server在一分钟内没有收到这么多的心跳数就会触发自我保护机制

举个例子就明白了:
假设有100个客户端,发送心跳间隔为30s,那么一分钟如果全部正常的话server收到的心跳应该是200次,
如果server一分钟收到的心跳<200*85%,即170个触发自我保护机制

 

租赁器Lease

public class Lease {

    enum Action {
        Register, Cancel, Renew
    };
//Eureka服务器在接收到实例的最后一次发出的心跳后,需要等待多久才可以将此实例删除,默认为90秒
// 即租赁有效时间段
    public static final int DEFAULT_DURATION_IN_SECS = 90;

    private T holder;//微服务对象
    private long evictionTimestamp;//服务剔除时间戳
    private long registrationTimestamp;//服务注册时间戳
    private long serviceUpTimestamp;// 恢复正常工作时间戳
    // Make it volatile so that the expiration task would see this quicker
    private volatile long lastUpdateTimestamp;//最后一次更新的时间,注册下架续约都是更新操作
    private long duration;//duration ms后没有心跳剔除服务

   /**
     * @param r 微服务实例
     * @param durationInSecs Eureka服务器在接收到实例的最后一次发出的心跳后,需要等待多久才可以将此实例删除
     *  eureka.instance.leaseExpirationDurationInSeconds=30s,后一次发出的心跳后,30s后还没有新的心跳剔除服务
     *  eureka.instance.leaseRenewalIntervalInSeconds=10s心跳间隔时间
     */
    public Lease(T r, int durationInSecs) {
        holder = r;
        registrationTimestamp = System.currentTimeMillis();
        lastUpdateTimestamp = registrationTimestamp;
        duration = (durationInSecs * 1000);

    }

    /**
     * Renew the lease, use renewal duration if it was specified by the
     * associated {@link T} during registration, otherwise default duration is
     * {@link #DEFAULT_DURATION_IN_SECS}.租赁续约
     */
    public void renew() {
        lastUpdateTimestamp = System.currentTimeMillis() + duration;

    }

    /**
     * 服务下架/剔除操作
     */
    public void cancel() {
        if (evictionTimestamp <= 0) {
            evictionTimestamp = System.currentTimeMillis();
        }
    }

    /**
     * Mark the service as up. This will only take affect the first time called,
     * subsequent calls will be ignored.
     */
    public void serviceUp() {
        if (serviceUpTimestamp == 0) {
            serviceUpTimestamp = System.currentTimeMillis();
        }
    }

    /**
     * Set the leases service UP timestamp.
     */
    public void setServiceUpTimestamp(long serviceUpTimestamp) {
        this.serviceUpTimestamp = serviceUpTimestamp;
    }

    /**
     * Checks if the lease of a given {@link com.netflix.appinfo.InstanceInfo} has expired or not.
     */
    public boolean isExpired() {
        return isExpired(0l);
    }

    /**
     * Checks if the lease of a given {@link com.netflix.appinfo.InstanceInfo} has expired or not.
     *
     * Note that due to renew() doing the 'wrong" thing and setting lastUpdateTimestamp to +duration more than
     * what it should be, the expiry will actually be 2 * duration. This is a minor bug and should only affect
     * instances that ungracefully shutdown. Due to possible wide ranging impact to existing usage, this will
     * not be fixed.
     *
     * @param additionalLeaseMs any additional lease time to add to the lease evaluation in ms.
    * 此处有bug,文档有说明 lastUpdateTimestamp的时间是 
    * lastUpdateTimestamp = System.currentTimeMillis() + duration;
    * 对象是否过期
    * System.currentTimeMillis() > lastUpdateTimestamp + duration 
    * 判断是否过期的时候又加了一个duration 相当于是2*duration 后才算是过期
     */
    public boolean isExpired(long additionalLeaseMs) {
        return (evictionTimestamp > 0 || System.currentTimeMillis() > (lastUpdateTimestamp + duration + additionalLeaseMs));
    }

    /**
     * Gets the milliseconds since epoch when the lease was registered.
     *
     * @return the milliseconds since epoch when the lease was registered.
     */
    public long getRegistrationTimestamp() {
        return registrationTimestamp;
    }

    /**
     * Gets the milliseconds since epoch when the lease was last renewed.
     * Note that the value returned here is actually not the last lease renewal time but the renewal + duration.
     *
     * @return the milliseconds since epoch when the lease was last renewed.
     */
    public long getLastRenewalTimestamp() {
        return lastUpdateTimestamp;
    }

    /**
     * Gets the milliseconds since epoch when the lease was evicted.
     *
     * @return the milliseconds since epoch when the lease was evicted.
     */
    public long getEvictionTimestamp() {
        return evictionTimestamp;
    }

    /**
     * Gets the milliseconds since epoch when the service for the lease was marked as up.
     *
     * @return the milliseconds since epoch when the service for the lease was marked as up.
     */
    public long getServiceUpTimestamp() {
        return serviceUpTimestamp;
    }

    /**
     * Returns the holder of the lease.
     */
    public T getHolder() {
        return holder;
    }

}

 

集群同步

 

 /**
     * Replicates all eureka actions to peer eureka nodes except for replication
     * traffic to this node.
     *
     */
    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()) {
                // If the url represents this host, do not replicate to yourself.
                 // 判断是否是自身的URL,过滤掉
                if (peerEurekaNodes.isThisMyUrl(node.getServiceUrl())) {
                    continue;
                }
                 // 发起同步请求
                replicateInstanceActionsToPeers(action, appName, id, info, newStatus, node);
            }
        } finally {
            tracer.stop();
        }
    }

    /**
     * Replicates all instance changes to peer eureka nodes except for
     * replication traffic to this node.
     *
     */
    private void replicateInstanceActionsToPeers(Action action, String appName,
                                                 String id, InstanceInfo info, InstanceStatus newStatus,
                                                 PeerEurekaNode node) {
        try {
            InstanceInfo infoFromRegistry = null;
            CurrentRequestVersion.set(Version.V2);
            switch (action) {
                case Cancel:
                    node.cancel(appName, id);
                    break;
                case Heartbeat:
                    InstanceStatus overriddenStatus = overriddenInstanceStatusMap.get(id);
                    infoFromRegistry = getInstanceByAppAndId(appName, id, false);
                    node.heartbeat(appName, id, infoFromRegistry, overriddenStatus, false);
                    break;
                case Register:
                    node.register(info);
                    break;
                case StatusUpdate:
                    infoFromRegistry = getInstanceByAppAndId(appName, id, false);
                    node.statusUpdate(appName, id, newStatus, infoFromRegistry);
                    break;
                case DeleteStatusOverride:
                    infoFromRegistry = getInstanceByAppAndId(appName, id, false);
                    node.deleteStatusOverride(appName, id, infoFromRegistry);
                    break;
            }
        } catch (Throwable t) {
            logger.error("Cannot replicate information to {} for action {}", node.getServiceUrl(), action.name(), t);
        }
    }

集群同步步骤

1.判断集群节点是否为空,为空则返回
2.isReplication 代表是否是一个复制请求, isReplication = true 表示是其他Eureka Server发过来的同步请求
这个时候是不需要继续往下同步的。否则会陷入同步死循环
3.循环集群节点,过滤掉自身的节点
4.发起同步请求 ,调用replicateInstanceActionsToPeers

PS: 这里提到了PeerEurekaNode , 对于PeerEurekaNodes的集群节点更新及数据读取,在服务启动的时候,对PeerEurekaNodes集群开启了线程更新集群节点信息。每15分钟一次

总结

1:@EnableEurekaServer注入一个Marker类,说明是一个注册中心
2:EurekaServerAutoConfiguration注入一个filter,来拦截jersey请求转发给resource
3:服务注册,就是把信息存到一个ConcurrentHashMap>
4:对于注册冲突拿最新的微服务实例
5:server每分钟内收到的心跳数低于理应收到的85%就会触发自我保护机制
6:Lease的renew bug, duration多加了一次,理应加一个expireTime表示过期时间
7:集群同步:先注册到一台server,然后遍历其他的集群的其他server节点调用register注册到其他server,
isReplication=true代表此次注册来源于集群同步的注册,代表此次注册不要再进行集群同步,避免无限注册

 

你可能感兴趣的:(微服务)