Web осень 2013 лекция 9

Preview:

Citation preview

Дополнительные темы

Дмитрий Смаль

2

• Запуск фоновых процессов. Cron.

• Очереди сообщений и задач. RabbitMQ, Celery.

• Real-time сообщения. Comet.

• Полнотекстовый поиск. Sphinx.

• Использование NoSQL хранилищ. Memcached, Redis, Tarantool.

Мы рассмотрим

3

Типичные задачи для Web

Массовый импорт данных Массовый экспорт данных Расчет рейтингов (например, лучшие

вопросы) Очистка устаревших данных И вообще все периодические работы Статистика, статистика, статистика

4

Cron

Cron – стандартный планировщик задач в linux

Задача = команда оболочки (bash) в linux

5

Crontab

MAILTO=mialinx@gmail.comPATH=/bin:/usr/bin:/home/project/binSHELL=/bin/bash1 */3 * * * indexer --rotate --all1 1 * * * backup.sh 1 3 7 * * big_backup_all.sh1 * * * * calc_best.py* * * * * send_mail.py

Команда

Минута

ЧасДень

6

Проблемы

1. Скрипт может работать долго

2. Скрипт может в принципе не успевать обработать нужный объем данных

3. Скрипт может потреблять много ресурсов

4. В каждом скрипте нужно настраивать окружение: соединение с базой, пути к файлам и т.п.

5. Не чаще раза в минуту

7

Решения

1. Использовать блокировку файлов (flock)

2. Распараллелить обработку. Запускать несколько процессов

3. Запускать скрипты на отдельной машине. Использовать scribe для перемещения логов

4. Использовать management command (в Django), писать весь код в ./lib

Cron плохо масштабируется, дает задержки

8

Очереди сообщений

9

BuzzWords

10

Архитектура очередей

11

Преимущества очередей

1. Асинхронная связь и буферизация

2. Слабая связанность

3. Масштабируемость

4. Балансировка и эластичность нагрузки

5. Гарантированная доставка

12

Hello World (sender)

## ваш скрипт-отправитель, например views.py

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()

13

Hello World (consumer)

## consumer.py – ваш скрипт обработчик

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()

14

Exchanges

fanout – отправляет во все очередиdirect - по совпадению routing_keytopic – по совпадению (c шаблонами)headers – на основании аттрибутов

15

Что нужно еще ?

1. Протокол передачи параметров задач

2. Получение результатов

3. Слежение за worker – процессами Нужное количество Ограничение нагрузки Борьба с падениями Борьба с утечками памяти

Удобный мониторинг

16

Очередь задач - Celery

1. Работает поверх RabbitMQ, MongoDB, Redis, DB

2. Задача = функция python

3. Удобный интерфейс запуска задач

4. Замена Cron

17

Архитектура Celery

18

Задачи Celery

## tasks.py – файл с функциями-задачами

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“

19

Настройки Celery

## celeryconfig.py – файл с настройками celery

import tasks ## Важно! Загрузить задачи в celery

BROKER_URL = 'amqp://guest@localhost//‘

СELERY_RESULT_BACKEND = 'amqp'

CELERY_TASK_SERIALIZER = 'json'

CELERY_RESULT_SERIALIZER = 'json'

CELERY_ACCEPT_CONTENT=['json']

CELERY_TIMEZONE = 'Europe/Moscow'

CELERY_ENABLE_UTC = True

20

Запуск задания

## views.py – код вашего приложения

## Нам нужны обертки для выполнения задач

from tasks import fib

fib.delay(10)

result = fib.delay(20)

print result.get(timeout=10)

PROFIT!!!

21

Доставка сообщений пользователю

Как сообщить пользователю о событии ?

Polling (каждые 10 секунд…) LongPolling (Comet) ServerPush WebSockets

22

Отличия

23

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; # включаем доставку

}

24

Получение сообщений

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);

});

25

Отправка сообщений

import urllib2

request = urllib2.Request(

'http://localhost/publish/',

'{"hello": 1}',

{}

)

# Может занять много времени

response = urllib2.urlopen(request)

print response

26

Архитектура

Application

Nginx + mod_pushSender

task

Queue

27

Полнотекстовый поиск

28

Основы Sphinx

Document – единица информационного поиска, содержит id, поля (полнотекстовые), атрибуты (не полнотекстовые).

Index – множество документов, по которым идет поиск, можно считать «таблицей», физически набор файлов на диске.

Source – способ пополнения индекса

29

Варианты установки

Stand-alone server (API, SphinxQL)

MySQL storage engine (SphinxSE)

MySQL Sphinx

Application

SQL

Application

MySQL

Sphinx

SQL

SQL

API, SphinxQL

30

Пример конфигурации

searchd {

listen = 127.0.0.1:3334

log = /var/lib/sphinxsearch/sphinx.log

}

indexer {

mem_limit = 100M

max_xmlpipe2_field = 32M

}

index ask_question_idx {

source = ask_question_source

path = /var/lib/sphinxsearch/ask_idx

morphology = libstemmer_ru, libstemmer_en

html_strip = 1

}

31

Пример конфигурации (source)

source ask_question_source {

type = mysql

sql_host = localhost ## и остальные опции

sql_query_range = SELECT MIN(id), MAX(id) FROM questions

sql_range_step = 1000

sql_query = SELECT id, title, text, rating FROM questions WHERE id>=$start AND id<=$end

sql_attr_uint = id

sql_field_string = title

sql_field_string = text

sql_attr_uint = rating

}

32

Использование API

use Sphinx::Search;

my $sph = Sphinx::Search->new();

$sph->SetMatchMode(SPH_MATCH_ALL);

$sph->SetRankingMode(SPH_RANK_PROXIMITY_BM25);

$sph->SetSortMode(SPH_SORT_RELEVANCE);

$sph->SetFieldWeights({ title => 100, text => 50 });

$sph->SetLimits(0, 10);

$results = $sph->Query("search terms");

my @ids = map { $_->{id} } @{ $results->{matches} };

print @ids;

33

Интеграция с Django

from djangosphinx import SphinxSearch

class Question(models.Model):

title = models.CharField(max_length=100)

text = models.CharField(max_length=1000)

rating = models.IntegerField()

search = SphinxSearch(

index='ask_question_idx',

weights={ 'title': 100, 'text': 50, }

)

def search_question(req):

results = Question.search.query(reg.GET['query'])\

.order_by('@weight')

...

34

Индексация

1. Запускать индексатор по cron/usr/bin/indexer --rotate --

all --config=/path/to/sphinx.conf

2. Иcпользовать real-time индексы

35

Ключевые особенности

1. Легкая интеграция с базами и приложениями

2. Batch и real-time индексы

3. Гибкий язык поисковых запросов

4. SQL – like syntax

5. Хорошие функции релевантности

6. Масштабирование

36

In-Memory storage

37

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")

38

Архитектура Memcached

39

Типичный пример использования

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 mc is None:

result = heavy_func(arg1) mc.set(str(arg1), result)

return result

Спасибо за внимание

Дмитрий Смаль, smal@corp.mail.ru