Python tornado что это
Введение в Tornado
Установка
python setup.py build
sudo python setup.py install
При этом важно чтобы были установлены пакеты python-dev, python-pycurl и python-simplejson. Будем считать что с установкой вы справились и продолжим дальше.
Структура приложения
Мы не будем делать что-то более сложное чем «хелловорлд», но ключевые моменты я постараюсь передать. Каждое приложение на Торнадо, как правило, состоит из, собственно, класса приложения и классов хэндлеров (обработчиков) запросов. Класс простого приложения содержит в себе привязку хэндлеров к путям приложения и ряд настроек. Мы же можем определить в нём, например, подключение к базе данных, чтобы не создавать его при обработке каждого запроса, и затем передать его в классы хендлеров. Для этого мы объявим некий базовый хендлер и будем наследовать от него остальные.
Так как приложение на Торнадо, это, по сути, сервер, он может запускаться с разнообразными параметрами, определёнными разработчиками. Для упрощения их получения используется модуль Торнадо options.
Класс приложения наследуется от класса tornado.web.Application. Он может выглядеть например так:
Здесь в словаре настроек присутствуют пути до каталогов с шаблонами и статикой (контент, который не изменяется, например графика или файлы таблиц стилей). Они необходиы для движка шаблонов фреймворка и для статической отдачи файлов. Параметр xsrf_cookies включает генерацию токенов для форм для защиты XSRF. cookie_secret, как несложно догадаться, строка, используемая для генерации cookies. Здесь мы покачто не воспользуемся этим функционалом, но в дальнейших постах я опишу эти вещи. Следующая после настроек строка инициализирует класс приложения с обозначенными выше параметрами. Дальше я объявил подключение к базе данных MongoDB, чуть ниже мы передадим его в хендлеры.
Хендлеры
Мы объявляем класс базового хэндлера и проксируем в нём инициализированное выше подключение к базе данных. Затем объявляем корневой контроллер, наследуемый от базового и выводим по запросу Id первой записи в коллекции. Вы же слышали о MongoDB, правда?
Теперь осталось написать функцию main, которая получит управление после запуска сервера и инициализирует его.
Мы парсим командную строку на наличие переданных параметров (например, номер порта, который равен 8888 по умолчанию), создаём сервер, обслуживающий наше приложение и стартуем его.
Вот полный код нашего приложения, благо он небольшой по объёму:
import tornado. httpserver
import tornado. ioloop
import tornado. options
import tornado. web
from tornado. options import define, options
MONGODB_HOST = «192.168.1.2»
MONGODB_PORT = 27017
Tornado: пример веб приложения
Плюсы tornado
В плюсах и минусах я буду приводить свое личное ощущение по сравнению с django.
1. Асинхронность.
Торнадо представляет из себя бесконечный цикл (ioloop), который постоянно проверяет наличие событий. Все это происходит в одном потоке. К примеру кто-то обратился по адресу /home/. Допустим в качестве обработчика этого события зарегистрирован HomeHandler (handler в торнадо примерно тоже самое, что view в django). ioloop вызывает HomeHandler и начинается выполняться его код. Что происходит в это время с ioloop? Он блокируется. Eсли обратиться другой пользователь, он будет ждать, пока обработается предыдущее событие.
Но все же, какое преимущество дает только один запущенный поток? Чем плохо на каждый запрос создать новый процесс или поток?
Вот тут на помощь приходят асинхронные решения. С ними можно использовать WebSocket’ы, или те же keep-alive http запросы, в асинхронных фреймворках они не съедят все ресурсы.
2. Работа с WebSocket’ами
Это отчасти следствие асинхронности, но лучше выделить это отдельным пунктом. Про вебсокеты можно почитать тут.
3. Менее слабая зависимость от ORM и html-шаблонизатора.
Минусы tornado
1. Меньшая популярность (чем у django).
Это означает, что многое из того, что есть готового для django, для tornado придется делать самому. Админку например.
2. Сложность кода.
Как ни крути, но асинхронный код писать сложнее, чем синхронный. Т.е. более высокий порог вхождения.
Проект типового приложения на tornado
Преставляет из себя ACL приложение, т.е. приложение с правами доступа. Права основаны на модели: у каждого юзера есть поле permissions:
В данном случае у пользователя есть права «только чтение» для модели model_name_1, «читать и записывать» для model_name_2, и «читать, записывать, удалять» для model_name_3.
Проект следует структуре django: есть приложения (apps), каждое из которых выполняет определеную функцию. Вот пример приложений:
Каждое приложение содержит модели, хендлеры, формы.
Tornado vs Aiohttp: путешествие в дебри асинхронных фреймворков
Привет! Я Дима, и я довольно давно и плотно сижу на Python. Сегодня хочу показать вам отличия двух асинхронных фреймворков — Tornado и Aiohttp. Расскажу историю выбора между фреймворками в нашем проекте, чем отличаются корутины в Tornado и в AsyncIO, покажу бенчмарки и дам немного полезных советов, как забраться в дебри фреймворков и успешно оттуда выбраться.
Как вы знаете, Авито — довольно большой сервис объявлений. У нас много данных и нагрузки, 35 миллионов пользователей каждый месяц и 45 миллионов активных объявлений ежедневно. Я работаю техлидом группы разработки рекомендаций. Моя команда пишет микросервисы, сейчас у нас работает их примерно двадцать. На все это сверху наливается нагрузка — вроде 5к RPS.
Выбор асинхронного фреймворка
Сначала расскажу, как мы оказались там, где находимся сейчас. В 2015 году нам нужно было выбрать асинхронный фреймворк, потому что знали:
Спустя три года мы поняли многое.
Во-первых, вышел Python 3.5 с механикой async/await. Мы разобрались, в чём же разница между yield и yield from и как Tornado согласуется с await (спойлер: не очень хорошо).
Во-вторых, мы столкнулись со странными проблемами с производительностью при наличии большого количества корутин в планировщике, даже когда CPU занят не полностью.
В-третьих, мы обнаружили, что при выполнении большого количества http-запросов к другим сервисам Tornado нужно специально дружить с асинхронным dns-резолвером, он не уважает таймауты на установление соединения и отправку запроса, которые мы указываем. И в целом оптимальный метод делать http-запросы в Tornado — это — curl, что довольно странно само по себе.
В своём докладе на PyCon Russia 2018 Андрей Светлов говорил: «Если вы хотите написать какое-то асинхронное веб-приложение, пожалуйста, просто пишите async, await. Event loop, наверное, вам вообще скоро будет не нужен. Не залезайте в дебри фреймворков, чтобы не запутаться. Не используйте низкоуровневые примитивы, и всё у вас будет нормально. ». За последние три года нам, к сожалению, пришлось достаточно часто залезать во внутренности Tornado, узнавать оттуда очень много всего интересного и видеть гигантские трейсбеки на 30-40 вызовов.
Yield vs yield from
Одной из самых больших проблем для понимания в асинхронном питоне является различие между yield from и yield.
Подробнее об этом написал Гвидо Ван Россум. Прилагаю перевод с небольшими сокращениями.
Меня спрашивали несколько раз, почему PEP 3156 настаивает на использовании yield-from вместо yield, что исключает возможность бэкпорта в Python 3.2 или даже 2.7.
(. )
всякий раз, когда вы хотите получить результат future, вы используете yield.
Это реализовано следующим образом. Функция, содержащая yield, является (очевидно) генератором, поэтому должен быть какой-то итерирующий код. Назовём его планировщиком. На самом деле планировщик не «итерирует» в классическом смысле (с for-loop); вместо этого он поддерживает две коллекции future.
вторая коллекция future, поддерживаемая планировщиком, состоит из future, которые всё ещё ожидают ввода-вывода. Они каким-то образом передаются на оболочку select/poll/и т.д. которая дает обратный вызов, когда дескриптор файла готов для ввода-вывода. Обратный вызов фактически выполняет операцию ввода-вывода, запрошенную future, задает результирующее значение future результату операции ввода-вывода и перемещает future в очередь выполнения.
Теперь мы дошли до самого интересного. Предположим, вы пишете сложный протокол. Внутри вашего протокола вы читаете байты из сокета с помощью метода recv(). Эти байты попадают в буфер. Метод recv() завернут в оболочку async, которая устанавливает ввод-вывод и возвращает future, который выполняется при завершении ввода-вывода, как я объяснял выше. Теперь предположим, что какая-то другая часть вашего кода хочет читать данные из буфера по одной строке за раз. Предположим, вы использовали метод readline(). Если размер буфера больше средней длины строки, ваш метод readline() может просто получать следующую строку из буфера без блокировки; но иногда буфер не содержит целой строки, и readline() в свою очередь вызывает recv () в сокете.
Вопрос: должен ли readline() возвращать future или нет? Было бы не очень хорошо, если бы он иногда возвращал байтовую строку, а иногда future, заставляя вызывающего выполнять проверку типа и условный yield. Поэтому ответ заключается в том, что readline() всегда должен возвращать future. Когда вызывается readline (), он проверяет буфер, и если он находит там, по крайней мере, целую строку, он создает future, задает результат future строки, взятой из буфера, и возвращает future. Если в буфере нет целой строки, он инициирует ввод-вывод и ожидает его, а когда ввод-вывод завершен, начинает заново.
Но теперь мы создаём множество future, которые не требуют блокировки ввода-вывода, но всё равно вынуждают обращение к планировщику, — поскольку readline() возвращает future, от вызывающего требуется yield, и что означает обращение к планировщику.
Планировщик может передать контроль прямо в корутину, если видит, что выведен future, который уже завершен, или может вернуть future в очередь выполнения. Последнее сильно замедлит работу (при условии, что существует более одной исполняемой корутины), так как не только требуется ожидание в конце очереди, но и локальность памяти (если она вообще существует), вероятно, также потеряна.
Чистый эффект всего этого заключается в том, что авторы корутины должны знать о yield future, и, следовательно, существует больший психологический барьер для реорганизации сложного кода в более читаемые корутины — намного сильнее существующего сопротивления, потому что вызовы функций в Python довольно медленны. И я помню из разговора с Glyph, что в типичной асинхронной структуре ввода-вывода важна скорость.
Теперь давайте сравним это с yield-from.
Возможно, вы слышали, что «yield from S» примерно эквивалентно «for i in S: yield i». В простейшем случае это так, но для понимания корутин этого недостаточно. Рассмотрим следующее (пока не думайте о async I/O):
Этот код напечатает две строки, содержащие «okay» и «42» (а затем выдаст необработанную StopIteration, которую вы можете подавить, добавив yield в конце gen1). Вы можете увидеть этот код в действии на pythontutor.com по ссылке.
Теперь рассмотрим следующее:
Это работает точно так же. Теперь подумайте. Как это работает? Здесь не может использоваться простое расширение yield-from в for-loop, поскольку в этом случае код выдавал бы None. (Попробуйте). Yield-from действует как «прозрачный канал» между driver и gen1. То есть, когда gen1 дает значение «okay», оно выходит из gen2, через yield-from, в драйвер, и когда драйвер посылает значение 42 обратно в gen2, это значение возвращается обратно через yield-from в gen1 снова (где становится результатом yield).
То же самое произошло бы, если бы driver выдал ошибку в генератор: ошибка проходит через yield-from во внутренний генератор, который обрабатывает её. Например:
Код выдаст «okay» и «bah», как и следующий код:
Во второй примере gen2 вызывает gen1 с использованием yield-from, поэтому он выглядит следующим образом:
Я использую математическое обозначение полуоткрытого интервала [. ), чтобы показать, что другой фрейм можно добавить справа, когда самый правый генератор использует yield-from для вызова другого генератора, в то время как левое окончание более или менее фиксированное. Левое окончание — это то, что видит драйвер (т.е. планировщик).
Теперь я готов вернуться к примеру readline(). Мы можем переписать readline() как генератор, который вызывает read(), другой генератор, используя yield-from; последний, в свою очередь, вызывает recv(), что выполняет фактический ввод-вывод из сокета. Слева у нас приложение, которое мы рассматриваем также как генератор, вызывающий readline(), снова используя yield-from. Схема такова:
Теперь генератор recv() задаёт I/O, связывает его с future и передает его планировщику, используя *yield* (не yield-from!). future проходит налево по обеим стрелкам yield-from в планировщик (расположен слева от «[»). Обратите внимание, что планировщик не знает, что он содержит стек генераторов; все, что он знает, это то, что он содержит самый левый генератор и что он только что выдал future. Когда ввод-вывод завершен, планировщик задает результат future и отправляет его обратно в генератор; результат перемещается вправо по обеим стрелкам yiled-from в генератор recv, который получает байты, которые он хотел прочитать из сокета в качестве результата yield.
Другими словами, планировщик фреймворка, основанного на yield-from, обрабатывает операции ввода-вывода точно так же, как планировщик фреймворка на основе yield, который я описал ранее. *Но:* ему не нужно беспокоиться об оптимизации, когда future уже выполнен, поскольку планировщик вообще не участвует в передаче контроля между readline() и read() или между read() и recv(), и обратно. Поэтому планировщик вообще не участвует, когда app() вызывает readline(), а readline() может удовлетворить запрос из буфера (не вызывая read()) — взаимодействие между app() и readline() в этом случае полностью обрабатывается интерпретатором байт-кода Python. Планировщик может быть проще, а количество future, создаваемых и управляемых планировщиком, меньше, потому что отсутствуют future, которые создаются и уничтожаются при каждом вызове корутины. Единственными future, которые по-прежнему необходимы, являются те, которые представляют собой фактический ввод-вывод, например, созданный recv().
Если дочитали до этого момента, вы заслуживаете награды. Я опустил много деталей реализации, но приведенная выше иллюстрация по сути верно отражает картину.
Еще одна вещь, которую я хотел бы отметить. *Можно* сделать так, чтобы часть кода использовала yield-from, а другая часть — yield. Но yield требует, чтобы в каждом звене цепи присутствовал future, а не просто корутина. Поскольку есть несколько преимуществ использования yield-from, я хочу, чтобы пользователю не нужно было помнить, когда использовать yield, а когда yield-from, — проще всегда использовать yield-from. Простое решение позволяет даже recv() использовать yield-from для передачи future ввода-вывода планировщику: метод __iter__ является фактически генератором, который выдает future.
И ещё кое-что. Какое значение возвращает yield-from? Оказывается, это возвращаемое значение *внешнего* генератора.
Таким образом, хотя стрелки связывают крайние левые и правые фреймы с целью *yielding*, они также передают обычные возвращаемые значения обычным образом, по одному фрейму стека за раз. Исключения перемещаются таким же способом; конечно, на каждом уровне требуется try/except, чтобы поймать их.
Оказывается, yield from — это практически то же самое, что и await.
Русские Блоги
Python изучения заметок-введение в Торнадо
каталог
введение
Вспомните, как разворачивается Django
Веб-приложение python, представленное Django, использует протокол wsgi для взаимодействия с сервером (размещенным на сервере), и эти серверы обычно основаны наМногопоточностьДа этоКаждый сервер веб-запросов будет иметь соответствующий поток для обработки с веб-приложением (например, Django)。
Рассмотрим два типа сценариев применения
Большое количество пользователей и высокий параллелизм
Такие как покупки шипов, покупки для Double Eleven, покупки билетов на весенний фестиваль
Большое количество постоянных соединений HTTP
Метод отправки и получения нескольких HTTP-запросов / ответов с использованием одного и того же TCP-соединения вместо открытия нового соединения для каждого нового запроса / ответа.
Для HTTP 1.0 вы можете добавить его в заголовок запросаConnection: Keep-Alive。
Для HTTP 1.1 все соединения являются постоянными по умолчанию.
Для этих двух сценариев многопоточные серверы часто трудно справиться.
Проблема C10K
Для проблемы высокого параллелизма, упомянутой ранее, мы обычно используем концепцию C10K для ее описания. C10K—Concurrently handling ten thousandСоединения, то есть 10000 подключений одновременно. Для одного сервера это вообще невозможно предоставить, а использование нескольких серверов для распределения означает большие затраты. Как решить проблему C10K?
Tornado
Tornado учел факторы производительности в начале проектирования и стремится решить проблему C10K, что делает его очень эффективнымРешения(Коллекция серверов и фреймворков).
1.1 Что такое Торнадо
Tornado, полное название Tornado Web Server, представляет собой веб-сервер и инфраструктуру веб-приложений, написанную на Python и используемую FriendFeed на своем собственном сайте FriendFeed. После приобретения Facebook платформа была открыта для публики в сентябре 2009 года как программное обеспечение с открытым исходным кодом.
Производительность: Торнадо обладает отличными характеристиками. Он пытается решить проблему C10k, которая заключается в обработке параллелизма, превышающего или равного 10000. Следующая таблица сравнивается с некоторыми другими веб-платформами и серверами:
Инфраструктура Tornado и сервер вместе образуют полноценную альтернативу WSGI. Использование сетевой структуры tornado или http-сервера tornod в одном контейнере WSGI имеет определенные ограничения: для максимизации производительности tornado рекомендуется использовать как сетевую среду tornado, так и HTTP-сервер.
1.2 Торнадо и Джанго
Django
Джанго ушелБольшой и полныйНаправление, ориентируясь наЭффективное развитиеОн наиболее известен своим полностью автоматизированным фоном управления: просто используйте ORM и делайте простые определения объектов, он может автоматически генерировать структуру базы данных и полнофункциональный фон управления.
Удобство, предоставляемое Django, также означает, что встроенный ORM Django тесно связан с другими модулями в платформе, и приложения должны использовать встроенный ORM Django, в противном случае вы не сможете пользоваться удобством, обеспечиваемым платформой на основе его ORM.
Tornado
Торнадо гуляетМеньше и хорошоНаправление, ориентируясь наПревосходная производительностьОн наиболее известен своим асинхронным неблокирующим дизайном.
2.1 Установка
Автоматическая установка
Ручная установка
Примечания по использованию платформы
Tornado should run on any Unix-like platform, although for the best performance and scalability only Linux (with epoll) and BSD (with kqueue) are recommended for production deployment (even though Mac OS X is derived from BSD and supports kqueue, its networking performance is generally poor so it is recommended only for development use). Tornado will also run on Windows, although this configuration is not officially supported and is recommended only for development use.
Торнадо должен бежать дальшеUnix-подобная платформаДля лучшей производительности и масштабируемости при развертывании в сети, рекомендуется толькоLinuxиBSD(Поскольку полное использование Linux-инструмента epoll и BSD-kqueue является причиной того, что Tornado не полагается на многопроцессорность / многопоточность для достижения высокой производительности).
Для Mac OS X, хотя он также является производным от BSD и поддерживает kqueue, его производительность в сети обычно слабая, поэтому он рекомендуется только для разработки.
Для Windows Tornado официально не предоставляет поддержку конфигурации, но может работать, но рекомендуется только для разработки.
2.2 Hello Itcast
По коду
Создайте новый файл hello.py со следующим кодом:
Запустите следующую команду, чтобы включить торнадо:
Откройте браузер и введите URL 127.0.0.1:8000 (или localhost: 8000), чтобы увидеть эффект:
Объяснение кода
модуль веб-фреймворка торнадо
RequestHandler
Application
Основным классом приложения веб-платформы Tornado является интерфейс к серверу, который содержит таблицу информации о маршрутизации.Первым первым параметром, который он получает, является список кортежей отображения информации о маршрутизации, его метод listen (port) используется для создания Экземпляр http-сервера и привязка к указанному порту (Примечание. В настоящее время сервер не включает прослушивание.)。
Базовый модуль io loop Tornado объединяет в себе epoll Linux и kqueue BSD, краеугольный камень высокой производительности торнадо. На примере Linux epoll принцип состоит в следующем:
IOLoop.current()
Возвращает экземпляр IOLoop текущего потока.
IOLoop.start()
Цикл ввода / вывода экземпляра IOLoop запущен, и прослушивание сервера включено.
Обобщите идеи написания веб-программы Tornado
2.3 httpserver
В предыдущем разделе мы говорили, что в методе tornado.web.Application.listen () (app.listen (8000) в примере кода) был создан пример сервера http, привязанный к данному порту. Можем ли мы сделать это сами? Для достижения этой части функции?
Теперь мы изменим предыдущий пример кода следующим образом:
В этой модифицированной версии мы представили модуль tornado.httpserver, который, как следует из названия, представляет собой реализацию tornado на сервере HTTP.
Мы создали экземпляр HTTP-сервера http_server, поскольку сервер обслуживает только что созданное веб-приложение и направляет полученные клиентские запросы соответствующему обработчику через таблицу сопоставления маршрутизации в веб-приложении, поэтому при создании объекта http_server Необходимо вывести объект приложения веб-приложения. http_server.listen (8000) привязывает сервер к порту 8000.
Фактически app.listen (8000) является сокращением для этого процесса в версии кода.
Один и несколько процессов
То, что мы только что достиглиЕдиный процесс, Вы можете просмотреть по команде:
Мы также можемЗапустить несколько процессов одновременноИзмените приведенный выше код следующим образом:
Метод http_server.bind (port) предназначен для привязки сервера к указанному порту.
Метод http_server.start (num_processes = 1) указывает, сколько процессов запущено.Значением по умолчанию параметра num_processes является значение 1, что означает, что по умолчанию запускается только один процесс; если num_processes имеет значение None или 0, создать дочерние процессы num_processes.
В этом примере мы используем http_server.start (0), и моя виртуальная машина устанавливает количество ядер ЦП на 2, и результаты демо-версии:
Http_server.listen (8000), который мы написали ранее, фактически эквивалентен:
объяснение
Метод app.listen () можно использовать только в однопроцессном режиме.
Для app.listen () с созданием вручную экземпляра HTTPServer
Для этих двух методов рекомендуется использовать последний метод, который заключается в создании экземпляра HTTPServer, поскольку он полезен для понимания целостности рабочего процесса веб-приложения торнадо и удобен для всех, чтобы помнить состав модуля и структуру программы, разработанную торнадо. Может быть сокращено.
2. О многопроцессорности
Хотя торнадо дает нам возможность запустить несколько процессов одновременно, но потому что:
Этот многопроцессный подход не рекомендуется, ноВручную запустить несколько процессов и связать разные порты。
2.4 options
В предыдущих примерах мы записали параметры служебного порта в программу, что очень негибко.
Торнадо предоставляет нам удобный инструмент,модуль-определение глобальных параметров tornado.options, хранение, преобразование。
tornado.options.define()
Метод, используемый для определения переменных параметров option. Определенные переменные можно получить и использовать в глобальном tornado.options.options. Передать параметры:
tornado.options.options
Объект глобальных параметров, все определенные переменные параметров будут использоваться в качестве атрибутов этого объекта.
tornado.options.parse_command_line()
Создайте новый opt.py, давайте посмотрим, как использовать его с кодом:
Выполните следующую команду, чтобы запустить программу:
Эффект заключается в следующем:
tornado.options.parse_config_file(path)
Параметры импорта из файла конфигурации. Формат параметров в файле конфигурации следующий:
Давайте рассмотрим код, чтобы понять, как его использовать. Создайте новый конфигурационный файл конфигурации. Обратите внимание, что строки и списки соответствуют синтаксису python:
Измените файл opt.py:
объяснение
Когда мы вызываем метод parse_command_line () или parse_config_file () в коде, tornado по умолчанию настраивает для нас стандартный модуль ведения журнала, то есть функция ведения журнала включена по умолчанию, а информация журнала выводится на стандартный вывод (экран).