案例学习-Sysbench压测故障

背景: 故障源于知识星球的一个案例: 我们的数据库需要做在线升级丝滑的验证,所以构造了一个测试环境,客户端Sysbench 用长连接一直打压力,同时Server 端的数据库做在线升级,这个在线升级会让 Server进程重启,所以毫无疑问连接会断开重连,所以期望升级的时候 Sysbench端 QPS 跌0几秒钟然后快速恢复。 但是每次升级都是 Sysbench端 QPS永久跌0,再也不能恢复,所以需要分析为什么,问题出在哪里?有人说是服务端的问题因为只有服务端做了变更 整个测试过程中 Sysbench 是配置的2个连接去压 Server 故障重现: https://articles.zsxq.com/id_zj5qazqa4odz.html 下面也保留一份。 1. 数据库 docker run -it -d --net=host -e MYSQL_ROOT_PASSWORD=123 --name=plantegg mysql 2. 安装依赖包 yum install -y java-1.8.0-openjdk.x86_64 java-1.8.0-openjdk-devel.x86_64 podman-docker.noarch wireshark sysbench mysql.x86_64 mysql -h127.1 -uroot -p123 3. 生成数据库 mysql -h127.1 -uroot -p123 -e "create database test" 4. 生成测试数据 sysbench --mysql-user='root' --mysql-password='123' --mysql-db='test' --mysql-host='127.0.0.1' --mysql-port='3306' --tables='16' --table-size='10000' --range-size='5' --db-ps-mode='disable' --skip-trx='on' --mysql-ignore-errors='all' --time='1180' --report-interval='1' --histogram='on' --threads=1 oltp_read_only prepare 5. 压测 sysbench --mysql-user='root' --mysql-password='123' --mysql-db='test' --mysql-host='127.0.0.1' --mysql-port='3306' --tables='16' --table-size='10000' --range-size='5' --db-ps-mode='disable' --skip-trx='on' --mysql-ignore-errors='all' --time='1180' --report-interval='1' --histogram='on' --threads=1 oltp_read_only run 6. kill,mysql进程,复现故障 # mysql -h127.1 -uroot -p123 Welcome to the MariaDB monitor. Commands end with ; or \g. Your MySQL connection id is 14 Server version: 8.3.0 MySQL Community Server - GPL Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. MySQL [(none)]> show processlist; +----+-----------------+-----------------+------+---------+------+------------------------+------------------+ | Id | User | Host | db | Command | Time | State | Info | +----+-----------------+-----------------+------+---------+------+------------------------+------------------+ | 5 | event_scheduler | localhost | NULL | Daemon | 94 | Waiting on empty queue | NULL | | 13 | root | 127.0.0.1:33492 | test | Sleep | 0 | | NULL | | 14 | root | 127.0.0.1:51398 | NULL | Query | 0 | init | show processlist | +----+-----------------+-----------------+------+---------+------+------------------------+------------------+ MySQL [(none)]> kill 13; Query OK, 0 rows affected (0.000 sec) ...

2024-04-01 · 6 min · 1098 words

MQTT

MQTT ( Message Queuing Telemetry Transport).它是一种轻量级的发布-订阅网络协议,用于在设备之间传输消息。 MQTT 专为硬件资源有限或网络带宽有限的远程位置连接而设计,非常适合 machine-to-machine (M2M) 通信和物联网 (IoT) 应用。 历史: Andy Stanford-Clark (IBM ) 和 Arlen Nipper(当时在Eurotech, Inc.工作)于 1999 年编写了该协议的第一个版本。 它用于监控SCADA工业控制系统内的石油管道。目标是建立一个带宽高效、重量轻且消耗很少电池电量的协议,因为这些设备是通过卫星链路连接的,而卫星链路在当时是极其昂贵的。 从历史上看,“MQTT”中的“MQ”来自IBM MQ(当时的“MQSeries”)产品线,代表“消息队列”,其实他并没有队列。 涉及几个版本: v3.1, 3.1.1 ,5.0。应用比较广泛的是3.1.1版本。MQTT-SN(传感器网络的 MQTT)是主要协议的变体,针对非 TCP/IP 网络上的电池供电嵌入式设备,例如Zigbee。 from wiki https://en.wikipedia.org/wiki/MQTT#cite_note-6 优势及缺点 1. 优势 效率和低带宽使用:MQTT消息小且需要最小的带宽,使得该协议成为具有有限处理能力的IoT设备和带宽受限网络环境的理想选择。 轻量级协议:其简单性和低代码占用使其易于在具有有限内存和处理能力的设备上实施,例如微控制器和小型传感器。 解耦通信:发布-订阅模型允许设备和服务器之间进行解耦通信。发布者和订阅者不需要彼此了解,提高了系统的可扩展性和灵活性。 服务质量等级:MQTT支持三个级别的服务质量(QoS)进行消息传递,使用户可以根据应用程序要求选择更快的传递但可靠性较低,或者传递速度较慢但保证传递。 保留消息:MQTT代理可以保留主题上的消息,确保新订阅者立即收到最后发布的消息,即使在他们订阅之前就已发送。 Last Will 和 Testament (LWT)功能:如果设备意外断开连接,允许设备发布消息到指定的主题,使得设备断开连接的监控和通知更加方便。 安全性:支持使用SSL/TLS进行消息加密的安全通信,除了应用层安全实践,如身份验证和授权。 2. 缺点 依赖代理:中心代理架构可能成为失败的单一点。如果代理关闭,整个消息系统将受到影响。 安全开销:虽然MQTT支持SSL/TLS,但实施这些安全措施可能会增加复杂性和开销,特别是在非常受限的设备上。 服务质量开销:更高的QoS级别(1和2)确保消息传递,但引入了额外的开销,这可能影响性能,特别是在受限网络上。 消息载荷:MQTT不强制执行任何载荷格式,这意味着消息的解释取决于发布者和订阅者之间的协议。这种灵活性很强大,但如果不仔细管理,也可能导致不一致。 代理性能和可扩展性:MQTT系统的性能和可扩展性可能严重依赖于代理的能力。大量的消息或大量的客户端需要一个健壮、配置良好的代理。 其中MQTT的payload格式是没有要求的, 可以是二进制, 名为字符串, json/xml或者自定义个格式, 对于业务的哦通讯协议要双方协定好。 使用场景 1. 物联网(IoT) MQTT 在 IoT 中得到广泛使用,用于将各种设备、传感器和执行器连接到互联网或其他网络。这使得可以远程监控和控制设备,如智能家电、环境传感器和工业机器。 智能家居:远程控制照明、供暖、安全系统和其他家用电器。 农业:监控土壤湿度和温度传感器,以优化灌溉系统。 2. 工业自动化 ...

2024-03-17 · 3 min · 528 words

io_uring echo-server

io_uring io_uring是Linux特有的异步I/O API。io_uring的名称来自用户空间和内核空间之间共享的环形缓冲区。在内核和用户空间之间进行通信使用环形缓冲区作为主要的通信模式。这种思想在业务系统中还是挺常见的: 比如用MQ、Kafka消息队列推送信息。一个接收队列, 一个发送队列,另外设计上也有Actor模型的影子, 应用和kernel, 分别是两个actor, 而actor的邮箱就是 SQ, CQ两个队列, 对于应用程序, 消息发送到SQ, 从CQ中获取结果,对于kernel是从SQ中获取信息, 将处理结果发送到CQ中。 传统echoserver 做一个简单echoserver。 #include <arpa/inet.h> #include <assert.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #define BUF_SIZE 512 int main(int argc, char const* argv[]) { int serverfd, clientfd; struct sockaddr_in server, client; int len; int port = 1234; char buffer[BUF_SIZE]; if (argc == 2) { port = atoi(argv[1]); } serverfd = socket(AF_INET, SOCK_STREAM, 0); if (serverfd < 0) { perror("create socket"); exit(1); } server.sin_family = AF_INET; server.sin_addr.s_addr = INADDR_ANY; server.sin_port = htons(port); len = sizeof(server); if (bind(serverfd, (struct sockaddr*)&server, len) < 0) { perror("bind socket"); exit(1); } if (listen(serverfd, 2048) < 0) { perror("listen socket"); exit(1); } while (1) { len = sizeof(client); if ((clientfd = accept(serverfd, (struct sockaddr*)&client, &len)) < 0) { perror("accept error"); exit(4); } memset(buffer, 0, sizeof(buffer)); int size = read(clientfd, buffer, sizeof(buffer)); if (size < 0) { perror("read error"); exit(5); } if (size == 0) { close(clientfd); continue; } if (write(clientfd, buffer, size) < 0) { perror("write error"); exit(6); } close(clientfd); } close(serverfd); return 0; } 使用python写一个客户端 ...

2024-01-07 · 8 min · 1623 words

C struct检查成员是否存在

如何判断C struct中是否存在某一成员,最开始想法是通过offsetof宏定义 #define offsetof(st, m) \ ((size_t)&(((st *)0)->m)) C语言的offsetof()宏是ANSI C标准库中的一个特性,位于stddef.h头文件中。它用于计算给定结构体或联合体类型中某个成员的偏移量(以字节为单位), 但是这种方式仅能检查存在的成员, 不存在编译会有下面类似报错 XXX.c:11:35: error: ‘struct MyStruct’ has no member named ‘member3’; did you mean ‘member1’? 生成成员名的map C/C++ 不支持反射,与例如 Java 或 C# 不同。这意味着在 C/C++ 中,你不能在运行时"检测"未知结构(或类)的成员,你必须在编译时知道它 - 通常是通过 #include 包含定义结构的头文件。实际上,你不能访问未定义结构的内容 - 只能传递指针。 from https://cplusplus.com/forum/beginner/283156/ 同样作者给出了一个解决方案,那就是把对象放到map中, 对于C语言的,可以使用脚本处理一下。 大概思路根据脚本解析结构体,生成包含成员的数组,通过数组判断 # generate_struct_member_array.py import re def filter_content(content): # Remove comments content = re.sub(r'\/\*[\s\S]*?\*\/|\/\/.*', '', content) # Remove #define statements content = re.sub(r'#define\s+\w+\s+\w+', '', content) return content def parse_header(header_content): struct_pattern = re.compile(r'struct\s+(\w+)\s*{([^}]*)};', re.DOTALL) structs = struct_pattern.findall(header_content) struct_member_arrays = [] for struct_name, struct_body in structs: members = re.findall(r'\b\w+\s+(\w+)\s*(?:\[\w*\])?;', struct_body) struct_member_arrays.append((struct_name, members)) return struct_member_arrays def generate_array_code(struct_member_arrays): code = "" for struct_name, members in struct_member_arrays: code += f"const char *{struct_name}_member_names[] = {{\n" for member in members: code += f'\t"{member}",\n ' code = code.rstrip(', ') code += "};\n" return code def write_to_file(file_path, content): with open(file_path, 'w') as output_file: output_file.write(content) def read_and_generate(input_file_path, output_file_path): # Read the input header file content with open(input_file_path, 'r') as header_file: header_content = header_file.read() # Filter content (remove comments and #define statements) filtered_content = filter_content(header_content) # Parse the header file struct_member_arrays = parse_header(filtered_content) # Generate code for arrays array_code = generate_array_code(struct_member_arrays) # Write generated code to a new file write_to_file(output_file_path, array_code) print(f"Generated code has been written to {output_file_path}") # Specify the path to the input header file and the output header file input_header_file_path = 'hello.h' output_header_file_path = 'generated_define.h' # Read the input header file, generate code, and write to the output header file read_and_generate(input_header_file_path, output_header_file_path) hello.h ...

2024-01-01 · 4 min · 753 words

SNI

SNI是TLS协议扩展, 在握手的开始标识其尝试连接的主机名, 当多个HTTPS服务部署在同一IP地址上,客户端就可以通过这个标识指定它将使用哪一个服务, 同时服务端也无需使用相同的证书,它在概念上相当于HTTP/1.1基于名称的虚拟主机。SNI扩展最早在2003年的RFC 3546中出现。 HTTP服务通过 Http header “Host”, 来选择指定服务, HTTPS服务就是通过这个SNI来区分。通过可以通过nginx来验证一下 #user nobody; worker_processes 1; #error_log logs/error.log; error_log logs/error.log debug; #error_log logs/error.log info; #pid logs/nginx.pid; events { worker_connections 1024; } http { access_log logs/access.log ; server { listen 8443 ssl ; server_name test; ssl_certificate certs/cert.crt; ssl_certificate_key certs/cert.key; location / { return 200 "https-test\r\n"; } } server { listen 8443 ssl ; server_name garlic; ssl_certificate certs/cert2.crt; ssl_certificate_key certs/cert.key; location / { return 200 "https-garlic\r\n"; } } server { listen 8443 ssl ; server_name 10.10.10.10; ssl_certificate certs/cert3.crt; ssl_certificate_key certs/cert.key; location / { root html; index index.html index.htm; } } server { listen 9443 ssl ; server_name snitest; ssl_certificate certs/cert4.crt; ssl_certificate_key certs/cert.key; location / { root html; index index.html index.htm; } } server { listen 8080 ; server_name http-test; location / { return 200 "http-test\r\n"; } } server { listen 8080 ; server_name http-garlic; location / { return 200 "http-garlic\r\n"; } } } ...

2023-12-11 · 4 min · 648 words

linux sendfile

文件复制 使用c语言实现一下文件复制功能: 第一个版本:使用fgetc, fputc。 #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/sendfile.h> #include <stdio.h> #include <unistd.h> int main(int argc, char **argv) { const char *fromfile = argv[1]; const char *tofile = argv[2]; char c; FILE *fromfd = fopen(fromfile, "r"); FILE *tofd = fopen(tofile, "w"); while ((c = fgetc(fromfd)) != EOF) { fputc(c, tofd); } fclose(fromfd); fclose(tofd); } 使用fallocate -l 512M a1.txt, 生成一个512M的文件 garlic@garlic:~/sendfile$ sudo strace -c ./fget a1.txt b1.txt % time seconds usecs/call calls errors syscall ------ ----------- ----------- --------- --------- ---------------- 58.23 4.191305 31 131072 write 41.77 3.006373 22 131074 read 0.00 0.000051 12 4 close 0.00 0.000000 0 8 mmap 0.00 0.000000 0 3 mprotect 0.00 0.000000 0 1 munmap 0.00 0.000000 0 3 brk 0.00 0.000000 0 2 pread64 0.00 0.000000 0 1 1 access 0.00 0.000000 0 1 execve 0.00 0.000000 0 2 1 arch_prctl 0.00 0.000000 0 1 set_tid_address 0.00 0.000000 0 4 openat 0.00 0.000000 0 4 newfstatat 0.00 0.000000 0 1 set_robust_list 0.00 0.000000 0 1 prlimit64 0.00 0.000000 0 1 getrandom 0.00 0.000000 0 1 rseq ------ ----------- ----------- --------- --------- ---------------- 100.00 7.197729 27 262184 2 total 使用strace统计一下结果, 耗时7s, 主要系统调用是read和write, 分别进行了13万左右的调用。(第二次运行快一些,应该是这部分数据还没有换出内存) ...

2023-09-16 · 23 min · 4773 words

getopt_long

getopt_long可以用来解析命令行参数,也可以进行参数解析。例如 #include <stdio.h> /* for printf */ #include <stdlib.h> /* for exit */ #include <getopt.h> static int verbose_flag; int parse(int argc, char **argv) { int c; int digit_optind = 0; while (1) { int this_option_optind = optind ? optind : 1; int option_index = 0; static struct option long_options[] = { {"verbose", no_argument, &verbose_flag, 1}, {"add", required_argument, 0, 0 }, {"append", no_argument, 0, 0 }, {"delete", required_argument, 0, 0 }, {"verbose", no_argument, 0, 0 }, {"create", required_argument, 0, 'c'}, {"file", required_argument, 0, 0 }, {0, 0, 0, 0 } }; c = getopt_long(argc, argv, "abc:d:012", long_options, &option_index); if (c == -1) break; switch (c) { case 0: printf("option %s", long_options[option_index].name); if (optarg) printf(" with arg %s", optarg); printf("\n"); break; case '0': case '1': case '2': if (digit_optind != 0 && digit_optind != this_option_optind) printf("digits occur in two different argv-elements.\n"); digit_optind = this_option_optind; printf("option %c\n", c); break; case 'a': printf("option a\n"); break; case 'b': printf("option b\n"); break; case 'c': printf("option c with value '%s'\n", optarg); break; case 'd': printf("option d with value '%s'\n", optarg); break; case '?': break; default: printf("?? getopt returned character code 0%o ??\n", c); } } if (optind < argc) { printf("non-option ARGV-elements: "); while (optind < argc) printf("%s ", argv[optind++]); printf("\n"); } if (verbose_flag) printf ("verbose flag is set\n"); return 0; } void main() { char *argv[6]={0}; int argc = 6; argv[0] = "prog1"; argv[1] = "--add"; argv[2] = "AA"; argv[3] = "-d"; argv[4] = "BB"; argv[5] = "--verbose"; parse(argc, argv); optind = 1; char *argv2[4]={0}; int argc2 = 4; argv2[0] = "prog2"; argv2[1] = "--file"; argv2[2] = "CC"; argv2[3] = "--verbose"; //argv2[3] = "--delete"; //argv2[4] = "DD"; parse(argc2, argv2); return ; } ...

2023-08-09 · 3 min · 617 words

nginx realserver 使用域名方式功能分析

nginx使用域名方式访问RS的一些方案整理: proxy_pass 直接放域名无upstream server { location / { proxy_pass http://backends.example.com:8080; } } 没有uptream配置;无法配置相关负载策略; 系统启动成功,域名解析成功后相关解析IP信息不会更新; 域名解析过程是在nginx启动时proxy_pass命令解析时完成,依赖系统DNS服务。如果解析失败, 无法启动重新加载。 Upstream中直接使用域名 upstream backends { server backends.example.com:8080 max_fails=3; } server { location / { proxy_pass http://backends; } } 域名解析过程是在nginx启动时upstream server命令解析时完成, IP列表保存; 系统启动成功,域名解析成功后相关解析IP列表信息不会更新; 依赖系统DNS服务。如果解析失败, 无法启动重新加载; proxy_pass结合通过变量方式 resolver 10.0.0.2 valid=10s; server { location / { set $backend_servers backends.example.com; proxy_pass http://$backend_servers:8080; } } 使用resolver命令设置的域名服务器,通过valid设置DNS信息有效时间; 解析失败不影响nginx服务启动。 开源版本总结: Nginx配置中使用域名只有在nginx启动时完成地址解析, 获取DNS解析后的地址列表。 Nginx开源版本中可以通过proxy_pass , server(upstream)配置命令中使用域名均命令nginx启动的命令解析阶段进行,解析错误将导致无法启动、无法重新加载。 Proxy_pass + resovle解决了这个问题,但是不使用upstream无法实现现有的多server配置。 其他解决方案 Nginx plus resolver 10.0.0.2 valid=10s; upstream backends { zone backends 64k; server backends.example.com:8080 resolve; } server { location / { proxy_pass http://backends; } } 商业版本支持在现有的server命中增加 resolve参数支持的域名解析 可以通过zone 指令共享配置。 Dynamic Configuration of Upstreams with the NGINX Plus API | NGINX Plus ...

2023-07-27 · 1 min · 174 words

SNMP

SNMP 全称为Simple Network Management Protocol(简单网络管理协议),是一种广泛使用的网络管理协议,允许管理员监视和管理网络设备,如路由器、交换机、服务器和打印机。它是一种应用层协议,促进网络设备与集中的网络管理系统之间的管理信息交换。 组成 SNMP 管理的网络由三个关键组件组成: Managed devices Agent – software which runs on managed devices Network management station (NMS) – software which runs on the manage from wiki OID & MIB OID:对象标识符 MIB:管理信息库 安装验证 node2 sudo apt-get install snmpd 编辑文件/etc/snmp/snmpd.conf # agentaddress: The IP address and port number that the agent will listen on. # By default the agent listens to any and all traffic from any # interface on the default SNMP port (161). This allows you to # specify which address, interface, transport type and port(s) that you # want the agent to listen on. Multiple definitions of this token # are concatenated together (using ':'s). # arguments: [transport:]port[@interface/address],... agentaddress 127.0.0.1,[::1],192.168.2.76 ...

2023-06-06 · 3 min · 588 words

用 flex和bison 解析配置文件

flex和bison是常用的词法分析和语法分析工具, flex可以将源文本以指定个规则识别为单词, bison可以将这些识别出来的单词进行结构化处理。下面可以通过一个bind9 config 解析程序进行说明。 词法分析,语法分析编译过程也是两个过程,通过词法分析, 语法分析, 语义分析前端工作完成输入源代码的识别, 通过中间语言,优化方式完成实现代码可扩展和优化(中端工作), 最终通过生成目标码, 一般也成为后端工作, 实现把源代码变成目标代码的过程。 使用flex和bison可以方便的解析配置文件。就是把字符串分解为token,再将这些token解析到配置文件对应的结构。为之后提供查询修改。 仅简单分析一下包含zone block的bind9配置 识别token bind9.l 标识出要是别的关键字, 也可以过滤掉一些注释和空白字符 %{ #include <stdio.h> #include <stdlib.h> #include "y.tab.h" #include "tree.h" %} %% [ \t\r\n]+ /* ignore whitespace*/ "//"(.)*"\n" /* ignore single-line comments */ "/*"(.)*"*/" /* ignore multi-line comments */ "{" { return LBRACE; } "}" { return RBRACE; } ";" { return SEMICOLON; } "=" { return EQUALS; } "zone" { return ZONE; } [a-zA-Z0-9\._/\-]+ { yylval.value = strdup(yytext); return NAME; } \"[^"]*\" { yylval.value = strdup(yytext); return STRING; } %% int yywrap() { return 1; } ...

2023-04-24 · 6 min · 1164 words