1. 程式人生 > 資料庫 >MySQL如何生成唯一的server-id

MySQL如何生成唯一的server-id

前言

我們都知道MySQL用server-id來唯一的標識某個資料庫例項,並在鏈式或雙主複製結構中用它來避免sql語句的無限迴圈。這篇文章分享下我對server-id的理解,然後比較和權衡生成唯一server-id的幾種方式。

server_id的用途

簡單說來,server_id有兩個用途:

1. 用來標記binlog event的源產地,就是SQL語句最開始源自於哪裡。

2. 用於IO_thread對主庫binlog的過濾。如果沒有設定replicate-same-server-id=1,那麼當從庫的io_thread發現event的源與自己的server-id相同時,就會跳過該event,不把該event寫入到relay log中。從庫的sql_thread自然就不會執行該event。這在鏈式或雙主結構中可以避免sql語句的無限迴圈。

注意:相同server-id的event在io_thread這一層就過濾了;而對於replicate-(do|ignore)-等規則,則是在sql_thread這一層過濾的。io_thread和sql_thread都有過濾的功能。

server_id為何不能重複

在同一個叢集中,server-id一旦重複,可能引發一些詭異問題。

看看下面兩種情況:

圖1:主庫與從庫的server-id不同,但是兩個或多個從庫的server-id相同

這種情況下複製會左右搖擺。當兩個從庫的server-id相同時,如果從庫1已經連線上主庫,此時從庫2也需要連線到主庫,發現之前有server-id相同的連線,就會先登出該連線,然後重新註冊。

參考下面的程式碼片段:

int register_slave(THD* thd,uchar* packet,uint packet_length)
{
 int res;
 SLAVE_INFO *si;
...
 if (!(si->master_id= uint4korr(p)))
 si->master_id= server_id;
 si->thd= thd;
 pthread_mutex_lock(&LOCK_slave_list);
/* 先登出相同server-id的連線*/
 unregister_slave(thd,0); 
/* 重新註冊*/
 res= my_hash_insert(&slave_list,(uchar*) si);
 pthread_mutex_unlock(&LOCK_slave_list);
 return res;
...
}

兩臺從庫不停的註冊,不停的登出,會產生很多relay log檔案,檢視從庫狀態會看到relay log檔名不停改變,從庫的複製狀態一會是yes一會是正在連線中。

圖2:鏈式或雙主結構中,主庫與從庫的server-id相同

從庫1同時又是relay資料庫,它能正確同步,然後把relay-log內容重寫到自己的binlog中。當server-id為100的從庫2 io執行緒獲取binlog時,發現所有內容都是源自於自己,就會丟棄這些event。因此從庫2無法正確同步主庫的資料。只有直接寫relay server的event能正確同步到從庫2。

上面兩種情況可以看到,在同一個replication set中,保持server-id的唯一性非常重要。

server_id的動態修改

無意中發現server-id竟然是可以動態修改的,可別高興的太早。好處是,上面圖1的情況下,直接修改其中一個從庫的server-id就可以解決server-id衝突的問題。壞處很隱蔽,如下圖的結構:

現在假設active-master因為某種原因與passive-master的同步斷開後,passive-master上進行了一些ddl變更。然後某dba突發奇想把passive-master的server-id修改為400。當雙master的複製啟動後,那些之前在passive-master上執行的server-id為200的ddl變更,會從此陷入死迴圈。如果是alter table t engine=innodb,它會一直不停,可能你會發現。但是像update a=a+1;這樣的sql,你很難發現。當然這種場景只是我的杜撰,這兒有個更真實的例子主備備的兩個備機轉為雙master時出現的詭異slave lag問題:http://hatemysql.com/2010/10/15/主備備的兩個備機轉為雙master時出現的詭異slave-lag問題/。

舉這兩個例子只是想說明修改server-id有點危險,最好不要去修改,那麼能一步到位生成它嗎?

生成唯一的server_id

常用的方法有如下幾種:

1. 採用隨機數

mysql的server-id是4位元組整數,範圍從0-4294967295,因此採用該範圍內的隨機數來作為server-id產生衝突的可能性是非常小的。

2. 採用時間戳

直接用date +%s來生成server-id。一天86400秒來計算,往後計算50年,最大的server-id也才使用到86400*365*50,完全在server-id範圍內。

3. 採用ip地址+埠

這是我們經常採用的方法。例如ip為192.168.122.23,埠為3309,那麼server-id可以寫為122233309。產生衝突的可能性比較小:遇到*.*.122.23 或者*.*.12.223,而且搭建了同一個replication set的3309才會出現。

4. 採用集中的發號器

在管理伺服器上採用自增的id來統一分配server-id。這可以保證不衝突,但是需要維護中心節點。

5. 分開管理每個replication set

在每個replication set中為mysql庫增加一個管理表,保證每個從庫的server-id不衝突。

上面的幾種方法都不賴,但是:

  • 方法4加了維護負擔,而且開發環境、測試環境、線上環境都維護一套發號器的話,有點麻煩,混在一起又可能遇到網段隔離的風險,還有發號器資料庫許可權的問題難於控制。所以不推薦。
  • 方法5實現了自治,但是管理成本有點高。從庫要能夠寫主庫的server-id表,複雜。
  • 5種方法都存在的問題是,使用冷備的資料來擴容,server-id需要手動去修改,否則就與冷備源的server-id衝突。而且,當mysql啟動的時候,你無法判斷該mysql是剛通過備份擴容的,還是之前一直正常執行的。所以你不知道這個server-id到底要不要改。而我希望server-id對dba完全透明,又絕不產生衝突,即可徹底遮蔽這個討厭的東西。

建議的方法

其實很簡單。ipv4是4位元組的整數,與server-id的範圍完全一樣。我們認為只有ip地址+端口才能唯一的確定一個mysql例項,所以總是希望把ip資訊和埠資訊都整合到server-id中。但是別忘了,一個ip上不能同時啟動兩個一樣的埠。所以,server-id只需採用ip地址的整數形式:select INET_ATON('192.168.12.45'),3232238637!所有新上線的例項,mysql啟動指令碼強制對server-id進行檢查,發現server-id不對就進行糾正,然後啟動。這種方法有個前提條件:同一機器上的多個instance不要有主從關係,否則server-id一樣就會導致問題。這種情況一般只會在測試環境出現,在線上基本是沒有的。滿足了這個前提,所有問題迎刃而解。

總結

以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,謝謝大家對我們的支援。