C++封装Redis操作函数

1、在Linux上安装Redis

1.1、编译源码安装

安装Redis的方法有很多,我这里直接拿源码来编译安装,使用的Redis版本是6.2.1,大家可以自行去Redis官网下载对应的源码,这里给出一份已经下载好的Redis-6.2.1安装包 点击进入下载页面,下载完成之后,将代码上传到Linux服务器上,可以参考我这篇文章来搭建一个CentOS环境 bifang框架运行环境搭建入门指南
解压出来之后如图所示,大家可以看一下README.md,里面有说明教程,这里就不对其进行分析了,直接开始编译安装
在这里插入图片描述

输入 make 编译源代码
输入 make install 将Redis安装到默认目录(/usr/local/bin)里去
输入 cp redis.conf /etc/ 将Redis的配置文件复制到/etc目录下

1.2、配置redis.service

输入 vim /usr/lib/systemd/system/redis.service,将下面的内容复制进去

# example systemd service unit file for redis-server
#
# In order to use this as a template for providing a redis service in your
# environment, _at the very least_ make sure to adapt the redis configuration
# file you intend to use as needed (make sure to set "supervised systemd"), and
# to set sane TimeoutStartSec and TimeoutStopSec property values in the unit's
# "[Service]" section to fit your needs.
#
# Some properties, such as User= and Group=, are highly desirable for virtually
# all deployments of redis, but cannot be provided in a manner that fits all
# expectable environments. Some of these properties have been commented out in
# this example service unit file, but you are highly encouraged to set them to
# fit your needs.
#
# Please refer to systemd.unit(5), systemd.service(5), and systemd.exec(5) for
# more information.

[Unit]
Description=Redis
Documentation=https://redis.io/documentation
#Before=your_application.service another_example_application.service
#AssertPathExists=/var/lib/redis
Wants=network.target
After=network.target

[Service]
#ExecStart=/usr/local/bin/redis-server --supervised systemd --daemonize no
## Alternatively, have redis-server load a configuration file:
ExecStart=/usr/local/bin/redis-server /etc/redis.conf
LimitNOFILE=10032
NoNewPrivileges=yes
#OOMScoreAdjust=-900
#PrivateTmp=yes
#Type=notify
TimeoutStartSec=infinity
TimeoutStopSec=infinity
UMask=0077
#User=redis
#Group=redis
#WorkingDirectory=/var/lib/redis

[Install]
WantedBy=multi-user.target

之后就可以通过service来控制Redis了
输入 service redis start 启动Redis
输入 service redis status 查看Redis状态,如下图所示,就是Redis启动成功了
在这里插入图片描述
再输入redis-cli,登录Redis控制台,输入keys *,出现如下图所示的情况,证明Redis可用
在这里插入图片描述

1.3、安装Redis的c库hiredis

进入Redis源码目录,输入cd deps/hiredis/,进入hiredis目录
输入make,编译代码
输入make install,安装hiredis库,这样Redis的整个安装就结束了

2、Redis常用API

要想封装出一个易用的Redis库,就需要先知道官方究竟开放了哪些接口给我们使用。由于前面是用源码安装的,所以我们要看API可以直接进去源码里面的deps/hiredis/目录下就有了,大部分有用的信息都在hiredis.h文件里面,大家可以先把这个文件大致浏览一遍,内容比起MySQL的要少很多,接下来会列举我们用到的几个重要的结构体和API

2.1、结构体

  • redisContext相当于一个控制器的作用,调用初始化函数之后就能得到他们,在使用各种API时经常得把他们作为参数传递进去
typedef struct redisContext {
    const redisContextFuncs *funcs;   /* Function table */

    int err; /* Error flags, 0 when there is no error */
    char errstr[128]; /* String representation of error when applicable */
    redisFD fd;
    int flags;
    char *obuf; /* Write buffer */
    redisReader *reader; /* Protocol reader */

    enum redisConnectionType connection_type;
    struct timeval *connect_timeout;
    struct timeval *command_timeout;

    struct {
        char *host;
        char *source_addr;
        int port;
    } tcp;

    struct {
        char *path;
    } unix_sock;

    /* For non-blocking connect */
    struct sockadr *saddr;
    size_t addrlen;

    /* Optional data and corresponding destructor users can use to provide
     * context to a given redisContext.  Not used by hiredis. */
    void *privdata;
    void (*free_privdata)(void *);

    /* Internal context pointer presently used by hiredis to manage
     * SSL connections. */
    void *privctx;

    /* An optional RESP3 PUSH handler */
    redisPushFn *push_cb;
} redisContext;
  • redisReply是使用Redis命令之后的返回值,里面有查询的结果以及错误信息,需要了解里面各个变量的意义,大家自行看官方每个变量的注释即可
/* This is the reply object returned by redisCommand() */
typedef struct redisReply {
    int type; /* REDIS_REPLY_* */
    long long integer; /* The integer when type is REDIS_REPLY_INTEGER */
    double dval; /* The double when type is REDIS_REPLY_DOUBLE */
    size_t len; /* Length of string */
    char *str; /* Used for REDIS_REPLY_ERROR, REDIS_REPLY_STRING
                  REDIS_REPLY_VERB, and REDIS_REPLY_DOUBLE (in additional to dval). */
    char vtype[4]; /* Used for REDIS_REPLY_VERB, contains the null
                      terminated 3 character content type, such as "txt". */
    size_t elements; /* number of elements, for REDIS_REPLY_ARRAY */
    struct redisReply **element; /* elements vector for REDIS_REPLY_ARRAY */
} redisReply;

2.2、API

// 连接Redis,使用后面那个,可以设置超时时间
redisContext *redisConnect(const char *ip, int port);
redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv);
// 关闭Redis并释放对应的内存
void redisFree(redisContext *c);
// 重新连接Redis(需要注意的是重新连接之后需要重新输入密码才可以使用Redis)
int redisReconnect(redisContext *c);
// 设置Redis命令超时时间
int redisSetTimeout(redisContext *c, const struct timeval tv);
// 向Redis发出命令。在阻塞上下文中,它与调用redisAppendCommand,然后调用redisGetReply相同。
// 如果执行请求时出错,函数将返回NULL,否则返回应答。在非阻塞上下文中,它与仅调用redisAppendCommand相同,并且始终返回NULL。
void *redisvCommand(redisContext *c, const char *format, va_list ap);
void *redisCommand(redisContext *c, const char *format, ...);
void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);
// 释放查询结果redisReply的内存
void freeReplyObject(void *reply);
// 将命令写入输出缓冲区。在阻塞模式下使用这些函数可以获得命令管道
int redisvAppendCommand(redisContext *c, const char *format, va_list ap);
int redisAppendCommand(redisContext *c, const char *format, ...);
int redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);

3、Redis封装细节

3.1、和之前封装MySQL进行对比

我们这里不实现Redis的事务,而且也没有MySQL那些预处理的功能,所以比起之前的MySQL封装会简单很多,只需要两个类Redis和RedisManager即可

3.2、封装两个常用的类

/**
 * brief: Redis类
 */
class Redis;

/**
 * brief: Redis管理类
 */
class RedisManager;

3.2.1、Redis

该类要要提供的功能有:初始化Redis、连接Redis、执行Redis命令,后者有可以细化为很多部分,不如 ping、del、keys等操作,具体Redis支持的功能可以上百度去查,我这里大部分的方法都是模仿java的jredis提供的接口来实现的,接下来一一举例

  • 连接数据库的方法实现如下,需要对返回值进行判断,在使用auth指令登录后,返回值中的str成员必须是“OK”才算登录成功了
bool Redis::reconnect()
{
    if (!m_context)
        return false;
    if (redisReconnect(m_context.get()))
        return false;

    redisSetTimeout(m_context.get(), m_cmdTimeout);
    if (!m_passwd.empty())
    {
        redisReply* r = (redisReply*)redisCommand(m_context.get(), "auth %s", m_passwd.c_str());
        if (!r)
        {
            std::cout << "auth error(" << m_host << ":" << m_port << ")" << std::endl;
            return false;
        }

        if (r->type != REDIS_REPLY_STATUS)
        {
            std::cout << "auth reply type error:" << r->type << "(" << m_host << ":" << m_port << ")" << std::endl;
            return false;
        }

        if (!r->str)
        {
            std::cout << "auth reply str error:NULL(" << m_host << ":" << m_port << ")" << std::endl;
            return false;
        }

        if (strncasecmp(r->str, "OK", 2))
        {
            std::cout << "auth error:" << r->str << "(" << m_host << ":" << m_port << ")" << std::endl;
            return false;
        }
    }
    return true;
}

bool Redis::connect()
{
    redisContext* conn = redisConnectWithTimeout(m_host.c_str(), m_port, m_connectTimeout);
    if (conn)
    {
        m_context.reset(conn, redisFree);
        redisSetTimeout(m_context.get(), m_cmdTimeout);

        if (!m_passwd.empty())
        {
            redisReply* r = (redisReply*)redisCommand(conn, "auth %s", m_passwd.c_str());
            if (!r)
            {
                std::cout << "auth error(" << m_host << ":" << m_port << ")" << std::endl;
                return false;
            }

            if (r->type != REDIS_REPLY_STATUS)
            {
                std::cout << "auth reply type error:" << r->type << "(" << m_host << ":" << m_port << ")" << std::endl;
                return false;
            }

            if (!r->str)
            {
                std::cout << "auth reply str error:NULL(" << m_host << ":" << m_port << ")" << std::endl;
                return false;
            }

            if (strncasecmp(r->str, "OK", 2))
            {
                std::cout << "auth error:" << r->str << "(" << m_host << ":" << m_port << ")" << std::endl;
                return false;
            }
        }
        return true;
    }
    return false;
}
  • 连接上之后便可开始使用Redis了,利用官方的redisvCommand这些接口封装成一个好几个更易用的方法,如下所示,使用了智能指针去托管返回信息,这样就不需要每次都自己去释放返回值了
ReplyPtr Redis::cmd(const char* format, ...)
{
    va_list ap;
    va_start(ap, format);
    redisReply* r = (redisReply*)redisvCommand(m_context.get(), format, ap);
    if (!r)
    {
        std::cout << "redisvCommand error:(" << format << ")(" << m_host << ":" << m_port << ")" << std::endl;
        va_end(ap);
        return nullptr;
    }
    ReplyPtr ret(r, freeReplyObject);
    if (r->type == REDIS_REPLY_ERROR)
    {
        std::cout << "redisvCommand error:(" << format << ")(" << m_host << ":" << m_port
            << ")" + (r->str ? ", errstr:" + std::string(r->str) : "") << std::endl;
        va_end(ap);
        return nullptr;
    }
    va_end(ap);
    return ret;
}

ReplyPtr Redis::cmd(const std::vector<std::string>& argv)
{
    std::vector<const char*> v;
    std::vector<size_t> l;
    for (auto& i : argv)
    {
        v.push_back(i.c_str());
        l.push_back(i.size());
    }

    redisReply* r = (redisReply*)redisCommandArgv(m_context.get(), argv.size(), &v[0], &l[0]);
    if (!r)
    {
        std::cout << "redisCommandArgv error:(" << m_host << ":" << m_port << ")" << std::endl;
        return nullptr;
    }
    ReplyPtr ret(r, freeReplyObject);
    if (r->type == REDIS_REPLY_ERROR)
    {
        std::cout << "redisCommandArgv error:(" << m_host << ":" << m_port
            << ")" + (r->str ? ", errstr:" + std::string(r->str) : "") << std::endl;
        return nullptr;
    }
    return ret;
}

bool Redis::appendCmd(const char* format, ...)
{
    va_list ap;
    va_start(ap, format);
    int ret = redisvAppendCommand(m_context.get(), format, ap);
    va_end(ap);
    return !ret;
}

bool Redis::appendCmd(const std::vector<std::string>& argv)
{
    std::vector<const char*> v;
    std::vector<size_t> l;
    for (auto& i : argv)
    {
        v.push_back(i.c_str());
        l.push_back(i.size());
    }
    return !redisAppendCommandArgv(m_context.get(), argv.size(), &v[0], &l[0]);
}
  • 基于上面封装的cmd这个方法,我们就可以逐步细化来吧Redis那些操作全部封装成类方法了,因为命令Redis的指令比较多,最快的方法就是去看一下java的jredis代码,仿照它的接口自己来一个一个实现,要注意不同操作所带来的返回值的类型是不一致的,要对其进行判断,这里贴几个比较典型的方法,就不全部贴出来了
int64_t Redis::del(std::unordered_set<std::string> keys)
{
    std::string cmd_str = "DEL";
    for (auto it = keys.begin(); it != keys.end(); it++)
        cmd_str += " " + *it;
    ReplyPtr reply = cmd(cmd_str.c_str());
    if (!reply || reply->type != REDIS_REPLY_INTEGER)
        return -1;
    return reply->integer;
}

std::unordered_set<std::string> Redis::keys(const std::string& pattern)
{
    std::unordered_set<std::string> s;
    ReplyPtr reply = cmd("KEYS %s", pattern.c_str());
    if (!reply || reply->type != REDIS_REPLY_ARRAY)
        return std::move(s);
    for (size_t i = 0; i < reply->elements; i++)
        s.insert(reply->element[i]->str);
    return std::move(s);
}

int64_t Redis::ttl(const std::string& key)
{
    ReplyPtr reply = cmd("TTL %s", key.c_str());
    if (!reply || reply->type != REDIS_REPLY_INTEGER)
        return -3;
    return reply->integer;
}

bool Redis::exists(const std::string& key)
{
    ReplyPtr reply = cmd("EXISTS %s", key.c_str());
    if (!reply || reply->type != REDIS_REPLY_INTEGER)
        return false;
    return (bool)reply->integer;
}

bool Redis::expire(const std::string& key, int64_t seconds)
{
    ReplyPtr reply = cmd("EXPIRE %s %ld", key.c_str(), seconds);
    if (!reply || reply->type != REDIS_REPLY_INTEGER)
        return false;
    return (bool)reply->integer;
}

bool Redis::persist(const std::string& key)
{
    ReplyPtr reply = cmd("PERSIST %s", key.c_str());
    if (!reply || reply->type != REDIS_REPLY_INTEGER)
        return false;
    return (bool)reply->integer;
}

bool Redis::set(const std::string& key, const std::string& value)
{
    return !!cmd("SET %s %s", key.c_str(), value.c_str());
}

bool Redis::hset(const std::string& key, const std::string& field, const std::string& value)
{
    return !!cmd("HSET %s %s %s", key.c_str(), field.c_str(), value.c_str());
}

3.2.2、RedisManager

该类是一个管理类,用于统一管理所有Redis连接,这样可以很方便地结合配置文件来使用 Redis,而且也可以在每次分配连接时都检查是否需要重新连接数据库,防止服务器因为超时把连接断开,并且提供了回收机制,当Redis池的数据小于我们设置的容量时,每一个被释放的连接都可以重新回到数据池里面循环使用,是借用智能指针来实现这个功能的,大家有兴趣可以看一看具体实现的做法

/**
 * brief: Redis管理类
 */
class RedisManager
{
public:
    typedef Mutex MutexType;

    struct RedisConf
    {
        std::string host;
        int port;
        std::string passwd;
        uint32_t connect_timeout = 100;
        uint32_t cmd_timeout = 100;
        uint32_t poolSize = 10;
    };

    ~RedisManager();

    void add(const std::string& name, const std::string& host,
        int port, const std::string& passwd, uint32_t connectTimeout,
        uint32_t cmdTimeout, uint32_t poolSize = 10);

    Redis::ptr get(const std::string& name);

    void checkConnection(int sec = 30);

private:
    void freeRedis(const std::string& name, Redis* r);

private:
    MutexType m_mutex;
    std::unordered_map<std::string, std::list<Redis*> > m_connections;
    std::unordered_map<std::string, RedisConf> m_sqlDefines;
};

4、总结并附上本文源代码

Redis官方给出的API使用起来非常方便,比MySQL简单很多,毕竟作为菲关系型数据库确实功能上少很多用起来也简单很多,有兴趣的同学建议还是去看一下Redis的源码,这样对自己的能力应该会有很大的提升。
最后附上一份源代码,大家可以下载下去调试使用看看,有什么错误的地方也欢迎大家指出
redis c++封装.zip

  • 22
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论
要在C++中构建Redis,你可以使用redis_plus_plus库。这是一个基于hiredisC++客户端库,兼容C++ 17、C++ 14和C++ 11。它提供了许多特性,包括大多数Redis命令、连接池、Redis脚本、线程安全性、发布/订阅、管道、事务、Redis集群、Redis Sentinel、类似STL的接口、通用命令接口、Redis Stream、Redlock、Redis ACL以及TLS/SSL支持。\[2\] 要在Linux下编译安装redis_plus_plus,你需要按照以下步骤进行操作: 1. 创建一个临时目录。 2. 在该目录下创建一个新文件redis.h,并将以下代码复制到文件中: ```cpp #ifndef _REDIS_H_ #define _REDIS_H_ #include <iostream> #include <string.h> #include <string> #include <stdio.h> #include <hiredis/hiredis.h> class Redis { public: Redis(){} ~Redis() { this->_connect = NULL; this->_reply = NULL; } bool connect(std::string host, int port) { this->_connect = redisConnect(host.c_str(), port); if(this->_connect != NULL && this->_connect->err) { printf("connect error: %s\n", this->_connect->errstr); return 0; } return 1; } std::string get(std::string key) { this->_reply = (redisReply*)redisCommand(this->_connect, "GET %s", key.c_str()); std::string str = this->_reply->str; freeReplyObject(this->_reply); return str; } void set(std::string key, std::string value) { redisCommand(this->_connect, "SET %s %s", key.c_str(), value.c_str()); } private: redisContext* _connect; redisReply* _reply; }; #endif //_REDIS_H_ ``` 3. 使用适当的编译器编译你的代码,并链接hiredis库。例如,你可以使用以下命令编译: ``` g++ redis.cpp -o redis -L/usr/local/lib/ -lhiredis ``` 其中,redis.cpp是你的源代码文件,-o redis指定输出文件名为redis,-L/usr/local/lib/指定hiredis库的路径,-lhiredis指定链接hiredis库。\[1\] 这样,你就可以在C++中使用redis_plus_plus库来构建Redis了。 #### 引用[.reference_title] - *1* *3* [Redis基础,Linux下安装Redis和hredisC++调用RedisRedis中字符串设计](https://blog.csdn.net/qq_41854911/article/details/121600815)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [【C++redis client: redis_plus_plus](https://blog.csdn.net/yzf279533105/article/details/129697932)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

彼 方

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值