Какие инструменты для блокировок и локов можно использовать между процессами
Что проверяют
Понимаете ли вы, что sync.Mutex и каналы работают только внутри одного процесса. Дальше обычно спрашивают, что будет, если процесс, державший блокировку, упал или завис.
Как отвечать
Сначала разведите уровни. Между горутинами хватает sync.Mutex, sync.RWMutex и каналов. Между процессами и инстансами сервиса нужны внешние механизмы:
- файловые блокировки:
flockчерезgolang.org/x/sys/unixили библиотекуgofrs/flock. Блокировка рекомендательная, её уважают только те, кто сам её берёт, зато ядро снимает её, когда процесс умирает. На NFS на неё не полагаются; - PostgreSQL:
pg_try_advisory_lock(key)на сессию илиpg_advisory_xact_lock(key)на транзакцию. Для очереди задач удобнееSELECT ... FOR UPDATE SKIP LOCKED: каждый воркер забирает свои строки и не ждёт соседей; - Redis:
SET key token NX PX 30000с уникальным токеном, снятие через Lua-скрипт, который удаляет ключ, только если токен совпал. Готовые библиотеки:go-redsync/redsync,bsm/redislock; - etcd: блокировка привязана к лизу с TTL, в Go это
concurrency.NewSessionиconcurrency.NewMutexиз клиента etcd; - Kubernetes: чтобы работал один инстанс из нескольких, есть
leaderelectionизclient-goповерх объектаLease.
Сильный ответ заканчивается проблемой TTL. Процесс может замереть на паузе GC или сетевом таймауте, лиз истечёт, блокировку возьмёт другой, а первый продолжит писать. Защита: fencing token, растущий номер, который хранилище проверяет при записи и отклоняет устаревший.
Где спотыкаются
Отвечают про sync.Mutex и RWMutex на вопрос про процессы. Это самая частая ошибка, она сразу показывает, что вопрос не услышан.
Берут сессионный advisory lock через пул соединений. Блокировка живёт на конкретном соединении, а pgxpool или database/sql отдаст следующий запрос другому, и снятие уйдёт не туда. Нужно выделенное соединение или блокировка на транзакцию.
Снимают блокировку в Redis простым DEL без проверки токена. Если свой TTL уже истёк, так удаляется чужая блокировка.