🦾 IT-Качалка Давида Шекунца 💪
2023-10-04 22:25
Короче, постом выше написал про то, как мы избввились от привязки сущностей к инстансам, сделав упрощенные распределенные локи
Все работало, показывало перфоманс, но ровно до вчерашнего дня...
Началось все с того, что не могли зареконектиться 5 контроллеров, посмотрели на их поведение, подумали: "Аномалии бывают" – через 2 часа таких контроллеров стало 40, через час их стало 300, а через еще полчаса 2000...
Причем они не просто "реконектились", они в абсолютно рандомном порядке получали и отдавали сообщений, время от времени возвращаясь в норму на пару минут и обратно в хаос
Хуже еще то, что во втором идентичном контуре (с меньшей нагрузкой) все абсолютно нормально
Ну ок, первое предположение неправильно работают локи, тестируем, все ок, дальше смотрим как привязываются сокеты, тут тоже все попротоколу, далее предполагаем, что проблема в БД, но и она в норме, потом думаем, что тупят сервисы, но event loop и memory в прекрасном состоянии
Тут мы случайно заметили, что сообщения уже на выходе от нас на контроллер имеют время на 5 минут раньше, чем реальное, поэтому он их отбрасывает, как слишком старые
Проверяем корректность времени кластера, все ок, проверяем ноду, все ок, проверяем железо, все ок
Решили, что поскольку мы не можем найти источник проблемы лучше сделать набор защитных механизмов (связанных с локами и таймстемпами), выкатить в прод и посмотреть
Что мы ни делали, результата не было
В итоге я принял решение переписать парсинг сообщений с распределенного лока на БД (чуйка подсказывала что проблема где-то здесь)
За час я написал очередь, пуллер, health checks, миграции и индексы, убрал весь старый код, выпустили и все абсолютно нормализовалось...
Все разошлись спать (а один уже уснул прям напротив монитора), но я решил все-таки попробовать понять что пошло не так
И тут мое внимание привлек 1 лог: сообщения на контроллер отправляются сразу с двух сервисов и сразу в 2 открытых TCP сокета....
Выяснилась поразительную вещь: контроллер в теории оказывается может не закрыть предыдущие соединения, но даже в этом случае запись в это соединение должно его закрывать, и оказалось, что где-то на уровне proxy / multiplexer / kubernetes / cloud / dns / провайдера просто происходит ДУБЛИРОВАНИЕ TCP СОКЕТА
То есть он не просто не закрывался (что является стандартным поведением), он еще мог принимать сообщения
Поэтому во время реконектов контроллеров, каждый контроллер успевал приконектиться к каждому сервису с TCP сокетами, оставив на каждом из них открытое соединение, которое вело в никуда
Когда мы запрашивали сообщения с сокет сервисов, он брал последнее присланное сообщение от контроллера и с вероятностью в 12% (1 из 8 инстансов) это было реально последнее сообщение из реально открытого сокета, но в 88% это было уже устаревшее сообщение из сокета, который просто не закрылся....
В новой же модели, мы достаем сообщения из БД, куда в кчу скидывают все контроллеры, а отправляем ответ на основании id сервиса, который последний принял сокет
Заключение: никогда не рассчитывайте, что TCP сокет закроется или вы как-то сможете проверить, что он реально закрыт
Как фикс этой ситуации мы просто делаем бродкаст события при переподключении, чтобы все инстансы убили у себя старое соединение по этому контроллеру