PostgreSQL异步客户端(并模拟redis 数据结构)
以前为了不在游戏逻辑(对象属性)变更时修改数据库,就弄了个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实现进行改造优化)
- #include <string>
- #include <list>
- #include <iostream>
- #include <unordered_map>
- #include <memory>
- #include <queue>
- #include <assert.h>
- #include <functional>
- #include <sstream>
- #include <chrono>
- #include "fdset.h"
- #include "libpq-events.h"
- #include "libpq-fe.h"
- #include "libpq/libpq-fs.h"
- using namespace std;
- class AsyncPGClient
- {
- public:
- /*TODO::传递错误信息*/
- typedef std::function<void(const PGresult*)> RESULT_CALLBACK;
- typedef std::function<void(bool value)> BOOL_RESULT_CALLBACK;
- typedef std::function<void(const string& value)> STRING_RESULT_CALLBACK;
- typedef std::function<void(const std::unordered_map<string, string>& value)> STRINGMAP_RESULT_CALLBACK;
- AsyncPGClient() : mKVTableName("kv_data"), mHashTableName("hashmap_data")
- {
- mfdset = ox_fdset_new();
- }
- ~AsyncPGClient()
- {
- for (auto& kv : mConnections)
- {
- PQfinish((*kv.second).pgconn);
- }
- ox_fdset_delete(mfdset);
- mfdset = nullptr;
- }
- void get(const string& key, const STRING_RESULT_CALLBACK& callback = nullptr)
- {
- mStringStream << "SELECT key, value FROM public." << mKVTableName << " where key = '" << key << "';";
- postQuery(mStringStream.str(), [callback](const PGresult* result){
- if (callback != nullptr && result != nullptr)
- {
- if (PQntuples(result) == && PQnfields(result) == )
- {
- callback(PQgetvalue(result, , ));
- }
- }
- });
- }
- void set(const string& key, const string& v, const BOOL_RESULT_CALLBACK& callback = nullptr)
- {
- mStringStream << "INSERT INTO public." << mKVTableName << "(key, value) VALUES('" << key << "', '" << v << "') ON CONFLICT(key) DO UPDATE SET value = EXCLUDED.value;";
- postQuery(mStringStream.str(), [callback](const PGresult* result){
- if (callback != nullptr)
- {
- if (PQresultStatus(result) == PGRES_COMMAND_OK)
- {
- callback(true);
- }
- else
- {
- cout << PQresultErrorMessage(result);
- callback(false);
- }
- }
- });
- }
- void hget(const string& hashname, const string& key, const STRING_RESULT_CALLBACK& callback = nullptr)
- {
- hmget(hashname, { key }, [callback](const std::unordered_map<string, string>& value){
- if (callback != nullptr && !value.empty())
- {
- callback((*value.begin()).second);
- }
- });
- }
- void hmget(const string& hashname, const std::vector<string>& keys, const STRINGMAP_RESULT_CALLBACK& callback = nullptr)
- {
- mStringStream << "SELECT key, value FROM public." << mHashTableName << " where ";
- auto it = keys.begin();
- do
- {
- mStringStream << "key='" << (*it) << "'";
- ++it;
- } while (it != keys.end() && &(mStringStream << " or ") != nullptr);
- mStringStream << ";";
- postQuery(mStringStream.str(), [callback](const PGresult* result){
- if (callback != nullptr)
- {
- std::unordered_map<string, string> ret;
- if (PQresultStatus(result) == PGRES_TUPLES_OK)
- {
- int num = PQntuples(result);
- int fileds = PQnfields(result);
- if (fileds == )
- {
- for (int i = ; i < num; i++)
- {
- ret[PQgetvalue(result, i, )] = PQgetvalue(result, i, );
- }
- }
- }
- callback(ret);
- }
- });
- }
- void hset(const string& hashname, const string& key, const string& value, const BOOL_RESULT_CALLBACK& callback = nullptr)
- {
- mStringStream << "INSERT INTO public." << mHashTableName << "(hashname, key, value) VALUES('" << hashname << "', '" << key << "', '" << value
- << "') ON CONFLICT (hashname, key) DO UPDATE SET value = EXCLUDED.value;";
- postQuery(mStringStream.str(), [callback](const PGresult* result){
- if (callback != nullptr)
- {
- callback(PQresultStatus(result) == PGRES_COMMAND_OK);
- }
- });
- }
- void hgetall(const string& hashname, const STRINGMAP_RESULT_CALLBACK& callback = nullptr)
- {
- mStringStream << "SELECT key, value FROM public." << mHashTableName << " where hashname = '" << hashname << "';";
- postQuery(mStringStream.str(), [callback](const PGresult* result){
- if (callback != nullptr)
- {
- std::unordered_map<string, string> ret;
- if (PQresultStatus(result) == PGRES_TUPLES_OK)
- {
- int num = PQntuples(result);
- int fileds = PQnfields(result);
- if (fileds == )
- {
- for (int i = ; i < num; i++)
- {
- ret[PQgetvalue(result, i, )] = PQgetvalue(result, i, );
- }
- }
- }
- callback(ret);
- }
- });
- }
- void postQuery(const string&& query, const RESULT_CALLBACK& callback = nullptr)
- {
- mPendingQuery.push({ std::move(query), callback});
- mStringStream.str(std::string());
- mStringStream.clear();
- }
- void postQuery(const string& query, const RESULT_CALLBACK& callback = nullptr)
- {
- mPendingQuery.push({ query, callback });
- mStringStream.str(std::string());
- mStringStream.clear();
- }
- public:
- void poll(int millSecond)
- {
- ox_fdset_poll(mfdset, millSecond);
- std::vector<int> closeFds;
- for (auto& it : mConnections)
- {
- auto fd = it.first;
- auto connection = it.second;
- auto pgconn = connection->pgconn;
- if (ox_fdset_check(mfdset, fd, ReadCheck))
- {
- if (PQconsumeInput(pgconn) > && PQisBusy(pgconn) == )
- {
- bool successGetResult = false;
- while (true)
- {
- auto result = PQgetResult(pgconn);
- if (result != nullptr)
- {
- successGetResult = true;
- if (connection->callback != nullptr)
- {
- connection->callback(result);
- connection->callback = nullptr;
- }
- PQclear(result);
- }
- else
- {
- break;
- }
- }
- if (successGetResult)
- {
- mIdleConnections.push_back(connection);
- }
- }
- if (PQstatus(pgconn) == CONNECTION_BAD)
- {
- closeFds.push_back(fd);
- }
- }
- if (ox_fdset_check(mfdset, fd, WriteCheck))
- {
- if (PQflush(pgconn) == )
- {
- //移除可写检测
- ox_fdset_del(mfdset, fd, WriteCheck);
- }
- }
- }
- for (auto& v : closeFds)
- {
- removeConnection(v);
- }
- }
- void trySendPendingQuery()
- {
- while (!mPendingQuery.empty() && !mIdleConnections.empty())
- {
- auto& query = mPendingQuery.front();
- auto& connection = mIdleConnections.front();
- if (PQsendQuery(connection->pgconn, query.request.c_str()) == )
- {
- cout << PQerrorMessage(connection->pgconn) << endl;
- if (query.callback != nullptr)
- {
- query.callback(nullptr);
- }
- }
- else
- {
- ox_fdset_add(mfdset, PQsocket(connection->pgconn), WriteCheck);
- connection->callback = query.callback;
- }
- mPendingQuery.pop();
- mIdleConnections.pop_front();
- }
- }
- size_t pendingQueryNum() const
- {
- return mPendingQuery.size();
- }
- size_t getWorkingQuery() const
- {
- return mConnections.size() - mIdleConnections.size();
- }
- void createConnection( const char *pghost, const char *pgport,
- const char *pgoptions, const char *pgtty,
- const char *dbName, const char *login, const char *pwd,
- int num)
- {
- for (int i = ; i < num; i++)
- {
- auto pgconn = PQsetdbLogin(pghost, pgport, pgoptions, pgtty, dbName, login, pwd);
- if (PQstatus(pgconn) == CONNECTION_OK)
- {
- auto connection = std::make_shared<Connection>(pgconn, nullptr);
- mConnections[PQsocket(pgconn)] = connection;
- PQsetnonblocking(pgconn, );
- ox_fdset_add(mfdset, PQsocket(pgconn), ReadCheck);
- mIdleConnections.push_back(connection);
- }
- else
- {
- cout << PQerrorMessage(pgconn);
- PQfinish(pgconn);
- pgconn = nullptr;
- }
- }
- if (!mConnections.empty())
- {
- sCreateTable((*mConnections.begin()).second->pgconn, mKVTableName, mHashTableName);
- }
- }
- private:
- void removeConnection(int fd)
- {
- auto it = mConnections.find(fd);
- if (it != mConnections.end())
- {
- auto connection = (*it).second;
- for (auto it = mIdleConnections.begin(); it != mIdleConnections.end(); ++it)
- {
- if ((*it)->pgconn == connection->pgconn)
- {
- mIdleConnections.erase(it);
- break;
- }
- }
- ox_fdset_del(mfdset, fd, ReadCheck | WriteCheck);
- PQfinish(connection->pgconn);
- mConnections.erase(fd);
- }
- }
- private:
- static void sCreateTable(PGconn* conn, const string& kvTableName, const string& hashTableName)
- {
- {
- string query = "CREATE TABLE public.";
- query += kvTableName;
- query += "(key character varying NOT NULL, value json, CONSTRAINT key PRIMARY KEY(key))";
- PGresult* exeResult = PQexec(conn, query.c_str());
- auto status = PQresultStatus(exeResult);
- auto errorStr = PQresultErrorMessage(exeResult);
- PQclear(exeResult);
- }
- {
- string query = "CREATE TABLE public.";
- query += hashTableName;
- query += "(hashname character varying, key character varying, value json, "
- "CONSTRAINT hk PRIMARY KEY (hashname, key))";
- PGresult* exeResult = PQexec(conn, query.c_str());
- auto status = PQresultStatus(exeResult);
- auto errorStr = PQresultErrorMessage(exeResult);
- PQclear(exeResult);
- }
- }
- private:
- struct QueryAndCallback
- {
- std::string request;
- RESULT_CALLBACK callback;
- };
- struct Connection
- {
- PGconn* pgconn;
- RESULT_CALLBACK callback;
- Connection(PGconn* p, RESULT_CALLBACK c)
- {
- pgconn = p;
- callback = c;
- }
- };
- const string mKVTableName;
- const string mHashTableName;
- stringstream mStringStream;
- fdset_s* mfdset;
- std::unordered_map<int, shared_ptr<Connection>> mConnections;
- std::list<shared_ptr<Connection>> mIdleConnections;
- std::queue<QueryAndCallback> mPendingQuery;
- /*TODO::监听wakeup支持*/
- /*TODO::考虑固定分配connection给某业务*/
- /*TODO::编写储存过程,替换现有的hashtable模拟方式,如循环使用jsonb_set以及 select value->k1, value->k2 from ...*/
- /*TODO::编写储存过程,实现list*/
- };
- int main()
- {
- using std::chrono::system_clock;
- AsyncPGClient asyncClient;
- asyncClient.createConnection("192.168.12.1", "", nullptr, nullptr, "postgres", "postgres", "", );
- system_clock::time_point startTime = system_clock::now();
- auto nowTime = time(NULL);
- for (int i = ; i < ; i++)
- {
- if(false)
- {
- string test = "INSERT INTO public.kv_data(key, value) VALUES ('";
- test += std::to_string(nowTime*+i);
- test += "', '{\"hp\":100000}') ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value;";
- asyncClient.postQuery(test);
- }
- else
- {
- asyncClient.postQuery("select * from public.kv_data where key='dd';");
- }
- }
- asyncClient.postQuery("INSERT INTO public.kv_data(key, value) VALUES ('dodo5', '{\"hp\":100000}') "
- " ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value", [](const PGresult* result){
- cout << "fuck" << endl;
- });
- asyncClient.get("dd", [](const string& value){
- cout << "get dd : " << value << endl;
- });
- asyncClient.set("dd", "{\"hp\":456}", [](bool isOK){
- cout << "set dd : " << isOK << endl;
- });
- asyncClient.hget("heros:dodo", "hp", [](const string& value){
- cout << "hget heros:dodo:" << value << endl;
- });
- asyncClient.hset("heros:dodo", "hp", "{\"hp\":1}", [](bool isOK){
- cout << "hset heros:dodo:" << isOK << endl;
- });
- asyncClient.hmget("heros:dodo", { "hp", "money" }, [](const unordered_map<string, string>& kvs){
- cout << "hmget:" << endl;
- for (auto& kv : kvs)
- {
- cout << kv.first << " : " << kv.second << endl;
- }
- });
- asyncClient.hgetall("heros:dodo", [](const unordered_map<string, string>& kvs){
- cout << "hgetall:" << endl;
- for (auto& kv : kvs)
- {
- cout << kv.first << " : " << kv.second << endl;
- }
- });
- while (true)
- {
- asyncClient.poll();
- asyncClient.trySendPendingQuery();
- if (asyncClient.pendingQueryNum() == && asyncClient.getWorkingQuery() == )
- {
- break;
- }
- }
- auto elapsed = system_clock::now() - startTime;
- cout << "cost :" << chrono::duration<double>(elapsed).count() << "s" << endl;
- cout << "enter any key exit" << endl;
- cin.get();
- return ;
- }
代码地址:https://github.com/IronsDu/accumulation-dev/blob/master/examples/Pgedis.cpp
PostgreSQL异步客户端(并模拟redis 数据结构)的更多相关文章
- redis数据结构、持久化、缓存淘汰策略
Redis 单线程高性能,它所有的数据都在内存中,所有的运算都是内存级别的运算,而且单线程避免了多线程的切换性能损耗问题.redis利用epoll来实现IO多路复用,将连接信息和事件放到队列中,依次放 ...
- Redis 数据结构与内存管理策略(上)
Redis 数据结构与内存管理策略(上) 标签: Redis Redis数据结构 Redis内存管理策略 Redis数据类型 Redis类型映射 Redis 数据类型特点与使用场景 String.Li ...
- Redis学习系列六ZSet(有序列表)及Redis数据结构的过期
一.简介 ZSet可以说是Redis中最有趣的数据结构了,因为他兼具了Hash集合和Set的双重特性,也是用的最多的,保证了value值的唯一性的同时,,同时又保证了高性能,最主要的是还可以给每个Va ...
- 5种Redis数据结构详解
本文主要和大家分享 5种Redis数据结构详解,希望文中的案例和代码,能帮助到大家. 转载链接:https://www.php.cn/php-weizijiaocheng-388126.html 2. ...
- 深入Redis客户端(redis客户端属性、redis缓冲区、关闭redis客户端)
深入Redis客户端(redis客户端属性.redis缓冲区.关闭redis客户端) Redis 数据库采用 I/O 多路复用技术实现文件事件处理器,服务器采用单线程单进程的方式来处理多个客户端发送过 ...
- Redis 数据结构使用场景
转自http://get.ftqq.com/523.get 一.redis 数据结构使用场景 原来看过 redisbook 这本书,对 redis 的基本功能都已经熟悉了,从上周开始看 redis 的 ...
- Redis 数据结构与内存管理策略(下)
Redis 数据结构与内存管理策略(下) 标签: Redis Redis数据结构 Redis内存管理策略 Redis数据类型 Redis类型映射 Redis 数据类型特点与使用场景 String.Li ...
- 第18章 Redis数据结构常用命令
18-1 字符串的一些基本命令 18-1 :配置Spring关于Redis字符串的运行环境 <bean id="poolConfig" class="redis.c ...
- redis 模拟redis server接收信息
一.实现说明 客户端使用jedis正常set值到redis服务器 2. 模拟服务器接收jedis发送的信息 二.jedis客户端代码 package com.ahd.redis; import r ...
随机推荐
- 杭电1010Tempter of the Bone
Tempter of the Bone Problem Description The doggie found a bone in an ancient maze, which fascinated ...
- java复制File文件操作
==========================复制File操作========================= /** * * @param newPath要赋值的路径 * @para ...
- linux 的 scp 命令 可以 在 linux 之间复制 文件 和 目录
转自:http://blog.csdn.net/snlying/article/details/6184102 Linux系统中scp命令的用法. scp就是secure copy的简写,用于在lin ...
- sql 分组后 组内排名
语法:ROW_NUMBER() OVER(PARTITION BY COLUMN ORDER BY COLUMN) 简单的说row_number()从1开始,为每一条分组记录返回一个数字,这里的ROW ...
- putty 中文乱码解决方法
解决putty.exe 中文乱码的问题 export NLS_LANG="AMERICAN_AMERICA.ZHS16GBK"
- atprogram.exe : Atmel Studio Command Line Interface
C:\Program Files\Atmel\Atmel Studio 6.1\atbackend\atprogram.exe No command specified.Atmel Studio Co ...
- XHTML标签的嵌套规则--很基础很重要
XHTML的标签有许多:div.ul.li.dl.dt.dd.h1~h6.p.a.addressa.span. strong……我们在运用这些标签搭建页面结构的时候,是可以将它们无限嵌套的,但是,嵌套 ...
- Java数据结构之树和二叉树
从这里开始将要进行Java数据结构的相关讲解,Are you ready?Let's go~~ Java中的数据结构模型可以分为一下几部分: 1.线性结构 2.树形结构 3.图形或者网状结构 接下来的 ...
- MySQL 127.0.0.1和localhost本质区别
登录方式: [root@10-4-14-168 ~]# mysql -uroot -p Enter password: 查看权限表 mysql> SELECT user,host,passwor ...
- cocos2d jsb 打包 Android APK
1.首先要会普通的cpp 打包成Android APK 下面所说的是在cocos2d-x 2.2.2 或者 2.3 版本号中.本文在Eclipse总用ndk编译cocos2d-x. 老生常谈cocos ...