高并发服务器的限制有哪些,如何提高并发量

简介: 高并发服务器的限制有哪些,如何提高并发量

并发量限制因素 (五元组)

五元组: (srcip, dstip, srcport, dstport, proto)

  1. 文件句柄, 文件描述符数量  open files
  2. 系统内存限制
  3. 端口数量限制
  4. 网络带宽的限制 (一般不做考虑)
  5. 数据库的并发量限制

准备

先将 open files 修改到 100W的上限

查看单个进程可以打开的文件句柄的数目, open files的大小

命令:ulimit

ulimit -a 显示当前所有的资源限制
ulimit -H 设置硬件资源限制
ulimit -S 设置软件资源限制
ulimit -n 设置进程最大打开文件描述符数

我的已经被我自己修改为了100W的量级了

修改方式:

命令修改 : ulimit -n  <value>    缺陷:不是永久修改, 不涉及写磁盘, 重启shell之后修改消失

修改配置文件  limits.conf 文件限制着用户可以使用的最大文件数,最大线程,最大内存等资源使用量。    vim /etc/security/limits.conf        涉及写磁盘, 每一次登录shell都会加载配置文件, 永久修改         配置文件记忆技巧, 资源使用限制涉及到系统安全, 故而在security中

并发量:服务器可以承载的客户端的连接数量, 也就是可以维护的 sockfd的数量.

客户端并发量测试

初始版本测试结果如下:

思考局限

srcip 客户端的ip是固定的 srcport 客户端的可用端口数理论值是 65535

dstip +  dstport 固定, 服务器ip 跟 端口固定

此时并发量可以达到2.8W, 然后报错不能分配地址了. 其实是客户端端口分配上限了.

如何打破

增加服务器端口数,  此时完全先从五元组确定唯一连接的方向切入,思考出可以增加服务端的监视窗口数量来提高并发量, 打破限制.       (多端口, 多窗口监视,有效提升客户接入量)

  • 将服务器的端口数开启到100个
  • 核心改变代码:

创建监视端口 init_listen_sock

int init_sock(short port) {
  int sockfd = socket(AF_INET, SOCK_STREAM, 0);
  if (sockfd == -1) {
    err_exit("socket");
  }
  sockaddr_in addr;
  memset(&addr, 0, sizeof(addr));
  addr.sin_port = htons(port);
  addr.sin_family = AF_INET;
  addr.sin_addr.s_addr = INADDR_ANY;
  if (-1 == bind(sockfd, (SA*)&addr, sizeof(addr))) {
    err_exit("bind");
  }
  if (-1 == listen(sockfd, 5)) {
    err_exit("listen");
  }
  return sockfd;
}
  //循环向eventloop中添加多监视窗口, 多port
    int i = 0;
  for (i = 0; i < LISTEN_PORT; ++i) {
    sockfd = init_sock(i + port);
    //将其加入到event_loop中
    struct epoll_event ev;
    ev.events = EPOLLIN;  //level 触发 
    //注册监视事件
    struct sockitem* si = (struct sockitem*)malloc(sizeof(struct sockitem));
    si->sockfd = sockfd;
    si->callback = accept_cb;//设置事件处理器
    ev.data.ptr = si;
    epoll_ctl(eventloop->epfd, EPOLL_CTL_ADD, sockfd, &ev);
  }
  • 再测试

经过漫长等待之后它终于还是没有达到100W左右, 而是killed了, 因为内存限制被killed了

其实这个不是正常现象,我这里是因为内存限制而产生了killed, 实际上,内存限制没有打破的情况下也还是无法达到100W, 会出现    connection timeout 连接超时的错误

超时连接问题何在

100 * 2.8W  服务端端口数(100端口) * 客户端端口数 (随机2.8W端口, 之前测试) 可以达到100W,所以接入量的限制不是五元组. 而是其他因素


针对connect的超时连接错误, 我们透过TCP三次握手去看, 问题在于服务端没有向客户端返回一个ACK, 导致了connect 超时.                                       --- server 的ACK为何没有到此时的限制其实在于协议栈了. iptables, 一种过滤装置  防火墙

connection timeout的解决办法

修改 /etc/sysctl.conf 配置文件, 打破限制.

net.nf_conntrack_max 就是防火墙的限制

fs.file-max = 1048576        
net.nf_conntrack_max = 1048576
net.ipv4.tcp_rmem =128 256 512
net.ipv4.tcp_wmem =128 256 512

至此其实可以完成百万接入量了, 只是我的服务器内存是在太小, 无法达到要求, 内存足够是可以跑到100W的.

实际开发中的处理方案

采用多进程的方式, 而不是多端口的方式.

测试代码:   MAX_PORT : 代表的是端口数, 与服务器端口数保持一致

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <errno.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <fcntl.h>
#define MAX_BUFFER    128
#define MAX_EPOLLSIZE (384*1024)
#define MAX_PORT    1
#define TIME_SUB_MS(tv1, tv2)  ((tv1.tv_sec - tv2.tv_sec) * 1000 + (tv1.tv_usec - tv2.tv_usec) / 1000)
int isContinue = 0;
static int ntySetNonblock(int fd) {
  int flags;
  flags = fcntl(fd, F_GETFL, 0);
  if (flags < 0) return flags;
  flags |= O_NONBLOCK;
  if (fcntl(fd, F_SETFL, flags) < 0) return -1;
  return 0;
}
// s设置好地址可复用
static int ntySetReUseAddr(int fd) {
  int reuse = 1;
  return setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *)&reuse, sizeof(reuse));
}
int main(int argc, char **argv) {
  if (argc <= 2) {
    printf("Usage: %s ip port\n", argv[0]);
    exit(0);
  }
  const char *ip = argv[1];
  int port = atoi(argv[2]);
  int connections = 0;
  char buffer[128] = {0};
  int i = 0, index = 0;
  struct epoll_event events[MAX_EPOLLSIZE];
  int epoll_fd = epoll_create(MAX_EPOLLSIZE);
  strcpy(buffer, " Data From MulClient\n");
  struct sockaddr_in addr;
  memset(&addr, 0, sizeof(struct sockaddr_in));
  addr.sin_family = AF_INET;
  addr.sin_addr.s_addr = inet_addr(ip);
  struct timeval tv_begin;
  gettimeofday(&tv_begin, NULL);
  while (1) {
    if (++index >= MAX_PORT) index = 0;
    struct epoll_event ev;
    int sockfd = 0;
    if (connections < 340000 && !isContinue) {
      sockfd = socket(AF_INET, SOCK_STREAM, 0);
      if (sockfd == -1) {
        perror("socket");
        goto err;
      }
      //ntySetReUseAddr(sockfd);
      addr.sin_port = htons(port+index);
      if (connect(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_in)) < 0) {
        perror("connect");
        goto err;
      }
      ntySetNonblock(sockfd);
      ntySetReUseAddr(sockfd);
      sprintf(buffer, "Hello Server: client --> %d\n", connections);
      send(sockfd, buffer, strlen(buffer), 0);
      ev.data.fd = sockfd;
      ev.events = EPOLLIN | EPOLLOUT;
      epoll_ctl(epoll_fd, EPOLL_CTL_ADD, sockfd, &ev);
      connections ++;
    }
    //connections ++;
    if (connections % 1000 == 999 || connections >= 340000) {
      struct timeval tv_cur;
      memcpy(&tv_cur, &tv_begin, sizeof(struct timeval));
      gettimeofday(&tv_begin, NULL);
      //计算出时间
      int time_used = TIME_SUB_MS(tv_begin, tv_cur);
      printf("connections: %d, sockfd:%d, time_used:%d\n", connections, sockfd, time_used);
      //每一次仅仅只是拿出来
      int nfds = epoll_wait(epoll_fd, events, connections, 100);
      for (i = 0;i < nfds;i ++) {
        int clientfd = events[i].data.fd;
        if (events[i].events & EPOLLOUT) {
          sprintf(buffer, "data from %d\n", clientfd);
          send(sockfd, buffer, strlen(buffer), 0);
        } else if (events[i].events & EPOLLIN) {
          char rBuffer[MAX_BUFFER] = {0};       
          ssize_t length = recv(sockfd, rBuffer, MAX_BUFFER, 0);
          if (length > 0) {
            printf(" RecvBuffer:%s\n", rBuffer);
            if (!strcmp(rBuffer, "quit")) {
              isContinue = 0;
            }
          } else if (length == 0) {
            printf(" Disconnect clientfd:%d\n", clientfd);
            connections --;
            close(clientfd);
          } else {
            if (errno == EINTR) continue;
            printf(" Error clientfd:%d, errno:%d\n", clientfd, errno);
            close(clientfd);
          }
        } else {
          printf(" clientfd:%d, errno:%d\n", clientfd, errno);
          close(clientfd);
        }
      }
    }
    usleep(1 * 1000);
  }
  return 0;
err:
  printf("error : %s\n", strerror(errno));
  return 0;
}

文章小结

做并发测试时候出现了问题,我们思考的方式是: 内存限制,  open files 文件句柄数限制,  五元组组合限制, 网络带宽, 数据库... 方向入手思考


对于各种系统资源的限制, 可以通过修改配置文件的方式做出永久修改   /etc/security/limits.conf       +     /etc/sysctl.conf  


五元组组合限制      防火墙限制


ulimit -a 查看所有的资源限制, free -h 查看内存限制, htop 动态观察CPU  + 内存占用情况, 便于分析异常


相关文章
|
3月前
|
开发框架 缓存 .NET
并发请求太多,服务器崩溃了?试试使用 ASP.NET Core Web API 操作筛选器对请求进行限流
并发请求太多,服务器崩溃了?试试使用 ASP.NET Core Web API 操作筛选器对请求进行限流
208 0
|
6月前
|
弹性计算
阿里云3M带宽云服务器并发多大?阿里云3M带宽云服务器测评参考
在探讨云服务器3M带宽能支持多大并发这一问题时,我们首先要明白一个关键点:并发量并非仅由带宽决定,还与网站本身的大小密切相关。一般来说,一个优化良好的普通网站页面大小可能只有几K,为便于计算,我们可以暂且假定每个页面大小为50K。
1235 1
|
1月前
|
缓存 NoSQL Ubuntu
大数据-39 Redis 高并发分布式缓存 Ubuntu源码编译安装 云服务器 启动并测试 redis-server redis-cli
大数据-39 Redis 高并发分布式缓存 Ubuntu源码编译安装 云服务器 启动并测试 redis-server redis-cli
55 3
|
6月前
|
算法 Java
并发垃圾回收算法对于大规模服务器应用的优势
并发垃圾回收算法对于大规模服务器应用的优势
|
5月前
|
Java
Java Socket编程与多线程:提升客户端-服务器通信的并发性能
【6月更文挑战第21天】Java网络编程中,Socket结合多线程提升并发性能,服务器对每个客户端连接启动新线程处理,如示例所示,实现每个客户端的独立操作。多线程利用多核处理器能力,避免串行等待,提升响应速度。防止死锁需减少共享资源,统一锁定顺序,使用超时和重试策略。使用synchronized、ReentrantLock等维持数据一致性。多线程带来性能提升的同时,也伴随复杂性和挑战。
100 0
|
6月前
|
弹性计算 缓存 测试技术
阿里云ECS云服务器2核4G能支持多少人同时访问?2核4G5M并发量评测
阿里云ECS云服务器2核4G能支持多少人同时访问?2核4G5M并发量评测,2核4G服务器并发数性能测试,阿小云账号下的2核4G服务器支持20人同时在线访问,然而应用不同、类型不同、程序效率不同实际并发数也不同,2核4G服务器的在线访问人数取决于多个变量因素
|
4月前
|
缓存 弹性计算 数据库
阿里云2核4G服务器支持多少人在线?程序效率、并发数、内存CPU性能、公网带宽多因素
2核4G云服务器支持的在线人数取决于多种因素:应用效率、并发数、内存、CPU、带宽、数据库性能、缓存策略、CDN和OSS使用,以及用户行为和系统优化。阿里云的ECS u1实例2核4G配置,适合轻量级应用,实际并发量需结合具体业务测试。
79 0
阿里云2核4G服务器支持多少人在线?程序效率、并发数、内存CPU性能、公网带宽多因素
|
4月前
|
监控 应用服务中间件 nginx
高并发架构设计三大利器:缓存、限流和降级问题之Nginx的并发连接数计数的问题如何解决
高并发架构设计三大利器:缓存、限流和降级问题之Nginx的并发连接数计数的问题如何解决
|
4月前
|
设计模式 安全 NoSQL
Java面试题:设计一个线程安全的单例模式,并解释其内存占用和垃圾回收机制;使用生产者消费者模式实现一个并发安全的队列;设计一个支持高并发的分布式锁
Java面试题:设计一个线程安全的单例模式,并解释其内存占用和垃圾回收机制;使用生产者消费者模式实现一个并发安全的队列;设计一个支持高并发的分布式锁
66 0
|
4月前
|
设计模式 存储 缓存
Java面试题:结合建造者模式与内存优化,设计一个可扩展的高性能对象创建框架?利用多线程工具类与并发框架,实现一个高并发的分布式任务调度系统?设计一个高性能的实时事件通知系统
Java面试题:结合建造者模式与内存优化,设计一个可扩展的高性能对象创建框架?利用多线程工具类与并发框架,实现一个高并发的分布式任务调度系统?设计一个高性能的实时事件通知系统
55 0