PostgreSQL libpq接続プール

C ++でPostgreSQLを操作するための素晴らしいlibpqライブラリがあります。 ライブラリは十分に文書化されており、 PostgresPROからロシア語への完全な翻訳もあります。



サーバーバックエンドを作成するとき、このライブラリに接続プールが存在しないという事実に直面しました。データベースの操作はかなり集中的なモードであると想定され、1つの接続が明らかに不十分でした。 毎回、受信したデータを送信するための接続を確立することは単純に狂っています。 接続は最長の操作であるため、接続プールを書き込むことが決定されました。



アイデアは、プログラムの開始時にいくつかの接続を作成し、それらをキューに保存することです。



データが到着すると、キューから空き接続を取得します。空き接続がない場合は、表示されるまで待機し、それを使用してデータを挿入し、接続を戻します。 アイデアは非常にシンプルで、すぐに実装され、最も重要なことは、作業の速度が非常に速いことです。



このようなデモプレートを使用して、デモと呼ばれるPostgreSQLのデータベースを作成します



構造
-- Table: public.demo -- DROP TABLE public.demo; CREATE TABLE public.demo ( id integer NOT NULL DEFAULT nextval('demo_id_seq'::regclass), name character varying(256), CONSTRAINT demo_pk PRIMARY KEY (id) ) WITH ( OIDS=FALSE ); ALTER TABLE public.demo OWNER TO postgres;
      
      







データベースへの接続となるクラスを記述していますが、接続パラメーターはコードに直接記述されて単純化されますが、実際には、サーバー設定を変更するときにプログラムを再コンパイルする必要がないように、実際には構成ファイルに格納してそこから読み取る必要があります



pgconnection.h
 #ifndef PGCONNECTION_H #define PGCONNECTION_H #include <memory> #include <mutex> #include <libpq-fe.h> class PGConnection { public: PGConnection(); std::shared_ptr<PGconn> connection() const; private: void establish_connection(); std::string m_dbhost = "localhost"; int m_dbport = 5432; std::string m_dbname = "demo"; std::string m_dbuser = "postgres"; std::string m_dbpass = "postgres"; std::shared_ptr<PGconn> m_connection; }; #endif //PGCONNECTION_H
      
      





pgconnection.cpp
 #include "pgconnection.h" PGConnection::PGConnection() { m_connection.reset( PQsetdbLogin(m_dbhost.c_str(), std::to_string(m_dbport).c_str(), nullptr, nullptr, m_dbname.c_str(), m_dbuser.c_str(), m_dbpass.c_str()), &PQfinish ); if (PQstatus( m_connection.get() ) != CONNECTION_OK && PQsetnonblocking(m_connection.get(), 1) != 0 ) { throw std::runtime_error( PQerrorMessage( m_connection.get() ) ); } } std::shared_ptr<PGconn> PGConnection::connection() const { return m_connection; }
      
      







リソースの漏れを防ぐために、スマートポインターに接続を保存します。



コンストラクターで、データベースへの接続を確立するPQsetdbLogin関数を呼び出し、PGconn *接続へのポインターを返し、接続を非同期操作モードにします。



操作の最後に、PQfinish関数によって接続を削除する必要があります。この関数には、PQsetdbLogin関数によって返されるポインターが渡されます。 したがって、m_connection.reset()呼び出しの最後のパラメーターは、&PQfinish関数のアドレスです。 スマートポインターが範囲外になり、参照カウンターがリセットされると、スマートポインターはこの関数を呼び出し、接続を正しく終了します。



ここで、接続プールの作業を作成、保存、管理するクラスが必要です。



pgbackend.h
 #ifndef PGBACKEND_H #define PGBACKEND_H #include <memory> #include <mutex> #include <string> #include <queue> #include <condition_variable> #include <libpq-fe.h> #include "pgconnection.h" class PGBackend { public: PGBackend(); std::shared_ptr<PGConnection> connection(); void freeConnection(std::shared_ptr<PGConnection>); private: void createPool(); std::mutex m_mutex; std::condition_variable m_condition; std::queue<std::shared_ptr<PGConnection>> m_pool; const int POOL = 10; }; #endif //PGBACKEND_H
      
      







pgbackend.cpp
 #include <iostream> #include <thread> #include <fstream> #include <sstream> #include "pgbackend.h" PGBackend::PGBackend() { createPool(); } void PGBackend::createPool() { std::lock_guard<std::mutex> locker_( m_mutex ); for ( auto i = 0; i< POOL; ++i ){ m_pool.emplace ( std::make_shared<PGConnection>() ); } } std::shared_ptr<PGConnection> PGBackend::connection() { std::unique_lock<std::mutex> lock_( m_mutex ); while ( m_pool.empty() ){ m_condition.wait( lock_ ); } auto conn_ = m_pool.front(); m_pool.pop(); return conn_; } void PGBackend::freeConnection(std::shared_ptr<PGConnection> conn_) { std::unique_lock<std::mutex> lock_( m_mutex ); m_pool.push( conn_ ); lock_.unlock(); m_condition.notify_one(); }
      
      







createPool関数では、接続のプールを作成し、10個の接続を確立しました。 次に、PGBackendクラスを作成し、接続関数(データベースへの無料接続を返す)およびfreeConnection(接続をキューに戻す)を介して操作します。



これはすべて、条件変数に基づいて機能します。キューが空の場合、空き接続はなく、ストリームは条件変数を介して起動されるまでスリープ状態になります。



接続プールでバックエンドを使用する最も単純な例は、main.cppファイルに記載されています。 「戦闘状態」では、何らかのイベントのサイクルが確実に発生し、その発生時にデータベースとの作業が実行されます。 私はこのブーストを持っています:asioは非同期で動作し、ネットワークからイベントを受け取り、すべてをデータベースに書き込みます。 接続のプールでアイデアを複雑にしないために、ここに持ってくる必要はありません。 ここでは、1つのPGBackendインスタンスを介してサーバーで動作する50個のスレッドを作成します。



main.cpp
 #include <thread> #include <iostream> #include "pgbackend.h" void testConnection(std::shared_ptr<PGBackend> pgbackend) { //   auto conn = pgbackend->connection(); std::string demo = "SELECT max(id) FROM demo; " ; PQsendQuery( conn->connection().get(), demo.c_str() ); while ( auto res_ = PQgetResult( conn->connection().get()) ) { if (PQresultStatus(res_) == PGRES_TUPLES_OK && PQntuples(res_)) { auto ID = PQgetvalue (res_ ,0, 0); std::cout<< ID<<std::endl; } if (PQresultStatus(res_) == PGRES_FATAL_ERROR){ std::cout<< PQresultErrorMessage(res_)<<std::endl; } PQclear( res_ ); } //    pgbackend->freeConnection(conn); } int main(int argc, char const *argv[]) { auto pgbackend = std::make_shared<PGBackend>(); std::vector<std::shared_ptr<std::thread>> vec; for ( size_t i = 0; i< 50 ; ++i ){ vec.push_back(std::make_shared<std::thread>(std::thread(testConnection, pgbackend))); } for(auto &i : vec) { i.get()->join(); } return 0; }
      
      







これは次のコマンドでコンパイルされます:



 g++ main.cpp pgbackend.cpp pgconnection.cpp -o pool -std=c++14 -I/usr/include/postgresql/ -lpq -lpthread
      
      





データベース接続の数に注意してください-このパラメーターはmax_connections(整数)パラメーターによって設定されます。



ソースコード



All Articles