Руслан Волшебник
Руслан Волшебник16 октября 2019 г. 11:07

Как реализовать таймер на Django для викторины в telegram боте?

Django, Telegram, Bot, celery

Здравствуйте.

Я делаю викторину в telegram боте на Django.
Суть заключается в том, что нужно выдавать вопрос с ответами пользователю на определенное время, а после окончание времени ему выдается следующий вопрос, но если пользователь вовремя ответил на вопрос, то таймер обновляется и ему выдаётся другой.

У меня в голове только мысль использовать Celery.
А именно, после выполнения функции отправки вопроса, создавать задачу, которая через определенное время снова выполнит эту функцию, а если пользователь уложился в этот промежуток времени и ответил, то остановить задачу и выполнять всё по новой.

Я не спец, но мне кажется, что создавать на каждый вопрос "таску" это не правильное решение.

Как подобное правильно реализовать?

Прошу вас просветить меня.

Рекомендуем хостинг TIMEWEB
Рекомендуем хостинг TIMEWEB
Стабильный хостинг, на котором располагается социальная сеть EVILEG. Для проектов на Django рекомендуем VDS хостинг.

Вам это нравится? Поделитесь в социальных сетях!

17
Evgenii Legotckoi
  • 16 октября 2019 г. 15:17
  • Ответ был помечен как решение.

Добрый день.

Ну у меня таска создаётся на каждый лайк/дислайк, чтобы уведомление создать, так что я бы не сказал, что это так уж и неправильно.

Тут скорее вопрос в том, как именно сообщать будете боту, что истекло время на ответ. Тут один только таск в celery не поможет.

Если у вас уже есть механизм, которые оповещает бота о подобных изменениях, то не вопрос, таск в celery это решает. А вот если нет, то нужно, чтоюы бот сам опрашивал сервер по истечению времени. В таком случае не обязательно наличие таска в celery. Достаточно будет иметь запись о возможном варианте ответа, у которой будет установлено время итечения актуальности, например поле answer_expired. Таким образом алгоритм получается следующий:

Пользователь получает вопрос и думает над ним, а бот создаёт на сервере незаконченный вопрос на ответ со сроком истечения времени ответа.
При этом бот считает время, и если пользователь успел ответить, то бот завершает вариант ответа, и создаёт новую запись ответа для следующего вопроса. Если пользователь не успел ответить, то бот автоматически берёт следующий вопрос, а предыдущий помечает неотвеченным. Акцентирую внимание на том, что бот сам отсчтиывает время. Если же пользователь по какой-то причине перестал отвечать боту на вопросы и даже закрыл приложние. То при продолжении викторины можно проверить время истчеения ответа на вопрос (запись о варианте ответа с answer_expired) и уже после этого решить, продолжить отвечать на текущий вопрос, если он актуален, или же взять следующий.

Таким образом можно обойтись и без celery, если время будет считать клиент, лишь сверяясь со временем истечения ответа на вопрос.

Если же у вас сервер может посылать какие-то подобные сигналы боту (честно, не знаю как там дела обстоят с ботами, не делал такого), то тут уже можно подключать celery, то поле answer_expired я бы оставил, пригодится.

Вот такие у меня мысли на этот счёт. А так я бы не сказал, что испльзование celery с тасками такое уже и плохое решение. Я например реально использую это для лайков/дислайков, чтобы отсрочить время появления уведомления на одну минуту. Просто пользователь может поставить лайк, а через 5 секунд передумать и убрать его. Таск в итоге выполяентся через минуту, но если лайк уже не найден в базе данных, то и уведомление не создаётся.

    Руслан Волшебник
    • 17 октября 2019 г. 8:51

    "Таким образом можно обойтись и без celery, если время будет считать клиент, лишь сверяясь со временем истечения ответа на вопрос."

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

    Спасибо вам. Думаю этой информации мне будет достаточно, чтобы прийти к решению задачи.

      Evgenii Legotckoi
      • 17 октября 2019 г. 9:02

      ок. А в обратную сторону как-то работает? То есть можно как-то стрельнуть в сторону пользователя с сервера информацией? просто тогда таск в celery завершится, а дальше что? пользователь может ответить на вопрос, а ему в ответ, "извините, вы не успели" ?

      Я правильно понимаю алгоритм работы ботов в телеграме?

        Руслан Волшебник
        • 17 октября 2019 г. 10:26

        Да, вы всё верно поняли.

          Руслан Волшебник
          • 17 октября 2019 г. 11:05
          • (ред.)

          У меня возник ещё вопрос.
          В самом вопросе я писал вариант с созданием таски для каждого вопроса. Так получается не мало тасков.
          А вопрос то вот в чём. Не лучше ли мне сделать таску, которая каждые, допустим, 100мс проверяет не прошло ли время, которое находится в answer_expired?
          В таком варианте правда погрешность выше.

            Evgenii Legotckoi
            • 17 октября 2019 г. 11:22
            • (ред.)

            А какая разница будет?
            В любом случае у вас будет по одной таске на каждый активный вопрос.
            Или вы хотите одну большую таску, которая будет перебирать например сразу сотню активных вопросов?
            Думаю, что лучше одну таску на каждый активный вопрос.

            Представьте себе ситуацию. У вас одновременно 1000 активных вопросов, пускай их задали одновременно (так будет проще).

            • Вариант 1. У вас по таску на кждый вопрос, а длительность ответа на вопрос 2 минуты. Таким образом, в течение двух минут у вас будет выполнение таска 1000 раз, при этом каждый таск будет выполняться только для одного вопроса. Выборка данных из базы даных для одной записи будет допустим 1 мс с учетом соединения с базой данных. Ну небольшие расходы на работу таска и т.д. Пускай будет ещё 1 мс. Таким образом выполнение всех этих тасков займёт 2 секунды, в течение 2 минут ожидания всех вопросов.
            • Вариант 2. У вас один жирный таск, который каждые 100мс делает проверку всех активных вопросов. Получается, что выборка всех тасков, конечно не займёт 1 секунду, база данных сработает гораздо быстрее, на маленьких порциях данных тут больше времени соеднинение занимает, но допустим всё заберёт за 20мс, но время работы python кода не уменьшится, и получим, что затратим 1 секунду на перебор всех активных вопросов и проверку их времени. При этом будем делать проверку каждые 100мс, в итоге получается что-то сумасшедшее, поскольку каждые 100мс мы будет ждать 1 секунду на все проверки... Мне кажется я тут бредовую ситуацию описал, но если честно, то она возможна в данном случае.
            • Вариант 3. Один таск проверяет один вопрос каждые 100мс. Ну это теже самые яйца, что и в первом варианте, только чаще.
              Руслан Волшебник
              • 17 октября 2019 г. 11:31

              Я вас понял) Спасибо ещё раз. Вы помогли мне во всём разобраться.

                Руслан Волшебник
                • 23 октября 2019 г. 12:25

                Снова здравствуйте. Появилась идея.
                В telegram боте можно редактировать сообщения. Собственно есть мысль сделать периодические задачи, которые будут менять текст сообщения.
                Допустим, на вопрос даётся 10 секунд, мы отправляем сообщение с вопросом и ещё сообщение с таймером. Так вот, задача будет каждую секунду обновлять сообщение с таймером.

                Для работы с периодическими задачами нашёл библиотеку django-celery-beat

                Что думаете об этом? Будут ли нормально работать 1000 периодических задач одновременно?

                И ещё хотелось бы понять, что делать, если упираешься в границы. Т.е., например, выдерживает максимум 1000 задач и как быть? Увеличивать мощности или менять инструменты разработки?

                  Evgenii Legotckoi
                  • 24 октября 2019 г. 3:11

                  Добрый день.

                  Знаете. Для более полного рассуждения на эту тему мне нужно больше информации о самом процессе апдейта сообщения.
                  Может расскажете, как вы будете делать Update сообщения? Вы используете там getUpdate метод или web hook?

                  Насчёт 1000 тасков. Ну не прям одновременно, всё будет зависеть от количества инстансов celery и количества ядер на сервере. Но я бы смотрел по факту, как проект будет развиваться.

                  Когда я упираюсь в границы, то я начинаю рефакторить проект и изучать различные способы оптимизации. Пока что я только один раз докупал мощности сервера и только потому, что в минимальном тарифе было только 5 Гб на жёстком диске, хотя признаю, что сейчас мне бы не хватило минимального тарифа и я в любом случае докупал бы мощности. В большинстве случаев скорее всего достаточно будет изучить способы отпимизации в первую очередь, а потом уже думать над расширением мощностей.

                  Насчет того, выдерживает ли 1000 задач, тоже вопрос из разряда тех, на которые невозможно ответить, не зная всех условий. Всё зависит от того, насколько затратный таск будет. Плюс издержки на соединение бота с сервером и т.д.

                  Я думаю, что тут большее влияние будет иметь как раз это самое соединение по сети, то есть сделать инкремент счётчика не проблема, проблема будет передать данное сообщение. Каждое соедиение будет расходовать ресурс времени, пока таск не завершится. Так что тут скорее может встать проблема ожидания ответа бота, чем нагруженность всей системы. Но это лишь моё дилетантское мнение на этот счёт. Без практического опыта реализации подобных ботов - это лишь оценочное мнение из близлежащих плоскостей.

                  Ну а в случае Django у меня например больше расходуется память, чем процессорное время. Нагрузка на процессор у меня сейчас примерно 8% при посещаемости 1500 человек в сутки, а вот память выедается полностью, ещё и в swap лезет.

                  В любом случае докупить мощности - не проблема. Это всегда можно сделать.

                    Руслан Волшебник
                    • 24 октября 2019 г. 5:30
                    • (ред.)

                    "Может расскажете, как вы будете делать Update сообщения? Вы используете там getUpdate метод или web hook?"
                    Использую webhook.

                    send_question - функция отправки вопроса с ответами. Она уже есть и работает.

                    А далее мысли по поводу реализации таймера.

                    1. Сразу же отправить сообщение с таймером. Затем из ответа достать message_id(это id сообщения, текст, которого мы будем редактировать) и text(текст сообщения). В тексте сообщения будет число(это же таймер).

                    2. Записать это число куда-нибудь. Так как число - это промежуточный результат, который нет необходимости хранить в бд, то я хочу добавить его в redis(он и так используюется на сервере), ну и ещё в данном случае редис сработает в 10 раз быстрее(проверенно мной). Храниться будет для каждого пользователя, redis.set(chat_id, timer). chat_id - это уникальный идентификатор пользователя в telegram.

                    3. Создаем PeriodicTask для пользователя, если таски нет в бд. И запускаем её. Вот пример.

                    @app.task
                    def update_timer(chat_id, message_id):
                        timer = int(redis.get(chat_id))
                        if timer == 0:
                            PeriodicTask.stop()
                            bot.send_message(chat_id, "Время вышло")
                            # тут должна быть функция, которая отправляет новые вопросы и запускает таймер заново
                            return
                        text = timer - 1
                        bot.edit_message_text(chat_id, message_id, text)
                        redis.set(chat_id, text)
                    

                    Как-то так.

                      Руслан Волшебник
                      • 24 октября 2019 г. 6:22

                      Вот я сейчас начал пробовать и столкнулся вот с чем.
                      Слишком долго приходится ждать пока celery beat запустить задачу, от 2 до 5 секунд.
                      И получается, что таймер отправился, а остчет начался через эти 2-5 секунд.

                      Сама задача нормально работает, а вот как ускорить процесс запуска задачи не понятно.

                        Evgenii Legotckoi
                        • 24 октября 2019 г. 7:55

                        Если честно, то даже и не знаю, что сказать.
                        Обычно celery применяют для отложенных задач, или задач, которые нужно выполнять в фоновом режиме.
                        Возможно, что этот инструмент просто не подходит для такой задачи.

                        Мне сама по себе идея таймера на отложенных задачах в celery кажется немного диковатой. Максимум я бы отправлял сообщение о том, что время истекло в таком случае. А таймер должен на клиенте крутиться. Но как тут обстоит дело с ботами телеграм - не знаю ((

                          Руслан Волшебник
                          • 24 октября 2019 г. 8:01

                          Понял. Всё равно большое спасибо.

                            Руслан Волшебник
                            • 24 октября 2019 г. 8:04

                            Ах да, не могли бы посоветовать инструмент для подобной задачи или хотя бы в какую сторону копать?

                              Руслан Волшебник
                              • 24 октября 2019 г. 8:34

                              Судя по моим результатам поиска в гугле, стоит оставить затею с отображением таймера у клиента в боте, так как нельзя запустить таймер напрямую у клиента. Спасибо за всё. Вопросов больше нет.

                                Evgenii Legotckoi
                                • 24 октября 2019 г. 8:35

                                ну да. я вам хотел тоже самое написать ))

                                  Evgenii Legotckoi
                                  • 24 октября 2019 г. 8:53

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

                                  https://stackoverflow.com/questions/48652282/celery-adding-7000-tasks-with-apply-async-takes-12-seconds

                                    Комментарии

                                    Только авторизованные пользователи могут публиковать комментарии.
                                    Пожалуйста, авторизуйтесь или зарегистрируйтесь
                                    Дмитрий

                                    C++ - Тест 004. Указатели, Массивы и Циклы

                                    • Результат:60баллов,
                                    • Очки рейтинга-1
                                    Дмитрий

                                    C++ - Тест 003. Условия и циклы

                                    • Результат:92баллов,
                                    • Очки рейтинга8
                                    d
                                    • dsfs
                                    • 26 апреля 2024 г. 4:56

                                    C++ - Тест 004. Указатели, Массивы и Циклы

                                    • Результат:80баллов,
                                    • Очки рейтинга4
                                    Последние комментарии
                                    k
                                    kmssr8 февраля 2024 г. 18:43
                                    Qt Linux - Урок 001. Автозапуск Qt приложения под Linux как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
                                    АК
                                    Анатолий Кононенко5 февраля 2024 г. 1:50
                                    Qt WinAPI - Урок 007. Работаем с ICMP Ping в Qt Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
                                    EVA
                                    EVA25 декабря 2023 г. 10:30
                                    Boost - статическая линковка в CMake проекте под Windows Ошибка LNK1104 часто возникает, когда компоновщик не может найти или открыть файл библиотеки. В вашем случае, это файл libboost_locale-vc142-mt-gd-x64-1_74.lib из библиотеки Boost для C+…
                                    J
                                    JonnyJo25 декабря 2023 г. 8:38
                                    Boost - статическая линковка в CMake проекте под Windows Сделал всё по-как у вас, но выдаёт ошибку [build] LINK : fatal error LNK1104: не удается открыть файл "libboost_locale-vc142-mt-gd-x64-1_74.lib" Хоть убей, не могу понять в чём дел…
                                    G
                                    Gvozdik18 декабря 2023 г. 21:01
                                    Qt/C++ - Урок 056. Подключение библиотеки Boost в Qt для компиляторов MinGW и MSVC Для решения твой проблемы добавь в файл .pro строчку "LIBS += -lws2_32" она решит проблему , лично мне помогло.
                                    Сейчас обсуждают на форуме
                                    G
                                    George137 мая 2024 г. 0:27
                                    добавить qlineseries в функции в функции: "GPlotter::addSeries(QString title, QVector &arr)" я вызываю метод setChart(...), я в конструктор передал адрес на QChartView элемент
                                    BlinCT
                                    BlinCT5 мая 2024 г. 5:46
                                    Написать свой GraphsView Всем привет. В Qt есть давольно старый обьект дял работы с графиками ChartsView и есть в 6.7 новый но очень сырой и со слабым функционалом GraphsView. По этой причине я хочу написать х…
                                    PS
                                    Peter Son3 мая 2024 г. 17:57
                                    Best Indian Food Restaurant In Cincinnati OH Ready to embark on a gastronomic journey like no other? Join us at App india restaurant and discover why we're renowned as the Best Indian Food Restaurant In Cincinnati OH . Whether y…
                                    Evgenii Legotckoi
                                    Evgenii Legotckoi2 мая 2024 г. 14:07
                                    Мобильное приложение на C++Qt и бэкенд к нему на Django Rest Framework Добрый день. По моему мнению - да, но то, что будет касаться вызовов к функционалу Андроида, может создать огромные трудности.
                                    IscanderChe
                                    IscanderChe30 апреля 2024 г. 4:22
                                    Во Flask рендер шаблона не передаётся в браузер Доброе утро! Имеется вот такой шаблон: <!doctype html><html> <head> <title>{{ title }}</title> <link rel="stylesheet" href="{{ url_…

                                    Следите за нами в социальных сетях