今天国泉分享在项目中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不是线程安全的,需要让他变成线程安全,这里我封装了一个线程安全队列concurrentQueue
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最大连接数,这也是同步方式的坏处,如果你有什么好办法,欢迎讨论。