design - RabbitMQ/AMQP-एक माइक्रो सर्विस आर्किटेक्चर[बंद] में सर्वश्रेष्ठ प्रैक्टिस क्यू/टॉपिक डिज़ाइन




esb spring-amqp (2)

"एक एक्सचेंज, या कई?" का जवाब देने से पहले। सवाल। मैं वास्तव में एक और सवाल पूछना चाहता हूं: क्या हमें वास्तव में इस मामले के लिए एक कस्टम एक्सचेंज की आवश्यकता है?

विभिन्न प्रकार के ऑब्जेक्ट ईवेंट प्रकाशित किए जाने वाले विभिन्न प्रकार के संदेशों से मेल खाने के लिए इतने नटखट होते हैं, लेकिन यह कभी-कभी आवश्यक नहीं होता है। क्या होगा अगर हम सभी 3 प्रकार की घटनाओं को एक "राइट" इवेंट के रूप में सार करते हैं, जिनके उप-प्रकार "निर्मित", "अपडेट" और "हटाए गए" हैं?

| object | event   | sub-type |
|-----------------------------|
| user   | write   | created  |
| user   | write   | updated  |
| user   | write   | deleted  |

समाधान 1

इसका समर्थन करने का सबसे सरल उपाय यह है कि हम केवल "user.write" कतार डिज़ाइन कर सकते हैं, और सभी उपयोगकर्ताओं को इस संदेश को सीधे वैश्विक डिफ़ॉल्ट एक्सचेंज के माध्यम से इस कतार में घटना संदेश लिख सकते हैं। सीधे एक कतार में प्रकाशित करते समय, सबसे बड़ी सीमा यह है कि यह माना जाता है कि केवल एक ऐप इस प्रकार के संदेशों की सदस्यता लेता है। इस कतार की सदस्यता लेने वाले एक ऐप के कई उदाहरण भी ठीक हैं।

| queue      | app  |
|-------------------|
| user.write | app1 |

समाधान २

सबसे सरल समाधान तब काम नहीं कर सकता था जब कोई दूसरा ऐप हो (अलग-अलग प्रोसेसिंग लॉजिक वाला) कतार में प्रकाशित किसी भी संदेश की सदस्यता लेना चाहता है। जब कई ऐप्स सदस्यता लेते हैं, तो हमें कम से कम एक "फैनआउट" प्रकार की आवश्यकता होती है, जिसमें कई कतारों के साथ बाइंडिंग होती है। ताकि संदेशों को प्रकाशित किया जा सके, और एक्सचेंज संदेशों को प्रत्येक कतारों में दोहराए। प्रत्येक कतार प्रत्येक अलग-अलग ऐप के प्रसंस्करण कार्य का प्रतिनिधित्व करती है।

| queue           | subscriber  |
|-------------------------------|
| user.write.app1 | app1        |
| user.write.app2 | app2        |

| exchange   | type   | binding_queue   |
|---------------------------------------|
| user.write | fanout | user.write.app1 |
| user.write | fanout | user.write.app2 |

यह दूसरा समाधान ठीक काम करता है अगर प्रत्येक ग्राहक परवाह करता है और सभी उप-प्रकारों को "user.write" ईवेंट के लिए संभालना चाहता है या कम से कम इन सभी उप-प्रकार की घटनाओं को प्रत्येक ग्राहकों को उजागर करने के लिए कोई समस्या नहीं है। उदाहरण के लिए, यदि सब्सक्राइबर ऐप केवल ट्रांज़िशन लॉग रखने के लिए है; या यद्यपि ग्राहक केवल user.created को संभालता है, जब user.updated या user.deleted होता है तो इसके बारे में बताना ठीक है। यह कम सुरुचिपूर्ण हो जाता है जब कुछ ग्राहक आपके संगठन के बाहरी से होते हैं, और आप केवल उन्हें कुछ विशिष्ट उप-प्रकार की घटनाओं के बारे में सूचित करना चाहते हैं। उदाहरण के लिए, यदि app2 केवल user.created को हैंडल करना चाहता है और उसे user.updated या user.deleted का ज्ञान बिल्कुल नहीं होना चाहिए।

समाधान 3

उपरोक्त समस्या को हल करने के लिए, हमें "user.write" से "user.created" अवधारणा को निकालना होगा। विनिमय का "विषय" प्रकार मदद कर सकता है। संदेशों को प्रकाशित करते समय, हम user.created / user.updated / user.deleted को रूटिंग कुंजी के रूप में उपयोग करते हैं, ताकि हम "user.write.app1" की बाध्यकारी कुंजी "उपयोगकर्ता। *" और बाइंडिंग कुंजी सेट कर सकें। "User.created.app2" कतार "user.created" हो।

| queue             | subscriber  |
|---------------------------------|
| user.write.app1   | app1        |
| user.created.app2 | app2        |

| exchange   | type  | binding_queue     | binding_key  |
|-------------------------------------------------------|
| user.write | topic | user.write.app1   | user.*       |
| user.write | topic | user.created.app2 | user.created |

समाधान 4

"विषय" विनिमय प्रकार अधिक लचीला होता है यदि संभावित रूप से अधिक घटना उप-प्रकार होगी। लेकिन अगर आपको स्पष्ट रूप से घटनाओं की सही संख्या पता है, तो आप बेहतर प्रदर्शन के लिए "प्रत्यक्ष" एक्सचेंज प्रकार का उपयोग कर सकते हैं।

| queue             | subscriber  |
|---------------------------------|
| user.write.app1   | app1        |
| user.created.app2 | app2        |

| exchange   | type   | binding_queue    | binding_key   |
|--------------------------------------------------------|
| user.write | direct | user.write.app1   | user.created |
| user.write | direct | user.write.app1   | user.updated |
| user.write | direct | user.write.app1   | user.deleted |
| user.write | direct | user.created.app2 | user.created |

"एक मुद्रा, या कई?" प्रश्न पर वापस आएं। अब तक, सभी समाधान केवल एक एक्सचेंज का उपयोग करते हैं। ठीक काम करता है, कुछ भी गलत नहीं। फिर, हमें कई एक्सचेंजों की आवश्यकता कब हो सकती है? यदि एक "विषय" एक्सचेंज में बहुत सारे बाइंडिंग हैं, तो एक मामूली प्रदर्शन ड्रॉप है। यदि "विषय विनिमय" पर बहुत अधिक बाइंडिंग का प्रदर्शन अंतर वास्तव में एक मुद्दा बन जाता है, तो आप बेहतर प्रदर्शन के लिए "विषय" एक्सचेंज बाइंडिंग की संख्या को कम करने के लिए अधिक "प्रत्यक्ष" एक्सचेंजों का उपयोग कर सकते हैं। लेकिन, यहाँ मैं "एक एक्सचेंज" समाधान की फ़ंक्शन सीमाओं पर अधिक ध्यान केंद्रित करना चाहता हूं।

समाधान 5

एक मामले में हम अलग-अलग समूहों या घटनाओं के आयामों के लिए कई एक्सचेंजों पर स्वाभाविक रूप से विचार कर सकते हैं। उदाहरण के लिए, उपरोक्त बनाए गए, अपडेट किए गए और हटाए गए ईवेंट के अलावा, अगर हमारे पास ईवेंट का एक और समूह है: लॉगिन और लॉगआउट - "डेटा लिखने" के बजाय "उपयोगकर्ता व्यवहार" का वर्णन करने वाले ईवेंट का एक समूह। घटनाओं के अलग-अलग समूह को पूरी तरह से अलग-अलग रूटिंग रणनीतियों और रूटिंग कुंजी और कतार नामकरण सम्मेलनों की आवश्यकता हो सकती है, ऐसा इसलिए है ताकि एक अलग user.behavior विनिमय हो।

| queue              | subscriber  |
|----------------------------------|
| user.write.app1    | app1        |
| user.created.app2  | app2        |
| user.behavior.app3 | app3        |

| exchange      | type  | binding_queue      | binding_key     |
|--------------------------------------------------------------|
| user.write    | topic | user.write.app1    | user.*          |
| user.write    | topic | user.created.app2  | user.created    |
| user.behavior | topic | user.behavior.app3 | user.*          |

अन्य समाधान

ऐसे अन्य मामले हैं जब हमें एक वस्तु प्रकार के लिए कई एक्सचेंजों की आवश्यकता हो सकती है। उदाहरण के लिए, यदि आप एक्सचेंजों पर अलग-अलग अनुमतियाँ सेट करना चाहते हैं (उदाहरण के लिए केवल एक ऑब्जेक्ट प्रकार की चयनित घटनाओं को बाहरी एप्लिकेशन से एक एक्सचेंज में प्रकाशित करने की अनुमति दी जाती है, जबकि दूसरा एक्सचेंज आंतरिक ऐप से किसी भी ईवेंट को स्वीकार करता है)। एक अन्य उदाहरण के लिए, यदि आप घटनाओं के एक ही समूह के राउटिंग रणनीतियों के विभिन्न संस्करणों का समर्थन करने के लिए एक संस्करण संख्या के साथ प्रत्यय वाले विभिन्न एक्सचेंजों का उपयोग करना चाहते हैं। एक अन्य उदाहरण के लिए, आप एक्सचेंज-टू-एक्सचेंज बाइंडिंग के लिए कुछ "आंतरिक एक्सचेंज" को परिभाषित करना चाह सकते हैं, जो एक स्तरित तरीके से रूटिंग नियमों का प्रबंधन कर सकता है।

सारांश में, अभी भी, "अंतिम समाधान आपके सिस्टम की जरूरतों पर निर्भर करता है", लेकिन उपरोक्त सभी समाधान उदाहरणों के साथ, और पृष्ठभूमि के विचारों के साथ, मुझे आशा है कि यह कम से कम सही दिशा में एक सोच प्राप्त कर सकता है।

मैंने इस समस्या पृष्ठभूमि, समाधान और अन्य संबंधित विचारों को एक साथ रखकर एक ब्लॉग पोस्ट भी बनाया।

हम अपने microservice बुनियादी ढांचे (कोरियोग्राफी) के लिए AMQP आधारित दृष्टिकोण शुरू करने के बारे में सोच रहे हैं। हमारे पास कई सेवाएं हैं, ग्राहक-सेवा, उपयोगकर्ता-सेवा, लेख-सेवा आदि कहते हैं। हम RabbitMQ को अपने केंद्रीय संदेश प्रणाली के रूप में पेश करने की योजना बना रहे हैं।

मैं विषयों / कतारों आदि के बारे में प्रणाली के डिजाइन के लिए सर्वोत्तम प्रथाओं की तलाश कर रहा हूँ। एक विकल्प यह होगा कि हम अपने सिस्टम में होने वाली हर एक घटना के लिए एक संदेश कतार बनाएँ, उदाहरण के लिए:

user-service.user.deleted
user-service.user.updated
user-service.user.created
...

मुझे लगता है कि सैकड़ों संदेश कतारें बनाना सही दृष्टिकोण नहीं है, है ना?

मैं उदाहरण के लिए स्प्रिंग और इन अच्छे एनोटेशन का उपयोग करना चाहूंगा:

  @RabbitListener(queues="user-service.user.deleted")
  public void handleEvent(UserDeletedEvent event){...

क्या केवल "उपयोगकर्ता-सेवा-अधिसूचना" जैसे कुछ को एक कतार में रखना बेहतर नहीं है और फिर उस कतार में सभी सूचनाएं भेजें? मैं अभी भी सभी घटनाओं के एक सबसेट पर श्रोताओं को पंजीकृत करना चाहूंगा, इसलिए इसे कैसे हल किया जाए?

मेरा दूसरा सवाल: अगर मैं एक कतार पर सुनना चाहता हूं जो पहले नहीं बनाई गई थी, तो मुझे रैबिटक्यू में एक अपवाद मिलेगा। मुझे पता है कि मैं AmqpAdmin के साथ एक कतार "घोषित" कर सकता हूं, लेकिन क्या मुझे अपने सैकड़ों की हर कतार के लिए हर एक माइक्रो सर्विस में ऐसा करना चाहिए, क्योंकि यह हमेशा हो सकता है कि कतार अब तक बनाई नहीं गई थी?


डेरिक की सलाह ठीक है, सिवाय इसके कि वह अपनी कतारों को कैसे नाम देता है। कतारों को केवल रूटिंग कुंजी के नाम की नकल नहीं करनी चाहिए। रूटिंग कुंजियाँ संदेश के तत्व हैं, और कतारों को इस बारे में परवाह नहीं करनी चाहिए। यही कारण है कि बाइंडिंग के लिए कर रहे हैं।

कतार से जुड़े लोगों के नाम पर रखा जाना चाहिए कि कतार से जुड़े उपभोक्ता क्या करेंगे। इस कतार के संचालन का इरादा क्या है। कहते हैं कि आप उपयोगकर्ता को एक ईमेल भेजना चाहते हैं जब उनका खाता बनाया जाता है (जब रूटिंग कुंजी user.event.created के साथ संदेश ऊपर डरिक के उत्तर का उपयोग करके भेजा जाता है)। आप एक कतार नाम sendNewUserEmail (या उन पंक्तियों के साथ कुछ, एक शैली में, जो आपको उपयुक्त लगे) बना देंगे। इसका मतलब यह है कि यह समीक्षा करना आसान है और यह जानना ठीक है कि कतार क्या करती है।

यह महत्वपूर्ण क्यों है? खैर, अब आपके पास एक और रूटिंग कुंजी है, user.cmd.create। मान लें कि यह ईवेंट तब भेजा गया है जब कोई अन्य उपयोगकर्ता किसी और के लिए एक खाता बनाता है (उदाहरण के लिए, एक टीम के सदस्य)। आप अभी भी उस उपयोगकर्ता को एक ईमेल भेजना चाहते हैं, इसलिए आप उन संदेशों को सेंड करने के लिए बाइंडिंग बनाते हैं जैसे कि SendNewUserEser क्यू।

यदि कतार को बंधन के नाम पर रखा गया था, तो यह भ्रम पैदा कर सकता है, खासकर अगर रूटिंग कुंजी बदल जाती है। कतार के नाम डिकॉयल्ड और स्व वर्णनात्मक रखें।





spring-amqp