转自: http://www.ideawu.net/blog/archives/740.html

著名的 C10K 问题提出的时候, 正是 2001 年, 到如今 12 年后的 2013 年, C10K 已经不是问题了, 任何一个普通的程序员, 都能利用手边的语言和库, 轻松地写出 C10K 的服务器. 这既得益于软件的进步, 也得益于硬件性能的提高.

现在, 该是考虑 C1000K, 也就是百万连接的问题的时候了. 像 Twitter, weibo, Facebook 这些网站, 它们的同时在线用户有上千万, 同时又希望消息能接近实时地推送给用户, 这就需要服务器能维持和上千万用户的 TCP 网络连接, 虽然可以使用成百上千台服务器来支撑这么多用户, 但如果每台服务器能支持一百万连接(C1000K), 那么只需要十台服务器.

有很多技术声称能解决 C1000K 问题, 例如 Erlang, Java NIO 等等, 不过, 我们应该首先弄明白, 什么因素限制了 C1000K 问题的解决. 主要是这几点:

  1. 操作系统能否支持百万连接?
  2. 操作系统维持百万连接需要多少内存?
  3. 应用程序维持百万连接需要多少内存?
  4. 百万连接的吞吐量是否超过了网络限制?

下面来分别对这几个问题进行分析.

1. 操作系统能否支持百万连接?

对于绝大部分 Linux 操作系统, 默认情况下确实不支持 C1000K! 因为操作系统包含最大打开文件数(Max Open Files)限制, 分为系统全局的, 和进程级的限制.

全局限制

在 Linux 下执行:

cat /proc/sys/fs/file-nr

会打印出类似下面的一行输出:

5100	0	101747

  

第三个数字 101747 就是当前系统的全局最大打开文件数(Max Open Files), 可以看到, 只有 10 万, 所以, 在这台服务器上无法支持 C1000K. 很多系统的这个数值更小, 为了修改这个数值, 用 root 权限修改 /etc/sysctl.conf 文件:

fs.file-max = 1020000
net.ipv4.ip_conntrack_max = 1020000
net.ipv4.netfilter.ip_conntrack_max = 1020000

  

需要重启系统服务生效:

# Linux
$ sudo sysctl -p /etc/sysctl.conf
# BSD
$ sudo /etc/rc.d/sysctl reload

  

进程限制

执行:

ulimit -n

  

输出:

1024

说明当前 Linux 系统的每一个进程只能最多打开 1024 个文件. 为了支持 C1000K, 你同样需要修改这个限制.

临时修改

ulimit -n 1020000

不过, 如果你不是 root, 可能不能修改超过 1024, 会报错:

-bash: ulimit: open files: cannot modify limit: Operation not permitted

永久修改

编辑 /etc/security/limits.conf 文件, 加入如下行:

# /etc/security/limits.conf
work hard nofile 1020000
work soft nofile 1020000

第一列的 work 表示 work 用户, 你可以填 *, 或者 root. 然后保存退出, 重新登录服务器.

注意: Linux 内核源码中有一个常量(NR_OPEN in /usr/include/linux/fs.h), 限制了最大打开文件数, 如 RHEL 5 是 1048576(2^20), 所以, 要想支持 C1000K, 你可能还需要重新编译内核.

2. 操作系统维持百万连接需要多少内存?

解决了操作系统的参数限制, 接下来就要看看内存的占用情况. 首先, 是操作系统本身维护这些连接的内存占用. 对于 Linux 操作系统, socket(fd) 是一个整数, 所以, 猜想操作系统管理一百万个连接所占用的内存应该是 4M/8M, 再包括一些管理信息, 应该会是 100M 左右. 不过, 还有 socket 发送和接收缓冲区所占用的内存没有分析. 为此, 我写了最原始的 C 网络程序来验证:

服务器

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <arpa/inet.h>
#include <netinet/tcp.h>
#include <sys/select.h> #define MAX_PORTS 10 int main(int argc, char **argv){
struct sockaddr_in addr;
const char *ip = "0.0.0.0";
int opt = 1;
int bufsize;
socklen_t optlen;
int connections = 0;
int base_port = 7000;
if(argc > 2){
base_port = atoi(argv[1]);
} int server_socks[MAX_PORTS]; for(int i=0; i<MAX_PORTS; i++){
int port = base_port + i;
bzero(&addr, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons((short)port);
inet_pton(AF_INET, ip, &addr.sin_addr); int serv_sock;
if((serv_sock = socket(AF_INET, SOCK_STREAM, 0)) == -1){
goto sock_err;
}
if(setsockopt(serv_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) == -1){
goto sock_err;
}
if(bind(serv_sock, (struct sockaddr *)&addr, sizeof(addr)) == -1){
goto sock_err;
}
if(listen(serv_sock, 1024) == -1){
goto sock_err;
} server_socks[i] = serv_sock;
printf("server listen on port: %d\n", port);
} //optlen = sizeof(bufsize);
//getsockopt(serv_sock, SOL_SOCKET, SO_RCVBUF, &bufsize, &optlen);
//printf("default send/recv buf size: %d\n", bufsize); while(1){
fd_set readset;
FD_ZERO(&readset);
int maxfd = 0;
for(int i=0; i<MAX_PORTS; i++){
FD_SET(server_socks[i], &readset);
if(server_socks[i] > maxfd){
maxfd = server_socks[i];
}
}
int ret = select(maxfd + 1, &readset, NULL, NULL, NULL);
if(ret < 0){
if(errno == EINTR){
continue;
}else{
printf("select error! %s\n", strerror(errno));
exit(0);
}
} if(ret > 0){
for(int i=0; i<MAX_PORTS; i++){
if(!FD_ISSET(server_socks[i], &readset)){
continue;
}
socklen_t addrlen = sizeof(addr);
int sock = accept(server_socks[i], (struct sockaddr *)&addr, &addrlen);
if(sock == -1){
goto sock_err;
}
connections ++;
printf("connections: %d, fd: %d\n", connections, sock);
}
}
} return 0;
sock_err:
printf("error: %s\n", strerror(errno));
return 0;
}

  

注意, 服务器监听了 10 个端口, 这是为了测试方便. 因为只有一台客户端测试机, 最多只能跟同一个 IP 端口创建 30000 多个连接, 所以服务器监听了 10 个端口, 这样一台测试机就可以和服务器之间创建 30 万个连接了.

客户端

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <arpa/inet.h>
#include <netinet/tcp.h> int main(int argc, char **argv){
if(argc <= 2){
printf("Usage: %s ip port\n", argv[0]);
exit(0);
} struct sockaddr_in addr;
const char *ip = argv[1];
int base_port = atoi(argv[2]);
int opt = 1;
int bufsize;
socklen_t optlen;
int connections = 0; bzero(&addr, sizeof(addr));
addr.sin_family = AF_INET;
inet_pton(AF_INET, ip, &addr.sin_addr); char tmp_data[10];
int index = 0;
while(1){
if(++index >= 10){
index = 0;
}
int port = base_port + index;
printf("connect to %s:%d\n", ip, port); addr.sin_port = htons((short)port); int sock;
if((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1){
goto sock_err;
}
if(connect(sock, (struct sockaddr *)&addr, sizeof(addr)) == -1){
goto sock_err;
} connections ++;
printf("connections: %d, fd: %d\n", connections, sock); if(connections % 10000 == 9999){
printf("press Enter to continue: ");
getchar();
}
usleep(1 * 1000);
/*
bufsize = 5000;
setsockopt(serv_sock, SOL_SOCKET, SO_SNDBUF, &bufsize, sizeof(bufsize));
setsockopt(serv_sock, SOL_SOCKET, SO_RCVBUF, &bufsize, sizeof(bufsize));
*/
} return 0;
sock_err:
printf("error: %s\n", strerror(errno));
return 0;
}

  

我测试 10 万个连接, 这些连接是空闲的, 什么数据也不发送也不接收. 这时, 进程只占用了不到 1MB 的内存. 但是, 通过程序退出前后的 free 命令对比, 发现操作系统用了 200M(大致)内存来维护这 10 万个连接! 如果是百万连接的话, 操作系统本身就要占用 2GB 的内存! 也即 2KB 每连接.

可以修改

/proc/sys/net/ipv4/tcp_wmem
/proc/sys/net/ipv4/tcp_rmem

  

来控制 TCP 连接的发送和接收缓冲的大小(多谢 @egmkang).

3. 应用程序维持百万连接需要多少内存?

通过上面的测试代码, 可以发现, 应用程序维持百万个空闲的连接, 只会占用操作系统的内存, 通过 ps 命令查看可知, 应用程序本身几乎不占用内存.

4. 百万连接的吞吐量是否超过了网络限制?

假设百万连接中有 20% 是活跃的, 每个连接每秒传输 1KB 的数据, 那么需要的网络带宽是 0.2M x 1KB/s x 8 = 1.6Gbps, 要求服务器至少是万兆网卡(10Gbps).

总结

Linux 系统需要修改内核参数和系统配置, 才能支持 C1000K. C1000K 的应用要求服务器至少需要 2GB 内存, 如果应用本身还需要内存, 这个要求应该是至少 10GB 内存. 同时, 网卡应该至少是万兆网卡.

当然, 这仅仅是理论分析, 实际的应用需要更多的内存和 CPU 资源来处理业务数据.

测试工具

测试操作系统最大连接数的工具: https://github.com/ideawu/c1000k

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

参考:

http://www.cyberciti.biz/faq/linux-increase-the-maximum-number-of-open-files/
http://www.lognormal.com/blog/2012/09/27/linux-tcpip-tuning/

构建C1000K的服务器(1) – 基础的更多相关文章

  1. 【转】构建C1000K的服务器(1) – 基础

    原文来自 ideawu 构建C1000K的服务器(1) – 基础 著名的 C10K 问题提出的时候, 正是 2001 年, 到如今 12 年后的 2013 年, C10K 已经不是问题了, 任何一个普 ...

  2. 2013-09-16 构建C1000K的服务器(1) – 基础

    http://www.ideawu.net/blog/archives/740.html 著名的 C10K 问题提出的时候, 正是 2001 年, 到如今 12 年后的 2013 年, C10K 已经 ...

  3. 构建C1000K的服务器(2) – 实现百万连接的comet服务器

    转自:http://www.ideawu.net/blog/archives/742.html 这是关于 C1000K 序列文章的第二篇, 在前一篇文章 构建C1000K的服务器(1) – 基础 中, ...

  4. 利用openssl构建根证书-服务器证书-客户证书

    利用openssl构建根证书-服务器证书-客户证书 OpenSSL功能远胜于KeyTool,可用于根证书,服务器证书和客户证书的管理 一.构建根证书 1.构建根证书前,需要构建随机数文件(.rand) ...

  5. howto:在构建基于debian的docker基础镜像时,更换国内包源

    debian经常被用作构建应用镜像的基础镜像,如微软在构建linux下的dotnetcore基础镜像时,提供了基于debian 8(jessie)和debian 9(stretch)的镜像. 由于这些 ...

  6. 构建伪Update服务器工具isr-evilgrade

    构建伪Update服务器工具isr-evilgrade   现在大部分软件都提供更新功能.软件一旦运行,就自动检查对应的Update服务器.如果发现新版本,就会提示用户,并进行下载和安装.而用户往往相 ...

  7. C语言构建小型Web服务器

    #include <stdio.h> #include <sys/socket.h> #include <stdlib.h> #include <string ...

  8. 在NVIDIA-Jetson平台上构建智能多媒体服务器

    在NVIDIA-Jetson平台上构建智能多媒体服务器 Building a Multi-Camera Media Server for AI Processing on the NVIDIA Jet ...

  9. 自定义构建基于.net core 的基础镜像

    先说一个问题 首先记录一个问题,今天在用 Jenkins 构建项目的时候突然出现包源的错误: /usr/share/dotnet/sdk/2.2.104/NuGet.targets(114,5): e ...

随机推荐

  1. [HTTP趣谈]origin,referer和host区别

    发起一个ajax请求时,request header里面有三个属性会涉及请求源信息.前端可能用不到这些值,但是,后台业务系统会比较关心它们,场景可能有: 处理跨域请求时,必须判断来源请求方是否合法:  ...

  2. java 调度框架quartz

    核心代码如下: public class SchedulerTest { public static void main(String[] args) { //创建schedulerFactory类 ...

  3. 第八次 Java 作业 重写正方形周长方法

    # 题目 编写一个应用程序,创建一个矩形类,类中具有长.宽两个成员变量和求周长的方法. 再创建一个矩形类的子类——正方形类,类中定义求面积方法.重写求周长的方法. 在主类中,输入一个正方形边长,创建正 ...

  4. random库

    伪随机数的原因: random库中函数主要用于产生各种分布的伪随机数序列.random库中的随机函数是按照一定算法模拟产生的,其概率是确定的.可见的,所以被称为伪随机数.而真正意义上的随机数是按照实验 ...

  5. 使用nodegui 开发高性能的跨平台桌面端应用

    nodegui 是基于qt + nodejs 的跨平台桌面开发方案,官方同时也提供了很不错的文档 简单使用 使用官方的starter clone 代码 git clone https://github ...

  6. 3.深入学习Servlet的Response和Request

    一.HttpServletResponse web服务器接受到客户端的HTTP请求,对于这个请求分别创建一个代表请求的对象HttpServletRequest和一个代表响应的对象HttpServlet ...

  7. 用jdk1.6的pack200和unpack200,对jar文件进行压缩和解压 .pack.gz

    用jdk1.6的pack200和unpack200,对jar文件进行压缩和解压 解压xxx.jar.pack.gz为xxx.jar:unpack200 -r xxx.jar.pack.gz xxx.j ...

  8. GCC编译UDF和gdb调试UDF

    Fluent版本:19.0 前面我们介绍过使用VS来编译和调试UDF,其实我们也可以用GCC来编译UDF.gdb调试UDF.本次介绍的方法更具有通用性,也适用于Linux下Fluent的UDF编译和调 ...

  9. MVC WebApi Swagger帮助文档 接口用法

    1.WebApi在解决方案Apps.WebApi中 2.将Apps.WebApi设置为启动项目之后,可以直接浏览到Api的帮助文档,并直接进行调试 3.登录接口 4.登录获取的token来访问其他接口 ...

  10. Scrum Meeting 博客汇总

    Scrum Meeting 博客汇总 一.Scrum Meeting 1. Alpha 第一次 Scrum Meeting 第二次 Scrum Meeting 第三次 Scrum Meeting 第四 ...