图片分离小故障一例
有需求对某应用的图片和页面内容进行拆分。计划进行的很顺利,存储设备上成功分成了/vol/html和/vol/image,然后分别挂载在应用服务器上发布为html.domain.com和image.domain.com。但在恢复应用运行后出现一个问题:新图片的上传总是提示失败。
查看服务器上的日志,还好jvm-default.log记录的相当详细。上传程序首先在html.domain.com/dynamic/domain*/下创建一个tmp/,上传的图片*.jpg和缩略图s_*.jpg就暂存在该tmp/下。然后再mv到相应的image.domain.com/domain*/$date下。图片上传到tmp是成功的,特意选择一个比较偏僻的domain9,由系统来新建这个image.domain.com/domain*/$date,也成功了。
开发同事去检查程序,试图提供更详细的报错信息,然后发现报错的地方写的是if (!TmpPicReNameNewPic || !TmpSmallPicReNameNewSmallPic ) {*};
于是想到有一种可能,虽然存储还是同一个存储,但是分成了两次mount,或许linux系统就将该存储当成了两个磁盘,而rename方法在linux的实现是不可以跨磁盘操作的。见man文档:
oldpath and newpath are not on the same mounted filesystem. (Linux permits a filesystem to be mounted at multiple points, but rename(2) does not work across different mount points, even if the same filesystem is mounted on both.)
解决办法有两种,一种是修改程序,把rename改成真正的mv(即先cp,然后rm) ;另一种是想办法只挂载一次存储。考虑到这个猜测有可能不正确,折腾程序比较麻烦,正好这次图文拆分也不是彻底的分离到不同服务器上,而是存储上的两个路径而已。决定先mount NetAppIp:/vol/ /mnt;ln -s /mnt/html /www/html.domain.com;ln -s /mnt/image /www/image.domain.com;重启服务再测试上传,果然成功了!
awk的效率
偶然和某人谈到日志处理。最简单常见的需求,日志中访问量最大的前十个IP及其访问次数。
最常见的shell命令:cat access.log | cut -d ‘ ‘ -f 4 |sort|uniq -c|sort -nr|head
我最常用的awk命令:awk ‘{a[$4]++}END{for(i in a){print a[i],i}}’ access.log | sort -nr | head
对方表示上一种速度最快,而我说是下一种。
最后找到一个13G大小的access.log,用time命令分别检测命令用时。结果处理一个13GB的日志,shell花了13分钟,awk花了1分半钟……
读《基于动态内容的缓存加速技术》笔记
《程序员》2010年11月刊的86-89四页,刊登了F5售前技术顾问,原VIACDN(TOM的CDN部门,后来独立运营)架构师徐超的文章《基于动态内容的缓存加速技术——F5 Web Accelerator产品技术剖析》。
文章的前三分之一内容,主要是讲述RFC2616和一般浏览器、服务器的具体实现。已经比较熟悉了,略过……
然后进入关键内容。
“让浏览器缓存能够为动态页面工作”:
A. 修改Transfer-Encoding: chunked的内容输出Content-Length。
这个squid已经做到了。其实质就是在获取url的body时,累加每个chunk的size,直到碰上MSB为0的last-chunk标记。
B. 添加Last-Modified。
这个squid差一步。squid实质已经获取了文件在本地的mtime,但只在源header不存在last-modified的时候才用于LM-factor算法。
C. 删除Expires。
大概是为了统一成HTTP/1.1的header,所以选择了删除expires?或者就是纯粹了节省代码吧……
D. 修改Cache-Control。有Expires按这个设定max-age,或者按配置设定;有private的修改成public;添加must-revalidated。
这个squid用header_access和header_replace就能完成。
“Web Accelerator如何识别动态页面的更新并保持更新”:
A. 预加载。
这个通过squid的补丁html prefetching可以完成
B. 根据上一章节的修改结果,浏览器对”动态html”每次都可以发送IMS到F5。F5向源站请求该html,并比对缓存内容。
其他过程和一般的IMS过程一样,关键在比对缓存内容。如果这个”动态”只是html文字内容的更新,还是正常范围;如果”动态交互”的结果还包括其他CSS/JS/JPG/GIF的变动,这个功能就比较有用了。
根据预加载的功能,每次确认html时重新预比对。考虑到CSS/JS/JPG/GIF一般来说都是会输出Last-Modified的,这个比对返回结果应该比较快。都没问题的话,跳过这步;
如果有部分文件mtime也变动了,开始预加载,并另存为一个带有版本号的url(比如logo.gif;pv=***),用’,’而不能用’?’,因为?在浏览器上是不缓存的;
同时修改主文件html的内容,替换url链接为新版本号的url。最后发送这个新版本html给浏览器。
这种版本号控制的方法也节省了缓存更新时的删除操作IO,而统一交给LRU之类的完成。
想到LRU,一个疑问:当缓存不足时,假如logo.gif已经被prune出去了,那这个时候的F5怎么办?几个猜测办法:
1、浪费一点带宽和时间,以新版本号url的形式重新进入缓存;
2、用一个版本号/mtime的K-V数据库完成url版本控制;
3、F5作为特定类型给某些网站使用,就不考虑海量文件的问题。
最后,我还是觉得url版本控制这种事情,还是由网站开发人员来做CMS比较靠谱。
“反向动态代理”
上面的方式,确实保证了数据”实时”性,而且充分利用了浏览器缓存,但一次刷新引发F5和源站之间几十上百次的比对,还是比较郁闷的。所以当网站的”动态”结构比较清晰时,比如一个论坛,明确知道帖子列表变动就是因为有人发帖了;而且可以从发帖的POST请求url里推导出版面url的,可以采用这种技术。
一旦接收到POST请求,F5自动根据配置purge版面url的缓存。而不用去比对。
“MultiConnect技术”
因为浏览器对同一域名并发连接很少,所以F5可以自动替换html里的url,根据配置把image.x.com换成img1.x.com/img2.x.com/img3.x.com……前提是你的DNS上确实有这些解析。
不过要注意,域名太多也会拖慢浏览器速度的。dns解析需要时间。
总之,个人感觉这些技术还是比较现实的,但都用很强的应用场景针对性。呵呵~
resin-status
和apache、nginx一样,resin也自带了一个比较简易的status模块,只需要在resin.conf里配置就行了。在里添加如下一段:
<servlet-mapping servlet-class='com.caucho.servlets.ResinStatusServlet'> <url-pattern>/resin-status</url-pattern> <init enable="read"/> </servlet-mapping>
重启resin即可。
然后curl访问http://127.0.0.1:8080/resin-status就可以看到输出了。
用浏览器的话,大致如下图:
可以关注线程数、连接数、内存使用大小和请求处理数。
如果要做监控的话,直接从html里获取相应数值即可。
唯一需要稍微注意的是请求处理数。因为其他的都是AVERAGE,只有这个是COUNTER型的。要是想很方便的看到rps。可以采用如下方法查看:
[root@rtuku ~]# a=`curl -s http://127.0.0.1:8081/resin-status|awk -F/ '/Invocation/{print $NF}'`;sleep 1;curl -s http://10.168.168.56:8081/resin-status|awk -F/ '/Invocation/{print($NF-'$a'}' 718
这里比较有趣的是因为这行的数据本身是有个括号的,所以就有了各种各样的写法和报错了。一并贴上来,可以体会一下awk的系统变量/内部函数/字符类型的用法:
[root@rtuku logs]# a=`curl -s http://127.0.0.1:8080/resin-status|awk -F/ '/Invocation/{print $NF}'`;sleep 10;curl -s http://127.0.0.1:8080/resin-status|awk -F/ '/Invocation/{print $NF-'$a'}' awk: cmd. line:1: /Invocation/{print $NF-3949612)} awk: cmd. line:1: ^ syntax error [root@rtuku logs]# a=`curl -s http://127.0.0.1:8080/resin-status|awk -F/ '/Invocation/{print $NF}'`;sleep 10;curl -s http://127.0.0.1:8080/resin-status|awk -F/ '/Invocation/{print $NF-'"$a"'}' awk: cmd. line:1: /Invocation/{print $NF-4025373)} awk: cmd. line:1: ^ syntax error [root@rtuku logs]# a=`curl -s http://127.0.0.1:8080/resin-status|awk -F/ '/Invocation/{print $NF}'`;sleep 10;curl -s http://127.0.0.1:8080/resin-status|awk -F/ '/Invocation/{print $NF-"'$a'"}' 7607 [root@rtuku logs]# a=`curl -s http://127.0.0.1:8080/resin-status|awk -F/ '/Invocation/{print substr($NF,0,length($NF)-1)}'`;sleep 10;curl -s http://127.0.0.1:8080/resin-status|awk -F/ '/Invocation/{print $NF-'$a'}' 7927 [root@rtuku logs]# a=`curl -s http://127.0.0.1:8080/resin-status|awk -F/ '/Invocation/{print $NF}'`;sleep 10;curl -s http://127.0.0.1:8080/resin-status|awk -F/ '/Invocation/{print($NF-'$a'}' 7212
记录(2011-1-12)
之前经常看到一些稀奇古怪又很好玩的东西,转头就忘了,从今天开始,在这里记录一些工作用得上用不上能用得上不用用得上的名词,免得以后再忘了~~不定期更新。
—————————————————
2011-1-11
spread:分布式分组消息系统。官网:http://spread.org/
可以用来处理大规模集群的应用日志;(Fedora的spread-logging)
可以用来完成SQL的replication;(RepDB项目,试验期,无视)
可以用来完成数据同步(zope项目,不懂python,无视)
最后,只要能发送消息,那么也可以完成对集群的运维管理。(自己瞎想,不知道有没有现成的)
mogileFS:分布式文件系统,memcached的同门师兄弟。官网:http://www.danga.com/mogilefs/
包含有性能不亚于lighttpd的web服务器perbal,提供web、代理、负载均衡、缓存等多种功能;
包含有跨平台任务分发框架gearman,可以用于分布式计算的map-reduce调度,也可以用于一般的日志分析处理。
提供直接的nginx-mogilefs-module,但不支持fuse(即不能mount使用)。
适用于增量型小图片存储。
有时间深入学习perl再看。
HAproxy:4/7层负载均衡,功能比较完善。但公认的测评报告不多,到底能用到什么程度呢?官网:http://haproxy.1wt.eu/
Varnish:内存型反向代理缓存软件。比squid更充分的利用硬件和操作系统的新功能。不过对大容量缓存性能下降较快。
Apache Traffic Server:雅虎2009年转交给apache基金会的顶级开源项目,同样是缓存软件。
Squid3.2:从3.2开始,squid终于支持多CPU了,一定要测试一下。
puppet:轻量级集中式配置管理软件。官网:http://www.puppetlabs.com/
中文wiki:http://puppet.wikidot.com/
puppet北京用户组博客:http://www.comeonsa.com/
从系统镜像分发到服务状态检测,大型数据中心运维的一揽子计划~
————————————
2011-1-12
DRBD:分布式块设备复制软件。官网:http://www.drbd.org/
从2.6.33开始,DRBD进入linux主线kernel。用来实现集群数据的高可用。
常见于MySQL集群应用,可用性高于普通的replication。算是NetApp的mirror的“穷人”版本吧?
看到新浪研发中心博客中有使用DRBD的HA方案解决mooseFS的master单点问题的测试。
HTTP的auth请求模拟
开发同事需要在程序中调用一个“安全级别比较高”的url,起初没觉得有啥问题,我们wget或者curl的时候,按照标准url的格式(即’请求方法://用户名:密码@域名/文件路径’)写就完全OK了。不过很快同事转来了报错:
java.io.IOException: Server returned HTTP response code: 401 for URL: http://test:test1234@test.domain.com/interface/addcategory.php?parentid=2&id=2&name=gbox_%D0%C7%BC%CA%D5%F9%B0%D4&groupname=game&code=satrcraft&m=09286f9d135d5debe7052bea42a27eef
原来用的是IO的方式,我用telnet模拟一下,结果还真是这样:
[root@cms ~]# telnet test.domain.com 80 Trying 123.124.125.126... Connected to test.domain.com (123.124.125.126). Escape character is '^]'. GET http://test:test1234@test.domain.com/interface/addcategory.php?parentid=2&id=2&name=gbox_%D0%C7%BC%CA%D5%F9%B0%D4&groupname=game&code=satrcraft&m=09286f9d135d5debe7052bea42a27eef HTTP/1.0 HTTP/1.1 401 Authorization Required Date: Tue, 11 Jan 2011 03:39:37 GMT Server: Apache/1.3.37 (Unix) PHP/4.4.9 WWW-Authenticate: Basic realm="CMS-Testdotcom" Connection: close Content-Type: text/html; charset=iso-8859-1
查了一下HTTP协议,原来auth是走的另外一个header完成Authorization,其格式是Authorization: Basic ‘encoded_base64(user:passwd)’。服务器会自动的用decoded_base64()解析字符串得到真正的用户名和密码。原来wget和curl这些工具不单单是发个请求这么简单啊~~
重新试验,先计算test:test1234的base64值:
[root@cms ~]# echo test:test1234|openssl base64 dGVzdDp0ZXN0MTIzNAo= [root@cms ~]# telnet test.domain.com 80 Trying 123.124.125.126... Connected to test.domain.com (123.124.125.126). Escape character is '^]'. GET http://test.domain.com/interface/addcategory.php?parentid=2&id=2&name=gbox_%D0%C7%BC%CA%D5%F9%B0%D4&groupname=game&code=satrcraft&m=09286f9d135d5debe7052bea42a27eef HTTP/1.0 Authorization: Basic dGVzdDp0ZXN0MTIzNAo= HTTP/1.1 200 OK Date: Tue, 11 Jan 2011 05:21:32 GMT Server: Apache/1.3.37 (Unix) PHP/4.4.9 X-Powered-By: PHP/4.4.9 Connection: close Content-Type: text/html 2 || 2|| gbox_星际争霸 || satrcraft || game <br/>09286f9d135d5debe7052bea42a27eef<br/>2Connection closed by foreign host.
果然就可以了~~
mysqlreport指南
mysqlreport是mysql性能监测时最常用的工具,对了解mysql运行状态和配置调整都有很大的帮助。找了一些mysql的资料,发现大多数是关于php+mysql开发的,服务配置基本就是固定的几条。干脆找上mysqlreport的官网,啃下来这篇指南。翻译都是随着我个人的语言习惯,对直接能用mysql命令上看到结果的英文则保留下来。方便以后查找:
原文地址:http://hackmysql.com/mysqlreportguide
《mysqlreport指南》
本指南的写作目的是解释mysqlreport上出具的各种数据。通过指南,你可以在阅读完mysqlreport的报告后,完整的解释并且理解一个最根本的问题——而这也正是你使用mysqlreport的目的所在——(我的)MySQL服务器到底运行的怎么样?
当前的mysqlreport版本会自动生成一份尽可能完整的数据报表,最多可达14节121行。您不必再像以前那样输入各种各样的参数定义了。不过如果您的mysql服务配置上关闭了某些部分的话,报表的相关部分也会随之关闭。比如,你关闭了’query cache’,那么mysqlreport的第4节’Query Cache’也就不存在了。所以报表长度是浮动的。
为了便于理解和观察,本指南采用了对一份完整报表做出逐行解释的方法。下面就是将要被详细解析的那份报表,为了方便,在最前端加上了行号。
1 MySQL 5.0.3 uptime 0 0:34:26 Fri Sep 1 19:46:02 2006 2 3 __ Key _________________________________________________________________ 4 Buffer used 380.00k of 512.00M %Used: 0.07 5 Current 59.32M %Usage: 11.59 6 Write hit 97.04% 7 Read hit 99.58% 8 9 __ Questions ___________________________________________________________ 10 Total 98.06k 47.46/s 11 DMS 81.23k 39.32/s %Total: 82.84 12 QC Hits 16.58k 8.02/s 16.91 13 COM_QUIT 200 0.10/s 0.20 14 Com_ 131 0.06/s 0.13 15 -Unknown 82 0.04/s 0.08 16 Slow 5 s 0 0.00/s 0.00 %DMS: 0.00 Log: ON 17 DMS 81.23k 39.32/s 82.84 18 SELECT 64.44k 31.19/s 65.72 79.33 19 INSERT 16.75k 8.11/s 17.08 20.61 20 UPDATE 41 0.02/s 0.04 0.05 21 REPLACE 0 0.00/s 0.00 0.00 22 DELETE 0 0.00/s 0.00 0.00 23 Com_ 131 0.06/s 0.13 24 change_db 119 0.06/s 0.12 25 show_fields 9 0.00/s 0.01 26 show_status 2 0.00/s 0.00 27 28 __ SELECT and Sort _____________________________________________________ 29 Scan 38 0.02/s %SELECT: 0.06 30 Range 14 0.01/s 0.02 31 Full join 3 0.00/s 0.00 32 Range check 0 0.00/s 0.00 33 Full rng join 0 0.00/s 0.00 34 Sort scan 14 0.01/s 35 Sort range 26 0.01/s 36 Sort mrg pass 0 0.00/s 37 38 __ Query Cache _________________________________________________________ 39 Memory usage 17.81M of 32.00M %Used: 55.66 40 Block Fragmnt 13.05% 41 Hits 16.58k 8.02/s 42 Inserts 48.50k 23.48/s 43 Prunes 33.46k 16.20/s 44 Insrt:Prune 1.45:1 7.28/s 45 Hit:Insert 0.34:1 46 47 __ Table Locks _________________________________________________________ 48 Waited 1.01k 0.49/s %Total: 1.24 49 Immediate 80.04k 38.74/s 50 51 __ Tables ______________________________________________________________ 52 Open 107 of 1024 %Cache: 10.45 53 Opened 118 0.06/s 54 55 __ Connections _________________________________________________________ 56 Max used 77 of 600 %Max: 12.83 57 Total 202 0.10/s 58 59 __ Created Temp ________________________________________________________ 60 Disk table 10 0.00/s 61 Table 26 0.01/s Size: 4.00M 62 File 3 0.00/s 63 64 __ Threads _____________________________________________________________ 65 Running 55 of 77 66 Cache 0 %Hit: 0.5 67 Created 201 0.10/s 68 Slow 0 0.00/s 69 70 __ Aborted _____________________________________________________________ 71 Clients 0 0.00/s 72 Connects 8 0.00/s 73 74 __ Bytes _______________________________________________________________ 75 Sent 38.46M 18.62k/s 76 Received 7.98M 3.86k/s 77 78 __ InnoDB Buffer Pool __________________________________________________ 79 Usage 3.95M of 7.00M %Used: 56.47 80 Read hit 99.99% 81 Pages 82 Free 195 %Total: 43.53 83 Data 249 55.58 %Drty: 0.00 84 Misc 4 0.89 85 Latched 0 0.00 86 Reads 574.56k 0.6/s 87 From file 176 0.0/s 0.03 88 Ahead Rnd 4 0.0/s 89 Ahead Sql 2 0.0/s 90 Writes 160.82k 0.2/s 91 Flushes 1.04k 0.0/s 92 Wait Free 0 0/s 93 94 __ InnoDB Lock _________________________________________________________ 95 Waits 0 0/s 96 Current 0 97 Time acquiring 98 Total 0 ms 99 Average 0 ms 100 Max 0 ms 101 102 __ InnoDB Data, Pages, Rows ____________________________________________ 103 Data 104 Reads 225 0.0/s 105 Writes 799 0.0/s 106 fsync 541 0.0/s 107 Pending 108 Reads 0 109 Writes 0 110 fsync 0 111 112 Pages 113 Created 23 0.0/s 114 Read 226 0.0/s 115 Written 1.04k 0.0/s 116 117 Rows 118 Deleted 25.04k 0.0/s 119 Inserted 25.04k 0.0/s 120 Read 81.91k 0.1/s 121 Updated 0 0/s
报表抬头:第1行
本行包括三部分信息:MySQL版本,MySQL运行时间,服务器当前时间。显示版本是为了提醒有些功能可能这台mysql没有;显示运行时间是为了评估报表数据的精准度。事实上,如果一台mysql运行时间连几个小时都没有的,生成的很可能是一份扭曲并且充满误导意见的报表。甚至几个小时都不够,因为它可能是在半夜没啥访问量的时候运行的几个小时。所以建议最少要运行过一个24小时,时间越长越能反应真实情况。
在本例中,运行时间只有34分钟,所以报表也不具有真实的代表意义。
索引报表:3-7行
索引报表是第一个主要章节,因为索引(key或者说index)对于mysql数据库,是最最最重要的。虽然报表不可能直接告诉你这个库的索引好还是不好,但它能告诉你这个索引缓冲区(key buffer)被利用的怎么样了。
注意:本报表仅汇总默认的MyISAM表的共享key buffer信息,而不会管管理员自建的其他空间。
缓冲区使用情况:第4行
对于mysql,我们的第一个问题就是:到底用了多少key buffer?如果不太多,没问题~因为mysql只会在有需求的时候才分配系统内存给key buffer。也就是说,my.cnf中定义了’key_buffer_size=512M’,
不代表mysql启动时就创建一个512M大小的key buffer。
本行显示的,是mysql曾经使用过的key buffer峰值大小。而事实上,mysql应该用的更少,或者诡异的更多。这个更多的情况,mysql的术语叫“高水位”这个情况和my.cnf里的’key_buffer_size’是否足够大密切相关。当“水位”已经达到80-90%的时候,赶紧加大你的’key_buffer_size’吧。
注意:永远不用“担心”这个值超过95%,mysql文档指出,key buffer中的一部分会被mysql主程序用于内部数据结构,这些是mysqlreport无法统计的内容。所以,所谓的95%,其实已经是100%了……
当前情况:第5行
这行只有在mysql版本高于4.1.2时出现,因为之前mysql的’show status’中没有’key_blocks_unused’。这行数据显示的是mysql当前使用的key buffer大小。如果上行的used%太大的话,那么这行必然不会超过used,除非碰上那个传说中的bug了。综合这两行,相信对’key_buffer_size’的设置是否合理就有谱了~
本例中,mysql使用了60M的key buffer(12%),这就很不错,离满负荷运行还早着呢。
写命中:第6行
从本质上说,索引是基于内存的。因为访问内存的速度比硬盘快太多了。不过,mysql从磁盘里进行一点点读写操作总是不可避免的。
这行数据显示了写索引的效率(具体意思是:写入磁盘的key与写入内存的key的比值)。这个值没有什么参考答案,而是取决于业务类型。如果mysql主要执行的是update/insert之类的操作,那么正常比值接近0%;如果执行的select居多,那比值超过90%也是正常的。不过如果你看到的是一个负数,那说明mysql总是在往那个慢的要死的磁盘里写索引,这就很不妙了。
要想知道到底比值正常与否,请参考之后的DMS报表内容。
读命中:第7行
比写命中重要多了的就是读命中。同样,这个值就是读自磁盘的key与读自内存的比值。这个比值最好别低于99%!!再低就有问题了——很可能就是key buffer太小。mysql没法从内存里读到,只好找硬盘了……
当然,如果你刚重启过一次mysql,那在一两个小时内,命中率低一点也是正常的。
请求报表:9-26行
第二个主要章节。它展示了很多关于mysql在做什么以及做的怎么样的内容。请求(question)包括SQL查询(query),也包括mysql协议通讯。大家经常关注的一个性能是mysql的qps(每秒执行查询数)。不过从一个更广泛的眼光看来,这个衡量标准其实是很随意的……mysql需要处理很多其他的请求。本报表试图展现的,就是这么一个更完整的内容。
总值:第10行
本行第一列,回答自运行起mysql一共处理多少请求,第二列,得出自运行起平均每秒钟处理多少请求。大家可能以为第二列这个值就是我们想要的qps了。但mysql真的做了这么多事情么?继续往下看。
(再次提醒大家注意questions和queries的区别)
查询的总体分布报表(DTQ):第11-15行
所有的请求都可以被粗略的分入五类:数据操作语句(DMS),查询缓存命中(QC Hits),COM_QUIT,其他的COM_命令以及其他未知的东东。接下来的五行分别显示这些,从大到小排列。这样你可以一眼看出mysql最重要的任务是什么了。一般的说,DMS和QCHits是主角,COM_是必须的,额,路人甲……
再详细解释每行之前,提示一下,第三列的比值分母是上面那行的总值。比如本例中DMS占到了82.84%就挺不错的。
DMS包括:select/insert/replace/update/delete(其他的比较偏门,mysqlreport干脆就排除他们了)。正常的说,mysql最应该做的事情,就是这些DMS了。详细内容见17-22行。
QC Hits是mysql从查询缓存里直接获取结果的数量。我们梦寐以求的就是让这个命中率变高,因为这意味着mysql响应变的相当快。但这也意味着你必须接受一定的数据差异性。详细理由见QC报表中的insert/prune和hit/insert比值部分。
在本例中QC Hits达到了16.91%,看来很不错的样子。可别被这么一条数据迷惑了,38-45行的QC报表会给你一个完全不一样的结论。
COM_QUIT,嗯,凑数的。
COM_,如果这个值比较高的话,或许会有些问题,详见23-26行。
Unknown,理想情况下,有上面四个类别就够了。因为有时候,mysql处理了几个请求,却没有记录相应的操作数。所以Unknown有+-两种。+说明mysqlreport统计的多了,-就是少了。这个类别浮动性很大,某些特定情况下,可能会排名很靠前,不过最好还是在最底下吧。
慢查询:第16行
第16行非常重要,展示的是mysql执行的慢查询数。默认情况下,配置的’long_query_time’是10秒钟。事实上,大家都觉得这太长了,一般都改成1秒,甚至更短,mysql在版本5以后,支持到微妙us级别的。
配置的’long_query_time’会在’Slow’后面显示,默认8个字符,所以如果配置的是’9.999999 ms’,只会显示成’999.999 ‘,不过应该不会这么无聊吧~
理想情况下最好这里永远都是0,不过不太可能,多少还是有点。只要第三列的比例低于0.05%就行。
第四列,DMS中的slow比值;第五列,慢查询日志是否记录。强烈建议选择ON。
DMS:17-22行
和DTQ一样,第一行也是总值,其余内容也是动态排名。这部分内容可以解释mysql服务器偏重于什么类型:select还是insert,或者其他。一般来说会是select吧。明确这个问题,对我们理解其他数值有很大的帮助。比如说:一个insert型的mysql,他的写比率接近1.0,同时带来比较高的表锁。然后很可能用innodb表;一个select型的mysql则相反,读比率高,表锁少,而且很可能用的是MyISAM表。
本例就是一个select型的。65.72%的总请求是select(在DMS的比例提高到79.33%),显然我们可以朝着select的方向进行优化了。
COM_:23-26行
这部分内容都很直观,在mysql协议里都有,想com_change_db,一眼就知道是干嘛的。
如果在DTQ排名里COM_比较高,那说明mysql忙着干自己的事情而不是响应SQL查询。比如说,如果一台mysql的com_rollback高,可能糟糕了,你的事物回滚失败了。结合之前的DTQ报表分析这个东西吧~
一般这些东西不能出什么问题,不过时不时看一眼还是有必要的。
select/sort报表:28-36行
select和sort都是select_的内容。其中最主要的是29和31行:scan和full join。scan展示的是对全表进行扫描的select语句个数。full join和scan很像,除了它还出现于多表查询。程序联合多表进行全表扫描,听起来就慢的可怕……总之,对于这两个数值,只有更低,没有最低!
QC报表:38-45行
只有mysql版本支持QC并且my.cnf开启了QC的时候该报表才会出现。
内存使用:39行
如果内存使用的接近最大设置值,在更下面的Prune数据上也会有反应,因为QC里的查询会被踢出来。
内存碎片:40行
内存块碎片(block fragmnt)的详细解释参见《MYSQL手册》的5.14.3章节所述:
‘query_cache_min_res_unit’的默认值是4KB,大多数情况下足够用了。如果你有一个返回超小结果的海量查询,默认的块大小(即4KB)可能会导致大量的内存碎片,同样也浪费了很多空闲内存块。因为内存不足,碎片会强制删除(prune,我也不知道为啥不叫delete或者purge)QC里的部分内容。这时候你就得降低这个’query_cache_min_res_unit’设置。至于空闲块和QC的删除阀值,分别由’Qcache_free_blocks’和’Qcache_lowmem_prunes’定义。
内存碎片的计算方法是空闲内存块除以总内存块数。比值越大,碎片越多,10-20%就已经超出平均水平了。
本例的13.05%还是可以接受的,不过最好还是检查一下’query_cache_min_res_unit’是不是能调调~
Hits/Inserts/Prunes:41-43行
Hits是最重要的,它反映了select有多少是从QC里获得的应答,当然越多越好。至于insert和prune,或许从44行的比值中更好理解一下。之前有提到prune多说明qc太小,当然这只是一种可能而已。
本例中,只有55%的QC被利用,而碎片又不是太高。prune达到每秒16次,比QCHits高了一倍!打个不太恰当的(这话我加的,感觉比直接理解技术更难懂的比喻)比方:这台mysql的QC就像苹果树一样,苹果还没摘呢,树枝已经被砍掉了……
insert/prune和hit/insert比:44-45行
insert/prune是一个波动性的QC指标。一个稳定运行中的QC,insert进QC的查询数量应该大于prune掉的查询数量。而一个不稳定的QC,比值或许是1:1,甚至偏向prune。这说明两个问题:1、QC大小不够;2、mysql试图缓存一切,结果帮了倒忙~
如果是第一种情况,简单的加大QC大小就够了。然后再观察碎片和内存使用率的情况。
但更多的时候是第二种情况。因为QC设置里开启的默认type1就是要求mysql尽可能的缓存一切东西。
mysql官方说明里这么解释这个’type 1’的:“缓存一切查询结果,除非查询时使用’select sql_no_cache’方式”。可惜这个’sql_no_cache’基本没人用。另一个稍微好一些的方式是’type 2 demand’,解释如下:“只有在查询使用’select sql_cache’时才缓存查询结果”。这个type对开发人员要求比较多,因为他们得明确指出哪些要缓存,哪些不要。不过也没人比他们更清楚到底哪些是该缓存的啦~
hit/insert用来反映QC的有效性。理想情况是:mysql插入一批稳定的查询到QC里,然后源源不断的命中这批结果……所以,如果QC的有效性足够,这个比值应该是偏向hit的。如果不幸的偏向了insert,那说明QC其实没起到太大的作用。比如说1:1,一次insert用了一次hit,然后就被替换了,这完全违背了使用QC的初衷。不过还有更糟的,比如0.34:1,一次都没用上,就被prune掉了……
本例的QCHits不低,hit/insert却不高。再考虑到内存使用和碎片情况也还可以。或许真的有必要换成type2的DEMAND了~~
表锁报表:47-49行
表锁报表包括两行,一行是总数,一行是当前数。锁等待对于数据库来说永远是糟糕的事情。第三列的总比值反应了一个综述的情况,无论如何不能高过10%,否则肯定就带来一大堆的索引和慢查询问题!
表报表:51-53行
也是两行,一行是当前mysql打开表的个数、表缓存的使用率,一行是mysql运行以来的平均值。
这里有两个值比较重要。一个是表缓存使用率,哪怕高到100%都行。不过要是真高到100%了,可能你的’table_cache’设置已经不够了,赶紧加大吧。第二个是当前打开表的比率,这个也能协助判断’table_cache’设置是否合理。一般这个值应该小于每秒1次。不过一个负载比较高而又运行的还不错的mysql,可能能达到每秒打开7次表,依然保持100%的表缓存~
连接数报表:55-57行
如果最大连接数曾经接近过100%,请加大’max_connection’设置。不过事实上,默认的100已经足够绝大多数哪怕相当繁忙的mysql使用了,盲目加大这个设置其实不对。一个mysql链接持续1秒钟,100个就是足足100秒。所以如果连接数太高,或者说一直在慢慢涨,问题很可能在别的地方,比如慢查询、糟糕的索引、甚至DNS解析太慢。在修改这个数的事情,还是先去研究一下为什么100个还不够呢?
至于每秒连接数,只要mysql运行正常,高低无所谓。不过大多数mysql这个值在每秒5次以下~~
临时表报表:59-62行
mysql可以在内存、磁盘甚至临时文件上创建临时表。这三种情况分别对应下面的三条报告。这些数据没有标准值,不过必须要知道的是磁盘上的临时表示最慢的。mysql一般也避免在磁盘上创建临时表,除非达到了’tmp_table_size’的阀值。这个阀值会显示在内存(Table)那行的Size列后面。至于内存和临时文件的数值到底该多少,取决于mysql数据库的硬件配置。
线程、中断、流量报表:64-76行
这三个报表是最重要的,系统级别的问题不用多说了都……
这里面有一个需要注意的地方:66行的线程命中率。每个mysql的连接都是一个单独的线程。mysql启动时,只创建不多的几个线程和一个线程缓存,以节省不断创建和销毁线程的开销,哪怕这个开销不怎么明显。当mysql的连接数超过了线程缓存数(由’thread_cache_size’定义)时,MySQL开始出现线程抖动(‘thread thrash’)。为了接纳新的连接,mysql疯狂的创建新线程,结果自然是线程命中率大幅下滑。
线程抖动是问题么?Yahoo的Jeremy Zawondy在博客中写了如下一段话:
所以上面这个故事的教训就是:如果你的服务器老是接受一些快速连接,想办法加大你的线程缓存吧,直到你的`show status`命令里’threads_created’不再飙升为止!你的CPU绝对会感谢你的。线程缓存不是啥大问题。可是你解决完真的大问题后,它就是最大的问题了……(汗)
所以,如果真出现线程抖动的话,加大’thread_cache_size’吧。
比如本例,线程缓存命中率只有可怜的0.05%!基本意味着每个连接都是新创建了线程。不过看看报表其他的内容,这也是必然的:缓存里一个线程都没有(66行),201个线程被创建(67行),202个连接(57行)……
innodb缓冲池报表:78-92行
mysql版本5.0.2之后才在`show status`里加入了innodb_内容。所以这部分报表只在该版本之后有效。
innodb存储引擎的重要特性就是它把表数据和索引都缓存在缓冲池里。缓冲池内的页大小是16KB,可以包含各种不同的数据类型。本报表就展示了这些页的数值。
注:因为mysqlreport比较老,对innodb的采集分析不如myisam多。
使用率:79行
这个可以类比第4行的’key buffer used’。不过myisam引擎只缓存索引,而innodb也存数据。所以要想知道这个使用率的详细情况,还得看81-85行的内容。
显然,必须避免mysql运行到缓冲池溢出的地步。myisam溢出,只会导致性能下降(索引读写变慢),而innodb的溢出问题就多了,因为几乎所有东西都依赖缓冲池。所以最好还是配置好自增长缓冲池(‘auto-extending buffer pool’)吧。
读命中:80行
同样跟地7行的’key read hit’类似。不过对innodb来说这个数值更重要。缓冲池显示的是从内存读取的比例,所以一般都接近100%,绝大多数情况至少大于99.98%。
页:81-85行
各种各样的关于缓冲池中页的指标。比如82行的空闲页、83行的数据页、84行的杂页、85行的锁定页。
空闲页就是79行使用率的对立方。
数据页和空闲页一样是自描述的。我们没法知道这些页里有哪些数据类型。本行还有一列%Dirty,展示已经被修改过,但还没有被刷新到磁盘存储的数据页的比率。
另两样就更没什么可说的了。mysql手册上只是简单的这么描述这两样页:“用于管理分配行锁和自适应哈希索引导致的开销使用的页”和“目前正在读写、或者因为其他原因无法被刷新的页”。
读:86-89行
接下来的四行是关于innodb缓冲池的读情况的。86行是从内存读取的数量。在一个比较繁忙的innoDB引擎mysql服务器上,这个值应该非常大,因为innodb总是试图从内存里的缓冲池读取页。这个数值可以用来衡量innodb缓冲池的吞吐量。因为几乎所有inondb需要的东西都是在缓冲池里,所以缓冲池的读性能是越快越好。哪怕超过每秒200000次也不是不可能的。
87行的数值就小的多了。官方解释叫做“innodb无法从缓冲池获得而只能进行单页读取的逻辑读操作数”,其实就是从磁盘读的数量。
88行,随机读。官方解释“innodb启动的随机读取数。只有对表的大部分内容进行随机扫描的时候才会出现”。
89行,顺序读。官方解释“innodb启动的顺序读取数。只有进行全表扫描的时候才会出现”。全表扫描可不是什么好事,这个数值还是小点好。
写:90行
和86行类似,也可以用来衡量innodb的吞吐量。本行显示写的数量以及读写的比率。如果服务器主要操作是update和insert的话,这个值也会比较高。
刷新:91行
缓冲池的页刷新请求数。
空闲等待:92行
mysql手册上这么描述这个变量:
一般情况下,innodb缓冲池的写操作是后台运行的。不过,如果出现必须要读写一个页可偏偏没有可用的新页时,(innodb)就只能先等待页的刷新了。这个变量就是这些等待的总数。只要缓冲池的大小设置得当,等待数应该会很小。
innodb锁报表:94-100行
innodb的行锁变量是mysql5.0.3之后加入的。MyISAM引擎是表锁,而innoDB是行锁。所以当你使用innodb时这几个变量的值非常重要。
等待:95行
“等待某行解锁的累积次数”,最好是0次。
当前:96行
“当前正在等待解锁的行个数”,最好是0次。
时间分析:97-100行
98-100行显示了毫秒(ms)级行锁等待数据。分别是总值、平均值和最大值。同样最好是0次。
innoDB数据、页、行报表:102-121行
这部分报告,一般广泛的用于衡量innodb引擎的吞吐量指标。
数据:103-110行
第一部分,数据,列出了四种类型的操作:读、写、刷新(fsync)和等待(pending)。
第一个类型:读,指的是整个innodb引擎完成所有的数据读取次数——注意:不是整个数据读取字节数
或者类型,而是innodb完成的数据读取次数。
第二个类型:写,和读一样也是次数的统计。
第三个类型:刷新,同样的,innodb从内存写入磁盘的次数。这个值应该会比前两个小。
第四个类型:等待,又被分成了三行(108-110),分别是读、写、刷新的等待次数。
页:112-115行
这部分包括三种自描述类型:创建、读取、写入,分别用来表示缓冲池中页的创建、读取和写入的数量
和速率(即每秒操作数)。由于没有指出具体是哪种页,这几个数值也就是用来概述一下innodb引擎的吞
吐量罢了。
行:117-121行
最后一部分,行,只是一些比较泛泛的数值。包括对行的delete/insert/read/update操作。这些数值会比较大,而速率部分同样也是些概述参考……
结论
现在我们已经阅读甚至分析完了整个报表,可以对本例mysql做出一个总体的评价了。
总的来说,这个服务器运行情况还是不错的,几个关键指标都很好:key buffer只用了12%,DMS和QCHits占了请求的99%,也没有什么Com_问题,表锁也不错,表缓存只用了10%,连接数很低。
至于innodb引擎,服务器也在使用,但比重不大。目前这些innodb的状态变量反映出的情况看,一些正常。
不过还是有些优化的余地。首先一点,也是最重要的一点,就是查询缓存QC不太稳定;然后是线程缓存,建议调高’thread_cache_size’,知道线程缓存命中率回升到满意值~~
日志计算(awk进阶)
曾经用awk写过一个日志流量计算的单行命令。因为awk中没有sort函数,所以在中途采用了|sort|的方式,导致效率很低。在计算50GB+的日志时,运算时间慢的不可忍受。
设想了很多方法来加快运算,比如舍弃awk改用perl来完成,如下:
#!/usr/bin/perl use warnings; use strict; my $access_log = $ARGV[0]; my $log_pattern = qr'^.*?:(\d\d:\d\d):(\S+\s){5}\d+\s(\d+).+'; my %flow; my ($traffic, $result) = (0, 0); open FH,"< $access_log" or die "Cannot open access_log"; while (defined(my $log = <FH>)) { $flow{$1} += $3 if $log =~ /$log_pattern/; #print $1." ".$3." ".$flow{$1} * 8 / 300 / 1024 / 1024,"\n"; } close FH; foreach my $key ( sort keys %flow ) { my $minute = $1 if $key =~ /\d\d:\d(\d)/; $traffic += $flow{$key}; if ( $minute == '0' or $minute == '5' ) { $result = $traffic if $traffic > $result; $traffic = '0'; } } print $result * 8 / 300 / 1024 / 1024;
好吧,这个正则太过垃圾,请无视,但至少在管道和系统sort上浪费的时间还是大大的节省了的。
然后在CU上翻到一个老帖子,提供一个比较不错的awk思路,命令如下:
awk ‘!b[substr($4,14,5)]++{print v,a[v]}{v=substr($4,14,5);a[v]+=$10}END{print v,a[v]}’
这里用substr()跟指定FS相比那个效率高未知,不过采用!b++的方式来判断某时间刻度结束,输出该时刻总和,在顺序输入日志的前提下,运算速度极快(就剩下一个加法和赋值了)。
注意:此处b[]内不能偷懒写v,!b[v]++永远只会输出时刻的第一行数值。
不过真实使用时不尽如人意。逻辑上推导了很久没发现问题,结果在重新运行前面的perl时看了看while中的print,发现这个日志因为是从各节点合并出来的日志,其时间并不是顺序排列的!!
另,刚知道gawk3.1以上提供了asort()和asorti()函数,可以研究一下~
采用time命令测试一下awk ‘{a[substr($4,14,5)]+=$10}END{n=asorti(a,b);for(i=1;i<=n;i++){print b[i],a[b[i]]*8/60/1024/1024}}’ example.com_log | awk ‘{if($2>a){a=$2;b=$1}}END{print b,a}’
一个35G的日志文件只用了6分钟。
然后更简单的awk ‘{a[substr($4,14,5)]+=$10}END{n=asort(a);print a[n]*8/60/1024/1024}’ example.com_log
不过简单的运行发现只比前一种快不到10秒钟。而前一种还能输出峰值的时间,可读性更好一些~
resin与ipv6
一台nginx+resin的应用服务器出现大量的ipv6下的CLOSE_WAIT。重启后十五分钟就累积到了1500+。
调整resin的keepalive-timeout和nginx的proxy_read_timeout,没有丝毫改变。决定先关掉ipv6。
如果要关掉整个linux的ipv6,需要修改/etc/modprobe.conf然后reboot,显然没法在生产环境上直接操作,只能寻求应用监听上的办法。
修改sysctl -w net.ipv6.bindv6only=1,然后重启应用,赫然发现resin重启失败,只有perl进程,没有java进程了。改回0,java立刻启动成功。
google了一下,在http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=560044中看到了答案——原来java默认就是用ipv6。
增加resin/bin/wrapper.pl相关行如下:
$EXTRA_JAVA_ARGS=”-Djava.util.logging.manager=com.caucho.log.LogManagerImpl”;
$EXTRA_JAVA_ARGS.=” -Djavax.management.builder.initial=com.caucho.jmx.MBeanServerBuilderImpl”;
$EXTRA_JAVA_ARGS.=” -Djava.net.preferIPv4Stack=true”;#新增参数
重启,就只在ipv4上了。