您的位置:首页 > 其它

基于zookeeper的主备切换方法

2016-03-30 10:25 281 查看
zookeeper的ZOO_EPHEMERAL节点(如果ZOO_EPHEMERAL满足不了需求,可以考虑和ZOO_SEQUENCE结合使用),在会话关闭或过期时,会自动删除,利用这一特性可以实现两个或多节点间的主备切换。

实现方法:

1)在进程启动时调用zookeeper_init()初始化:

bool X::init_zookeeper()

{

    // 第一次调用时_clientid总是为NULL,

    // 状态为ZOO_EXPIRED_SESSION_STATE时,需要重新调用zookeeper_init,

    // 这个时候可传入的_clientid为前一次zookeeper_init()产生的_clientid

    // 请注意zookeeper_init()是一个异步调用,返回非NULL并不表示会话建立成功,

    // 只有当zk_watcher中的type为ZOO_SESSION_EVENT和state为ZOO_CONNECTED_STATE时,

    // 才真正表示会话建立成功。

    _zhandle = zookeeper_init(zk_hosts, zk_watcher, 5000, _clientid, this, 0);

    if (NULL == _zhandle)

    {

        MYLOG_ERROR("init zookeeper failed: %s\n", zerror(errno));

        return false;

    }

    MYLOG_INFO("init zookeeper(%s) successfully\n", zk_hosts);

    return true;

}

2)进入工作之前,先尝试切换成主,只有成功切换成主后才进入work

bool X::run()

{

    while (true)

    {

        int num_items = 0;

        // 备机最简单的方法是每隔一定时间,如1秒就尝试转成master,

        // 如果不使用轮询,则可以采用监视_zk_path的方式

        mooon::sys::CUtils::millisleep(1000);

        // 如果不是master,则尝试转成master,如果转成不成功则继续下一次尝试

        if (!is_master() && !change_to_master())

            continue;

        do_work();

    }

}

bool X::is_master() const

{

    return _is_master;

}

bool X::change_to_master()

{

    static uint64_t log_counter = 0; // 打log计数器,备状态时的日志输出

    // ZOO_EPHEMERAL|ZOO_SEQUENCE

    // _myip为本地IP地址,可以通过它来判断当前谁是master

    // _zk_path值示例:/master/test,注意需要先保证/master已存在

    int errcode = zoo_create(_zhandle, _zk_path.c_str(), _myip.c_str(), _myip.size()+1, &ZOO_OPEN_ACL_UNSAFE, ZOO_EPHEMERAL, NULL, 0);

    // (-4)connection loss,比如为zookeeper_init()指定了无效的hosts(一个有效的host也没有)

    if (errcode != ZOK)

    {

        _is_master = false;

        // 减少为备状态时的日志输出

        if (0 == log_counter++ % 600)

        {

            MYLOG_DEBUG("become master[%s] failed: (%d)%s\n", _zk_path.c_str(), errcode, zerror(errcode));

        }

        return false;

    }

    else

    {

        _is_master = true;

        log_counter = 0;

        MYLOG_INFO("becase master[%s]\n", _zk_path.c_str());

        // sleep一下,以便让原master正在进行的完成

        mooon::sys::CUtils::millisleep(2000);

        return true;

    }

}

3)当zookeeper会话成功建立或过期时均会触发zk_watcher,可通过type和state来区分

void zk_watcher(zhandle_t *zh, int type, int state, const char *path, void *context)

{

    X* x = static_cast<X*>(context);

    MYLOG_DEBUG("zh=%p, type=%d, state=%d, context=%p, path=%s\n", zh, type, state, context, path);

    // zookeeper_init成功时type为ZOO_SESSION_EVENT,state为ZOO_CONNECTED_STATE

    if ((ZOO_SESSION_EVENT == type) && (ZOO_CONNECTED_STATE == state))

    {

        x->on_zookeeper_connected(path);

    }

    else if ((ZOO_SESSION_EVENT == type) && (ZOO_EXPIRED_SESSION_STATE == state))

    {

        // 需要重新调用zookeeper_init(),简单点可以退出当前进程重启

        x->on_zookeeper_expired();

    }

}

附: zookeeper日志

默认情况下zookeeper日志是输出到stderr,但可以通过zoo_set_log_stream()来定向到自己的日志输出中,还可以使用zoo_set_debug_level()来控制zookeeper的日志级别。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: