在數(shù)據(jù)庫(kù)上實(shí)現(xiàn)每3秒最多只插入一條記錄

由于下游的流量限制,經(jīng)常有這樣的需求毯焕,每一段時(shí)間只能有固定量的請(qǐng)求磺樱。多于的流量會(huì)造成服務(wù)不可用,或高延時(shí)芜辕。所以需要在上游做一些擁塞的控制活孩,于是就有了如題的需求。
我們將這個(gè)問題簡(jiǎn)化一下询兴,假設(shè)數(shù)據(jù)庫(kù)表里只有兩個(gè)字段:
------------
1 id(自增)
2 time
------------
我們需要實(shí)現(xiàn)的是诗舰,在大量插入請(qǐng)求過來時(shí)训裆,每3秒最多只有一條記錄寫成功蜀铲。
對(duì)分析過程不敢興趣的同學(xué)记劝,可以直接跳到最后的結(jié)論部分厌丑。

注:本例在mysql中實(shí)現(xiàn)渔呵。
建表語(yǔ)句如下:

create table test (
    id int not null auto_increment, 
    time int not null, 
    primary key (`id`));

默認(rèn)為innodb。

第一版
最開始的實(shí)現(xiàn)是這樣的耕驰。分為兩步录豺,第一步獲得當(dāng)前的max time,之后判斷要插入的行的time是否大于max time厚骗,如果大于领舰,則將本行的時(shí)間修改為time+3迟螺,再插入,偽代碼大概是這樣的:

begin; //開啟事務(wù)
1 select max(time) max_time from test;

2 if cur_time > max_time:
    insert into test(id, time) values(my_id, cur_time + 3);
end;

但是這樣會(huì)有問題锉桑,我們知道m(xù)ysql的select是不加鎖的窍株,是基于mvcc的讀球订。所以如果同時(shí)來兩個(gè)請(qǐng)求,它們?cè)诘谝徊侥玫较嗤膍ax_time微驶,都認(rèn)為可以繼續(xù)執(zhí)行步驟2,最后導(dǎo)致都執(zhí)行成功苟耻。這和我們題目要求的每3秒最多插入一條不符合扶檐,因此該方案不可取。

第二版
由于同一個(gè)3秒內(nèi)只能成功一條官卡,我們自然的想到通過unique_key的方式來實(shí)現(xiàn)。首先在time字段上增加unique索引哮翘,如下:

create unique index unique_time on test(time);

之后我們將用戶需要插入的時(shí)間按3秒取整,比如用戶插入的時(shí)間是4阻课, 按3秒取整后是3艰匙, 如果是6,按3秒取整后是6
3->3
4->3
5->3
6->6
7->6
....
這樣之后署驻,兩個(gè)用戶同時(shí)來旺上,第一步獲得max(time)糖埋,都可以更新,但在第二步征候,由于有unique_key的存在祟敛,只有一個(gè)會(huì)成功。偽代碼大概是這樣的:

begin;
1 select max(time) max_time from test;

2 if round(cur_time, 3) > max_time:
    insert into test(id, time) values(my_id, round(cur_time, 3);
end;

初看上去卒煞,是挺好的一個(gè)解決方案畔裕,我們將一個(gè)連續(xù)的長(zhǎng)時(shí)間段,按段映射為了一個(gè)個(gè)固定的時(shí)間值扮饶,從數(shù)軸上看,每一段只能有一條記錄成功扛点。好像已經(jīng)完美的解決了需求陵究。但是奥帘,考慮這樣一種情況:
系統(tǒng)內(nèi)最大時(shí)間初始為0
第5秒的時(shí)候來了一個(gè)請(qǐng)求,取整為3松蒜,更新成功已旧。
第6秒的時(shí)候又來了一個(gè)請(qǐng)求,取整為6惊楼,更新成功胁后。
但是嗦枢,這之間的更新間隔只有1秒。換句話說侣诺,這種方案只滿足了平均意義上的沒每3秒插入一條氧秘,但是沒有解決嚴(yán)格的每3秒插入一條這個(gè)條件。所以還是不行的搔确。

第三版
有了前面兩種失敗的方案做鋪墊膳算,我們自然的想到把這兩種方案結(jié)合一下。繼續(xù)在unique_key上做文章华匾,但是這次机隙,我們將time的更新策略變一下,如果滿足插入條件旭旭,直接插入max_time + 3印颤,偽代碼如下:

begin;
1 select max(time) max_time from test;

2 if cur_time > max_time:
    insert into test(id, time) values(my_id, max_time + 3);
end;

這樣已經(jīng)可以滿足題目中的要求了年局。
其實(shí)在細(xì)細(xì)想一下矢否,好像脑溢,還是有問題。
假設(shè)當(dāng)前max(time)為6验庙,5秒之后社牲,來了一個(gè)請(qǐng)求搏恤,cur_time為11,之后max_time被更新為6+3 = 9藤巢,1秒之后又來了個(gè)請(qǐng)求息罗,12 > 9,再次更新成功绍刮。
所以這個(gè)方案還是不可行的。

結(jié)論:

第四版
有了前面的失敗經(jīng)驗(yàn)捌木,這次刨裆,我們將unique_key從time上取下來彬檀,在數(shù)據(jù)庫(kù)里單獨(dú)增加一列dup_id,如下:

alter table test add column dup_id int not null;
create unique index unique_dup_id on test(dup_id);

然后偽代碼如下:

begin;
1 select max(time) max_time, max(dup_id) max_dup_id from test;

2 if (cur_time > max_time):
    insert into test (id, time, dup_id) values(my_id, cur_time + 3, max_dup_id + 1)
end;

其實(shí)這兩步操作完全沒必要放在一個(gè)事務(wù)中努潘,可以寫成獨(dú)立的兩條語(yǔ)句坤学。
如下:

1 select max(time) max_time, max(dup_id) max_dup_id from test;

2 if (cur_time > max_time):
    insert into test (id, time, dup_id) values(my_id, cur_time + 3, max_dup_id + 1)

第五版(簡(jiǎn)化版)
如果還是覺得添加一列蠻復(fù)雜,有一種簡(jiǎn)單一些的方案压怠,使用事務(wù)來完成菌瘫,主體思路是將max(time)鎖起來,偽代碼如下:

begin;
1 select max(time) max_time from test for update;

2 if (cur_time > max_time):
    insert into test(id, time) values(my_id, cur_time+3);
end;

這種for update的方式需要注意:
1 time需要有索引雨让,如果沒有索引忿等,則鎖全表
2 當(dāng)time上有索引時(shí),鎖[max_time, +∞](間隙鎖)娃闲,鎖住一個(gè)范圍

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末皇帮,一起剝皮案震驚了整個(gè)濱河市蛋辈,隨后出現(xiàn)的幾起案子将谊,更是在濱河造成了極大的恐慌尊浓,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,383評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件栋齿,死亡現(xiàn)場(chǎng)離奇詭異瓦堵,居然都是意外死亡菇用,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門惋鸥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來卦绣,“玉大人飞蚓,你說我怎么就攤上這事玷坠【⒚辏” “怎么了?”我有些...
    開封第一講書人閱讀 157,852評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵兄渺,是天一觀的道長(zhǎng)挂谍。 經(jīng)常有香客問我,道長(zhǎng)口叙,這世上最難降的妖魔是什么妄田? 我笑而不...
    開封第一講書人閱讀 56,621評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮脚曾,結(jié)果婚禮上启具,老公的妹妹穿的比我還像新娘。我一直安慰自己鲁冯,他們只是感情好晓褪,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,741評(píng)論 6 386
  • 文/花漫 我一把揭開白布涣仿。 她就那樣靜靜地躺著,像睡著了一般好港。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上钧汹,一...
    開封第一講書人閱讀 49,929評(píng)論 1 290
  • 那天丈探,我揣著相機(jī)與錄音拔莱,去河邊找鬼。 笑死塘秦,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的爪幻。 我是一名探鬼主播须误,決...
    沈念sama閱讀 39,076評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼奶甘!你這毒婦竟也來了甩十?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,803評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤鸭轮,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后窃爷,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體按厘,經(jīng)...
    沈念sama閱讀 44,265評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡逮京,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,582評(píng)論 2 327
  • 正文 我和宋清朗相戀三年束莫,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片策严。...
    茶點(diǎn)故事閱讀 38,716評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡妻导,死狀恐怖怀各,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情狐肢,我是刑警寧澤,帶...
    沈念sama閱讀 34,395評(píng)論 4 333
  • 正文 年R本政府宣布妓美,位于F島的核電站鲤孵,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏普监。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,039評(píng)論 3 316
  • 文/蒙蒙 一毙玻、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧梧疲,春花似錦幌氮、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)陆盘。三九已至,卻和暖如春太防,著一層夾襖步出監(jiān)牢的瞬間酸员,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評(píng)論 1 266
  • 我被黑心中介騙來泰國(guó)打工酿愧, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留嬉挡,地道東北人汇恤。 一個(gè)月前我還...
    沈念sama閱讀 46,488評(píng)論 2 361
  • 正文 我出身青樓基括,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親风皿。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,612評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容