1. Penyelesaian
Apabila setiap proses pekerja dibuat, kaedah ngx_worker_process_init() akan dipanggil untuk memulakan proses pekerja semasa Langkah proses ini, iaitu, setiap proses pekerja akan memanggil kaedah epoll_create() untuk mencipta pemegang epoll yang unik untuk dirinya sendiri. Untuk setiap port yang perlu dipantau, terdapat deskriptor fail yang sepadan dengannya dan proses pekerja hanya menambah deskriptor fail pada pemegang epoll proses semasa melalui kaedah epoll_ctl() dan mendengar peristiwa terima yang Dicetuskan oleh acara penubuhan sambungan pelanggan untuk mengendalikan acara tersebut. Ia juga boleh dilihat dari sini bahawa jika proses pekerja tidak menambah deskriptor fail yang sepadan dengan port yang perlu dipantau ke pemegang epoll proses, maka peristiwa yang sepadan tidak boleh dicetuskan. Berdasarkan prinsip ini, nginx menggunakan kunci dikongsi untuk mengawal sama ada proses semasa mempunyai kebenaran untuk menambah port yang perlu dipantau pada pemegang epoll proses semasa Dalam erti kata lain, hanya proses yang memperoleh kunci akan mendengar ke pelabuhan sasaran. Dengan cara ini, ia dijamin bahawa hanya satu proses pekerja akan dicetuskan setiap kali peristiwa berlaku. Rajah berikut ialah gambarajah skema bagi kitaran kerja proses pekerja:
Mengenai proses dalam rajah, satu perkara yang perlu dijelaskan ialah selepas memasukkan kitaran, setiap proses pekerja Ia akan cuba memperoleh kunci kongsi Jika ia tidak diperoleh, deskriptor fail port yang dipantau akan dialih keluar daripada pemegang epoll proses semasa (ia akan dialih keluar walaupun ia tidak wujud). . Bayangkan sahaja, jika mengikut teori, deskriptor fail port pendengaran dikeluarkan daripada pemegang epoll apabila proses semasa melepaskan kunci, maka sebelum proses pekerja seterusnya memperoleh kunci, deskriptor fail yang sepadan dengan setiap port dalam tempoh ini adalah Jika tiada pemegang epoll untuk mendengar, acara itu akan hilang. Jika, sebaliknya, deskriptor fail yang dipantau dialih keluar hanya apabila pemerolehan kunci gagal, seperti yang ditunjukkan dalam rajah Memandangkan pemerolehan kunci gagal, ini bermakna mesti ada proses memantau deskriptor fail ini, jadi ia selamat. untuk mengeluarkannya pada masa ini. Tetapi satu masalah yang akan menyebabkannya ialah, mengikut rajah di atas, apabila proses semasa menyelesaikan pelaksanaan gelung, ia akan melepaskan kunci dan kemudian mengendalikan peristiwa lain Ambil perhatian bahawa ia tidak melepaskan deskriptor fail yang dipantau semasa proses ini . Pada masa ini, jika proses lain memperoleh kunci dan memantau deskriptor fail, maka terdapat dua proses memantau deskriptor fail pada masa ini, jika peristiwa penubuhan sambungan berlaku pada klien, dua pekerja akan dicetuskan. Masalah ini boleh diterima kerana dua sebab utama:
Fenomena gerombolan gemuruh ini hanya akan mencetuskan bilangan proses pekerja yang lebih kecil, yang lebih baik daripada membangunkan semua proses pekerja setiap kali
Sebab utama masalah panik seperti ini berlaku ialah proses semasa melepaskan kunci, tetapi tidak melepaskan deskriptor fail yang dipantau, tetapi proses pekerja terutamanya melepaskan kunci selepas melepaskan kunci itu adalah untuk memproses acara baca dan tulis dan periksa bit bendera sambungan klien Proses ini sangat singkat Selepas pemprosesan selesai, ia akan cuba mendapatkan kunci Pada masa ini, deskriptor fail yang dipantau akan dikeluarkan perbandingan, Dalam erti kata lain, proses pekerja yang memperoleh kunci menunggu lebih lama untuk memproses acara penubuhan sambungan pelanggan, jadi kebarangkalian masalah kumpulan gemuruh masih agak kecil.
2. Penerangan kod sumber
Kaedah peristiwa awal proses pekerja dijalankan terutamanya dalam ngx_process_events_and_timers() kaedah. Di bawah kita Mari kita lihat bagaimana kaedah ini mengendalikan keseluruhan proses Berikut ialah kod sumber kaedah ini:
void ngx_process_events_and_timers(ngx_cycle_t *cycle) { ngx_uint_t flags; ngx_msec_t timer, delta; if (ngx_trylock_accept_mutex(cycle) == ngx_error) { return; } // 这里开始处理事件,对于kqueue模型,其指向的是ngx_kqueue_process_events()方法, // 而对于epoll模型,其指向的是ngx_epoll_process_events()方法 // 这个方法的主要作用是,在对应的事件模型中获取事件列表,然后将事件添加到ngx_posted_accept_events // 队列或者ngx_posted_events队列中 (void) ngx_process_events(cycle, timer, flags); // 这里开始处理accept事件,将其交由ngx_event_accept.c的ngx_event_accept()方法处理; ngx_event_process_posted(cycle, &ngx_posted_accept_events); // 开始释放锁 if (ngx_accept_mutex_held) { ngx_shmtx_unlock(&ngx_accept_mutex); } // 如果不需要在事件队列中进行处理,则直接处理该事件 // 对于事件的处理,如果是accept事件,则将其交由ngx_event_accept.c的ngx_event_accept()方法处理; // 如果是读事件,则将其交由ngx_http_request.c的ngx_http_wait_request_handler()方法处理; // 对于处理完成的事件,最后会交由ngx_http_request.c的ngx_http_keepalive_handler()方法处理。 // 这里开始处理除accept事件外的其他事件 ngx_event_process_posted(cycle, &ngx_posted_events); }
Dalam kod di atas, kami telah meninggalkan kebanyakan kerja menyemak, hanya meninggalkan. kod rangka. Pertama, proses pekerja akan memanggil kaedah ngx_trylock_accept_mutex() untuk mendapatkan kunci Jika kunci diperoleh, ia akan mendengar deskriptor fail yang sepadan dengan setiap port. Kemudian kaedah ngx_process_events() akan dipanggil untuk memproses peristiwa yang dipantau dalam pemegang epoll. Kemudian kunci kongsi akan dilepaskan, dan akhirnya acara baca dan tulis klien yang disambungkan akan diproses. Mari kita lihat bagaimana kaedah ngx_trylock_accept_mutex() memperoleh kunci kongsi:
ngx_int_t ngx_trylock_accept_mutex(ngx_cycle_t *cycle) { // 尝试使用cas算法获取共享锁 if (ngx_shmtx_trylock(&ngx_accept_mutex)) { // ngx_accept_mutex_held为1表示当前进程已经获取到了锁 if (ngx_accept_mutex_held && ngx_accept_events == 0) { return ngx_ok; } // 这里主要是将当前连接的文件描述符注册到对应事件的队列中,比如kqueue模型的change_list数组 // nginx在启用各个worker进程的时候,默认情况下,worker进程是会继承master进程所监听的socket句柄的, // 这就导致一个问题,就是当某个端口有客户端事件时,就会把监听该端口的进程都给唤醒, // 但是只有一个worker进程能够成功处理该事件,而其他的进程被唤醒之后发现事件已经过期, // 因而会继续进入等待状态,这种现象称为"惊群"现象。 // nginx解决惊群现象的方式一方面是通过这里的共享锁的方式,即只有获取到锁的worker进程才能处理 // 客户端事件,但实际上,worker进程是通过在获取锁的过程中,为当前worker进程重新添加各个端口的监听事件, // 而其他worker进程则不会监听。也就是说同一时间只有一个worker进程会监听各个端口, // 这样就避免了"惊群"问题。 // 这里的ngx_enable_accept_events()方法就是为当前进程重新添加各个端口的监听事件的。 if (ngx_enable_accept_events(cycle) == ngx_error) { ngx_shmtx_unlock(&ngx_accept_mutex); return ngx_error; } // 标志当前已经成功获取到了锁 ngx_accept_events = 0; ngx_accept_mutex_held = 1; return ngx_ok; } // 前面获取锁失败了,因而这里需要重置ngx_accept_mutex_held的状态,并且将当前连接的事件给清除掉 if (ngx_accept_mutex_held) { // 如果当前进程的ngx_accept_mutex_held为1,则将其重置为0,并且将当前进程在各个端口上的监听 // 事件给删除掉 if (ngx_disable_accept_events(cycle, 0) == ngx_error) { return ngx_error; } ngx_accept_mutex_held = 0; } return ngx_ok; }
Dalam kod di atas, ia pada asasnya melakukan tiga perkara utama:
Lulus ngx_shmtx_trylock () kaedah cuba menggunakan kaedah cas untuk mendapatkan kunci dikongsi; ;
Jika kunci tidak diperoleh, panggil kaedah ngx_disable_accept_events() untuk melepaskan deskriptor fail yang dipantau.
Atas ialah kandungan terperinci Bagaimana untuk menyelesaikan masalah panik nginx. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!