Eureka源碼分析:Eureka不會進行二次Replication的原因
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
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的原因