"إذا أراد العامل أن يؤدي عمله بشكل جيد، فعليه أولاً أن يشحذ أدواته." - كونفوشيوس، "مختارات كونفوشيوس. لو لينجونج"
الصفحة الأمامية > برمجة > كيفية التعامل مع ظروف السباق باستخدام Java وPostgreSQL

كيفية التعامل مع ظروف السباق باستخدام Java وPostgreSQL

تم النشر بتاريخ 2024-08-01
تصفح:683

How to deal with race conditions using Java and PostgreSQL

استخدام القفل للتحكم في تزامن قاعدة البيانات

تخيل أنك تعمل على نظام للتجارة الإلكترونية ويحاول الآلاف من الأشخاص شراء آخر منتج متبقي في نفس الوقت. ومع ذلك، يمكن للعديد منهم المتابعة إلى الخروج وإنهاء الطلب. عند التحقق من المخزون الخاص بك، لديك منتج بكمية سلبية. كيف كان هذا ممكنا، وكيف يمكنك حل هذا؟

دعونا نبرمج! أول شيء قد تفكر فيه هو التحقق من المخزون قبل الخروج. ربما شيء من هذا القبيل:

public void validateAndDecreaseSolution(long productId, int quantity {
    Optional stockByProductId = 
 stockRepository.findStockByProductId(productId);

    int stock = stockByProductId.orElseThrow().getStock();
    int possibleStock = stock - quantity;

    if (stock 



يمكنك استخدام هذا التحقق، ولكن عندما نتحدث عن مئات أو آلاف أو ملايين أو حتى عشرات الطلبات في الثانية، فإن هذا التحقق لن يكون كافيًا. عندما تصل 10 طلبات إلى هذا الجزء من التعليمات البرمجية في نفس الوقت بالضبط وترجع قاعدة البيانات نفس القيمة لـ StockByProductId، فسوف ينكسر الرمز الخاص بك. أنت بحاجة إلى طريقة لحظر الطلبات الأخرى أثناء قيامنا بهذا التحقق.

الحل الأول - للتحديث

أضف بيان قفل على SELECT الخاص بك. في هذا المثال قمت بذلك باستخدام FOR UPDATE مع Spring Data. كما تقول وثائق PostgreSQL

للتحديث يؤدي إلى قفل الصفوف التي تم استردادها بواسطة عبارة SELECT كما لو كانت للتحديث. وهذا يمنع تعديلها أو حذفها بواسطة معاملات أخرى حتى تنتهي المعاملة الحالية.

@Query(value = "SELECT * FROM stocks s WHERE s.product_id = ?1 FOR UPDATE", nativeQuery = true)
Optional findStockByProductIdWithLock(Long productId);
public void validateAndDecreaseSolution1(long productId, int quantity) {
    Optional stockByProductId = stockRepository.findStockByProductIdWithLock(productId);

    // ... validate

    stockRepository.decreaseStock(productId, quantity);
}

ستنتظر جميع الطلبات المقدمة إلى جدول الأسهم باستخدام معرف المنتج حتى انتهاء المعاملة الفعلية. الهدف هنا هو التأكد من حصولك على آخر قيمة محدثة للسهم.

الحل الثاني - pg_advisory_xact_lock

هذا الحل مشابه للحل السابق، ولكن يمكنك تحديد مفتاح القفل. سنقوم بقفل المعاملة بالكامل حتى ننتهي من جميع عمليات التحقق من الصحة وتخفيض المخزون.

public void acquireLockAndDecreaseSolution2(long productId, int quantity) {
    Query nativeQuery = entityManager.createNativeQuery("select pg_advisory_xact_lock(:lockId)");
    nativeQuery.setParameter("lockId", productId);
    nativeQuery.getSingleResult();

    Optional stockByProductId = stockRepository.findStockByProductId(productId);

    // check stock and throws exception if it is necessary

    stockRepository.decreaseStock(productId, quantity);
}

لن يتفاعل الطلب التالي إلا مع منتج بنفس المعرف بعد انتهاء هذه المعاملة.

الحل الثالث - جملة WHERE

في هذه الحالة، لن نقفل صفنا أو معاملتنا. دعونا نسمح لهذه المعاملة بالاستمرار حتى بيان التحديث. لاحظ الشرط الأخير: المخزون > 0. وهذا لن يسمح لمخزوننا بأن يكون أقل من الصفر. لذا، إذا حاول شخصان الشراء في نفس الوقت، فسوف يتلقى أحدهما خطأ لأن قاعدة بياناتنا لن تسمح بالمخزون

@Transactional
@Modifying
@Query(nativeQuery = true, value = "UPDATE stocks SET stock = stock - :quantity WHERE product_id = :productId AND stock > 0")
int decreaseStockWhereQuantityGreaterThanZero(@Param("productId") Long productId, @Param("quantity") int quantity);

خاتمة

يستخدم الحلان الأول والثاني القفل المتشائم كاستراتيجية. والثالث هو قفل متفائل. يتم استخدام استراتيجية القفل المتشائمة عندما تريد الوصول المقيد إلى أحد الموارد أثناء قيامك بتنفيذ أي مهمة تتضمن هذا المورد. سيتم تأمين المورد الهدف لأي وصول آخر حتى تنتهي من العملية. كن حذرا مع الجمود!

مع القفل المتفائل، يمكنك إجراء استعلامات مختلفة على نفس المورد دون أي حظر. يتم استخدامه عندما يكون من غير المحتمل حدوث صراعات. عادةً، سيكون لديك إصدار مرتبط بالصف الخاص بك، وعندما تقوم بتحديث هذا الصف، ستقارن قاعدة البيانات إصدار الصف الخاص بك بإصدار الصف في قاعدة البيانات. وإذا كان كلاهما متساويين، فسيكون التغيير ناجحا. إذا لم يكن الأمر كذلك، عليك إعادة المحاولة. كما ترون، لا أستخدم أي صف إصدار في هذه المقالة، لكن الحل الثالث لا يمنع أي طلبات ويتحكم في التزامن باستخدام شرط المخزون > 0.

إذا كنت تريد رؤية الكود الكامل، يمكنك التحقق من ذلك على GitHub الخاص بي.

هناك العديد من الاستراتيجيات الأخرى لتنفيذ القفل المتشائم والمتفائل، يمكنك البحث أكثر عن التحديث مع تخطي القفل على سبيل المثال.

بيان الافراج تم إعادة نشر هذه المقالة على: https://dev.to/ramoncunha/how-to-deal-with-race-conditions-using-java-and-postgresql-4jk6?1 إذا كان هناك أي انتهاك، يرجى الاتصال بـ Study_golang@163 .com لحذفه
أحدث البرنامج التعليمي أكثر>

تنصل: جميع الموارد المقدمة هي جزئيًا من الإنترنت. إذا كان هناك أي انتهاك لحقوق الطبع والنشر الخاصة بك أو الحقوق والمصالح الأخرى، فيرجى توضيح الأسباب التفصيلية وتقديم دليل على حقوق الطبع والنشر أو الحقوق والمصالح ثم إرسالها إلى البريد الإلكتروني: [email protected]. سوف نتعامل مع الأمر لك في أقرب وقت ممكن.

Copyright© 2022 湘ICP备2022001581号-3