🦾 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 сокет закроется или вы как-то сможете проверить, что он реально закрытКак фикс этой ситуации мы просто делаем бродкаст события при переподключении, чтобы все инстансы убили у себя старое соединение по этому контроллеру