精华内容
下载资源
问答
  • Linux系统,问题定位 问题描述 网络电视台-红领巾之歌项目中,首页模块,100用户并发访问,发现应用服务器的带宽超过400mbps,大大超出现网环境百兆带宽要求,系统带宽性能不达标。   备注:测试环境拓扑图 ...

    Linux系统,问题定位

    问题描述

    网络电视台-红领巾之歌项目中,首页模块,100用户并发访问,发现应用服务器的带宽超过400mbps,大大超出现网环境百兆带宽要求,系统带宽性能不达标。

     

    备注:测试环境拓扑图

    图-1

    原因分析

    1.         通过Analysis打开LR测试结果,分析Windows Resources对IIS应用服务器的带宽监控数据(见图-2),可知应用服务器带宽不达标主要是接收带宽(流入的数据量)超过450mbps(45MB/s),发送带宽很小。应用服务器收到大量数据导致带宽瓶颈。

        

        

    图-2(单位MByte/s,绿线为接收带宽,红线为发送带宽)

    2.         由图-1测试环境拓扑结构可知,IIS应用服务器收到的数据可能来自三个部分:用户PC、文件接收站、数据库/redis服务器。分析上述三个部分传给IIS应用服务器的数据量大小:

    l  用户PC,就是测试中的LR负载机,负载机发送给IIS应用服务器的数据就LR脚本中的3个http请求(采用httpwatch分析可得到3个请求的数据量约5KB),100用户并发测试所产生的请求流量约500KB=0.5MB。可知负载机对应用服务器请求产生的流量不是带宽瓶颈的原因。

    l  根据LR测试结果Windows Resources(见图-3),可知文件接收站往应用服务器发生的流量约8.5KB=0.008MB。可知文件接收站对应用服务器的流量不是带宽瓶颈的原因。

    图-3(单位MByte/s,蓝线为发送带宽)

    l  打开对数据库/redis服务器的nmon监控结果,分析NET数据(见图-4),可知测试过程中该服务器平均每秒往IIS应用服务器发送约45000KB=45MB数据量,该数值和IIS应用服务器接收带宽基本一致,即可判断数据库/redis服务器产生的流量是造成应用服务器带宽瓶颈的原因。

    图-4

    3.         因为数据库/redis服务器上同时部署mysql数据库服务和redis缓存服务,IIS应用服务器和这两个服务均存在数据交互。为定位是哪一个服务产生大流量的数据,采用dump抓包工具分析数据库(3309端口)和redis服务(6379端口)产生的流量。抓包可知,单用户访问首页,redis服务给应用服务器返回约3MB的数据量,数据库返回的数据量很小。故可判断应用服务器带宽瓶颈是linux服务器上的redis服务返回数据量太大导致。

    4.         打开抓包文件20120803_redis.dmp,分析可知该文件内包含系统所有注册用户的信息(users表),活动动态中的文章信息、媒体报道信息等。这些数据有很大部分是用户访问首页不需要知道的。

    dump相关参考命令:

    time tcpdump -i eth0 -s 1500 dst  host 192.168.19.34 and src port 3309 -s 0  -w  20120803_mysql.dmp

    time tcpdump -i eth0 -s 1500 dst  host 192.168.19.34 and src port 6379 -s 0  -w  20120803_redis.dmp

    解决方案

     修改程序,按功能需要返回对应的redis数据。优化后,100用户并发访问首页,应用服务器的接收带宽仅1.4MB(约15mbps)

    展开全文
  • 如果这是一个ext2/ext3/ext4文件系统的固有问题,那么早在200X年问题就该被发现并解决了的,什么现在都2019年了还存在问题? 这肯定是新问题! 事后言,这是overlayfs的问题。但是锅的本源,还是vfs的设计问题...

    又是一个假期,早早早退,然而高速公路离家最近的出口封闭,无奈只能穿越景区,就为了去吃顿饭。饭罢了,该思考和总结了。


    大量线程争抢锁导致CPU自旋乃至内核hang住的例子层出不穷。

    我曾经解过很多关于这方面的内核bug:

    • nat模块复制tso结构不完全导致SSL握手弹证书慢。
    • IP路由neighbour系统对pointopoint设备的处理不合理导致争锁。
    • IPv6路由缓存设计不合理导致争锁。
    • Overlayfs的mount设计不合理导致争锁。

    本文解一例。即描述 Overlayfs的mount设计不合理导致争锁 问题以及相应的解法。

    后面几篇文章,我会描述其它的几个例子。

    Linux内核里存在着很多垃圾代码,盲从Linux内核视其无比神圣的人,便违背了规则丧失了独立。Linux内核不是神话,它只是一个可以运行的,功能性能还可以的一个操作系统内核,它给我们多了一种选择,除此之外便不是什么了。

    Linix内核并不完美,相反,没有什么是完美的,Linux内核存在很多缺陷!

    从业至今,很多个节假日前遇到 大量线程抢锁 的问题,但是但凡节假日,这种问题总是被我捕获并解锁,然后让我可以过一个比较爽的节假日。


    Linux内核解锁案例已经够多了,这种事很多人都知道,但都藏着掖着,觉得教会徒弟饿死师傅,因为一旦掌握了这些,这将是他们求职就业以及晋升的杀手锏。

    但我却不这么想。

    我觉得解决这种事都是些很Low的事情,你怎么能拿Linux内核的缺陷来当自己的资本呢?!

    无论如何,每当我发现Linux内核有这样那样给的缺陷,并且我比较感兴趣,我会怀着比较激动的心情解决之,然后将问题本身和解决方案完全公开。内核都公开了,问题还藏着吗?就算不看我的解决方案,社区十有八九早就已经把问题修复了…

    解锁的过程更多的是提供一种分析解决问题的过程和思路。

    我不是想解决问题,我只是想多玩一会儿而已。


    近日,发现有Docker环境在使能overlayfs的情况下,CPU利用率飙高,经过简单排查是mntput函数里面的一个自旋锁占用了大量的CPU,引发问题的应用程序都毫无例外地在执行频繁open/close文件的动作。确切地说,是 频繁的close操作 导致了问题。

    为什么?这是为什么?

    为什么在这个行业干了十几年了只有这次碰到了频繁打开关闭文件导致的问题?如果这是一个ext2/ext3/ext4文件系统的固有问题,那么早在200X年问题就该被发现并解决了的,为什么现在都2019年了还存在问题?

    这肯定是新问题!

    事后言,这是overlayfs的问题。但是锅的本源,还是vfs的设计问题,换句话说,这锅,overlayfs不该背。

    overlayfs在文件被close的时候,消耗了大量的CPU事件在一个根本没有必要的大写锁上。说它没有必要,也不是很应该,只是说说罢了。毕竟,事后所言,造成这个性能问题的根源,在于 一个优化没有做好。

    依然事后所言,你看,一个优化没有覆盖完全,还不如不优化,不优化的话还不会被埋冤。


    这一切还要从现象说起。

    Linux内核在操作任何结构体对象的时候,基本都遵循一个风格,即 懒惰释放 !不管有关无关,在释放结构对象的时候,只要涉及,Linux内核就会去dec其引用计数。这非常合理,但是Linux内核的vfs子系统却在这些之外,借用了一些trick。看我们看个究竟吧。


    无论是什么文件,当一个文件被close的时候,跟踪调用栈,最终会调用 mntput函数

    让我们看看 mntput函数 的逻辑,它最终由 mntput_no_expire 表示:

    
    static void mntput_no_expire(struct mount *mnt)
    {
    put_again:
        br_read_lock(&vfsmount_lock);
    
        // 问题出在这里!虽然是likely,但likely只是一厢情愿。
        // 问题是谁能保证每一个文件系统都能遵守规则去维护mnt_ns字段呢??
        // 比如overlayfs干脆就没有为upper/lower的mount结构设置mnt_ns!
        if (likely(mnt->mnt_ns)) { 
            /* shouldn't be the last one */
            mnt_add_count(mnt, -1);
            br_read_unlock(&vfsmount_lock);
            return;
        }
    
        br_read_unlock(&vfsmount_lock);
    
        br_write_lock(&vfsmount_lock);
        mnt_add_count(mnt, -1);
    
        // 超大概率会从下面的if块返回,因为文件close的mntput要比umount的mntput高频太多
        if (mnt_get_count(mnt)) { 
            br_write_unlock(&vfsmount_lock);
            return;
        }
        ...
    
    	// 下面是真正的最后清理工作,脱链表,释放内存等
    	// 只有在真正的mount结构销毁的时候才会到达这里,比如unmount操作
        list_del(&mnt->mnt_instance);
        br_write_unlock(&vfsmount_lock);
        mntfree(mnt);
    }
    
    

    我们知道的事实是,一个文件,关联于其上的是:

    • mount对象
    • mnt_namespace对象
    • file对象
    • inode对象

    其中,和Linux常见的对象之间的关系不同。mount对象和mnt_namespace对象的关系比较微妙,它们是 附着 关系,而不是 引用 关系。即:

    • mnt_namespace对象附着在mount对象上,而不是mount对象引用mnt_namespace对象。

    它们二者分别是独立被维护的。当mount对象初始化需要关联一个mnt_namesace对象时,有一个来附着便是,并不需要获取其引用计数。对应的,只有在umount操作时,才会清空mount对象的mnt_ns这个附着于其上mnt_namespace对象,注意⚠️,是直接赋空:

    
    void umount_tree(struct mount *mnt, int propagate)
    {
        LIST_HEAD(tmp_list);
        struct mount *p;
    
        for (p = mnt; p; p = next_mnt(p, mnt))
            list_move(&p->mnt_hash, &tmp_list);
    
        if (propagate)
            propagate_umount(&tmp_list);
    
        list_for_each_entry(p, &tmp_list, mnt_hash) {
            list_del_init(&p->mnt_expire);
            list_del_init(&p->mnt_list);
            __touch_mnt_namespace(p->mnt_ns);
    
            // 没有递减引用计数,直接赋值为NULL。
            p->mnt_ns = NULL;
    
            ...
    

    mntput_no_expire 中,文件系统非常巧妙但不合理地利用上述mount对象和mnt_namespace对象的这种关系 企图用来优化mount的put操作。

    优化效果还不错,但是如果并不理解或者某种原因不能利用上述关系的文件系统,将无法获得这个优化所带来的收益,成为优化效果的弃婴。

    overlayfs就是其中一例。overlayfs的mount对象将没有任何mnt_namespace附着于其上。所以操作overlayfs的每一个文件的每一次close操作,最终均会落到 br_write_lock 这个大写锁上,造成系统的CPU跑高。


    下面是复现和解决的步骤。

    为了离线重现和解决问题,采用下面的步骤。

    首先准备一个overlay文件系统:

    [root@localhost overlay]# mount -t overlay overlay -olowerdir=./lower,upperdir=./upper,workdir=./worker ./merge
    [root@localhost overlay]# tree
    .
    ├── lower
    ├── merge
    ├── upper
    └── worker
        └── work
    

    编写一个超级简单的不断open/close的程序:

    /* loop.c */
    
    #include <unistd.h>
    #include <fcntl.h>
    #include <stdio.h>
    
    void loop(unsigned char *file)
    {
        int fd;
    
        while (1) {
            fd = open(file, O_RDWR|O_CREAT);
            close(fd);
        }
    }
    
    int main(int argc, char **argv)
    {
        loop(argv[1]);
        return 0;
    
    }
    

    执行10个进程(太多也行,但我的虚拟机撑不住…),不断打开关闭overlayfs的文件:

    
    [root@localhost overlay]# ./a.out merge/test &
    [1] 1834
    [root@localhost overlay]# ./a.out merge/test &
    [2] 1835
    [root@localhost overlay]# ./a.out merge/test &
    [3] 1836
    [root@localhost overlay]# ./a.out merge/test &
    [4] 1837
    [root@localhost overlay]# ./a.out merge/test &
    [5] 1838
    [root@localhost overlay]# ./a.out merge/test &
    [6] 1839
    [root@localhost overlay]# ./a.out merge/test &
    [7] 1840
    [root@localhost overlay]# ./a.out merge/test &
    [8] 1841
    [root@localhost overlay]# ./a.out merge/test &
    [9] 1842
    [root@localhost overlay]# ./a.out merge/test &
    [10] 1843
    
    

    下面是perf热点:

    在这里插入图片描述

    systemtap确认在调用sys_close进而到达mntput_no_expire时,mount的mnt_ns字段确实是NULL。问题确认。

    问题是为什么overlayfs的mount 对象会没有mnt_namespace?

    按照上述vfs mount对象和mnt_namespace对象的关系的分析,除非是频度很低的umount操作,否则一个正常的mount对象的mnt_ns字段一定不会为NULL,就一定会进入只有read lock的快速路径。

    那么overlayfs的mount对象便属于少见的不正常的mount对象。

    我猜是作者遗漏了。

    我们看overlayfs的mount的生成过程:

    static int ovl_fill_super(struct super_block *sb, void *data, int silent)
    {
        ...
        if (ufs->config.upperdir) {
            ufs->upper_mnt = clone_private_mount(&upperpath);
        ....
        for (i = 0; i < numlower; i++) {
            struct vfsmount *mnt = clone_private_mount(&stack[i]);
    

    根源在 clone_private_mount 函数:

    // 看注释,原来是故意的。
    /**
     * clone_private_mount - create a private clone of a path
     * This creates a new vfsmount, which will be the clone of @path.  The new will
     * not be attached anywhere in the namespace and will be private (i.e. changes
     * to the originating mount won't be propagated into this).
     * Release with mntput().
     */
    struct vfsmount *clone_private_mount(struct path *path)
    {
        struct mount *old_mnt = real_mount(path->mnt);
        struct mount *new_mnt;
    
        if (IS_MNT_UNBINDABLE(old_mnt))
            return ERR_PTR(-EINVAL);
    
        down_read(&namespace_sem);
        new_mnt = clone_mnt(old_mnt, path->dentry, CL_PRIVATE);
        up_read(&namespace_sem);
        if (IS_ERR(new_mnt))
            return ERR_CAST(new_mnt);
    
        return &new_mnt->mnt;
    }
    

    看注释就够了:

    The new will not be attached anywhere in the namespace and will be private (i.e. changes to the originating mount won’t be propagated into this).

    所以从注释的建议看,mount对象从一开始创建就没有打算附着任何mnt_namespace,只是为了对外不可见!也因为如此,无法在mntput_no_expire中被优化。

    是迎合内核函数API的注释要求,还是迎合优化trick?二者可以兼得!

    为overlayfs的mount对象附着一个脱链的dummy mnt_namespace对象不就得了吗?既对外不可见,又可以被优化。

    缺什么,加上便是了。

    基于RH centos 7.2的 Linux kernel 3.10 内核的patch如下:

    
    --- super.c.orig    2019-05-23 20:43:30.071000000 +0800
    +++ super.c    2019-05-23 19:58:33.800000000 +0800
    @@ -18,7 +18,9 @@
     #include <linux/sched.h>
     #include <linux/statfs.h>
     #include <linux/seq_file.h>
    +#include <linux/kallsyms.h>
     #include "overlayfs.h"
    +#include "mount.h"
    
     MODULE_AUTHOR("Miklos Szeredi <miklos@szeredi.hu>");
     MODULE_DESCRIPTION("Overlay filesystem");
    @@ -884,6 +886,7 @@
         return ctr;
     }
    
    +struct mnt_namespace *(*sym_create_mnt_ns)(struct vfsmount *mnt);
     static int ovl_fill_super(struct super_block *sb, void *data, int silent)
     {
         struct path upperpath = { NULL, NULL };
    @@ -1011,6 +1014,13 @@
                 pr_err("overlayfs: failed to clone upperpath\n");
                 goto out_put_lowerpath;
             }
    +
    +        if (sym_create_mnt_ns) {
    +            struct mnt_namespace *stub = sym_create_mnt_ns(ufs->upper_mnt);
    +            if (stub) {
    +                printk("upper stub ok\n");
    +            }
    +        }
    
             ufs->workdir = ovl_workdir_create(ufs->upper_mnt, workpath.dentry);
             err = PTR_ERR(ufs->workdir);
    @@ -1039,6 +1049,12 @@
              * will fail instead of modifying lower fs.
              */
             mnt->mnt_flags |= MNT_READONLY;
    +        if (sym_create_mnt_ns) {
    +            struct mnt_namespace *stub = sym_create_mnt_ns(mnt);
    +            if (stub) {
    +                printk("lower stub ok\n");
    +            }
    +        }
    
             ufs->lower_mnt[ufs->numlower] = mnt;
             ufs->numlower++;
    @@ -1134,6 +1150,7 @@
    
     static int __init ovl_init(void)
     {
    +    sym_create_mnt_ns = (void *)kallsyms_lookup_name("create_mnt_ns");
         return register_filesystem(&ovl_fs_type);
     }
    
    

    这个patch里还埋着个大坑。它会内存泄露!

    上面的分析结论明确指出,mount对象和mnt_namespace对象是独立维护的,mount对象并没有拿mnt_namespace对象的引用计数,只是在mount时赋值,在umount时赋NULL即可。

    之所以可以如此鲁莽,是因为内核的vfs子系统将所有的mount对象按照mnt_namespace组织成了N棵树,每一个mnt_namespace对象都会有一棵mount树。所以说,除非是mount根,否则其所有的mnt_namespace均继承mount根的mnt_namespace。

    mount根拿mnt_namespace就够了。mount根释放前,mnt_namespace不会释放。而mount子对象又不会在mount根对象之后释放,所以 维护好mount根的前提下,二者有此必有彼!

    overlayfs作为一个堆叠组合的文件系统,本身并非内核vfs子系统内建的文件系统类型,因此不应该纳入到这个树形结构体系。

    因此,在这个patch中,我平白无故的给mount对象new了一个mnt_namespace对象,这个mnt_namespace对象并不属于树形体系,因此,既然我分配了它,我就要负责销毁它!

    那么如何销毁以及在哪销毁呢?

    应该在overlayfs的super超级块的ovl_put_super回调函数中进行销毁。将在fill_super里分配的若干mnt_namespace对象给销毁,这才完美!这才不至于内存泄漏!

    static void ovl_put_super(struct super_block *sb)
    {
        struct ovl_fs *ufs = sb->s_fs_info;
        unsigned i;
        struct mount *m;
    
        dput(ufs->workdir);
        m = real_mount(ufs->upper_mnt);
        if (m->mnt_ns)
            put_mnt_ns(m->mnt_ns);
        mntput(ufs->upper_mnt);
        for (i = 0; i < ufs->numlower; i++) {
            m = real_mount(ufs->lower_mnt[i]);
            if (m->mnt_ns)
                put_mnt_ns(m->mnt_ns);
            mntput(ufs->lower_mnt[i]);
        }
    
        kfree(ufs->config.lowerdir);
        kfree(ufs->config.upperdir);
        kfree(ufs->config.workdir);
        kfree(ufs);
    }
    

    值得注意的是,高版本内核,即 >3.10 版本的内核,它们下意识将rw lock/spinlock改成了rcu lock,但是这并不正确!

    将spinlock改成rwlock,将rwlock改成rcu lock,这几乎成了一个范式,但是真正懂这个范式的并不多!

    本文描述的问题,根本就不在于用什么锁的问题,根本在于mount对象和mnt_namespace对象的关系以及它们如何结合的问题!

    我们来看最新的,或者说比较新的5.2内核:
    在这里插入图片描述

    最终,当mnt_ns存在的判断失败后,还是会掉入万劫不复的自旋锁的:
    在这里插入图片描述

    还是老问题,谁能确保mount对象的mnt_ns就一定会被设置呢?

    此时,你可能需要重新思考Linux内核社区的那些所谓的文人墨客。怼天怼地整天show me the code的放旷之外,其实他们也会犯错。

    在很多人看来他们写出了不正确的代码,然而在社区看来确实根深蒂固而又铁石心肠地认为这是佳作,这无疑证明Linux内核社区根本就是一个熟人社区。

    这里必须要提到的一个人,David Miller,此人崇尚代码的简介可维护可复用,有点过头,以至于他觉得代码整洁之道带来的性能问题都是无关紧要的,together with一群卫道的尚士,也就不多说了。

    David Miller代码整洁之道无可厚非,然则其为此引入的性能bug也是众矢之的!从业的几年中,遭遇了其不下五次的自行引入的bug,以至于…

    以至于每当我排查分析网络性能问题的时候,90%的概率,呃…至少80%的概率吧,按照David Miller的范式风格去排查,基本也就药到病除了…

    不是为了怼而怼,而是他真真的出了差错。而且不止一次。

    关于细节,今天太晚,且听我来日分解,明早演绎。


    浙江温州皮鞋湿,下雨进水不会胖。

    展开全文
  • 今天终于回忆起了2016年初解决的neigh锁的问题,并进行了溯源,发现二者竟然是兄弟问题啊!两件事联系起来,竟然貌似得到了一种 解决问题并引入Bug的模式 ,哈哈,原来写Bug也是和经验以及风格相关的哦! ...

    ZheJiang WenZhou skinshoe ? wet,rain ☔️flooding water will not fat!


    事情过去很久了,2016年初的事了,当时排查了一起自旋锁导致CPU飙高的问题,当时没有把问题记录下来,现在趁着假期重新回顾。

    引用下面的一篇文章,隐隐约约记录着些来龙去脉。
    Linux3.5内核对路由子系统的重构对Redirect路由以及neighbour子系统的影响: https://blog.csdn.net/dog250/article/details/50754780

    好吧,下面才是细节。

    正文

    别以为数据包出了路由子系统,拿到了 下一跳的dst entry 就从此大吉大利了,七七八十一难还差四十九难呢!

    数据包在通过了路由查找逻辑后,下一跳dst entry会附着在数据包skb本身,指导数据包真正发送。具体的发送逻辑由 邻居子系统 来统筹安排。

    不同的网卡类型对接的是不同的网络,这意味着数据包从不同的网卡发送时的行为将有所不同。比如对于以太网或者NBMA网络这种多点接入的网络,需要一个明确的gateway作为下一跳,否则数据包不知道发给谁:

    ip route add 100.100.100.0/24 via 192.168.56.40
    

    而对于类似点对点的网络,数据包只需要从简单发送到网线上即可,因为对端肯定只有一个设备,它不收谁收:

    ip route add 100.100.100.0/24 dev tunp2p
    

    比较复杂的情况是上述的多点接入网络,由于需要指定一个明确的下一跳,比如上述例子中的192.168.56.40,而和这个所谓的下一跳同等地位的设备可能会非常多,为了保证数据包仅仅发给它而不是错误地发给别的设备,那么就需要一种机制来保证在更底层的协议层面,保证正确的寻址,从而确保数据包被正确投递。

    不用说大家也知道,这就是 地址解析协议, 对于以太网而言,解析IPv4下一跳用的是ARP协议,解析IPv6下一跳用的是ICMPv6协议(注意!不存在ARPv6!)。

    Linux作为常用的服务器系统以及lastmile转发设备的系统,几乎最常遇到的网络类型就是以太网了。而Linux的ARP可以说是实现的非常完备,没啥说的,可以说,Linux内核的邻居解析就是 专门为以太网设计的。这点不多说,如果谁手边有Linux系统的网卡直接接入了非以太网,比如老式的X.25这种,请求让我登录玩玩。

    那么,当Linux面对非以太网设备时,比如POINTOPOINT设备时,就玩不转了。毕竟这是Linux非典型的应用场景。

    在描述问题之前,我们先来看一下迄至5.0-rc2版本的Linux内核邻居子系统是 如何定义邻居 的。

    • 对于多点接入网络:下一跳gatewat即邻居
    • 对于点对点网络:目标IP即邻居

    如果再看看Linux内核是如何管理这些邻居的,会出现什么问题就一目了然了。还是老方法,一张图足以解释,胜过代码分析:
    在这里插入图片描述
    试想一下一个极端情况,你在一个满负载B类网段的同一个以太网同时往65534台机器同时发包会怎样,答案是大量的邻居会被创建,争抢那些write lock!

    事实上,没人会用这么大一个以太网,要是真用了,光是各种广播就把网络给flood掉了吧。我们平时常用的C类网段在服务器动辄24+核的机器上,不足以让这个问题暴露。

    那么我们如何复现这个问题以证实它确实是个问题呢?这也简单,我们用虚拟的POINTOPOINT设备来复现。

    被测机,设为C1:

    • 配置:
    ip addr add dev enp0s3 192.168.56.101/24
    
    ip tun add test mode ipip remote 1.1.1.2 local 1.1.1.1
    ip link set test up
    ip add add 2.2.2.1 brd 255.255.255.255 peer 2.2.2.2 dev test
    
    iptables -t mangle -A OUTPUT -p udp -j MARK --set-mark 100
    iptables -t mangle -A OUTPUT -p udp -j MARK --set-mark 100
    
    ip ru add fwmark 100 tab vtab
    
    ip r add 0.0.0.0/0 dev test tab vtab
    

    测试机,设为C2:

    • 配置:
    ip addr add dev enp0s3 192.168.56.201/24
    

    跑下面的脚本,绑定不同的源IP地址往C1上发包,触发C1往不同IP回包创建邻居,可以多跑几个实例。脚本如下:

    #!/usr/bin/python
    from scapy.all import *
    import socket
    
    msg = "aa"
    
    while True:
        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        s.setsockopt(socket.IPPROTO_IP, 19, 1)
        addr = RandIP()
        try:
            s.bind((str(addr), 1234))
        except Exception as e:
            pass
        address = ('192.168.56.101', 31500)  
        try:
            # 往C1发包,触发其疯狂回复ICMP unreachable,以创建邻居
            s.sendto(msg, address)
        except Exception as e:
            #print e
            pass
        s.close()
    

    此时,在C1上执行:

    ip nei ls nud noarp
    

    你将绝望地看到一刹那时间数以万计的neigh被创建了出来,并且这个过程会一直持续,很快触发GC,于是乎大家都在争锁。

    top结果以及perf top的结果请测试后自行拉取。

    和之前关于IPv6以及inet peer的问题几乎一模一样,都是锁的不合理使用导致的,就连我这几幅图都是同一个图复制小修小改的。

    光破不立不是真本事,既然是锁导致的,那就解锁呗。

    其实,这个问题我在2016年初就遇到并解决了,参见:
    https://blog.csdn.net/dog250/article/details/50754780
    今天只是重新梳理,就着几天前解决的那个IPv6的soft lockup问题,我发现它们竟然属于同一类,所以就做个一致性的总结。

    当时还提了一个patch:
    https://lore.kernel.org/patchwork/patch/652657/
    然而并没有人搭理我这种虽然对内核感兴趣但并不care它的外人。David Miller不理人。倒是西邮王聪说了几句,他的意思是说,我这个patch回滚了David Miller的一个bugfix:

    Well, you just basically revert another bug fix:

    commit 0bb4087cbec0ef74fd416789d6aad67957063057
    Author: David S. Miller davem@davemloft.net
    Date: Fri Jul 20 16:00:53 2012 -0700

    但如果真的回滚了一个bug的fix,那为什么不采用另外的方案呢?

    我还被怼了几句,内核社区就一熟人社区,谁认识谁啊!我超级看不惯那些社区的人,牛X轰轰的。

    时间到了2018年,又有人提出了类似的patch:
    https://patchwork.ozlabs.org/patch/860390/
    同样的,没有得到回应!

    正确的做法应该是这个样子的:

    在这里插入图片描述
    修正这个问题,非常之简单,patch如下:

    diff --git a/net/ipv4/ip_output.c b/net/ipv4/ip_output.c
    index 64878ef..d7c0594 100644
    --- a/net/ipv4/ip_output.c
    +++ b/net/ipv4/ip_output.c
    @@ -202,6 +202,8 @@ static int ip_finish_output2(struct net *net, struct sock *sk, struct sk_buff *s
     
     	rcu_read_lock_bh();
     	nexthop = (__force u32) rt_nexthop(rt, ip_hdr(skb)->daddr);
    +	if (dev->flags & (IFF_LOOPBACK | IFF_POINTOPOINT))
    +		nexthop = 0;
     	neigh = __ipv4_neigh_lookup_noref(dev, nexthop);
     	if (unlikely(!neigh))
     		neigh = __neigh_create(&arp_tbl, &nexthop, dev, false);
    

    问题是解决了,然而这不算完。

    需要明确的几个问题:

    • 为什么内核社区没有发现这个问题?
    • 这个问题是固有的,还是半途被引入的?

    又该溯源了。

    来吧,看看这个:
    https://lists.openwall.net/netdev/2012/07/20/199

    在这里插入图片描述
    就是这样被引入的。其实在上面贴的那个 ipv4: Make neigh lookup keys for loopback/point-to-point devices be INADDR_ANY patch中,也有提到:
    在这里插入图片描述
    David Miller的风格我是不敢苟同的,他可能是一个优秀的代码设计者,却不是一个优秀的工程师,而绝顶的高手,一般是all for one的类型,很遗憾,显然David Miller并不是,他只是一个coder。

    这并不是David Miller的第一次,前文中我描述的IPv6的问题,都是如此风格一致地引入了锁的问题。在添加新的功能或者解决旧的Bug时,David Miller习惯于以下的策略:

    • 试图用一种统一的方式,去处理所有的场景,以保持代码的简洁明了!

    当然,所有的maintainer都这样。换位思考,也不是不应该。

    在IPv6的soft lockup问题的引入中,David Miller采用了 "always" 这个词,而在这个neigh lock问题的引入中,David Miller采用了 "entirely" 这个词,这足以显现其风格。

    以至于,再出现类似的问题导致的故障,我必须去review一下David Miller提交的所有patch了,十有八九会中标。

    缘由

    • 2016年初解决了这个neigh锁导致的CPU stall问题
    • 2019年初解决了那个IPv6 rt cache锁导致的CPU stall问题

    两件事看起来都是离散的独立事件,但是在解决完IPv6的rt cache CPU stall问题后,总觉得似曾相识…

    今天终于回忆起了2016年初解决的neigh锁的问题,并进行了溯源,发现二者竟然是兄弟问题啊!两件事联系起来,竟然貌似得到了一种 解决问题并引入Bug的模式 ,哈哈,原来写Bug也是和经验以及风格相关的哦!


    ZheJiang WenZhou skinshoe ? wet,rain ☔️flooding water will not fat!

    展开全文
  • Linux转发性能评估与优化(转发瓶颈分析与解决方案)

    万次阅读 多人点赞 2015-06-28 00:22:26
    线速问题很多人对这个线速概念存在误解。认为所谓线速能力就是路由器/交换机就像一根网线一样。而这,是不可能的。应该考虑到的一个概念就是延迟。数据包进入路由器或者交换机,存在一个核心延迟操作,这就是选路,...

    线速问题

    很多人对这个线速概念存在误解。认为所谓线速能力就是路由器/交换机就像一根网线一样。而这,是不可能的。应该考虑到的一个概念就是延迟。数据包进入路由器或者交换机,存在一个核心延迟操作,这就是选路,对于路由器而言,就是路由查找,对于交换机而言,就是查询MAC/端口映射表,这个延迟是无法避开的,这个操作需要大量的计算机资源,所以不管是路由器还是交换机,数据包在内部是不可能像在线缆上那样近光速传输的。类比一下你经过十字街头的时候,是不是要左顾右盼呢?

           那么,设备的线速能力怎么衡量呢?如果一个数据包经过一个路由器,那么延迟必览无疑,可是设备都是有队列或者缓冲区的,那么试想一个数据包紧接一个数据包从输入端口进入设备,然后一个数据包紧接一个数据包从输出端口发出,这是可以做到的,我们对数据包不予编号,因此你也就无法判断出来的数据包是不是刚刚进去的那个了,这就是线速。

           我们可以用电容来理解转发设备。有人可能会觉得电容具有通高频阻低频的功效,我说的不是这个,所以咱不考虑低频,仅以高频为例,电容具有存储电荷的功能,这就类似存储转发,电容充电的过程类似于数据包进入输入队列缓冲区,电容放电的过程类似于数据包从输出缓冲区输出,我们可以看到,在电流经过电容的前后,其速度是不变的,然而针对具体的电荷而言,从电容放出的电荷绝不是刚刚在在另一侧充电的那个电荷,电容的充电放电拥有固有延迟。

           我们回到转发设备。对于交换机和路由器而言,衡量标准是不同的。

           对于交换机而言,线速能力是背板总带宽,因为它的查表操作导致的延迟并不大,大量的操作都在数据包通过交换矩阵的过程,因此背板带宽直接导致了转发效率。而对于路由器,衡量标准则是一个端口每秒输入输出最小数据包的数量,假设数据包以每秒100个进入,每秒100个流出,那么其线速就是100pps。

           本文针对路由器而不针对交换机。路由器的核心延迟在路由查找,而这个查找不会受到数据包长度的影响,因此决定路由器线速能力的核心就在数据包输出的效率,注意,不是数据包输入的效率,因为只要队列足够长,缓存足够大,输入总是线速的。但是输入操作就涉及到了如何调度的问题。这也就说明了为何很多路由器都支持输出流量控制而不是输入流量控制的原因,因为输入流控即使完美完成,它也会受到路由器输出端口自身输出效率的影响,流控结果将不再准确。

           在写这个方案的前晚,有一个故事。我最近联系到了初中时一起玩摇滚玩音响的超级铁的朋友,他现在搞舞台设计,灯光音响之类的。我问他在大型舞台上,音箱摆放的位置不同,距离后级,前置,音源也不同,怎么做到不同声道或者相同声道的声音同步的,要知道,好的耳朵可以听出来毫秒级的音差...他告诉我要统一到达时间,即统一音频流到达各个箱子的时间,而这要做的就是调延迟,要求不同位置的箱子路径上要有不同的延迟。这对我的设计方案的帮助是多么地大啊。

           然后,在第二天,我就开始整理这个令人悲伤最终心碎的Linux转发优化方案。

    声明本文只是一篇普通文章,记录这个方案的点点滴滴,并不是一个完整的方案,请勿在格式上较真,内容上也只是写了些我认为重要且有意思的。完整的方案是不便于以博文的形式发出来的。见谅。

    问题综述

    Linux内核协议栈作为一种软路由运行时,和其它通用操作系统自带的协议栈相比,其效率并非如下文所说的那样非常低。然而基于工业路由器的评判标准,确实是低了。
     
           市面上各种基于Linux内核协议栈的路由器产品,甚至网上也有大量的此类文章,比如什么将Linux变成路由器之类的,无非就是打开ip_forward,加几条iptables规则,搞个配置起来比较方便的WEB界面...我想说这些太低级了,甚至超级低级。我很想谈一下关于专业路由器的我的观点,但是今天是小小的生日,玩了一天,就不写了。只是把我的方案整理出来吧。

           Linux的转发效率到底低在哪儿?如何优化?这是本文要解释的问题。依然如故,本文可以随意转载并基于这个思路实现编码,但是一旦用于商业目的,不保证没有个人或组织追责,因此文中我尽量采用尽可能模糊的方式阐述细节。

    瓶颈分析概述


    1.DMA和内存操作

    我们考虑一下一个数据包转发流程中需要的内存操作,暂时不考虑DMA。
    *)数据包从网卡拷贝到内存
    *)CPU访问内存读取数据包元数据
    *)三层报头修改,如TTL
    *)转发到二层后封装MAC头
    *)数据包从内存拷贝到输出网卡
    这几个典型的内存操作为什么慢?为什么我们总是对内存操作有这么大的意见?因为访问内存需要经过总线,首先总线竞争(特别在SMP和DMA下)就是一个打群架的过程,另外因为内存自身的速度和CPU相比差了几个数量级,这么玩下去,肯定会慢啊!所以一般都是尽可能地使用CPU的cache,而这需要一定的针对局部性的数据布局,对于数据包接收以及其它IO操作而言,由于数据来自外部,和进程执行时的局部性利用没法比。所以必须采用类似Intel I/OAT的技术才能改善。

    1.1.Linux作为服务器时

    采用标准零拷贝map技术完全胜任。这是因为,运行于Linux的服务器和线速转发相比就是个蜗牛,服务器在处理客户端请求时消耗的时间是一个硬性时间,无法优化,这是代偿原理。Linux服务唯一需要的就是能快速取到客户端的数据包,而这可以通过DMA快速做到。本文不再具体讨论作为服务器运行的零拷贝问题,自己百度吧。

    1.2.Linux作为转发设备时

    需要采用DMA映射交换的技术才能实现零拷贝。这是Linux转发性能低下的根本。由于输入端口的输入队列和输出端口的输出队列互不相识,导致了不能更好的利用系统资源以及多端口数据路由到单端口输出队列时的队列锁开销过大,总线争抢太严重。DMA影射交换需要超级棒的数据包队列管理设施,它用来调度数据包从输入端口队列到输出端口队列,而Linux几乎没有这样的设施。

    虽然近年在路由器领域有人提出了输入队列管理,但是这项技术对于Linux而言就是另一个世界,而我,把它引入了Linux世界。

    2.网卡对数据包队列Buff管理

    在Linux内核中,几乎对于所有数据结构,都是需要时alloc,完毕后free,即使是kmem_cache,效果也一般,特别是对于高速线速设备而言(skb内存拷贝,若不采用DMA,则会频繁拷贝,即便采用DMA,在很多情况下也不是零拷贝)。

           即使是高端网卡在skb的buffer管理方面,也没有使用完全意义上的预分配内存池,因此会由于频繁的内存分配,释放造成内存颠簸,众所周知,内存操作是问题的根本,因为它涉及到CPU Cache,总线争抢,原子锁等,实际上,内存管理才是根本中的根本,这里面道道太多,它直接影响CPU cache,后者又会影响总线...从哪里分配内存,分配多少,何时释放,何时可以重用,这就牵扯到了内存区域着色等技术。通过分析Intel千兆网卡驱动,在我看来,Linux并没有做好这一点。

    3.路由查找以及其它查找操作

    Linux不区分对待路由表和转发表,每次都要最长前缀查找,虽然海量路由表时trie算法比hash算法好,但是在路由分布畸形的情况下依然会使trie结构退化,或者频繁回溯。路由cache效率不高(查询代价太大,不固定大小,仅有弱智的老化算法,导致海量地址访问时,路由cache冲突链过长),最终在内核协议栈中下课。

           如果没有一个好的转发表,那么Linux协议栈在海量路由存在时对于线速能力就是一个瓶颈,这是一个可扩展性问题。

           另外,很多的查询结果都是可以被在一个地方缓存的,但是Linux协议栈没有这种缓存。比如,路由查询结果就是下一跳,而下一跳和输出网卡关联,而输出网卡又和下一跳的MAC地址以及将要封装的源MAC地址关联,这些本应该被缓存在一个表项,即转发表项内,然而Linux协议栈没有这么做。

    4.不合理的锁

    为何要加锁,因为SMP。然而Linux内核几乎是对称的加锁,也就是说,比如每次查路由表时都要加锁,为何?因为怕在查询的期间路由表改变了...然而你仔细想想,在高速转发情景下,查找操作和修改操作在单位时间的比率是多少呢?不要以为你用读写锁就好了,读写锁不也有关抢占的操作吗(虽然我们已经建议关闭了抢占)?起码也浪费了几个指令周期。这些时间几率不对称操作的加锁是不必要的。

           你只需要保证内核本身不会崩掉即可,至于说IP转发的错误,不管也罢,按照IP协议,它本身就是一个尽力而为的协议。

    5.中断与软中断调度

    Linux的中断分为上半部和下半部,动态调度下半部,它可以在中断上下文中运行,也可以在独立的内核线程上下文中运行,因此对于实时需求的环境,在软中断中处理的协议栈处理的运行时机是不可预知的。Linux原生内核并没有实现Solaris,Windows那样的中断优先级化,在某些情况下,Linux靠着自己动态的且及其优秀的调度方案可以达到极高的性能,然而对于固定的任务,Linux的调度机制却明显不足。

           而我需要做的,就是让不固定的东西固定化。

    6.通用操作系统内核协议栈的通病

    作为一个通用操作系统内核,Linux内核并非仅仅处理网络数据,它还有很多别的子系统,比如各种文件系统,各种IPC等,它能做的只是可用,简单,易扩展。

           Linux原生协议栈完全未经网络优化,且基本装机在硬件同样也未经优化的通用架构上,网卡接口在PCI-E总线上,如果DMA管理不善,总线的占用和争抢带来的性能开销将会抵消掉DMA本意带来的好处(事实上对于转发而言并没有带来什么好处,它仅仅对于作为服务器运行的Linux有好处,因为它只涉及到一块网卡)

    [ 注意,我认为内核处理路径并非瓶颈,这是分层协议栈决定的,瓶颈在各层中的某些操作,比如内存操作(固有开销)以及查表操作(算法不好导致的开销)]

    综述:Linux转发效率受到以下几大因素影响


    IO/输入输出的队列管理/内存修改拷贝 (重新设计类似crossbar的队列管理实现DMA ring交换)
    各种表查询操作,特别是最长前缀匹配,诸多本身唯一确定的查询操作之间的关联没有建立
    SMP下处理器同步(锁开销)(使用大读锁以及RCU锁)以及cache利用率
    中断以及软中断调度

    Linux转发性能提升方案

    概述

    此方案的思路来自基于crossbar的新一代硬件路由器。设计要点:

    1.重新设计的DMA包管理队列( 思路来自Linux O(1)调度器,crossbar阵列以及VOQ[虚拟输出队列]
    2.重新设计的基于定位而非最长前缀查找的转发表
    3.长线程处理(中断线程化,处理流水线化,增加CPU亲和)
    4.数据结构无锁化(基于线程局部数据结构)
    5.实现方式
    5.1.驱动以及内核协议栈修改
    5.2.完全的用户态协议栈
    5.3.评估:用户态协议栈灵活,但是在某些平台要处理空间切换导致的cache/tlb/mmu表的flush问题

    内核协议栈方案

    优化框架

    0.例行优化

    1).网卡多队列绑定特定CPU核心(利用RSS特性分别处理TX和RX)
    [ 可以参见《Effective Gigabit Ethernet Adapters-Intel千兆网卡8257X性能调优]
    2).按照包大小统计动态开关积压延迟中断ThrottleRate以及中断Delay(对于Intel千兆卡而言)
    3).禁用内核抢占,减少时钟HZ,由中断粒度驱动(见上面)
    4).如果不准备优化Netfilter,编译内核时禁用Netfilter,节省指令
    5).编译选项去掉DEBUG和TRACE,节省指令周期
    6).开启网卡的硬件卸载开关(如果有的话)
    7).最小化用户态进程的数量,降低其优先级
    8).原生网络协议栈优化
        由于不再作为通用OS,可以让除了RX softirq的task适当饥饿
        *CPU分组(考虑Linux的cgroup机制),划一组CPU为数据面CPU,每一个CPU绑定一个RX softirq或者
        *增加rx softirq一次执行的netdev_budget以及time limit,或者
        *只要有包即处理,每一个。控制面/管理面的task可以绑在别的CPU上。

    宗旨:
    原生协议栈的最优化方案

    1.优化I/O,DMA,减少内存管理操作

        1).减少PCI-E的bus争用,采用crossbar的全交叉超立方开关的方式
            [ Tips:16 lines 8 bits PCI-E总线拓扑(非crossbar!)的网络线速不到满载60% pps]
        2).减少争抢式DMA,减少锁总线[Tips:优化指令LOCK,最好采用RISC,方可调高内核HZ]
            [ Tips:交换DMA映射,而不是在输入/输出buffer ring之间拷贝数据!现在,只有傻逼才会在DMA情况拷贝内存,正确的做法是DMA重映射,交换指针!]
        3).采用skb内存池,避免频繁内存分配/释放造成的内存管理框架内的抖动
            [ Tips:每线程负责一块网卡(甚至输入和输出由不同的线程负责会更好),保持一个预分配可循环利用的ring buffer,映射DMA]

    宗旨:
    减少cache刷新和tlb刷新,减少内核管理设施的工作(比如频繁的内存管理)

    2.优化中断分发

    1).增加长路径支持,减少进程切换导致的TLB以及Cache刷新
    2).利用多队列网卡支持中断CPU亲和力利用或者模拟软多队列提高并行性
    3).牺牲用户态进程的调度机会,全部精力集中于内核协议栈的处理,多CPU多路并行的
        [ Tips:如果有超多的CPU,建议划分cgroup ]
    4).中断处理线程化,内核线程化,多核心并行执行长路经,避免切换抖动
    5).线程内部,按照IXA NP微模块思想采用模块化(方案未实现,待商榷)

    宗旨:
    减少cache刷新和tlb刷新
    减少协议栈处理被中断过于频繁打断[ 要么使用IntRate,要么引入中断优先级]

    3.优化路由查找算法

    1).分离路由表和转发表,路由表和转发表同步采用RCU机制
    2).尽量采用线程局部数据
    每个线程一张转发表(由路由表生成,OpenVPN多线程采用,但失败),采用定位而非最长前缀查找(DxR或者我设计的那个)。若不采用为每个线程复制一份转发表,则需要重新设计RW锁或者使用RCU机制。
    3).采用hash/trie方式以及DxR或者我设计的DxRPro定位结构

    宗旨:
    采用定位而非查找结构
    采用局部表,避免锁操作

    4.优化lock

    1).查询定位局部表,无锁(甚至RW锁都没有)不禁止中断
    2).临界区和内核线程关联,不禁中断,不禁抢占(其实内核编译时抢占已经关闭了)
    3).优先级锁队列替换争抢模型,维持cache热度
    4).采用Windows的自旋锁机制
            [ Tips:Linux的ticket spin lock由于采用探测全局lock的方式,会造成总线开销和CPU同步开销,Windows的spin lock采用了探测CPU局部变量的方式实现了真正的队列lock,我设计的输入输出队列管理结构(下面详述)思路部分来源于Windows的自旋锁设计]

    宗旨:锁的粒度与且仅与临界区资源关联,粒度最小化


    优化细节概览

    1.DMA与输入输出队列优化


    1.1.问题出在哪儿
    如果你对Linux内核协议栈足够熟悉,那么就肯定知道,Linux内核协议栈正是由于软件工程里面的天天普及的“一件好事”造成了转发性能低效。这就是“解除紧密耦合”。

           Linux协议栈转发和Linux服务器之间的根本区别在于,后者的应用服务并不在乎数据包输入网卡是哪个,它也不必关心输出网卡是哪一个,然而对于Linux协议栈转发而言,输入网卡和输出网卡之间确实是有必要相互感知的。Linux转发效率低的根本原因不是路由表不够高效,而是它的队列管理以及I/O管理机制的低效,造成这种低效的原因不是技术实现上难以做到,而是Linux内核追求的是一种灵活可扩展的性能,这就必须解除出入网卡,驱动和协议栈之间关于数据包管理的紧密耦合。

           我们以Intel千兆网卡驱动e1000e来说明上述的问题。顺便说一句,Intel千兆驱动亦如此,其它的就更别说了,其根源在于通用的网卡驱动和协议栈设计并不是针对转发优化的。

    初始化:
    创建RX ring:RXbuffinfo[MAX]
    创建TX ring:TXbuffinfo[MAX]

    RX过程:
    i = 当前RX ring游历到的位置;
    while(RXbuffinfo中有可用skb) {
            skb = RXbufferinfo[i].skb;
            RXbuffinfo[i].skb = NULL;
            i++;
            DMA_unmap(RXbufferinfo[i].DMA);
            [Tips:至此,skb已经和驱动脱离,完全交给了Linux协议栈]
            [Tips:至此,skb内存已经不再由RX ring维护,Linux协议栈拽走了skb这块内存]
            OS_receive_skb(skb);
            [Tips:由Linux协议栈负责释放skb,调用kfree_skb之类的接口]
            if (RX ring中被Linux协议栈摘走的skb过多) {
                    alloc_new_skb_from_kmem_cache_to_RXring_RXbufferinfo_0_to_MAX_if_possible;
                    [Tips:从Linux核心内存中再次分配skb]
            }
    }

    TX过程:
    skb = 来自Linux协议栈dev_hard_xmit接口的数据包;
    i = TX ring中可用的位置
    TXbufferinfo[i].skb = skb;
    DMA_map(TXbufferinfo[i].DMA);
    while(TXbufferinfo中有可用的skb) {
            DMA_transmit_skb(TXbufferinfo[i]);
    }
    [异步等待传输完成中断或者在NAPI poll中主动调用]
    i = 传输完成的TXbufferinfo索引
    while(TXbufferinfo中有已经传输完成的skb) {
            skb = TXbufferinfo[i];
            DMA_unmap(TXbufferinfo[i].DMA);
            kfree(skb);
            i++;
    }
    以上的流程可以看出,在持续转发数据包的时候,会涉及大量的针对skb的alloc和free操作。如果你觉得上面的代码不是那么直观,那么下面给出一个图示:



           频繁的会发生从Linux核心内存中alloc skb和free skb的操作,这不仅仅是不必要的,而且还会损害CPU cache的利用。不要寄希望于keme_cache,我们可以看到,所有的网卡和socket几乎是共享一块核心内存的,虽然可以通过dev和kmem cache来优化,但很遗憾,这个优化没有质的飞跃。

    1.2.构建新的DMA ring buffer管理设施-VOQ,建立输入/输出网卡之间队列的关联。
    类比Linux O(1)调度器算法,每一个cpu全局维护一个唯一的队列,散到各个网卡,靠交换队列的DMA映射指针而不是拷贝数据的方式优化性能,达到零拷贝,这只是其一。关于交换DMA映射指针而不是拷贝数据这一点不多谈,因为几乎所有的支持DMA的网卡驱动都是这么做的,如果它们不是这么做的,那么肯定有人会将代码改成这么做的。

           如果类比高端路由器的crossbar交换阵列结构以及真实的VOQ实现,你会发现,在逻辑上,每一对可能的输入/输出网卡之间维护一条数据转发通路是避免队头阻塞以及竞争的好方法。这样排队操作只会影响单独的网卡,不需要再全局加锁。在软件实现上,我们同样可以做到这个。你要明白,Linux的网卡驱动维护的队列信息被内核协议栈给割裂,从此,输入/输出网卡之间彼此失联,导致最优的二分图算法无法实施。

           事实上,你可能觉得把网卡作为一个集合,把需要输出的数据包最为另一个集合,转发操作需要做的就是建立数据包和网卡之间的一条路径,这是一个典型的二分图匹配问题,然而如果把建立路径的操作与二分图问题分离,这就是不再是网卡和数据包之间的二分图匹配问题了。因为分离出来的路由模块导致了针对每一个要转发的数据包,其输出网卡是唯一确定的。这个问题变成了处理输出网卡输出操作的CPU集合和输出网卡之间的二分图匹配问题。

           这里有一个优化点,那就是如果你有多核CPU,那么就可以为每一块网卡的输出操作绑定一个唯一的CPU,二分图匹配问题迎刃而解,剩下的就是硬件总线的争用问题(对于高性能crossbar路由器而言,这也是一个二分图匹配问题,但对于总线结构的通用系统而言有点区别,后面我会谈到)了,作为我们而言,这一点除了使用性价比更高的总线,比如我们使用PCI-E 16Lines 8 bits,没有别的办法。作为一个完全的方案,我不能寄希望于底层存在一个多核CPU系统,如果只有一个CPU,那么我们能寄希望于Linux进程调度系统吗?还是那个观点,作为一个通用操作系统内核,Linux不会针对网络转发做优化,于是乎,进程调度系统是此方案的另一个优化点,这个我后面再谈。

           最后,给出我的数据包队列管理VOQ的设计方案草图。



    在我的这个针对Linux协议栈的VOQ设计中,VOQ总要要配合良好的输出调度算法,才能发挥出最佳的性能。

    2.分离路由表和转发表以及建立查找操作之间的关联

    Linux协议栈是不区分对待路由表和转发表的,而这在高端路由器上显然是必须的。诚然,我没有想将Linux协议栈打造成比肩专业路由器的协议栈,然而通过这个排名第二的核心优化,它的转发效率定会更上一层楼。

           在大约三个月前,我参照DxR结构以及借鉴MMU思想设计了一个用于转发的索引结构,可以实现3步定位,无需做最长前缀匹配过程,具体可以参见我的这篇文章 以DxR算法思想为基准设计出的路由项定位结构图解,我在此就不再深度引用了。需要注意的是,这个结构可以根据现行的Linux协议栈路由FIB生成,而且在路由项不规则的情况下可以在最差情况下动态回退到标准DxR,比如路由项不可汇聚,路由项在IPv4地址空间划分区间过多且分布不均。我将我设计的这个结构称作DxR Pro++。

           至于说查找操作之间的关联,这也是一个深度优化,底层构建高速查询流表实现协议栈短路(流表可参照conntrack设计),这个优化思想直接参照了Netfilter的conntrack以及SDN流表的设计。虽然IP网络是一个无状态网络,中间路由器的转发策略也应该是一个无状态的转发。然而这是形而上意义上的理念。如果谈到深度优化,就不得不牺牲一点纯洁性。

           设计一个流表,流的定义可以不必严格按照五元组,而是可以根据协议头的任意字段,每一个表项中保存的信息包括但不限于以下的元素:
    *流表缓存路由项
    *流表缓存neighbour
    *流表缓存NAT
    *流表缓存ACL规则       
    *流表缓存二层头信息

    这样可以在协议栈的底层保存一张可以高速查询的流表,协议栈收到skb后匹配这张表的某项,一旦成功,可以直接取出相关的数据(比如路由项)直接转发,理论上只有一个流的第一个数据包会走标准协议栈的慢速路径(事实上,经过DxR Pro++的优化,一经不慢了...)。在直接快速转发中,需要执行一个HOOK,执行标准的例行操作,比如校验和,TTL递减等。  
            关于以上的元素,特别要指出的是和neighbour与二层信息相关的。数据转发操作一向被认为瓶颈在发不在收,在数据发送过程,会涉及到以下耗时的操作:>添加输出网卡的MAC地址作为源-内存拷贝>添加next hop的MAC地址作为目标-内存拷贝又一次,我们遇到了内存操作,恼人的内存操作!如果我们把这些MAC地址保存在流表中,可以避免吗?貌似只是可以快速定位,而无法避免内存拷贝...再一次的,我们需要硬件的特性来帮忙,这就是分散聚集I/O(Scatter-gather IO),原则上,Scatter-gather IO可以将不连续的内存当成连续的内存使用,进而直接映射DMA,因此我们只需要告诉控制器,一个将要发送的帧的MAC头的位置在哪里,DMA就可以直接传输,没有必要将MAC地址拷贝到帧头的内存区域。如下图所示:



    特别要注意,上述的流表缓存项中的数据存在大量冗余,因为next hop的MAC地址,输出网卡的MAC地址,这些是可以由路由项唯一确定的。之所以保存冗余数据,其原则还是为了优化,而标准的通用Linux内核协议栈,它却是要避免冗余的...既然保存了冗余数据,那么慢速路径的数据项和快速路经的数据项之间的同步就是一个必须要解决的问题。我基于读写的不对称性,着手采用event的方式通知更新,比如慢速路径中的数据项(路由,MAC信息,NAT,ACL信息等),一旦这些信息更改,内核会专门触发一个查询操作,将快速流表中与之相关的表项disable掉即可。值得注意的是,这个查询操作没必要太快,因为相比较快速转发而言,数据同步的频率要慢天文数字个数量级...类似Cisco的设备,可以创建几个内核线程定期刷新慢速路径表项,用来发现数据项的更改,从而触发event。

    [Tips:可以高速查找的流表结构可用多级hash(采用TCAM的类似方案),也可以借鉴我的DxR Pro++结构以及nf-HiPac算法的多维区间匹配结构,我个人比较推崇nf-HiPac]


    3.路由Cache优化

    虽说Linux的路由cache早已下课,但是它下课的原因并不是cache机制本身不好,而是Linux的路由cache设计得不好。因此以下几点可以作为优化点来尝试。
    *)限制路由软cache的大小,保证查找速度[实施精心设计的老化算法和替换算法]
    [ 利用互联网访问的时间局部性以及空间局部性(需要利用计数统计)]
    [ 自我PK:如果有了我的那个3步定位结构,难道还用的到路由cache吗]
    *)预制常用IP地址到路由cache,实现一步定位
    [ 所谓常用IP需要根据计数统计更新,也可以静态设置]

    4.Softirq在不支持RSS多队列网卡时的NAPI调度优化

    *)将设备按照协议头hash值均匀放在不同CPU,远程唤醒softirq,模拟RSS软实现
    目前的网络接收软中断的处理机制是,哪个CPU被网卡中断了,哪个CPU就处理网卡接收软中断,在网卡只能中断固定CPU的情况下,这会影响并行性,比如只有两块网卡,却有16核CPU。如何将尽可能多的CPU核心调动起来呢?这需要修改网络接收软中断处理逻辑。我希望多个CPU轮流处理数据包,而不是固定被中断的数据包来处理。修改逻辑如下:

    1.所有的rx softirq内核线程组成一个数组
    struct task_struct rx_irq_handler[NR_CPUS];

    2.所有的poll list组成一个数组
    struct list_head polll[NR_CPUS];

    3.引入一把保护上述数据的自旋锁
    spinlock_t rx_handler_lock;

    4.修改NAPI的调度逻辑
    void __napi_schedule(struct napi_struct *n)
    {
        unsigned long flags;
    
        static int curr = 0;
        unsigned int hash = curr++%NR_CPUS;
        local_irq_save(flags);
        spin_lock(&rx_handler_lock);
        list_add_tail(&n->poll_list, polll[hash]);
        local_softirq_pending(hash) |= NET_RX_SOFTIRQ;
        spin_unlock(&rx_handler_lock);
        local_irq_restore(flags);
    }

    [ Tips:注意和DMA/DCA,CPU cache亲和的结合,如果连DMA都不支持,那他妈的还优化个毛]

    理论上一定要做基于传输层以及传输层以下的元组做hash,不能随机分派,在计算hash的时候也不能引入任何每包可变的字段。由于某些高层协议比如TCP以及绝大多数的基于非TCP的应用协议是高度按序的,中间节点的完全基于数据包处理的并行化会引起数据包在端节点的乱序到达,从而引发重组和重传开销。自己为了提高线速能力貌似爽了一把,却给端主机带来了麻烦。然而我目前没有考虑这个,我只是基于轮转调度的方式来分发poll过程到不同的CPU来处理,这显然会导致上述的乱序问题。

           若想完美解决上述问题,需要增加一个调度层,将RX softirq再次分为上下两半部RX softirq1和RX softirq2,上半部RX softirq1仅仅是不断取出skb并分派到特定CPU核心,下半部才是协议栈处理,修改NAPI的poll逻辑,每当poll出来一个skb,就计算这个skb的hash值,然后将其再次分派到特定的CPU的队列中,最后唤醒有skb需要处理的CPU上的RX softirq2,这期间需要引入一个位图来记录有无情况。

           但是有一个需要权衡的逻辑,是不是真的值得将RX softirq做再次分割,将其分为上下半部,这期间的调度开销和切换开销到底是多少,需要基准测试来评估。

    *)延长net softirq的执行时间,有包就一直dispatch loop。管理/控制平面进程被划分到独立的cgroup/cpuset中。

    5.Linux调度器相关的修改

    这个优化涉及到方案的完备性,毕竟我们不能保证CPU核心的数量一定大于网卡数量的2倍(输入处理和输出处理分离),那么就必须考虑输出事件的调度问题。
    依照数据包队列管理的设计方案,考虑单CPU核心,如果有多块网卡的输出位图中有bit被置位,那么到底调度哪一个网卡进行输出呢?这是一个明确的task调度问题。你放心把这个工作交给Linux内核的调度器去做吗?我不会。

           因为我知道,虽然有好几个网卡可能都有数据包等待发送,但是它们的任务量并不同,这又是一个二分图问题。我需要三个指标加权来权衡让哪个网卡先发送,这三个指标是,队头等待时间,队列数据包长度总和以及数据包数量。由此可以算出一个优先级prio,总的来讲就是虚拟输出队列中等待越久,数据包越多,长度越长的那个网卡最值得发送数据。计算队列总长势必会引发非局部访问,比如访问其它网卡的虚拟输出队列,这就会引发锁的问题,因此考虑简单情形,仅仅使用一个指标,即数据包长度。在Linux当前的CFS调度器情形下,需要将排队虚拟输出队列的数据包长度与task的虚拟时间,即vruntime关联,具体来讲就是,在输入网卡对输出网卡的输出位图置位的时候,有下列序列:
    //只要有skb排队,无条件setbit
    setbit(outcart, incard);
    //只要有skb排队,则将与输出网卡关联的输出线程的虚拟时间减去一个值,该值由数据包长度与常量归一化计算所得。
    outcard_tx_task_dec_vruntime(outcard, skb->len);

    对Linux CFS调度不熟悉的,可以自行google。事实上,一旦某个输出网卡的输出task开始运行,它也是按照这种基于虚拟时间流逝的CFS方式来调度数据包的,即摘下一个最值得发送的数据包队列描述符放入TX ring。

           最后有一个思考,如果不采用CFS而采用RT调度类是不是更好?单独网卡输出的实时性和多块网卡输出之间的公平性如何权衡?另外,采用RT调度类就一定带有实时性吗?

    6.内置包分类和包过滤的情况

    这是一个关于Netfilter优化的话题,Netfilter的性能一直被人诟病,其很大程度上都是由于iptables造成的,一定要区分对待Netfilter和iptables。当然排除了这个误会,并不表明Netfilter就完全无罪。Netfilter是一个框架,它本身在我们已经关闭了抢占的情况下是没有锁开销的,关键的在它内部的HOOK遍历执行中,作为一些callback,内部的逻辑是不受控制的,像iptables,conntrack(本来数据结构粒度就很粗-同一张hash存储两个方向的元组,又使用大量的大粒度锁,内存不紧张时我们可以多用几把锁,空间换自由,再说,一把锁能占多大空间啊),都是吃性能的大户,而nf-HiPac就好很多。因此这部分的优化不好说。

           不过建议还是有的,为一段临界区加锁的时候千万不要盲目,如果一个数据结构被读的频率比被写的频率高很多,以至于后者可以被忽略的地步,那么劝各位不要锁定它,即使RW锁,RCU锁都不要,而是采用复制的形式,拷贝出一个副本,然后读副本,写原本,写入原本后采用原子事件的方式通知副本失效。比如上面提到的关于快速流表的同步问题,一旦路由发生变化,就触发一个原子事件,查询快速流表中与之相关的项,失效掉它。查询可以很慢,因为路由更新的频率很低。

    本节不多谈,建议如下:
    *)预处理ACL或者NAT的ruleset(采用nf-hipac方案替换非预处理的逐条匹配)
    [Hipac算法类似于一种针对规则的预处理,将matches进行了拆分,采用多维区间匹配算法
    ]
    *)包调度算法(CFS队列,RB树存储包到达时间*h(x),h为包长的函数)

    7.作为容器的skb

    skb作为一个数据包的容器存在,它要和真正的数据包区分开来,事实上,它仅仅作为一个数据包的载体,像一辆卡车运载数据包。它是不应该被释放的,永远不该被释放。每一块网卡都应该拥有自己的卡车车队,如果我们把网卡看作是航空港,Linux路由器看作是陆地,那么卡车从空港装载货物(数据包),要么把它运输到某个目的地(Linux作为服务器),要么把它运输到另一个空港(Linux作为转发路由器),其间这辆卡车运送个来回即可,这辆卡车一直属于货物到达的那个空港,将货物运到另一个空港后空车返回即可。卡车的使用不必中心调度,更无需用完后销毁,用的时候再造一辆(Linux的转发瓶颈即在此!!!)。

           其实还有更加高效的做法,那就是卡车将货物运输到另一个港口或者运输到陆地目的地后,不必空车返回,而是直接排入目的港口或者目的地的出港队列,等待运输货物满载返回所属的港口。但是对于Linux而言,由于需要路由查找后才知道卡车返回哪里,因此在出发的时候,卡车并不能确定它一定会返回它所属的港口...因此需要对包管理队列做一定的修正,即解除网卡的RX ring和skb的永久绑定关系。为了统一起见,新的设计将路由到本机的数据包也作为转发处理,只是输出网卡变成了一个BSD socket,新的设计如下图所示:



    其实,类比火车和出租车我们就能看到这个区别。对于火车而言,它的线路是固定的,比如哈尔滨到汉口的火车,它属于哈尔滨铁路局,满客到达汉口后,下客,然后汉口空车重新上客,它一定返回哈尔滨。然而对于出租车,就不是这样,嘉定的沪C牌的出租车理论上属于嘉定,不拒载情况下,一个人打车到松江,司机到松江后,虽然期待有人打他的车回嘉定,但是乘客上车后(路由查找),告诉司机,他要到闵行,到达后,又一人上车,说要到嘉兴...越走越远,但事实就是这样,因为乘客上车前,司机是不能确定他要去哪里的。

    用户态协议栈方案

    1.争议

    在某些平台上,如果不解决user/kernel切换时的cache,tlb刷新开销,这种方案并不是我主推的,这些平台上不管是写直通还是写回,访问cache都是不经MMU的,也不cache mmu权限,且cache直接使用虚地址。

    2.争议解决方案

    可以采用Intel I/OAT的DCA技术,避免上下文切换导致的cache抖动

    3.采用PF_RING的方式

    修改驱动,直接与DMA buffer ring关联(参见内核方案的DMA优化)。

    4.借鉴Tilera的RISC超多核心方案

    并行流水线处理每一层,流水级数为同时处理的包的数量,CPU核心数+2,流水数量为处理模块的数量。
    [ 流水线倒立]

    本质上来讲,用户态协议栈和内核协议栈的方案是雷同的,无外乎还是那几种思想。用户态协议栈实现起来限制更少,更灵活,同时也更稳定,但是并不是一味的都是好处。需要注意的是,大量存在的争议都是形而上的,仁者见仁,智者见智。

    稳定性

    对于非专业非大型路由器,稳定性问题可以不考虑,因为无需7*24,故障了大不了重启一下而已,无伤大雅。但是就技术而言,还是有几点要说的。在高速总线情形下,并行总线容易窜扰,内存也容易故障,一个位的错误,一个电平的不稳定都会引发不可预知的后果,所以PCI-E这种高速总线都采用串行的方式传输数据,对于硬盘而言,SATA也是一样的道理。

           在多网卡DMA情况下,对于通过的基于PCI-E的设备而言,总线上的群殴是很激烈的,这是总线这种拓扑结构所决定的,和总线类型无关,再考虑到系统总线和多CPU核心,这种群殴会更加激烈,因为CPU们也会参与进来。群殴的时候,打翻桌椅而不是扳倒对方是很常有的事,只要考虑一下这种情况,我就想为三年前我与客户的一次争吵而向他道歉。

           2012年,我做一个VPN项目,客户说我的设备可能下一秒就会宕机,因为不确定性。我说if(true) {printf("cao ni ma!\n")(当然当时我不敢这么说);确定会执行吗?他说不一定。我就上火了...可是现在看来,他是对的。

    VOQ设计后良好的副作用-QoS

    VOQ是本方案的一个亮点,本文几乎是围绕VOQ展开的,关于另一个亮点DxR Pro,在其它的文章中已经有所阐述,本文只是加以引用而已。临近末了,我再次提到了VOQ,这次是从一个宏观的角度,而不是细节的角度来加以说明。
          
           只要输入缓冲区队列足够大,数据包接收几乎就是线速的,然而对于输出,却受到了调度算法,队头拥塞等问题的影响,即输入对于系统来讲是被动的,中断触发的,而输出对于系统来讲则是主动的,受制于系统的设计。因此对于转发而言“收易发难”就是一个真理。因此对于QoS的位置,大多数系统都选择在了输出队列上,因为输入队列上即便对流量进行了干预,流量在输出的时候还是会受到二次无辜的干预,而这会影响输入队列上的QoS干预效果。我记得曾经研究过Linux上的IMQ输入队列流控,当时只是关注了实现细节,并没有进行形而上的思考,现在不了。

           有了VOQ以后,配合设计良好的调度算法,几乎解决了所有问题,这是令人兴奋的。上文中我提到输出操作的时候,输出线程采用基于数据包长度以及虚拟时间的加权公平调度算法进行输出调度,但是这个算法的效果只是全速发送数据包。如果这个调度算法策略化,做成一个可插拔的,或者说把Linux的TC模块中的框架和算法移植进来,是不是会更好呢?

           唉,如果你百度“路由器 线速”,它搜出来的几乎都是“路由器 限速”,这真是一个玩笑。其实对于转发而言,你根本不用添加任何TC规则就能达到限速的效果,Linux盒子在网上上一串,马上就被自动限速了,难道不是这样吗?而加上VOQ以后,你确实需要限速了。就像在拥挤的中国城市中区,主干道上写着限速60,这不是开玩笑吗?哪个市中心的熙熙攘攘的街道能跑到60....但是一旦上了高速,限速100/120,就是必须的了。

    VOQ设计后良好的副作用-队头拥塞以及加速比问题


    用硬件路由器的术语,如果采用将数据包路由后排队到输出网卡队列的方案,那么就会有多块网卡同时往一块网卡排队数据包的情况,这对于输出网卡而言是被动的,这又是一个令人悲伤的群殴过程,为了让多个包都能同时到达,输出带宽一定要是各个输入带宽的加和,这就是N倍加速问题,我们希望的是一个输出网卡主动对数据包进行调度的过程,有序必然高效。这就是VOQ设计的精髓。

           对于Linux而言,由于它的输出队列是软件的,因此N加速比问题变成了队列锁定问题,总之,还是一个令人遗憾的群殴过程,因此应对方案的思想是一致的。因此在Linux中我就模拟了一个VOQ。从这里我们可以看出VOQ和输出排队的区别,VOQ对于输出过程而言是主动调度的过程,显然更加高效,而输出排队对于输出过程而言则是一个被动被争抢的过程,显然这是令人感到无望的。

           需要说明的是,VOQ只是一个逻辑上的概念,类比了硬件路由器的概念。如果依然坚持使用输出排队而不是VOQ,那么设计多个输出队列,每一个网卡一个队列也是合理的,它甚至更加简化,压缩掉了一个网卡分派过程,简化了调度。于是我们得到了Linux VOQ设计的第三版:将虚拟输出队列VOQ关联到输出网卡而不是输入网卡(下面一小节我将分析原因)。

    总线拓扑和Crossbar


    真正的硬件路由器,比如Cisco,华为的设备,路由转发全由线卡硬件执行,数据包在此期间是静止在那里的,查询转发表的速度是如此之快,以至于相对将数据包挪到输出网卡队列的开销,查表开销可以忽略。因此在真正的硬件路由器上,如何构建一个高性能交换网络就是重中之重。
       
           不但如此,硬件路由器还需要考虑的是,数据包在路由查询过后是由输入处理逻辑直接通过交换网络PUSH到输出网卡队列呢,还是原地不动,然后等待输出逻辑通过交换网络把数据包PULL到那里。这个不同会影响到交换网络仲裁器的设计。如果有多个网卡同时往一个网卡输出数据包,PUSH方式可能会产生冲突,因为在Crossbar的一条路径上,它相当于一条总线,而且冲突一般会发生在交换网络内部,因此这种PUSH的情况下,一般会在交换网络内部的开关节点上携带cache,用来暂存冲突仲裁失败的数据包。反之,如果是PULL方式,情况就有所不同。因此把输出队列放在交换网络的哪一侧带来的效果是不同的。

           但是对于通用系统架构,一般都是采用PCI-E总线连接各个网卡,这是一种典型的总线结构,根本就没有所谓的交换网络。因此所谓的仲裁就是总线仲裁,这并不是我关注的重点,谁让我手上只有一个通用架构的设备呢?!我的优化不包括总线仲裁器的设计,因为我不懂这个。

           因此,对于通用架构总线拓扑的Linux协议栈转发优化而言,虚拟输出队列VOQ关联在输入网卡还是输出网卡,影响不会太大。但是考虑到连续内存访问带来的局部性优化,我还是倾向将VOQ关联到输出网卡。如果VOQ关联到输入网卡,那么在进行输出调度的时候,输出网卡的输出线程就要从输出位图指示的每一个待发送数据的输入网卡VOQ中与自己关联的队列调度数据包,无疑,这些队列在内存中是不连续的,如果关联到输出网卡,对于每一个输出网卡而言,VOQ是连续的。如下图所示:




    实现相关


    前面我们提到skb只是作为容器(卡车)存在。因此skb是不必释放的。理想情况下,在Linux内核启动,网络协议栈初始化的时候,根据自身的硬件性能和网卡参数做一次自测,然后分配MAX个skb,这些skb可以先均匀分配到各个网卡,同时预留一个socket skb池,供用户socket取。后面的事情就是skb运输行为了,卡车开到哪里算哪里,运输过程不空载。

           可能你会觉得这个没有必要,因为skb本身甚至整个Linux内核中绝大部分内存分配都是被预先分配并cache的,slab就是做这个的,不是有kmem_cache机制吗?是这样的,我承认Linux内核在这方面做得很不错。但是kmem_cache是一个通用的框架,为何不针对skb再提高一个层次呢?每一次调用alloc_skb,都会触发到kmem_cache框架内的管理机制做很多工作,更新数据结构,维护链表等,甚至可能会触及到更加底层的伙伴系统。因此,期待直接使用高效的kmem_cache并不是一个好的主意。

           你可能会反驳说万一系统内存吃紧也不释放吗?万事并不绝对,但这并不是本文的范畴,这涉及到很多方面,比如cgroup等。

           针对skb的修改,我添加了一个字段,指示它的所属地(某个网卡?socket池?...),当前所属地,这些信息可以维护skb不会被free到kmem_cache,同时也可以最优化cache利用率。这部分修改已经实现,目前正在针对Intel千兆卡的驱动做进一步修改。关于DxR Pro的性能,我在用户态已经经过测试,目前还没有移植到内核。

           关于快速查找表的实现,目前的思路是优化nf_conntrack,做多级hash查找。

    最后的声明

    本文仅仅是针对Linux做的转发调优方案,如果需要更加优化的方案, 请参考ASIC以及NP等硬件方案,不要使用总线拓扑,而是使用交叉阵列拓扑。
    展开全文
  • 妥善处理解决网络I/O瓶颈

    千次阅读 2010-09-28 06:32:00
    由于网络应用系统瓶颈已经体现在了I/O这块,如何更好的提高性能自然也只能围绕I/O这块来做。设计的模式要依靠这块重点来提高性能。这里转一篇对I/O有很好分析的文章。 使用异步 I/O 大大提高应用程序的性能...
  • 3.5 Network bottlenecks A performance problem in the network subsystem can be the cause of many problems, such ... To analyze these anomalies to detect network bottlenecks, each Linux
  • Linux系统瓶颈分析(经典)

    千次阅读 2015-03-03 18:40:55
    Linux系统瓶颈分析(经典) ... 1.0 性能监控介绍 性能优化就是找到系统处理中的瓶颈以及去除这些的过程,多数管理员...可以实现性能优化,通常通过对内核的一些配置是可以简单的解决问题,但并不适合每个环境,性能优化其实
  • 文章目录目录前言Neutron 的网络实现模型基于虚拟网络设备的虚拟机流量走向Neutron 网络实现模型的性能瓶颈SR-IOV 技术简介在 Neutron 中引入 SR-IOV 技术基于 SR-IOV 技术的虚拟机流量走向Neutron 配置启用 SR-IOV ...
  • 补遗关于网络接收的软中断负载均衡,已经有了成熟的方案,但是该方案并不特别适合数据包转发,它对服务器的小包处理非常好,这就是RPS。我针对RPS做了一个patch,提升了其转发效率。下面是我转载的我自己的原文。线...
  • Linux 网络基础篇

    千次阅读 多人点赞 2020-05-30 22:47:36
    从今天开始开始更新Linux网络基础,希望大家多多关注,指出错误,我的Linux系统管理已经更新完成,有需要的可以去看看,链接: 链接: 从零开始学习Linux. Linux 网络基础篇第一章 计算机基础第一节 进制转换第二章...
  • 解决Linux 负载过高问题过程记录

    万次阅读 多人点赞 2018-08-23 11:42:25
    解决问题的思路 1.top命令查看该机器的负载状况 2.cd /proc/pid 查看对应高占用程序的位置 3.进入对应程序中查看日志,根据CPU和内存这两个因素分析 4.ps -ajxf 查看进程及其之下的线程,通过stat查看是否存在D...
  • Linux网络监控工具总结

    万次阅读 2016-10-17 23:23:15
    GitHub Linux网络监控工具总结 AderXCoding/system/tools/network_monitor本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可, 转载请注明出处 对任何规模的业务来说, 网络监控工具都...
  • Linux系统和网络性能监测》读书笔记  性能调优是找出系统瓶颈并消除这些瓶颈的过程,很多系统管理员认为性能调优仅仅是调整下内核的参数即可解决问题,事实上情况并不是这样...更多
  • Linux常用网络工具总结

    万次阅读 2017-04-20 12:02:58
    本文整理了在实践过程中使用的Linux网络工具,这些工具提供的功能非常强大,我们平时使用的只是冰山一角,比如lsof、ip、tcpdump、iptables等。本文不会深入研究这些命令的强大用法,因为每个命令都足以写一篇文章,...
  • linux问题

    千次阅读 2019-05-21 09:14:52
    1. apache怎么实现负载均衡 答案: 多台机器跑apache,然后其中一台跑nginx,让nginx去代理...2. 一台Linux服务器负载高,连接慢,怎么查看 答案: 先用w看负载多少,用top看哪个进程占用cpu高,同时用top按M看哪个...
  • Linux系统网络性能实例分析

    千次阅读 2016-10-28 16:35:52
    术语 LinuxTCP/IP栈和 Linux网络栈可互换使用,因为 TCP/IP栈是 Linux内核的组成部分,也被看作是 Linux默认的网络栈。 一、实例分析中使用的基准测试 1、 NetBench NetBench是一种 Ziff-Davis基准测试, 可以测量...
  • 关于linux下shutdown关闭不掉tomcat问题解决办法 Oops~ 今天写下今天遇到的问题,相信众位程序员一定也有遇到的--linux下shutdown关闭不掉tomcat问题。 首先描述下遇到的情况。当多次启动tomcat后,发现在系统下有...
  • IO分为磁盘IO和网络IO,比如我们使用Java提供的流读取磁盘时,会涉及到一些流操作,回顾一下,Java流可总结下图: 也就是2大类,字节流和字符流。 我们知道,字节是信息存储的最小单元,这里你可能会有疑问,...
  • linux【测试】iperf3网络测速工具

    千次阅读 2020-09-02 16:17:17
    网络管理员可以根据这些信息了解并判断网络性能问题,从而定位网络瓶颈解决网络故障。 Iperf 是一款基于命令行模式的网络性能测试工具,是跨平台的,提供横跨Windows、Linux、Mac的全平台支持。iperf 全程使用内存...
  • 浅谈网卡绑定和网络瓶颈

    千次阅读 2016-11-03 11:27:38
    生产系统有一个很容易让大家忽视的性能瓶颈点网卡。服务器网卡现在基本上普及千兆或者是万兆的光口。但是这个千兆或者万兆的基本单位是bit。直接通过命令查看网卡的速率: [root@host03 obase]# ethtool bond0 ...
  • 顾名思义,TcpDump可以将网络中传送的数据包的“头”完全截获下来提供分析。它支持针对网络层、协议、主机、网络或端口的过滤,并提供and、or、not...tcpdump存在于基本的FreeBSD系统中,由于它需要将网络界面设置
  • 总结自己的系统性能瓶颈定位和性能调优方法,建立一个粗略的分析模型。 在进行分析时,对于产生的问题,可以从3方面入手分析: 1 测试端问题: 包括测试工具本身固有的缺陷和测试机器资源问题,在测试中都有可能...
  • sysctl优化linux网络

    千次阅读 2012-06-12 20:01:16
     sysctl优化linux网络  1, 优化网络设备接收队列  net.core.netdev_max_backlog=3000  该文件表示在每个网络接口接收数据包的速率比内核处理这些包的速率快时,允许送到队列的数据包的最大数目
  • 为了解决这个问题。 小编给大家带来了利用双网卡来增强网速。利用双网卡什么意思呢?当然不是使用相同类型的网卡,两块相同的网卡,利用同一种线路就算接上两条网线,因为路由器的限制,还是停留...
  • 一、查看哪些IP连接本机 netstat -an 二、查看TCP连接数 1)统计80端口连接数 netstat -nat | grep -i "80" | wc -l ...3)统计已连接上的,状态“established netstat -anp | grep ESTABLISHED | wc -l 4)、...
  • linux命令是对Linux系统进行管理的命令。对于Linux系统来说,无论是中央处理器、内存、磁盘驱动器、键盘、鼠标,还是用户等都是文件,Linux系统管理的命令是它正常运行的核心,与之前的DOS命令类似。linux命令在系统...
  • Linux 操作系统原理 — 网络 I/O 虚拟化

    千次阅读 多人点赞 2021-06-18 22:37:01
    文章目录目录IOMMU — CPU 硬件支撑的 I/O 虚拟化方案需求背景DMA Remapping FeatureIOMMU 硬件单元PCI Passthrough开启 IOMMUUIO Framework — 用户态网络协议栈方案VFIO — 高性能的用户态虚拟化 I/O 设备方案 ...
  • 如何迅速分析出系统I/O瓶颈
  • Linux Netfilter/NAT的两个典型问题

    千次阅读 2021-01-01 11:27:00
    十年前以及更久,那是Netfilter的黄金时期,几乎任何网络相关的功能,均可以在Netfilter上实现,当时懂Netfilter的人绝对是Linux网络领域的大佬,但是随着进入了移动互联网时代,互联网巨头们在大流量大并发的大背景...
  • 网络管理员可以根据这些信息了解并判断网络性能问题,从而定位网络瓶颈解决网络故障。 下面介绍Iperf的主要功能。 (1)TCP方面 1 测试网络带宽。 2 支持多线程,在客户端与服务端支持...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 47,691
精华内容 19,076
关键字:

linux为解决网络瓶颈问题

linux 订阅