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)

hiredis异步接口封装并导出到Lua的更多相关文章

  1. 基于C#的MongoDB数据库开发应用(3)--MongoDB数据库的C#开发之异步接口

    在前面的系列博客中,我曾经介绍过,MongoDB数据库的C#驱动已经全面支持异步的处理接口,并且接口的定义几乎是重写了.本篇主要介绍MongoDB数据库的C#驱动的最新接口使用,介绍基于新接口如何实现 ...

  2. C++ Redis mset 二进制数据接口封装方案

    C++ Redis mset 二进制数据接口封装方案 需求 C++中使用hiredis客户端接口访问redis: 需要使用mset一次设置多个二进制数据 以下给出三种封装实现方案: 简单拼接方案 在r ...

  3. NodeJS中常见异步接口定义(get、post、jsonp)

    越来越多的人在使用nodeJS,作为一门服务端语言,我们不可避免的要写异步接口(ajax和jsonp).再次强调ajax和jsonp是两个概念,但是由于jquery的封装,使这两种异步接口的调用方式, ...

  4. vue2.0 + vux (五)api接口封装 及 首页 轮播图制作

    1.安装 jquery 和 whatwg-fetch (优雅的异步请求API) npm install jquery --save npm install whatwg-fetch --save 2. ...

  5. javascript 异步请求封装成同步请求

    此方法是异步请求封装成同步请求,加上token验证,环境试用微信小程序,可以修改文件中的ajax,进行封装自己的,比如用axios等 成功码采用标准的 200 到 300 和304 ,需要可以自行修改 ...

  6. java单元测试之如何实现异步接口的测试案例

    测试是软件发布的重要环节,单元测试在实际开发中是一种常用的测试方法,java单元测试主要用junit,最新是junit5,本人开发一般用junit4.因为单元测试能够在软件模块组合之前尽快发现问题,所 ...

  7. Postman实现数字签名,Session依赖, 接口依赖, 异步接口结果轮询

    Script(JS)为Postman赋予无限可能 基于Postman 6.1.4 Mac Native版 演示结合user_api_demo实现 PS 最近接到任务, 要把几种基本下单接口调试和持续集 ...

  8. Java微信公众平台接口封装源码分享

    前言:      这篇博客是在三月初动手项目的时候准备写的,但是为了完成项目只好拖延时间写这篇博客,顺便也可以在项目中应用我自己总结的的一些经验.今天看来,这些方法的应用还是可以的,至少实现了我之前的 ...

  9. 基于Verilog的带FIFO输出缓冲的串口接收接口封装

    一.模块框图及基本思路 rx_module:串口接收的核心模块,详细介绍请见“基于Verilog的串口接收实验” rx2fifo_module:rx_module与rx_fifo之间的控制模块,其功能 ...

随机推荐

  1. Struts(十九):类型转换、类型转换错误消息及显示

    类型转换概念 1.从html表单页面到一个Action对象,类型转化是从字符串到一个非字符串:html并没有“类型”的概念,每个表单输入的信息都只可能是一个字符串或者一个字符串数组,但是在服务器端,必 ...

  2. url,href,src之间的区别

    发现自己居然没把url.href.src关系及使用搞清楚,今天就理一下.主要包括:url.src.href定义以及使用区别. URL(Uniform Resource Locator) 统一资源定位符 ...

  3. Java基础之关键字,标识符,变量

    Java基础 首先,来看一下Java基础知识图解,以下便是在java学习中我们需要学习设计到的一些知识(当然不是很完全). 这些都是接下来在以后的学习中我们会学到的一些知识. 1 关键字 首次先来学习 ...

  4. 数据库性能优化(database tuning)性能优化绝不仅仅只是索引

    一毕业就接触优化方面的问题,专业做优化也有至少5年之多的时间了,可现在还是经常听到很多人认为优化很简单,就是建索引的问题,这确实不能怪大家,做这行20多年的时间里,在职业生涯的每个阶段,几乎都能听到这 ...

  5. [LeetCode] Average of Levels in Binary Tree 二叉树的层平均值

    Given a non-empty binary tree, return the average value of the nodes on each level in the form of an ...

  6. Python系列之 - python运算符

    废话不多说,上节说的是数据类型,本篇讲讲数据运算. 在算式"1+2"中,"1"和"2"被称为操作数,"+"被称为运算符 ...

  7. [USACO16OPEN]关闭农场Closing the Farm_Silver

    题目描述 FJ和他的奶牛们正在计划离开小镇做一次长的旅行,同时FJ想临时地关掉他的农场以节省一些金钱. 这个农场一共有被用M条双向道路连接的N个谷仓(1<=N,M<=3000).为了关闭整 ...

  8. ●BZOJ 4289 PA2012 Tax

    ●赘述题目 算了,题目没有重复的必要. 注意理解:对答案造成贡献的是每个点,就是了. 举个栗子: 对于如下数据: 2 1 1 2 1 答案是 2: ●题解 方法:建图(难点)+最短路. 先来几个链接: ...

  9. [BSGS算法]纯水斐波那契数列

    学弟在OJ上加了道"非水斐波那契数列",求斐波那契第n项对1,000,000,007取模的值,n<=10^15,随便水过后我决定加一道升级版,说是升级版,其实也没什么变化,只 ...

  10. Codeforces278E Tourists

    来自FallDream的博客,未经允许,请勿转载,谢谢. 给定一张无向图,有点权,要支持单点修改点权和询问从一个点到另一个点不重复经过节点的路径上点权最小值的最小值. n,m<=10^5 考虑求 ...