2012-02-15 17 views
19

我需要在PHP中使用互斥鎖或信號量,它嚇壞了我。爲了澄清,我不害怕編寫無死鎖的代碼,這些代碼能夠正確同步或者擔心併發編程的風險,但是PHP處理邊緣情況的能力如何。在PHP中預防競爭條件的最可靠和安全的方法

快速背景:寫一個位於用戶和第三方信用卡網關之間的信用卡處理程序接口。需要防止重複的請求,並且已經有一個適用的系統,但是如果用戶點擊提交(w/out JS啓用,所以我不能禁用它們的按鈕)幾毫秒,一個競爭條件發生在我的PHP腳本沒有意識到已經有重複的請求。需要一個信號量/互斥量,所以我可以確保每個唯一事務只有一個成功的請求。

我在多核心Linux機器上通過PHP-FPM與多進程運行PHP後面的nginx。我想確定

  1. 信號量在所有php-fpm進程和所有核心(i686內核)之間共享。
  2. php-fpm處理一個PHP進程崩潰,同時持有一個互斥量/信號量並相應地釋放它。
  3. php-fpm在持有互斥鎖/信號量時處理會話中止並相應地釋放它。

是的,我知道。非常基本的問題,認爲任何其他軟件都不存在適當的解決方案是愚蠢的。但是,這是PHP,它肯定不是建立在考慮併發的情況下,它經常崩潰(取決於你已經加載了哪些擴展),並且處於不穩定的環境中(PHP-FPM和Web)。

關於(1),我假設PHP是否使用POSIX函數,這兩個條件在SMP i686機器上都適用。至於(2),我從短暫略讀文檔中看到有一個參數決定了這種行爲(儘管爲什麼有人希望PHP不釋放互斥鎖,但是會話被終止了,我不明白)。但(3)是我的主要關注點,我不知道假設php-fpm能夠正確處理所有邊緣案例是否安全。我(顯然)永遠不想要死鎖,但我不確定我可以信任PHP從不讓我的代碼處於無法獲取互斥鎖的狀態,因爲抓住它的會話不是正常地就是非正常地終止。

我已經考慮過使用MySQL LOCK TABLES的方法,但這裏有更多的疑問,因爲雖然我相信MySQL的鎖比PHP鎖更多,但我擔心如果PHP在緩存MySQL的同時中止請求(帶* out *崩潰)會話鎖,MySQL可能會鎖定表(尤其是因爲我可以輕鬆設想導致此問題發生的代碼)。

老實說,我最舒服的是一個非常基本的C擴展,我可以準確地看到POSIX調用正在做什麼以及用什麼參數來確保我想要的確切行爲......但我並不期待編寫代碼。

任何人都有關於PHP願意分享的任何與併發有關的最佳實踐?

+1

我不熟悉你的設置,但是當我看到你需要從被處理停止重複請求,我想知道爲什麼你無法檢查存儲在用戶會話中的[表單鍵](http://net.tutsplus.com/tutorials/php/secure-your-forms-with-form-keys/)。 – nickb 2012-02-15 07:14:40

+0

這基本上是我現有的重​​復請求預防,而且似乎有一個特定的時間點,對於某些具有多個PHP進程的設置,存在與兩個PHP處理後端的爭用條件,請參閱token/nonce/whatever尚未處理。隨機數通常用於防止重放攻擊(或普通的XSS),但在我的情況下,它已經受到保護 - 除了數毫秒後作爲競爭條件重播的情況。 – 2012-02-15 07:28:25

+0

也許這(略有過時)的論文在PHP和會話鎖定/比賽條件對你很有意思。但是,似乎至少PHP 5.3.2已經在會話中使用一致的'flock()':http://thwartedefforts.org/2006/11/11/race-conditions-with-ajax-and-php-sessions/ – Kaii 2012-02-15 08:45:55

回答

8

事實上,我認爲沒有必要爲一個複雜的互斥/信號燈任何解決方案。

閱讀dqhendricks的回答,並做了一些研究之後,我發現通過$_SESSION這種形式的關鍵是你所需要的。在PHP中,會話被鎖定並且等待用戶會話被釋放。您只需在第一個有效請求上輸入表格密鑰unset()即可。第二個請求必須等到第一個請求釋放會話。

然而,在(未會話或源IP爲基礎)的負載均衡方案事情變得更復雜的運行時。對於這樣的情況,我敢肯定你會發現在這個偉大的一張有價值的解決方案:http://thwartedefforts.org/2006/11/11/race-conditions-with-ajax-and-php-sessions/

我複製你的使用情況與下面的演示。只是把這個文件到你的網絡服務器,並對其進行測試:

<?php 
session_start(); 
if (isset($_REQUEST['do_stuff'])) { 
    // do stuff 
    if ($_REQUEST['uniquehash'] == $_SESSION['uniquehash']) { 
    echo "valid, doing stuff now ... "; flush(); 
    // delete formkey from session 
    unset($_SESSION['uniquehash']); 
    // release session early - after committing the session data is read-only 
    session_write_close(); 
    sleep(20); 
    echo "stuff done!"; 
    } 
    else { 
    echo "nope, {$_REQUEST['uniquehash']} is invalid."; 
    }  
} 
else { 
    // show form with formkey 
    $_SESSION['uniquehash'] = md5("foo".microtime().rand(1,999999)); 
?> 
<html> 
<head><title>session race condition example</title></head> 
<body> 
    <form method="POST"> 
    <input type="hidden" name="PHPSESSID" value="<?=session_id()?>"> 
    <input type="text" name="uniquehash" 
     value="<?= $_SESSION['uniquehash'] ?>"> 
    <input type="submit" name="do_stuff" value="Do stuff!"> 
    </form> 
</body> 
</html> 
<?php } ?> 
+1

+1會話被鎖定.... – zaf 2012-02-15 08:52:18

+0

感謝您的鏈接,它給了我需要的信息。但是PHP真的需要把他們的遊戲推上去 - 爲了會話保護而默認爲'flock'。真?我希望使用信號量/互斥/ mysql /等,以避免性能方面的原因! – 2012-02-15 15:21:48

+0

該文檔的後半部分給出了我需要釋放MySQL鎖的所有信息(尤其是MySQL會釋放鎖,如果會話死了,不一定如果它中止假設持久連接),我需要推出我自己的會話處理程序。謝謝!順便說一句,上面的代碼示例仍然容易受到競爭條件的影響,如果兩個請求都在第5行比較之後,請求達到未設置狀態。 – 2012-02-15 15:32:44

0

如果問題只發生在按下幾毫秒的時間間隔之後,是不是軟件的去抖動器會工作?就像在一個會話變量中保存一個按鈕的時間一樣,並且不允許多一秒鐘?只是一個早晨喝咖啡的想法。乾杯。

+0

這基本上就是我現有的重​​復請求預防,而且似乎有一定的時間點,對於某些具有多個PHP進程的設置,存在與兩個PHP處理後端的爭用條件,請參閱token/nonce/whatever尚未處理。 – 2012-02-15 07:29:16

1

您可以將隨機散列存儲在會話數據中的數組中,並將散列打印爲隱藏表單輸入值。當請求進入時,如果會話數組中存在隱藏的哈希值,則可以從會話中刪除哈希並處理該表單,否則不要。

這應該防止重複表單提交,以及幫助防止CSRF攻擊。

+0

請看我對這個問題本身的評論。我已經擁有隨時可以防止重播和CSRF/XSS攻擊。它們不會阻止競爭條件,因爲會話或mysql訪問不是原子的。 – 2012-02-15 07:31:37

+0

會話寫* *原子通過'flock()' – Kaii 2012-02-15 15:27:15

1

你有一個有趣的問題,但你沒有任何數據或代碼來顯示。

對於80%的情況,由於PHP本身而導致的任何惡意事件發生的機率幾乎爲零,如果遵循有關阻止用戶多次提交表單的標準程序和實踐,這幾乎適用於所有其他設置,而不僅僅是PHP 。

如果你是20%,你的環境需要它,那麼一種選擇是使用消息隊列,我敢肯定,你所熟悉的。同樣,這個想法是語言不可知的。與語言無關。其全部關於數據如何移動。

0

我爲了防止會話比賽條件中的代碼做的是,在會話存儲數據的最後一次操作後,我用PHP函數session_write_close()通知,如果你使用的是PHP 7,你需要在PHP中禁用默認的輸出緩衝。 INI。如果你有耗時的操作,在調用session_write_close()之後執行它們會更好。

我希望這會幫助別人,對我來說它救了我的命:)