linux-高可用性计算:如何在不冒误报的情况下处理不返回系统的调用?
作者:互联网
作为高可用性系统的一部分,我有一个在Linux计算机上运行的进程.该进程有一个主线程,该线程从网络上的其他计算机接收请求并作出响应.还有一个心跳线线程,该线程定期发送多播心跳线数据包,以使网络上的其他进程知道该进程仍处于活动状态并且可用-如果他们一段时间不从中获取任何心跳线数据包,则其中一个他们将假定此过程已终止,并将接管其职责,以便整个系统可以继续工作.
一切都很好,但是前一天整个系统出现故障,当我调查为什么发现以下内容时:
>由于(显然是)Linux内核中的错误,该进程的主线程进行的系统调用导致内核“哎呀”.
>由于内核“ oops”,系统调用从未返回,从而使进程的主线程永久挂起.
>心跳线OTOH继续正常运行,这意味着网络上的其他节点从未意识到此节点已发生故障,并且它们都没有介入以承担其职责…因此所请求的任务没有执行并有效地停止了系统的操作.
我的问题是,是否有一个优雅的解决方案可以处理此类故障? (显然,要做的一件事是修复Linux内核,这样它就不会“大惊小怪”,但是考虑到Linux内核的复杂性,如果我的软件也可以更优雅地处理将来的其他内核错误,那就太好了).
我不喜欢的一种解决方案是将心跳生成器放入主线程中,而不是将其作为单独的线程运行,或者以其他方式将其与主线程绑定,以便如果主线程无限期挂起,心跳将不会发送.我不喜欢这种解决方案的原因是因为主线程不是实时线程,因此这样做可能会导致偶尔出现误报的情况,因为将慢速完成操作误认为是节点故障.如果可以的话,我想避免误报.
理想情况下,可以采用某种方法来确保失败的syscall会返回错误代码,或者如果这不可能,则会使我的进程崩溃;这些中的任何一个都将停止心跳数据包的生成并允许故障转移继续进行.有没有办法做到这一点,或者不可靠的内核也会使我的用户进程也变得不可靠?
解决方法:
我的第二个建议是使用ptrace查找当前指令指针.您可以有一个父线程,它跟踪您的进程并每秒中断一次,以检查当前的RIP值.这有点复杂,所以我编写了一个演示程序:(仅x86_64,但是应该可以通过更改寄存器名称来解决.)
#define _GNU_SOURCE
#include <unistd.h>
#include <sched.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/syscall.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <linux/ptrace.h>
#include <sys/user.h>
#include <time.h>
// this number is arbitrary - find a better one.
#define STACK_SIZE (1024 * 1024)
int main_thread(void *ptr) {
// "main" thread is now running under the monitor
printf("Hello from main!");
while (1) {
int c = getchar();
if (c == EOF) { break; }
nanosleep(&(struct timespec) {0, 200 * 1000 * 1000}, NULL);
putchar(c);
}
return 0;
}
int main(int argc, char *argv[]) {
void *vstack = malloc(STACK_SIZE);
pid_t v;
if (clone(main_thread, vstack + STACK_SIZE, CLONE_PARENT_SETTID | CLONE_FILES | CLONE_FS | CLONE_IO, NULL, &v) == -1) { // you'll want to check these flags
perror("failed to spawn child task");
return 3;
}
printf("Target: %d; %d\n", v, getpid());
long ptv = ptrace(PTRACE_SEIZE, v, NULL, NULL);
if (ptv == -1) {
perror("failed monitor sieze");
exit(1);
}
struct user_regs_struct regs;
fprintf(stderr, "beginning monitor...\n");
while (1) {
sleep(1);
long ptv = ptrace(PTRACE_INTERRUPT, v, NULL, NULL);
if (ptv == -1) {
perror("failed to interrupt main thread");
break;
}
int status;
if (waitpid(v, &status, __WCLONE) == -1) {
perror("target wait failed");
break;
}
if (!WIFSTOPPED(status)) { // this section is messy. do it better.
fputs("target wait went wrong", stderr);
break;
}
if ((status >> 8) != (SIGTRAP | PTRACE_EVENT_STOP << 8)) {
fputs("target wait went wrong (2)", stderr);
break;
}
ptv = ptrace(PTRACE_GETREGS, v, NULL, ®s);
if (ptv == -1) {
perror("failed to peek at registers of thread");
break;
}
fprintf(stderr, "%d -> RIP %x RSP %x\n", time(NULL), regs.rip, regs.rsp);
ptv = ptrace(PTRACE_CONT, v, NULL, NULL);
if (ptv == -1) {
perror("failed to resume main thread");
break;
}
}
return 2;
}
请注意,这不是生产质量代码.您将需要进行大量的修复工作.
基于此,您应该能够确定程序计数器是否在前进,并且可以将其与其他信息(例如/ proc / PID / status)结合起来,以查看系统调用中是否繁忙.您也许还可以扩展ptrace的用法来检查正在使用的系统调用,以便可以检查它是否是合理的选择.
这是一个hacky解决方案,但我认为您不会找到针对此问题的非hacky解决方案.尽管存在漏洞,但我认为(未经测试)它不会特别慢.我的实现每秒在非常短的时间内每秒暂停一次受监视线程-我想这应该在100微秒的范围内.理论上,效率损失约为0.01%.
标签:heartbeat,linux,high-availability,failover 来源: https://codeday.me/bug/20191012/1901016.html