<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"><channel><title>نوشته های Mohsen Rajabi</title><link>https://virgool.ir/feed/@m.rajabi</link><description>Tech Lead | Building Large-Scale and Scalable Software Solutions | Passionate about Teaching and Emerging Technologies</description><language>fa</language><pubDate>2026-11-11 04:27:24</pubDate><image><url>https://files.virgool.ir/upload/users/17291/avatar/avatar.png</url><title>Mohsen Rajabi</title><link>https://virgool.ir/@m.rajabi</link></image><item><title>چطور قیمت‌های لحظه‌ای را در مفید پیاده‌سازی کردیم؟</title><link>https://virgool.ir/@m.rajabi/چطور-قیمت-های-لحظه-ای-را-در-مفید-پیاده-سازی-کردیم-jqnh3kkzj2us</link><description>این مقاله رو بر اساس تجربیات واقعی تیممون نوشتم، جایی که با چالشهای فنی روبرو شدیم و با همکاری نزدیک، راه حل های مناسبی پیدا کردیم. هدف ما این بود که قیمت های لحظه ای سهام و صندوق ها رو به صورت real-time روی اپلیکیشن مفید نمایش بدیم، تا کاربرانمون تجربه ای روان و به روز داشته باشن.این تصمیم، آغازگر یک سفر فنی پرچالش بود. ما با حجم عظیمی از داده ها روبرو بودیم که باید با کمترین تأخیر ممکن (Latency) از هسته معاملات به صفحه نمایش کاربر میرسید. در ابتدای مسیر، ابهامات زیادی وجود داشت: نقطه شروع دقیق کجاست؟ چگونه باید گلوگاه ها را شناسایی و رفع کنیم؟ و چگونه معماری را طراحی کنیم که در اوج ترافیک بازار، پایدار بماند؟در ظاهر، این خواسته ساده به نظر میرسد؛ اما در عمل، با چالش هایی رو به رو شدیم که اگر از ابتدا درست دیده نمیشدند، میتوانستند کل سیستم را به بن بست برسانند.داده دقیقاً از کجا می آید؟با چه نرخی تولید میشود؟چقدر بزرگ میشود؟و مهمتر از همه، چطور میشود این حجم داده را بدون قربانی کردن latency، پایداری و سادگی، به دست میلیون ها کاربر رساند؟با چه روشی به سمت کلاینت داده ها ارسال شود؟در این مقاله، قصد داریم تجربه تیم خود را در مواجهه با این چالشها، انتخابهای معماری، و راهکارهایی که برای دستیابی به این هدف پیاده سازی کردیم، به اشتراک بگذاریم.منبع داده قیمت لحظهای و چالش های اولیهاولین قدم، درک عمیق از منبع داده بود. قیمتهای لحظه ای کجا تولید میشوند و با چه نرخی؟داده های حیاتی بازار، شامل قیمت لحظه ای سهام و صندوق ها، توسط هسته مرکزی سازمان بورس (RLC) تولید میشوند. برای دریافت این داده ها با حداکثر سرعت، یک تیم تخصصی دیگر در داخل مفید، وظیفه خواندن این جریان داده را بر عهده دارد. آنها برای این کار از زبان ++C استفاده کرده اند؛ انتخابی هوشمندانه که به دلیل نزدیکی به سخت افزار و پرفورمنس بالا، تأخیر در خواندن اطلاعات را به حداقل میرساند.این تیم، پیام های دریافتی را پس از پردازش اولیه، در فرمت بهینه Protobuf (برای کاهش حجم و افزایش سرعت سریالیزیشن) داخل یک کلاستر Kafka قرار میدهند. تا این نقطه، ما یک خط لوله (Pipeline) بسیار سریع داشتیم که داده ها را از هسته بورس به Kafka منتقل میکرد.سوال بعدی این بود: با چه حجمی از داده روبرو هستیم؟نرخ تولید پیام ها رابطه مستقیمی با حجم معاملات روزانه بازار دارد. اما برای طراحی سیستم، ما نیاز به یک برآورد دقیق از وضعیت نرمال و پیک داشتیم. تحلیلهای ما نشان داد که ما به طور کلی با ۴ جریان (Queue) اصلی داده مواجه هستیم:صف قیمتهای آخرین معامله: حدود ۱۰۰۰ پیام در ثانیه.صف قیمتهای پایانی: حدود ۵۰۰ پیام در ثانیه.صف تغییرات دارایی کاربر: حدود ۲۰۰ پیام در ثانیه.صف تغییرات بازدهی کاربر: حدود ۲۰۰ پیام در ثانیه.با یک محاسبه سرانگشتی، ما در حالت نرمال بازار باید حدود ۱۹۰۰ پیام در ثانیه را پردازش میکردیم. نکته حیاتی این بود که در روزهای پر نوسان بازار، این عدد به راحتی میتوانست در لحظاتی حدود ۲ تا ۳ برابر شود. معماری ما باید برای بدترین سناریو آماده می بود.استراتژی انتقال داده به سرویسهای داخلیچالش اصلی در این مرحله، انتقال این حجم عظیم پیام از Kafka به لایه Backend سرویس خودمان با کمترین تأخیر ممکن بود. ما به ابزاری نیاز داشتیم که بتواند نقش یک واسط بسیار سریع را بازی کند. اینجا چند گزینه داشتیم: RabbitMQ Stream، Kafka و Redis Pub/Sub. برای انتخاب، ملاک هامون شامل throughput (حجم پیام در ثانیه)، latency (تأخیر)، scalability (قابلیت مقیاسپذیری)، ease of maintenance (نگهداری ساده)، و مصرف منابع بود. من خودم قبلاً در پروژههای قبلی با RabbitMQ کار کرده بودم و میدونستم که سرعت خوبی داره، اما میخواستیم مقایسه کاملی داشته باشیم.پ.ن: جدول با کمک AI تولید شده.https://gist.github.com/EngRajabi/39a3bab203bd1091676c1ddbadf41e1aانتخاب ما: Redis Pub/SubKafka برای ingestion و نگهداری جریان اصلی داده انتخاب بسیار مناسبی بود؛ اما در لایه fan-out لحظهای به سرویسهای SSE، نیاز ما بیشتر به broadcast سریع، ساده و ephemeral بود تا replay و durability. به همین دلیل، در این نقطه از معماری Redis Pub/Sub را به عنوان یک لایه سبک و کم تاخیر بین Kafka و Backend انتخاب کردیم. ما Redis Pub/Sub را انتخاب کردیم. هدف ما در این لایه، نگهداری تاریخچه پیامها یا replay نبود؛ بلکه رساندن آخرین تغییرات قیمت با کمترین تأخیر به backend بود. Redis Pub/Sub با semantics از نوع at-most-once دقیقاً با ماهیت داده های ما سازگار بود: اگر قیمت چند ثانیه قبل از دست برود، با پیامهای بعدی جایگزین میشود. در مقابل، Kafka، Redis Streams و RabbitMQ Streams برای سناریوهایی مناسب ترند که replay، durability، consumer offset , acknowledgment یا پردازش قابل اطمینان تری لازم است.ما در استک فناوری خود از قبل Redis داشتیم و این انتخاب، پیچیدگی نگهداری یک تکنولوژی جدید را به تیم SRE تحمیل نمیکرد. توسعه آن نیز بسیار ساده بود.پیاده سازی: با همکاری تیم SRE، ما از ابزار Redpanda Connect برای انتقال داده ها از تاپیکهای Kafka به کانال های Redis Pub/Sub استفاده کردیم. مزیت Redpanda Connect برای ما این بود که بدون نوشتن سرویس واسط اختصاصی، pipeline بین Kafka و Redis را declarative و قابل مانیتورینگ نگه داشتیم. برای به حداکثر رساندن سرعت، یک نود Redis اختصاصی را در حالت تماماً حافظه (Memory-Only) پیکربندی کردیم.نکته در مورد قابلیت اطمینان: در این سناریو، اگر Redis ریست میشد، داده های چند لحظه از دست میرفت و قیمت ها برای چند ثانیه قطع میشدند. با توجه به اینکه دیتابیس اصلی در پس زمینه با تأخیری حدود ۵ دقیقه آپدیت میشد، این سطح از ریسک (Incident) برای نمایش قیمت لحظه ای قابل قبول بود.وضعیت ریسورس های Redis در عملیات:به نظر می‌رسد این لود برای Redis بیشتر شبیه به یک شوخی است!تحویل داده به کلاینتاکنون داده‌ها با سرعت نور به Redis ما می‌رسیدند. چالش بزرگ بعدی، ارسال این ۱۹۰۰+ پیام در ثانیه به صد ها هزاران کلاینت (مرورگر و موبایل) متصل بود. ما گزینه‌های مختلفی برای ارتباط Real-Time با کلاینت بررسی کردیم، گزینه‌ها SignalR، SocketIO، Lightstreamer و SSE بودن.ما به تکنولوژی‌ای نیاز داشتیم که:مقیاس‌پذیری (Scalability) بالایی داشته باشد.روی تمام مرورگرها و دیوایس‌ها به راحتی اجرا شود.ترجیحاً نیاز به کتابخانه سنگین سمت کلاینت نداشته باشد.با استک .NET ما هماهنگ باشد.توسعه سریع و راحتپ.ن: جدول با کمک AI تولید شده.انتخاب ما: (SSE (Server-Sent Eventsانتخاب بین این گزینه‌ها دشوار بود، اما ما ملاک‌های روشنی داشتیم: رایگان بودن، سادگی در اسکیل شدن، نگهداری آسان، عدم تحریم، سازگاری کامل با استک NET. و اجرای بدون دردسر روی تمام دستگاه‌ها. روی .Net نسخه 10 یه سری کلاس و قابلیت برای راحتی کار اضافه شده، اما این به این معنی نیست که در نسخه های قدیمی تر نتوانید استفاده کنید.SSE برنده قاطع این مقایسه برای نیاز ما بود. برخلاف WebSockets که ارتباطی دوطرفه و پیچیده‌تر ایجاد می‌کند، ما فقط نیاز داشتیم داده را از سرور به کلاینت ارسال (Push) دهیم. SSE یک استاندارد وب بسیار ساده است که روی HTTP کار می‌کند. نیاز به هیچ کتابخانه اضافی در سمت کلاینت ندارد (در جاوااسکریپت EventSource به صورت built-in وجود دارد)، به راحتی توسط Load Balancer ها مدیریت می‌شود و مکانیزم‌های تلاش مجدد (Retry) داخلی دارد. از نظر سازگاری بسیار عالی هست و بخاطر سادگی پیاده سازی تقریبا از سال 2011 پشتیبانی میشه، طبق caniuse در سال های اخیر پشتیبانی 100% داره و پشتیبانی 96% به صورت Global‌ داره.ما یک پروژه آزمایشی (POC) با کمک GitHub Copilot ایجاد کردیم. نتایج شگفت‌انگیز بود؛ سادگی پیاده‌سازی و عملکرد عالی آن، تصمیم ما را قطعی کرد.نمونه کد بک اند[HttpGet]&#xA;public async Task GetPrices()&#xA;{&#xA;    Response.Headers.Append(&#34;Content-Type&#34;, &#34;text/event-stream&#34;);&#xA;    Response.Headers.Append(&#34;Cache-Control&#34;, &#34;no-cache&#34;);&#xA;    Response.Headers.Append(&#34;Connection&#34;, &#34;keep-alive&#34;);&#xA;    Response.Headers.Append(&#34;X-Accel-Buffering&#34;, &#34;no&#34;);&#xA;&#xA;    var id = Guid.NewGuid().ToString(&#34;N&#34;);&#xA;    var cancellationToken = HttpContext.RequestAborted;&#xA;    var reader = SubscribersCoordinatorHostedService.Subscribe(id, cancellationToken);&#xA;&#xA;    await foreach (var (eventName, eventData) in reader.ReadAllAsync(cancellationToken).ConfigureAwait(false))&#xA;    {&#xA;        await Response.WriteAsync($&#34;event: {eventName}\ndata: {eventData}\n\n&#34;, cancellationToken).ConfigureAwait(false);&#xA;        await Response.Body.FlushAsync(cancellationToken).ConfigureAwait(false);&#xA;    }&#xA;}نمونه کد فرانت اندconst source = new EventSource(&#34;/prices/stream&#34;);&#xA;&#xA;source.onmessage = (event) =&gt; {&#xA;  const price = JSON.parse(event.data);&#xA;  render(price);&#xA;};&#xA;&#xA;source.onerror = () =&gt; {&#xA;  console.log(&#34;Retrying...&#34;);&#xA;};پیاده‌سازی سمت کلاینت؛ غلبه بر محدودیت‌هابرای پیاده‌سازی مکانیزم SSE در سمت کلاینت، چالش‌ها دست‌کمی از بک‌اند نداشتند.ما روی این موضوع متمرکز شدیم تا این جریان مداوم داده را بدون افتِ کیفیتِ تجربه کاربری، به صفحه نمایش بیاوریم. ما در سمت کلاینت با ۶ چالش اساسی روبه‌رو بودیم که برای هر کدام استراتژی مشخصی چیدیم:۱. محدودیت اتصالات همزمان در مرورگر (Connection Limit)روی HTTP/1.1محدودیت ۶ عدد کانکشن همزمان باز به ازای هر دامنه وجود داره.راه‌حل: خوشبختانه از آنجا که زیرساخت ما به طور کامل روی HTTP/2 سوار بود، این محدودیت به صورت پیش‌فرض به ۱۰۰ استریم همزمان (Multiplexing) افزایش پیدا کرده است. در نتیجه بدون نیاز به تغییر خاصی در کلاینت یا پیاده‌سازی SharedWorker، از این گلوگاه به سلامت عبور کردیم.۲. چالش ارسال توکن و احراز هویت (Authentication)کلاس پیش‌فرض EventSource در جاوااسکریپت، فقط از متد GET پشتیبانی می‌کند و متأسفانه هیچ راهِ ساده‌ای برای تنظیم هدرهای سفارشی (مثل Authorization: Bearer) به ما نمی‌دهد. پس چطور باید کاربر را احراز هویت می‌کردیم؟راه‌حل: ما سه راهکار پیش رو داشتیم:راهکار اول: استفاده از فلگ withCredentials: true که کوکی‌ها را همراه با درخواست ارسال می‌کند.راهکار دوم: ارسال توکن به عنوان Query String در URL. این روش از نظر امنیتی اصلاً توصیه نمی‌شود، چون توکن‌ها ممکن است در لاگ‌های سرور یا پراکسی‌ها ذخیره شوند و نشت پیدا کنند.راهکار سوم (انتخاب ما): استفاده از کتابخانه‌های ثالث مثل پکیج eventsource. این ابزارها با شبیه‌سازی استریم (اغلب با استفاده از Fetch API) به ما اجازه می‌دهند هم از متد POST استفاده کنیم و هم هدرهای سفارشی را کاملاً مدیریت کنیم. این کتابخانه داده‌ها را به صورت chunk-by-chunk می‌خواند و فرمت text/event-stream را به صورت دستی پارس می‌کند تا رفتار SSE را بازسازی کند.انتخاب ما استفاده از پکیچ Eventsource بود. ما نیاز داشتیم که یکسری پیچیدگی ها رو کم بکنیم و تمرکز اصلی رو بزاریم روی نحوه و بیزینس پیاده سازی. این پکیچ یکسری پیچیدگی ها رو کم میکنه و یکسری امکانات اضافه و ساده برای شما محیا میکنه.۳. طوفان به‌روزرسانی‌هابا توجه به نرخ بالای تولید پیام، اگر می‌خواستیم به ازای هر رویداد (Event) مستقیماً State صفحه را آپدیت کنیم، مرورگر درگیر رندرهای متوالی و سنگین می‌شد و باعث افزایش تعداد render ها و افت responsiveness رابط کاربری می‌شد.راه‌حل: برای آپدیت همزمان چندین قیمت در صفحه، ما از استراتژی Batch Update (به‌روزرسانی دسته‌ای) استفاده کردیم. به این شکل که داده‌های دریافتی را موقتاً داخل یک ref جمع‌آوری کرده و سپس در فواصل زمانی مشخص، کل داده‌های جدید را یکجا به UI تزریق می‌کردیم. این کار تجربه کاربری را به شدت روان و بدون لگ کرد.۴. مدیریت خطا و اتصال مجدد (Reconnect)در دنیای واقعیِ اینترنت موبایل و وای‌فای، قطعی اتصال اجتناب‌ناپذیر است.راه‌حل: یکی از زیبایی‌های ذاتی SSE این است که مرورگر به صورت پیش‌فرض استراتژی خوبی برای Reconnect دارد. اما برای کنترل دقیق‌تر، ما تصمیم گرفتیم خودمان دست به کار شویم. در زمان بروز خطا (داخل متد onerror)، کانکشن را به صورت دستی close می‌کنیم و با استفاده از یک setTimeout (و در نظر گرفتن تاخیر منطقی برای جلوگیری از فشار به سرور)، اتصال را مجدداً برقرار می‌کنیم.۵. مدیریت منابع هنگام جابه‌جایی در صفحاتوقتی کاربر در اپلیکیشن حرکت می‌کند و وارد جزئیات صندوق‌ها یا سهم‌های مختلف می‌شود، اتصالاتی که برای صفحات قبلی باز شده‌اند دیگر اعتباری ندارند و فقط منابع کلاینت و سرور را هدر می‌دهند.راه‌حل: مکانیزم پاکسازی (Cleanup). در زمان عوض شدن صفحه و صندوق‌ها (هنگام unmount شدن کامپوننت)، ما فوراً متد close را برای اتصال فعلی صدا می‌زنیم و برای دیتای جدید در صفحه جدید، یک کانکشن تازه باز می‌کنیم. این کارِ به ظاهر ساده، تاثیر فوق‌العاده‌ای در بهینه ماندن مصرف مموری مرورگر و کاهش بار سرور داشت.۶. چالش مقادیر اولیهاتصال SSE فقط مسئول دیتاهای لحظه‌ای بود. برای جلوگیری از inconsistency، کلاینت ابتدا snapshot آخرین وضعیت قیمت‌ها را از API معمولی دریافت می‌کرد که کمی تاخیر داشت و سپس stream فقط تغییرات بعدی را اعمال می‌کرد. در reconnect هم همین snapshot/re-sync انجام می‌شد تا از دست رفتن پیام‌های Redis Pub/Sub یا قطعی اینترنت باعث نمایش داده stale نشود.گلوگاه Backend - پردازش با نهایت سرعتاینجا به یکی از بزرگترین چالش‌های فنی رسیدیم. ما توانسته بودیم داده را به Redis برسانیم و روش ارسال به کلاینت را هم انتخاب کرده بودیم. اما چگونه باید در سرویس NET. خود، این حجم عظیم داده (۱۹۰۰ پیام در ثانیه از چندین صف مختلف) را از Redis می‌خواندیم و به شکل کارآمدی بین کاربران توزیع می‌کردیم؟ما با ترکیبی از داده‌های عمومی (مثل قیمت سهم) و اختصاصی (مثل دارایی کاربر) روبرو بودیم. داده های عمومی برای همه کاربران یکسان بودند برای مثال قیمت یه سهم. داده های اختصاصی داده هایی بودند که مخصوص یک کاربر خاص هستند، برای مثال کاربر مقداری از دارایی خودش رو فروخته یا به مقدار اون اضافه کرده و خریده و باید در لحظه در دارایی خودش لحاظ شده ببینه. معماری ما باید این ترکیب دیتا رو ساپورت میکرد. همچنین معماری باید برای افزودن صف‌های جدید در آینده انعطاف‌پذیر می‌بود.چالش‌ها:حجم بالای پیام در لحظه.ترکیبی از داده‌های عمومی (Public) و داده‌های اختصاصی کاربر (Private).لزوم مدیریت همزمانی (Concurrency) بالا.نیاز به معماری توسعه‌پذیر برای افزودن صف‌های جدید در آینده.تعداد کاربر در لحظه بالا که نیاز به broadcast دارنبرای پیاده‌سازی این بخش حیاتی در NET.، ما دو الگوی اصلی TPL Dataflow و Channel را بررسی کردیم:پ.ن: جدول با کمک AI تولید شده.انتخاب ما: System.Threading.Channelsملاک‌های اصلی ما سادگی پیاده‌سازی، سرعت توسعه، کمترین تأخیر، بیشترین بازدهی و مدیریت بهینه رم و CPU بود. TPL Dataflow برای pipeline های پیچیده‌تر، transform های چند مرحله‌ای، batching و مدل actor-like گزینه قدرتمندی است. اما کتابخانه System.Threading.Channels دقیقاً برای همین سناریوها طراحی شده است: انتقال داده بین Producer و Consumer با نهایت سرعت و کمترین سربار. Channels در .NET مثل یک لوله FIFO هوشمند و کاملاً asynchronous عمل می‌کند که مایکروسافت آن را در قلب دو موتور مهم خود Kestrel و SignalR استفاده کرده است. در تجربیات خودمون، عالی عمل کرد. این ساختار کاملاً thread-safe و lock-free است و در بسیاری از سناریوها تقریباً zero-allocation کار می‌کند، بدون اینکه نگران synchronization، race condition یا مدیریت دستی queue باشید. نتیجه‌اش کدی تمیزتر، مقیاس‌پذیرتر و با عملکرد بسیار بالاتر است.طبق این بنچمارک، مصرف حافظه و سرعت Channel به مراتب بهتر از Dataflow هست.ما یک معماری لایه بندی شده و ماژولار طراحی کردیم، هر Redis Channel رو در HostedService گذاشتیم که پیام ها رو به Channel داخلی میریزه. برای جلوگیری از رشد بی نهایت صف ها، از BoundedChannel با ظرفیت ۱۰۰k و DropOldest استفاده کردیم. این به این معنی هست که حداکثر ظرفیت چنل ۱۰۰k هست به دو دلیل: دلیل اول اینکه بی نهایت نباشیم که اگر کند شدیم کلی پیام رو نگه نداریم. دوم اینکه مشخص شدن تعداد باعث افزایش سرعت عملیات رو چنل میشه. اگر هم پیام بیشتری بیاد چون DropOldest گذاشتیم باعث میشه قدیمی ترین پیام حذف بشه. یه Coordinator هم گذاشتیم برای مدیریت Redis Channel ها . Coordinator میاد Channel ها رو مدیریت میکنه و برای هر کاربر Channelجدا میسازه.ما همه پیام ها را کورکورانه به همه کاربران ارسال نمیکردیم. هر connection بر اساس context صفحه، دارایی ها یا watchlist کاربر فقط subset موردنیاز را دریافت میکرد. این filtering باعث شد fan-out واقعی کنترل شده بماند و تعداد eventهای خروجی با تعداد کل نمادهای بازار ضرب نشود.این معماری scalable هست و برای صف های جدید آمادهست.RLC → C++ Ingestion → Kafka/Protobuf → Redpanda Connect → Redis Pub/Sub → .NET Hosted Services → Coordinator → Per-user/per-subscription channels → SSE → Client batch rendererبرو جلو نترسوقتی این معماری رو توسعه دادیم و روی محیط های مختلف تست های سنگین مون رو با همراهی تیم SRE پاس کردیم، نوبت به لحظه نفسگیر انتشار رسید. استراتژی ما «برو بالا، نترس» بود، اما با کمربند ایمنی!ما برای مدیریت این فرآیند از Unleash به عنوان ابزار Feature Toggle استفاده کردیم. در قدم اول، این فیچر رو فقط برای ۳۰ درصد از کاربران باز کردیم. همزمان چشم مون به مانیتورها و داشبوردهای گرافانا بود. خوشبختانه همه چیز عالی بود و هیچ مشکل یا افت پرفورمنسی در بک اند و زیرساخت ندیدیم.اما تو همین فاز ۳۰ درصد، بازخورد مهمی از سمت کاربران و مدیر محصول تیم گرفتیم: افکت بصری تغییر قیمت ها روی صفحه چندان جذاب نبود و حس Real-time بودن رو به خوبی منتقل نمیکرد. اینجا بود که با یکی از توسعه دهنده های فرانت اند تیم دست به کار شدیم. ما تصمیم گرفتیم افکت تغییر قیمت پلتفرم TradingView رو الگو قرار بدیم؛ افکتی که در دنیا کاملاً شناخته شده و تست شده است و از نظر تجربه کاربری نتیجه ای عالی دارد.نکته فنی و جذاب این بخش این بود که ما نمیخواستیم برای این افکت بصری، کدهای جاوااسکریپت (JS) اضافه بنویسیم. درگیر کردن JS روی کلاینت برای پردازش های اینچنینی میتونست در مقیاس بالا برامون سربار (Overhead) ایجاد کنه. در نتیجه، بچه های فرانت اند با چند تا تریک ساده و تمیز CSS همون افکت TradingView رو پیاده سازی کردن که هم به شدت سبک بود و هم خروجی بینقصی داشت.بعد از اعمال این تغییرات ظاهری و اطمینان کامل از پایداری سیستم، با خیال راحت فیچر رو برای ۵۰ درصد کاربران فعال کردیم. چند روزی در این وضعیت موندیم تا دیتای رفتار سیستم و کاربران به اندازه کافی جمع آوری بشه. وقتی مطمئن شدیم همه چیز سر جای خودشه، شیر رو بیشتر باز کردیم؛ اول به ۷۰ درصد رسیدیم و در نهایت، به ۱۰۰ درصد.اعداد سخن میگویندطراحی معماری روی کاغذ یک چیز است و عملکرد آن زیر بار واقعی چیز دیگر. یکی از نگرانی های اصلی ما، میزان مصرف منابع سرور بود. با توجه به اینکه قرار بود هزاران کانکشن باز (Open Connections) به صورت همزمان داشته باشیم، مدیریت حافظه حیاتی بود. یکی از بزرگترین ترس ها این بود: &#34;نکند صف ها پر شده باشند و ما خبر نداریم؟&#34;.نتیجه نهایی حتی برای خود ما هم شگفت انگیز بود. به لطف ساختار سبک SSE و مدیریت حافظه بهینه در .NET (به خصوص استفاده از System.Threading.Channels که سربار بسیار کمی دارد)، ما توانستیم با منابعی بسیار محدود، ترافیک عظیمی را مدیریت کنیم.ما از Prometheus برای جمع آوری متریک ها و Grafana برای بصری سازی استفاده کردیم. اما متریک های پیشفرض کافی نبودند. ما متریکهای اختصاصی (Custom Metrics) را روی Channelها سوار کردیم تا سلامت جریان داده را لحظه به لحظه رصد کنیم:Channel Depth (عمق کانال): چه تعداد پیام در صف منتظر پردازش هستند؟ اگر این عدد بالا برود، یعنی مصرف کننده (Consumer) کند شده است.Ingest vs. Drain Rate: سرعت ورود داده به کانال در مقابل سرعت خروج.Active SSE Connections: تعداد کاربران آنلاین و متصل به هر نود.این داشبوردها به ما اجازه داد گلوگاهها را قبل از اینکه کاربر متوجه کندی شود، شناسایی کنیم.نتیجه مانیتورینگ بسیار جالب بود. با وجود ورود ۱۹۰۰ پیام در ثانیه، به لطف سرعت بالای معماری، نمودارها نشان میدادند که در هر لحظه نهایتاً ۱۵ پیام داخل Channel باقی میماند و بلافاصله تخلیه میشد. این یعنی تأخیر پردازش داخلی ما عملاً صفر (Near Zero) بود. این نشون میده Channel چقدر عالیه ♥️بنچمارک و کاراییشاید بپرسید نتیجه این همه وسواس در انتخاب تکنولوژی (Redis In-Memory + SSE + Channels) چه بود؟ نتایج در محیط عملیاتی (Production) فراتر از انتظار ما بود.معماری ما به شدت سبک (Lightweight) از آب درآمد. ما توانستیم با منابعی بسیار محدود، ترافیک عظیمی را هندل کنیم:معماری: کلاستر کوبرنتیز شامل ۶ پاد (Pod) فعال.مصرف رم: هر پاد به طور میانگین تنها ۵۵ مگابایت رم مصرف میکند.مصرف پردازنده: در اوج ترافیک بازار، مصرف CPU هر پاد حدود ۲۰٪ است.ظرفیت: این کلاستر کوچک، پاسخگوی چند ده هزار کانکشن باز همزمان است، بدون اینکه کلاینت ها افت سرعتی احساس کنند.این بهره وری بالا (High Efficiency) به دلیل حذف سربارهای اضافی پروتکل هایی مثل WebSocket و استفاده بهینه از حافظه توسط System.Threading.Channels است.این یعنی ما عملاً با سخت افزاری بسیار سبک، در حال سرویس دهی به ده ها هزار کاربر بدون هیچگونه افت کیفیت یا تأخیر هستیم.فوت کوزه گریدر مسیر پیاده سازی، با چند دیوار محکم برخورد کردیم که تجربه آنها برای هر تیمی ارزشمند است:چالش پراکسی (The Proxy Trap): وقتی اولین بار سرویس را بالا آوردیم، متوجه شدیم برخی کاربران دیتا را با تأخیر میگیرند یا کلا قطع میشوند. مشکل از کدهای ما نبود، بلکه از زیرساخت شبکه و پراکسیها (مثل Nginx یا پراکسیهای سازمانی) بود. تکنولوژی SSE یک اتصال باز طولانی است. بسیاری از پراکسی ها به طور پیشفرض پاسخ ها را &#34;بافر&#34; (Buffer) میکنند تا یکجا بفرستند، که این کار SSE را خراب میکند.راه حل: ما هدرهای X-Accel-Buffering: no را تنظیم کردیم و مطمئن شدیم تنظیمات پراکسی ها اجازه عبور استریم های طولانی بدون بافرینگ را میدهند.چالش نشت حافظه (Memory Leak): خطرناکترین سناریو در این معماری، کاربرانی هستند که اینترنت شان قطع میشود یا تب را میبندند، اما سرور هنوز دارد برایشان در Channel اختصاصی مینویسد. اگر این کانال ها پاک نشوند، سرور منفجر میشود.راهحل: ما مکانیزم RequestAborted در HttpContext را به شدت جدی گرفتیم. به محض اینکه سیگنال قطع ارتباط از کلاینت بیاید (TCP FIN)، ما بلافاصله Channel مربوطه را Dispose کرده و از لیست Coordinator حذف میکنیم.مقیاسپذیری (Scale): برای مدیریت روزهای شلوغ بازار اگر لود مون ۲ برابر شد چیکار کنیم ؟ آیا worst case رو دیدم برای مقیاس شدن.راه حل: از HPA (Horizontal Pod Autoscaler) در کوبرنتیز استفاده کردیم. سیستم ما به گونهای تنظیم شده که با افزایش تعداد کاربران و بالا رفتن مصرف CPU و RAM، به طور خودکار تعداد پادها را افزایش میدهد تا همیشه پاسخگوی نیاز کاربران مفید باشد.چالش قطع شدن کانکشن: وقتی کلاینت درخواست SSE رو ارسال میکنه ممکنه در اون لحظه ما دیتایی برای ارسال نداشته باشیم. برای مثال ممکنه یک سهم یا صندوق برای لحظاتی بسته شده و قیمت جدیدی وجود نداره. در این حالت مرورگر درخواست رو cancel میکنه و باعث retry و خطا میشه.راه حل: ما از مکانیزم tcp ایده گرفتیم که در بازه های مختلف HB (heartbeat) ارسال میکنه که هم باعث میشه سلامت اتصال بررسی بشه هم باعث بسته نشدن اتصال و استفاده مجدد بشه. ما هم اومدیم در فاصله زمانی حدود 20 ثانیه از سمت سرور یک مسیج HB تولید میکنیم که اتصال پایدار بمونه.نتیجه گیریاین پروژه برای ما یادآوری کرد که در سیستم‌های real-time همیشه پیچیده‌ترین ابزار، بهترین جواب نیست. Kafka را برای جایی نگه داشتیم که durability و replay مهم بود؛ Redis Pub/Sub را برای fan-out سریع و ephemeral انتخاب کردیم؛ در backend با System.Threading.Channels یک مسیر سبک و کم‌سربار برای توزیع پیام‌ها ساختیم؛ و در سمت کلاینت با SSE، بدون نیاز به پروتکل دوطرفه، قیمت‌ها را با latency پایین push کردیم.نکته‌ی کلیدی این بود که قبل از انتخاب ابزار، ماهیت داده را درست فهمیدیم: قیمت لحظه‌ای داده‌ای replaceable است، نه event غیرقابل‌از‌دست‌رفتن. همین تصمیم باعث شد بتوانیم در جاهایی مثل Redis Pub/Sub و DropOldest آگاهانه loss محدود را بپذیریم و در عوض freshness، سادگی و پایداری سیستم را حفظ کنیم.در نهایت، موفقیت این معماری فقط نتیجه‌ی انتخاب تکنولوژی نبود؛ نتیجه‌ی ترکیب درست monitoring، rollout تدریجی، snapshot/re-sync، heartbeat، feature flag و همکاری نزدیک بین تیم محصول، backend، frontend و SRE بود.</description><category>Mohsen Rajabi</category><author>Mohsen Rajabi</author><pubDate>Tue, 09 Jun 2026 15:18:03 +0330</pubDate></item><item><title>چرا استفاده از استثناها باید استثنایی باشند ؟</title><link>https://virgool.ir/@m.rajabi/چرا-استفاده-از-استثناها-باید-استثنایی-باشند-afg88mmbjcmq</link><description>در مقاله ایی که Matt Warren نوشته، خیلی خوب توضیح میده که، چرا استثناها باید استثنایی باشند.Why Exceptions should be Exceptionalنکته ایی که وجود داره این هست که باید استفاده از Exception به صورت نادر و خاص باشد و نباید در فلو عادی برنامه رخ دهد. برای مثال در دستورات العمل های کد نویسی NASA استفاده از Exception در خیلی مواقع ممنوع است. در دستور العمل های ماکروسافت برای توسعه هم استفاده از Exception در فلو عادی ممنوع است. بیایید یک نمونه از بنچمارک که exception تولید میکنه و بدون اون رو ببینیم. متد result بدون exception هست و متد exception متدی هست که throw exception داره. تفاوت سرعت رو مشاهده کنید.نمونه 1نمونه ۲همیشه سعی کنید برای مدیریت خطا هاتون از Result Pattern استفاده کنید. لینک آموزشتا اینجا مزیت استفاده کمتر از Exception رو دیدیم. اما در Net. چه اتفاقی افتاد.همه چیز از یک issues در سال 2019 روی پروژه runtime شروع شد. این درخواست توسط chrishaly ایجاد شده بود. ماجرای این issues این بود که چرا ایجاد Exception روی Net. حدود 10 برابر کند تر از زبان Java است. در این درخواست یک سناریو ایجاد Exception به زبان Net. و Java نوشته شده بود که نشان می داد در Net. بسیار کند تر عمل میکنه.به فاصله کوتاهی این موضوع در یک issues دیگر مطرح شد.لینک issuesلینک issuesبریم یک نگاهی کنیم به کد های ماکروسافت، اگر نگاهی به کد های net. بندازید متوجه میشید که خیلی جاها از throw exception برای هندل کردن خطا استفاده شده است و این باعث کندی و اختلال زیاد می شود. ایجاد Exception بسیار کند هست.برای نمونه من یک پروژه متوسط نوشته شده که هیچ exception دستی در آن ایجاد نمی شود و بر اساس Result Pattern خطا ها هندل می شود، متریک ش رو بهتون نشون میدم. حالا نگاه کنید چه مقدار exception رخ میدهد ؟ به تصویر زیر دقت کنید بیشتر از 1000 بار exception رخ می دهد. خود ماکروسافت سعی کرده در جاهایی که Exception میدهد رو بازنویسی کنه. مثل این قسمت که در HttpConnectionPool که throw exception باعث کندی زیادی میشد در Net 7. تغییر داده. لینکاما بزرگترین تغییری که در این زمینه داشتیم در Net 9. بود که باز طراحی قسمت Runtime و CoreCLR ماکروسافت توانست بهبودی زیادی ایجاد کند. چند مورد از بهبود ها در این قسمت ها انجام شده است.لینکلینکلینکنتیجه این تغییرات باورنکردی بود. در سناریو های مختلف بین حدود 50 تا 76 درصد بهبود.همین تغییرات نسبتا ساده باعث شده شما با آپدیت Net. خودتون یک performance زیادی به دست بیاورد.</description><category>Mohsen Rajabi</category><author>Mohsen Rajabi</author><pubDate>Mon, 23 Dec 2024 23:17:38 +0330</pubDate></item><item><title>چگونه پروژه Net. را در سال 2023 شروع کنیم</title><link>https://virgool.ir/@m.rajabi/چگونه-پروژه-net-را-در-سال-2023-شروع-کنیم-dtjutmhy1uqu</link><description>در این مقاله در مورد معماری پروژه صحبت نمی کنیم. این مقاله بدون توجه به معماری پروژه شما است. (معماری تمیز، برش عمودی، Nlayer، معماری شش ضلعی و…).در این مقاله، بهترین روش ها و ابزارها برای شروع یک پروژه net. در سال 2023 را مورد بحث قرار می دهیم. همچنین یاد می گیریم که چگونه از ابزارها و تحلیلگرها برای بهبود کد استفاده کنیم.  استفاده از best practice هااستفاده از آخرین ویژگی ها زبان و ابزار هارعایت اصول Clean Codeیکسان سازی دست خط برنامه نویس هابهبود کدچک کردن کیفیت کد و پیدا کردن باگچک کردن مشکلات امنیتی کدآیا تا به حال فکر کرده اید که پروژه ای مانند AspCore در GitHub بیش از 1100 توسعه دهنده دارد، چگونه مدیریت می شود؟ چگونه دست خط یکسان است؟ بررسی کد چگونه انجام می شود؟ best practice ها یا موارد بهینه سازی چگونه مشاهده می شود؟در سال های اخیر، پیشرفت های زیادی در net. انجام شده است. به خصوص ازnet 5. به بعد، می توانیم علاوه بر تحلیلگرهای نوشته شده دیگران، از آنالیزهای اختصاصی .net استفاده کنیم.این مقاله تجربه شخصی من در تیم های بزرگ نرم افزاری و مقالات متعدد است.در این مقاله سعی داریم از بهترین ابزارها و تنظیمات برای بهبود کد و حل مشکلات استفاده کنیم.تجربه من نشان داده است که وقتی از این ابزارها استفاده می کنید.بسیاری از باگ های نرم افزاری ساده قبل از اجرای کد حل می شوند!اگر در یک تیم بزرگ کار می کنید، این مقاله به شما کمک می کند تا ساختار و دست خط کد بین تیم را دنبال کنید.این مقاله به شما کمک می کند تا Clean Code را در پروژه رعایت کنید و از Smell Code جلوگیری کنید.این مقاله به شما کمک می کند تا کد را ساده و کارآمد نگه داریداین مقاله به شما کمک می‌کند تا بسیاری از کارهای دستی را که در فرآیند بررسی کد انجام می‌شوند و مستعد خطا هستند، خودکار کنید.به مثال های زیر توجه کنید:چگونه می توانیم بررسی کنیم که کدی که نوشته ایم پیچیدگی دارد؟چگونه می توانیم Smell Code را در داخل کد پیدا کنیم؟چگونه متوجه می شویم که شرایط بسیار متفاوتی در داخل متد وجود دارد (به دلیل وجود سوئیچ های if و مختلف)؟همه موارد فوق را با چشم و خطای انسانی می توانیم درک کنیم. اگر به طور خودکار همه موارد بالا را بفهمیم و حتی به کد اجازه ندهیم چه می شود?خب بریم سر کارابتدا ساختار فایل های ما باید اینگونه باشدفایل Directory.Build.propsابتدا در مورد Directory.Build.props صحبت خواهیم کرد.در این فایل تنظیمات کل پروژه را مشخص می کنیم.از چه نسخه ای از آنالایزر استفاده می کنیم؟در این تنظیمات مشخص می کنیم که برنامه روی کدام نسخه از SDK تنظیم شودتنظیمات آنالایزرتنظیمات آنالایزرهای شخص ثالثفایل زیر بر اساس تجربه من در تیم ها و پروژه های بزرگ است. در دنیای تحلیلگرها، اینها بهترین هستند. https://gist.github.com/EngRajabi/c33855cfa41e555ec0303458a8c62fe5 انجام تمام تنظیمات در این مقاله یک کار طولانی و دشوار است. در اینجا در مورد برخی تنظیمات صحبت خواهیم کرد.LangVersion: این ویژگی نسخه زبان سی شارپ را که روی آخرین نسخه تنظیم شده است را مشخص می کندAnalysisLevel: این ویژگی نسخه تحلیلگر را مشخص می کند. که روی آخرین نسخه تنظیم شده است. جزئیات بیشترTreatWarningsAsErrors و CodeAnalysisTreatWarningsAsErrors: فعال کردن این 2 ویژگی باعث می شود که Warnings در حالت Error نمایش داده شود بنابراین باید آنها را برطرف کنیم.RunAnalyzerDuringBuild: این ویژگی باعث می شود که آنالیز در حین build اجرا شودRunAnalyzersDuringLiveAnalysis: این ویژگی باعث می شود که آنالیز در حین کدنویسی اجرا شود.EnforceCodeStyleInBuild: این ویژگی باعث می شود که خطاهای Code Style به عنوان Error اضافه شوند.ManagePackageVersionsCentrally: این ویژگی برای مدیریت بسته ها در سطح جهانی استفاده می شود. جزئیات بیشترNullable: این ویژگی قابلیت Nullable را فعال می کند. جزئیات بیشترNoWarn و NoError: برای تنظیم خطاهایی که می خواهیم نادیده بگیریم.ReportAnalyzer: این ویژگی گزارش تحلیلگر را فعال می کندAnalysisMode: این ویژگی تنظیمات تحلیلگر .net را مشخص می کند که همه در اینجا فعال هستند. جزئیات بیشترFeatures: این ویژگی به کامپایلر می گوید که دقیق تر عمل کند. جزئیات بیشترEnableNETAnalyzers: این ویژگی آنالایزرهای .net را فعال می کند.در &lt;!-- third-party analyzers --&gt; ما شروع به اضافه کردن تحلیلگرهای شخص ثالث کردیم. این آنالایزرها به همراه net. به ما کمک می کنند تا کد بهتری داشته باشیم. در مورد معرفی آنالایزرها می توانید لینک nuget آنها را بررسی کنید.Microsoft.CodeAnalysis.NetAnalyzers: این تحلیلگر به صورت پیش فرض در SDK موجود است. اما اگر نیاز به استفاده از آخرین نسخه آن دارید، این بسته را نصب کنید. به همین دلیل است که ما این بسته را نصب می کنیم.Microsoft.VisualStudio.Threading.Analyzers: این تحلیلگر مایکروسافت است که با خطاها و مشکلات Threading سروکار دارد.Microsoft.CodeAnalysis.BannedApiAnalyzers: این تحلیلگر به ما کمک می کند تا استفاده از یک سری کدها را منسوخ اعلام کنیم. مانند استفاده از DateTimeOffset.UtcDateTime به جای DateTimeOffset.DateTime. این تنظیمات را در فایل BannedSymbols.txt قرار می دهیمسری تحلیلگرهای Roslynator: این آنالایزرها یک سری نقش های مفید را به پروژه اضافه می کنند.SonarAnalyzer.CSharp: این آنالایزر بسیار محبوب SonarQube است که بسیار مفید است.Meziantou.Analyzer: این تحلیلگر توسط Meziantou که یکی از توسعه دهندگان مایکروسافت است نوشته شده است.SecurityCodeScan.VS2019: این تحلیلگر برای بررسی های امنیتی استفایل .editorconfigمی توانید تنظیماتی را در این فایل قرار دهید که در IDE بررسی می شود.ما یک سری تحلیلگر را در بالا تعریف کردیم. ما می توانیم برخی از تنظیمات آنها را با این فایل تغییر دهیم.به عنوان مثال، خطای ایجاد شده توسط هر یک از آنالایزرها را می توان به عنوان یک پیشنهاد در این فایل مطرح کرد. یا حتی آن را خاموش کنید. یا در اینجا یک خطای جدید ایجاد کنید.فایل زیر بر اساس تجربه من و تقلید از پروژه های بزرگ مانند AspCore است. همچنین CodeMetrice به آن اضافه شده است که پیچیدگی کد را محاسبه می کند و در صورت بالا بودن پیچیدگی خطا ایجاد می کند.پیشنهاد میکنم از این فایل استفاده کنید https://gist.github.com/EngRajabi/63779b0d02eea78ce220ff7a49e5a068 فایل Directory.Packages.propsدر این فایل ما بسته های nuget را مدیریت می کنیم. https://gist.github.com/EngRajabi/f1a44483dd54915e1544e16afa9de15d فایل BannedSymbols.txtدر این فایل می توانیم یک سری ویژگی های net. را به صورت غیرمجاز تعریف کنیم. این مانع از استفاده برنامه نویسان می شود. https://gist.github.com/EngRajabi/e66e1d16042e434d122dfe14c0bdae01 فایل .gitignoreدر این فایل مواردی را که نیازی به اضافه شدن به source controle ندارند را مشخص می کنیم. این فایل حاوی best practice ها است. https://gist.github.com/EngRajabi/9ce5f603c2c0f72da60a5251785346d8 فایل global.jsonدر این فایل ما SDK پروژه را پیکربندی می کنیم. با این تنظیمات، ما مشخص می کنیم که پروژه ما از کدام نسخه SDK استفاده می کند و شما مجاز به ساخت با کدام نسخه هستید. https://gist.github.com/EngRajabi/966e06b758be47f75af93c2371df387e فایل nuget.configدر این فایل مشخص می کنیم که از کدام repo برای بسته ها استفاده کنیم. در سازمان های بزرگ آدرس repo شرکت در اینجا مشخص می شود. اگر مخزن خاصی ندارید، به این فایل نیازی نیست. https://gist.github.com/EngRajabi/5183e95fc65e04272cd8e9f9e3845041 فایل AssemblyInfo.csدر این فایل تنظیمات CLS را مشخص می کنیم. https://gist.github.com/EngRajabi/a314fb17ca48e3855c3a4f84ffa7381e با افزودن فایل ها و تنظیمات فوق، پروژه شما دارای تحلیلگرهای مفیدی خواهد بود که باعث کاهش باگ، بهبود کد، کیفیت بالاتر کد، کد قابل نگهداری و یکنواخت شدن دست خط برنامه نویسان مختلف می شود.تجربه به من ثابت کرده است. اکثر آنالیزهایی که به برنامه نویسان اعلام می شد جدید بود و آنها از آن اطلاعی نداشتند و این تحلیل ها باعث یادگیری آنها شد.با رشد تیم و افزایش تعداد افراد، این تنظیمات بهره وری کار را تا حد زیادی افزایش می دهد و کیفیت کار را بهبود می بخشد.این تحلیل ها در سیستم هر برنامه نویس شروع به کار می کنند. اگر برنامه نویسی موارد خطا را مدیریت نکند، پروژه Build نخواهد شد. اگر کد خود را commit کند، در فرآیند CICD هنگام Build پروژه، تمام آنالیزها انجام می شود و باعث می شود CICD با خطا مواجه شود. و نیاز به اصلاح دارد.اگر پروژه جدیدی ایجاد کرده اید، حتما از تنظیمات بالا استفاده کنید.اگر در حال حاضر پروژه ای دارید می توانید تنظیمات فوق را در آن قرار دهید، توجه داشته باشید که با تنظیمات بالا خطاهای زیادی ایجاد می شود. اگر بتوانید خطاها را برطرف کنید، ارزشش را دارد. و در آینده سود خواهید برد.زمانی که من سرپرست Backend شرکت SadadPSP بودم، تعداد برنامه نویسان دات نت 15 نفر بیشتر بود. من همیشه نگران نگهداری کد بوده ام.من همیشه مسئول بالا بردن کیفیت کد، حفظ دستخط کد پروژه و استفاده از ابزارهایی برای بهبود کار بودم.این مقاله حاصل تجربیات من در ابتدا در شرکت SadadPsp و اکنون در کارگذاری مفید است. امیدوارم استفاده کرده باشید.لطفا با دوستان خود به اشتراک بگذارید</description><category>Mohsen Rajabi</category><author>Mohsen Rajabi</author><pubDate>Fri, 28 Apr 2023 19:50:47 +0330</pubDate></item></channel></rss>