负载均衡(Load Balance),意思是将负载(工作任务,访问请求)进行平衡、分摊到多个操作单元(服务器,组件)上进行执行。是解决高性能,单点故障(高可用),扩展性(水平伸缩)的终极解决方案。
Dart中的负载均衡, 是对多个子isolate的调度。
负载较低的条目(较小的 load 值)具有更高的优先级。
_LoadBalancerEntry
class _LoadBalancerEntry implements Comparable<_LoadBalancerEntry> {
/// The position in the heap queue.
///
/// Maintained when entries move around the queue.
/// Only needed for [LoadBalancer._decreaseLoad].
/*
堆队列中的位置。
当条目在队列中移动时维护。
只需要[loadbalancer._decreaseLoad]。
*/
int queueIndex;
// The current load on the isolate.
// 当前的isolate
int load = 0;
// The service used to execute commands.
// 用于执行命令的服务。
Runner runner;
_LoadBalancerEntry(this.runner, this.queueIndex);
Future<R> run<R, P>(LoadBalancer balancer, int load, FutureOr<R> Function(P argument) function, P argument, Duration? timeout,
FutureOr<R> Function()? onTimeout) {
// print('_LoadBalancerEntry 异步执行, load:$load, argument0: $argument');
return runner.run<R, P>(function, argument, timeout: timeout, onTimeout: onTimeout).whenComplete(() {
// print('_LoadBalancerEntry 同步执行, 先执行完成load:$load, argument1: $argument');
balancer._decreaseLoad(this, load);
});
}
Future close() => runner.close();
@override
int compareTo(_LoadBalancerEntry other) => load - other.load;
}
实现 Comparable 接口,这样类的实例可以相互比较。每个条目代表一个隔离区(isolate)或工作线程,它们可以执行任务并具有负载(load)。
以下是这个类的关键点解释:
-
类成员变量:
queueIndex: 表示该条目在堆队列中的位置。当条目在队列中移动时,这个值会被更新。load: 表示当前隔离区的负载量。runner: 一个Runner类型的对象,用于执行命令。
-
构造函数:
_LoadBalancerEntry(this.runner, this.queueIndex): 构造函数接收一个Runner实例和一个队列索引,并将它们初始化。
-
方法:
run: 一个泛型方法,接收一个LoadBalancer实例、负载量、一个函数、函数参数、超时时间以及超时回调函数。这个方法使用runner的run方法异步执行传入的函数,并在执行完成后调用balancer._decreaseLoad方法来减少负载。close: 返回runner.close()的结果,用于关闭Runner实例。
-
compareTo 方法:
@override int compareTo(_LoadBalancerEntry other) => load - other.load;: 这个方法用于比较两个_LoadBalancerEntry实例的负载量。它返回当前实例的负载量与另一个实例负载量的差值。这是实现Comparable接口所必需的方法。
这个类在 LoadBalancer 中可能用于维护一个基于负载的优先队列,其中负载较低的条目(较小的 load 值)具有更高的优先级。这是为了实现工作负载的均匀分配,确保负载不会集中在单个隔离区或工作线程上。
LoadBalancer
/*
一个运行池,按负载排序。保持一个运行池,并允许运行功能,通过运行池与最低的isolate负载。
运行池条目的数量在创建池时是固定的。
当运行池关闭时,所有的运行者也都关闭了。负载均衡器不可重入。执行[run]函数不应该同步调用负载均衡器上的方法。
*/
class LoadBalancer implements Runner {
/// A stand-in future which can be used as a default value.
///
/// The future never completes, so it should not be exposed to users.
/*
可作为默认值使用的替代future。
future永远不会完成,所以它不应该暴露给用户。
*/
static final _defaultFuture = Completer<Never>().future;
/// Reusable empty fixed-length list.
/// 可重用的空定长列表。
static final _emptyQueue = List<_LoadBalancerEntry>.empty(growable: false);
/// A heap-based priority queue of entries, prioritized by `load`.
///
/// The entries of the list never change, only their positions.
/// Those with positions below `_length`
/// are considered to currently be in the queue.
/// All operations except [close] should end up with all entries
/// still in the pool. Some entries may be removed temporarily in order
/// to change their load and then add them back.
///
/// Each [_LoadBalancerEntry] has its current position in the queue
/// as [_LoadBalancerEntry.queueIndex].
///
/// Is set to an empty list on clear.
/*
基于堆的条目优先级队列,按' load '排序。列表的条目永远不会改变,只是它们的位置。
那些位置低于' _length '的被认为是当前在队列中。除了[close]之外的所有操作都应该在池中保留所有条目。
有些条目可能会被暂时删除,以便改变它们的负载,然后再将它们添加回来。
每个[_LoadBalancerEntry]在队列中的当前位置为[_LoadBalancerEntry.queueIndex]。
被设置为空列表。
小根堆-load值最小的在最上面。
*/
List<_LoadBalancerEntry> _queue;
/// Current number of elements in [_queue].
///
/// Always a number between zero and [_queue.length].
/// Elements with indices below this value are
/// in the queue, and maintain the heap invariant.
/// Elements with indices above this value are temporarily
/// removed from the queue and are ordered by when they
/// were removed.
/*
[_queue]中的当前元素数。
总是一个介于0和[_queue.length]之间的数字。
索引低于此值的元素在队列中,并保持堆不变性。
索引高于此值的元素将暂时从队列中删除,并按删除时间排序。
*/
int _length;
/// The future returned by [stop].
///
/// Is `null` until [stop] is first called.
/*
由[停止]返回的future。在[stop]第一次被调用之前,为' null '。
*/
Future<void>? _stopFuture;
/// Create a load balancer backed by the [Runner]s of [runners].
/// 创建一个由[runners]的[Runner]s支持的负载均衡器。
LoadBalancer(Iterable<Runner> runners) : this._(_createEntries(runners));
LoadBalancer._(List<_LoadBalancerEntry> entries)
: _queue = entries,
_length = entries.length;
/// The number of runners currently in the pool.
/// 当前池中运行者的数量。
int get length => _length;
/// Asynchronously create [size] runners and create a `LoadBalancer` of those.
///
/// This is a helper function that makes it easy to create a `LoadBalancer`
/// with asynchronously created runners, for example:
/// ```dart
/// var isolatePool = LoadBalancer.create(10, IsolateRunner.spawn);
/// ```
/*
异步创建[size]运行器并创建一个' LoadBalancer '。
这是一个辅助函数,可以很容易地创建一个“LoadBalancer”。
使用异步创建的运行程序,例如:
/// var isolatePool = LoadBalancer.create(10, IsolateRunner.spawn);
*/
static Future<LoadBalancer> create(int size, Future<Runner> Function() createRunner) {
return Future.wait(Iterable.generate(size, (_) => createRunner()), cleanUp: (Runner runner) {
runner.close();
/// 传入runners, 创建并返回LoadBalancer实例
}).then((runners) => LoadBalancer(runners));
}
/// 创建_LoadBalancerEntry实例, 每个_LoadBalancerEntry实例分配一个索引index
static List<_LoadBalancerEntry> _createEntries(Iterable<Runner> runners) {
var index = 0;
return runners.map((runner) => _LoadBalancerEntry(runner, index++)).toList(growable: false);
}
/// Execute the command in the currently least loaded isolate.
///
/// The optional [load] parameter represents the load that the command
/// is causing on the isolate where it runs.
/// The number has no fixed meaning, but should be seen as relative to
/// other commands run in the same load balancer.
/// The `load` must not be negative.
///
/// If [timeout] and [onTimeout] are provided, they are forwarded to
/// the runner running the function, which will handle a timeout
/// as normal. If the runners are running in other isolates, then
/// the [onTimeout] function must be a constant function.
/*
在当前加载最少的isolate中执行命令。可选的[load]参数表示命令在其运行的isolate上造成的负载。
这个数字没有固定的含义,但应该被看作是相对于在同一负载平衡器中运行的其他命令。
“负载”不能是负的。如果提供了[timeout]和[onTimeout],它们将被转发给运行该函数的运行器,运行器将正常处理超时。
如果运行程序在其他isolate中运行,则[onTimeout]函数必须是一个常数函数。
*/
@override
Future<R> run<R, P>(FutureOr<R> Function(P argument) function, P argument,
{Duration? timeout, FutureOr<R> Function()? onTimeout, int load = 100}) {
RangeError.checkNotNegative(load, 'load');
if (_length == 0) {
// Can happen if created with zero runners,
// or after being closed.
if (_stopFuture != null) {
throw StateError("Load balancer has been closed");
}
throw StateError("No runners in pool");
}
// for (var element in _queue) {
// print('---run, load:${element.load}, queueIndex: ${element.queueIndex}---');
// }
var entry = _queue.first;
entry.load += load;
_bubbleDown(entry, 0);
return entry.run(this, load, function, argument, timeout, onTimeout);
}
/// Execute the same function in the least loaded [count] isolates.
///
/// This guarantees that the function isn't run twice in the same isolate,
/// so `count` is not allowed to exceed [length].
///
/// The optional [load] parameter represents the load that the command
/// is causing on the isolate where it runs.
/// The number has no fixed meaning, but should be seen as relative to
/// other commands run in the same load balancer.
/// The `load` must not be negative.
///
/// If [timeout] and [onTimeout] are provided, they are forwarded to
/// the runners running the function, which will handle any timeouts
/// as normal.
List<Future<R>> runMultiple<R, P>(int count, FutureOr<R> Function(P argument) function, P argument,
{Duration? timeout, FutureOr<R> Function()? onTimeout, int load = 100}) {
RangeError.checkValueInInterval(count, 1, _length, 'count');
RangeError.checkNotNegative(load, 'load');
if (count == 1) {
return List<Future<R>>.filled(1, run(function, argument, load: load, timeout: timeout, onTimeout: onTimeout));
}
var result = List<Future<R>>.filled(count, _defaultFuture);
if (count == _length) {
// No need to change the order of entries in the queue.
for (var i = 0; i < _length; i++) {
var entry = _queue[i];
entry.load += load;
result[i] = entry.run(this, load, function, argument, timeout, onTimeout);
}
} else {
// Remove the [count] least loaded services and run the
// command on each, then add them back to the queue.
for (var i = 0; i < count; i++) {
_removeFirst();
}
// The removed entries are stored in `_queue` in positions from
// `_length` to `_length + count - 1`.
for (var i = 0; i < count; i++) {
var entry = _queue[_length];
entry.load += load;
_addNext();
result[i] = entry.run(this, load, function, argument, timeout, onTimeout);
}
}
return result;
}
@override
Future<void> close() {
var stopFuture = _stopFuture;
if (stopFuture != null) return stopFuture;
var queue = _queue;
var length = _length;
_queue = _emptyQueue;
_length = 0;
return _stopFuture = MultiError.waitUnordered(
[for (var i = 0; i < length; i++) queue[i].close()],
).then(ignore);
}
/// Place [element] in heap at [index] or above.
///
/// Put element into the empty cell at `index`.
/// While the `element` has higher priority than the
/// parent, swap it with the parent.
///
/// Ignores [element]'s initial [_LoadBalancerEntry.queueIndex],
/// but sets it to the final position when the element has
/// been placed.
/*
上浮算法
*/
void _bubbleUp(_LoadBalancerEntry element, int index) {
while (index > 0) {
var parentIndex = (index - 1) ~/ 2;
var parent = _queue[parentIndex];
if (element.compareTo(parent) > 0) break;
_queue[index] = parent..queueIndex = index;
index = parentIndex;
}
_queue[index] = element..queueIndex = index;
}
/// Place [element] in heap at [index] or above.
///
/// Put element into the empty cell at `index`.
/// While the `element` has lower priority than either child,
/// swap it with the highest priority child.
///
/// Ignores [element]'s initial [_LoadBalancerEntry.queueIndex],
/// but sets it to the final position when the element has
/// been placed.
/*
下沉算法, 从上往下堆化
将元素放入' index '处的空单元格中。当“element”的优先级低于任何一个子元素时,将其与优先级最高的子元素交换。
*/
void _bubbleDown(_LoadBalancerEntry element, int index) {
while (true) {
var childIndex = index * 2 + 1; // Left child index.
if (childIndex >= _length) break;
var child = _queue[childIndex];
var rightChildIndex = childIndex + 1;
if (rightChildIndex < _length) {
var rightChild = _queue[rightChildIndex];
if (rightChild.compareTo(child) < 0) {
childIndex = rightChildIndex;
child = rightChild;
}
}
if (element.compareTo(child) <= 0) break;
_queue[index] = child..queueIndex = index;
index = childIndex;
}
_queue[index] = element..queueIndex = index;
// print('---😄😄下沉算法---');
for (var element in _queue) {
print('---_bubbleDown, load:${element.load}, queueIndex: ${element.queueIndex}---');
}
print('\n');
}
/// Removes the first entry from the queue, but doesn't stop its service.
///
/// The entry is expected to be either added back to the queue
/// immediately or have its stop method called.
///
/// After the remove, the entry is stored as `_queue[_length]`.
_LoadBalancerEntry _removeFirst() {
assert(_length > 0);
_LoadBalancerEntry entry = _queue.first;
_length--;
if (_length > 0) {
var replacement = _queue[_length];
_queue[_length] = entry..queueIndex = _length;
_bubbleDown(replacement, 0);
}
return entry;
}
/// Adds next unused entry to the queue.
///
/// Adds the entry at [_length] to the queue.
/*
将下一个未使用的条目添加到队列中。将[_length]条目添加到队列中。
*/
void _addNext() {
assert(_length < _queue.length);
var index = _length;
var entry = _queue[index];
_length = index + 1;
_bubbleUp(entry, index);
}
/// Decreases the load of an element which is currently in the queue.
///
/// Elements outside the queue can just have their `load` modified directly.
/*
减少当前在队列中的元素的负载。队列外的元素可以直接修改它们的“load”。
*/
void _decreaseLoad(_LoadBalancerEntry entry, int load) {
assert(load >= 0);
entry.load -= load;
var index = entry.queueIndex;
// Should always be the case unless the load balancer
// has been closed, or events are happening out of their
// proper order.
if (index < _length) {
_bubbleUp(entry, index);
}
}
}
- 这个类使用一个基于堆的优先级队列来管理运行器,确保任务尽可能均匀地分配到负载最低的运行器上。
_load值越小,表示运行器的负载越低,优先级越高。run和runMultiple方法用于调度任务,close方法用于关闭所有运行器。_loadBalancerEntry类的实例在_queue中维护其在堆中的位置,并通过compareTo方法进行比较。
这个类的设计目的是实现一个高效的负载均衡器,能够根据运行器的负载动态调度任务。
使用
void main() {
var N = 45;
var sw = Stopwatch()..start();
// Compute fib up to 42 with 6 isolates.
parfib(N, 6).then((v1) {
var t1 = sw.elapsedMilliseconds;
sw.stop();
sw.reset();
print('fib#6($N) = ${v1[N]}, ms: $t1');
sw.start();
// Then compute fib up to 88 with 2 isolates.
// parfib(N, 2).then((v2) {
// var t2 = sw.elapsedMilliseconds;
// sw.stop();
// print('fib#3($N) = ${v2[N]}, ms: $t2');
// });
});
}
// 处理单个任务的结果
void handleTaskResult(int result) {
print('Task result: $result');
}
// Compute fibonacci 1..limit
Future<List<int>> parfib(int limit, int parallelity) async {
/// 使用 runners 初始化 LoadBalancer
var pool = await LoadBalancer.create(parallelity, IsolateRunner.spawn);
// 创建一个任务列表,用于收集所有任务的 Future。
var fibs = List<Future<int>?>.filled(limit + 1, null);
// Schedule all calls with exact load value and the heaviest task
// assigned first.
/*
安排所有具有准确负载值的调用,并首先分配最重的任务。
*/
void schedule(int a, int b, int i) {
// print('parfib 调度任务, schedule0, a: $a, i0: $i');
if (i < limit) {
schedule(a + b, a, i + 1);
}
// 处理单个任务
var future = pool.run<int, int>(fib, i, load: a);
// print('parfib 调度任务, schedule1, a: $a, i1: $i');
// 单个任务结果-记录过程
// 执行这个then后, 会打印每个任务的结果, 不用等到下面的Future.wait才打印结果
future.then(handleTaskResult);
// 将每个任务的执行包装在 Future 中,并添加到任务列表
fibs[i] = future;
}
schedule(0, 1, 0);
print('---开始Future.wait---');
// 使用 Future.wait 并发执行所有任务,并等待它们全部完成
final results = await Future.wait(fibs.cast<Future<int>>());
// 打印所有任务的结果
print('---results: $results---');
/// 任务执行完毕后关闭 LoadBalancer
await pool.close();
return results;
return LoadBalancer.create(parallelity, IsolateRunner.spawn).then((LoadBalancer pool) {
var fibs = List<Future<int>?>.filled(limit + 1, null);
// Schedule all calls with exact load value and the heaviest task
// assigned first.
/*
安排所有具有准确负载值的调用,并首先分配最重的任务。
*/
void schedule(int a, int b, int i) {
if (i < limit) {
schedule(a + b, a, i + 1);
}
fibs[i] = pool.run<int, int>(fib, i, load: a);
}
schedule(0, 1, 0);
// And wait for them all to complete.
return Future.wait(fibs.cast<Future<int>>()).whenComplete(pool.close);
});
}
int fib(int n) {
if (n < 2) return n;
return fib(n - 1) + fib(n - 2);
}