apache / apache/servicecomb-java-chassis

在SafeMode下如果pullInstances成功后无法在退出SafeMode后老化旧的实例

オープン
#2,820 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る
主要言語
Java
スター
1.9k
フォーク
814
平均マージ
8日 23時間
マージ済み PR(30日)
1

説明

**Describe the bug**
背景:servicecomb-java-chassis 2.1.5在使用过程中,我们的rpc调用服务端会校验客户端在请求头中携带的目标实例ID,发现某些rpc请求的实例ID对应已经老化的实例ID。
现象:服务缓存中存在已经老化的旧实例。
原因分析:RefreshableMicroserviceCache缓存会在pullInstance后更新记录的revisionId,如果在safemode期间pullInstance成功了会导致此处rev被更新的和sc中的一致,在退出安全模式后携带此rev再次pullInstance时如果此时实例在sc中未有变更会直接响应304,不会触发缓存刷新,导致在安全模式中不老化的实例无法正常下线。
解决建议:在安全模式下不更新缓存中记录的revisionId。
**To Reproduce**
复现方法:参考以下测试用例。
_RefreshableMicroserviceCacheTest.java_
```
@Test
public void refresh_exit_safe_mode_down_old_instance() {
ArrayList instances = new ArrayList<>();
findServiceInstancesOprHolder.value = params -> {
Assert.assertEquals("consumerId", params[0]);
Assert.assertEquals("app", params[1]);
Assert.assertEquals("svc", params[2]);
Assert.assertEquals("0.0.0.0+", params[3]);
MicroserviceInstances microserviceInstances = new MicroserviceInstances();
microserviceInstances.setNeedRefresh(!String.valueOf(instances.hashCode()).equals(params[4]));
microserviceInstances.setRevision(String.valueOf(instances.hashCode()));
microserviceInstances.setMicroserviceNotExist(false);

FindInstancesResponse instancesResponse = new FindInstancesResponse();
instancesResponse.setInstances(instances);

microserviceInstances.setInstancesResponse(instancesResponse);
return microserviceInstances;
};

// at the beginning, no instances in cache
List cachedInstances = microserviceCache.getInstances();
Assert.assertEquals(0, cachedInstances.size());
Assert.assertNull(microserviceCache.getRevisionId());

// find 1 instance from sc
MicroserviceInstance microserviceInstance = new MicroserviceInstance();
instances.add(microserviceInstance);
microserviceInstance.setInstanceId("instanceId00");

microserviceCache.refresh();
Assert.assertEquals(MicroserviceCacheStatus.REFRESHED, microserviceCache.getStatus());

cachedInstances = microserviceCache.getInstances();
Assert.assertEquals(1, cachedInstances.size());
MicroserviceInstance instance = cachedInstances.iterator().next();
Assert.assertEquals("instanceId00", instance.getInstanceId());
Assert.assertEquals("-738005329", microserviceCache.getRevisionId());

// 2nd time, clear pull instances
MicroserviceInstance microserviceInstance1 = new MicroserviceInstance();
instances.clear();
instances.add(microserviceInstance1);
microserviceInstance1.setInstanceId("instanceId01");

microserviceCache.refresh();
Assert.assertEquals(MicroserviceCacheStatus.REFRESHED, microserviceCache.getStatus());
cachedInstances = microserviceCache.getInstances();
Assert.assertEquals(1, cachedInstances.size());
Assert.assertEquals("instanceId01", cachedInstances.get(0).getInstanceId());

// 3nd time, 进入安全模式,第三次拉取不下线实例
microserviceCache.onSafeModeChanged(new SafeModeChangeEvent(true));

MicroserviceInstance microserviceInstance2 = new MicroserviceInstance();
instances.clear();
instances.add(microserviceInstance2);
microserviceInstance2.setInstanceId("instanceId02");

microserviceCache.refresh();
Assert.assertEquals(MicroserviceCacheStatus.REFRESHED, microserviceCache.getStatus());

cachedInstances = microserviceCache.getInstances();
Assert.assertEquals(2, cachedInstances.size());
Assert.assertEquals("instanceId02", cachedInstances.get(0).getInstanceId());
Assert.assertEquals("instanceId01", cachedInstances.get(1).getInstanceId());

// 4nd time, 退出安全模式,第四次拉取需要下线实例
microserviceCache.onSafeModeChanged(new SafeModeChangeEvent(false));

microserviceCache.refresh();
Assert.assertEquals(MicroserviceCacheStatus.REFRESHED, microserviceCache.getStatus());

cachedInstances = microserviceCache.getInstances();
Assert.assertEquals(1, cachedInstances.size());
Assert.assertEquals("instanceId02", cachedInstances.get(0).getInstanceId());
}
```

**Expected behavior**
在退出安全模式后可以及时下线老实例。

**Platform And Runtime (please complete the following information):**
环境无差别,稳定复现

**Additional context**

コントリビューションガイド

このリポジトリのコントリビューションガイドは索引されていません

調査の方向性

RefreshableMicroserviceCache と、名前が示されているテスト RefreshableMicroserviceCacheTest.java から始め、特に refresh() と safe-mode の状態変更を確認してください。refresh_exit_safe_mode_down_old_instance のシーケンスを再現してください。safe mode を抜けた後、以前キャッシュされていたインスタンスが削除され、現在のインスタンスだけが残るはずです。

索引モデルが issue の本文から書いたものです。

評価

技術スタック
java
領域
backend, distributed-systems
issue の種類
バグ
難易度
3/5
見積もり時間
1〜2日
活発さ
停滞
明瞭さ
明確に書かれている
初心者へのやさしさ
48/100

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。