Открытые соединения через Spring Websocket STOMP приводят к тому, что наш сервер умирает

Поэтому мы используем веб-сокет Spring STOMP + RabbitMQ на бэкэнде, и у нас возникают проблемы с дескрипторами открытых файлов. Через определенное время мы достигли предела на сервере, и сервер не принимает никаких подключений, включая как веб-сокеты, так и конечные точки API.

2018-09-14 18:04:13.605  INFO 1288 --- [MessageBroker-1] 
o.s.w.s.c.WebSocketMessageBrokerStats    : WebSocketSession[2 current WS(2)- 
HttpStream(0)-HttpPoll(0), 1159 total, 0 closed abnormally (0 connect 
failure, 0 send limit, 63 transport error)], stompSubProtocol[processed 
CONNECT(1014)-CONNECTED(1004)-DISCONNECT(0)], stompBrokerRelay[9 sessions, 
127.0.0.1:61613 (available), processed CONNECT(1015)-CONNECTED(1005)- 
DISCONNECT(1011)], inboundChannel[pool size = 2, active threads = 2, queued 
tasks = 2, completed tasks = 12287], outboundChannelpool size = 0, active 
threads = 0, queued tasks = 0, completed tasks = 4225], sockJsScheduler[pool 
size = 1, active threads = 1, queued tasks = 3, completed tasks = 683]

И мы получаем следующие исключения:

2018-09-14 18:04:13.761 ERROR 1288 --- [http-nio-127.0.0.1-8443-Acceptor-0] 
org.apache.tomcat.util.net.NioEndpoint   : Socket accept failed

java.io.IOException: Too many open files
    at sun.nio.ch.ServerSocketChannelImpl.accept0(Native Method)
    at sun.nio.ch.ServerSocketChannelImpl.accept(ServerSocketChannelImpl.java:422)
    at sun.nio.ch.ServerSocketChannelImpl.accept(ServerSocketChannelImpl.java:250)
    at org.apache.tomcat.util.net.NioEndpoint$Acceptor.run(NioEndpoint.java:455)
    at java.lang.Thread.run(Thread.java:748)

Ограничение файлового дескриптора по умолчанию для Linux составляет 1024, и даже если мы увеличим его примерно до 65000, в какой-то момент он достигнет предела, несмотря ни на что.

Мы хотим решить эту проблему со стороны бэкенда и желательно с помощью Spring без каких-либо обходных путей. Любые идеи?

ОБНОВЛЕНИЕ

RabbitMQ и приложение находятся на разных серверах. На самом деле RabbitMQ работает на Compose. Мы можем воспроизвести эту проблему, не отправляя сообщения DISCONNECT от клиента.

ОБНОВЛЕНИЕ 2

Сегодня я понял, что все файловые дескрипторы и java-потоки всегда остаются там, что бы ни случилось. Я реализовал обходной путь, который включает отправку сообщений DISCONNECT из Spring и закрытие объектов WebSocketSession без изменений. Я реализовал их, проверив следующие ссылки:

И в качестве примечания, серверная сторона отправляет такие сообщения: simpMessagingTemplate.convertAndSend("/queue/" + sessionId, payload). Таким образом, мы гарантируем, что каждый клиент получит соответствующее сообщение по соответствующей sessionId.

Это какой-то баг? Почему дескрипторы файлов не закрываются? Никто не сталкивался с этой проблемой раньше?

ОБНОВЛЕНИЕ 3

Каждый раз, когда сокет закрывается, я вижу следующее исключение. Неважно, как он закрывается, будь то сообщение DISCONNECT от клиента или webSocketSession.close() код с сервера.

[reactor-tcp-io-66] o.s.m.s.s.StompBrokerRelayMessageHandler : TCP connection failure in session 45r7i9u3: Transport failure: epoll_ctl(..) failed: No such file or directory
io.netty.channel.unix.Errors$NativeIoException: epoll_ctl(..) failed: No such file or directory
at io.netty.channel.unix.Errors.newIOException(Errors.java:122)
at io.netty.channel.epoll.Native.epollCtlMod(Native.java:134)
at io.netty.channel.epoll.EpollEventLoop.modify(EpollEventLoop.java:186)
at io.netty.channel.epoll.AbstractEpollChannel.modifyEvents(AbstractEpollChannel.java:272)
at io.netty.channel.epoll.AbstractEpollChannel.clearFlag(AbstractEpollChannel.java:125)
at io.netty.channel.epoll.AbstractEpollChannel$AbstractEpollUnsafe.clearEpollRdHup(AbstractEpollChannel.java:450)
at io.netty.channel.epoll.AbstractEpollChannel$AbstractEpollUnsafe.epollRdHupReady(AbstractEpollChannel.java:442)
at io.netty.channel.epoll.EpollEventLoop.processReady(EpollEventLoop.java:417)
at io.netty.channel.epoll.EpollEventLoop.run(EpollEventLoop.java:310)
at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:884)
at java.lang.Thread.run(Thread.java:748)

Вот я изменил уровень логов на TRACE и вижу, что вебсокеты действительно закрываются, но тут же кидаются эти исключения. Так что на данный момент я очень подозрительно отношусь к этому исключению. Количество зависших потоков Java всегда идет рука об руку с количеством веб-сокетов, т.е. создание 400 веб-сокетов всегда приводит к ~400 зависшим потокам в основном процессе. И ресурсы памяти никогда не освобождаются.

Поиск в Google этого исключения приводит только к 4 результатам ниже: (остальные - другие исключения)

Обновление библиотеки netty до последней версии (4.1.29.Final) тоже не помогло, поэтому я соответствующим образом изменил теги вопроса. Я также подумываю создать проблему против netty. Я пробовал много вещей и несколько раз экспериментировал на уровне приложения, но ничего не работает. На данный момент я открыт для любых идей.


person leventunver    schedule 19.09.2018    source источник
comment
Какие версии spring/rabbitMQ вы используете? Возможно, это какой-то скрытый баг в библиотеках. Работает ли Rabbit на том же сервере, что и уязвимое приложение? Вы можете попробовать проанализировать с помощью JVisualVM или другого подобного инструмента, особенно поиска в heapdump для объектов, содержащих открытые сокеты и т. д. после возникновения IOException   -  person Kamil Piwowarski    schedule 21.09.2018
comment
Привет @KamilPiwowarski. Версия RabbitMq — 3.7.7, а версия Spring Boot — 1.5.9. Это происходит, когда клиентская сторона не отправляет сообщения DISCONNECT. Поэтому я вижу в этом дыру в безопасности. Когда я пытаюсь написать JS-код и не отправлять разъединения, то после примерно 1000 сообщений сервер выходит из строя. Я не уверен, как другие люди решают эту проблему, так как это похоже на распространенную проблему. Rabbit находится не на том же сервере, а на Compose. Я попытаюсь посмотреть на это и с JVisualVM.   -  person leventunver    schedule 24.09.2018
comment
@leventunver, какая у вас версия клиентской библиотеки RabbitMQ и используете ли вы какие-либо библиотеки-оболочки для управления соединением, например. spring-amqp.   -  person Karol Dowbecki    schedule 24.09.2018
comment
@KarolDowbecki спасибо за ответ. Моя версия клиента 5.2.0, и единственная включенная зависимость - spring-boot-starter-websocket, и я думаю, что она не включает spring-amqp . В моем случае проблема заключается не в том, что потоки Java создаются немедленно, а в том, что они не закрываются. Возможно, мне следует переключить версию на последнюю и повторить попытку.   -  person leventunver    schedule 24.09.2018
comment
Вы нашли решение этой проблемы, я столкнулся с той же проблемой? Спасибо за помощь в продвижении   -  person Suliman Alzamel    schedule 14.01.2019
comment
@SulimanAlzamel Да, но это был совершенно отдельный вопрос. Вот решение: stackoverflow.com/a/48660544/6999882   -  person leventunver    schedule 14.01.2019
comment
@leventunver спасибо   -  person Suliman Alzamel    schedule 15.01.2019


Ответы (2)


Если вы всегда используете try-with-resource или закрываете открытые файлы в блоке finally, возможно, вы действительно превысили лимит дескриптора файла, и вам нужен другой хост, чтобы принимать ваши запросы. Для этого вам необходимо масштабировать приложение и балансировать нагрузку. Я предлагаю вам развернуть rabbitmq в кластере, чтобы сосредоточиться на этой проблеме.

Есть случаи, когда RabbitMQ игнорирует ограничения вашего файлового дескриптора.

Снимок экрана ограничения системы документации RabbitMQ

person Jade Devin Nocum Cabatlao    schedule 22.09.2018
comment
Привет. Спасибо за ответ. Я добавил больше деталей о контексте. Итак, у нас уже есть балансировщик нагрузки, и RabbitMQ работает на Compose и на другой машине с самим приложением. Мы воспроизводим проблему, игнорируя сообщение DISCONNECT в веб-сокетах. Если вы этого не сделаете, веб-сокеты останутся открытыми навсегда, что отстой. Мы ищем способ решить это из бэкэнда. - person leventunver; 24.09.2018
comment
@leventunver Мои извинения за это. Просто вопрос. Вы отправляете тактовые импульсы как с сервера, так и с клиента? И почему ваш stompBrokerRelay подключается к 127.0.0.1 в журнале, который вы показали? Это только для демонстрации? - person Jade Devin Nocum Cabatlao; 25.09.2018
comment
серверы находятся за обратным прокси nginx. Вот почему они видны как локальный ip. Я ничего не реализовал явно для сердцебиения, однако я вижу сообщения HEARTBEAT, когда клиент js открыт в браузере. Heartbeats умирают после закрытия клиента. Нет пульсаций на стороне сервера. Это решит? - person leventunver; 25.09.2018
comment
Дополнительная информация: Очереди создаются как автоудаление на rabbitmq. Даже через какое-то время очереди закрываются и соединение с браузером закрывается, я все равно вижу, что там висят потоки Java. Прошу прощения, если мои слова не подходят для этой темы, так как я новичок в этом деле. - person leventunver; 25.09.2018

У клиентской Java-библиотеки RabbitMQ время от времени возникают проблемы с управлением дескрипторами открытых файлов. Это редко бывает плохо, но есть подводные камни, например. ChannelManager строка 218.

Вы хотите попробовать несколько разных версий клиентской библиотеки Java, так как это проблема на стороне клиента. В одной версии у меня были тысячи потоков Java, порожденных из-за ошибки при создании соединения (не уверен, какая версия была затронута, я заметил это, используя FlightRecorder и перейдя в раздел блокировки, все потоки ожидали получения класса соединения RabbitMQ (?) замок).

person Karol Dowbecki    schedule 24.09.2018