《MYSQL教程Mysql全局ID生成方法》要點:
本文介紹了MYSQL教程Mysql全局ID生成方法,希望對您有用。如果有疑問,可以聯(lián)系我們。
生產(chǎn)系統(tǒng)隨著業(yè)務(wù)增長總會經(jīng)歷一個業(yè)務(wù)量由小變大的過程,可擴展性是考量數(shù)據(jù)庫系統(tǒng)高可用性的一個重要指標;在單表/數(shù)據(jù)庫數(shù)據(jù)量過大,更新量不斷飆漲時,MySQL DBA往往會對業(yè)務(wù)系統(tǒng)提出sharding的方案.既然要sharding,那么不可避免的要討論到sharding key問題,在有些業(yè)務(wù)系統(tǒng)中,必須保證sharding key全局唯一,比如存放商品的數(shù)據(jù)庫等,那么如何生成全局唯一的ID呢,下文將從DBA的角度介紹幾種常見的方案.
MYSQL學(xué)習(xí)
1、使用CAS思想
MYSQL學(xué)習(xí)
什么是CAS協(xié)議 MYSQL學(xué)習(xí)
Memcached于1.2.4版本新增CAS(Check and Set)協(xié)議類同于Java并發(fā)的CAS(Compare and Swap)原子操作,處理同一item被多個線程更改過程的并發(fā)問題
MYSQL學(xué)習(xí)
CAS的基本原理
MYSQL學(xué)習(xí)
基本原理非常簡單,一言以蔽之,就是“版本號”,每個存儲的數(shù)據(jù)對象,都有一個版本號.
MYSQL學(xué)習(xí)
我們可以從下面的例子來理解:
MYSQL學(xué)習(xí)
不采用CAS,則有如下的情景:MYSQL學(xué)習(xí)
??第一步,A取出數(shù)據(jù)對象X;
??第二步,B取出數(shù)據(jù)對象X;
??第三步,B修改數(shù)據(jù)對象X,并將其放入緩存;
??第四步,A修改數(shù)據(jù)對象X,并將其放入緩存.
MYSQL學(xué)習(xí)
結(jié)論:第四步中會產(chǎn)生數(shù)據(jù)寫入沖突.
MYSQL學(xué)習(xí)
采用CAS協(xié)議,則是如下的情景.MYSQL學(xué)習(xí)
??第一步,A取出數(shù)據(jù)對象X,并獲取到CAS-ID1;
MYSQL學(xué)習(xí)
?第二步,B取出數(shù)據(jù)對象X,并獲取到CAS-ID2;?MYSQL學(xué)習(xí)
?第三步,B修改數(shù)據(jù)對象X,在寫入緩存前,檢查CAS-ID與緩存空間中該數(shù)據(jù)的CAS-ID是否一致.結(jié)果是“一致”,就將修改后的帶有CAS-ID2的X寫入到緩存.
MYSQL學(xué)習(xí)
??第四步,A修改數(shù)據(jù)對象Y,在寫入緩存前,檢查CAS-ID與緩存空間中該數(shù)據(jù)的CAS-ID是否一致.結(jié)果是“不一致”,則拒絕寫入,返回存儲失敗.
MYSQL學(xué)習(xí)
這樣CAS協(xié)議就用了“版本號”的思想,解決了沖突問題.(樂觀鎖概念)
MYSQL學(xué)習(xí)
其實這里并不是嚴格的CAS,而是使用了比較交換原子操作的思想.
MYSQL學(xué)習(xí)
生成思路如下:每次生成全局id時,先從sequence表中獲取當(dāng)前的全局最大id.然后在獲取的全局id上做加1操作,加1后的值更新到數(shù)據(jù)庫,如加1后的值為203,表名是users,數(shù)據(jù)表結(jié)構(gòu)如下:MYSQL學(xué)習(xí)
CREATE TABLE `SEQUENCE` ( `name` varchar(30) NOT NULL COMMENT '分表的表名', `gid` bigint(20) NOT NULL COMMENT '最大全局id', PRIMARY KEY (`name`) ) ENGINE=innodb
sql語句
MYSQL學(xué)習(xí)
update sequence set gid = 203 where name = 'users' and gid < 203;
sql語句的 and gid < 203 是為了保證并發(fā)環(huán)境下gid的值只增不減.
MYSQL學(xué)習(xí)
如果update語句的影響記錄條數(shù)為0說明,已經(jīng)有其他進程提前生成了203這個值,并寫入了數(shù)據(jù)庫.需要重復(fù)以上步驟從新生成.
MYSQL學(xué)習(xí)
代碼實現(xiàn)如下:MYSQL學(xué)習(xí)
//$name 表名 function next_id_db($name){ //獲取數(shù)據(jù)庫全局sequence對象 $seq_dao = Wk_Sequence_Dao_Sequence::getInstance(); $threshold = 100; //最大嘗試次數(shù) for($i = 0; $i < $threshold; $i++){ $last_id = $seq_dao->get_seq_id($name);//從數(shù)據(jù)庫獲取全局id $id = $last_id +1; $ret = $seq_dao->set_seq_id($name, $id); if($ret){ return $id; break; } } return false; }
2、使用全局鎖
MYSQL學(xué)習(xí)
在進行并發(fā)編程時,一般都會使用鎖機制.其實,全局id的生成也是解決并發(fā)問題.
MYSQL學(xué)習(xí)
生成思路如下:
MYSQL學(xué)習(xí)
在使用redis的setnx辦法和memcace的add辦法時,如果指定的key已經(jīng)存在,則返回false.利用這個特性,實現(xiàn)全局鎖
MYSQL學(xué)習(xí)
每次生成全局id前,先檢測指定的key是否存在,如果不存在則使用redis的incr辦法或者memcache的increment進行加1操作.這兩個辦法的返回值是加1后的值,如果存在,則程序進入循環(huán)等待狀態(tài).循環(huán)過程中不斷檢測key是否還存在,如果key不存在就執(zhí)行上面的操作.
MYSQL學(xué)習(xí)
代碼如下:
MYSQL學(xué)習(xí)
//使用redis實現(xiàn) //$name 為 邏輯表名 function next_id_redis($name){ $redis = Wk_Redis_Util::getRedis();//獲取redis對象 $seq_dao = Wk_Sequence_Dao_Sequence::getInstance();//獲取存儲全局id數(shù)據(jù)表對象 if(!is_object($redis)){ throw new Exception("fail to create redis object"); } $max_times = 10; //最大執(zhí)行次數(shù) 避免redis不可用的時候 進入死循環(huán) while(1){ $i++; //檢測key是否存在,相當(dāng)于檢測鎖是否存在 $ret = $redis->setnx("sequence_{$name}_flag",time()); if($ret){ break; } if($i > $max_times){ break; } $time = $redis->get("sequence_{$name}_flag"); if(is_numeric($time) && time() - $time > 1){//如果循環(huán)等待時間大于1秒,則不再等待. break; } } $id = $redis->incr("sequence_{$name}"); //如果操作失敗,則從sequence表中獲取全局id并加載到redis if (intval($id) === 1 or $id === false) { $last_id = $seq_dao->get_seq_id($name);//從數(shù)據(jù)庫獲取全局id if(!is_numeric($last_id)){ throw new Exception("fail to get id from db"); } $ret = $redis->set("sequence_{$name}",$last_id); if($ret == false){ throw new Exception("fail to set redis key [ sequence_{$name} ]"); } $id = $redis->incr("sequence_{$name}"); if(!is_numeric($id)){ throw new Exception("fail to incr redis key [ sequence_{$name} ]"); } } $seq_dao->set_seq_id($name, $id);//把生成的全局id寫入數(shù)據(jù)表sequence $redis->delete("sequence_{$name}_flag");//刪除key,相當(dāng)于釋放鎖 $db = null; return $id; }
3、redis和db結(jié)合
MYSQL學(xué)習(xí)
使用redis直接操作內(nèi)存,可能性能會好些.但是如果redis死掉后,如何處理呢?把以上兩種方案結(jié)合,提供更好的穩(wěn)定性.
代碼如下:MYSQL學(xué)習(xí)
function next_id($name){ try{ return $this->next_id_redis($name); } catch(Exception $e){ return $this->next_id_db($name); } }
4、Flicker的解決方案
MYSQL學(xué)習(xí)
因為mysql本身支持auto_increment操作,很自然地,我們會想到借助這個特性來實現(xiàn)這個功能.Flicker在解決全局ID生成方案里就采用了MySQL自增長ID的機制(auto_increment + replace into + MyISAM).一個生成64位ID方案具體就是這樣的:
先創(chuàng)建單獨的數(shù)據(jù)庫(eg:ticket),然后創(chuàng)建一個表:MYSQL學(xué)習(xí)
CREATE TABLE Tickets64 ( id bigint(20) unsigned NOT NULL auto_increment, stub char(1) NOT NULL default '', PRIMARY KEY (id), UNIQUE KEY stub (stub) ) ENGINE=MyISAM
當(dāng)我們插入記錄后,執(zhí)行SELECT * from Tickets64,查詢結(jié)果就是這樣的:
MYSQL學(xué)習(xí)
+-------------------+------+
| id??????????????? | stub |
+-------------------+------+
| 72157623227190423 |??? a |
+-------------------+------+MYSQL學(xué)習(xí)
在我們的應(yīng)用端需要做下面這兩個操作,在一個事務(wù)會話里提交:MYSQL學(xué)習(xí)
REPLACE INTO Tickets64 (stub) VALUES ('a'); SELECT LAST_INSERT_ID();
這樣我們就能拿到不斷增長且不重復(fù)的ID了.
到上面為止,我們只是在單臺數(shù)據(jù)庫上生成ID,從高可用角度考慮,
接下來就要解決單點故障問題:Flicker啟用了兩臺數(shù)據(jù)庫服務(wù)器來生成ID,
通過區(qū)分auto_increment的起始值和步長來生成奇偶數(shù)的ID.
MYSQL學(xué)習(xí)
TicketServer1: auto-increment-increment = 2 auto-increment-offset = 1 TicketServer2: auto-increment-increment = 2 auto-increment-offset = 2
最后,在客戶端只需要通過輪詢方式取ID就可以了.
MYSQL學(xué)習(xí)
??優(yōu)點:充分借助數(shù)據(jù)庫的自增ID機制,提供高可靠性,生成的ID有序.
MYSQL學(xué)習(xí)
??缺點:占用兩個獨立的MySQL實例,有些浪費資源,成本較高.MYSQL學(xué)習(xí)
以上內(nèi)容是小編給大家分享的Mysql全局ID生成辦法,希望大家喜歡.MYSQL學(xué)習(xí)
維易PHP培訓(xùn)學(xué)院每天發(fā)布《MYSQL教程Mysql全局ID生成方法》等實戰(zhàn)技能,PHP、MYSQL、LINUX、APP、JS,CSS全面培養(yǎng)人才。
轉(zhuǎn)載請注明本頁網(wǎng)址:
http://www.snjht.com/jiaocheng/11239.html