Dalam seni bina perkhidmatan mikro hari ini, proksi terbalik memainkan peranan penting dalam mengurus dan menghalakan permintaan masuk ke pelbagai perkhidmatan bahagian belakang.
Proksi terbalik terletak di hadapan pelayan web aplikasi dan memintas permintaan yang datang daripada mesin klien. Ini mempunyai banyak faedah seperti pengimbangan beban, alamat IP pelayan asal tersembunyi yang membawa kepada keselamatan yang lebih baik, caching, pengehadan kadar, dll.
Dalam seni bina diedarkan dan perkhidmatan mikro, satu titik masuk diperlukan. Pelayan Reverse Proxy seperti Nginx membantu dalam senario sedemikian. Jika kami mempunyai beberapa contoh pelayan kami berjalan, mengurus dan memastikan penghalaan permintaan yang cekap menjadi rumit. Proksi terbalik seperti Nginx adalah penyelesaian yang sempurna dalam kes ini. Kami boleh menghalakan domain kami ke Alamat IP pelayan Nginx dan Nginx akan menghalakan permintaan masuk mengikut konfigurasi ke salah satu kejadian sambil menjaga beban yang dikendalikan oleh setiap satu.
Saya akan mengesyorkan membaca artikel ini dari Nginx yang menerangkan secara terperinci bagaimana Nginx dapat menyokong permintaan skala besar dengan kebolehpercayaan dan kelajuan super: Seni Bina Nginx
Ringkasnya, Nginx mempunyai proses Master dan sekumpulan proses pekerja. Ia juga mempunyai proses pembantu seperti Pemuat Cache dan Pengurus Cache. Proses tuan dan pekerja melakukan semua kerja berat.
Proses pekerja mengendalikan berbilang sambungan tanpa sekatan, mengurangkan suis konteks. Mereka adalah satu-benang, dijalankan secara bebas dan menggunakan memori yang dikongsi untuk sumber yang dikongsi seperti cache dan data sesi. Seni bina ini membantu Nginx mengurangkan bilangan suis konteks dan meningkatkan kelajuan lebih pantas daripada seni bina berbilang proses yang menyekat.
Mengambil inspirasi daripada ini, kami akan menggunakan konsep proses tuan dan pekerja yang sama dan akan melaksanakan pelayan proksi terbalik dipacu peristiwa kami sendiri yang akan dapat mengendalikan beribu-ribu sambungan setiap proses pekerja.
Pelaksanaan proksi terbalik kami mengikut prinsip reka bentuk utama ini:
├── config.yaml # Server configuration ├── src/ │ ├── config-schema.ts # Configuration validation schemas │ ├── config.ts # Configuration parsing logic │ ├── index.ts # Application entry point │ ├── server-schema.ts # Server message schemas │ └── server.ts # Core server implementation └── tsconfig.json # TypeScript configuration
Sistem konfigurasi menggunakan YAML. Begini caranya:
server: listen: 8080 # Port the server listens on. workers: 2 # Number of worker processes to handle requests. upstreams: # Define upstream servers (backend targets). - id: jsonplaceholder url: jsonplaceholder.typicode.com - id: dummy url: dummyjson.com headers: # Custom headers added to proxied requests. - key: x-forward-for value: $ip # Adds the client IP to the forwarded request. - key: Authorization value: Bearer xyz # Adds an authorization token to requests. rules: # Define routing rules for incoming requests. - path: /test upstreams: - dummy # Routes requests to "/test" to the "dummy" upstream. - path: / upstreams: - jsonplaceholder # Routes all other requests to "jsonplaceholder".
Permintaan masuk dinilai mengikut peraturan. Berdasarkan laluan, proksi terbalik menentukan pelayan huluan untuk memajukan permintaan.
Kami menggunakan Zod untuk menentukan skema ketat untuk pengesahan konfigurasi:
import { z } from "zod"; const upstreamSchema = z.object({ id: z.string(), url: z.string(), }); const headerSchema = z.object({ key: z.string(), value: z.string(), }); const ruleSchema = z.object({ path: z.string(), upstreams: z.array(z.string()), }); const serverSchema = z.object({ listen: z.number(), workers: z.number().optional(), upstreams: z.array(upstreamSchema), headers: z.array(headerSchema).optional(), rules: z.array(ruleSchema), }); export const rootConfigSchema = z.object({ server: serverSchema, }); export type ConfigSchemaType = z.infer<typeof rootConfigSchema>;
Modul config.ts menyediakan fungsi utiliti untuk menghuraikan dan mengesahkan fail konfigurasi.
import fs from "node:fs/promises"; import { parse } from "yaml"; import { rootConfigSchema } from "./config-schema"; export async function parseYAMLConfig(filepath: string) { const configFileContent = await fs.readFile(filepath, "utf8"); const configParsed = parse(configFileContent); return JSON.stringify(configParsed); } export async function validateConfig(config: string) { const validatedConfig = await rootConfigSchema.parseAsync( JSON.parse(config) ); return validatedConfig; }
Pelayan menggunakan modul kluster Node.js untuk kebolehskalaan dan modul http untuk mengendalikan permintaan. Proses induk mengedarkan permintaan kepada proses pekerja, yang memajukannya ke pelayan huluan. Mari terokai fail server.ts secara terperinci, yang mengandungi logik teras pelayan proksi terbalik kami. Kami akan memecahkan setiap komponen dan memahami cara ia berfungsi bersama untuk mencipta pelayan proksi berskala.
Pelaksanaan pelayan mengikut seni bina master-worker menggunakan modul Kluster Node.js. Reka bentuk ini membolehkan kami:
Proses Induk:
Proses Pekerja:
├── config.yaml # Server configuration ├── src/ │ ├── config-schema.ts # Configuration validation schemas │ ├── config.ts # Configuration parsing logic │ ├── index.ts # Application entry point │ ├── server-schema.ts # Server message schemas │ └── server.ts # Core server implementation └── tsconfig.json # TypeScript configuration
Proses induk mencipta kumpulan pekerja dan menghantar konfigurasi kepada setiap pekerja melalui pembolehubah persekitaran. Ini memastikan semua pekerja mempunyai akses kepada konfigurasi yang sama.
server: listen: 8080 # Port the server listens on. workers: 2 # Number of worker processes to handle requests. upstreams: # Define upstream servers (backend targets). - id: jsonplaceholder url: jsonplaceholder.typicode.com - id: dummy url: dummyjson.com headers: # Custom headers added to proxied requests. - key: x-forward-for value: $ip # Adds the client IP to the forwarded request. - key: Authorization value: Bearer xyz # Adds an authorization token to requests. rules: # Define routing rules for incoming requests. - path: /test upstreams: - dummy # Routes requests to "/test" to the "dummy" upstream. - path: / upstreams: - jsonplaceholder # Routes all other requests to "jsonplaceholder".
Proses induk menggunakan strategi pengedaran rawak mudah untuk menetapkan permintaan kepada pekerja. Walaupun tidak secanggih algoritma round-robin atau paling kurang sambungan, pendekatan ini menyediakan pengagihan beban yang baik untuk kebanyakan kes penggunaan. Logik pengedaran permintaan:
Setiap pekerja mendengar mesej, memadankan permintaan dengan peraturan penghalaan dan memajukannya ke pelayan huluan yang sesuai.
import { z } from "zod"; const upstreamSchema = z.object({ id: z.string(), url: z.string(), }); const headerSchema = z.object({ key: z.string(), value: z.string(), }); const ruleSchema = z.object({ path: z.string(), upstreams: z.array(z.string()), }); const serverSchema = z.object({ listen: z.number(), workers: z.number().optional(), upstreams: z.array(upstreamSchema), headers: z.array(headerSchema).optional(), rules: z.array(ruleSchema), }); export const rootConfigSchema = z.object({ server: serverSchema, }); export type ConfigSchemaType = z.infer<typeof rootConfigSchema>;
Proses induk berkomunikasi dengan pekerja dengan membina muatan mesej standard, termasuk semua maklumat permintaan yang diperlukan, menggunakan Node.js IPC (Komunikasi Antara Proses) dan mengesahkan struktur mesej menggunakan skema Zod.
Pekerja mengendalikan pemprosesan permintaan dan proksi sebenar. Setiap pekerja:
Pekerja memilih pelayan huluan dengan:
Mekanisme pemajuan permintaan:
Untuk menjalankan pelayan, ikut langkah berikut:
import fs from "node:fs/promises"; import { parse } from "yaml"; import { rootConfigSchema } from "./config-schema"; export async function parseYAMLConfig(filepath: string) { const configFileContent = await fs.readFile(filepath, "utf8"); const configParsed = parse(configFileContent); return JSON.stringify(configParsed); } export async function validateConfig(config: string) { const validatedConfig = await rootConfigSchema.parseAsync( JSON.parse(config) ); return validatedConfig; }
if (cluster.isPrimary) { console.log("Master Process is up ?"); for (let i = 0; i < workerCount; i++) { const w = cluster.fork({ config: JSON.stringify(config) }); WORKER_POOL.push(w); console.log(Master Process: Worker Node spinned: ${i}); } const server = http.createServer((req, res) => { const index = Math.floor(Math.random() * WORKER_POOL.length); const worker = WORKER_POOL.at(index); if (!worker) throw new Error("Worker not found."); const payload: WorkerMessageSchemaType = { requestType: "HTTP", headers: req.headers, body: null, url: ${req.url}, }; worker.send(JSON.stringify(payload)); worker.once("message", async (workerReply: string) => { const reply = await workerMessageReplySchema.parseAsync( JSON.parse(workerReply) ); if (reply.errorCode) { res.writeHead(parseInt(reply.errorCode)); res.end(reply.error); } else { res.writeHead(200); res.end(reply.data); } }); }); server.listen(port, () => { console.log(Reverse Proxy listening on port: ${port}); }); }
const server = http.createServer(function (req, res) { const index = Math.floor(Math.random() * WORKER_POOL.length); const worker = WORKER_POOL.at(index); const payload: WorkerMessageSchemaType = { requestType: "HTTP", headers: req.headers, body: null, url: ${req.url}, }; worker.send(JSON.stringify(payload)); });
Dalam tangkapan skrin di atas, kita dapat melihat bahawa terdapat 1 Nod Induk dan 2 Proses Pekerja sedang berjalan. Pelayan proksi terbalik kami sedang mendengar pada port 8080.
Dalam fail config.yaml, kami menerangkan dua pelayan huluan iaitu: jsonplaceholder dan dummy. Jika kami mahu semua permintaan datang ke pelayan kami untuk dihalakan ke jsonplaceholder, kami meletakkan peraturan sebagai:
├── config.yaml # Server configuration ├── src/ │ ├── config-schema.ts # Configuration validation schemas │ ├── config.ts # Configuration parsing logic │ ├── index.ts # Application entry point │ ├── server-schema.ts # Server message schemas │ └── server.ts # Core server implementation └── tsconfig.json # TypeScript configuration
Begitu juga, jika kami mahu permintaan kami ke titik akhir /test harus dihalakan ke pelayan huluan dummy kami, kami meletakkan peraturan sebagai:
server: listen: 8080 # Port the server listens on. workers: 2 # Number of worker processes to handle requests. upstreams: # Define upstream servers (backend targets). - id: jsonplaceholder url: jsonplaceholder.typicode.com - id: dummy url: dummyjson.com headers: # Custom headers added to proxied requests. - key: x-forward-for value: $ip # Adds the client IP to the forwarded request. - key: Authorization value: Bearer xyz # Adds an authorization token to requests. rules: # Define routing rules for incoming requests. - path: /test upstreams: - dummy # Routes requests to "/test" to the "dummy" upstream. - path: / upstreams: - jsonplaceholder # Routes all other requests to "jsonplaceholder".
Jom uji ini!
Wah, memang bagus! Kami menavigasi ke localhost:8080 tetapi sebagai tindak balas kami dapat melihat kami menerima halaman utama untuk jsonplaceholder.typicode.com. Pengguna akhir tidak tahu bahawa kami melihat respons daripada pelayan yang berasingan. Itulah sebabnya pelayan Proksi Songsang adalah penting. Jika kami mempunyai berbilang pelayan yang menjalankan kod yang sama dan tidak mahu mendedahkan semua port mereka kepada pengguna akhir, gunakan proksi terbalik sebagai lapisan abstraksi. Pengguna akan memukul pelayan proksi terbalik, pelayan yang sangat teguh dan pantas, dan ia akan menentukan pelayan mana untuk meminta laluan.
Jom tekan localhost:8080/todos sekarang dan lihat apa yang berlaku.
Permintaan kami mendapat proksi terbalik kepada pelayan jsonplaceholder sekali lagi dan menerima respons JSON daripada URL yang diselesaikan: jsonplaceholder.typicode.com/todos.
Mari kita bayangkan aliran permintaan yang lengkap:
Klien menghantar permintaan → Proses Induk
Proses Induk → Pekerja Terpilih
Pekerja → Pelayan Huluan
Pelayan Huluan → Pekerja
Pekerja → Proses Induk
Proses Induk → Pelanggan
Seni bina berbilang proses menyediakan beberapa faedah prestasi:
Walaupun berfungsi, pelaksanaan semasa boleh dipertingkatkan dengan:
Membina pelayan proksi terbalik dari awal mungkin kelihatan menakutkan pada mulanya, tetapi seperti yang telah kami terokai, ini merupakan pengalaman yang menggembirakan. Dengan menggabungkan kluster Node.js, TypeScript dan pengurusan konfigurasi berasaskan YAML, kami telah mencipta sistem berskala dan cekap yang diilhamkan oleh Nginx.
Masih ada ruang untuk mempertingkatkan pelaksanaan ini — pengimbangan beban, caching atau sokongan WebSocket yang lebih baik hanyalah beberapa idea untuk diterokai. Tetapi reka bentuk semasa menetapkan asas yang kukuh untuk bereksperimen dan berskala lebih jauh. Jika anda telah mengikutinya, anda kini bersedia untuk menyelam lebih dalam ke dalam proksi terbalik atau bahkan mula membina penyelesaian tersuai yang disesuaikan dengan keperluan anda.
Jika anda ingin menyambung atau melihat lebih banyak kerja saya, lihat GitHub saya, LinkedIn.
Repositori untuk projek ini boleh didapati di sini.
Saya ingin mendengar pendapat, maklum balas atau idea anda untuk penambahbaikan. Terima kasih kerana membaca, dan selamat mengekod! ?
Atas ialah kandungan terperinci Membina Pelayan Proksi Songsang Skala seperti Nginx dengan Node.js dan TypeScript. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!