Inline-callback в tornado server для asyncmongo

    Пару недель назад разработчики tornado добавили нативный модуль по созданию inline callback (аналог inlineCallbacks в Twisted, Seq в Node.js, Fibers в Ruby).
    Ниже примеры использования и примеры с участием asyncmongo (асинхронным драйвером для mongoDB)

    Для сравнения приведу классический пример с калбеками
        class AsyncHandler(RequestHandler):
            @asynchronous
            def get(self):
                http_client = AsyncHTTPClient()
                http_client.fetch("http://example.com",
                                  callback=self.on_fetch)
    
            def on_fetch(self, response):
                do_something_with_response(response)
                self.render("template.html")
    

    А так, с новым модулем «tornado.gen»
        class GenAsyncHandler(RequestHandler):
            @asynchronous
            @gen.engine
            def get(self):
                http_client = AsyncHTTPClient()
                response = yield gen.Task(http_client.fetch, "http://example.com")
                do_something_with_response(response)
                self.render("template.html")
    

    Вызовы сделаны через генераторы, подобно как в Twisted.

    Так же можно вызывать массив методов, возврат управления происходит при отработке всех указанных методов
        def get(self):
            http_client = AsyncHTTPClient()
            response1, response2 = yield [gen.Task(http_client.fetch, url1),
                                          gen.Task(http_client.fetch, url2)]
    


    Ещё они добавили удобный механизм (методы Callback и Wait), для ожидания отработки всех нужных методов, запущенных в разное время
        class GenAsyncHandler2(RequestHandler):
            @asynchronous
            @gen.engine
            def get(self):
                http_client = AsyncHTTPClient()
                http_client.fetch("http://example.com",
                                  callback=(yield gen.Callback("key"))
                response = yield gen.Wait("key")
                do_something_with_response(response)
                self.render("template.html")
    

    Все выше примеры были взяты из модуля tornado.gen

    Пример как можно использовать вместе с asyncmongo
    class MainHandler(tornado.web.RequestHandler):
        @property
        def db(self):
            if not hasattr(self, '_db'):
                self._db = asyncmongo.Client(pool_id='mydb', host='127.0.0.1', \
                    port=27017, maxcached=10, maxconnections=50, dbname='test')
            return self._db
    
        @web.asynchronous
        @gen.engine
        def get(self):
            r, error = yield gen.Task(self.db.user.save, { 'login':'tester' })
            
            r, error = yield gen.Task(self.db.user.find_one, {})
            self.write(str(r[0]))
            
            self.finish()
    

    Здесь по очереди вызываются методы save и find_one, с получением результата.

    Для следующего примера я сделал костыль-обертку, что-б использование mongodb было похоже на классическое ( db.user.save({ 'login':'tester' }), db.user.find_one({}) )
    class MainHandler(tornado.web.RequestHandler):
        @property
        def db(self):
            if not hasattr(self, '_db'):
                self._db = tornadomongo.mongo_client(pool_id='mydb', host='127.0.0.1', port=27017, maxcached=10, maxconnections=50, dbname='test')
            return self._db
    
        @web.asynchronous
        @gen.engine
        def get(self):
            # save
            r = yield self.db.user.save({ 'login':'tester' })
            
            # find
            try:
                r = yield self.db.user.find_one({})
                self.write(str(r))
            except tornadomongo.MongoError as e:
                self.write('error: '+str(e))
            
            self.finish()
    

    В отличие от предыдущего примера, в этом не нужно каждый раз проверять вернувшееся значение error, вместо него сработает исключение. (Хотя опять же нужно делать проверку на исключение :)

    Кто захочет попробовать, модуль tornadomongo лежит тут: hg clone bitbucket.org/lega911/tornadomongo
    В ней пришлось сделать «грязный inject» в модуль tornado, для возможности создания исключения, что позволяет этот модуль использовать только для экспериментов.

    Я уже перевел некоторые модули одного проекта на tornado.gen, в итоге количество кода уменьшилось, читаемость кода повысилась (того же функционала).

    PS: На момент написания статьи, модуль не был описан в документации, сейчас выложили: www.tornadoweb.org/documentation/gen.html
    • +19
    • 3,2k
    • 5
    Поделиться публикацией
    Похожие публикации
    Ой, у вас баннер убежал!

    Ну. И что?
    Реклама
    Комментарии 5
      0
      Все замечательно кроме бесполезности этого решения для большинства случаев. Оверхед от инлайн колбэка с yeld-ами на порядок, если не полтора увеличивает время отработки.

      Нужно очень постараться, чтобы селект в монго занял столько времени, что потребует асинхронной обертки, например какие-то сложные массовые операции, большие инсерты и т.д. В противном случае больше смысла оборачивать в асинхронку запись в лог (все таки I/O операция :)), чем запрос к mongo.
        0
        > Все замечательно кроме бесполезности этого решения для большинства случаев. Оверхед от инлайн колбэка с yeld-ами на порядок, если не полтора увеличивает время отработки.

        Зато улучшается читаемость кода и скорость разработки, ибо сейчас все сидели бы в asm или c++.

        > Нужно очень постараться, чтобы селект в монго занял столько времени, что потребует асинхронной обертки, например какие-то сложные массовые операции, большие инсерты и т.д.

        Вы сами ответили на свое предложение, да и глупо использовать синхронные методы в асинхронном фреймворке.
          0
          >Зато улучшается читаемость кода и скорость разработки, ибо сейчас все сидели бы в asm или c++.
          >да и глупо использовать синхронные методы в асинхронном фреймворке.

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

          У нас всегда есть некоторые порции операций ввода-вывода, умещающиеся в один цикл, монго реагирует очень быстро, нет смысла создавать лишний оверхед.

          Обращение к монго с архитектурной точки зрения не слишком отличается от обращения к файлу или какому ни будь kvstore, нет смысла городить обертки. Файловый ввод\вывод, вообще лочит работу воркера и… все на это забили, потому что это не важно, никому и в голову не придет делать, например, асинхронный логгер, пишущий в файл.

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

          Основная цель существования торнадо — сделать асинхронный сетевой IO. Тупо работу с сокетами.

          Надеюсь, что прогнав пару тестов с колбеками и без вы поймете то, о чем я говорю. Если не получится, то почитайте хотя бы то, что пишет сам разработчик торнадо по поводу своего опыта написания асинхронной обертки к MySQL.
            0
            > Инлайн колбэки дорогие
            > Надеюсь, что прогнав пару тестов с колбеками и без вы поймете то, о чем я говорю

            Прогнал тесты: pastebin.com/6SRADFpr
            На моем буке вызов inline callback занимает 0,000007 сек — так что смешно говорить что они дорогие.

            Про селект в mongo: в проекте который сейчас разрабатываю база около 2Гб, выборка 20 элементов занимает 4..6мс, если уж экономить то мили-секундами, а не микро-секундами.
            Сделал 1000 (в 100 потоков) запросов к проекту: через asyncmongo 9.5 сек, через pymongo 11.5 сек, разница в 2сек.

            Я согласен что в данном случае время затрачиваемое на запрос к монго «не существенно», но все же.
            У вас есть цифры того что pymongo лучше чем asyncmongo?
            0
            P.S. Я все это пишу к тому, что пример неудачный, если бы мы делали запрос к стороннему API по http, где ждать явно больше 3-4 ms (столько мы пожертвовали на красоту), то вопросов бы не возникло.

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

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