利用web应用中的不均衡性重构你的技术细节


前提:
Lamp架构(原理上虽然jsp,.net等都一样,但我现在只懂php了)

1.用户对准确度的要求是不一样的.银行会要求客户帐上的钱的数目永远是精确的,但是论坛的在线人数,贴子数没这个必要.每天有成千上成的贴子发表。谁会去对质?我们只半个小时算一个甚至每天算一次就行了.所以,应用中要是没加Cron,那就肯定不是一个好应用.想象一下awstats,每当你的老板让你给演示一下前一周的访问周数据时,它就嘎吱嘎吱地算上老半天,然后在你的老板快要睡着时IE终于弹出一个页:服务器超时…. 你就知道要把一些服务设为定时跑一次,而不是时时保持最新最准确了
2.瓶颈永远是先在CPU/内存/磁盘IO中的一个出现.他们很少是同时告急的.因此,针对这个设计一个.比如:某台服务器上CPU占用平均为60%左右,而内存占用是5%左右。那么我们可以预先猜测,CPU资源会首先占尽。由于日志显示占用CPU最多的是mysqld进程,说明mysql查询耗尽了CPU.再查之,原来是贴子主题太多,一个表有上G大小,索引不当.
找到问题,于是马上做出改进.原来的URL已经优化成***.com/topic/10/10001.html,***.com/topic/12/12001.html…这样的了,但其实后台是用rewrite的.而id从1到100,000的主题都因为已经太老,无人参与回复了,所以没必要每次都查库.马上写个脚本,将***.com/topic/1.html….***.com/topic/10/10001.html…这些页面都读下来,保存为web根下/topic/***/***.html文件。现在这些文件是实际存在的了,Apache会优先读这个静态文件,而不是适用rewriterule了.
(注:我的apache 的rewrite规则是这样写的,这样文件找不到的时候会交给index.php来处理.

rewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
rewriterule . /index.php [L]

)
现在再看一看,mysqld占CPU是少了不了.但还是不够啊.咱们的内存还没有用上呢。才10%,不行.于是又有了办法:架上squid或是类似缓存服务,大量起用内存缓存,现在就好多了.内存,CPU都用上了.如果这时CPU使用率仍然领先内存,那咱们再启用mem_cache,不过启用mem_cache后要改程序,改得好才能真正提交效率.
3.同是数据,用户名跟用户发的贴子内容这两个数据就不一样.一个咱们用varchar存在库中,另一个咱们得用text来存.这两个占用资源也大不一样啊.而且,贴子主题和发表用户名的读取的次数远大于贴子内容读取次数.这也是一个不均衡。于是基于这样的考虑,我把存储贴子数据表分开了。
现在的表是这样的:一个表存储用户名,发表时间,贴子标题等信息.另外建10个表,专门用来存储发贴内容.具体存在哪一个表,由这个主题贴ID的最后一位来定.
好了,由于发贴内容比较长,我把它分表(为了简化模型,是分表。实际有可能是分库,分服务器)了.而贴子主题由于要进行列表显示,搜索等操作,就没有分表.内容的查询效率是提高了。可是由于贴子主题要进行大量列表显示和搜索,且都在一个表中,还是慢啊.
不急,咱再分析.
a:这个表中存储的数据都比较短小.
b:这个表中常用来做列表显示,用户往往在论坛中是看了第一页标题,可能没什么吸引人的,于是又点了下一页…部分贴子会进去看一下内容.但是你见过用户一直点到第100页吗?而且,用户看贴子的量也绝对大于发贴子….除非他是供职于某论坛的版主….
好了,又总结出不均衡性来了.大多数情况下用户只看前100页,那咱们就把这100页数据拿出来,建一个内存表,再放一份.Mysql内存表有一个限制,就是字段不能超过255字节,但咱们的这个表刚好符合.现在咱们写数据时主题表和存主题的内存表中都写入一次,读数据的时候,就从内存表中读…这样是不是快很多了?

参考:

把握web 开发的平衡与不平衡,
http://www.162cm.com/archives/388.html

Mysql server tuning(译文)

从某个英文pdf文档译过来的,很久了,还是Mysql Conference 2007时的PDF.
(如果能找到,我把原文地址和pdf给出来)
向原作者致谢….

调整Server Config时需要考虑的事情
设置是针对负载的,不存在说有什么针对一个16GB内存的优化方案这种说法.
存储引擎的选择或是混用是很重要的
有些设置取决于您的硬件和操作系统。

Approaching?tuning
您只需要设置一部分,其余的一些要具体分析(这些也很难决定如何去调整)
有些设置会影响Mysql的行为和安全,要小心
有些设置是可以基于连接的,利用这一点!
首先优化您的查询,否则您需要重新检阅您的设置。
常见错误:
运行默认设置。(当然如果您用来在您的本儿上存您的DVD收集资料, 那当然可以)
直接使用其他人的My.conf文件,都没检查一下是否符合您的需求。
虽然只有一两个查询需要,您仍为它设置了一个巨大的全局设置。您可以这样:
SET sort_buffer_size=128000000;

set sort_buffer_size=DEFAULT

我们能得到的信息:
SHOW STATUS
SHOW INNODB STATUS
操作系统的命令比如:vmstat,iostat,mpstat

使用内存:
为Mysql server分配更多内存常常带来显著的性能提升.
但是分配过多的内存会损失性能甚至是稳定性
检查一下:
Swapping
查看swap IO ,这比简单地使用swap space要好
使用了32位以上的地址(确保OS和mysql server都是64位的)

战胜Swapping
有些操作系统会使用Swap,虽然内存充足。
处理好IO cache和swap间的平衡。
设置VM子系统为只有在最后一次使用resort的时候才swap
tune VM subsystem to use swap only as last resort
echo 1 >/proc/sys/vm/swappiness
如果在linux上使用innodb可以用直接磁盘IO:
innodb_flush_method=O_DIRECt

了解一下变量的单位:
table_cache=128M
错了,它的单位是用个(entries)来衡量的。
key_buffer_size=1024
错了,key buffer size要用bytes来设定。
innodb_max_dirty_pages_pct=8GB
这个设置准确无误。

了解作用域和内存分配规则
key_buffer_size
全局的,由所有线程共用。
sort_buffer_size
需要排序时将内存分配给线程。
read_buffer_size:
一次性就分配指定大小的内存,虽然可能更小的内存就够了
过大的值反而会让事情慢得糟糕
更多的分配常常由OS来完成。
tmp_table_size:
指定一个最大值,在需要的时候会增长到这个值 。
调得过大也没什么事

细节:
关于SHOW STATUS
show global status:了解服务器的全局状态。了解负载的好方法。在Mysql4.1和以下版本中唯一一个。
show local status:
查询/会话配置的好东西
从mysql 5.0开始能用
show status 默认就是这个
有些变量是全局的
也出现会在show status 的输出中
mysqladmin extended -i100 -r
抽样一下,看看Mysql server现在干了些什么,这是个好办法
Show status:
aborted clients:
不用担心,许多程序都没有恰当地关闭连接。
aborted_connects:
可能意味着认证失败,网络超时或其他错误。
值得看一看,因为可能引起主机blocked out
max_connect_errors=100000
可能意味着有人尝试暴力密码破解
binlog_cache_disk_use/binlog_cache_use
bin log 溢出到磁盘的频率。
如果太频繁,需要增大–binlog_cache_size
com_xxx:
按查询来了解服务器负载
查询可以是相当复杂多样的

升级了!Fedora 7 moonshine 升级成功

昨天以5Mb/s的速度提心吊胆地下Fedora core 7,一直担心网管会过来叫我停。还好,到下完他也没有过来。

马上烧盘,先在台式机上安装,接着又在notebook上安装。终于都安装成功,不过有点小问题:

1. Fedoar Core 6时,我的硬盘 显示是hda1,hda2…..hda6,没想到到7之后,变成了sda1,sda2,sda3,直接导致我的swap分区和windows分区无法加载。于是改了fstab,将hda3改成sda3,加载成功。

2. 原来安装的madwifi不起作用了.madwifi是依赖于内核的,内核升级一次,madwifi就需要重新安装一次。不过这次我惊喜地发现,yum search ***时,下载的不再是primary.xml,而是primary.sqlite.bz2,也就是说源数据不再以xml格式存储了,而是sqlite的格式,同时压缩方式也改了。不错的进步。原来分析这个数据要老长一段时间 .这次我不再自行编译madwifi,而是yum install madwifi kmod-madwifi,安装之后,仍是无动静。这时想起来madwifi是依赖于内核的,果然,重启之后就能用wireless 连AP了。
升级之后,界面漂亮多了,不再是那个难看的DNA了。本来宏伟前几天老秀他新装的vista的界面(在这这前,我的fedora core 6+beryl可要比他的XP 好看多了),现在,fedora 7 界面也不输vista 了。

150行代码,搞定中文分词

中文分词一直是一个看起来似乎比较神秘的东西。记得java中的lucene好像自带了两个分词器。一个是按汉字分,就是一个字分成一个词。比如”我要到饭馆吃饭“,就被分成”我/要/到/饭/馆/吃/饭”.别一个是相邻的两个字分成一个词,分出来的结果是”我要/要到/到饭/饭馆/馆吃/吃饭”.然而这两种虽说在做搜索时建索引什么的操作时也是相当有用的,但是毕竟是一种权宜之计,咱不能一直停留在这个水平上。

下面是来自Rlucene的一段示例代码,154行,利用sogou的词库搞定了中文分词。代码如下:

#! /usr/bin/ruby
require “socket”
#通过网络得到分词结果
def segChinese_net(line)
@conn=TCPSocket.open(“localhost”,1099)
@conn.write(line)
line=@conn.read
return line.split(” “)
end
@f= open(“dict/sogou.txt”)
@datas=@f.read.split(“n”)
@f.close

@f2=open(“dict/firstword.txt”)
@firstwords=@f2.read.split(“n”)
@f2.close
@maintable=Hash.new()
@firstwords.each{|x|
@maintable[x]=[x]
}
@datas.each{|x|
@maintable[x[0].chr+x[1].chr+x[2].chr].push(x)
}
def segChinese(line)
temp=0
max=(line.length/3)
words=[]
while(temp
pos=temp*3
str1=line[pos].chr+line[pos+1].chr+line[pos+2].chr
str12=str1
str1234=str1
if(temp<(max-1))
str2=line[pos+3].chr+line[pos+4].chr+line[pos+5].chr
str12=line[pos].chr+line[pos+1].chr+line[pos+2].chr+line[pos+3].chr+line[pos+4].chr+line[pos+5].chr
end
if(temp<(max-3))
str1234=line[pos].chr+line[pos+1].chr+line[pos+2].chr+line[pos+3].chr+line[pos+4].chr+line[pos+5].chr+line[pos+6].chr+line[pos+7].chr+line[pos+8].chr+line[pos+9].chr+line[pos+10].chr+line[pos+11].chr
end
if(!@maintable[str1].nil?)
if(!@maintable[str1].index(str1234).nil?)
temp+=4
words.push(str1234)
else
if(!@maintable[str1].index(str12).nil?)
temp+=2
words.push(str12)
else
words.push(str1)
temp+=1
end
end
else
words.push(str1)
temp+=1
end
end
return words
end
#return an array of the segemention.
#得到当前句子的分词结果.
def segment(str)

chars=Hash.new(0)
chars[“char”]=”~!@#$%^&*()_+}{[]\|”‘:;/?><,. tnba”
chars[“num”]=”0123456789″
chars[“alpha”]=”abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ”
bytes=Hash.new(0)
i=0
buffer=””
#oldtype 表示上一个字符的种类:字符还是字母还是数字
#newtype表示本字符的种类
#type:1,表示字符,2表示数字,3表示字母,4表示属于汉字(多字节字符)
#Buffer用来存储当未完成的一个词.
#只有当当前字符类型与前一个字符类型不一致时才输出buffer,并清空buffer,改为当前字符值.
#当与前一字符类型相同时,仅是把当前字符推入buffer(加到buffer的后面)
#oldtype为4时,当输出一个buffer时,还需要对当前buffer进行中文分词
oldtype=0
newtype=0
flag=0
words=[]
0.upto(str.length-1){|a|
if(flag>0)
flag=flag-1
else
x=str[a]
if(!chars[“char”].index(x.chr).nil?)
newtype=1
if(newtype==oldtype)
buffer=buffer+x.chr
else
if(oldtype==4)
words.concat(segChinese(buffer.strip))
buffer=x.chr
else
words.push( buffer) if (buffer.strip!=””)
buffer=x.chr
end
end
oldtype=1
end
if(!chars[“num”].index(x.chr).nil?)
newtype=2
if(newtype==oldtype)
buffer=buffer+x.chr
else
if(oldtype==4)
words.concat(segChinese(buffer.strip))
buffer=x.chr
else
words.push(buffer) if (buffer.strip!=””)
buffer=x.chr
end
end
oldtype=2
end
if(!chars[“alpha”].index(x.chr).nil?)
newtype=3
if(newtype==oldtype)
buffer=buffer+x.chr
else
if(oldtype==4)
words.concat(segChinese(buffer.strip))
buffer=x.chr
else
words.push(buffer) if (buffer.strip!=””)
buffer=x.chr
end
end
oldtype=3
end
if(x>127)
flag=+2
newtype=4
if(newtype==oldtype)
buffer=buffer+str[a].chr+str[a+1].chr+str[a+2].chr
else
words.push(buffer) if(buffer.strip!=””)
buffer=str[a].chr+str[a+1].chr+str[a+2].chr
end
oldtype=4
end
end
}
if(oldtype==4)
words.concat(segChinese(buffer.strip))
else
words.push(buffer) if(buffer.strip!=””)
end
total=””
words.each{|x| total=total+x+” “}
return total
#puts total
end

ruby果然就是牛B极了。在学着用BCB折腾nsapi的时候 ,发现了asp,于是我觉得asp真是一个比较牛B的东西。接着发现了php,于是我扔掉了asp, 接着发现python比php有前途,现在,python还没摸透,又瞅 上ruby了。世界变化真是太快了,我快跟不上啦。

不过要实用,可能还得做些调整。 一般来说,分词的词库得自己整,sogou这个示例可以,真起来肯定有问题。而且要实用,在结果准确度上还要下功能。

lighttpd上搞定wordpress,给出详细步聚.

千辛万苦,终于将博客搬家了。原来是用的edong.com的主机,现在转到了lighttpd+fastcgi上面。

第一步:导出原空间的数据。这个得用phpmyadmin来进行。注意的时,在导出数据时,由于导出来的中文有乱码,故而我先将所有text,varchar类型数据转成了blob类型 ,然后选中将“二进制区域使用十六进制显示”这一项,这样,导出的数据中就不会有显示不了的乱码什么的,有文字的地方都是0xf535acd….这样的东东。

第二步:自然是在新服务器上用mysql -u *** -p newdatabasename < export.sql 将数据库导入进去。

第三步:修改wp-config.php中数据库连接信息。

第四步:如果这时你访问时,能直接用原密码直接进入数据库,那么很幸运,你直接登陆就是了!

如果能登陆,但进入后台时,提示你无权限进入本页面,那么也很幸运,你又可以再熟悉一次安装流程了。这时很简单,把wp-config.php中的表示数据表前缀的改掉,然后再走流程安装一次。安装完后之后,记得登陆phpmyadmin把你需要的表比如wp_posts改成新的前缀名(之前你当然得删除wordpress的安装流程为你生成的对应的表)。比如,本来我的表都是带wp_的前缀,现在将table_prefix改为wp_2然后进行了安装,那么您应该把wp2_posts表删掉,然后把wp_posts改名为wp2_posts.同理,其他需要转移的表比如comments,categories等 等也要转移地过来。

在安装时,我遇到了一点小麻烦:当输入我的email进行下一步时,系统提示这是第二步,并告诉我将要进行一些建表,加载数据操作。但是不幸地是,没有告诉我新生成的密码!我找到了这一行:

$random_password = substr(md5(uniqid(microtime())), 0, 6);

改成为

$random_password=”123456″;

然后将

@wp_mail($admin_email,_(“New wordpress blog”,….)

注释掉。现在没有什么拦住我了。我猜想是因为新的机器上没有sendmail服务,所以导致安装进程不能进行下去。现在安装成功能,默认密码就是:123456.

最后一步:太重要了:修改永久链接,以保持和原来的url一致。如果你的博客pr不高,访问量不怎么高,那么做不做无所谓。但是从SEO角度讲,最好还是保持一致。我原来的博客的链接设置的是:

/archives/%post_id%.html


但是现在是lighttpd做server,它不支持apache的.htaccess文件里写mod_rewrite规则,怎么办?

我google了一下,找到一段巨复杂的正则表达式,却仍 不管用。自己看了一下.htaccess:

RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]

就是指当请求的地址不是一个文件也不是一个目录时,转向到index.php这个文件来处理,由index.php来判断、处理。okay,现在好办了,我马上在lighttpd里加了这么一段:

server.error-handler-404= “/index.php”

.okay,现在重启lighttpd,一切完美!现在你的wordpress又可以像在apache下拥有mod_rewrite一样可以随意设置永久链接了!

特别感谢老覃同志的帮忙.