1. 程式人生 > >Eureka源碼分析:Eureka不會進行二次Replication的原因

Eureka源碼分析:Eureka不會進行二次Replication的原因

實例 .get 新版 replica ide 倉庫 efault springmvc XML

Eureka不會進行二次同步註冊信息

Eureka會將本實例中的註冊信息同步到它的peer節點上,這是我們都知道的特性。然而,當peer節點收到同步數據後,並不會將這些信息再同步到它自己的peer節點上。如果有A, B, C三個實例,A配B, B配C, C配A, 那麽當向A註冊一個新服務時,只有A, B兩個Eureka實例會有新服務的註冊信息,C是沒有的。這一點在官方wiki上並沒有明確說明。下面通過源碼來查找一下原因。

構建

Eureka當前版本 (https://github.com/Netflix/eureka) 使用gradle作為構建工具,不過Git倉庫裏提供了gradlew,所以也不用我們自己去下載。如果自己下載了gradle

的最新版本3.X,那麽反而會因為兼容性問題導致構建失敗,因為官方使用的是2.1.0版本。構建之前一定要科學上網,否則很多依賴是下載不到的:

./gradlew build -x test // 跳過測試
  • 1
  • 1

源碼定位與分析

Eureka本質上是一個Servlet應用,它使用jersey作為RESTful框架來構造自己的REST服務。在eureka-server模塊中可以找到web.xml文件,在build/libs目錄下可以看到構建完成後生成的war包。我們要找的代碼,在eureka-core模塊的com.netflix.eureka.resource包下。jersey相比SpringMVC

來說它是一個更加標準的HTTP RESTful框架,因此很自然可以猜想到resource就是請求路由所在的包。

查閱官方wiki, 我們知道eureka對外暴露接口的形式為:

GET/POST/PUT/DELETE /eureka/v2/apps/{APP-ID}
  • 1
  • 1

我們在ApplicationsResource類中可以發現如下註解:

@Path("/{version}/apps")
@Produces({"application/xml", "application/json"})
public class ApplicationsResource {
    // ... ...
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

由此可以確定該類就是我們要找的重點。查閱該類,會發現有如下方法:

/**
     * Gets information about a particular [email protected] com.netflix.discovery.shared.Application}.
     *
     * @param version
     *            the version of the request.
     * @param appId
     *            the unique application identifier (which is the name) of the
     *            application.
     * @return information about a particular application.
     */
    @Path("{appId}")
    public ApplicationResource getApplicationResource(
            @PathParam("version") String version,
            @PathParam("appId") String appId) {
        CurrentRequestVersion.set(Version.toEnum(version));
        return new ApplicationResource(appId, serverConfig, registry); // 交給ApplicationResource處理
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

通過註釋得知該方法用來處理客戶端查詢某個服務信息的HTTP請求。但是方法體中並沒有具體的查詢邏輯,而是委托給了ApplicationReource類進行處理。查閱該類,可以發現註冊信息的CRUD操作邏輯都是在這裏實現的。我們重點關註一下註冊方法,因為Eureka在收到新服務的註冊信息時會馬上將其同步到peer節點中:

@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

        // 參數檢查 ...

        // 調用註冊邏輯
        registry.register(info, "true".equals(isReplication));
        return Response.status(204).build();  // 204 to be backwards compatible
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

註意register的第二個參數:

"true".equals(isReplication)
  • 1
  • 1

isReplication是從請求頭中獲取的字符串,因此可以得知,Eureka在向peer節點發送同步請求時會在請求頭中攜帶自定義的x-netflix-discovery-replication頭:

public static final String HEADER_REPLICATION = "x-netflix-discovery-replication";
  • 1
  • 1

peer節點通過該請求頭來判斷當前請求是其它Eureka節點的同步請求還是服務的註冊請求。我們假定當前請求是上一個Eureka發來的同步請求,那麽這裏第二個參數的值應該為true

繼續看register()方法,這是個PeerAwareInstanceRegistry接口的接口方法,該接口只有一個唯一實現PeerAwareInstanceRegistryImpl

/**
     * Registers the information about the [email protected] InstanceInfo} and replicates
     * this information to all peer eureka nodes. If this is replication event
     * from other replica nodes then it is not replicated.
     *
     * @param info
     *            the [email protected] InstanceInfo} to be registered and replicated.
     * @param isReplication
     *            true if this is a replication event from other replica nodes,
     *            false otherwise.
     */
    @Override
    public void register(final InstanceInfo info, final boolean isReplication) {
        int leaseDuration = Lease.DEFAULT_DURATION_IN_SECS;
        if (info.getLeaseInfo() != null && info.getLeaseInfo().getDurationInSecs() > 0) {
            leaseDuration = info.getLeaseInfo().getDurationInSecs();
        }
        // 調用父類的實現執行註冊邏輯
        super.register(info, leaseDuration, isReplication);
        // 將該註冊請求同步到peer節點中
        replicateToPeers(Action.Register, info.getAppName(), info.getId(), info, null, isReplication);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

首先通過英文註釋可以了解到,如果當前註冊請求是一個Replication請求,那麽該註冊請求不會被再次Replicate到下一個peer節點中。我們進入到關鍵的replicateToPeers()方法中看看為什麽不會被同步:

private void replicateToPeers(Action action, String appName, String id,
                                  InstanceInfo info /* optional */,
                                  InstanceStatus newStatus /* optional */, boolean isReplication) {
        Stopwatch tracer = action.getTimer().start();
        try {
            // ... ...

            // If it is a replication already, do not replicate again as this will create a poison replication
            // 如果 isReplication 為true, 即當前是個同步註冊信息的請求
            // 這裏就直接返回了
            if (peerEurekaNodes == Collections.EMPTY_LIST || isReplication) {
                return;
            }

            // 向自己的peer節點同步註冊信息
            for (final PeerEurekaNode node : peerEurekaNodes.getPeerEurekaNodes()) {
                // If the url represents this host, do not replicate to yourself.
                if (peerEurekaNodes.isThisMyUrl(node.getServiceUrl())) {
                    continue;
                }
                replicateInstanceActionsToPeers(action, appName, id, info, newStatus, node);
            }
        } finally {
            tracer.stop();
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

可以看到下面的代碼

if (peerEurekaNodes == Collections.EMPTY_LIST || isReplication) {
    return;
}
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

就是Eureka不進行二次Replication的原因,如果當前請求是從其他Eureka發來的同步請求,那麽該方法就直接返回了,不再執行後面的同步邏輯。

結論

通過上面的追蹤我們確認了Eureka不進行二次同步是作者有意而為之,並不是bug。但是官方wiki上並沒有明確寫明這一點,文檔並不太完善。可以考慮提一個Issue提醒一下開發組。

http://blog.csdn.net/neosmith/article/details/52912645

Eureka源碼分析:Eureka不會進行二次Replication的原因