Percona Xtradb Cluster的設計與實現

Percona Xtradb Cluster的實現是在原mysql代碼上通過Galera包將不同的mysql實例連接起來,實現了multi-master的集羣架構,如下圖所示:


 
上圖中有三個實例,組成了一個集羣,而這三個節點與普通的主從架構不同,它們都可以作爲主節點,三個節點是對等的,這種一般稱爲multi-master架構,當有客戶端要寫入或者讀取數據時,隨便連接哪個實例都是一樣的,讀到的數據是相同的,寫入某一個節點之後,集羣自己會將新數據同步到其它節點上面,這種架構不共享任何數據,是一種高冗餘架構。
 
一般的使用方法是,在這個集羣上面,再搭建一箇中間層,這個中間層的功能包括負責根據指點算法找到一個實例供客戶端連接,負責使三個實例的負載基本平衡,負責在客戶端與實例的連接斷開之後重連,也可以負責讀寫分離(在機器性能不同的情況下可以做這樣的優化)等等,使用這個中間層之後,把這三個實例的架構在客戶端方面是透明的,客戶端只需要指定這個集羣的數據源地址,連接到中間層即可,中間層會負責客戶端與服務器實例連接的傳遞工作。
 
這裏其實最核心的問題是,在三個實例之間,因爲它們的關係是對等的,multi-master架構的,那麼在同時寫入的時候,如何保證整個集羣數據的一致性,完整性與正確性。
 
在通常mysql的使用過程中,也不難實現一種multi-master架構,但是一般需要上層應用來配合,比如先要約定每個表必須要有自增列,並且如果是2個節點的情況下,一個節點只能寫偶數的值,而另一個節點只能寫奇數的值,同時2個節點之間互相做複製,因爲2個節點寫入的東西不同,所以複製不會衝突,在這種約定之下,可以基本實現多master的架構,也可以保證數據的完整性與一致性。但這種方式使用起來還是有限制,同時不具有擴展性,不是真正意義上的集羣。
 
而通過使用Galera,它在裏面通過判斷鍵值的衝突方式實現了真正意義上的multi-master,下面就這個問題展開深入解析它的實現方式。
 
Percona Xtradb Cluster的實現方式基本上是在Percona MySQL Server的基礎上將一個開源包Galera加入進來,實現了數據的同步。
 
Galera提供了很多接口,下面是幾個最重要的接口:
 
1. 連接到指定集羣名字的集羣中,相當於是一個新的節點加入到一個已經存在的集羣或者建立一個新的集羣
    wsrep_status_t (*connect) (wsrep_t*     wsrep,
                               const char *  cluster_name,
                               const char *  cluster_url,
                               const char *  state_donor,
                               wsrep_bool_t bootstrap);
  
2. 下面這個接口的作用是,在這個函數裏面一直接收其它節點廣播出來的數據,並且調用複製APPLY函數執行復制操作。這個函數只要返回,說明執行出錯或者發生其它問題,只要正常這個函數一直阻塞在這裏(具體應該不是阻塞,而是一直在recv函數中執行操作),而裏面一直在調用某幾個回調函數,回調函數會在初始化的時候指定。
wsrep_status_t (*recv)(wsrep_t* wsrep, void* recv_ctx);
 
3. 下面這個接口的作用是在數據庫事務提交時,會使用2階段提交方式,在ha_trans_commit中,首先會針對每一個存儲引擎執行一個ht->prepare函數,而對於Galera,在內部實現也是當作一個內嵌的存儲引擎使用的,所以它執行的是wsrep_prepare,這個函數的功能是將在執行過程中產生的binlog通過下面會介紹到的接口append_data傳到其它節點上面去(或者沒有傳過去,只是將這些數據對象存儲在本地,等待提交操作),然後再通過下面這個接口pre_commit去與其它節點的Galera通訊檢查有沒有衝突,這個過程也就是在介紹Galera的文章中說到的certification階段。下面表示出了這個驗證過程。
wsrep_status_t (*pre_commit)(wsrep_t*                wsrep,
                                 wsrep_conn_id_t         conn_id,
                                 wsrep_ws_handle_t*      ws_handle,
                                 uint32_t                flags,
                                 wsrep_trx_meta_t*       meta);
 
4. 下面這個接口的作用是,在由於某種原因導致在複製的過程中,其它節點已經成功驗證,並沒有衝突,但本節點自己出錯了,則此時會通過這個接口模擬複製的過程在本節點將這個事務複製APPLY。
wsrep_status_t (*replay_trx)(wsrep_t*            wsrep,
                                 wsrep_ws_handle_t*  ws_handle,
                                 void*               trx_ctx);
 
5.  下面這個接口是很重要的,在Galera中解決了一個關鍵問題是不同對等節點同時做寫操作時,如果保證它們之間的衝突的問題。
 wsrep_status_t (*append_key)(wsrep_t*            wsrep,
                                 wsrep_ws_handle_t*  ws_handle,
                                 const wsrep_key_t*  keys,
                                 size_t              count,
                                 enum wsrep_key_type type,
                                 wsrep_bool_t        copy);
 
6. 下面這個接口的作用上面已經提到過了,它是用來將同步數據傳遞到其它節點上保證所有節點的一致性,這個接口與上面的接口是對應的,相當於是key-value模式,這個是value,上面的是key,基本的作用是,key用來保證每個節點併發寫入時不會衝突(當然衝突時會直接報錯回滾),value用來保證每個節點的數據都是一致的,而對於mysql的實現方式而言,value完全就是binlog。
    wsrep_status_t (*append_data)(wsrep_t*                wsrep,
                                  wsrep_ws_handle_t*      ws_handle,
                                  const struct wsrep_buf* data,
                                  size_t                  count,
                                  enum wsrep_data_type    type,
                                  wsrep_bool_t            copy);
  
7.  下面這個接口的作用是初始化一個Galera對象,一個實例對應一個這樣的對象,在起動服務器的時候初始化,將所有需要的參數,環境變量包括:集羣姓名、實例地址,更重要的是,因爲Galera拿到的是binlog,它是不知道如何複製數據的,所以這裏必須要指定一個接口來做binlog的複製,初始化時告訴Galera,複製數據的接口爲wsrep_apply_cb,那麼這個接口接收到的數據就是binlog,它來解析並APPLY這些binlog,到這裏時,Galera已經根據這個binlog對應的key做了衝突判斷,並且驗證通過的。還有一些類似的接口也要在這裏指定,後面再做介紹。
   wsrep_status_t (*init)   (wsrep_t*                      wsrep,
                              const struct wsrep_init_args* args);
  
上面這幾個接口是最基本的操作接口,這是Galera對外提供的接口。那麼現在已經知道這些接口的情況下,就整個mysql工作流程到Galera包的調用說明一下一個PXC事務的工作方式。
 
首先在mysqld啓動的時候,執行函數wsrep_init_startup,這個函數的功能是初始化整個mysql的Galera系統,它主要實現下面一些功能:
  • 在這個函數中首先做的是wsrep_init,這裏初始化很多變量,從變量wsrep_provider中找到對應的Galera動態鏈接包,找到它的接口(包括上面介紹的7個),最主要的是下面結構的初始化:
 
  struct wsrep_init_args wsrep_args;
 
  struct wsrep_gtid const state_id = { local_uuid, local_seqno };
 
  wsrep_args.data_dir                  = wsrep_data_home_dir;
  wsrep_args.node_name             = (wsrep_node_name) ? wsrep_node_name : "";
  wsrep_args.node_address         = node_addr;
  wsrep_args.node_incoming       = inc_addr;
  wsrep_args.options                   = (wsrep_provider_options) ?  wsrep_provider_options : "";
  wsrep_args.proto_ver               = wsrep_max_protocol_version;
 
  wsrep_args.state_id                  = &state_id;
 
  wsrep_args.logger_cb               = wsrep_log_cb;
  wsrep_args.view_handler_cb   = wsrep_view_handler_cb;
  wsrep_args.apply_cb               = wsrep_apply_cb;
  wsrep_args.commit_cb             = wsrep_commit_cb;
  wsrep_args.unordered_cb         = wsrep_unordered_cb;
  wsrep_args.sst_donate_cb        = wsrep_sst_donate_cb;
  wsrep_args.synced_cb             = wsrep_synced_cb;
 
  rcode = wsrep->init(wsrep, &wsrep_args);
  
這個結構是Galera節點信息初始化的入口,在最後它會通過wsrep->init(wsrep, &wsrep_args);將當前wsrep初始化,這個wsrep就是當前實例的Galera的全局對象。
wsrep_args.data_dir表示的是當前數據庫的目錄,對應變量wsrep_data_home_dir
wsrep_args.node_name表示的是當前實例的名字,對應變量wsrep_node_name,一般默認會被設置爲機器名
wsrep_args.node_address表示當前節點的地址,對應變量wsrep_node_address,可以不設置,如果不設置的話,系統會自己找到ip。
wsrep_args.node_incoming表示可以接收連接請求的節點,對應狀態變量wsrep_incoming_addresses
wsrep_args.state_id表示一個節點的UUID,對應狀態變量wsrep_cluster_state_uuid
 
再下面的是幾個回調函數的初始化,需要回調函數的原因有:
  1. mysql與galera是分層的,上層可以調用galera的接口來複制數據庫的,但是對於galera,它是不知道如何提交一個事務,如何判斷寫入結果集是不是衝突,也不知道如何做sst,更不知道如何解析binlog來做複製。所以上層必須要向galera下層說明如何去解析,如何去執行等。
  2. 框架的一般實現方式,這屬於廢話。
下面分別介紹每個回調函數的功能:
  1. wsrep_log_cb:用來做日誌的,因爲galera產生一些日誌後,需要告訴mysql,轉換爲mysql的日誌,所以它需要這個接口告訴mysql,讓mysql記錄底層產生的日誌。
  2. wsrep_view_handler_cb:這是Galera啓動之後,第一個需要執行的回調函數,在新節點的Galera加入到並且已經連接到集羣中時,新node會拿自己的local_state_uuid和集羣的cluster_state_uuid對比,如果這兩者有區別時,就需要做sst或者ist,此時這個回調函數所做的工作是,根據不同的同步方式,準備不同的同步命令:
    1. 對於mysqldump,這裏只需要將數據接收地址及執行方式告訴集羣即可。因爲對於這種的實現方式是,doner直接在遠程連接新加入的mysql實例,將它自己的所有數據dump出來直接用mysql執行了,所以新加入的節點不需要做任何操作,因爲doner在導入的時候,如果新節點已經啓動完成,也是可以做操作的。
    2. 對於rsync,這裏需要做的是告訴集羣同步方式及執行命令,因爲這與mysqldump不同,對於rsync與xtrabackup,它們用到的同步方式分別是腳本wsrep_sst_rsync,wsrep_sst_xtrabackup,這2個腳本都放在安裝目錄的bin下面,在這裏會把相應的命令生成出來,對於rsync,類似是這樣的命令:
      wsrep_sst_rsync --role 'joiner' --address '192.168.236.231' --auth 'pxc_sst:txsimIj3eSb8fttz' --datadir '/home/q/zhufeng.wang/pxcdata2/' --defaults-file '/home/q/zhufeng.wang/pxcdata2/my.cnf' --parent '11048',
      在這個命令中,--role指明當前節點是joiner還是doner,因爲在腳本中會做不同處理,--address表示向什麼地址同步,--auth是在配置文件中指定的權限信息,--datadir表示當前新加入的庫的數據目錄,--defaults-file表示新加入節點的配置文件,--parent表示加入者的進程id號。
      這裏生成的命令的作用是,以joiner的角色執行腳本wsrep_sst_rsync,從腳本中可以看到,它會給mysql返回一個“ready 192.168.236.231:4444/rsync_sst”的字符串,地址和ip表示的是加入節點上面用來傳數據的RSYNC(具體說應該是NC)的地址和端口,4444是腳本中已經寫好的值,如果這個端口已經在用了,則會失敗。而後面的rsync_sst是用來檢查是不是已經有人在做了,如果文件rsync_sst.pid已經存在,則說明正在執行,此時執行會失敗。如果正常的話則會創建另一個文件rsync_sst.conf,這是一個配置文件,用來讓加入節點做rsync操作的,裏面指定了path,read only,timeout等信息。
      上面的問題解釋清楚之後,腳本需要執行的就是等待donor給它傳數據了,它會被阻塞,直到數據成功傳送完成。
      此時加入節點已經將信息“執行方式+192.168.236.231:4444/rsync_sst“傳給了Galera,Galera在得到這個信息,並且等到選擇一個合適的donor出來之後,這個donor會給端口4444傳數據。腳本等待並且接收數據完成之後,就說明恢復完成了,因爲rsync是直接複製文件的。那麼此時腳本會返回一個UUID:seqno信息給加入節點,加入節點在這之前一直處於等待狀態,如果收到這些信息之後說明恢復完成,則繼續啓動數據庫。
    3. 對於xtrabackup,前面信息都是一樣的,只是用到的腳本不同,腳本是wsrep_sst_xtrabackup,也同樣會先執行一個參數角色爲joiner的腳本wsrep_sst_xtrabackup,這個部分也會給加入節點返回一個字符串信息:ready 192.168.236.231:4444/xtrabackup_sst,返回之後,腳本就在等待NC將所有donor備份的數據傳過來直到完成。還是同樣地,Galera得到192.168.236.231:4444/xtrabackup_sst信息之後,集羣會選擇一個donor出來給這個地址發送備份數據。等待傳送完成之後,腳本會繼續執行,此時執行的就是xtrabackup的數據恢復操作,恢復完成之後,腳本會給加入節點輸出UUID:seqno信息,而此時加入節點一直在等待的,如果發現腳本給它這些信息之後,說明已經做完sst,那麼加入完成,數據庫正常繼續啓動。
  3. wsrep_apply_cb:這個函數其實很容易明白,從名字即可看出是apply,因爲在Galera層,上面已經講過了,它是不知道binlog是什麼東西的,它只知道key是什麼東西,同時它判斷是不是衝突也就是根據key來實現的,根本不會用到binlog,那麼在Galera中,如果已經判斷到一個節點上面的一個事務操作沒有出現衝突,那Galera怎麼知道如何去做複製呢?讓其它節點也產生同樣的修改呢?那麼這個函數就是告訴Galera怎麼去做複製,在下層,如果判斷出不衝突,則Galera直接執行這個回調函數即可,因爲這個函數處理的直接就是binlog的恢復操作。它拿到的數據就是一個完成的binlog數據。
  4. wsrep_commit_cb:同樣的道理,對於Galera,也是有事務的,它在完成一個操作之後,如果本地執行成功,則需要執行提交,如果失敗了,則要回滾,那麼Galera也還是不知道如何去提交,或者回滾,或者回滾提交需要做什麼事情,那麼這裏也是要告訴Galera這些東西。
  5. wsrep_unordered_cb:什麼都不做
  6. wsrep_sst_donate_cb:這個回調函數很重要,從名字看出,它是用來提供donate的,確實是的,在上面wsrep_view_handler_cb中介紹的做SST的時候已經介紹了一點這方面的信息,那這個函數是什麼時候用呢?
    它是在wsrep_view_handler_cb函數告訴加入節點的地址端口及執行方式之後,這個是告訴pxc集羣了,那麼集羣會選擇一個合適的節點去做donor,那麼選擇出來之後,它怎麼知道如何去做?那麼這裏就是要告訴它如何去做,把數據發送到哪裏等等,因爲wsrep_view_handler_cb告訴集羣的信息只是一個地址、端口及執行方式,是一個字符串而已,那麼wsrep_sst_donate_cb拿到的東西就是這個信息,而此時加入節點正處於等待donor傳數據給它的狀態,應該是阻塞的。
    那麼這個回調函數做的是什麼呢?首先它會根據傳給它的執行方式判斷是如何去做,現在有幾種情況:
    1. 對於mysqldump方式,這裏做的事情是執行下面的命令:              "wsrep_sst_mysqldump "
                    WSREP_SST_OPT_USER " '%s' "
                    WSREP_SST_OPT_PSWD " '%s' "
                    WSREP_SST_OPT_HOST " '%s' "
                    WSREP_SST_OPT_PORT " '%s' "
                    WSREP_SST_OPT_LPORT " '%u' "
                    WSREP_SST_OPT_SOCKET " '%s' "
                    WSREP_SST_OPT_DATA " '%s' "
                    WSREP_SST_OPT_GTID " '%s:%lld'"
      很明顯,它現在執行的是腳本  wsrep_sst_mysqldump ,後面是它的參數,其實裏面用到的就是mysqldump命令,腳本中主要的操作是mysqldump $AUTH -S$WSREP_SST_OPT_SOCKET --add-drop-database --add-drop-table --skip-add-locks --create-options --disable-keys --extended-insert --skip-lock-tables --quick --set-charset --skip-comments --flush-privileges --all-databases
      可以看出它是將全庫導出的,導出之後直接在遠程將導出的sql文件通過mysql命令直接執行了,簡單說就是遠程執行了一堆的sql語句,將所有的信息複製到(遠程執行sql)新加入節點中。而這也正是mysqldump不像其它2種執行方式一樣在新加入節點還需要做些操作,包括恢復,等待等操作,mysqldump方式在新加入節點也是不需要等待
    2. 對於rsync方式,執行的命令是:wsrep_sst_rsync "
                           WSREP_SST_OPT_ROLE " 'donor' "
                           WSREP_SST_OPT_ADDR " '%s' "
                           WSREP_SST_OPT_AUTH " '%s' "
                           WSREP_SST_OPT_SOCKET " '%s' "
                           WSREP_SST_OPT_DATA " '%s' "
                           WSREP_SST_OPT_CONF " '%s' "
                           WSREP_SST_OPT_GTID " '%s:%lld'"
      可以看出,它執行的還是腳本wsrep_sst_rsync ,不過現在的角色是donor,在腳本中,它就是把庫下面所有的有用的文件都一起用rsync傳給加入節點的NC,這又與上面說的接起來了,這邊傳數據,那麼收數據,這邊完成之後,那麼收數據完成了,則SST也就做完了。
    3. 對於Xtrabackup方式,執行的命令是一樣的,只是用到的腳本是wsrep_sst_xtrabackup,道理與上面完全相同,這邊做備份,那麼接收數據,等各自完成之後,也就完成了SST。
  7. wsrep_synced_cb:這個回調函數的作用是,在mysql啓動的時候,會有一些狀態不一致的情況,那麼當狀態不一致的時候,系統會將當前的狀態設置爲不可用,也就是wsrep_ready這個狀態變量爲OFF,那麼爲了保證數據的一致性,或者由於複製啓動的時候會影響到數據的一致性,那麼在這個狀態的時候,從庫的複製線程會等待這個狀態變爲ON的時候纔會繼續執行,否則一直等待,那麼這個回調函數做的事情就是告訴新加入節點或者donor節點,現在的同步已經完成了,狀態已經是一致的了,可以繼續做下面的操作了。所以此時複製會繼續開始。
自從上次說了wsrep_init_startup之後,感覺已經過了很久了,中間大概都有5000個字了。。。
那麼現在還接着wsrep_init_startup繼續說:
 
在執行了上面所說的wsrep_init之後,接下來要做的事情是:
執行函數wsrep_start_replication,這個所做的事情是調用第一個接口connect,這個函數的功能已經說過了,就是加入集羣,算是報個到吧。
接着再做創建一個線程wsrep_rollback_process,專門做回滾操作。
接着再做wsrep_create_appliers,專門做APPLY操作,這裏創建的線程是wsrep_replication_process,可以有多個,這個會根據變量wsrep_slave_threads(其實只創建wsrep_slave_threads-1個線程,後面會說到原因)來創建的,這個就是真正的所謂“多線程複製”。
 
在這個線程中,用到了接口wsrep->recv(wsrep, ( void *)thd);
對於函數recv,從來不返回,除非是出錯了,這裏面的邏輯下面簡單說一下:
還是從上面做SST開始吧:
 
在做SST前,其實只是創建一個線程,如果設置的線程數大於1,則做上面已經說了,會創建wsrep_slave_threads-1個線程,這原因是:
SST這前,只創建一個,是爲了讓單獨一個線程去處理SST的工作。
 
上面已經說過了,在新加入節點將地址信息發送給集羣之後,集羣會選擇一個donor出來
而創建的那一個單獨的線程wsrep_replication_process會阻塞在wsrep->recv裏面,因爲選擇出來的donor發現它是新加入的節點,所以它會直接在裏面調用回調函數wsrep_sst_donate_cb,它的參數就是集羣給它的“方式+192.168.236.231:4444/rsync_sst”類似的東西,所以這就回到了上面對回調函數wsrep_sst_donate_cb的說明了,它先做備份,然後再將數據複製到新加入節點中。
 
在做完SST之後,然後把剩下的線程創建起來(如果配置的wsrep_slave_threads大於1的話)。
 
再接着上面的wsrep_init_startup函數說:
此時調用最後一個wsrep_sst_wait,很明顯,是等待SST完成,前面已經說過了,新節點一直在等,直到已經做完SST,然後再繼續啓動數據庫。
 
到此爲止,談到的都是Galera的啓動過程及SST的東西,下面就正常執行過程講述一下,可以大概的瞭解它的原理。
 
首先客戶端選擇一個集羣中的服務器連接上去之後,此時其實與連接一個單點是完全相同的。
在執行一個sql的時候,在非pxc的情況下,執行的最終調用的函數是mysql_parse,而對於pxc來說,它調用的是wsrep_mysql_parse,對於它們的區別,後面再講。
 
首先,將一個操作可以分爲幾個階段:
  1. 語句分析:對於這個階段,執行的都是本地操作,不會涉及到集羣的操作,所以和普通的mysql執行分析操作沒有區別。
  2. 本地執行:對於第2階段,這裏會做一個很重要的操作,那就是在插入、更新、刪除記錄時會調用一個wsrep_append_keys函數,它的作用就是將當前被修改的記錄的關鍵字(其實就是當前表的關鍵字在這行記錄中對應的列的值,如果沒有關鍵字,則就是rowid,因爲pxc只支持innodb)提取出來,再按照Galera接口需要要的格式組裝起來,然後再通過Galera的接口append_key(上面已經詳細介紹過了)傳給集羣。
    對於不同操作,其實KEY的最終數據是不同的,如果是插入,KEY當然只有新記錄的鍵值,對於更新,包括了舊數據及新數據的鍵值,對於刪除,則只有老數據的鍵值。同時如果鍵值具有多個列,則它們的組合方式是以列優先,其次纔是新舊值,也就是說,對於更新記錄,它的順序應該是下面這樣的:
    舊鍵第一個值 新鍵第一個值 舊鍵第二個值 新鍵第二個值 ...... ...... 舊鍵第n個值 新鍵第n個值
    當然對於插入行,只是簡單的下面的格式:
    新鍵第一個值 新鍵第二個值 ...... 新鍵第n個值
    還是上面說的,galera完全是靠這些KEY來判斷是不是已經和其它客戶端的操作造成了衝突的。
     
  3. 事務提交:在這個階段,首先會做一個兩階段提交的wsrep_prepare,這裏面會執行一個核心操作,對應的函數是wsrep_run_wsrep_commit,在這個函數中做了2個重要的操作,首先將這個事務產生的binlog通過接口append_data傳給集羣,這部分data是與上面的key對應的,組成了key-value結構,key是用來判斷是不是衝突的,value是用來複制的,它是完完整整的binlog,在做複製的時候其它節點直接拿來做apply。另一件事就是調用接口pre_commit,這在上面已經說過了,其實就裏請求集羣對當前key-value進行驗證,如果驗證通過,則地本繼續執行提交,將數據固化,而如果驗證失敗,則回滾即可。
那麼現在連接服務器的工作流程已經介紹完,但是對於其它的節點而言,因爲集羣已經驗證通過,本地服務器已經提交完成,那麼其它節點在驗證通過之後,會在recv接口中繼續執行,調用前面說到的wsrep_apply_cb回調函數,那麼此時它拿到的就是上面事務提交時的binlog數據,直接做apply就好了。
 
上面提到,在pxc中用到的執行函數爲wsrep_mysql_parse,而在普通的mysql中用到的是mysql_parse,這2個的區別是什麼呢?首先wsrep_mysql_parse調用了mysql_parse,只是在它之後,還做了另外一些操作,下面就這個操作做一個描述:
 
這裏存在一種情況,在當前執行節點的某一個事務在執行pre_commit操作並等待的過程中,此時有另一個事務,這個事務有可能是來自其它節點的複製事務(在本地表現爲複製線程),它的優先級比當前事務高,並且正好這2個事務在某一個數據行上面是衝突的,那麼此時就會將當前事務殺死(實際上是調用了一次abort_pre_commit函數,這個函數也是Galera的一個接口,上面沒有做過介紹),而正在此時,pre_commit已經在其它節點上面完成驗證,並且其它節點都已經複製完成,在這種情況下,pre_commit函數會返回一個錯誤碼WSREP_BF_ABORT,表示其它節點複製完成,當前事務被殺死,那麼此時當前連接會一直返回到wsrep_mysql_parse中,也就是跳出mysql_parse(因爲上面的操作其實都在這個函數中,只是wsrep_mysql_parse將mysql_parse包了一層),執行後面的操作,這裏的操作是在將剛纔被殺死的事務重新做一遍,以slave模式做一遍,像複製一樣,具體對應的函數爲wsrep_replay_transaction。
 
關於區別,僅此而已。
 
PXC的DDL實現說明
 
下面再詳細說一下關於在PXC中的DDL實現方式,這裏面可能存在一些問題:
pxc對於DDL,就不那麼嚴謹了,所有的DDL都是通過簡單的對象封鎖實現的,這裏又用到了Galera的另外2個接口,之前沒有介紹到的,分別是:
wsrep_status_t (*to_execute_start)(wsrep_t*                wsrep,
                                       wsrep_conn_id_t         conn_id,
                                       const wsrep_key_t*      keys,
                                       size_t                  keys_num,
                                       const struct wsrep_buf* action,
                                       size_t                  count,
                                       wsrep_trx_meta_t*       meta);
 及
wsrep_status_t (*to_execute_end)(wsrep_t* wsrep, wsrep_conn_id_t conn_id);
  
to_execute_start是用來上鎖的,主要的參數就是keys,裏面的信息就是數據庫名及表名等對象名,如果是對庫操作,則只有庫名,參數action表示的是當前執行的DDL的語句級的binlog,那麼這個是用來複制的,在其它節點直接執行這個binlog就好了。
to_execute_end是解鎖的,等中間的操作完成之後,就會調用這個接口。
 
在鎖定區間內,因爲這是Galera的接口,所以相當於在整個集羣中對操作對象做了鎖定。
在鎖定之後,執行的DDL操作只是本地的,與集羣的是沒有任何關係的,因爲此時其實已經通過一開始的to_execute_start函數已經在其它節點上面將這個操作已經完成了。
 
關於這一點,很容易測試出來
構造一個2個節點的集羣,第一個節點先設置wsrep_on爲0,使得它的任何操作不做同步;
現在創建一個表,這個表是本地的,不會複製到另一個節點上面去。
此時將wsrep_on設置爲1
然後再執行一次這個創建表的語句
此時發現,本地的執行報錯了,然後再觀察一下另一個節點,竟然神奇的出現了?
 
其實這是一種不一致的狀態。
上面的測試,如果第二次創建的表名是相同的,但是結構不同,這就導致2個節點的結構不同,而創建成功了。
 
上面的測試是說到了另一個節點某一個表不存在的情況,本地是存在的。
現在如果反過來說,本地是不存在的,而另一個節點已經存在,那麼按照上面執行的邏輯可以知道,通過to_execute_start,另一個節點的複製肯定是不能成功的,因爲已經存在了,這點只會在log文件中寫一個錯誤日誌表現出來,而沒有采取任何其它措施,那個節點還是正常的。而本地節點則正常的創建這個表,問題還是一樣的。
 
其實上面關於ddl的問題官方的wiki上面已經說清楚了,請參照:
具體這個問題爲:
Q: How DDLs are handled by Galera?


Q&A
  1. 如果出現不一致,節點如何被集羣踢出
構造下面一種場景:
正常運行的2個節點的集羣,分別命名爲A和B,現在先在A上面創建一個表
A:create table t (sno int primary key);
B:然後發現B已經同步了這個表
A:設置A節點,讓它的修改不再同步到其它節點,這是一種手動構造不一致的方式
     set wsrep_on=0;
然後在A上面插入一條記錄,那麼這個記錄就不會被複制到B上面
A:insert into t values(1);
此時查看A已經有一條1的記錄,而B上面還是空表,說明是正常的。
現在在B的節點上面插入一條同樣的記錄,因爲上面的wsrep_on只是設置了不從A同步數據到B,而此時B還是可以同步數據到A的,那麼在B上執行同樣的插入語句:
B:insert into t values(1);
此時A掛了……
 
分析:這個掛是在recv接口中發生的,因爲複製是在這個裏面發生的,通過調用回調函數wsrep_apply_cb實現,而此時A節點的t表已經有一條記錄了,B節點再做的時候,這裏複製會執行失敗,導致wsrep_apply_cb接口執行時會報錯,然後recv知道報錯之後,就直接將自己這個進程退出了,這樣就造成了節點被中踢出的現象(其實是自己不想幹了)。不過這種表現上是優雅的,因爲自己在離開前已經辦好了一切離開的手續了,屬於正常退出。
 

到現在爲止,所有的關於pxc實現方式已經基本搞清楚,其它問題均是mysql本身的問題

總結:pxc架構上面可謂是實現了真正的集羣,最主要的特點是多主的,並且基本沒有延時,這樣就解決了mysql主從複製的很多問題,但其中還是存在一些問題的,比如2個節點容易出現腦裂現象,這必須要通過另一個工具去做一個監控及評判,而官方推薦最好是搞三個節點,但可能會造成一定程度的空間及機器的浪費,但在實際使用時可以適當的在一個實例上面多放置幾個庫,來提高使用效率。

from:http://www.cnblogs.com/bamboos/p/3543309.html

發佈了47 篇原創文章 · 獲贊 18 · 訪問量 54萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章