IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    hiredis异步接口封装并导出到Lua

    金庆发表于 2017-01-05 10:42:00
    love 0
    hiredis异步接口封装并导出到Lua

    (金庆的专栏 2017.1)

    hiredis 不支持 Windows, Windows 下使用 wasppdotorg / hiredis-for-windows 。
    Linux 下仍是 redis/hiredis。
    hiredis-for-windows 是以 hiredis 0.13.3 为基础移植的。

    hiredis-for-windows 需要稍加修正:
        * 去除 inline 宏
        * TCP_NODELAY 改在连接之前设置。
    详见其Issue.

    Cluster 支持采用 shinberg/cpp-hiredis-cluster。这是个CPP库,支持异步,
    要求 hiredis >= 0.12.0。
    jinq0123/cpp-hiredis-cluster 在 develop 分支上更改了接口,让它更好用。

    因为网络库是boost asio, 所以需要asio适配器,采用 jinq0123/hiredis-boostasio-adapter。

    cpp-hiredis-cluster 提供的是统一的Command接口,接收字符串命令,返回 redisReply.
    对于常用命令,需要更简单的接口。
    在Lua手游服务器代码中新建CRedis类,封装 cpp-hiredis-cluster,
    为常用redis命令封装更好用的接口。
    CRedis类封装了asio, 接口是根据应用需要定义的,所以是专用接口,
    不在 cpp-hiredis-cluster 中实现。

    bool CRedis::Init(io_service& rIos, const std::string& sHost, uint16_t uPort)
    创建 RedisCluster 对象。
    io_service 用于创建一个 redis 事件适配器,
    RedisCluster创建需要一个适配器。
    sHost, uPort 用于初始化连接redis cluster, 获取集群信息。


    using Cmd = RedisCluster::AsyncHiredisCommand;

    bool CRedis::Init(io_service& rIos, const std::string& sHost, uint16_t uPort)
    {
        m_pAdapter.reset(new Adapter(rIos));
        try
        {
            m_pCluster.reset(Cmd::createCluster("127.0.0.1", 7000, *m_pAdapter));
        }
        catch (const RedisCluster::ClusterException &e)
        {
            LOG_ERROR("Cluster exception: " << e.what());
            return false;
        }

        return true;
    }

    static Cmd::Action handleException(const RedisCluster::ClusterException &exception,
        RedisCluster::HiredisProcess::processState state)
    {
        // Check the exception type.
        // Retry in case of non-critical exceptions.
        if (!dynamic_cast<const RedisCluster::CriticalException*>(&exception))
        {
            LOG_WARN("Exception in processing async redis callback: "
                << exception.what() << " Retry...");
            // retry to send a command to redis node
            return Cmd::RETRY;
        }
        LOG_ERROR("Critical exception in processing async redis callback: "
            << exception.what());
        return Cmd::FINISH;
    }

    static void handleSetReply(const redisReply &reply, const CRedis::SetCb& setCb)
    {
        if (!setCb) return;
        if (reply.type == REDIS_REPLY_STATUS)
        {
            const std::string OK("OK");
            if (OK == reply.str)
            {
                setCb(true);
                return;
            }
        }

        LOG_WARN("Set reply: " << reply.str);
        setCb(false);
    }

    void CRedis::Set(const string& sKey, const string& sValue, const SetCb& setCb)
    {
        assert(m_pCluster);
        Cmd::commandf2(*m_pCluster, sKey,
            [setCb](const redisReply& reply) {
                handleSetReply(reply, setCb);
            },
            handleException,
            "set %s %s", sKey.c_str(), sValue.c_str());
    }

    static void handleGetReply(const redisReply& reply,
        const CRedis::ReplyStringCb& hdlStrReply)
    {
        if (!hdlStrReply) return;
        using Rt = CRedis::ReplyType;
        if (reply.type == REDIS_REPLY_NIL)
        {
            hdlStrReply(Rt::NIL, "");
            return;
        }
        std::string sReply(reply.str, reply.len);
        if (reply.type == REDIS_REPLY_STRING)
            hdlStrReply(Rt::OK, sReply);
        else
            hdlStrReply(Rt::ERR, sReply);
    }

    void CRedis::Get(const std::string& sKey, const ReplyStringCb& hdlStrRepl)
    {
        assert(m_pCluster);
        Cmd::commandf2(*m_pCluster, sKey,
            [hdlStrRepl](const redisReply& reply) {
                handleGetReply(reply, hdlStrRepl);
            },
            handleException,
            "get %s", sKey.c_str());
    }

    handleException 是Cmd::command() 接口中需要的异常处理,这里是尽量重试。

    Cmd::command() 中的第3个参数是 redis 应答的回调,读取 redisReply, 然后触发命令的回调。

    CRedis::Get() 执行 redis GET 命令,固定1个参数,返回是字符串,nil, 或错误。

        enum class ReplyType
        {
            OK = 0,  // redis replys string/integer/array
            NIL = 1,  // redis replys nil
            ERR = 2,  // redis replys error status
        };

        // 简单的常用命令会自动解析reply, 使用更易用的回调。
        using ReplyStringCb = function<void (ReplyType, const string& sReply)>;

    CRedis::Get() 的回调就是 ReplyStringCb。

    void CRedis::Set() 的回调只需知道成功或失败,SetCb 定义为:
        using SetCb = function<void (bool ok)>;

    失败时会有错误信息,已统一打印日志,不再暴露出来。

    SET 命令完整的参数列表还有其他参数:
    set key value [EX seconds] [PX milliseconds] [NX|XX]

    因为常用的就 "set key value", 其他扩展的命令需要使用通用的 command() 接口,
    并需要读取 redisReply.

    使用 LuaIntf 导出 CRedis 到 Lua.
    因为只有一个 CRedis, 所以导出为模块 c_redis:

    #include <LuaIntf/LuaIntf.h>

    namespace {

    void Set(const string& sKey, const string& sValue, const LuaRef& luaSetCb)
    {
        // Default is empty callback.
        auto setCb = ToFunction<CRedis::SetCb>(luaSetCb);
        GetRedis().Set(sKey, sValue, setCb);
    }

    void Get(const string& sKey, const LuaRef& luaReplyStringCb)
    {
        auto replyStringCb = ToFunction<CRedis::ReplyStringCb>(
            luaReplyStringCb);
        GetRedis().Get(sKey, replyStringCb);
    }

    }  // namespace

    void Bind(lua_State* L)
    {
        LuaBinding(L).beginModule("c_redis")
            .addFunction("set", &Set)
            .addFunction("get", &Get)
        .endModule();
    }

    需要将 lua 的回调函数转成 cpp 的回调:

    template <class Function>
    Function ToFunction(const LuaIntf::LuaRef& luaFunction)
    {
        // Default is empty.
        if (!luaFunction)
            return Function();  // skip nil
        if (luaFunction.isFunction())
            return luaFunction.toValue<Function>();  // Todo: catch
        LOG_WARN_TO("ToFunction", "Lua function expected, but got "
            << luaFunction.typeName());
        return Function();
    }

    Lua 这样调用:
    c_redis.set("FOO", "1234")
    c_redis.set("FOO", "1234", function(ok) print(ok) end)
    c_redis.get("FOO", function(reply_type, reply)
        assert("string" == type(reply))
        if 0 == reply_type or 1 == reply_type then
            print("FOO="..reply)
        else
            print("Error: "..reply)
        end
    end)


    金庆 2017-01-05 18:42 发表评论


沪ICP备19023445号-2号
友情链接