CentOS环境利用mariadb(mysql)数据库使用golang实现分布式系统的Leader选举
一、准备工作
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 root@'%' 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:19930309@tcp(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:19930309@tcp(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