МногоПоток. Исключаем параллельное использование данных. (MySQL)

AleXPrischepA

Client
Регистрация
06.05.2015
Сообщения
194
Благодарностей
90
Баллы
28
Доброго времени форумчане, коллеги и просто прохожие.... ;-)

Все мы, рано или поздно приходим к многопоточным шаблонам. А с многопотоком обязательно приходит вопрос "КААААК???" организовать ЗАПРЕТ на параллельное использование данных?! Ибо результаты "такого многопотока" никому не нужны...

photo_2024-02-24_15-16-44.jpg

Предлагаю вашему вниманию своё видение/решение этого вопроса. А вижу и понимаю я это так: если есть 2 "работника" и более, ТО уже нужен старший / управляющий, по другому практически невозможно.

И так есть 2 шаблона болванки: Manager.zp и Worker.zp (далее М и W соответственно), логика работы:
1) W генерирует уник. id_key и сохраняет его в таблицу tb_key нашей БД, с пометкой request. Также в БД сохраняется текущее время в unix формате (время требуется для контроля за ключами).
2) М видя ключ с пометкой request, берёт его и помечает свободные данные со статусом ready, в рабочих таблица tb_accounts и tb_proxy данным ключом + присваивает статус work / выдаёт работу W, и сам id_key помечается как work.
3) W получив "задание" - исполняет его, после чего помечает свой id_key как stop.
4) М видя id_key с пометкой stop, меняет статус данных на chill, а id_key удаляется из таблицы.
ключи.JPG
д32.JPG
д1.JPG

!!! В таблице tb_key, данные столбца id_key помечены как PRIMARY KEY, это означает что даже если будет сгенерирован НЕ уникальный id_key, при попытке его сохранить в БД, сама база данных не позволит этого сделать.

Кроме раздачи заданий Manager.zp:
1) Выдаёт задачи с некоторой задержкой по времени. (полезно для "нагруженных" шаблонов, дабы равномерно распределить пиковые нагрузки, в результате одновременного выполнения которых, можно "положить" всю работу)
2) Удаляет ключи из таблицы tb_key которые небыли удаленны из-за какого либо сбоя.
3) Освобождает данные в таблицах tb_accounts и tb_proxy которые заняты длительное время (в результате каких либо ошибок).
4) Следит чтоб данные "ОСТЫЛИ" некоторое время, после использования.
5) Обеспечивает равномерное использование всех данных, в работу идут первыми данные которые дольше простаивают.

Все тайминги можно регулировать прямо на лету, так как они хранятся в таблице tb_config и подтягиваются по необходимости.

!!! ОБРАТИТЕ ВНИМАНИЕ !!! При задании временных промежутков/таймингов, на соответствующий ему комментарий. Если временные промежутки буду заданы НЕ верно = параллельное использование данных.

Почему именно так?! Потому что после сброса блокировки данных, данные помечаются статусом "ready" (готовы к использованию).
оооо.JPG
Для работы шаблону необходимо:
1) База данных (у меня установлен Open Server 5.3.7, файл импорта БД приложу)
2) Для подключения к БД во входных настройках шаблона указать: имя БД, IP сервера, имя и пароль пользователя.
3) Дописать шаблоны под себя + заполнить таблицы своими данными.
4.JPG

Для удобной работы с БД можно использовать HeidiSQL (установочный файл в архиве).
 

Вложения

Для запуска проектов требуется программа ZennoPoster.
Это основное приложение, предназначенное для выполнения автоматизированных шаблонов действий (ботов).
Подробнее...

Для того чтобы запустить шаблон, откройте программу ZennoPoster. Нажмите кнопку «Добавить», и выберите файл проекта, который хотите запустить.
Подробнее о том, где и как выполняется проект.

Последнее редактирование:

ShikoFess

Client
Регистрация
21.12.2017
Сообщения
131
Благодарностей
106
Баллы
43
А почему бы просто не сделать поле status, присвоить ему по умолчанию 0. Брать только WHERE status=0 и присваивать забранному аккаунту status = 1. Ты никак не возмешь этот же аккаунт в многопотоке
 
  • Спасибо
Реакции: DeepFarm, brun0 и Ribas

Ахилес

Client
Регистрация
11.11.2020
Сообщения
846
Благодарностей
344
Баллы
63
А почему бы просто не сделать поле status, присвоить ему по умолчанию 0. Брать только WHERE status=0 и присваивать забранному аккаунту status = 1. Ты никак не возмешь этот же аккаунт в многопотоке
в легкую возьмешь одну и туже строку в работу, да еще и не один раз :bk:
странное конечно утверждение... но ладно, каждый нарабатывает опыт своим путем ;-)
 
  • Спасибо
Реакции: Jeronimo и AleXPrischepA

AleXPrischepA

Client
Регистрация
06.05.2015
Сообщения
194
Благодарностей
90
Баллы
28
А почему бы просто не сделать поле status, присвоить ему по умолчанию 0. Брать только WHERE status=0 и присваивать забранному аккаунту status = 1. Ты никак не возмешь этот же аккаунт в многопотоке
Какое время пройдет между получением данных со status=0 и последующим присвоением этим данным status = 1 ?
При +/- параллельном старте потоков, как с добрым утром получишь параллельное использование.
 
  • Спасибо
Реакции: Jeronimo

Jeronimo

Client
Регистрация
07.01.2014
Сообщения
229
Благодарностей
203
Баллы
43
AleXPrischepA намудрили вы конечно)

Задача решается гораздо проще с помощью блокировки таблицы через запрос, и статус аккаунта в таблице БД.
В таблице с аккаунтами должен быть столбец "Статус".
Обычно он принимает следующие значения: free, busy, ban.

Алгоритм работы:
1. Рабочий поток отправляет SQL запрос на получение username аккаунта (с которым он будет в дальнейшем работать) по определённым критериям (например, только со статусом free). Открывается сессия, выполняет блокировка таблицы (чтобы другие потоки не могли к ней обращаться) по заданным критериям в переменную заносится username аккаунта. Далее в этой же сессии происходит смена статуса аккаунта (username которого получили ранее) на busy. Далее разблокировка таблицы и закрытие сессии. Все эти действия выполняются в одном c# кубике за доли секунды, что позволяет с высокой скоростью обращаться за нужными данными, избегая работы одних и тех же аккаунтов в разных потоках.
2. Выполняется SQL запрос на получение нужных данных по полученному username (куки, пароль и т.п.). Далее уже текущий поток начинает работу с аккаунтом. По завершении работы отправляет SQL запрос на смену статуса аккаунта на free.

Работаю по такому алгоритму достаточно давно. Нагрузка на БД большая, запросов очень много, но при этом ни один поток не работает одновременно с одним и тем же аккаунтом.
 

AleXPrischepA

Client
Регистрация
06.05.2015
Сообщения
194
Благодарностей
90
Баллы
28
AleXPrischepA намудрили вы конечно)
Согласен... :-), про блокировку и c# слышал, знаю... Но нужно знание c#, коих у меня маловато..., а стандартными кубиками блокировки нет, в рамках одного подключения к БД, два и более запроса не выполнить.
 

Ахилес

Client
Регистрация
11.11.2020
Сообщения
846
Благодарностей
344
Баллы
63
Jeronimo про

Согласен... :-), про блокировку и c# слышал, знаю... Но нужно знание c#, коих у меня маловато..., а стандартными кубиками блокировки нет, в рамках одного подключения к БД, два и более запроса не выполнить.
не крутится весь мир вокруг C#....
изучите как работает механизм транзакций (ну хотя бы для начала https://blog-kopilka.ru/blog/transaction) и сразу станет ясно что речь идет про одну атомарную транзакцию, которую можно и нужно выполнять за один раз... ну и естественно стандартный кубик легко с этим справится... ведь он отправляет только текст... а вот смысловой текст уже зависит от уровня знания SQL языка. :bk:
 
  • Спасибо
Реакции: indigo666

ShikoFess

Client
Регистрация
21.12.2017
Сообщения
131
Благодарностей
106
Баллы
43
Согласен... :-), про блокировку и c# слышал, знаю... Но нужно знание c#, коих у меня маловато..., а стандартными кубиками блокировки нет, в рамках одного подключения к БД, два и более запроса не выполнить.
Так я тебе тоже самое и написал, ок про блокировку не указал в сообщении. Мудреный шаб получается слишком. https://zenno.club/discussion/threads/zagotovka-dlja-raboty-s-bd-mysql-v-kubike-c.64766/ Вот тут все есть просто 1 кубиком C# решается с локом бд
 
  • Спасибо
Реакции: webposter

Jeronimo

Client
Регистрация
07.01.2014
Сообщения
229
Благодарностей
203
Баллы
43
Согласен... :-), про блокировку и c# слышал, знаю... Но нужно знание c#, коих у меня маловато..., а стандартными кубиками блокировки нет, в рамках одного подключения к БД, два и более запроса не выполнить.
Честно говоря, и я не знаток c#. Лет 5 назад мне за 20$ написали кусочек кода, который юзаю по сей день.
Готов поделиться им с комьюнити бесплатно:

C#:
project.Variables["username"].Value = null; //не обязательно!!! Только для обнуления переменной, если не перезапускаете шаблон полностью

Sql sqlConn = new Sql(project); //Подключаемся к БД
var cmd = sqlConn.Cmd();
cmd.Connection.Open(); //открываем сессию
cmd.DbLock("" + project.Variables["bd_tableName"].Value + " WRITE"); //Блокируем таблицу

var reader = cmd.ExReader(String.Format("SELECT username FROM "+ project.Variables["bd_tableName"].Value +" WHERE STATUS='free' LIMIT 1;")); //Получаем username аккаунта, подходящего по условиям запроса.

if(reader.HasRows) //проверяем имеются ли данные в ответе. Если имеются, то переходим к считыванию
{
    while (reader.Read()) // построчно в цикле (while) считываем данные. Получим максимум 1 строку, т.к. в запросе стоит LIMIT 1
    {
        string dbUsername = reader["username"].ToString(); //получаем задание. В нашем случае это ссылка для парсинга.
        project.Variables["username"].Value = dbUsername; //сохраняем link в переменную зеннопостера
    }
}
reader.Close();

//Обновление статуса. Выполняется только если было получено задание (если id не пустое!)
if(!string.IsNullOrEmpty(project.Variables["username"].Value))
{
    //после получения задания необходимо поменять статус задания, чтобы другие потоки больше его не видели после разблокировки таблицы
    cmd.ExNonQ(String.Format("UPDATE "+ project.Variables["bd_tableName"].Value +" SET status='busy' WHERE username= '" + project.Variables["username"].Value + "';"));
}
    
cmd.DbUnLock(); //разблокировка всех таблиц
cmd.Connection.Close(); //закрываем сессию
 

Ахилес

Client
Регистрация
11.11.2020
Сообщения
846
Благодарностей
344
Баллы
63
Честно говоря, и я не знаток c#. Лет 5 назад мне за 20$ написали кусочек кода, который юзаю по сей день.
Готов поделиться им с комьюнити бесплатно:

C#:
project.Variables["username"].Value = null; //не обязательно!!! Только для обнуления переменной, если не перезапускаете шаблон полностью

Sql sqlConn = new Sql(project); //Подключаемся к БД
var cmd = sqlConn.Cmd();
cmd.Connection.Open(); //открываем сессию
cmd.DbLock("" + project.Variables["bd_tableName"].Value + " WRITE"); //Блокируем таблицу

var reader = cmd.ExReader(String.Format("SELECT username FROM "+ project.Variables["bd_tableName"].Value +" WHERE STATUS='free' LIMIT 1;")); //Получаем username аккаунта, подходящего по условиям запроса.

if(reader.HasRows) //проверяем имеются ли данные в ответе. Если имеются, то переходим к считыванию
{
    while (reader.Read()) // построчно в цикле (while) считываем данные. Получим максимум 1 строку, т.к. в запросе стоит LIMIT 1
    {
        string dbUsername = reader["username"].ToString(); //получаем задание. В нашем случае это ссылка для парсинга.
        project.Variables["username"].Value = dbUsername; //сохраняем link в переменную зеннопостера
    }
}
reader.Close();

//Обновление статуса. Выполняется только если было получено задание (если id не пустое!)
if(!string.IsNullOrEmpty(project.Variables["username"].Value))
{
    //после получения задания необходимо поменять статус задания, чтобы другие потоки больше его не видели после разблокировки таблицы
    cmd.ExNonQ(String.Format("UPDATE "+ project.Variables["bd_tableName"].Value +" SET status='busy' WHERE username= '" + project.Variables["username"].Value + "';"));
}
   
cmd.DbUnLock(); //разблокировка всех таблиц
cmd.Connection.Close(); //закрываем сессию
Sql sqlConn = new Sql(project); //Подключаемся к БД

а толку от этого кусочка кода, когда нет кастомного класса Sql из общего кода ? :bk:
 
  • Спасибо
Реакции: eagleowl и seodamage

AleXPrischepA

Client
Регистрация
06.05.2015
Сообщения
194
Благодарностей
90
Баллы
28
изучите как работает механизм транзакций (ну хотя бы для начала https://blog-kopilka.ru/blog/transaction) и сразу станет ясно что речь идет про одну атомарную транзакцию, которую можно и нужно выполнять за один раз... ну и естественно стандартный кубик легко с этим справится...
Благодарю за наводку...
 

ShikoFess

Client
Регистрация
21.12.2017
Сообщения
131
Благодарностей
106
Баллы
43
Работают со множествов шабов в многопотоке по примерам из ссылке выше. Ни разу не было замечено чтобы разные потоки брали один аккаунт. Все работает как часы
 

WebBot

Client
Регистрация
04.04.2015
Сообщения
1 718
Благодарностей
1 376
Баллы
113
Все это делается в два запроса, без всяких блокировок таблиц и прочих танцев с бубном

первый запрос меняет статус строки .. при UPDATE происходит автоматическая блокировка на уровне строк таблицы (не всей таблицы, а только строки которая апдейтиться), поэтому 1 и ту же строку не могут апдейтить одновременно несколько потоков, а после апдейта и разблокировки строки она уже не будет подходить по параметрам ( status уже будет не 0 )

UPDATE tbl SET id=LAST_INSERT_ID(id), status=1 WHERE status=0 LIMIT 1

следующей функцией получаем id той строки статус которой был изменен

SELECT LAST_INSERT_ID()

такое работает если нужно что бы каждый поток брал 1 строку ... и UPDATE и SELECT должны выполняться в одной сессии
 

Ахилес

Client
Регистрация
11.11.2020
Сообщения
846
Благодарностей
344
Баллы
63
Все это делается в два запроса, без всяких блокировок таблиц и прочих танцев с бубном

первый запрос меняет статус строки .. при UPDATE происходит автоматическая блокировка на уровне строк таблицы (не всей таблицы, а только строки которая апдейтиться), поэтому 1 и ту же строку не могут апдейтить одновременно несколько потоков, а после апдейта и разблокировки строки она уже не будет подходить по параметрам ( status уже будет не 0 )

UPDATE tbl SET id=LAST_INSERT_ID(id), status=1 WHERE status=0 LIMIT 1

следующей функцией получаем id той строки статус которой был изменен

SELECT LAST_INSERT_ID()

такое работает если нужно что бы каждый поток брал 1 строку ... и UPDATE и SELECT должны выполняться в одной сессии
а что бы не зависеть от сессий.... потому что это та еще негарантированная хрень.... лучше использовать транзакции и не парить себе мозг

START TRANSACTION;

UPDATE tbl
SET id = LAST_INSERT_ID(id), status = 1
WHERE status = 0
LIMIT 1;

SELECT LAST_INSERT_ID();

COMMIT;
 

DeepFarm

Seller
Регистрация
11.05.2023
Сообщения
89
Благодарностей
14
Баллы
8
string zapros = @"LOCK TABLES webhook WRITE;
SELECT data FROM webhook WHERE mode=""odobr"" LIMIT 1;
DELETE FROM webhook WHERE mode=""odobr"" LIMIT 1;
UNLOCK TABLES;";

project.Variables["responce"].Value = ZennoPoster.Db.ExecuteQuery(zapros, null, ZennoLab.InterfacesLibrary.Enums.Db.DbProvider.MySqlClient, "server="+project.Variables["hostMySQL"].Value+";user id="+project.Variables["loginMySQL"].Value+";password="+project.Variables["passMySQL"].Value+";database="+project.Variables["baseMySQL"].Value+";port="+project.Variables["portMySQL"].Value+"", " ", "");

Вот запрос в c# без колдовства... берет одну строку - одним потоком
 

Petr_G

Client
Регистрация
20.10.2017
Сообщения
86
Благодарностей
34
Баллы
18

marushin

Client
Регистрация
12.01.2015
Сообщения
185
Благодарностей
55
Баллы
28
И open server совсем не нужен! Mysql прекрасно ставится на windows.
и намертво приколачивается к системе, быкапы только через сам мускул, нельзя скопировать просто директорию. с open server, копируй - переноси куда хочешь

По теме, в MySQL уже всё придумано, нет смысла что то изобретать. Как выше ответили, транзакции решают вопрос многопотока, собственно они для этого и нужны.
 
Последнее редактирование:

Wide

Client
Регистрация
04.02.2013
Сообщения
944
Благодарностей
252
Баллы
63

Кто просматривает тему: (Всего: 1, Пользователи: 0, Гости: 1)