
Mari kita lihat solusi apa yang dimiliki masalah ini.
Distribusi objek "menurut kapasitas"
Agar tidak mempelajari abstraksi yang tidak menarik, kami akan mempertimbangkan contoh tugas tertentu - pemantauan . Saya yakin Anda akan dapat menghubungkan metode yang diusulkan dengan tugas spesifik Anda sendiri.
Objek pemantauan "Setara"
Contohnya adalah pengumpul metrik kami untuk Zabbix , yang secara historis berbagi arsitektur yang sama dengan pengumpul log PostgreSQL. Memang
, setiap objek pemantauan (host) menghasilkan untuk zabbix yang hampir secara stabil menghasilkan kumpulan metrik yang sama dengan frekuensi yang sama sepanjang waktu:

Seperti yang Anda lihat di grafik, perbedaan antara nilai min-max dari jumlah metrik yang dihasilkan tidak melebihi 15% . Oleh karena itu, kita dapat menganggap semua objek sama dalam "burung beo" yang sama .
"Ketidakseimbangan" yang kuat antar objek
Berbeda dengan model sebelumnya, host yang dipantau jauh dari homogen bagi pengumpul log .
Misalnya, satu host dapat menghasilkan satu juta rencana per hari di log, puluhan ribu lainnya, dan beberapa - bahkan hanya beberapa. Dan rencananya sendiri sangat berbeda dalam hal volume dan kompleksitas dan dalam hal distribusi sepanjang hari. Jadi ternyata beban "bergetar" dengan kuat , pada saat:

Nah, karena beban bisa berubah begitu banyak, maka Anda perlu belajar cara mengelolanya ...
Koordinator
Kami segera memahami bahwa kami jelas perlu menskalakan sistem kolektor, karena satu node terpisah dengan seluruh beban suatu hari nanti akan berhenti berfungsi. Dan untuk ini kami membutuhkan seorang koordinator - seseorang yang akan mengelola seluruh kebun binatang.
Ternyata seperti ini:

Setiap pekerja memuatnya "dalam burung beo" dan sebagai persentase dari CPU secara berkala me-reset master, mereka - ke kolektor. Dan dia, berdasarkan data ini, dapat mengeluarkan perintah seperti "letakkan host baru pada pekerja yang dibongkar # 4" atau "hostA harus ditransfer ke pekerja # 3" .
Di sini Anda juga perlu mengingat bahwa, tidak seperti objek pemantauan, kolektor itu sendiri tidak memiliki "daya" yang sama sama sekali - misalnya, di satu inti Anda mungkin memiliki 8 inti CPU, dan di sisi lain - hanya 4, dan bahkan frekuensi yang lebih rendah. Dan jika Anda memuatnya dengan tugas "sama", yang kedua akan mulai "diam", dan yang pertama akan menganggur. Oleh karena itu berikut ...
Tugas koordinator
Faktanya, hanya ada satu tugas - untuk memastikan distribusi yang paling merata dari seluruh beban (dalam% cpu) di antara semua pekerja yang tersedia. Jika kita dapat menyelesaikannya dengan sempurna, maka keseragaman distribusi% cpu-load pada kolektor akan diperoleh "secara otomatis".
Jelas bahwa meskipun setiap objek menghasilkan beban yang sama, seiring waktu, beberapa di antaranya mungkin "mati", dan beberapa yang baru muncul. Oleh karena itu, Anda harus mampu mengelola seluruh situasi secara dinamis dan menjaga keseimbangan secara konstan .
Penyeimbangan dinamis
Kita bisa menyelesaikan masalah sederhana (zabbix) dengan cukup mudah:
- kami menghitung kapasitas relatif setiap kolektor "dalam tugas"
- bagi semua tugas di antara mereka secara proporsional
- kami mendistribusikan secara merata antar pekerja

Tapi apa yang harus dilakukan jika ada objek yang "sangat tidak sama", seperti untuk pengumpul log? ..
Penilaian keseragaman
Di atas, kami menggunakan istilah " distribusi seragam secara maksimal " sepanjang waktu , tetapi bagaimana Anda bisa secara formal membandingkan dua distribusi, mana yang "lebih seragam"?
Untuk mengevaluasi keseragaman dalam matematika, telah lama ada yang namanya deviasi standar . Siapa yang malas membaca:
S[X] = sqrt( sum[ ( x - avg[X] ) ^ 2 of X ] / count[X] )
Karena jumlah pekerja di masing-masing pengumpul mungkin juga berbeda untuk kami, penyebaran beban harus dinormalisasi tidak hanya di antara mereka, tetapi juga di antara pengumpul secara keseluruhan .
Artinya, distribusi beban kepada pekerja dari dua pengumpul
[ (10%, 10%, 10%, 10%, 10%, 10%) ; (20%) ]juga tidak terlalu baik, karena yang pertama ternyata 10% , dan yang kedua - 20% , yang, seolah-olah, dua kali lipat secara relatif.
Oleh karena itu, kami memperkenalkan jarak metrik tunggal untuk perkiraan umum "keseragaman":
d([%wrk], [%col]) = sqrt( S[%wrk] ^ 2 + S[%col] ^ 2 )Artinya, nilai deviasi root-mean-square untuk kumpulan nilai beban untuk semua pekerja dan untuk semua kolektor diambil sebagai koordinat vektor, yang panjangnya akan kami coba untuk meminimalkan.
Pemodelan
Jika kita memiliki beberapa objek, maka kita dapat "menguraikan" secara kasar di antara pekerja sehingga metriknya minimal . Tetapi kami memiliki ribuan objek, jadi metode ini tidak akan berhasil. Tapi kita tahu bahwa kolektor dapat "memindahkan" objek dari satu pekerja ke pekerja lain - mari buat model opsi ini menggunakan metode penurunan gradien .
Jelas bahwa kami mungkin tidak menemukan metrik minimum yang "ideal", tetapi yang lokal pasti. Dan beban itu sendiri dapat sangat bervariasi dari waktu ke waktu sehingga sama sekali tidak perlu mencari yang "ideal" untuk waktu yang tidak terbatas .
Artinya, kita hanya perlu menentukan objek mana dan ke pekerja mana yang paling efisien untuk "dipindahkan". Dan mari kita jadikan pemodelan lengkap yang sepele:
- ( host, worker)
- host worker' «»
«» . - « »
- d «»
Kami menyusun semua pasangan dalam urutan menaik dari metrik . Idealnya, kita harus selalu menerapkan transfer pasangan pertama karena memberikan metrik target minimum. Sayangnya, pada kenyataannya, proses transfer itu sendiri "membutuhkan sumber daya", jadi Anda tidak boleh menjalankannya untuk objek yang sama lebih sering daripada interval "pendinginan" tertentu .
Dalam kasus ini, kita dapat mengambil pasangan kedua, ketiga, ... berdasarkan peringkat - jika hanya metrik target yang akan menurun relatif terhadap nilai saat ini.
Jika tidak ada tempat untuk mengurangi - ini dia minimum lokal!
Contoh pada gambar:

Sama sekali tidak perlu memulai iterasi "sepenuhnya". Misalnya, Anda dapat melakukan analisis beban rata-rata selama interval 1 menit, dan setelah selesai, lakukan satu transfer.
Pengoptimalan mikro
Jelas bahwa algoritma dengan kompleksitas
T() x W()tidak terlalu bagus. Namun di dalamnya Anda tidak boleh lupa untuk menerapkan beberapa pengoptimalan yang kurang lebih jelas yang terkadang dapat mempercepatnya.
Nol "beo"
Jika sebuah objek / tugas / host telah menghasilkan beban "0 buah" pada interval yang diukur , maka itu bukan sesuatu yang dapat dipindahkan ke suatu tempat - bahkan tidak perlu dipertimbangkan dan dianalisis.
Transfer mandiri
Saat membuat pasangan, tidak perlu mengevaluasi efisiensi pemindahan objek ke pekerja yang sama , di mana objek itu sudah berada. Bagaimanapun, itu sudah akan
T x (W - 1)- sepele, tapi bagus!
Beban tak terlihat
Karena kita memodelkan pemindahan beban, dan objek hanyalah alat, tidak ada gunanya mencoba mentransfer% cpu yang "sama" - nilai metrik akan tetap sama, meskipun untuk distribusi objek yang berbeda.
Artinya, cukup mengevaluasi satu model untuk tupel (wrkSrc, wrkDst,% cpu) . Nah, dan Anda dapat menganggap "sama", misalnya, nilai% cpu yang cocok dengan 1 tempat desimal.
Implementasi contoh JavaScript
var col = {
'c1' : {
'wrk' : {
'w1' : {
'hst' : {
'h1' : 5
, 'h2' : 1
, 'h3' : 1
}
, 'cpu' : 80.0
}
, 'w2' : {
'hst' : {
'h4' : 1
, 'h5' : 1
, 'h6' : 1
}
, 'cpu' : 20.0
}
}
}
, 'c2' : {
'wrk' : {
'w1' : {
'hst' : {
'h7' : 1
, 'h8' : 2
}
, 'cpu' : 100.0
}
, 'w2' : {
'hst' : {
'h9' : 1
, 'hA' : 1
, 'hB' : 1
}
, 'cpu' : 50.0
}
}
}
};
// ""
let $iv = (obj, fn) => Object.values(obj).forEach(fn);
let $mv = (obj, fn) => Object.values(obj).map(fn);
// initial reparse
for (const [cid, c] of Object.entries(col)) {
$iv(c.wrk, w => {
w.hst = Object.keys(w.hst).reduce((rv, hid) => {
if (typeof w.hst[hid] == 'object') {
rv[hid] = w.hst[hid];
return rv;
}
// ,
if (w.hst[hid]) {
rv[hid] = {'qty' : w.hst[hid]};
}
return rv;
}, {});
});
c.wrk = Object.keys(c.wrk).reduce((rv, wid) => {
// ID -
rv[cid + ':' + wid] = c.wrk[wid];
return rv;
}, {});
}
//
let S = col => {
let wsum = 0
, wavg = 0
, wqty = 0
, csum = 0
, cavg = 0
, cqty = 0;
$iv(col, c => {
$iv(c.wrk, w => {
wsum += w.cpu;
wqty++;
});
csum += c.cpu;
cqty++;
});
wavg = wsum/wqty;
wsum = 0;
cavg = csum/cqty;
csum = 0;
$iv(col, c => {
$iv(c.wrk, w => {
wsum += (w.cpu - wavg) ** 2;
});
csum += (c.cpu - cavg) ** 2;
});
return [Math.sqrt(wsum/wqty), Math.sqrt(csum/cqty)];
};
// -
let distS = S => Math.sqrt(S[0] ** 2 + S[1] ** 2);
//
let iterReOrder = col => {
let qty = 0
, max = 0;
$iv(col, c => {
c.qty = 0;
c.cpu = 0;
$iv(c.wrk, w => {
w.qty = 0;
$iv(w.hst, h => {
w.qty += h.qty;
});
w.max = w.qty * (100/w.cpu);
c.qty += w.qty;
c.cpu += w.cpu;
});
c.cpu = c.cpu/Object.keys(c.wrk).length;
c.max = c.qty * (100/c.cpu);
qty += c.qty;
max += c.max;
});
$iv(col, c => {
c.nrm = c.max/max;
$iv(c.wrk, w => {
$iv(w.hst, h => {
h.cpu = h.qty/w.qty * w.cpu;
h.nrm = h.cpu * c.nrm;
});
});
});
// ""
console.log(S(col), distS(S(col)));
//
let wrk = {};
let hst = {};
for (const [cid, c] of Object.entries(col)) {
for (const [wid, w] of Object.entries(c.wrk)) {
wrk[wid] = {
wid
, cid
, 'wrk' : w
, 'col' : c
};
for (const [hid, h] of Object.entries(w.hst)) {
hst[hid] = {
hid
, wid
, cid
, 'hst' : h
, 'wrk' : w
, 'col' : c
};
}
}
}
// worker
let move = (col, hid, wid) => {
let w = wrk[wid]
, h = hst[hid];
let wsrc = col[h.cid].wrk[h.wid]
, wdst = col[w.cid].wrk[w.wid];
wsrc.cpu -= h.hst.cpu;
wsrc.qty -= h.hst.qty;
wdst.qty += h.hst.qty;
// "" CPU
if (h.cid != w.cid) {
let csrc = col[h.cid]
, cdst = col[w.cid];
csrc.qty -= h.hst.qty;
csrc.cpu -= h.hst.cpu/Object.keys(csrc.wrk).length;
wsrc.hst[hid].cpu = h.hst.cpu * csrc.nrm/cdst.nrm;
cdst.qty += h.hst.qty;
cdst.cpu += h.hst.cpu/Object.keys(cdst.wrk).length;
}
wdst.cpu += wsrc.hst[hid].cpu;
wdst.hst[hid] = wsrc.hst[hid];
delete wsrc.hst[hid];
};
// (host, worker)
let moveCheck = (orig, hid, wid) => {
let w = wrk[wid]
, h = hst[hid];
// -
if (h.wid == w.wid) {
return;
}
let col = JSON.parse(JSON.stringify(orig));
move(col, hid, wid);
return S(col);
};
// (hsrc,hdst,%cpu)
let checked = {};
// ( -> )
let moveRanker = col => {
let currS = S(col);
let order = [];
for (hid in hst) {
for (wid in wrk) {
// ( 0.1%) ""
let widsrc = hst[hid].wid;
let idx = widsrc + '|' + wid + '|' + hst[hid].hst.cpu.toFixed(1);
if (idx in checked) {
continue;
}
let _S = moveCheck(col, hid, wid);
if (_S === undefined) {
_S = currS;
}
checked[idx] = {
hid
, wid
, S : _S
};
order.push(checked[idx]);
}
}
order.sort((x, y) => distS(x.S) - distS(y.S));
return order;
};
let currS = S(col);
let order = moveRanker(col);
let opt = order[0];
console.log('best move', opt);
//
if (distS(opt.S) < distS(currS)) {
console.log('move!', opt.hid, opt.wid);
move(col, opt.hid, opt.wid);
console.log('after move', JSON.parse(JSON.stringify(col)));
return true;
}
else {
console.log('none!');
}
return false;
};
// -
while(iterReOrder(col));Akibatnya, beban di waduk kami didistribusikan hampir sama setiap saat, segera meratakan puncak yang muncul:
