groupmq-plus 1.1.2 → 1.1.3
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/dist/index.cjs +113 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +123 -1
- package/dist/index.d.ts +123 -1
- package/dist/index.js +113 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -1349,5 +1349,127 @@ declare function getWorkersStatus<T = any>(workers: Worker<T>[]): {
|
|
|
1349
1349
|
}>;
|
|
1350
1350
|
};
|
|
1351
1351
|
//#endregion
|
|
1352
|
-
|
|
1352
|
+
//#region src/strategies/priority-strategy.d.ts
|
|
1353
|
+
type GroupConfig = {
|
|
1354
|
+
priority: number;
|
|
1355
|
+
concurrency: number;
|
|
1356
|
+
};
|
|
1357
|
+
/**
|
|
1358
|
+
* 自定义优先级计算函数
|
|
1359
|
+
* @param groupId 组 ID
|
|
1360
|
+
* @param config 从 Redis 读取的组配置
|
|
1361
|
+
* @returns 优先级数值(数字越大优先级越高)
|
|
1362
|
+
*/
|
|
1363
|
+
type OnGetPriority = (groupId: string, config: GroupConfig) => number | Promise<number>;
|
|
1364
|
+
/**
|
|
1365
|
+
* 严格优先级算法
|
|
1366
|
+
* 总是选择优先级最高的组,可能导致低优先级组饥饿
|
|
1367
|
+
*/
|
|
1368
|
+
type StrictAlgorithmConfig = {
|
|
1369
|
+
type: 'strict';
|
|
1370
|
+
};
|
|
1371
|
+
/**
|
|
1372
|
+
* 加权随机算法(默认)
|
|
1373
|
+
* 根据优先级计算概率选择,确保低优先级组也有机会被执行
|
|
1374
|
+
*/
|
|
1375
|
+
type WeightedRandomAlgorithmConfig = {
|
|
1376
|
+
type: 'weighted-random';
|
|
1377
|
+
/**
|
|
1378
|
+
* 最低优先级组的保底权重比例,默认 0.1(10%)
|
|
1379
|
+
* 例如:VIP 优先级 100,普通用户优先级 1
|
|
1380
|
+
* 普通用户的权重会被提升到 100 * 0.1 = 10,而不是 1
|
|
1381
|
+
* 这样普通用户大约有 10/(100+10) ≈ 9% 的概率被选中
|
|
1382
|
+
*/
|
|
1383
|
+
minWeightRatio?: number;
|
|
1384
|
+
};
|
|
1385
|
+
/**
|
|
1386
|
+
* 时间衰减算法
|
|
1387
|
+
* 等待时间越长,优先级加成越高,确保所有任务最终都会被执行
|
|
1388
|
+
*/
|
|
1389
|
+
type AgingAlgorithmConfig = {
|
|
1390
|
+
type: 'aging';
|
|
1391
|
+
/**
|
|
1392
|
+
* 每等待多少毫秒增加 1 点优先级,默认 60000(1分钟)
|
|
1393
|
+
* 例如:VIP 优先级 100,普通用户优先级 1
|
|
1394
|
+
* 普通用户等待 100 分钟后,优先级变为 1 + 100 = 101,超过 VIP
|
|
1395
|
+
*/
|
|
1396
|
+
intervalMs?: number;
|
|
1397
|
+
};
|
|
1398
|
+
/**
|
|
1399
|
+
* 算法配置类型
|
|
1400
|
+
*/
|
|
1401
|
+
type AlgorithmConfig = StrictAlgorithmConfig | WeightedRandomAlgorithmConfig | AgingAlgorithmConfig;
|
|
1402
|
+
interface PriorityStrategyOptions {
|
|
1403
|
+
/**
|
|
1404
|
+
* 调度算法配置,默认 { type: 'weighted-random' }
|
|
1405
|
+
*
|
|
1406
|
+
* @example
|
|
1407
|
+
* // 严格优先级(VIP 绝对优先)
|
|
1408
|
+
* algorithm: { type: 'strict' }
|
|
1409
|
+
*
|
|
1410
|
+
* // 加权随机(默认,平衡公平性)
|
|
1411
|
+
* algorithm: { type: 'weighted-random', minWeightRatio: 0.1 }
|
|
1412
|
+
*
|
|
1413
|
+
* // 时间衰减(确保无饥饿)
|
|
1414
|
+
* algorithm: { type: 'aging', intervalMs: 60000 }
|
|
1415
|
+
*/
|
|
1416
|
+
algorithm?: AlgorithmConfig;
|
|
1417
|
+
/** 默认优先级(未配置的组使用此值),默认 1 */
|
|
1418
|
+
defaultPriority?: number;
|
|
1419
|
+
/** 优先级缓存 TTL(毫秒),默认 5000ms。设为 0 禁用缓存 */
|
|
1420
|
+
cacheTtlMs?: number;
|
|
1421
|
+
/**
|
|
1422
|
+
* 自定义优先级计算函数
|
|
1423
|
+
* 如果提供,将使用此函数计算优先级,否则直接使用 config.priority
|
|
1424
|
+
*
|
|
1425
|
+
* @example
|
|
1426
|
+
* // 根据 VIP 等级计算优先级
|
|
1427
|
+
* onGetPriority: (groupId, config) => {
|
|
1428
|
+
* if (groupId.startsWith('vip:')) return config.priority * 10;
|
|
1429
|
+
* return config.priority;
|
|
1430
|
+
* }
|
|
1431
|
+
*/
|
|
1432
|
+
onGetPriority?: OnGetPriority;
|
|
1433
|
+
}
|
|
1434
|
+
declare class PriorityStrategy implements DispatchStrategy {
|
|
1435
|
+
/** 本地缓存:groupId -> { priority, expiresAt } */
|
|
1436
|
+
private cache;
|
|
1437
|
+
/** 手动覆盖的优先级(优先级最高) */
|
|
1438
|
+
private overrides;
|
|
1439
|
+
private algorithmConfig;
|
|
1440
|
+
private defaultPriority;
|
|
1441
|
+
private cacheTtlMs;
|
|
1442
|
+
private onGetPriority?;
|
|
1443
|
+
constructor(options?: PriorityStrategyOptions);
|
|
1444
|
+
/**
|
|
1445
|
+
* 手动覆盖某个组的优先级(优先级高于 Redis 配置和 getPriority)
|
|
1446
|
+
* 主要用于测试或临时调整
|
|
1447
|
+
*/
|
|
1448
|
+
setPriority(groupId: string, priority: number): void;
|
|
1449
|
+
/**
|
|
1450
|
+
* 清除手动覆盖,恢复使用 Redis 配置或 getPriority
|
|
1451
|
+
*/
|
|
1452
|
+
clearPriority(groupId: string): void;
|
|
1453
|
+
/**
|
|
1454
|
+
* 获取组的基础优先级
|
|
1455
|
+
* 优先级来源顺序:overrides > cache > onGetPriority(config) 或 config.priority
|
|
1456
|
+
*/
|
|
1457
|
+
private resolvePriority;
|
|
1458
|
+
/**
|
|
1459
|
+
* 严格优先级算法:总是返回优先级最高的组
|
|
1460
|
+
*/
|
|
1461
|
+
private selectByStrict;
|
|
1462
|
+
/**
|
|
1463
|
+
* 加权随机算法:根据优先级计算概率选择
|
|
1464
|
+
* 使用 minWeightRatio 确保低优先级组也有机会
|
|
1465
|
+
*/
|
|
1466
|
+
private selectByWeightedRandom;
|
|
1467
|
+
/**
|
|
1468
|
+
* 时间衰减算法:等待时间越长,优先级加成越高
|
|
1469
|
+
*/
|
|
1470
|
+
private selectByAging;
|
|
1471
|
+
getNextGroup(queue: Queue<any>): Promise<string | null>;
|
|
1472
|
+
}
|
|
1473
|
+
//#endregion
|
|
1474
|
+
export { AddOptions, AgingAlgorithmConfig, AlgorithmConfig, BackoffStrategy, BullBoardGroupMQAdapter, DispatchStrategy, FlowJob, FlowOptions, GroupMQBullBoardAdapterOptions, Job, OnGetPriority, PriorityStrategy, PriorityStrategyOptions, Queue, QueueOptions, RepeatOptions, ReservedJob, StrictAlgorithmConfig, UnrecoverableError, WeightedRandomAlgorithmConfig, Worker, WorkerEvents, WorkerOptions, getWorkersStatus, waitForQueueToEmpty };
|
|
1353
1475
|
//# sourceMappingURL=index.d.cts.map
|
package/dist/index.d.ts
CHANGED
|
@@ -1349,5 +1349,127 @@ declare function getWorkersStatus<T = any>(workers: Worker<T>[]): {
|
|
|
1349
1349
|
}>;
|
|
1350
1350
|
};
|
|
1351
1351
|
//#endregion
|
|
1352
|
-
|
|
1352
|
+
//#region src/strategies/priority-strategy.d.ts
|
|
1353
|
+
type GroupConfig = {
|
|
1354
|
+
priority: number;
|
|
1355
|
+
concurrency: number;
|
|
1356
|
+
};
|
|
1357
|
+
/**
|
|
1358
|
+
* 自定义优先级计算函数
|
|
1359
|
+
* @param groupId 组 ID
|
|
1360
|
+
* @param config 从 Redis 读取的组配置
|
|
1361
|
+
* @returns 优先级数值(数字越大优先级越高)
|
|
1362
|
+
*/
|
|
1363
|
+
type OnGetPriority = (groupId: string, config: GroupConfig) => number | Promise<number>;
|
|
1364
|
+
/**
|
|
1365
|
+
* 严格优先级算法
|
|
1366
|
+
* 总是选择优先级最高的组,可能导致低优先级组饥饿
|
|
1367
|
+
*/
|
|
1368
|
+
type StrictAlgorithmConfig = {
|
|
1369
|
+
type: 'strict';
|
|
1370
|
+
};
|
|
1371
|
+
/**
|
|
1372
|
+
* 加权随机算法(默认)
|
|
1373
|
+
* 根据优先级计算概率选择,确保低优先级组也有机会被执行
|
|
1374
|
+
*/
|
|
1375
|
+
type WeightedRandomAlgorithmConfig = {
|
|
1376
|
+
type: 'weighted-random';
|
|
1377
|
+
/**
|
|
1378
|
+
* 最低优先级组的保底权重比例,默认 0.1(10%)
|
|
1379
|
+
* 例如:VIP 优先级 100,普通用户优先级 1
|
|
1380
|
+
* 普通用户的权重会被提升到 100 * 0.1 = 10,而不是 1
|
|
1381
|
+
* 这样普通用户大约有 10/(100+10) ≈ 9% 的概率被选中
|
|
1382
|
+
*/
|
|
1383
|
+
minWeightRatio?: number;
|
|
1384
|
+
};
|
|
1385
|
+
/**
|
|
1386
|
+
* 时间衰减算法
|
|
1387
|
+
* 等待时间越长,优先级加成越高,确保所有任务最终都会被执行
|
|
1388
|
+
*/
|
|
1389
|
+
type AgingAlgorithmConfig = {
|
|
1390
|
+
type: 'aging';
|
|
1391
|
+
/**
|
|
1392
|
+
* 每等待多少毫秒增加 1 点优先级,默认 60000(1分钟)
|
|
1393
|
+
* 例如:VIP 优先级 100,普通用户优先级 1
|
|
1394
|
+
* 普通用户等待 100 分钟后,优先级变为 1 + 100 = 101,超过 VIP
|
|
1395
|
+
*/
|
|
1396
|
+
intervalMs?: number;
|
|
1397
|
+
};
|
|
1398
|
+
/**
|
|
1399
|
+
* 算法配置类型
|
|
1400
|
+
*/
|
|
1401
|
+
type AlgorithmConfig = StrictAlgorithmConfig | WeightedRandomAlgorithmConfig | AgingAlgorithmConfig;
|
|
1402
|
+
interface PriorityStrategyOptions {
|
|
1403
|
+
/**
|
|
1404
|
+
* 调度算法配置,默认 { type: 'weighted-random' }
|
|
1405
|
+
*
|
|
1406
|
+
* @example
|
|
1407
|
+
* // 严格优先级(VIP 绝对优先)
|
|
1408
|
+
* algorithm: { type: 'strict' }
|
|
1409
|
+
*
|
|
1410
|
+
* // 加权随机(默认,平衡公平性)
|
|
1411
|
+
* algorithm: { type: 'weighted-random', minWeightRatio: 0.1 }
|
|
1412
|
+
*
|
|
1413
|
+
* // 时间衰减(确保无饥饿)
|
|
1414
|
+
* algorithm: { type: 'aging', intervalMs: 60000 }
|
|
1415
|
+
*/
|
|
1416
|
+
algorithm?: AlgorithmConfig;
|
|
1417
|
+
/** 默认优先级(未配置的组使用此值),默认 1 */
|
|
1418
|
+
defaultPriority?: number;
|
|
1419
|
+
/** 优先级缓存 TTL(毫秒),默认 5000ms。设为 0 禁用缓存 */
|
|
1420
|
+
cacheTtlMs?: number;
|
|
1421
|
+
/**
|
|
1422
|
+
* 自定义优先级计算函数
|
|
1423
|
+
* 如果提供,将使用此函数计算优先级,否则直接使用 config.priority
|
|
1424
|
+
*
|
|
1425
|
+
* @example
|
|
1426
|
+
* // 根据 VIP 等级计算优先级
|
|
1427
|
+
* onGetPriority: (groupId, config) => {
|
|
1428
|
+
* if (groupId.startsWith('vip:')) return config.priority * 10;
|
|
1429
|
+
* return config.priority;
|
|
1430
|
+
* }
|
|
1431
|
+
*/
|
|
1432
|
+
onGetPriority?: OnGetPriority;
|
|
1433
|
+
}
|
|
1434
|
+
declare class PriorityStrategy implements DispatchStrategy {
|
|
1435
|
+
/** 本地缓存:groupId -> { priority, expiresAt } */
|
|
1436
|
+
private cache;
|
|
1437
|
+
/** 手动覆盖的优先级(优先级最高) */
|
|
1438
|
+
private overrides;
|
|
1439
|
+
private algorithmConfig;
|
|
1440
|
+
private defaultPriority;
|
|
1441
|
+
private cacheTtlMs;
|
|
1442
|
+
private onGetPriority?;
|
|
1443
|
+
constructor(options?: PriorityStrategyOptions);
|
|
1444
|
+
/**
|
|
1445
|
+
* 手动覆盖某个组的优先级(优先级高于 Redis 配置和 getPriority)
|
|
1446
|
+
* 主要用于测试或临时调整
|
|
1447
|
+
*/
|
|
1448
|
+
setPriority(groupId: string, priority: number): void;
|
|
1449
|
+
/**
|
|
1450
|
+
* 清除手动覆盖,恢复使用 Redis 配置或 getPriority
|
|
1451
|
+
*/
|
|
1452
|
+
clearPriority(groupId: string): void;
|
|
1453
|
+
/**
|
|
1454
|
+
* 获取组的基础优先级
|
|
1455
|
+
* 优先级来源顺序:overrides > cache > onGetPriority(config) 或 config.priority
|
|
1456
|
+
*/
|
|
1457
|
+
private resolvePriority;
|
|
1458
|
+
/**
|
|
1459
|
+
* 严格优先级算法:总是返回优先级最高的组
|
|
1460
|
+
*/
|
|
1461
|
+
private selectByStrict;
|
|
1462
|
+
/**
|
|
1463
|
+
* 加权随机算法:根据优先级计算概率选择
|
|
1464
|
+
* 使用 minWeightRatio 确保低优先级组也有机会
|
|
1465
|
+
*/
|
|
1466
|
+
private selectByWeightedRandom;
|
|
1467
|
+
/**
|
|
1468
|
+
* 时间衰减算法:等待时间越长,优先级加成越高
|
|
1469
|
+
*/
|
|
1470
|
+
private selectByAging;
|
|
1471
|
+
getNextGroup(queue: Queue<any>): Promise<string | null>;
|
|
1472
|
+
}
|
|
1473
|
+
//#endregion
|
|
1474
|
+
export { AddOptions, AgingAlgorithmConfig, AlgorithmConfig, BackoffStrategy, BullBoardGroupMQAdapter, DispatchStrategy, FlowJob, FlowOptions, GroupMQBullBoardAdapterOptions, Job, OnGetPriority, PriorityStrategy, PriorityStrategyOptions, Queue, QueueOptions, RepeatOptions, ReservedJob, StrictAlgorithmConfig, UnrecoverableError, WeightedRandomAlgorithmConfig, Worker, WorkerEvents, WorkerOptions, getWorkersStatus, waitForQueueToEmpty };
|
|
1353
1475
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -2761,5 +2761,117 @@ function sleep(ms) {
|
|
|
2761
2761
|
}
|
|
2762
2762
|
|
|
2763
2763
|
//#endregion
|
|
2764
|
-
|
|
2764
|
+
//#region src/strategies/priority-strategy.ts
|
|
2765
|
+
var PriorityStrategy = class {
|
|
2766
|
+
constructor(options = {}) {
|
|
2767
|
+
this.cache = /* @__PURE__ */ new Map();
|
|
2768
|
+
this.overrides = /* @__PURE__ */ new Map();
|
|
2769
|
+
this.algorithmConfig = options.algorithm ?? { type: "weighted-random" };
|
|
2770
|
+
this.defaultPriority = options.defaultPriority ?? 1;
|
|
2771
|
+
this.cacheTtlMs = options.cacheTtlMs ?? 5e3;
|
|
2772
|
+
this.onGetPriority = options.onGetPriority;
|
|
2773
|
+
}
|
|
2774
|
+
/**
|
|
2775
|
+
* 手动覆盖某个组的优先级(优先级高于 Redis 配置和 getPriority)
|
|
2776
|
+
* 主要用于测试或临时调整
|
|
2777
|
+
*/
|
|
2778
|
+
setPriority(groupId, priority) {
|
|
2779
|
+
this.overrides.set(groupId, priority);
|
|
2780
|
+
}
|
|
2781
|
+
/**
|
|
2782
|
+
* 清除手动覆盖,恢复使用 Redis 配置或 getPriority
|
|
2783
|
+
*/
|
|
2784
|
+
clearPriority(groupId) {
|
|
2785
|
+
this.overrides.delete(groupId);
|
|
2786
|
+
}
|
|
2787
|
+
/**
|
|
2788
|
+
* 获取组的基础优先级
|
|
2789
|
+
* 优先级来源顺序:overrides > cache > onGetPriority(config) 或 config.priority
|
|
2790
|
+
*/
|
|
2791
|
+
async resolvePriority(queue, groupId) {
|
|
2792
|
+
const override = this.overrides.get(groupId);
|
|
2793
|
+
if (override !== void 0) return override;
|
|
2794
|
+
const now = Date.now();
|
|
2795
|
+
const cached = this.cache.get(groupId);
|
|
2796
|
+
if (cached && cached.expiresAt > now) return cached.priority;
|
|
2797
|
+
const config = await queue.getGroupConfig(groupId);
|
|
2798
|
+
let priority;
|
|
2799
|
+
if (this.onGetPriority) priority = await this.onGetPriority(groupId, config);
|
|
2800
|
+
else priority = config.priority !== 1 ? config.priority : this.defaultPriority;
|
|
2801
|
+
if (this.cacheTtlMs > 0) this.cache.set(groupId, {
|
|
2802
|
+
priority,
|
|
2803
|
+
expiresAt: now + this.cacheTtlMs
|
|
2804
|
+
});
|
|
2805
|
+
return priority;
|
|
2806
|
+
}
|
|
2807
|
+
/**
|
|
2808
|
+
* 严格优先级算法:总是返回优先级最高的组
|
|
2809
|
+
*/
|
|
2810
|
+
selectByStrict(groups) {
|
|
2811
|
+
groups.sort((a, b) => b.priority - a.priority);
|
|
2812
|
+
return groups[0].groupId;
|
|
2813
|
+
}
|
|
2814
|
+
/**
|
|
2815
|
+
* 加权随机算法:根据优先级计算概率选择
|
|
2816
|
+
* 使用 minWeightRatio 确保低优先级组也有机会
|
|
2817
|
+
*/
|
|
2818
|
+
selectByWeightedRandom(groups) {
|
|
2819
|
+
if (groups.length === 1) return groups[0].groupId;
|
|
2820
|
+
const minWeightRatio = this.algorithmConfig.minWeightRatio ?? .1;
|
|
2821
|
+
const minWeight = Math.max(...groups.map((g) => g.priority)) * minWeightRatio;
|
|
2822
|
+
const weights = groups.map((g) => ({
|
|
2823
|
+
groupId: g.groupId,
|
|
2824
|
+
weight: Math.max(g.priority, minWeight)
|
|
2825
|
+
}));
|
|
2826
|
+
const totalWeight = weights.reduce((sum, w) => sum + w.weight, 0);
|
|
2827
|
+
let random = Math.random() * totalWeight;
|
|
2828
|
+
for (const w of weights) {
|
|
2829
|
+
random -= w.weight;
|
|
2830
|
+
if (random <= 0) return w.groupId;
|
|
2831
|
+
}
|
|
2832
|
+
return groups[0].groupId;
|
|
2833
|
+
}
|
|
2834
|
+
/**
|
|
2835
|
+
* 时间衰减算法:等待时间越长,优先级加成越高
|
|
2836
|
+
*/
|
|
2837
|
+
selectByAging(groups) {
|
|
2838
|
+
const now = Date.now();
|
|
2839
|
+
const intervalMs = this.algorithmConfig.intervalMs ?? 6e4;
|
|
2840
|
+
const adjustedGroups = groups.map((g) => {
|
|
2841
|
+
let ageBonus = 0;
|
|
2842
|
+
if (g.oldestTimestamp) {
|
|
2843
|
+
const waitTime = now - g.oldestTimestamp;
|
|
2844
|
+
ageBonus = Math.floor(waitTime / intervalMs);
|
|
2845
|
+
}
|
|
2846
|
+
return {
|
|
2847
|
+
groupId: g.groupId,
|
|
2848
|
+
adjustedPriority: g.priority + ageBonus
|
|
2849
|
+
};
|
|
2850
|
+
});
|
|
2851
|
+
adjustedGroups.sort((a, b) => b.adjustedPriority - a.adjustedPriority);
|
|
2852
|
+
return adjustedGroups[0].groupId;
|
|
2853
|
+
}
|
|
2854
|
+
async getNextGroup(queue) {
|
|
2855
|
+
const readyGroups = await queue.getReadyGroups(0, 100);
|
|
2856
|
+
if (readyGroups.length === 0) return null;
|
|
2857
|
+
const groupInfos = await Promise.all(readyGroups.map(async (groupId) => {
|
|
2858
|
+
const priority = await this.resolvePriority(queue, groupId);
|
|
2859
|
+
const info = {
|
|
2860
|
+
groupId,
|
|
2861
|
+
priority
|
|
2862
|
+
};
|
|
2863
|
+
if (this.algorithmConfig.type === "aging") info.oldestTimestamp = await queue.getGroupOldestTimestamp(groupId);
|
|
2864
|
+
return info;
|
|
2865
|
+
}));
|
|
2866
|
+
switch (this.algorithmConfig.type) {
|
|
2867
|
+
case "strict": return this.selectByStrict(groupInfos);
|
|
2868
|
+
case "aging": return this.selectByAging(groupInfos);
|
|
2869
|
+
case "weighted-random":
|
|
2870
|
+
default: return this.selectByWeightedRandom(groupInfos);
|
|
2871
|
+
}
|
|
2872
|
+
}
|
|
2873
|
+
};
|
|
2874
|
+
|
|
2875
|
+
//#endregion
|
|
2876
|
+
export { BullBoardGroupMQAdapter, Job, PriorityStrategy, Queue, UnrecoverableError, Worker, getWorkersStatus, waitForQueueToEmpty };
|
|
2765
2877
|
//# sourceMappingURL=index.js.map
|