以前为了不在游戏逻辑(对象属性)变更时修改数据库,就弄了个varchar字段来表示json,由服务器逻辑(读取到内存)去操作它。

但这对运维相当不友好,也不能做一些此Json数据里查询。

所以后面就用了下ssdb,然而就在前几天才了解到postgresql支持json了(其实早在两年前就行了吧···)

就这点差不多就可以算当作mongodb用了,不过还是不支持redis的高级数据结构。

于是我就想模拟(实现)下redis(的数据结构)。

就抽空看了下它的c api库:libpq,发现其请求-等待模型,在网络延迟高的时候,特别影响qps。所以我就写了一个异步客户端,并简易模拟了redis的kv,hash。

开8个链接到pg server,其速度比1个链接快5倍。 在我的测试中,每秒打到30k QPS

(目前不支持list,以及后期还要通过储存过程对现在的hash实现进行改造优化)

  1. #include <string>
  2. #include <list>
  3. #include <iostream>
  4. #include <unordered_map>
  5. #include <memory>
  6. #include <queue>
  7. #include <assert.h>
  8. #include <functional>
  9. #include <sstream>
  10. #include <chrono>
  11.  
  12. #include "fdset.h"
  13.  
  14. #include "libpq-events.h"
  15. #include "libpq-fe.h"
  16. #include "libpq/libpq-fs.h"
  17.  
  18. using namespace std;
  19.  
  20. class AsyncPGClient
  21. {
  22. public:
  23. /*TODO::传递错误信息*/
  24. typedef std::function<void(const PGresult*)> RESULT_CALLBACK;
  25. typedef std::function<void(bool value)> BOOL_RESULT_CALLBACK;
  26. typedef std::function<void(const string& value)> STRING_RESULT_CALLBACK;
  27. typedef std::function<void(const std::unordered_map<string, string>& value)> STRINGMAP_RESULT_CALLBACK;
  28.  
  29. AsyncPGClient() : mKVTableName("kv_data"), mHashTableName("hashmap_data")
  30. {
  31. mfdset = ox_fdset_new();
  32. }
  33.  
  34. ~AsyncPGClient()
  35. {
  36. for (auto& kv : mConnections)
  37. {
  38. PQfinish((*kv.second).pgconn);
  39. }
  40.  
  41. ox_fdset_delete(mfdset);
  42. mfdset = nullptr;
  43. }
  44.  
  45. void get(const string& key, const STRING_RESULT_CALLBACK& callback = nullptr)
  46. {
  47. mStringStream << "SELECT key, value FROM public." << mKVTableName << " where key = '" << key << "';";
  48.  
  49. postQuery(mStringStream.str(), [callback](const PGresult* result){
  50. if (callback != nullptr && result != nullptr)
  51. {
  52. if (PQntuples(result) == && PQnfields(result) == )
  53. {
  54. callback(PQgetvalue(result, , ));
  55. }
  56. }
  57. });
  58. }
  59.  
  60. void set(const string& key, const string& v, const BOOL_RESULT_CALLBACK& callback = nullptr)
  61. {
  62. mStringStream << "INSERT INTO public." << mKVTableName << "(key, value) VALUES('" << key << "', '" << v << "') ON CONFLICT(key) DO UPDATE SET value = EXCLUDED.value;";
  63.  
  64. postQuery(mStringStream.str(), [callback](const PGresult* result){
  65. if (callback != nullptr)
  66. {
  67. if (PQresultStatus(result) == PGRES_COMMAND_OK)
  68. {
  69. callback(true);
  70. }
  71. else
  72. {
  73. cout << PQresultErrorMessage(result);
  74. callback(false);
  75. }
  76. }
  77. });
  78. }
  79.  
  80. void hget(const string& hashname, const string& key, const STRING_RESULT_CALLBACK& callback = nullptr)
  81. {
  82. hmget(hashname, { key }, [callback](const std::unordered_map<string, string>& value){
  83. if (callback != nullptr && !value.empty())
  84. {
  85. callback((*value.begin()).second);
  86. }
  87. });
  88. }
  89.  
  90. void hmget(const string& hashname, const std::vector<string>& keys, const STRINGMAP_RESULT_CALLBACK& callback = nullptr)
  91. {
  92. mStringStream << "SELECT key, value FROM public." << mHashTableName << " where ";
  93. auto it = keys.begin();
  94. do
  95. {
  96. mStringStream << "key='" << (*it) << "'";
  97.  
  98. ++it;
  99. } while (it != keys.end() && &(mStringStream << " or ") != nullptr);
  100. mStringStream << ";";
  101.  
  102. postQuery(mStringStream.str(), [callback](const PGresult* result){
  103. if (callback != nullptr)
  104. {
  105. std::unordered_map<string, string> ret;
  106. if (PQresultStatus(result) == PGRES_TUPLES_OK)
  107. {
  108. int num = PQntuples(result);
  109. int fileds = PQnfields(result);
  110. if (fileds == )
  111. {
  112. for (int i = ; i < num; i++)
  113. {
  114. ret[PQgetvalue(result, i, )] = PQgetvalue(result, i, );
  115. }
  116. }
  117. }
  118.  
  119. callback(ret);
  120. }
  121. });
  122. }
  123.  
  124. void hset(const string& hashname, const string& key, const string& value, const BOOL_RESULT_CALLBACK& callback = nullptr)
  125. {
  126. mStringStream << "INSERT INTO public." << mHashTableName << "(hashname, key, value) VALUES('" << hashname << "', '" << key << "', '" << value
  127. << "') ON CONFLICT (hashname, key) DO UPDATE SET value = EXCLUDED.value;";
  128.  
  129. postQuery(mStringStream.str(), [callback](const PGresult* result){
  130. if (callback != nullptr)
  131. {
  132. callback(PQresultStatus(result) == PGRES_COMMAND_OK);
  133. }
  134. });
  135. }
  136.  
  137. void hgetall(const string& hashname, const STRINGMAP_RESULT_CALLBACK& callback = nullptr)
  138. {
  139. mStringStream << "SELECT key, value FROM public." << mHashTableName << " where hashname = '" << hashname << "';";
  140. postQuery(mStringStream.str(), [callback](const PGresult* result){
  141. if (callback != nullptr)
  142. {
  143. std::unordered_map<string, string> ret;
  144. if (PQresultStatus(result) == PGRES_TUPLES_OK)
  145. {
  146. int num = PQntuples(result);
  147. int fileds = PQnfields(result);
  148. if (fileds == )
  149. {
  150. for (int i = ; i < num; i++)
  151. {
  152. ret[PQgetvalue(result, i, )] = PQgetvalue(result, i, );
  153. }
  154. }
  155. }
  156.  
  157. callback(ret);
  158. }
  159. });
  160. }
  161.  
  162. void postQuery(const string&& query, const RESULT_CALLBACK& callback = nullptr)
  163. {
  164. mPendingQuery.push({ std::move(query), callback});
  165. mStringStream.str(std::string());
  166. mStringStream.clear();
  167. }
  168.  
  169. void postQuery(const string& query, const RESULT_CALLBACK& callback = nullptr)
  170. {
  171. mPendingQuery.push({ query, callback });
  172. mStringStream.str(std::string());
  173. mStringStream.clear();
  174. }
  175.  
  176. public:
  177. void poll(int millSecond)
  178. {
  179. ox_fdset_poll(mfdset, millSecond);
  180.  
  181. std::vector<int> closeFds;
  182.  
  183. for (auto& it : mConnections)
  184. {
  185. auto fd = it.first;
  186. auto connection = it.second;
  187. auto pgconn = connection->pgconn;
  188.  
  189. if (ox_fdset_check(mfdset, fd, ReadCheck))
  190. {
  191. if (PQconsumeInput(pgconn) > && PQisBusy(pgconn) == )
  192. {
  193. bool successGetResult = false;
  194.  
  195. while (true)
  196. {
  197. auto result = PQgetResult(pgconn);
  198. if (result != nullptr)
  199. {
  200. successGetResult = true;
  201. if (connection->callback != nullptr)
  202. {
  203. connection->callback(result);
  204. connection->callback = nullptr;
  205. }
  206. PQclear(result);
  207. }
  208. else
  209. {
  210. break;
  211. }
  212. }
  213.  
  214. if (successGetResult)
  215. {
  216. mIdleConnections.push_back(connection);
  217. }
  218. }
  219.  
  220. if (PQstatus(pgconn) == CONNECTION_BAD)
  221. {
  222. closeFds.push_back(fd);
  223. }
  224. }
  225.  
  226. if (ox_fdset_check(mfdset, fd, WriteCheck))
  227. {
  228. if (PQflush(pgconn) == )
  229. {
  230. //移除可写检测
  231. ox_fdset_del(mfdset, fd, WriteCheck);
  232. }
  233. }
  234. }
  235.  
  236. for (auto& v : closeFds)
  237. {
  238. removeConnection(v);
  239. }
  240. }
  241.  
  242. void trySendPendingQuery()
  243. {
  244. while (!mPendingQuery.empty() && !mIdleConnections.empty())
  245. {
  246. auto& query = mPendingQuery.front();
  247. auto& connection = mIdleConnections.front();
  248.  
  249. if (PQsendQuery(connection->pgconn, query.request.c_str()) == )
  250. {
  251. cout << PQerrorMessage(connection->pgconn) << endl;
  252. if (query.callback != nullptr)
  253. {
  254. query.callback(nullptr);
  255. }
  256. }
  257. else
  258. {
  259. ox_fdset_add(mfdset, PQsocket(connection->pgconn), WriteCheck);
  260. connection->callback = query.callback;
  261. }
  262.  
  263. mPendingQuery.pop();
  264. mIdleConnections.pop_front();
  265. }
  266. }
  267.  
  268. size_t pendingQueryNum() const
  269. {
  270. return mPendingQuery.size();
  271. }
  272.  
  273. size_t getWorkingQuery() const
  274. {
  275. return mConnections.size() - mIdleConnections.size();
  276. }
  277.  
  278. void createConnection( const char *pghost, const char *pgport,
  279. const char *pgoptions, const char *pgtty,
  280. const char *dbName, const char *login, const char *pwd,
  281. int num)
  282. {
  283. for (int i = ; i < num; i++)
  284. {
  285. auto pgconn = PQsetdbLogin(pghost, pgport, pgoptions, pgtty, dbName, login, pwd);
  286. if (PQstatus(pgconn) == CONNECTION_OK)
  287. {
  288. auto connection = std::make_shared<Connection>(pgconn, nullptr);
  289. mConnections[PQsocket(pgconn)] = connection;
  290. PQsetnonblocking(pgconn, );
  291. ox_fdset_add(mfdset, PQsocket(pgconn), ReadCheck);
  292. mIdleConnections.push_back(connection);
  293. }
  294. else
  295. {
  296. cout << PQerrorMessage(pgconn);
  297. PQfinish(pgconn);
  298. pgconn = nullptr;
  299. }
  300. }
  301.  
  302. if (!mConnections.empty())
  303. {
  304. sCreateTable((*mConnections.begin()).second->pgconn, mKVTableName, mHashTableName);
  305. }
  306. }
  307.  
  308. private:
  309. void removeConnection(int fd)
  310. {
  311. auto it = mConnections.find(fd);
  312. if (it != mConnections.end())
  313. {
  314. auto connection = (*it).second;
  315. for (auto it = mIdleConnections.begin(); it != mIdleConnections.end(); ++it)
  316. {
  317. if ((*it)->pgconn == connection->pgconn)
  318. {
  319. mIdleConnections.erase(it);
  320. break;
  321. }
  322. }
  323.  
  324. ox_fdset_del(mfdset, fd, ReadCheck | WriteCheck);
  325. PQfinish(connection->pgconn);
  326. mConnections.erase(fd);
  327. }
  328. }
  329.  
  330. private:
  331. static void sCreateTable(PGconn* conn, const string& kvTableName, const string& hashTableName)
  332. {
  333. {
  334. string query = "CREATE TABLE public.";
  335. query += kvTableName;
  336. query += "(key character varying NOT NULL, value json, CONSTRAINT key PRIMARY KEY(key))";
  337. PGresult* exeResult = PQexec(conn, query.c_str());
  338. auto status = PQresultStatus(exeResult);
  339. auto errorStr = PQresultErrorMessage(exeResult);
  340. PQclear(exeResult);
  341. }
  342.  
  343. {
  344. string query = "CREATE TABLE public.";
  345. query += hashTableName;
  346. query += "(hashname character varying, key character varying, value json, "
  347. "CONSTRAINT hk PRIMARY KEY (hashname, key))";
  348. PGresult* exeResult = PQexec(conn, query.c_str());
  349. auto status = PQresultStatus(exeResult);
  350. auto errorStr = PQresultErrorMessage(exeResult);
  351. PQclear(exeResult);
  352. }
  353. }
  354.  
  355. private:
  356. struct QueryAndCallback
  357. {
  358. std::string request;
  359. RESULT_CALLBACK callback;
  360. };
  361.  
  362. struct Connection
  363. {
  364. PGconn* pgconn;
  365. RESULT_CALLBACK callback;
  366.  
  367. Connection(PGconn* p, RESULT_CALLBACK c)
  368. {
  369. pgconn = p;
  370. callback = c;
  371. }
  372. };
  373.  
  374. const string mKVTableName;
  375. const string mHashTableName;
  376.  
  377. stringstream mStringStream;
  378. fdset_s* mfdset;
  379.  
  380. std::unordered_map<int, shared_ptr<Connection>> mConnections;
  381. std::list<shared_ptr<Connection>> mIdleConnections;
  382.  
  383. std::queue<QueryAndCallback> mPendingQuery;
  384.  
  385. /*TODO::监听wakeup支持*/
  386. /*TODO::考虑固定分配connection给某业务*/
  387.  
  388. /*TODO::编写储存过程,替换现有的hashtable模拟方式,如循环使用jsonb_set以及 select value->k1, value->k2 from ...*/
  389. /*TODO::编写储存过程,实现list*/
  390. };
  391.  
  392. int main()
  393. {
  394. using std::chrono::system_clock;
  395.  
  396. AsyncPGClient asyncClient;
  397. asyncClient.createConnection("192.168.12.1", "", nullptr, nullptr, "postgres", "postgres", "", );
  398. system_clock::time_point startTime = system_clock::now();
  399.  
  400. auto nowTime = time(NULL);
  401.  
  402. for (int i = ; i < ; i++)
  403. {
  404. if(false)
  405. {
  406. string test = "INSERT INTO public.kv_data(key, value) VALUES ('";
  407. test += std::to_string(nowTime*+i);
  408. test += "', '{\"hp\":100000}') ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value;";
  409.  
  410. asyncClient.postQuery(test);
  411. }
  412. else
  413. {
  414. asyncClient.postQuery("select * from public.kv_data where key='dd';");
  415. }
  416. }
  417.  
  418. asyncClient.postQuery("INSERT INTO public.kv_data(key, value) VALUES ('dodo5', '{\"hp\":100000}') "
  419. " ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value", [](const PGresult* result){
  420. cout << "fuck" << endl;
  421. });
  422.  
  423. asyncClient.get("dd", [](const string& value){
  424. cout << "get dd : " << value << endl;
  425. });
  426.  
  427. asyncClient.set("dd", "{\"hp\":456}", [](bool isOK){
  428. cout << "set dd : " << isOK << endl;
  429. });
  430.  
  431. asyncClient.hget("heros:dodo", "hp", [](const string& value){
  432. cout << "hget heros:dodo:" << value << endl;
  433. });
  434.  
  435. asyncClient.hset("heros:dodo", "hp", "{\"hp\":1}", [](bool isOK){
  436. cout << "hset heros:dodo:" << isOK << endl;
  437. });
  438.  
  439. asyncClient.hmget("heros:dodo", { "hp", "money" }, [](const unordered_map<string, string>& kvs){
  440. cout << "hmget:" << endl;
  441. for (auto& kv : kvs)
  442. {
  443. cout << kv.first << " : " << kv.second << endl;
  444. }
  445. });
  446.  
  447. asyncClient.hgetall("heros:dodo", [](const unordered_map<string, string>& kvs){
  448. cout << "hgetall:" << endl;
  449. for (auto& kv : kvs)
  450. {
  451. cout << kv.first << " : " << kv.second << endl;
  452. }
  453. });
  454.  
  455. while (true)
  456. {
  457. asyncClient.poll();
  458. asyncClient.trySendPendingQuery();
  459. if (asyncClient.pendingQueryNum() == && asyncClient.getWorkingQuery() == )
  460. {
  461. break;
  462. }
  463. }
  464.  
  465. auto elapsed = system_clock::now() - startTime;
  466. cout << "cost :" << chrono::duration<double>(elapsed).count() << "s" << endl;
  467. cout << "enter any key exit" << endl;
  468. cin.get();
  469. return ;
  470. }

代码地址:https://github.com/IronsDu/accumulation-dev/blob/master/examples/Pgedis.cpp

PostgreSQL异步客户端(并模拟redis 数据结构)的更多相关文章

  1. redis数据结构、持久化、缓存淘汰策略

    Redis 单线程高性能,它所有的数据都在内存中,所有的运算都是内存级别的运算,而且单线程避免了多线程的切换性能损耗问题.redis利用epoll来实现IO多路复用,将连接信息和事件放到队列中,依次放 ...

  2. Redis 数据结构与内存管理策略(上)

    Redis 数据结构与内存管理策略(上) 标签: Redis Redis数据结构 Redis内存管理策略 Redis数据类型 Redis类型映射 Redis 数据类型特点与使用场景 String.Li ...

  3. Redis学习系列六ZSet(有序列表)及Redis数据结构的过期

    一.简介 ZSet可以说是Redis中最有趣的数据结构了,因为他兼具了Hash集合和Set的双重特性,也是用的最多的,保证了value值的唯一性的同时,,同时又保证了高性能,最主要的是还可以给每个Va ...

  4. 5种Redis数据结构详解

    本文主要和大家分享 5种Redis数据结构详解,希望文中的案例和代码,能帮助到大家. 转载链接:https://www.php.cn/php-weizijiaocheng-388126.html 2. ...

  5. 深入Redis客户端(redis客户端属性、redis缓冲区、关闭redis客户端)

    深入Redis客户端(redis客户端属性.redis缓冲区.关闭redis客户端) Redis 数据库采用 I/O 多路复用技术实现文件事件处理器,服务器采用单线程单进程的方式来处理多个客户端发送过 ...

  6. Redis 数据结构使用场景

    转自http://get.ftqq.com/523.get 一.redis 数据结构使用场景 原来看过 redisbook 这本书,对 redis 的基本功能都已经熟悉了,从上周开始看 redis 的 ...

  7. Redis 数据结构与内存管理策略(下)

    Redis 数据结构与内存管理策略(下) 标签: Redis Redis数据结构 Redis内存管理策略 Redis数据类型 Redis类型映射 Redis 数据类型特点与使用场景 String.Li ...

  8. 第18章 Redis数据结构常用命令

    18-1 字符串的一些基本命令 18-1 :配置Spring关于Redis字符串的运行环境 <bean id="poolConfig" class="redis.c ...

  9. redis 模拟redis server接收信息

    一.实现说明 客户端使用jedis正常set值到redis服务器   2. 模拟服务器接收jedis发送的信息 二.jedis客户端代码 package com.ahd.redis; import r ...

随机推荐

  1. 杭电1010Tempter of the Bone

    Tempter of the Bone Problem Description The doggie found a bone in an ancient maze, which fascinated ...

  2. java复制File文件操作

    ==========================复制File操作========================= /**  *   * @param newPath要赋值的路径  * @para ...

  3. linux 的 scp 命令 可以 在 linux 之间复制 文件 和 目录

    转自:http://blog.csdn.net/snlying/article/details/6184102 Linux系统中scp命令的用法. scp就是secure copy的简写,用于在lin ...

  4. sql 分组后 组内排名

    语法:ROW_NUMBER() OVER(PARTITION BY COLUMN ORDER BY COLUMN) 简单的说row_number()从1开始,为每一条分组记录返回一个数字,这里的ROW ...

  5. putty 中文乱码解决方法

    解决putty.exe 中文乱码的问题 export NLS_LANG="AMERICAN_AMERICA.ZHS16GBK"

  6. atprogram.exe : Atmel Studio Command Line Interface

    C:\Program Files\Atmel\Atmel Studio 6.1\atbackend\atprogram.exe No command specified.Atmel Studio Co ...

  7. XHTML标签的嵌套规则--很基础很重要

    XHTML的标签有许多:div.ul.li.dl.dt.dd.h1~h6.p.a.addressa.span. strong……我们在运用这些标签搭建页面结构的时候,是可以将它们无限嵌套的,但是,嵌套 ...

  8. Java数据结构之树和二叉树

    从这里开始将要进行Java数据结构的相关讲解,Are you ready?Let's go~~ Java中的数据结构模型可以分为一下几部分: 1.线性结构 2.树形结构 3.图形或者网状结构 接下来的 ...

  9. MySQL 127.0.0.1和localhost本质区别

    登录方式: [root@10-4-14-168 ~]# mysql -uroot -p Enter password: 查看权限表 mysql> SELECT user,host,passwor ...

  10. cocos2d jsb 打包 Android APK

    1.首先要会普通的cpp 打包成Android APK 下面所说的是在cocos2d-x 2.2.2 或者 2.3 版本号中.本文在Eclipse总用ndk编译cocos2d-x. 老生常谈cocos ...