文档章节

PHP数据库长连接mysql_pconnect的细节

rick009
 rick009
发布于 2012/12/12 20:06
字数 2533
阅读 1698
收藏 1

【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>

PHP的MySQL持久化连接,美好的目标,却拥有糟糕的口碑,往往令人敬而远之。这到底是为啥么。近距离观察后发现,这家伙也不容易啊,要看Apache的脸色,还得听MySQL指挥。

对于作为Apache模块运行的PHP来说,要实现MySQL持久化连接,首先得取决于Apache这个web服务器是否支持Keep-Alive。

Keep-Alive Keep-Alive是什么东西?它是http协议的一部分,让我们复习一下没有Keep-Alive的http请求,从客户在浏览器输入一个有效url地址开始,浏览器就会利用socket向url对应的web服务器发送一条TCP请求,这个请求成功一次就得需要来回握三次手才能确定,成功以后,浏览器利用socket TCP连接资源向web服务器请求http协议,发送以后就等着web服务器把http返回头和body发送回来,发回来后浏览器关闭socket连接,然后做http返回头和body的解析工作,最后呈现在浏览器上的就是漂亮的页面了。这里面有什么问题呢?TCP连接需要三次握手,也就是来回请求三次方能确定一个TCP请求是否成功,然后TCP关闭呢?来回需要4次请求才能完成!每次http请求就3次握手,4次拜拜,这来来回回的不嫌累啊,多少时间和资源都被浪费在socket连接关闭上了,能不能一次socket TCP连接发送多次http请求呢?于是Keep-Alive就应运而生,http/1.0里需要客户端自己在请求头加入Connection:Keep-alive方能实现,在这里我们只考虑http1.1了,只需要设置一下Apache,让它默认就是Keep-Alive持久连接模式(Apache必须1.2+才能支持Keep-Alive)。在httpd.conf里找到KeepAive配置项,果断设置为On,MaxKeepAliveRequests果断为0(一个持久TCP最多允许的请求数,如果过小,很容易在TCP未过期的情况下,达到最大连接,那下次连接就又是新的TCP连接了,这里设置0表示不限制),然后对于mysql_pconnect最重要的选项KeepAliveTimeout设置为15(表示15秒)。

好了,重启Apache,测试一下,赶紧写行东西:

<!-- lang: php -->
<?php
    echo "Apache进程号:". getmypid();
?>

很简单,获取当前PHP执行者(Apache)的进程号,用浏览器浏览这个页面,看到什么?对,有看到一串进程号数字,15秒内,连续刷新页面,看看进程号有无变化?木有吧?现在把手拿开,交叉在胸前,度好时间,1秒,2秒,3,...15,16。好,过了15秒了,再去刷新页面,进程号有没有变化?变了!又是一个新的Apache进程了,为什么15秒后就变成新的进程了?记得我们在Apache里设置的KeepAliveTimeout吗?它的值就是15秒。现在我们应该大致清楚了,在web服务器默认打开KeepAlive的情况下,客户端第一次http成功请求后,Apache不会立刻断开socket,而是一直监听来自这一客户端的请求,监听多久?根据KeepAliveTimeout选项配置的时间决定,一旦超过这一时间,Apache就会断开socket了,那么下次同一客户端再次请求,Apache就会新开一个进程来相应。所以我们之前15内不停的刷新页面,看到的进程号都是一致的,表明是浏览器请求给了同一个Apache进程。

浏览器是怎么知道不需要重新进行TCP连接就可以直接发送http请求呢?因为http返回头里就会带上Connection:keep-alive,Keep-alive:15两行,意思就是让客户端浏览器明白,这次socket连接我这边还没关闭呢,你可以在15内继续使用这个连接,并发送http请求,于是乎浏览器就知道应该怎么做了。

PHP怎么做 那么,PHP的MySQL连接资源是怎么被hold住的呢,这需要查看PHP的mysql_pconnect的函数代码,我看了下,大概的做法就是mysql_pconnect根据当前Apache进程号,生成hash key,找hash表内有无对应的连接资源,没有则推入hash表,有则直接使用。有些代码片段可以说明(具体可查看PHP5.3.8源码ext/mysql/PHP_mysql.c文件690行PHP_mysql_do_connect函数)

<!-- lang: cpp -->
#1.生成hash key
	user=php_get_current_user();//获取当前PHP执行者(Apache)的进程唯一标识号
	//hashed_details就是hash key
	hashed_details_length = spprintf(&hashed_details, 0, "MySQL__%s_", user);
    #2.如果未找到已有资源,就推入hash表,名字叫persistent_list,如果找到就直接使用
	/* try to find if we already have this link in our persistent list */
	if (zend_hash_find(&EG(persistent_list), hashed_details, hashed_details_length+1, (void **) &le)==FAILURE) {  
		/* we don't */
		...
		...
		/* hash it up(推入hash表) */
		Z_TYPE(new_le) = le_plink;
		new_le.ptr = mysql;
		if (zend_hash_update(&EG(persistent_list), hashed_details, hashed_details_length+1, (void *) &new_le, sizeof(zend_rsrc_list_entry), NULL)==FAILURE) {
			...
     		...      
			}
   		}
		else
		{/* The link is in our list of persistent connections(连接已在hash表里)*/
            ...
            ...
            mysql = (PHP_mysql_conn *) le->ptr;//直接使用对应的sql连接资源
            ...
            ...
		}

zend_hash_find比较容易看明白,原型是zend_hash_find(hash表,key名,key长,value);如果找到,value就有值了。

MySQL的wait_timeout和interactive_timeout 说完Keep-Alive,该到MySQL家串串门了,说的是mysql_pconnect,怎么能绕开MySQL的设置。影响mysql_pconnect最重要的两个参数就是wait_timeout和interactive_timeout,它们是什么东西?先撇一边,首先让我们把上面的代码改动一下PHP代码

<!-- lang: php -->
<?php
    $conn = mysql_pconnect("localhost","root","123456") or die("Can not connect to MySQL");
    echo "MySQL线程号:". MySQL_thread_id($conn). "<br />";
    echo "Apache进程号". getmypid();
?>

以上的代码没啥好解释的,让我们用浏览器浏览这个页面,看到什么?看到两个显眼的数字。一个是MySQL线程号,一个是Apache进程号,好了,15秒后再刷新这个页面,发现这两个id都变了,因为已经是新的Apache进程了,进程id是新的,hash key就变了,PHP只好重新连接MySQL,连接资源推入persistent list。如果15内刷新呢?Apache进程肯定不变,MySQL线程号会变吗?答案得问MySQL了。首先这个MySQL_thread_id是什么东西?shell方式登录MySQL后执行命令'show processlist;',看到了什么?

mysql> show processlist;
+-----+------+-----------+------+--------+-----+------+-----------------+ | Id  | User | Host      | db   | Command| Time| State| Info           |
+-----+------+-----------+------+--------+-----+------+-----------------+ | 348 | root | localhost | NULL | Query  |    0| NULL | show processlist| | 349 | root | localhost | NULL | Sleep  |    2|      | NULL            |
+-----+------+-----------+------+--------+-----+------+-----------------+

发现了很重要的信息,这个processlist列表就是记录了正在跑的线程,忽略Info列为show processlist那行,那行是你当前shell登录MySQL的线程。PHP连接MySQL的线程就是Id为349那行,如果读者自己做测试,应该知道这个Id=349在你的测试环境里是另外一个值,我们把这个值和网页里输出的MySQL_thread_id($conn)做做比较,对!他们是一样的。接下来最重要的是观察Command列和Time列,Command = Sleep,表明什么?表明我们mysql_pconnect连接后就一直在sleep,Time字段就告诉我们,这个线程Sleep了多久,那么Sleep了多久这个线程才能作废呢?那就是wait_timeout或者interactive_timeout要做的工作了,他们默认的值都是8小时,天啊,太久了,所以如果说web服务器关掉KeepAlive支持,那个这个processlist很容易就被撑爆,就爆出那个Too many connections的错误了,max_connectiosns配置得再多也没用。为了观察这两个参数,我们可以在MySQL配置文件my.cnf里设置这两个值,找到[MySQLd]节点,在里面设置多两行

interactive_timeout = 60 wait_timeout = 30 配置完后,重启MySQL,shell登录MySQL,这时候show processlist可以发现只有当前线程。然后运行那个带有mysql_pconnect的PHP页面,再回来MySQL端show processlist可发现,多了一个Commond为Sleep的线程,不停的show processlist(方向键上+enter键)观察Time列的变化2,5,10...14!,突然那个Sleep线程程被kill掉了,咋回事,还没到30秒呢,噢!忘了修改一下Apache keepalive的参数了,把KeepAliveTimeOut从15改成120(只为观察,才这么改),重启Apache。刷新那个页面,好,开始不停的show processlist,2..5..10..14,15,..20...26....28,29!线程被kill,这次是因为wait_timeout起了作用,浏览器那边停了30秒,30内如果浏览器刷新,那这个Time又会从0开始计时。这种连接不属于interactive connection(MySQL shell登录那种连接就属于interactive connection),所以采用了wait_timeout的值。如果mysql_pconnect的第4个参数改改呢

<!-- lang: php -->
<?php
$conn = mysql_pconnect('localhost','root','123456',MySQL_CLIENT_INTERACTIVE);
echo "MySQL线程号:".MySQL_thread_id($conn)."<br />";
echo "Apache进程号:".getmypid();
?>

刷新下页面,MySQL那边开始刷show processlist,这回Time > 30也不会被kill,>60才被kill了,说明设置了MySQL_CLIENT_INTERACTIVE,就会被MySQL视为interactive connection,那么这次PHP的MySQL连接在120秒内未刷新的情况下,何时作废将取决于MySQL的interactive_timeout的配置值。

总结 PHP的mysql_pconnect要达到功效,首先必须保证Apache是支持keep alive的,其次KeepAliveTimeOut应该设置多久呢,要根据自身站点的访问情况做调整,时间太短,keep alive没啥意义,时间太长,就很可能为一个闲客户端连接牺牲很多服务器资源,毕竟hold住socket监听进程是要消耗cpu内存的。最后Apache的KeepAliveTimeOut配置得和MySQL的time out配置要有个平衡点,联系以上的观察,假设mysql_pconnect未带上第4个参数,如果Apache的KeepAliveTimeOut设置的秒数比wait_timeout小,那真正对mysql_pconnect起作用的是Apache而不是MySQL的配置。这时如果MySQL的wait_timeout偏大,并发量大的情况下,很可能就一堆废弃的connection了,MySQL这边如果不及时回收,那就很可能Too many connections了。可是如果KeepAliveTimeOut太大呢,又回到之前的问题,所以貌似Apache。KeepAliveTimeOu不要太大,但比MySQL。wait_timeout 稍大,或者相等是比较好的方案,这样可以保证keep alive过期后,废弃的MySQL连接可以及时被回收。

本文转载自:http://www.nowamagic.net/librarys/veda/detail/95

rick009

rick009

粉丝 10
博文 17
码字总数 14541
作品 0
海淀
技术主管
私信 提问
加载中

评论(7)

鲜橙J

引用来自“蛤蟆王子”的评论

引用来自“MeiKai”的评论

那如果php以fastcgi运行在lighttpd上又会如何

php的mysql_pconnect只能使用在作为web服务器模块的情况下才能使用,也就是只能在Apache下才能使用,在其它的使用FastCGI的情况下,PHP 会为向 web 服务器提出的每个 PHP 页面请求生成并结束一个 PHP 解释器线程。由于该线程会随每个请求的结束而结束,因此任何在这个线程中利用的任何资源(例如指向 SQL 数据库服务器的连接)都会随线程的结束而关闭。在这种情况下,使用永久连接不会获得任何地改变——因为它们根本不是永久的(这在php文档中有说明)

这个也是不对的吧。fastcgi并不是每个请求生成一个线程吧,fastcgi是会先生成一批php进程,每个请求过来,会分配到这些php进程进行处理。请求结束后,长连接是不会被回收的
鲜橙J
觉得你说的不对,keepalive过期后,apache不一定是新开进程,可能是从现在的apache进程里重新选一个进程吧
rick009
rick009 博主

引用来自“MeiKai”的评论

单例模式的数据库连接是否是所有数据库请求都使用同一个连接,我在电脑上试过,资源符号都是一样的

不好意思,之前的php代码显示没有设置好,你可以显示那个mysql的线程号试试
MeiKai
MeiKai
单例模式的数据库连接是否是所有数据库请求都使用同一个连接,我在电脑上试过,资源符号都是一样的
MeiKai
MeiKai
这里的线程应该同传统意义上的线程是不同的吧?
rick009
rick009 博主

引用来自“MeiKai”的评论

那如果php以fastcgi运行在lighttpd上又会如何

php的mysql_pconnect只能使用在作为web服务器模块的情况下才能使用,也就是只能在Apache下才能使用,在其它的使用FastCGI的情况下,PHP 会为向 web 服务器提出的每个 PHP 页面请求生成并结束一个 PHP 解释器线程。由于该线程会随每个请求的结束而结束,因此任何在这个线程中利用的任何资源(例如指向 SQL 数据库服务器的连接)都会随线程的结束而关闭。在这种情况下,使用永久连接不会获得任何地改变——因为它们根本不是永久的(这在php文档中有说明)
MeiKai
MeiKai
那如果php以fastcgi运行在lighttpd上又会如何
PHP使用数据库永久连接方式操作MySQL的是与非

首先看看数据库永久连接的定义:   永久的数据库连接是指在脚本结束运行时不关闭的连接。当收到一个永久连接的请求时。PHP 将检查是否已经存在一个(前面已经开启的)相同的永久连接。如果...

云栖希望。
2017/12/18
0
0
开发一套PHP框架,连接MYSQL的时候头疼了下

连接MYSQL, 设计模式问题 以下三种连接的方式 1.用mysql_connect()函数建立一个单例类 2.直接使用mysql_pconnect()建立长连接 3.建立一个数据库连接类,实例化的时候建立短连接,销毁的时候关...

西門扫雪
2013/05/28
460
9
对付 MySQL 的死连接,Sleep的进程的来源探究[转]

对付 MySQL 的死连接,Sleep的进程的来源探究[转] 当前的连接数: mysql show status like '%Threadsconnected%'; +-------------------+-------+ | Variablename | Value | +--------------......

刘赤龙
2010/06/08
624
0
mysql_connect和mysql_pconnect的区别

php中mysqlpconnect()的实现方式:其实mysqlpconnect()本身并没有做太多的处理,它唯一做的只是在php运行结束后不主动close掉mysql的连接。mysqlpconnect()与mysqlconnect()的区别包括:  ...

mac_zhao
2015/01/23
1K
0
discuz 7.0 db_mysql.php 详解2

//禁止外部引用,即非本地使用. if(!defined('INDISCUZ')) { exit('Access Denied'); } class dbstuff { var $version = ''; //mysql版本号 var $querynum = 0; //执行次数 var $link; //mys......

五味格子
2011/05/11
29
0

没有更多内容

加载失败,请刷新页面

加载更多

ArrayList 源码分析

一、概述 本文基于 JDK8 ArrayList 底层通过动态数组的数据结构实现 内存需要连续的空间保证 添加操作涉及到数组的动态扩容 添加,删除都涉及到位置移动操作 随机查找效率快(下标查找) Ar...

hncboy
今天
4
0
采购单品汇总_华南.xlsx

import pandas as pdimport matplotlib.pyplot as pltimport matplotlib as mp1mp1.rcParams["font.family"] = "STFangsong"# 加载《销售》表数据df1 = pd.read_excel(r"C:\Us......

龙玉滕
今天
5
0
OSChina 周五乱弹 —— 一次四千 要4次还能多给一千

Osc乱弹歌单(2019)请戳(这里) 【今日歌曲】 @这次装个文艺青年吧 :#今日歌曲推荐# 分享金志文的单曲《远走高飞》: 版权又回来了现在听歌得好几个软件 《远走高飞》- 金志文 手机党少年们...

小小编辑
今天
10
0
Spring Cloud Alibaba 实战(十) - Spring Cloud GateWay

> 本文主要内容是:为什么要使用网关,整合Gateway,Gateway核心学习:Route,Predicate,Filter,最后使用Gateway聚合微服务请求 先总结至此的架构 1 网关的价值 不使用网关行嘛? 各个请求直接打在...

JavaEdge
今天
4
0
【CKB.DEV 茶话会】第二期:聊聊 CKB 钱包和 Nervos DAO 全流程

CKB.DEV 茶话会第二期:聊聊 CKB 钱包和 Nervos DAO 全流程 为了鼓励更多优秀的开发者和研究人员参与到 CKB 的开发和生态建设中去,我们希望组织一系列 CKB Developer Seminar(CKB.DEV 茶话...

NervosCommunity
今天
4
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部