1. Вы находитесь в сообществе Rubukkit. Мы - администраторы серверов Minecraft, разрабатываем собственные плагины и переводим на различные языки плагины наших коллег из других стран.
    Скрыть объявление
Скрыть объявление
В преддверии глобального обновления, мы проводим исследования, которые помогут нам сделать опыт пользования форумом ещё удобнее. Помогите нам, примите участие!

Помогите Запрос к Базе Данных

Тема в разделе "Разработка плагинов для новичков", создана пользователем tiu, 14 фев 2021.

Статус темы:
Закрыта.
  1. Автор темы
    tiu

    tiu Активный участник Пользователь

    Баллы:
    61
    Имя в Minecraft:
    tiu
    Добрый день, я тут разрабатывал некий датацентер и случился казус.

    У нас есть некий метод get(String data), который отправляет запрос на БД. Ответ же на запрос приходит в другом методе answer(String data). Внимание, ВОПРОС: Как нужно правильно сделать запрос к бд, чтобы при этом задержать метод get(), но при этом не останавливая основной поток, т.к. если остановить осн поток, ответ не дойдёт до получателя. Есть некие наработки. Поток t создан исключительно в целях тестирования.

    Просьба скидывать ответ с примерами, а то я тупой:creeper:

    PHP:
    package DataCenter;

    import org.bukkit.Bukkit;

    import DataCenter.enums.Packet;

    public class 
    test{
      
        private static 
    String answer="";
      
        private static 
    Object lock=new Object();
      
        public static 
    void answer(String data) {
            
    Bukkit.getLogger().info("answer="+data);
            
    synchronized (lock) {
                
    answer=data;
                
    lock.notifyAll();
            }
        }
      
        private static 
    Packet last;
      
        private static 
    Runnable r=new Runnable() {
            @
    Override
            
    public void run() {
                
    Messenger.SendRequest(lastMessenger.server);
                
    Bukkit.getLogger().info("SubThread started");
                
    synchronized (lock) {
                    try {
                        
    lock.wait();
                        
    Bukkit.getLogger().info("NOTIFY");
                        
    notifyAll();
                    } catch (
    InterruptedException e) {
                        
    e.printStackTrace();
                    }
                    return;
                }
            }
        };
      
        public static 
    String get(Packet packet) {
            
    Thread t=new Thread(r);
            
    last=packet;
            
    Messenger.SendRequest(lastMessenger.server);
            
    t.start();
          
            
    synchronized (lock) {
                try {
                    
    lock.wait(2000);
                    return 
    answer;
                } catch (
    InterruptedException e) {
                    
    e.printStackTrace();
                }
                return 
    answer;
            }
        }
      
    }
    P.s. Если чо, объект Packet потом преращается в нормальное сообщение
    P.s.s. Вся проблема в строчке lock.wait(2000);, которая стопает осн поток и мешает ответу добраться до сервера. Запихать приём в отд поток-не вариант(
     
  2. Dymeth

    Dymeth Активный участник Пользователь

    Баллы:
    98
    Имя в Minecraft:
    Dymeth
    Убирай свои локи и вейты, убирай раннейблы и потоки. Полный мусор. Возможно, когда-то ты разберёшься с потоками нормально, и напишешь что-то стоящее, но сейчас это лишь усложняет тебе жизнь.

    Единственное, что нужно сделать - отправить запрос в бд и получить ответ НЕ в основном потоке. Для этого отлично подходит баккитовский шедулер:
    PHP:
    Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
        
    // Запрос к БД
        // Обработка результата запроса и действия после обработки
    });
    Можно сделать отдельный метод для отправки асинхронных запросов, и всегда использовать именно его. Выглядеть это может примерно так:
    PHP:
    public void queryAsync(PreparedStatement statementConsumer<ResultSetonResult) {
        
    Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
            try {
                
    ResultSet resultSet statement.executeQuery();
                
    onResult.accept(resultSet);
            } catch (
    Exception e) {
                
    e.printStackTrace();
            }
        });
    }
    Это самое простое, что вообще можно придумать. Само собой, когда разберёшься, сможешь переделать под себя - например, индивидуально под каждый запрос организовать обработку ошибок.

    Если ты хочешь принимать ответ в основном серверном потоке - значит это будет уже не асинхронный запрос в БД, а синхронный. И основной поток будет дожидаться ответа, а все остальные механики сервера в этот момент будут простаивать. Никак иначе это работать не может.

    В основном потоке можно сделать уже обработку ответа, полученного асинхронно. Например, можно запустить синхронный шедулер после асинхронного получения ответа. Но какой в этом смысл?
     
    Последнее редактирование: 15 фев 2021
  3. Автор темы
    tiu

    tiu Активный участник Пользователь

    Баллы:
    61
    Имя в Minecraft:
    tiu
    Боюсь, я изначально неправильно выразился. Под БД подразумевается другой сервер, а канал общения - PluginMessager, что ограничивает меня моментально. Безусловно, это костыль похлеще отправки сообщения игроку пакетами без JSON'а, но так было интересней

    Что-ж, получается, нужно как-то в шедулер или в отдельный поток впихать implements PluginMessageListener, чтобы принимать сообщения в отдельном потоке, а потом размораживать основной поток и ретурнить ответ из метода get().

    Или создать свой протокол общения между серверами без банжи, но я туда лезть пока не хочю

    Или просто на SQL перейти:mad:

    Вообщем, если вы знаете как впихать implements PluginMessageListener в асинк поток, я буду рад узнать этот способ, или другие идеи решения проблем) В любом случае, спасибо за помощь
     
  4. Dymeth

    Dymeth Активный участник Пользователь

    Баллы:
    98
    Имя в Minecraft:
    Dymeth
    С PluginMessages ситуация совсем иная, нежели с БД. PluginMessage's является системой, в которой в любой момент любая сторона можно отправить любое сообщение. При отправке сообщений не происходит блокировка потока, т.к. ответа ожидать бесполезно. Короче говоря, полноценной готовой системы вопрос-ответ в PluginMessage's просто не существует.

    Но можно попробовать организовать подобие такой системы самостоятельно. Тут уже всё зависит от реализации отвечающей стороны. Кто отвечает на запрос? Банжа? Плагин на банже? Клиентский мод?
     
  5. Автор темы
    tiu

    tiu Активный участник Пользователь

    Баллы:
    61
    Имя в Minecraft:
    tiu
    На запрос отвечает другой сервер с Bukkit'ом, а сам запрос отправляется через банжу. После обработки сервер-датацентр отправляет ответ, но на обработку может потребоваться время, и на сервере-датацентре реализована очередь запросов. То есть, схема следующая: Сервер n=>BungeeCord=>DataCenter=>BungeeCord=>Server n
     
  6. alexandrage

    alexandrage Старожил Пользователь

    Баллы:
    173
    Не нужно ничего изобретать и городить потоки. В баките есть sendPluginMessage и слушатель ответов PluginMessageListener. Собственно и в банжекорде тоже это имеется. Все уже изобрели за тебя.
     
  7. Автор темы
    tiu

    tiu Активный участник Пользователь

    Баллы:
    61
    Имя в Minecraft:
    tiu
    Ситуация немного иная. У меня есть метод get(), который должен возвращать String, получаемый после запроса при ответе в методе OnPluginMessageReceived(). То есть нужно отправить запрос и дождаться ответа, замораживая при этом поток, из которого пришёл запрос, не замораживая основной поток, или пихнуть OnPluginMessageReceived() в отдельный поток, и заморозить основной поток. Этом-то и проблема, что вроде как нету способа пихнуть OnPluginMessageReceived() в отдельный поток... А так всё довольно просто.
     
  8. alexandrage

    alexandrage Старожил Пользователь

    Баллы:
    173
    Нифига не понял, но очень интересно. Ты просто шлешь sendPluginMessage и слушаешь ответ в PluginMessageListener. Никаких блокировок делать не нужно. Как ответ придет тогда и ивент отработает.
     
  9. Автор темы
    tiu

    tiu Активный участник Пользователь

    Баллы:
    61
    Имя в Minecraft:
    tiu
    Боюсь, что конкретно на примере реализации БД не всё так просто. У нас есть метод get(), который ретурнит String с ответом со стороны БД. То есть, в методе get() мы должны дождаться ответа, а потом ретурнуть ответ из onPluginMessageRecieved().
     
  10. Dymeth

    Dymeth Активный участник Пользователь

    Баллы:
    98
    Имя в Minecraft:
    Dymeth
    То, что ты хочешь сделать, реализовать теоретически возможно. Действительно придётся потанцевать с потоками и их блокировками. Фактически нужно будет реализовывать блокирующую операцию получения ответа на какой-то запрос. Боюсь, для тебя это будет пока сложновато.

    Но есть альтернатива. Предлагаю тебе воспользоваться следующим способом.

    При создании запроса можно запоминать куда-то действия, которые будут выполнены при получении ответа. Эти действия можно заключить, например, в объект Consumer<ByteArrayDataInput>. Но я предлагаю для удобства объявить отдельный класс/интерфейс MessagingRequest. У него будут два метода: .send() и .onResponse(). Первый будет вызываться для отправки данных. А второй будет вызываться при получении ответа на этот запрос.

    Но тут встаёт вопрос - как по ответу понять, какой был задан вопрос, какие действия нужно выполнить?

    Всё будет работать идеально, только если новый запрос отправляется в "DataCenter" только после получения ответа на старый вопрос, и никак иначе.

    Если же нужно отправить сразу несколько запросов подряд, не дожидаясь ответа - придётся где-то хранить целую очередь из действий, которые выполнятся при ответе. Для подобного можно использовать, например List<MessagingRequest>. При получении ответа просто берём первый элемент (и сразу удаляем его из списка) - это и будет объект запорса, который отправлялся раньше всех (в текущей очереди).

    Но если сервер отвечает на вопросы не в том порядке, в котором они приходят на сервер, то всё становится ещё сложнее. В этом случае придётся определять необходимый MessagingRequest по самому содержимому ответа. Сделать это можно, например, путём добавления к каждому запросу какой-то уникальной подписи. Это может быть что угодно: например, рандомный int. При отправке ответа "DataCenter" обязан указать эту же подпись в ответе.
    Таким образом, при получении ответа у нас будет уникальный номер запроса. А это значит, что все запросы можно хранить в каком-нибудь Map<Integer, MessagingRequest>.

    Ну и, собственно, когда мы поняли, какой у нас MessagingRequest, мы просто вызываем у него метод onResponse(). Естественно, в этом методе нужно будет передать массив полученных данных и объект игрока, которому пришло сообщение (если это требуется).

    Абсолютно всё разжёвывать не стал, но надеюсь, что ты разберёшься и сам реализуешь моменты, о которых я не рассказал
     
  11. alexandrage

    alexandrage Старожил Пользователь

    Баллы:
    173
    Ну как ответ от бд придет, так и отправится ответный sendPluginMessage В чем твоя проблема то?
     
  12. Автор темы
    tiu

    tiu Активный участник Пользователь

    Баллы:
    61
    Имя в Minecraft:
    tiu
    [Тема закрыта] Итак, объясню подробней решение, использованное мной для моих потомков. Как и сказал многоуважаемый Dymeth, всё достаточно просто. Создаём объект отправляемого пакета данных и присваиваем ему некоторое значение UUID(Уникальный Идентификатор). Перед отправлением пакета мы должны запомнить его в некоторый Map<Integer, Thread>, где Integer - наш уникальный идентификатор, а Thread - поток, запускаемый при получении ответа. в Thread должен находится метод (ниже пример его создания)
    Код:
    Thread test=new Thread() {
         @Override
          public void run() {
                String answer=Query.answer;
                test(answer);
          }
                   
          public void test(String answer) {
                 Bukkit.getLogger().info(answer);
          }
    };
    Также в классе с Map<Integer, Thread> должен находиться метод, запускаемый при получении PluginMessage. В этом методе мы присваиваем указанной в классе переменной answer значение, полученное в ответе со стороны БД, а затем запускаем поток, указанный в мепе выше. Поток выполнит действия, указанные вами, не останавливая основной поток сервера, и, соответственно, не замедляя его работу. Объяснил максимально подробно и понятно, но опустил некоторые моменты. Желаю удачи.
     
Статус темы:
Закрыта.

Поделиться этой страницей