Это второй день моего участия в августовском испытании обновлений, подробности о мероприятии:Испытание августовского обновления
Перед нами мы более глубоко проанализировали эволюцию IO к NIO, где упоминалось мультиплексирование Epoll, сегодня оно в основном объединяет Redis, nginx, ориентируясь на ePoll.
Сначала мы должны выяснить, что такое мультиплексирование, простое для понимания: сервер процессов может одновременно обрабатывать несколько дескрипторов сокетов.
- Мультиплекс: несколько клиентских подключений (соединения являются дескрипторами сокетов)
- Мультиплексирование: используйте один процесс для одновременной обработки подключений от нескольких клиентов.
Не говорите чепухи, просто делайте это (я установил на машину Redis и nginx, если хотите потренироваться, можете выполнить шаги ниже)
nginx
начать и отслеживать
Запускайте и отслеживайте nginx с помощью следующих команд
strace -ff -o ./nginxout /usr/local/nginx/sbin/nginx
После выполнения вышеуказанной команды обнаруживается, что генерируются следующие три файла, соответствующие трем процессам.
Через PS -EF | GREP Nginx, вы можете найти, что Nginx на самом деле имеет два процесса Master-14607, а работник-14608 (файл конфигурации имеет только один работник по умолчанию). Что такое 14606?
переходный процесс
Взгляните на файл nginxout.14606 следующим образом:
mkdir("/usr/local/nginx/scgi_temp", 0700) = -1 EEXIST (File exists)
stat("/usr/local/nginx/scgi_temp", {st_mode=S_IFDIR|0700, st_size=4096, ...}) = 0
openat(AT_FDCWD, "/usr/local/nginx/logs/access.log", O_WRONLY|O_CREAT|O_APPEND, 0644) = 4
fcntl(4, F_SETFD, FD_CLOEXEC) = 0
openat(AT_FDCWD, "/usr/local/nginx/logs/error.log", O_WRONLY|O_CREAT|O_APPEND, 0644) = 5
fcntl(5, F_SETFD, FD_CLOEXEC) = 0
socket(AF_INET, SOCK_STREAM, IPPROTO_IP) = 6
setsockopt(6, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
ioctl(6, FIONBIO, [1]) = 0
bind(6, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("0.0.0.0")}, 16) = 0
listen(6, 511) = 0
listen(6, 511) = 0
prlimit64(0, RLIMIT_NOFILE, NULL, {rlim_cur=1024*1024, rlim_max=1024*1024}) = 0
mmap(NULL, 384, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0) = 0x7fe7f8911000
rt_sigaction(SIGHUP, {sa_handler=0x41eed5, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7fe7f84d8b20}, NULL, 8) = 0
rt_sigaction(SIGUSR1, {sa_handler=0x41eed5, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7fe7f84d8b20}, NULL, 8) = 0
rt_sigaction(SIGWINCH, {sa_handler=0x41eed5, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7fe7f84d8b20}, NULL, 8) = 0
rt_sigaction(SIGTERM, {sa_handler=0x41eed5, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7fe7f84d8b20}, NULL, 8) = 0
rt_sigaction(SIGQUIT, {sa_handler=0x41eed5, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7fe7f84d8b20}, NULL, 8) = 0
rt_sigaction(SIGUSR2, {sa_handler=0x41eed5, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7fe7f84d8b20}, NULL, 8) = 0
rt_sigaction(SIGALRM, {sa_handler=0x41eed5, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7fe7f84d8b20}, NULL, 8) = 0
rt_sigaction(SIGINT, {sa_handler=0x41eed5, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7fe7f84d8b20}, NULL, 8) = 0
rt_sigaction(SIGIO, {sa_handler=0x41eed5, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7fe7f84d8b20}, NULL, 8) = 0
rt_sigaction(SIGCHLD, {sa_handler=0x41eed5, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7fe7f84d8b20}, NULL, 8) = 0
rt_sigaction(SIGSYS, {sa_handler=SIG_IGN, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7fe7f84d8b20}, NULL, 8) = 0
rt_sigaction(SIGPIPE, {sa_handler=SIG_IGN, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7fe7f84d8b20}, NULL, 8) = 0
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7fe7f8907a10) = 14607
exit_group(0) = ?
+++ exited with 0 +++
~
Вы можете видеть, что последняя строка этого файла показывает, что он выходит, но он все еще делает то, что должен делать перед выходом.Перехват выглядит следующим образом:
// 创建socket
socket(AF_INET, SOCK_STREAM, IPPROTO_IP) = 6
// 绑定端口
bind(6, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("0.0.0.0")}, 16) = 0
//监听
listen(6, 511)
Немного понятно? Не волнуйтесь, продолжим просмотр
На самом деле это переходная программа.Почему вы так говорите?Если вы внимательно посмотрите на картинку выше, то обнаружите, что там есть операция клонирования, а это значит, что 14606 родила 14607.
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7fe7f8907a10) = 14607
Есть розетка .., связываться .., слушать .. Но не найден операция epoll, давайте перейдем к процессу Matser (14607), чтобы увидеть
основной процесс
set_robust_list(0x7fe7f8907a20, 24) = 0
getpid() = 14607
setsid() = 14607
umask(000) = 022
openat(AT_FDCWD, "/dev/null", O_RDWR) = 7
dup2(7, 0) = 0
dup2(7, 1) = 1
close(7) = 0
openat(AT_FDCWD, "/usr/local/nginx/logs/nginx.pid", O_RDWR|O_CREAT|O_TRUNC, 0644) = 7
pwrite64(7, "14607\n", 6, 0) = 6
close(7) = 0
dup2(5, 2) = 2
close(3) = 0
rt_sigprocmask(SIG_BLOCK, [HUP INT QUIT USR1 USR2 ALRM TERM CHLD WINCH IO], NULL, 8) = 0
socketpair(AF_UNIX, SOCK_STREAM, 0, [3, 7]) = 0
ioctl(3, FIONBIO, [1]) = 0
ioctl(7, FIONBIO, [1]) = 0
ioctl(3, FIOASYNC, [1]) = 0
fcntl(3, F_SETOWN, 14607) = 0
fcntl(3, F_SETFD, FD_CLOEXEC) = 0
fcntl(7, F_SETFD, FD_CLOEXEC) = 0
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7fe7f8907a10) = 14608
rt_sigsuspend([], 8
Тогда вы обнаружите, что в мастер-процессе очень мало контента, потому что мастер на самом деле не работает, а мастер — это прямо клон, создающий рабочий процесс
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7fe7f8907a10) = 14608
рабочий процесс
Так что нам все еще нужно посмотреть вниз и посмотреть, что делает рабочий процесс (поскольку контента много, я скопировал сюда основной контент и добавил несколько комментариев)
// 开辟了一块内核空间
epoll_create(512) = 8
// 将上面提到的6 放到这块内核空间中(6、7分别是ipv4、ipv6的)
epoll_ctl(8, EPOLL_CTL_ADD, 6, {EPOLLIN|EPOLLRDHUP, {u32=4169998352, u64=140634284171280}}) = 0
close(3) = 0
epoll_ctl(8, EPOLL_CTL_ADD, 7, {EPOLLIN|EPOLLRDHUP, {u32=4169998560, u64=140634284171488}}) = 0
// 等待就绪
epoll_wait(8,
Для 6 и 7 на рисунке выше вы можете перейти непосредственно к fd основного процесса (cd /proc/14607/fd)
Тогда посмотрите ситуацию с мониторингом около 80 портов в Nginx.
Вышесказанное о EPOLL в Nginx,
redis
начать и отслеживать
strace -ff -o ./redisout /software/redis-5.0.10/src/redis-server
Журнал становится все больше
основной процесс
Войдите в основной процесс, чтобы увидеть, обратите внимание, что следующее изображение содержит большое количество информации
Извлечение важного содержимого
epoll_create(1024) = 5
socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP) = 6
bind(6, {sa_family=AF_INET6, sin6_port=htons(6379), sin6_flowinfo=htonl(0), inet_pton(AF_INET6, "::", &sin6_addr), sin6_scope_id=0}, 28) = 0
listen(6, 511) = 0
fcntl(6, F_GETFL) = 0x2 (flags O_RDWR)
fcntl(6, F_SETFL, O_RDWR|O_NONBLOCK) = 0
socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) = 7
setsockopt(7, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
bind(7, { =AF_INET, sin_port=htons(6379), sin_addr=inet_addr("0.0.0.0")}, 16) = 0
listen(7, 511) = 0
fcntl(7, F_GETFL) = 0x2 (flags O_RDWR)
fcntl(7, F_SETFL, O_RDWR|O_NONBLOCK) = 0
gettimeofday({tv_sec=1628522184, tv_usec=972588}, NULL) = 0
gettimeofday({tv_sec=1628522184, tv_usec=972618}, NULL) = 0
gettimeofday({tv_sec=1628522184, tv_usec=972647}, NULL) = 0
gettimeofday({tv_sec=1628522184, tv_usec=972677}, NULL) = 0
epoll_ctl(5, EPOLL_CTL_ADD, 6, {EPOLLIN, {u32=6, u64=6}}) = 0
epoll_ctl(5, EPOLL_CTL_ADD, 7, {EPOLLIN, {u32=7, u64=7}}) = 0
epoll_ctl(5, EPOLL_CTL_ADD, 3, {EPOLLIN, {u32=3, u64=3}}) = 0
ioctl(1, TCGETS, {B38400 opost isig icanon echo ...}) = 0
Что касается того, почему он продолжает увеличиваться, вы можете использовать команду tail -f redisout.14719, чтобы увидеть, что он опрашивается.
Опять же, используя epoll, мы описали ранее, что nginx заблокирован, но как его опрашивает redis? На самом деле, при тщательном размышлении должно быть какое-то представление
- Redis - это одна нить. В дополнение к чтению и письму нужно делать другие вещи (Fork Threads do lru, RDB, AOF ...), поэтому необходимо опросить, выполнять IO и другие операции в цикле.
- Nginx тоже однопоточный, как и redis, используется epoll, разница в том, что nginx имеет несколько процессов, что повышает стабильность и параллелизм.
Ввод-вывод — деликатная работа. Последние несколько статей в основном были посвящены тому, как эффективно обрабатывать ввод-вывод. Решение мультиплексирования epoll — это вопрос событий сообщений ввода-вывода. Оно может сообщить рабочему потоку, что соединение готово, управляемым событиями способом чтение/запись); рабочие потоки получают эти соединения, а затем выполняют чтение/запись. IO трудно понять сразу все.Его нужно рассматривать со многих сторон.В статье в основном анализируется некоторая логика мультиплексирования с точки зрения реального журнала операций.Я надеюсь,что это поможет вам немного.к лучшему!
Когда 8 сентября наступит осень, когда расцветут мои цветы, сотня цветов убьет - Хуан Чао