1. 程式人生 > >CentOS環境利用mariadb(mysql)數據庫使用golang實現分布式系統的Leader選舉

CentOS環境利用mariadb(mysql)數據庫使用golang實現分布式系統的Leader選舉

資源 net 安裝git mina sha 新的 軟件 not null 模擬

一、準備工作

1.下載安裝vmware,步驟省略。

2.下載CentOS系統ios包:http://isoredirect.centos.org/centos/7/isos/x86_64/CentOS-7-x86_64-Everything-1611.iso

3.下載安裝Xshell5,步驟省略。

4.下載安裝git,步驟省略。

5.mariadb用於golang的api:https://github.com/go-sql-driver/mysql

6.vmware中依次點擊“創建新的虛擬機”->“典型”->“安裝程序光盤映像文件”選擇上面下載的ios文件,然後一路下一步即可快速安裝CentOS系統。

至於為什麽安裝CentOS,其實其他linux版本也可以。安裝完成的CentOS系統如下圖:

技術分享

二、安裝配置mariadb數據庫

選擇mariadb數據庫的原因是,CentOS 7 版本將MySQL數據庫軟件從默認的程序列表中移除,用mariadb代替了。好在MariaDB的完全兼容MySQL的API和命令行,不影響我們使用。

點擊屏幕左上角的Applications,打開Terminal。

首先切換到root權限:

輸入su,回車,看到Password:後輸入密碼,註意密碼不會顯示出來,輸入完畢直接回車就好。

然後輸入命令安裝mariadb數據庫:

yum install mariadb mariadb-server

所有的提示輸入y回車,如下圖所示。

技術分享

最後出現Complete!說明安裝成功,我們將mariadb加入開機啟動項並啟動。

mariadb加入開機啟動項:

systemctl enable mariadb.service

mariadb啟動:

systemctl start mariadb.service

接下來進行MariaDB的相關簡單配置,輸入命令:

mysql_secure_installation

屏幕顯示:Enter current password for root (enter for none),初始密碼為空,我們直接回車即可。

接下來的提示依次是:設置密碼(若Y設置密碼,需要輸入兩次密碼)、是否刪除匿名用戶、是否禁止root遠程登錄、是否刪除test數據庫、是否重新加載權限表。

我將密碼設置成19930309,後四個選項分別輸入n、n、n、y。

然後我們配置MariaDB的字符集,依次修改以下幾個文件,可能用到的命令有以下幾個。

(1)打開文件:vi 文件名.後綴名

(2)編輯文件:打開文件後按鍵盤“i”

(3)退出編輯:ESC

(4)保存修改並關閉文件:輸入:wq回車

(5)撤銷修改:輸入:undo回車

在/etc/my.cnf文件的[mysqld]標簽下添加

init_connect=‘SET collation_connection = utf8_unicode_ci‘
init_connect=‘SET NAMES utf8‘
character-set-server=utf8
collation-server=utf8_unicode_ci
skip-character-set-client-handshake

在/etc/my.cnf.d/client.cnf文件的[client]標簽中添加

default-character-set=utf8

在/etc/my.cnf.d/mysql-clients.cnf文件的[mysql]標簽中添加

default-character-set=utf8

配置完成後,輸入命令重啟mariadb:

systemctl restart mariadb

進入mariadb:

mysql -u root -p

提示輸入密碼,輸入我上一部設置的密碼19930309,註意不會顯示,輸入完直接回車就好。

查看一下我們設置好的字符集,輸入:

show variables like "%character%";show variables like "%collation%";

這裏為了使用方便,我將輸入命令的位置由虛擬機的terminal換成了Xshell,與terminal裏輸入並無差別,Xshell的使用方法在我另一篇博客裏有提及(http://www.cnblogs.com/renjiashuo/p/7247388.html),這裏不做贅述。

顯示為

技術分享

字符集配置完成。

輸入一下命令查看一下目前已有的數據庫:

show databases;

可以看到我們現在有4個database。

技術分享

三、使用數據庫與golang實現分布式系統的leader選舉

在Xshell中輸入命令使用test表:

use test

下面在Xshell輸入一個命令給數據庫授予外網訪問權限:

grant all privileges on *.* to [email protected]%‘ identified by ‘19930309‘;

其中,root為我們設置的數據庫用戶名,19930309為我的數據庫密碼。

創建一個數據表用於當前leader的記錄與更新:

CREATE TABLE service_election (
  anchor tinyint(3) unsigned NOT NULL,
  service_id varchar(128) NOT NULL,
  last_seen_active timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (anchor)
) ENGINE=InnoDB

  

可以輸入以下命令查看此test數據庫下包含的所有表:

show tables;

下面查看一下我們創建的數據庫表:

SHOW FULL COLUMNS from service_election;

可以看到表已經創建成功了,如下圖所示。

技術分享

數據庫完成後,我們開始編寫golang程序。

首先使用git獲取golang對mysql的api,下面是git命令:

查看golang的位置:

which go

設置GOROOT:

export GOROOT=/c/Go

我將golang程序放到了d盤的golang文件夾下,那麽設置一下GOPATH:

export GOPATH=/d/golang/

獲取zookeeper對go的api:

go get github.com/go-sql-driver/mysql

下面在d盤golang文件夾下新建兩個go項目,分別寫入如下代碼來模擬Leader選舉的過程:

package main

import (
	"database/sql"
	"fmt"
	_ "github.com/go-sql-driver/mysql"
	"time"
)

func main() {
	// 連接數據庫
	db, err := sql.Open("mysql", "root:[email protected](192.168.40.128:3306)/test?charset=utf8")
	checkErr(err)
	// 如果用於記錄leader的數據庫表中沒有數據,則插入數據,將本進程設為leader
	rows, err := db.Query("SELECT * FROM service_election for update")
	checkErr(err)
	if rows == nil{
		_, err = db.Exec("replace into service_election (anchor, service_id, last_seen_active) values (1, ‘user1‘, now())")
		checkErr(err)
	}
	db.Query("commit");

	// 輪詢
	for {
		// 加鎖
		rows, err := db.Query("SELECT * FROM service_election where anchor = ‘1‘ for update")
		checkErr(err)
		for rows.Next() {
			var anchor int
			var service_id string
			var last_seen_active string
			rows.Columns()
			err = rows.Scan(&anchor, &service_id, &last_seen_active)
			checkErr(err)
			//fmt.Println(last_seen_active)
			local, _ := time.LoadLocation("Local")
			tm2, _ := time.ParseInLocation("2006-01-02 15:04:05", last_seen_active, local)
			//fmt.Println(tm2.Unix())
			//fmt.Println(tm2)
			now := time.Now()
			//fmt.Println(now.Unix())
			// 每5秒更新一下時間戳,若發現時間戳超過10秒沒被更新,則競選leader
			if service_id == "user1" && now.Unix()-tm2.Unix() > 5 {
				// 插入數據
				_, err = db.Exec("update service_election set service_id = ‘user1‘,last_seen_active = now() where anchor = ‘1‘")
				checkErr(err)
			}else if now.Unix()-tm2.Unix() > 10 {
				// 插入數據
				_, err = db.Exec("update service_election set service_id = ‘user1‘,last_seen_active = now() where anchor = ‘1‘")
				checkErr(err)
			}
			// 解鎖
			db.Query("commit");
		}
		// 查詢本進程是否為leader
		rows, err = db.Query("SELECT * FROM service_election")
		checkErr(err)
		for rows.Next() {
			var anchor int
			var service_id string
			var last_seen_active string
			rows.Columns()
			err = rows.Scan(&anchor, &service_id, &last_seen_active)
			checkErr(err)
			if service_id == "user1" {
				fmt.Println("I am the master.")
			} else {
				fmt.Println("I am the slave.")
			}
		}
		time.Sleep(time.Second * 5)
	}
	db.Close()
}

func checkErr(err error) {
	if err != nil {
		panic(err)
	}
}

  

package main

import (
	"database/sql"
	"fmt"
	_ "github.com/go-sql-driver/mysql"
	"time"
)

func main() {
	// 連接數據庫
	db, err := sql.Open("mysql", "root:[email protected](192.168.40.128:3306)/test?charset=utf8")
	checkErr(err)
	// 如果用於記錄leader的數據庫表中沒有數據,則插入數據,將本進程設為leader
	rows, err := db.Query("SELECT * FROM service_election for update")
	checkErr(err)
	if rows == nil{
		_, err = db.Exec("replace into service_election (anchor, service_id, last_seen_active) values (1, ‘user2‘, now())")
		checkErr(err)
	}
	db.Query("commit");

	// 輪詢
	for {
		// 加鎖
		rows, err := db.Query("SELECT * FROM service_election where anchor = ‘1‘ for update")
		checkErr(err)
		for rows.Next() {
			var anchor int
			var service_id string
			var last_seen_active string
			rows.Columns()
			err = rows.Scan(&anchor, &service_id, &last_seen_active)
			checkErr(err)
			//fmt.Println(last_seen_active)
			local, _ := time.LoadLocation("Local")
			tm2, _ := time.ParseInLocation("2006-01-02 15:04:05", last_seen_active, local)
			//fmt.Println(tm2.Unix())
			//fmt.Println(tm2)
			now := time.Now()
			//fmt.Println(now.Unix())
			// 每5秒更新一下時間戳,若發現時間戳超過10秒沒被更新,則競選leader
			if service_id == "user2" && now.Unix()-tm2.Unix() > 5 {
				// 插入數據
				_, err = db.Exec("update service_election set service_id = ‘user2‘,last_seen_active = now() where anchor = ‘1‘")
				checkErr(err)
			}else if now.Unix()-tm2.Unix() > 10 {
				// 插入數據
				_, err = db.Exec("update service_election set service_id = ‘user2‘,last_seen_active = now() where anchor = ‘1‘")
				checkErr(err)
			}
			// 解鎖
			db.Query("commit");
		}
		// 查詢本進程是否為leader
		rows, err = db.Query("SELECT * FROM service_election")
		checkErr(err)
		for rows.Next() {
			var anchor int
			var service_id string
			var last_seen_active string
			rows.Columns()
			err = rows.Scan(&anchor, &service_id, &last_seen_active)
			checkErr(err)
			if service_id == "user2" {
				fmt.Println("I am the master.")
			} else {
				fmt.Println("I am the slave.")
			}
		}
		time.Sleep(time.Second * 5)
	}
	db.Close()
}

func checkErr(err error) {
	if err != nil {
		panic(err)
	}
}

  

兩個程序代碼的差別僅僅在於,數據庫表的service_id項一個設置成了user1,另一個設置成了uesr2。在實際應用中,我們可以將這項設置成各自物理機的ip。

分別運行兩個程序,可以看到一個程序不斷輸出“I am the master.”,另一個程序不斷輸出“I am the slave.”,如果關閉master程序,則大約10秒後,slave程序開始輸出master,之後再打開之前關閉的程序,它已經成為了新的slave。如圖所示。

技術分享

技術分享

自此,CentOS環境利用mariadb(mysql)數據庫使用golang實現分布式系統的Leader選舉實現完畢。

四、參考網絡資源

http://www.cnblogs.com/starof/p/4680083.html

http://www.linuxidc.com/Linux/2016-03/128880.htm

http://blog.csdn.net/a19352226/article/details/50814900

http://code.openark.org/blog/mysql/leader-election-using-mysql

CentOS環境利用mariadb(mysql)數據庫使用golang實現分布式系統的Leader選舉