springcloud Eureka详解

1.原理

eureka用于服务治理,实现各个微服务实例的自动化注册与发现,不同可用区域的服务注册中心通过异步的方式相互复制各自的状态,eureka客户端向注册中心注册服务并周期性地发送心跳更新服务租约,同时也能从服务端查询所有其他服务的信息。

  • 服务提供者须实现的功能
    1.进行服务注册
    2.将自身信息注册并同步到其他所有注册中心
    3.进行服务续约
  • 服务消费者须实现的功能
    1.获取服务
    2.进行服务调用
    3.服务下线并传播该事件
  • 服务注册中心须提供的功能
    1.将无法提供服务的实例剔除
    2.自我保护(当心跳失败的比例低于85%,开启自我保护机制,让这些实例不会过期,此时可能会出现调用服务失败的情况,这个时候需要容错机制如Hystrix)

2.源码

当我们启动一个客户端时做了2件事

  • yml文件添加eureka.client.serviceUrl.defaultZone = http://…
  • 启动类加@EnableDiscoveryClient注解

1.首先看代码如何从配置文件中读取服务地址:
在EndpointUtils类中实现了该功能

public static Map<String, List<String>> getServiceUrlsMapFromConfig(EurekaClientConfig clientConfig, String instanceZone, boolean preferSameZone) {
        Map<String, List<String>> orderedUrls = new LinkedHashMap();
        // region和zone是一对多的关系,如果获取不到使用default
        String region = getRegion(clientConfig);    //从配置文件中读取region
        String[] availZones = clientConfig.getAvailabilityZones(clientConfig.getRegion()); //从配置文件中读取可用zones(多个)
        if (availZones == null || availZones.length == 0) {
            availZones = new String[]{"default"}; //如果没有配置使用default
        }

        logger.debug("The availability zone for the given region {} are {}", region, availZones);
        int myZoneOffset = getZoneOffset(instanceZone, preferSameZone, availZones);  //根据是否在同一zone和是否可用获取最终zone(区域亲和特性)
        String zone = availZones[myZoneOffset];
        List<String> serviceUrls = clientConfig.getEurekaServerServiceUrls(zone);  //获取serviceUrls
        if (serviceUrls != null) {
            orderedUrls.put(zone, serviceUrls);
        }

		...
    }

	//获取zone
    private static int getZoneOffset(String myZone, boolean preferSameZone, String[] availZones) {
        for(int i = 0; i < availZones.length; ++i) {
            if (myZone != null && availZones[i].equalsIgnoreCase(myZone.trim()) == preferSameZone) {
                return i;
            }
        }

        logger.warn("DISCOVERY: Could not pick a zone based on preferred zone settings. My zone - {}, preferSameZone - {}. Defaulting to {}", new Object[]{myZone, preferSameZone, availZones[0]});
        return 0;
    }

2.实现服务注册等功能
@EnableDiscoveryClient主要用来开启DiscoveryClient实例,该类用于与eureka server相互协作,主要实现4个功能

  • 向Eureka server注册服务实例
  • 向Eureka server续约
  • 服务关闭取消续约
  • 查询Eureka server服务实例列表
    当我们获取到服务端url后就可以进行服务注册等事情了,在DiscoveryClient类的构造方法中可以找到initScheduledTasks方法的调用,该方法启动了一个定时任务进行服务注册,获取,续约
   private void initScheduledTasks() {
        int renewalIntervalInSecs;
        int expBackOffBound;
        if (this.clientConfig.shouldFetchRegistry()) {
            renewalIntervalInSecs = this.clientConfig.getRegistryFetchIntervalSeconds();
            expBackOffBound = this.clientConfig.getCacheRefreshExecutorExponentialBackOffBound();
            this.cacheRefreshTask = new TimedSupervisorTask("cacheRefresh", this.scheduler, this.cacheRefreshExecutor, renewalIntervalInSecs, TimeUnit.SECONDS, expBackOffBound, new DiscoveryClient.CacheRefreshThread());
            this.scheduler.schedule(this.cacheRefreshTask, (long)renewalIntervalInSecs, TimeUnit.SECONDS);
        }

        if (this.clientConfig.shouldRegisterWithEureka()) {
            renewalIntervalInSecs = this.instanceInfo.getLeaseInfo().getRenewalIntervalInSecs();
            expBackOffBound = this.clientConfig.getHeartbeatExecutorExponentialBackOffBound();
            logger.info("Starting heartbeat executor: renew interval is: {}", renewalIntervalInSecs);
            this.heartbeatTask = new TimedSupervisorTask("heartbeat", this.scheduler, this.heartbeatExecutor, renewalIntervalInSecs, TimeUnit.SECONDS, expBackOffBound, new DiscoveryClient.HeartbeatThread());
            this.scheduler.schedule(this.heartbeatTask, (long)renewalIntervalInSecs, TimeUnit.SECONDS); //心跳机制
            this.instanceInfoReplicator = new InstanceInfoReplicator(this, this.instanceInfo, this.clientConfig.getInstanceInfoReplicationIntervalSeconds(), 2); //进行服务注册 instanceInfo对象存储服务注册所需要的信息
            this.statusChangeListener = new StatusChangeListener() {
                public String getId() {
                    return "statusChangeListener";
                }

                public void notify(StatusChangeEvent statusChangeEvent) {
                    if (InstanceStatus.DOWN != statusChangeEvent.getStatus() && InstanceStatus.DOWN != statusChangeEvent.getPreviousStatus()) {
                        DiscoveryClient.logger.info("Saw local status change event {}", statusChangeEvent);
                    } else {
                        DiscoveryClient.logger.warn("Saw local status change event {}", statusChangeEvent);
                    }

                    DiscoveryClient.this.instanceInfoReplicator.onDemandUpdate();
                }
            };
            if (this.clientConfig.shouldOnDemandUpdateStatusChange()) {
                this.applicationInfoManager.registerStatusChangeListener(this.statusChangeListener);
            }

            this.instanceInfoReplicator.start(this.clientConfig.getInitialInstanceInfoReplicationIntervalSeconds());
        } else {
            logger.info("Not registering with Eureka server per configuration");
        }

    }
	//InstanceInfoReplicator实现了Runnable接口重写了run方法进行服务注册
    public void run() {
        boolean var6 = false;

        ScheduledFuture next;
        label53: {
            try {
                var6 = true;
                this.discoveryClient.refreshInstanceInfo();
                Long dirtyTimestamp = this.instanceInfo.isDirtyWithTime();
                if (dirtyTimestamp != null) {
                    this.discoveryClient.register(); //服务注册
                    this.instanceInfo.unsetIsDirty(dirtyTimestamp);
                    var6 = false;
                } else {
                    var6 = false;
                }
                break label53;
            } catch (Throwable var7) {
                logger.warn("There was a problem with the instance info replicator", var7);
                var6 = false;
            } finally {
                if (var6) {
                    ScheduledFuture next = this.scheduler.schedule(this, (long)this.replicationIntervalSeconds, TimeUnit.SECONDS);
                    this.scheduledPeriodicRef.set(next);
                }
            }

            next = this.scheduler.schedule(this, (long)this.replicationIntervalSeconds, TimeUnit.SECONDS);
            this.scheduledPeriodicRef.set(next);
            return;
        }

        next = this.scheduler.schedule(this, (long)this.replicationIntervalSeconds, TimeUnit.SECONDS);
        this.scheduledPeriodicRef.set(next);
    }

最终进行服务的注册,获取,续约都是发起rest请求
3.注册中心处理请求
以 服务注册 请求为例,在ApplicationResource类中有addInstance方法

 @POST
    @Consumes({"application/json", "application/xml"})
    public Response addInstance(InstanceInfo info, @HeaderParam("x-netflix-discovery-replication") String isReplication) {
        logger.debug("Registering instance {} (replication={})", info.getId(), isReplication);
        if (this.isBlank(info.getId())) {
            return Response.status(400).entity("Missing instanceId").build();
        } else if (this.isBlank(info.getHostName())) {
            return Response.status(400).entity("Missing hostname").build();
        } else if (this.isBlank(info.getIPAddr())) {
            return Response.status(400).entity("Missing ip address").build();
        } else if (this.isBlank(info.getAppName())) {
            return Response.status(400).entity("Missing appName").build();
        } else if (!this.appName.equals(info.getAppName())) {
            return Response.status(400).entity("Mismatched appName, expecting " + this.appName + " but was " + info.getAppName()).build();
        } else if (info.getDataCenterInfo() == null) {
            return Response.status(400).entity("Missing dataCenterInfo").build();
        } else if (info.getDataCenterInfo().getName() == null) {
            return Response.status(400).entity("Missing dataCenterInfo Name").build();
        } else {
        	//前面进行一堆校验
            DataCenterInfo dataCenterInfo = info.getDataCenterInfo();
            if (dataCenterInfo instanceof UniqueIdentifier) {
                String dataCenterInfoId = ((UniqueIdentifier)dataCenterInfo).getId();
                if (this.isBlank(dataCenterInfoId)) {
                    boolean experimental = "true".equalsIgnoreCase(this.serverConfig.getExperimental("registration.validation.dataCenterInfoId"));
                    if (experimental) {
                        String entity = "DataCenterInfo of type " + dataCenterInfo.getClass() + " must contain a valid id";
                        return Response.status(400).entity(entity).build();
                    }

                    if (dataCenterInfo instanceof AmazonInfo) {
                        AmazonInfo amazonInfo = (AmazonInfo)dataCenterInfo;
                        String effectiveId = amazonInfo.get(MetaDataKey.instanceId);
                        if (effectiveId == null) {
                            amazonInfo.getMetadata().put(MetaDataKey.instanceId.getName(), info.getId());
                        }
                    } else {
                        logger.warn("Registering DataCenterInfo of type {} without an appropriate id", dataCenterInfo.getClass());
                    }
                }
            }

            this.registry.register(info, "true".equals(isReplication)); //注册中心进行注册服务
            return Response.status(204).build();
        }
    }

在注册函数中现调用publishEvent方法,将该注册事件传播出去,注册信息instanceInfo对象中元数据信息存储在ConcurrentHashMap对象中,双层map,第一层key为appName属性,第二层key为instanceId属性。

3.yml配置

服务注册类配置(eureka.client.*)
regisryFetchIntervalSeconds: 获取注册信息间隔时间
registerWithEureka : 是否将自身注册到服务端(默认为true,服务端需配置成false)
fetchRegistry : 是否从服务端获取注册信息(默认为true,服务端需配置成false)
服务实例类配置(eureka.instance.*)
leaseRenewalIntervalInSeconds : 客户端向服务端发送心跳时间间隔 (默认30)
leaseExpirationDurationInSeconds : 超时剔除该服务 (默认90)

参考资料:《springcloud微服务实战》

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