Суть решаемой проблемы:
Решение:
В конце поста будет приведен простой код для node.js который позволит оптимизировать работу множества скриптов на одной ноде.
Активным пользователям моих ботов:
Просьба протестировать нового бота https://t.me/autoupvotebot - если ошибок найдено не будет, на эту версию будут переведены все боты , что обеспечит стабильную и комфортную для вас работу.
Update:
Уже обновлены:
https://t.me/golosrobot
https://t.me/hottabot
https://t.me/mapvotebot (бот в числе прочего голосующий за посты размещенные с клиента мапалы)
Для обновления введите в ботах команду /menu
- вы увидите переименованную кнопку "Запуск" - нажимайте ее, это возобновит работу.
Некоторые боты будут переведены на новую версию уже сейчас.
В последнее время моими телеграм ботами для голосования пользуются все больше людей и естественно нагрузка возрасла в 10-ки раз по сравнению даже с прошлым месяцем. Я поднял дополнительные ноды, распределил нагрузки, добавил мощности серверам, но узкое место оказалось в самой пропускной способности нод.
Обратите внимание на скрин, там изредка мелькают строки белого цвета. Это череда новых блоков. Желтые же строки - это обращение к ноде.
Сейчас на сервере работают примерно 10 ботов, в каждом примерно 20 пользователей, это 200 практически одновременных запросов каждые 3 секунды 24/7. Такую нагрузку нода очень часто не выдерживает и боты пропускают транзакции или становятся на вынужденную паузу, а пользователи сердятся на несовершенство скриптов :)
Так как каждый пользователь бота "опрашивает" новые блоки с интервалом в 3 секунды, то сделать работу комфортной можно либо устанавливая по ноде на каждых 20-30 человек, что экономически абсурдно, либо пересмотреть принципы работы с опрашиванием блоков.
Решение я выбрал такое:
Вместо сотен отдельных сессий пользователей работает только один ОТДЕЛЬНЫЙ скрипт который опрашивает блоки. И сохраняет данные блока в базу данных Redis, все это происходит за доли миллисикунд, так как redis при правильной настройке не просто база, а мощный кеширующий инструмент.
Далее, каждый из пользователей бота обращается не к ноде, а уже к redis, что бы получить данные блока с операциями блокчейна. Таким образом, нагрузка должна упасть в 10-ки, а то и больше раз.
Разработчикам и владельцам нод
Суть метода простая, на сервере работает javascript который с интервалом в 3 секунды обновляет в базе данных содержимое свежего блока на golos.io
Далее вместо того, что бы подключаться всеми своими скриптами одновременно к одной ноде, вы берете данные о блоках из базы данных, а нода работает с минимальной нагрузкой, словно ее использует лишь один скрипт.
Вы можете использовать любую базу данных, как-то mongo, mysql, rethink и другие, для себя же я выбрал именно redis, для меня она проверенный инструмент способный держать нагрузку во время кэширования большого объема данных.
Весь код для постоянного сохранения текущего блока в redis уместился в несколько десятков строк
require('events').EventEmitter.prototype._maxListeners = 100000
const Promise = require("bluebird")
const _ = require('lodash')
const golos = require("golos-js")
const redis = require("redis")
const client = redis.createClient()
golos.config.set('websocket','ws://localhost:9090')
let trig = {existBlock:true}
const dynamicSnap = new Promise((resolve, reject) => {
golos.api.getDynamicGlobalProperties((err, res) => {
if (err) {console.log(err)}
else {
resolve(res)
}
})
})
const FIRSTBLOCK = n => n.head_block_number
const SENDBLOCK = currentblock => {
golos.api.getBlock(currentblock, (err, result) => {
if (err) {
console.log(err)
} else if (result === null){
trig.existBlock = false
}else {
let JSONblock = JSON.stringify(result)
client.hmset("GolosLastBlock", "data",JSONblock);
console.log(JSONblock)
trig.existBlock = true
}
})
}
const NEXTBLOCKS = firstblock => {
let currentblock = firstblock
setInterval(() => {
if(trig.existBlock){
currentblock++
}else{
console.warn(`Проблема блока ${currentblock}`)}
SENDBLOCK(currentblock)
}, 3000)
}
dynamicSnap
.then(FIRSTBLOCK)
.then(NEXTBLOCKS)
.catch(e => console.log(e));
Далее вам достаточно брать данные блока из базы redis
client.hgetall("GolosLastBlock", function(err, LB) {
let LastBlockRedis = JSON.parse(LB.data)
filterOperations(LastBlockRedis)
}
Где предполагается, что функция filterOperations обрабатывается вашим скриптом.
@vik спасибо за мануал, однозначно пригодится.
Нет ли подобного [готового] решения не для чтения блоков, а для записи?
Т.е. обслуживать 200+ клиентов одновременно пишущих в блокчейн?
А чем обычный load balancer не устраивает? 200+ клиентов просто раскидываются по разным нодам, как это сейчас делается на golos.io
Подразумевается собственная нода/ноды. И прожорливые клиенты, где один за десятерых :) Баланс нагрузки распределенный между несколькими собственными нодами будет отличным решением, но для этого все равно нужно ставить несколько нод.
Если речь об одной ноде, отвязки от использования паблик нод и рациональном расходовании серверов, то можно пороботвать так же отправлять подписанные блоки в буфер одновременно от всех клиентов, а уже из буфера отправлять их в ноду ровной очередью. Однако этот путь в перспективе может привести к задержкам.
Согласен, тоже склоняюсь к варианту складывать подписанные запросы в буфер, формировать из них одну общую транзакцию и её уже транслировать в сеть.
Собственная нода масштабируется вертикально (покупкой более производительного железа), горизонтально (покупкой дополнительных нод и балансировкой по ним, о чём я написал выше), либо, третий вариант - оптимизация (когда при имеющихся ресурсах оптимизированный программный код работает быстрее/оптимальнее, чем старый, на том же железе). Либо комбинация этих вариантов одновременно. Других вариантов масштабирования нет.
Кэшики/очереди улучшают ситуацию только на начальном этапе и при серьёзной нагрузке, в конечном счёте, всё равно понадобится горизонтальное масштабирование (хотя в вашем частном случае при ваших нагрузках может быть достаточно и кэшика/очереди с редиской, и не нужно изобретать велосипед и что-то переусложнять).
@vik, Поздравляю!
Ваш пост был упомянут в моем хит-параде в следующих категориях:
Ваш пост поддержали следующие Инвесторы Сообщества "Добрый кит":
litrbooh, t3ran13, boddhisattva, strecoza, ukrainian, neo, chiliec, natalia, fetta, andrvik, oleg257, dreamer, dimarss, vik, shuler, genyakuc, brovaryleaks, vadbars, elviento, volv, vasilisapor2, nefer, tom123, renat242, nikalaich, semasping, ladyzarulem, svinsent, gryph0n, voltash, tnam0rken, arystarch, zivchakh, francesco, asuleymanov, bystree, exan, stranniksenya, boltyn, on1x, oksana0407, igor66, sva-lana, borisss, anomalywolf, del137, bammbuss, dmitrijv, talia, chin, graff0x, mixtura, m1m2, kertar, dimas102, nerengot, bag, dim447, vealis, astrofilosof, dignityinside, foxycat, wedge, karmoputnik, ineon73, gbot, bounty-compaing, etnospace, verdon
Поэтому я тоже проголосовал за него!
Если Вы проголосуете за этот комментарий, то поможете сделать "Доброго Кита" сильнее!
Да, на мой взгляд, это отличное решение.
а еще у тебя блоки потом удаляются ?
так как redis хранит все в оперативке все блоки бесконечно туда не затолкнуть.
Но в redis есть возможность установить время жизни сохраненной информации при добавлении этой информации. И хранить блок например сутки или двое. может неделю :) затем он сам удалится.
Готовлю пост с таким же примером, но на php + laravel
Блок хранится 3 секунды только, далее заменяется новым, принцип буффера :)
Подключила.
Сколько времени требуется на тест?
Требуется максимальное число пользователей, что бы сравнить степень нагрузки.
https://t.me/hottabot так же получил новую версию.
Постепенно будут обновляться и другие.
хотабычна надо обновлять, Вик? привет)
Просто снимите с паузы запуском :)
Так же как и
@golosrobot
ок, понятно, спасибо )
хорошо, поняла.
надеюсь, все будет отлично )
спасибо!