Л9: Взаимодействие веб-приложений
DESCRIPTION
Л9: Взаимодействие веб-приложенийTRANSCRIPT
Инфраструктура веб-приложений
Сергей Лихобабин
Техносфера. 2014
Запуск фоновых процессов. Cron.
Очереди сообщений и задач. RabbitMQ, Celery.
Real-time сообщения.
Полнотекстовый поиск. Sphinx.
Использование NoSQL хранилищ. Memcached,Redis, Tarantool.
Типичные задачиМассовый импорт данных
Массовый экспорт данных
Расчет рейтингов (например, лучшие вопросы)
Очистка устаревших данных
И вообще все периодические работы
Статистика, статистика, статистика
Cron – стандартный планировщик задач в linux
Задача = команда оболочки (bash) в linux
Проблемы
Скрипт может работать долго
Скрипт может в принципе не успеватьобработать нужный объем данных
Скрипт может потреблять много ресурсов
В каждом скрипте нужно настраиватьокружение: соединение с базой, пути к файлами т.п.
Не чаще раза в минуту
Решения
Использовать блокировку файлов (flock)
Распараллелить обработку. Запускатьнесколько процессов
Запускать скрипты на отдельной машине.Использовать scribe для перемещения логов
Использовать management command (в Django)
Cron плохо масштабируется, дает задержки
Очереди сообщений
Архитектура очередей
Преимущества очередейАсинхронная связь и буферизация
Масштабируемость
Балансировка и эластичность нагрузки
Гарантированная доставка
Пример (отправкасобытий)
import pika params = pika.ConnectionParameters(host='localhost')connection = pika.BlockingConnection(params) channel = connection.channel() channel.queue_declare(queue='hello') channel.basic_publish( exchange='', routing_key='hello', body='{ "id": 10, "msg": "hello world!"}')connection.close()
Пример (получениесобытий)
import pika params = pika.ConnectionParameters(host='localhost')connection = pika.BlockingConnection()channel = connection.channel()channel.queue_declare(queue='hello')
def callback(ch, method, properties, body): print " [x] Received %r" % (body,) ch.basic_ack(delivery_tag = method.delivery_tag)
channel.basic_consume(callback, queue='hello')channel.start_consuming()
Что еще нужно дляхорошей очереди
Протокол передачи параметров задач
Получение результатов
Слежение за worker – процессами
Нужное количество
Ограничение нагрузки
Борьба с падениями
Борьба с утечками памяти
Удобный мониторинг
CeleryРаботает поверх RabbitMQ, MongoDB, Redis, DB
Задача = функция python
Удобный интерфейс запуска задач
Замена Cron
Архитектура Celery
Описание задач Celery from celery import Celery app = Celery('tasks', broker='amqp://guest@localhost//'
def _poor_fib(x): ## глупая реализация фибоначи
@app.task def fib(x): print _poor_fib(x) @app.periodic_task(run_every=timedelta(seconds=60)) def cronlike(): print "Look at me! I'm a cron“
Вызов задач Celery ## views.py – код вашего приложения
from tasks import fib
fib.delay(10) result = fib.delay(20) print result.get(timeout=10)
Асинхронная доставкасообщений
Polling (каждые 10 секунд…)
LongPolling (Comet)
ServerPush
WebSockets
Nginx mod_push location /publish/ { set $push_channel_id $arg_cid; # id канала push_store_messages off; # не храним сообщения push_publisher; # включаем отправку allow 127.0.0.1; deny all;}
location /listen/ { push_subscriber_concurrency broadcast; # всем! set $push_channel_id $arg_cid; # id канала default_type application/json; # MIME тип сообщения push_subscriber; # включаем доставку}
Comet client-side function comet (id, onmessage) { $.get('http://host/listen/', { cid: id }) .done(function(data) { onmessage(data); comet(id, onmessage); }) .fail(function(data) { comet(id, onmessage); }); }
comet(123, function(data) { console.log(data); });
Отправка сообщений import urllib2 request = urllib2.Request( 'http://localhost/publish/', '{"hello": 1}', {})
# Может занять много времени response = urllib2.urlopen(request) print response
Полнотекстовый поиск
Особенности SphinxЛегкая интеграция с базами и приложениями
Batch и real-time индексы
Гибкий язык поисковых запросов
SQL – like syntax
Хорошие функции релевантности
Масштабирование
In-memory storage
Memcached import memcache mc = memcache.Client(['127.0.0.1:11211'], debug=0) mc.set("some_key", "Some value") value = mc.get("some_key")
mc.set("another_key", 3) mc.delete("another_key")
mc.set("key", "1") mc.incr("key") mc.decr("key")
Memcached import memcache mc = memcache.Client(['127.0.0.1:11211'], debug=0)
def heavy_func(arg1): result = db.get('complex sql') ** 100500; result = template(result) # some very heavy computation :) return result
def fast_func(arg1): result = mc.get(str(arg1)) if result is None: result = heavy_func(arg1) mc.set(str(arg1), result) return result
Полезные ссылкиhttp://www.rabbitmq.com/getstarted.html -RabbitMQ
http://docs.celeryproject.org/en/latest/getting-started/first-steps-with-celery.html - Celery
http://sphinxsearch.com/docs/2.0.9/ - Sphinx