29
Делаем очередь поверх Кассандры Максим Кирюшкин telecan.ru 12-я конференция .NET разработчиков 15 мая 2016 dotnetconf.ru

Делаем очередь поверх Кассандры

Embed Size (px)

Citation preview

Page 1: Делаем очередь поверх Кассандры

Делаем очередь поверх

Кассандры

Максим Кирюшкин

telecan.ru

12-я конференция .NET разработчиков

15 мая 2016

dotnetconf.ru

Page 2: Делаем очередь поверх Кассандры

2

Постановка задачи

LPWAN

low-power wide-area network

Page 3: Делаем очередь поверх Кассандры

3

Постановка задачи

• Множество источников сообщений

• Многоступенчатая обработка в реальном

времени

• Длительное хранение

• Выдача данных по запросу

• Подписка сторонних клиентов на события

• Высокая надёжность

Page 4: Делаем очередь поверх Кассандры

4

Варианты решения

Очередь:

• RabbitMq

• ActiveMQ Apollo

• Kafka

Хранение:

• SQL

• NoSQL

Page 5: Делаем очередь поверх Кассандры

5

Зачем искать альтернативы?

• Очереди удаляют сообщения после

обработки

• Гибкие правила обработки – сложно

• Повторная обработка => повторное

добавление в очередь

• Требуется очередь + хранение – иметь

две системы накладно

• Лишние операции копирования

Page 6: Делаем очередь поверх Кассандры

6

Несколько слов про Кассандру

RowKey: { SuperColumnKey: { ColumKey: Value } } Децентрализованная база (модель BigTable) с настраиваемой целостностью для чтения и записи.

Page 7: Делаем очередь поверх Кассандры

7

Общая идея очереди в Кассандре

• Wide rows

• Обработка в один или несколько потоков

• Помечаем обрабатываемые сообщения

• Результат в той же или отдельной таблице

Page 8: Делаем очередь поверх Кассандры

8

Очереди в Кассандре – антипаттерн

• Cassandra создана для хранения

• Выборка с условием не предусмотрена

• Удаление – сложная задача для

распределённого хранилища

• Нет системы подписки на события

(временно)

Page 9: Делаем очередь поверх Кассандры

9

Решение проблемы с удалением

Использовать отложенное удаление (TTL)

INSERT INTO tab (rkey, clkey, val) VALUES ('rawhash', 'cluster', 42) USING TTL 86400;

UPDATE tab USING TTL 86400 SET val=42 WHERE rkey='some' AND clkey='any';

Page 10: Делаем очередь поверх Кассандры

10

Полезная вещь: транзакции

INSERT INTO rawdata (row_date, moment, handled, message) VALUES ('2016-05-15', '2016-05-15 11:23:00', 0, 'message') IF NOT EXISTS;

UPDATE rawdata SET handled=2 WHERE row_date='2016-05-15' AND moment='2016-05-15 11:23:00' IF handled=1;

[applied] true [applied] | raw_date | moment | handled | message false | 2016-05-15 | '2016-05-15 11:23:00' | 0 | message

Page 11: Делаем очередь поверх Кассандры

11

Общая идея очереди в Кассандре

• Wide rows

• Обработка в один или несколько потоков

• Помечаем обрабатываемые сообщения

• Результат в той же или отдельной таблице

Page 12: Делаем очередь поверх Кассандры

12

Наполнение с переменной нагрузкой

CREATE TABLE IF NOT EXISTS rawdata ( row_date date, -- дата, определяющая строку, yyyy-mm-dd moment timeUUID, -- момент поступления в систему, идентификатор handled tinyint, -- 0 - unhandled, 1 - handling, 2 - handled, 3... – error message blob, -- данные пакета PRIMARY KEY (row_date, moment) ) WITH CLUSTERING ORDER BY (moment ASC);

Page 13: Делаем очередь поверх Кассандры

13

Наполнение с переменной нагрузкой CREATE TABLE IF NOT EXISTS rawdata ( row_date date, -- дата, определяющая строку, yyyy-mm-dd row_sec int, -- начальная секунда строки, [0..86400) moment timeUUID, -- момент поступления в систему, идентификатор handled tinyint, -- 0 - unhandled, 1 - handling, 2 - handled, 3... - error message blob, -- данные пакета PRIMARY KEY ((row_date, row_sec), moment) ) WITH CLUSTERING ORDER BY (moment ASC);

CREATE TABLE IF NOT EXISTS rawdata_meta_rows ( row_date date, -- дата строки row_sec int, -- начальная секунда строки, [0..86400) PRIMARY KEY ((row_date), row_sec) ) WITH CLUSTERING ORDER BY (row_sec DESC);

CREATE TABLE IF NOT EXISTS rawdata_meta ( mkey text, -- варианты: csec (current second), nsec (next second) msubkey text, -- ключ ячейки в строке (csec: yyyy-mm-dd) mval text, -- значение PRIMARY KEY ((mkey), msubkey) );

хранит все ключи партиций (строк) из rawdata

кэш значений переменных

Page 14: Делаем очередь поверх Кассандры

14

Наполнение с переменной нагрузкой

curS := ТекущаяСекундаДня( ) или 0

nextS := СекундаПерехода( )

ЦИКЛ добавления сообщений

msg := СформироватьСообщение( )

ЕСЛИ сработал таймер ТОГДА

nextS := СекундаПерехода( )

ЕСЛИ ТекущееВремя( ) >= nextS ТОГДА

curS := nextS

ЗаписатьСообщение(msg, curS)

Page 15: Делаем очередь поверх Кассандры

15

Наполнение с переменной нагрузкой

ЦИКЛ проверки

curBlock := НаполняемыйБлок( )

cnt := КоличествоЗаписей(curBlock)

ЕСЛИ cnt > N ТОГДА

nextS := ТекущееВремя( ) + K

ЗадатьСекундуПерехода(nextS)

УснутьНа(Z) // Z <= K

Page 16: Делаем очередь поверх Кассандры

16

Наполнение с переменной нагрузкой

Если не был задан переход nextS

Если был задан переход и посчитали новый

INSERT INTO rawdata_meta (mkey, msubkey, mval) VALUES ('nsec', '2016-05-15', nextS) IF NOT EXISTS USING TTL 86400;

UPDATE rawdata_meta USING TTL 86400 SET mval=nextS WHERE mkey='nsec' AND msubkey='2016-05-15‘ IF mval=oldNextS;

Page 17: Делаем очередь поверх Кассандры

17

Наполнение с переменной нагрузкой

Page 18: Делаем очередь поверх Кассандры

18

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

Однопоточная:

• Любой язык

выборка –> обработка –> сохранение

Многопоточная:

• Hadoop (MapReduce)

• Apache Spark

• Другое решение

Page 19: Делаем очередь поверх Кассандры

19

Однопоточная обработка: выборка

1. Определение ключа партиции и кластера по rawdata_meta_rows

2. Выборка из конкретной строки с условием и сортировкой

3. Пагинация (LIMIT, OFFSET)

CREATE TABLE IF NOT EXISTS rawdata ( row_date date, -- дата, определяющая строку, yyyy-mm-dd row_sec int, -- начальная секунда строки, [0..86400) moment timeUUID, -- момент поступления в систему, идентификатор handled tinyint, -- 0 - unhandled, 1 - handling, 2 - handled, 3... - error message blob, -- данные пакета PRIMARY KEY ((row_date, row_sec), moment) ) WITH CLUSTERING ORDER BY (moment ASC);

Page 20: Делаем очередь поверх Кассандры

20

Неочевидные проблемы выборки

Сложности прямых запросов:

• Сортировка доступна только по ключу

• Нет понятия OFFSET

• Нельзя просто взять и применить условия

SELECT * FROM rawdata WHERE row_date='2016-05-10‘ AND row_sec=0 AND handled=0;

No supported secondary index found for the non primary key columns restrictions

Page 21: Делаем очередь поверх Кассандры

21

Неочевидные проблемы выборки

Можно применить индексы, но индекс по

флагу не эффективен

Each value in the index becomes a single row in the index, resulting in a huge row for all the false values, for example. Indexing a multitude of indexed columns having foo = true and foo = false is not useful.

Page 22: Делаем очередь поверх Кассандры

22

Неочевидные проблемы выборки

Есть в Кассандре materialized views, но их

функционал пока слишком слаб: нет условий

выборки, сортировки и т.д.

CREATE MATERIALIZED VIEW rawdata_unhandled AS SELECT row_date, row_sec, handled, moment, message FROM rawdata WHERE row_date IS NOT NULL AND row_sec IS NOT NULL AND moment IS NOT NULL AND handled IS NOT NULL PRIMARY KEY ((row_date, row_sec), handled, moment);

Page 23: Делаем очередь поверх Кассандры

23

Решение проблем с выборкой

Внешнее приложение должно взять полный контроль на себя и точно знать, что запрашивать

SELECT row_date, row_sec, moment, handled FROM rawdata WHERE row_date='2016-05-15' AND row_sec=0 LIMIT 100;

row_date | row_sec | moment | handled ... 2016-05-15 | 0 | d6686343-1804-11e6-bebe-87c17418206b | 0

SELECT row_date, row_sec, moment, handled FROM rawdata WHERE row_date='2016-05-15' AND row_sec=0 AND moment > d6686343-1804-11e6-bebe-87c17418206b LIMIT 100;

Page 24: Делаем очередь поверх Кассандры

24

Многопоточная обработка

Hadoop, Apache Spark:

• Отлично подходят для Big Data Analytics

• Вся обработка через запуск обработчика на одно или группу сообщений

• Возврат в очередь, смена последовательности, отложенная обработка – затруднены

• Много копирований в процессе разделения и передачи сообщений

Page 25: Делаем очередь поверх Кассандры

25

Многопоточная обработка

• Простой вариант – сколько угодно потоков, читаем несколько записей вперёд, ставим флаг обработки, обрабатываем, ставим флаг готовности

• Сложный вариант – потоки договариваются между собой, кто какие записи берёт; разбивка по миллисекундам

• Сложный вариант улучшенный – очередь изначально разбивается на уровне ключа, потоки оповещают друг друга о вхождении в работу

Page 26: Делаем очередь поверх Кассандры

26

Оповещение о событиях

• Триггеры

• CDC (Change Data Capture)

• Внешний источник оповещений

CREATE TRIGGER IF NOT EXISTS trigger_name ON table_name USING 'java_class';

Page 27: Делаем очередь поверх Кассандры

27

Когда Кассандра не подходит

• Низкая нагрузка и малое количество узлов

системы

• Сохранение сообщений не требуется

• Необходима гибкая подписка и система

оповещения

• Привычность решения важнее

производительности и отказоустойчивости

Page 28: Делаем очередь поверх Кассандры

28

Когда решение имеет смысл

• Очень высокая нагрузка

• Децентрализованная распределённая

среда

• Сообщения сохраняются после обработки

• Необходима повторная обработка

сообщений

• Сложная зависимая обработка (например,

цепочки сообщений)

Page 29: Делаем очередь поверх Кассандры

29

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

Максим Кирюшкин

[email protected]

telecan.ru