تبسيط تكامل البرامج: برنامج تعليمي Apache Camel
نشرت: 2022-03-11نادرًا ما توجد البرامج ، إن وجدت ، في فراغ إعلامي. على الأقل ، هذا هو الافتراض الذي يمكننا نحن مهندسو البرمجيات القيام به لمعظم التطبيقات التي نطورها.
على أي نطاق ، يتواصل كل جزء من البرنامج - بطريقة أو بأخرى - مع بعض البرامج الأخرى لأسباب مختلفة: للحصول على بيانات مرجعية من مكان ما ، وإرسال إشارات المراقبة ، والتواصل مع خدمات أخرى أثناء كونك جزءًا من برنامج موزع النظام والمزيد.
في هذا البرنامج التعليمي ، ستتعرف على بعض أكبر تحديات دمج البرامج الكبيرة وكيف يحلها Apache Camel بسهولة.
المشكلة: التصميم المعماري لتكامل النظم
ربما تكون قد قمت بما يلي مرة واحدة على الأقل في حياتك في هندسة البرمجيات:
- حدد جزءًا من منطق عملك يجب أن يبدأ في إرسال البيانات.
- في نفس طبقة التطبيق ، اكتب تحويلات البيانات وفقًا لما يتوقعه المستلم.
- قم بلف البيانات في بنية مناسبة للنقل والتوجيه عبر الشبكة.
- افتح اتصالاً بتطبيق هدف باستخدام برنامج تشغيل مناسب أو عميل SDK.
- إرسال البيانات والتعامل مع الاستجابة.
لماذا يعتبر هذا خط عمل سيئ؟
على الرغم من أنه لا يوجد لديك سوى عدد قليل من الاتصالات من هذا النوع ، إلا أنه يظل قابلاً للإدارة. مع تزايد عدد العلاقات بين الأنظمة ، يتم خلط منطق الأعمال للتطبيق مع منطق التكامل ، والذي يتعلق بتكييف البيانات ، وتعويض الاختلافات التكنولوجية بين نظامين ، ونقل البيانات إلى النظام الخارجي باستخدام SOAP أو REST أو الطلبات الأكثر غرابة .
إذا كنت تدمج العديد من التطبيقات ، فسيكون من الصعب للغاية تتبع الصورة الكاملة للتبعيات في مثل هذا الرمز: أين يتم إنتاج البيانات وما هي الخدمات التي تستهلكها؟ سيكون لديك العديد من الأماكن حيث يتم تكرار منطق التكامل ، للإقلاع.
باستخدام هذا النهج ، على الرغم من أن المهمة قد تم إنجازها تقنيًا ، إلا أننا نواجه مشكلات ضخمة تتعلق بإمكانية صيانة التكامل وقابلية التوسع. إن إعادة التنظيم السريع لتدفقات البيانات في هذا النظام قريبة من المستحيل ، ناهيك عن المشكلات الأعمق مثل نقص المراقبة ، وقطع الدائرة ، واستعادة البيانات الشاقة ، إلخ.
كل هذا مهم بشكل خاص عند دمج البرامج في نطاق مؤسسة كبيرة إلى حد كبير. للتعامل مع تكامل المؤسسة يعني العمل مع مجموعة من التطبيقات ، والتي تعمل على مجموعة واسعة من المنصات وتتواجد في مواقع مختلفة. إن تبادل البيانات في مثل هذا المشهد من البرامج يتطلب الكثير. يجب أن تفي بمعايير الأمان العالية للصناعة وتوفر طريقة موثوقة لنقل البيانات. في بيئة المؤسسة ، يتطلب تكامل الأنظمة تصميم معماري منفصل ومفصل بدقة.
ستقدم لك هذه المقالة الصعوبات الفريدة التي تواجهك في تكامل البرامج بالإضافة إلى تقديم بعض الحلول القائمة على الخبرة لمهام التكامل. سنتعرف على Apache Camel ، وهو إطار عمل مفيد يمكنه التخفيف من أسوأ أجزاء صداع مطور التكامل. سوف نتبع بمثال عن كيفية مساعدة Camel في إنشاء اتصال في مجموعة من الخدمات المصغرة التي تدعمها Kubernetes.
صعوبات الاندماج
من الأساليب المستخدمة على نطاق واسع لحل المشكلة فصل طبقة التكامل في التطبيق الخاص بك. يمكن أن توجد داخل نفس التطبيق أو كقطعة برمجية مخصصة تعمل بشكل مستقل - في الحالة الأخيرة تسمى البرامج الوسيطة.
ما المشكلات التي تواجهها عادةً عند تطوير البرامج الوسيطة ودعمها؟ بشكل عام ، لديك العناصر الرئيسية التالية:
- جميع قنوات البيانات لا يمكن الاعتماد عليها إلى حد ما. قد لا تحدث المشكلات الناشئة عن عدم الموثوقية هذه بينما تكون كثافة البيانات منخفضة إلى متوسطة. كل مستوى تخزين من ذاكرة التطبيق إلى ذاكرات التخزين المؤقت السفلية والمعدات الموجودة تحتها عرضة للفشل المحتمل. تظهر بعض الأخطاء النادرة فقط مع كميات ضخمة من البيانات. حتى منتجات البائعين الناضجة الجاهزة للإنتاج بها مشكلات تعقب أخطاء لم يتم حلها تتعلق بفقدان البيانات. يجب أن يكون نظام البرامج الوسيطة قادرًا على إبلاغك بضحايا البيانات وإعادة تسليم رسائل الإمداد في الوقت المناسب.
- تستخدم التطبيقات بروتوكولات وتنسيقات بيانات مختلفة. هذا يعني أن نظام التكامل هو ستار لتحويل البيانات والمحولات للمشاركين الآخرين ويستخدم مجموعة متنوعة من التقنيات. يمكن أن تتضمن هذه استدعاءات REST API العادية ، ولكن يمكن أيضًا الوصول إلى وسيط قائمة الانتظار ، أو إرسال أوامر CSV عبر FTP ، أو سحب البيانات دفعة واحدة إلى جدول قاعدة البيانات. هذه قائمة طويلة ولن تصبح أقصر من أي وقت مضى.
- التغييرات في تنسيقات البيانات وقواعد التوجيه أمر لا مفر منه. عادة ما تؤدي كل خطوة في عملية تطوير التطبيق ، والتي تغير هيكل البيانات ، إلى تغييرات في تنسيقات وتحويلات بيانات التكامل. في بعض الأحيان ، تكون تغييرات البنية التحتية مع تدفقات بيانات المؤسسة المعاد تنظيمها ضرورية. على سبيل المثال ، قد تحدث هذه التغييرات عند تقديم نقطة واحدة للتحقق من صحة البيانات المرجعية التي يجب أن تعالج جميع إدخالات البيانات الرئيسية في جميع أنحاء الشركة. مع أنظمة
N
، قد ينتهي بنا الأمر بالحصول على الحد الأقصى من التوصيلاتN^2
تقريبًا بينها ، وبالتالي فإن عدد الأماكن التي يجب تطبيق التغييرات فيها ينمو بسرعة كبيرة. سيكون مثل الانهيار الجليدي. للحفاظ على قابلية الصيانة ، يجب أن توفر طبقة البرامج الوسيطة صورة واضحة للتبعيات مع التوجيه متعدد الاستخدامات وتحويل البيانات.
يجب وضع هذه الأفكار في الاعتبار عند تصميم الدمج واختيار أنسب حل للبرامج الوسيطة. إحدى الطرق الممكنة للتعامل معها هي الاستفادة من ناقل خدمة المؤسسة (ESB). لكن ESBs التي يقدمها كبار البائعين ثقيلة جدًا بشكل عام وغالبًا ما تكون مشكلة أكثر مما تستحق: يكاد يكون من المستحيل أن تكون بداية سريعة مع ESB ، ولديها منحنى تعليمي حاد للغاية ، ويتم التضحية بمرونتها لقائمة طويلة من الميزات والأدوات المدمجة. في رأيي ، تعتبر حلول التكامل خفيفة الوزن مفتوحة المصدر أفضل بكثير - فهي أكثر مرونة ، ويسهل نشرها في السحابة ، ويسهل قياسها.
تكامل البرامج ليس بالأمر السهل. اليوم ، بينما نبني هياكل الخدمات الصغيرة ونتعامل مع أسراب من الخدمات الصغيرة ، لدينا أيضًا توقعات عالية حول مدى كفاءة التواصل.
أنماط تكامل المؤسسة
كما هو متوقع ، مثل تطوير البرمجيات بشكل عام ، فإن تطوير توجيه البيانات وتحويلها ينطوي على عمليات متكررة. تم تلخيص الخبرة في هذا المجال وتنظيمها من قبل المتخصصين الذين يتعاملون مع مشاكل التكامل لبعض الوقت. في النتيجة ، هناك مجموعة من القوالب المستخرجة تسمى أنماط تكامل المؤسسة المستخدمة لتصميم تدفقات البيانات. تم وصف طرق التكامل هذه في الكتاب الذي يحمل نفس الاسم من قبل جريجور هوبه وبوبي وولف ، والذي يشبه إلى حد كبير كتاب Gang of Four المهم ولكن في مجال برامج اللصق.
لإعطاء مثال ، يقدم نمط التسوية مكونًا يقوم بتعيين الرسائل المتساوية لغويًا والتي لها تنسيقات بيانات مختلفة إلى نموذج أساسي واحد ، أو المُجمِّع عبارة عن EIP الذي يجمع سلسلة من الرسائل في رسالة واحدة.
نظرًا لأنها عبارة عن تجريدات راسخة لا تعتمد على التكنولوجيا وتستخدم لحل المشكلات المعمارية ، تساعد EIPs في كتابة تصميم معماري ، والذي لا يتعمق في مستوى الكود ولكنه يصف تدفقات البيانات بتفاصيل كافية. مثل هذا الترميز لوصف طرق التكامل لا يجعل التصميم موجزًا فحسب ، بل يضع أيضًا تسمية مشتركة ولغة مشتركة ، وهما أمران مهمان للغاية في سياق حل مهمة تكامل مع أعضاء الفريق من مختلف مجالات العمل.
تقديم أباتشي كاميل
منذ عدة سنوات ، كنت أقوم ببناء تكامل مؤسسي في شبكة ضخمة من متاجر البقالة بالتجزئة مع متاجر في مواقع موزعة على نطاق واسع. لقد بدأت بحل ESB مملوك ، والذي تبين أن صيانته مرهقة للغاية. بعد ذلك ، صادف فريقنا Apache Camel ، وبعد القيام ببعض أعمال "إثبات المفهوم" ، قمنا بسرعة بإعادة كتابة جميع تدفقات البيانات لدينا في مسارات Camel.
يمكن وصف Apache Camel بأنه "موجه وسيط" ، وهو إطار عمل برمجي وسيط موجه نحو الرسائل ينفذ قائمة EIPs ، والتي تعرفت عليها. يستفيد من هذه الأنماط ، ويدعم جميع بروتوكولات النقل الشائعة ، ويحتوي على مجموعة كبيرة من المحولات المفيدة المضمنة. يتيح Camel التعامل مع عدد من إجراءات التكامل دون الحاجة إلى كتابة التعليمات البرمجية الخاصة بك.
بصرف النظر عن هذا ، أود أن أفرد ميزات Apache Camel التالية:
- تتم كتابة طرق التكامل كخطوط أنابيب مصنوعة من كتل. إنه يخلق صورة شفافة تمامًا للمساعدة في تعقب تدفق البيانات.
- يحتوي الجمل على محولات للعديد من واجهات برمجة التطبيقات الشائعة. على سبيل المثال ، الحصول على البيانات من Apache Kafka ومراقبة مثيلات AWS EC2 والتكامل مع Salesforce - يمكن حل جميع هذه المهام باستخدام المكونات المتاحة خارج الصندوق.
يمكن كتابة مسارات Apache Camel في Java أو Scala DSL. (يتوفر أيضًا تكوين XML ولكنه يصبح مطولًا جدًا ولديه إمكانيات تصحيح أسوأ.) لا يفرض قيودًا على المجموعة التقنية لخدمات الاتصال ، ولكن إذا كنت تكتب بلغة Java أو Scala ، فيمكنك تضمين Camel في تطبيق بدلاً من ذلك من تشغيله بشكل مستقل.
يمكن وصف تدوين التوجيه الذي يستخدمه Camel بالرمز الكاذب البسيط التالي:
from(Source) .transform(Transformer) .to(Destination)
Source
Transformer
Destination
هي نقاط نهاية تشير إلى مكونات التنفيذ بواسطة URIs الخاصة بهم.
ما الذي يمكّن Camel من حل مشاكل الاندماج التي وصفتها سابقًا؟ لنلقي نظرة. أولاً ، لا يعيش منطق التوجيه والتحويل الآن إلا في تكوين Apache Camel المخصص. ثانيًا ، من خلال DSL المختصر والطبيعي بالتزامن مع استخدام EIPs ، تظهر صورة التبعيات بين الأنظمة. إنه مصنوع من تجريدات مفهومة ، ومنطق التوجيه قابل للتعديل بسهولة. وأخيرًا ، لا يتعين علينا كتابة أكوام من كود التحويل لأنه من المحتمل أن يتم تضمين المحولات المناسبة بالفعل.
يجب أن أضيف ، Apache Camel هو إطار عمل ناضج ويحصل على تحديثات منتظمة. لديها مجتمع كبير وقاعدة معرفية تراكمية كبيرة.
لديها عيوبها الخاصة. لا ينبغي اعتبار الجمل مجموعة تكامل معقدة. إنه صندوق أدوات بدون ميزات عالية المستوى مثل أدوات إدارة عمليات الأعمال أو أجهزة مراقبة النشاط ، ولكن يمكن استخدامه لإنشاء مثل هذه البرامج.
قد تكون الأنظمة البديلة ، على سبيل المثال ، Spring Integration أو Mule ESB. بالنسبة إلى Spring Integration ، على الرغم من أنها تعتبر خفيفة الوزن ، من واقع خبرتي ، فإن تجميعها معًا وكتابة الكثير من ملفات تكوين XML يمكن أن يتضح أنها معقدة بشكل غير متوقع وليست طريقة سهلة للخروج. Mule ESB عبارة عن مجموعة أدوات قوية وعملية للغاية ، ولكن كما يوحي الاسم ، فهي حافلة خدمة مؤسسية ، لذا فهي تنتمي إلى فئة وزن مختلفة. يمكن مقارنة Mule بـ Fuse ESB ، وهو منتج مشابه يعتمد على Apache Camel مع مجموعة غنية من الميزات. بالنسبة لي ، يعد استخدام Apache Camel لخدمات اللصق أمرًا لا يحتاج إلى تفكير اليوم. إنه سهل الاستخدام وينتج وصفًا واضحًا لما يحدث - وفي الوقت نفسه ، يعمل بشكل كافٍ لبناء عمليات تكامل معقدة.
كتابة طريق نموذج
لنبدأ في كتابة الكود. سنبدأ من تدفق البيانات المتزامن الذي يوجه الرسائل من مصدر واحد إلى قائمة المستلمين. ستتم كتابة قواعد التوجيه في Java DSL.
سنستخدم Maven لبناء المشروع. قم أولاً بإضافة التبعية التالية إلى ملف pom.xml
:
<dependencies> ... <dependency> <groupId>org.apache.camel</groupId> <artifactId>camel-core</artifactId> <version>2.20.0</version> </dependency> </dependencies>
بدلاً من ذلك ، يمكن إنشاء التطبيق أعلى camel-archetype-java
.
يتم التصريح عن تعريفات مسار الجمال في طريقة RouteBuilder.configure
.
public void configure() { errorHandler(defaultErrorHandler().maximumRedeliveries(0)); from("file:orders?noop=true").routeId("main") .log("Incoming File: ${file:onlyname}") .unmarshal().json(JsonLibrary.Jackson, Order.class) // unmarshal JSON to Order class containing List<OrderItem> .split().simple("body.items") // split list to process one by one .to("log:inputOrderItem") .choice() .when().simple("${body.type} == 'Drink'") .to("direct:bar") .when().simple("${body.type} == 'Dessert'") .to("direct:dessertStation") .when().simple("${body.type} == 'Hot Meal'") .to("direct:hotMealStation") .when().simple("${body.type} == 'Cold Meal'") .to("direct:coldMealStation") .otherwise() .to("direct:others"); from("direct:bar").routeId("bar").log("Handling Drink"); from("direct:dessertStation").routeId("dessertStation").log("Handling Dessert"); from("direct:hotMealStation").routeId("hotMealStation").log("Handling Hot Meal"); from("direct:coldMealStation").routeId("coldMealStation").log("Handling Cold Meal"); from("direct:others").routeId("others").log("Handling Something Other"); }
في هذا التعريف ، نقوم بإنشاء مسار يجلب السجلات من ملف JSON ، ويقسمها إلى عناصر ، ويوجه إلى مجموعة من المعالجات بناءً على محتوى الرسالة.
لنقم بتشغيله على بيانات الاختبار المعدة. سنحصل على الإخراج:
INFO | Total 6 routes, of which 6 are started INFO | Apache Camel 2.20.0 (CamelContext: camel-1) started in 10.716 seconds INFO | Incoming File: order1.json INFO | Exchange[ExchangePattern: InOnly, BodyType: com.antongoncharov.camel.example.model.OrderItem, Body: OrderItem{, type='Drink', name='Americano', qty='1'}] INFO | Handling Drink INFO | Exchange[ExchangePattern: InOnly, BodyType: com.antongoncharov.camel.example.model.OrderItem, Body: OrderItem{, type='Hot Meal', name='French Omelette', qty='1'}] INFO | Handling Hot Meal INFO | Exchange[ExchangePattern: InOnly, BodyType: com.antongoncharov.camel.example.model.OrderItem, Body: OrderItem{, type='Hot Meal', name='Lasagna', qty='1'}] INFO | Handling Hot Meal INFO | Exchange[ExchangePattern: InOnly, BodyType: com.antongoncharov.camel.example.model.OrderItem, Body: OrderItem{, type='Hot Meal', name='Rice Balls', qty='1'}] INFO | Handling Hot Meal INFO | Exchange[ExchangePattern: InOnly, BodyType: com.antongoncharov.camel.example.model.OrderItem, Body: OrderItem{, type='Dessert', name='Blueberry Pie', qty='1'}] INFO | Handling Dessert
كما هو متوقع ، قام Camel بتوجيه الرسائل إلى الوجهات.
خيارات نقل البيانات
في المثال أعلاه ، يكون التفاعل بين المكونات متزامنًا ويتم إجراؤه من خلال ذاكرة التطبيق. ومع ذلك ، هناك العديد من الطرق للتواصل عندما نتعامل مع تطبيقات منفصلة لا تشترك في الذاكرة:
- تبادل الملفات. ينتج أحد التطبيقات ملفات من البيانات المشتركة ليستخدمها الآخر. إنه المكان الذي تعيش فيه روح المدرسة القديمة. طريقة الاتصال هذه لها عدد كبير من النتائج: نقص المعاملات والاتساق ، ضعف الأداء ، والتنسيق المنعزل بين الأنظمة. انتهى الأمر بالعديد من المطورين إلى كتابة حلول تكامل محلية الصنع لجعل العملية أكثر أو أقل قابلية للإدارة.
- قاعدة بيانات مشتركة. اجعل التطبيقات تخزن البيانات التي ترغب في مشاركتها في مخطط مشترك لقاعدة بيانات واحدة. يعد تصميم مخطط موحد والتعامل مع الوصول المتزامن للجداول من أبرز تحديات هذا النهج. كما هو الحال مع تبادل الملفات ، من السهل أن يصبح هذا عنق الزجاجة دائمًا.
- استدعاء API البعيد. قم بتوفير واجهة للسماح لتطبيق ما بالتفاعل مع تطبيق آخر قيد التشغيل ، مثل استدعاء الطريقة النموذجية. تشترك التطبيقات في الوظائف عبر استدعاءات API ، لكنها تربطها بإحكام في هذه العملية.
- المراسلة. اجعل كل تطبيق يتصل بنظام مراسلة مشترك ، وتبادل البيانات واستدعاء السلوك بشكل غير متزامن باستخدام الرسائل. لا يجب أن يكون المرسل أو المستلم جاهزًا للعمل في نفس الوقت لتسليم الرسالة.
هناك المزيد من الطرق للتفاعل ، ولكن يجب أن نضع في اعتبارنا أنه بشكل عام ، هناك نوعان من التفاعل: متزامن وغير متزامن. الأول يشبه استدعاء دالة في التعليمات البرمجية الخاصة بك - سينتظر تدفق التنفيذ حتى يتم تنفيذه وإرجاع قيمة. باستخدام نهج غير متزامن ، يتم إرسال نفس البيانات عبر قائمة انتظار رسائل وسيطة أو موضوع اشتراك. يمكن تنفيذ استدعاء دالة عن بعد غير متزامن باعتباره EIP للطلب والرد.
ومع ذلك ، فإن الرسائل غير المتزامنة ليست حلاً سحريًا ؛ أنها تنطوي على قيود معينة. نادرًا ما ترى واجهات برمجة تطبيقات المراسلة على الويب ؛ تعد خدمات REST المتزامنة أكثر شيوعًا. ولكن يتم استخدام البرامج الوسيطة للمراسلة على نطاق واسع في شبكة إنترانت المؤسسة أو البنية التحتية الخلفية للنظام الموزع.
استخدام قوائم انتظار الرسائل
لنجعل مثالنا غير متزامن. يُطلق على نظام البرنامج الذي يدير قوائم الانتظار وموضوعات الاشتراك اسم وسيط الرسائل. إنه مثل RDBMS للجداول والأعمدة. تعمل قوائم الانتظار كتكامل من نقطة إلى نقطة بينما تكون الموضوعات مخصصة للاتصال بالنشر والاشتراك مع العديد من المستلمين. سنستخدم Apache ActiveMQ كوسيط رسائل JMS لأنه متين وقابل للتضمين.
أضف التبعية التالية. أحيانًا يكون من المفرط إضافة activemq-all
، الذي يحتوي على جميع برامج ActiveMQ ، إلى المشروع ، لكننا سنبقي تبعيات تطبيقنا غير معقدة.
<dependency> <groupId>org.apache.activemq</groupId> <artifactId>activemq-all</artifactId> <version>5.15.2</version> </dependency>
ثم ابدأ الوسيط برمجيًا. في Spring Boot ، نحصل على تكوين تلقائي لهذا عن طريق توصيل تبعية spring-boot-starter-activemq
مافن.
قم بتشغيل وسيط رسائل جديد باستخدام الأوامر التالية ، مع تحديد نقطة نهاية الموصل فقط:
BrokerService broker = new BrokerService(); broker.addConnector("tcp://localhost:61616"); broker.start();
وأضف مقتطف التكوين التالي إلى نص طريقة configure
:
ConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://localhost:61616"); this.getContext().addComponent("activemq", ActiveMQComponent.jmsComponent(connectionFactory));
الآن يمكننا تحديث المثال السابق باستخدام قوائم انتظار الرسائل. سيتم إنشاء قوائم الانتظار تلقائيًا عند تسليم الرسالة.
public void configure() { errorHandler(defaultErrorHandler().maximumRedeliveries(0)); ConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://localhost:61616"); this.getContext().addComponent("activemq", ActiveMQComponent.jmsComponent(connectionFactory)); from("file:orders?noop=true").routeId("main") .log("Incoming File: ${file:onlyname}") .unmarshal().json(JsonLibrary.Jackson, Order.class) // unmarshal JSON to Order class containing List<OrderItem> .split().simple("body.items") // split list to process one by one .to("log:inputOrderItem") .choice() .when().simple("${body.type} == 'Drink'") .to("activemq:queue:bar") .when().simple("${body.type} == 'Dessert'") .to("activemq:queue:dessertStation") .when().simple("${body.type} == 'Hot Meal'") .to("activemq:queue:hotMealStation") .when().simple("${body.type} == 'Cold Meal'") .to("activemq:queue:coldMealStation") .otherwise() .to("activemq:queue:others"); from("activemq:queue:bar").routeId("barAsync").log("Drinks"); from("activemq:queue:dessertStation").routeId("dessertAsync").log("Dessert"); from("activemq:queue:hotMealStation").routeId("hotMealAsync").log("Hot Meals"); from("activemq:queue:coldMealStation").routeId("coldMealAsync").log("Cold Meals"); from("activemq:queue:others").routeId("othersAsync").log("Others"); }
حسنًا ، الآن أصبح التفاعل غير متزامن. يمكن للمستهلكين المحتملين لهذه البيانات الوصول إليها عندما يكونون مستعدين لذلك. هذا مثال على الاقتران السائب ، والذي نحاول تحقيقه في بنية تفاعلية. لن يؤدي عدم توفر إحدى الخدمات إلى منع الخدمات الأخرى. علاوة على ذلك ، يمكن للمستهلك القياس والقراءة من قائمة الانتظار بالتوازي. قد يتم تحجيم قائمة الانتظار نفسها وتقسيمها. يمكن لقوائم الانتظار المستمرة تخزين البيانات على القرص ، في انتظار معالجتها ، حتى عندما ينخفض جميع المشاركين. وبالتالي ، فإن هذا النظام أكثر تسامحًا مع الأخطاء.

حقيقة مذهلة هي أن CERN تستخدم Apache Camel و ActiveMQ لمراقبة أنظمة مصادم الهادرونات الكبير (LHC). هناك أيضًا أطروحة ماجستير مثيرة للاهتمام تشرح اختيار حل وسيط مناسب لهذه المهمة. لذلك ، كما يقولون في الكلمة الرئيسية ، "لا JMS - لا فيزياء الجسيمات!"
يراقب
في المثال السابق ، أنشأنا قناة البيانات بين خدمتين. إنها نقطة فشل محتملة إضافية في العمارة ، لذلك علينا الاهتمام بها. دعنا نلقي نظرة على ميزات المراقبة التي يوفرها Apache Camel. في الأساس ، يعرض معلومات إحصائية حول طرقه عبر MBeans ، والتي يمكن الوصول إليها بواسطة JMX. يعرض ActiveMQ إحصائيات قائمة الانتظار بنفس الطريقة.
لنقم بتشغيل خادم JMX في التطبيق ، لتمكينه من العمل بخيارات سطر الأوامر:
-Dorg.apache.camel.jmx.createRmiConnector=true -Dorg.apache.camel.jmx.mbeanObjectDomainName=org.apache.camel -Dorg.apache.camel.jmx.rmiConnector.registryPort=1099 -Dorg.apache.camel.jmx.serviceUrlPath=camel
قم الآن بتشغيل التطبيق بحيث أن الطريق قد قام بعمله. افتح أداة jconsole
القياسية واتصل بعملية التطبيق. اتصل service:jmx:rmi:///jndi/rmi://localhost:1099/camel
. انتقل إلى المجال org.apache.camel في شجرة MBeans.
يمكننا أن نرى أن كل شيء عن التوجيه تحت السيطرة. لدينا عدد الرسائل أثناء الرحلة ، وعدد الأخطاء ، وعدد الرسائل في قوائم الانتظار. يمكن ربط هذه المعلومات ببعض أدوات المراقبة ذات الوظائف الغنية مثل Graphana أو Kibana. يمكنك القيام بذلك عن طريق تنفيذ مكدس ELK المعروف.
هناك أيضًا وحدة تحكم ويب قابلة للتوصيل وقابلة للتمديد توفر واجهة مستخدم لإدارة Camel و ActiveMQ وغير ذلك الكثير ، تسمى hawt.io.
طرق الاختبار
يتمتع Apache Camel بوظائف واسعة جدًا لكتابة طرق الاختبار بمكونات وهمية. إنها أداة قوية ، لكن كتابة مسارات منفصلة للاختبار فقط هي عملية تستغرق وقتًا طويلاً. سيكون من الأكثر كفاءة تشغيل الاختبارات على طرق الإنتاج دون تعديل خط الأنابيب الخاص بهم. تمتلك Camel هذه الميزة ويمكن تنفيذها باستخدام مكون AdviceWith.
لنقم بتمكين منطق الاختبار في مثالنا ونجري اختبارًا نموذجيًا.
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <dependency> <groupId>org.apache.camel</groupId> <artifactId>camel-test</artifactId> <version>2.20.0</version> <scope>test</scope> </dependency>
فئة الاختبار هي:
public class AsyncRouteTest extends CamelTestSupport { @Override protected RouteBuilder createRouteBuilder() throws Exception { return new AsyncRouteBuilder(); } @Before public void mockEndpoints() throws Exception { context.getRouteDefinition("main").adviceWith(context, new AdviceWithRouteBuilder() { @Override public void configure() throws Exception { // we substitute all actual queues with mock endpoints mockEndpointsAndSkip("activemq:queue:bar"); mockEndpointsAndSkip("activemq:queue:dessertStation"); mockEndpointsAndSkip("activemq:queue:hotMealStation"); mockEndpointsAndSkip("activemq:queue:coldMealStation"); mockEndpointsAndSkip("activemq:queue:others"); // and replace the route's source with test endpoint replaceFromWith("file://testInbox"); } }); } @Test public void testSyncInteraction() throws InterruptedException { String testJson = "{\"id\": 1, \"order\": [{\"id\": 1, \"name\": \"Americano\", \"type\": \"Drink\", \"qty\": \"1\"}, {\"id\": 2, \"name\": \"French Omelette\", \"type\": \"Hot Meal\", \"qty\": \"1\"}, {\"id\": 3, \"name\": \"Lasagna\", \"type\": \"Hot Meal\", \"qty\": \"1\"}, {\"id\": 4, \"name\": \"Rice Balls\", \"type\": \"Hot Meal\", \"qty\": \"1\"}, {\"id\": 5, \"name\": \"Blueberry Pie\", \"type\": \"Dessert\", \"qty\": \"1\"}]}"; // get mocked endpoint and set an expectation MockEndpoint mockEndpoint = getMockEndpoint("mock:activemq:queue:hotMealStation"); mockEndpoint.expectedMessageCount(3); // simulate putting file in the inbox folder template.sendBodyAndHeader("file://testInbox", testJson, Exchange.FILE_NAME, "test.json"); //checks that expectations were met assertMockEndpointsSatisfied(); } }
قم الآن بإجراء اختبارات للتطبيق باستخدام mvn test
. يمكننا أن نرى أن طريقنا قد تم تنفيذه بنجاح من خلال نصيحة الاختبار. لا توجد رسائل مرت عبر قوائم الانتظار الفعلية وقد تم اجتياز الاختبارات.
INFO | Route: main started and consuming from: file://testInbox <...> INFO | Incoming File: test.json <...> INFO | Asserting: mock://activemq:queue:hotMealStation is satisfied
استخدام Apache Camel مع Kubernetes Cluster
إحدى مشكلات التكامل اليوم هي أن التطبيقات لم تعد ثابتة. في البنية التحتية السحابية ، نتعامل مع الخدمات الافتراضية التي تعمل على عقد متعددة في نفس الوقت. إنه يمكّن بنية الخدمات المصغرة بشبكة من الخدمات الصغيرة وخفيفة الوزن التي تتفاعل فيما بينها. تتمتع هذه الخدمات بعمر غير موثوق به ، وعلينا اكتشافها ديناميكيًا.
يعد لصق الخدمات السحابية معًا مهمة يمكن حلها باستخدام Apache Camel. إنه مثير للاهتمام بشكل خاص بسبب نكهة EIP وحقيقة أن Camel لديه الكثير من المحولات ويدعم مجموعة واسعة من البروتوكولات. يضيف الإصدار الأخير 2.18 مكون ServiceCall ، والذي يقدم ميزة استدعاء API وحل عنوانها عبر آليات اكتشاف الكتلة. حاليًا ، يدعم Consul و Kubernetes و Ribbon وما إلى ذلك. يمكن العثور بسهولة على بعض أمثلة التعليمات البرمجية ، حيث يتم تكوين ServiceCall مع Consul. سنستخدم Kubernetes هنا لأنه حل المجموعات المفضل لدي.
سيكون مخطط التكامل على النحو التالي:
ستكون خدمة Order
وخدمة Inventory
عبارة عن تطبيقين تافهين من Spring Boot لإرجاع بيانات ثابتة. نحن لسنا مقيدين بمكدس تقني معين هنا. تنتج هذه الخدمات البيانات التي نريد معالجتها.
وحدة تحكم خدمة الطلب:
@RestController public class OrderController { private final OrderStorage orderStorage; @Autowired public OrderController(OrderStorage orderStorage) { this.orderStorage = orderStorage; } @RequestMapping("/info") public String info() { return "Order Service UU/orders") public List<Order> getAll() { return orderStorage.getAll(); } @RequestMapping("/orders/{id}") public Order getOne(@PathVariable Integer id) { return orderStorage.getOne(id); } }
ينتج بيانات بالتنسيق:
[{"id":1,"items":[2,3,4]},{"id":2,"items":[5,3]}]
تشبه وحدة التحكم في خدمة Inventory
تمامًا خدمة Order
:
@RestController public class InventoryController { private final InventoryStorage inventoryStorage; @Autowired public InventoryController(InventoryStorage inventoryStorage) { this.inventoryStorage = inventoryStorage; } @RequestMapping("/info") public String info() { return "Inventory Service UU/items") public List<InventoryItem> getAll() { return inventoryStorage.getAll(); } @RequestMapping("/items/{id}") public InventoryItem getOne(@PathVariable Integer id) { return inventoryStorage.getOne(id); } }
InventoryStorage
هو مستودع عام يحتفظ بالبيانات. في هذا المثال ، تقوم بإرجاع كائنات ثابتة محددة مسبقًا ، والتي يتم تنظيمها بالتنسيق التالي.
[{"id":1,"name":"Laptop","description":"Up to 12-hours battery life","price":499.9},{"id":2,"name":"Monitor","description":"27-inch, response time: 7ms","price":200.0},{"id":3,"name":"Headphones","description":"Soft leather ear-cups","price":29.9},{"id":4,"name":"Mouse","description":"Designed for comfort and portability","price":19.0},{"id":5,"name":"Keyboard","description":"Layout: US","price":10.5}]
دعنا نكتب مسار بوابة يربط بينهما ، ولكن بدون ServiceCall في هذه الخطوة:
rest("/orders") .get("/").description("Get all orders with details").outType(TestResponse.class) .route() .setHeader("Content-Type", constant("application/json")) .setHeader("Accept", constant("application/json")) .setHeader(Exchange.HTTP_METHOD, constant("GET")) .removeHeaders("CamelHttp*") .to("http4://localhost:8082/orders?bridgeEndpoint=true") .unmarshal(formatOrder) .enrich("direct:enrichFromInventory", new OrderAggregationStrategy()) .to("log:result") .endRest(); from("direct:enrichFromInventory") .transform().simple("${null}") .setHeader("Content-Type", constant("application/json")) .setHeader("Accept", constant("application/json")) .setHeader(Exchange.HTTP_METHOD, constant("GET")) .removeHeaders("CamelHttp*") .to("http4://localhost:8081/items?bridgeEndpoint=true") .unmarshal(formatInventory);
تخيل الآن أن كل خدمة لم تعد مثيلًا محددًا ولكنها عبارة عن سحابة من الحالات التي تعمل كمثال واحد. سنستخدم Minikube لتجربة مجموعة Kubernetes محليًا.
قم بتكوين مسارات الشبكة لرؤية عُقد Kubernetes محليًا (المثال المحدد لبيئة Mac / Linux):
# remove existing routes sudo route -n delete 10/24 > /dev/null 2>&1 # add routes sudo route -n add 10.0.0.0/24 $(minikube ip) # 172.17.0.0/16 ip range is used by docker in minikube sudo route -n add 172.17.0.0/16 $(minikube ip) ifconfig 'bridge100' | grep member | awk '{print $2}' # use interface name from the output of the previous command # needed for xhyve driver, which I'm using for testing sudo ifconfig bridge100 -hostfilter en5
قم بلف الخدمات في حاويات Docker بتكوين Dockerfile مثل هذا:
FROM openjdk:8-jdk-alpine VOLUME /tmp ADD target/order-srv-1.0-SNAPSHOT.jar app.jar ADD target/lib lib ENV JAVA_OPTS="" ENTRYPOINT exec java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /app.jar
أنشئ صور الخدمة وادفعها إلى سجل Docker. الآن قم بتشغيل العقد في مجموعة Kubernetes المحلية.
تكوين نشر Kubernetes.yaml:
apiVersion: extensions/v1beta1 kind: Deployment metadata: name: inventory spec: replicas: 3 selector: matchLabels: app: inventory template: metadata: labels: app: inventory spec: containers: - name: inventory image: inventory-srv:latest imagePullPolicy: Never ports: - containerPort: 8081
فضح عمليات النشر هذه كخدمات في المجموعة:
kubectl expose deployment order-srv --type=NodePort kubectl expose deployment inventory-srv --type=NodePort
يمكننا الآن التحقق مما إذا كانت الطلبات يتم تقديمها من خلال عقد تم اختيارها عشوائيًا من المجموعة. قم بتشغيل curl -X http://192.168.99.100:30517/info
بالتتابع عدة مرات للوصول إلى minikube NodePort للخدمة المكشوفة (باستخدام المضيف والمنفذ). في الإخراج ، نرى أننا حققنا موازنة الطلب.
Inventory Service UUID = 22f8ca6b-f56b-4984-927b-cbf9fcf81da5 Inventory Service UUID = b7a4d326-1e76-4051-a0a6-1016394fafda Inventory Service UUID = b7a4d326-1e76-4051-a0a6-1016394fafda Inventory Service UUID = 22f8ca6b-f56b-4984-927b-cbf9fcf81da5 Inventory Service UUID = 50323ddb-3ace-4424-820a-6b4e85775af4
أضف camel-kubernetes
و camel-netty4-http
إلى ملف pom.xml
الخاص بالمشروع. ثم قم بتكوين مكون ServiceCall لاستخدام اكتشاف العقدة الرئيسية Kubernetes المشتركة لجميع مكالمات الخدمة بين تعريفات المسار:
KubernetesConfiguration kubernetesConfiguration = new KubernetesConfiguration(); kubernetesConfiguration.setMasterUrl("https://192.168.64.2:8443"); kubernetesConfiguration.setClientCertFile("/Users/antongoncharov/.minikube/client.crt"); kubernetesConfiguration.setClientKeyFile("/Users/antongoncharov/.minikube/client.key"); kubernetesConfiguration.setNamespace("default”); ServiceCallConfigurationDefinition config = new ServiceCallConfigurationDefinition(); config.setServiceDiscovery(new KubernetesClientServiceDiscovery(kubernetesConfiguration)); context.setServiceCallConfiguration(config);
يكمل برنامج ServiceCall EIP نظام Spring Boot جيدًا. يمكن تكوين معظم الخيارات مباشرة في ملف application.properties
.
قم بتمكين مسار الجمل باستخدام مكون ServiceCall:
rest("/orders") .get("/").description("Get all orders with details").outType(TestResponse.class) .route() .hystrix() .setHeader("Content-Type", constant("application/json")) .setHeader("Accept", constant("application/json")) .setHeader(Exchange.HTTP_METHOD, constant("GET")) .removeHeaders("CamelHttp*") .serviceCall("customer-srv","http4:customer-deployment?bridgeEndpoint=true") .unmarshal(formatOrder) .enrich("direct:enrichFromInventory", new OrderAggregationStrategy()) .to("log:result") .endRest(); from("direct:enrichFromInventory") .transform().simple("${null}") .setHeader("Content-Type", constant("application/json")) .setHeader("Accept", constant("application/json")) .setHeader(Exchange.HTTP_METHOD, constant("GET")) .removeHeaders("CamelHttp*") .serviceCall("order-srv","http4:order-srv?bridgeEndpoint=true") .unmarshal(formatInventory);
قمنا أيضًا بتنشيط Circuit Breaker في الطريق. إنه خطاف تكامل يسمح بإيقاف مكالمات النظام عن بُعد مؤقتًا في حالة حدوث أخطاء في التسليم أو عدم توفر المستلم. تم تصميم هذا لتجنب فشل نظام التعاقب. يساعد مكون Hystrix في تحقيق ذلك من خلال تنفيذ نمط قاطع الدائرة.
لنقم بتشغيله ونرسل طلب اختبار ؛ سنحصل على الاستجابة مجمعة من كلتا الخدمتين.
[{"id":1,"items":[{"id":2,"name":"Monitor","description":"27-inch, response time: 7ms","price":200.0},{"id":3,"name":"Headphones","description":"Soft leather ear-cups","price":29.9},{"id":4,"name":"Mouse","description":"Designed for comfort and portability","price":19.0}]},{"id":2,"items":[{"id":5,"name":"Keyboard","description":"Layout: US","price":10.5},{"id":3,"name":"Headphones","description":"Soft leather ear-cups","price":29.9}]}]
كانت النتيجة كما هو متوقع.
حالات استخدام أخرى
لقد أوضحت كيف يمكن لـ Apache Camel دمج الخدمات المصغرة في مجموعة. ما هي الاستخدامات الأخرى لهذا الإطار؟ بشكل عام ، يكون مفيدًا في أي مكان قد يكون فيه التوجيه المستند إلى القواعد حلاً. For instance, Apache Camel can be a middleware for the Internet of Things with the Eclipse Kura adapter. It can handle monitoring by ferrying log signals from various components and services, like in the CERN system. It can also be an integration framework for enterprise SOA or be a pipeline for batch data processing, although it doesn't compete well with Apache Spark in this area.
خاتمة
You can see that systems integration isn't an easy process. We're lucky because a lot of experience has been gathered. It's important to apply it correctly to build flexible and fault-tolerant solutions.
To ensure correct application, I recommend having a checklist of important integration aspects. Must-have items include:
- Is there a separate integration layer?
- Are there tests for integration?
- Do we know the expected peak data intensity?
- Do we know the expected data delivery time?
- Does message correlation matter? What if a sequence breaks?
- Should we do it in a synchronous or asynchronous way?
- Where do formats and routing rules change more frequently?
- Do we have ways to monitor the process?
In this article, we tried Apache Camel, a lightweight integration framework, which helps save time and effort when solving integration problems. As we showed, it can serve as a tool, supporting the relevant microservice architecture by taking full responsibility for data exchange between microservices.
If you're interested in learning more about Apache Camel, I highly recommend the book “Camel in Action” by the framework's creator, Claus Ibsen. Official documentation is available at camel.apache.org.