Upload
dotnetconf
View
187
Download
4
Embed Size (px)
Citation preview
Делаем очередь поверх
Кассандры
Максим Кирюшкин
telecan.ru
12-я конференция .NET разработчиков
15 мая 2016
dotnetconf.ru
2
Постановка задачи
LPWAN
low-power wide-area network
3
Постановка задачи
• Множество источников сообщений
• Многоступенчатая обработка в реальном
времени
• Длительное хранение
• Выдача данных по запросу
• Подписка сторонних клиентов на события
• Высокая надёжность
4
Варианты решения
Очередь:
• RabbitMq
• ActiveMQ Apollo
• Kafka
Хранение:
• SQL
• NoSQL
5
Зачем искать альтернативы?
• Очереди удаляют сообщения после
обработки
• Гибкие правила обработки – сложно
• Повторная обработка => повторное
добавление в очередь
• Требуется очередь + хранение – иметь
две системы накладно
• Лишние операции копирования
6
Несколько слов про Кассандру
RowKey: { SuperColumnKey: { ColumKey: Value } } Децентрализованная база (модель BigTable) с настраиваемой целостностью для чтения и записи.
7
Общая идея очереди в Кассандре
• Wide rows
• Обработка в один или несколько потоков
• Помечаем обрабатываемые сообщения
• Результат в той же или отдельной таблице
8
Очереди в Кассандре – антипаттерн
• Cassandra создана для хранения
• Выборка с условием не предусмотрена
• Удаление – сложная задача для
распределённого хранилища
• Нет системы подписки на события
(временно)
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';
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
11
Общая идея очереди в Кассандре
• Wide rows
• Обработка в один или несколько потоков
• Помечаем обрабатываемые сообщения
• Результат в той же или отдельной таблице
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);
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
кэш значений переменных
14
Наполнение с переменной нагрузкой
curS := ТекущаяСекундаДня( ) или 0
nextS := СекундаПерехода( )
ЦИКЛ добавления сообщений
msg := СформироватьСообщение( )
ЕСЛИ сработал таймер ТОГДА
nextS := СекундаПерехода( )
ЕСЛИ ТекущееВремя( ) >= nextS ТОГДА
curS := nextS
ЗаписатьСообщение(msg, curS)
15
Наполнение с переменной нагрузкой
ЦИКЛ проверки
curBlock := НаполняемыйБлок( )
cnt := КоличествоЗаписей(curBlock)
ЕСЛИ cnt > N ТОГДА
nextS := ТекущееВремя( ) + K
ЗадатьСекундуПерехода(nextS)
УснутьНа(Z) // Z <= K
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;
17
Наполнение с переменной нагрузкой
18
Обработка сообщений
Однопоточная:
• Любой язык
выборка –> обработка –> сохранение
Многопоточная:
• Hadoop (MapReduce)
• Apache Spark
• Другое решение
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);
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
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.
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);
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;
24
Многопоточная обработка
Hadoop, Apache Spark:
• Отлично подходят для Big Data Analytics
• Вся обработка через запуск обработчика на одно или группу сообщений
• Возврат в очередь, смена последовательности, отложенная обработка – затруднены
• Много копирований в процессе разделения и передачи сообщений
25
Многопоточная обработка
• Простой вариант – сколько угодно потоков, читаем несколько записей вперёд, ставим флаг обработки, обрабатываем, ставим флаг готовности
• Сложный вариант – потоки договариваются между собой, кто какие записи берёт; разбивка по миллисекундам
• Сложный вариант улучшенный – очередь изначально разбивается на уровне ключа, потоки оповещают друг друга о вхождении в работу
26
Оповещение о событиях
• Триггеры
• CDC (Change Data Capture)
• Внешний источник оповещений
CREATE TRIGGER IF NOT EXISTS trigger_name ON table_name USING 'java_class';
27
Когда Кассандра не подходит
• Низкая нагрузка и малое количество узлов
системы
• Сохранение сообщений не требуется
• Необходима гибкая подписка и система
оповещения
• Привычность решения важнее
производительности и отказоустойчивости
28
Когда решение имеет смысл
• Очень высокая нагрузка
• Децентрализованная распределённая
среда
• Сообщения сохраняются после обработки
• Необходима повторная обработка
сообщений
• Сложная зависимая обработка (например,
цепочки сообщений)
29
Спасибо за внимание
Максим Кирюшкин
telecan.ru