線程,同步,單例,高併發,高訪問,死鎖前端
在過去的工做中,我曾經面對過5w每秒的高併發秒殺功能,在這個過程當中,整個Web系統遇到了不少的問題和挑戰。若是Web系統不作針對性的優化,會垂手可得地陷入到異常狀態。咱們如今一塊兒來討論下,優化的思路和方法哈。java
一個秒殺或者搶購頁面,一般分爲2個部分,一個是靜態的HTML等內容,另外一個就是參與秒殺的Web後臺請求接口。mysql
一般靜態HTML等內容,是經過CDN的部署,通常壓力不大,核心瓶頸實際上在後臺請求接口上。這個後端接口,必須可以支持高併發請求,同時,很是重要的一點,必須儘量「快」,在最短的時間裏返回用戶的請求結果。爲了實現儘量快這一點,接口的後端存儲使用內存級別的操做會更好一點。仍然直接面向 MySQL之類的存儲是不合適的,若是有這種複雜業務的需求,都建議採用異步寫入。android
固然,也有一些秒殺和搶購採用「滯後反饋」,就是說秒殺當下不知道結果,一段時間後才能夠從頁面中看到用戶是否秒殺成功。可是,這種屬於「偷懶」行爲,同時給用戶的體驗也很差,容易被用戶認爲是「暗箱操做」。nginx
咱們一般衡量一個Web系統的吞吐率的指標是QPS(Query Per Second,每秒處理請求數),解決每秒數萬次的高併發場景,這個指標很是關鍵。舉個例子,咱們假設處理一個業務請求平均響應時間爲100ms,同時, 系統內有20臺Apache的Web服務器,配置MaxClients爲500個(表示Apache的最大鏈接數目)。web
那麼,咱們的Web系統的理論峯值QPS爲(理想化的計算方式):redis
20*500/0.1 = 100000 (10萬QPS)算法
咦?咱們的系統彷佛很強大,1秒鐘能夠處理完10萬的請求,5w/s的秒殺彷佛是「紙老虎」哈。實際狀況,固然沒有這麼理想。在高併發的實際場景下,機器都處於高負載的狀態,在這個時候平均響應時間會被大大增長。sql
就Web服務器而言,Apache打開了越多的鏈接進程,CPU須要處理的上下文切換也越多,額外增長了CPU的消耗,而後就直接致使平均響應時間 增長。所以上述的MaxClient數目,要根據CPU、內存等硬件因素綜合考慮,絕對不是越多越好。能夠經過Apache自帶的abench來測試一 下,取一個合適的值。而後,咱們選擇內存操做級別的存儲的Redis,在高併發的狀態下,存儲的響應時間相當重要。網絡帶寬雖然也是一個因素,不過,這種 請求數據包通常比較小,通常不多成爲請求的瓶頸。負載均衡成爲系統瓶頸的狀況比較少,在這裏不作討論哈。數據庫
那麼問題來了,假設咱們的系統,在5w/s的高併發狀態下,平均響應時間從100ms變爲250ms(實際狀況,甚至更多):
20*500/0.25 = 40000 (4萬QPS)
因而,咱們的系統剩下了4w的QPS,面對5w每秒的請求,中間相差了1w。
而後,這纔是真正的惡夢開始。舉個例子,高速路口,1秒鐘來5部車,每秒經過5部車,高速路口運做正常。忽然,這個路口1秒鐘只能經過4部車,車流量仍然依舊,結果一定出現大塞車。(5條車道突然變成4條車道的感受)
同理,某一個秒內,20*500個可用鏈接進程都在滿負荷工做中,卻仍然有1萬個新來請求,沒有鏈接進程可用,系統陷入到異常狀態也是預期以內。
其實在正常的非高併發的業務場景中,也有相似的狀況出現,某個業務請求接口出現問題,響應時間極慢,將整個Web請求響應時間拉得很長,逐漸將Web服務器的可用鏈接數佔滿,其餘正常的業務請求,無鏈接進程可用。
更可怕的問題是,是用戶的行爲特色,系統越是不可用,用戶的點擊越頻繁,惡性循環最終致使「雪崩」(其中一臺Web機器掛了,致使流量分散到其餘正常工做的機器上,再致使正常的機器也掛,而後惡性循環),將整個Web系統拖垮。
若是系統發生「雪崩」,貿然重啓服務,是沒法解決問題的。最多見的現象是,啓動起來後,馬上掛掉。這個時候,最好在入口層將流量拒絕,而後再將重啓。若是是redis/memcache這種服務也掛了,重啓的時候須要注意「預熱」,而且極可能須要比較長的時間。
秒殺和搶購的場景,流量每每是超乎咱們系統的準備和想象的。這個時候,過載保護是必要的。若是檢測到系統滿負載狀態,拒絕請求也是一種保護措施。在前端設置過濾是最簡單的方式,可是,這種作法是被用戶「千夫所指」的行爲。更合適一點的是,將過載保護設置在CGI入口層,快速將客戶的直接請求返回。
秒殺和搶購收到了「海量」的請求,實際上裏面的水分是很大的。很多用戶,爲了「搶「到商品,會使用「刷票工具」等類型的輔助工具,幫助他們發送儘可 能多的請求到服務器。還有一部分高級用戶,製做強大的自動請求腳本。這種作法的理由也很簡單,就是在參與秒殺和搶購的請求中,本身的請求數目佔比越多,成功的機率越高。
這些都是屬於「做弊的手段」,不過,有「進攻」就有「防守」,這是一場沒有硝煙的戰鬥哈。
部分用戶經過瀏覽器的插件或者其餘工具,在秒殺開始的時間裏,以本身的帳號,一次發送上百甚至更多的請求。實際上,這樣的用戶破壞了秒殺和搶購的公平性。
這種請求在某些沒有作數據安全處理的系統裏,也可能形成另一種破壞,致使某些判斷條件被繞過。例如一個簡單的領取邏輯,先判斷用戶是否有參與記 錄,若是沒有則領取成功,最後寫入到參與記錄中。這是個很是簡單的邏輯,可是,在高併發的場景下,存在深深的漏洞。多個併發請求經過負載均衡服務器,分配到內網的多臺Web服務器,它們首先向存儲發送查詢請求,而後,在某個請求成功寫入參與記錄的時間差內,其餘的請求獲查詢到的結果都是「沒有參與記錄」。 這裏,就存在邏輯判斷被繞過的風險。
應對方案:
在程序入口處,一個帳號只容許接受1個請求,其餘請求過濾。不只解決了同一個帳號,發送N個請求的問題,還保證了後續的邏輯流程的安全。實現方案, 能夠經過Redis這種內存緩存服務,寫入一個標誌位(只容許1個請求寫成功,結合watch的樂觀鎖的特性),成功寫入的則能夠繼續參加。
或者,本身實現一個服務,將同一個帳號的請求放入一個隊列中,處理完一個,再處理下一個。
不少公司的帳號註冊功能,在發展早期幾乎是沒有限制的,很容易就能夠註冊不少個帳號。所以,也致使了出現了一些特殊的工做室,經過編寫自動註冊腳 本,積累了一大批「殭屍帳號」,數量龐大,幾萬甚至幾十萬的帳號不等,專門作各類刷的行爲(這就是微博中的「殭屍粉「的來源)。舉個例子,例如微博中有轉 發抽獎的活動,若是咱們使用幾萬個「殭屍號」去混進去轉發,這樣就能夠大大提高咱們中獎的機率。
這種帳號,使用在秒殺和搶購裏,也是同一個道理。例如,iPhone官網的搶購,火車票黃牛黨。
應對方案:
這種場景,能夠經過檢測指定機器IP請求頻率就能夠解決,若是發現某個IP請求頻率很高,能夠給它彈出一個驗證碼或者直接禁止它的請求:
所謂道高一尺,魔高一丈。有進攻,就會有防守,永不休止。這些「工做室」,發現你對單機IP請求頻率有控制以後,他們也針對這種場景,想出了他們的「新進攻方案」,就是不斷改變IP。
有同窗會好奇,這些隨機IP服務怎麼來的。有一些是某些機構本身佔據一批獨立IP,而後作成一個隨機代理IP的服務,有償提供給這些「工做 室」使用。還有一些更爲黑暗一點的,就是經過木馬黑掉普通用戶的電腦,這個木馬也不破壞用戶電腦的正常運做,只作一件事情,就是轉發IP包,普通用戶的電 腦被變成了IP代理出口。經過這種作法,黑客就拿到了大量的獨立IP,而後搭建爲隨機IP服務,就是爲了掙錢。
應對方案:
說實話,這種場景下的請求,和真實用戶的行爲,已經基本相同了,想作分辨很困難。再作進一步的限制很容易「誤傷「真實用戶,這個時候,一般只能經過設置業務門檻高來限制這種請求了,或者經過帳號行爲的」數據挖掘「來提早清理掉它們。
殭屍帳號也仍是有一些共同特徵的,例如帳號極可能屬於同一個號碼段甚至是連號的,活躍度不高,等級低,資料不全等等。根據這些特色,適當設置參與門檻,例如限制參與秒殺的帳號等級。經過這些業務手段,也是能夠過濾掉一些殭屍號。
看到這裏,同窗們是否明白你爲何搶不到火車票?若是你只是老老實實地去搶票,真的很難。經過多帳號的方式,火車票的黃牛將不少車票的名額佔據,部分強大的黃牛,在處理驗證碼方面,更是「技高一籌「。
高級的黃牛刷票時,在識別驗證碼的時候使用真實的人,中間搭建一個展現驗證碼圖片的中轉軟件服務,真人瀏覽圖片並填寫下真實驗證碼,返回給中轉軟件。對於這種方式,驗證碼的保護限制做用被廢除了,目前也沒有很好的解決方案。
由於火車票是根據身份證明名制的,這裏還有一個火車票的轉讓操做方式。大體的操做方式,是先用買家的身份證開啓一個搶票工具,持續發送請 求,黃牛帳號選擇退票,而後黃牛買家成功經過本身的身份證購票成功。當一列車箱沒有票了的時候,是沒有不少人盯着看的,何況黃牛們的搶票工具也很強大,即 使讓咱們看見有退票,咱們也不必定能搶得過他們哈。
最終,黃牛順利將火車票轉移到買家的身份證下。
解決方案:
並無很好的解決方案,惟一能夠動心思的也許是對帳號數據進行「數據挖掘」,這些黃牛帳號也是有一些共同特徵的,例如常常搶票和退票,節假日異常活躍等等。將它們分析出來,再作進一步處理和甄別。
咱們知道在多線程寫入同一個文件的時候,會存現「線程安全」的問題(多個線程同時運行同一段代碼,若是每次運行結果和單線程運行的結果是一 樣的,結果和預期相同,就是線程安全的)。若是是MySQL數據庫,能夠使用它自帶的鎖機制很好的解決問題,可是,在大規模併發的場景中,是不推薦使用 MySQL的。秒殺和搶購的場景中,還有另一個問題,就是「超發」,若是在這方面控制不慎,會產生髮送過多的狀況。咱們也曾經據說過,某些電商搞搶購活動,買家成功拍下後,商家卻不認可訂單有效,拒絕發貨。這裏的問題,也許並不必定是商家奸詐,而是系統技術層面存在超發風險致使的。
假設某個搶購場景中,咱們一共只有100個商品,在最後一刻,咱們已經消耗了99個商品,僅剩最後一個。這個時候,系統發來多個併發請求,這批請求讀取到的商品餘量都是99個,而後都經過了這一個餘量判斷,最終致使超發。(同文章前面說的場景)
在上面的這個圖中,就致使了併發用戶B也「搶購成功」,多讓一我的得到了商品。這種場景,在高併發的狀況下很是容易出現。
解決線程安全的思路不少,能夠從「悲觀鎖」的方向開始討論。
悲觀鎖,也就是在修改數據的時候,採用鎖定狀態,排斥外部請求的修改。遇到加鎖的狀態,就必須等待。
雖然上述的方案的確解決了線程安全的問題,可是,別忘記,咱們的場景是「高併發」。也就是說,會不少這樣的修改請求,每一個請求都須要等待 「鎖」,某些線程可能永遠都沒有機會搶到這個「鎖」,這種請求就會死在那裏。同時,這種請求會不少,瞬間增大系統的平均響應時間,結果是可用鏈接數被耗 盡,系統陷入異常。
那好,那麼咱們稍微修改一下上面的場景,咱們直接將請求放入隊列中的,採用FIFO(First Input First Output,先進先出),這樣的話,咱們就不會致使某些請求永遠獲取不到鎖。看到這裏,是否是有點強行將多線程變成單線程的感受哈。
而後,咱們如今解決了鎖的問題,所有請求採用「先進先出」的隊列方式來處理。那麼新的問題來了,高併發的場景下,由於請求不少,極可能一瞬 間將隊列內存「撐爆」,而後系統又陷入到了異常狀態。或者設計一個極大的內存隊列,也是一種方案,可是,系統處理完一個隊列內請求的速度根本沒法和瘋狂涌 入隊列中的數目相比。也就是說,隊列內的請求會越積累越多,最終Web系統平均響應時候仍是會大幅降低,系統仍是陷入異常。
這個時候,咱們就能夠討論一下「樂觀鎖」的思路了。樂觀鎖,是相對於「悲觀鎖」採用更爲寬鬆的加鎖機制,大都是採用帶版本號 (Version)更新。實現就是,這個數據全部請求都有資格去修改,但會得到一個該數據的版本號,只有版本號符合的才能更新成功,其餘的返回搶購失敗。 這樣的話,咱們就不須要考慮隊列的問題,不過,它會增大CPU的計算開銷。可是,綜合來講,這是一個比較好的解決方案。
有不少軟件和服務都「樂觀鎖」功能的支持,例如Redis中的watch就是其中之一。經過這個實現,咱們保證了數據的安全。
互聯網正在高速發展,使用互聯網服務的用戶越多,高併發的場景也變得愈來愈多。電商秒殺和搶購,是兩個比較典型的互聯網高併發場景。雖然咱們解決問題的具體技術方案可能千差萬別,可是遇到的挑戰倒是類似的,所以解決問題的思路也殊途同歸。
5、關於併發
經過上面的搶購和秒殺的例子來探討併發的出現場景及處理高併發的相應技術。
關於多線程和併發的知識:
一、 什麼是併發?
在互聯網時代,所講的併發、高併發,一般是指併發訪問。也就是在某個時間點,有多少個訪問同時到來。(PV(page view)即頁面瀏覽量。)
一臺服務器在單位時間裏能處理的請求越多,服務器的能力越高,也就是服務器併發處理能力越強。服務器的本質工做就是,爭取以最快的速度將內核緩衝區中的用戶請求數據一個不剩地都拿出來,而後儘快處理,再將響應數據放到一塊又可以與發送數據的緩衝區中,接着處理下一撥請求。
二、 衡量服務器併發處理能力?
高併發相關經常使用的一些指標有響應時間(Response Time),吞吐量(Throughput),每秒查詢率QPS(Query Per Second),併發用戶數等。
響應時間:系統對請求作出響應的時間。例如系統處理一個HTTP請求須要200ms,這個200ms就是系統的響應時間。
吞吐量:單位時間內處理的請求數量。
QPS:每秒響應請求數。在互聯網領域,這個指標和吞吐量區分的沒有這麼明顯。
併發用戶數:同時承載正常使用系統功能的用戶數量。例如一個即時通信系統,同時在線量必定程度上表明瞭系統的併發用戶數。
三、 如何提升服務器的併發處理能力?
有兩種方式:第一種就是提升單機的性能;
第二種就是互聯網的分佈式開發架構,增長機器的數量。
其實還能夠經過減小無用請求來下降服務器的壓力。例如微博的殭屍粉,購票的黃牛。
【1】提升CPU併發計算能力
(1)多進程&多線程
(2)減小進程切換,使用線程,考慮進程綁定CPU
(3)減小使用沒必要要的鎖,考慮無鎖編程
(4)考慮進程優先級
(5)關注系統負載
(6)關注CPU使用率,除了用戶空間和內核空間的CPU使用率之外,還要關注I/O wait
【2】減小系統調用
【3】考慮減小內存分配和釋放
(1)改善數據結構和算法複製度
(2)使用內存池
(3)考慮使用共享內存
【4】考慮使用持久鏈接
【5】改進I/O模型
(1)DMA技術
(2)異步I/O
(3)改進多路I/O就緒通知策略,epoll
(4)Sendfile
(5)內存映射
(6)直接I/O
【6】改進服務器併發策略
(1)一個進程處理一個鏈接,非阻塞I/O,使用長鏈接
(2)一個進程處理多個鏈接,異步I/O,使用長鏈接
【7】改進硬件環境
四、 總結
高併發(High Concurrency)是互聯網分佈式系統架構設計中必須考慮的因素之一,它一般是指,經過設計保證系統可以同時並行處理不少請求。
提升系統併發能力的方式,方法論上主要有兩種:垂直擴展(Scale Up)與水平擴展(Scale Out)。前者垂直擴展能夠經過提高單機硬件性能,或者提高單機架構性能,來提升併發性,但單機性能老是有極限的,互聯網分佈式架構設計高併發終極解決方案仍是後者:水平擴展。
互聯網分層架構中,各層次水平擴展的實踐又有所不一樣:
(1)反向代理層能夠經過「DNS輪詢」的方式來進行水平擴展;
(2)站點層能夠經過nginx來進行水平擴展;
(3)服務層能夠經過服務鏈接池來進行水平擴展;
(4)數據庫能夠按照數據範圍,或者數據哈希的方式來進行水平擴展;
各層實施水平擴展後,可以經過增長服務器數量的方式來提高系統的性能,作到理論上的性能無限。
五、 多線程和併發有什麼關係?
【1】多線程
(1)java自己就是多線程的;
(2)java的多線程是一種編程思想,一個程序執行以後就成爲一個進程,所謂的多線程就是在一個進程中有多個線程存在,在宏觀上是多個線程同時運行,能夠使進程同時完成多件事,微觀上是cpu在多個線程之間切換,在某一時刻仍是執行一個線程。
【2】併發
(3)併發通常指 "併發訪問", 如若干應用程序從不一樣的客戶端同時訪問同一個服務器端的數據庫,服務器應用必定的承受能力處理併發的請求,併發請求被進程或是進程裏面的線程處理,併發訪問用併發編程處理其實最好的解決辦法就是多線程。
(4)併發編程是多線程編程的一種應用。
(5)不管你是作web開發,仍是數據庫開發,仍是桌面開發,都會用到多線程的。
(6)由於有併發的這個實際的需求,因此出現了多線程。
【3】
(1)高併發不是JAVA的專有的東西,是語言無關的廣義的,爲提供更好互聯網服務而提出的概念。舉個極端的例子,就是100我的,1人分配1臺web服務器,那麼服務器資源是他們獨佔的,他們不須要搶佔服務器資源,100個請求被100臺服務器並行處理,速度一定很快,這就是高併發。固然這是不可能的,可是,咱們老是努力去作,讓少許的服務器也能達到近似的能力。這就須要服務器的HTML畫面,後臺業務邏輯,db數據存取等等細節上的處理都達到一個並行的極致,以此來實現整個服務器對全部請求的高並行。這是戰略上的並行。多線程只是爲了達到高併發目的,在某個細節點上,爲實現某併發功能而採用的一種具體的實現方法,這種功能也能夠由多進程實現,固然,也能夠由多進程,多線程一塊兒實現。這是戰術上的並行。那麼能夠說,高併發是目的,多線程是某種手段(不是惟一的),高併發能夠由多線程實現,可是多線程不表明就是高併發。
(2)併發與多線程之間的關係就是目的與手段之間的關係。併發(Concurrent)的反面是串行。串行比如多個車輛行駛在一股車道上,它們只能「魚貫而行」。而併發比如多個車輛行駛在多股車道上,它們能夠「並駕齊驅」。併發的極致就是並行(Parallel)。多線程就是將本來多是串行的計算「改成」併發(並行)的一種手段、途徑或者模型。所以,有時咱們也稱多線程編程爲併發編程。固然,目的與手段之間經常是一對多的關係。併發編程還有其餘的實現途徑,例如函數式(Functional programming)編程。多線程編程每每是其餘併發編程模型的基礎,因此多線程編程的重要性不言而喻。
綜合上面幾點能夠看出:併發,就是服務器同時處理多個請求的能力,而處理併發的最好的辦法,就是採用多線程。cpu快速調用不一樣的就緒隊列,看起來好像是同步進行,實際仍是一個線程一個線程的處理的,但這樣,就大大提升了服務器的效率,減小了線程的等待時間。
便可以這麼理解:多線程是處理高併發的一種編程方法。即併發須要用多線程實現。
當有海量的請求時,服務器沒有鏈接的進程可用,系統就會產生異常。
六、 在具體項目中,什麼狀況下用到了多線程?
【1】若是作 java web方面開發的話幾乎用不到多線程!由於有多線程的地方 servlet 容器或者其餘開發框架都已經實現掉了!通常在網絡應用程序中使用多線程的地方很是多!另外,拷貝文件使用多線程,是沒有用的!以多線程來提升效率的場景通常在 CPU 計算型,而不是在 IO 讀寫型。CPU 能夠會有多個核心並行處理計算,可是磁盤 IO 就沒這功能了,磁頭只有一個,根本不可能靠多線程提升效率!通常來講,磁盤 IO 的併發能力爲 0,也就是說沒法支持併發!網絡 IO 的話因爲帶寬的限制的,使用多線程處理最多也只能達到帶寬的極值。對於磁盤 IO 來講,多線程能夠用於一個線程專門用於讀寫文件,其餘的線程用於對讀取數據進行處理,這樣纔有可能更好地利用 CPU 資源。若是僅僅是單純的文件複製,使用多線程操做的話,會使用磁頭在磁盤上不停地進行尋道操做,使得效率更爲低下!
【2】壓力測試時,會用到多線程。
【3】服務器編程時,會用到多線程。
【4】使用監聽器時,可能會用到多線程。
【5】跑JOB時,可能會用到多線程。
【6】還有一種極爲廣泛的使用多線程的場景是UI編程,通常UI界面繪製於主線程,爲了避免阻塞主線程讓用戶體驗更流暢,須要建立單獨的線程處理耗時操做,處理完了再更新主界面,典型的案例就是android應用開發。
【7】一些C/S模式好比說網絡遊戲(基於socket協議)通常在服務器那邊處理的時候一個客戶端,一個線程;還有就是一些銀行軟件,用到了線程同步等等。
七、 高併發事件的出現的場景
(1) 服務器在每秒內處理請求數是必定,當訪問量太大時,沒有多餘的進程來處理這些請求,就會形成系統的癱瘓。系統崩潰,例如前段時間微博的癱瘓,就是短期內流量太大了,致使系統處於異常狀態,頁面沒法顯示,用戶沒法登錄等;電商中搶購,秒殺中出現超發的狀況,多個用戶同時購買同一物品,訪問修改同一數據等狀況。
(2) 因爲購票、查詢和瀏覽的數量激增,12306網站天天訪問量比平時增加數十倍,常常出現登陸難的現象,近日甚至出現了付款不出票、系統乾脆癱瘓的狀況。網絡訂票本來是一項便民利民的措施,卻因爲鐵道部未正確預估網站併發狀況、技術支持不足而致使相反的結果。IT業界人士紛紛指出,12306網站癱瘓最大的緣由是「技術之罪」。並針對高併發網站引起的技術考驗,紛紛獻技獻策、展開討論,提出了不少具指導意義的技術解決方案。
(3) 這次12306網站難登陸並癱瘓是系統架構規劃的問題,致使不能有效支持大併發量集中訪問。同時,12306在IT管理上也有問題,未能進行有效的壓力測試和運行模擬。
八、 高併發下出現的超發問題的解決方案
項目中:
【1】在點擊購物車的提交訂單按鈕時,會判斷庫存是否足夠,不足,會拒絕提交訂單。
【2】當庫存只有一件時,兩個客戶,或多個客戶通過在購物車提交訂單判斷,在訂單頁面進行支付時判斷,同時都知足了購買一件產品的需求,那麼,讓誰購買成功?豈不是多個用戶同時都支付了這同一件商品?這種狀況就是超發,下面將解決高併發下的這種超發的問題。
【3】假設 個,B客戶購買6個:
(1)在點擊提交訂單時,兩這同時判斷庫存知足,進入到訂單界面;(2)而後 A先支付完成,在支付時,減小庫存(此時作判斷,若是庫存足夠,支付成功,不然支付失敗)---》PS:跟12306購票系統類似。(3)而後B慢一步也進入到支付,判斷,此時庫存只有5個,不讓支付,支付失敗。(4)若是二者同時進行支付呢?怎麼改變庫存,讓誰購買成功?這就涉及到了加鎖,同步的問題。下面就討論這個問題 的解決方案。、
【4】涉及到的知識點技術
(1) 悲觀鎖:解決線程安全的思路不少,能夠從「悲觀鎖」的方向開始討論。悲觀鎖,也就是在修改數據的時候,採用鎖定狀態,排斥外部請求的修改。遇到加鎖的狀態,就必須等待。雖然上述的方案的確解決了線程安全的問題,可是,別忘記,咱們的場景是「高併發」。也就是說,會不少這樣的修改請求,每一個請求都須要等待「鎖」,某些線程可能永遠都沒有機會搶到這個「鎖」,這種請求就會死在那裏。同時,這種請求會不少,瞬間增大系統的平均響應時間,結果是可用鏈接數被耗盡,系統陷入異常。
本身理解:悲觀鎖就是給要修改的數據加上鎖,同步。其餘請求須要等待。把數據鎖住。
怎麼實現?具體怎麼在代碼中寫。須要瞭解的。
通常意義上的加鎖指兩個層面:代碼層次,如java中的同步鎖,典型的就是同步關鍵字synchronized ;數據庫層次,如悲觀鎖(物理鎖)和樂觀鎖。
悲觀鎖:經過數據庫的forupdate字段實現加鎖。
for update要放到mysql的事務中,即begin和commit中,否者則不起做用。
(2)FIFO隊列思路
那好,那麼咱們稍微修改一下上面的場景,咱們直接將請求放入隊列中的,採用FIFO(First Input FirstOutput,先進先出),這樣的話,咱們就不會致使某些請求永遠獲取不到鎖。看到這裏,是否是有點強行將多線程變成單線程的感受哈。
而後,咱們如今解決了鎖的問題,所有請求採用「先進先出」的隊列方式來處理。那麼新的問題來了,高併發的場景下,由於請求不少,極可能一瞬間將隊列內存「撐爆」,而後系統又陷入到了異常狀態。或者設計一個極大的內存隊列,也是一種方案,可是,系統處理完一個隊列內請求的速度根本沒法和瘋狂涌 入隊列中的數目相比。也就是說,隊列內的請求會越積累越多,最終Web系統平均響應時候仍是會大幅降低,系統仍是陷入異常。(3)樂觀鎖思路
這個時候,咱們就能夠討論一下「樂觀鎖」的思路了。樂觀鎖,是相對於「悲觀鎖」採用更爲寬鬆的加鎖機制,大都是採用帶版本號(Version)更新。實現就是,這個數據全部請求都有資格去修改,但會得到一個該數據的版本號,只有版本號符合的才能更新成功,其餘的返回搶購失敗。這樣的話,咱們就不須要考慮隊列的問題,不過,它會增大CPU的計算開銷。可是,綜合來講,這是一個比較好的解決方案。有不少軟件和服務都「樂觀鎖」功能的支持,例如Redis中的watch就是其中之一。經過這個實現,咱們保證了數據的安全。
本身理解:修改版本號,version。
假設數據庫中賬戶信息表中有一個 version字段,當前值爲 1 ;而當前賬戶餘額字段( balance )爲 $100 。 操做員 A 此時將其讀出( version=1 ),並從其賬戶餘額中扣除 $50( $100-$50 )。 2 在操做員 A 操做的過程當中,操做員 B 也讀入此用戶信息( version=1 ),並 從其賬戶餘額中扣除 $20 ( $100-$20 )。 3 操做員 A完成了修改工做,將數據版本號加一( version=2 ),連同賬戶扣 除後餘額( balance=$50 ),提交至數據庫更新,此時因爲提交數據版本大 於數據庫記錄當前版本,數據被更新,數據庫記錄 version 更新爲 2 。 4 操做員 B 完成了操做,也將版本號加一( version=2 )試圖向數據庫提交數據( balance=$80 ),但此時比對數據庫記錄版本時發現,操做員 B提交的 數據版本號爲 2 ,數據庫記錄當前版本也爲 2 ,不知足「 提交版本必須大於記 錄當前版本才能執行更新 「 的樂觀鎖策略,所以,操做員 B 的提交被駁回。 這樣,就避免了操做員 B 用基於
version=1 的舊數據修改的結果覆蓋操做員 A 的操做結果的可能。 從上面的例子能夠看出,樂觀鎖機制避免了長事務中的數據庫加鎖開銷(操做員 A和操做員 B 操做過程當中,都沒有對數據庫數據加鎖),大大提高了大併發量下的系統總體性能表現。
(4)Mysql的數據庫的鎖機制
鎖
(5)關於事務和髒數據
髒數據,不可重複讀,幻讀
解決:
九、 我對多線程及高併發的瞭解
我對線程和併發的瞭解可能比較淺顯,理論化,基礎化,由於我接觸的項目中,並無深刻的處理過併發和多線程的處理,我對於併發的處理只是在分佈式開發和負載均衡中提升項目的併發處理能力。多線程的瞭解就是對同步(就是加鎖)和servlet的單例多線程有必定的瞭解。
談到高併發,就是服務器端同時處理海量請求的能力。當出現高併發時,可能會出現超發的狀況:(詳細介紹,結合本身項目中遇到的超發問題)
當請求超過服務器的承受限制,還可能會使服務器崩潰。此時,咱們能夠經過分佈式開發的架構,將業務細化爲多個子業務,這樣既能夠增長了後臺服務器對高訪問的業務的請求處理能力,同時還便於後期的升級維護,各個子業務之間的維護升級更加簡單,經過遠程調用來實現各個子業務之間的聯繫。此外,還能夠經過集羣,來提升服務器的併發能力。這種提升服務器的併發能力的方式也成爲水平提升服務器的併發能力,這也是將來發展的趨勢。
此外,就是經過單機,經過提升cpu等單個物理機器的性能,來提升服務器的併發能力。
PS:web服務器都是已經實現了多線程的。
因此我要了解的就是怎麼處理超發的問題,在處理超發的前提下,我要對什麼是高併發,高併發和多線程有什麼關係搞清楚。
關於實際中編碼中我所接觸到的對於多線程,和併發訪問有四點:
【1】在講多線程時,關於火車票的簡單代碼和同步代碼塊,瞭解了多線程的底層代碼。
【2】在講servlet時的單例多線程。
雙重檢驗,爲何要用多線程?就是爲了提升cpu的利用率,(提升併發能力)若是同步方法,跟單線程的區別並不大,因此,同步共同的資源對象,縮小鎖定的範圍,故用雙重檢驗。
【3】在講解電商項目時,分佈式開發和Nginx的負載均衡的集羣創建。
【4】Web服務器都是實現了多線程的,現階段不須要編寫多線程。