[БЕЗ_ЗВУКА] В этом видео мы поговорим про RabbitMQ. Rabbit — это целая платформа для построения, разного рода обмена сообщениями и паттерна pub/sub, publisher-subscriber, ну и разного рода очередей. Очередь — это очень частый паттерн в системной архитектуре, который позволяет вынести разного рода тяжелые операции из запроса пользователя в разного рода оффлайн-скрипты, оффлайн-воркеры и не заставлять пользователя ждать, пока завершится операция. Например, если пользователь загружает видео, не будем же мы целый час его держать на связи, крутя в него индикатор загрузки. Мы загрузим видео, положим его на диск и скажем пользователю: ваше видео обрабатывается, подождите немножко. В это время видео попадает в очередь, и когда до него доходит очередь, простите за тавтологию, уже другой скрипт, другой процесс, который, скорее всего, работает даже на другом сервере, начинает конвертировать это видео. В этом примере мы рассмотрим операцию не такую тяжелую, как конвертация видео — мы будем создавать превью для картинок. Давайте посмотрим сначала, как это выглядит, а потом перейдем к коду. Итак, выберем изображение. У меня вот есть фотография. Загрузим и сразу же перейдем к просмотру файлов, которые нам генерируются. Обратите внимание, у меня файлы появляются один за другим, я специально там добавил задержку, чтобы было видно. Вот получается, что вот моя исходная загруженная картинка, а вот превьюшки разного размера, которые построились моим оффлайн-воркером для изменения размеров картинки. При этом обратите внимание, что ответ мне дался сразу, сразу как только я загрузил картинку. Теперь рассмотрим код, в котором я расскажу, каким образом все происходит. Для начала, для начала мы выполняем подключение к Rabbit. Обратите внимание, у меня вот здесь стоит равно — дело в том, что соединение к Rabbit у меня, оно не локальная функция, она глобальная. Если я поставлю здесь точку с запятой, то работать не будет, потому что создастся локальная переменная в области видимости функции. И при попытке уже из другой функции воспользоваться этим соединением, вы будете ловить панику, потому что там nil внутри лежит. Такая проблема у людей бывает. Вот. Создали соединение, все отлично. Теперь получаем канал, с которым мы будем уже общаться с Rabbit. Попробуем определить там очередь сообщений, которую мы хотим. Я не буду детально останавливаться на параметрах, но просто если очереди нет, она создастся, если есть, ничего не произойдет. Окей? Все, мы написали, что очередь очередь создана, очередь имеет такое-то имя, столько сообщений, столько консьюмеров. Вот эта строка, вот она — очередь создана, там 0 сообщений и один консьюмер. Консьюмер — это мой оффлайн-воркер, мой гошный сервис, который как раз таки принимает эти задачи из очереди. Потом я стартанул сервер, и вот пришло новое сообщение, которое я положил в очередь. Каким образом класть сообщение в очередь? Вернемся в нашу функцию, тут есть много всякого интересного. Итак, для начала мы вычитаем файл из ответа — напомню, что для этого у нас есть FormFile. Напечатаем какую-то информацию на экран пользователю. Потом дело в том, что я не могу одновременно сначала построить md5, потом построить еще что-то — у меня нету еще имени. Использовать имя, которое передано пользователям крайне небезопасно. Я генерирую случайную строку, кладу во временный файл. Далее тут небольшой трюк. Я хочу одновременно в один проход, в одно чтение построить и md5 от файла, и записать Fn на диск. Для этого есть TeeReader, куда можно передать еще один ридер, куда дополнительно будет писаться. И, получается, в один проход я и пишу файл, и строю хэш по нему. Так, окей, далее мы пишем непосредственно файл на диск, флашим все, строим md5, переименовываем картинку по реальному md5. Теперь создаем задачу: имя файла, md5 от этого файла, чтобы знать к какому файлу обратиться. В реальной жизни у вас, скорее всего, в эту задачу войдет адрес, куда была загружена картинка, чтобы ваш фреймворк знал, куда идти, на каком хранилище сохранена она. Либо еще какая-то дополнительная информация. Вот. Дальше я ввожу сообщение, которое вы видели. И теперь теперь я уже публикую это сообщение в мой в Rabbit. Собственно, я говорю, в каком виде его доставить, какие там данные лежат, какое тело. Обратите внимание: rabbitChan — это канал общения с Rabbit, это не канал, не гошный канал, который для общения с горутинами. Ага, хорошо. Я опубликовал сообщение, все туда ушло. То есть, обратите внимание: я в пользовательском запросе просто положил файл на диск и не я пережимал его. Я его, как есть, положил и все. И отправил сообщение в очередь, что вот этот вот файл нужно пережать. Теперь рассмотрим очередь. Рассмотрим воркера, который получает сообщение из очереди. Итак, там, ага. Вот он. Да, вот логи, смотрим логи. То есть worker started получил сообщение, и вот вся информация, которая есть в этом сообщении. Так. Начинаем мы, как всегда с подключения к Rabbit — мы не можем им пользоваться, не подключившись, либо [НЕРАЗБОРЧИВО], к сожалению, подтягивается только из астрала. Так, опять получаем канал, определяем очередь, устанавливаем, какое количество сообщений мы хотим префечить, то есть подтягивать из очереди просто в свою буфер, для того чтобы быстро их обрабатывать. Теперь получаем канал уже, из которого мы будем читать сообщения, то есть в данном случае tasks — это будет уже гошный канал, гошный канал. И по нему мы можем итерироваться уже, используя for range или select. И в отдельной горутине я вызываю воркера, чтобы, собственно, он работал над моими тасками. При этом я, конечно же, могу вызвать много воркеров, я могу написать цикл от 1 до 10, например: := 0; i<+ 10; i++. И вот таким вот нехитрым образом мы сразу же получили 10 воркеров, то есть мы смасштабировали нашу программу. Если у нас 10 ядер в процессоре, то 10 воркеров займут все эти ядра. Картинки все-таки — операция, которая сильно грузит процессор. Вот так. Так, допустим, вот начали воркера делать. Теперь мы просто итерируемся по этому каналу, напоминаю, через for range. Я получаю какой-то таск — taskItem, далее я распаковываю его. У меня в таске есть тело этого сообщения. Распаковываюсь, и не получилось — сразу возвращаю его обратно в очередь, либо могу сказать, что просто удалить. А теперь я строю путь, который оригинальный, временный путь, куда я положил данные. И теперь для нескольких размеров картинок я буду вызывать функцию ResizeImage, куда буду передавать оригинальный путь, новый путь для картинки и размер, который нужно создать. Все. То есть фактически, посмотрите: задача сводится буквально к тому, чтобы просто читать из канала. То есть если бы это было на одном сервере, я мог бы обойтись каналами внутри Go прямо. И, действительно, картинки пережимались бы в фоне от пользовательских запросов. Но в реальность, если у вас есть потребность пережимать картинки через очередь, скорее всего, этих картинок у вас много, серверов у вас тоже много. И вы не можете себе позволить такую роскошь как грузить сервера, которые обрабатывают пользовательские запросы, еще и ресайзом картинок. Поэтому это выносится на отдельный сервер. Собственно, вот и все. Еще можно рассмотреть функцию ResizeImage. Я использую внешнюю библиотеку, я также открываю картинку, вычитываю файл jpeg, получаю структуру «картинка из стандартной библиотеки». И использую функцию Resize — это уже, к сожалению внешняя библиотека. И сохраняю ее по новому пути, который мне передался. Вот и все, буквально в несколько сотен строчек мы получили систему, которая принимает пользовательские запросы, кладет их в очередь и в оффлайне, то есть каким-то внешним сервисом, уже выполняет операции над этими запросами. И в этом нам помог Rabbit.