當我們再點下一頁镶摘,程序又正常工作了枕赵。剛剛出現(xiàn)的奇怪的那一幕睹限,其實就是由于并發(fā)產生的沖突。
在什么地方我們做了并發(fā)操作了呢豪硅?其實就是在一個用戶瀏覽頁面的同時,
還有人在往數據庫里面寫入數據挺物。你會發(fā)現(xiàn)thinkPHP這樣的框架懒浮,還是PHPCMS
這樣的開源系統(tǒng),他們都存在這樣的bug识藤。并發(fā)產生的問題砚著,往往難以捕捉,更
難以重現(xiàn)痴昧,而我們準備的這個案例稽穆,算是并發(fā)案例沖突中相對容易重新的典型案例我們如果調整一下剛剛操作的順序,我們會得到一些其他的結果剪个。
比如說:我們分頁條目數是每頁5條秧骑,在用戶瀏覽某一頁的時候,后
臺管理員發(fā)布了新的新聞扣囊,新聞的數量小于5條的情況下乎折,我們點下
一頁,會看到有幾條重復的新聞侵歇,也有更早的新聞這樣影響用戶體驗骂澄,
但畢竟我們點一下一的頁時候內容還是能接的上的,用戶瀏覽某一頁
的時候惕虑,后臺發(fā)布的新聞數量大于分頁條目數(5條)
那么再點擊下一頁坟冲,其中會有若干條新聞被跳過去了,無論用戶點多
少次下一頁溃蔫,都看不到那些條目健提,這種產生的并發(fā)沖突后果是很嚴重
的,而且普遍存在伟叛。他具有很好的隱蔽性私痹,在過去很多年幾乎不被察覺在大門戶網站時代,網站編輯并不會頻繁的發(fā)布新聞统刮,而用戶也很
少守著新聞列表去逐篇閱讀紊遵,然而在微博誕生以后,這種問題就暴露出來了侥蒙。
由于社區(qū)類應用信息的產生室友用戶產生的暗膜,不在是靠編輯在后臺發(fā)布的
,就好像微博鞭衩,每時每分秒学搜,都有很多用戶分享心得內容這樣一來你在查看微博列表的同時娃善,內容就已經更新 了 很多
,而且很多人打發(fā)無聊的時間瑞佩,很多人也會去逐一查看会放,不停的上劃,
把所有遺漏的條目全部看一遍這是如果項目設計不合理钉凌,產生并發(fā)事故咧最,就會對用戶體驗造成極大的影響。
來看一個更為常見的例子御雕,大家可能還記得矢沿,我們在微信群搞的抽獎的互動,
我們的上百份禮品酸纲,瞬間就被秒殺光捣鲸。在做這類搶購與秒殺抽獎等應用的時候,并發(fā)將導致更多的問題闽坡。通常
比較容易出現(xiàn)的bug有實際商品的訂單量大于庫存量栽惶。通俗點來說就是,明
明已經售完疾嗅,但還是有用戶買到了商品外厂,庫存值變?yōu)樨摰摹S只蛘呙髅髅霘?br> 到商品的用戶代承,訂單失敗汁蝶。還有企業(yè)的項目,在商品秒殺期間论悴,明明用戶數量不多掖棉,卻導致服務器
宕機。諸如此類的問題就不一一列舉了膀估。由于是文字直播幔亥,打字速度比較慢,
大家可以看現(xiàn)在正在進行的視屏直播看一下用常規(guī)思維來梳理業(yè)務流程程序是怎樣編寫的察纯。
還是以商城秒殺業(yè)務為例帕棉。首先我們需要用產品庫存這樣的一個字段來記
錄庫存信息,每當有用戶購買商品的時候捐寥,先查看庫存笤昨,判斷庫存大于0的時
候祖驱,用戶才能購買當用戶完成購買流程后握恳,將庫存數量減一,直到所有商品賣完捺僻,重復此過
程乡洼,直到庫存賣完秒殺活動結束崇裁。如果按常規(guī)的思路來設計,這樣的流程是沒
有問題的束昵,商品畢竟是一件一件賣出的拔稳,但是,在互聯(lián)網并發(fā)的情況下锹雏,就完
全不是這樣的巴比。要知道熱銷商品很有可能在同一時間,有多個用戶都在進行購買流程操作
按照之前的業(yè)務設計礁遵,假如有ABCD 4個用戶同時在秒殺某件商品時轻绞,庫存
僅剩2件,按照之前的業(yè)務流程設計查詢庫存大于0佣耐,就可以繼續(xù)后面的購買操
作并付款然而當任意用戶購買成功后庫存即減一政勃,ABCD4個用戶都認為自己查詢
時都有庫存,因此他們都可以完成購買流程兼砖,導致的結果就是庫存數為負數奸远。也
就是說,商品實際銷售量大于活動的商品數量讽挟,這樣會導致公司的虧損懒叛。
有些公司為了解決這個問題,采用了一種思路耽梅,雖說4個人同時操作芍瑞,但是交易
成功的這次網絡請求到達服務器的時間總會有個先后順序那么可以將訂單支付成功之后的庫存減一之后的值也隨訂單保存,如果這個
值小于0褐墅,就證明有用戶購買了產品拆檬,已經是賣完的,于是標記訂單失敗妥凳。這樣看上去避免公司造成額外的損失竟贯,但卻會給用戶帶來極大的不滿,是一
種極差的用戶體驗逝钥。它并沒有真正的解決我們的問題屑那。當然還有些公司解決方案
也不高明,我們知道無論是數據庫還是文件都可以給他加鎖艘款,在很早期的程序設
計和軟件開發(fā)里面持际,鎖是解決并發(fā)問題的萬能靈藥。無論是c++,或java哗咆,提到多
進程或多線程的時候蜘欲,往往也會提到鎖這個字。那么作為最早期的通用解決方案晌柬,
用到秒殺方案是否合適呢姥份?看一下加鎖后的工作流程:還是ABCD 4個用戶同時秒殺郭脂,他們都去查詢庫存。
當某一個用戶澈歉,比如A的請求展鸡,優(yōu)先到達時,我們就將數據表鎖住埃难,不讓其他的數
據庫連接來動這張表莹弊,待用戶A完成購買流程,將庫存量減一后涡尘,把鎖打開箱硕,其他
的連接才可以再次操作這張表。如此一來悟衩,可以保障一個用戶查看庫存以及庫存減一這段時間內剧罩,不可能還有
其他用戶可以對表做出修改,這一并發(fā)沖突的問題就沒有了座泳。不過這樣的做法真的
合適嗎惠昔?要知道ABCD 4個用戶都是在同一時間段去秒殺的,由于A用戶在操作中鎖
表挑势,導致其他用戶只能等待镇防,而且A完成整個業(yè)務需要消耗一段時間,只能等A完成
以后其他用戶才能操作這樣一來單位時間內的業(yè)務處理量會大幅降低潮饱,我們所看到的現(xiàn)象就是網站卡
死来氧,或者服務器宕機關于并發(fā)性能如何設計,我們可能需要單獨的一次或幾次課來
為大家講解香拉。不過鎖這種很原始的并發(fā)沖突解決方案啦扬,我們可以看到他并不適合互
聯(lián)網項目。之所以大家會有并發(fā)沖突的程序凫碌,是因為大部分程序員扑毡,思維模式都是
線性的。作為程序邏輯思維來講盛险,線性思維是沒有錯的瞄摊,因為計算機執(zhí)行指令的時候本
身就是線性的。然而如果把業(yè)務也看做是線性的苦掘,就會產生問題了换帜。任何一個程序操作,他都會消耗一定的時間鹤啡,即便你的CPU速度再快惯驼,也只是
縮短了這個時間范圍而已,如果只有一個用戶操作揉忘,比如我們在后臺發(fā)布文章跳座,看
自己發(fā)布的新聞,我們是無法感知并發(fā)帶來的沖突的泣矛。這就對我們的程序員提出了
更高的要求疲眷。理論上來講,所有跨越時間段的操作過程中如果涉及到數據修改就會有可能產
生并發(fā)沖突您朽,因此我們在設計程序的時候狂丝,要保障應用程序的質量,就需要去做并
發(fā)沖突處理哗总,只是實現(xiàn)業(yè)務需求與實現(xiàn)業(yè)務的同時做好質量需求几颜,就是好程序員與
壞程序員的差別。那么分析了產生并發(fā)沖突的原因以后讯屈,就比較容易思考解決方案了蛋哭。大體的思
路有兩種:一種是將并發(fā)操作變?yōu)閱尉€操作,另一種是讓所有跨越時間的段的操作
不去更改數據涮母。我們現(xiàn)在來看一下分頁谆趾,或者上拉或者下拉刷新的解決方法。我們剛剛提出的
2種的解決思路叛本,哪一種比較合適呢沪蓬?對于發(fā)布數據和瀏覽數據,比如微博来候,我們有
可能把這種并發(fā)操作變?yōu)閱尉€操作嗎跷叉?好像不太容易。那么我們能夠走得路就剩下
第二條营搅,也就是跨時間段的過程中不要改變數據云挟,我們剛剛產生的bug到底是什么數
據改變導致了bug∽剩回顧下我們的代碼實現(xiàn)的本質植锉,就容易找到其中的緣由了。通常我們在實現(xiàn)分頁的時候峭拘,首頁看到的是最新的數據俊庇,那么從數據庫中取數
據的sel語句是select * from news order by desc limt 0,10鸡挠,這樣取到
最新的數據辉饱,如果點擊下一頁,查詢語句不變拣展,只是分頁條目不在是第0-9彭沼,而是
第10-19條,如果在這個過程中有新的數據插入备埃,我們會發(fā)現(xiàn)有一個東西變了姓惑,就
是原有數據在數據庫的排序序號變了褐奴,如果我新發(fā)布一條數據,原來的第一條最新
的新聞就會變成第二條于毙,原來的第10條會變成第11條敦冬。這就是一個時間段內的操作
過程中有數據發(fā)生了改變。既然我們無法把這樣的并發(fā)操作變成單線操作唯沮,我們可以選擇不讓數據發(fā)生改
變脖旱,這樣并發(fā)bug就可以得到很好的解決了。需要了解詳細解決方案的介蛉,我會把無bug程序實例分享給大家萌庆。課后可以聯(lián)系
赫赫要資料,或者是聽我們的視屏直播課币旧,陳老師有詳細的解決践险。跨時間段的讓數據不改變不好走,那我們可以選擇第一種思路吹菱,讓并發(fā)操作變
為單線操作捏境,之前提到的加鎖是解決方案之一,但是對用戶體驗不好性能很差毁葱,基
本上無法再互聯(lián)網項目中使用垫言。如果不能加鎖,那么常用的解決方案是什么倾剿?我們可以用隊列筷频。如果我們將所有的用戶請求進行排隊,有一個服務來訂閱這
個隊列前痘,那么不管有多少用戶訪問凛捏,最終到服務器端,處理服務的就只有一個進程芹缔。
這樣就實現(xiàn)了一個由并發(fā)操作轉換成單線操作坯癣。關于消息隊列的使用以及本地服務的相關知識,在源代碼的技術經理/架構師
在線課中為大家詳盡的展示最欠。這個課程的眾籌將會在明天晚上8點開始示罗,眾籌的規(guī)
則和細節(jié)都已經出來了,前期眾籌的費用主要用來購買阿里云的服務芝硬,搭建實驗環(huán)境蚜点。