云计算百科
云计算领域专业知识百科平台

全网最细 C++ Redis 开发教程:库安装 + 通用命令 + 五大类型 + 集群适配----《Hello Redis!》(4)

文章目录

  • 前言
  • 使用C++接口操作Redis
    • 安装redis-plus-plus
    • Redis相关的C++接口
    • string相关的接口
    • list相关的接口
    • set相关的接口
    • hash相关的接口
    • zset相关的接口
    • 集群相关的接口

前言

在前面的系列文章中,我们已经系统掌握了 Redis 的核心特性、五大基础数据类型(string/hash/list/set/zset)的命令用法与业务场景。但在实际的后端项目开发中,通过编程语言调用 Redis 接口实现业务逻辑,才是落地应用的关键。对于追求高性能的 C++ 后端开发而言,redis-plus-plus是基于 hiredis 封装、支持 C++11 及以上标准的主流 Redis 客户端库,接口简洁、功能完备,完美适配生产环境开发。

本文将带你从零开始,完成C++ 操作 Redis 的全流程实战:从 Ubuntu 环境下 hiredis 与 redis-plus-plus 的安装编译,到 RESP 通信协议的核心原理;从 Redis 通用命令(ping、exists、expire 等)的 C++ 接口实现,到 string、list、set、hash、zset 五大核心类型的 API 实战调用;再到 Redis 集群的适配使用、Optional类型判空、迭代器用法等关键细节。

全文以可直接运行的代码示例为核心,覆盖开发中高频使用的所有接口,帮你快速掌握 C++ 与 Redis 的集成开发,轻松将 Redis 的缓存、队列、排行榜等能力落地到实际项目中。

使用C++接口操作Redis

这里自己选择C++操作Redis的库是redis-plus-plus

使用C++接口能操作Redis的原因:

Redis自己定义了一套请求/响应格式(RESP协议);他们网络通信的应用层都是用的RESP协议,所以…

关于RESP协议的介绍:

它的优点是:1.简单好实现 2.快速进行解析 3.RESP协议的内容是肉眼可读的

虽然RESP协议是需要TCP传输的,但是跟TCP不是强耦合(RESP协议本身不依赖TCP的任何特性)

RESP协议是基于TCP的应用层协议

注意:redis-plus-plus要C++11及以上才能使用!!!

安装redis-plus-plus

这里只介绍如何在ubantu下下载

需要先安装hiredis:apt install libhiredis-dev

再下载redis-plus-plus的源码:https://github.com/sewenew/redis-plus-plus

然后使用cmake去构建:cd redis-plus-plus mkdir build cd build cmake .. make make install

Redis相关的C++接口

官方API的参考手册:redis-plus-plus/src/sw/redis++/redis.h at master · sewenew/redis-plus-plus · GitHub

redis-plus-plus这个库的接口风格还是很统一的:

一个函数参数需要传递多个值时,往往都是支持初始化列表或者一对迭代器的方式来实现

一个函数返回值需要表示多个数据的时候,也往往会借助插入迭代器来实现往一个容器中添加元素的效果

当某个场景涉及到无效值时,往往会搭配std::optional来进行使用

如果放在系统目录下的话,需要包含的头文件是#include <sw/redis++/redis++.h>

用makefile编译程序时记得引入库文件:redis++的静态库 hiredis的静态库 线程库

在ubantu下:redis++的静态库文件位置:/usr/local/lib/libredis++.a

hiredis的静态库文件位置:`/usr/lib/x86\\_64-linux-gnu/libhiredis.a`

检测自己的C++程序能不能成功连接上本地的Redis服务器:

sw::redis::Redis redis("tcp://127.0.0.1:6379");//创建一个redis对象
下面的接口都是拿这个对象举例!
string result = redis.ping();//ping一下,如果返回的是PONG,就说明通信没问题

清空所有数据库的全部数据:redis.flushall()–进行单元测试的时候,清除数据的操作放在开始好些(日常情况下flushall千万不要用!!!)

检查键是否存在:

exists:
auto ret = redis.exists("key");

auto ret = redis.exists({"key", "key2", "key3"});

删除键:

auto ret = redis.del("key");

auto ret = redis.del({"key", "key2", "key3"});

keys:

redis.keys(需要查找啥, 输出迭代器);

eg:
vector<string> result;
auto it = std::back_inserter(result);
redis.keys("*", it);

这里使用迭代器进行插入的原因:进行解耦合

expire:给key设置过期时间

redis.expire("key", std::chrono::seconds(10));
//如果用了using namespace std::chrono_literals,那这直接写10s就行了!

ttl:获取剩余过期时间

auto time = redis.ttl("key");

type:获取Redis键对应的数据类型

string result = redis.type("key");

引申:有关命名空间的补充:

using sw::redis::Redis是指以后编译器见到Redis就处理成sw::redis::Redis

using namespace std::chrono_literals:那之后编译器见到这个命名空间里有的东西就会自动加上这个前缀,比如:10s就是std::chrono::seconds(10)

注意:redis-plus-plus里面的时间只认std::chrono::duration类型的!

string相关的接口

以下接口都以举例的方式说明

set,mset和get,mget的用法:

redis.set("key", "111");
//这里的"key"和"111"其实是Redis自己封装的一个sw::redis::StringView类型(C++17的话是std::string::view的别名)
auto value = redis.get("key");//返回值是OptionalString类型.失败的话会返回这个类型的空对象
// optional 可以隐式转成 bool 类型, 可以直接在 if 中判定. 如果是无效元素, 就是返回 false
value.value()里面存的就是"111"

redis.set("key", "111", std::chrono::seconds(10));//在设置这组值的同时设置10秒过期时间
long long time = redis.ttl("key");//查询还有多久过期

auto ret = redis.exists("key");//检查key是否存在

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

// 第一种写法
redis.mset({ std::make_pair("key1", "111"), std::make_pair("key2", "222"), std::make_pair("key3", "333") });

// 第二种写法
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);//自动调用容器的 push_back 方法插入元素
//下面这个操作返回结果的顺序和传入的 key 顺序完全一致,不存在的 key 对应位置会是 nullopt
redis.mget({"key1", "key2", "key3", "key4"}, it);

getrange和setrange:

string result = redis.getrange("key", 2, 5);//读取 key 对应字符串中,从索引2到5的子串;
redis.setrange("key", 2, "xyz");//从索引 2 的位置开始,用 "xyz" 覆盖原字符串的内容;

incr和decr:

long long result = redis.incr("key");
result = redis.decr("key");

Redis在处理数字型key时,优先用incr或decr来轮换得到最初数字,而不是get然后手动转成数字

关于StringView和string的区别:

StringView:相当于只是一个只读的快捷方式,string是一个完整的字符串拷贝

–使用StringView的话,性能会更好(因为减少了两次拷贝)

–而且string不好表示无效值,如果用string*的话,又不好管理内存

注意:如果用OptionalString的时候,没有先判断它有没有值就强行取值的话是会崩溃!!!–所以在访问得到的结果之前都要判空一下

引申:STL中的五种迭代器类型:1.输入迭代器 2.输出迭代器 3.前向迭代器 4.双向迭代器 5.随机访问迭代器

list相关的接口

lpush,rpush和lrange,lpop和rpop,blpop和brpop:

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

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

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

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

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

// 插入多个元素, 基于容器
vector<string> values = {"777", "666", "555"};
redis.rpush("key", values.begin(), values.end());

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

//lpop和rpop的用法
auto result = redis.lpop("key");
auto result = redis.rpop("key");

//blpop和brpop的用法:
auto result = redis.blpop({"key", "key2", "key3"}, std::chrono::seconds(10));
auto result = redis.brpop({"key", "key2", "key3"}, std::chrono::seconds(10));

result->first里面是key result->second里面是value

注意:blpop和brpop的返回值类型是optionalstringpair(pair里面那两个元素都是string类型的,一个是key(元素来自哪个列表),一个是value)

llen的用法:

long long len = redis.llen("key");

set相关的接口

sadd:

// 一次添加一个元素
redis.sadd("key", "111");

// 一次添加多个元素(使用初始化列表)
redis.sadd("key", {"222", "333", "444"});

// 一次添加多个元素(使用迭代器)
set<string> elems = {"555", "666", "777"};
redis.sadd("key", elems.begin(), elems.end());

smembers:

vector<string> result;//用set<string>会更合适
auto it = std::inserter(result, result.end());//这里从哪开始插入都没事,因为set的顺序本来就没啥意义
redis.smembers("key", it);

sismember:

bool result = redis.sismember("key", "555");

scard:

long long result = redis.scard("key");

spop:

auto result = redis.spop("key");//返回值类型是OptionalString

sinter:

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

sinterstore:

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

set不能直接用back_inserter的原因:std::set没有push_back方法

hash相关的接口

hset:

redis.hset("key", "f1", "111");

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

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());

hget:

auto result = redis.hget("key", "f3");//返回值是OptionalString

hexists:

bool result = redis.hexists("key", "f4");

hdel:

long long result = redis.hdel("key", "f1");//删除单个字段
long long result = redis.hdel("key", {"f2", "f3"});//批量删除字段

hlen:

long long len = redis.hlen("key");

hkeys:

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

hvals:

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

hmset:

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());

hmget:

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

zset相关的接口

zadd:

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());

zrange:

// 指向的容器只是包含一个 string, 就是只查询 member
vector<string> memberResults;
auto it = std::back_inserter(memberResults);
redis.zrange("key", 0, 1, it);
printContainer(memberResults);

// 指向的容器包含的是一个 pair, 里面有 string 和 double, 就是查询 member 同时带有 score
vector<std::pair<string, double>> membersWithScore;
auto it2 = std::back_inserter(membersWithScore);
redis.zrange("key", 0, 1, it2);

zcard:

long long result = redis.zcard("key");

zrem:

long long del_count = redis.zrem("key", "zhangsan");

long long del_count = redis.zrem("key", {"zhangsan", "lisi", "wangwu"});

std::vector<std::string> members = {"zhangsan", "lisi", "wangwu"};
long long del_count = redis.zrem("key", members.begin(), members.end());

zscore:

auto score = redis.zscore("key", "zhangsan");

zrank:

auto rank = redis.zrank("key", "zhaoliu");//返回值是OptionalLongLong类型的

集群相关的接口

这个就是把原来的Redis类改成用RedisCluster类就行了–其他的接口都是一样的

也就是改成用RedisCluster cluster("tcp://127.0.0.1:6379");这里面填集群中任意一个节点地址都行

赞(0)
未经允许不得转载:网硕互联帮助中心 » 全网最细 C++ Redis 开发教程:库安装 + 通用命令 + 五大类型 + 集群适配----《Hello Redis!》(4)
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!