[Source learn together - Micro Services] Source Nexflix Eureka Seven: the registration process to Debug Eureka unit test

Foreword

A talk on how eureka client is registered, has been with the source code http request is sent up, so when devoting time to see eureka client registration, regiter just looking for a place to find for a long time, then the client sends a request to http server how to end, server end is to deal with it?

With such a question today began reading the source code.

Should the reprint please indicate the source: a flower count romantic

Source Reading

Where to read from?

Last lecture we know, follow up client has been registered to the AbstractJersey2EurekaHttpClient.registermethod, which look at the source code here:

public EurekaHttpResponse<Void> register(InstanceInfo info) {
        String urlPath = "apps/" + info.getAppName();
        Response response = null;
        try {
            // 发送请求,类似于:http://localhost:8080/v2/apps/ServiceA
            // 发送的是post请求,服务实例的对象被打成了一个json发送,包括自己的主机、ip、端口号
            // eureka server 就知道了这个ServiceA这个服务,有一个服务实例,比如是在192.168.31.109、host-01、8761端口
            Builder resourceBuilder = jerseyClient.target(serviceUrl).path(urlPath).request();
            addExtraProperties(resourceBuilder);
            addExtraHeaders(resourceBuilder);
            response = resourceBuilder
                    .accept(MediaType.APPLICATION_JSON)
                    .acceptEncoding("gzip")
                    .post(Entity.json(info));
            return anEurekaHttpResponse(response.getStatus()).headers(headersOf(response)).build();
        } finally {
            if (logger.isDebugEnabled()) {
                logger.debug("Jersey2 HTTP POST {}/{} with instance {}; statusCode={}", serviceUrl, urlPath, info.getId(),
                        response == null ? "N/A" : response.getStatus());
            }
            if (response != null) {
                response.close();
            }
        }
    }

That this case we can certainly guess, server-side should have a controller to receive the http request, then quietly do some registered logic.

Then we have from the /apps/start with this keyword, global search:

image.png

Global search results are as follows, where you can see a lot of test calls up a box here is not to call our controller interface similar to it? Directly into the point of view, then a follow up step by step.

Source code analysis

Then he says, and follow-up ApplicationResourceof this class can be found as follows:

@Path("{appId}")
public ApplicationResource getApplicationResource(
        @PathParam("version") String version,
        @PathParam("appId") String appId) {
    CurrentRequestVersion.set(Version.toEnum(version));
    return new ApplicationResource(appId, serverConfig, registry);
}

This can be appreciated appName appId as we pass before, this is followed by a direct construction of ApplicationResourceexample, and then follow the code, enter ApplicationResource, we can see a lot @GET, @POSTand other interfaces restful, we remember the register method above, HTTP request sent POST method is used, so here we see the direct @POSTrequest

@POST
@Consumes({"application/json", "application/xml"})
public Response addInstance(InstanceInfo info,
                            @HeaderParam(PeerEurekaNode.HEADER_REPLICATION) String isReplication) {
    logger.debug("Registering instance {} (replication={})", info.getId(), isReplication);
    // validate that the instanceinfo contains all the necessary required fields
    if (isBlank(info.getId())) {
        return Response.status(400).entity("Missing instanceId").build();
    } else if (isBlank(info.getHostName())) {
        return Response.status(400).entity("Missing hostname").build();
    } else if (isBlank(info.getIPAddr())) {
        return Response.status(400).entity("Missing ip address").build();
    } else if (isBlank(info.getAppName())) {
        return Response.status(400).entity("Missing appName").build();
    } else if (!appName.equals(info.getAppName())) {
        return Response.status(400).entity("Mismatched appName, expecting " + 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();
    }

    // handle cases where clients may be registering with bad DataCenterInfo with missing data
    DataCenterInfo dataCenterInfo = info.getDataCenterInfo();
    if (dataCenterInfo instanceof UniqueIdentifier) {
        String dataCenterInfoId = ((UniqueIdentifier) dataCenterInfo).getId();
        if (isBlank(dataCenterInfoId)) {
            boolean experimental = "true".equalsIgnoreCase(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();
            } else if (dataCenterInfo instanceof AmazonInfo) {
                AmazonInfo amazonInfo = (AmazonInfo) dataCenterInfo;
                String effectiveId = amazonInfo.get(AmazonInfo.MetaDataKey.instanceId);
                if (effectiveId == null) {
                    amazonInfo.getMetadata().put(AmazonInfo.MetaDataKey.instanceId.getName(), info.getId());
                }
            } else {
                logger.warn("Registering DataCenterInfo of type {} without an appropriate id", dataCenterInfo.getClass());
            }
        }
    }

    registry.register(info, "true".equals(isReplication));
    return Response.status(204).build();  // 204 to be backwards compatible
}

Since the code is not very long, where the interception gave out. In fact, here to do it is very simple.

  1. Do some routine chek, check the registration examples InstanceInfoof some basic information
  2. DataCenter related operations, there is also the Amazon cloud, we skip
  3. registry.register(info, "true".equals(isReplication)); Here is the core of registration, we continue down
public void register(final InstanceInfo info, final boolean isReplication) {
    int leaseDuration = Lease.DEFAULT_DURATION_IN_SECS;
    if (info.getLeaseInfo() != null && info.getLeaseInfo().getDurationInSecs() > 0) {
        leaseDuration = info.getLeaseInfo().getDurationInSecs();
    }
    super.register(info, leaseDuration, isReplication);
    replicateToPeers(Action.Register, info.getAppName(), info.getId(), info, null, isReplication);
}

public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {
    try {
        read.lock();
        Map<String, Lease<InstanceInfo>> gMap = registry.get(registrant.getAppName());
        REGISTER.increment(isReplication);
        if (gMap == null) {
            final ConcurrentHashMap<String, Lease<InstanceInfo>> gNewMap = new ConcurrentHashMap<String, Lease<InstanceInfo>>();
            gMap = registry.putIfAbsent(registrant.getAppName(), gNewMap);
            if (gMap == null) {
                gMap = gNewMap;
            }
        }
        Lease<InstanceInfo> existingLease = gMap.get(registrant.getId());
        // Retain the last dirty timestamp without overwriting it, if there is already a lease
        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)
                    this.expectedNumberOfRenewsPerMin = this.expectedNumberOfRenewsPerMin + 2;
                    this.numberOfRenewsPerMinThreshold =
                            (int) (this.expectedNumberOfRenewsPerMin * serverConfig.getRenewalPercentThreshold());
                }
            }
            logger.debug("No previous lease information found; it is new registration");
        }
        Lease<InstanceInfo> lease = new Lease<InstanceInfo>(registrant, leaseDuration);
        if (existingLease != null) {
            lease.setServiceUpTimestamp(existingLease.getServiceUpTimestamp());
        }
        gMap.put(registrant.getId(), lease);
        synchronized (recentRegisteredQueue) {
            recentRegisteredQueue.add(new Pair<Long, String>(
                    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();
        }
        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();
    }
}

到了这里东西就有点多了,我们慢慢梳理。

  1. reda.lock() 这里使用的是读锁,方便多个服务实例同时来注册
  2. 这里关键信息是registry的数据结构,同时这也是保存注册实例的对象。
private final ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>> registry
            = new ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>>();

ConcurrentHashMap的key是appName
第二层Map的key是appId,所以数据结构格式类似于:

{
    “ServiceA”: {
        “001”: Lease<InstanceInfo>,
        “002”: Lease<InstanceInfo>,
        “003”: Lease<InstanceInfo>
    },
    “ServiceB”: {
        “001”: Lease<InstanceInfo>
    }
}
  1. 这里面还有两个队列recentRegisteredQueuerecentlyChangedQueue,其中registerQueue默认保存最近1000条注册的实例信息。
  2. 后面就是一些状态设置之类的操作

注册表使用场景

我们注册完成之后,打开eureka 后台配置页面,可以看到自己的实例已经在页面上了,那么这个东东是如何展示的呢?

我们都知道eureka-resources模块下有很多jsp信息,点开status.jsp查看一下:

image.png

这里用到了 serverContext.getRegistry().getSortedApplications(), 然后在通过获取的Applicaiton 去执行app.getInstances()等到了所有大的服务实例信息。

这里我们还需要回头看下EurekaBootStrap中的代码,看看Application是如何来的。

PeerAwareInstanceRegistryImpl.javagetSortedApplications()一直跟到 AbstractInstanceRegistry.javagetApplicationsFromMultipleRegions(),如下图所示:

image.png

看到这里是不是就真相大白了?
这里再总结一下:

在jsp代码中,拿到了EurekaServerContext,所以之前为什么要将这个东东放到一个Holder里面去,就是随时都要从这个里面去获取一些数据

Then will, get to the registry, PeerAwareInstanceRegistry, registry, get all the information from the inside of the service from EurekaServerContext, the underlying data structure from the map, get the information about all the services registered, traverse, packaged into something called the Application of go an Application on behalf of a service, which contains many service instances.

Services registered a flow chart of Eureka

image.png

Declare

This article starting from my blog: https://www.cnblogs.com/wang-meng and public numbers: One ramiflorous be considered romantic , should reprint please indicate the source!

Interested partner may be concerned about the small number of individual public: One branch count romantic flowers

22.jpg

Guess you like

Origin www.cnblogs.com/wang-meng/p/12116408.html