redis-plus-plus源码分析

redis-plus-plus源码分析

简介

作为c++ 的redis客户端,由于项目中集成redis,选择了使用redis-plus-plus库作为客户端,初次看redis-plus-plus代码,分享下看代码的过程,以便参考。

从官方的readme说起

官方readme已经详细些了安装步骤和使用的demo,安装过程很顺利,没有遇到问题,主要是看demo然后参照源码,去看下怎么连接redis,如何支持redis集群,如果获取数据和设置数据,demo如下:

#include <sw/redis++/redis++.h>
#include <iostream>
using namespace sw::redis;

int main() {
  try {
    // Create an Redis object, which is movable but NOT copyable.
    auto redis = Redis("tcp://127.0.0.1:6379");

    // ***** STRING commands *****

    redis.set("key", "val");
    auto val = redis.get("key");  // val is of type OptionalString. See 'API
                                  // Reference' section for details.
    if (val) {
      // Dereference val to get the returned value of std::string type.
      std::cout << *val << std::endl;
    }  // else key doesn't exist.

    // ***** LIST commands *****

    // std::vector<std::string> to Redis LIST.
    std::vector<std::string> vec = {"a", "b", "c"};
    redis.rpush("list", vec.begin(), vec.end());

    // std::initializer_list to Redis LIST.
    redis.rpush("list", {"a", "b", "c"});

    // Redis LIST to std::vector<std::string>.
    vec.clear();
    redis.lrange("list", 0, -1, std::back_inserter(vec));

    // ***** HASH commands *****

    redis.hset("hash", "field", "val");

    // Another way to do the same job.
    redis.hset("hash", std::make_pair("field", "val"));

    // std::unordered_map<std::string, std::string> to Redis HASH.
    std::unordered_map<std::string, std::string> m = {{"field1", "val1"},
                                                      {"field2", "val2"}};
    redis.hmset("hash", m.begin(), m.end());

    // Redis HASH to std::unordered_map<std::string, std::string>.
    m.clear();
    redis.hgetall("hash", std::inserter(m, m.begin()));

    // Get value only.
    // NOTE: since field might NOT exist, so we need to parse it to
    // OptionalString.
    std::vector<OptionalString> vals;
    redis.hmget("hash", {"field1", "field2"}, std::back_inserter(vals));

    // ***** SET commands *****

    redis.sadd("set", "m1");

    // std::unordered_set<std::string> to Redis SET.
    std::unordered_set<std::string> set = {"m2", "m3"};
    redis.sadd("set", set.begin(), set.end());

    // std::initializer_list to Redis SET.
    redis.sadd("set", {"m2", "m3"});

    // Redis SET to std::unordered_set<std::string>.
    set.clear();
    redis.smembers("set", std::inserter(set, set.begin()));

    if (redis.sismember("set", "m1")) {
      std::cout << "m1 exists" << std::endl;
    }  // else NOT exist.

    // ***** SORTED SET commands *****

    redis.zadd("sorted_set", "m1", 1.3);

    // std::unordered_map<std::string, double> to Redis SORTED SET.
    std::unordered_map<std::string, double> scores = {{"m2", 2.3}, {"m3", 4.5}};
    redis.zadd("sorted_set", scores.begin(), scores.end());

    // Redis SORTED SET to std::vector<std::pair<std::string, double>>.
    // NOTE: The return results of zrangebyscore are ordered, if you save the
    // results in to `std::unordered_map<std::string, double>`, you'll lose the
    // order.
    std::vector<std::pair<std::string, double>> zset_result;
    redis.zrangebyscore("sorted_set",
                        UnboundedInterval<double>{},  // (-inf, +inf)
                        std::back_inserter(zset_result));

    // Only get member names:
    // pass an inserter of std::vector<std::string> type as output parameter.
    std::vector<std::string> without_score;
    redis.zrangebyscore(
        "sorted_set",
        BoundedInterval<double>(1.5, 3.4, BoundType::CLOSED),  // [1.5, 3.4]
        std::back_inserter(without_score));

    // Get both member names and scores:
    // pass an back_inserter of std::vector<std::pair<std::string, double>> as
    // output parameter.
    std::vector<std::pair<std::string, double>> with_score;
    redis.zrangebyscore(
        "sorted_set",
        BoundedInterval<double>(1.5, 3.4, BoundType::LEFT_OPEN),  // (1.5, 3.4]
        std::back_inserter(with_score));

    // ***** SCRIPTING commands *****

    // Script returns a single element.
    auto num = redis.eval<long long>("return 1", {}, {});

    // Script returns an array of elements.
    std::vector<std::string> nums;
    redis.eval("return {ARGV[1], ARGV[2]}", {}, {"1", "2"},
               std::back_inserter(nums));

    // mset with TTL
    auto mset_with_ttl_script = R"(
        local len = #KEYS
        if (len == 0 or len + 1 ~= #ARGV) then return 0 end
        local ttl = tonumber(ARGV[len + 1])
        if (not ttl or ttl <= 0) then return 0 end
        for i = 1, len do redis.call("SET", KEYS[i], ARGV[i], "EX", ttl) end
        return 1
    )";

    // Set multiple key-value pairs with TTL of 60 seconds.
    auto keys = {"key1", "key2", "key3"};
    std::vector<std::string> args = {"val1", "val2", "val3", "60"};
    redis.eval<long long>(mset_with_ttl_script, keys.begin(), keys.end(),
                          args.begin(), args.end());

    // ***** Pipeline *****

    // Create a pipeline.
    auto pipe = redis.pipeline();

    // Send mulitple commands and get all replies.
    auto pipe_replies = pipe.set("key", "value")
                            .get("key")
                            .rename("key", "new-key")
                            .rpush("list", {"a", "b", "c"})
                            .lrange("list", 0, -1)
                            .exec();

    // Parse reply with reply type and index.
    auto set_cmd_result = pipe_replies.get<bool>(0);

    auto get_cmd_result = pipe_replies.get<OptionalString>(1);

    // rename command result
    pipe_replies.get<void>(2);

    auto rpush_cmd_result = pipe_replies.get<long long>(3);

    std::vector<std::string> lrange_cmd_result;
    pipe_replies.get(4, back_inserter(lrange_cmd_result));

    // ***** Transaction *****

    // Create a transaction.
    auto tx = redis.transaction();

    // Run multiple commands in a transaction, and get all replies.
    auto tx_replies =
        tx.incr("num0").incr("num1").mget({"num0", "num1"}).exec();

    // Parse reply with reply type and index.
    auto incr_result0 = tx_replies.get<long long>(0);

    auto incr_result1 = tx_replies.get<long long>(1);

    std::vector<OptionalString> mget_cmd_result;
    tx_replies.get(2, back_inserter(mget_cmd_result));

    // ***** Generic Command Interface *****

    // There's no *Redis::client_getname* interface.
    // But you can use *Redis::command* to get the client name.
    val = redis.command<OptionalString>("client", "getname");
    if (val) {
      std::cout << *val << std::endl;
    }

    // Same as above.
    auto getname_cmd_str = {"client", "getname"};
    val = redis.command<OptionalString>(getname_cmd_str.begin(),
                                        getname_cmd_str.end());

    // There's no *Redis::sort* interface.
    // But you can use *Redis::command* to send sort the list.
    std::vector<std::string> sorted_list;
    redis.command("sort", "list", "ALPHA", std::back_inserter(sorted_list));

    // Another *Redis::command* to do the same work.
    auto sort_cmd_str = {"sort", "list", "ALPHA"};
    redis.command(sort_cmd_str.begin(), sort_cmd_str.end(),
                  std::back_inserter(sorted_list));

    // ***** Redis Cluster *****

    // Create a RedisCluster object, which is movable but NOT copyable.
    auto redis_cluster = RedisCluster("tcp://127.0.0.1:7000");

    // RedisCluster has similar interfaces as Redis.
    redis_cluster.set("key", "value");
    val = redis_cluster.get("key");
    if (val) {
      std::cout << *val << std::endl;
    }  // else key doesn't exist.

    // Keys with hash-tag.
    redis_cluster.set("key{tag}1", "val1");
    redis_cluster.set("key{tag}2", "val2");
    redis_cluster.set("key{tag}3", "val3");

    std::vector<OptionalString> hash_tag_res;
    redis_cluster.mget({"key{tag}1", "key{tag}2", "key{tag}3"},
                       std::back_inserter(hash_tag_res));
  } catch (const Error &e) {
    // Error handling.
  }
}

构造Redis连接

Redis对象建立

构造Uri对象
auto redis = Redis(“tcp://127.0.0.1:6379”);
解析 “tcp://127.0.0.1:6379”

Created with Raphaël 2.3.0 开始 是否包含"//" 截取//前面字符 是否包含"@" 截取//后面到@前面字符,获取用户的认证信息 结束 yes no yes no

默认解析的格式如下
// 指定dbnum
tcp://usr:passwd@ip:port/dbnum
// 没有指定dbnum,含有自定义参数
tcp://usr:passwd@ip:port?parameter_string
首先成三部分, tcp user:passwd ip:port
给 ConnectionOptions 赋值,加进去userid,passwd,host,port,dbnum,type
解析自定义参数
自定义参数的格式 如:
user=xxx&password=xxx&db=xxx
最终给Uri的ConnectionOptions、ConnectionPoolOptions赋值完成构造

使用Uri给ConnectionPool构造,使用ConnectionOptions,ConnectionPoolOptions给ConnectionPool构造函数,生成一个 ConnectionPool智能指针,仅仅是赋值,并不涉及建立连接,Redis对象构造完成。

Redis连接建立

执行
redis.set(“key”, “val”);
在这里插入图片描述
在这里插入图片描述
可以看到构造Redis时候并没有给_connection赋值,只是初始化,此时先构造一个SafeConnection对象
在这里插入图片描述

Connection ConnectionPool::fetch() {
    std::unique_lock<std::mutex> lock(_mutex); //锁住连接池
	
	/*
	此处先判断,连接池是否是空,如果是空的判断_used_connections是否和连接池容量相等,如果相等则等待直到连接池不为空
	如果不相等则新建Connection对象,此时连接没有建立
	连接池如果不是空的,从连接池中获取
	*/
    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(); //建立连接使用是hiredis的redisConnect接口
        } catch (const Error &) {
            // Failed to reconnect, return it to the pool, and retry latter.
            release(std::move(connection));
            throw;
        }
    }

    return connection;
}

在 SafeConnection中构造函数调用了
_pool的fetch方法拿到了连接,给到了_connection变量

set的执行在这里插入图片描述
CmdArgs 类组装命令 SET KEY VAL
在这里插入图片描述
调用了Connection的send方法
在这里插入图片描述
完成一次到redis操作

总结

redis++库对hiredis做了封装,使用了懒加载即使用的时候才去建立连接。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值