В сети уже есть несколько обзоров производительности этого решения от Amazon. В этой статье я не преследовал цели проверить уже полученные результаты, меня интересовали некоторые особенности, не освещенные в других источниках, а именно:
- В документации написано, что Amazon старается сохранять порядок сообщений, насколько хорошо он сохраняется?
- Как быстро будет получено сообщение при использовании длинного опроса?
- Насколько пакетная обработка ускоряет процесс?
Постановка задачи
Наиболее поддерживаемая библиотека для AWS в erlang — erlcloud [1].Чтобы инициализировать библиотеку, просто вызовите методы start и configure, как указано на github. Мои сообщения будут содержать набор случайных символов, сгенерированных следующей функцией:
Для измерения скорости мы будем использовать известную функцию, использующую timer:tc, но с некоторыми изменениями:random_string(0) -> []; random_string(Length) -> [random_char() | random_string(Length-1)].
random_char() -> random:uniform(95) + 31 .
test_avg(M, F, A, R, N) when N > 0 ->
{Ret, L} = test_loop(M, F, A, R, N, []),
Length = length(L),
Min = lists:min(L),
Max = lists:max(L),
Med = lists:nth(round((Length / 2)), lists:sort(L)),
Avg = round(lists:foldl(fun(X, Sum) -> X + Sum end, 0, L) / Length),
io:format("Range: ~b - ~b mics~n"
"Median: ~b mics~n"
"Average: ~b mics~n",
[Min, Max, Med, Avg]),
Ret.
test_loop(_M, _F, _A, R, 0, List) ->
{R, List};
test_loop(M, F, A, R, N, List) ->
{T, Result} = timer:tc(M, F, [R|A]),
test_loop(M, F, A, Result, N - 1, [T|List]).
Изменения касаются вызова тестируемой функции, в этой версии я добавил аргумент R, который позволяет использовать значение, возвращаемое из предыдущего запуска, это необходимо для генерации номеров сообщений и сбора дополнительной информации относительно перетасовки при получении сообщения.
сообщение.
Таким образом, функция отправки сообщения с номером будет выглядеть так: send_random(N, Queue) ->
erlcloud_sqs:send_message(Queue, [N + 1 | random_string(6000 + random:uniform(6000))]),
N + 1 .
И ее звонок со сбором статистики: test_avg(ЭMODULE, send_random, [QueueName], 31, 20)
здесь 31 - номер первого сообщения, это число выбрано не случайно, дело в том, что эрланг не очень хорошо различает последовательности чисел и строки и в сообщении это будет символ номер 31, можно передавать и меньшие числа до SQS, но в этом случае получаются небольшие непрерывные диапазоны (#x9 | #xA | #xD | [#x20 до #xD7FF] | [#xE000 до #xFFFD] | [#x10000 до #x10FFFF], подробнее [2 ]), и если вы выйдете за пределы диапазона, вы получите исключение.
Таким образом, функция send_random формирует и отправляет сообщение в очередь с именем Queue, в начале которой стоит число, определяющее ее номер, функция возвращает номер следующего числа, которое затем используется следующим поколением функция.
Функция test_avg принимает QueueName, которое становится вторым аргументом функции send_random, первый аргумент — это количество и количество повторений.
Функция, которая будет получать сообщения и проверять их порядок, будет выглядеть так: checkorder(N, []) -> N;
checkorder(N, [H | T]) ->
[{body, [M | _]}|_] = H,
K = if M > N -> M;
true -> io:format("Wrong ~b less than ~b~n", [M, N]),
N
end,
checkorder(K, T).
receive_checkorder(LastN, Queue) -> [{messages, List} | _] = erlcloud_sqs:receive_message(Queue), remove_list(Queue, List), checkorder(LastN, List).
Удаление сообщений: remove_msg(_, []) -> wrong;
remove_msg(Q, [{receipt_handle, Handle} | _]) -> erlcloud_sqs:delete_message(Q, Handle);
remove_msg(Q, [_ | T]) -> remove_msg(Q, T).
remove_list(_, []) -> ok; remove_list(Q, [H | T]) -> remove_msg(Q, H), remove_list(Q, T).
список, переданный на удаление, содержит много ненужной информации (тело сообщения и т. д.), функция удаления находит хэндл_чека, который необходим для формирования запроса, или возвращает неверный результат, если хэндл_квитанции не найден
Перетасовка сообщений
Забегая вперед, могу сказать, что даже при небольшом количестве сообщений перемешивание оказалось достаточно значительным и возникла дополнительная задача: необходимо оценить степень перемешивания.К сожалению, хороших критериев найти не удалось и было решено отображать максимальное и среднее расхождение с правильным положением.
Зная размер такого окна, можно восстановить порядок сообщений при получении, что, естественно, ухудшает скорость обработки.
Чтобы вычислить такую разницу, достаточно изменить всего одну функцию проверки порядка сообщений: checkorder(N, []) -> N;
checkorder({N, Cnt, Sum, Max}, [H | T]) ->
[{body, [M | _]}|_] = H,
{N1, Cnt1, Sum1, Max1} = if M < N ->
{N, Cnt + 1, Sum + N - M, if Max < N - M -> N - M; true -> Max end };
true -> {M, Cnt, Sum, Max}
end,
checkorder({N1, Cnt1, Sum1, Max1}, T).
Вызов функции run series будет выглядеть так: {_, Cnt, Sum, Max} = test_avg(ЭMODULE, receive_checkorder, [QueueName], {0, 0, 0, 0}, Size)
Я получаю количество элементов, прибывших позже ожидаемого, сумму их расстояний от наибольшего полученного элемента и максимальное смещение.
Самое интересное для меня здесь максимальное смещение, остальные характеристики можно назвать спорными и они могут быть не очень хорошо рассчитаны (например, если один элемент считывается раньше, то будут учитываться все элементы, которые должны идти до него в данном случае перестановка).
К результатам:
Размер (шт.) | 20 | 50 | 100 | 150 | 200 | 250 | 300 | 400 | 500 | 600 | 700 | 800 | 900 | 1000 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Максимальное смещение (шт.) | 11 | 32 | 66 | 93 | 65 | 139 | 184 | 155 | 251 | 241 | 218 | 249 | 359 | 227 |
Среднее смещение (шт.) | 5.3 | 10.5 | 23.9 | 43 | 25.6 | 45.9 | 48.4 | 65.6 | 74.2 | 74.2 | 78.3 | 72.3 | 110.8 | 82.8 |
Результаты меня удивили, сообщения не просто перемешиваются, ограничений этому просто нет, то есть при увеличении количества сообщений нужно увеличивать размер просматриваемого окна.
То же самое в виде графика:
Длинный опрос
Как я уже писал, Amazon SQS не поддерживает подписки, для этого можно использовать Amazon SNS, но если требуются быстрые очереди с несколькими процессорами, это не подходит, чтобы не тянуть метод получения сообщений, Amazon реализовал Long Опрос, который позволяет зависать, ожидая сообщения до двадцати секунд, а так как SQS тарифицируется по количеству вызванных методов, то это должно значительно снизить затраты на очередь, но вот в чем проблема: для небольшого количества сообщений( согласно официальной документации), очередь может ничего не вернуть.Такое поведение критично для очередей, в которых нужно быстро реагировать на событие и, вообще говоря, если это происходит часто, то Long Polling не имеет особого смысла, поскольку становится эквивалентным периодическому опросу с временем ответа SQS. Для проверки создадим два процесса, один из которых будет отправлять сообщения в случайное время, а второй будет постоянно находиться в Long Polling, при этом моменты отправки и получения сообщений будут сохраняться для последующего сравнения.
Чтобы включить этот режим, в параметрах очереди установите Время ожидания приема сообщения = 20 секунд. send_sleep(L, Queue) ->
timer:sleep(random:uniform(10000)),
Call = erlang:now(),
erlcloud_sqs:send_message(Queue, random_string(6000 + random:uniform(6000))),
[Call | L].
эта функция переходит в режим сна на случайное количество миллисекунд, после чего запоминает момент и отправляет сообщение remember_moment(L, []) -> L;
remember_moment(L, [_ | _]) -> [erlang:now() | L].
receive_polling(L, Queue) -> [{messages, List} | _] = erlcloud_sqs:receive_message(Queue), remove_list(Queue, List), remember_moment(L, List).
эти две функции позволяют получать сообщения и запоминать моменты, в которые это произошло.
После одновременного выполнения этих функций с помощью spawn я получаю два списка, разница между которыми показывает время ответа на сообщение.
При этом не учитывается, что сообщения могут быть смешанными; в общем, это просто еще больше увеличит время реакции.
Давайте посмотрим, что произошло:
Интервал сна | 10000 | 7500 | 5000 | 2500 |
---|---|---|---|---|
Минимальное время (сек) | 0.27 | 0.28 | 0.27 | 0.66 |
Максимальное время (сек) | 10.25 | 7.8 | 5.36 | 5.53 |
Среднее время (сек) | 1.87 | 1.87 | 1.84 | 1.88 |
То есть: 10 секунд, 7,5 секунд. Остальные строки — минимальное, максимальное и среднее время ожидания получения сообщения.
То же самое в виде графика:
Среднее время во всех случаях было одинаковым; можно сказать, что между отправкой таких одиночных сообщений и их получением в среднем проходит две секунды.
Довольно долго.
В этом тесте выборка была достаточно маленькой, 20 сообщений, поэтому минимальные-максимальные значения — это скорее вопрос удачи, чем какая-то зависимость.
Пакетная отправка
Для начала проверим, насколько важен эффект «прогрева» очереди при отправке сообщений:Количество записей | 20 | 50 | 100 | 150 | 200 | 250 | 300 | 400 | 500 | 600 | 700 | 800 | 900 | 1000 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Минимальное время (сек) | 0.1 | 0.1 | 0.1 | 0.09 | 0.09 | 0.09 | 0.09 | 0.1 | 0.09 | 0.1 | 0.1 | 0.09 | 0.09 | 0.09 |
Максимальное время (сек) | 0.19 | 0.37 | 0.41 | 0.41 | 0.37 | 0.38 | 0.37 | 0.43 | 0.39 | 0.66 | 0.74 | 0.48 | 0.53 | 0.77 |
Среднее время (сек) | 0.12 | 0.12 | 0.12 | 0.12 | 0.12 | 0.12 | 0.12 | 0.12 | 0.12 | 0.12 | 0.12 | 0.12 | 0.12 | 0.12 |
можно сказать, что никакого прогрева не наблюдается, то есть очередь ведет себя примерно одинаково для этих объемов данных, только максимум почему-то увеличивается, а среднее и минимальное остаются на своих местах.
То же самое для чтения с удалением
Количество записей | 20 | 50 | 100 | 150 | 200 | 250 | 300 | 400 | 500 | 600 | 700 | 800 | 900 | 1000 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Минимальное время (сек) | 0.001 | 0.14 | 0 | 0.135 | 0 | 0.135 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
Максимальное время (сек) | 0.72 | 0.47 | 0.65 | 0.65 | 0.69 | 0.51 | 0.75 | 0.75 | 0.76 | 0.73 | 0.82 | 0.79 | 0.74 | 0.91 |
Среднее время (сек) | 0.23 | 0.21 | 0.21 | 0.21 | 0.21 | 0.21 | 0.21 | 0.21 | 0.21 | 0.2 | 0.2 | 0.2 | 0.2 | 0.21 |
Насыщения здесь тоже нет, среднее значение в районе 200мс.
Иногда чтение было мгновенным (быстрее 1мс), но это означает, что сообщение не было получено, по документации SQS-серверы это умеют, нужно просто запросить сообщение еще раз.
Перейдем непосредственно к блочному и многопоточному тестированию.
К сожалению, библиотека erlcloud не содержит функций для пакетной отправки сообщений, но такие функции несложно реализовать на основе существующих; в функции отправки сообщения нужно изменить запрос на следующий: Doc = sqs_xml_request(Config, QueueName, "SendMessageBatch",
encode_message_list(Messages, 1)),
и добавьте функцию генерации запроса: encode_message_list([], _) -> [];
encode_message_list([H | T], N) ->
MesssageId = string:concat("SendMessageBatchRequestEntry.", integer_to_list(N)),
[{string:concat(MesssageId, ".
Id"), integer_to_list(N)}, {string:concat(MesssageId, ".
MessageBody"), H} | encode_message_list(T, N + 1)].
в библиотеке вам также следует исправить версию API, например, на 2011-10-01, иначе Amazon в ответ на ваши запросы вернет Bad request.
Функции тестирования аналогичны тем, которые используются в других тестах: gen_messages(0) -> [];
gen_messages(N) -> [random_string(5000 + random:uniform(1000)) | gen_messages(N - 1)].
send_batch(N, Queue) -> erlang:display(erlcloud_sqs:send_message_batch(Queue, gen_messages(10))), N + 1 .
Здесь нам просто нужно было изменить длину сообщений, чтобы весь пакет умещался в 64кб, иначе будет сгенерировано исключение.
Были получены следующие данные записи:
Количество потоков | 0 | 1 | 2 | 4 | 5 | 10 | 20 | 50 | 100 |
---|---|---|---|---|---|---|---|---|---|
Максимальная задержка (сек) | 0.452 | 0.761 | 0.858 | 1.464 | 1.698 | 3.14 | 5.272 | 11.793 | 20.215 |
Средняя задержка (сек) | 0.118 | 0.48 | 0.436 | 0.652 | 0.784 | 1.524 | 3.178 | 9.1 | 19.889 |
Время отправки сообщения (сек) | 0.118 | 0.048 | 0.022 | 0.017 | 0.016 | 0.016 | 0.017 | 0.019 | 0.02 |
Количество потоков | 0 | 1 | 2 | 4 | 5 | 10 | 20 | 50 | 100 |
---|---|---|---|---|---|---|---|---|---|
Максимальная задержка (сек) | 0.762 | 2.998 | 2.511 | 2.4 | 2.606 | 2.751 | 4.944 | 11.653 | 18.517 |
Средняя задержка (сек) | 0.205 | 1.256 | 1.528 | 1.566 | 1.532 | 1.87 | 3.377 | 7.823 | 17.786 |
Время отправки сообщения (сек) | 0.205 | 0.126 | 0.077 | 0.04 | 0.031 | 0.02 | 0.019 | 0.017 | 0.019 |
Синий цвет пишет, красный читает. Из этих данных можно сделать вывод, что максимальная пропускная способность достигается при записи около 10 потоков, а при чтении — около 50; при дальнейшем увеличении количества потоков количество сообщений, отправляемых в единицу времени, не увеличивается.
Выводы
Получается, что Amazon SQS существенно меняет порядок сообщений, имеет не очень хорошее время ответа и пропускную способность, и противостоять этому можно только надежностью и небольшой (в случае небольшого количества сообщений) комиссией.То есть, если вам не критична скорость, не важно, что сообщения путаются и вы не хотите администрировать или нанимать администратора сервера очередей — это ваш выбор.
Ссылки
- Erlcloud на github github.com/gleber/erlcloud
- www.w3.org/TR/REC-xml/#charsets
-
Обзор Блога №38. Блогер Четверг
19 Oct, 24 -
Наручные Часы Nike Amp+
19 Oct, 24 -
Опубликованы Исходные Коды Android 4.0
19 Oct, 24