Database connection pool

Handwritten database connection pool


The returned is a shared with connection_ PTR object

Key technical points

MySQL database programming, singleton mode, queue container, C++11 multithreading programming, thread mutual exclusion, thread synchronous communication (mutex, conditional variables) and unique_lock, CAS based atomic shaping, intelligent pointer shared_ptr, lambda expression, producer consumer thread model

Project background

In order to improve the access bottleneck of MySQL database (based on C/S design), in addition to adding cache server to cache common data (such as redis), you can also add connection pool to improve the access efficiency of MySQL Server. In the case of high concurrency, a large number of TCP triple handshakes, MySQL Server connection authentication The performance time spent by MySQL Server closing connections, recycling resources and TCP waving four times is also obvious. Increasing connection pool is to reduce the performance loss of this part.

Popular connection pools in the market include Alibaba's druid, c3p0 and apache dbcp connection pools. They can significantly improve the performance of a large number of database additions, deletions, changes and queries in a short time, but they have one thing in common: they are all implemented by Java.

Then this project is to provide the access efficiency of MySQL Server and realize the database connection pool module based on C + + code in the C/C + + project.

Introduction to connection pool function point

The connection pool generally includes the ip address, port number, user name, password and other performance parameters used for database connection, such as initial connection volume, maximum connection volume, maximum idle time, connection timeout, etc. this project is a connection pool based on C + + language, and mainly realizes the general basic functions supported by all the above connection pools.

Initial connection size: indicates that the connection pool will create an initSize number of connection connections with MySQL Server in advance. When an application initiates MySQL access, it does not need to create a new connection with MySQL Server. It can directly obtain an available connection from the connection pool. After use, it does not release the connection, Instead, return the current connection to the connection pool.

Maximum connection size: when the number of concurrent requests to access MySQL Server increases, the initial connection is not enough. At this time, more connections will be created for the application according to the number of new requests. However, the upper limit of the number of newly created connections is maxSize. Connections cannot be created indefinitely, because each connection will occupy a socket resource, Generally, the connection pool and server program are deployed on the same host. If the connection pool occupies too many socket resources, the server cannot receive too many client requests. When these connections are used, they are returned to the connection pool again for maintenance.

Maximum idle time: when there are many concurrent requests to access MySQL, the number of connections in the connection pool will increase dynamically. The upper limit is maxSize. When these connections are used up, they will be returned to the connection pool again. If these newly added connections have not been used again in the specified maxIdleTime, the newly added connection resources will be recycled. Just keep the initial connection quantity initSize connections.

Connection timeout: when the number of concurrent requests from MySQL is too large, and the number of connections in the connection pool has reached maxSize, and there are no idle connections available, then the application cannot obtain connections from the connection pool successfully. It obtains connections through blocking methods. If the connection timeout exceeds, the connection acquisition fails, Unable to access database. The project mainly realizes the four functions of the above connection pool, and the other connection pools have more expansion functions, which can be realized by themselves.

Introduction to MySQL Server parameters

mysql> show variables like ‘max_connections’; This command can view the maximum number of connections supported by MySQL Server, exceeding max_ MySQL Server will directly reject connections with the number of connections. Therefore, when using the connection pool to increase the number of connections, MySQL Server's max_ The connections parameter should also be adjusted appropriately to fit the connection upper limit of the connection pool.

Function realization design

ConnectionPool.cpp and ConnectionPool.h: connection pool code implementation

Connection.cpp and Connection.h: implementation of database operation code, addition, deletion, modification and query code

Connection pool mainly includes the following function points:

1. Connection pool only needs one instance, so ConnectionPool is designed in singleton mode

2. The Connection to MySQL can be obtained from the ConnectionPool

3. All idle connections are maintained in a thread safe Connection queue, and thread mutex lock is used to ensure thread safety of the queue

4. If the Connection queue is empty, you need to obtain another Connection. At this time, you need to dynamically create a Connection. The upper limit is maxSize

5. If the idle connection time of the queue exceeds maxIdleTime, it will be released. Only the initial initSize connections can be retained. This function node must be placed in an independent thread

6. If the Connection queue is empty and the number of connections has reached the maximum maxSize at this time, wait for connectionTimeout time. If idle connections cannot be obtained, obtaining connections fails. Here, obtain idle connections from the Connection queue. You can use mutex mutex with timeout to realize Connection timeout

7. Shared for the connection obtained by the user_ PTR is managed by smart pointer, and the function of connection release is customized with lambda expression (instead of releasing the connection, the connection is returned to the connection pool)

8. The producer consumer thread model is used to design the production and consumption of connections, and the synchronous communication mechanism between threads, condition variables and mutual exclusion locks are used

Connection pool code and detailed introduction

The main functions are 6 functions
Get instance, load configuration item, constructor
Producer thread (add a little when the connection is used up), consumer (Interface), timed thread (recycle resources)

shared_ptr destructor, custom delete, thread back

class ConnectionPool
{
public:
	// Get connection pool object instance
	static ConnectionPool* getConnectionPool();
	// Provide an interface to the outside to obtain an available free connection from the connection pool
	shared_ptr<Connection> getConnection(); // The interface adopts RAII and does not require user management resources
private:
	// Singleton #1 constructor privatization
	ConnectionPool();

	// Load configuration item from configuration file
	bool loadConfigFile();

	// Run in a separate thread, dedicated to producing new connections
	void produceConnectionTask();

	// Scan idle connections that exceed maxIdleTime for connection recycling
	void scannerConnectionTask();

	string _ip; // mysql ip address
	unsigned short _port; // mysql port number 3306
	string _username; // mysql login user name
	string _password; // mysql login password
	string _dbname; // Connected database name
	int _initSize; // Initial number of connections to the connection pool
	int _maxSize; // Maximum number of connections in the connection pool
	int _maxIdleTime; // Connection pool maximum idle time
	int _connectionTimeout; // Timeout for connection pool to get connections

	queue<Connection*> _connectionQue; // Queue for storing mysql connections
	mutex _queueMutex; // Maintain thread safe mutexes for connection queues
	atomic_int _connectionCnt; // Record the total number of connection connections created by the connection 
	condition_variable cv; // Set the condition variable, which is used to connect the communication between the production thread and the consumption thread
};

Connection pool constructor

// Construction of connection pool
ConnectionPool::ConnectionPool()
{
	// Configuration item loaded
	if (!loadConfigFile())
	{
		return;
	}

	// Create initial number of connections
	for (int i = 0; i < _initSize; ++i)
	{
		Connection* p = new Connection();
		p->connect(_ip, _port, _username, _password, _dbname);
		p->refreshAliveTime(); // Refresh the start time of idle
		_connectionQue.push(p);
		_connectionCnt++;
	}

	// Start a new thread as the connection producer Linux thread = > pthread_ create
	thread produce(std::bind(&ConnectionPool::produceConnectionTask, this));
	produce.detach();

	// Start a new timing thread, scan idle connections that exceed maxIdleTime, and recycle connections
	thread scanner(std::bind(&ConnectionPool::scannerConnectionTask, this));
	scanner.detach();
}

Get singleton

// Thread safe lazy singleton function is reconstructed when the interface is called
ConnectionPool* ConnectionPool::getConnectionPool()
{
	static ConnectionPool pool; // lock and unlock
	return &pool;
}

Producer thread

// Run in a separate thread, dedicated to producing new connections
void ConnectionPool::produceConnectionTask()
{
	for (;;)
	{
		unique_lock<mutex> lock(_queueMutex);
		while (!_connectionQue.empty())
		{
			cv.wait(lock); // The queue is not empty. Here, the production thread enters the waiting state and releases the lock
		}

		// The number of connections has not reached the maximum. Continue to create new connections
		if (_connectionCnt < _maxSize)
		{
			Connection* p = new Connection();
			p->connect(_ip, _port, _username, _password, _dbname);
			p->refreshAliveTime(); // Refresh the start time of idle
			_connectionQue.push(p);
			_connectionCnt++;
		}

		// Notify the consumer thread that it is ready to consume the connection
		cv.notify_all();
	}
}

Consumer thread interface

// Provide an interface to the outside to obtain an available free connection from the connection pool
shared_ptr<Connection> ConnectionPool::getConnection()
{
	unique_lock<mutex> lock(_queueMutex);
	while (_connectionQue.empty())
	{
		// sleep
		if (cv_status::timeout == cv.wait_for(lock, chrono::milliseconds(_connectionTimeout)))
		{
			if (_connectionQue.empty())
			{
				LOG("Getting idle connections timed out...Failed to get connection!");
				return nullptr;
			}
		}
	}

	/*
	shared_ptr When the smart pointer is destructed, the connection resource will be delete d directly, which is equivalent to
	Call the destructor of the connection, and the connection is close d.
	You need to customize shared here_ PTR releases resources by returning the connection directly to the queue
	*/
	shared_ptr<Connection> sp(_connectionQue.front(),
		[&](Connection* pcon) {
			// This is called in the server application thread, so we must consider the thread safe operation of the queue.
			unique_lock<mutex> lock(_queueMutex);
			pcon->refreshAliveTime(); // Refresh the start time of idle
			_connectionQue.push(pcon);
		});

	_connectionQue.pop();
	cv.notify_all();  // After consuming the connection, notify the producer thread to check. If the queue is empty, hurry to produce the connection

	return sp;
}

Timed thread (reclaim connection)

// Scan idle connections that exceed maxIdleTime for connection recycling
void ConnectionPool::scannerConnectionTask()
{
	for (;;)
	{
		// Simulation of timing effect through sleep
		this_thread::sleep_for(chrono::seconds(_maxIdleTime));

		// Scan the entire queue to release redundant connections
		unique_lock<mutex> lock(_queueMutex);
		while (_connectionCnt > _initSize)
		{
			Connection* p = _connectionQue.front();
			if (p->getAliveeTime() >= (_maxIdleTime * 1000))
			{
				_connectionQue.pop();
				_connectionCnt--;
				delete p; // Call ~ Connection() to release the connection
			}
			else
			{
				break; // The connection of the queue head does not exceed_ maxIdleTime, there must be no other connection
			}
		}
	}
}

Pressure test

Development platform selection

MySQL database programming, multithreading programming, thread mutual exclusion and synchronous communication operation, intelligent pointer, design pattern, container and so on can be directly implemented at the C + + language level. Therefore, the project chooses to develop directly on the windows platform. Of course, it can also be compiled and run directly with g + + on the Linux platform.

The time spent in verifying the data insertion operation. The first test uses ordinary database access operation and the second test uses database access operation with connection pool. Compared with the time spent in the same amount of data in the two operations, the performance stress test results are as follows (the computer configuration is not too high - Intel) ® Core ™ i5-8300H CPU @ 2.30GHz 2.30 GHz);

Amount of data per threadTime spent unused connection poolTime spent using connection pool
1000Single thread: 5160ms four thread: 8783msSingle thread: 869ms four thread: 1346ms
2500Single thread: 11614ms four thread: 18742msSingle thread: 2170ms four thread: 3403ms
5000Single thread: 4173ms four thread: 6813ms

(different computers have different reduction rates)

Test code and configuration file:

  • Dependency
#Configuration file for database connection pool
ip=127.0.0.1
port=3306
username=root
password=12345678
dbname=chat
initSize=10
maxSize=1024
#The default unit of maximum idle time is seconds
maxIdleTime=60
#The connection timeout is in milliseconds
connectionTimeOut=100
CREATE DATABASE chart;

CREATE TABLE IF NOT EXISTS `user`(
   `id` INT(11) UNSIGNED AUTO_INCREMENT,
   `name` VARCHAR(50),
   `age` VARCHAR(11),
   `sex` enum('male', 'female'),
   PRIMARY KEY ( `id` )
)ENGINE=InnoDB DEFAULT CHARSET=utf8;
#define _CRT_SECURE_NO_WARNINGS

#include "pch.h"
#include <iostream>
using namespace std;
#include "Connection.h"
#include "CommonConnectionPool.h"

void f() {
	ConnectionPool *cp = ConnectionPool::getConnectionPool();
	for (int i = 0; i < 5000; ++i)
	{
		char sql[1024] = { 0 };
		sprintf(sql, "insert into user(name,age,sex) values('%s',%d,'%s')",
			"zhang san", 20, "male");
		shared_ptr<Connection> sp = cp->getConnection();
		sp->update(sql);
		/*Connection conn;
		char sql[1024] = { 0 };
		sprintf(sql, "insert into user(name,age,sex) values('%s',%d,'%s')",
			"zhang san", 20, "male");
		conn.connect("127.0.0.1", 3306, "root", "12345678", "chat");
		conn.update(sql);*/
	}
}

int main()
{
	// Connection conn;
	// conn.connect("127.0.0.1", 3306, "root", "12345678", "chat");
#if 1
	clock_t begin = clock();
	f();
	clock_t end = clock();
	std::cout << (end - begin) << "ms" << endl;
#endif

#if 1

	clock_t begin4 = clock();
	thread t1(f);
	thread t2(f);
	thread t3(f);
	thread t4(f);

	t1.join();
	t2.join();
	t3.join();
	t4.join();

	clock_t end4 = clock();
	std::cout << (end4 - begin4) << "ms" << endl;
#endif



	return 0;
}

Lazy singleton class. Instantiate yourself at the first call!
Hungry Chinese singleton class. It has been instantiated by itself during class initialization
Singleton mode is to ensure that a class has only one instance

Connection operation code

#include "pch.h"
#include "public.h"
#include "Connection.h"
#include <iostream>
using namespace std;

Connection::Connection()
{
	// Initialize database connection
	_conn = mysql_init(nullptr);
}

Connection::~Connection()
{
	// Release database connection resources
	if (_conn != nullptr)
		mysql_close(_conn);
}

bool Connection::connect(string ip, unsigned short port,
	string username, string password, string dbname)
{
	// Connect to database
	MYSQL* p = mysql_real_connect(_conn, ip.c_str(), username.c_str(),
		password.c_str(), dbname.c_str(), port, nullptr, 0);
	return p != nullptr;
}

bool Connection::update(string sql)
{
	// Update operations insert, delete, update
	if (mysql_query(_conn, sql.c_str()))
	{
		LOG("Update failed:" + sql);
		return false;
	}
	return true;
}

MYSQL_RES* Connection::query(string sql)
{
	// Query operation select
	if (mysql_query(_conn, sql.c_str()))
	{
		LOG("Query failed:" + sql);
		return nullptr;
	}
	return mysql_use_result(_conn);
}

Keywords: C++ Database

Added by Tazerenix on Wed, 01 Dec 2021 12:52:29 +0200