1、概述php
(能够直接略过此段)redis 最近作为 nosql 数据服务应用愈来愈普遍,其相对于 memcached 的最大优势是提供了更加丰富的数据结构,因此应用场景就更为普遍。redis 的出现可谓是广大网络应用开发者的福音,同时有大量的开源人员贡献了客户端代码,象针对 java 语言的 jedis,php 语言的 phpredis/predis 等,这些语言的 redis 库既丰富又好用,而对 C/C++ 程序员彷佛就没那么幸运了,官方提供了 C 版的 hiredis 做为客户端库,不少爱好者都是基于 hiredis 进行二次封装和开发造成了 C++ 客户端库,但这些库(包括官方的 hiredis)大都使用麻烦,给使用者形成了许多出错的机会。一直想开发一个更易用的接口型的 C++ 版 redis 客户端库(注:官方提供的库基本属于协议型的,这意味着使用者须要花费不少精力去填充各个协议字段同时还得要分析服务器可能返回的不一样的结果类型),但每 当看到 redis 那 150 多个客户端命令时便心生退缩,由于要给每一个命令提供一个方便易用的 C++ 函数接口,则意味着很是巨大的开发工做量。java
在后来的屡次项目开发中被官方的 hiredis 库多次摧残后,终于忍受不了,决定从新开发一套全新的 redis 客户端 API,该库不只要实现这 150 多个客户端命令,同时须要提供方便灵活的链接池及链接池集群管理功能(幸运的是在 acl 库中已经具有了通用的网络链接池及链接池集群管理模块),另外,根据以前的实践,有可能提供的函数接口要远大于这 150 多个,缘由是针对同一个命令可能会由于不一样的参数类型场景提供多个函数接口(最终的结果是提供了3,4百个函数 API);在仔细研究了 redis 的通讯协议后便着手开始进行设计开发了(redis 的协议设计仍是很是简单实用的,即能支持二进制,同时又便于手工调试)。在开发过程当中大量参考了 http://redisdoc.com 网站上的中文在线翻译版(很是感谢 黄键宏 同窗的辛勤工做)。git
2、acl redis 库分类程序员
根据 redis 的数据结构类型,分红 12 个大类,每一个大类提供不一样的函数接口,这 12 个 C++ 类展现以下:github
一、redis_key:redis 全部数据类型的统一键操做类;由于 redis 的数据结构类型都是基本的 KEY-VALUE 类型,其中 VALUE 分为不一样的数据结构类型;redis
二、redis_connectioin:与 redis-server 链接相关的类;算法
三、redis_server:与 redis-server 服务管理相关的类;sql
四、redis_string:redis 中用来表示字符串的数据类型;数据库
五、redis_hash:redis 中用来表示哈希表的数据类型;每个数据对象由 “KEY-域值对集合” 组成,即一个 KEY 对应多个“域值对”,每一个“域值对”由一个字段名与字段值组成;服务器
六、redis_list:redis 中用来表示列表的数据类型;
七、redis_set:redis 中用来表示集合的数据类型;
八、redis_zset:redis 中用来表示有序集合的数据类型;
九、redis_pubsub:redis 中用来表示“发布-订阅”的数据类型;
十、redis_hyperloglog:redis 中用来表示 hyperloglog 基数估值算法的数据类型;
十一、redis_script:redis 中用来与 lua 脚本进行转换交互的数据类型;
十二、redis_transaction:redis 中用以事务方式执行多条 redis 命令的数据类型(注:该事务处理方式与数据库的事务有很大不一样,redis 中的事务处理过程没有数据库中的事务回滚机制,仅能保证其中的多条命令都被执行或都不被执行);
除了以上对应于官方 redis 命令的 12 个类别外,在 acl 库中还提供了另外几个类:
1三、redis_command:以上 12 个类的基类;
1四、redis_client:redis 客户端网络链接类;
1五、redis_result:redis 命令结果类;
1六、redis_pool:针对以上全部命令支持链接池方式;
1七、redis_manager:针对以上全部命令容许与多个 redis-server 服务创建链接池集群(即与每一个 redis-server 创建一个链接池)。
3、acl redis 使用举例
1)、下面是一个使用 acl 框架中 redis 客户端库的简单例子:
/** * @param conn {acl::redis_client&} redis 链接对象 * @return {bool} 操做过程是否成功 */ bool test_redis_string(acl::redis_client& conn, const char* key) { // 建立 redis string 类型的命令操做类对象,同时将链接类对象与操做类 // 对象进行绑定 acl::redis_string string_operation(&conn); const char* value = "test_value"; // 添加 K-V 值至 redis-server 中 if (string_operation.set(key, value) == false) { const acl::redis_result* res = string_operation.get_result(); printf("set key: %s error: %s\r\n", key, res ? res->get_error() : "unknown error"); return false; } printf("set key: %s ok!\r\n", key); // 须要重置链接对象的状态,或直接调用 conn.reset() 也可 string_operation.reset(); // 从 redis-server 中取得对应 key 的值 acl::string buf; if (string_operation.get(key, buf) == false) { const acl::redis_result* res = string_operation.get_result(); printf("get key: %s error: %s\r\n", key, res ? res->get_error() : "unknown error"); return false; } printf("get key: %s ok, value: %s\r\n", key, buf.c_str()); // 探测给定 key 是否存在于 redis-server 中,须要建立 redis 的 key // 类对象,同时将 redis 链接对象与之绑定 acl::redis_key key_operation; conn.reset(); // 重置链接状态 key_operation.set_client(conn); // 将链接对象与操做对象进行绑定 if (key_operation.exists(key) == false) { if (conn.eof()) { printf("disconnected from redis-server\r\n"); return false; } printf("key: %s not exists\r\n", key); } else printf("key: %s exists\r\n", key); // 删除指定 key 的字符串类对象 conn.reset(); // 先重置链接对象状态 if (key_operation.del(key, NULL) < 0) { printf("del key: %s error\r\n", key); return false; } else printf("del key: %s ok\r\n", key); return true; } /** * @param redis_addr {const char*} redis-server 服务器地址, * 格式为:ip:port,如:127.0.0.1:6379 * @param conn_timeout {int} 链接 redis-server 的超时时间(秒) * @param rw_timeout {int} 与 redis-server 进行通讯的 IO 超时时间(秒) */ bool test_redis(const char* redis_addr, int conn_timeout, int rw_timeout) { // 建立 redis 客户端网络链接类对象 acl::redis_client conn(redis_addr, conn_timeout, rw_timeout); const char* key = "test_key"; return test_redis_string(conn, key); }
上面的简单例子的操做过程是:在 redis-server 中添加字符串类型数据 --> 从 redis-server 中获取指定的字符串数据 --> 判断指定指定 key 的对象在 redis-server 上是否存在 ---> 从 redis-server 中删除指定 key 的数据对象(即该例中的字符串对象)。经过以上简单示例,使用者须要注意如下几点:
a)、acl 中的 redis 库的设计中 redis 链接类对象与命令操做类对象是分离的,12 个 redis 命令操做类对应 acl redis 库中相应的 12 个命令操做类;
b)、在使用 redis 命令操做类时须要先将 redis 链接类对象与命令操做类对象进行绑定(以便于操做类内部能够利链接类中的网络链接、协议组包以及协议解析等方法);
c)、在重复使用一个 redis 链接类对象时,须要首先重置该链接类对象的状态(即调用:acl::redis_client::reset()),这样主要是为了释放上一次命令操做过程的中间内存资源;
d)、一个 redis 链接类对象能够被多个命令类操做类对象使用(使用前需先绑定一次);
e)、将 redis 链接对象与命令操做对象绑定有两种方式:能够在构造函数中传入非空 redis 链接对象,或调用操做对象的 set_client 方法进行绑定。
2)、对上面的例子稍加修改,使之可以支持链接池方式,示例代码以下:
/** * @param conn {acl::redis_client&} redis 链接对象 * @return {bool} 操做过程是否成功 */ bool test_redis_string(acl::redis_client& conn, const char* key) { ...... // 代码与上述代码相同,省略 return true; } // 子线程处理类 class test_thread : public acl::thread { public: test_thread(acl::redis_pool& pool) : pool_(pool) {} ~test_thread() {} protected: // 基类(acl::thread)纯虚函数 virtual void* run() { acl::string key; // 给每一个线程一个本身的 key,以便以测试,其中 thread_id() // 函数是基类 acl::thread 的方法,用来获取线程惟一 ID 号 key.format("test_key: %lu", thread_id()); acl::redis_client* conn; for (int i = 0; i < 1000; i++) { // 从 redis 客户端链接池中获取一个 redis 链接对象 conn = (acl::redis_client*) pool_.peek(); if (conn == NULL) { printf("peek redis connection error\r\n"); break; } // 进行 redis 客户端命令操做过程 if (test_redis_string(*conn) == false) { printf("redis operation error\r\n"); break; } } return NULL; } private: acl::redis_pool& pool_; }; void test_redis_pool(const char* redis_addr, int max_threads, int conn_timeout, int rw_timeout) { // 建立 redis 链接池对象 acl::redis_pool pool(redis_addr, max_threads); // 设置链接 redis 的超时时间及 IO 超时时间,单位都是秒 pool.set_timeout(conn_timeout, rw_timeout); // 建立一组子线程 std::vector<test_thread*> threads; for (int i = 0; i < max_threads; i++) { test_thread* thread = new test_thread(pool); threads.push_back(thread); thread->set_detachable(false); thread->start(); } // 等待全部子线程正常退出 std::vector<test_thread*>::iterator it = threads.begin(); for (; it != threads.end(); ++it) { (*it)->wait(); delete (*it); } }
除了建立线程及 redis 链接池外,上面的例子与示例 1) 的代码与功能无异。
3)、下面对上面的示例2)稍做修改,使之能够支持 redis 集群链接池的方式,示例代码以下:
/** * @param conn {acl::redis_client&} redis 链接对象 * @return {bool} 操做过程是否成功 */ bool test_redis_string(acl::redis_client& conn, const char* key) { ...... // 与上面示例代码相同,略去 return true; } // 子线程处理类 class test_thread : public acl::thread { public: test_thread(acl::redis_manager& manager) : manager_(manager) {} ~test_thread() {} protected: // 基类(acl::thread)纯虚函数 virtual void* run() { acl::string key; // 给每一个线程一个本身的 key,以便以测试,其中 thread_id() // 函数是基类 acl::thread 的方法,用来获取线程惟一 ID 号 key.format("test_key: %lu", thread_id()); acl::redis_pool* pool; acl::redis_client* conn; for (int i = 0; i < 1000; i++) { // 从链接池集群管理器中得到一个 redis-server 的链接池对象 pool = (acl::redis_pool*) manager_.peek(); if (pool == NULL) { printf("peek connection pool failed\r\n"); break; } // 从 redis 客户端链接池中获取一个 redis 链接对象 conn = (acl::redis_client*) pool_.peek(); if (conn == NULL) { printf("peek redis connection error\r\n"); break; } // 进行 redis 客户端命令操做过程 if (test_redis_string(*conn) == false) { printf("redis operation error\r\n"); break; } } return NULL; } private: (acl::redis_manager& manager_; }; void test_redis_pool(const char* redis_addr, int max_threads, int conn_timeout, int rw_timeout) { // 建立 redis 集群链接池对象 acl::redis_manager manager(conn_timeout, rw_timeout); // 添加多个 redis-server 的服务器实例地址 manager.set("127.0.0.1:6379", max_threads); manager.set("127.0.0.1:6380", max_threads); manager.set("127.0.0.1:6381", max_threads); // 设置链接 redis 的超时时间及 IO 超时时间,单位都是秒 pool.set_timeout(conn_timeout, rw_timeout); // 建立一组子线程 std::vector<test_thread*> threads; for (int i = 0; i < max_threads; i++) { test_thread* thread = new test_thread(manager); threads.push_back(thread); thread->set_detachable(false); thread->start(); } // 等待全部子线程正常退出 std::vector<test_thread*>::iterator it = threads.begin(); for (; it != threads.end(); ++it) { (*it)->wait(); delete (*it); } }
该示例只修改了几处代码便支持了集群 redis 链接池方式,其处理过程是:建立集群链接池对象(能够添加多个 redis-server 服务地址) --> 从集群链接池对象中取得一个链接池对象 ---> 从该链接池对象中取得一个链接 ---> 该链接对象与 redis 操做类对象绑定后进行操做。
4、小结
以上介绍了 acl 框架中新增长的 redis 库的使用方法及处理过程,该库将复杂的协议及网络处理过程隐藏在实现内部,使用户使用起来感受象是在调用本的函数。在示例 2)、3) 中提到了 acl 线程的使用,有关 acl 库中更为详细地使用线程的文章参见:《使用 acl_cpp 库编写多线程程序》。
源码下载:
国内:http://git.oschina.net/zsxxsz/acl/tree/master
http://sourceforge.net/projects/acl/
svn:svn://svn.code.sf.net/p/acl/code/trunk
github:https://github.com/zhengshuxin/acl
BBS:http://www.acl-dev.com/
QQ群:242722074