@hile/micro 2.0.8 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -14
- package/dist/application.d.ts +66 -3
- package/dist/application.js +676 -114
- package/dist/registry.d.ts +8 -1
- package/dist/registry.js +146 -36
- package/dist/server.d.ts +1 -0
- package/dist/server.js +13 -2
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -108,20 +108,50 @@ const result = await response();
|
|
|
108
108
|
|
|
109
109
|
### 熔断器 (Circuit Breaker)
|
|
110
110
|
|
|
111
|
-
`call()` 自动跟踪每个 namespace 下各 peer
|
|
111
|
+
`call()` 和 `stream()` 自动跟踪每个 namespace 下各 peer 的调用结果。熔断状态保存在调用方 `Application` 的本地内存中,不依赖 Redis、数据库或 Registry 共享状态。
|
|
112
112
|
|
|
113
113
|
| 场景 | 行为 |
|
|
114
114
|
|------|------|
|
|
115
|
-
|
|
|
116
|
-
|
|
|
117
|
-
|
|
|
118
|
-
|
|
|
115
|
+
| `closed` 状态调用失败 | 累计连续失败次数 |
|
|
116
|
+
| 连续失败达到阈值 | peer 进入 `open`,选路时通过 Registry `exclude` 临时排除 |
|
|
117
|
+
| 冷却时间到期 | peer 进入 `half-open`,只放行少量探测请求 |
|
|
118
|
+
| `half-open` 探测成功 | 累计成功次数,达到阈值后恢复 `closed` |
|
|
119
|
+
| `half-open` 探测失败 | 重新进入 `open`,冷却时间指数退避 |
|
|
120
|
+
| 所有 `open` peer 都被排除 | 重置该 namespace 的本地熔断状态,重新选择 peer;若 Registry 不可用但已选中的缓存 Client 仍连接,则沿用缓存发起探测,避免完全饿死 |
|
|
119
121
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
122
|
+
如果 peer 正处于 `half-open` 且探测名额已满,本次调用不会占用额外探测名额;有其他 peer 时改选其他 peer,没有可用 peer 时需要在已有探测完成后再尝试。
|
|
123
|
+
|
|
124
|
+
默认策略:
|
|
125
|
+
|
|
126
|
+
| 配置 | 默认值 | 说明 |
|
|
127
|
+
|------|--------|------|
|
|
128
|
+
| `failureThreshold` | `3` | 连续失败 3 次后打开熔断 |
|
|
129
|
+
| `failureWindowMs` | `60000` | 连续失败统计窗口 |
|
|
130
|
+
| `successThreshold` | `2` | half-open 连续成功 2 次后恢复 |
|
|
131
|
+
| `cooldownMs` | `10000` | 首次打开后的冷却时间 |
|
|
132
|
+
| `maxCooldownMs` | `120000` | 指数退避冷却上限 |
|
|
133
|
+
| `halfOpenMaxProbes` | `1` | half-open 同时放行的探测数 |
|
|
134
|
+
|
|
135
|
+
可以在构造 `Application` 时覆盖:
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
const app = new Application({
|
|
139
|
+
namespace: 'checkout',
|
|
140
|
+
registry: { host, port },
|
|
141
|
+
circuitBreaker: {
|
|
142
|
+
failureThreshold: 3,
|
|
143
|
+
failureWindowMs: 60_000,
|
|
144
|
+
successThreshold: 2,
|
|
145
|
+
cooldownMs: 10_000,
|
|
146
|
+
maxCooldownMs: 120_000,
|
|
147
|
+
halfOpenMaxProbes: 1,
|
|
148
|
+
shouldRecordFailure: (err) => true,
|
|
149
|
+
shouldRetry: (err) => true,
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
`shouldRecordFailure` 返回 `false` 的错误不会计入熔断;`shouldRetry` 返回 `false` 的错误不会自动重试。可用它们区分业务错误和连接、超时等基础设施错误。
|
|
125
155
|
|
|
126
156
|
### 请求超时
|
|
127
157
|
|
|
@@ -174,7 +204,7 @@ await app.call('svc', '/api', data, { timeout: 5000, retries: 3 }); // 超时 5s
|
|
|
174
204
|
await app.call('svc', '/api', data, { timeout: 5000, retries: 0 }); // 超时 5s, 不重试
|
|
175
205
|
```
|
|
176
206
|
|
|
177
|
-
重试策略:失败 → `
|
|
207
|
+
重试策略:失败 → 按 `shouldRecordFailure` 更新本地熔断状态 → 按 `shouldRetry` 判断是否继续下一次尝试 → 下次发现服务时用 `getActiveExcludes()` 排除 `open` 或探测名额已满的 `half-open` peer → Registry `/‑/find` 返回其他 peer。
|
|
178
208
|
|
|
179
209
|
### 流式调用 (Stream)
|
|
180
210
|
|
|
@@ -213,7 +243,7 @@ for await (const chunk of stream) {
|
|
|
213
243
|
**注意事项**:
|
|
214
244
|
- 普通 handler(返回非 async iterable)被 `stream()` 调用时会报错 `"Invalid async iterable"`
|
|
215
245
|
- 不需要流式传输时用 `call()`,不要用 `stream()` 取单次返回值
|
|
216
|
-
- `stream()` 享有与 `call()`
|
|
246
|
+
- `stream()` 享有与 `call()` 相同的选路与熔断;同步建流失败可按 `retries` 重试,流已返回后的异步错误会计入熔断但不会自动重放流
|
|
217
247
|
|
|
218
248
|
### 健康检查
|
|
219
249
|
|
|
@@ -236,7 +266,7 @@ const health = await app.dispatch('/-/health', {});
|
|
|
236
266
|
|
|
237
267
|
### 缓存降级
|
|
238
268
|
|
|
239
|
-
当 Registry 不可用但本地仍有已缓存的 Client 连接时,`get()`
|
|
269
|
+
当 Registry 不可用但本地仍有已缓存的 Client 连接时,`get()` 自动降级使用缓存。熔断器在“所有 `open` peer 都被排除”并触发本地 reset 时,也会保留已经选中的可用缓存 Client,不会因为 Registry 短暂不可用而丢弃仍可通信的连接。
|
|
240
270
|
|
|
241
271
|
| 场景 | 行为 |
|
|
242
272
|
|------|------|
|
|
@@ -409,7 +439,7 @@ class Application extends Server {
|
|
|
409
439
|
},
|
|
410
440
|
): Promise<T>;
|
|
411
441
|
|
|
412
|
-
// 流式调用:get + stream +
|
|
442
|
+
// 流式调用:get + stream + 熔断;同步建流失败可重试
|
|
413
443
|
// provider handler 必须返回 async generator,consumer 获得 Readable stream
|
|
414
444
|
stream(
|
|
415
445
|
namespace: string,
|
package/dist/application.d.ts
CHANGED
|
@@ -15,6 +15,28 @@ type EnvRequestResult<T extends Record<string, Record<string, any>>, R> = R exte
|
|
|
15
15
|
} ? {
|
|
16
16
|
[K in N]: EnvFieldsForRequest<T, N, F>;
|
|
17
17
|
} : never;
|
|
18
|
+
export type CircuitBreakerStatus = 'closed' | 'open' | 'half-open';
|
|
19
|
+
export type CircuitBreakerOptions = {
|
|
20
|
+
/** 连续失败达到阈值后打开熔断器,默认 3 */
|
|
21
|
+
failureThreshold?: number;
|
|
22
|
+
/** 连续失败统计窗口(毫秒),默认 60000 */
|
|
23
|
+
failureWindowMs?: number;
|
|
24
|
+
/** half-open 探测连续成功达到阈值后恢复,默认 2 */
|
|
25
|
+
successThreshold?: number;
|
|
26
|
+
/** 首次打开后的冷却时间(毫秒),默认 10000 */
|
|
27
|
+
cooldownMs?: number;
|
|
28
|
+
/** 指数退避冷却时间上限(毫秒),默认 120000 */
|
|
29
|
+
maxCooldownMs?: number;
|
|
30
|
+
/** half-open 状态下同时放行的探测请求数,默认 1 */
|
|
31
|
+
halfOpenMaxProbes?: number;
|
|
32
|
+
/** 返回 false 的错误不会计入熔断,默认全部计入 */
|
|
33
|
+
shouldRecordFailure?: (err: unknown) => boolean;
|
|
34
|
+
/** 返回 false 的错误不会自动重试,默认全部允许重试 */
|
|
35
|
+
shouldRetry?: (err: unknown) => boolean;
|
|
36
|
+
};
|
|
37
|
+
type RegistryLookupOptions = {
|
|
38
|
+
allowExcludedCachedFallback?: boolean;
|
|
39
|
+
};
|
|
18
40
|
export type GetEnvVariablesResult<T extends Record<string, Record<string, any>>, Requests extends readonly EnvRequest<T>[]> = UnionToIntersection<EnvRequestResult<T, Requests[number]>>;
|
|
19
41
|
export type ApplicationProps = {
|
|
20
42
|
namespace: string;
|
|
@@ -23,30 +45,68 @@ export type ApplicationProps = {
|
|
|
23
45
|
registryLookupTimeoutMs?: number;
|
|
24
46
|
/** 单次 request() 等待响应的上限(毫秒),默认 `30000` */
|
|
25
47
|
requestTimeoutMs?: number;
|
|
48
|
+
/** 本地内存熔断策略配置 */
|
|
49
|
+
circuitBreaker?: CircuitBreakerOptions;
|
|
26
50
|
} & MicroServerProps;
|
|
27
51
|
export declare class Application extends Server {
|
|
28
52
|
private registry?;
|
|
29
53
|
private reconnectTimeout?;
|
|
30
54
|
private registryReconnectPromise;
|
|
55
|
+
private registryReconnectGeneration;
|
|
31
56
|
/** 为 true 时不再向 Registry 重连(listen 返回的 teardown 已触发) */
|
|
32
57
|
private stopped;
|
|
58
|
+
private listenGeneration;
|
|
33
59
|
private readonly _registry_address;
|
|
34
60
|
private readonly _registryLookupTimeoutMs;
|
|
35
61
|
private readonly _requestTimeoutMs;
|
|
62
|
+
private readonly _circuitBreaker;
|
|
36
63
|
private readonly namespaces;
|
|
37
|
-
private static readonly CB_COOLDOWN_MS;
|
|
38
64
|
private readonly circuitBreakers;
|
|
39
|
-
private readonly fallbacks;
|
|
40
65
|
private readonly topics;
|
|
66
|
+
private readonly publishedTopics;
|
|
67
|
+
private readonly publishedTopicRevisions;
|
|
68
|
+
private readonly publishedTopicDirty;
|
|
69
|
+
private readonly publishedTopicVersions;
|
|
70
|
+
private readonly publishedTopicSignatures;
|
|
71
|
+
private readonly topicSyncs;
|
|
72
|
+
private readonly topicUpdateVersions;
|
|
73
|
+
private publishIntentVersion;
|
|
41
74
|
constructor(props: ApplicationProps);
|
|
75
|
+
private dispatchTopicUpdate;
|
|
42
76
|
listen(port?: number): Promise<() => Promise<void>>;
|
|
43
77
|
private scheduleRegistryRetry;
|
|
78
|
+
private canUsePubSub;
|
|
79
|
+
private assertCanUsePubSub;
|
|
80
|
+
private ensureRegistryReconnectScheduled;
|
|
81
|
+
private handleRegistrySyncFailure;
|
|
82
|
+
private registryRequestOptions;
|
|
83
|
+
private recordPublishedTopic;
|
|
84
|
+
private enqueueTopicSync;
|
|
85
|
+
private syncPublishedTopic;
|
|
86
|
+
private syncUnpublishedTopic;
|
|
87
|
+
private syncUnsubscribedTopic;
|
|
88
|
+
private syncRestoredSubscription;
|
|
89
|
+
private restoreSubscription;
|
|
90
|
+
private rollbackLocalSubscription;
|
|
44
91
|
private reconnectToRegistry;
|
|
92
|
+
private peerKey;
|
|
93
|
+
private getPeerStates;
|
|
94
|
+
private getOrCreatePeerState;
|
|
95
|
+
private deletePeerState;
|
|
96
|
+
private openCircuit;
|
|
97
|
+
private acquireCircuitProbe;
|
|
98
|
+
private shouldRecordCircuitFailure;
|
|
99
|
+
private shouldRetryCircuitFailure;
|
|
45
100
|
private recordSuccess;
|
|
46
101
|
private recordFailure;
|
|
102
|
+
private getActiveCircuitExcludes;
|
|
47
103
|
private getActiveExcludes;
|
|
104
|
+
private trackCircuitStream;
|
|
105
|
+
private selectCircuitClient;
|
|
106
|
+
private selectCircuitProbe;
|
|
48
107
|
private findFromRegistry;
|
|
49
108
|
get(namespace: string, exclude?: string[]): Promise<Client>;
|
|
109
|
+
protected resolveClient(namespace: string, exclude?: string[], options?: RegistryLookupOptions): Promise<Client>;
|
|
50
110
|
call<T = any>(namespace: string, url: string, data: any, options?: {
|
|
51
111
|
timeout?: number;
|
|
52
112
|
retries?: number;
|
|
@@ -60,7 +120,10 @@ export declare class Application extends Server {
|
|
|
60
120
|
update: (payload: T) => Promise</*elided*/ any>;
|
|
61
121
|
unpublish: () => Promise</*elided*/ any>;
|
|
62
122
|
}>;
|
|
63
|
-
/**
|
|
123
|
+
/**
|
|
124
|
+
* 对同一 topic 可多次 subscribe,各自独立回调。
|
|
125
|
+
* 传入同一个 callback 引用第二次调用时幂等返回 unsubscribe,不重复注册。
|
|
126
|
+
*/
|
|
64
127
|
subscribe<T = any>(topic: string, callback: (data: T) => any, isReconnect?: boolean): Promise<() => Promise<void>>;
|
|
65
128
|
}
|
|
66
129
|
export {};
|