HandlerSocket是通过socket和innodb通信的,但是网络通信如果双方连接没有按照规则正常关闭,可能导致wati_close状态的TCP连接大量存在,占用大量端口资源,需要慎重处理。
最近碰到这样的一个案例,检查一个服务器上的php fastcgi的连接情况,发现一些异常:
[root@banping~]# netstat -anpo | grep php-fpm|wc -l
517
而实际是没有这么多连接的,进一步检查详细信息,发现很多wati_close状态的TCP连接,占用了大量端口,而服务器端对应的是HandlerSocket插件的9998端口,初步判断是socket连接没有正常关闭导致的问题:
[root@banping ~]# netstat -anpo | grep php-fpm
tcp 1 0 10.0.0.1:43635 10.0.0.2:9998 CLOSE_WAIT 3592/php-fpm: pool off (0.00/0/0)
tcp 1 0 10.0.0.1:43699 10.0.0.2:9998 CLOSE_WAIT 3592/php-fpm: pool off (0.00/0/0)
tcp 1 0 10.0.0.1:43335 10.0.0.2:9998 CLOSE_WAIT 3592/php-fpm: pool off (0.00/0/0)
tcp 1 0 10.0.0.1:43306 10.0.0.2:9998 CLOSE_WAIT 2894/php-fpm: pool off (0.00/0/0)
......
但是很难定位在客户端的哪个具体操作会导致这个问题,或许是在有一定负载压力的情况下才会出来这种死连接。
估计的原因有几种可能,一是HandlerSocket自身代码的问题,二是PHP客户端的实现问题,这个客户端并没有显示的断开socket连接的接口提供,三是自身代码问题或压力负载导致的。
不好定位问题,可以先通过Linux的keepalive参数来控制TCP的超时来释放CLOSE_WAIT的连接:
[root@banping ~]# vi /etc/sysctl.conf
net.ipv4.tcp_keepalive_time = 300
net.ipv4.tcp_keepalive_probes = 2
net.ipv4.tcp_keepalive_intvl = 2
[root@banping ~]# sysctl -p
把超时时间设为300秒,这个默认是7200秒,把tcp_keepalive_intvl(keepalive探测包的发送间隔)和tcp_keepalive_probes (如果对方不予应答,探测包的发送次数)都设为2秒,这样就能很快的释放连接。
默认参数可以这样查看:
[root@banping ~]# cat /proc/sys/net/ipv4/tcp_keepalive_time
7200
[root@banping ~]# cat /proc/sys/net/ipv4/tcp_keepalive_probes
9
[root@banping ~]# cat /proc/sys/net/ipv4/tcp_keepalive_intvl
75
然后过一段时间我们再观察CLOSE_WAIT连接数:
[root@banping ~]# netstat -anpo | grep php-fpm|grep CLOSE_WAIT|grep 9998|wc -l
203
这没有解决程序上的本质问题,需要继续观察评估效果。
前几天一台MySQL服务器的备份遭遇了一个错误,信息如下:
innobackupex: Error: Connection to mysql child process (pid=20080) timedout. (Time limit of 900 seconds exceeded. You may adjust time limit by editing the value of parameter "$mysql_response_timeout" in this script.) while waiting for reply to MySQL request: 'FLUSH TABLES WITH READ LOCK;' at /home/mysql/backup/xtrabackup-1.6/innobackupex line 336.
这是使用XtraBackup容易引起的一个经典错误,引起这个错误的原因也很简单,就是XtraBackup在备完innodb表的时候,要获得对所有表的锁定来备份MyISAM表和其他文件,来保证数据的一致性,如果在此过程中数据库不断的有DML操作,XtraBackup就可能迟迟无法获得对所有表的锁定,最后超时。
要避免这个问题,一个是加大超时的时间限制,但这显然不是一个好的解决办法,另外就是在备份的时候加上--no-lock参数,就是在复制MyISAM表和.frm等文件的时候不锁表,但要保证这时没有对MyISAM表的DML操作和Innodb表的DDL操作,这一般通过应用端权衡时间是容易办到的。
但是要注意的是,如果备份出来的文件要用于主从复制,那么不锁表会导致没有输出 slave_info文件和binlog_info文件,这对于一些人的应用场景可能会有影响。这时可以加一个--safe-slave-backup参数,使得在从库上备份的时候停止SQL THRED,这样即使从库能从主库接收binlog文件,但是不会应用,relay log position就不会移动了。但是单纯的停掉slave SQL Thread是不会影响binlog position的。
因为临时表的原因,--safe-slave-backup需要在SHOW STATUS的输出中slave_open_temp_tables为0的时候才停止slave SQL Thread,否则会导致复制出问题。因为临时表是基于用户session的,因此如果正在操作临时表的数据的时候停止slave SQL Thread,会导致可能后续的数据不一致,在其他停用slave SQL Thread的场景也要注意这个问题。
XtraBackup是percona公司提供的开源工具,以热备Innodb表著称而被广泛采用。
XtraBackup对Innodb的备份之所以是热备,无需锁表,是基于Innodb自身的崩溃恢复机制,它首先复制所有的Innodb数据文件,这样复制出来的文件肯定是不一致的,然后对每个文件进行崩溃恢复处理,最终达到一致。就和MySQL在启动Innodb的时候一样,会通过比较数据文件头和redo log文件头信息来检查数据是否是一致的,如果不一致就尝试通过前滚(把redo log中所有提交的事务写入数据文件)和回滚(从数据文件中撤销所有redo log中未提交的事务引起的修改)来使数据达到最终一致。
XtraBackup在启动的时候会记录一个LSN(log sequence number),然后就把所有的Innodb数据文件复制出来,这样复制出来的数据文件是不一致的,但是XtraBackup会在后台运行一个进程把所有对redo log file的修改记录下来,只要有了这个数据,就能进行崩溃恢复。只所以要额外记录下来,是因为MySQL自身的redo log file是可重用的。
以上的操作是由xtrabackup二进制程序(比如xtrabackup_55)完成的,如果使用innobackupex 脚本,刚才的步骤完成以后,innobackupex就会去备份MyISAM表和.frm文件,这时要保证数据的一致性就会先锁表了,通过FLUSH TABLES WITH READ LOCK命令锁表然后把文件复制出来,再释放掉这个锁。
在恢复数据的时候,要经过prepare(recovery)和restore两个步骤。在prepare结束以后,Innodb的表恢复到了复制Innodb文件结束的时间点,这个时间点也就是锁表复制MyISAM表的起点,所以最终数据是一致的。一般我们在恢复的时候执行两次prepare,是因为第二次prepare会帮助我们生成redo log文件,从而加快MySQL数据库启动的速度。
我们再来看一下实际备份的日志来理解这个过程:
......
110701 03:29:13 innobackupex: Starting ibbackup with command: xtrabackup_55 --defaults-file="/home/mysql/3306/my.cnf" --backup --suspend-at-end --log-stream --target-dir=./
innobackupex: Waiting for ibbackup (pid=22334) to suspend
innobackupex: Suspend file '/home/mysql/3306/data/xtrabackup_suspended'
xtrabackup: suspend-at-end is enabled.
xtrabackup: uses posix_fadvise().
xtrabackup: cd to /home/mysql/3306/data
xtrabackup: Target instance is assumed as followings.
xtrabackup: innodb_data_home_dir = /home/mysql/3306/data
xtrabackup: innodb_data_file_path = ibdata1:512M:autoextend
xtrabackup: innodb_log_group_home_dir = /home/mysql/3306/redolog
xtrabackup: innodb_log_files_in_group = 3
xtrabackup: innodb_log_file_size = 134217728
110701 3:29:13 InnoDB: Using Linux native AIO
110701 3:29:13 InnoDB: Warning: allocated tablespace 268, old maximum was 0
xtrabackup: Stream mode.
>> log scanned up to (2371741708)
110701 03:29:15 innobackupex: Continuing after ibbackup has suspended
innobackupex: Starting to backup InnoDB tables and indexes
innobackupex: from original InnoDB data directory '/home/mysql/3306/data'
innobackupex: Backing up as tar stream 'ibdata1'
>> log scanned up to (2371741708)
>> log scanned up to (2371742105)
>> log scanned up to (2371742105)
innobackupex: Backing up file '/home/mysql/3306/data/test/t.ibd'
>> log scanned up to (2371742115)
innobackupex: Backing up files '/home/mysql/3306/data/banping/*.ibd' (16 files)
......
110701 03:29:35 innobackupex: Connected to database with mysql child process (pid=22630)
>> log scanned up to (2371742526)
110701 03:29:39 innobackupex: Starting to lock all tables...
>> log scanned up to (2371742526)
>> log scanned up to (2371742526)
110701 03:29:51 innobackupex: All tables locked and flushed to disk
110701 03:29:51 innobackupex: Starting to backup .frm, .MRG, .MYD, .MYI,
innobackupex: .TRG, .TRN, .ARM, .ARZ, .CSM, .CSV and .opt files in
innobackupex: subdirectories of '/home/mysql/3306/data'
innobackupex: Backing up files '/home/mysql/3306/data/banping/*.{frm,MYD,MYI,MRG,TRG,TRN,ARM,ARZ,CSM,CSV,opt,par}' (17 files)
innobackupex: Backing up file '/home/mysql/3306/data/test/t.frm'
......
110701 03:29:53 innobackupex: Finished backing up .frm, .MRG, .MYD, .MYI, .TRG, .TRN, .ARM, .ARZ, .CSV, .CSM and .opt files
innobackupex: Resuming ibbackup
xtrabackup: The latest check point (for incremental): '2371742526'
>> log scanned up to (2371742526)
xtrabackup: Transaction log of lsn (2371741708) to (2371742526) was copied.
110701 03:29:55 innobackupex: All tables unlocked
110701 03:29:55 innobackupex: Connection to database server closed
innobackupex: Backup created in directory '/home/mysql/backup/data/3306'
innobackupex: MySQL binlog position: filename 'bin.000014', position 309836330 mysql,information_schema,performance_schema
innobackupex: MySQL slave binlog position: master host '', filename '', position
innobackupex: You must use -i (--ignore-zeros) option for extraction of the tar stream.
110701 03:29:55 innobackupex: completed OK!
前几天网上一个朋友反映碰到了网站性能的问题:
怎么看LINUX服务器是不是假死啊 我的网站老是出现可以PING 也可以登录SSH 就是网站不能访问啊 重启了 又可以了
从这个free的输出看内存有很多空闲,我请他输出vmstat 5的结果如下所示:
从这个输出可见内存使用较少,CPU有一定的使用,但并不高,IO有等待,初步判断瓶颈在IO上,后来登录到服务器查看,主要发现了三个问题,一是MySQL都用默认的参数在跑,内存给的是8M,有30多个schema。二是apache连接数配置的太少,很容易就溢出,三是文件系统有问题,需要用fsck来修复,不排除是磁盘故障。
于是我做了以下动作:
一是调整MySQL的配置,由于他的大部分表是MyISAM引擎,部分memory引擎,加大key_buffer_size到1G,毫无疑问这是MyISAM引擎最重要的参数,如果只能调整一个参数,那么就要增大这个值来给MySQL使用更多的内存而不是空闲在那里。另外我调整了两个参数如下:
#add by banping
max_connections = 1000
key_buffer_size = 1G
myisam_sort_buffer_size = 128M
实际上这个myisam_sort_buffer_size参数意义不大,这是个字面上蒙人的参数,它用于ALTER TABLE, OPTIMIZE TABLE, REPAIR TABLE 等命令时需要的内存。如果真的要提高排序用的内存,是要调整sort_buffer_size参数,但这个参数是为每个session分配的,要慎用。
二是增加了apache连接数的大小,通过ps -ef|grep httpd|wc -l命令查看,挂死的时候连接数达到了上限,它是使用prefork模式的,我修改了配置文件:
[root@centos conf]# vi extra/httpd-mpm.conf
<IfModule mpm_prefork_module>
StartServers 5
MinSpareServers 5
MaxSpareServers 10
ServerLimit 1000
MaxClients 350
MaxRequestsPerChild 0
</IfModule>
通过top命令可见一些httpd进程占用17M左右的内存,按照平均10M计算,那么350*10就是3.4G的内存,而服务器的内存配置是4G。可以估算他这个服务器目前最多也就能撑500个以下的连接,这时还要减少分配给MySQL的内存。
三是文件系统的问题,发现有些文件虽然是rw的属性,但是操作的时候是read only的,通过以下命令发现了一堆错误:
[root@centos ~]# dmesg |grep error
EXT3-fs error (device sdb1): ext3_lookup: unlinked inode 110350817 in dir #84690004
EXT3-fs error (device sdb1): ext3_journal_start_sb: Detected aborted journal
EXT3-fs error (device sdb1): ext3_lookup: unlinked inode 97047037 in dir #84689192
EXT3-fs error (device sdb1): ext3_lookup: unlinked inode 95801904 in dir #84689116
EXT3-fs error (device sdb1): ext3_lookup: unlinked inode 101257606 in dir #84689449
EXT3-fs error (device sdb1): ext3_lookup: unlinked inode 110563813 in dir #84690017
[root@centos ~]# cat /etc/fstabLABEL=/ / ext3 defaults 1 1LABEL=/home /home ext3 defaults 1 2LABEL=/var /var ext3 defaults 1 2LABEL=/boot /boot ext3 defaults 1 2/dev/sdb1 /web ext3 defaults 1 2tmpfs /dev/shm tmpfs defaults 0 0devpts /dev/pts devpts gid=5,mode=620 0 0sysfs /sys sysfs defaults 0 0proc /proc proc defaults 0 0LABEL=SWAP-sda5 swap swap defaults 0 0
这时可做的操作是先umount文件系统后执行fsck检查和修复再mount,再有问题可能就是磁盘的硬件故障了,但是考虑到风险我并没有做这个操作,而且要做必须先要停掉应用,否则是无法umount的。
之后的观察了几天发现在apache连接数超过256的时候仍然会挂掉,这时我怀疑是ServerLimit参数没生效,这个ServerLimit参数对应的是src/include/httpd.h文件中的#define HARD_SERVER_LIMIT 256这个变量值,这个值会限制连接数的大小,而不管你的MaxClients参数大小是多少。我修改了apache的配置后是restart而不是stop再start的,估计这样的操作由问题。
后来caoz知道了这个问题,他说apache连接溢出有多种可能,上去看了这台服务器,找到了连接数增长的原因是因为默认的KeepAlive On和Timeout 300导致连接没有及时的释放。改变这两个参数的值,连接数的压力就下来了。高人啊,一下就找到了本质的原因。然后他通过fsck修复了文件系统的问题。
通过这个事例我们可以发现很多事情的产生必然有背后的原因,做决定要有充足的依据做支撑,解决是表象背后的本质问题才是真正的解决问题。
HandlerSocket Plugin for MySQL的协议提供了比较丰富的功能,但是第三方开发者提供的客户端参差不齐,文档也大多不完善,所以应用起来还是有些累。
简单分析下HandlerSocket的协议:
1、OpenIndex
<indexid> <dbname> <tablename> <indexname> <columns> [<fcolumns>]
<indexid>编号(后续操作都会用到)
<dbname> 数据库名
<tablename> 表名
<indexname>索引名
<columns>需要返回结果的列名列表
[<fcolumns>]可选,用于过滤条件的列名列表,这个f是filter的意思
2、get data
<indexid> <op> <vlen> <v1> ... <vn> [LIM] [IN] [FILTER ...]
<indexid> opened index id
<op> 操作符 '=', '>', '>=', '<', and '<='
<vlen> 参数中提供的索引包含的列的个数
<v1> ... <vn>索引中包含的列,这个参数可以比实际索引中的列少
[LIM] <limit> <offset>
[IN] <icol> <ivlen> <iv1> ... <ivn> 列名,长度,值
[FILTER ...] <ftyp> <fop> <fcol> <fval> 过滤类型F或W(条件或循环),操作符,列名,值。
3、Updating/Deleting data
<indexid> <op> <vlen> <v1> ... <vn> [LIM] [IN] [FILTER ...] MOD
MOD <mop> <m1> ... <mk>
<mop> is 'U' (update), '+' (increment), '-' (decrement), 'D' (delete),
4、Inserting data
<indexid> + <vlen> <v1> ... <vn>
5、Authentication 这个是后来加上的,以前的旧版本没有权限控制
A <atyp> <akey>
详情参见官方文档:https://github.com/ahiguti/HandlerSocket-Plugin-for-MySQL/blob/master/docs-en/protocol.en.txt
对于PHP客户端,有个开源的项目在以下地址:http://code.google.com/p/php-handlersocket/
常见的调用方法如下:
1、新建一个对象
$hs = new HandlerSocket($hs_host, $hs_port);
2、打开索引
$hs->openIndex(23, $dbname, $table, 'index_id', 'return_column','filter_column');
3、简单查询
$value = $hs->executeSingle(23, '=', array($user_id), 9999999, 0);
4、复杂查询,含过滤条件
$value = $hs->executeSingle(23, '=', array($user_id), 9999999, 0,null,null,array(array('F','<',0,$maxid)));
以上是php-handlersocket 0.1.0版本实现的接口,0.2.0增加了IN过滤方式。
HandlerSocket查询速度还是很快的,而且比传统的NoSQL通过key get data的方式有更丰富的过滤条件。唯一的遗憾是没有提供排序功能。排序就要占用CPU资源,这对于HandlerSocket的设计初衷是违背的,所以也无可厚非。
技术组织
最近评论
历史归档
广告位



