Торнадо широко используется в Zhihu.Когда вы используете Chrome для открытия веб-версии Zhihu и используете инструменты разработчика для тщательного наблюдения за запросами в Сети, вы обнаружите, что существует специальный запрос с кодом состояния 101, который использует браузер. Технология веб-сокетов устанавливает длительное соединение с внутренним сервером для получения уведомлений, активно отправляемых сервером. Бэкенд-сервер здесь использует сервер торнадо. Помимо предоставления услуг веб-сокетов, сервер Tornado также может предоставлять услуги длинных соединений, службы коротких ссылок HTTP, службы UDP и т. д. Сервер Tornado является открытым исходным кодом Facebook, а также широко используется в бэкэнде Palm Reader.
Такая мощная структура Tornado, как ее использовать, эта статья поможет читателю шаг за шагом изучить основы использования tornado в качестве веб-сервера.
Hello, World
import tornado.ioloop
import tornado.web
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write("Hello, world")
def make_app():
return tornado.web.Application([
(r"/", MainHandler),
])
if __name__ == "__main__":
app = make_app()
app.listen(8888)
tornado.ioloop.IOLoop.current().start()
Это официальный пример Hello, World, предоставленный, выполнитьpython hello.py
, откройте браузер и посетите http://localhost:8888/, чтобы увидеть нормальный вывод сервераHello, world
.
Обычный веб-сервер торнадо обычно состоит из четырех компонентов.
- Экземпляр ioloop, который является глобальным циклом событий торнадо, является ядром сервера.
tornado.ioloop.IOLoop.current()
Это экземпляр tornado ioloop по умолчанию. - Экземпляр приложения, который представляет завершенное серверное приложение, будет подключаться к порту сокета сервера для предоставления внешних служб. В экземпляре ioloop может быть несколько экземпляров приложения. В примере только один экземпляр. На самом деле разрешено несколько экземпляров приложения, но, как правило, несколько экземпляров приложения используются редко.
- Класс обработчика представляет бизнес-логику.Когда мы разрабатываем серверную часть, мы пишем кучу обработчиков для обслуживания клиентских запросов.
- Таблица маршрутизации, которая связывает указанное правило URL-адреса и обработчик для формирования таблицы сопоставления маршрутизации. Когда приходит запрос, запросите таблицу сопоставления маршрутизации в соответствии с запрошенным URL-адресом доступа, чтобы найти соответствующий бизнес-обработчик.
Связь между этими четырьмя компонентами заключается в том, что ioloop содержит несколько приложений (управляющих несколькими служебными портами), приложение содержит таблицу маршрутизации, а таблица маршрутизации содержит несколько обработчиков. ioloop — это ядро службы, отвечающее за получение запросов клиентов и ответ на них, управление работой бизнес-обработчика и выполнение внутренних запланированных задач сервера.
Когда приходит запрос, ioloop читает запрос и распаковывает его в объект HTTP-запроса, находит таблицу маршрутизации соответствующего приложения в сокете, запрашивает обработчик, подключенный к таблице маршрутизации, через URL-адрес объекта запроса, а затем выполняет обработчик. После выполнения метод-обработчик обычно возвращает объект, и ioloop отвечает за упаковку объекта в объект ответа http и его сериализацию для клиента.
Тот же экземпляр ioloop работает в однопоточной среде.
Факториал сервис
Ниже мы пишем обычный веб-сервер, который будет обслуживать факториал. Это поможет нам рассчитатьn!
значение . Сервер предоставит кэш факториалов, а вычисленные будут сохранены, и в следующий раз не нужно будет их пересчитывать. Преимущество использования Python заключается в том, что нам не нужно беспокоиться о переполнении результата вычисления факториала, а целые числа Python могут быть бесконечно большими.
# fact.py
import tornado.ioloop
import tornado.web
class FactorialService(object): # 定义一个阶乘服务对象
def __init__(self):
self.cache = {} # 用字典记录已经计算过的阶乘
def calc(self, n):
if n in self.cache: # 如果有直接返回
return self.cache[n]
s = 1
for i in range(1, n):
s *= i
self.cache[n] = s # 缓存起来
return s
class FactorialHandler(tornado.web.RequestHandler):
service = FactorialService() # new出阶乘服务对象
def get(self):
n = int(self.get_argument("n")) # 获取url的参数值
self.write(str(self.service.calc(n))) # 使用阶乘服务
def make_app():
return tornado.web.Application([
(r"/fact", FactorialHandler), # 注册路由
])
if __name__ == "__main__":
app = make_app()
app.listen(8888)
tornado.ioloop.IOLoop.current().start()
воплощать в жизньpython fact.py
, откройте браузер, введите http://localhost:8888/fact?n=50, вы увидите вывод браузера608281864034267560872252163321295376887552831379210240000000000
, если мы не укажем параметр n, доступhttp://localhost:8888/fact
, вы можете увидеть вывод браузера400: Bad Request
, говорит вам, что запрос неверный, то есть одним параметром меньше.
Использование Redis
В приведенном выше примере кэш хранится в локальной памяти.Если вы измените порт, а затем другой факториальный сервис, если вы получите доступ к нему через этот новый порт, его необходимо пересчитать для каждого n, потому что локальная память не может быть кросс- процесс и кросс-машина.
Итак, в этом примере мы будем использовать Redis для кэширования результатов вычислений, чтобы можно было полностью избежать двойных вычислений. Кроме того, мы будем возвращать не обычный текст, а возвращать json, а заодно добавим в ответ поле, чтобы сказать, что расчет идет из кеша или факт. Кроме того, мы предоставляем параметры по умолчанию, если клиент не предоставляет n, то по умолчанию n=1.
import json
import redis
import tornado.ioloop
import tornado.web
class FactorialService(object):
def __init__(self):
self.cache = redis.StrictRedis("localhost", 6379) # 缓存换成redis了
self.key = "factorials"
def calc(self, n):
s = self.cache.hget(self.key, str(n)) # 用hash结构保存计算结果
if s:
return int(s), True
s = 1
for i in range(1, n):
s *= i
self.cache.hset(self.key, str(n), str(s)) # 保存结果
return s, False
class FactorialHandler(tornado.web.RequestHandler):
service = FactorialService()
def get(self):
n = int(self.get_argument("n") or 1) # 参数默认值
fact, cached = self.service.calc(n)
result = {
"n": n,
"fact": fact,
"cached": cached
}
self.set_header("Content-Type", "application/json; charset=UTF-8")
self.write(json.dumps(result))
def make_app():
return tornado.web.Application([
(r"/fact", FactorialHandler),
])
if __name__ == "__main__":
app = make_app()
app.listen(8888)
tornado.ioloop.IOLoop.current().start()
Когда мы снова посетим http://localhost:8888/fact?n=50, мы увидим, что вывод браузера выглядит следующим образом.{"cached": false, "fact": 608281864034267560872252163321295376887552831379210240000000000, "n": 50}
, обновите снова, вывод браузера{"cached": true, "fact": 608281864034267560872252163321295376887552831379210240000000000, "n": 50}
, вы можете видеть, что кешированное поле запрограммировано со значением false, что указывает на то, что кеш действительно сохранил результат вычисления. Давайте перезапустим процесс,
Снова посетите это соединение и посмотрите на вывод браузера, вы можете обнаружить, что кешированный результат по-прежнему равен true. Указывает, что кэшированный результат больше не хранится в локальной памяти.
Сервис расчета Пи
Далее мы добавим еще один сервис для вычисления пи.Формул для вычисления пи много, и мы используем самую простую.
Мы предоставляем параметр n в сервисе как показатель точности pi. Чем больше n, тем точнее вычисление pi. Мы также кэшируем результаты вычислений на сервере Redis, чтобы избежать повторных вычислений.
# pi.py
import json
import math
import redis
import tornado.ioloop
import tornado.web
class FactorialService(object):
def __init__(self, cache):
self.cache = cache
self.key = "factorials"
def calc(self, n):
s = self.cache.hget(self.key, str(n))
if s:
return int(s), True
s = 1
for i in range(1, n):
s *= i
self.cache.hset(self.key, str(n), str(s))
return s, False
class PiService(object):
def __init__(self, cache):
self.cache = cache
self.key = "pis"
def calc(self, n):
s = self.cache.hget(self.key, str(n))
if s:
return float(s), True
s = 0.0
for i in range(n):
s += 1.0/(2*i+1)/(2*i+1)
s = math.sqrt(s*8)
self.cache.hset(self.key, str(n), str(s))
return s, False
class FactorialHandler(tornado.web.RequestHandler):
def initialize(self, factorial):
self.factorial = factorial
def get(self):
n = int(self.get_argument("n") or 1)
fact, cached = self.factorial.calc(n)
result = {
"n": n,
"fact": fact,
"cached": cached
}
self.set_header("Content-Type", "application/json; charset=UTF-8")
self.write(json.dumps(result))
class PiHandler(tornado.web.RequestHandler):
def initialize(self, pi):
self.pi = pi
def get(self):
n = int(self.get_argument("n") or 1)
pi, cached = self.pi.calc(n)
result = {
"n": n,
"pi": pi,
"cached": cached
}
self.set_header("Content-Type", "application/json; charset=UTF-8")
self.write(json.dumps(result))
def make_app():
cache = redis.StrictRedis("localhost", 6379)
factorial = FactorialService(cache)
pi = PiService(cache)
return tornado.web.Application([
(r"/fact", FactorialHandler, {"factorial": factorial}),
(r"/pi", PiHandler, {"pi": pi}),
])
if __name__ == "__main__":
app = make_app()
app.listen(8888)
tornado.ioloop.IOLoop.current().start()
Поскольку оба обработчика должны использовать Redis, мы извлекаем Redis отдельно и передаем его через параметры. Кроме того, Handler может передавать параметры через функцию инициализации.При регистрации маршрута может быть предоставлен словарь для передачи любых параметров.Ключ словаря должен соответствовать имени параметра. мы бегаемpython pi.py
, откройте браузер для доступаhttp://localhost:8888/pi?n=200
, вы можете увидеть вывод браузера{"cached": false, "pi": 3.1412743276, "n": 1000}
, это значение очень близко к пи.
Читайте более продвинутые статьи о Python, обратите внимание на паблик-аккаунт "Code Cave"