0.RESP

  • RESP:Redis自定义的应用层协议
    • 传输层基于TCP,但是和TCP又没有强耦合
  • 优点
    • 简单好实现
    • 快速进行解析
    • 肉眼可读
  • 工作流程:请求和响应之间的通信模型是一问一答的形式
    • 客户端给服务器发一个请求(Redis 命令),服务器返回一个响应
    • 客户端和服务器只需要按照格式,构造出字符串,往socket中写入或者从socket中读取字符串,按照上述格式解析

1.前置依赖 – hiredis

  • redis-plus-plus 是基于hiredis实现的,hiredis是⼀个C语⾔实现的Redis客⼾端
  • 安装apt install libhiredis-dev

2.安装 redis-plus-plus

  • C++操作Redis的库有很多,此处使用redis-plus-plus,该库功能强大,使用简单,
  • 安装
    git clone https://github.com/sewenew/redis-plus-plus.git
    
    cd redis-plus-plus
    mkdir build && cd build
    
    cmake .. -DCMAKE_INSTALL_PREFIX=/usr
    make
    make install
    
  • 说明redis-plus-plus该库接口风格非常统一,用起来非常舒服
    • 当一个函数,参数需要传递多个值的时候,往往都是支持初始化列表或者是一对迭代器的方式,来进行实现
    • 当一个函数,返回值需要表示多个数据的时候,也往往会接入插入迭代器,来实现往一个容器中添加元素的效果
    • 当某些场景涉及到无效值的时候,往往会搭配std::optional来进行使用

3.示例:通用命令

1.Quick Start

  • main.cc
    #include <iostream>
    #include <string>
    #include <sw/redis++/redis++.h>
    using namespace std;
    
    int main()
    {
        // 构建Redis对象的时候,在构造函数中,指定Redis服务器的地址和端口
        sw::redis::Redis redis("tcp://127.0.0.1:6379");
        string ret = redis.ping();
        cout << ret << endl;
        return 0;
    }
    
  • Makefile
    main:main.cc
    	g++ -std=c++17 -o $@ $^ -l redis++ -l hiredis -l pthread
    
    .PHONY:clean
    clean:
    	rm main
    

2.Get && Set

  • 此处的redis.get()返回的是一个optional类型
    • optional类型在C++14中,正式纳入标准库
    • 因为redis.get()会返回nil无效值,用std::string不方便表现nil,而std::string*虽然可以用nullptr表示无效,但是返回指针又设计内存管理问题
    • 所以综上,此处使用optional表示"非法值"/“无效值”
    void TestGetAndSet(sw::redis::Redis& redis)
    {
    	// 清空数据库,避免之前残留的数据干扰
    	redis.flushall();
    
    	// 使用set设置key
    	redis.set("key1", "233");
    	redis.set("key2", "666");
    	redis.set("key3", "888");
    
    	// 使用get获取到key对应的value
    	auto value1 = redis.get("key1");
    
    	// optional 可以隐式转成 bool 类型, 可以直接在 if 中判定. 如果是无效元素, 就是返回 false
    	if(value1)
    	{
    		cout << "value1=" << value1.value() << endl; // sw::redis::OptionalString没有重载<<
    	}
    
    	auto value2 = redis.get("key2");
    	if(value2)
    	{
    		cout << "value2=" << value2.value() << endl;
    	}
    
    	auto value3 = redis.get("key3");
    	if (value2)
    	{
    		cout << "value3=" << value3.value() << endl;
    	}
    
    	auto value4 = redis.get("key4");
    	if (value4)
    	{
    		// 如果不加判断,则会在此处抛出异常 std::bad_optional_acess
    		cout << "value4=" << value4.value() << endl;
    	}
    }
    

3.Exists

void TestExists(sw::redis::Redis &redis)
{
    redis.flushall();

    redis.set("key1", "111");
    redis.set("key3", "111");

    auto ret = redis.exists("key1");
    cout << ret << endl;

    ret = redis.exists("key2");
    cout << ret << endl;

    // exists()的重载形式,可以用一个初始化列表传参
    ret = redis.exists({"key1", "key2", "key3"});
    cout << ret << endl;
}

4.Keys

void TestKeys(sw::redis::Redis &redis)
{
    redis.flushall();

    redis.set("key1", "111");
    redis.set("key2", "222");
    redis.set("key3", "333");
    redis.set("key4", "444");
    redis.set("key5", "555");
    redis.set("key6", "666");

    // keys 的第二个参数, 是一个 "插入迭代器". 需要先准备好一个保存结果的容器.
    // 接下来再创建一个插入迭代器指向容器的位置. 就可以把 keys 获取到的结果依次通过刚才的插入迭代器插入到容器的指定位置中了.
    // 插入迭代器 -> 位置 + 动作
    vector<string> ret;
    auto it = std::back_inserter(ret);
    redis.keys("*", it);
    PrintContainer(ret);
}

5.Expire && TTL

void TestExpireAndTTL(sw::redis::Redis &redis)
{
    redis.flushall();

    redis.set("key", "111");
    // 10s -> std::chrono::seconds(10);
    redis.expire("key", 10s); // 此处10s为字面值,类似于2.33f

    std::this_thread::sleep_for(3s);

    auto time = redis.ttl("key");
    cout << time << endl;
}

6.Type

void TestType(sw::redis::Redis& redis)
{
    redis.flushall();

    redis.set("key1", "111");
    string result = redis.type("key");
    cout << "key1: " << result << endl;

    redis.lpush("key2", "111");
    result = redis.type("key2");
    cout << "key2: " << result << endl;

    redis.hset("key3", "aaa", "111");
    result = redis.type("key3");
    cout << "key3: " << result << endl;

    redis.sadd("key4", "aaa");
    result = redis.type("key4");
    cout << "key4: " << result << endl;

    redis.zadd("key5", "吕布", 99);
    result = redis.type("key5");
    cout << "key5: " << result << endl;
}

4.示例:String

1.Set with expire

void TestSetWithExpire(Redis& redis)
{
    redis.flushall();

    redis.set("key", "111", 10s);

    std::this_thread::sleep_for(3s);

    long long time = redis.ttl("key");
    std::cout << "time: " << time << std::endl;
}

2.Set NX / XX

void TestSetNXAndXX(Redis &redis)
{
    redis.flushall();

    redis.set("key", "111");

    // set 的重载版本中, 没有单独提供 NX 和 XX 的版本, 必须搭配过期时间的版本来使用
    // 过期时间填0s则为永不过期
    redis.set("key", "222", 0s, sw::redis::UpdateType::EXIST); 

    auto value = redis.get("key");
    if (value)
    {
        std::cout << "value: " << value.value() << std::endl;
    }
}

3.Mset

void TestMset(Redis &redis)
{
    redis.flushall();

    // 第一种写法, 使用初始化列表描述多个键值对
    // redis.mset({ std::make_pair("key1", "111"), std::make_pair("key2", "222"), std::make_pair("key3", "333") });

    // 第二种写法, 可以把多个键值对提前组织到容器中. 以迭代器的形式告诉 mset
    vector<pair<string, string>> keys = {
        {"key1", "111"},
        {"key2", "222"},
        {"key3", "333"}
    };
    redis.mset(keys.begin(), keys.end());

    auto value = redis.get("key1");
    if (value)
    {
        std::cout << "value: " << value.value() << std::endl;
    }

    value = redis.get("key2");
    if (value)
    {
        std::cout << "value: " << value.value() << std::endl;
    }

    value = redis.get("key3");
    if (value)
    {
        std::cout << "value: " << value.value() << std::endl;
    }
}

4.Mget

void TestMget(Redis &redis)
{
    redis.flushall();

    vector<std::pair<string, string>> keys = {
        {"key1", "111"},
        {"key2", "222"},
        {"key3", "333"}};
    redis.mset(keys.begin(), keys.end());

    vector<sw::redis::OptionalString> result;
    auto it = std::back_inserter(result);
    redis.mget({"key1", "key2", "key3", "key4"}, it);

    PrintContainerOptional(result);
}

5.Getrange && Setrange

void TestRange(Redis &redis)
{
    redis.flushall();

    redis.set("key", "DieKSnowK");

    string result = redis.getrange("key", 2, 5);
    cout << "result: " << result << endl;

    redis.setrange("key", 2, "xyz");

    auto value = redis.get("key");
    cout << "value: " << value.value() << endl;
}

5.示例:list

1.Push && Range

void TestPushAndRange(Redis &redis)
{
    redis.flushall();

    // 插入单个元素
    redis.lpush("key", "111");

    // 插入一组元素, 基于初始化列表
    redis.lpush("key", {"222", "333", "444"});

    // 插入一组元素, 基于迭代器
    vector<string> values = {"555", "666", "777"};
    redis.lpush("key", values.begin(), values.end());

    // lrange 获取到列表中的元素
    vector<string> results;
    auto it = std::back_inserter(results);
    redis.lrange("key", 0, -1, it);

    PrintContainer(results);
}

2.Pop

void TestPop(Redis &redis)
{
    redis.flushall();

    // 构造一个 list
    redis.rpush("key", {"1", "2", "3", "4"});

    auto result = redis.lpop("key");
    if (result)
    {
        std::cout << "lpop: " << result.value() << std::endl;
    }

    result = redis.rpop("key");
    if (result)
    {
        std::cout << "rpop: " << result.value() << std::endl;
    }
}

3.blpop

  • TIPS:对于std::optional类型来说,可以直接使用->访问optional内部包含的元素的成员
    void TestBlpop(Redis &redis)
    {
        redis.flushall();
    
        auto result = redis.blpop({"key", "key2", "key3"}, 10s);
    
        // 此处可以考虑在其他redis-cli插入数据以便进一步观察效果
    
        if (result)
        {
            std::cout << "key:" << result->first << std::endl;
            std::cout << "elem:" << result->second << std::endl;
        }
        else
        {
            std::cout << "result 无效!" << std::endl;
        }
    }
    

4.llen

void TestLlen(Redis &redis)
{
    redis.flushall();

    redis.lpush("key", {"111", "222", "333", "444"});
    long long len = redis.llen("key");
    cout << "len: " << len << endl;
}

6.示例:set

1.Sadd && Smembers

  • 此处要注意保存结果的方式和之前的区别
    void TestSaddAndSmembers(Redis& redis)
    {
        redis.flushall();
    
        // 一次添加一个元素
        redis.sadd("key", "111");
    
        // 一次添加多个元素(使用初始化列表)
        redis.sadd("key", {"222", "333", "444"});
    
        // 一次添加多个元素(使用迭代器)
        set<string> elems = {"555", "666", "777"};
        redis.sadd("key", elems.begin(), elems.end());
    
        // 获取到上述元素
        // vector<string> result;
        // auto it = std::back_inserter(result);
    
        // 此处用来保存 smembers 的结果, 使用 set 可能更合适.
        set<string> result;
    
        // 由于此处 set 里的元素顺序是固定的. 指定一个 result.end() 或者 result.begin() 或者其他位置的迭代器, 都无所谓
        auto it = std::inserter(result, result.end());
        redis.smembers("key", it);
    
        PrintContainer(result);
    }
    

2.Sismerber

void TestSismember(Redis& redis)
{
    redis.flushall();

    redis.sadd("key", {"111", "222", "333", "444"});

    bool result = redis.sismember("key", "555");
    cout << "result: " << result << endl;
}

3.Scard

void TestScard(Redis &redis)
{
    redis.flushall();

    redis.sadd("key", {"111", "222", "333"});
    long long result = redis.scard("key");
    cout << "result: " << result << endl;
}

4.Spop

void TestSpop(Redis& redis)
{
    redis.flushall();

    redis.sadd("key", {"111", "222", "333", "444"});
    auto result = redis.spop("key");
    if (result)
    {
        std::cout << "result: " << result.value() << std::endl;
    }
    else
    {
        std::cout << "result 无效!" << std::endl;
    }
}

5.Sinter

void TestSinter(Redis &redis)
{
    redis.flushall();

    redis.sadd("key1", {"111", "222", "333"});
    redis.sadd("key2", {"111", "222", "444"});

    set<string> result;
    auto it = std::inserter(result, result.end());
    redis.sinter({"key1", "key2"}, it);

    PrintContainer(result);
}

6.Sinterstore

void TestSinterstore(Redis &redis)
{
    redis.flushall();

    redis.sadd("key1", {"111", "222", "333"});
    redis.sadd("key2", {"111", "222", "444"});

    long long len = redis.sinterstore("key3", {"key1", "key2"});
    cout << "len: " << len << endl;

    set<string> result;
    auto it = std::inserter(result, result.end());
    redis.smembers("key3", it);

    PrintContainer(result);
}

7.示例:hash

1.Hset && Hget

void TestHsetAndHget(Redis &redis)
{
    redis.flushall();

    redis.hset("key", "f1", "111");
    redis.hset("key", std::make_pair("f2", "222"));

    // hset 能够一次性插入多个 field-value 对
    redis.hset("key", {std::make_pair("f3", "333"),
                       std::make_pair("f4", "444")});
                       
    vector<std::pair<string, string>> fields = {
        std::make_pair("f5", "555"),
        std::make_pair("f6", "666")};
    redis.hset("key", fields.begin(), fields.end());

    auto result = redis.hget("key", "f3");
    if (result)
    {
        std::cout << "result: " << result.value() << std::endl;
    }
    else
    {
        std::cout << "result 无效!" << std::endl;
    }
}

2.Hexists

void TestHexists(Redis &redis)
{
    redis.flushall();

    redis.hset("key", "f1", "111");
    redis.hset("key", "f2", "222");
    redis.hset("key", "f3", "333");

    bool result = redis.hexists("key", "f4");
    std::cout << "result: " << result << std::endl;
}

3.Hdel

void TestHdel(Redis &redis)
{
    redis.flushall();

    redis.hset("key", "f1", "111");
    redis.hset("key", "f2", "222");
    redis.hset("key", "f3", "333");

    long long result = redis.hdel("key", "f1");
    std::cout << "result: " << result << std::endl;

    result = redis.hdel("key", {"f2", "f3"});
    std::cout << "result: " << result << std::endl;

    long long len = redis.hlen("key");
    std::cout << "len: " << len << std::endl;
}

4.Hkeys && Hvals

void TestHkeysAndHvals(Redis &redis)
{
    redis.flushall();

    redis.hset("key", "f1", "111");
    redis.hset("key", "f2", "222");
    redis.hset("key", "f3", "333");

    vector<string> fields;
    auto itFields = std::back_inserter(fields);
    redis.hkeys("key", itFields);
    PrintContainer(fields);

    vector<string> values;
    auto itValues = std::back_inserter(values);
    redis.hvals("key", itValues);
    PrintContainer(values);
}

5.Hmget && Hmset

void TestHmgetAndHmset(Redis &redis)
{
    redis.flushall();

    redis.hmset("key", {std::make_pair("f1", "111"),
                        std::make_pair("f2", "222"),
                        std::make_pair("f3", "333")});

    vector<std::pair<string, string>> pairs = {
        std::make_pair("f4", "444"),
        std::make_pair("f5", "555"),
        std::make_pair("f6", "666")};
    redis.hmset("key", pairs.begin(), pairs.end());

    vector<string> values;
    auto it = std::back_inserter(values);
    redis.hmget("key", {"f1", "f2", "f3"}, it);
    PrintContainer(values);
}

8.示例:zset

1.Zadd && Zrange

  • zrange支持两种主要的风格:
    • 只查询member, 不带score
    • 查询member同时带score
  • 关键就是看插入迭代器指向的容器的类型
    • 指向的容器只是包含一个string, 就是只查询member
    • 指向的容器包含的是一个pair, 里面有stringdouble, 就是查询member同时带有score
    void TestZaddAndZrange(Redis &redis)
    {
        redis.flushall();
    
        redis.zadd("key", "吕布", 99);
        redis.zadd("key", {std::make_pair("赵云", 98),
                           std::make_pair("典韦", 97)});
    
        vector<std::pair<string, double>> members = {
            std::make_pair("关羽", 95),
            std::make_pair("张飞", 93)};
        redis.zadd("key", members.begin(), members.end());
    
        vector<string> memberResults;
        auto it = std::back_inserter(memberResults);
        redis.zrange("key", 0, -1, it);
        PrintContainer(memberResults);
    
        vector<std::pair<string, double>> membersWithScore;
        auto it2 = std::back_inserter(membersWithScore);
        redis.zrange("key", 0, -1, it2);
        PrintContainerPair(membersWithScore);
    }
    

2.Zcard

void TestZcard(Redis &redis)
{
    redis.flushall();

    redis.zadd("key", "zhangsan", 90);
    redis.zadd("key", "lisi", 91);
    redis.zadd("key", "wangwu", 92);
    redis.zadd("key", "zhaoliu", 93);

    long long result = redis.zcard("key");
    std::cout << "result: " << result << std::endl;
}

3.Zrem

void TestZrem(Redis &redis)
{
    redis.flushall();

    redis.zadd("key", "zhangsan", 90);
    redis.zadd("key", "lisi", 91);
    redis.zadd("key", "wangwu", 92);
    redis.zadd("key", "zhaoliu", 93);

    redis.zrem("key", "zhangsan");

    long long result = redis.zcard("key");
    std::cout << "result: " << result << std::endl;
}

4.Zscore

void TestZscore(Redis &redis)
{
    redis.flushall();

    redis.zadd("key", "zhangsan", 90);
    redis.zadd("key", "lisi", 91);
    redis.zadd("key", "wangwu", 92);
    redis.zadd("key", "zhaoliu", 93);

    auto score = redis.zscore("key", "zhangsan");
    if (score)
    {
        std::cout << "score: " << score.value() << std::endl;
    }
    else
    {
        std::cout << "score 无效" << std::endl;
    }
}

Logo

助力广东及东莞地区开发者,代码托管、在线学习与竞赛、技术交流与分享、资源共享、职业发展,成为松山湖开发者首选的工作与学习平台

更多推荐