Melampaui Web Server: Proses Berhak Istimewa dan Tugas Timer
API7.ai
November 3, 2022
Pada artikel sebelumnya, kami memperkenalkan API OpenResty, shared dict, dan cosocket, yang semuanya mengimplementasikan fungsionalitas dalam lingkup NGINX dan server web, menyediakan server web yang dapat diprogram dengan biaya lebih rendah dan lebih mudah untuk dirawat.
Namun, OpenResty dapat melakukan lebih dari itu. Mari kita pilih beberapa fitur dalam OpenResty yang melampaui server web dan memperkenalkannya hari ini. Mereka adalah tugas timer, proses istimewa, dan ngx.pipe non-blocking.
Tugas Timer
Di OpenResty, terkadang kita perlu secara teratur melakukan tugas tertentu di latar belakang, seperti menyinkronkan data, membersihkan log, dll. Jika Anda harus merancangnya, bagaimana Anda akan melakukannya? Cara termudah yang terpikir adalah menyediakan antarmuka API ke luar untuk melakukan tugas-tugas ini, kemudian menggunakan crontab sistem untuk memanggil curl secara berkala untuk mengakses antarmuka ini, dan kemudian mengimplementasikan kebutuhan ini secara tidak langsung.
Namun, ini tidak hanya akan terfragmentasi tetapi juga membawa kompleksitas yang lebih tinggi ke operasi dan pemeliharaan. Jadi, OpenResty menyediakan ngx.timer untuk menyelesaikan kebutuhan semacam ini. Anda dapat menganggap ngx.timer sebagai permintaan klien yang disimulasikan oleh OpenResty untuk memicu fungsi callback yang sesuai.
Tugas timer OpenResty dapat dibagi menjadi dua jenis berikut.
ngx.timer.atdigunakan untuk menjalankan tugas timer satu kali.ngx.timer.everydigunakan untuk menjalankan tugas timer dengan periode tetap.
Ingat pertanyaan yang memicu pemikiran yang saya tinggalkan di akhir artikel sebelumnya? Pertanyaannya adalah bagaimana cara mengatasi batasan bahwa cosocket tidak dapat digunakan di init_worker_by_lua, dan jawabannya adalah ngx.timer.
Kode berikut memulai tugas timer dengan penundaan 0. Ini memulai fungsi callback handler, dan dalam fungsi ini, ia menggunakan cosocket untuk mengakses sebuah situs web.
init_worker_by_lua_block { local function handler() local sock = ngx.socket.tcp() local ok, err = sock:connect(“api7.ai", 80) end local ok, err = ngx.timer.at(0, handler) }
Dengan cara ini, kita mengatasi batasan bahwa cosocket tidak dapat digunakan pada tahap ini.
Kembali ke kebutuhan pengguna yang kami sebutkan di awal bagian ini, ngx.timer.at tidak memenuhi kebutuhan untuk menjalankan secara berkala; dalam contoh kode di atas, ini adalah tugas satu kali.
Jadi, bagaimana kita melakukan ini secara berkala? Anda tampaknya memiliki dua opsi berdasarkan API ngx.timer.at.
- Anda dapat mengimplementasikan tugas berkala sendiri dengan menggunakan loop tak terbatas
while truedalam fungsi callback yangsleepsebentar setelah menjalankan tugas. - Anda juga dapat membuat timer baru di akhir fungsi callback.
Namun, sebelum membuat pilihan, ada satu hal yang perlu kita klarifikasi: timer pada dasarnya adalah permintaan, meskipun permintaan tersebut tidak dimulai oleh klien. Untuk permintaan, ia harus keluar setelah menyelesaikan tugasnya dan tidak dapat selalu tinggal. Jika tidak, ini mudah menyebabkan berbagai kebocoran sumber daya.
Oleh karena itu, solusi pertama menggunakan while true untuk mengimplementasikan tugas berkala tidak dapat diandalkan. Solusi kedua layak tetapi secara rekursif membuat timer, yang tidak mudah dipahami.
Jadi, apakah ada solusi yang lebih baik? API ngx.timer.every baru di belakang OpenResty dirancang khusus untuk menyelesaikan masalah ini, dan ini adalah solusi yang lebih dekat dengan crontab.
Kelemahannya adalah Anda tidak pernah memiliki kesempatan untuk membatalkan tugas timer setelah memulainya. Lagi pula, ngx.timer.cancel masih merupakan fungsi yang harus dilakukan.
Pada titik ini, Anda akan menghadapi masalah: timer berjalan di latar belakang dan tidak dapat dibatalkan; jika ada banyak timer, ini mudah menghabiskan sumber daya sistem.
Oleh karena itu, OpenResty menyediakan dua direktif, lua_max_pending_timers dan lua_max_running_timers untuk membatasinya. Yang pertama mewakili jumlah maksimum timer yang menunggu untuk dieksekusi, dan yang kedua mewakili jumlah maksimum timer yang sedang berjalan saat ini.
Anda juga dapat menggunakan API Lua untuk mendapatkan nilai tugas timer yang sedang menunggu dan berjalan saat ini, seperti yang ditunjukkan dalam dua contoh berikut.
content_by_lua_block { ngx.timer.at(3, function() end) ngx.say(ngx.timer.pending_count()) }
Kode ini akan mencetak 1, menunjukkan bahwa ada satu tugas terjadwal yang menunggu untuk dieksekusi.
content_by_lua_block { ngx.timer.at(0.1, function() ngx.sleep(0.3) end) ngx.sleep(0.2) ngx.say(ngx.timer.running_count()) }
Kode ini akan mencetak 1, menunjukkan bahwa ada satu tugas terjadwal yang sedang berjalan.
Proses Istimewa
Selanjutnya, mari kita lihat proses istimewa. Seperti yang kita semua tahu, NGINX dibagi menjadi proses Master dan proses Worker, di mana proses worker menangani permintaan pengguna. Kita dapat mengetahui jenis proses melalui API process.type yang disediakan di lua-resty-core. Misalnya, Anda dapat menggunakan resty untuk menjalankan fungsi berikut.
$ resty -e 'local process = require "ngx.process" ngx.say("process type:", process.type())'
Anda akan melihat bahwa ini mengembalikan hasil single alih-alih worker, yang berarti bahwa resty memulai NGINX dengan proses Worker, bukan proses Master. Ini benar. Dalam implementasi resty, Anda dapat melihat bahwa proses Master dimatikan dengan baris seperti ini.
master_process off;
OpenResty memperluas NGINX dengan menambahkan privileged agent, Proses istimewa memiliki fitur khusus berikut.
-
Ini tidak memantau port apa pun, yang berarti tidak menyediakan layanan ke luar.
-
Ini memiliki hak istimewa yang sama dengan proses
Master, yang umumnya adalah hak istimewa penggunaroot, memungkinkannya melakukan banyak tugas yang tidak mungkin dilakukan oleh prosesWorker. -
Proses istimewa hanya dapat dibuka dalam konteks
init_by_lua. -
Selain itu, proses istimewa hanya masuk akal jika mereka berjalan dalam konteks
init_worker_by_luakarena tidak ada permintaan yang dipicu, dan mereka tidak masuk ke kontekscontent,access, dll.
Mari kita lihat contoh proses istimewa yang diaktifkan.
init_by_lua_block { local process = require "ngx.process" local ok, err = process.enable_privileged_agent() if not ok then ngx.log(ngx.ERR, "enables privileged agent failed error:", err) end }
Setelah membuka proses istimewa dengan kode ini dan memulai layanan OpenResty, kita dapat melihat bahwa proses istimewa sekarang menjadi bagian dari proses NGINX.
nginx: master process nginx: worker process nginx: privileged agent process
Namun, jika hak istimewa hanya dijalankan sekali selama fase init_worker_by_lua, yang bukan ide yang baik, bagaimana seharusnya kita memicu proses istimewa?
Ya, jawabannya tersembunyi dalam pengetahuan yang baru saja diajarkan. Karena tidak mendengarkan port, yaitu tidak dapat dipicu oleh permintaan terminal, satu-satunya cara untuk memicunya secara berkala adalah dengan menggunakan ngx.timer yang baru saja kami perkenalkan:
init_worker_by_lua_block { local process = require "ngx.process" local function reload(premature) local f, err = io.open(ngx.config.prefix() .. "/logs/nginx.pid", "r") if not f then return end local pid = f:read() f:close() os.execute("kill -HUP " .. pid) end if process.type() == "privileged agent" then local ok, err = ngx.timer.every(5, reload) if not ok then ngx.log(ngx.ERR, err) end end }
Kode di atas mengimplementasikan kemampuan untuk mengirim sinyal HUP ke proses master setiap 5 detik. Secara alami, Anda dapat membangun ini untuk melakukan hal-hal yang lebih menarik, seperti memeriksa database untuk melihat apakah ada tugas untuk proses istimewa dan mengeksekusinya. Karena proses istimewa memiliki hak istimewa root, ini jelas sedikit program "backdoor".
ngx.pipe Non-blocking
Terakhir, lihat ngx.pipe non-blocking, yang menggunakan pustaka standar Lua untuk menjalankan perintah baris eksternal yang mengirim sinyal ke proses Master dalam contoh kode yang baru saja kami jelaskan.
os.execute("kill -HUP " .. pid)
Secara alami, operasi ini akan memblokir. Jadi, apakah ada cara non-blocking untuk memanggil program eksternal di OpenResty? Lagi pula, Anda tahu bahwa jika Anda menggunakan OpenResty sebagai platform pengembangan lengkap dan bukan sebagai server web, ini adalah yang Anda butuhkan. Untuk alasan ini, perpustakaan lua-resty-shell dibuat, dan menggunakannya untuk memanggil baris perintah adalah non-blocking:
$ resty -e 'local shell = require "resty.shell" local ok, stdout, stderr, reason, status = shell.run([[echo "hello, world"]]) ngx.say(stdout)
Kode ini adalah cara berbeda untuk menulis hello world, memanggil perintah echo sistem untuk menyelesaikan output. Demikian pula, Anda dapat menggunakan resty.shell sebagai alternatif untuk panggilan os.execute di Lua.
Kita tahu bahwa implementasi dasar lua-resty-shell bergantung pada API ngx.pipe di lua-resty-core, jadi contoh ini menggunakan lua-resty-shell untuk mencetak hello world, menggunakan ngx.pipe sebagai gantinya, akan terlihat seperti ini.
$ resty -e 'local ngx_pipe = require "ngx.pipe" local proc = ngx_pipe.spawn({"echo", "hello world"}) local data, err = proc:stdout_read_line() ngx.say(data)'
Di atas adalah kode dasar dari implementasi lua-resty-shell. Anda dapat memeriksa dokumentasi ngx.pipe dan kasus uji untuk informasi lebih lanjut tentang cara menggunakannya. Oleh karena itu, saya tidak akan membahasnya di sini.
Ringkasan
Itu saja. Kami telah menyelesaikan konten utama untuk hari ini. Dari fitur-fitur di atas, kita dapat melihat bahwa OpenResty juga mencoba mendekati arah platform universal sambil membuat NGINX yang lebih baik, berharap bahwa pengembang dapat mencoba menyatukan tumpukan teknologi dan menggunakan OpenResty untuk memenuhi kebutuhan pengembangan mereka. Ini cukup ramah untuk operasi dan pemeliharaan karena biaya pemeliharaan lebih rendah selama Anda menerapkan OpenResty di atasnya.
Terakhir, saya akan meninggalkan Anda dengan pertanyaan yang memicu pemikiran. Karena mungkin ada beberapa Worker NGINX, timer akan berjalan sekali untuk setiap Worker, yang tidak dapat diterima dalam sebagian besar skenario. Bagaimana kita dapat memastikan bahwa timer hanya berjalan sekali?
Jangan ragu untuk meninggalkan komentar dengan solusi Anda, dan jangan ragu untuk membagikan artikel ini dengan kolega dan teman Anda sehingga kita dapat berkomunikasi dan meningkatkan bersama.