ارتباط سرویس‌ها


ارتباط هم‌زمان (Synchronous Communication)

HTTP (RESTful APIs)

  • ویژگی‌ها: ساده، گسترده‌پذیر، Stateless.
  • مزایا: پیاده‌سازی و فهم آسان، اکوسیستم گسترده، سازگاری با ابزارها و پروکسی‌ها.
  • معایب: سربار (overhead) در هر درخواست، تاخیر بالقوه برای درخواست‌های سنگین.

نکته: برای درخواست‌‌های کوتاه و پاسخ‌های سریع مناسب است؛ زمانی که نیاز به تضمین تاخیر پایین داریم باید گزینه‌های سریع‌تر را بررسی کنیم.

gRPC

  • ویژگی‌ها: مبتنی بر HTTP/2، پشتیبانی از Streaming، پروتکل باینری.
  • مزایا: تأخیر و مصرف پهنای‌باند کمتر، عملکرد بالا، مناسب برای ارتباطات سرویس‌به‌سرویس.
  • معایب: پیچیدگی بیشتر در خوانایی و دیباگ، نیاز به تعریف پروتکل (proto)، نگهداری کمی سخت‌تر نسبت به REST.

ارتباط ناهم‌زمان (Asynchronous Communication)

Messaging Systems (Message Brokers)

  • مثال‌ها: RabbitMQ، Apache Kafka.
  • الگو: پابلیشر پیام را به بروکر می‌فرستد؛ یک یا چند کانسومر می‌توانند پیام را مصرف کنند.
  • مزایا: جداسازی قوی بین پرو듀سِر و کانسومر، مقیاس‌پذیری، مقاوم در برابر قطعی موقت.
  • معایب: پیچیدگی در مدیریت، مانیتورینگ و تست.

Event-Driven Communication

  • معمولاً از Kafka و سیستم‌های مشابه استفاده می‌شود. پابلیشرها ایونت تولید می‌کنند و چندین کانسومر می‌توانند آن را پردازش کنند.
  • تفاوت اصلی با مدل pub-sub (queue) این است که ممکن است چند سرویس هم‌زمان منتظر همان ایونت باشند.
  • چالش‌ها: دیباگ، تست، ترتیب (ordering) و تضمین اتصال (idempotency) نیاز به طراحی دستی دارد.

ارتباط ترکیبی (Hybrid Communication)

WebSockets

  • کاربرد: برقراری یک کانال دائمی (single TCP connection) برای تعاملات Real-time مثل داشبورد زنده یا چت.
  • معایب: نیاز به زیرساخت و مدیریت ارتباط‌های دائمی در سمت سرور.

Remote Procedure Call (RPC)

  • اجازه می‌دهد یک سرویس فانکشن سرویس دیگر را مستقیماً فراخوانی کند؛ gRPC نمونه‌ای از RPC مدرن است.

جمع بندی

  • برای استریم کردن بین میکرو سرویس ها بهتره از grpc به جای وب سوکت استفاده کنیم ، به دلیل اینکه کانکشن خودش هندل میشه ، نیاز نیست مثل وب سوکت پینگ بیم و از خودمون پالیسی بزاریم ، همچنین اسکیماش خیلی استاندارد تره

  • برای درخواست های request-response در صورتی که استریم نباشن و یا لود بالایی نداشته باشن ، بهتره از rest(openapi3) استفاده کنیم زیرا هم داکیومنتیشن قوی داره و هم سرعت توسعه بالاست و در صورتی که زیر پروداکشن باتلنک endpoint هایی که فشار روشون هستند شناسایی شدن ، تون وقت میبریم grpc


معماری رویداد-محور (Event-Driven Architecture)

تعریف: الگوی طراحی که در آن اجزا با تولید، شناسایی و مصرف ایونت‌ها با هم تعامل می‌کنند.

ویژگی‌ها:

  • Event producers — تولیدکنندگان ایونت.
  • Event consumers — مصرف‌کنندگان ایونت.
  • Event channels — کانال‌های انتقال (مثلاً topics در Kafka).
  • Event processing — منطق پردازش و تبدیل ایونت.

مزایا: Loose coupling، مقیاس‌پذیری و انعطاف‌پذیری بالا.

چالش‌ها:

  • Complexity
  • Consistency
  • Latency
  • Debugging and Monitoring
  • Security

بخش مصاحبه (Interview)

اگر درخواست باید Real-time پاسخ داده شود معمولاً از راهکارهای هم‌زمان مثل gRPC یا REST استفاده می‌کنیم؛ اما اگر پردازش طولانی‌مدت باشد، مدل‌های ناهم‌زمان بهتر هستند.

زمانی که نیاز به ارتباط سینک داریم، آیا منطقی است از مسیج بروکرها یا صف استفاده کرد؟

قواعد کلی:

  1. برای ارتباطات request-response و زمانی که پاسخ فوری لازم است، از sync (مثل gRPC) استفاده کنید.
  2. از Kafka/بروکرها زمانی استفاده کنید که بخواهید سرویس‌ها را Loose-couple کنید، یا بار بالا و پردازش آسنکرون دارید.

مواردی که می‌شود از Kafka/بروکر استفاده کرد:

  • وقتی می‌خواهیم سرویس‌ها جدا و مستقل باشند.
  • وقتی حجم داده بسیار زیاد است (مثلاً نگهداری فایل‌ها در S3 و ارسال متادیتا به بروکر).
  • وقتی به معماری Event-Driven نیاز داریم.
  • می‌تواند نقش توزیع بار را ایفا کند: چند پاد (pod) می‌توانند یک تاپیک را کانسوم کنند (شبیه بارگزاری بین مصرف‌کنندگان).

اما: زمانی که سرعت پاسخ‌دهی مهم و حیاتی است، استفاده از پیام‌بروکر به‌عنوان جایگزین sync معمولاً توجیه‌پذیر نیست.


Client–Server (Request–Response) — سناریوی پردازش طولانی (Long-Running)

الگوی پیشنهادی:

202 Accepted + Polling

  1. کلاینت درخواست را می‌فرستد و سرور آن را در یک صف (queue) قرار می‌دهد.
  2. کلاینت برای بررسی وضعیت - progress - باید polling کند یا از روش‌های push مانند server-send-event(sse) یا webhook استفاده شود.

نکات فنی مهم:

  • Idempotency & Tracing:

کلاینت باید request_id یکتا تولید کند و آن را در هدر (مثلاً X-Request-ID) ارسال کند. این شناسه می‌تواند توسط Gateway کش شود تا idempotency تضمین شود.

  • اگر پاسخ timeout شد، همان request_id برای Retry استفاده شود تا از دوباره‌کاری جلوگیری شود.
  • در کنار polling، می‌توان از Server-Sent Events (SSE) یا Webhook برای نوتیفیکیشن استفاده کرد، اما callbackها نیازمند منطق بیشتر در سمت سرور (و مدیریت retry برای زمانی که کلاینت در دسترس نیست) هستند.

مزایا/معایب Polling:

  • مزیت: سادگی، پیاده‌سازی آسان.
  • عیب: مقیاس‌پذیری ضعیف — polling زیاد موجب بار اضافه می‌شود و coupling میان کلاینت و سرور افزایش می‌یابد.

نکته: request_id در header برای تراسینگ/ترنسپورت مناسب است؛ اگر آن را در body بگذاریم، معمولاً مربوط به دامنهٔ بیزینسی و idempotency تجاری است.


تفاوت Queue و Pub/Sub

ویژگیQueuePub/Sub
حذف پیام پس از مصرف✅ بله❌ معمولاً خیر
چندین مصرف‌کننده مستقلخیر (یک مصرف‌کننده در هر پیام point-to-point )بله (چند سابسکرایبر می‌توانند همان پیام را بخوانند)
کاربرد معمولپردازش ترتیبی و پردازش کارهاتوزیع ایونت‌ها و اطلاع‌رسانی

توضیح: در Kafka می‌توان با استفاده از consumer group رفتار صف را شبیه‌سازی کرد؛ پیام‌ها در پارتیشن‌ها به‌صورت round-robin توزیع می‌شوند و کانسومرها موازی پردازش می‌کنند.

در RabbitMQ بروکر به کانسومر اطلاع می‌دهد که داده جدید موجود است؛ در Kafka، خود کانسومر باید پیام جدید را از بروکر بخواند (pull).


Message Queues vs Event-Driven — مزایا و معایب خلاصه

  • مزایا: آسان‌تر برای مقیاس، نیاز کمتر به polling، decoupling، تحمل خطای بالاتر (messages persisted).
  • معایب: پیاده‌سازی پیچیده‌تر (retry، idempotency، fault tolerance)، دیباگ دشوارتر، گاهی latency بالاتر نسبت به sync.

الگوها و پیاده‌سازی (Implement)

Kafka Retry Pattern

  1. Consumer-Level Retries:

کانسومر پس از شکست پردازش، تکرار محلی (retry) انجام می‌دهد.

  1. Dead Letter Queue (DLQ):

پیام‌هایی که پس از چند تلاش موفق نیستند، به DLQ فرستاده می‌شوند تا بررسی و دیباگ شوند.

  1. Exponential Backoff with Jitter:

استفاده از افزایش فاصله بین تلاش‌ها به‌همراه Jitter برای جلوگیری از thundering herd.

Kafka Idempotency

  • ا At-least-once delivery نیاز به راه‌هایی برای جلوگیری از پردازش دوباره دارد:
    • ا acks=all
    • ا request_id منحصربه‌فرد که در Redis یا دیتاستوری دیگری ثبت و بررسی می‌شود.
    • ا Transactional Consumer و قابلیت‌های Kafka برای exactly-once semantics (مثلاً استفاده از transactional.id).
    • استفاده از عملیات upsert در پایگاه داده برای جلوگیری از درج تکراری.
    • افزودن version به پیام‌ها تا در مواجهه با پیام‌های قدیمی‌تر بتوانیم تصمیم درستی بگیریم.

Tracing در Kafka

  • تقریباً مشابه tracing در الگوی sync است: سِت کردن trace id، انتشار آن در هدر پیام و دنبال کردن مسیر پیام از تولید تا مصرف. پس می تونیم اینجوری بگیم دقیقا مانن استانداد های http که نباید اطلاعات بیزینس دامین رو توی هدر های http بیاریم ، بهتره اطلاعات ترنسپورت تریس رو داخل هدر بریزیم و تنها اطلاعات بیزینس رو داخل پیلود و یا باردی بریزیم

Fault-tolerant Strategy (وقتی Kafka کرش کند)

مسائل احتمالی:

  • از دست رفتن پیام‌ها در صورت خرابی replicaها.
  • ا Offsets نامعتبر برای کانسومرها.
  • تولیدکننده نتواند پیام ارسال کند.
  • پشتِ صف (backlog) افزایش یابد و تاخیر بالا برود.

راهکارها:

  • تنظیم replication مناسب (حداقل ۳ broker توصیه‌شده). - replication.factor >= 3
  • استفاده از cluster با چند broker و controller quorum (KRaft یا ZooKeeper/raft)،
  • پیاده سازی producers با retries و acks=all و idempotence؛ consumers با retry/DLQ و commit مناسب
  • فعال‌سازی enable.idempotence=true در Producer.
  • برای Consumer: enable.auto.commit=false و مدیریت دستی commit.
  • کش موقت تولیدکننده‌ها یا MirrorMaker برای بازنگهداری پیام‌ها (مثلاً در Redis) در مواقع بحرانی. همچنین می توان از upsert هم استفاده کرد

سناریوی طراحی سیستم (پیشنهاد جریان)

  1. کلاینت پیام با req_id تولید و به تاپیک orders-topic می‌فرستد — نیازی به retry در سمت کلاینت نیست.
  2. سرور از orders-topic کانسوم می‌کند.
  3. قبل از پردازش، در Redis چک می‌کند که پیام تکراری نباشد.
  4. در صورت موفقیت، نتیجه ذخیره می‌شود.
  5. در صورت شکست پردازش، پیام به orders-retry-topic فرستاده می‌شود.
  6. اگر تعداد retry‌ها از سقف گذشت، پیام به orders-dlq منتقل می‌شود.
  7. پردازش مجدد از روی orders-retry-topic و orders-dlq با منطق مشخص انجام می‌شود.

سناریوهای پیچیده — مثال کیف پول (Saga Pattern)

مسئله: چند سرویس باید تراکنش را کامل کنند؛ یکی ناموفق شود.

پیشنهاد:

  • سرویس کیف پول برای هر تراکنش وضعیت داشته باشد: PENDING, COMPLETE, FAILED.
  • نگهداری یک جدول لاگ برای ثبت مراحل Saga: هر تغییر وضعیت یک سطر جدید اضافه می‌کند.
  • پیاده‌سازی الگوی Saga (choreography or orchestration) برای برگشت یا Compensating Action در صورت خطا.

نکات مربوط به تاخیر سرویس‌ها:

  • ارتباط ناهم‌زمان و نمایش صفحهٔ «در حال پردازش» به کاربر.
  • اطلاع‌رسانی نهایی با پیامک/ایمیل یا نوتیفیکیشن.
  • استفاده از صف برای جلوگیری از race condition.
  • مدیریت خطاها با retry و تضمین idempotency در سراسر مسیر.

آیا Kafka تضمین می‌دهد که پیام به کانسومر می‌رسد؟

  • ا At-least-once: بله — Kafka تضمین می‌کند پیام حداقل یک‌بار به کانسومر تحویل داده شود، اما ممکن است بیشتر از یک‌بار باشد.
  • ا Exactly-once: با تنظیمات ویژه و پیاده‌سازی درست (مثلاً transactional producers/consumers) می‌توان به نزدیک Exactly-Once رسید. consumer با isolation.level=read_committed

جمع‌بندی: Kafka به‌تنهایی تضمین صددرصدی ارسال را نمی‌دهد؛ اما با پیکربندی مناسب، مکانیزم‌های ack و retry و استفاده از DLQ و idempotency می‌توان احتمال از دست رفتن پیام را به‌طور قابل‌توجهی کاهش داد.