转自:http://hi.baidu.com/mantianxing521/blog/item/89887a2cf7170de48b139949.html

昨天公司杨公让我在linux下给他个FTP空间,共享保存资料什么的。结果发现无法添加用户。

在linux下,我做为root登录,然后使用adduser填加用户.
系统提示:adduser: unable to open password file

 

 

[root@localhost root]# ls -l /etc/shadow
-r--------    1 root     root         1110 3月 11 10:13 /etc/shadow
我用chmod 600 /etc/shadow
提示:chmod: 正在更改 ‘/etc/shadow’ 的权限: 不允许的操作

我回去我的red hat上看了下.
/etc/shadow文件的权限也是只有root只读,但可以使用adduser命令.

adduser命令填加用户时,不对shadow文件就行写入操作吗??

建议用pwck检查一下passwd文件的合法性
[root@localhost root]# pwck
pwck: cannot open file /etc/passwd
shadow文件本来就是只读的。你检查一下passwd文件。
是不是加了权限位
lsattr /etc/passwd
chattr -i !$

[root@localhost root]# lsattr /etc/passwd
----i-------- /etc/passwd

从你的lsattr /etc/passwd执行结果来看, 你的文件加了权限保护
用chattr -i /etc/passwd去掉它
前几天按一个网上的系统加固的文章,做了一次加固,没想到是这个问题.

解决方法:
# cd /etc
# chattr +i passwd
# useradd foo
useradd: unable to open password file
# lsattr passwd
----i--- passwd
# chattr -i passwd
# lsattr passwd
- passwd
## chattr -i /etc/gshadow           //关键的一步

#useradd foo

 

 

         参考资料:

 

 

chattr可以防止关键文件被修改

  在linux下,有些配置文件是不允许任何人包括root修改的,为了防止被误删除或修改,

  可以设定该文件的"不可修改位(immutable)"。

  例如:

  chattr +i /etc/fstab

  如果需要修改文件则:

  chattr -i /etc/fstab

  以后再修改文件。

参考资料2:

chattr   [+-=]   [ASacdistu]   [文件或目录名称]
参数说明:
+-=:分别是"+"(增加)、"-"(减少)、"="(设定)属性
A:当设定了属性A,这个文件(或目录)的存取时间atime(access)将不可被修改,可避免诸如手提电脑容易产生磁盘I/O错误的情况;
S:这个功能有点类似sync,是将数据同步写入磁盘中,可以有效避免数据流失;
a:设定a后,这个文件将只能增加数据而不能删除,只有root才能设定这个属性;
c:设定这个属性后,将会自动将此文件压缩,在读取时自动解压缩。但是在存储的时候,会现进行压缩在存储(对于大文件很有用);
d:当dump(备份)程序执行时,设定d属性将可使该文件(或目录)具有dump功效;
i:这个参数可以让一个文件”不能被删除、更名、设定链接,也无法写入数据,对于系统安全有很大的助益
j:当使用ext3文件系统格式时,设定j属性将使文件在写入时先记录在日志中,但是当filesystem设定参数为data=journalled时,由于已经设定了日志,所以这个属性无效

s:当文件设定了s参数时,它会被完全移出这个硬盘空间
u:与s相反,当使用u配置文件时,数据内容其实还可以存在于磁盘中,可以用来取消删除

示例:
chattr   +i   /etc/shadow    //该文件无法更动了

chattr   -i   /etc/shadow   //解除该属性

       这个指令比较重要,尤其是在系统的安全方面。由于这些属性是隐藏性质,所以需要使用lsattr才能看到这些属性。
   
lsattr   [-aR]
参数说明:
-a   :将隐藏文件的属性也显示出来
-R   :连同子目录的数据一并显示出来

chattr   +i   test1
lsattr
posted @ 2010-08-16 18:23 小马歌 阅读(4242) | 评论 (0)编辑 收藏
 
linux学习步骤2008-08-26 15:061,linux不是玩具

如果你想系统的学习linux,你必需清楚的认识到这一点.现在有很多人,号称是linux爱好者,但是他们大部分的事情还是用windows 做,而linux只是作为一个随便玩玩的系统.他们懂得一些linux的基本操作,知道有linux这个玩艺,也因为经常听人家说linux好就咬定了 linux就是好.只要有人说linux比windows差,那么他们就奋起反驳,他们也看不起用windows的人,认为用windows的人水平低. 但是自己用电脑的大部分时间还是用windows并且把重要的文件都放在了windows分区,因为他们骨子里只把linux当做玩具,没有真正领悟 linux的精髓所在.如果你真正想系统的学习linux,想用linux有所作为,那么请忘记windows的思维方式,慢慢感受和习惯linux的操作方式,总有一天你会真正体验到他的奥妙所在,从而从自己内心深处喜欢他,使用他,而不是为了赶时髦或向人炫耀而使用他.

2,手头有一本好的入门教材吗

"如果你还没有好好的读完过一本linux安装及入门教材就不要到初学者论坛来问题!因为你连问问题的资格都没有! " 这是我的一个比较偏激的观点.

如果你现在开始下决心学习linux了,那么第一件你要做的事情是到书店去挑一本好书.而不是到论坛社区去问该用什么版本,该如何学习 linux.一本好的入门教材可以让你快速领悟linux的操作方式,系统的基本使用等等.而且都是前人总结经验写出来的,他可以帮你搭起一个学习 linux的框架,对linux有一个总体的认识.就好比建一撞大楼前先打好地基,搭好混凝土框架.以后就可以慢慢往这个框架里添砖加瓦,最后建起大楼. 而那些想急于学习的,不想看书,只想靠混论坛来学习linux知识的人,就好比建一撞大楼,今天建好第一层,然后第一层就要全部粉刷好,装修好,家具买好住进去,然后第二天再开始建第二层.这样没有整体规划的学习是学不好linux的,最后的结果是浪费自己的时间,也浪费别人的时间. 如果你不信,可以让实事去告诉你,以一年为限,你和你的一个朋友同时从0开始学linux,计算机基础差不多.你去买几本好书自己慢慢看边看边做实验,让你的朋友整天到各大论坛瞎混零散的学些linux知识.也许一开始,你的朋友会比你懂得多,但是一年以后你的水平肯定在你的朋友之上. 在初学阶段,不要随便在论坛上发问,多看书是根本,即使真的非问不可的问题,也要先自己动手察资料解决.实在不行再来问,高手不会鄙视菜鸟,因为每一个高手都是从菜鸟过来的,他们都知道菜鸟的苦衷.但是高手会看不起那些自己不愿动手动脑不会学习解决问题而只想得到现成答案的人.那样的人会被高手在心里暗骂成不配用linux的蠢货.

你需要以下几种书:
一本好入门教材->一本linux指令参考手册->linux系统管理手册->讲解linux系统原理的书.

一开始,你只需要入门教材就可以了.并且严格安教材的讲解去学习,不要一天到晚想着去装显卡驱动啦,装游戏啦,装软件啦,这些都没有必要.你就当你自己的电脑不能上网,只能通过看书学习.慢慢看书,稳抓稳打.慢慢地就融会贯通了.这时候你再到linux初学者论坛去看看,你在那里早已经是高手了.

另外,一开始你要选定一本入门教材,但是不能只看一本,因为有些书上讲的,可能是另外的书上没有的.因此,重点选一本教材看完.然后再看两三本入门教材浏览一下.作为对第一本书的知识的扩充和巩固. 在看书过程中,如果你有什么不懂的,不要急于上论坛问,自己认真看几次,实在不懂没有关系,把问题放在那里.不要钻牛角尖不懂不肯放手,这种精神是好的, 但是方法是错误的.你尽管学下去,也许你看完书本以后的内容,就对前面不懂的内容豁然开朗了.这是在学习linux中常有的情况.

另外,现在很多入门书籍是针对非技术型用户的.(我把那些对linux本身不感兴趣,只想用他来上网,听音乐和打字的人称为非技术型用户,而把对linux本身感兴趣的人称为技术用户)
这些针对非技术型用户的入门书籍几乎通篇都是插图,讲解的内容都是如何在图形界面下操作.这些书籍并不能帮你成为一个高手.反而会让你养成倚赖鼠标和图形界面操作的习惯以后很难改掉.因此不能看这些书,一定要买那些一开始就从系统基本机构基本命令开始讲解的书籍.我看过的第一本linux入门书籍共有十多章,但是他从第十一章开始才大致的讲解了一下图形界面的知识.我很感谢这本书,让我一开始就脱离了windows的思维方式,给我以后的进一步学习带来了很大帮助.

完成以上的内容,你就完成了建大楼打地基建混凝土框架的过程了.可以接下来进一步学习了.

3,你看完系统自带的文档了吗?

当你完成入门的过程后,就可以开始读其他的文档,然后到论坛的精华区看看,向你建起来的大楼框架中添砖头了.当然 ,首先要读的,还是系统自带的文档.

绝大多数linux发行版都自带非常详细的文档.比如我一直在用的redhat,他有从系统安装到系统安全,针对不同层次的人的详尽文档.静下心来,把这些文档读完,比看任何论坛的精华区都有用.书籍和文档就好比是你每天都离不开的一日三餐,论坛区的精华文档就好比是点心和水果.你可以不吃点心和水果,但是决不能不吃正餐.

当然,不能为看文档而看文档,你一定要边看边安文档中说的做试验验证.这样才印象深刻,否则看过就忘记了等于没有看.

另外,如果真心想学习linux就不要吝啬,也不要害怕丢失数据而不敢做实验.我建议你去买一个小的二手硬盘,然后放开手干.不要怕丢失数据而不敢做,如果你没有学会技能,将来做了linux系统管理员或者网络管理员到那时因为不会而丢了数据就是大事情了.

4,学习linux不是逛自由市场.

经常看到有人问用什么版本的linux好,其实只要你认真学习无论什么版本都挺好的.要知道,开发linux发行版的人都是通读过linux内核代码,对linux原理极其精通的人,而且每一个开发团队都对他的发行版做过测试后放出的.那些国际知名的大品牌更是如此. 因此,讨论什么版本好并无意义,关键是你是不是真心想学.不过,为了避免曲高和寡,最好选用的人多的版本,比如redhat manrake suse 等等. 国内有一两个linux版本做的也不错,但是国内的linux都是面向非技术型用户开发的,因此,如果你想成为高手,建议不要用国内的版本.
学习linux不是逛自由市场,选定版本就要静下心来学习.不要今天换版本明天要升级.这样对你没有好处.我见过一些人号称用过十几种甚至几十种linux,向人谈论起来头头是到,好像懂的很多,但是如果你让他去用linux搭建一个web服务器,做一个linux网关,他就什么都不会了.他们把时间都浪费在了版本的转换上了.

5,你能看懂英文文档吗?

谈论这个问题,我有点低气不足,因为我自己的英语很差.但是,至少我可以无障碍的读完一般的计算机文档.计算机英语很简单,只要熟悉了计算机专业英语,高中毕业的水平就可以轻松的阅读计算机文档了.如果你的英语实在太差了,连最简单的计算机英语文档都看不懂,那么在学习linux的同时,请赶紧学习英语.也许你说,你可以看翻译的文档,当我还是一个菜菜鸟的时候,也是这样认为的.但是,后来才发现,如果你想深入学习linux,看不懂因为文档实在是太难了.写的最好的,最全面的文档都是英语写的,最先发布的技术信息也都是用英语写的.即便是非英语国家的人发布技术文档,也都首先翻译成英语在国际学术杂志和网络上发表.你去看看各大软硬件生产商的官方网站,有哪一个不是用英语作为其主站的? 长期用windows的人会很不习惯这一点,装个软件还要看半天文档,应为windows用起来实在太简单了.但是如果你想学习linux就必需学会看各种文档,而大部分的文档都是用英语写的.我发现很多人甚至连man文档都不会看,有什么命令不会用了就跑到论坛上来问,还装出一副可怜相,乞讨一个命令的用法.有这些时间还不如自己看看man文档,即使你一个一个单词的翻译成中文再自己看都比问别人强,因为别人的回答再怎么详尽都比不上man文档详尽.安装一个新的软件时先看README,再看INSTALL然后看FAQ,最后才动手安装,这样遇到问题就知道为什么.否则,说明文档都不看,结果出了问题再来找答案反而浪费时间! 古人说欲速则不达就是这个道理!

6,忘记windows的思维方式

思想性的转变比暂时性的技术提高更有用,因为他能帮助你加快学习速度.现在很多人用linux.但是,他们用linux的方式完全是 windows的那一套方式.骨子里都是windows的思想.这样是不能领悟linux的精髓体验不到他的优越性的.我前几天看到一个朋友要把刚装了不到2天的mandrake 10 删除掉,我问他为什么,他说太慢了,受不了,还是用windows快.然后我留意了一下他用linux的方式,他的所有操作都带着windows的影子. 他连最基本的删除,移动文件这样的操作都要用鼠标,这样当然慢了!最后我只好说,你删除吧,你不适合用linux,linux不是这样用的.各位可以去看看那些linux高级用户,他们是怎样操作的.通常他们都是在X上开一个xterm或者rxvt终端,80%以上的操作都在这个终端下用命令完成,因为 linux的命令行十分强大,速度也十分快,简单的几个命令的组合就能完成非常复杂的操作.举一个例子:linux的常用命令find,去看看man文档,初学者一定会觉得太复杂而不原意用,但是你一旦学会了就对他爱不释手.他的功能实在太强了,在配合exec参数或者通过管道重定向到xargs命令和 grep命令,那么他能完成非常复杂的操作,如果同样的操作你用图形界面的工具来完成,恐怕要多花十几陪的时间.因此linux高手经常会说:如果没有 find和grep我们还怎么活.但是现在大部分的linux初级用户受到windows影响都喜欢用图形界面的工具来完成一些基本的操作,我并不是说图形界面不好.只是由于linux和windows设计思想的不同他们的操作方式也有很大不同.在windows下用图形界面操作会比敲命令快,但是 linux是一个命令行组成的操作系统,他的精髓在命令行! 无论图形界面发展到什么水平这个原理是不会变的!

7,入门以后多学命令

当你看完了一两本入门书籍后就应该扩充自己的知识,多学习linux命令,但是不要在初学阶段就系统的学习linux命令,初学阶段只要学会书上提到过的命令就可以了.单靠学习各种命令而成为高手是不可能的,但不会命令而成为高手也是不可能的.这就好比学英语,什么语法都不懂,只捧着单词手册背单词是学不会英语的,但是没有单词词汇量英语水平也提不高的.

在linux中学习命令的最好办法是学习bash脚本编程.bash脚本比起其他语言来学习简单,但是功能却十分强大.通过学习bash编程,能让你掌握大量的linux命令.另外,买一本命令参考手册是必要的,遇到不知道怎么用的命令可以随时查询,这要比察man文档快.特别适合英语不好,看不懂man文档的人.

在linux中,命令可分为系统基本命令和应用程序命令.系统基本命令是所有的unix类系统都支持的命令,走到哪都不变,只要是unix类系统上就肯定有.比如ls,rm,rmdir,cp,cd,mv,cat等等.这样的基本命令大约有200个,这些命令是一定要掌握的,我买了一本< redhat 7 指令参考手册>这本书非常好,他根据命令的常用指数分类,标明3颗星的为最常用命令,一定要掌握,两颗星的其次,1颗星的只要知道一下就可以了虽然现在都已经FC3了,但是经典的UNIX基本命令几十年来都没有变过!另外有些命令是linux特有的或者是某一个应用程序的可执行文件比如xmms播放器.这些只要知道就可以了,不知道也无所谓.有些命令比较少用,因此通常都记不住他的用法,对于这些命令至少要知道有这个命令,脑子里有印象,需要用的时候察一下手册就可以了,但是决不能不知道这个命令的存在!

8,学会管理系统

等到有了基本知识,也掌握了一定量的命令用法后,就可以进一步学习管理系统.这些内容入门书上会有,但是不会很深入.要深入的学习系统管理,就要去买一本类似 之类的书.认真的看书并做实验,可以让你很快的进步.学习配置各种网络服务器,用linux搭建网络,这些都是学习linux系统管理和网络管理的好方法. 到了这个阶段就可以经常上网察察资料,看发布软件的官方网站文档和FAQ,看看论坛精华区文章.但是不能本末倒置,多看书还是根本.书籍和官方文档可以让你系统的学习,但是论坛可以让你学到一些小知识,小技巧.我本人也经常到论坛上来看看,因为即便是一个新手,也可能会发现一些你所不知道的小技巧,看论坛可以学到这些小技巧.但是我看文档和看论坛的时间比不会小于4:1 . 可以把平时积累的问题一次在论坛上发问. 但是初学的时候不要频繁上论坛,因为你要问的问题都在书上写着,耐心一点,你很快就能看到了.

9,了结系统结构

等你有了一定的系统管理知识,知道了/etc下那些配置文件有什么用,知道了一般的网络服务器如何配置后,就可以去了解系统结构了. 了解系统结构不是要你去看什么文件夹放什么内容,而是要学习一些原理性的东西.比如系统是如何引导的,引导后启动了那些东西.系统中哪些是最基本的库文件,有什么用等等.学习系统结构的最好方法是自己做一个linux系统,再也没有什么能比自己做一个linux系统更能学习系统结构的了.LFS (linux from strach)可以教你从源代码自己编译一个系统.通过自己编译一个系统,你就可以了结linux系统结构,知道哪些文件是干什么用的,以及他们如何协调工作.当然,在你达到LFS水平之前还有很多事情要做,比如学会如何编译安装源代码发布的软件和编译新的内核等等.到了LFS水平,那么在大多数 linux论坛上你就可以被人称作"高手"了!到了这个地步,就相当于一撞大楼已经基本建好,但是还需要粉刷和装修,真正的细活还在后面!

永远记住天外有天,人外有人的道理.即便有了LFS水平,在那些搞linux系统开发,通读过linux内核代码的人看来你还是一个菜鸟.因此, 请时刻保持虚心的态度.即便是在 论坛上只有一颗星级别的人,也有可能是一个潜在的,真正的高手! 大多数真正的高手平时都在搞研发工作,哪里有时间上论坛啊! 倒是有很多大学还没毕业的学生,整天混在论坛上.

10,学习专业课程

如果你不是计算机专业的,而想把linux学好,就一定要学习专业课程.学习微机原理,操作系统,计算机网络等等专业课程是必需的.为什么同时开始学习linux,有些人学的非常快,不到半年就成了高手,有些人玩来玩去还玩不出名堂,玩了一两年还是菜鸟? 因为那些学得快的人有基础,他们都学过专业课程.同样一篇文档,没有基础的人可能看了三遍还不明白,基础扎实的眼睛扫两下就懂了! 这就是专业和非专业的差别! 因此,要想达到更高的境界就一定要学习基础的专业课程.

11,保持虚心学习的态度

我想再重复一遍天外有天,人外有人的道理!
保持虚心的学习态度不仅能让你学到更多知识,而且会让你受人尊重.

在linux的世界里,如果你想靠混论坛,发水贴,换几颗星星增加一下级别,然后再面对菜鸟说几句牛哄哄的话来赢得别人的尊重是不可能的.即便是一个刚入门的菜鸟,也能分辨你回答问题的质量,从而知道你到底有多少水平. 另外,当你成了"高手"的时候,你也能从"菜鸟"那里学到很多知识.因为有很多问题是你从来没有想过的,认为自己肯定会的,但是实际遇到的时候会有困难. 而"菜鸟"们往往更善于发现这类问题.这就是中国人常说的"教学相长"!

在linux的世界里,越是水平高的人越谦虚,因为他们知道自己还有很多不知道的,而那些半瓶水就想晃荡的人反而自以为是,因为他们还不知道自己还有很多不知道的! 去看看,www.linuxforum.net 嵌入式开发和UNIX版块的牛人,他们很多都是有过好几年linux方面的工作经验,精通linux和unix的好手,但是每个人都保持着非常谦逊的态度,这些人是值得尊敬的
posted @ 2010-08-06 12:37 小马歌 阅读(1865) | 评论 (0)编辑 收藏
 

本书若不讲解一章关于连接到MySQL的应用程序优化的内容,那就不能算完整,因为人们常常把一些性能方面的问题都归咎到MySQL身上。书里面我们更多地是讲到MySQL的优化,但是,我们不想让你错过这个更大的图景。一个糟糕的应用设计会使你无论怎么优化MySQL也弥补不了它带来的损失。实际上,有时候对于这类问题的答案是把它们从MySQL上脱离开来,让应用自己或其他工具来做这些事情,这样或许会有较好的性能表现。

本章不是构建高性能应用的参考书,我们只是希望通过阅读这一章让你避免那些常见的会伤及MySQL性能的小错误。下文中我们以Web应用为主要讲解对象,因为MySQL主要是用在Web应用上的。

1 应用程序性能概述

对于更快性能的追求开始时很简单:应用响应请求花费了太长的时间,你总要为此做点什么吧。然而,真正的问题是什么呢?通常的瓶颈是缓慢的查询、锁、CPU饱和、网络延时和文件I/O。如果应用配置错误,或者不恰当地使用资源,以上任何一个因素都会引出一个大问题。

    1.1找出问题的根源

第一个任务是找出"肇事者"。如果你的应用具备了显示系统运行概况的功能,这做起来就简单了。如果你已经做到了这一步,但还是没法找出引起性能低下的原因,那你就要增加更多的概况信息的调用,去找出那些要么缓慢要么被多次调用的资源。

如果你的应用因为CPU高占用率而一直等待,并且应用里有高并发性,那我们提到过的"丢失的时间"可能就成问题了。鉴于此,有些时候在有限的并发条件下生成应用的概况信息是很有用的。

网络延时会占用大块的时间,哪怕是在局域网里。应用层面的概况信息已经包括了网络延时,因此,你应该在概况系统里看到网络往返延时带来的影响了。举例来说,如果一个页面执行了1 000个查询,即使每次只有1毫秒的延时,那累加起来也有0.5秒的响应时间,这对高性能应用来说已经是个很大的数目了。

如果应用层面概况信息收集得很充分,那就不难找出问题的根源。如果还没有内置概况功能,那就尽可能地加上它。如果你无法添加这个功能,那也可以试试第76页的"当你无法加入概况信息代码时"里提供的那些建议。这个总比钻研像"什么引起应用变慢"那样没头绪的理论设想要更快更容易。

    1.2寻找常见问题

同样的问题我在应用里一次又一次地遇到,其原因往往是人们使用了设计糟糕的原有系统,或者采用了简化开发的通用框架。虽然这在某些时候能让你在开发一些功能时变得方便又快速,但它们也给应用增加了风险,因为你不知道它们底下是怎么工作的。这里有一张清单你应该逐个检查一下:

在各个机器上的CPU、磁盘、网络和内存资源的使用情况如何?使用率对你而言是否合理?如果不合理,就检查那些影响资源使用的应用的程序基础。配置文件有时就是解决问题的最简单方法,举例来说,如果Apache耗光了内存,那是因为它创建1 000个工作者进程,每个工作进程需要50MB内存,这样,你就可通过配置文件配置这个应用能申请的Apache工作者进程数。你也可以配置系统,使之创建进程时少用些内存。

应用是否真正使用了它所取得的数据?一个常见的错误是:读取了1 000行数据,却只要显示10行就够了,其他990行就丢弃了(然而,如果应用缓存了余下的990条记录供以后使用,那么这可能是特意做的优化)。

应用里是否做了本该由数据库来做的处理?反之亦然。有个对应的例子是:读取了所有行的数据,然后在应用里计算它们的总数;以及在数据库里做复杂的字符串处理。数据库擅长于计数,而应用的编程语言擅长于正则表达式。你该使用正确的工具去干正确的活。

应用里执行了太多的查询?那些号称能"把程序员从SQL代码里解救出来"的ORM(Object-Relational Mapping)就因此常被人们责备。数据库服务器是被设计用来匹配多表数据的,因为要移除那些嵌套循环,代之以联接(Join)来做同样的查询。

应用里执行的查询太少了?我们只知道执行了太多的查询会成为问题。但是,有时"手工的联接"和与其相似的查询是个好主意,因为它们可以更加有效地利用缓存,更少的锁(尤其是MyISAM),有时当你在应用的代码里使用一个散列联接时(MySQL的嵌套循环的联接方法往往是低效的),查询的执行速度会更快。

应用是不是在毫无必要的时候还连到MySQL上去了?如果你能从缓存里读取数据,就不要去连数据库了。

应用连接到同一个MySQL实例的次数是不是太多了?这可能是因为应用的各个部分都各自开启了自己的数据库连接。有个建议在通常情况下都很对:从头到尾都重用同一个数据库连接。

应用是不是做了太多的"垃圾"查询?一个常见的例子是在做查询前才去选择需要的数据库。一个较好的做法是连接到名称明确的数据库,并使用表的全名做查询。(这样做,也便于通过日志或SHOW PROCESSLIST去查询情况,因为你可以直接执行这些查询语句,无需再更改数据库)。"准备"数据库连接又是另一个常见的问题,特别是Java写的数据库驱动程序,它在准备连接时会做大量的工作,它们中的大多数你都可以关闭。另一种垃圾查询是SET NAMES UTF8,这纯粹是多此一举(它无法改变客户端连接库的字符集,它只对服务器有影响)。如果你的应用已确定在多数任务下使用的是某一个字符集,那你就可以避免这样无谓的字符集设置命令。

应用使用连接池了吗?这既是好事情也是坏事情。它限制了连接的数量,这在连接上查询数不多的情况下(Ajax应用就是个典型的例子)是有利的;然而,它的不好的一面是,应用会受限于使用事务、临时表、连接指定的设置和定义用户变量。

应用使用了持久性连接吗?这样做的直接结果是会产生太多的数据库连接连到MySQL上。通常情况下,这是个坏主意,除了一种情形:由于慢速的网络导致MySQL的连接成本很高,如果每条连接上只执行一两个快速的查询,或者频繁地连接到MySQL,那样你会很快用完客户端的所有本地端口(更多内容请查看第328页的"网络配置")。如果你正确地配置了MySQL,根本不需要持久性连接,可以使用"跳过名称解析"来防止DNS的查找,并确认该线程的优先级足够高。

即使没有使用,应用是不是还打开着连接?如果是,特别是当这些连接连向多台服务器时,它们可能占用了其他进程需要的连接。举例来说,假设你连接到10台MySQL服务器。由一个Apache进程占用10个连接数,这不是个问题,但是它们中只有一条连接是在任何指定时间里做着一些操作,而其他9条连接绝大多数时间都处于睡眠状态。如果有一台服务器响应变得迟缓,或者网络延时变长,那其他几台服务器就遭殃了,因为它们根本没连接可用。对于这个问题的解决办法是控制应用使用数据库连接的方式。

举例来说,你可以在各个MySQL实例中依次做批量操作,在向下一个MySQL发起查询前,关闭当前的所有连接。如果你要的是时间消耗很大的操作,比如调用一个Web Service,可以先关闭与MySQL的连接,等这个耗时的调用完成后,再打开MySQL的连接,完成剩余的需要在数据库上操作的任务。

持久性连接与连接池的不同点比较模糊。持久性连接有与连接池相同的副作用,因为在各种情况下重新使用的连接往往都带有状态。

然而,连接池并不总是导致许多连接到服务器的联接,因为它们是队列化的,并在各进程间共享这些连接。在另一方面,持久化连接是基于每个进程来创建的,无法被其他进程所使用。 与持久性连接相比,连接池在连接策略上有更多的控制。你可以把一个连接池配置成自动扩充的,但是通常的做法还是当连接池满的时候,新的连接请求都被放在队列里等待。这使得这些请求都在应用服务器上等待,总好过MySQL因为连接太多而超载。 有太多的方法使查询和连接更加快速,一般性准则是避免把它们放在一起,胜于试着把它们加速。

2 Web服务器的议题

Apache是Web应用中使用最广泛的服务器软件。在各种用途下,它都能运行良好,但如果使用得不恰当,它也会占用大量的资源。最常见的一个情况是让它的进程活动了太长的时间,并把它用在各种不同类型的任务下却没有做相应的优化。

Apache经常在prefork配置项里使用mod_php、mod_perl、mod_python。预分叉(Prefork)是为每个请求分配一个进程。因为PHP、Perl和Python等脚本语言运行起来很费资源,每个进程占用50MB或100MB内存的情形也不罕见。当一个请求处理完后,它会把绝大多数内存归还给操作系统,但不会是全部。Apache会让这个进程保持在运行状态,以处理将要到来的请求。这就意味着如果这个新来的请求只是为了获得一个静态文件,比如一个CSS文件或一张图片,你都需要重新启用那个又"肥"又"大"的进程来处理这个简单请求。这也是为什么把Apache用作多用途Web服务器是件危险的事情。它是多用途的,若你对它进行了有针对性的配置,它才会有更好的性能表现。

另外有个主要的问题是如果你打开了Keep-Alive参数项,进程就会长时间地保持忙碌状态。即使你不这么做, 有些进程也会这样。如果内容是像"填鸭"一样传给客户端的,那这个读取数据的过程也会很漫长。

人们也经常犯这样的错误:按Apache默认开启的模块来运行。你可以按照Apache使用手册里的说明,把你不需要的模块都关闭掉,做法也很简单:查看Apache的配置文件,把不需要的模块都注释掉,然后重启Apache。你可以从php.ini文件中把不需要的PHP模块都移除。

如果你创建了一个多用途Apache才需要的配置当作Web服务器来用,你最后可能会被众多繁重的Apache进程所拖垮,这些进程纯粹浪费你的Web服务器上的资源。而且,它们会占用大量与MySQL的连接,以至于也浪费了MySQL的资源。这里有一些方法能给你的服务器"减负":

不要把Apache用作静态内容的服务,如果一定要用,那也至少要换个另外的Apache实例来处理这些事情。常见的替代品有lighttpd和nginx。

使用一个缓存代理服务器,比如Squid或Varnish,使用户请求无须抵达Web服务器后才能被响应。即使在这个层面上你无法缓存所有的页面,你也能缓存大部分页面,并通过Edge Side Includes(ESL,http://www.esi.org)技术把页面上的小块动态部分放到缓存的静态部分里。

对动态内容和静态内容都设置过期策略。你可以使用缓存代理软件,像Squid,去验证内容的明确性。Wikipedia就是用这样的技术在缓存里移除内容已发生变化的文档。

有时你可能需要改变一下应用,使它能使用更长的超期时间。举例来说,如果你告诉浏览器要永久缓存CSS和JavaScript文件,然后又对这个网站静态HTML文件做了一些修改,这样这些页面的显示效果可能会变得很糟。对此,你需要使用一个唯一的文件名对每次修订后的页面文件都作一个明确的版本标记。举例来说,你可以自定义你的网站发布脚本,把CSS文件复制到/css/123_frontpage.css目录下,这里的123就是Subversion里的修订号。你也可以用同样的方法来处理图片文件-- 不要重用原来的文件名,否则,即使你更新了文件内容,页面不会再被更新,不管浏览器要将原来的页面缓存多久。

不要让Apache与客户端做"填鸭"式通信。这不仅仅是慢,而且很容易招致拒绝性服务攻击。典型地,硬件化的负载平衡器会处理好缓存,Apache就能很快地结束响应,然后让负载平衡器从缓存里读出数据去"喂"客户端。你也可以使用lighttpd、Squid,或者设为事件驱动模式下的Apache作为应用的前端。

开启gzip压缩。现在的CPU很廉价,它可以用来节省大量的网络流量。如果你想节省CPU周期,那可以使用轻量级的Web服务器,比如lighttpd,来缓存和提供压缩过的页面。

不要将Apache上的长距离连接配置为"保活"(Keep-Alive)模式,因为它会使Apache上臃肿的进程长时间处于运行状态。代替的方案是,用一个服务端的代理来处理"保活"的连接,使服务器免受这类客户端的伤害。如果将Apache与代理之间的连接方式设为"保活",那是不错的主意,因为代理仅使用几个连接从服务器上读取数据。下图说明了以上两者的差异。

以上这些策略应该可以帮助Apache减少进程的使用数,使你的服务器不会因为太多的进程而崩溃。然而,有些具体的操作仍然会引起Apache的进程长时间地运行,吞掉大量的系统资源。有一个例子就是查询外部资源时具有很高的延迟,比如访问一个远程Web服务器。这样的问题还是无法用上述那些方法来解决。

    2.1找出最佳并发数

每个Web服务器都有它的一个最佳并发数--它的含义是服务器能同时处理的并发连接数目,它们既能尽可能快地处理客户端请求,又不会使服务器过载。这个"神奇的数目"需要做多次的尝试-失败的反复才能得到,相比于它能带来的好处,这还是值得一做。

对于大流量的网站而言,Web服务器同时处理几千个连接是件很平常的事情。然而,这些连接中只有很少的一部分需要主动地去处理请求,而其他那些都是读取请求、文件上传、"喂"内容,或者仅仅等待客户端的下一步请求。

并发数增加时,服务器会在某一点上达到它的吞吐量顶峰,在此之后,吞吐量会变得平稳,往往还会开始下降。更重要的是,系统的响应时间(延迟)开始增加。

想要知道究竟,就要设想如果你只有一颗CPU,而服务器同时接收到100个请求,接下来会发生什么?假如一个CPU秒只能处理一个请求,而且你使用了一个完美的操作系统,没有任务调度的开销,也没有上下文切换的开销,那么这些请求总共需要100个CPU秒才能完成。

那么,怎样去做才是处理这些请求的最好办法?你可以把它们一个接一个放进队列里,或者对它们进行并行处理,每个请求在每一个轮回中都获得一样多的处理时间。这两种方式里,吞吐量都是每一秒一个请求。然而,如果使用队列,平均延迟有50秒(并发数=1),如果并行处理,那延迟有100秒(并发数=100)。在实际环境下,并发处理方法的平均延迟还会更高,因为其中还有个切换开销。

对于高CPU占有率的工作负载而言,其最佳并发数就是CPU(或者是CPU里的核)的数目。然而,进程不总是可以运行的,因为它们会执行阻塞式调用,比如I/O、数据库查询和网络请求等。因此,最佳并发数往往会多于CPU数目。

你可以估计最佳并发数,但是这需要精确的分析模型。通常情况下,还是通过实验的方法比较容易,你尝试着不同的并发数,然后观察系统在降低响应时间前,能达到多大的顶峰吞吐量。

3 缓存

缓存对于高负载的应用而言极其重要。一个典型Web应用里,直接提供服务要比使用缓存(包括缓存校验、作废)多生成很多内容,所以,缓存能够将应用的性能提高好几个数量级。这个技巧的关键在于找出缓存粒度和作废策略的最佳结合点。同时,你需要决定缓存哪些内容,在哪里缓存。

一个典型的高负载应用有许多层的缓存。缓存不仅仅发生在你的服务器上:它出现在整个流程的每一个步骤上,包括用户的Web浏览器里(这就是网页头部的有关作废设置内容的用途)。通常而言,缓存越靠近客户端,就越能节省更多的资源,更加高效。一副图片从浏览器缓存里读出要好于从Web服务器的内存里读取,而后者又好于从服务器的磁盘上读取。每一种缓存都其独有的特性,比如尺寸、延时等,在接下来的章节里我们将对它们逐一进行叙述。

你可以把缓存想象成两大类:被动缓存和主动缓存。被动缓存除了保存和返回数据不做其他事情。当你从被动缓存那里请求一些内容时,它要么给你需要的结果,要么告诉你"你要的数据不存在"。一个被动缓存的例子就是memcached。

相反地,主动缓存在找不到请求的数据时,它会做点别的事情。一般就是把你的请求传递给应用的某一部分--它能生成请求所需要的内容,然后主动缓存就会存储这部分内容,并返回给客户端。Squid缓存代理服务器就是一个主动缓存。

当设计应用时,你总希望你的缓存是主动型(也叫透明型)的,因为对于应用,它们可以隐藏"检查-生成-存储"这个逻辑。你可以在被动缓存之上构建你的主动缓存。

缓存并不总是有用, 你需要确定缓存是不是真地提高了系统的性能,因为它可能一点用处也没有。举例来说,在实际应用中,从lighttpd的内存中读取内容要比从缓存代理那里读取快一些。如果那个代理的缓存是建于磁盘上的,那结论会更明显。 这个原因很简单:缓存也有自己的运行开销,它们主要检查缓存的开销和提供被命中缓存内容的开销,另外还有将缓存内容作废和保存数据的开销。只有当这些开销的总和小于服务器生成和提供数据所要的开销时,缓存才有用。

如果你知道所有这些操作的总开销,你就能计算缓存能起多大的作用。没有缓存时的开销就是服务器为每个请求生成数据所需要的总开销。有缓存时的开销就是检查缓存的开销,加上缓存没命中的可能性乘以生成这些数据的开销,再加上缓存命中的可能性乘以从缓存里取出这些数据的开销。

如果有缓存时的开销小于没缓存的时候的开销,那使用缓存就可以提高系统性能,但是也不能保证肯定是这样。记在脑子里的一个例子就是从lighttpd内存里读取内容的开销要比代理从磁盘缓存上读取的开销要小,一些缓存总会比另外一些便宜。

    3.1在应用之下的缓存

MySQL服务器有它自己的内部缓存,你也可以构建你自己的缓存和汇总表。你可以自定义缓存表,以便于更好地将它用于过滤、排序、与其他表做联接、计数,以及其他用途。缓存表比其他应用层的缓存更加持久,因为它们在服务器重启后还会继续存在。

    3.2应用层面的缓存

典型的应用层面的缓存一般都是将数据放在本机内存里,或者放在网络上的另外一台机器的内存里。

应用层面的缓存一般要比更低层面的缓存有更高的效率,因为应用可以把部分计算结果存放在缓存里。因而,缓存对两类工作很有帮助:读取数据和在这些读取数据之上做计算。一个很好的例子是HTML文本的各个分块。应用能够产生HTML段落,比如头条新闻,然后将它们缓存起来。随后打开的页面里就能将这些被缓存起来的头条新闻直接放到页面上。通常来讲,缓存之前处理的数据越多,使用缓存之后能节省的工作量也越多。

这里有个不足之处就是缓存的命中率越多,要提高它而花费的钱就越多。假如你需要50个不同版本的头条新闻,能根据用户所在的不同地域来显示不同的头条。你需要有足够的内存来保存这全部50个版本的头条新闻,任何一个给定版本的头条被请求得越少,那它的作废操作也会越复杂。

应用缓存有许多种类型,以下是其中的一部分:

  本地缓存

这种缓存一般都比较小,只存在于请求处理时的进程内存空间里。它们可用于避免对同一资源的多次请求。因此,它也没什么精彩之处:它往往只是应用程序代码里的一个变量或一个散列表。举例来说,如果需要显示用户名,而你只知道用户ID,于是就设计一个函数叫get_name_from_id,把缓存功能放在这个函数里,具体代码如下:

代码片段

如果你使用的是Perl,那么Memoize模块就是缓存函数调用结果的标准办法:

代码片段

  本地共享内存式缓存

这种缓存大小中等(几个GB)、访问快速,同时,难于在各机器间同步。它们适用于小型的、半静态的数据存储。举例来说,像每个州的城市列表、共享数据存储里的分块函数(使用映射表),或者应用了存活时间(Time-to-live,TTL)策略的数据。共享内存的最大好处是访问时非常快速--一般要比任何一种远程缓存要快很多。

  分布存内存式缓存

分布式内存缓存的最著名的例子是memcached。分布式缓存比本地共享缓存要大,增长也容易。每一份缓存的数据只被创建一次,因为不会浪费你的内存,当同一份数据在各处缓存时也不会引起数据一致性问题。分布式内存擅长于对共享对象的排序,比如用户信息文件、评论和HTML片段。

这种缓存比本地共享缓存有更高的延迟,因此最有效的使用它们的方法是"多取"操作(比如在一次往返时,读取多个对象数据)。它们也要事先规划好怎么加入更多的节点,以及当一个节点崩溃时该怎么做。在这两种情形下,应用都要决定如何在各节点间分布或重新分布缓存对象。

  磁盘缓存

磁盘是慢速的,所以,持久性对象最适合做磁盘缓存。对象往往不适合放在内存里,静态内容也是(比如预生成的自定义图片)。

非常有效地使用磁盘缓存和Web服务器的技巧是用404错误处理过程来捕捉没命中的缓存。加入你的Web应用要在页面的头部显示一个用户自定义的图片,暂且将这个图片命名为/images/welcomeback/john.jpg。如果这个图片不存在,它就会产生一个404错误,同时触发错误处理过程。接着,错误处理过程就生成这个图片,并存放在磁盘上,然后再启动一个重定向,或者仅仅把这个图片"回填"到浏览器里,那么,以后的访问都可以直接从文件里返回这个图片了。 你可以将这项技巧用于许多类型的内容,举例来说,你用不着再缓存那块用来显示最新头条新闻的HTML代码了,而把它们放入一个JavaScript文件里,然后在页面的头部插入指向这个js文件的引用。

缓存失效的操作也很简单:删除这个文件就可以了。你可以通过运行一个周期性的任务,将N分钟前创建的文件都删除掉,来实现TTL失效策略。 如果想对缓存的尺寸做限制,那你可以实现一个最近最少使用(Least Recently Used,LRU)的失效策略,根据缓存内容的创建时间来删除内容。 这个失效策略需要你在文件系统的挂接(Mount)选项上开启"访问时间"这个开关项。(实际操作时忽略noatime挂接选项来达到这个目的)。如果这么做了,你就应该使用内存文件系统来避免大量的磁盘操作。更多内容请查看第331页的"选择文件系统"。

    3.3缓存控制策略

缓存引出的问题跟你数据库设计时违背了基本范式一样:它们包含了重复数据,这意味更新数据时要更新多个地方,还要避免读到过期的"坏"数据。以下是几个常用的缓存控制策略:

  存活时间

每个缓存的对象都带有一个作废日期,用一个删除进程定时检查该数据的作废时间是否到达,如果是就立即删除它,你也可以暂时不理会它,直到下一次访问它时,如果已经超过作废时间,那才用一个更新的版本来替换它。这种作废策略最适用于很少变动或几乎不用刷新的数据。

  显式作废

如果缓存里的数据过于"陈旧"而无法被接受,那么更新缓存数据的进程就立即将该旧版本的数据作废。这个策略里有两个变体类型:写-作废和写-更新。写-作废策略非常简单:直接将该数据标志为作废(也可以有从缓存里把它删除掉的选择)。写-更新策略就有更多的工作要做,因为你还要用最新的数据来替换旧缓存数据。但是,这个策略非常有用。特别是当生成缓存数据的代价很昂贵时(这个功能在写的进程里可能已经具备)。更新了缓存之后,将来的请求就用不着再等应用来生成这份数据了。如果你是在后台执行作废过程的,比如是基于TTL的作废过程,你可以在一个独立于任何用户请求的进程里生成最新版本的数据去替换缓存里已作废的数据。

  读时作废

相对于在改变源数据时使缓存里对应的旧数据作废,有一个替代性的方法是保存一些信息来帮你判断从缓存里读出的数据是否已经作废。它有个比显式作废更显著的优点:随着时间的增长,它开销是固定的。假设你要将一个对象作废,而缓存里有100万个对象依赖于它。如果在写时将它作废,你就不得不将缓存里的相关100万个对象都作废。而100万次读的延迟是相当小的,这样就可以摊薄作废操作的时间成本,避免了加载时的长时间延迟。

采用写时作废策略的最简单的方法是实行对象版本化管理。在这个方法里,当把对象保存到缓存里时,你同时要保存该数据所依赖的版本号或时间戳。举例来说,假设你将一个用户在博客发表的文章的统计信息保存到缓存里,这些信息包括了发表文章的数量。当将它作为blog_stats对象缓存时,你同时也要把该用户当前的版本号也保存起来,因为这个统计信息依赖于具体某个用户。

无论什么时候你更新了依赖于用户的数据,也要随之改变用户的版本号。假设用户版本初始为0,你生成并缓存这些统计信息。当用户发表了一篇文章后,你就将用户版本号改为1(最好将这个版本号与文章存放在一起,尽管这个例子我们不必这么做)。那么,当你需要显示统计信息时,就先比较缓存的blog_stats对象的版本和缓存的用户版本,因为这时用户的版本比这个对象的版本要高,这样你就知道这份统计信息里的数据已经陈旧,须要更新了。 这种用于内容作废的方法相当粗糙,因为它预先假设了缓存里的依赖于用户的数据也跟其他数据进行互动。这个条件并不总是成立。举例来说,如果用户编辑了一篇文章,你也会去增加用户的版本号,这使得缓存里的统计数据都要作废了,哪怕真正的统计信息(文章的数目)实际上根本没发生变化。折中的方案是朴素的,一个简单的缓存作废策略不仅仅要易于实现,还要有更高的效率。

对象版本化管理是标签式缓存的一个简化形式,后者可以处理更复杂的依赖关系。一个标签化缓存了解不同类别的依赖关系,并能单独追踪每一个对象的版本号。在上一章的图书俱乐部的例子里,你可以这样给评论做缓存:用用户版本号和书本版本号一起给评论做标签,具体像user_ver=1234和book_ver=5678这样。如果其中一个版本发生了变化,你就要刷新缓存。

    3.4缓存对象的层次

把对象按层次结构存放在缓存中,有助于读取、作废和内存使用的操作。你不仅要将对象本身缓存起来,还要缓存它们的ID和对象分组的ID,这样就能方便成组地读取它们。

电子商务网站上的搜索结果就是这种技术很好的例子。一次搜索可能返回一个匹配的产品清单,清单里包含了产品的名称、描述、缩略图和价格。如果把整个列表存放到缓存里,那读取时的效率是低下的,因为其他的搜索可能也会包含了同样的某几个产品,这样做的结果就是数据重复、浪费内存。这个策略也难以在产品价格发生变化时到缓存里找到对应的产品并使其作废,因为必须逐个清单地去查看是否存在这个价格变化了的产品。

一个可以代替缓存整个清单的方法是把搜索结果里尽量少的信息缓存起来,比如搜索的结果数目和结果清单里的产品ID,这样你就可以单独缓存每一个产品资料了。这个方法解决了两个问题:一是消除了重复数据;二是更容易在单独产品的粒度上将缓存数据作废。

这个方法的缺点是你不得不从缓存里读取多个对象数据,而不是立即读取到整个搜索结果。然而,另一方面这也让你能更快地按照产品ID对搜索结果进行排序。现在,一次缓存命中就返回一个ID列表,如果缓存允许一次调用返回多个对象(Memcached有一个mget()调用支持这个功能),你就可以用这些ID再到缓存里去读取对应的产品资料。

如果你使用不当,这个方法也会产生古怪的结果。假设你使用TTL策略来作废搜索结果,当产品资料发生变化时,明确地将缓存里对应的单个产品资料作废。现在试着想象一个产品的描述发生了变化,它不再包含跟缓存里搜索结果匹配的关键字,而搜索结果还没到作废时间。于是,你的用户就会看到"陈旧"的搜索结果,因为缓存里的这个搜索结果仍然引用了那个描述已经发生变化的产品。

于多数应用来说,这一般不成为问题。如果你的应用无法容忍这个问题,那么就可以使用以版本为基础的缓存策略,在搜索之后,把产品版本号和搜索结果放在一起。在缓存里找到一个搜索结果后,把结果里的每个产品的版本号跟当前产品的版本号(也是在缓存里的)进行比较,如果发现有版本不符的,就通过重新搜索来获取新的搜索结果。

    3.5内容的预生成

除了在应用层面上缓存数据之外,你还可以使用后台进程向服务器预先请求一些页面,然后将它们转换为静态页面保存在服务器上。如果页面是动态变化的,那你可以预生成页面中的一部分,然后使用一种技术,比如服务端整合,来生成最终页面。这样有助于减少预生成内容的大小和开销,因为本来你要为了各个最终页面上的细微差别而不得不重复存储大量的内容。

缓存预生成的内容会占用大量空间,也不可能总是去预生成所有东西。无论哪种形式的缓存,预生成内容里的最重要部分就是请求最多的那些内容。因此,像我们在本章的前面提到过的那样,你可以通过404错误处理程序来对内容作"按需生成"。这些预生成的内容一般都放在内存文件系统里,避免放在磁盘上。

4 扩展Mysql

如果MySQL完不成你所需要的任务,有一种可能性就是扩展它的能力。在这里,我们不是打算告诉你怎么去做扩展,而是要提一下这个可能性里的一些具体途径。如果你有兴趣去深究其中的任何一条途径,那么网上有很多资源可供使用,也有很多关于这个主题的书可以参考。 当我们说 "MySQL完不成你所需要的任务"时,其中包含了两个含义:一是MySQL根本做不到,二是MySQL能做到,但是使用的办法不够好。无论哪个含义都是我们要扩展MySQL的理由。一个好消息是MySQL现在变得越来越模块化、多用途了。举例来说,MySQL 5.1 有大量可用的功能插件,它甚至允许存储引擎也是插件形式的,这样你就用不着把它们编译到MySQL服务器里了。

使用存储引擎将MySQL扩展为特定用途的数据库服务器是个伟大的想法。Brian Aker已经编写了一个存储引擎的框架和一系列的文章、幻灯片来指导用户如何开发自己的存储引擎。这已经构成了一些主要的第三方存储引擎的基础。如果跟踪MySQL的内部邮件列表,你会发现现在有许多公司正在编写他们自己的内置存储引擎。举例来说,Friendster使用一个特别的存储引擎来做社交图操作,另外,我们还知道有一家公司正在做一个用来做模糊搜索的引擎。编写一个简单的自定义引擎一点也不难。

你也可以把存储引擎直接用作软件某一部分的接口。Sphinx就是个很好的例子,它直接与Sphinx全文检索软件通信。

MySQL 5.1 也允许全文检索解析器插件,如果你能编写UDF(请查看第5章),它擅长处理CPU密集的任务,这些任务必须在服务器线程环境下运行,对于SQL而言又太慢太笨重。因此,你可以用它们完成系统管理、服务集成、读取操作系统信息、调用Web服务、同步数据,以及其他更多相类似的任务。

MySQL代理另外有一个很棒的选项,可以让你向MySQL协议增加你自己的功能。Paul McCullagh的可扩展大二进制流框架项目(http://www.blobstreaming.org)为你打通了在MySQL里存储大型对象的道路。 因为MySQL是免费的、开源的软件,所以当你感觉它功能不够用时,你还可以去查看服务器代码。我们知道一些公司已经扩展了MySQL内部解析器的语法。近年来,还有第三方提交的许多有趣的MySQL扩展,涵盖了性能概要、扩展及其他新奇的应用。当人们想扩展MySQL,MySQL的开发者们总是反应积极,并乐于提供帮助。你可以通过邮件列表internals@lists.mysql.com(注册用户请访问http://lists.mysql.com)、MySQL论坛和IRC频道#mysql-dev跟他们取得联系。

5 可替代的Mysql

MySQL不是一个能适用于所有需要的万能解决方案。有些工作全部放到MySQL之外会更好,即使MySQL在理论上也能做到。

一个很明显的例子是在传统的文件系统里对数据进行排序而不是在表里。图像文件是又一个经典的案例:你可以把它们都放在BLOB字段里,但是这在多数时候都不是个好主意(注3)。通常的做法是把图像文件或其他大型二进制文件存在文件系统里,然后把文件名放在MySQL里。这样,应用就可以在MySQL之外读取文件了。在Web应用里,你可以把文件名放在<img>元素的src属性里。

全文检索也是应该放在MySQL之外处理的任务之一--MySQL不像Lucene或Sphinx那样擅长于这类检索。

NDB API 可以被用于某一类型的任务。比如,虽然MySQL的NDB Cluster存储引擎不适合在高性能要求的Web应用中作排序操作,但是可以通过直接使用NDB API 来存储网站的session数据或用户注册信息。关于 NDB API,你可以访问http://dev.mysql.com/doc/ndbapi/ en/index.html来获取更多信息。Apache上也有相应的NDB模块,你可以从http://code.google. com/p/mod-ndb/下载。

最后,对于有些操作,比如图形化的关系、树的遍历,关系数据库并不擅长做这些。MySQL也不擅长分布式数据处理,因为它缺少并行查询的执行能力。你可能需要使用别的工具(与MySQL一起使用)来达到这一目的。

posted @ 2010-08-05 10:55 小马歌 阅读(311) | 评论 (1)编辑 收藏
 
在 Apache 服务器中,KeepAlive 是一个布尔值,On 代表打开,Off 代表关闭,这个指令在其他众多的 HTTPD 服务器中都是存在的。

  KeepAlive 配置指令决定当处理完用户发起的 HTTP 请求后是否立即关闭 TCP 连接,如果 KeepAlive 设置为On,那么用户完成一次访问后,不会立即断开连接,如果还有请求,那么会继续在这一次 TCP 连接中完成,而不用重复建立新的 TCP 连接和关闭TCP 连接,可以提高用户访问速度。

  那么我们考虑3种情况:
  1。用户浏览一个网页时,除了网页本身外,还引用了多个 javascript 文件,多个 css 文件,多个图片文件,并且这些文件都在同一个 HTTP 服务器上。
  2。用户浏览一个网页时,除了网页本身外,还引用一个 javascript 文件,一个图片文件。
  3。用户浏览的是一个动态网页,由程序即时生成内容,并且不引用其他内容。

  对于上面3中情况,我认为:1 最适合打开 KeepAlive ,2 随意,3 最适合关闭 KeepAlive

  下面我来分析一下原因。

  在 Apache 中,打开和关闭 KeepAlive 功能,服务器端会有什么异同呢?

  先看看理论分析。

  打开 KeepAlive 后,意味着每次用户完成全部访问后,都要保持一定时间后才关闭会关闭 TCP 连接,那么在关闭连接之前,必然会有一个Apache 进程对应于该用户而不能处理其他用户,假设 KeepAlive 的超时时间为 10 秒种,服务器每秒处理 50个独立用户访问,那么系统中 Apache 的总进程数就是 10 * 50 = 500 个,如果一个进程占用 4M 内存,那么总共会消耗 2G内存,所以可以看出,在这种配置中,相当消耗内存,但好处是系统只处理了 50次 TCP 的握手和关闭操作。

  如果关闭 KeepAlive,如果还是每秒50个用户访问,如果用户每次连续的请求数为3个,那么 Apache 的总进程数就是 50 * 3= 150 个,如果还是每个进程占用 4M 内存,那么总的内存消耗为 600M,这种配置能节省大量内存,但是,系统处理了 150 次 TCP的握手和关闭的操作,因此又会多消耗一些 CPU 资源。

  在看看实践的观察。

  我在一组大量处理动态网页内容的服务器中,起初打开 KeepAlive功能,经常观察到用户访问量大时Apache进程数也非常多,系统频繁使用交换内存,系统不稳定,有时负载会出现较大波动。关闭了 KeepAlive功能后,看到明显的变化是: Apache 的进程数减少了,空闲内存增加了,用于文件系统Cache的内存也增加了,CPU的开销增加了,但是服务更稳定了,系统负载也比较稳定,很少有负载大范围波动的情况,负载有一定程度的降低;变化不明显的是:访问量较少的时候,系统平均负载没有明显变化。


  总结一下:
  在内存非常充足的服务器上,不管是否关闭 KeepAlive 功能,服务器性能不会有明显变化;
  如果服务器内存较少,或者服务器有非常大量的文件系统访问时,或者主要处理动态网页服务,关闭 KeepAlive 后可以节省很多内存,而节省出来的内存用于文件系统Cache,可以提高文件系统访问的性能,并且系统会更加稳定。


  补充1:
  关于是否应该关闭 KeepAlive 选项,我觉得可以基于下面的一个公式来判断。

  在理想的网络连接状况下,系统的 Apache 进程数和内存使用可以用如下公式表达:
HttpdProcessNumber = KeepAliveTimeout * TotalRequestPerSecond / Average(KeepAliveRequests)
HttpdUsedMemory = HttpdProcessNumber * MemoryPerHttpdProcess

  换成中文:
总Apache进程数 = KeepAliveTimeout * 每秒种HTTP请求数 / 平均KeepAlive请求
Apache占用内存 = 总Apache进程数 * 平均每进程占用内存数

  需要特别说明的是:
  [平均KeepAlive请求] 数,是指每个用户连接上服务器后,持续发出的 HTTP 请求数。当 KeepAliveTimeout 等 0或者 KeepAlive 关闭时,KeepAliveTimeout 不参与乘的运算从上面的公式看,如果 [每秒用户请求]多,[KeepAliveTimeout] 的值大,[平均KeepAlive请求] 的值小,都会造成 [Apache进程数] 多和 [内存]多,但是当 [平均KeepAlive请求] 的值越大时,[Apache进程数] 和 [内存] 都是趋向于减少的。

  基于上面的公式,我们就可以推算出当 平均KeepAlive请求 <= KeepAliveTimeout 时,关闭 KeepAlive 选项是划算的,否则就可以考虑打开。

     补充2:  KeepAlive 该参数控制Apache是否允许在一个连接中有多个请求,默认打开。但对于大多数论坛类型站点来说,通常设置为off以关闭该支持。

     补充3:  如果服务器前跑有应用squid服务,或者其它七层设备,KeepAlive On 设定要开启持续长连接

实际在 前端有 squid 的情况下, KeepAlive 很关键。记得 On
posted @ 2010-08-02 15:09 小马歌 阅读(159) | 评论 (0)编辑 收藏
 
一、什么是Java事务
通常的观念认为,事务仅与数据库相关。
事务必须服从ISO/IEC所制定的ACID原则。ACID是原子性(atomicity)、一致性(consistency)、隔离性(isolation)和持久性(durability)的缩写。事务的原子性表示事务执行过程中的任何失败都将导致事务所做的任何修改失效。一致性表示当事务执行失败时,所有被该事务影响的数据都应该恢复到事务执行前的状态。隔离性表示在事务执行过程中对数据的修改,在事务提交之前对其他事务不可见。持久性表示已提交的数据在事务执行失败时,数据的状态都应该正确。
通俗的理解,事务是一组原子操作单元,从数据库角度说,就是一组SQL指令,要么全部执行成功,若因为某个原因其中一条指令执行有错误,则撤销先前执行过的所有指令。更简答的说就是:要么全部执行成功,要么撤销不执行。
既然事务的概念从数据库而来,那Java事务是什么?之间有什么联系?
实际上,一个Java应用系统,如果要操作数据库,则通过JDBC来实现的。增加、修改、删除都是通过相应方法间接来实现的,事务的控制也相应转移到Java程序代码中。因此,数据库操作的事务习惯上就称为Java事务。
二、为什么需要事务
事务是为解决数据安全操作提出的,事务控制实际上就是控制数据的安全访问。具一个简单例子:比如银行转帐业务,账户A要将自己账户上的1000元转到B账户下面,A账户余额首先要减去1000元,然后B账户要增加1000元。假如在中间网络出现了问题,A账户减去1000元已经结束,B因为网络中断而操作失败,那么整个业务失败,必须做出控制,要求A账户转帐业务撤销。这才能保证业务的正确性,完成这个操走就需要事务,将A账户资金减少和B账户资金增加方到一个事务里面,要么全部执行成功,要么操作全部撤销,这样就保持了数据的安全性。
三、Java事务的类型
Java事务的类型有三种:JDBC事务、JTA(Java Transaction API)事务、容器事务。
1、JDBC事务
JDBC 事务是用 Connection 对象控制的。JDBC Connection 接口( java.sql.Connection )提供了两种事务模式:自动提交和手工提交。 java.sql.Connection 提供了以下控制事务的方法:
public void setAutoCommit(boolean)
public boolean getAutoCommit()
public void commit()
public void rollback()
使用 JDBC 事务界定时,您可以将多个 SQL 语句结合到一个事务中。JDBC 事务的一个缺点是事务的范围局限于一个数据库连接。一个 JDBC 事务不能跨越多个数据库。
2、JTA(Java Transaction API)事务
JTA是一种高层的,与实现无关的,与协议无关的API,应用程序和应用服务器可以使用JTA来访问事务。
JTA允许应用程序执行分布式事务处理--在两个或多个网络计算机资源上访问并且更新数据,这些数据可以分布在多个数据库上。JDBC驱动程序的JTA支持极大地增强了数据访问能力。
如果计划用 JTA 界定事务,那么就需要有一个实现 javax.sql.XADataSource 、 javax.sql.XAConnection 和 javax.sql.XAResource 接口的 JDBC 驱动程序。一个实现了这些接口的驱动程序将可以参与 JTA 事务。一个 XADataSource 对象就是一个 XAConnection 对象的工厂。 XAConnection s 是参与 JTA 事务的 JDBC 连接。
您将需要用应用服务器的管理工具设置 XADataSource 。从应用服务器和 JDBC 驱动程序的文档中可以了解到相关的指导。
J2EE 应用程序用 JNDI 查询数据源。一旦应用程序找到了数据源对象,它就调用 javax.sql.DataSource.getConnection() 以获得到数据库的连接。
XA 连接与非 XA 连接不同。一定要记住 XA 连接参与了 JTA 事务。这意味着 XA 连接不支持 JDBC 的自动提交功能。同时,应用程序一定不要对 XA 连接调用 java.sql.Connection.commit() 或者 java.sql.Connection.rollback() 。相反,应用程序应该使用 UserTransaction.begin()、 UserTransaction.commit() 和 serTransaction.rollback() 。
3、容器事务
容器事务主要是J2EE应用服务器提供的,容器事务大多是基于JTA完成,这是一个基于JNDI的,相当复杂的API实现。相对编码实现JTA事务管理,我们可以通过EJB容器提供的容器事务管理机制(CMT)完成同一个功能,这项功能由J2EE应用服务器提供。这使得我们可以简单的指定将哪个方法加入事务,一旦指定,容器将负责事务管理任务。这是我们土建的解决方式,因为通过这种方式我们可以将事务代码排除在逻辑编码之外,同时将所有困难交给J2EE容器去解决。使用EJB CMT的另外一个好处就是程序员无需关心JTA API的编码,不过,理论上我们必须使用EJB。
四、三种事务差异
1、JDBC事务控制的局限性在一个数据库连接内,但是其使用简单。
2、JTA事务的功能强大,事务可以跨越多个数据库或多个DAO,使用也比较复杂。
3、容器事务,主要指的是J2EE应用服务器提供的事务管理,局限于EJB应用使用。
五、总结
事务控制是构建J2EE应用不可缺少的一部分,合理选择应用何种事务对整个应用系统来说至关重要。一般说来,在单个JDBC 连接连接的情况下可以选择JDBC事务,在跨多个连接或者数据库情况下,需要选择使用JTA事务,如果用到了EJB,则可以考虑使用EJB容器事务
posted @ 2010-07-23 12:09 小马歌 阅读(236) | 评论 (0)编辑 收藏
 

说在前面: 
1、以下题目,除了编程任务外其他都需要写在给你提供的草纸上。纸张是珍贵的地球资源,请节约使用。编程任务在有相应的环境时,会要求上机书写,实在没有条件,就只能写在草纸上了。 
2、时间: 
基础任务+进阶任务+设计任务 = 90分钟 
编程任务 = 60分钟 

基础任务: 
1、请列举你能想到的UNIX信号,并说明信号用途。 
2、请列举、你能想到的所有的字符串查找算法,并加注释简单说明。 
3、有一个IP地址(192.168.0.1),请写出其32位无符号整数形式。 
4、写出、你能想到的所有HTTP返回状态值,并说明用途(比如:返回404表示找不到页面) 

基础任务-选作(会得到额外分数): 
1、画几个你最熟悉的SERVER端模型出来(格式不重要,尽量将图画清楚,说明思路即可) 

进阶任务: 
1、PHP的垃圾收集机制是怎样的? 
说明: 
1)如果,你熟悉PHP源码,那么请从源码入手,回答些问题,会获得额外加分 
2)如果,你不熟悉PHP源码,那么尽你所能,多写点东西,包括利用自己的编程直觉得到的信息,都可以。 
3)对,则有分,错误不扣,不写无分。 
2、请写出HTTP头,并符合以下要求: 
1)这是一个post请求 
2)目标:http://www.example.com:8080/test 
3)POST变量: 
username: test 
pwd: test2 
intro: Hello world! 
4)包含以下COOKIE信息: 
cur_query: you&me 
说明: 
1)如果,你记不得某个HTTP协议中的指令字了,那么,无奈这举是用“汉字”代替。 
2)如果,你能记住更多的HTTP协议指令字,那么多写几句,总是没坏处,对吧? 
3)最关键的,只需要画出正确的“轮廓”(还记得httpwatch等工具打印出来的头部吗?那就是“轮廓”的含义),也会有分数,但如果,连“轮廓”都写错了,那么就很遗憾了。 

设计任务: 
1、最近总有人骚扰我们的投票模块,需要你来设计一个投票限制的东东 
要求如下: 
1)要求每个QQ号码(假设此QQ号码在UNIT32内可以表示)10分钟这内只能投5票。 
2)我们的用户很踊跃,平均每天要有2000万人左右通过此程序投票。 
说明: 
1)无需写代码,只需要图跟文字即可。 
2)对于关键逻辑,请用图加代码表示出来,这也是对你文字表达能力的一个考验。 
3)对你能想到的所有的边界条件列出来,这是对你逻辑思维全面与敏捷性的考验。 
4)存储部分,尽你所能吧。如果,你需要一个自己设计的存储层,那么把这个存储层的实现,用文字+图片方式描述清楚,要是设计合理,你会获得华丽的奖分。 

编程任务: 
1、我们碰到了大麻烦,一个新来的传教士惹恼了上帝,上帝很愤怒,要求我们把圣经(bbe.txt)背熟,直至他说哪个单词,我们就要飞快的回答出这个单词在第几行第几个单词位置。听说你是个优秀的程序员,那么髟助我们完成这个不可能的任务吧。 
要求如下: 
1)/myworks/example/bbe.txt,98版本英文圣经一本 
2)输入部分要求如下:php ./example.php [单词] 
3)输出部分如下:[单词] 1,2 2,4 5,6 表示:此单词在1行2列(第二个单词),2行4列... 
说明: 
1)此文本4MB之巨... 
2)单词的含义:由英文字母(大小写),数字(0-9)组成的串 
3)提供给你的机器OS为ubuntu 9.10,内存只有1G,而且,很不幸的,其中700M用来做了别的 
4)上机考试不允许上网,但我装了man文档以及读取CHM以及PDF的阅读器,在电脑的桌面的CHM文件夹中,有相应的PHP参考手册 
5)算法复杂度要求不能大于O(N^2)(就是N的平方) 
6)什么?PHP低效且用起来不顺手,好的,你可以用别的语言来实现。但注意:提供给你的机器上只有python 2.4/perl 5.8/gcc[g++] 4.1 


By xhttp.cn 整理:http://www.xhttp.cn/2010/05/2

posted @ 2010-07-22 22:40 小马歌 阅读(142) | 评论 (0)编辑 收藏
 

小编之前也曾报导过PHP开发人员容易忽略的几点精华,除了一些精华技术方法外,很多细微之处也是程 序员们容易忽略的,下面我们为您总结了10个关于PHP你可能不知道的事情。

关于PHP更多内容,欢迎访问:PHP开发基础入门

1.使用ip2long() 和long2ip()函数来把IP地址转化成整型存储到数据库里。

这种方法把存储空间降到了接近四分之一(char(15)的15个字节对整形的4个字节),计算一个特定的地址是不是在一个区段内页更简单了, 而且加快了搜索和排序的速度(虽然有时仅仅是快了一点)。

2.在验证Email地址的时候使用checkdnsrr() 函数验证域名是否存在。

这个内置函数能够确认指定的域名能够解析成IP地址。该函数的PHP 文档的用户评论部分有一个简单的用户自定义函数,这个函数基于checkdnsrr(),用来验证 email 地址的合法性。对于那些认为自己的Email地址是memory@wwwphp100.net而不是memory@php100.net的家伙们,这个方 法可以很方便的抓住他们。

3.如果你使用的是PHP 5和MySQL 4.1 或者更高的版本,考虑用mysqli_* 系列函数。

一个很好的功能就是你可以使用预处理语句,如果你在维护一个数据库密集型站点,这个功能能够加快查询速度。一些评估分数。

4.学会爱上三元运算符。

5.如果你在项目中感觉到有可复用的部分,在你写下一行代码前先看看PEAR中是否已经有了。

很多PHP程序员都知道 PEAR 是一个很好的资源库,虽然还有很多程序员不知道。这个在线资源库包含了超过400个可以复用的程序片段,这些程序片段你可以立即用刀你的程序里。除非说你 的项目真的是非常特别的,你总能找到帮你节省时间的 PEAR包。

6.用 highlight_file()来自动的打印出格式化的很漂亮的源代码。

如果你在留言板、IRC 这些地方寻求一个脚本的帮助的话,这个函数用起来非常的顺手。当然了,要小心不要意外的泄露出你的数据库连接信息和密码等。

7.使用 error_reporting(0)函数来防止用户看到潜在的敏感错误信息。

在理想情况下,发布服务器应该在php.ini 里完全禁止。但是如果你用的是一个共享的 web 服务器的话,你没有自己的 php.ini 文件,那么这种情况下你最好的选择就是在所有脚本的第一行前加上 error_reporting(0);(或者使用 require_once() 方法)。这就能够在出错的时候完全屏蔽敏感的SQL查询语句和路径名。

8.在网数据库中存储很大的字符串之前使用 gzcompress() 和 gzuncompress() 来显式的压缩/解压字符串。

这个PHP内置函数使用 gzip 算法,可以压缩普通文本达 90%。在我每次要读写BLOB类型的字段的时候都使用这些函数。唯一额例外就是当我需要全文检索的时候。

9.通过“引用”传递参数的方法从一个函数中得到多个返回值。

就像三元运算符一样,大部分受过正式编程训练的程序员都知道这个技巧。但是那些 HTML 背景大于 Pascal 背景的程序员都或多或少的有过这样的疑问“在仅能使用一次 return 的情况下,从一个函数里返回多个值?”答案就是在变量前加上一个 “&” 符号,通过“引用”传递而非“值”传递。

10.完全理解“魔术引号”和 SQL 注入的危险性。

我希望阅读到这里的开发者都已经很对SQL注入很了解了。不过我还是把这条列在这里,是因为这个确实有点难以理解。

posted @ 2010-07-22 22:28 小马歌 阅读(115) | 评论 (0)编辑 收藏
 

一、http_load

程序非常小,解压后也不到100K

http_load以并行复用的方式运行,用以测试web服务器的吞吐量与负载。但是它不同于大多数压力测试工

具,它可以以一个单一的进程运行,一般不会把客户机搞死。还可以测试HTTPS类的网站请求。

下载地址:http://soft.vpser.net/test/http_load/http_load-12mar2006.tar.gz
安装很简单
#tar zxvf http_load-12mar2006.tar.gz
#cd http_load-12mar2006
#make && make install

命令格式:http_load  -p 并发访问进程数  -s 访问时间  需要访问的URL文件

参数其实可以自由组合,参数之间的选择并没有什么限制。比如你写成http_load -parallel 5 -seconds

300 urls.txt也是可以的。我们把参数给大家简单说明一下。
-parallel 简写-p :含义是并发的用户进程数。
-fetches 简写-f :含义是总计的访问次数
-rate    简写-p :含义是每秒的访问频率
-seconds简写-s :含义是总计的访问时间

准备URL文件:urllist.txt,文件格式是每行一个URL,URL最好超过50-100个测试效果比较好.文件格式

如下:
http://www.vpser.net/uncategorized/choose-vps.html
http://www.vpser.net/vps-cp/hypervm-tutorial.html
http://www.vpser.net/coupons/diavps-april-coupons.html
http://www.vpser.net/security/vps-backup-web-mysql.html
例如:

http_load -p 30 -s 60  urllist.txt
参数了解了,我们来看运行一条命令来看看它的返回结果
命令:% ./http_load -rate 5 -seconds 10 urls说明执行了一个持续时间10秒的测试,每秒的频率为5。

49 fetches, 2 max parallel, 289884 bytes, in 10.0148 seconds5916 mean bytes/connection4.89274

fetches/sec, 28945.5 bytes/secmsecs/connect: 28.8932 mean, 44.243 max, 24.488 minmsecs/first

-response: 63.5362 mean, 81.624 max, 57.803 minHTTP response codes: code 200 — 49 

结果分析:
1.49 fetches, 2 max parallel, 289884 bytes, in 10.0148 seconds
说明在上面的测试中运行了49个请求,最大的并发进程数是2,总计传输的数据是289884bytes,运行的时间是10.0148秒
2.5916 mean bytes/connection说明每一连接平均传输的数据量289884/49=5916
3.4.89274 fetches/sec, 28945.5 bytes/sec
说明每秒的响应请求为4.89274,每秒传递的数据为28945.5 bytes/sec
4.msecs/connect: 28.8932 mean, 44.243 max, 24.488 min说明每连接的平均响应时间是28.8932 msecs

,最大的响应时间44.243 msecs,最小的响应时间24.488 msecs
5.msecs/first-response: 63.5362 mean, 81.624 max, 57.803 min
6、HTTP response codes: code 200 — 49     说明打开响应页面的类型,如果403的类型过多,那可能

要注意是否系统遇到了瓶颈。
特殊说明:
测试结果中主要的指标是 fetches/sec、msecs/connect 这个选项,即服务器每秒能够响应的查询次数,

用这个指标来衡量性能。似乎比 apache的ab准确率要高一些,也更有说服力一些。
Qpt-每秒响应用户数和response time,每连接响应用户时间。
测试的结果主要也是看这两个值。当然仅有这两个指标并不能完成对性能的分析,我们还需要对服务器的

cpu、men进行分析,才能得出结论

 

二、webbench

webbench是Linux下的一个网站压力测试工具,最多可以模拟3万个并发连接去测试网站的负载能力。下载地址可以到google搜,我这里给出一个
下载地址:http://soft.vpser.net/test/webbench/webbench-1.5.tar.gz
这个程序更小,解压后不到50K,呵呵
安装非常简单
#tar zxvf webbench-1.5.tar.gz
#cd webbench-1.5
#make && make install
会在当前目录生成webbench可执行文件,直接可以使用了

用法:

webbench -c 并发数 -t 运行测试时间 URL
如:
webbench -c 5000 -t 120 http://www.vpser.net

三、ab
ab是apache自带的一款功能强大的测试工具
安装了apache一般就自带了,
用法可以查看它的说明

$ ./ab
./ab: wrong number of arguments
Usage: ./ab [options] [http://]hostname[:port]/path
Options are:
-n requests Number of requests to perform
-c concurrency Number of multiple requests to make
-t timelimit Seconds to max. wait for responses
-p postfile File containing data to POST
-T content-type Content-type header for POSTing
-v verbosity How much troubleshooting info to print
-w Print out results in HTML tables
-i Use HEAD instead of GET
-x attributes String to insert as table attributes
-y attributes String to insert as tr attributes
-z attributes String to insert as td or th attributes
-C attribute Add cookie, eg. ‘Apache=1234. (repeatable)
-H attribute Add Arbitrary header line, eg. ‘Accept-Encoding: gzip’
Inserted after all normal header lines. (repeatable)
-A attribute Add Basic WWW Authentication, the attributes
are a colon separated username and password.
-P attribute Add Basic Proxy Authentication, the attributes
are a colon separated username and password.
-X proxy:port Proxyserver and port number to use
-V Print version number and exit
-k Use HTTP KeepAlive feature
-d Do not show percentiles served table.
-S Do not show confidence estimators and warnings.
-g filename Output collected data to gnuplot format file.
-e filename Output CSV file with percentages served
-h Display usage information (this message)
参数众多,一般我们用到的是-n 和-c
例如:
./ab -c 1000 -n 100 http://www.vpser.net/index.php

这个表示同时处理1000个请求并运行100次index.php文件.
四、Siege
一款开源的压力测试工具,可以根据配置对一个WEB站点进行多用户的并发访问,记录每个用户所有请求过程的相应时间,并在一定数量的并发访问下重复进行。
官方:http://www.joedog.org/
Siege下载:http://soft.vpser.net/test/siege/siege-2.67.tar.gz
解压:
# tar -zxf siege-2.67.tar.gz
进入解压目录:
# cd siege-2.67/
安装:
#./configure ; make
#make install

使用
siege -c 200 -r 10 -f example.url
-c是并发量,-r是重复次数。 url文件就是一个文本,每行都是一个url,它会从里面随机访问的。

example.url内容:

http://www.licess.cn
http://www.vpser.net
http://soft.vpser.net

结果说明
Lifting the server siege… done.
Transactions: 3419263 hits //完成419263次处理
Availability: 100.00 % //100.00 % 成功率
Elapsed time: 5999.69 secs //总共用时
Data transferred: 84273.91 MB //共数据传输84273.91 MB
Response time: 0.37 secs //相应用时1.65秒:显示网络连接的速度
Transaction rate: 569.91 trans/sec //均每秒完成 569.91 次处理:表示服务器后
Throughput: 14.05 MB/sec //平均每秒传送数据
Concurrency: 213.42 //实际最高并发数
Successful transactions: 2564081 //成功处理次数
Failed transactions: 11 //失败处理次数
Longest transaction: 29.04 //每次传输所花最长时间
Shortest transaction: 0.00 //每次传输所花最短时间

posted @ 2010-07-22 17:00 小马歌 阅读(309) | 评论 (0)编辑 收藏
 

今天,数据库的操作越来越成为整个应用的性能瓶颈了,这点对于Web应用尤其明显。关于数据库的性能,这并不只是DBA才需要担心的事,而这更是我们程序员需要去关注的事情。当我们去设计数据库表结构,对操作数据库时(尤其是查表时的SQL语句),我们都需要注意数据操作的性能。这里,我们不会讲过多的SQL语句的优化,而只是针对MySQL这一Web应用最多的数据库。希望下面的这些优化技巧对你有用。

1. 为查询缓存优化你的查询

大多数的MySQL服务器都开启了查询缓存。这是提高性最有效的方法之一,而且这是被MySQL的数据库引擎处理的。当有很多相同的查询被执行了多次的时候,这些查询结果会被放到一个缓存中,这样,后续的相同的查询就不用操作表而直接访问缓存结果了。

这里最主要的问题是,对于程序员来说,这个事情是很容易被忽略的。因为,我们某些查询语句会让MySQL不使用缓存。请看下面的示例: 

1 // 查询缓存不开启
2 $r = mysql_query("SELECT username FROM user WHERE signup_date >= CURDATE()");
3   
4 // 开启查询缓存
5 $today = date("Y-m-d");
6 $r = mysql_query("SELECT username FROM user WHERE signup_date >= '$today'");

上面两条SQL语句的差别就是 CURDATE() ,MySQL的查询缓存对这个函数不起作用。所以,像 NOW() 和 RAND() 或是其它的诸如此类的SQL函数都不会开启查询缓存,因为这些函数的返回是会不定的易变的。所以,你所需要的就是用一个变量来代替MySQL的函数,从而开启缓存。

 

2. EXPLAIN 你的 SELECT 查询

使用 EXPLAIN 关键字可以让你知道MySQL是如何处理你的SQL语句的。这可以帮你分析你的查询语句或是表结构的性能瓶颈。

EXPLAIN 的查询结果还会告诉你你的索引主键被如何利用的,你的数据表是如何被搜索和排序的……等等,等等。

挑一个你的SELECT语句(推荐挑选那个最复杂的,有多表联接的),把关键字EXPLAIN加到前面。你可以使用phpmyadmin来做这个事。然后,你会看到一张表格。下面的这个示例中,我们忘记加上了group_id索引,并且有表联接:

当我们为 group_id 字段加上索引后:

我们可以看到,前一个结果显示搜索了 7883 行,而后一个只是搜索了两个表的 9 和 16 行。查看rows列可以让我们找到潜在的性能问题。

3. 当只要一行数据时使用 LIMIT 1

当你查询表的有些时候,你已经知道结果只会有一条结果,但因为你可能需要去fetch游标,或是你也许会去检查返回的记录数。

在这种情况下,加上 LIMIT 1 可以增加性能。这样一样,MySQL数据库引擎会在找到一条数据后停止搜索,而不是继续往后查少下一条符合记录的数据。

下面的示例,只是为了找一下是否有“中国”的用户,很明显,后面的会比前面的更有效率。(请注意,第一条中是Select *,第二条是Select 1) 

01 // 没有效率的:
02 $r = mysql_query("SELECT * FROM user WHERE country = 'China'");
03 if (mysql_num_rows($r) > 0) {
04     // ...
05 }
06   
07 // 有效率的:
08 $r = mysql_query("SELECT 1 FROM user WHERE country = 'China' LIMIT 1");
09 if (mysql_num_rows($r) > 0) {
10     // ...
11 }

4. 为搜索字段建索引

索引并不一定就是给主键或是唯一的字段。如果在你的表中,有某个字段你总要会经常用来做搜索,那么,请为其建立索引吧。

从上图你可以看到那个搜索字串 “last_name LIKE ‘a%’”,一个是建了索引,一个是没有索引,性能差了4倍左右。

另外,你应该也需要知道什么样的搜索是不能使用正常的索引的。例如,当你需要在一篇大的文章中搜索一个词时,如: “WHERE post_content LIKE ‘%apple%’”,索引可能是没有意义的。你可能需要使用MySQL全文索引 或是自己做一个索引(比如说:搜索关键词或是Tag什么的)

5. 在Join表的时候使用相当类型的例,并将其索引

如果你的应用程序有很多 JOIN 查询,你应该确认两个表中Join的字段是被建过索引的。这样,MySQL内部会启动为你优化Join的SQL语句的机制。

而且,这些被用来Join的字段,应该是相同的类型的。例如:如果你要把 DECIMAL 字段和一个 INT 字段Join在一起,MySQL就无法使用它们的索引。对于那些STRING类型,还需要有相同的字符集才行。(两个表的字符集有可能不一样) 

1 // 在state中查找company
2 $r = mysql_query("SELECT company_name FROM users
3     LEFT JOIN companies ON (users.state = companies.state)
4     WHERE users.id = $user_id");
5   
6 // 两个 state 字段应该是被建过索引的,而且应该是相当的类型,相同的字符集。

6. 千万不要 ORDER BY RAND()

想打乱返回的数据行?随机挑一个数据?真不知道谁发明了这种用法,但很多新手很喜欢这样用。但你确不了解这样做有多么可怕的性能问题。

如果你真的想把返回的数据行打乱了,你有N种方法可以达到这个目的。这样使用只让你的数据库的性能呈指数级的下降。这里的问题是:MySQL会不得不去执行RAND()函数(很耗CPU时间),而且这是为了每一行记录去记行,然后再对其排序。就算是你用了Limit 1也无济于事(因为要排序)

下面的示例是随机挑一条记录 

1 // 千万不要这样做:
2 $r = mysql_query("SELECT username FROM user ORDER BY RAND() LIMIT 1");
3   
4 // 这要会更好:
5 $r = mysql_query("SELECT count(*) FROM user");
6 $d = mysql_fetch_row($r);
7 $rand = mt_rand(0,$d[0] - 1);
8   
9 $r = mysql_query("SELECT username FROM user LIMIT $rand, 1");

7. 避免 SELECT *

从数据库里读出越多的数据,那么查询就会变得越慢。并且,如果你的数据库服务器和WEB服务器是两台独立的服务器的话,这还会增加网络传输的负载。

所以,你应该养成一个需要什么就取什么的好的习惯。 

1 // 不推荐
2 $r = mysql_query("SELECT * FROM user WHERE user_id = 1");
3 $d = mysql_fetch_assoc($r);
4 echo "Welcome {$d['username']}";
5   
6 // 推荐
7 $r = mysql_query("SELECT username FROM user WHERE user_id = 1");
8 $d = mysql_fetch_assoc($r);
9 echo "Welcome {$d['username']}";

8. 永远为每张表设置一个ID

我们应该为数据库里的每张表都设置一个ID做为其主键,而且最好的是一个INT型的(推荐使用UNSIGNED),并设置上自动增加的AUTO_INCREMENT标志。

就算是你 users 表有一个主键叫 “email”的字段,你也别让它成为主键。使用 VARCHAR 类型来当主键会使用得性能下降。另外,在你的程序中,你应该使用表的ID来构造你的数据结构。

而且,在MySQL数据引擎下,还有一些操作需要使用主键,在这些情况下,主键的性能和设置变得非常重要,比如,集群,分区……

在这里,只有一个情况是例外,那就是“关联表”的“外键”,也就是说,这个表的主键,通过若干个别的表的主键构成。我们把这个情况叫做“外键”。比如:有一个“学生表”有学生的ID,有一个“课程表”有课程ID,那么,“成绩表”就是“关联表”了,其关联了学生表和课程表,在成绩表中,学生ID和课程ID叫“外键”其共同组成主键。

9. 使用 ENUM 而不是 VARCHAR

ENUM 类型是非常快和紧凑的。在实际上,其保存的是 TINYINT,但其外表上显示为字符串。这样一来,用这个字段来做一些选项列表变得相当的完美。

如果你有一个字段,比如“性别”,“国家”,“民族”,“状态”或“部门”,你知道这些字段的取值是有限而且固定的,那么,你应该使用 ENUM 而不是 VARCHAR。

MySQL也有一个“建议”(见第十条)告诉你怎么去重新组织你的表结构。当你有一个 VARCHAR 字段时,这个建议会告诉你把其改成 ENUM 类型。使用 PROCEDURE ANALYSE() 你可以得到相关的建议。

10. 从 PROCEDURE ANALYSE() 取得建议

PROCEDURE ANALYSE() 会让 MySQL 帮你去分析你的字段和其实际的数据,并会给你一些有用的建议。只有表中有实际的数据,这些建议才会变得有用,因为要做一些大的决定是需要有数据作为基础的。

例如,如果你创建了一个 INT 字段作为你的主键,然而并没有太多的数据,那么,PROCEDURE ANALYSE()会建议你把这个字段的类型改成 MEDIUMINT 。或是你使用了一个 VARCHAR 字段,因为数据不多,你可能会得到一个让你把它改成 ENUM 的建议。这些建议,都是可能因为数据不够多,所以决策做得就不够准。

在phpmyadmin里,你可以在查看表时,点击 “Propose table structure” 来查看这些建议

一定要注意,这些只是建议,只有当你的表里的数据越来越多时,这些建议才会变得准确。一定要记住,你才是最终做决定的人。

11. 尽可能的使用 NOT NULL

除非你有一个很特别的原因去使用 NULL 值,你应该总是让你的字段保持 NOT NULL。这看起来好像有点争议,请往下看。

首先,问问你自己“Empty”和“NULL”有多大的区别(如果是INT,那就是0和NULL)?如果你觉得它们之间没有什么区别,那么你就不要使用NULL。(你知道吗?在 Oracle 里,NULL 和 Empty 的字符串是一样的!)

不要以为 NULL 不需要空间,其需要额外的空间,并且,在你进行比较的时候,你的程序会更复杂。 当然,这里并不是说你就不能使用NULL了,现实情况是很复杂的,依然会有些情况下,你需要使用NULL值。

下面摘自MySQL自己的文档:

“NULL columns require additional space in the row to record whether their values are NULL. For MyISAM tables, each NULL column takes one bit extra, rounded up to the nearest byte.”

12. Prepared Statements

Prepared Statements很像存储过程,是一种运行在后台的SQL语句集合,我们可以从使用 prepared statements 获得很多好处,无论是性能问题还是安全问题。

Prepared Statements 可以检查一些你绑定好的变量,这样可以保护你的程序不会受到“SQL注入式”攻击。当然,你也可以手动地检查你的这些变量,然而,手动的检查容易出问题,而且很经常会被程序员忘了。当我们使用一些framework或是ORM的时候,这样的问题会好一些。

在性能方面,当一个相同的查询被使用多次的时候,这会为你带来可观的性能优势。你可以给这些Prepared Statements定义一些参数,而MySQL只会解析一次。

虽然最新版本的MySQL在传输Prepared Statements是使用二进制形势,所以这会使得网络传输非常有效率。

当然,也有一些情况下,我们需要避免使用Prepared Statements,因为其不支持查询缓存。但据说版本5.1后支持了。

在PHP中要使用prepared statements,你可以查看其使用手册:mysqli 扩展 或是使用数据库抽象层,如: PDO

01 // 创建 prepared statement
02 if ($stmt = $mysqli->prepare("SELECT username FROM user WHERE state=?")) {
03   
04     // 绑定参数
05     $stmt->bind_param("s", $state);
06   
07     // 执行
08     $stmt->execute();
09   
10     // 绑定结果
11     $stmt->bind_result($username);
12   
13     // 移动游标
14     $stmt->fetch();
15   
16     printf("%s is from %s\n", $username, $state);
17   
18     $stmt->close();
19 }

13. 无缓冲的查询

正常的情况下,当你在当你在你的脚本中执行一个SQL语句的时候,你的程序会停在那里直到没这个SQL语句返回,然后你的程序再往下继续执行。你可以使用无缓冲查询来改变这个行为。

关于这个事情,在PHP的文档中有一个非常不错的说明: mysql_unbuffered_query() 函数:

“mysql_unbuffered_query() sends the SQL query query to MySQL without automatically fetching and buffering the result rows as mysql_query() does. This saves a considerable amount of memory with SQL queries that produce large result sets, and you can start working on the result set immediately after the first row has been retrieved as you don’t have to wait until the complete SQL query has been performed.”

上面那句话翻译过来是说,mysql_unbuffered_query() 发送一个SQL语句到MySQL而并不像mysql_query()一样去自动fethch和缓存结果。这会相当节约很多可观的内存,尤其是那些会产生大量结果的查询语句,并且,你不需要等到所有的结果都返回,只需要第一行数据返回的时候,你就可以开始马上开始工作于查询结果了。

然而,这会有一些限制。因为你要么把所有行都读走,或是你要在进行下一次的查询前调用 mysql_free_result() 清除结果。而且, mysql_num_rows()mysql_data_seek() 将无法使用。所以,是否使用无缓冲的查询你需要仔细考虑。

14. 把IP地址存成 UNSIGNED INT

很多程序员都会创建一个 VARCHAR(15) 字段来存放字符串形式的IP而不是整形的IP。如果你用整形来存放,只需要4个字节,并且你可以有定长的字段。而且,这会为你带来查询上的优势,尤其是当你需要使用这样的WHERE条件:IP between ip1 and ip2。

我们必需要使用UNSIGNED INT,因为 IP地址会使用整个32位的无符号整形。

而你的查询,你可以使用 INET_ATON() 来把一个字符串IP转成一个整形,并使用 INET_NTOA() 把一个整形转成一个字符串IP。在PHP中,也有这样的函数 ip2long()long2ip()。 

1 $r = "UPDATE users SET ip = INET_ATON('{$_SERVER['REMOTE_ADDR']}') WHERE user_id = $user_id";

15. 固定长度的表会更快

如果表中的所有字段都是“固定长度”的,整个表会被认为是 “static” 或 “fixed-length”。 例如,表中没有如下类型的字段: VARCHAR,TEXT,BLOB。只要你包括了其中一个这些字段,那么这个表就不是“固定长度静态表”了,这样,MySQL 引擎会用另一种方法来处理。

固定长度的表会提高性能,因为MySQL搜寻得会更快一些,因为这些固定的长度是很容易计算下一个数据的偏移量的,所以读取的自然也会很快。而如果字段不是定长的,那么,每一次要找下一条的话,需要程序找到主键。

并且,固定长度的表也更容易被缓存和重建。不过,唯一的副作用是,固定长度的字段会浪费一些空间,因为定长的字段无论你用不用,他都是要分配那么多的空间。

使用“垂直分割”技术(见下一条),你可以分割你的表成为两个一个是定长的,一个则是不定长的。

16. 垂直分割

“垂直分割”是一种把数据库中的表按列变成几张表的方法,这样可以降低表的复杂度和字段的数目,从而达到优化的目的。(以前,在银行做过项目,见过一张表有100多个字段,很恐怖)

示例一:在Users表中有一个字段是家庭地址,这个字段是可选字段,相比起,而且你在数据库操作的时候除了个人信息外,你并不需要经常读取或是改写这个字段。那么,为什么不把他放到另外一张表中呢? 这样会让你的表有更好的性能,大家想想是不是,大量的时候,我对于用户表来说,只有用户ID,用户名,口令,用户角色等会被经常使用。小一点的表总是会有好的性能。

示例二: 你有一个叫 “last_login” 的字段,它会在每次用户登录时被更新。但是,每次更新时会导致该表的查询缓存被清空。所以,你可以把这个字段放到另一个表中,这样就不会影响你对用户ID,用户名,用户角色的不停地读取了,因为查询缓存会帮你增加很多性能。

另外,你需要注意的是,这些被分出去的字段所形成的表,你不会经常性地去Join他们,不然的话,这样的性能会比不分割时还要差,而且,会是极数级的下降。

17. 拆分大的 DELETE 或 INSERT 语句

如果你需要在一个在线的网站上去执行一个大的 DELETE 或 INSERT 查询,你需要非常小心,要避免你的操作让你的整个网站停止相应。因为这两个操作是会锁表的,表一锁住了,别的操作都进不来了。

Apache 会有很多的子进程或线程。所以,其工作起来相当有效率,而我们的服务器也不希望有太多的子进程,线程和数据库链接,这是极大的占服务器资源的事情,尤其是内存。

如果你把你的表锁上一段时间,比如30秒钟,那么对于一个有很高访问量的站点来说,这30秒所积累的访问进程/线程,数据库链接,打开的文件数,可能不仅仅会让你泊WEB服务Crash,还可能会让你的整台服务器马上掛了。

所以,如果你有一个大的处理,你定你一定把其拆分,使用 LIMIT 条件是一个好的方法。下面是一个示例: 

01 while (1) {
02     //每次只做1000条
03     mysql_query("DELETE FROM logs WHERE log_date <= '2009-11-01' LIMIT 1000");
04     if (mysql_affected_rows() == 0) {
05         // 没得可删了,退出!
06         break;
07     }
08     // 每次都要休息一会儿
09     usleep(50000);
10 }

18. 越小的列会越快

对于大多数的数据库引擎来说,硬盘操作可能是最重大的瓶颈。所以,把你的数据变得紧凑会对这种情况非常有帮助,因为这减少了对硬盘的访问。

参看 MySQL 的文档 Storage Requirements 查看所有的数据类型。

如果一个表只会有几列罢了(比如说字典表,配置表),那么,我们就没有理由使用 INT 来做主键,使用 MEDIUMINT, SMALLINT 或是更小的 TINYINT 会更经济一些。如果你不需要记录时间,使用 DATE 要比 DATETIME 好得多。

当然,你也需要留够足够的扩展空间,不然,你日后来干这个事,你会死的很难看,参看Slashdot的例子(2009年11月06日),一个简单的ALTER TABLE语句花了3个多小时,因为里面有一千六百万条数据。

19. 选择正确的存储引擎

在 MySQL 中有两个存储引擎 MyISAM 和 InnoDB,每个引擎都有利有弊。酷壳以前文章《MySQL: InnoDB 还是 MyISAM?》讨论和这个事情。

MyISAM 适合于一些需要大量查询的应用,但其对于有大量写操作并不是很好。甚至你只是需要update一个字段,整个表都会被锁起来,而别的进程,就算是读进程都无法操作直到读操作完成。另外,MyISAM 对于 SELECT COUNT(*) 这类的计算是超快无比的。

InnoDB 的趋势会是一个非常复杂的存储引擎,对于一些小的应用,它会比 MyISAM 还慢。他是它支持“行锁” ,于是在写操作比较多的时候,会更优秀。并且,他还支持更多的高级应用,比如:事务。

下面是MySQL的手册

20. 使用一个对象关系映射器(Object Relational Mapper)

使用 ORM (Object Relational Mapper),你能够获得可靠的性能增涨。一个ORM可以做的所有事情,也能被手动的编写出来。但是,这需要一个高级专家。

ORM 的最重要的是“Lazy Loading”,也就是说,只有在需要的去取值的时候才会去真正的去做。但你也需要小心这种机制的副作用,因为这很有可能会因为要去创建很多很多小的查询反而会降低性能。

ORM 还可以把你的SQL语句打包成一个事务,这会比单独执行他们快得多得多。

目前,个人最喜欢的PHP的ORM是:Doctrine

21. 小心“永久链接”

“永久链接”的目的是用来减少重新创建MySQL链接的次数。当一个链接被创建了,它会永远处在连接的状态,就算是数据库操作已经结束了。而且,自从我们的Apache开始重用它的子进程后——也就是说,下一次的HTTP请求会重用Apache的子进程,并重用相同的 MySQL 链接。

在理论上来说,这听起来非常的不错。但是从个人经验(也是大多数人的)上来说,这个功能制造出来的麻烦事更多。因为,你只有有限的链接数,内存问题,文件句柄数,等等。

而且,Apache 运行在极端并行的环境中,会创建很多很多的了进程。这就是为什么这种“永久链接”的机制工作地不好的原因。在你决定要使用“永久链接”之前,你需要好好地考虑一下你的整个系统的架构。

(全文完)

posted @ 2010-07-22 16:49 小马歌 阅读(199) | 评论 (0)编辑 收藏
 

Cassandra 的数据存储结构

Cassandra 的数据模型是基于列族(Column Family)的四维或五维模型。它借鉴了 Amazon 的 Dynamo 和 Google's BigTable 的数据结构和功能特点,采用 Memtable 和 SSTable 的方式进行存储。在 Cassandra 写入数据之前,需要先记录日志 ( CommitLog ),然后数据开始写入到 Column Family 对应的 Memtable 中,Memtable 是一种按照 key 排序数据的内存结构,在满足一定条件时,再把 Memtable 的数据批量的刷新到磁盘上,存储为 SSTable 。

图 1. Cassandra 的数据模型图:

  1. Cassandra 的数据模型的基本概念:
  2. 1. Cluster : Cassandra 的节点实例,它可以包含多个 Keyspace
    2. Keyspace : 用于存放 ColumnFamily 的容器,相当于关系数据库中的 Schema 或 database3. ColumnFamily : 用于存放 Column 的容器,类似关系数据库中的 table 的概念 4. SuperColumn :它是一个特列殊的 Column, 它的 Value 值可以包函多个 Column5. Columns:Cassandra 的最基本单位。由 name , value , timestamp 组成

下面是关于数据模型实例分析 :


图 2. 数据模型实例分析
图 2. 数据模型实例分析


Cassandra 节点的安装和配置

获取 Cassandra

 # wget  http://labs.renren.com/apache-mirror/cassandra/0.6.0/apache- 
cassandra-0.6.0-rc1-bin.tar.gz
# tar -zxvf apache-cassandra-0.6.0-rc1-bin.tar.gz
# mv apache-cassandra-0.6.0-rc1 cassandra
# ls Cassandra



Cassandra 的目录说明

bin 存放与 Cassandra 操作的相关脚本
conf 存放配置文件的目录
interface Cassandra 的 Thrift 接口定义文件,可以用于生成各种编程语言的接口代码
Javadoc 源代码的 javadoc
lib Cassandra 运行时所需的 jar 包

 

配制 Cassandra 节点的数据存储目录和日志目录

修改配制文件 storage-conf.xml:


默认的内容

				
<CommitLogDirectory>/var/lib/cassandra/commitlog</CommitLogDirectory>
<DataFileDirectories>
<DataFileDirectory>/var/lib/cassandra/data</DataFileDirectory>
</DataFileDirectories>



配置后的内容

				
<CommitLogDirectory>/data3/db/lib/cassandra/commitlog</CommitLogDirectory>
<DataFileDirectories>
<DataFileDirectory>/data3/db/lib/cassandra/data</DataFileDirectory>
</DataFileDirectories>

 

修改日志配制文件 log4j.properties:


log4j.properties 配置

				
# 日志路径
#log4j.appender.R.File=/var/log/cassandra/system.log
# 配置后的日志路径 :
log4j.appender.R.File=/data3/db/log/cassandra/system.log

 

创建文件存放数据和日志的目录

 # mkdir – p /data3/db/lib/cassandra 
# mkdir – p /data3/db/log/Cassandra

 

配制完成后,启动 Cassandra

 # bin/Cassandra 

 

显示信息

 INFO 09:29:12,888 Starting up server gossip 
INFO 09:29:12,992 Binding thrift service to localhost/127.0.0.1:9160

 

看到这两行启动回显信息时,说明 Cassandra 已启动成功。

连接到 Cassandra 并添加、获取数据

Cassandra 的 bin 目录已自带了命令行连接工具 cassandra-cli,可使用它连接到 Cassandra,并添加、读取数据。


连接到 Cassandra,并添加、读取数据

				
# bin/cassandra-cli --host localhost --port 9160
Connected to: "Test Cluster" on localhost/9160
Welcome to cassandra CLI.
Type 'help' or '?' for help. Type 'quit' or 'exit' to quit.
cassandra>
cassandra> set Keyspace1.Standard2['studentA']['age'] = '18'
Value inserted
cassandra> get Keyspace1.Standard2['studentA']
=> (column=age, value=18, timestamp=1272357045192000)
Returned 1 results

 

停止 Cassandra 服务


查出 Cassandra 的 pid:16328

				
# ps -ef | grep cassandra
# kill 16328

 

Cassandra 配制文件 storage-conf.xml 相关配制介绍


清单 1. storage-conf.xml 节点配制说明清单

				
<!-- 集群时显示的节点名称 -->
<ClusterName>Test Cluster</ClusterName>
<!-- 节点启动时,是否自动加入到集群中,默认为 false -->
<AutoBootstrap>false</AutoBootstrap>
<!-- 集群的节点配制 -->
<Seeds>
<Seed>127.0.0.1</Seed>
</Seeds>
<!-- 节点之间通迅的监听地址 -->
<ListenAddress>localhost</ListenAddress>
<!--
基于 Thrift 的 cassandra 客户端监听地址,
集群时设为:0.0.0.0 表示侦听所有客户端 , 默认为:localhost
-->
<ThriftAddress>localhost</ThriftAddress>
<!-- 客户端连接的端口 -->
<ThriftPort>9160</ThriftPort>
<!--
FlushDataBufferSizeInMB 将 memtables 上的数据写入在 Disk 上,
超过设定好的限制大小时 ( 默认 32M),则将数据写入磁盘,
FlushIndexBufferSizeInMB 超过设定的时长(默认 8 分钟)后,
将 memtables 由的数据写入磁盘中
-->
<FlushDataBufferSizeInMB>32</FlushDataBufferSizeInMB>
<FlushIndexBufferSizeInMB>8</FlushIndexBufferSizeInMB>
<!--
节点之间的日志记录同步模式。
默认:periodic, 对应配制 CommitLogSyncPeriodInMS
启动 batch 时,则对应的配制 CommitLogSyncBatchWindowInMS
-->
<CommitLogSync>periodic</CommitLogSync>
<!-- 默认为每 10 秒同步一次日志记录 -->
<CommitLogSyncPeriodInMS>10000</CommitLogSyncPeriodInMS>
<!--
<CommitLogSyncBatchWindowInMS>1</CommitLogSyncBatchWindowInMS> -->

 


常用编程语言使用 Cassandra 来存储数据

在使用 Cassandra 时,通常情况下都需要使用第三方插件 Thrift 来生成与 Cassandra 相关的库文件 , 您可以在 http://incubator.apache.org/thrift 下载此插件,并学习它的使用方法。以下是分别在 Java、PHP、Python、C#、Ruby 五种常用编程语言中使用 Cassandra:

Java 程序使用 Cassandra

把 libthrift-r917130.jar,apache-cassandra-0.6.0-rc1.jar 加入到 Eclipse 的编译路径中。

建立数据库连接:使用 libthrift-r917130.jar 的 TTransport 的 open 方法建立起与 Cassandra 服务端 (IP:192.168.10.2 端口:9160) 的连接。

数据库操作:使用 Cassandra.Client 创建一个客户端实例。调用 Client 实例的 insert 方法写入数据,通过 get 方法获取数据。

关闭数据库连接:使用 TTransport 的 close 方法断开与 Cassandra 服务端的连接。


清单 2. Java 连接 Cassandra,写入并读取数据。

				
package com.test.cassandra;|
import java.io.UnsupportedEncodingException;
import org.apache.thrift.transport.TTransport;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.TException;
import org.apache.cassandra.thrift.Cassandra;
import org.apache.cassandra.thrift.Column;
import org.apache.cassandra.thrift.ColumnOrSuperColumn;
import org.apache.cassandra.thrift.ColumnPath;
import org.apache.cassandra.thrift.ConsistencyLevel;
import org.apache.cassandra.thrift.InvalidRequestException;
import org.apache.cassandra.thrift.NotFoundException;
import org.apache.cassandra.thrift.TimedOutException;
import org.apache.cassandra.thrift.UnavailableException;
/*
* 使 Java 客户端连接 Cassandra 并进行读写操作
* @author jimmy
* @date 2010-04-10
*/
public class JCassandraClient{
public static void main(String[] args) throws InvalidRequestException,
NotFoundException, UnavailableException, TimedOutException,
TException, UnsupportedEncodingException {

// 建立数据库连接
TTransport tr = new TSocket("192.168.10.2", 9160);
TProtocol proto = new TBinaryProtocol(tr);
Cassandra.Client client = new Cassandra.Client(proto);
tr.open();
String keyspace = "Keyspace1";
String cf = "Standard2";
String key = "studentA";
// 插入数据
long timestamp = System.currentTimeMillis();
ColumnPath path = new ColumnPath(cf);
path.setColumn("age".getBytes("UTF-8"));
client.insert(keyspace,key,path,"18".getBytes("UTF-8"),
timestamp,ConsistencyLevel.ONE);
path.setColumn("height".getBytes("UTF-8"));
client.insert(keyspace,key,path,"172cm".getBytes("UTF-8"),
timestamp,ConsistencyLevel.ONE);
// 读取数据
path.setColumn("height".getBytes("UTF-8"));
ColumnOrSuperColumn cc = client.get(keyspace, key, path, ConsistencyLevel.ONE);
Column c = cc.getColumn();
String v = new String(c.value, "UTF-8");
// 关闭数据库连接
tr.close();
}
}

 

PHP 程序使用 Cassandra

在 PHP 代码中使用 Cassandra,需要借助 Thrift 来生成需要的 PHP 文件,通过使用 thrift --gen php interface/cassandra.thrift 生成所需要的 PHP 文件,生成的 PHP 文件中提供了与 Cassandra 建立连接、读写数据时所需要的函数。


清单 3. PHP 连接 Cassandra,写入并读取数据。

				
<?php
$GLOBALS['THRIFT_ROOT'] = '/usr/share/php/Thrift';
require_once
$GLOBALS['THRIFT_ROOT'].'/packages/cassandra/Cassandra.php';
require_once
$GLOBALS['THRIFT_ROOT'].'/packages/cassandra/cassandra_types.php';
require_once $GLOBALS['THRIFT_ROOT'].'/transport/TSocket.php';
require_once $GLOBALS['THRIFT_ROOT'].'/protocol/TBinaryProtocol.php';
require_once
$GLOBALS['THRIFT_ROOT'].'/transport/TFramedTransport.php';
require_once
$GLOBALS['THRIFT_ROOT'].'/transport/TBufferedTransport.php';
try {
// 建立 Cassandra 连接
$socket = new TSocket('192.168.10.2', 9160);
$transport = new TBufferedTransport($socket, 1024, 1024);
$protocol = new TBinaryProtocolAccelerated($transport);
$client = new CassandraClient($protocol);
$transport->open();
$keyspace = 'Keyspace1';
$keyUser = "studentA";
$columnPath = new cassandra_ColumnPath();
$columnPath->column_family = 'Standard1';
$columnPath->super_column = null;
$columnPath->column = 'age';
$consistency_level = cassandra_ConsistencyLevel::ZERO;
$timestamp = time();
$value = "18";
// 写入数据
$client->insert($keyspace, $keyUser, $columnPath, $value,
$timestamp, $consistency_level);
$columnParent = new cassandra_ColumnParent();
$columnParent->column_family = "Standard1";
$columnParent->super_column = NULL;
$sliceRange = new cassandra_SliceRange();
$sliceRange->start = "";
$sliceRange->finish = "";
$predicate = new cassandra_SlicePredicate();
list() = $predicate->column_names;
$predicate->slice_range = $sliceRange;
$consistency_level = cassandra_ConsistencyLevel::ONE;
$keyUser = studentA;
// 查询数据
$result = $client->get_slice($keyspace, $keyUser, $columnParent,
$predicate, $consistency_level);
// 关闭连接
$transport->close();
} catch (TException $tx) {
}?>

 

Python 程序使用 Cassandra

在 Python 中使用 Cassandra 需要 Thrift 来生成第三方 Python 库,生成方式: thrift --gen py interface/cassandra.thrift, 然后在 Python 代码中引入所需的 Python 库,生成的 Python 库提供了与 Cassandra 建立连接、读写数据时所需要的方法。


清单 4. Python 连接 Cassandra,写入并读取数据。

				
from thrift import Thrift
from thrift.transport import TTransport
from thrift.transport import TSocket
from thrift.protocol.TBinaryProtocol import
TBinaryProtocolAccelerated
from cassandra import Cassandra
from cassandra.ttypes import *
import time
import pprint
def main():
socket = TSocket.TSocket("192.168.10.2", 9160)
transport = TTransport.TBufferedTransport(socket)
protocol = TBinaryProtocol.TBinaryProtocolAccelerated(transport)
client = Cassandra.Client(protocol)
pp = pprint.PrettyPrinter(indent=2)
keyspace = "Keyspace1"
column_path = ColumnPath(column_family="Standard1", column="age")
key = "studentA"
value = "18 "
timestamp = time.time()
try:
# 打开数据库连接
transport.open()
# 写入数据
client.insert(keyspace,key,column_path,
value,timestamp,ConsistencyLevel.ZERO)
# 查询数据
column_parent = ColumnParent(column_family="Standard1")
slice_range = SliceRange(start="", finish="")
predicate = SlicePredicate(slice_range=slice_range)
result = client.get_slice(keyspace,key,column_parent,
predicate,ConsistencyLevel.ONE)
pp.pprint(result)
except Thrift.TException, tx:
print 'Thrift: %s' % tx.message
finally:
# 关闭连接
transport.close()
if __name__ == '__main__':
main()

 

C# 使用 Cassandra

在 C# 中使用 Cassandra 需要 Thrift.exe 来生成动态链接库,使用 ./thrift.exe --gen csharp interface/cassandra.thrift 生成所需要的 DLL 文件,生成的 DLL 提供了与 Cassandra 建立连接,读写数据等所需的类和方法,在编程环境中引入生成的 DLL,即可使用。


清单 5. C# 连接 Cassandra,写入并读取数据。

				
namespace CshareCassandra{
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Apache.Cassandra;
using Thrift.Protocol;
using Thrift.Transport;
class CassandraClient{
static void Main(string[] args){
// 建立数据库连接
TTransport transport = new TSocket("192.168.10.2", 9160);
TProtocol protocol = new TBinaryProtocol(transport);
Cassandra.Client client = new Cassandra.Client(protocol);
transport.Open();
System.Text.Encoding utf8Encoding = System.Text.Encoding.UTF8;
long timeStamp = DateTime.Now.Millisecond;
ColumnPath nameColumnPath = new ColumnPath(){
Column_family = "Standard1",
Column = utf8Encoding.GetBytes("age")};
// 写入数据
client.insert("Keyspace1","studentA",nameColumnPath,
utf8Encoding.GetBytes("18"),timeStamp, ConsistencyLevel.ONE);
// 读取数据
ColumnOrSuperColumn returnedColumn = client.get("Keyspace1",
"studentA", nameColumnPath, ConsistencyLevel.ONE);
Console.WriteLine("Keyspace1/Standard1: age: {0}, value: {1}",
utf8Encoding.GetString(returnedColumn.Column.Name),
utf8Encoding.GetString(returnedColumn.Column.Value));
// 关闭连接
transport.Close();
}
}}

 

Ruby 使用 Cassandra

在 Ruby 中使用 Cassandra 需要先安装 gem,安装命令:gem install cassandra

安装完成后,打开 Ruby 的 irb 开始使用 Cassandra。


清单 6. Ruby 连接 Cassandra,写入并读取数据

				
> require 'rubygems'
> require 'cassandra'
# 建立数据库连接
> cdb = Cassandra.new('Keyspace1',"192.168.10.1:9160", :retries => 3)
# 写入数据
> cdb.insert(:Standard1, 'studentA', {'age' => '18'})
# 读取数据
> cdb.get(:Standard1, :studentA)
# 关闭连接
> cdb.disconnect

 


搭建 Cassandra 集群环境

Cassandra 的集群是没有中心节点的,各个节点的地位完全相同,节点之间是通过 gossip 的协议来维护集群的状态。


以下是两台安装了 Linux 系统的服务器,且初步设置了 Cassandra 环境和启用了端口 7000,9160:

服务器名 端口 IP 地址
ServiceA 7000,9160 192.168.10.3
ServiceB 7000,9160 192.168.10.2

 

配制服务器 ServiceA、ServiceB 的 storage-conf.xml 文件


ServiceA 的配置

				
<Seeds>
<Seed>192.168.10.3</Seed>
</Seeds>
<ListenAddress>192.168.10.2</ListenAddress>
<ThriftAddress>0.0.0.0</ThriftAddress>



ServiceB 的配置

				
<Seeds>
<Seed>192.168.10.3</Seed>
<Seed>192.168.10.2</Seed>
</Seeds>
<ListenAddress>192.168.10.2</ListenAddress>
<ThriftAddress>0.0.0.0</ThriftAddress>

 

配制完成后,分别启动 ServiceA 和 ServiceB 上的 Cassandra 服务。

查看 ServiceA 和 ServiceB 是否集群成功,可使用 Cassandra 自带的客户端命令

 bin/nodetool --host 192.168.10.2 ring 



集群成功则会返回以下类似信息:

				
Address Status Load Range Ring
106218876142754404016344802054916108445
192.168.10.2 Up 2.55 KB 31730917190839729088079827277059909532 |<--|
192.168.10.3 Up 3.26 KB 106218876142754404016344802054916108445 |-->|

 

使用 Cassandra 命令行工具进行集群测试

从 ServiceB 连接到 ServiceA,可使用命令:

 cassandra-cli -host 192.168.10.3 -port 9160 



集群测试一

				
写入集群数据
ServiceA 连接到 ServiceA:

# set Keyspace1.Standard2['studentAA']['A2A'] = 'a2a'

ServiceB 连接到 ServiceA:

# set Keyspace1.Standard2['studentBA']['B2A'] = 'b2a'

ServiceA 连接到 ServiceB:
# set Keyspace1.Standard2['studentAB']['A2B'] = 'a2b'

 

获取集群数据:

 ServiceA 连接到 ServiceA : 
# get Keyspace1.Standard2['studentAA'],
get Keyspace1.Standard2['studentBA'],
get Keyspace1.Standard2['studentAB']

ServiceB 连接到 ServiceA :
# get Keyspace1.Standard2['studentAA'],
get Keyspace1.Standard2['studentBA'],
get Keyspace1.Standard2['studentAB']

ServiceA 连接到 ServiceB :
# get Keyspace1.Standard2['studentAA'],
get Keyspace1.Standard2['studentBA'],
get Keyspace1.Standard2['studentAB']

 

清单 8. 集群测试清单二

ServiceA 停止 Cassandra 服务,ServiceA 连接到 ServiceB 并写入数据

 # set Keyspace1.Standard2['studentAR']['A2R'] = 'a2R'

 

启动 ServiceA,并链接到 ServiceA 本身,读取刚才在 ServiceB 写入的数据

 # bin/cassandra-cli -host 192.168.10.3 -port 9160 
# get Keyspace1.Standard2['studentAR']

 


总结

以上我们介绍了 Cassandra 的数据模型、节点安装和配置、常用编程语言中使用 Cassandra 以及 Cassandra 的集群和测试。Cassandra 是一个高性能的 P2P 去中心化的非关系型数据库,可以分布式进行读写操作。在系统运行时可以随意的添加或删降字段,是 SNS 应用的理想数据库。

posted @ 2010-07-22 16:17 小马歌 阅读(917) | 评论 (0)编辑 收藏
仅列出标题
共95页: First 上一页 59 60 61 62 63 64 65 66 67 下一页 Last