
لن أقدم تعريفًا للعديد من الإيجارات ، لقد كتبوا بالفعل عن هذا الأمر عدة مرات هنا وهنا . ومن الأفضل الانتقال مباشرة إلى موضوع المقال والبدء بالأسئلة التالية:
لماذا لا يكون التطبيق فوريًا على الفور؟
يحدث أن تم تطوير التطبيق في البداية للتثبيت فقط على جانب العميل. يمكنك استدعاء تطبيق محاصر أو برنامج كمنتج . يشتري العميل صندوقًا وينشر التطبيق على خوادمه (هناك العديد من الأمثلة على هذه التطبيقات).
ولكن مع مرور الوقت ، قد تعتقد شركة المطورين أنه سيكون من الجيد وضع التطبيق في السحابة بحيث يتم تأجيره (البرنامج كخدمة). تتميز طريقة النشر هذه بمزايا لكل من العملاء وشركة المطورين. يمكن للعملاء الحصول بسرعة على نظام عمل وعدم القلق بشأن النشر والإدارة. عند استئجار تطبيق ، لا تحتاج إلى استثمارات كبيرة لمرة واحدة.
وستتلقى شركة المطورين عملاء جدد ، بالإضافة إلى مهام جديدة: نشر التطبيق في السحابة ، وإدارة ، وتحديث الإصدارات الجديدة ، وترحيل البيانات أثناء التحديث ، والنسخ الاحتياطي للبيانات ، ومراقبة السرعة والأخطاء ، وإصلاح المشكلات في حالة حدوثها.
لماذا يجب أن يكون التطبيق في السحابة متعدد المستأجرين؟
لوضع تطبيق في السحابة ، ليس من الضروري جعله متعدد المستأجرين. ولكن بعد ذلك ، ستكون هناك المشكلة التالية: بالنسبة لكل عميل ، سيتعين عليك نشر موقف مخصص في السحابة مع التطبيق المؤجر ، وهذا مكلف بالفعل ، سواء من حيث استهلاك موارد حامل السحابة ومن حيث الإدارة. يعد تطبيق الاستئجار المتعدد في التطبيق أكثر ربحية بحيث يمكن لمثيل واحد خدمة العديد من العملاء (المؤسسات).
إذا كان التطبيق يسحب 1000 مستخدم يعمل في وقت واحد ، فمن المفيد تجميع العملاء (المؤسسات) بحيث يمنحون إجمالي الحمل المطلوب 1000 مستخدم لكل مثيل تطبيق. وبعد ذلك سيكون هناك الاستهلاك الأمثل للموارد السحابية.
افترض أن التطبيق مستأجر من قبل مؤسسة لـ 20 مستخدمًا (موظفون في المنظمة). ثم تحتاج إلى تجميع 50 من هذه المنظمات للوصول إلى الحمل الصحيح. من المهم عزل المنظمات عن بعضها البعض. تقوم مؤسسة بتأجير تطبيق ما ، ولا تسمح إلا لموظفيها بالذهاب إلى هناك ، وتخزن فقط بياناتها ، ولا ترى أن المنظمات الأخرى يتم تقديمها أيضًا بواسطة نفس التطبيق.
لا يعني تطبيق الاستئجار المتعدد أنه لم يعد بالإمكان نشر التطبيق محليًا على خادم المؤسسة. يمكنك دعم طريقتين للنشر في نفس الوقت:
- تطبيق متعدد المستأجرين في السحابة.
- تطبيق المستأجر واحد على خادم العميل.
لقد جاء طلبنا بطريقة مماثلة: من غير المستأجر إلى المستأجر متعددة. وفي هذه المقالة سوف أشارك بعض الأساليب في تطوير الاستئجار المتعدد.
كيف يتم تطبيق الاستئجار المتعدد في تطبيق تم تصميمه على أنه غير مستأجر؟
سنحدد هذا الموضوع على الفور ، وسندرس التطوير فقط ، ولن نتطرق إلى مسائل الاختبار ، وإصدار إصدار ، ونشر ، وإدارة. في كل هذه المجالات ، ينبغي أيضًا مراعاة ظهور الاستئجار المتعدد ، ولكن في الوقت الحالي ، سنتحدث فقط عن التنمية.
لفهم ماهية التطبيق الذي لم يكن مستأجرًا وأصبح متعدد المستأجرين ، سأصف الغرض منه ، وهو قائمة بالخدمات والتقنيات المستخدمة.
هذا هو نظام ECM (DirectumRX) ، والذي يتكون من 10 خدمات (5 خدمات متجانسة و 5 خدمات مجهرية). يمكن وضع جميع هذه الخدمات إما على خادم واحد قوي ، أو على عدة خوادم.
الخدمات هي- خدمة الويب - لخدمة عملاء الويب (المتصفحات).
- خدمة WCF - لخدمة عملاء سطح المكتب (تطبيقات WPF).
- خدمة لتطبيقات الهاتف المحمول.
- خدمة لأداء العمليات الخلفية.
- خدمة لتخطيط العمليات الخلفية.
- خدمة تنفيذ مخطط سير العمل
- سير العمل بلوك تنفيذ الخدمة
- خدمة تخزين المستندات (البيانات الثنائية).
- خدمة لتحويل المستندات إلى html (المعاينة في المستعرض).
- خدمة تخزين نتائج التحويل في html
كومة من التقنيات المستخدمة:
.NET + SQLServer / Postgres + NHibernate + IIS + RabbitMQ + Redis
لذا ، ما الذي يجعل الخدمات تصبح متعددة المستأجرين؟ للقيام بذلك ، تحتاج إلى تحسين الآليات التالية في الخدمات ، أي إضافة المعرفة حول المستأجرين إلى:
- تخزين البيانات ؛
- ORM.
- تخزين البيانات
- معالجة الطلب ؛
- معالجة رسائل الصف ؛
- التكوين.
- تسجيل.
- أداء مهام الخلفية ؛
- التفاعل مع microservices.
- التفاعل مع وسيط الرسائل.
في حالة طلبنا ، كانت هذه هي الأماكن الرئيسية التي تتطلب التحسينات. لننظر فيها بشكل منفصل.
اختيار طريقة تخزين البيانات
عندما تقرأ مقالات حول الاستئجار المتعدد ، فإن أول شيء يتم تسويته هو كيفية تنظيم تخزين البيانات. في الحقيقة ، النقطة مهمة.
بالنسبة لنظام ECM الخاص بنا ، التخزين الرئيسي هو قاعدة بيانات علائقية ، تحتوي على حوالي 100 جدول. كيفية تنظيم تخزين بيانات العديد من المنظمات بحيث لا ترى المنظمة A بأي شكل من الأشكال بيانات المؤسسة B؟
تعرف العديد من المخططات (تمت كتابة الكثير بالفعل حول هذه المخططات):
- إنشاء قاعدة بيانات خاصة بك لكل مؤسسة (لكل مستأجر) ؛
- استخدام قاعدة بيانات واحدة لجميع المنظمات ، ولكن لكل مؤسسة تضع مخططها الخاص بها في قاعدة البيانات ؛
- استخدم قاعدة بيانات واحدة لجميع المؤسسات ، لكن أضف عمودًا "مفتاح المستأجر / المؤسسة" في كل جدول.
اختيار المخطط ليس عرضيًا. في حالتنا ، يكفي النظر في حالات إدارة النظام لفهم الخيار المفضل. الحالات كالتالي:
- إضافة مستأجر (منظمة جديدة تستأجر نظامًا) ؛
- إزالة المستأجر (رفضت المنظمة الإيجار) ؛
- نقل المستأجر إلى حامل سحابة آخر (إعادة توزيع الحمل بين حوامل السحابة عندما يتوقف أحد الحاملين عن التعامل مع الحمل).
النظر في قضية نقل المستأجر. تتمثل المهمة الرئيسية للنقل في نقل بيانات المؤسسة إلى منصة أخرى. ليس من الصعب إجراء النقل إذا كان لدى المستأجر قاعدة بيانات خاصة به ، لكن سيكون صداعًا إذا قمت بخلط بيانات المنظمات المختلفة في 100 جدول. حاول استخراج البيانات الضرورية فقط من الجداول ، ونقلها إلى قاعدة بيانات أخرى ، حيث توجد بالفعل بيانات من مستأجرين آخرين ، وحتى لا تتقاطع معرفاتهم.
الحالة التالية هي إضافة مستأجر جديد. القضية هي أيضا ليست بسيطة. إضافة المستأجر هي الحاجة إلى ملء أدلة النظام والمستخدمين والحقوق ، بحيث يمكنك تسجيل الدخول إلى النظام على الإطلاق. يمكن حل هذه المهمة بشكل أفضل عن طريق استنساخ قاعدة بيانات مرجعية ، تحتوي بالفعل على كل ما تحتاجه.
يتم حل قضية إزالة المستأجر بسهولة عن طريق تعطيل قاعدة بيانات المستأجر.
لهذه الأسباب ، اخترنا مخطط: مستأجر واحد - قاعدة بيانات واحدة .
ORM
اخترنا طريقة تخزين البيانات ، والسؤال التالي: كيفية تدريس ORM للعمل مع المخطط المحدد؟
نحن نستخدم نيبرنيت. كان مطلوبًا أن يعمل Nhibernate مع العديد من قواعد البيانات والانتقال بشكل دوري إلى الصحيح ، على سبيل المثال ، اعتمادًا على طلب http. إذا عالجنا طلب المؤسسة A ، فستستخدم قاعدة البيانات A ، وإذا كان الطلب من المنظمة B ، فإن قاعدة البيانات B.
NHibernate لديه مثل هذه الفرصة. تحتاج إلى تجاوز تنفيذ NHibernate.Connection.DriverConnectionProvider . عندما يريد NHibernate فتح اتصال قاعدة بيانات ، فإنه يستدعي DriverConnectionProvider للحصول على سلسلة اتصال. هنا سنستبدلها بما يلزم:
public class MyDriverConnectionProvider : DriverConnectionProvider { protected override string ConnectionString => TenantRegistry.Instance.CurrentTenant.ConnectionString; }
ما هو TenantRegistry.Instance.CurrentTenant سأقول بعد قليل.
التخزين المؤقت للبيانات
غالبًا ما تقوم الخدمات بتخزين البيانات مؤقتًا لتقليل استعلامات قاعدة البيانات أو عدم حساب نفس الشيء عدة مرات. المشكلة هي أنه يجب تقسيم المستودعات مؤقتًا من قبل المستأجرين إذا تم تخزين بيانات المستأجر مؤقتًا. من غير المقبول استخدام ذاكرة التخزين المؤقت لبيانات إحدى المؤسسات عند معالجة طلب من مؤسسة أخرى. الحل الأبسط هو إضافة معرف المستأجر إلى مفتاح كل ذاكرة التخزين المؤقت:
var tenantCacheKey = cacheKey + TenantRegistry.Instance.CurrentTenant.Id;
يجب أن يتم تذكر هذه المشكلة عند إنشاء كل ذاكرة التخزين المؤقت. هناك الكثير من المخابئ في خدماتنا. لكي لا ننسى أن نأخذ في الاعتبار معرف المستأجر في كل منهما ، فمن الأفضل توحيد العمل مع ذاكرات التخزين المؤقت. على سبيل المثال ، قم بعمل آلية عامة للتخزين المؤقت تؤدي إلى تخزينها مؤقتًا في سياق المستأجرين.
تسجيل
عاجلاً أم آجلاً ، سوف يحدث خطأ ما في النظام ، ستحتاج إلى فتح ملف السجل والبدء في دراسته. السؤال الأول هو: نيابة عن أي مستخدم والمنظمة التي ارتكبت هذه الإجراءات؟
أنها مريحة عندما يكون في كل سطر من السجل معرف المستأجر واسم مستخدم المستأجر. تصبح هذه المعلومات ضرورية ، على سبيل المثال ، وقت الرسالة:
2019-05-24 17:05:27.985 <message> [User2 :Tenant1] 2019-05-24 17:05:28.126 <message> [User3 :Tenant2] 2019-05-24 17:05:28.173 <message> [User4 :Tenant3]
يجب على المطور ألا يفكر في المستأجر الذي يجب أن يكتب إلى السجل ، ويجب أن يكون آليًا ، ومخفيًا "تحت غطاء" نظام التسجيل.
نستخدم NLog ، لذلك سأقدم مثالاً على ذلك. أسهل طريقة لتأمين معرّف المستأجر هي إنشاء NLog.LayoutRenderers.LayoutRenderer ، والذي سيتيح لك الحصول على معرف المستأجر لكل إدخال سجل:
[LayoutRenderer("tenant")] public class TenantLayoutRenderer : LayoutRenderer { protected override void Append(StringBuilder builder, LogEventInfo logEvent) { builder.Append(TenantRegistry.Instance.CurrentTenant.Id); } }
ثم استخدم LayoutRenderer في قالب السجل:
<target layout="${odate} ${message} [${user} :${tenant}]"/>
تنفيذ التعليمات البرمجية
في الأمثلة أعلاه ، غالبًا ما أستخدم الكود التالي:
TenantRegistry.Instance.CurrentTenant
حان الوقت لنعرف ماذا يعني ذلك. لكن عليك أولاً أن تفهم الطريقة التي نتبعها في الخدمات:
يجب أن يرتبط أي تنفيذ تعليمات برمجية (معالجة طلب http ، معالجة رسالة قائمة انتظار ، تنفيذ مهمة خلفية في سلسلة رسائل منفصلة) ببعض المستأجر.
هذا يعني أنه في أي مكان في تنفيذ التعليمات البرمجية قد يسأل المرء: "لأي مستأجر يعمل هذا الخيط؟" أو بطريقة أخرى ، "ما هو المستأجر الحالي؟"
TenantRegistry.Instance.CurrentTenant هو المستأجر الحالي للتيار الحالي. يمكن ربط الدفق والمستأجر في طلباتنا. تكون متصلة بشكل مؤقت ، على سبيل المثال ، أثناء معالجة طلب http أو أثناء معالجة رسالة من قائمة الانتظار. تتم إحدى الطرق لربط المستأجر في جدول مثل هذا:
يمكن الحصول على المستأجر المرتبط بخيط في أي مكان في التعليمات البرمجية ، من خلال الاتصال TenantRegistry - وهذا هو المفرد ، ونقطة الوصول للعمل مع المستأجرين. لذلك ، يمكن لـ Nhibernate و NLog الوصول إلى هذا المفرد (عند نقاط الامتداد) لمعرفة سلسلة الاتصال أو معرف المستأجر.
مهام الخلفية
تحتوي الخدمات في الغالب على مهام خلفية تحتاج إلى تنفيذها في توقيت. يمكن لمهام الخلفية الوصول إلى قاعدة بيانات المؤسسة ، ومن ثم يجب تنفيذ مهمة الخلفية لكل مستأجر. للقيام بذلك ، ليس من الضروري بدء مؤقت أو مؤشر ترابط منفصل لكل مستأجر. من الممكن القيام بمهمة في مستأجرين مختلفين داخل مؤشر ترابط واحد / مؤقت. للقيام بذلك ، في معالج المؤقت ، نقوم بفرز المستأجرين وربط كل مستأجر بدفق وتنفيذ مهمة خلفية:
لا يمكن إرفاق مستأجرين بالتدفق في نفس الوقت ؛ إذا تم إرفاق أحدهما ، فسيتم فصل الآخر عن التدفق. نحن نستخدم هذا النهج بفعالية حتى لا ننتج مؤشرات الترابط / المؤقتات لمهام الخلفية.
كيفية ربط طلب http مع المستأجر
لمعالجة طلب http الخاص بالعميل ، يلزمك معرفة المؤسسة التي أتى منها. إذا كان المستخدم مصادقًا بالفعل ، فيمكن تخزين معرف المستأجر في ملف تعريف ارتباط المصادقة (إذا تم تنفيذ العمل مع التطبيق من خلال المستعرض) أو في الرمز المميز JWT. لكن ماذا لو لم يوثق المستخدم بعد؟ على سبيل المثال ، قام مستخدم مجهول بفتح موقع ويب للتطبيق ويريد المصادقة. للقيام بذلك ، يرسل طلبًا بتسجيل الدخول وكلمة المرور. في قاعدة البيانات التي منظمة للبحث عن هذا المستخدم؟
أيضًا ، سيتم تلقي طلبات مجهولة للحصول على صفحة تسجيل الدخول إلى التطبيق ، وقد تختلف بالنسبة للمنظمات المختلفة ، على سبيل المثال ، لغة الترجمة.
لحل مشكلة ارتباط طلب http مجهول والمنظمة (المستأجر) ، نستخدم المجالات الفرعية للمؤسسات. يتكون اسم المجال الفرعي من اسم المنظمة. يجب على المستخدمين استخدام المجال الفرعي للعمل مع النظام:
https://company1.service.com https://company2.service.com
تتوفر نفس خدمة الويب متعددة المستأجرين على هذه العناوين. لكن الخدمة الآن تفهم من المنظمة التي سيأتي طلب http مجهول ، مع التركيز على اسم المجال.
يتم تنفيذ ربط اسم المجال والمستأجر في ملف تكوين خدمة الويب:
<tenant name="company1" db="database1" host="company1.service.com" /> <tenant name="company2" db="database2" host="company2.service.com" />
حول تكوين الخدمات سيتم وصفها أدناه.
Mikroservisy. تخزين البيانات
عندما قلت إن نظام ECM يحتاج إلى 100 جدول ، تحدثت عن خدمات متجانسة. ولكن يحدث أن تتطلب الخدمة المجهرية تخزينًا علائقيًا ، حيث يلزم وجود 2-3 جداول لتخزين بياناتها. من الناحية المثالية ، يكون لكل خدمة microservice مساحات تخزين خاصة بها ، والتي لا يمكنها الوصول إليها إلا. وتقرر الخدمة المجهرية كيفية تخزين البيانات في سياق المستأجرين.
لكن ذهبنا في الاتجاه الآخر: قررنا تخزين جميع بيانات المؤسسة في قاعدة بيانات واحدة. إذا تطلبت خدمة microservice تخزينًا علائقيًا ، فستستخدم قاعدة بيانات المؤسسة الحالية بحيث لا يتم توزيع البيانات عبر وحدات تخزين مختلفة ، ولكن يتم جمعها في قاعدة بيانات واحدة. خدمات متجانسة استخدام نفس قاعدة البيانات.
تعمل Microservices فقط مع جداولها في قاعدة البيانات ، ولا تحاول العمل مع جداول متراصة أو خدمات microservice أخرى. هناك إيجابيات وسلبيات لهذا النهج.
الايجابيات:
- بيانات المنظمة في مكان واحد ؛
- سهلة النسخ الاحتياطي واستعادة بيانات المنظمة.
- في النسخ الاحتياطي ، بيانات جميع الخدمات متسقة.
سلبيات:
- قاعدة بيانات واحدة لجميع الخدمات هي رقبة ضيقة عند التوسع (متطلبات زيادة موارد نظم إدارة قواعد البيانات) ؛
- تتمتع الخدمات الصغيرة بوصول مادي إلى طاولات بعضها البعض ، لكن لا تستخدم هذه الميزة.
Mikroservisy. معرفة المستأجرين ليست مطلوبة دائمًا.
قد لا تعرف الخدمة الدقيقة أنها تعمل في بيئة متعددة المستأجرين. النظر في واحدة من خدماتنا ، والتي تشارك في تحويل المستندات إلى HTML.
ماذا تفعل الخدمة:
- يأخذ رسالة من قائمة انتظار RabbitMQ لتحويل مستند.
- يسترد معرف المستند ومعرف المستأجر من الرسالة
- قم بتنزيل مستند من خدمة تخزين المستندات.
- لهذا ينشئ طلبًا ينقل فيه معرف المستند ومعرف المستأجر
- يحول المستند إلى html.
- يعطي html للخدمة لتخزين نتائج التحويل.
الخدمة لا تخزن المستندات ولا تخزن نتائج التحويل. لديه معرفة غير مباشرة بالمستأجرين: يمر معرف المستأجر عبر الخدمة أثناء العبور.
Mikroservisy. ليست هناك حاجة المجالات الفرعية
كتبت أعلاه أن النطاقات الفرعية تساعد في حل مشكلة طلبات http المجهولة:
https://company1.service.com https://company2.service.com
ولكن لا تعمل جميع الخدمات مع طلبات مجهولة ، فمعظمها تتطلب مصادقة مرت بالفعل. لذلك ، لا تهتم الخدمات الصغيرة التي تعمل عبر http بالاسم المضيف الذي جاء منه الطلب ، فهي تتلقى جميع المعلومات حول المستأجر من الرمز المميز JWT أو ملف تعريف ارتباط المصادقة الذي يأتي مع كل طلب.
تكوين
يجب تهيئة الخدمات حتى يعرفوا عن المستأجرين. وهي:
- تحديد سلاسل للاتصال بقاعدة بيانات المستأجرين ؛
- ربط أسماء النطاقات بالمستأجرين ؛
- تحديد اللغة الافتراضية والمنطقة الزمنية للمستأجر.
يمكن للمستأجرين لديها العديد من الإعدادات. بالنسبة لخدماتنا ، نقوم بتعيين إعدادات المستأجر في ملفات تكوين XML. هذا ليس web.config وليس app.config. هذا ملف xml منفصل ، يجب أن تكون التغييرات فيه قادرة على اللحاق دون إعادة تشغيل الخدمات بحيث لا يؤدي إضافة مستأجر جديد إلى إعادة تشغيل النظام بأكمله.
قائمة الإعدادات شيء من هذا القبيل:
<block name="TENANTS"> <tenant name="Jupiter" db="DirectumRX_Jupiter" login="admin" password="password" hyperlinkUriScheme="jupiter" hyperlinkFileExtension=".jupiter" hyperlinkServer="http://jupiter-rx.directum.ru/Sungero" helpAddress="http://jupiter-rx.directum.ru/Sungero/help" devHelpAddress="http://jupiter-rx.directum.ru/Sungero/dev_help" language="Ru-ru" isAttributesSignatureAbsenceAllowed="false" endorsingSignatureLocksSignedProperties="false" administratorEmail ="admin@jupiter-company.ru" feedbackEmail="support@jupiter-company.ru" isSendFeedbackAllowed="true" serviceUserPassword="password" utcOffset="5" collaborativeEditingEnabled="false" collaborativeEditingForced="false" /> <tenant name="Mars" db="DirectumRX_Mars" login="admin" password="password" hyperlinkUriScheme="mars" hyperlinkFileExtension=".mars" hyperlinkServer="http://mars-rx.directum.ru/Sungero" helpAddress="http://mars-rx.directum.ru/Sungero/help" devHelpAddress="http://mars-rx.directum.ru/Sungero/dev_help" language="Ru-ru" isAttributesSignatureAbsenceAllowed="false" endorsingSignatureLocksSignedProperties="false" administratorEmail ="root@mars-ooo.ru" feedbackEmail="support@mars-ooo.ru" isSendFeedbackAllowed="true" serviceUserPassword="password" utcOffset="-1" collaborativeEditingEnabled="false" collaborativeEditingForced="false" /> </block>
عندما تستأجر مؤسسة جديدة خدمة ما ، فإنها تحتاج إلى إضافة مستأجر جديد إلى ملف التكوين الخاص بها. ومن المرغوب فيه ألا تشعر المنظمات الأخرى بهذا. من الناحية المثالية ، يجب ألا يكون هناك إعادة تشغيل للخدمات.
في الولايات المتحدة ، لا تستطيع جميع الخدمات التقاط أحد التهيئة دون إعادة التشغيل ، ولكن الخدمات الأكثر أهمية (متراصة) قادرة على القيام بذلك.
يؤدي
عندما يصبح التطبيق متعدد المستأجرين ، يبدو أن تعقيد التطوير قد زاد بشكل كبير. ولكن بعد ذلك تعتاد على التعددية المتعددة ، وتعامل دعمها كشرط عادي.
تجدر الإشارة أيضًا إلى أن الاستئجار المتعدد ليس فقط التطوير ، ولكن أيضًا الاختبار ، والإدارة ، والنشر ، والتحديث ، والنسخ الاحتياطي ، وترحيل البيانات. ولكن أفضل عنهم مرة أخرى.