redis连接池之同步
今天国泉分享在项目中redis同步连接池的设计思路,异步方式后面整理好后在上博。
连接池是什么?
国泉的理解是程序在初始化时就建立多个长连接对象,程序运行中循环利用,节省新建连接时带来的开销,充分利用多核优势,这样性能瓶颈就会在redis,而不是在我们这。
那怎么设计呢?国泉的想法是这样的
1、我们应该有一个redis操作类取名redisClient,他有连接、断开redis-server、操作(具体业务)的接口,他就是一个长连接对象,一个可以干活的工人。
2、接下来我们创建N个工人来服务,这样就需要一个容器来存放这N个对象,国泉的想法是用queue队列,优点是用的时候不需要去遍历,缺点是有指针变量的内存开销,我把它比喻成排好队等着去干活的工人。
3、到这里这伙人需要一个头,那就是一个包工头来给他们安排工作,取名redisPool,当一个请求过来,若他需要redis操作,他就从包工头这里要拿走一个(pop)工人,当他用完后将这个工人(push)还给包工头,如此反复。
有了上面的思路后,我们就可以先着手设计这两个类了,c++操作redis的库有很多,这里国泉用的是hiredis,下面我们先来设计redisClient,他能支持连接、断开、SET、DEL字符串操作redis的接口
1 #pragma once 2 3 #include <hiredis/hiredis.h> 4 #include <string> 5 6 7 class RedisClient 8 { 9 public: 10 RedisClient() 11 { 12 _port = 0; 13 _context = 0; 14 } 15 public: 16 bool Connect(const char *ip, int port) 17 { 18 _ip = ip; 19 _port = port; 20 _context = ::redisConnect(ip, port); 21 if (_context && _context->err) 22 return false; 23 24 return true; 25 } 26 void CloseConnect() 27 { 28 ::redisFree(_context); 29 } 30 public: 31 bool setString(const char * key, const char * value) 32 { 33 redisReply *_reply = (redisReply*)::redisCommand(_context, "set %s %s",key,value); 34 bool b = false; 35 if(_reply) 36 { 37 b = (_reply->type == REDIS_REPLY_STATUS && strcasecmp(_reply->str, "OK") == 0); 38 ::freeReplyObject(_reply); 39 _reply = NULL; 40 } 41 return b; 42 } 43 bool getString(const char * key, std::string & value) 44 { 45 redisReply *_reply = (redisReply*)::redisCommand(_context, "get %s",key); 46 bool b = false; 47 if(_reply && _reply->type == REDIS_REPLY_STRING) 48 { 49 value = _reply->str; 50 ::freeReplyObject(_reply); 51 _reply = NULL; 52 b = true; 53 } 54 return b; 55 } 56 57 private: 58 redisContext * _context; 59 std::string _ip; 60 int _port; 61 };
接下来我们的包工头类redisPool
由于std::queue不是线程安全的,需要让他变成线程安全,这里我封装了一个线程安全队列concurrentQueuehttps://www.cnblogs.com/GuoQuanLiu/articles/9498425.html
1 #include "concurrentQueue.h" 2 class RedisPool 3 { 4 public: 5 RedisPool(); 6 ~RedisPool(); 7 8 void setClientNum(int num) 9 {_client_num=num;} 10 11 bool start() 12 { 13 for(int i = 0;i < _client_num;i++) 14 { 15 RedisClientPtr ptr(new RedisClient()); 16 if(ptr->Connect(configInfo::getInstance().redis_ip(),configInfo::getInstance().redis_port())) 17 _clients.push(ptr); 18 } 19 20 return !_clients.empty(); 21 } 22 23 RedisClientPtr getNextClient() 24 { 25 if(_clients.empty()) 26 { 27 return RedisClientPtr(new RedisClient()); 28 } 29 30 RedisClientPtr ptr; 31 _clients.pop(ptr); 32 if(ptr == nullptr) 33 { 34 return RedisClientPtr(new RedisClient()); 35 } 36 return ptr; 37 } 38 39 void push(RedisClientPtr rc) 40 {_clients.push(rc);} 41 42 private: 43 int _client_num; 44 concurrentQueue<RedisClientPtr> _clients; 45 };
使用方式
#include "RedisPool" //创建一个全局或者单例包工头 RedisPool g_rdPool; //假设有一个登录协议,利用构造和析构获取和归还工人,最好写基类里,这样不会出错。 class login { public: login() { _ptr = nullptr; } ~login() { if(_ptr) g_rdPool.push(_ptr); } void action() { getRedis()->setString("20180818","GuoQuan"); std::string str; getRedis()->getString("20180818",str); //// } private: RedisClientPtr getRedis() { if(nullptr == _ptr) _ptr = g_rdPool.getNextClient(); return _ptr; } RedisClientPtr _ptr; } void main() {
g_rdPool.setClientNum(10);
g_rdPool.start();
login lg;
lg.action();
}
总结:国泉这样设计,一开始有一个明显的弊端,若并发量很高的时候,工人不够用的情况下会被创建很多出来,最坏的情况就是超出redis-server最大连接数,这也是同步方式的坏处,如果你有什么好办法,欢迎讨论。