Hi,大家好,美好的一天從這里開始,今天主要講解淘寶雙11秒殺在哪個位置,淘寶0點0分0秒搶購攻略,以及淘寶雙11秒殺的一系列相關干貨,其實這篇文章主要還是為新手朋友整理的,總的來說思路還是很重要!
其實對于交易平臺來說,面對的不僅僅是人肉,還有很多腳本,外掛自動化的搶購系統,壓力可想而知。
秒殺的優化手段很多,就拿數據庫來說,有用排隊機制的,有用異步消息的,有用交易合并的。
今天,我要給大家介紹一種更極端的秒殺應對方法,裸秒。
(其實我很久以前就寫過類似的文章,趁雙十一跟大伙再練練)
目前可能只有PostgreSQL支持裸秒,也即是說,來吧,強暴我吧,一起上。 有點淫蕩,但是確實就是這么暴力。
PostgreSQL提供了一種ad lock,可以讓用戶盡情的釋放激情,以一臺32核64線程的機器為例,每秒可以獲取、探測約130萬次的ad lock。
試想一下,對單條記錄的秒殺操作,達到了單機100萬/s的處理能力后,秒殺算什么?100臺機器就能處理1億/s的秒殺請求,不行我的小心臟受不了了,下面聽我娓娓道來。
秒殺場景簡介
雖然秒殺已經很普遍了,但是出于文章的完整性,還是簡單介紹一下秒殺的業務背景。
例如,Iphone的1元秒殺,如果我只放出1臺Iphone,我們把它看成一條記錄,秒殺開始后,誰先搶到(更新這條記錄的鎖),誰就算秒殺成功。
對數據庫來說,秒殺瓶頸在于并發的對同一條記錄的多次更新請求,只有一個或者少量請求是成功的,其他請求是以失敗或更新不到記錄而告終。
例如有100臺IPHONE參與秒殺,并發來搶的用戶有100萬,對于數據庫來說,最小粒度的為行鎖,當有一個用戶在更新這條記錄時,其他的999999個用戶是在等待中度過的,以此類推。
除了那100個幸運兒,其他的用戶的等待都是無謂的,甚至它們不應該到數據庫中來浪費資源。
傳統的做法,使用一個標記位來表示這條記錄是否已經被更新,或者記錄更新的次數(幾臺Iphone)。
update tbl set xxx=xxx,upd_cnt=upd_cnt+1 where id=pk and upd_cnt+1<=5; — 假設可以秒殺5臺
這種方法的弊端:
獲得鎖的用戶在處理這條記錄時,可能成功,也可能失敗,或者可能需要很長時間,(例如數據庫響應慢)在它結束事務前,其他會話只能等著。
等待是非常不科學的,因為對于沒有獲得鎖的用戶,等待是在浪費時間。
常用的秒殺優化手段
1. 一般的優化處理方法是先使用for update nowait的方式來避免等待,即如果無法即可獲得鎖,那么就不等待。
begin;select
1 from tbl where id=pk for update nowait; —
如果用戶無法即刻獲得鎖,則返回錯誤。從而這個事務回滾。update tbl set xxx=xxx,upd_cnt=upd_cnt+1
where id=pk and upd_cnt+1<=5;end;
這種方法可以減少用戶的等待時間,因為無法即刻獲得鎖后就直接返回了。
2. 合并請求,即將多個更新合并到一個更新的請求,這種做法需要修改內核,同時會破壞ACID,因為如果合并后的請求失敗了,會導致合并中的所有人的請求失敗。(與分組提交不一樣,分組提交是不會破壞ACID的)。
那么接下來我們看看AD LOCK。
什么是ad lock
手冊中的說明,AD LOCK是一種面向用戶的輕量級鎖,鎖的目標是一個整型,分為事務級和會話級的鎖,以及共享和排他鎖。
在單個DB內,只要鎖的整型值不一樣,就可以獲得鎖,如果值一樣,可以使用TRY來加鎖,沒有獲得則立即返回FALSE。
https://www.postgresql.org/docs/current/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS
Table 9-87. Advisory Lock Functions
Name | Return Type | Description |
---|---|---|
pg_advisory_lock(key bigint) | void | Obtain exclusive session level advisory lock |
pg_advisory_lock(key1 int, key2 int) | void | Obtain exclusive session level advisory lock |
pg_advisory_lock_shared(key bigint) | void | Obtain shared session level advisory lock |
pg_advisory_lock_shared(key1 int, key2 int) | void | Obtain shared session level advisory lock |
pg_advisory_unlock(key bigint) | boolean | Release an exclusive session level advisory lock |
pg_advisory_unlock(key1 int, key2 int) | boolean | Release an exclusive session level advisory lock |
pg_advisory_unlock_all() | void Release | all session level advisory locks held by the current session |
pg_advisory_unlock_shared(key bigint) | boolean | Release a shared session level advisory lock |
pg_advisory_unlock_shared(key1 int, key2 int) | boolean | Release a shared session level advisory lock |
pg_advisory_xact_lock(key bigint) | void | Obtain exclusive transaction level advisory lock |
pg_advisory_xact_lock(key1 int, key2 int) | void | Obtain exclusive transaction level advisory lock |
pg_advisory_xact_lock_shared(key bigint) | void | Obtain shared transaction level advisory lock |
pg_advisory_xact_lock_shared(key1 int, key2 int) | void | Obtain shared transaction level advisory lock |
pg_try_advisory_lock(key bigint) | boolean | Obtain exclusive session level advisory lock if available |
pg_try_advisory_lock(key1 int, key2 int) | boolean | Obtain exclusive session level advisory lock if available |
pg_try_advisory_lock_shared(key bigint) | boolean | Obtain shared session level advisory lock if available |
pg_try_advisory_lock_shared(key1 int, key2 int) | boolean | Obtain shared session level advisory lock if available |
pg_try_advisory_xact_lock(key bigint) | boolean | Obtain exclusive transaction level advisory lock if available |
pg_try_advisory_xact_lock(key1 int, key2 int) | boolean | Obtain exclusive transaction level advisory lock if available |
pg_try_advisory_xact_lock_shared(key bigint) | boolean | Obtain shared transaction level advisory lock if available |
pg_try_advisory_xact_lock_shared(key1 int, key2 int) | boolean | Obtain shared transaction level advisory lock if available |
通常數據庫支持的最小粒度的鎖(指開放給用戶的)是行鎖,行鎖相比LWLOCK,SPINLOCK等是非常重的,所以傳統的行鎖在秒殺中會成為非常大的瓶頸,包括鎖的等待。
ad lock的用途
ad lock的用途,除了我接下來要說的秒殺,其實還有很多用途,例如
并發的安全性檢查,
遞歸調用中用于UPSERT的場景,
業務邏輯設計中用來確保原子操作等。
ad lock的性能指標
因為AD LOCK很輕量化,不需要訪問數據,不需要執行冗長的代碼,所以很高效。
32核64線程機器測試可以達到131萬次/s的鎖請求。
vi test.sql
\set id random(1,100000000)
select pg_try_advisory_xact_lock(:id);
pgbench -M prepared -n -r -P 1 -f ./test.sql -c 96 -j 96 -T 100transaction type: ./test.sql
scaling
factor: 1query mode: preparednumber of clients: 96number of threads:
96duration: 100 snumber of transactions actually processed:
131516823latency average = 0.072 ms
latency stddev = 0.070 ms
tps = 1314529.211060 (including connections establishing)
tps = 1315395.309707 (excluding connections establishing)script statistics:
– statement latencies in milliseconds: 0.001 \set id random(1,100000000) 0.074 select pg_try_advisory_xact_lock(:id);
ad lock用于秒殺的例子
在數據庫中,商品通常有唯一ID,我們可以對這個ID加鎖,(當然,如果對不同的表這個ID有重疊的可能,我們可以加偏移量或者其他的手段來達到無沖突)。
加鎖成功才會去對行加鎖,執行更新,這樣就能規避掉無效的行鎖等待,以及冗長的查詢代碼。
使用 AD LOCK 對單條記錄的并發更新處理QPS可以達到39.1萬/s,被秒殺的商品很快就會變成售罄狀態,不會再浪費數據庫的資源。
create table test(id int primary key, crt_time timestamp);insert into test values (1);vi test.sql
update test set crt_time=now() where id=1 and pg_try_advisory_xact_lock(1);
pgbench -M prepared -n -r -P 1 -f ./test.sql -c 64 -j 64 -T 100transaction type: ./test.sql
scaling
factor: 1query mode: preparednumber of clients: 64number of threads:
64duration: 100 snumber of transactions actually processed:
39104368latency average = 0.163 ms
latency stddev = 0.216 ms
tps = 391012.743072 (including connections establishing)
tps = 391175.983419 (excluding connections establishing)script statistics:
– statement latencies in milliseconds: 0.163 update test set crt_time=now() where id=1 and pg_try_advisory_xact_lock(1);
此時數據庫主機還有66.2%的空閑CPU資源可用使用。
top
– 13:12:43 up 51 days, 18:41, 2 users, load average: 1.12, 0.97,
0.78Tasks: 1463 total, 28 running, 1435 sleeping, 0 stopped, 0
zombieCpu(s): 24.5%us, 9.3%sy, 0.0%ni, 66.2%id, 0.0%wa, 0.0%hi, 0.0%si,
0.0%stMem: 529321832k total, 235226420k used, 294095412k free, 903076k
buffersSwap: 0k total, 0k used, 0k free, 62067636k cached
對比傳統的例子
傳統的消除等待的做法是這樣的,通過select for update nowait。
begin;select
1 from tbl where id=pk for update nowait; —
如果用戶無法即刻獲得鎖,則返回錯誤。從而這個事務回滾。update tbl set xxx=xxx,upd_cnt=upd_cnt+1
where id=pk and upd_cnt+1<=5;end;
在PG中,可以使用do語句,把以上合成到一個塊里面操作。
使用傳統的方法,每秒可以處理8.6萬。
vi
test.sqldo language plpgsql $$ declare begin with t as (select * from
test where id=1 for update nowait) update test set crt_time=now() from t
where t.id=test.id; exception when others then return; end; $$;
pgbench -M prepared -n -r -P 1 -f ./test.sql -c 64 -j 64 -T 100
transaction type: ./test.sql
scaling factor: 1
query mode: prepared
number of clients: 64
number of threads: 64
duration: 100 s
number of transactions actually processed: 8591222
latency average = 0.744 ms
latency stddev = 0.713 ms
tps = 85888.823884 (including connections establishing)
tps = 85924.666940 (excluding connections establishing)
script statistics:
– statement latencies in milliseconds:
0.744 do language plpgsql $$ declare begin with t as (select * from
test where id=1 for update nowait) update test set crt_time=now() from t
where t.id=test.id; exception when others then return; end; $$;
CPU剩余54.5%
top
– 13:13:48 up 51 days, 18:42, 2 users, load average: 8.14, 2.69,
1.37Tasks: 1464 total, 21 running, 1442 sleeping, 0 stopped, 1
zombieCpu(s): 41.7%us, 3.8%sy, 0.0%ni, 54.5%id, 0.0%wa, 0.0%hi, 0.0%si,
0.0%stMem: 529321832k total, 235256052k used, 294065780k free, 903176k
buffersSwap: 0k total, 0k used, 0k free, 62068308k cached
ad lock相比其他秒殺優化的優勢
使用AD LOCK可以使得CPU開銷最小化,等待最小化,從本文的測試CASE來看,單條記錄的更新可以達到39.1萬/s。
傳統的手段只能達到8.6萬/s。
使用AD LOCK不破壞ACID,單個請求單個事務,不影響其他的事務。
合并優化,本質上是破壞了ACID的,如果合并失敗,會導致所有相關的請求失敗。
這篇文章的所有內容到這里就完了,希望能幫助到你們,看完了淘寶雙11秒殺在哪個位置「秒懂:淘寶0點0分0秒搶購攻略」,收獲很多,歡迎幫忙分享一下。我在這邊先謝謝各位了哈!
本文發布者:百事通,不代表巢座耶立場,轉載請注明出處:http://www.sdwldmy.com/p/7742.html
版權聲明:本文內容由互聯網用戶自發貢獻,該文觀點僅代表作者本人。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。如發現本站有涉嫌抄襲侵權/違法違規的內容, 請發送郵件至 jubao226688#126.com 舉報,一經查實,本站將立刻刪除。