منذ بضعة أشهر أصدرنا Encore.ts - إطار عمل خلفي مفتوح المصدر لـ TypeScript.
نظرًا لوجود الكثير من أطر العمل بالفعل، أردنا مشاركة بعض قرارات التصميم غير الشائعة التي اتخذناها وكيف تؤدي إلى أرقام أداء رائعة.
لقد نشرنا سابقًا معايير توضح كيف أن Encore.ts أسرع بمقدار 9 مرات من Express وأسرع مرتين من Fastify.
هذه المرة قمنا بمقارنة Encore.ts مع ElysiaJS وHono، وهما إطاران حديثان عاليان الأداء لـ TypeScript.
قمنا بقياس كل إطار عمل مع أو بدون التحقق من صحة المخطط، باستخدام TypeBox للتحقق من الصحة مع ElsyiaJS وHono لأنها مكتبة تحقق مدعومة محليًا لهذه الأطر. (يحتوي Encore.ts على التحقق من صحة النوع المدمج الخاص به والذي يعمل بشكل شامل.)
لكل معيار حصلنا على أفضل نتيجة من خمس جولات. تم تنفيذ كل تشغيل من خلال تقديم أكبر عدد ممكن من الطلبات مع 150 عاملًا متزامنًا، على مدار 10 ثوانٍ. تم إجراء عملية توليد التحميل باستخدام oha، وهي أداة اختبار تحميل HTTP المستندة إلى Rust وTokio.
كفى كلام، دعونا نرى الأرقام!
(راجع الكود المعياري على GitHub.)
بصرف النظر عن الأداء، يحقق Encore.ts ذلك مع الحفاظ على التوافق بنسبة 100% مع Node.js.
كيف يكون هذا ممكنا؟ من خلال اختباراتنا، حددنا ثلاثة مصادر رئيسية للأداء، وكلها تتعلق بكيفية عمل Encore.ts ضمن الغطاء.
يقوم Node.js بتشغيل كود JavaScript باستخدام حلقة حدث ذات ترابط واحد. على الرغم من طبيعته أحادية الترابط، إلا أنه قابل للتطوير بشكل كبير من الناحية العملية، نظرًا لأنه يستخدم عمليات الإدخال/الإخراج غير المحظورة، كما تم تحسين محرك JavaScript الأساسي V8 (الذي يعمل على تشغيل Chrome أيضًا).
لكن هل تعلم ما هو الأسرع من حلقة الأحداث ذات الخيط الواحد؟ واحد متعدد الخيوط.
يتكون Encore.ts من جزأين:
A TypeScript SDK الذي تستخدمه عند كتابة الواجهات الخلفية باستخدام Encore.ts.
وقت تشغيل عالي الأداء، مع حلقة أحداث متعددة الخيوط وغير متزامنة مكتوبة بلغة Rust (باستخدام Tokio وHyper).
يتعامل Encore Runtime مع جميع عمليات الإدخال/الإخراج مثل قبول ومعالجة طلبات HTTP الواردة. يعمل هذا كحلقة حدث مستقلة تمامًا تستخدم العديد من سلاسل الرسائل التي يدعمها الجهاز الأساسي.
بمجرد معالجة الطلب وفك تشفيره بالكامل، يتم تسليمه إلى حلقة أحداث Node.js، ثم يأخذ الاستجابة من معالج واجهة برمجة التطبيقات ويكتبها مرة أخرى إلى العميل.
(قبل أن تقول ذلك: نعم، لقد وضعنا حلقة حدث في حلقة الحدث الخاصة بك، حتى تتمكن من تكرار الحدث أثناء تكرار الحدث.)
تم تصميم Encore.ts، كما يوحي الاسم، من الألف إلى الياء لـ TypeScript. لكن لا يمكنك فعليًا تشغيل TypeScript: يجب أولاً تجميعه إلى JavaScript، عن طريق تجريد جميع معلومات النوع. وهذا يعني أن تحقيق الأمان في وقت التشغيل أصعب بكثير، مما يجعل من الصعب القيام بأشياء مثل التحقق من صحة الطلبات الواردة، مما يؤدي إلى أن تصبح حلول مثل Zod شائعة لتحديد مخططات واجهة برمجة التطبيقات في وقت التشغيل بدلاً من ذلك.
يعمل Encore.ts بشكل مختلف. باستخدام Encore، يمكنك تحديد واجهات برمجة التطبيقات الآمنة للنوع باستخدام أنواع TypeScript الأصلية:
import { api } from "encore.dev/api"; interface BlogPost { id: number; title: string; body: string; likes: number; } export const getBlogPost = api( { method: "GET", path: "/blog/:id", expose: true }, async ({ id }: { id: number }) => Promise{ // ... }, );
يقوم Encore.ts بعد ذلك بتحليل الكود المصدري لفهم مخطط الطلب والاستجابة الذي تتوقعه كل نقطة نهاية لواجهة برمجة التطبيقات، بما في ذلك أشياء مثل رؤوس HTTP ومعلمات الاستعلام وما إلى ذلك. تتم بعد ذلك معالجة المخططات وتحسينها وتخزينها كملف Protobuf.
عند بدء تشغيل Encore Runtime، يقرأ ملف Protobuf هذا ويحسب مسبقًا وحدة فك ترميز الطلب ووحدة تشفير الاستجابة، المُحسّنة لكل نقطة نهاية لواجهة برمجة التطبيقات، باستخدام تعريف النوع الدقيق الذي تتوقعه كل نقطة نهاية لواجهة برمجة التطبيقات. في الواقع، يتعامل Encore.ts مع التحقق من صحة الطلب مباشرةً في Rust، مما يضمن عدم اضطرار الطلبات غير الصالحة أبدًا إلى لمس طبقة JS، مما يخفف من العديد من هجمات رفض الخدمة.
يثبت فهم Encore لمخطط الطلب أيضًا أنه مفيد من منظور الأداء. تستخدم أوقات تشغيل JavaScript مثل Deno وBun بنية مشابهة لتلك الخاصة بوقت التشغيل المستند إلى Rust في Encore (في الواقع، يستخدم Deno أيضًا Rust Tokio Hyper)، لكنه يفتقر إلى فهم Encore لمخطط الطلب. ونتيجة لذلك، يحتاجون إلى تسليم طلبات HTTP غير المعالجة إلى محرك JavaScript أحادي الخيط للتنفيذ.
من ناحية أخرى، يتعامل Encore.ts مع الكثير من معالجة الطلبات داخل Rust، ويقوم فقط بتسليم كائنات الطلب التي تم فك تشفيرها. من خلال التعامل مع المزيد من دورة حياة الطلب في Rust متعدد الخيوط، يتم تحرير حلقة أحداث JavaScript للتركيز على تنفيذ منطق أعمال التطبيق بدلاً من تحليل طلبات HTTP، مما يؤدي إلى تعزيز أداء أكبر.
ربما لاحظ القراء الحذرون وجود اتجاه: مفتاح الأداء هو إلغاء تحميل أكبر قدر ممكن من العمل من حلقة أحداث JavaScript ذات الترابط الواحد قدر الإمكان.
لقد بحثنا بالفعل في كيفية قيام Encore.ts بإلغاء تحميل معظم دورة حياة الطلب/الاستجابة إلى Rust. إذن ما الذي يجب فعله أيضًا؟
حسنًا، تطبيقات الواجهة الخلفية تشبه السندويشات. لديك الطبقة العليا القاسية، حيث يمكنك التعامل مع الطلبات الواردة. في المركز لديك الطبقة اللذيذة (أي منطق عملك بالطبع). في الجزء السفلي لديك طبقة الوصول إلى البيانات، حيث يمكنك الاستعلام عن قواعد البيانات، واستدعاء نقاط نهاية واجهة برمجة التطبيقات الأخرى، وما إلى ذلك.
لا يمكننا فعل الكثير بشأن منطق الأعمال - نريد كتابة ذلك بلغة TypeScript، بعد كل شيء! - ولكن ليس هناك فائدة كبيرة من جعل جميع عمليات الوصول إلى البيانات تضغط على حلقة أحداث JS الخاصة بنا. إذا نقلنا تلك العناصر إلى Rust، فسنقوم بتحرير حلقة الأحداث بشكل أكبر حتى نتمكن من التركيز على تنفيذ كود التطبيق الخاص بنا.
وهذا ما فعلناه.
باستخدام Encore.ts، يمكنك الإعلان عن موارد البنية التحتية مباشرة في كود المصدر الخاص بك.
على سبيل المثال، لتحديد موضوع Pub/Sub:
import { Topic } from "encore.dev/pubsub"; interface UserSignupEvent { userID: string; email: string; } export const UserSignups = new Topic("user-signups", { deliveryGuarantee: "at-least-once", }); // To publish: await UserSignups.publish({ userID: "123", email: "[email protected]" });
"ما هي تقنية Pub/Sub التي تستخدمها؟"
— كل منهم!
يتضمن وقت تشغيل Encore Rust تطبيقات لتقنيات Pub/Sub الأكثر شيوعًا، بما في ذلك AWS SQS SNS وGCP Pub/Sub وNSQ، مع المزيد من المخططات (Kafka وNATS وAzure Service Bus وما إلى ذلك). يمكنك تحديد التنفيذ على أساس كل مورد في تكوين وقت التشغيل عند تشغيل التطبيق، أو السماح لأتمتة Encore's Cloud DevOps بالتعامل معه نيابةً عنك.
بعيدًا عن Pub/Sub، يتضمن Encore.ts عمليات تكامل البنية التحتية لقواعد بيانات PostgreSQL وSecrets وCron Jobs والمزيد.
يتم تنفيذ جميع عمليات تكامل البنية التحتية هذه في Encore.ts Rust Runtime.
هذا يعني أنه بمجرد الاتصال بـ .publish()، يتم تسليم الحمولة إلى Rust الذي يتولى نشر الرسالة، وإعادة المحاولة إذا لزم الأمر، وما إلى ذلك. وينطبق الشيء نفسه على استعلامات قاعدة البيانات، والاشتراك في رسائل Pub/Sub، والمزيد.
النتيجة النهائية هي أنه مع Encore.ts، يتم إلغاء تحميل كل المنطق غير التجاري تقريبًا من حلقة أحداث JS.
في جوهر الأمر، مع Encore.ts، يمكنك الحصول على واجهة خلفية متعددة الخيوط حقًا "مجانًا"، بينما تظل قادرًا على كتابة كل منطق عملك في TypeScript.
يعتمد ما إذا كان هذا الأداء مهمًا أم لا على حالة الاستخدام الخاصة بك. إذا كنت تبني مشروعًا صغيرًا كهواية، فهو أكاديمي إلى حد كبير. ولكن إذا كنت تقوم بشحن واجهة إنتاج خلفية إلى السحابة، فقد يكون لذلك تأثير كبير جدًا.
يؤثر زمن الوصول المنخفض بشكل مباشر على تجربة المستخدم. لتوضيح ما هو واضح: الواجهة الخلفية الأسرع تعني واجهة أمامية أكثر سرعة، مما يعني مستخدمين أكثر سعادة.
تعني الإنتاجية الأعلى أنه يمكنك خدمة نفس العدد من المستخدمين باستخدام عدد أقل من الخوادم، وهو ما يتوافق بشكل مباشر مع انخفاض فواتير السحابة. أو على العكس من ذلك، يمكنك خدمة عدد أكبر من المستخدمين بنفس عدد الخوادم، مما يضمن إمكانية التوسع بشكل أكبر دون مواجهة اختناقات في الأداء.
على الرغم من أننا متحيزون، إلا أننا نعتقد أن Encore تقدم حلاً ممتازًا جدًا والأفضل من بين جميع الحلول لبناء واجهات خلفية عالية الأداء في TypeScript. إنه سريع، وآمن للنوع، ومتوافق مع النظام البيئي Node.js بأكمله.
وكلها مفتوحة المصدر، لذا يمكنك التحقق من الكود والمساهمة على GitHub.
أو قم بتجربته وأخبرنا برأيك!
تنصل: جميع الموارد المقدمة هي جزئيًا من الإنترنت. إذا كان هناك أي انتهاك لحقوق الطبع والنشر الخاصة بك أو الحقوق والمصالح الأخرى، فيرجى توضيح الأسباب التفصيلية وتقديم دليل على حقوق الطبع والنشر أو الحقوق والمصالح ثم إرسالها إلى البريد الإلكتروني: [email protected]. سوف نتعامل مع الأمر لك في أقرب وقت ممكن.
Copyright© 2022 湘ICP备2022001581号-3