У нас есть клиентское серверное приложение, 1 сервер, около 10 клиентов. Они взаимодействуют через сокеты tcp, используя пользовательские запросы.
Система работала бесперебойно в течение многих месяцев, но в какой-то момент после ежедневного запланированного сервера FULL GC, который занял около 50 секунд, мы выяснили, что время между запросами, отправленными клиентом и ответы, полученные от сервера, были большими, > 10-20 с. Спустя примерно 3 часа система восстановилась, все снова стало нормально.
При исследовании проблемы мы обнаружили:
- Нет проблем с сборкой мусора на обоих клиентах и сервере.
- Время обработки запроса на сервере было небольшим.
- Загрузка на сервер была высокой.
- Полоса пропускания сети не была насыщенной.
- Соединения не были reset во время FULL GC (ежедневный FULL GC был обычным событием до тех пор)
- Аппарат и ОС недавно изменились с Centos 6 (ядро 2.6.32) до Centos 7 (ядро 3.10.0), но новая конфигурация была протестирована полностью. Также версия Oracle JDK изменилась с 1.7.65 до 1.7.75.
Мы взяли дамп потока на сервере:
java.lang.Thread.State: RUNNABLE
at java.io.FilterInputStream.read(FilterInputStream.java:83)
at util.network.BytesBasedSocketConnection$ReadConnectionRunnable.run(BytesBasedSocketConnection.java:293)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471)
at java.util.concurrent.FutureTask.run(FutureTask.java:262)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:178)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:292)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:745)
FilterInputStream.read()
:
public int read() throws IOException {
return in.read();
}
in
в нашем коде есть BufferedInputStream
.
Вопросы: почему большинство соединений замедлилось после полной паузы GC? Почему stacktrace заканчивается на FilterInputStream.read()
? Разве он не должен заканчиваться где-то в BufferedInputStream
или во входном потоке сокета? Может ли это прочитать привести к высокой нагрузке на сервер?
Код, который мы используем для чтения:
int constructLength = _socketDIS.readInt();
ByteArrayOutputStream constructBOAS = new ByteArrayOutputStream(constructLength);
for (int i = 0; i != constructLength; i++)
constructBOAS.write(_socketDIS.read());
constructBOAS.close();
byte[] bytes = constructBOAS.toByteArray();
где:
_socketDIS = new DataInputStream(new BufferedInputStream(_socket.getInputStream()));
Вот стек из хорошо работающих клиентских подключений:
java.lang.Thread.State: RUNNABLE
at java.net.SocketInputStream.socketRead0(Native Method)
at java.net.SocketInputStream.read(SocketInputStream.java:152)
at java.net.SocketInputStream.read(SocketInputStream.java:122)
at java.io.BufferedInputStream.fill(BufferedInputStream.java:235)
at java.io.BufferedInputStream.read(BufferedInputStream.java:254)
- locked <0x00007f522cbebca8> (a java.io.BufferedInputStream)
at java.io.DataInputStream.readInt(DataInputStream.java:387)
at util.network.BytesBasedSocketConnection$ReadConnectionRunnable.run(BytesBasedSocketConnection.java:287)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471)
at java.util.concurrent.FutureTask.run(FutureTask.java:262)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:178)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:292)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:745)
UPDATE:
Относительно ответа EJP:
-
Не было задействовано EOS, соединения были вверх, но они были очень медленными
-
Даже если бы EOS я не мог видеть, как код мог бы вращаться в EOS,
for
ограничивается значениемconstructLength
. Тем не менее, предлагаемое улучшение действительно. -
Столбец с проблемой заканчивается прочитанным в
DataInputStream
((_socketDIS.read()
), который наследуется отFilterInputStream.read()
, см. выше код.DataInputStream
, а неBufferedInputStream
отсутствуетread()
. Здесь вFilterInputStream.read()
существуетin.read()
, вызываемый вBufferedInputStream
, этот имеет свой собственный методread()
. Но stacktrace останавливается посередине, не достигаетBufferedInputStream.read()
. Почему?