У меня есть считыватель сокетов python, который каждую минуту прослушивает входящие UDP-пакеты от 5000 клиентов. Когда я начал его развертывать, он работал нормально, но теперь, когда у меня около 4000 клиентов, я теряю около 50% входящих данных. У виртуальной машины много памяти и процессора, поэтому я предполагаю, что это что-то с моим UDP прослушиватель сокетов на сервере получает сразу слишком много данных. Через cron каждую минуту клиенты отправляют следующие данные:
site8385','10.255.255.255','1525215422','3.3.0-2','Jackel','00:15:65:20:39:10'
Это часть моего сценария слушателя, касающаяся чтения сокетов.
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
port = 18000
s.bind(('', port))
while True:
# Establish connection with client.
d = s.recvfrom(1024)
Может быть, размер буфера слишком мал? Как определить размер входящих пакетов, чтобы можно было настроить значение 1024?
Это 5000 / минута около 83 / секунда +/- несколько сотен% или около того, или это шторм, когда почти все 5000 приходят сразу, а затем ничего не происходит в течение 59 секунд?
@abarnert Да. Все приходят сразу, то 59 секунд ничего не происходит. Байт составляет около 80 на одного клиента.
Количество байтов не должно быть проблемой (если только вы не используете канал со скоростью 10 Мбит / с, чего, вероятно, нет). Но количество пакетов в секунду могло быть. Сколько работы вы делаете после этого d.recvfrom
?
@abarnert Совсем немного. Каждая запись записывается в базу данных mariadb.
Хорошо, есть два способа атаковать это: (1) попробуйте увеличить размер приемного буфера, чтобы он не начинал отбрасывать пакеты, пока вы не отстанете намного дальше, или (2) попытайтесь обслужить буфер как можно быстрее, просто просто отправка всех сообщений в какой-то другой канал (скажем, multiprocessing.Queue
, обслуживаемый multiprocessing.Process
- или, что проще, просто бросать задачи в multiprocessing.Pool
или concurrent.futures.ProcessPoolExecutor
).
@abarnert Увеличил буфер до 64 КБ, но он обрабатывается примерно так же, как когда он был установлен на 80 КБ. Я также закомментировал записи sql, но это тоже не повлияло.
Я не уверен, какой буфер вы увеличили, но это не ускорит процесс; это просто означает, что ядро дает вам больше времени, прежде чем отбрасывать пакеты (при условии, что это происходит в вашем ядре, а не в вашей сетевой карте, вашем маршрутизаторе, вашем восходящем провайдере и т. д.).
Между тем, если вы закомментируете все работу, а не только запись SQL, будет ли это иметь какое-то значение?
@abarnert Комментирование всей работы очень помогло, и почти все они получили. Когда я говорю, что изменил размер буфера, я имею в виду эту строку: d = s.recvfrom (1024)
О, размер буфера что вообще не имеет значения. Вы всегда будете получать одно сообщение, и если это обычно около 80 байт, не имеет значения, насколько больше 80 вы установите максимум. Я имел в виду использование setsockopt
для увеличения приемного буфера сокета и, возможно, использование sysctl
для увеличения максимального приемного буфера сокета, чтобы вы могли увеличить его еще больше. Посмотрите SO_RCVBUF
на своей платформе для получения подробной информации (он должен быть в man 7 socket
), и несколько других sockopts, а также упомянутые там значения файловой системы sysctl
или /proc
. (Или, держу пари, есть хороший вопрос по ServerFault.)
@abarnert Так что это определенно sql пишет, что замедляет его. Позвольте мне выбрать варианты, которые вы предоставили. Спасибо.
Каждые 60 секунд вы получаете шторм из ~ 5000 сообщений. Вы обрабатываете их последовательно, и на это уходит «совсем немного» времени. Итак, довольно быстро один из ваших буферов заполняется, и ваша ОС, сетевая карта или маршрутизатор начинают отбрасывать пакеты. (Скорее всего, это буфер, который ваше ядро выделяет для этого конкретного сокета, и ядро отбрасывает пакеты, но все другие варианты тоже возможны.)
Вы можете попробовать увеличить эти буферы. Это даст вам намного больше «допустимого времени задержки», так что вы сможете отстать еще до того, как ядро начнет отбрасывать пакеты. Если вы хотите пойти по этому пути, первым делом setsockopt
повысит значение SO_RCVBUF
, но вам действительно нужно узнать обо всех проблемах, которые могут здесь возникнуть.
Если вы управляете клиентским кодом, вы также можете заставить клиентов смещать свои пакеты (например, просто спать для random.random() * 55
перед send
).
Но, вероятно, лучше попытаться обслуживать эти пакеты как можно быстрее и выполнять обработку в фоновом режиме.
Попытка сделать это в потоке может быть идеальной, но также может быть очень неудобной для правильной работы. Более простое решение - использовать только фоновый поток или их пул:
def process_msg(d):
# your actual processing code
with concurrent.futures.ThreadPoolExecutor(max_workers=12) as x:
while True:
d = s.recvfrom(1024)
x.submit(process_msg, d)
Этот май на самом деле не помогает. Если ваша обработка привязана к ЦП, а не к вводу-выводу, фоновые потоки будут просто бороться за GIL с основным потоком. Если вы используете Python 2.7 или 3.2 или что-то еще старое, даже потоки, связанные с вводом-выводом, могут мешать в некоторых ситуациях. Но в любом случае есть простое решение: просто замените этот ThreadPoolExecutor
на ProcessPoolExecutor
(и, возможно, уменьшите max_workers
на 1 меньше, чем количество ядер, которое у вас есть, чтобы убедиться, что принимающий код может иметь целое ядро).
2
1. Redhat has a nice doc on Network Performance Tuning. It's written more from the sysadmin's point of view than the programmer's, and it expects you to either know, or know how to look up, a lot of background information—but it should be helpful if you're willing to do that. You may also want to try searching Server Fault rather than Stack Overflow if you want to go down this road.
Вы должны уже знать размер входящих дейтаграмм из определения протокола вашего приложения. Обычный трюк состоит в том, чтобы использовать буфер на один байт больше максимального, поэтому, если вы когда-либо получите дейтаграмму такого размера, вы знаете, что это ошибка протокола и, возможно, она также была усечена.