خوب، Kafka و RabbitMQ - این دو ابزار، اغلب در جلسات فنی در مورد معماری توزیع شده مطرح می شوند. من بخشی از یک سری از چنین جلساتی بوده ام که در مورد جوانب مثبت و منفی آنها و اینکه آیا آنها با نیازهای ما مطابقت دارند یا خیر صحبت کرده ایم. در اینجا من یافتههایم را برای دیگران و خود آیندهام مستند میکنم.
اسپویل : ما در نهایت از هر دو برای موارد مختلف استفاده کردیم!
با توجه به قابلیت های مسیریابی پیام، kafka بسیار سبک است. تولیدکنندگان(Producers) به Topic ها پیام ارسال میکنند. topic ها می توانند پارتیشن هایی نیز داشته باشند (مانند sharding). kafka پیامها را در ساختار دادهای بسیار سادهاش ثبت میکند که شبیه یک log است! و می تواند به اندازه فضا دیسک scale شود.
در kafka، مصرف کنندگان(Consumers) برای خواندن پیام ها به پارتیشن ها متصل می شوند. kafka از یک رویکرد مبتنی بر pull-based استفاده میکند، بنابراین مسئولیت واکشی پیامها و ردیابی پیامهای خوانده شده بر عهده مصرفکنندگان است.
همچنین RabbitMQ نیز دارای قابلیت مسیریابی بسیار قوی ای است. می تواند پیام ها را از طریق یک سیستم پیچیده از مبادلات و صف ها هدایت کند. تولیدکنندگان(Producers) پیام هایی را به exchange هایس ارسال می کنند که بر اساس پیکربندی آنها عمل می کنند. به عنوان مثال، آنها می توانند پیام را به هر صفی که با آنها متصل است ارسال کنند، یا پیام را به برخی از صف های(queues) مشخص شده تحویل دهند، یا حتی اگر پیام ها در زمان مشخص خوانده نشد، منقضی شوند.
همچنین exchange ها میتوانند پیامها را به exchange های دیگر ارسال کنند، که تنوع گستردهای از جایگشتها را ممکن میسازد. مصرف کنندگان(Consumers) می توانند به پیام ها در یک صف یا الگوی صف گوش دهند. برخلاف کافکا، RabbitMQ پیامها را به مصرفکنندگان میفرستد، بنابراین مصرفکنندگان نیازی به پیگیری آنچه خواندهاند ندارند.
سیستم های توزیع شده می توانند 3 رویکرد داشته باشند:
در صورت عدم موفقیت در تحویل پیام، هیچ تلاش مجددی انجام نمی شود، به این معنی که داده ها ممکن است از بین بروند، اما تکرار داده ها ممکن نیست. به دلایل واضح این روش پر کاربرد نیست.
در صورت عدم موفقیت در تحویل پیام، سعی مجدد کند تا تایید موفقیت آمیز تحویل انجام می شود. این روش تضمین می کند که هیچ داده ای از بین نمی رود، اما می تواند منجر به تحویل تکراری شود!
اطمینان حاصل می شود که پیام ها دقیقاً یک بار تحویل داده می شوند. این مطلوب ترین روش تحویل است و دستیابی به آن در یک محیط توزیع شده تقریباً غیرممکن است!
کافکا و RabbitMQ هر دو ضمانت تحویل حداکثر یک بار و حداقل یک بار را ارائه می دهند.
کافکا با استفاده از تولیدکنندگان (idempotent (enable.idempotence=true)) روش دقیقاً یک بار تحویل را بین تولیدکننده به کارگزار ارائه می دهد.
تحویل پیام دقیقاً یک بار به مصرف کنندگان پیچیده تر است. در پایان مصرفکنندگان با استفاده از API تراکنشها و فقط خواندن پیامهای متعلق به تراکنشهای کامیت شده (isolation.level=read_committed) به دست میآید.
برای دستیابی واقعی به این هدف، مصرفکنندگان باید از پردازش پیامهای non-idempotent در صورت لغو تراکنش اجتناب کنند، که همیشه ممکن نیست. بنابراین، ترنزکشن های کافکا به نظر من چندان مفید نیستند.
در RabbitMQ، به دلیل ترکیب مسیریابی پیچیده و تحویل مبتنی بر push-based، تحویل دقیقاً یک بار پشتیبانی نمیشود. به طور کلی، توصیه می شود از حالت at-least-once delivery همراه با idempotent consumers استفاده کنید.
توجه: Kafka Streams نمونهای از سیستم واقعاً idempotent است که با حذف عملیات non-idempotent در یک تراکنش به آن دست مییابد. اما از حوصله این مقاله خارج است. توصیه میکنم اگر میخواهید بیشتر در آن تحقیق کنید، «“Enabling Exactly-Once in Kafka Streams” by Confluent» را بخوانید.
عملکرد صف های پیام به متغیرهای زیادی مانند اندازه پیام، تعداد node ها، پیکربندی replication، تضمین های تحویل و غیره بستگی دارد. من روی سرعت پیام های تولید شده در مقابل مصرف تمرکز خواهم کرد. دو موردی که پیش می آید عبارتند از:
در RabbitMQ پیام ها را برای مصرف در DRAM ذخیره می شود. در مواردی که مصرف کنندگان خیلی عقب نباشند، پیام ها به سرعت از DRAM ارائه می شوند. زمانی که تعداد زیادی از پیامها خوانده نشده باشند و از صف پشتیبانگیری شده باشد، عملکرد(performance) پایین می آید. در این حالت، پیام ها به دیسک push داده می شوند و خواندن از روی آن کندتر است. بنابراین، RabbitMQ با صف های خالی سریعتر کار می کند.
کافکا از ورودی/خروجی(I/O) متوالی دیسک برای خواندن تکههای log به صورت منظم استفاده میکند. در صورت مصرف پیامهای تازه، عملکرد بیشتر بهبود مییابد، زیرا پیامها از حافظه پنهان صفحه سیستمعامل بدون هیچ گونه خواندن ورودی/خروجی ارائه میشوند. با این حال، لازم به ذکر است که اجرای تراکنشها همانطور که در بالا توضیح داده شد، تأثیر منفی بر توان عملیاتی خواهد داشت.
به طور کلی، کافکا می تواند میلیون ها پیام را در یک ثانیه پردازش کند و سریعتر از RabbitMQ است. در حالی که RabbitMQ می تواند بیش از 20 هزار پیام در ثانیه پردازش کند.
تداوم پیام ها جنبه دیگری است که در هر دوی این ابزارها متفاوت است.
کافکا با ماندگاری (یا به قول خودشان retention ) در ذهن طراحی شده است. یک سیستم کافکا را میتوان به گونهای پیکربندی کرد که پیامها را - چه تحویلشده و چه تحویلنشده، با پیکربندی log.retention.hours یا log.retention.bytes حفظ کند.
حفظ پیام ها بر عملکرد کافکا تأثیری ندارد. مصرف کنندگان می توانند با تغییر افست پیام هایی که خوانده اند، پیام های حفظ شده را دوباره پخش کنند.
از طرف دیگر، RabbitMQ بسیار متفاوت عمل می کند. پیامهایی که به چند صف تحویل داده میشوند، در این صفها تکرار میشوند. سپس این نسخه ها به طور مستقل از یکدیگر توسط خط مشی صف هایی که در آن قرار دارند و مبادلاتی که عبور می کنند اداره می شوند. بنابراین برای تداوم پیام ها در RabbitMQ:
ناگفته نماند، این کار تاثیر performance ای خواهد داشت زیرا دیسک در یک عملیات حافظه درگیر است.
میتوان گفت که RabbitMQ موارد استفاده مسیریابی پیچیده ای را ارائه می دهد که با معماری ساده کافکا قابل تحقق نیستند. با این حال، کافکا توان عملیاتی و ماندگاری بالاتری از پیام ها را فراهم می کند.
جدای از این تفاوتها، هر دوی آنها قابلیتهای مشابهی مانند تحمل خطا، در دسترس بودن بالا، مقیاسپذیری و غیره را ارائه میکنند. با در نظر گرفتن این موضوع، ما در موارد کوچک از RabbitMQ برای consistent polling در سیستم تراکنشهای خود و از کافکا برای ایجاد سرویس های سریع استفاده کردیم.
یک جمله ی معروف است که میگوید:
کافکا شبیه لوله است پیام ها را خیلی سریع انتقال میدهد.اما rabbitMq میتواند مسیر یابی های پیچیده را هندل کند.
Understanding When to Use RabbitMQ or Apache Kafka