# Отказоустойчивость и горизонтальное масштабирование ПринтМенеджера

## **Назначение**
Эта статья разбирает отказоустойчивую и масштабируемую архитектуру обработки заданий печати, копирования и сканирования. 

## **Когда нужна эта схема**

Базовой конфигурации из одного ПринтМенеджера достаточно, пока хватает производительности одного сервера и нет требований по отказоустойчивости. Горизонтальное масштабирование и балансировка нагрузки решают задачи:

* **Производительность** — несколько ПринтМенеджеров обрабатывают задания параллельно, увеличивая общую пропускную способность.  
* **Отказоустойчивость** — потеря одного или нескольких серверов не останавливает обработку заданий на оставшихся.

## **Расположение компонентов**

Главное архитектурное отличие этой схемы от базовой конфигурации в наборе компонентов и в взаимном расположении: несколько равноправных узлов обработки перед общими базой данных и реплицированным хранилищем.

## **Что меняется относительно базовой конфигурации**

| Компонент базовой конфигурации | Что происходит в кластере |
| ----- | ----- |
| Nginx как точка входа | Точкой входа становится **балансировщик HAProxy**. Это  реверс-прокси, но умеющий распределять запросы между несколькими активными ПринтМенеджерами по определенным правилам. Сам nginx не пропадает, но перестает быть точкой входа. |
| Redis (брокер) | Улучшается до master-slave **кластера с поддержкой Redis Sentinel,** который обеспечивает отказоустойчивость кластера Redis. |
| PostgreSQL | Становится **общей базой данных** на весь кластер — логически один компонент, к которому обращаются все узлы. Внутри эта база данных должна быть физически или логически реплицирована. |
| Локальное хранилище образов документов CUPS | Меняется на **общее NFS-хранилище** — папка которая доступна на чтение и запись каждому приложению, т.е. задание может быть создано на одном узле, а прочитано на другом. По одному CUPS на каждый из узлов, каждый из CUPS содержит одинаковое количество принтеров. Поскольку CUPS становится больше, то нагрузка на каждый из них становится меньше, а значит возможна печать большего количества документов одновременно без перегрузки. |
| Celery (один экземпляр) | По одному Celery на каждом узле — обрабатывают различные задачи; именно это даёт прирост производительности. |
| Django | По одному Django на каждом узле. Поскольку Django становится больше, они независимо обрабатывают разные запросы извне |
|  Планировщик | Становится **распределённым**: экземпляр существует на каждом узле, но согласовывает запуск периодических задач через Redis Sentinel, чтобы одна и та же задача не запускалась одновременно на всех узлах. |
| Конвертер-сервер | Не меняет своих функций, но иным способом обрабатывает TCP запросы полученные от сокета HAProxy, чтобы правильно определять адрес физического ковертера. |

Несмотря на появление HAProxy, Redis Sentinel и общего NFS-хранилища, архитектура обработки самого задания принципиально не меняется. Все изменения направлены на одно: чтобы несколько экземпляров ПринтМенеджера могли совместно обрабатывать задания, используя общие данные и не мешая друг другу. 

## **Как обрабатывается запрос**

**В базовой конфигурации** клиент отправляет запрос напрямую на единственный сервер: nginx → Django → Celery → CUPS.

**В кластере** тот же путь проходит через дополнительную точку координации:

1. Клиент отправляет запрос не на конкретный сервер, а на **балансировщик**.  
2. Балансировщик направляет его на один из активных узлов.  
3. На выбранном узле запрос обрабатывается как обычно: nginx → Django.  
4. Django передаёт задачу через Redis — кластер координирует, чтобы её взял в работу ровно один свободный Celery, независимо от того, на каком узле он находится.  
5. Метаданные задания кладутся в общую PostgreSQL, образ документа — в общее NFS-хранилище.  
6. Печать выполняет тот узел, куда Celery отправитзадачу.

Поскольку Celery и Django на каждом узле работает независимо и асинхронно друг относительно друга, то с ростом числа узлов растёт и пропускная способность по заданиям — это и есть причина увеличивать число узлов ради производительности, отдельно от вопроса отказоустойчивости.

## **Принцип отказоустойчивости**

Основа отказоустойчивости – это возможности системы перераспредлять запросы и задачи с узлов, которые отказали, на работающие узлы.

* для перераспределения запросов нужен HAProxy  
* для перераспределения задач нужен Redis Sentinel

HAProxy внутри себя имеет реплику, и внешний трафик с запросами может быть направлен на одну или другую реплику с использованием различных механизмов. Балансировщик определяет доступен ли узел, и если нет, то отправляет запрос в первый встречный работающий узел.

Redis Sentinel кластеру для работы необходим **кворум** — большинство узлов Redis должно оставаться активным: активных узлов должно быть строго больше, чем неактивных. Если это условие нарушается, Redis Sentinel не могут договориться, кто из оставшихся узлов должен взять на себя роль мастера, и кластер теряет работоспособность целиком.

| Узлов в кластере | Можно потерять одновременно |
| :---: | :---: |
| 3 | 1 |
| 5 | 2 |
| 7 | 3 |

Классический пример — 3 узла (1 мастер \+ 2 слейва): такая конфигурация переживает потерю любого одного узла, но не двух сразу.