为了保证可用性,传统的关系型数据库(mysql)通常采用一主多备的方式,当主宕机后,切换到备机。
有多种方式可以做到主备自动失效检测切换,比如传统的HA软件heartbeat,keepalived等等,采取虚拟IP的方式对客户端透明;
这里主备自动切换方案没有采取VIP的方式,而是用zookeeper对数据库集群进行管理,可以做到主备失效检测切换,主库的选举,
比如考虑到mysql对多核利用率不高而一个节点部署多个mysql实例,如果采用VIP的方式是不可行的。
基本做法是,每个mysql实例上部署一个agent节点,agent负责对该mysql实例定时进行ping操作,agent在zookeeper上注册临时节点,
如果agent或者该agent代理的mysql宕掉,注册在zookeeper上的节点就会发生变化,利用zookeeper watch功能实现mysq实例失效后的主库的选举操作,
读节点也通过watch master节点的功能实现指向新的master。
如上图所示,zookeeper上的master节点存储当前主节点名称;
servers下面保存在线的节点,命名规则一般为ip:port_序列号;
nodeid为当前的序列号,每当节点获取一个序列号时,该节点值自就增,以便于分配给下一个节点。
过程如下:
a、初始化阶段,创建servers,master,nodeid节点
b、每个client创建servers子节点,zoo_create("/servers/xxx",EPHEMERAL)
c、zoo_get_child(/servers,NULL)
d、若当前client的序列号id是当前最小的节点,则当前节点是master,设置master节点退出
e、否则,zoo_exsists(lastid before id,watcher)//当前节点watch比当前节点id次小的那个节点的状态
如果id不存在,那么退出
否则等待watch触发,重新选举master
相关伪代码参考示例如下:
try {
ProposoWatcher wc = new ProposoWatcher();
final ZooKeeper zk = new ZooKeeper("10.1.1.24:2181", 60000, wc);
wc.setZk(zk);
if (zk.exists("/master", false) == null) {
zk.create("/master", null, Ids.OPEN_ACL_UNSAFE,
CreateMode.PERSISTENT);
}
zk.create("/servers/" + nodeName, new byte[0], Ids.OPEN_ACL_UNSAFE,
CreateMode.EPHEMERAL);
List<String> list = zk.getChildren("/servers", false);
String[] nodes = list.toArray(new String[list.size()]);
Arrays.sort(nodes);
if (nodeName.equals(nodes[0])) {//初次
System.out.println("this is master" + nodes[0]);
zk.setData("/master", nodeName.getBytes(), -1);
} else {
// 监控比自己次小的Node
String lower = "";
for (int i = 0; i < nodes.length; i++) {
if (nodeName.equals(nodes[i])) {
lower = nodes[i - 1];
break;
}
}
zk.exists("/servers/" + lower, true);
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//watcher
public static class ProposoWatcher implements Watcher {
private ZooKeeper zk;
public ZooKeeper getZk() {
return zk;
}
public void setZk(ZooKeeper zk) {
this.zk = zk;
}
@Override
public void process(WatchedEvent event) {
// TODO Auto-generated method stub
switch (event.getType()) {
case None: {
// connectionEvent(event);
break;
}
case NodeDeleted: {
// servers.remove(event.getPath());
System.out.println("node removed**" + event.getPath());
String delName = event.getPath().substring(
event.getPath().lastIndexOf("/"));
// final ZooKeeper zk1 = zk;
List<String> children = new ArrayList<String>();
try {
children = zk.getChildren("/servers", false);
} catch (KeeperException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
String[] nodes = children.toArray(new String[children.size()]);
Arrays.sort(nodes);
for (int i = 0; i < nodes.length; i++) {
if (nodes[i].compareTo(delName) > 0) {
// TODO:curr Node become master
System.out.println("this is master" + nodes[i]);
try {
zk.setData("/master", nodes[i].getBytes(), -1);
} catch (KeeperException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
break;
}
}
break;
}
}
}
}