一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務器之家:專注于服務器技術及軟件下載分享
分類導航

Mysql|Sql Server|Oracle|Redis|MongoDB|PostgreSQL|Sqlite|DB2|mariadb|Access|數據庫技術|

服務器之家 - 數據庫 - Mysql - Mysql全局ID生成方法

Mysql全局ID生成方法

2020-05-26 15:51chenpingzhao Mysql

本文給大家介紹mysql全局id生成方法,涉及到mysql全局id相關知識,感興趣的朋友一起學習吧

生產系統隨著業務增長總會經歷一個業務量由小變大的過程,可擴展性是考量數據庫系統高可用性的一個重要指標;在單表/數據庫數據量過大,更新量不斷飆漲時,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,數據表結構如下:

?
1
2
3
4
5
CREATE TABLE `SEQUENCE` (
  `name` varchar(30) NOT NULL COMMENT '分表的表名',
  `gid` bigint(20) NOT NULL COMMENT '最大全局id',
  PRIMARY KEY (`name`)
) ENGINE=innodb

sql語句

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

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

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

代碼實現如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//$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不存在就執行上面的操作。

代碼如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
//使用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死掉后,如何處理呢?把以上兩種方案結合,提供更好的穩定性。
代碼如下:

?
1
2
3
4
5
6
7
8
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),然后創建一個表:

?
1
2
3
4
5
6
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 |
+-------------------+------+

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

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

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

?
1
2
3
4
5
6
TicketServer1:
auto-increment-increment = 2
auto-increment-offset = 1
TicketServer2:
auto-increment-increment = 2
auto-increment-offset = 2

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

 •優點:充分借助數據庫的自增ID機制,提供高可靠性,生成的ID有序。

 •缺點:占用兩個獨立的MySQL實例,有些浪費資源,成本較高。

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

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 亚洲国产精品久久久久久网站 | 国产第一页无线好源 | 成人精品区 | 精品无码国产污污污免费网站2 | 青草草视频在线观看 | 午夜在线观看免费完整直播网 | 草莓永久地域网名入2022 | xxxx俄罗斯大白屁股 | 国产在线精品观看 | 国产精品理论片在线观看 | 欧美3d怪物交videos网站 | 福利视频一区青娱 | 福利国模私拍视频在线观看 | 精品在线免费播放 | 国产免费又粗又猛又爽视频国产 | 欧美精品v欧洲高清 | 嫩草影院永久在线一二三四 | 亚洲精品影视 | 四虎2021地址入口 | www.在线观看视频 | 国产精品免费看香蕉 | 男生的j桶女人屁免费视频 男生操男生 | 小小水蜜桃视频高清在线播放 | 加勒比伊人 | 美女黄板视频 | 91探花在线观看 | 亚洲精品色综合久久 | 好吊妞视频998www| 4tube高清性欧美 | w7w7w7w7w免费 | 免费观看日本 | 加勒比伊人 | 大学生初次破苞免费视频 | 99自拍视频在线观看 | 国产第7页 | 国产伦精品一区二区三区免费迷 | 男人女人性生活视频 | 99热这里只有精品国产在热久久 | 全黄一级裸片视频免费 | 国产外围 | 青青青视频蜜桃一区二区 |