文章目录
- 前言
- 使用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-plusmkdir buildcd buildcmake ..makemake 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++.ahiredis的静态库文件位置:`/usr/lib/x86\_64-linux-gnu/libhiredis.a`
检测自己的C++程序能不能成功连接上本地的Redis服务器:
sw::redis::Redisredis("tcp://127.0.0.1:6379");//创建一个redis对象---下面的接口都是拿这个对象举例!string result=redis.ping();//ping一下,如果返回的是PONG,就说明通信没问题清空所有数据库的全部数据:
redis.flushall()–进行单元测试的时候,清除数据的操作放在开始好些(日常情况下flushall千万不要用!!!)
检查键是否存在:
exists:autoret=redis.exists("key");autoret=redis.exists({"key","key2","key3"});删除键:
autoret=redis.del("key");autoret=redis.del({"key","key2","key3"});
keys:redis.keys(需要查找啥,输出迭代器);eg:vector<string>result;autoit=std::back_inserter(result);redis.keys("*",it);这里使用迭代器进行插入的原因:进行解耦合
expire:给key设置过期时间redis.expire("key",std::chrono::seconds(10));//如果用了using namespace std::chrono_literals,那这直接写10s就行了!
ttl:获取剩余过期时间autotime=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的别名)autovalue=redis.get("key");//返回值是OptionalString类型.失败的话会返回这个类型的空对象// optional 可以隐式转成 bool 类型, 可以直接在 if 中判定. 如果是无效元素, 就是返回 falsevalue.value()里面存的就是"111"redis.set("key","111",std::chrono::seconds(10));//在设置这组值的同时设置10秒过期时间longlongtime=redis.ttl("key");//查询还有多久过期autoret=redis.exists("key");//检查key是否存在// set 的重载版本中, 没有单独提供 NX 和 XX 的版本, 必须搭配过期时间的版本来使用.redis.set("key","222",0s,sw::redis::UpdateType::EXIST);//对应的是XXredis.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;autoit=std::back_inserter(result);//自动调用容器的 push_back 方法插入元素//下面这个操作返回结果的顺序和传入的 key 顺序完全一致,不存在的 key 对应位置会是 nulloptredis.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:longlongresult=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;autoit=std::back_inserter(results);redis.lrange("key",0,-1,it);//lpop和rpop的用法autoresult=redis.lpop("key");autoresult=redis.rpop("key");//blpop和brpop的用法:autoresult=redis.blpop({"key","key2","key3"},std::chrono::seconds(10));autoresult=redis.brpop({"key","key2","key3"},std::chrono::seconds(10));result->first里面是key result->second里面是value注意:
blpop和brpop的返回值类型是optionalstringpair(pair里面那两个元素都是string类型的,一个是key(元素来自哪个列表),一个是value)
llen的用法:longlonglen=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>会更合适autoit=std::inserter(result,result.end());//这里从哪开始插入都没事,因为set的顺序本来就没啥意义redis.smembers("key",it);
sismember:boolresult=redis.sismember("key","555");
scard:longlongresult=redis.scard("key");
spop:autoresult=redis.spop("key");//返回值类型是OptionalString
sinter:set<string>result;autoit=std::inserter(result,result.end());redis.sinter({"key1","key2"},it);
sinterstore:longlonglen=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:autoresult=redis.hget("key","f3");//返回值是OptionalString
hexists:boolresult=redis.hexists("key","f4");
hdel:longlongresult=redis.hdel("key","f1");//删除单个字段longlongresult=redis.hdel("key",{"f2","f3"});//批量删除字段
hlen:longlonglen=redis.hlen("key");
hkeys:vector<string>fields;autoitFields=std::back_inserter(fields);redis.hkeys("key",itFields);
hvals:vector<string>values;autoitValues=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;autoit=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, 就是只查询 membervector<string>memberResults;autoit=std::back_inserter(memberResults);redis.zrange("key",0,-1,it);printContainer(memberResults);// 指向的容器包含的是一个 pair, 里面有 string 和 double, 就是查询 member 同时带有 scorevector<std::pair<string,double>>membersWithScore;autoit2=std::back_inserter(membersWithScore);redis.zrange("key",0,-1,it2);
zcard:longlongresult=redis.zcard("key");
zrem:longlongdel_count=redis.zrem("key","zhangsan");longlongdel_count=redis.zrem("key",{"zhangsan","lisi","wangwu"});std::vector<std::string>members={"zhangsan","lisi","wangwu"};longlongdel_count=redis.zrem("key",members.begin(),members.end());
zscore:autoscore=redis.zscore("key","zhangsan");
zrank:autorank=redis.zrank("key","zhaoliu");//返回值是OptionalLongLong类型的
集群相关的接口
这个就是把原来的Redis类改成用
RedisCluster类就行了–其他的接口都是一样的也就是改成用
RedisCluster cluster("tcp://127.0.0.1:6379");这里面填集群中任意一个节点地址都行