Работа с ZeroMQ и PostgreSQL в asyncio

    Наверное, все уже слышали о чудесной библиотеке asyncio?

    Если нет, то коротко: эта либа стала стандартом на асинхронное сетевое программирование на Python. tornado и twisted или научатся с ней работать или постепенно уйдут в маргиналы.

    asyncio прекрасна, но это просто библиотека для работы с TCP, UDP, UNIX сокетами, PIPES и асинхронным запуском subprocess.

    Чтобы всем было здорово нужны сторонние библиотеки, умеющие работать с asyncio. Кое-что уже есть, но мало.

    Вот я с коллегами и сделал пару: одну для ZeroMQ и другую для PostgreSQL

    Если вы прочно сидите на Python 2 и не интересуетесь Python 3 — не ходите под кат во избежание глупых вопросов и прочих недоразумений.


    Зачем оно мне было надо?

    Потому что мы начинаем новый проект в котором без ZeroMQ и Postgress не обойтись. Очень хочется делать на asyncio. Пришлось запилить недостающие кусочки.

    Сразу скажу, обе либы добротные. Я Python Core Developer и в коде asyncio тоже есть немало моего труда. Думаю, я понял как хорошо писать для системы, которую сам же помогал создавать.

    Плюс (почти) 100% покрытие кода обоих библиотек тестами и внятная (надеюсь) документация.

    aiozmq — позволяет использовать ZeroMQ сокеты с asyncio.

    Документация — здесь.

    aiozmq работает с низкоуровневыми ZeroMQ сокетами и, главное, даёт из коробки механизм Remote Procedure Call (плюс PubSub и Notify).

    Короткий пример:

        import asyncio
        import aiozmq
        import aiozmq.rpc
    
        class ServerHandler(aiozmq.rpc.AttrHandler):
            @aiozmq.rpc.method
            def remote_func(self, a:int, b:int) -> int:
                return a + b
    
        @asyncio.coroutine
        def go():
            server = yield from aiozmq.rpc.start_server(
                ServerHandler(), bind='tcp://127.0.0.1:5555')
            client = yield from aiozmq.rpc.open_client(
                connect='tcp://127.0.0.1:5555')
    
            ret = yield from client.rpc.remote_func(1, 2)
            assert 3 == ret
    
            server.close()
            client.close()
    
        asyncio.set_event_loop_policy(aiozmq.ZmqEventLoopPolicy())
        asyncio.get_event_loop().run_until_complete(go())
    


    Теперь перейдем к aiopg

    Эта либа умеет работать с psycopg2 в асинхронном режиме и в качестве приятного бонуса даёт connection pool

    Документация — здесь.

        @asyncio.coroutine
        def test_select():
            pool = yield from aiopg.create_pool(dsn)
    
            with (yield from pool.cursor()) as cur:
                yield from cur.execute('SELECT 1')
                ret = yield from cur.fetchone()
                assert ret == (1,), ret
    
    


    Обе библиотеки выложены на GitHub: aiozmq и aiopg

    Пробуйте, если понравилось — используйте.

    Найдете баги — пишите на гитхабе в issues, а ещё лучше сразу делайте pull request

    Похожие публикации

    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 36

      0
      Не понял, зачем торнадо работать с asyncio? Синтаксис же почти один в один, что это даст торнадо? К тому же gen, в отличии от asyncio, имеет один синтаксис во 2 и 3 версии питона. Если речь о совместном использовании библиотек, это давно возможно: www.tornadoweb.org/en/stable/asyncio.html
        +2
        Вот вы сами и ответили на вопрос «зачем» — чтобы использовать библиотеки, сделанные для других систем. Причем если я создаю что-то для asyncio это можно будет использовать и в tornado. А наоборот не получится. Какую библиотеку делать перспективней?

        P.S. Вообще-то торнадо научилась понимать asyncio event loop не так уж и давно — три месяца назад :)
          0
          Понял, спасибо :) А вы orm прикрутить к aiopg не планируете, случаем?
            0
            ORM — нет, не планирую. По крайней мере сейчас мне это не нужно
          0
          --
          +5
          Во-первых, спасибо за библиотеки. Прекрасно, что уже появляются практические примеры применения asyncio.

          Во-вторых, у меня есть к вам огромная просьба — напишите статью про то, как применять asyncio на практике. Я прошелся по документации, почитал введение в Twisted (чтобы видеть перспективу) нашел пару примеров в доках и пару в сети — и все. Этого очень мало, чтобы вникнуть в тему как следует. Asyncio дает возможность писать асинхронные приложения как колбеками, так и корутинами, и было бы круто узнать особенности этих подходов на примерах.

          Я уверен, что туториал от кор-девелопера — это то, что оценит много людей, не только на Хабре, но и вообще в коммьюнити Питона. Если что, с удовольствием займусь переводом на английский.

          Спасибо!

          P.S. AMA на Реддите в r/python не хотите замутить? :-)
            0
            1. Пожалуйста.
            2. Я начинал писать серию таких статей но застрял на полпути. Сделать что-то толковое требует немалого времени и драйва. Иначе ничего не выходит. Если статьи и появятся то точно не на хабре а у меня в блоге. Хабр ревнив и требует эксклюзивности, а это не нравится уже мне.
            На callbacks писать не надо, это сложно и вообще может быть нужно только разработчикам библиотек. Если либы хорошие то пользователю кроме yield from ничего требоваться не должно.
            P.S. Что такое AMA на Реддите в r/python не знаю, так что, наверное, замутить не хочу.
              0
              Выкиньте доки по twisted в окно, imho с торнадо проще и быстрее разобраться. А принципы примерно одинаковые. Про asyncio пока не скажу, но принципиально все тоже самое.

              Что собственно вы хотите делать с корутинами? Фактически это теже колбэки, только выглядящие как синхронный код.

              def process(self, html):
              print «got html:», html

              def foo(self):
              async_fetch(«example.com», callback=self.process)

              вместо этого пишем

              @ gen.coroutine
              def foo(self):
              html = yield async_fetch(«example.com»)
              print «got html:», html

              где async_fetch, загружает страницу и вызывет self.process которому передает загруженны html код.
              Это все грубо говоря и размахивая руками, есть еще много тонкостей: foo теперь future которую тоже нужно ждать (yield), async_fetch должен быть генератором, exceptions надо ловить итд.
              Да, и важно понимать что такое генераторы и как они работают. На stack overflow был знаменитый пост где все объяснено.
                0
                Если советуете выкинуть twisted, то почему не распространаете совет и на tornado?
                asyncio принципиально похож на twisted/tornado ровно в том смысле, в каком программа на ассемблере похожа на программу на C: они ведь обе компилируются в машинный код!

                С корутинами я хочу (и уже имею) несколько критически важных вещей:
                1. Линейная структура кода. Все известные мне программисты мыслят линейно. Их можно научить думать в терминах обратных вызовов (я в свое время писал и для twisted и для tornado если что) — но это не совсем легко. В результате программа читается плохо и как минимум обработка ошибок очень страдает. Непойманные исключения улетают в никуда, это повсеместная проблема (кто-то где-то да забудет). Если исключение таки поймано, то у него ужасный traceback. В twisted предпринимали серьезные усилия чтобы этот traceback выглядел читаемо. С частичным успехом.
                2. Удобная отладка. Если остановился на строчке с yield from, то 'next' переходит на следующую строку а не начинает отлаживать внутренности event loop.
                3. Продуманный набор функций/классов для запуска, ожидания, отмены корутин, таймауты, блокировки, очереди и пр. В tornado.gen есть только очень куцый минимум того, что работает в asyncio

                P.S.
                twisted был великой библиотекой. Десять лет назад.
                tornado, пожалуй, получше twisted. Но сравнение с asyncio с треском проигрывает. Единственное преимущество на сегодня — tornado.web, но за полгода и для asyncio появится достойная альтернатива (собственно говоря они уже есть но каждая в чем-то недоделана).
                  +1
                  Я советую выкинуть доки от twisted, а не сам twisted. Для начинающих там все очень долго и путано. Разобраться и начать работать с tornado гораздо быстрее. Впрочем это мой опыт и я на нем не настаиваю.

                  Asyncio выглядит интересно и я думаю попробовать переписать что-нибудь под него, чтобы составить собственное мнение. Но вобщем с asyncio есть три большие проблемы:
                  • Python 3.4. Все сидят на 2.6-2.7 и переходить на 3.x не собираются. Печально, но факт. Twisted/Tornado работают в 2.6-2.7
                  • Переводить продакшн код с Twisted/Tornado на Asyncio тоже особо незачем.
                  • Для asyncio пока мало библиотек. Надеюсь скоро это изменится.

                  Так что asyncio пока только для новых проектов и смелых/отчаянных команд. Хотя есть надежда что asyncio поможет сдвинуть переход на 3.x с мертвой точки.

                  Как по мне, так asyncio, twisted и tornado, одно и тоже с разницей в API и деталях, когда как треды и greenlets уже совсем другой принцип.
                    0
                    Ok, так согласен
                      0
                      Twisted/Tornado работают в 2.6-2.7

                      Prerequisites: Tornado runs on Python 2.6, 2.7, 3.2, 3.3, and 3.4.
                      я думаю попробовать переписать что-нибудь под него, чтобы составить собственное мнение.

                      Рекомендую — что-нибудь не очень ценное :) На форумах пишут, и я согласен: «портируясь на async, никогда не знаешь, какой кусок старого кода тебя утопит, отъев более 100мс внутри ioloop». У нас так вёл себя, например, DNS при отпавшей сети.
                        0
                        В asyncio запросы DNS делаются в thread pool если что.
                        И вообще если не думать — выстрелить себе в ногу можно абсолютно любым инструментом.
                        asyncio в этом отношении не проще и не сложнее.
                +1
                А что с производительностью? Для двух соединений получающих данные мелкими порциями (100-200 байт) каждые 1-5ms, что будет выгоднее: два потока с zmq.Poller или один поток с вашим ивент лупом?
                  0
                  про потоки не понял. Имеются в виду threading.Thread или что-то другое?
                    0
                    Да, threading.Thread.

                    Можно даже рассмотреть 3 варианта:
                    1. Два threading.Thread с zmq.Poller
                    2. Один threading.Thread и один aiozmq ивент луп
                    3. Два threading.Thread в каждом по aiozmq ивент лупу.
                      0
                      Я пробовал 2 потока zmq vs aiozmq, получилось где-то 5600 и 6200rps соответственно (5-7б на сообщение).
                        0
                        Можно взглянуть на код?
                          0
                          Примерно такой. Этот у меня выдал 5500 (zmq) и 7100 (aizmq) на py 3.4.
                            0
                            В примере с потоками я вижу только один поток клиента.

                            Мне хотелось сравнить aiozmq именно с многопоточным вариантом, чтобы понять, как эффективно и честно выделяется время потокам на работу с сетью

                            Кроме того у вас используется два zmq контекста в одном процессе. Если заменить на zmq.Context().instance(), производительность изменится?
                    0
                    Сделал тесты: asvetlov.blogspot.com/2014/04/aiozmq-benchmark.html
                    Если что непонятно — спрашивайте
                      0
                      Спасибо. Дальнейшие комментарии по тебе буду оставлять в вашем блоге.
                    0
                    Почему только request-reply? Остальные паттерны не ложатся на архитектуру asyncio?
                      0
                      ложатся прекрасно. Просто request-reply должен быть самый сложный и медленный для aiorpc.
                      0
                      Майк Бейер довольно жёстко и, вроде бы, обоснованно, с бенчмарками прошёлся по асинхронному в/в в подключении к БД: techspot.zzzeek.org/2015/02/15/asynchronous-python-and-databases
                      Вкратце: «Оно не надо; это только для веба».
                      Что вы думаете о его статье?
                        0
                        У Майка своя колокольня — SQLAlchemy никогда не заработает с корутинами.
                        Но в целом согласен: «оно не надо, это только для веба».
                        Вопрос: вы, случайно, не разработкой для веба на хлеб зарабатываете?
                        Тогда ненужность асинхры — не звучит.
                          0
                          Частично для веба. Но веб-то бывает разный: если не случается с10к, то можно по-всякому подойти. А даже если и случится. Кто-то пробовал разрешить на сервере 400 воркер-тредов?

                          В обсуждениях на Реддите, на Y-Combinator и в блогах люди высказывались по-разному, но с цифр в пользу async io почему-то никто не привёл :)

                          На YC дядя, не называя конкретики, пишет: «Я работал в одной из самых больших в мире соцсетей. Там на каждый сокет порождается тред, а потом ещё и подтреды. И нормально. В принципе, ограничение на количество сокетов у нас есть… примерно 4 миллиона на один сервер».
                            0
                            Может у дядьки с одной из самых больших в мире соцсетей одни из самых мощных в мире серверов, откуда я знаю?

                            Попробуйте 400 worker-threads и классический сервер о 16 ядрах начнет спотыкаться. На 1600 — ляжет и заплачет. В то время как asyncio (tornado, twisted, nodejs) будет работать и не жужжать.

                            Но основной затык не в этом.
                            Так вышло, что синхронные сервера для питона все работают по стандарту WSGI. Который не поддерживает websockets и прочие варианты push-messages просто по определению. И это проблемма.
                              0
                              Возможно, я лично проверю бенчмарки, благо, Бейер их выложил. У него ноут о 4 или 8 ядрах справлялся с синхронными запросами лучше, чем asyncio; один из тестов был на 350 воркеров.

                              Правда, в его тесте участвует только голый код на Python + драйвера, безо всяких серверов и фреймворков. Что сервера и фреймворки добавляют катастрофический оверхед, я ни минуты не сомневаюсь. В этом смысле Tornado, с которой у меня небольшой опыт есть, выигрывает: в нём оверхеда, равно как и функционала, не слишком много.

                              А если нужно держать открытыми тысячи сокетов — тут, конечно, пора экономить; но на эту область Бейер и не замахивался.
                                0
                                Один из пойнтов Бейера и был, что IO-bound задачи на Пайтоне очень быстро превращаются в CPU-bound. Конечно, если спящих сокетов не на порядки большое, чем живых.
                                Автора SQLAlchemy я не могу заподозрить в питононенавистничестве :)
                                  0
                                  Его можно заподозрить в защите SQLAlchemy.
                                  Потому что для асихронных библиотек она катастрофически не подходит.
                                    0
                                    Asyncio/Tornado расходуют много* cpu на промолачивание самой асинхрощины (перекладывание функций туда сюда, жонглирование со списками, регистрация, удаление и перебор событий и т.п.) поэтому оно работает гораздо медленнее чем синхронный подход, так пустое wsgi приложение может работать в 2 раза быстрее пустого async питон приложения.
                                    Асинхрон выгоден (иногда необходим) на долго висящих соединениях (веб-сокеты, http-закачка...), где не выгодно держать гору потоков.

                                    На обычных, коротких web запросах async не (всегда) оправдан, например такой сценарий — зачастую достаточно 50-100* потоков wsgi (~3-6Mb RAM) что-б нагрузить БД (для одного сервера*), а больше потоков смысла вводить нет, т.к. уже уперлись в БД.
                                    В данной ситуации async может показать результаты хуже, особенно если запросы отрабатывают быстро, питон начнет упираться в CPU, придется форкать приложение, и в итоге БД будет иметь меньше CPU, а значит меньшая общая производительность.
                                    Конечно все зависит от деталей.

                                    Ещё разработчики торнадо предлагали использовать блокирующий доступ к БД в самом торнадо (они так делали у себя в friendfeed).

                                    И это все без учета сложности async кода о котором можно филосовствовать часами.
                                      0
                                      Еще раз. Мне на сайте нужна система извещений пользователя о том что ему пришло какое-то сообщение.
                                      WSGI не предполагает ничего подобного.
                                      Если делать pull requests (не websockets и даже не long polling, это всё выходит за рамки WSGI) с приемлемой частотой (раз в секунду хотя бы) — быстро завалим любой сервер, как бы быстро запросы не отрабатывали.
                                        0
                                        Я про ваш проект ничего не говорил, async для веб-сокетов самое-то.
                                        Речь про то, что из-за популярности (пропаганды) async, многие начинают делать «блоги» (обычные сайты) на асинхронных фреймворках.
                                          0
                                          В том-то и дело.
                                          1. Пишем блог
                                          2. Добавляем комментарии (в топку disqus, мы же велосипедим!)
                                          3. Добавляем runtime notifications для комментариев
                                          На пункте 3 приплыли.

                                          На самом деле не так много сайтов обходятся без такой системы.
                                            0
                                            Ничего не приплыли, делается микросервис (в сотню строк) который через zmq получает евенты и отправляет через вебсокеты, и не надо весь проект делать асинхронным.

                          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                          Самое читаемое