Linux服务端并发数怎么考虑?

1. 开场白

在开始今天的文章之前,先抛一个面试题出来:
你接触过的单机最大并发数是多少?
你认为当前正常配置的服务器物理机最大并发数可以到多少?
说说你的理解和分析。
思考几分钟,如果你可以有理有据地说出答案,那确实就不用再往下看了,关上手机去陪陪家人是个不错的选择。
在这里插入图片描述
对于后端开发人员来说,并发数往往和技术难度是呈正相关的,实际上也确实如此:体量决定架构。
服务端根据不同业务场景会有不同的侧重点,单纯追求高并发其实并不是根本目的,高可用&稳定性更重要。
所以最终我们的目的是:保证高可用高稳定的基础上追求高并发,降本增效。
高可用&高并发是我们直观感受到的,本质上这是个复杂的系统工程,每个环节都会影响结果,每一块都值得研究和深入。
在这里插入图片描述
2. 服务器最大并发数分析

服务器最大的并发上限是多少?
在这里插入图片描述
2.1 五元组

做过通信的盆友们一定听过五元组这个概念,一个五元组可以唯一标记一个网络连接,所以要理解和分析最大并发数,就必须理解五元组:
这样的话,就可以基本认为:理论最大并发数 = 服务端唯一五元组数。
在这里插入图片描述
2.2 端口&IP组合数

那么对于服务器来说,服务端唯一五元组数最大是多少呢?
有人说是65535,显然不是,但是之所以会有这类答案是因为当前Linux的端口号是2字节大小的short类型,总计2^16个端口,除去一些系统占用的端口,可用端口确实只剩下64000多了。
对于服务端本身来说,DestPort数量确实有限,假定有多张网卡,每个网卡绑定多个IP,服务端的Port端口数和IP数的组合类型也是有限的。
对于客户端来说,本身的端口和IP也是一样有限的,虽然这是个组合问题,但是数量还是有限的:
在这里插入图片描述
2.3 并发数理论极限

看了前面的端口&IP的组合数计算,好像并发数并不会特别大。
错了,是真的会很大。
分析一下,前面的计算都是针对单个服务器或者客户端的,但是实际上每个服务器会应对全网的所有客户端,那么从服务端看,源IP和源Port的数量是非常大的。
理论上服务端可以接受的客户端IP是232(按照IPv4计算),端口数是216,目前端口号仍然是16bit的,所有这个理论最大值是2^48,果然很大!
在这里插入图片描述
2.4 实际情况
天下没有免费的午餐。
每一条连接都是要消耗系统资源的,所以实际中可能会设置最大并发数来保证服务器的安全和稳定,所以这个理论最大并发数是不可能达到的。
实际中并发数和业务是直接相关的,像Redis这种内存型的服务端并发十几万都是没问题的,大部分来讲几十/几百/几千/几万等是存在的。

3. 客户端最大连接数

理解了服务器的最大并发数是2^48,那么客户端最多可以连接多少服务器呢?
对于客户端来说,当然可以借助于多网卡多IP来增加连接能力,我们仍然假定客户端只有1张网卡1个IP,由于端口数的限制到2^16,再去掉系统占用的端口,剩下可用的差不多64000。
也就是说,客户端虽然可以连接任意的目的IP和目的端口,但是客户端自身端口是有限的,所以客户端的理论最大连接数是2^16,含系统占用端口。
在这里插入图片描述
4. NAT环境下的客户端
解决前面的两个问题之后,来看另外一个问题:
一个公网出口NAT服务设备最多可同时支持多少内网IP并发访问外网服务?
毕竟公网IP都是有限并且要花钱的,我们大部分机器都是在局域网中结合NAT来进行外网访问的,所以这个场景还是很熟悉的。
来看下内网机器访问外网时的IP&端口替换和映射还原的过程,就明白了:
因为这时的客户端是NAT设备,所以NAT环境下最多支持65535个并发访问外网。
在这里插入图片描述
5.小结
虽然理论服务端并发数非常大,但是我们也没有必要觉得并发数高就厉害,服务复杂程度不一样,切忌唯并发数来判断业务和开发者水平。
试想echo服务和订单交易服务显然是不一样的,我们应该做的是在服务稳定和高可用的前提下去从缓存/网络/数据库等多个角度来优化提高性能。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,这是一个比较复杂的问题。下面是一个简单的C++代码框架,可以实现一个基于Linux服务端,可以接受多个客户端的连接,并将收到的据存储到据库中。 ```c++ #include <iostream> #include <sys/socket.h> #include <arpa/inet.h> #include <unistd.h> #include <string.h> #include <thread> #include <queue> #include <mutex> #include <condition_variable> #include <mysql/mysql.h> // 需要安装mysql开发包 using namespace std; // 据库连接参 const char* db_host = "localhost"; const char* db_user = "root"; const char* db_password = "password"; const char* db_name = "database_name"; // 线程池参 const int thread_num = 4; // 线程 queue<int> client_queue; // 客户端队列 mutex client_mutex; // 客户端队列锁 condition_variable client_cv; // 客户端队列条件变 // 据库连接 MYSQL* mysql_conn; // 处理客户端请求的线程函 void process_client(int client_fd) { // TODO: 处理客户端请求,将据存储到据库中 } // 线程池函 void thread_pool() { while (true) { unique_lock<mutex> lock(client_mutex); client_cv.wait(lock, []{ return !client_queue.empty(); }); int client_fd = client_queue.front(); client_queue.pop(); lock.unlock(); process_client(client_fd); } } int main() { // 初始化据库连接 mysql_conn = mysql_init(NULL); if (mysql_conn == NULL) { cerr << "mysql_init error: " << mysql_error(mysql_conn) << endl; exit(1); } if (mysql_real_connect(mysql_conn, db_host, db_user, db_password, db_name, 0, NULL, 0) == NULL) { cerr << "mysql_real_connect error: " << mysql_error(mysql_conn) << endl; exit(1); } // 创建套接字 int server_fd = socket(AF_INET, SOCK_STREAM, 0); if (server_fd == -1) { cerr << "socket error" << endl; exit(1); } // 绑定套接字 sockaddr_in server_addr; server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = htonl(INADDR_ANY); server_addr.sin_port = htons(8888); if (bind(server_fd, (sockaddr*)&server_addr, sizeof(server_addr)) == -1) { cerr << "bind error" << endl; exit(1); } // 监听套接字 if (listen(server_fd, 10) == -1) { cerr << "listen error" << endl; exit(1); } // 创建线程池 for (int i = 0; i < thread_num; i++) { thread([&]{ thread_pool(); }).detach(); } // 接受客户端连接,加入客户端队列 while (true) { int client_fd = accept(server_fd, NULL, NULL); if (client_fd == -1) { cerr << "accept error" << endl; continue; } unique_lock<mutex> lock(client_mutex); client_queue.push(client_fd); lock.unlock(); client_cv.notify_one(); } return 0; } ``` 上述代码中的主要逻辑是:首先初始化一个MySQL连接,在主线程中创建一个套接字并进行监听。然后创建一个线程池,线程为4。每个线程会不断从客户端队列中取出客户端套接字,并调用process_client函进行处理。process_client函中可以使用MySQL的C API来连接据库,将据存储到据库中。 当有新的客户端连接时,将其加入到客户端队列中,然后通过条件变通知线程池中的线程进行处理。由于客户端队列是共享资源,因此需要使用互斥锁进行保护。 需要注意的是,以上代码仅为一个简单的框架,还有一些细节需要根据具体情况进行处理,如异常处理、据库连接池等。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值