【SpringCloud 技术专题】「Eureka 源码分析」从源码层面让你认识 Eureka 工作流程和运作机制(下)

{"type":"doc","content":[{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"承接上文的对应的Eureka的上篇介绍,我们开始介绍,详见 [【SpringCloud技术专题】「Eureka源码分析」从源码层面让你认识Eureka工作流程和运作机制(上)]","attrs":{}}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"原理回顾","attrs":{}}]},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"Eureka Server 提供服务注册服务,各个节点启动后,会在Eureka Server中进行注册,这样Eureka Server中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观的看到。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"Eureka Client 是一个Java 客户端,用于简化与Eureka Server的交互,客户端同时也具备一个内置的、使用轮询负载算法的负载均衡器。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"在应用启动后,将会向Eureka Server发送心跳(默认周期为30秒),如果Eureka Server在多个心跳周期(默认3个心跳周期=90秒)没有收到某个节点的心跳,Eureka Server将会从服务注册表中把这个服务节点移除。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","text":"高可用情况下的:Eureka Server之间将会通过复制的方式完成数据的同步;","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":5,"align":null,"origin":null},"content":[{"type":"text","text":"Eureka Client具有缓存的机制,即使所有的Eureka Server 都挂掉的话,客户端依然可以利用缓存中的信息消费其它服务的API;","attrs":{}}]}]}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"EurekaServer 启动流程分析","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"EurekaServer 处理服务注册、集群数据复制","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"EurekaClient 是如何注册到 EurekaServer 的?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"刚才在org.springframework.cloud.netflix.eureka.server.InstanceRegistry 的每个方法都打了一个断点,而且现在EurekaServer已经处于Debug运行状态,那么我们就随便找一个被 @EnableEurekaClient 的微服务启动试试微服务来试试吧,直接Run。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"当启动后,就一定会调用注册register方法,那么就接着往下看,拭目以待;","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"实例注册方法机制","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"InstanceRegistry.register(final InstanceInfo info, final boolean isReplication) 方法进断点了。\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"InstanceRegistry.register顺着堆栈信息往上看,是 ApplicationResource.addInstance 方法被调用了,分析addInstance;","attrs":{}}]}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"ApplicationResource 类","attrs":{}}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"主要是处理接收 Http 的服务请求。","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"@POST\n@Consumes({\"application/json\", \"application/xml\"})\npublic Response addInstance(InstanceInfo info,\n @HeaderParam(PeerEurekaNode.HEADER_REPLICATION) String isReplication) {\n logger.debug(\"Registering instance {} (replication={})\", info.getId(), isReplication);\n // validate that the instanceinfo contains all the necessary required fields\n if (isBlank(info.getId())) {\n return Response.status(400).entity(\"Missing instanceId\").build();\n } else if (isBlank(info.getHostName())) {\n return Response.status(400).entity(\"Missing hostname\").build();\n } else if (isBlank(info.getAppName())) {\n return Response.status(400).entity(\"Missing appName\").build();\n } else if (!appName.equals(info.getAppName())) {\n return Response.status(400).entity(\"Mismatched appName, expecting \" + appName + \" but was \" + info.getAppName()).build();\n } else if (info.getDataCenterInfo() == null) {\n return Response.status(400).entity(\"Missing dataCenterInfo\").build();\n } else if (info.getDataCenterInfo().getName() == null) {\n return Response.status(400).entity(\"Missing dataCenterInfo Name\").build();\n }\n\n // handle cases where clients may be registering with bad DataCenterInfo with missing data\n DataCenterInfo dataCenterInfo = info.getDataCenterInfo();\n if (dataCenterInfo instanceof UniqueIdentifier) {\n String dataCenterInfoId = ((UniqueIdentifier) dataCenterInfo).getId();\n if (isBlank(dataCenterInfoId)) {\n boolean experimental = \"true\".equalsIgnoreCase(serverConfig.getExperimental(\"registration.validation.dataCenterInfoId\"));\n if (experimental) {\n String entity = \"DataCenterInfo of type \" + dataCenterInfo.getClass() + \" must contain a valid id\";\n return Response.status(400).entity(entity).build();\n } else if (dataCenterInfo instanceof AmazonInfo) {\n AmazonInfo amazonInfo = (AmazonInfo) dataCenterInfo;\n String effectiveId = amazonInfo.get(AmazonInfo.MetaDataKey.instanceId);\n if (effectiveId == null) {\n amazonInfo.getMetadata().put(AmazonInfo.MetaDataKey.instanceId.getName(), info.getId());\n }\n } else {\n logger.warn(\"Registering DataCenterInfo of type {} without an appropriate id\", dataCenterInfo.getClass());\n }\n }\n }\n registry.register(info, \"true\".equals(isReplication));\n return Response.status(204).build(); // 204 to be backwards compatible\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这里的写法貌似看起来和我们之前 Controller 的 RESTFUL 写法有点不一样,仔细一看,原来是Jersey RESTful 框架,是一个产品级的RESTful service 和 client 框架。与Struts类似,它同样可以和hibernate,spring框架整合。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"看到 registry.register(info, \"true\".equals(isReplication)); 注册啊,原来EurekaClient客户端启动后会调用会通过Http(s)请求,直接调到ApplicationResource.addInstance 方法,只要是和注册有关的,都会调用这个方法。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"接着我们深入 registry.register(info, \"true\".equals(isReplication)) 查看;","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"@Override\npublic void register(final InstanceInfo info, final boolean isReplication) {\n handleRegistration(info, resolveInstanceLeaseDuration(info), isReplication);\n super.register(info, isReplication);\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"handleRegistration(info, resolveInstanceLeaseDuration(info), isReplication) 方法;","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"private void handleRegistration(InstanceInfo info, int leaseDuration,\n boolean isReplication) {\n log(\"register \" + info.getAppName() + \", vip \" + info.getVIPAddress()\n + \", leaseDuration \" + leaseDuration + \", isReplication \"\n + isReplication);\n publishEvent(new EurekaInstanceRegisteredEvent(this, info, leaseDuration,\n isReplication));\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"然后通过 ApplicationContext 发布了一个事件 EurekaInstanceRegisteredEvent 服务注册事件,可以给 EurekaInstanceRegisteredEvent 添加监听事件,那么用户就可以在此刻实现自己想要的一些业务逻辑。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"然后我们再来看看 super.register(info, isReplication) 方法,该方法是 InstanceRegistry 的父类 PeerAwareInstanceRegistryImpl 的方法。","attrs":{}}]}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"服务户厕机制","attrs":{}}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"进入PeerAwareInstanceRegistryImpl 类的 register(final InstanceInfo info, final boolean isReplication) 方法;","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"@Override\npublic void register(final InstanceInfo info, final boolean isReplication) {\n // 注释:续约时间,默认时间是常量值 90 秒\n int leaseDuration = Lease.DEFAULT_DURATION_IN_SECS;\n // 注释:续约时间,当然也可以从配置文件中取出来,所以说续约时间值也是可以让我们自己自定义配置的\n if (info.getLeaseInfo() != null && info.getLeaseInfo().getDurationInSecs() > 0) {\n leaseDuration = info.getLeaseInfo().getDurationInSecs();\n }\n // 注释:将注册方的信息写入 EurekaServer 的注册表,父类为 AbstractInstanceRegistry\n super.register(info, leaseDuration, isReplication);\n // 注释:EurekaServer 节点之间的数据同步,复制到其他Peer\n replicateToPeers(Action.Register, info.getAppName(), info.getId(), info, null, isReplication);\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"进入super.register(info, leaseDuration, isReplication),如何写入EurekaServer 的注册表的,进入AbstractInstanceRegistry.register(InstanceInfo registrant, int leaseDuration, boolean isReplication) 方法。","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {\n try {\n read.lock();\n // 注释:registry 这个变量,就是我们所谓的注册表,注册表是保存在内存中的;\n Map> gMap = registry.get(registrant.getAppName());\n REGISTER.increment(isReplication);\n if (gMap == null) {\n final ConcurrentHashMap> gNewMap = new ConcurrentHashMap>();\n gMap = registry.putIfAbsent(registrant.getAppName(), gNewMap);\n if (gMap == null) {\n gMap = gNewMap;\n }\n }\n Lease existingLease = gMap.get(registrant.getId());\n // Retain the last dirty timestamp without overwriting it, if there is already a lease\n if (existingLease != null && (existingLease.getHolder() != null)) {\n Long existingLastDirtyTimestamp = existingLease.getHolder().getLastDirtyTimestamp();\n Long registrationLastDirtyTimestamp = registrant.getLastDirtyTimestamp();\n logger.debug(\"Existing lease found (existing={}, provided={}\", existingLastDirtyTimestamp, registrationLastDirtyTimestamp);\n if (existingLastDirtyTimestamp > registrationLastDirtyTimestamp) {\n logger.warn(\"There is an existing lease and the existing lease's dirty timestamp {} is greater\" +\n \" than the one that is being registered {}\", existingLastDirtyTimestamp, registrationLastDirtyTimestamp);\n logger.warn(\"Using the existing instanceInfo instead of the new instanceInfo as the registrant\");\n registrant = existingLease.getHolder();\n }\n } else {\n // The lease does not exist and hence it is a new registration\n synchronized (lock) {\n if (this.expectedNumberOfRenewsPerMin > 0) {\n // Since the client wants to cancel it, reduce the threshold\n // (1\n // for 30 seconds, 2 for a minute)\n this.expectedNumberOfRenewsPerMin = this.expectedNumberOfRenewsPerMin + 2;\n this.numberOfRenewsPerMinThreshold =\n (int) (this.expectedNumberOfRenewsPerMin * serverConfig.getRenewalPercentThreshold());\n }\n }\n logger.debug(\"No previous lease information found; it is new registration\");\n }\n Lease lease = new Lease(registrant, leaseDuration);\n if (existingLease != null) {\n lease.setServiceUpTimestamp(existingLease.getServiceUpTimestamp());\n }\n gMap.put(registrant.getId(), lease);\n synchronized (recentRegisteredQueue) {\n recentRegisteredQueue.add(new Pair(\n System.currentTimeMillis(),\n registrant.getAppName() + \"(\" + registrant.getId() + \")\"));\n }\n // This is where the initial state transfer of overridden status happens\n if (!InstanceStatus.UNKNOWN.equals(registrant.getOverriddenStatus())) {\n logger.debug(\"Found overridden status {} for instance {}. Checking to see if needs to be add to the \"\n + \"overrides\", registrant.getOverriddenStatus(), registrant.getId());\n if (!overriddenInstanceStatusMap.containsKey(registrant.getId())) {\n logger.info(\"Not found overridden id {} and hence adding it\", registrant.getId());\n overriddenInstanceStatusMap.put(registrant.getId(), registrant.getOverriddenStatus());\n }\n }\n InstanceStatus overriddenStatusFromMap = overriddenInstanceStatusMap.get(registrant.getId());\n if (overriddenStatusFromMap != null) {\n logger.info(\"Storing overridden status {} from map\", overriddenStatusFromMap);\n registrant.setOverriddenStatus(overriddenStatusFromMap);\n }\n\n // Set the status based on the overridden status rules\n InstanceStatus overriddenInstanceStatus = getOverriddenInstanceStatus(registrant, existingLease, isReplication);\n registrant.setStatusWithoutDirty(overriddenInstanceStatus);\n\n // If the lease is registered with UP status, set lease service up timestamp\n if (InstanceStatus.UP.equals(registrant.getStatus())) {\n lease.serviceUp();\n }\n registrant.setActionType(ActionType.ADDED);\n recentlyChangedQueue.add(new RecentlyChangedItem(lease));\n registrant.setLastUpdatedTimestamp();\n invalidateCache(registrant.getAppName(), registrant.getVIPAddress(), registrant.getSecureVipAddress());\n logger.info(\"Registered instance {}/{} with status {} (replication={})\",\n registrant.getAppName(), registrant.getId(), registrant.getStatus(), isReplication);\n } finally {\n read.unlock();\n }\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"发现这个方法有点长,大致阅读,主要更新了注册表的时间之外,还更新了缓存等其它东西,大家有兴趣的可以深究阅读该方法;","attrs":{}}]}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"集群之间的复制","attrs":{}}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"replicateToPeers(Action.Register, info.getAppName(), info.getId(), info, null, isReplication) 的这个方法。","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"private void replicateToPeers(Action action, String appName, String id,\n InstanceInfo info /* optional */,\n InstanceStatus newStatus /* optional */, boolean isReplication) {\n Stopwatch tracer = action.getTimer().start();\n try {\n if (isReplication) {\n numberOfReplicationsLastMin.increment();\n }\n // If it is a replication already, do not replicate again as this will create a poison replication\n // 注释:如果已经复制过,就不再复制 \n if (peerEurekaNodes == Collections.EMPTY_LIST || isReplication) {\n return;\n }\n // 遍历Eureka Server集群中的所有节点,进行复制操作 \n for (final PeerEurekaNode node : peerEurekaNodes.getPeerEurekaNodes()) {\n // If the url represents this host, do not replicate to yourself.\n if (peerEurekaNodes.isThisMyUrl(node.getServiceUrl())) {\n continue;\n }\n // 没有复制过,遍历Eureka Server集群中的node节点,依次操作,包括取消、注册、心跳、状态更新等。\n replicateInstanceActionsToPeers(action, appName, id, info, newStatus, node);\n }\n } finally {\n tracer.stop();\n }\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"每当有注册请求,首先更新 EurekaServer 的注册表,然后再将信息同步到其它EurekaServer的节点上去;","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"接下来我们看看 node 节点是如何进行复制操作的,进入 replicateInstanceActionsToPeers 方法。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"private void replicateInstanceActionsToPeers(Action action, String appName,\n String id, InstanceInfo info, InstanceStatus newStatus,\n PeerEurekaNode node) {\n try {\n InstanceInfo infoFromRegistry = null;\n CurrentRequestVersion.set(Version.V2);\n switch (action) {\n case Cancel:\n node.cancel(appName, id);\n break;\n case Heartbeat:\n InstanceStatus overriddenStatus = overriddenInstanceStatusMap.get(id);\n infoFromRegistry = getInstanceByAppAndId(appName, id, false);\n node.heartbeat(appName, id, infoFromRegistry, overriddenStatus, false);\n break;\n case Register:\n node.register(info);\n break;\n case StatusUpdate:\n infoFromRegistry = getInstanceByAppAndId(appName, id, false);\n node.statusUpdate(appName, id, newStatus, infoFromRegistry);\n break;\n case DeleteStatusOverride:\n infoFromRegistry = getInstanceByAppAndId(appName, id, false);\n node.deleteStatusOverride(appName, id, infoFromRegistry);\n break;\n }\n } catch (Throwable t) {\n logger.error(\"Cannot replicate information to {} for action {}\", node.getServiceUrl(), action.name(), t);\n }\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"节点之间的复制状态操作,都在这里体现的淋漓尽致,那么我们就拿 Register 类型 node.register(info) 来看,我们来看看 node 究竟是如何做到同步信息的,进入 node.register(info) 方法看看;","attrs":{}}]}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"同级之间的复制机制","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"PeerEurekaNode.register(final InstanceInfo info) 方法,一窥究竟如何同步数据。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"public void register(final InstanceInfo info) throws Exception {\n // 注释:任务过期时间给任务分发器处理,默认时间偏移当前时间 30秒\n long expiryTime = System.currentTimeMillis() + getLeaseRenewalOf(info);\n batchingDispatcher.process(\n taskId(\"register\", info),\n new InstanceReplicationTask(targetHost, Action.Register, info, null, true) {\n public EurekaHttpResponse execute() {\n return replicationClient.register(info);\n }\n },\n expiryTime\n );\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这里涉及到了 Eureka 的任务批处理,通常情况下Peer之间的同步需要调用多次,如果EurekaServer一多的话,那么将会有很多http请求,所以自然而然的孕育出了任务批处理,但是也在一定程度上导致了注册和下线的一些延迟,突出优势的同时也势必会造成一些劣势,但是这些延迟情况还是能符合常理在容忍范围之内的。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在 expiryTime 超时时间之内,批次处理要做的事情就是合并任务为一个List,然后发送请求的时候,将这个批次List直接打包发送请求出去,这样的话,在这个批次的List里面,可能包含取消、注册、心跳、状态等一系列状态的集合List。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我们再接着看源码,batchingDispatcher.process 这么一调用,然后我们就直接看这个 TaskDispatchers.createBatchingTaskDispatcher 方法。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public static TaskDispatcher createBatchingTaskDispatcher(String id,\n int maxBufferSize,\n int workloadSize,\n int workerCount,\n long maxBatchingDelay,\n long congestionRetryDelayMs,\n long networkFailureRetryMs,\n TaskProcessor taskProcessor) {\n final AcceptorExecutor acceptorExecutor = new AcceptorExecutor<>(\n id, maxBufferSize, workloadSize, maxBatchingDelay, congestionRetryDelayMs, networkFailureRetryMs\n );\n final TaskExecutors taskExecutor = TaskExecutors.batchExecutors(id, workerCount, taskProcessor, acceptorExecutor);\n return new TaskDispatcher() {\n @Override\n public void process(ID id, T task, long expiryTime) {\n acceptorExecutor.process(id, task, expiryTime);\n }\n\n @Override\n public void shutdown() {\n acceptorExecutor.shutdown();\n taskExecutor.shutdown();\n }\n };\n }\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这里的 process 方法会将任务添加到队列中,有入队列自然有出队列,具体怎么取任务,我就不一一给大家讲解了,我就讲讲最后是怎么触发任务的。进入 final TaskExecutors taskExecutor = TaskExecutors.batchExecutors(id, workerCount, taskProcessor, acceptorExecutor) 这句代码的 TaskExecutors.batchExecutors 方法。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"static TaskExecutors batchExecutors(final String name,\n int workerCount,\n final TaskProcessor processor,\n final AcceptorExecutor acceptorExecutor) {\n final AtomicBoolean isShutdown = new AtomicBoolean();\n final TaskExecutorMetrics metrics = new TaskExecutorMetrics(name);\n return new TaskExecutors<>(new WorkerRunnableFactory() {\n @Override\n public WorkerRunnable create(int idx) {\n return new BatchWorkerRunnable<>(\"TaskBatchingWorker-\" +name + '-' + idx, isShutdown, metrics, processor, acceptorExecutor);\n }\n }, workerCount, isShutdown);\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我们发现 TaskExecutors 类中的 batchExecutors 这个静态方法,有个 BatchWorkerRunnable 返回的实现类,因此我们再次进入 BatchWorkerRunnable 类看看究竟,而且既然是 Runnable,那么势必会有 run 方法。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"@Override\npublic void run() {\n try {\n while (!isShutdown.get()) {\n // 注释:获取信号量释放 batchWorkRequests.release(),返回任务集合列表\n List> holders = getWork();\n metrics.registerExpiryTimes(holders);\n\n List tasks = getTasksOf(holders);\n // 注释:将批量任务打包请求Peer节点\n ProcessingResult result = processor.process(tasks);\n switch (result) {\n case Success:\n break;\n case Congestion:\n case TransientError:\n taskDispatcher.reprocess(holders, result);\n break;\n case PermanentError:\n logger.warn(\"Discarding {} tasks of {} due to permanent error\", holders.size(), workerName);\n }\n metrics.registerTaskResult(result, tasks.size());\n }\n } catch (InterruptedException e) {\n // Ignore\n } catch (Throwable e) {\n // Safe-guard, so we never exit this loop in an uncontrolled way.\n logger.warn(\"Discovery WorkerThread error\", e);\n }\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这就是我们 BatchWorkerRunnable 类的 run 方法,这里面首先要获取信号量释放,才能获得任务集合,一旦获取到了任务集合的话,那么就直接调用 processor.process(tasks) 方法请求 Peer 节点同步数据,接下来我们看看 ReplicationTaskProcessor.process 方法;","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"@Override\npublic ProcessingResult process(List tasks) {\n ReplicationList list = createReplicationListOf(tasks);\n try {\n // 注释:这里通过 JerseyReplicationClient 客户端对象直接发送list请求数据\n EurekaHttpResponse response = replicationClient.submitBatchUpdates(list);\n int statusCode = response.getStatusCode();\n if (!isSuccess(statusCode)) {\n if (statusCode == 503) {\n logger.warn(\"Server busy (503) HTTP status code received from the peer {}; rescheduling tasks after delay\", peerId);\n return ProcessingResult.Congestion;\n } else {\n // Unexpected error returned from the server. This should ideally never happen.\n logger.error(\"Batch update failure with HTTP status code {}; discarding {} replication tasks\", statusCode, tasks.size());\n return ProcessingResult.PermanentError;\n }\n } else {\n handleBatchResponse(tasks, response.getEntity().getResponseList());\n }\n } catch (Throwable e) {\n if (isNetworkConnectException(e)) {\n logNetworkErrorSample(null, e);\n return ProcessingResult.TransientError;\n } else {\n logger.error(\"Not re-trying this exception because it does not seem to be a network exception\", e);\n return ProcessingResult.PermanentError;\n }\n }\n return ProcessingResult.Success;\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"感觉快要见到真相了,所以我们迫不及待的进入 JerseyReplicationClient.submitBatchUpdates(ReplicationList replicationList) 方法一窥究竟。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"@Override\npublic EurekaHttpResponse submitBatchUpdates(ReplicationList replicationList) {\n ClientResponse response = null;\n try {\n response = jerseyApacheClient.resource(serviceUrl)\n // 注释:这才是重点,请求目的相对路径,peerreplication/batch/\n .path(PeerEurekaNode.BATCH_URL_PATH)\n .accept(MediaType.APPLICATION_JSON_TYPE)\n .type(MediaType.APPLICATION_JSON_TYPE)\n .post(ClientResponse.class, replicationList);\n if (!isSuccess(response.getStatus())) {\n return anEurekaHttpResponse(response.getStatus(), ReplicationListResponse.class).build();\n }\n ReplicationListResponse batchResponse = response.getEntity(ReplicationListResponse.class);\n return anEurekaHttpResponse(response.getStatus(), batchResponse).type(MediaType.APPLICATION_JSON_TYPE).build();\n } finally {\n if (response != null) {\n response.close();\n }\n }\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"看到了相对路径地址,我们搜索下\"batch\"这样的字符串看看有没有对应的接收方法或者被@Path注解进入的;在 eureka-core-1.4.12.jar 这个包下面,果然搜到到了 @Path(\"batch\") 这样的字样,直接进入,发现这是 PeerReplicationResource 类的方法 batchReplication,我们进入这方法看看。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"@Path(\"batch\")\n@POST\npublic Response batchReplication(ReplicationList replicationList) {\n try {\n ReplicationListResponse batchResponse = new ReplicationListResponse();\n // 注释:这里将收到的任务列表,依次循环解析处理,主要核心方法在 dispatch 方法中。\n for (ReplicationInstance instanceInfo : replicationList.getReplicationList()) {\n try {\n batchResponse.addResponse(dispatch(instanceInfo));\n } catch (Exception e) {\n batchResponse.addResponse(new ReplicationInstanceResponse(Status.INTERNAL_SERVER_ERROR.getStatusCode(), null));\n logger.error(instanceInfo.getAction() + \" request processing failed for batch item \"\n + instanceInfo.getAppName() + '/' + instanceInfo.getId(), e);\n }\n }\n return Response.ok(batchResponse).build();\n } catch (Throwable e) {\n logger.error(\"Cannot execute batch Request\", e);\n return Response.status(Status.INTERNAL_SERVER_ERROR).build();\n }\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"看到了循环一次遍历任务进行处理,不知不觉觉得心花怒放,胜利的重点马上就要到来了,我们进入 PeerReplicationResource.dispatch 方法看看。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"private ReplicationInstanceResponse dispatch(ReplicationInstance instanceInfo) {\n ApplicationResource applicationResource = createApplicationResource(instanceInfo);\n InstanceResource resource = createInstanceResource(instanceInfo, applicationResource);\n\n String lastDirtyTimestamp = toString(instanceInfo.getLastDirtyTimestamp());\n String overriddenStatus = toString(instanceInfo.getOverriddenStatus());\n String instanceStatus = toString(instanceInfo.getStatus());\n\n Builder singleResponseBuilder = new Builder();\n switch (instanceInfo.getAction()) {\n case Register:\n singleResponseBuilder = handleRegister(instanceInfo, applicationResource);\n break;\n case Heartbeat:\n singleResponseBuilder = handleHeartbeat(resource, lastDirtyTimestamp, overriddenStatus, instanceStatus);\n break;\n case Cancel:\n singleResponseBuilder = handleCancel(resource);\n break;\n case StatusUpdate:\n singleResponseBuilder = handleStatusUpdate(instanceInfo, resource);\n break;\n case DeleteStatusOverride:\n singleResponseBuilder = handleDeleteStatusOverride(instanceInfo, resource);\n break;\n }\n return singleResponseBuilder.build();\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"随便抓一个类型,那我们也拿 Register 类型来看,进入 PeerReplicationResource.handleRegister 看看。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"private static Builder handleRegister(ReplicationInstance instanceInfo, ApplicationResource applicationResource) {\n // 注释:private static final String REPLICATION = \"true\"; 定义的一个常量值,而且还是回调 ApplicationResource.addInstance 方法\n applicationResource.addInstance(instanceInfo.getInstanceInfo(), REPLICATION);\n return new Builder().setStatusCode(Status.OK.getStatusCode());\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Peer节点的同步旅程终于结束了,最终又回调到了 ApplicationResource.addInstance 这个方法,这个方法在最终是EurekaClient启动后注册调用的方法,然而Peer节点的信息同步也调用了这个方法,仅仅只是通过一个变量 isReplication 为true还是false来判断是否是节点复制。剩下的ApplicationResource.addInstance流程前面已经提到过了,相信大家已经明白了注册的流程是如何扭转的,包括批量任务是如何处理EurekaServer节点之间的信息同步的了。","attrs":{}}]}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"EurekaClient 启动流程分析","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"调换运行模式","attrs":{}}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"Run运行discovery-eureka服务,Debug 运行 provider-user 服务,先观察日志先;","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"2017-10-23 19:43:07.688 INFO 1488 --- [ main] o.s.c.support.DefaultLifecycleProcessor : Starting beans in phase 0\n2017-10-23 19:43:07.694 INFO 1488 --- [ main] o.s.c.n.eureka.InstanceInfoFactory : Setting initial instance status as: STARTING\n2017-10-23 19:43:07.874 INFO 1488 --- [ main] c.n.d.provider.DiscoveryJerseyProvider : Using JSON encoding codec LegacyJacksonJson\n2017-10-23 19:43:07.874 INFO 1488 --- [ main] c.n.d.provider.DiscoveryJerseyProvider : Using JSON decoding codec LegacyJacksonJson\n2017-10-23 19:43:07.971 INFO 1488 --- [ main] c.n.d.provider.DiscoveryJerseyProvider : Using XML encoding codec XStreamXml\n2017-10-23 19:43:07.971 INFO 1488 --- [ main] c.n.d.provider.DiscoveryJerseyProvider : Using XML decoding codec XStreamXml\n2017-10-23 19:43:08.134 INFO 1488 --- [ main] c.n.d.s.r.aws.ConfigClusterResolver : Resolving eureka endpoints via configuration\n2017-10-23 19:43:08.344 INFO 1488 --- [ main] com.netflix.discovery.DiscoveryClient : Disable delta property : false\n2017-10-23 19:43:08.344 INFO 1488 --- [ main] com.netflix.discovery.DiscoveryClient : Single vip registry refresh property : null\n2017-10-23 19:43:08.344 INFO 1488 --- [ main] com.netflix.discovery.DiscoveryClient : Force full registry fetch : false\n2017-10-23 19:43:08.344 INFO 1488 --- [ main] com.netflix.discovery.DiscoveryClient : Application is null : false\n2017-10-23 19:43:08.344 INFO 1488 --- [ main] com.netflix.discovery.DiscoveryClient : Registered Applications size is zero : true\n2017-10-23 19:43:08.344 INFO 1488 --- [ main] com.netflix.discovery.DiscoveryClient : Application version is -1: true\n2017-10-23 19:43:08.345 INFO 1488 --- [ main] com.netflix.discovery.DiscoveryClient : Getting all instance registry info from the eureka server\n2017-10-23 19:43:08.630 INFO 1488 --- [ main] com.netflix.discovery.DiscoveryClient : The response status is 200\n2017-10-23 19:43:08.631 INFO 1488 --- [ main] com.netflix.discovery.DiscoveryClient : Starting heartbeat executor: renew interval is: 30\n2017-10-23 19:43:08.634 INFO 1488 --- [ main] c.n.discovery.InstanceInfoReplicator : InstanceInfoReplicator onDemand update allowed rate per min is 4\n2017-10-23 19:43:08.637 INFO 1488 --- [ main] com.netflix.discovery.DiscoveryClient : Discovery Client initialized at timestamp 1508758988637 with initial instances count: 0\n2017-10-23 19:43:08.657 INFO 1488 --- [ main] c.n.e.EurekaDiscoveryClientConfiguration : Registering application springms-provider-user with eureka with status UP\n2017-10-23 19:43:08.658 INFO 1488 --- [ main] com.netflix.discovery.DiscoveryClient : Saw local status change event StatusChangeEvent [timestamp=1508758988658, current=UP, previous=STARTING]\n2017-10-23 19:43:08.659 INFO 1488 --- [nfoReplicator-0] com.netflix.discovery.DiscoveryClient : DiscoveryClient_SPRINGMS-PROVIDER-USER/springms-provider-user:192.168.3.101:7900: registering service...\n2017-10-23 19:43:08.768 INFO 1488 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 7900 (http)\n2017-10-23 19:43:08.768 INFO 1488 --- [ main] c.n.e.EurekaDiscoveryClientConfiguration : Updating port to 7900\n2017-10-23 19:43:08.773 INFO 1488 --- [ main] c.s.cloud.MsProviderUserApplication : Started ProviderApplication in 882.1 seconds (JVM running for 10.398)\n","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"服务提供方主体加载流程","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"【1】:仔细查看下日志,先是 DefaultLifecycleProcessor 类处理了一些 bean,然后接下来肯定会调用一些实现 SmartLifecycle 类的start 方法;","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"【2】: 接着初始化设置了EurekaClient的状态为 STARTING,初始化编码使用的格式,哪些用JSON,哪些用XML;","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"【3】: 紧接着打印了强制获取注册信息状态为false,已注册的应用大小为0,客户端发送心跳续约,心跳续约间隔为30秒,最后打印Client初始化完成;","attrs":{}}]}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"EnableEurekaClient 组件。","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"@Target(ElementType.TYPE)\n@Retention(RetentionPolicy.RUNTIME)\n@Documented\n@Inherited\n@EnableDiscoveryClient\npublic @interface EnableEurekaClient {}\n","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"@EnableEurekaClient","attrs":{}}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"这个注解类竟然也使用了注解 @EnableDiscoveryClient,那么我们有必要去这个注解类看看。","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"@Target(ElementType.TYPE)\n@Retention(RetentionPolicy.RUNTIME)\n@Documented\n@Inherited\n@Import(EnableDiscoveryClientImportSelector.class)\npublic @interface EnableDiscoveryClient {}\n","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"@EnableDiscoveryClient","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这个注解类有个比较特殊的注解 ","attrs":{}},{"type":"link","attrs":{"href":"https://my.oschina.net/u/3201731","title":"","type":null},"content":[{"type":"text","text":"@Import","attrs":{}}]},{"type":"text","text":",由此我们猜想,这里的大多数逻辑是不是都写在这个 ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"EnableDiscoveryClientImportSelector","attrs":{}},{"type":"text","text":" 类呢?","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"EnableDiscoveryClientImportSelector","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"@Order(Ordered.LOWEST_PRECEDENCE - 100)\npublic class EnableDiscoveryClientImportSelector\n extends SpringFactoryImportSelector {\n @Override\n protected boolean isEnabled() {\n return new RelaxedPropertyResolver(getEnvironment()).getProperty(\n \"spring.cloud.discovery.enabled\", Boolean.class, Boolean.TRUE);\n }\n @Override\n protected boolean hasDefaultFactory() {\n return true;\n }\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"EnableDiscoveryClientImportSelector 类继承了 SpringFactoryImportSelector 类,但是重写了一个 isEnabled() 方法,默认值返回 true,为什么会返回true","attrs":{}},{"type":"text","text":"。","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"/**\n * Select and return the names of which class(es) should be imported based on\n * the {@link AnnotationMetadata} of the importing @{@link Configuration} class.\n */\n@Override\npublic String[] selectImports(AnnotationMetadata metadata) {\n if (!isEnabled()) { \n return new String[0];\n }\n AnnotationAttributes attributes = AnnotationAttributes.fromMap(\n metadata.getAnnotationAttributes(this.annotationClass.getName(), true));\n Assert.notNull(attributes, \"No \" + getSimpleName() + \" attributes found. Is \"\n + metadata.getClassName() + \" annotated with @\" + getSimpleName() + \"?\");\n // Find all possible auto configuration classes, filtering duplicates\n List factories = new ArrayList<>(new LinkedHashSet<>(SpringFactoriesLoader\n .loadFactoryNames(this.annotationClass, this.beanClassLoader)));\n if (factories.isEmpty() && !hasDefaultFactory()) {\n throw new IllegalStateException(\"Annotation @\" + getSimpleName()\n + \" found, but there are no implementations. Did you forget to include a starter?\");\n }\n if (factories.size() > 1) {\n // there should only ever be one DiscoveryClient, but there might be more than\n // one factory\n log.warn(\"More than one implementation \" + \"of @\" + getSimpleName()\n + \" (now relying on @Conditionals to pick one): \" + factories);\n }\n return factories.toArray(new String[factories.size()]);\n}\n","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"EnableDiscoveryClientImportSelector.selectImports","attrs":{}}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"首先通过注解获取了一些属性,然后加载了一些类名称,我们进入loadFactoryNames 方法看看。","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public static List loadFactoryNames(Class> factoryClass, ClassLoader classLoader) {\n String factoryClassName = factoryClass.getName();\n try {\n // 注释:public static final String FACTORIES_RESOURCE_LOCATION = \"META-INF/spring.factories\";\n // 注释:这个 jar 包下的一个配置文件\n Enumeration urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :\n ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));\n List result = new ArrayList();\n while (urls.hasMoreElements()) {\n URL url = urls.nextElement();\n Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));\n String factoryClassNames = properties.getProperty(factoryClassName);\n result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));\n }\n return result;\n }\n catch (IOException ex) {\n throw new IllegalArgumentException(\"Unable to load [\" + factoryClass.getName() +\n \"] factories from location [\" + FACTORIES_RESOURCE_LOCATION + \"]\", ex);\n }\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"加载了一个配置文件,配置文件里面写了啥呢?打开SpringFactoryImportSelector该文件所在的jar包的spring.factories文件一看。","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"# AutoConfiguration\norg.springframework.boot.autoconfigure.EnableAutoConfiguration=\\\norg.springframework.cloud.client.CommonsClientAutoConfiguration,\\\norg.springframework.cloud.client.discovery.noop.NoopDiscoveryClientAutoConfiguration,\\\norg.springframework.cloud.client.hypermedia.CloudHypermediaAutoConfiguration,\\\norg.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration,\\\norg.springframework.cloud.commons.util.UtilAutoConfiguration\n# Environment Post Processors\norg.springframework.boot.env.EnvironmentPostProcessor=\\\norg.springframework.cloud.client.HostInfoEnvironmentPostProcessor\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"都是一些 Configuration 后缀的类名,所以这些都是加载的一堆堆的配置文件类。factories对象里面只有一个类名路径为 org.springframework.cloud.netflix.eureka.EurekaDiscoveryClientConfiguration 。","attrs":{}}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"EurekaDiscoveryClientConfiguration","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"@Configuration\n@EnableConfigurationProperties\n@ConditionalOnClass(EurekaClientConfig.class)\n@ConditionalOnProperty(value = \"eureka.client.enabled\", matchIfMissing = true)\n@CommonsLog\npublic class EurekaDiscoveryClientConfiguration implements SmartLifecycle, Ordered {\n @Override\n public void start() {\n // only set the port if the nonSecurePort is 0 and this.port != 0\n if (this.port.get() != 0 && this.instanceConfig.getNonSecurePort() == 0) {\n this.instanceConfig.setNonSecurePort(this.port.get());\n }\n // only initialize if nonSecurePort is greater than 0 and it isn't already running\n // because of containerPortInitializer below\n if (!this.running.get() && this.instanceConfig.getNonSecurePort() > 0) {\n maybeInitializeClient();\n if (log.isInfoEnabled()) {\n log.info(\"Registering application \" + this.instanceConfig.getAppname()\n + \" with eureka with status \"\n + this.instanceConfig.getInitialStatus());\n }\n this.applicationInfoManager\n .setInstanceStatus(this.instanceConfig.getInitialStatus());\n if (this.healthCheckHandler != null) {\n this.eurekaClient.registerHealthCheck(this.healthCheckHandler);\n }\n this.context.publishEvent(\n new InstanceRegisteredEvent<>(this, this.instanceConfig));\n this.running.set(true);\n }\n }\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先看到该类实现了SmartLifecycle 接口,那么就肯定会实现 start 方法,而且这个 start 方法感觉应在会被加载执行的。this.applicationInfoManager.setInstanceStatus(this.instanceConfig.getInitialStatus()) 这段代码有一个观察者模式的回调存在。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"// ApplicationInfoManager.setInstanceStatus 的方法\npublic synchronized void setInstanceStatus(InstanceStatus status) {// 打上断点\n InstanceStatus prev = instanceInfo.setStatus(status);\n if (prev != null) {\n for (StatusChangeListener listener : listeners.values()) {\n try {\n listener.notify(new StatusChangeEvent(prev, status));\n } catch (Exception e) {\n logger.warn(\"failed to notify listener: {}\", listener.getId(), e);\n }\n }\n }\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这个方法会因为状态的改变而回调所有实现 StatusChangeListener 这个类的地方,前提得先注册到 listeners 中去才行。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"于是乎,我们断定,若想要回调,那么就必须有地方先注册这个事件,而且这个注册还必须提前执行在 start 方法前执行,于是我们得先在ApplicationInfoManager 这个类中找到注册到 listeners 的这个方法。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public void registerStatusChangeListener(StatusChangeListener listener) {// 打上断点\n listeners.put(listener.getId(), listener);\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"于是我们逆向找下 registerStatusChangeListener 被调用的地方。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"很不巧的是,尽然只有1个地方被调用,这个地方就是 DiscoveryClient.initScheduledTasks 方法,而且 initScheduledTasks方法又是在 DiscoveryClient 的构造函数里面调用的,同时我们也对 initScheduledTasks 以及 initScheduledTasks 被调用的构造方法地方打上断点。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"果不其然,EurekaDiscoveryClientConfiguration.start 方法被调用了,紧接着 this.applicationInfoManager.setInstanceStatus(this.instanceConfig.getInitialStatus()) 也进入断点,然后在往下走,又进入的DiscoveryClient.initScheduledTasks 方法中的 notify 回调处。","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"看着断点依次经过我们上述分析的地方,然后也符合日志打印的顺序,所以我们现在应该是有必要好好看看 DiscoveryClient.initScheduledTasks 这个方法究竟干了什么伟大的事情。然而又想了想,还不如看看 initScheduledTasks 被调用的构造方法。","attrs":{}}]}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"DiscoveryClient 经过 @Inject 注解过的构造方法。","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"@Inject\nDiscoveryClient(ApplicationInfoManager applicationInfoManager, EurekaClientConfig config, DiscoveryClientOptionalArgs args, Provider backupRegistryProvider) {\n if (args != null) {\n this.healthCheckHandlerProvider = args.healthCheckHandlerProvider;\n this.healthCheckCallbackProvider = args.healthCheckCallbackProvider;\n this.eventListeners.addAll(args.getEventListeners());\n } else {\n this.healthCheckCallbackProvider = null;\n this.healthCheckHandlerProvider = null;\n }\n \n this.applicationInfoManager = applicationInfoManager;\n InstanceInfo myInfo = applicationInfoManager.getInfo();\n\n clientConfig = config;\n staticClientConfig = clientConfig;\n transportConfig = config.getTransportConfig();\n instanceInfo = myInfo;\n if (myInfo != null) {\n appPathIdentifier = instanceInfo.getAppName() + \"/\" + instanceInfo.getId();\n } else {\n logger.warn(\"Setting instanceInfo to a passed in null value\");\n }\n\n this.backupRegistryProvider = backupRegistryProvider;\n\n this.urlRandomizer = new EndpointUtils.InstanceInfoBasedUrlRandomizer(instanceInfo);\n localRegionApps.set(new Applications());\n\n fetchRegistryGeneration = new AtomicLong(0);\n\n remoteRegionsToFetch = new AtomicReference(clientConfig.fetchRegistryForRemoteRegions());\n remoteRegionsRef = new AtomicReference<>(remoteRegionsToFetch.get() == null ? null : remoteRegionsToFetch.get().split(\",\"));\n\n if (config.shouldFetchRegistry()) {\n this.registryStalenessMonitor = new ThresholdLevelsMetric(this, METRIC_REGISTRY_PREFIX + \"lastUpdateSec_\", new long[]{15L, 30L, 60L, 120L, 240L, 480L});\n } else {\n this.registryStalenessMonitor = ThresholdLevelsMetric.NO_OP_METRIC;\n }\n\n if (config.shouldRegisterWithEureka()) {\n this.heartbeatStalenessMonitor = new ThresholdLevelsMetric(this, METRIC_REGISTRATION_PREFIX + \"lastHeartbeatSec_\", new long[]{15L, 30L, 60L, 120L, 240L, 480L});\n } else {\n this.heartbeatStalenessMonitor = ThresholdLevelsMetric.NO_OP_METRIC;\n }\n\n if (!config.shouldRegisterWithEureka() && !config.shouldFetchRegistry()) {\n logger.info(\"Client configured to neither register nor query for data.\");\n scheduler = null;\n heartbeatExecutor = null;\n cacheRefreshExecutor = null;\n eurekaTransport = null;\n instanceRegionChecker = new InstanceRegionChecker(new PropertyBasedAzToRegionMapper(config), clientConfig.getRegion());\n\n // This is a bit of hack to allow for existing code using DiscoveryManager.getInstance()\n // to work with DI'd DiscoveryClient\n DiscoveryManager.getInstance().setDiscoveryClient(this);\n DiscoveryManager.getInstance().setEurekaClientConfig(config);\n\n initTimestampMs = System.currentTimeMillis();\n\n logger.info(\"Discovery Client initialized at timestamp {} with initial instances count: {}\",\n initTimestampMs, this.getApplications().size());\n return; // no need to setup up an network tasks and we are done\n }\n\n try {\n // 注释:定时任务调度准备\n scheduler = Executors.newScheduledThreadPool(3,\n new ThreadFactoryBuilder()\n .setNameFormat(\"DiscoveryClient-%d\")\n .setDaemon(true)\n .build());\n\n // 注释:实例化心跳定时任务线程池\n heartbeatExecutor = new ThreadPoolExecutor(\n 1, clientConfig.getHeartbeatExecutorThreadPoolSize(), 0, TimeUnit.SECONDS,\n new SynchronousQueue(),\n new ThreadFactoryBuilder()\n .setNameFormat(\"DiscoveryClient-HeartbeatExecutor-%d\")\n .setDaemon(true)\n .build()\n ); // use direct handoff\n\n // 注释:实例化缓存刷新定时任务线程池\n cacheRefreshExecutor = new ThreadPoolExecutor(\n 1, clientConfig.getCacheRefreshExecutorThreadPoolSize(), 0, TimeUnit.SECONDS,\n new SynchronousQueue(),\n new ThreadFactoryBuilder()\n .setNameFormat(\"DiscoveryClient-CacheRefreshExecutor-%d\")\n .setDaemon(true)\n .build()\n ); // use direct handoff\n\n eurekaTransport = new EurekaTransport();\n scheduleServerEndpointTask(eurekaTransport, args);\n\n AzToRegionMapper azToRegionMapper;\n if (clientConfig.shouldUseDnsForFetchingServiceUrls()) {\n azToRegionMapper = new DNSBasedAzToRegionMapper(clientConfig);\n } else {\n azToRegionMapper = new PropertyBasedAzToRegionMapper(clientConfig);\n }\n if (null != remoteRegionsToFetch.get()) {\n azToRegionMapper.setRegionsToFetch(remoteRegionsToFetch.get().split(\",\"));\n }\n instanceRegionChecker = new InstanceRegionChecker(azToRegionMapper, clientConfig.getRegion());\n } catch (Throwable e) {\n throw new RuntimeException(\"Failed to initialize DiscoveryClient!\", e);\n }\n\n if (clientConfig.shouldFetchRegistry() && !fetchRegistry(false)) {\n fetchRegistryFromBackup();\n }\n\n // 注释:初始化调度任务\n initScheduledTasks();\n try {\n Monitors.registerObject(this);\n } catch (Throwable e) {\n logger.warn(\"Cannot register timers\", e);\n }\n\n // This is a bit of hack to allow for existing code using DiscoveryManager.getInstance()\n // to work with DI'd DiscoveryClient\n DiscoveryManager.getInstance().setDiscoveryClient(this);\n DiscoveryManager.getInstance().setEurekaClientConfig(config);\n\n initTimestampMs = System.currentTimeMillis();\n logger.info(\"Discovery Client initialized at timestamp {} with initial instances count: {}\",\n initTimestampMs, this.getApplications().size());\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"从往下看,initScheduledTasks 这个方法顾名思义就是初始化调度任务,所以这里面的内容应该就是重头戏,进入看看。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"private void initScheduledTasks() {\n if (clientConfig.shouldFetchRegistry()) {\n // registry cache refresh timer\n // 注释:间隔多久去拉取服务注册信息,默认时间 30秒\n int registryFetchIntervalSeconds = clientConfig.getRegistryFetchIntervalSeconds();\n int expBackOffBound = clientConfig.getCacheRefreshExecutorExponentialBackOffBound();\n // 注释:定时任务,每间隔 30秒 去拉取一次服务注册信息\n scheduler.schedule(\n new TimedSupervisorTask(\n \"cacheRefresh\",\n scheduler,\n cacheRefreshExecutor,\n registryFetchIntervalSeconds,\n TimeUnit.SECONDS,\n expBackOffBound,\n new CacheRefreshThread()\n ),\n registryFetchIntervalSeconds, TimeUnit.SECONDS);\n }\n\n if (clientConfig.shouldRegisterWithEureka()) {\n // 注释:间隔多久发送一次心跳续约,默认间隔时间 30 秒\n int renewalIntervalInSecs = instanceInfo.getLeaseInfo().getRenewalIntervalInSecs();\n int expBackOffBound = clientConfig.getHeartbeatExecutorExponentialBackOffBound();\n logger.info(\"Starting heartbeat executor: \" + \"renew interval is: \" + renewalIntervalInSecs);\n\n // Heartbeat timer\n // 注释:定时任务,每间隔 30秒 去想 EurekaServer 发送一次心跳续约\n scheduler.schedule(\n new TimedSupervisorTask(\n \"heartbeat\",\n scheduler,\n heartbeatExecutor,\n renewalIntervalInSecs,\n TimeUnit.SECONDS,\n expBackOffBound,\n new HeartbeatThread()\n ),\n renewalIntervalInSecs, TimeUnit.SECONDS);\n\n // InstanceInfo replicator\n // 注释:实例信息复制器,定时刷新dataCenterInfo数据中心信息,默认30秒\n instanceInfoReplicator = new InstanceInfoReplicator(\n this,\n instanceInfo,\n clientConfig.getInstanceInfoReplicationIntervalSeconds(),\n 2); // burstSize\n\n // 注释:实例化状态变化监听器\n statusChangeListener = new ApplicationInfoManager.StatusChangeListener() {\n @Override\n public String getId() {\n return \"statusChangeListener\";\n }\n\n @Override\n public void notify(StatusChangeEvent statusChangeEvent) {\n if (InstanceStatus.DOWN == statusChangeEvent.getStatus() ||\n InstanceStatus.DOWN == statusChangeEvent.getPreviousStatus()) {\n // log at warn level if DOWN was involved\n logger.warn(\"Saw local status change event {}\", statusChangeEvent);\n } else {\n logger.info(\"Saw local status change event {}\", statusChangeEvent);\n }\n\n // 注释:状态有变化的话,会回调这个方法\n instanceInfoReplicator.onDemandUpdate();\n }\n };\n\n // 注释:注册状态变化监听器\n if (clientConfig.shouldOnDemandUpdateStatusChange()) {\n applicationInfoManager.registerStatusChangeListener(statusChangeListener);\n }\n\n instanceInfoReplicator.start(clientConfig.getInitialInstanceInfoReplicationIntervalSeconds());\n } else {\n logger.info(\"Not registering with Eureka server per configuration\");\n }\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在这个方法从上往下一路注释分析下来,干了EurekaClient我们最想知道的一些事情,定时任务获取注册信息,定时任务刷新缓存,定时任务心跳续约,定时任务同步数据中心数据,状态变化监听回调等。但是唯独没看到注册,这是怎么回事呢?","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"instanceInfoReplicator.onDemandUpdate() 就是在状态改变的时候。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public boolean onDemandUpdate() {\n if (rateLimiter.acquire(burstSize, allowedRatePerMinute)) {\n scheduler.submit(new Runnable() {\n @Override\n public void run() {\n logger.debug(\"Executing on-demand update of local InstanceInfo\");\n\n Future latestPeriodic = scheduledPeriodicRef.get();\n if (latestPeriodic != null && !latestPeriodic.isDone()) {\n logger.debug(\"Canceling the latest scheduled update, it will be rescheduled at the end of on demand update\");\n latestPeriodic.cancel(false);\n }\n\n // 注释:这里进行了实例信息刷新和注册\n InstanceInfoReplicator.this.run();\n }\n });\n return true;\n } else {\n logger.warn(\"Ignoring onDemand update due to rate limiter\");\n return false;\n }\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"onDemandUpdate 这个方法,唯独 InstanceInfoReplicator.this.run() 这个方法还有点用,而且还是 run 方法呢,感情 InstanceInfoReplicator 这个类还是实现了 Runnable 接口?经过查看这个类,还真是实现了 Runnable 接口。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这个方法应该我们要找的注册所在的地方。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public void run() {\n try {\n discoveryClient.refreshInstanceInfo();\n Long dirtyTimestamp = instanceInfo.isDirtyWithTime();\n if (dirtyTimestamp != null) {\n discoveryClient.register();\n instanceInfo.unsetIsDirty(dirtyTimestamp);\n }\n } catch (Throwable t) {\n logger.warn(\"There was a problem with the instance info replicator\", t);\n } finally {\n Future next = scheduler.schedule(this, replicationIntervalSeconds, TimeUnit.SECONDS);\n scheduledPeriodicRef.set(next);\n }\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"discoveryClient.register() 这个 register 方法,原来注册方法就是这个。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"boolean register() throws Throwable {\n logger.info(PREFIX + appPathIdentifier + \": registering service...\");\n EurekaHttpResponse httpResponse;\n try {\n httpResponse = eurekaTransport.registrationClient.register(instanceInfo);\n } catch (Exception e) {\n logger.warn(\"{} - registration failed {}\", PREFIX + appPathIdentifier, e.getMessage(), e);\n throw e;\n }\n if (logger.isInfoEnabled()) {\n logger.info(\"{} - registration status: {}\", PREFIX + appPathIdentifier, httpResponse.getStatusCode());\n }\n return httpResponse.getStatusCode() == 204;\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"原来调用了 EurekaHttpClient 封装的客户端请求对象来进行注册的,再继续深探 registrationClient.register 方法,于是我们来到了 AbstractJerseyEurekaHttpClient.register 方法。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"@Override\npublic EurekaHttpResponse register(InstanceInfo info) {\n String urlPath = \"apps/\" + info.getAppName();\n ClientResponse response = null;\n try {\n Builder resourceBuilder = jerseyClient.resource(serviceUrl).path(urlPath).getRequestBuilder();\n addExtraHeaders(resourceBuilder);\n response = resourceBuilder\n .header(\"Accept-Encoding\", \"gzip\")\n .type(MediaType.APPLICATION_JSON_TYPE)\n .accept(MediaType.APPLICATION_JSON)\n // 注释:打包带上当前应用的所有信息 info\n .post(ClientResponse.class, info);\n return anEurekaHttpResponse(response.getStatus()).headers(headersOf(response)).build();\n } finally {\n if (logger.isDebugEnabled()) {\n logger.debug(\"Jersey HTTP POST {}/{} with instance {}; statusCode={}\", serviceUrl, urlPath, info.getId(),\n response == null ? \"N/A\" : response.getStatus());\n }\n if (response != null) {\n response.close();\n }\n }\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"调用的是 Jersey RESTful 框架来进行请求的,然后在 EurekaServer 那边就会在 ApplicationResource.addInstance 方法接收客户端的注册请求,因此我们的 EurekaClient 是如何注册的就到此为止了。","attrs":{}}]}]}],"attrs":{}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章