مسئله: چت یکبهیک و گروهی، تحویل بلادرنگ، تیکهای وضعیت، و کارکرد آفلاین. قلب مسئله: اتصالهای ماندگار و تضمین ترتیب/تحویل.
۱-۲) نیازمندی و تخمین
- FR: ارسال/دریافت ۱:۱، گروه تا ۵۱۲ نفر، وضعیت (ارسال/تحویل/خوانده)، آنلاینبودن، تاریخچه.
- NFR: تحویل آنلاین <100ms؛ پیام هرگز گم نشود؛ ترتیب هر گفتگو حفظ شود.
- فرض: 500M DAU × 40 پیام = 20B پیام/روز ≈ 230K msg/s میانگین، پیک ~1M/s. هر پیام ~200B → ~4TB/روز.
۳) طراحی
DIAGRAMمسیر یک پیام
A
→
Gateway A — WebSocket
→
Chat svc + صف
→
Gateway B
→
B
- هر کاربر یک اتصال WebSocket به یک Gateway دارد؛ رجیستری (Redis): user → کدام Gateway. میلیونها اتصال per node ممکن است — Gateway ها فقط لولهاند، Stateless در منطق.
- ارسال: A → Gateway → سرویس چت: ذخیره ماندگار (اول!) → پیدا کردن Gateway ی B → push. اگر B آفلاین: در صندوق آفلاین میماند + پوشنوتیفیکیشن.
- ذخیره: Wide-Column (مثل Cassandra) با کلید پارتیشن conversation_id و مرتبسازی message_id — «پیامهای این گفتگو» همیشه تکپارتیشن و مرتب.
- message_id ترتیبدار per-گفتگو (مثلاً Snowflake): ترتیب و idempotency و تیکها همه به همین ID تکیه میکنند.
۴) عمق
- تیکها: خودشان پیامهای سیستمی کوچکاند (delivered/read receipt) در همان کانال.
- گروه: پیام یکبار ذخیره میشود؛ fanout به اعضای آنلاین از طریق Gateway هایشان؛ اعضای آفلاین با اشارهگر «آخرین خوانده».
- آنلاینبودن (Presence): heartbeat هر ~۱۰ ثانیه در Redis با TTL؛ انتشار تغییرات فقط به علاقهمندان (دوستانِ باز بودن چت).
- همگامسازی چنددستگاهه: هر دستگاه offset خواندن خودش را دارد؛ سرور منبع حقیقت است.
به زبان ساده
پیامرسان یعنی اتصال دائمی هر کاربر + صف برای گیرنده آفلاین + تضمین ترتیب و تحویل؛ سرورها باید بدانند هر کاربر به کدام گره وصل است.
مثال واقعی
وقتی به دوست آفلاین پیام میدهی، پیام در صف میماند؛ وقتی آنلاین شد تحویل میگیرد و «تیک دوگانه» همان تأیید تحویلِ برگشتی است.