ترجمة وتكييف خدمات Java Microservices: دليل عملي . الأجزاء السابقة من الدليل:
دعونا نلقي نظرة على المشاكل المتأصلة في الخدمات الصغيرة في Java، بدءًا من الأشياء المجردة وانتهاءً بالمكتبات الملموسة.
كيفية جعل خدمة Java الصغيرة مرنة؟
تذكر أنه عندما تقوم بإنشاء خدمات صغيرة، فإنك تقوم بشكل أساسي بتداول استدعاءات طريقة JVM لمكالمات HTTP المتزامنة أو الرسائل غير المتزامنة. على الرغم من ضمان اكتمال استدعاء الأسلوب في الغالب (باستثناء إيقاف تشغيل JVM غير المتوقع)، فإن استدعاء الشبكة غير موثوق به بشكل افتراضي. قد ينجح الأمر، لكنه قد لا يعمل لأسباب مختلفة: زيادة التحميل على الشبكة، وتنفيذ قاعدة جديدة لجدار الحماية، وما إلى ذلك. لمعرفة كيف يُحدث هذا فرقًا، دعنا نلقي نظرة على مثال BillingService.أنماط مرونة HTTP/REST
لنفترض أن العملاء يمكنهم شراء كتب إلكترونية على موقع شركتك على الويب. للقيام بذلك، قمت للتو بتنفيذ خدمة صغيرة للفواتير يمكنها الاتصال بمتجرك عبر الإنترنت لإنشاء فواتير PDF فعلية. في الوقت الحالي، سنقوم بإجراء هذا الاستدعاء بشكل متزامن، عبر HTTP (على الرغم من أنه من المنطقي استدعاء هذه الخدمة بشكل غير متزامن، نظرًا لأن إنشاء PDF لا يجب أن يكون فوريًا من منظور المستخدم. سنستخدم نفس المثال في المثال التالي) القسم وانظر إلى الاختلافات).@Service
class BillingService {
@Autowired
private HttpClient client;
public void bill(User user, Plan plan) {
Invoice invoice = createInvoice(user, plan);
httpClient.send(invoiceRequest(user.getEmail(), invoice), responseHandler());
// ...
}
}
لتلخيص ذلك، إليك ثلاث نتائج محتملة لاستدعاء HTTP هذا.
- حسنًا: تمت المكالمة وتم إنشاء الحساب بنجاح.
- تأخير: تمت المكالمة، ولكن استغرق إكمالها وقتًا طويلاً.
- خطأ. فشلت المكالمة، ربما تكون قد أرسلت طلبًا غير متوافق، أو ربما لا يعمل النظام.
أنماط مرونة المراسلة
دعونا نلقي نظرة فاحصة على الاتصالات غير المتزامنة. قد يبدو برنامج BillingService الآن بهذا الشكل، بافتراض أننا نستخدم Spring وRabbitMQ للمراسلة. لإنشاء حساب، نقوم الآن بإرسال رسالة إلى وسيط الرسائل الخاص بنا RabbitMQ، حيث يوجد العديد من العاملين في انتظار رسائل جديدة. يقوم هؤلاء العمال بإنشاء فواتير PDF وإرسالها إلى المستخدمين المناسبين.@Service
class BillingService {
@Autowired
private RabbitTemplate rabbitTemplate;
public void bill(User user, Plan plan) {
Invoice invoice = createInvoice(user, plan);
// преобразует счет, например, в json и использует его How тело messages
rabbitTemplate.convertAndSend(exchange, routingkey, invoice);
// ...
}
}
تبدو الأخطاء المحتملة مختلفة بعض الشيء الآن، حيث لم تعد تتلقى استجابات "موافق" أو "خطأ" فورية كما فعلت مع اتصال HTTP المتزامن. وبدلاً من ذلك، قد يكون لدينا ثلاثة سيناريوهات محتملة قد تسوء، مما قد يثير الأسئلة التالية:
- هل تم تسليم رسالتي واستخدامها من قبل الموظف؟ أم أنها فقدت؟ (لا يتلقى المستخدم فاتورة).
- هل تم تسليم رسالتي مرة واحدة فقط؟ أم يتم تسليمها أكثر من مرة ومعالجتها مرة واحدة فقط؟ (سيتلقى المستخدم فواتير متعددة).
- التكوين: من "هل استخدمت مفاتيح/أسماء التوجيه الصحيحة للتبادل" إلى "هل تم تكوين وسيط الرسائل الخاص بي وصيانته بشكل صحيح أم أن قوائم الانتظار الخاصة به ممتلئة؟" (لا يتلقى المستخدم فاتورة).
- إذا كنت تستخدم تطبيقات JMS مثل ActiveMQ، فيمكنك استبدال السرعة بضمان الالتزامات على مرحلتين (XA).
- إذا كنت تستخدم RabbitMQ، فاقرأ هذا الدليل أولاً، ثم فكر جيدًا في التأكيدات والتسامح مع الأخطاء وموثوقية الرسالة بشكل عام.
- ربما يكون شخص ما على دراية جيدة بتكوين خوادم Active أو RabbitMQ، خاصة بالاشتراك مع المجموعات وDocker (أي شخص؟ ;))
ما هو الإطار الذي سيكون الحل الأفضل لخدمات Java الصغيرة؟
من ناحية، يمكنك تثبيت خيار شائع جدًا مثل Spring Boot . إنه يجعل من السهل جدًا إنشاء ملفات .jar، ويأتي مزودًا بخادم ويب مدمج مثل Tomcat أو Jetty، ويمكن تشغيله بسرعة وفي أي مكان. مثالية لبناء تطبيقات الخدمات الصغيرة. في الآونة الأخيرة، ظهر اثنان من أطر الخدمات الصغيرة المتخصصة، Kubernetes أو GraalVM ، مستوحاة جزئيًا من البرمجة التفاعلية. فيما يلي بعض المتنافسين الأكثر إثارة للاهتمام: Quarkus و Micronaut و Vert.x و Helidon . في النهاية، سيتعين عليك الاختيار بنفسك، ولكن يمكننا أن نقدم لك بعض التوصيات التي قد لا تكون قياسية تمامًا: باستثناء Spring Boot، يتم عادةً تسويق جميع أطر عمل الخدمات الصغيرة على أنها سريعة بشكل لا يصدق، مع بدء تشغيل فوري تقريبًا ، انخفاض استخدام الذاكرة، وقابلية التوسع إلى ما لا نهاية. تتميز المواد التسويقية عادةً برسومات رائعة تُظهر النظام الأساسي بجوار Spring Boot العملاق أو مع بعضها البعض. وهذا، من الناحية النظرية، يريح أعصاب المطورين الذين يدعمون المشاريع القديمة، والتي يستغرق تحميلها أحيانًا عدة دقائق. أو المطورين الذين يعملون في السحابة والذين يرغبون في بدء/إيقاف أي عدد من الحاويات الصغيرة التي يحتاجونها حاليًا في غضون 50 مللي ثانية. ومع ذلك، تكمن المشكلة في أن أوقات بدء التشغيل وأوقات إعادة التوزيع (المصطنعة) هذه لا تساهم في النجاح الشامل للمشروع. على الأقل تأثيرهم أقل بكثير من البنية التحتية القوية لإطار العمل، والتوثيق القوي، والمجتمع، ومهارات المطورين القوية. لذا فمن الأفضل أن ننظر إلى الأمر بهذه الطريقة: إذا كان الأمر كذلك حتى الآن:- يمكنك السماح لـ ORMs الخاصة بك بالعمل على نطاق واسع، مما يؤدي إلى إنشاء مئات الاستعلامات لسير العمل البسيط.
- أنت بحاجة إلى عدد لا نهائي من الجيجابايت لتشغيل وحدة متراصة معقدة إلى حد ما.
- لديك الكثير من التعليمات البرمجية والتعقيد مرتفع جدًا (نحن لا نتحدث عن عمليات التشغيل التي يحتمل أن تكون بطيئة مثل Hibernate الآن) بحيث يستغرق تحميل التطبيق الخاص بك عدة دقائق.
ما المكتبات الأفضل لاستدعاءات Java REST المتزامنة؟
على الجانب الفني منخفض المستوى، من المحتمل أن ينتهي بك الأمر بإحدى مكتبات عملاء HTTP التالية: HttpClient الأصلي لـ Java (منذ Java 11)، أو HttpClient الخاص بـ Apache ، أو OkHttp . لاحظ أنني أقول "ربما" هنا لأن هناك خيارات أخرى، بدءًا من عملاء JAX-RS القدامى الجيدين إلى عملاء WebSocket الحديثين . على أية حال، الاتجاه هو إنشاء عميل HTTP، والابتعاد عن العبث باستدعاءات HTTP بنفسك. للقيام بذلك، تحتاج إلى إلقاء نظرة على مشروع OpenFeign ووثائقه كنقطة انطلاق لمزيد من القراءة.ما هي أفضل الوسطاء لمراسلة جافا غير المتزامنة؟
على الأرجح، ستصادف ActiveMQ (Classic أو Artemis) الشهير أو RabbitMQ أو Kafka .- ActiveMQ وRabbitMQ هما وسطاء رسائل تقليديون متكاملون. أنها تنطوي على تفاعل "الوسيط الذكي" و "المستخدمين الأغبياء".
- تاريخيًا، يتمتع ActiveMQ بميزة التضمين السهل (للاختبار)، والذي يمكن تخفيفه باستخدام إعدادات RabbitMQ/Docker/TestContainer.
- لا يمكن أن يُطلق على كافكا اسم الوسيط "الذكي" التقليدي. وبدلاً من ذلك، فهو عبارة عن مخزن رسائل "غبي" نسبيًا (ملف سجل) يتطلب معالجة مستهلكين أذكياء.
ما المكتبات التي يمكنني استخدامها لاختبار الخدمات الصغيرة؟
ذلك يعتمد على المكدس الخاص بك. إذا كان لديك نظام Spring البيئي منشورًا، فسيكون من الحكمة استخدام الأدوات المحددة لإطار العمل . إذا كان JavaEE يشبه Arquillian . قد يكون من المفيد إلقاء نظرة على Docker ومكتبة Testcontainers الجيدة حقًا ، والتي تساعد بشكل خاص على إعداد قاعدة بيانات Oracle بسهولة وسرعة للتطوير المحلي أو اختبارات التكامل. لإجراء اختبار وهمي لخوادم HTTP بأكملها، راجع Wiremock . لاختبار المراسلة غير المتزامنة، حاول تنفيذ ActiveMQ أو RabbitMQ ثم كتابة الاختبارات باستخدام Awaitility DSL . بالإضافة إلى ذلك، يتم استخدام جميع أدواتك المعتادة - Junit و TestNG لـ AssertJ و Mockito . يرجى ملاحظة أن هذه ليست قائمة كاملة. إذا لم تجد أداتك المفضلة هنا، يرجى نشرها في قسم التعليقات.كيفية تمكين التسجيل لجميع خدمات Java الصغيرة؟
يعد تسجيل الدخول في حالة الخدمات الصغيرة موضوعًا مثيرًا للاهتمام ومعقدًا إلى حد ما. بدلاً من أن يكون لديك ملف سجل واحد يمكنك معالجته بأوامر less أو grep، لديك الآن ملفات سجل n، وتريد ألا تكون متناثرة للغاية. تم وصف ميزات النظام البيئي للتسجيل بشكل جيد في هذه المقالة (باللغة الإنجليزية). تأكد من قراءته وانتبه إلى القسم التسجيل المركزي من منظور الخدمات الصغيرة . من الناحية العملية، سوف تصادف طرقًا مختلفة: يقوم مسؤول النظام بكتابة نصوص برمجية معينة تجمع وتدمج ملفات السجل من خوادم مختلفة في ملف سجل واحد وتضعها على خوادم FTP للتنزيل. تشغيل مجموعات cat/grep/unig/sort في جلسات SSH المتوازية. هذا هو بالضبط ما تفعله Amazon AWS، ويمكنك إخبار مديرك بذلك. استخدم أداة مثل Graylog أو ELK Stack (Elasticsearch، Logstash، Kibana)كيف تجد خدماتي الصغيرة بعضها البعض؟
حتى الآن، افترضنا أن خدماتنا الصغيرة تعرف بعضها البعض وتعرف IPS المقابل. دعونا نتحدث عن التكوين الثابت. لذا، فإن وحدتنا المصرفية [ip = 192.168.200.1] تعرف أنها تحتاج إلى التحدث إلى خادم المخاطر [ip = 192.168.200.2]، والذي تم ترميزه ضمن ملف الخصائص. ومع ذلك، يمكنك جعل الأمور أكثر ديناميكية:- استخدم خادم تكوين قائم على السحابة حيث تسحب جميع الخدمات الصغيرة تكويناتها بدلاً من نشر ملفات application.properties على خدماتها الصغيرة.
- نظرًا لأن مثيلات الخدمة الخاصة بك يمكنها تغيير موقعها ديناميكيًا، فمن المفيد النظر إلى الخدمات التي تعرف مكان وجود خدماتك، وما هي عناوين IP الخاصة بها، وكيفية توجيهها.
- والآن بعد أن أصبح كل شيء ديناميكيا، تظهر مشاكل جديدة، مثل الانتخاب التلقائي للقائد: من هو السيد الذي يعمل على مهام معينة حتى لا يعالجها مرتين، على سبيل المثال؟ من يحل محل القائد عندما يفشل؟ وعلى أي أساس يتم الاستبدال؟
كيفية تنظيم الترخيص والمصادقة باستخدام خدمات Java المصغرة؟
هذا الموضوع يستحق أيضًا قصة منفصلة. مرة أخرى، تتراوح الخيارات من مصادقة HTTPS الأساسية المشفرة مع أطر عمل أمان مخصصة إلى تشغيل تثبيت Oauth2 باستخدام خادم التفويض الخاص به.كيف يمكنني التأكد من أن جميع البيئات الخاصة بي تبدو متشابهة؟
ما ينطبق على عمليات النشر بدون خدمة صغيرة ينطبق أيضًا على عمليات النشر التي تحتوي على خدمة صغيرة. جرب مزيجًا من Docker/Testcontainers وScripting/Ansible.لا يوجد سؤال: باختصار حول YAML
دعونا نبتعد عن المكتبات والقضايا ذات الصلة للحظة ونلقي نظرة سريعة على Yaml. يُستخدم تنسيق الملف هذا فعليًا كتنسيق "لكتابة التكوين كرمز". يتم استخدامه أيضًا بواسطة أدوات بسيطة مثل Ansible وعمالقة مثل Kubernetes. لتجربة ألم المسافة البادئة YAML، حاول كتابة ملف Ansible بسيط وانظر كم يتعين عليك تحرير الملف قبل أن يعمل كما هو متوقع. وهذا على الرغم من دعم التنسيق من قبل جميع بيئات التطوير المتكاملة الرئيسية! وبعد ذلك، عد لإنهاء قراءة هذا الدليل.Yaml:
- is:
- so
- great
ماذا عن المعاملات الموزعة؟ اختبار أداء؟ موضوعات أخرى؟
ربما يوما ما، في الإصدارات المستقبلية من الدليل. في الوقت الحالي، هذا كل شيء. ابقى معنا!القضايا المفاهيمية مع الخدمات المصغرة
إلى جانب المشاكل المحددة للخدمات الصغيرة في Java، هناك مشاكل أخرى، دعنا نقول، تلك التي تظهر في أي مشروع خدمة صغيرة. وهي تتعلق في الغالب بالتنظيم والفريق والإدارة.عدم تطابق الواجهة الأمامية والخلفية
يعد عدم تطابق الواجهة الأمامية والواجهة الخلفية مشكلة شائعة جدًا في العديد من مشاريع الخدمات الصغيرة. ماذا يعني ذلك؟ فقط في الوحدات القديمة الجيدة، كان لدى مطوري واجهة الويب مصدر واحد محدد للحصول على البيانات. في مشاريع الخدمات الصغيرة، أصبح لدى مطوري الواجهة الأمامية فجأة عدد n من المصادر للحصول على البيانات منها. تخيل أنك تقوم بإنشاء نوع من مشروع الخدمات الصغيرة لإنترنت الأشياء (إنترنت الأشياء) في جافا. لنفترض أنك تدير الآلات الجيوديسية والأفران الصناعية في جميع أنحاء أوروبا. وترسل لك هذه الأفران تحديثات منتظمة بدرجات حرارتها وما شابه. عاجلاً أم آجلاً، قد ترغب في العثور على أفران في واجهة المستخدم الإدارية، وربما باستخدام خدمات "البحث عن الأفران" الصغيرة. اعتمادًا على مدى صرامة تطبيق نظرائك في الواجهة الخلفية للتصميم المستند إلى المجال أو قوانين الخدمات الصغيرة، قد تقوم الخدمة الصغيرة "البحث عن فرن" بإرجاع معرفات الفرن فقط وليس بيانات أخرى مثل النوع أو الطراز أو الموقع. للقيام بذلك، سيحتاج مطورو الواجهة الأمامية إلى إجراء مكالمة إضافية واحدة أو ن (اعتمادًا على تطبيق الترحيل) في الخدمة المصغرة "الحصول على بيانات الفرن" باستخدام المعرفات التي تلقوها من الخدمة المصغرة الأولى. وعلى الرغم من أن هذا مجرد مثال بسيط، على الرغم من أنه مأخوذ من مشروع حقيقي (!)، إلا أنه يوضح المشكلة التالية: أصبحت محلات السوبر ماركت تحظى بشعبية كبيرة. وذلك لأنه لن يتعين عليك الذهاب إلى 10 أماكن مختلفة لشراء الخضروات وعصير الليمون والبيتزا المجمدة وورق التواليت. بدلاً من ذلك، تذهب إلى مكان واحد، فهو أسهل وأسرع. الأمر نفسه ينطبق على مطوري الواجهة الأمامية والخدمات الصغيرة.توقعات الإدارة
لدى الإدارة انطباع خاطئ بأنها بحاجة الآن إلى توظيف عدد لا حصر له من المطورين لمشروع (شامل)، حيث يمكن للمطورين الآن العمل بشكل مستقل تمامًا عن بعضهم البعض، كل منهم على الخدمة الصغيرة الخاصة به. مطلوب فقط القليل من العمل التكاملي في النهاية (قبل وقت قصير من الإطلاق). في الواقع، هذا النهج يمثل مشكلة كبيرة. وفي الفقرات التالية سنحاول توضيح السبب."القطع الأصغر" لا تساوي "القطع الأفضل"
سيكون من الخطأ الكبير افتراض أن الكود المقسم إلى 20 جزءًا سيكون بالضرورة ذا جودة أعلى من جودة قطعة واحدة كاملة. حتى لو أخذنا الجودة من وجهة نظر فنية بحتة، فقد تستمر خدماتنا الفردية في تشغيل 400 استعلام إسبات لاختيار مستخدم من قاعدة البيانات، واجتياز طبقات من التعليمات البرمجية غير المدعومة. مرة أخرى، نعود إلى مقولة سايمون براون: إذا فشلت في بناء وحدات متراصة بشكل صحيح، سيكون من الصعب بناء خدمات صغيرة مناسبة. غالبًا ما يكون الوقت متأخرًا جدًا للحديث عن التسامح مع الخطأ في مشاريع الخدمات الصغيرة. لدرجة أنه يكون من المخيف أحيانًا رؤية كيفية عمل الخدمات الصغيرة في المشاريع الحقيقية. والسبب في ذلك هو أن مطوري Java ليسوا مستعدين دائمًا لدراسة التسامح مع الأخطاء والشبكات والمواضيع الأخرى ذات الصلة على المستوى المناسب. "القطع" نفسها أصغر، لكن "الأجزاء الفنية" أكبر. تخيل أن فريق الخدمات الصغيرة لديك مطالب بكتابة خدمة فنية صغيرة لتسجيل الدخول إلى نظام قاعدة البيانات، شيء من هذا القبيل:@Controller
class LoginController {
// ...
@PostMapping("/login")
public boolean login(String username, String password) {
User user = userDao.findByUserName(username);
if (user == null) {
// обработка варианта с несуществующим пользователем
return false;
}
if (!user.getPassword().equals(hashed(password))) {
// обработка неверного пароля
return false;
}
// 'Ю-ху, залогинorсь!';
// установите cookies, делайте, что угодно
return true;
}
}
الآن قد يقرر فريقك (وربما يقنع رجال الأعمال) أن هذا الأمر بسيط للغاية وممل، فبدلاً من كتابة خدمة تسجيل دخول، من الأفضل كتابة خدمة UserStateChanged الصغيرة المفيدة حقًا دون أي آثار تجارية حقيقية وملموسة. وبما أن بعض الأشخاص يتعاملون حاليًا مع Java مثل الديناصورات، فلنكتب خدمة UserStateChanged الصغيرة بلغة Erlang العصرية. ودعنا نحاول استخدام الأشجار ذات اللون الأحمر والأسود في مكان ما، لأن Steve Yegge كتب أنه يتعين عليك معرفتها من الداخل إلى الخارج حتى تتمكن من التقديم إلى Google. من منظور التكامل والصيانة والتصميم الشامل، يعد هذا أمرًا سيئًا مثل كتابة طبقات من أكواد السباغيتي داخل قطعة واحدة متراصة. مثال مصطنع وعادي؟ هذا صحيح. ومع ذلك، يمكن أن يحدث هذا في الواقع.
قطع أقل - فهم أقل
ثم يطرح السؤال بطبيعة الحال حول فهم النظام ككل وعملياته وتدفقات العمل، ولكن في نفس الوقت أنت كمطور مسؤول فقط عن العمل على الخدمة الصغيرة المعزولة [95:login-101:updateUserProfile]. إنه ينسجم مع الفقرة السابقة، ولكن اعتمادًا على مؤسستك ومستوى الثقة والتواصل، يمكن أن يؤدي ذلك إلى الكثير من الارتباك والتجاهل وإلقاء اللوم في حالة حدوث عطل عرضي في سلسلة الخدمات الصغيرة. وليس هناك من يتحمل المسؤولية الكاملة عما حدث. وهذه ليست مسألة خيانة الأمانة على الإطلاق. في الواقع، من الصعب جدًا ربط الأجزاء المختلفة وفهم مكانها في الصورة العامة للمشروع.الاتصالات والخدمة
يختلف مستوى الاتصال والخدمة بشكل كبير حسب حجم الشركة. ومع ذلك، فإن العلاقة العامة واضحة: كلما زاد عددها، زادت المشكلة.- من يدير الخدمة الصغيرة رقم 47؟
- هل قاموا للتو بنشر إصدار جديد غير متوافق من الخدمة المصغرة؟ أين تم توثيق ذلك؟
- من الذي يجب أن أتحدث معه لطلب ميزة جديدة؟
- من سيدعم تلك الخدمة الصغيرة في Erlang، بعد أن ترك الشركة الشخص الوحيد الذي يعرف هذه اللغة؟
- لا تعمل جميع فرق الخدمات الصغيرة لدينا بلغات برمجة مختلفة فحسب، بل تعمل أيضًا في مناطق زمنية مختلفة! كيف ننسق كل هذا بشكل صحيح؟
GO TO FULL VERSION