عنوان:

race condition


نویسنده: محمد هاتف شمشاد
تاریخ: ۱۴۰۳/۰۸/۱۳ ۱۶:۴۰
آدرس: www.dntips.ir
من یک برنامه تحت وب دارم که با استفاده از دات نت 8 و ef توسعه داده شده.
یکی از دامین های برنامه، فاکتور خرید است که هر فاکتور باید شماره فاکتور یکسانی به فرمت " تاریخ روز/شروع از عددی ثابت (به عنوان مثال 14030813/100) " داشته باشد و فاکتورهای بعدی در همان روز در بخش دوم یک واحد افزایش پیدا میکنند (به عنوان مثال 14030813/101). همچنین من داخل متد SaveInvoiceAsync خودم متد GenerateInvoiceNumberAsync رو صدا میزنم که کارش اینه که اخرین فاکتور روز رو از دیتابیس فراخوانی کنه و در صورت وجود به بخش دوم آن یک واحد اضافه کنه.

حالا چالشی که من دارم اینه که اگر زمانی دوتا درخواست همزمان برای ثبت فاکتور ارسال بشه و race condition اتفاق بیوفته عملا دوتا شماره فاکتور یکسان برای فاکتور ایجاد میشه و این باعث خطا در سیستم میشه و من میخوام که این اتفاق نیوفته و در لایه اپلیکیشن این موضوع هندل بشه

با توجه به این که هیج update روی انتیتی های انجام نمیشه و صرفا insert انجام میشه optimistic concurrency هم کمکی نمیکنه بهم.

ممنون میشم از دوستان هرکس تجربه مشابه و راه حلی داره به اشتراک بذاره

نظرات

  • وحید نصیری در ۱۴۰۳/۰۸/۱۳ ۱۷:۳۵
    اگر قصد مدیریت آن‌را در سمت برنامه دارید، مطلب «اتریبیوت اختصاصی برای قفل کردن یک اکشن جهت جلوگیری از تداخلات درخواست‌های همزمان» دقیقا به همین موضوع پرداخته. اگر هم از DNTCommon.Web.Core استفاده می‌کنید، ابتدا سرویس ILockerService آن‌را تزریق کنید و سپس از متدهای همزمان و غیرهمزمان آن بسته به نوع متد جاری، استفاده کنید. از همین سرویس، در سایت جاری هم استفاده می‌شود. اگر هم قصد مدیریت‌ آن‌را در سمت بانک اطلاعاتی دارید، مطلب «مشکل همزمانی خواندن و به روز رسانی اطلاعات در برنامه‌های وب» را مطالعه کنید.
    • محمد هاتف شمشاد در ۱۴۰۳/۰۸/۱۹ ۱۵:۳۴
      مطابق پیشنهادی که در مطلب «اتریبیوت اختصاصی برای قفل کردن یک اکشن جهت جلوگیری از تداخلات درخواست‌های همزمان» دادید، یک برنامه تستی با داشتن تنها یک موجودیت user ایجاد کردم که بر روی پراپرتی email مورد فوق را با استفاده از کتابخانه‌ی AsyncKeyedLock شبیه سازی و با استفاده از برنامه Apache JMeter ، تعداد 1000 درخواست را در 0.01 ثانیه به برنامه ارسال کردم (UserEntityRaceCondition.zip : فایل jmx ) که در این حالت مطابق تصویر زیر 6 ایمیل تکراری ثبت شد.



      • وحید نصیری در ۱۴۰۳/۰۸/۱۹ ۱۷:۳۱
        • برای مثال شما بهتر است یک unique index را بر روی فیلد ایمیل تشکیل دهید تا خود بانک اطلاعاتی، اعتبارسنجی مرتبطی را انجام دهد:
        builder.HasIndex(user => user.Email).IsUnique();
        • و یا به ابتدای متد CreateUserAsyncKeyedLock، یک سطر زیر را اضافه کنید (کل عملیات/تراکنش را در لاک قرار دهید و نه فقط قسمتی از آن‌را):
        using var @lock = await _asyncKeyedLocker.LockAsync("EmailCheck");
        • محمد هاتف شمشاد در ۱۴۰۳/۰۸/۲۰ ۱۱:۰۶
          استفاده از unique index در مثال فوق کاملا جوابگو است، همانطور که برای فیلد نام کاربری استفاده شده است. اما در سناریوهای رزرواسیون، در حالت هایی امکان استفاده از آن وجود ندارد.
          با این حال بنا به گفته شما کل عملیات متد CreateUserAsyncKeyedLock را در لاک قرار دادم و با تکرار حالت قبلی، تنها یک ایمیل در دیتابیس ذخیره شد.
          ممنون از شما🙏
  • همراز در ۱۴۰۳/۰۸/۱۳ ۲۳:۲۲
    استفاه از ترنزکشن یا مکانیزم لاک کردن فقط در قسمت شماره دهی.
    لاک کردن قدیمی ترش میشه مثال زنبیلی که دم در هست و هرکس زودتر بیاد برمیداره و میبره کارش رو انجام میده و بعد که کارش تموم شد، زنبیل رو دوباره دم در میگذاره. نفر بعدی که میاد و میبینه زنبیل نیست باید صبر کنه تا نوبتش بشه.
    البته من خودم از استورد پروسیجر و ترنزکشن در کانکشن استفاده می کنم.
    پاسخ آقای نصیری هم بسیار جامع و مانع است ولی من هنوز استفاده نکرده ام و در آینده حتماً به سراغش خواهم رفت.
    با تشکر