redisplusplus笔记

2023-09-18 18:10:08

设计点

  • 在redis层使用函数作为模板类型参数,也就是command层定义的函数。
template <typename Cmd, typename ...Args>
auto Redis::command(Cmd cmd, Args &&...args)
    -> typename std::enable_if<!std::is_convertible<Cmd, StringView>::value, ReplyUPtr>::type {
    if (_connection) {
        // Single Connection Mode.
        // TODO: In this case, should we reconnect?
        auto &connection = _connection->connection();
        if (connection.broken()) {
            throw Error("Connection is broken");
        }

        return _command(connection, cmd, std::forward<Args>(args)...);
    } else {
        assert(_pool);

        // Pool Mode, i.e. get connection from pool.
        SafeConnection connection(*_pool);

        return _command(connection.connection(), cmd, std::forward<Args>(args)...);
    }
}

template <typename ...Args>
auto Redis::command(const StringView &cmd_name, Args &&...args)
    -> typename std::enable_if<!IsIter<typename LastType<Args...>::type>::value, ReplyUPtr>::type {
    auto cmd = [](Connection &connection, const StringView &name, Args &&...params) {
                    CmdArgs cmd_args;
                    cmd_args.append(name, std::forward<Args>(params)...);
                    connection.send(cmd_args);
    };

    return command(cmd, cmd_name, std::forward<Args>(args)...);
}

template <typename Input>
auto Redis::command(Input first, Input last)
    -> typename std::enable_if<IsIter<Input>::value, ReplyUPtr>::type {
    range_check("command", first, last);

    auto cmd = [](Connection &connection, Input start, Input stop) {
                    CmdArgs cmd_args;
                    while (start != stop) {
                        cmd_args.append(*start);
                        ++start;
                    }
                    connection.send(cmd_args);
    };

    return command(cmd, first, last);
}
  • 使用可变参数模板
template <typename Cmd, typename ...Args>
    ReplyUPtr _command(Connection &connection, Cmd cmd, Args &&...args);
//command系列
template <typename Cmd, typename ...Args>
    auto command(Cmd cmd, Args &&...args)
        -> typename std::enable_if<!std::is_convertible<Cmd, StringView>::value, ReplyUPtr>::type;
  • 在redis层最终会调用不带连接的command方法,调用带连接参数的_command方法(调用command层具体的函数,然后接收响应)
template <typename Cmd, typename ...Args>
ReplyUPtr Redis::_command(Connection &connection, Cmd cmd, Args &&...args) {
    assert(!connection.broken());

    cmd(connection, std::forward<Args>(args)...);

    auto reply = connection.recv();

    return reply;
}

redis与连接

在这里插入图片描述
Redis处理命令
在这里插入图片描述

connection主要方法及与reply关系
在这里插入图片描述
connection只支持移动语义,不支持拷贝和赋值
recv使用ReplyUPtr,即unique_ptr<redisReply, ReplyDeleter>,其中ReplyDeleter定义如下

struct ReplyDeleter {
    void operator()(redisReply *reply) const {
        if (reply != nullptr) {
            freeReplyObject(reply);
        }
    }
};

其调用redisGetReply获取响应,当中会处理以下几种异常情况

  • redisGetReply返回不是REDIS_OK,抛出异常
  • 调用broken判断连接是否断了
  • 在handle_error_reply标识为true,并且响应的type值为REDIS_REPLY_ERROR,抛出异常

send方法主要是调用redisAppendCommandArgv,会处理下面情况

  • redisAppendCommandArgv如果失败,会抛出异常
  • 调用broken()看连接是否断了
   bool broken() const noexcept {
        return !_ctx || _ctx->err != REDIS_OK;
    }

连接池及其管理
在这里插入图片描述
SafeConnection:包含连接池的引用,构造函数负责从连接池中得到可用的连接,析构函数中将连接还到连接池中
GuardedConnection:使用ConnectionPoolSPtr _pool指向连接池的指针
连接池分配连接是通过ConnectionPool::fetch

  • 在池子中没有连接时,创建连接,并且连接使用计数+1
  • 池子中有时,从池子中取
Connection ConnectionPool::fetch() {
    std::unique_lock<std::mutex> lock(_mutex);

    auto connection = _fetch(lock);

    auto connection_lifetime = _pool_opts.connection_lifetime;
    auto connection_idle_time = _pool_opts.connection_idle_time;

    if (_sentinel) {
        auto opts = _opts;
        auto role_changed = _role_changed(connection.options());
        auto sentinel = _sentinel;

        lock.unlock();

        if (role_changed || _need_reconnect(connection, connection_lifetime, connection_idle_time)) {
            try {
                connection = _create(sentinel, opts);
            } catch (const Error &) {
                // Failed to reconnect, return it to the pool, and retry latter.
                release(std::move(connection));
                throw;
            }
        }

        return connection;
    }

    lock.unlock();

    if (_need_reconnect(connection, connection_lifetime, connection_idle_time)) {
        try {
            connection.reconnect();
        } catch (const Error &) {
            // Failed to reconnect, return it to the pool, and retry latter.
            release(std::move(connection));
            throw;
        }
    }

    return connection;
}

Connection ConnectionPool::_fetch(std::unique_lock<std::mutex> &lock) {
    if (_pool.empty()) {
        if (_used_connections == _pool_opts.size) {
            _wait_for_connection(lock);
        } else {
            ++_used_connections;

            // Lazily create a new (broken) connection to avoid connecting with lock.
            return Connection(_opts, Connection::Dummy{});
        }
    }

    // _pool is NOT empty.
    return _fetch();
}

Connection ConnectionPool::_fetch() {
    assert(!_pool.empty());

    auto connection = std::move(_pool.front());
    _pool.pop_front();

    return connection;
}

连接释放到连接池通过ConnectionPool::release

void ConnectionPool::release(Connection connection) {
    {
        std::lock_guard<std::mutex> lock(_mutex);

        _pool.push_back(std::move(connection));
    }

    _cv.notify_one();
}

解析响应

主要在reply.cpp和reply.h文件中
通过模板函数来作具体类型的的解析转发

template <typename T>
inline T parse(redisReply &reply) {
    return parse(ParseTag<T>(), reply);
}

解析tag

template <typename T>
struct ParseTag {};

optional解析

template <typename T>
Optional<T> parse(ParseTag<Optional<T>>, redisReply &reply) {
    if (reply::is_nil(reply)) {
        // Because of a GCC bug, we cannot return {} for -std=c++17
        // Refer to https://gcc.gnu.org/bugzilla/show_bug.cgi?id=86465
#if defined REDIS_PLUS_PLUS_HAS_OPTIONAL
        return std::nullopt;
#else
        return {};
#endif
    }

    return Optional<T>(parse<T>(reply));
}

字符串解析

std::string parse(ParseTag<std::string>, redisReply &reply) {
#ifdef REDIS_PLUS_PLUS_RESP_VERSION_3
    if (!reply::is_string(reply) && !reply::is_status(reply)
            && !reply::is_verb(reply) && !reply::is_bignum(reply)) {
        throw ParseError("STRING or STATUS or VERB or BIGNUM", reply);
    }
#else
    if (!reply::is_string(reply) && !reply::is_status(reply)) {
        throw ParseError("STRING or STATUS", reply);
    }
#endif

    if (reply.str == nullptr) {
        throw ProtoError("A null string reply");
    }

    // Old version hiredis' *redisReply::len* is of type int.
    // So we CANNOT have something like: *return {reply.str, reply.len}*.
    return std::string(reply.str, reply.len);
}

解析void

主要是解析set命令类的

  • 看type是不是REDIS_REPLY_STATUS
  • str是否等于OK
void parse(ParseTag<void>, redisReply &reply) {
    if (!reply::is_status(reply)) {
        throw ParseError("STATUS", reply);
    }

    if (reply.str == nullptr) {
        throw ProtoError("A null status reply");
    }

    static const std::string OK = "OK";

    // Old version hiredis' *redisReply::len* is of type int.
    // So we have to cast it to an unsigned int.
    if (static_cast<std::size_t>(reply.len) != OK.size()
            || OK.compare(0, OK.size(), reply.str, reply.len) != 0) {
        throw ProtoError("NOT ok status reply: " + reply::to_status(reply));
    }
}

long long类型解析

  • 检查type是不是REDIS_REPLY_INTEGER
long long parse(ParseTag<long long>, redisReply &reply) {
    if (!reply::is_integer(reply)) {
        throw ParseError("INTEGER", reply);
    }

    return reply.integer;
}

double类型解析

  • 会区分协议版本,如果是resp3,检查类型是否是REDIS_REPLY_DOUBLE,如果是,则取dval,否则作字符串处理,然后转成double类型
  • 如果不是resp3,则字符串处理,转为double类型
double parse(ParseTag<double>, redisReply &reply) {
#ifdef REDIS_PLUS_PLUS_RESP_VERSION_3
    if (is_double(reply)) {
        return reply.dval;
    } else {
        // Return by string reply.
#endif
    try {
        return std::stod(parse<std::string>(reply));
    } catch (const std::invalid_argument &) {
        throw ProtoError("not a double reply");
    } catch (const std::out_of_range &) {
        throw ProtoError("double reply out of range");
    }
#ifdef REDIS_PLUS_PLUS_RESP_VERSION_3
    }
#endif
}

bool类型解析

  • resp3协议时,判断类型是否是REDIS_REPLY_BOOL或者REDIS_REPLY_INTEGER
  • resp2协议时,当作long long类型来解析
  • 根据值是1,0来处理
bool parse(ParseTag<bool>, redisReply &reply) {
#ifdef REDIS_PLUS_PLUS_RESP_VERSION_3
    long long ret = 0;
    if (is_bool(reply) || is_integer(reply)) {
        ret = reply.integer;
    } else {
        throw ProtoError("BOOL or INTEGER");
    }
#else
    auto ret = parse<long long>(reply);
#endif

    if (ret == 1) {
        return true;
    } else if (ret == 0) {
        return false;
    } else {
        throw ProtoError("Invalid bool reply: " + std::to_string(ret));
    }
}

error体系

在这里插入图片描述

更多推荐

Blender骨骼动画简明教程

Blender是首选的开源3D动画软件之一。令人惊讶的是,开始创建简单的角色动画并不需要太多时间。一旦获得最终的3D角色模型,你就可以使用该软件的众多动画功能和工具将其变为现实。推荐:用NSDT编辑器快速搭建可编程3D场景例如,Blender的绑定工具将帮助你实现角色所需的动作。还可以使用软件的姿势编辑功能添加和操作姿

【Vue】el 和 data短小精湛的细节!

hello,我是小索奇,精心制作的Vue教程持续更新哈,花费了大量的时间和精力,总结拓展了很多疑难点,想要学习&巩固&避坑就一起学习叭~el与data的两种写法el共有2种写法el表达式主要用来在模板中展示数据,它可以输出简单的变量值,也可以调用方法。语法是${表达式}创建Vue实例对象的时候配置el属性先创建Vue实

python学习--定义python中的类

创建类的语法类的组成类属性实例方法静态方法类方法classStudent:#Student为类的名称(类名)由一个或多个单词组成,每个单词首字母大写,其余小写native_place='吉林'#直接写在类的变量,称为类属性def_init_(self,name,age):self.name=name#self.name

zabbix自定义模板,邮件报警,代理服务器,自动发现与自动添加及snmp

1.自定义监控内容zabbix监控模板大全:www.zabbix.com/integration…监控案例1:登录人数检测需求:某公司确定已经安装好zabbix监控系统,限制某台服务器登录人数不超过3个,超过3个就发出报警信息。该服务器(192.168.73.114)已经添加至zabbix监控系统中具体步骤步骤一:在客

MyBatis高级

文章目录一、动态sql1、if2、choose...when...otherwise...3、where4、set5、foreach6、trim7、Sql片段二、分页1、分页的使用步骤1.1、导入maven依赖2、mybatis配置文件中指定方言3、java代码测试三、mybatis多表查询1、一对一2、一对多3、多对

Docker 一键安装Confluence(已支持最新版本)

Docker一键安装Confluence(已支持最新版本)本文用于Confluence在Docker的安装,仅用于记录安装方式Jira也可以参考这种方式安装,只有细微差别转载请注明来源Linux安装可参考链接Windows安装可查考链接条件允许时,请优先选择CentOS7原生安装一、查找想要的版本跟着文档走,不想换版本

23个销量最高的3D扫描仪【2023】

如果你可以3D扫描它,你就可以3D打印它。市场上3D扫描仪的种类和质量非常丰富,机器尺寸、功能和价格各异。这样的选择虽然本身是一件很棒的事情,但也会让从无用的东西中挑选出宝石成为一件苦差事。推荐:用NSDT编辑器快速搭建可编程3D场景无论你是在寻找适合学生或业余爱好者的完美入门级扫描仪、具有更好软件和工作流程的更强大的

Talk | ICCV’23 清华赵天辰:Ada3D-基于动态推理的3D感知模型压缩及软硬件协同优化

​本期为TechBeat人工智能社区第533期线上Talk!北京时间9月21日(周四)20:00,清华大学博士生—赵天辰的Talk已准时在TechBeat人工智能社区开播!他与大家分享的主题是:“Ada3D-基于动态推理的3D感知模型压缩及软硬件协同优化”,他介绍了他们提出的动态推理框架Ada3D,并进一步介绍了在硬件

Nginx服务配置及相关模块

目录一.Nginx配置文件1.1.主配置文件解析1.2.子配置文件启用二.子配置文件使用2.1.创建虚拟主机实验2.2.基于端口虚拟主机实验三.Nginx模块3.1.access模块3.2.自定义错误页面3.3.状态页开启一.Nginx配置文件1.1.主配置文件解析①yum安装主配置文件位置:/etc/nginx/ng

vue+express、gitee pm2部署轻量服务器

一、代码配置前后端接口都保持127.0.0.1:3000vue创建文件pm2.config.cjsmodule.exports={apps:[{name:'xin-web',//应用程序的名称script:'npm',//启动脚本args:'rundev',//启动脚本的参数cwd:'/home/vue/xin_web

vue基础知识十三:Vue中的$nextTick有什么作用?

一、NextTick是什么官方对其的定义在下次DOM更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的DOM什么意思呢?我们可以理解成,Vue在更新DOM时是异步执行的。当数据发生变化,Vue将开启一个异步更新队列,视图需要等队列中所有数据变化完成之后,再统一进行更新举例一下Html结构{{me

热文推荐