Redis:cpp.redis++通用接口
本博客讲解redis
的C++
客户端redis-plus-plus
,这个版本的客户端,接口和redis
原生命令几乎完全一致,博客内部不会详细讲解每个接口的具体功能,因为和redis
命令是一样的,而是重点讲解器用法与参数。所有接口可以参考文档:[redis-plus-plus/redis.h]。
redis对象
redis
的所有操作都基于一个redis
对象。
在C++
文件中写入以下代码:
bash
#include <iostream>
#include <sw/redis++/redis++.h>
int main()
{
sw::redis::Redis redis("tcp://127.0.0.1:6379");
std::string result = redis.ping();
std::cout << result << std::endl;
return 0;
}
在默认的路径下,redis++.h
头文件放在了sw/redis++/
目录下,其中sw
是库作者的名称缩写。
在main
函数中,先创建一个Redis
对象,其包含在命名空间sw::redis::
内部。在创建对象时,要制定ip
和端口号,格式如下:
cpp
tcp://IP地址:端口
其中redis
的默认端口为6379
。
redis
可以使用ping
来检测连通性,通过redis.ping()
,会返回一个字符串。
随后对代码进行编译:
cpp
g++ -o test_redis test.cpp -std=c++17 -l hiredis -l redis++ -l pthread
redis++
需要依赖三个库:hiredis
、redis++
、pthread
。
如果运行程序,输出了PONG
,那么说明redis
是正常可用的。
通用接口
set & get
set
函数声明如下:
cpp
bool set(const StringView &key,
const StringView &val,
const std::chrono::milliseconds &ttl = std::chrono::milliseconds(0),
UpdateType type = UpdateType::ALWAYS);
此处有一个StringView
类型,是一个只读的字符串,其效率比std::string
更高,不过在C++17
中也支持了std::string_view
。只需要把它当作一般的std::string
也没什么大问题。
函数中,前两位是key
和value
其余的都是缺省参数,所以可以只输入key
和value
。
get
函数声明如下:
cpp
OptionalString get(const StringView &key);
get
的返回值也很特别,是一个OptionalString
类型,这是因为redis
中如果查询为空,会返回nil
,对于std::string
不好表示这样的空,所以使用了OptionalString
来处理,一般用auto
接收即可。
要注意的是OptionalString
不支持operator<<
,要通过.value()
函数,获取其内部包含的std::string
即可。
示例:
cpp
#include <iostream>
#include <sw/redis++/redis++.h>
int main()
{
sw::redis::Redis redis("tcp://127.0.0.1:6379");
redis.set("key1", "111");
redis.set("key2", "222");
auto ret1 = redis.get("key1");
std::cout << ret1.value() << std::endl;
auto ret2 = redis.get("key2");
std::cout << ret2.value() << std::endl;
auto ret3 = redis.get("key3");
std::cout << ret3.value() << std::endl;
return 0;
}
输出结果:
cpp
111
222
terminate called after throwing an instance of 'std::bad_optional_access'
what(): bad optional access
Aborted (core dumped)
此处由于没有插入key3
,所以get
时得到的值为nil
,最后输出时抛出了异常。
如果要处理这个情况,那要用到OptionalString
的另一个特性。OptionalString
可以隐式转化为bool
类型,如果元素有效则为true
,元素无效则为false
。
输出之前,只需要判断一下返回值是否为true
即可:
cpp
int main()
{
sw::redis::Redis redis("tcp://127.0.0.1:6379");
redis.set("key1", "111");
redis.set("key2", "222");
auto ret1 = redis.get("key1");
if (ret1)
std::cout << ret1.value() << std::endl;
auto ret2 = redis.get("key2");
if (ret2)
std::cout << ret2.value() << std::endl;
auto ret3 = redis.get("key3");
if (ret3)
std::cout << ret3.value() << std::endl;
return 0;
}
exists
exists
用于检测key
是否存在,函数声明如下:
cpp
long long exists(const StringView &key);
这个很简单,就是判断一个key
是否存在。
但是其为什么要以long long
作为返回值?其实exists
还有另一个函数重载:
cpp
template <typename T>
long long exists(std::initializer_list<T> il);
其接收一个初始化列表,也就是可以同时接收多个key
,此时有多少个key
存在,就返回多少。
示例:
cpp
#include <iostream>
#include <sw/redis++/redis++.h>
int main()
{
sw::redis::Redis redis("tcp://127.0.0.1:6379");
redis.set("key1", "111");
redis.set("key2", "222");
std::cout << redis.exists("key1") << std::endl;
std::cout << redis.exists({"key1", "key2", "key3"}) << std::endl;
return 0;
}
输出结果:
cpp
1
2
此处要注意,输入的是一个初始化列表(C++11),需要用{}
把所有参数先包起来,构成一个初始化列表。
del
del
用于删除key
,函数声明如下:
cpp
long long del(const StringView &key);
template <typename T>
long long del(std::initializer_list<T> il);
和exists
一样,支持删除一个或多个key
,删除多个时要通过初始化列表。
flushall
flushall
用于清空整个redis
,函数声明如下:
cpp
void flushall(bool async = false);
该接口会清空redis
内的所有数据,开发环境慎用,一般而言参数直接用默认的false
即可。
keys
keys
用于搜索符合条件的key
,keys
函数声明如下:
cpp
template <typename Output>
void keys(const StringView &pattern, Output output);
keys
用于搜索所有符合条件的key
,此处第一个参数pattern
是匹配模式,output
是一个插入迭代器,用于获取符合条件的key
,元素类型是字符串,std::string
或者StringView
。
cpp
int main()
{
sw::redis::Redis redis("tcp://127.0.0.1:6379");
redis.flushall();
redis.set("key1", "111");
redis.set("key2", "222");
redis.set("key3", "222");
redis.set("key4", "222");
std::vector<std::string> result;
auto it = std::back_inserter(result);
redis.keys("*", it);
for (auto& ret : result)
std::cout << ret << std::endl;
return 0;
}
输出结果:
cpp
key3
key2
key1
key4
此处的插入迭代器,是一种专门用于将元素插入到容器中的迭代器,主要有以下三种:
std::front_insert_iterator
:在容器头部插入std::back_insert_iterator
:在容器尾部插入std::insert_iteraotr
:在容器任意位置插入
这种迭代器,++
和--
都是无效的,核心是operator=
。
比如模拟实现一个vector
的插入迭代器:
cpp
template <typename T>
class InsertIterator {
public:
// 构造函数,保存容器的引用和插入位置的迭代器
InsertIterator(std::vector<T>& vec, typename std::vector<T>::iterator pos)
: vec_(vec), pos_(pos) {}
// 重载赋值操作符,实现插入逻辑
InsertIterator& operator=(const T& value) {
pos_ = vec_.insert(pos_, value);
return *this;
}
// 重载前置和后置增加操作符,因为插入操作不改变迭代器位置
InsertIterator& operator++() { return *this; }
InsertIterator& operator++(int) { return *this; }
// 重载解引用操作符,以便可以链式调用
InsertIterator& operator*() { return *this; }
private:
std::vector<T>& vec_;
typename std::vector<T>::iterator pos_;
};
在operator=
内部,调用vector.insert
接口,完成元素插入容器的操作,此时pos
也会自动指向新的位置。
这种迭代器,可以完成插入与容器的解耦合,redis
返回的结果集,可以不用重载各种set
、vector
等容器,而是使用统一的插入迭代器完成。
ttl
ttl
用于获取key
的剩余超时时间,函数声明如下:
cpp
long long ttl(const StringView &key);
这个函数结合下一个接口一起展示。
expire
expire
用于设置超时时间,函数声明如下:
cpp
bool expire(const StringView &key, long long timeout);
bool expire(const StringView &key, const std::chrono::seconds &timeout);
expire
设置超时时间时,有两种模式,第一个重载的timeout
是long long
类型,以秒为单位,也可以使用std::chrono::seconds
来控制。
返回值与redis
原生的ttl
完全一致,如果key
不存在返回-2
,如果key
存在但是没有超时时间返回-1
。
示例:
cpp
#include <iostream>
#include <chrono>
#include <sw/redis++/redis++.h>
int main()
{
sw::redis::Redis redis("tcp://127.0.0.1:6379");
redis.flushall();
redis.set("key1", "111");
redis.set("key2", "222");
redis.set("key3", "222");
redis.expire("key1", 10);
redis.expire("key2", std::chrono::seconds(1));
std::cout << redis.ttl("key1") << std::endl;
std::cout << redis.ttl("key2") << std::endl;
std::cout << redis.ttl("key3") << std::endl;
std::cout << redis.ttl("key4") << std::endl;
return 0;
}
输出结果:
cpp
10
1
-1
-2
type
type
用于获取key
对于的value
的类型,函数声明如下:
cpp
std::string type(const StringView &key);
其返回值是标准的std::string
。