لماذا لا تتوافق تطبيقات C++ المصغرة مع الشبكة (وكيف يصلح service mesh ذلك)
\n\nتخيل: لقد بنيت خط أنابيب مثاليًا بلغة C++. كل تطبيق مصغر يعمل بسرعة، والذاكرة تُدار بإحكام، والسرعة تفوق الخيال. ولكن بمجرد تشغيل كل هذا في Kubernetes، يبدأ الجحيم. مهلات زمنية، إعادة محاولات، انقطاع الاتصالات، فقدان الحزم. تتعمق في الكود، وتدخل curl في كل زاوية، وتكتب حلولك الخاصة لإعادة المحاولات - ويتحول المشروع إلى مستنقع.
هل هذا مألوف؟ مرحبًا بك في عالم الأنظمة الموزعة. الخبر السار: المشكلة يحلها service mesh. الخبر السيئ: معظم الدروس التعليمية مكتوبة بلغة Go أو Java. وماذا عنا نحن مطوري C++ الذين لا يريدون جر Istio الثقيل مع Envoy، بينما يمكننا عمل حل خفيف ومفهوم؟
\n\nفي هذه المقالة، سنشرح كيفية دمج service mesh لتطبيقات C++ المصغرة دون ألم وتبعيات غير ضرورية. ستتعرف على ما هو الوكيل الجانبي sidecar، وكيفية إعداد موازنة التحميل وقابلية المراقبة، وستحصل على مثال كود حقيقي بلغة C++ مع gRPC.
\n\nما هو service mesh ولماذا هو مهم لمشاريع C++؟
\n\nService mesh هو طبقة بنية تحتية مخصصة لإدارة حركة مرور الشبكة بين التطبيقات المصغرة. بدلاً من تضمين منطق المهلات الزمنية وإعادة المحاولات وموازنة التحميل والتشفير في كل خدمة، تقوم بإخراجها إلى حاوية وكيل (sidecar) تعمل بجانب كل مثيل من خدمتك.
\n\nبالنسبة لـ C++، هذا مهم بشكل خاص للأسباب التالية:
\n- \n
- عادةً ما يُكتب كود C++ للأنظمة عالية التحميل حيث كل ميكروثانية لها أهميتها؛ \n
- منطق الشبكة المدمج يعقد الصيانة والاختبار؛ \n
- المكتبة القياسية لـ C++ لا توفر آليات جاهزة لـ service mesh (على عكس Go مع عملاء HTTP القياسيين لديها). \n
المكونات الرئيسية لـ service mesh:
\n- \n
- Data plane — خوادم الوكيل (عادةً Envoy، ولكن يمكنك استخدام خوادمك الخاصة بلغة C++); \n
- Control plane — طبقة التحكم (مثل Istio أو Consul أو طبقة مكتوبة يدويًا). \n
سنركز على data plane، لأن هذا هو المكان الذي تظهر فيه قوة C++.
\n\nكتابة وكيل جانبي sidecar خاص بنا بلغة C++ (مثال بسيط)
\n\nلنفترض أن لدينا تطبيقين مصغرين: auth_service و user_service. يتواصلان عبر gRPC. بدون service mesh، يجب على كل خدمة معالجة إعادة الاتصال والمهلات الزمنية والمقاييس بنفسها. مع الوكيل، لا.
إليك مثال بسيط لوكيل جانبي sidecar بلغة C++ باستخدام مكتبة libcurl و nlohmann/json. يقبل طلبات HTTP، ويوكلها إلى الخلفية، ويضيف إعادة محاولات، ويسجل التأخيرات.
// proxy.cpp\n#include <httplib.h>\n#include <nlohmann/json.hpp>\n#include <curl/curl.h>\n#include <chrono>\n#include <thread>\n\nusing json = nlohmann::json;\nusing namespace std::chrono_literals;\n\nclass SidecarProxy {\npublic:\n SidecarProxy(const std::string& backend_host, int backend_port, int max_retries = 3)\n : backend_host_(backend_host), backend_port_(backend_port), max_retries_(max_retries) {}\n\n void handle_request(const httplib::Request& req, httplib::Response& res) {\n auto start = std::chrono::high_resolution_clock::now();\n CURL* curl = curl_easy_init();\n std::string url = "http://" + backend_host_ + ":" + std::to_string(backend_port_) + req.path;\n curl_easy_setopt(curl, CURLOPT_URL, url.c_str());\n curl_easy_setopt(curl, CURLOPT_TIMEOUT, 5L);\n\n std::string response_string;\n curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, +[](void* ptr, size_t size, size_t nmemb, void* stream) {\n ((std::string*)stream)->append((char*)ptr, size * nmemb);\n return size * nmemb;\n });\n curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response_string);\n\n int retries = 0;\n CURLcode code;\n d