Mysql全域ID產生方法_Mysql

來源:互聯網
上載者:User

生產系統隨著業務增長總會經曆一個業務量由小變大的過程,可擴充性是考量資料庫系統高可用性的一個重要指標;在單表/資料庫資料量過大,更新量不斷飆漲時,MySQL DBA往往會對業務系統提出sharding的方案。既然要sharding,那麼不可避免的要討論到sharding key問題,在有些業務系統中,必須保證sharding key全域唯一,比如存放商品的資料庫等,那麼如何產生全域唯一的ID呢,下文將從DBA的角度介紹幾種常見的方案。

1、使用CAS思想

什麼是CAS協議

Memcached於1.2.4版本新增CAS(Check and Set)協議類同於Java並發的CAS(Compare and Swap)原子操作,處理同一item被多個線程更改過程的並發問題

CAS的基本原理

基本原理非常簡單,一言以蔽之,就是“版本號碼”,每個儲存的資料對象,都有一個版本號碼。

我們可以從下面的例子來理解:

不採用CAS,則有如下的情景:

 •第一步,A取出資料對象X;
 •第二步,B取出資料對象X;
 •第三步,B修改資料對象X,並將其放入緩衝;
 •第四步,A修改資料對象X,並將其放入緩衝。

結論:第四步中會產生資料寫入衝突。

採用CAS協議,則是如下的情景。

 •第一步,A取出資料對象X,並擷取到CAS-ID1;

•第二步,B取出資料對象X,並擷取到CAS-ID2; 

•第三步,B修改資料對象X,在寫入緩衝前,檢查CAS-ID與緩衝空間中該資料的CAS-ID是否一致。結果是“一致”,就將修改後的帶有CAS-ID2的X寫入到緩衝。

 •第四步,A修改資料對象Y,在寫入緩衝前,檢查CAS-ID與緩衝空間中該資料的CAS-ID是否一致。結果是“不一致”,則拒絕寫入,返回儲存失敗。

這樣CAS協議就用了“版本號碼”的思想,解決了衝突問題。(樂觀鎖概念)

其實這裡並不是嚴格的CAS,而是使用了比較交換原子操作的思想。

產生思路如下:每次產生全域id時,先從sequence表中擷取當前的全域最大id。然後在擷取的全域id上做加1操作,加1後的值更新到資料庫,如加1後的值為203,表名是users,資料表結構如下:

CREATE TABLE `SEQUENCE` (  `name` varchar(30) NOT NULL COMMENT '分表的表名',  `gid` bigint(20) NOT NULL COMMENT '最大全域id',  PRIMARY KEY (`name`)) ENGINE=innodb 

sql語句

update sequence set gid = 203 where name = 'users' and gid < 203; 

sql語句的 and gid < 203 是為了保證並發環境下gid的值只增不減。

如果update語句的影響記錄條數為0說明,已經有其他進程提前產生了203這個值,並寫入了資料庫。需要重複以上步驟從新產生。

代碼實現如下:

//$name 表名function next_id_db($name){  //擷取資料庫全域sequence對象  $seq_dao = Wk_Sequence_Dao_Sequence::getInstance();  $threshold = 100; //最大嘗試次數  for($i = 0; $i < $threshold; $i++){    $last_id = $seq_dao->get_seq_id($name);//從資料庫擷取全域id    $id = $last_id +1;    $ret = $seq_dao->set_seq_id($name, $id);    if($ret){      return $id;      break;    }  }  return false;}

2、使用全域鎖

在進行並發編程時,一般都會使用鎖機制。其實,全域id的產生也是解決並發問題。

產生思路如下:

在使用redis的setnx方法和memcace的add方法時,如果指定的key已經存在,則返回false。利用這個特性,實現全域鎖

每次產生全域id前,先檢測指定的key是否存在,如果不存在則使用redis的incr方法或者memcache的increment進行加1操作。這兩個方法的傳回值是加1後的值,如果存在,則程式進入迴圈等待狀態。迴圈過程中不斷檢測key是否還存在,如果key不存在就執行上面的操作。

代碼如下:

//使用redis實現//$name 為 邏輯表名function next_id_redis($name){  $redis = Wk_Redis_Util::getRedis();//擷取redis對象  $seq_dao = Wk_Sequence_Dao_Sequence::getInstance();//擷取儲存全域id資料表對象  if(!is_object($redis)){    throw new Exception("fail to create redis object");  }  $max_times = 10; //最大執行次數 避免redis停用時候 進入死迴圈  while(1){    $i++;    //檢測key是否存在,相當於檢測鎖是否存在    $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){//如果迴圈等待時間大於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);//從資料庫擷取全域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寫入資料表sequence  $redis->delete("sequence_{$name}_flag");//刪除key,相當於釋放鎖  $db = null;  return $id;} 

3、redis和db結合

使用redis直接操作記憶體,可能效能會好些。但是如果redis死掉後,如何處理呢?把以上兩種方案結合,提供更好的穩定性。
代碼如下:

function next_id($name){  try{    return $this->next_id_redis($name);  }  catch(Exception $e){    return $this->next_id_db($name);  }} 

4、Flicker的解決方案

因為mysql本身支援auto_increment操作,很自然地,我們會想到藉助這個特性來實現這個功能。Flicker在解決全域ID產生方案裡就採用了MySQL自增長ID的機制(auto_increment + replace into + MyISAM)。一個產生64位ID方案具體就是這樣的:
先建立單獨的資料庫(eg:ticket),然後建立一個表:

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 

當我們插入記錄後,執行SELECT * from Tickets64,查詢結果就是這樣的:

+-------------------+------+
| id                | stub |
+-------------------+------+
| 72157623227190423 |    a |
+-------------------+------+

在我們的應用端需要做下面這兩個操作,在一個事務會話裡提交:

REPLACE INTO Tickets64 (stub) VALUES ('a');SELECT LAST_INSERT_ID(); 

這樣我們就能拿到不斷增長且不重複的ID了。
到上面為止,我們只是在單台資料庫上產生ID,從高可用角度考慮,
接下來就要解決單點故障問題:Flicker啟用了兩台資料庫伺服器來產生ID,
通過區分auto_increment的起始值和步長來產生奇偶數的ID。

TicketServer1:auto-increment-increment = 2auto-increment-offset = 1TicketServer2:auto-increment-increment = 2auto-increment-offset = 2 

最後,在用戶端只需要通過輪詢方式取ID就可以了。

 •優點:充分藉助資料庫的自增ID機制,提供高可靠性,產生的ID有序。

 •缺點:佔用兩個獨立的MySQL執行個體,有些浪費資源,成本較高。

以上內容是小編給大家分享的Mysql全域ID產生方法,希望大家喜歡。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.