0


网络服务性能优化:Wrktcp与Perf工具详解

  • https://blog.csdn.net/radapp/article/details/140839872?spm=1001.2100.3001.7377&utm_medium=distribute.pc_feed_blog_category.none-task-blog-classify_tag-5-140839872-null-null.nonecase&depth_1-utm_source=distribute.pc_feed_blog_category.none-task-blog-classify_tag-5-140839872-null-null.nonecasewrktcp安装 码云地址:wrktcp: 支持tcp协议压测的wrk工具,全配置不依赖lua 直接下载,cd wrktcp-master && make,会生成wrktcp,就ok了,很简单
  • wrktcp使用 压测首先需要一个服务,写了一个epoll+边沿触发的服务,业务是判断ip是在国内还是国外,rq:00000015CHECKIP1.0.4.0,rs:000000010,写的有些就简陋兑付看吧,主要为了压测和分析性能瓶颈。
  1. #include <stdio.h>
  2. #include <ctype.h>
  3. #include <unistd.h>
  4. #include <stdlib.h>
  5. #include <sys/types.h>
  6. #include <sys/stat.h>
  7. #include <string.h>
  8. #include <arpa/inet.h>
  9. #include <sys/socket.h>
  10. #include <sys/epoll.h>
  11. #include <fcntl.h>
  12. #include <errno.h>
  13. #include <iostream>
  14. #include <sstream>
  15. #include <thread>
  16. #include <netinet/in.h>
  17. #include <netdb.h>
  18. #include <cstring>
  19. #include <map>
  20. #include <fstream>
  21. #include <cstdio>
  22. #include <cstdlib>
  23. #include <syslog.h>
  24. std::map<unsigned long, unsigned long> g_ip_list; // 存储 IP 范围
  25. bool init_ip_list(const char* file_name, std::map<unsigned long, unsigned long> &ip_list)
  26. {
  27. FILE *fp = nullptr;
  28. if ((fp = fopen(file_name, "r")) == nullptr)
  29. {
  30. return false;
  31. }
  32. int i = 0;
  33. int total_count = 0;
  34. char buf[64] = {0};
  35. while (fgets(buf, sizeof(buf), fp))
  36. {
  37. i++;
  38. if (buf[0] == '#')
  39. continue;
  40. char *pout = nullptr;
  41. char *pbuf = buf;
  42. char *pc[10];
  43. int j = 0;
  44. while ((pc[j] = strtok_r(pbuf, "|", &pout)) != nullptr)
  45. {
  46. j++;
  47. pbuf = nullptr;
  48. if (j > 7)
  49. break;
  50. }
  51. if (j != 7)
  52. {
  53. syslog(LOG_ERR, "%s:%d, unknown format the line is %d", __FILE__, __LINE__, i);
  54. continue;
  55. }
  56. if (strcmp(pc[2], "ipv4") == 0 && strcmp(pc[1], "CN") == 0)
  57. {
  58. unsigned long ip_begin = inet_addr(pc[3]);
  59. if (ip_begin == INADDR_NONE)
  60. {
  61. syslog(LOG_ERR, "%s:%d, ip is unknown, the line is %d, the ip is %s", __FILE__, __LINE__, i, pc[3]);
  62. continue;
  63. }
  64. int count = atoi(pc[4]);
  65. ip_begin = ntohl(ip_begin);
  66. unsigned long ip_end = ip_begin + count - 1;
  67. ip_list.insert(std::make_pair(ip_end, ip_begin));
  68. total_count++;
  69. }
  70. }
  71. syslog(LOG_INFO, "%s:%d, init_ip_list, total count is %d", __FILE__, __LINE__, total_count);
  72. fclose(fp);
  73. return true;
  74. }
  75. void extract_ip(char *buf, char *ip) {
  76. // 假设协议字符串格式总是 "00000015CHECKIPx.x.x.x"
  77. // 找到IP地址的起始位置
  78. char *start = strstr(buf, "CHECKIP");
  79. if (start == NULL) {
  80. fprintf(stderr, "Invalid protocol string\n");
  81. return;
  82. }
  83. // 跳过"CHECKIP"
  84. start += 7;
  85. // 复制IP地址到ip变量,注意检查边界
  86. strncpy(ip, start, 15); // IP地址最多15个字符,包括'\0'
  87. ip[15] = '\0'; // 确保字符串以'\0'结尾
  88. }
  89. // server
  90. int main(int argc, const char* argv[])
  91. {
  92. const char* file_name = "ip_list.txt";
  93. if (!init_ip_list(file_name, g_ip_list)) {
  94. std::cerr << "Failed to initialize IP list." << std::endl;
  95. return 1;
  96. }
  97. // 创建监听的套接字
  98. int lfd = socket(AF_INET, SOCK_STREAM, 0);
  99. if(lfd == -1)
  100. {
  101. perror("socket error");
  102. exit(1);
  103. }
  104. // 绑定
  105. struct sockaddr_in serv_addr;
  106. memset(&serv_addr, 0, sizeof(serv_addr));
  107. serv_addr.sin_family = AF_INET;
  108. serv_addr.sin_port = htons(9999);
  109. serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 本地多有的IP
  110. // 127.0.0.1
  111. // inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr.s_addr);
  112. // 设置端口复用
  113. int opt = 1;
  114. setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
  115. // 绑定端口
  116. int ret = bind(lfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
  117. if(ret == -1)
  118. {
  119. perror("bind error");
  120. exit(1);
  121. }
  122. // 监听
  123. ret = listen(lfd, 64);
  124. if(ret == -1)
  125. {
  126. perror("listen error");
  127. exit(1);
  128. }
  129. // 现在只有监听的文件描述符
  130. // 所有的文件描述符对应读写缓冲区状态都是委托内核进行检测的epoll
  131. // 创建一个epoll模型
  132. int epfd = epoll_create(100);
  133. if(epfd == -1)
  134. {
  135. perror("epoll_create");
  136. exit(0);
  137. }
  138. // 往epoll实例中添加需要检测的节点, 现在只有监听的文件描述符
  139. struct epoll_event ev;
  140. ev.events = EPOLLIN; // 检测lfd读读缓冲区是否有数据
  141. ev.data.fd = lfd;
  142. ret = epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);
  143. if(ret == -1)
  144. {
  145. perror("epoll_ctl");
  146. exit(0);
  147. }
  148. struct epoll_event evs[1024];
  149. int size = sizeof(evs) / sizeof(struct epoll_event);
  150. // 持续检测
  151. while(1)
  152. {
  153. // 调用一次, 检测一次
  154. int num = epoll_wait(epfd, evs, size, -1);
  155. printf("==== num: %d\n", num);
  156. for(int i=0; i<num; ++i)
  157. {
  158. // 取出当前的文件描述符
  159. int curfd = evs[i].data.fd;
  160. // 判断这个文件描述符是不是用于监听的
  161. if(curfd == lfd)
  162. {
  163. // 建立新的连接
  164. int cfd = accept(curfd, NULL, NULL);
  165. // 将文件描述符设置为非阻塞
  166. // 得到文件描述符的属性
  167. int flag = fcntl(cfd, F_GETFL);
  168. flag |= O_NONBLOCK;
  169. fcntl(cfd, F_SETFL, flag);
  170. // 新得到的文件描述符添加到epoll模型中, 下一轮循环的时候就可以被检测了
  171. // 通信的文件描述符检测读缓冲区数据的时候设置为边沿模式
  172. ev.events = EPOLLIN | EPOLLET; // 读缓冲区是否有数据
  173. ev.data.fd = cfd;
  174. ret = epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);
  175. if(ret == -1)
  176. {
  177. perror("epoll_ctl-accept");
  178. exit(0);
  179. }
  180. }
  181. else
  182. {
  183. // 处理通信的文件描述符
  184. // 接收数据
  185. char buf[128];
  186. memset(buf, 0, sizeof(buf));
  187. // 循环读数据
  188. while(1)
  189. {
  190. int len = recv(curfd, buf, sizeof(buf)-1, 0);
  191. if(len == 0)
  192. {
  193. // 非阻塞模式下和阻塞模式是一样的 => 判断对方是否断开连接
  194. printf("客户端断开了连接...\n");
  195. // 将这个文件描述符从epoll模型中删除
  196. epoll_ctl(epfd, EPOLL_CTL_DEL, curfd, NULL);
  197. close(curfd);
  198. break;
  199. }
  200. else if(len > 0)
  201. {
  202. // 通信
  203. // 接收的数据打印到终端
  204. write(STDOUT_FILENO, buf, len);
  205. char ip[16]; // 存储IP地址
  206. extract_ip(buf, ip);
  207. printf("Received IP: %s\n", ip);
  208. // 发送数据
  209. //send(curfd, buf, len, 0);
  210. // 验证 IP 地址
  211. struct in_addr address;
  212. int result = inet_pton(AF_INET, ip, &address); // 检查 IP 地址的有效性
  213. if (result < 0) {
  214. std::cout << "Invalid IP address: " << result << " " << ip << std::endl;
  215. send(curfd, "-Err\n", 5, 0);
  216. continue;
  217. }
  218. unsigned long ip_num = ntohl(address.s_addr);
  219. auto it = g_ip_list.lower_bound(ip_num);
  220. if (it != g_ip_list.end() && it->first >= ip_num && it->second <= ip_num) {
  221. send(curfd, "000000010", 9, 0); // 国内
  222. } else {
  223. send(curfd, "000000011", 9, 0); // 国外
  224. }
  225. }
  226. else
  227. {
  228. // len == -1
  229. if(errno == EAGAIN)
  230. {
  231. printf("数据读完了...\n");
  232. close(curfd);
  233. break;
  234. }
  235. else
  236. {
  237. perror("recv");
  238. exit(0);
  239. }
  240. }
  241. }
  242. }
  243. }
  244. }
  245. return 0;
  246. }

编译g++ epoll_test.cpp -o epoll_test,直接执行./epoll_test,监听0的9999端口

  • wrk配置文件sample_tiny.ini
  1. [common]
  2. # ip & port
  3. host = 127.0.0.1
  4. port = 9999
  5. [request]
  6. req_body = CHECKIP1.0.4.0
  7. [response]
  8. rsp_code_location = head

说下其中的坑,req_body就是要发的协议,但是wrktcp会在前面加长度固定8位:00000015;默认成功成功响应码是000000,设置rsp_code_location这个会让wrktcp从返回协议(000000010)头开始找成功响应码
上面那些说明:wrktcp的README有一些说明,但解释的不太全,需要自己去试和看源码

  • todo 固定协议前面加8位长度,不可能每个服务都是这样的协议,怎么去自定义的协议,希望大佬指教,好像wrk可以自定义协议。
  • wrk压测命令 ./wrktcp -t15 -c15 -d100s --latency sample_tiny.ini
  1. -t, --threads: 使用线程总数,一般推荐使用CPU核数的2倍-1
  2. -c, --connections: 连接总数,与线程无关。每个线程的连接数为connections/threads
  3. -d, --duration: 压力测试时间, 可以写 2s, 2m, 2h
  4. --latency: 打印延迟分布情况
  5. --timeout: 指定超时时间,默认是5000毫秒,越长占用统计内存越大。
  6. --trace: 打印出分布图
  7. --html: 将压测的结果数据,输出到html文件中。
  8. --test: 每个连接只会执行一次,一般用于测试配置是否正确。
  9. -v --version: 打印版本信息

测试了两遍,TPS能维持在1600左右

  1. Running 2m loadtest @ 127.0.0.1:9999 using sample_tiny.ini
  2. 15 threads and 15 connections
  3. Time:100s TPS:1644.64/0.00 Latency:7.69ms BPS:14.45KB Error:0
  4. Thread Stats Avg Stdev Max +/- Stdev
  5. Latency 4.66ms 14.17ms 318.09ms 98.89%
  6. Req/Sec 113.66 233.09 1.69k 94.95%
  7. Latency Distribution
  8. 50% 823.00us
  9. 75% 8.17ms
  10. 90% 9.15ms
  11. 99% 23.08ms
  12. 164554 requests in 1.67m, 1.41MB read
  13. Requests/sec: 1643.21 (Success:1643.21/Failure:0.00)
  14. Transfer/sec: 14.44KB
  • perf 压测监测服务:perf record -p 10263 -a -g -F 99 -- sleep 10 参数说明: -p : 进程 -a : 记录所有事件 -g : 启用基于 DWARF 调试信息的函数调用栈跟踪。这将记录函数调用栈信息,使得生成的报告更加详细,能够显示出函数调用的关系。 -F : 采样频率 --sleep:执行 sleep 命令,使系统休眠 10 秒钟。在这个期间,perf record 将记录指定进程的性能数据。

会在当前目录生成perf.data文件,执行perf report,会看到printf和write占用的CPU比较高,删除上面服务的printf和write函数,重新压测

重新压测之后,TPS能维持在3W+

  1. Running 2m loadtest @ 127.0.0.1:9999 using sample_tiny.ini
  2. 15 threads and 15 connections
  3. Time:100s TPS:32748.45/0.00 Latency:438.00us BPS:287.83KB Error:0
  4. Thread Stats Avg Stdev Max +/- Stdev
  5. Latency 519.35us 1.24ms 63.18ms 97.47%
  6. Req/Sec 2.19k 536.83 4.83k 76.97%
  7. Latency Distribution
  8. 50% 349.00us
  9. 75% 426.00us
  10. 90% 507.00us
  11. 99% 5.12ms
  12. 3275261 requests in 1.67m, 28.11MB read
  13. Requests/sec: 32716.39 (Success:32716.39/Failure:0.00)
  14. Transfer/sec: 287.55KB

本文转载自: https://blog.csdn.net/2401_86940371/article/details/142274195
版权归原作者 脚步的影子 所有, 如有侵权,请联系我们删除。

“网络服务性能优化:Wrktcp与Perf工具详解”的评论:

还没有评论