Большие потоки трафика и управление прерываниями в 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-кэше. Если количество записей достигнет этого значения, то сборщик мусора незамедлительно запустится.
    Поделиться публикацией
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама
    Комментарии 42
    • +5
      Замечательная заметка. Вообще такому тюнингу, в случае многоядерного сервера, место не только на роутерах. У нас 8-ядерный сервер вставал раком при копировании большого объёма данных по сети. Оказалось райд и сетевуха «сидели» на одном проце. Разнесли — всё устаканилось.
      • +2
        про FreeBSD хорошо написано тут, в целом же, linux лучше справляется со значительной сетевой нагрузкой
        • 0
          Это все здорово, а для роутера (Linksys WRT54GL) на DD-WRT можно такое сделать? Или ему уже не судьба больше 50 мбит в WAN прокачать?
          • +15
            если туда впаять xeon то справится.
          • +1
            видел в песочнице, пожалел, что кончились инвайты. Кстати, а почему нет пометки, что из песочницы? В последнее время она автоматически добавлялась под заголовком топика.
            • +1
              Потому что я получил инвайт за другой пост, а этот пост написал уже как зарегистрированный пользователь :)
          • +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
                                                    Не очень понял «Чем больше значение, тем выше задержка, но тем меньше нагрузка на процессор. Пробуйте уменьшать это значение в часы пик вплоть до ноля.» Если так, то уменьшение значения в часы пик приведёт к дополнительному росту нагрузки на процессор ведь?
                                                    • 0
                                                      Сейчас обнаружил интересный момент:
                                                      root@ххх:/proc/irq# echo 100000000 > 109/smp_affinity
                                                      -bash: echo: write error: Value too large for defined data type

                                                      Система не дает повесить прерывание на ядро > 31
                                                      В итоге повесил 9 прерываний от сетевой карты на 4 ядра. Тем не менее это дало прирост на сетевой карте +100 МБ/с на прием. В итоге из 10Гб карты мы выжали 1100ГБ/с вполне себе боевых данных, которые льются на машину.
                                                      Бешеный прирост производительности дали настройки дискового кэша — он непрерывно сбрасывается сейчас, а не ждет в памяти до победного, тормозя сетевую передачу во время сбрасывания данных.
                                                      dstat выглядит вот так:
                                                      ----total-cpu-usage---- -dsk/total- -net/total- ---paging-- ---system--
                                                      usr sys idl wai hiq siq| read writ| recv send| in out | int csw
                                                      15 12 70 0 0 2| 17M 1380M|1109M 28M| 0 0 | 469k 847k
                                                      15 12 70 0 0 3| 16M 1669M|1116M 28M| 0 0 | 481k 868k
                                                      14 12 72 0 0 2|8924k 1374M| 998M 25M| 0 0 | 438k 766k
                                                      15 13 69 0 0 3| 23M 1672M|1130M 28M| 0 0 | 482k 822k
                                                      15 12 70 0 0 3| 16M 1542M|1119M 28M| 0 0 | 482k 869k
                                                      15 13 69 0 0 3| 20M 1521M|1121M 28M| 0 0 | 483k 894k
                                                      15 12 70 0 0 3| 15M 1647M|1130M 28M| 0 0 | 482k 897k
                                                      15 12 71 1 0 2| 18M 1356M|1357M 34M| 0 0 | 477k 857k
                                                      15 13 69 0 0 2|8596k 1524M|1118M 28M| 0 0 | 478k 920k
                                                      15 12 70 0 0 3| 12M 1642M|1112M 28M| 0 0 | 479k 873k
                                                      15 12 70 0 0 2| 24M 1384M|1119M 28M| 0 0 | 473k 898k
                                                      15 13 70 0 0 3| 22M 1610M|1113M 28M| 0 0 | 477k 930k
                                                      15 12 70 0 0 2| 18M 1406M|1114M 28M| 0 0 | 472k 929k


                                                      Подскажите, какой язык надо выбирать, чтобы нормально отображался моноширинный текст?
                                                      • 0
                                                        Забыл сразу отписать. Таки позволяет, но немного другим способом:
                                                        echo 000001,00000000 > 109/smp_affinity

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