網站高並發,老出問題,真煩,我來研究研究


  很久以前我是學J2EE的,后來做了J2ME,又后來做了一點Android,再后來就學了PHP,勵志要用PHP在Web開發上有所發展。我上家公司的老板告訴我說:“你的方向錯了,現在是移動互聯網的時代,去搞PHP木有前途”。這話想來不錯,可是我覺得,再是移動互聯網的時代,它也得聯網不是,要聯網就得用PHP or RoR or Python or J2EE or ASP.NET等等寫聯網,所以,我學習PHP,是為了有個好基礎,有平台,其他才能做得起來。至於,選擇什么樣的技術實現,各有所愛,我覺得在我們這個小地方,用Java不合時宜,都是大公司用啊,.NET我不喜歡,畢竟是從Java陣營里出來的,這兩貨老死不相往來,RoR用的人好少,在我們這個小地方就更少了,不好就業,Python用的人也不多,也不好找工作,我對Python還是很有好感的,列在學習計划之列,前段時間學了一點,但是老是被中斷,工作上的事情一打擾,就不能夠有持續性,這樣學習的效果也不好,而且,我深刻體會到,要學以致用,才能記得牢,才能深刻理解內涵,我學了不用,而且斷斷續續,所以,學習的效果並不理想,看來,還得找個項目做做,才能基本掌握。話說,我學PHP,可是看書自學了半年,又在一家做網站的公司,也就是我的上家,用半生不熟的PHP寫了一個又一個具有實驗性的東西,才完全入門了。終於,跳槽到現在這家公司,從事真正的PHP開發工作了。

  廢話真多,書歸正傳。

  我們公司是做網貸平台的,到目前為止,我覺得我最大的貢獻,是解決了平台高並發請求時引發的一些問題。雖然我也做了很多其他的並不輕松的工作,但這對這個問題的解決,我還是很自豪的。因為,在我之前,這個問題沒有人真正解決掉,直到有一天,我研究投標模塊代碼的時候,想到了解決方法。

  我們每天上午10點開始投標,雖然用戶不是很多,但瞬間服務器的壓力還是有點的。這類似於秒殺,在那一時刻,會出現高並發的情況。每個標的都有一個總額,所有人的投標金額達到標的總額的時候,即是滿標。但由於高並發,會導致在滿標的一剎那,出現投標總金額超出標的總額的情況,然后會引發一系列其他問題。投標的流程是判斷是否滿標,如果沒滿,則該用戶可以投標;如果滿了,則中斷,並提示現在已滿標。問題的根本原因在於,在最后快滿標的那一秒,有多個用戶同時投標,當然不是絕對的同時,之間相差個幾毫秒,但你可以想象成同時。然后,每個請求同時判斷是否滿標,這時發現還沒有滿,則同時投標,投標數據進數據庫,並扣除用戶賬戶資金、發放獎勵、續投獎勵等一系列操作(所有用戶獎勵都會多發)。但實際上,標的不需要這么多資金了,這些同時投上的資金已然成為有效投資,最終導致超標的情況發生。出現這種情況后,我們就需要做一系列善后事宜,挨個給客戶打電話,說抱歉啦,金額超了,要扣除多發的獎勵啦。然后,技術再哼哧哼哧的改數據,煩死了,當然不是我悲催的改數據,是另外一位程序媛MM或我們技術部總監親自上陣。

  之前我們研究過解決方案,想是不是可以用隊列機制來解決。可是,PHP的運行機制,不太好搞隊列(當然有方法,但我們都對於此方法沒有經驗,不能貿然嘗試)。后來,我研究出可以用文件鎖的方式,類似於Java的同步鎖,可以寫同步代碼塊,這樣給判斷滿標的時候開始加鎖,到最后扣除賬戶金額、發完獎勵再釋放,就應該可以解決問題。可是,因為以前沒有用過這種方式,最后領導沒有采納,讓程序媛MM寫了一套代碼另外處理了,最后證明無效。關於文件鎖的使用,請自行google,以后我再寫一篇關於PHP文件鎖的技術文吧。

  后來我想,我們都是在投標之前去判斷是否滿標,這樣如果多個請求同時進來,那么就會同時判斷,必定會出現超標的情況。何不在投標數據進數據庫之后,我們再去判斷呢?於是,一道曙光照進了我的心田。

  搞PHP的,數據庫大多都用Mysql,上次我還特意研究了一下Mysql的鎖機制。很好的一點是,Mysql是自動加鎖的,當有一個insert or update請求進來的時候,會先鎖表,然后去執行insert or update操作,再釋放鎖,這樣就會避免多個請求同時修改表,造成數據混亂了。如果一個select和一個insert or update請求同時進來,則會先執行insert or update,再執行select。有了這個,我們就放心了。在一個請求的投標記錄進入數據庫之后,回返回該條記錄的id,我們立刻去根據此id查詢這條剛剛插進數據庫之前的記錄,獲得到之前的投標數據總金額,看看是否超標了,如果沒超,ok,程序繼續往下走,本條投標是有效的,只不過還得再判斷用戶的投標金額+之前的投標總額是否會超出標的總額,再做一下微處理;如果超了,那么,本條投標完全無效,程序立刻終止,提示用戶現在已滿標了。ok,問題解決。

  核心思想就在於,投標數據進數據庫之后,我們就可以把數據庫作為基礎,圍繞着它做文章,把數據庫中的數據作為唯一的參照標准,去分析在庫的數據。因為,Mysql有很好的鎖機制,我們可以很放心在庫的數據,從而能得到正確的分析結果。這樣,即使很多很多的請求同來到達,沒有問題,實際上每個請求只分析自己這條入庫的數據之前的記錄,然后只處理自己的記錄,從而不會影響別的請求,不會篡改別的請求寫入的數據,保證了正確的結果。

  代碼實現上,就很簡單了,加上花括號也不超過10行,思想是最重要的。

  現在,修復后的程序,跑了快兩個月了吧,天天投標,還沒有出現過超標的情況,並且,我很自信的說,今后也不會出現。當然像手機版等地方我還沒打補丁,如果用戶在那些地方投標,就可能超標,回頭還是要給補上。

  以前確實是沒怎么整過web,更別提這個高並發情況的處理,現在在這家公司,真的是積累了一些寶貴的經驗,還是很有收獲的。

----------------------------------------2014-04-11 補充----------------------------------------

  感謝園友的支持和評論,好多園友提出了不少寶貴的意見,其中,我看到有兩次提到“樂觀鎖”,於是我查了一下,明白了其中的奧妙,很不錯的,但是卻跟我們的業務不符,不能解決我們的問題。我畫了一張圖,不會用專業工具,手繪一張,見笑了,描述了投標流程:

投資表 -> 用戶投一次標,就往投資表中insert一次數據(應該還有個外鍵:借款id)

借款表 -> 主要字段:借款金額

其實流程中最關鍵的地方,是判斷向投資表中insert進去的投標數據是否有效,而園友提議用樂觀鎖,只能用於update的情況,而阻止不了insert,所以我覺得這種樂觀鎖的方式,不適用我們業務需求。

當然,還是很感謝園友提出的寶貴建議,讓我和其他人也能知道的更多。

----------------------------------------2014-04-12 補充----------------------------------------

有園友留言說,這么簡單的問題都沒考慮周到,程序弱爆了。還有人說用鎖和事務還有存儲過程可以解決問題。可能好多人多沒真正理解我要表達的問題,正好,58樓留言的同學,是唯一一個理解我的人,評論的很中肯。當然,理解不全面也是可以理解的,畢竟大家只是聽我敘述,沒有看實際的業務代碼,理解就有偏差了,沒關系,能給別人以參考幫助,就足以,也是記錄一下我的這個技術歷程。

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM