• redisplusplus笔记


    设计点

    • 在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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 使用可变参数模板
    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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 在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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    redis与连接

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

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

    struct ReplyDeleter {
        void operator()(redisReply *reply) const {
            if (reply != nullptr) {
                freeReplyObject(reply);
            }
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    其调用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;
        }
    
    • 1
    • 2
    • 3

    连接池及其管理
    在这里插入图片描述
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67

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

    void ConnectionPool::release(Connection connection) {
        {
            std::lock_guard<std::mutex> lock(_mutex);
    
            _pool.push_back(std::move(connection));
        }
    
        _cv.notify_one();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    解析响应

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

    template <typename T>
    inline T parse(redisReply &reply) {
        return parse(ParseTag<T>(), reply);
    }
    
    • 1
    • 2
    • 3
    • 4

    解析tag

    template <typename T>
    struct ParseTag {};
    
    • 1
    • 2

    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));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    字符串解析

    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    解析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));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    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
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    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));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    error体系

    在这里插入图片描述

  • 相关阅读:
    基于B/S机场飞机票网上订票管理系统【Java毕业设计·安装调试·代码讲解·文档报告】
    FFMpeg zoompan 镜头聚焦和移动走位
    Jenkins自动化:简化部署流程
    【JavaEE】Servlet API 详解(HttpServlet类)
    window安装rust
    F - New Year Snowmen
    【实践篇】Redis使用规范清单详解
    BurpSuite安装
    如何在 Ubuntu VPS 实例上安装 Chef 服务器、工作站和客户端
    mysql 创建学生表、课程表、学生选课表
  • 原文地址:https://blog.csdn.net/wuli2496/article/details/132990343