Большие потоки трафика и управление прерываниями в Linux

    В этой заметке я опишу методы увеличения производительности линуксового маршрутизатора. Для меня эта тема стала актуальна, когда проходящий сетевой трафик через один линуксовый маршрутизатор стал достаточно высоким (>150 Мбит/с, > 50 Kpps). Маршрутизатор помимо роутинга еще занимается шейпированием и выступает в качестве файрволла.

    Для высоких нагрузок стоит использовать сетевые карты Intel, на базе чипсетов 82575/82576 (Gigabit), 82598/82599 (10 Gigabit), или им подобные. Их прелесть в том, что они создают восемь очередей обработки прерываний на один интерфейс – четыре на rx и четыре на tx (возможно технологии RPS/RFS, появившиеся в ядре 2.6.35 сделают то же самое и для обычных сетевых карт). Также эти чипы неплохо ускоряют обработку трафика на аппаратном уровне.
    Для начала посмотрите содержимое /proc/interrupts, в этом файле можно увидеть что вызывает прерывания и какие ядра занимаются их обработкой.
    # cat /proc/interrupts 
               CPU0       CPU1       CPU2       CPU3       
      0:         53          1          9        336   IO-APIC-edge      timer
      1:          0          0          0          2   IO-APIC-edge      i8042
      7:          1          0          0          0   IO-APIC-edge    
      8:          0          0          0         75   IO-APIC-edge      rtc0
      9:          0          0          0          0   IO-APIC-fasteoi   acpi
     12:          0          0          0          4   IO-APIC-edge      i8042
     14:          0          0          0        127   IO-APIC-edge      pata_amd
     15:          0          0          0          0   IO-APIC-edge      pata_amd
     18:        150       1497      12301     473020   IO-APIC-fasteoi   ioc0
     21:          0          0          0          0   IO-APIC-fasteoi   sata_nv
     22:          0          0         15       2613   IO-APIC-fasteoi   sata_nv, ohci_hcd:usb2
     23:          0          0          0          2   IO-APIC-fasteoi   sata_nv, ehci_hcd:usb1
     45:          0          0          0          1   PCI-MSI-edge      eth0
     46:  138902469      21349     251748    4223124   PCI-MSI-edge      eth0-rx-0
     47:  137306753      19896     260291    4741413   PCI-MSI-edge      eth0-rx-1
     48:       2916  137767992     248035    4559088   PCI-MSI-edge      eth0-rx-2
     49:       2860  138565213     244363    4627970   PCI-MSI-edge      eth0-rx-3
     50:       2584      14822  118410604    3576451   PCI-MSI-edge      eth0-tx-0
     51:       2175      15115  118588846    3440065   PCI-MSI-edge      eth0-tx-1
     52:       2197      14343     166912  121908883   PCI-MSI-edge      eth0-tx-2
     53:       1976      13245     157108  120248855   PCI-MSI-edge      eth0-tx-3
     54:          0          0          0          1   PCI-MSI-edge      eth1
     55:       3127      19377  122741196    3641483   PCI-MSI-edge      eth1-rx-0
     56:       2581      18447  123601063    3865515   PCI-MSI-edge      eth1-rx-1
     57:       2470      17277     183535  126715932   PCI-MSI-edge      eth1-rx-2
     58:       2543      16913     173988  126962081   PCI-MSI-edge      eth1-rx-3
     59:  128433517      11953     148762    4230122   PCI-MSI-edge      eth1-tx-0
     60:  127590592      12028     142929    4160472   PCI-MSI-edge      eth1-tx-1
     61:       1713  129757168     136431    4134936   PCI-MSI-edge      eth1-tx-2
     62:       1854  126685399     122532    3785799   PCI-MSI-edge      eth1-tx-3
    NMI:          0          0          0          0   Non-maskable interrupts
    LOC:  418232812  425024243  572346635  662126626   Local timer interrupts
    SPU:          0          0          0          0   Spurious interrupts
    PMI:          0          0          0          0   Performance monitoring interrupts
    PND:          0          0          0          0   Performance pending work
    RES:   94005109   96169918   19305366    4460077   Rescheduling interrupts
    CAL:         49         34         39         29   Function call interrupts
    TLB:      66588     144427     131671      91212   TLB shootdowns
    TRM:          0          0          0          0   Thermal event interrupts
    THR:          0          0          0          0   Threshold APIC interrupts
    MCE:          0          0          0          0   Machine check exceptions
    MCP:        199        199        199        199   Machine check polls
    ERR:          1
    MIS:          0

    В данном примере используются сетевые карты Intel 82576. Здесь видно, что сетевые прерывания распределены по ядрам равномерно. Однако, по умолчанию так не будет. Нужно раскидать прерывания по процессорам. Чтобы это сделать нужно выполнить команду echo N > /proc/irq/X/smp_affinity, где N это маска процессора (определяет какому процессору достанется прерывание), а X — номер прерывания, виден в первом столбце вывода /proc/interrupts. Чтобы определить маску процессора, нужно возвести 2 в степень cpu_N (номер процессора) и перевести в шестнадцатиричную систему. При помощи bc вычисляется так: echo "obase=16; $[2 ** $cpu_N]" | bc. В данном примере распределение прерываний было произведено следующим образом:
    #CPU0
    echo 1 > /proc/irq/45/smp_affinity
    echo 1 > /proc/irq/54/smp_affinity
    
    echo 1 > /proc/irq/46/smp_affinity
    echo 1 > /proc/irq/59/smp_affinity
    echo 1 > /proc/irq/47/smp_affinity
    echo 1 > /proc/irq/60/smp_affinity
    
    #CPU1
    echo 2 > /proc/irq/48/smp_affinity
    echo 2 > /proc/irq/61/smp_affinity
    echo 2 > /proc/irq/49/smp_affinity
    echo 2 > /proc/irq/62/smp_affinity
    
    #CPU2
    echo 4 > /proc/irq/50/smp_affinity
    echo 4 > /proc/irq/55/smp_affinity
    echo 4 > /proc/irq/51/smp_affinity
    echo 4 > /proc/irq/56/smp_affinity
    
    #CPU3
    echo 8 > /proc/irq/52/smp_affinity
    echo 8 > /proc/irq/57/smp_affinity
    echo 8 > /proc/irq/53/smp_affinity
    echo 8 > /proc/irq/58/smp_affinity

    Также, если маршрутизатор имеет два интерфейса, один на вход, другой на выход (классическая схема), то rx с одного интерфейса следует группировать с tx другого интерфейса на одном ядре процессора. Например, в данном случае прерывания 46 (eth0-rx-0) и 59 (eth1-tx-0) были определены на одно ядро.
    Еще одним весьма важным параметром является задержка между прерываниями. Посмотреть текущее значение можно при помощи ethtool -c ethN, параметры rx-usecs и tx-usecs. Чем больше значение, тем выше задержка, но тем меньше нагрузка на процессор. Пробуйте уменьшать это значение в часы пик вплоть до ноля.
    При подготовке в эксплуатацию сервера с Intel Xeon E5520 (8 ядер, каждое с HyperThreading) я выбрал такую схему распределения прерываний:
    #CPU6
    echo 40 > /proc/irq/71/smp_affinity
    echo 40 > /proc/irq/84/smp_affinity
    
    #CPU7
    echo 80 > /proc/irq/72/smp_affinity
    echo 80 > /proc/irq/85/smp_affinity
    
    #CPU8
    echo 100 > /proc/irq/73/smp_affinity
    echo 100 > /proc/irq/86/smp_affinity
    
    #CPU9
    echo 200 > /proc/irq/74/smp_affinity
    echo 200 > /proc/irq/87/smp_affinity
    
    #CPU10
    echo 400 > /proc/irq/75/smp_affinity
    echo 400 > /proc/irq/80/smp_affinity
    
    #CPU11
    echo 800 > /proc/irq/76/smp_affinity
    echo 800 > /proc/irq/81/smp_affinity
    
    #CPU12
    echo 1000 > /proc/irq/77/smp_affinity
    echo 1000 > /proc/irq/82/smp_affinity
    
    #CPU13
    echo 2000 > /proc/irq/78/smp_affinity
    echo 2000 > /proc/irq/83/smp_affinity
    
    #CPU14
    echo 4000 > /proc/irq/70/smp_affinity
    #CPU15
    echo 8000 > /proc/irq/79/smp_affinity

    /proc/interrupts на этом сервере без нагрузки можно посмотреть тут. Не привожу это в заметке из-за громоздкости

    UPD:
    Если сервер работает только маршрутизатором, то тюнинг TCP стека особого значения не имеет. Однако есть параметры sysctl, которые позволяют увеличить размер кэша ARP, что может быть актуальным. При проблеме с размером ARP-кэша в dmesg будет сообщение «Neighbour table overflow».
    Например:
    net.ipv4.neigh.default.gc_thresh1 = 1024
    net.ipv4.neigh.default.gc_thresh2 = 2048
    net.ipv4.neigh.default.gc_thresh3 = 4096


    Описание параметров:
    gc_thresh1 — минимальное количество записей, которые должны быть в ARP-кэше. Если количество записей меньше, чем это значение, то сборщик мусора не будет очищать ARP-кэш.
    gc_thresh2 — мягкое ограничение количества записей в ARP-кэше. Если количество записей достигнет этого значения, то сборщик мусора запустится в течение 5 секунд.
    gc_thresh3 — жесткое ограничение количества записей в ARP-кэше. Если количество записей достигнет этого значения, то сборщик мусора незамедлительно запустится.
    Метки:
    Поделиться публикацией
    Комментарии 40
    • +5
      Замечательная заметка. Вообще такому тюнингу, в случае многоядерного сервера, место не только на роутерах. У нас 8-ядерный сервер вставал раком при копировании большого объёма данных по сети. Оказалось райд и сетевуха «сидели» на одном проце. Разнесли — всё устаканилось.
      • +2
        про FreeBSD хорошо написано тут, в целом же, linux лучше справляется со значительной сетевой нагрузкой
        • 0
          Это все здорово, а для роутера (Linksys WRT54GL) на DD-WRT можно такое сделать? Или ему уже не судьба больше 50 мбит в WAN прокачать?
          • +15
            если туда впаять xeon то справится.
          • +1
            видел в песочнице, пожалел, что кончились инвайты. Кстати, а почему нет пометки, что из песочницы? В последнее время она автоматически добавлялась под заголовком топика.
            • +1
              Потому что я получил инвайт за другой пост, а этот пост написал уже как зарегистрированный пользователь :)
              • +3
                понятно. Велкам!
            • +1
              В закладки, спасибо!
              • +3
                Полезная статья, спасибо!
                По-моему заголовок широк по смыслу, может быть, стоит переименовать в «Управление прерываниями в Linux»?
                • 0
                  за статью спасибо, однако не понятно, почему на 150 мегабитах в секунду встает эта проблема.

                  на моей практике:
                  раздача крупной статики на 400-600 мегабит/секунду ограничивалась дисковой подсистемой.
                  router\firewall на достаточно слабой (~гигагерц) однопроцессовой машине не загружен при 100 мегабитном потоке в одну сторону и 70 мегабитном в другую. 2 карточки (на самом деле их 3, не меняет сути дела).

                  Могу логи выдать в момент нагрузки, если интересно.

                  Вопрос, видимо к постановке проблемы, входящим условиям. Не к описанному методу решения.
                  • 0
                    В моем случае сервер занимается маршрутизацией и в обработке трафика принимают активное участие шейпер, ULOG, файрволл. Основную нагрузку дают шейпер и ULOG, маршрутизация и фильтрация — на их фоне практически не нагружают систему.
                    • +2
                      Было бы отлично еще сюда написать и про настройки шейпинга (в отдельном посте)
                      • +2
                        Это есть в планах, в том числе использование хэш-таблиц для меньшей нагрузки шейпира на систему.
                        • 0
                          шейпЕра, конечно же, извините. Просто изначально писал «шейпинга».
                          • 0
                            замечательно, будем ждать.
                          • +1
                            действительно, было бы неплохо.
                            Ну а если не хотите ждать, мне кажется здесь lartc.org/ есть всё
                            • 0
                              Спасибо, обязательно почитаю.
                          • 0
                            Выкиньте ULOG. ipt_netflow наше все!
                            • 0
                              Спасибо, обязательно попробую. Вообще ссылка на проект ipt_netflow уже с месяц висит в списке «Посмотреть, потестить», но никак руки не доходят.
                            • 0
                              ULOG можно заменить ipt_netflow. Неплохую разгрузку получите.
                          • 0
                            Давненько я не видел таких дельных статей! Автор — молодец! Спасибо за статью!
                            • +4
                              Если сервер работает только маршрутизатором, то тюнинг TCP стека особого значения не имеет. Однако есть параметры sysctl, которые позволяют увеличить размер кэша ARP, что может быть актуальным. Например:
                              net.ipv4.neigh.default.gc_thresh1 = 1024
                              net.ipv4.neigh.default.gc_thresh2 = 2048
                              net.ipv4.neigh.default.gc_thresh3 = 4096

                              Описания параметров добавлю в статью.
                              • 0
                                боюсь, что это преждевременная оптимизация опять. твикать ненагруженные узлы — только закапывать ошибки на будущее.
                                • 0
                                  Да, в комментах это не указал, но в статье написал. Увеличивать кэш ARP стоит только тогда, когда столкнетесь с сообщение в dmesg: «Neighbour table overflow».
                                • 0
                                  Эти параметры рекомендовано делать 1x, 4x и 8x
                                • +1
                                  если нет необходимости в conntrack его тоже надо отрубить
                                  *raw
                                  -A PREROUTING -j NOTRACK
                                  COMMIT

                                  увеличить очередь
                                  ifconfig eth0 txqueuelen 10000

                                  потом смотреть ошибки и по необходимости увеличить буфер
                                  ethtool -G eth0 rx 1024

                                  если надо conntrack то увеличивать
                                  net.ipv4.netfilter.ip_conntrack_max и /sys/module/ip_conntrack/parameters/hashsize
                                  и уменьшать интервалы
                                  net.ipv4.netfilter.ip_conntrack_icmp_timeout
                                  net.ipv4.netfilter.ip_conntrack_udp_timeout_stream
                                  net.ipv4.netfilter.ip_conntrack_udp_timeout
                                  net.ipv4.netfilter.ip_conntrack_tcp_timeout_close
                                  net.ipv4.netfilter.ip_conntrack_tcp_timeout_time_wait
                                  net.ipv4.netfilter.ip_conntrack_tcp_timeout_last_ack
                                  net.ipv4.netfilter.ip_conntrack_tcp_timeout_close_wait
                                  net.ipv4.netfilter.ip_conntrack_tcp_timeout_fin_wait
                                  net.ipv4.netfilter.ip_conntrack_tcp_timeout_established
                                  net.ipv4.netfilter.ip_conntrack_tcp_timeout_syn_recv
                                  net.ipv4.netfilter.ip_conntrack_tcp_timeout_syn_sent

                                  уменьшать количество правил iptables, если надо много однотипных списков то использовать ipset

                                  и пр. пр. пр.
                                  • +3
                                    вообще прерывания неплохо автоматом раскидываются таким скриптиком

                                    
                                    ncpus=`grep -ciw ^processor /proc/cpuinfo`
                                    test "$ncpus" -gt 1 || exit 1
                                    
                                    n=0
                                    for irq in `cat /proc/interrupts | grep eth | awk '{print $1}' | sed s/\://g`
                                    do
                                        f="/proc/irq/$irq/smp_affinity"
                                        test -r "$f" || continue
                                        cpu=$[$ncpus - ($n % $ncpus) - 1]
                                        if [ $cpu -ge 0 ]
                                                then
                                                    mask=`printf %x $[2 ** $cpu]`
                                                    echo "Assign SMP affinity: eth$n, irq $irq, cpu $cpu, mask 0x$mask"
                                                    echo "$mask" > "$f"
                                                    let n+=1
                                        fi
                                    done
                                    
                                    
                                    
                                    • 0
                                      >Нужно раскидать прерывания по процессорам. Чтобы это сделать нужно выполнить команду echo N > /proc/irq/X/smp_affinity

                                      По-моему, раскидывать вручную — лишнее. Есть irqbalance (http://www.irqbalance.org). И в ядре, вроде, есть встроеный балансировщик (CONFIG_IRQBALABCE)
                                      • 0
                                        irqbalance при большой нагрузке и большом кол-ве очередей сносит мозг :)
                                        • 0
                                          Преимущество ручного раскидывания — группировка определенных очередей на одном ядре.
                                          А когда на одном из серверов использовал irqbalance — неоднократные кернел паники, после чего он был отключен.
                                          Опция CONFIG_IRQBALANCE отсутствует начиная с версии 2.6.27, т.к. признана устаревшей.
                                        • 0
                                          Кстати про тюнинг параметров драйверов Вы тоже ни слова не написали, а тема очень обширна :)
                                          • 0
                                            А что посоветуете если в iptables большое количество записей >50k в этом случае оно медленно, но верное умирает.
                                            Ну кроме перехода на ipset :) возможно есть какие-нибудь твики?
                                            • +1
                                              Распределение прерываний по ядрам поможет и в этом случае. Еще стоит оптимизировать правила с использованием ветвлений.
                                              • +1
                                                есть твики centos.alt.ru/?p=32, только лучше ipset.

                                              • +1
                                                По поводу тюнинга параметров сетевого драйвера. Я сегодня работал с e1000, удалось сократить с 39K interrupts при дефолтовых настройках до 220 параметром InterruptThrottleRate=100

                                                rmmod e1000
                                                modprobe e1000
                                                procs -----------memory---------- ---swap-- -----io---- -system-- ----cpu----
                                                1 0 0 3680984 48968 35420 0 0 0 7 39964 35 0 1 99 0
                                                0 0 0 3680984 48968 35420 0 0 0 0 39954 33 0 1 99 0

                                                rmmod e1000
                                                modprobe e1000 InterruptThrottleRate=100
                                                0 0 0 3668768 49288 38696 0 0 0 0 220 35 0 0 100 0
                                                0 0 0 3668768 49288 38696 0 0 0 0 224 41 0 0 100 0

                                                Параметры e1000: www.intel.com/support/network/sb/CS-009209.htm
                                                • +2
                                                  Параметр InterruptThrottleRate (если значение >100) задает количество прерываний, генерируемых в секунду. Эффект аналогичен заданию значений rx-usecs tx-usecs утилитой ethtool, что является более гибким вариантом, поэтому этот параметр e1000 не описывал. Задержки между прерываниями позволяют снизить нагрузку за счет увеличения задержек обработки трафика. Я рекомендую снижать их как можно сильнее, вплоть до ноля, пока нагрузка в часы пик будет в пределах нормы. Соответственно параметр InterruptThrottleRate повышать, пока нагрузка в пределах нормы.
                                                • 0
                                                  какой профит? :)
                                                  • +1
                                                    «тюнинг TCP стека особого значения не имеет. Однако есть параметры sysctl, которые позволяют увеличить размер кэша ARP»

                                                    Кэш ARP не относится к тюнингу TCP-стека
                                                    • 0
                                                      В фразе подразумевался смысл «тюнинг TCP стека особого значения не имеет, однако имеет значение тюнинг ARP кэша». Пожалуй фразу мне следовало построить по-другому. В любом случае, спасибо за замечание :)
                                                    • 0
                                                      Не очень понял «Чем больше значение, тем выше задержка, но тем меньше нагрузка на процессор. Пробуйте уменьшать это значение в часы пик вплоть до ноля.» Если так, то уменьшение значения в часы пик приведёт к дополнительному росту нагрузки на процессор ведь?

                                                      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.