最新消息:聚合-分享-->bidutools.com是专注互联网技术的个人博客,大部分来自互联网,以作为笔记查阅。

基于libdrizzle开发异步高性能的MySQL代理服务器(转)

Mysql bidu 998浏览 0评论

MySQL数据库对每个客户端连接都会分配一个线程,所以连接非常宝贵。开发一个异步的MySQL代理服务器,PHP应用服务器可以长连接到这台Server,既减轻MYSQL的连接压力,又使PHP保持长连接减少connect/close的网络开销。

此Server考虑到了设置了数据库连接池尺寸,区分忙闲,mysqli断线重连,并设置了负载保护。基于swoole扩展开发,io循环使用epoll,是全异步非阻塞的,可以应对大量TCP连接。

程序的逻辑是:启动时创建N个MySQL连接,收到客户端发来的SQL后,分配1个MySQL连接,将SQL发往数据库服务器。然后等待数据库返回查询结果。当数据库返回结果后,再发给对应的客户端连接。

核心的数据结构是3个PHP数组。idle_pool是空闲的数据库连接,当有SQL请求时从idle_pool中移到busy_pool中。当数据库返回结果后从busy_pool中再移到idle_pool中,以供新的请求使用。当SQL请求到达时如果没有空闲的数据库连接,那会自动加入到wait_queue中。一旦有SQL完成操作,将自动从wait_queue中取出等待的请求进行处理。

如此循环使用。由于整个服务器是异步的单进程单线程所以完全不需要锁。而且是完全异步的,效率非常高。

当然本文的代码,如果要用于生产环境,还需做更多的保护机制和压力测试。在此仅抛砖引玉,提供一个解决问题的思路。

 

class DBServer
{
 protected $pool_size = 20;
 protected $idle_pool = array(); //空闲连接
 protected $busy_pool = array(); //工作连接
 protected $wait_queue = array(); //等待的请求
 protected $wait_queue_max = 100; //等待队列的最大长度,超过后将拒绝新的请求

/**
 * @var swoole_server
 */
 protected $serv;

function run()
 {
 $serv = new swoole_server("127.0.0.1", 9509);
 $serv->set(array(
 'worker_num' => 1,
 ));

$serv->on('WorkerStart', array($this, 'onStart'));
 //$serv->on('Connect', array($this, 'onConnect'));
 $serv->on('Receive', array($this, 'onReceive'));
 //$serv->on('Close', array($this, 'onClose'));
 $serv->start();
 }

function onStart($serv)
 {
 $this->serv = $serv;
 for ($i = 0; $i < $this->pool_size; $i++) {
 $db = new mysqli;
 $db->connect('127.0.0.1', 'root', 'root', 'test');
 $db_sock = swoole_get_mysqli_sock($db);
 swoole_event_add($db_sock, array($this, 'onSQLReady'));
 $this->idle_pool[] = array(
 'mysqli' => $db,
 'db_sock' => $db_sock,
 'fd' => 0,
 );
 }
 echo "Server: start.Swoole version is [" . SWOOLE_VERSION . "]\n";
 }

function onSQLReady($db_sock)
 {
 $db_res = $this->busy_pool[$db_sock];
 $mysqli = $db_res['mysqli'];
 $fd = $db_res['fd'];

echo __METHOD__ . ": client_sock=$fd|db_sock=$db_sock\n";

if ($result = $mysqli->reap_async_query()) {
 $ret = var_export($result->fetch_all(MYSQLI_ASSOC), true) . "\n";
 $this->serv->send($fd, $ret);
 if (is_object($result)) {
 mysqli_free_result($result);
 }
 } else {
 $this->serv->send($fd, sprintf("MySQLi Error: %s\n", mysqli_error($mysqli)));
 }
 //release mysqli object
 $this->idle_pool[] = $db_res;
 unset($this->busy_pool[$db_sock]);

//这里可以取出一个等待请求
 if (count($this->wait_queue) > 0) {
 $idle_n = count($this->idle_pool);
 for ($i = 0; $i < $idle_n; $i++) {
 $req = array_shift($this->wait_queue);
 $this->doQuery($req['fd'], $req['sql']);
 }
 }
 }
function onReceive($serv, $fd, $from_id, $data)
 {
 //没有空闲的数据库连接
 if (count($this->idle_pool) == 0) {
 //等待队列未满
 if (count($this->wait_queue) < $this->wait_queue_max) {
 $this->wait_queue[] = array(
 'fd' => $fd,
 'sql' => $data,
 );
 } else {
 $this->serv->send($fd, "request too many, Please try again later.");
 }
 } else {
 $this->doQuery($fd, $data);
 }
 }
function doQuery($fd, $sql)
 {
 //从空闲池中移除
 $db = array_pop($this->idle_pool);
 /**
 * @var mysqli
 */
 $mysqli = $db['mysqli'];
for ($i = 0; $i < 2; $i++) {
 $result = $mysqli->query($sql, MYSQLI_ASYNC);
 if ($result === false) {
 if ($mysqli->errno == 2013 or $mysqli->errno == 2006) {
 $mysqli->close();
 $r = $mysqli->connect();
 if ($r === true) continue;
 }
 }
 break;
 }
$db['fd'] = $fd;
 //加入工作池中
 $this->busy_pool[$db['db_sock']] = $db;
 }
}
$server = new DBServer();
$server->run();

来源:http://rango.swoole.com/archives/288



下面是一些论坛讨论:

MySQL的原生API都是同步调用的,会阻塞在网络上,请问是否有比较可靠的异步连接、发送、接收的C/C++实现方法,可以把MySQL的基本操作(增删改查原子操作)以网络事件通知异步实现?

要自己封装,做异步操作完成通知,如果是单连接,要做操作队列,不然要做连接池,mysql api好像不支持异步

目前我的打算是用操作队列缓冲+连接池~

解决MySQL写操作较多时比较慢的问题~

优化是第一步. 如果确实查询很多,就要应该用多线程. 最根本还是数据库设计的问题

异步解决高并发的重要手段。这一点我是支持LZ的。你提到的优化,目的是缩减同步IO的阻塞时间。
众所周知,同步阻塞IO要处理并发就得开n个线程。
线程少了会来不及处理,线程多了切换上下文的消耗会严重降低吞吐量。 
相比之下,异步IO仅需需要少量线程就可以达到很高的吞吐量,但是前提是线程不能被阻塞。
也就是说少量的线程必须一直保持激活状态并且快速处理请求。这种环境下异步数据库接口就显得十分重要了。

使用libdrizzle




使用libdrizzle实现mysql代理服务器的问题及优化

 

近期项目需要一个mysql代理服务器,实现mysql协议代理和路由功能,形成简单的mysql集群服务。现成的开源方案是mysql-proxy , 分析功能和源代码后发现跟我们的应用场景不太匹配,于是决定重新实现一个符合需求的mysql代理服务器,考虑到需要完美支持mysql协议,优先选择了libdrizzle库, libdrizzle是开源项目drizzle中的协议库,而drizzle可以看作mysql的分支版本,目前稳定版本是7.1.36 , 下面主要是记录使用libdrizzle中遇到的一些问题。
1. 关于nonblock模式的问题,现代应用服务器典型架构一般是使用reactor/proactor模式的事件驱动模型,如何把libdrizzle和应用服务器的驱动模型很好的结合起来尤其重要, libdrizzle支持nonblock模式,独立实现了事件驱动机制,使用poll监控网络事件,具体在drizzle_con_wait()中实现,然后通过drizzle_con_ready()遍历产生事件的网络连接,即drizzle_con_st对象,该接口难以与通常的网络事件驱动机制配合使用,性能也不太理想,具体用法可参见其自带的样例程序examples/client.cc , 也就是说libdrizzle的驱动模型需要重新封装成跟应用服务器相匹配,才能真正发挥nonblock模式的性能。

2. drizzle_result_st对象初始时一些内部数据没有初始化,容易造成程序崩溃,因此需要修改构造函数,初始化所有内部数据。涉及文件libdrizzle-2.0/structs.h 。相应字段为field, field_buffer,row 。

3. libdrizzle中运行时产生的内部对象都以双链表形式挂接在其上级对象中,例如drizzle_st对象中有个双链表维护其创建的drizzle_con_st对象,类似地,drizzle_con_st对象中有个双链表维护其创建的drizzle_result_st对象,所有的对象通过这种形式级联管理,并且这些对象中保存着上下文相关的状态,这样的实现方便资源管理,防止资源泄露,但在代理服务器中,请求和结果在不断转发过程中会形成大量的内存拷贝,为了减少转发过程中的内存拷贝,需要把drizzle_result_st显式的从drizzle_con_st中移除,当数据发往客户端完成后再删除,因此增加了drizzle_result_detach()接口,用于从drizzle_con_st对象中移除drizzle_result_st对象 , 涉及文件libdrizzle-2.0/result.h , libdrizzle-2.0/result.cc 。

void drizzle_result_detach(drizzle_result_st *result)
{

if (result->con)
{
result->con->result_count–;
if (result->con->result_list == result)
result->con->result_list= result->next;
}

if (result->prev)
result->prev->next= result->next;

if (result->next)
result->next->prev= result->prev;

result->con = NULL ;
result->prev = NULL ;
result->next = NULL ;
}

 

转载请注明:BiduTools.com聚合、分享 » 基于libdrizzle开发异步高性能的MySQL代理服务器(转)

发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址