2009年12月29日星期二

memcached全面剖析–5. memcached的应用和兼容程序

作者: charlee
网址: http://tech.idv2.com/2008/07/10/memcached-001

发表日:2008/7/2
作者:长野雅广(Masahiro Nagano)
原文链接:http://gihyo.jp/dev/feature/01/memcached/0001

mixi案例研究


mixi在提供服务的初期阶段就使用了memcached。 随着网站访问量的急剧增加,单纯为数据库添加slave已无法满足需要,因此引入了memcached。 此外,我们也从增加可扩展性的方面进行了验证,证明了memcached的速度和稳定性都能满足需要。 现在,memcached已成为mixi服务中非常重要的组成部分。
memcached-0005-01.png

图1 现在的系统组件

服务器配置和数量


mixi使用了许许多多服务器,如数据库服务器、应用服务器、图片服务器、 反向代理服务器等。单单memcached就有将近200台服务器在运行。 memcached服务器的典型配置如下:

  • CPU:Intel Pentium 4 2.8GHz

  • 内存:4GB

  • 硬盘:146GB SCSI

  • 操作系统:Linux(x86_64)


这些服务器以前曾用于数据库服务器等。随着CPU性能提升、内存价格下降, 我们积极地将数据库服务器、应用服务器等换成了性能更强大、内存更多的服务器。 这样,可以抑制mixi整体使用的服务器数量的急剧增加,降低管理成本。 由于memcached服务器几乎不占用CPU,就将换下来的服务器用作memcached服务器了。

memcached进程


每台memcached服务器仅启动一个memcached进程。分配给memcached的内存为3GB, 启动参数如下:
[bash]
/usr/bin/memcached -p 11211 -u nobody -m 3000 -c 30720
[/bash]
由于使用了x86_64的操作系统,因此能分配2GB以上的内存。32位操作系统中, 每个进程最多只能使用2GB内存。也曾经考虑过启动多个分配2GB以下内存的进程, 但这样一台服务器上的TCP连接数就会成倍增加,管理上也变得复杂, 所以mixi就统一使用了64位操作系统。
另外,虽然服务器的内存为4GB,却仅分配了3GB,是因为内存分配量超过这个值, 就有可能导致内存交换(swap)。连载的第2次中 前坂讲解过了memcached的内存存储“slab allocator”,当时说过,memcached启动时 指定的内存分配量是memcached用于保存数据的量,没有包括“slab allocator”本身占用的内存、 以及为了保存数据而设置的管理空间。因此,memcached进程的实际内存分配量要比 指定的容量要大,这一点应当注意。
mixi保存在memcached中的数据大部分都比较小。这样,进程的大小要比 指定的容量大很多。因此,我们反复改变内存分配量进行验证, 确认了3GB的大小不会引发swap,这就是现在应用的数值。

memcached使用方法和客户端


现在,mixi的服务将200台左右的memcached服务器作为一个pool使用。 每台服务器的容量为3GB,那么全体就有了将近600GB的巨大的内存数据库。 客户端程序库使用了本连载中多次提到车的Cache::Memcached::Fast,与服务器进行交互。当然,缓存的分布式算法使用的是 第4次介绍过的 Consistent Hashing算法。

应用层上memcached的使用方法由开发应用程序的工程师自行决定并实现。 但是,为了防止车轮再造、防止Cache::Memcached::Fast上的教训再次发生, 我们提供了Cache::Memcached::Fast的wrap模块并使用。

通过Cache::Memcached::Fast维持连接


Cache::Memcached的情况下,与memcached的连接(文件句柄)保存在Cache::Memcached包内的类变量中。 在mod_perl和FastCGI等环境下,包内的变量不会像CGI那样随时重新启动, 而是在进程中一直保持。其结果就是不会断开与memcached的连接, 减少了TCP连接建立时的开销,同时也能防止短时间内反复进行TCP连接、断开 而导致的TCP端口资源枯竭。
但是,Cache::Memcached::Fast没有这个功能,所以需要在模块之外 将Cache::Memcached::Fast对象保持在类变量中,以保证持久连接。
[perl]
package Gihyo::Memcached;
use strict; use warnings;
use Cache::Memcached::Fast;
my @server_list = qw/192.168.1.1:11211 192.168.1.1:11211/;
my $fast; ## 用于保持对象
sub new {
my $self = bless {}, shift;
if ( !$fast ) {
$fast = Cache::Memcached::Fast->new({ servers => \@server_list });
}
$self->{_fast} = $fast;
return $self;
}
sub get {
my $self = shift;
$self->{_fast}->get(@_);
}
[/perl]
上面的例子中,Cache::Memcached::Fast对象保存到类变量$fast中。

公共数据的处理和rehash


诸如mixi的主页上的新闻这样的所有用户共享的缓存数据、设置信息等数据, 会占用许多页,访问次数也非常多。在这种条件下,访问很容易集中到某台memcached服务器上。 访问集中本身并不是问题,但是一旦访问集中的那台服务器发生故障导致memcached无法连接,就会产生巨大的问题。
连载的第4次 中提到,Cache::Memcached拥有rehash功能,即在无法连接保存数据的服务器的情况下,会再次计算hash值,连接其他的服务器。
但是,Cache::Memcached::Fast没有这个功能。不过,它能够在连接服务器失败时,短时间内不再连接该服务器的功能。
[perl]
my $fast = Cache::Memcached::Fast->new({
max_failures => 3,
failure_timeout => 1
});
[/perl]
在failure_timeout秒内发生 max_failures 以上次连接失败,就不再连接该memcached服务器。我们的设置是1秒钟3次以上。
此外,mixi还为所有用户共享的缓存数据的键名设置命名规则,符合命名规则的数据会自动保存到多台memcached服务器中, 取得时从中仅选取一台服务器。创建该函数库后,就可以使memcached服务器故障不再产生其他影响。

memcached应用经验


到此为止介绍了memcached内部构造和函数库,接下来介绍一些其他的应用经验。

通过daemontools启动


通常情况下memcached运行得相当稳定,但mixi现在使用的最新版1.2.5 曾经发生过几次memcached进程死掉的情况。架构上保证了即使有几台memcached故障 也不会影响服务,不过对于memcached进程死掉的服务器,只要重新启动memcached, 就可以正常运行,所以采用了监视memcached进程并自动启动的方法。于是使用了daemontools。
daemontools是qmail的作者DJB开发的UNIX服务管理工具集,其中名为supervise的程序可用于服务启动、停止的服务重启等。

这里不介绍daemontools的安装了。mixi使用了以下的run脚本来启动memcached。
[bash]
#!/bin/sh
if [ -f /etc/sysconfig/memcached ];then
. /etc/sysconfig/memcached
fi
exec 2>&1
exec /usr/bin/memcached -p $PORT -u $USER -m $CACHESIZE -c $MAXCONN $OPTIONS
[/bash]

监视


mixi使用了名为“nagios”的开源监视软件来监视memcached。

在nagios中可以简单地开发插件,可以详细地监视memcached的get、add等动作。 不过mixi仅通过stats命令来确认memcached的运行状态。
[perl]
define command {
command_name check_memcached
command_line $USER1$/check_tcp -H $HOSTADDRESS$ -p 11211 -t 5 -E -s 'stats\r\nquit\r\n' -e 'uptime' -M crit
}
[/perl]
此外,mixi将stats目录的结果通过rrdtool转化成图形,进行性能监视,并将每天的内存使用量做成报表,通过邮件与开发者共享。

memcached的性能


连载中已介绍过,memcached的性能十分优秀。我们来看看mixi的实际案例。 这里介绍的图表是服务所使用的访问最为集中的memcached服务器。
memcached-0005-02.png

图2 请求数
memcached-0005-03.png

图3 流量
memcached-0005-04.png

图4 TCP连接数
从上至下依次为请求数、流量和TCP连接数。请求数最大为15000qps,流量达到400Mbps,这时的连接数已超过了10000个。该服务器没有特别的硬件,就是开头介绍的普通的memcached服务器。此时的CPU利用率为:
memcached-0005-05.png

图5 CPU利用率
可见,仍然有idle的部分。因此,memcached的性能非常高,可以作为Web应用程序开发者放心地保存临时数据或缓存数据的地方。

兼容应用程序


memcached的实现和协议都十分简单,因此有很多与memcached兼容的实现。一些功能强大的扩展可以将memcached的内存数据写到磁盘上,实现数据的持久性和冗余。连载第3次 介绍过,以后的memcached的存储层将变成可扩展的(pluggable),逐渐支持这些功能。
这里介绍几个与memcached兼容的应用程序。

repcached

为memcached提供复制(replication)功能的patch。

Flared

存储到QDBM。同时实现了异步复制和fail over等功能。

memcachedb

存储到BerkleyDB。还实现了message queue。

Tokyo Tyrant

将数据存储到Tokyo Cabinet。不仅与memcached协议兼容,还能通过HTTP进行访问。


Tokyo Tyrant案例


mixi使用了上述兼容应用程序中的Tokyo Tyrant。Tokyo Tyrant是平林开发的 Tokyo Cabinet DBM的网络接口。它有自己的协议,但也拥有memcached兼容协议,也可以通过HTTP进行数据交换。Tokyo Cabinet虽然是一种将数据写到磁盘的实现,但速度相当快。
mixi并没有将Tokyo Tyrant作为缓存服务器,而是将它作为保存键值对组合的DBMS来使用。主要作为存储用户上次访问时间的数据库来使用。它与几乎所有的mixi服务都有关,每次用户访问页面时都要更新数据,因此负荷相当高。MySQL的处理十分笨重,单独使用memcached保存数据又有可能会丢失数据,所以引入了Tokyo Tyrant。但无需重新开发客户端,只需原封不动地使用 Cache::Memcached::Fast 即可,这也是优点之一。关于Tokyo Tyrant的详细信息,请参考本公司的开发blog。

总结


到本次为止,“memcached全面剖析”系列就结束了。我们介绍了memcached的基础、内部结构、分散算法和应用等内容。读完后如果您能对memcached产生兴趣,就是我们的荣幸。关于mixi的系统、应用方面的信息,请参考本公司的开发blog。感谢您的阅读。

2009年12月28日星期一

memcached全面剖析–4. memcached的分布式算法

作者: charlee
网址: http://tech.idv2.com/2008/07/10/memcached-001

发表日:2008/7/2
作者:长野雅广(Masahiro Nagano)
原文链接:http://gihyo.jp/dev/feature/01/memcached/0001

memcached的分布式


正如第1次中介绍的那样,memcached虽然称为“分布式”缓存服务器,但服务器端并没有“分布式”功能。服务器端仅包括 第2次第3次 前坂介绍的内存存储功能,其实现非常简单。至于memcached的分布式,则是完全由客户端程序库实现的。这种分布式是memcached的最大特点。

memcached的分布式是什么意思?


这里多次使用了“分布式”这个词,但并未做详细解释。现在开始简单地介绍一下其原理,各个客户端的实现基本相同。
下面假设memcached服务器有node1~node3三台,应用程序要保存键名为“tokyo”“kanagawa”“chiba”“saitama”“gunma” 的数据。
memcached-0004-01.png

图1 分布式简介:准备
首先向memcached中添加“tokyo”。将“tokyo”传给客户端程序库后,客户端实现的算法就会根据“键”来决定保存数据的memcached服务器。服务器选定后,即命令它保存“tokyo”及其值。
memcached-0004-02.png

图2 分布式简介:添加时
同样,“kanagawa”“chiba”“saitama”“gunma”都是先选择服务器再保存。
接下来获取保存的数据。获取时也要将要获取的键“tokyo”传递给函数库。函数库通过与数据保存时相同的算法,根据“键”选择服务器。使用的算法相同,就能选中与保存时相同的服务器,然后发送get命令。只要数据没有因为某些原因被删除,就能获得保存的值。
memcached-0004-03.png

图3 分布式简介:获取时
这样,将不同的键保存到不同的服务器上,就实现了memcached的分布式。memcached服务器增多后,键就会分散,即使一台memcached服务器发生故障 无法连接,也不会影响其他的缓存,系统依然能继续运行。
接下来介绍第1次 中提到的Perl客户端函数库Cache::Memcached实现的分布式方法。

Cache::Memcached的分布式方法


Perl的memcached客户端函数库Cache::Memcached是 memcached的作者Brad Fitzpatrick的作品,可以说是原装的函数库了。

该函数库实现了分布式功能,是memcached标准的分布式方法。

根据余数计算分散


Cache::Memcached的分布式方法简单来说,就是“根据服务器台数的余数进行分散”。求得键的整数哈希值,再除以服务器台数,根据其余数来选择服务器。
下面将Cache::Memcached简化成以下的Perl脚本来进行说明。
[perl]
use strict;
use warnings;
use String::CRC32;
my @nodes = ('node1','node2','node3');
my @keys = ('tokyo', 'kanagawa', 'chiba', 'saitama', 'gunma');
foreach my $key (@keys) {
my $crc = crc32($key); # CRC値
my $mod = $crc % ( $#nodes + 1 );
my $server = $nodes[ $mod ]; # 根据余数选择服务器
printf "%s => %s\n", $key, $server;
}
[/perl]
Cache::Memcached在求哈希值时使用了CRC。

首先求得字符串的CRC值,根据该值除以服务器节点数目得到的余数决定服务器。上面的代码执行后输入以下结果:
[perl]
tokyo => node2
kanagawa => node3
chiba => node2
saitama => node1
gunma => node1
[/perl]
根据该结果,“tokyo”分散到node2,“kanagawa”分散到node3等。多说一句,当选择的服务器无法连接时,Cache::Memcached会将连接次数 添加到键之后,再次计算哈希值并尝试连接。这个动作称为rehash。不希望rehash时可以在生成Cache::Memcached对象时指定“rehash => 0”选项。

根据余数计算分散的缺点


余数计算的方法简单,数据的分散性也相当优秀,但也有其缺点。那就是当添加或移除服务器时,缓存重组的代价相当巨大。添加服务器后,余数就会产生巨变,这样就无法获取与保存时相同的服务器,从而影响缓存的命中率。用Perl写段代码来验证其代价。
[perl]
use strict;
use warnings;
use String::CRC32;
my @nodes = @ARGV;
my @keys = ('a'..'z');
my %nodes;
foreach my $key ( @keys ) {
my $hash = crc32($key);
my $mod = $hash % ( $#nodes + 1 );
my $server = $nodes[ $mod ];
push @{
$nodes {
$server
}
}, $key;
}
foreach my $node ( sort keys %nodes ) {
printf "%s: %s\n", $node, join ",", @{ $nodes{$node} };
}
[/perl]
这段Perl脚本演示了将“a”到“z”的键保存到memcached并访问的情况。将其保存为mod.pl并执行。
首先,当服务器只有三台时:
[bash]
$ mod.pl node1 node2 nod3
node1: a,c,d,e,h,j,n,u,w,x
node2: g,i,k,l,p,r,s,y
node3: b,f,m,o,q,t,v,z
[/bash]
结果如上,node1保存a、c、d、e……,node2保存g、i、k……,每台服务器都保存了8个到10个数据。
接下来增加一台memcached服务器。
[bash]
$ mod.pl node1 node2 node3 node4
node1: d,f,m,o,t,v
node2: b,i,k,p,r,y
node3: e,g,l,n,u,w
node4: a,c,h,j,q,s,x,z
[/bash]
添加了node4。可见,只有d、i、k、p、r、y命中了。像这样,添加节点后 键分散到的服务器会发生巨大变化。26个键中只有六个在访问原来的服务器,其他的全都移到了其他服务器。命中率降低到23%。在Web应用程序中使用memcached时,在添加memcached服务器的瞬间缓存效率会大幅度下降,负载会集中到数据库服务器上,有可能会发生无法提供正常服务的情况。
mixi的Web应用程序运用中也有这个问题,导致无法添加memcached服务器。但由于使用了新的分布式方法,现在可以轻而易举地添加memcached服务器了。这种分布式方法称为 Consistent Hashing。

Consistent Hashing


关于Consistent Hashing的思想,mixi株式会社的开发blog等许多地方都介绍过,这里只简单地说明一下。

Consistent Hashing的简单说明


Consistent Hashing如下所示:首先求出memcached服务器(节点)的哈希值,并将其配置到0~232的圆(continuum)上。然后用同样的方法求出存储数据的键的哈希值,并映射到圆上。然后从数据映射到的位置开始顺时针查找,将数据保存到找到的第一个服务器上。如果超过232仍然找不到服务器,就会保存到第一台memcached服务器上。
memcached-0004-04.png

图4 Consistent Hashing:基本原理
从上图的状态中添加一台memcached服务器。余数分布式算法由于保存键的服务器会发生巨大变化 而影响缓存的命中率,但Consistent Hashing中,只有在continuum上增加服务器的地点逆时针方向的 第一台服务器上的键会受到影响。
memcached-0004-05.png

图5 Consistent Hashing:添加服务器
因此,Consistent Hashing最大限度地抑制了键的重新分布。而且,有的Consistent Hashing的实现方法还采用了虚拟节点的思想。使用一般的hash函数的话,服务器的映射地点的分布非常不均匀。因此,使用虚拟节点的思想,为每个物理节点(服务器) 在continuum上分配100~200个点。这样就能抑制分布不均匀,最大限度地减小服务器增减时的缓存重新分布。
通过下文中介绍的使用Consistent Hashing算法的memcached客户端函数库进行测试的结果是,由服务器台数(n)和增加的服务器台数(m)计算增加服务器后的命中率计算公式如下:
(1 - n/(n+m)) * 100

支持Consistent Hashing的函数库


本连载中多次介绍的Cache::Memcached虽然不支持Consistent Hashing,但已有几个客户端函数库支持了这种新的分布式算法。第一个支持Consistent Hashing和虚拟节点的memcached客户端函数库是 名为libketama的PHP库,由last.fm开发。

至于Perl客户端,连载的第1次 中介绍过的Cache::Memcached::Fast和Cache::Memcached::libmemcached支持 Consistent Hashing。

两者的接口都与Cache::Memcached几乎相同,如果正在使用Cache::Memcached,那么就可以方便地替换过来。Cache::Memcached::Fast重新实现了libketama,使用Consistent Hashing创建对象时可以指定ketama_points选项。
[perl]
my $memcached = Cache::Memcached::Fast->new({
servers => ["192.168.0.1:11211","192.168.0.2:11211"],
ketama_points => 150
});
[/perl]
另外,Cache::Memcached::libmemcached 是一个使用了Brain Aker开发的C函数库libmemcached的Perl模块。libmemcached本身支持几种分布式算法,也支持Consistent Hashing,其Perl绑定也支持Consistent Hashing。

总结


本次介绍了memcached的分布式算法,主要有memcached的分布式是由客户端函数库实现,以及高效率地分散数据的Consistent Hashing算法。下次将介绍mixi在memcached应用方面的一些经验,和相关的兼容应用程序。

memcached全面剖析–3.memcached的删除机制和发展方向

作者: charlee
网址: //tech.idv2.com/2008/07/10/memcached-001

发表日:2008/7/2
作者:长野雅广(Masahiro Nagano)
原文链接:http://gihyo.jp/dev/feature/01/memcached/0001

memcached是缓存,所以数据不会永久保存在服务器上,这是向系统中引入memcached的前提。本次介绍memcached的数据删除机制,以及memcached的最新发展方向——二进制协议(Binary Protocol)和外部引擎支持。

memcached在数据删除方面有效利用资源


数据不会真正从memcached中消失


上次介绍过,memcached不会释放已分配的内存。记录超时后,客户端就无法再看见该记录(invisible,透明),其存储空间即可重复使用。

Lazy Expiration


memcached内部不会监视记录是否过期,而是在get时查看记录的时间戳,检查记录是否过期。这种技术被称为lazy(惰性)expiration。因此,memcached不会在过期监视上耗费CPU时间。

LRU:从缓存中有效删除数据的原理


memcached会优先使用已超时的记录的空间,但即使如此,也会发生追加新记录时空间不足的情况,此时就要使用名为 Least Recently Used(LRU)机制来分配空间。顾名思义,这是删除“最近最少使用”的记录的机制。因此,当memcached的内存空间不足时(无法从slab class 获取到新的空间时),就从最近未被使用的记录中搜索,并将其空间分配给新的记录。从缓存的实用角度来看,该模型十分理想。
不过,有些情况下LRU机制反倒会造成麻烦。memcached启动时通过“-M”参数可以禁止LRU,如下所示:
[bash]
$ memcached -M -m 1024
[/bash]
是用来指定最大内存大小的。不指定具体数值则使用默认值64MB。
指定“-M”参数启动后,内存用尽时memcached会返回错误。话说回来,memcached毕竟不是存储器,而是缓存,所以推荐使用LRU。

memcached的最新发展方向


memcached的roadmap上有两个大的目标。一个是二进制协议的策划和实现,另一个是外部引擎的加载功能。

关于二进制协议


使用二进制协议的理由是它不需要文本协议的解析处理,使得原本高速的memcached的性能更上一层楼,还能减少文本协议的漏洞。目前已大部分实现,开发用的代码库中已包含了该功能。memcached的下载页面上有代码库的链接。

二进制协议的格式


协议的包为24字节的帧,其后面是键和无结构数据(Unstructured Data)。实际的格式如下(引自协议文档):
[text]
Byte/ 0 | 1 | 2 | 3 |

/ | | | |
|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|
+---------------+---------------+---------------+---------------+
0/ HEADER /
/ /
/ /
/ /
+---------------+---------------+---------------+---------------+
24/ COMMAND-SPECIFIC EXTRAS (as needed) /
+/ (note length in th extras length header field) /
+---------------+---------------+---------------+---------------+
m/ Key (as needed) /
+/ (note length in key length header field) /
+---------------+---------------+---------------+---------------+
n/ Value (as needed) /
+/ (note length is total body length header field, minus /
+/ sum of the extras and key length body fields) /
+---------------+---------------+---------------+---------------+
Total 24 bytes
[/text]
如上所示,包格式十分简单。需要注意的是,占据了16字节的头部(HEADER)分为 请求头(Request Header)和响应头(Response Header)两种。头部中包含了表示包的有效性的Magic字节、命令种类、键长度、值长度等信息,格式如下:
[text]
Request Header
Byte/ 0 | 1 | 2 | 3 |
/ | | | |
|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|
+---------------+---------------+---------------+---------------+
0| Magic | Opcode | Key length |
+---------------+---------------+---------------+---------------+
4| Extras length | Data type | Reserved |
+---------------+---------------+---------------+---------------+
8| Total body length |
+---------------+---------------+---------------+---------------+
12| Opaque |
+---------------+---------------+---------------+---------------+
16| CAS |
| |
+---------------+---------------+---------------+---------------+
[/text]
[text]
Response Header
Byte/ 0 | 1 | 2 | 3 |
/ | | | |
|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|
+---------------+---------------+---------------+---------------+
0| Magic | Opcode | Key Length |
+---------------+---------------+---------------+---------------+
4| Extras length | Data type | Status |
+---------------+---------------+---------------+---------------+
8| Total body length |
+---------------+---------------+---------------+---------------+
12| Opaque |
+---------------+---------------+---------------+---------------+
16| CAS |
| |
+---------------+---------------+---------------+---------------+
[/text]
如希望了解各个部分的详细内容,可以checkout出memcached的二进制协议的代码树,参考其中的docs文件夹中的protocol_binary.txt文档。

HEADER中引人注目的地方


看到HEADER格式后我的感想是,键的上限太大了!现在的memcached规格中,键长度最大为250字节,但二进制协议中键的大小用2字节表示。因此,理论上最大可使用65536字节(216)长的键。尽管250字节以上的键并不会太常用,二进制协议发布之后就可以使用巨大的键了。
二进制协议从下一版本1.3系列开始支持。

外部引擎支持


我去年曾经试验性地将memcached的存储层改造成了可扩展的(pluggable)。

MySQL的Brian Aker看到这个改造之后,就将代码发到了memcached的邮件列表。memcached的开发者也十分感兴趣,就放到了roadmap中。现在由我和 memcached的开发者Trond Norbye协同开发(规格设计、实现和测试)。和国外协同开发时时差是个大问题,但抱着相同的愿景,最后终于可以将可扩展架构的原型公布了。代码库可以从memcached的下载页面 上访问。

外部引擎支持的必要性


世界上有许多memcached的派生软件,其理由是希望永久保存数据、实现数据冗余等,即使牺牲一些性能也在所不惜。我在开发memcached之前,在mixi的研发部也曾经 考虑过重新发明memcached。
外部引擎的加载机制能封装memcached的网络功能、事件处理等复杂的处理。因此,现阶段通过强制手段或重新设计等方式使memcached和存储引擎合作的困难 就会烟消云散,尝试各种引擎就会变得轻而易举了。

简单API设计的成功的关键


该项目中我们最重视的是API设计。函数过多,会使引擎开发者感到麻烦; 过于复杂,实现引擎的门槛就会过高。因此,最初版本的接口函数只有13个。具体内容限于篇幅,这里就省略了,仅说明一下引擎应当完成的操作:

  • 引擎信息(版本等)

  • 引擎初始化

  • 引擎关闭

  • 引擎的统计信息

  • 在容量方面,测试给定记录能否保存

  • 为item(记录)结构分配内存

  • 释放item(记录)的内存

  • 删除记录

  • 保存记录

  • 回收记录

  • 更新记录的时间戳

  • 数学运算处理

  • 数据的flush


对详细规格有兴趣的读者,可以checkout engine项目的代码,阅读器中的engine.h。

重新审视现在的体系


memcached支持外部存储的难点是,网络和事件处理相关的代码(核心服务器)与 内存存储的代码紧密关联。这种现象也称为tightly coupled(紧密耦合)。必须将内存存储的代码从核心服务器中独立出来,才能灵活地支持外部引擎。因此,基于我们设计的API,memcached被重构成下面的样子:
memcached-0003-001.png

重构之后,我们与1.2.5版、二进制协议支持版等进行了性能对比,证实了它不会造成性能影响。
在考虑如何支持外部引擎加载时,让memcached进行并行控制(concurrency control)的方案是最为容易的,但是对于引擎而言,并行控制正是性能的真谛,因此我们采用了将多线程支持完全交给引擎的设计方案。
以后的改进,会使得memcached的应用范围更为广泛。

总结


本次介绍了memcached的超时原理、内部如何删除数据等,在此之上又介绍了二进制协议和 外部引擎支持等memcached的最新发展方向。这些功能要到1.3版才会支持,敬请期待!
这是我在本连载中的最后一篇。感谢大家阅读我的文章!
下次由长野来介绍memcached的应用知识和应用程序兼容性等内容。

memcached全面剖析–2.理解memcached的内存存储

作者: charlee
网址: http://tech.idv2.com/2008/07/10/memcached-001

发表日:2008/7/2
作者:长野雅广(Masahiro Nagano)
原文链接:http://gihyo.jp/dev/feature/01/memcached/0001

Slab Allocation机制:整理内存以便重复使用


最近的memcached默认情况下采用了名为Slab Allocator的机制分配、管理内存。在该机制出现以前,内存的分配是通过对所有记录简单地进行malloc和free来进行的。但是,这种方式会导致内存碎片,加重操作系统内存管理器的负担,最坏的情况下,会导致操作系统比memcached进程本身还慢。Slab Allocator就是为解决该问题而诞生的。
下面来看看Slab Allocator的原理。下面是memcached文档中的slab allocator的目标:
the primary goal of the slabs subsystem in memcached was to eliminate memory fragmentation issues totally by using fixed-size memory chunks coming from a few predetermined size classes.
也就是说,Slab Allocator的基本原理是按照预先规定的大小,将分配的内存分割成特定长度的块,以完全解决内存碎片问题。
Slab Allocation的原理相当简单。 将分配的内存分割成各种尺寸的块(chunk),并把尺寸相同的块分成组(chunk的集合)(图1)。
memcached-0002-01.png

图1 Slab Allocation的构造图
而且,slab allocator还有重复使用已分配的内存的目的。也就是说,分配到的内存不会释放,而是重复利用。

Slab Allocation的主要术语


Page
分配给Slab的内存空间,默认是1MB。分配给Slab之后根据slab的大小切分成chunk。
Chunk
用于缓存记录的内存空间。
Slab Class
特定大小的chunk的组。

在Slab中缓存记录的原理


下面说明memcached如何针对客户端发送的数据选择slab并缓存到chunk中。
memcached根据收到的数据的大小,选择最适合数据大小的slab(图2)。memcached中保存着slab内空闲chunk的列表,根据该列表选择chunk,然后将数据缓存于其中。
memcached-0002-02.png

图2 选择存储记录的组的方法
实际上,Slab Allocator也是有利也有弊。下面介绍一下它的缺点。

Slab Allocator的缺点


Slab Allocator解决了当初的内存碎片问题,但新的机制也给memcached带来了新的问题。
这个问题就是,由于分配的是特定长度的内存,因此无法有效利用分配的内存。例如,将100字节的数据缓存到128字节的chunk中,剩余的28字节就浪费了(图3)。
memcached-0002-03.png

图3 chunk空间的使用
对于该问题目前还没有完美的解决方案,但在文档中记载了比较有效的解决方案。
The most efficient way to reduce the waste is to use a list of size classes that closely matches (if that's at all possible) common sizes of objects that the clients of this particular installation of memcached are likely to store.
就是说,如果预先知道客户端发送的数据的公用大小,或者仅缓存大小相同的数据的情况下,只要使用适合数据大小的组的列表,就可以减少浪费。
但是很遗憾,现在还不能进行任何调优,只能期待以后的版本了。但是,我们可以调节slab class的大小的差别。接下来说明growth factor选项。

使用Growth Factor进行调优


memcached在启动时指定 Growth Factor因子(通过-f选项),就可以在某种程度上控制slab之间的差异。默认值为1.25。但是,在该选项出现之前,这个因子曾经固定为2,称为“powers of 2”策略。
让我们用以前的设置,以verbose模式启动memcached试试看:
[bash]
$ memcached -f 2 -vv
[/bash]
下面是启动后的verbose输出:
[text]
slab class 1: chunk size 128 perslab 8192
slab class 2: chunk size 256 perslab 4096
slab class 3: chunk size 512 perslab 2048
slab class 4: chunk size 1024 perslab 1024
slab class 5: chunk size 2048 perslab 512
slab class 6: chunk size 4096 perslab 256
slab class 7: chunk size 8192 perslab 128
slab class 8: chunk size 16384 perslab 64
slab class 9: chunk size 32768 perslab 32
slab class 10: chunk size 65536 perslab 16
slab class 11: chunk size 131072 perslab 8
slab class 12: chunk size 262144 perslab 4
slab class 13: chunk size 524288 perslab 2
[/text]
可见,从128字节的组开始,组的大小依次增大为原来的2倍。这样设置的问题是,slab之间的差别比较大,有些情况下就相当浪费内存。因此,为尽量减少内存浪费,两年前追加了growth factor这个选项。
来看看现在的默认设置(f=1.25)时的输出(篇幅所限,这里只写到第10组):
[text]
slab class 1: chunk size 88 perslab 11915
slab class 2: chunk size 112 perslab 9362
slab class 3: chunk size 144 perslab 7281
slab class 4: chunk size 184 perslab 5698
slab class 5: chunk size 232 perslab 4519
slab class 6: chunk size 296 perslab 3542
slab class 7: chunk size 376 perslab 2788
slab class 8: chunk size 472 perslab 2221
slab class 9: chunk size 592 perslab 1771
slab class 10: chunk size 744 perslab 1409
[/text]
可见,组间差距比因子为2时小得多,更适合缓存几百字节的记录。从上面的输出结果来看,可能会觉得有些计算误差,这些误差是为了保持字节数的对齐而故意设置的。
将memcached引入产品,或是直接使用默认值进行部署时,最好是重新计算一下数据的预期平均长度,调整growth factor,以获得最恰当的设置。内存是珍贵的资源,浪费就太可惜了。
接下来介绍一下如何使用memcached的stats命令查看slabs的利用率等各种各样的信息。

查看memcached的内部状态


memcached有个名为stats的命令,使用它可以获得各种各样的信息。执行命令的方法很多,用telnet最为简单:
$ telnet 主机名 端口号
连接到memcached之后,输入stats再按回车,即可获得包括资源利用率在内的各种信息。此外,输入"stats slabs"或"stats items"还可以获得关于缓存记录的信息。结束程序请输入quit。
这些命令的详细信息可以参考memcached软件包内的protocol.txt文档。
[bash]
$ telnet localhost 11211
Trying ::1...
Connected to localhost.
Escape character is '^]'.
stats
STAT pid 481
STAT uptime 16574
STAT time 1213687612
STAT version 1.2.5
STAT pointer_size 32
STAT rusage_user 0.102297
STAT rusage_system 0.214317
STAT curr_items 0
STAT total_items 0
STAT bytes 0
STAT curr_connections 6
STAT total_connections 8
STAT connection_structures 7
STAT cmd_get 0
STAT cmd_set 0
STAT get_hits 0
STAT get_misses 0
STAT evictions 0
STAT bytes_read 20
STAT bytes_written 465
STAT limit_maxbytes 67108864
STAT threads 4
END
quit
[/bash]
另外,如果安装了libmemcached这个面向C/C++语言的客户端库,就会安装 memstat 这个命令。使用方法很简单,可以用更少的步骤获得与telnet相同的信息,还能一次性从多台服务器获得信息。
[bash]
$ memstat --servers=server1,server2,server3,...
[/bash]
libmemcached可以从下面的地址获得:

查看slabs的使用状况


使用memcached的创造着Brad写的名为memcached-tool的Perl脚本,可以方便地获得slab的使用情况 (它将memcached的返回值整理成容易阅读的格式)。可以从下面的地址获得脚本:

使用方法也极其简单:
$ memcached-tool 主机名:端口 选项
查看slabs使用状况时无需指定选项,因此用下面的命令即可:
$ memcached-tool 主机名:端口
获得的信息如下所示:
[text]
# Item_Size Max_age 1MB_pages Count Full?
1 104 B 1394292 s 1215 12249628 yes
2 136 B 1456795 s 52 400919 yes
3 176 B 1339587 s 33 196567 yes
4 224 B 1360926 s 109 510221 yes
5 280 B 1570071 s 49 183452 yes
6 352 B 1592051 s 77 229197 yes
7 440 B 1517732 s 66 157183 yes
8 552 B 1460821 s 62 117697 yes
9 696 B 1521917 s 143 215308 yes
10 872 B 1695035 s 205 246162 yes
11 1.1 kB 1681650 s 233 221968 yes
12 1.3 kB 1603363 s 241 183621 yes
13 1.7 kB 1634218 s 94 57197 yes
14 2.1 kB 1695038 s 75 36488 yes
15 2.6 kB 1747075 s 65 25203 yes
16 3.3 kB 1760661 s 78 24167 yes
[/text]
各列的含义为:





























含义
# slab class编号
Item_Size Chunk大小
Max_age LRU内最旧的记录的生存时间
1MB_pages 分配给Slab的页数
Count Slab内的记录数
Full? Slab内是否含有空闲chunk

从这个脚本获得的信息对于调优非常方便,强烈推荐使用。

内存存储的总结


本次简单说明了memcached的缓存机制和调优方法。希望读者能理解memcached的内存管理原理及其优缺点。
下次将继续说明LRU和Expire等原理,以及memcached的最新发展方向——可扩充体系(pluggable architecher)。

Apache配置文件中文详解

#
# 基于 NCSA 服务器的配置文件 由 Rob McCool 编写,龙子翻译
#
# Apache服务器主配置文件. 包括服务器指令的目录设置.
# 详见 http://www.apache.org/docs/
#
# 请在理解用途的基础上阅读各指令。
#
# 除非用ResourceConfig或AccessConfig覆盖这儿的标识
#
配置标识由三个基本部分组成:

  • 作为一个整体来控制Apache服务器进程的标识 (the 'global environment')

  • 用于定义主(默认)服务器参数的标识。

  • 响应虚拟主机不能处理的请求。
    同时也提供所有虚拟主机的设置值。
  • 虚拟主机的设置。在一个Apache服务器进程中配置不同的IP地址和主机名


#
# 配置和日志文件名:指定服务器控制文件命名时,
# 以 "/" (或 "drive:/" for Win32)开始,服务器将使用这些绝对路径。
# 如果文件名不是以"/"开始的,预先考虑服务器根目录--
# 因此 "logs/foo.log",如果服务器根目录是"/usr/local/apache",
# 服务器将解释为 "/usr/local/apache/logs/foo.log".
#
# 注: 指定的文件名需要用"/"代替"\"。
# (例, "c:/apache" 代替 "c:\apache").
# 如果省略了驱动器名,默认使用Apache.exe所在的驱动器盘符
# 建议指定盘符,以免混乱。
#

### Section 1: Global Environment
#当服务器响应主机头(header)信息时显示Apache的版本和操作系统名称

ServerTokens OS

#设置服务器的根目录

ServerRoot "/etc/httpd"
#ScoreBoardFile run/httpd.scoreboard

#设置运行Apache时使用的PidFile的路径

PidFile run/httpd.pid


#若300秒后没有收到或送出任何数据就切断该连接

Timeout 300

#不使用保持连接的功能,即客户一次请求连接只能响应一个文件,建议用户将此参数的值设置为On,即允许使用保持连接的功能

KeepAlive Off

#在使用保持连接功能时,设置客户一次请求连接能响应文件的最大上限

MaxKeepAliveRequests 100

#在使用保持连接功能时,两个相邻的连接的时间间隔超过15秒,就切断连接

KeepAliveTimeout 15

##
## Server-Pool Size Regulation (MPM specific)
##

# prefork MPM
# StartServers: number of server processes to start
# MinSpareServers: minimum number of server processes which are kept spare
# MaxSpareServers: maximum number of server processes which are kept spare
# MaxClients: maximum number of server processes allowed to start
# MaxRequestsPerChild: maximum number of requests a server process serves

#设置使用Prefork MPM运行方式的参数,此运行方式是Red hat默认的方式

<IfModule prefork.c>


#设置服务器启动时运行的进程数

StartServers 8


#Apache在运行时会根据负载的轻重自动调整空闲子进程的数目
#若存在低于5个空闲子进程,就创建一个新的子进程准备为客户提供服务

MinSpareServers 5


#若存在高于20个空闲子进程,就创建逐一删除子进程来提高系统性能

MaxSpareServers 20


#限制同一时间的连接数不能超过150

MaxClients 150


#限制每个子进程在结束处理请求之前能处理的连接请求为1000

MaxRequestsPerChild 1000
</IfModule>

# worker MPM
# StartServers: initial number of server processes to start

#设置使用Worker MPM运行方式的参数

<IfModule worker.c>
StartServers 2
MaxClients 150
MinSpareThreads 25
MaxSpareThreads 75
ThreadsPerChild 25
MaxRequestsPerChild 0
</IfModule>



# perchild MPM
# NumServers: constant number of server processes

#设置使用perchild MPM运行方式的参数

<IfModule perchild.c>
NumServers 5
StartThreads 5
MinSpareThreads 5
MaxSpareThreads 10
MaxThreadsPerChild 20
MaxRequestsPerChild 0
</IfModule>

#设置服务器的监听端口

#Listen 12.34.56.78:80
Listen 202.112.85.101:80

#

# Load config files from the config directory "/etc/httpd/conf.d".

#将/etc/httpd/conf.d目录下所有以conf结尾的配置文件包含进来

Include conf.d/*.conf

#
# Dynamic Shared Object (DSO) Support
#动态加载模块(DSO)
# Example:
# LoadModule foo_module modules/mod_foo.so
#

LoadModule access_module modules/mod_access.so
LoadModule auth_module modules/mod_auth.so
LoadModule auth_anon_module modules/mod_auth_anon.so
LoadModule auth_dbm_module modules/mod_auth_dbm.so
LoadModule auth_digest_module modules/mod_auth_digest.so
LoadModule include_module modules/mod_include.so
LoadModule log_config_module modules/mod_log_config.so
LoadModule env_module modules/mod_env.so
LoadModule mime_magic_module modules/mod_mime_magic.so
LoadModule cern_meta_module modules/mod_cern_meta.so
LoadModule expires_module modules/mod_expires.so
LoadModule headers_module modules/mod_headers.so
LoadModule usertrack_module modules/mod_usertrack.so
LoadModule unique_id_module modules/mod_unique_id.so
LoadModule setenvif_module modules/mod_setenvif.so
LoadModule mime_module modules/mod_mime.so
LoadModule dav_module modules/mod_dav.so
LoadModule status_module modules/mod_status.so
LoadModule autoindex_module modules/mod_autoindex.so
LoadModule asis_module modules/mod_asis.so
LoadModule info_module modules/mod_info.so
LoadModule cgi_module modules/mod_cgi.so
LoadModule dav_fs_module modules/mod_dav_fs.so
LoadModule vhost_alias_module modules/mod_vhost_alias.so
LoadModule negotiation_module modules/mod_negotiation.so
LoadModule dir_module modules/mod_dir.so
LoadModule imap_module modules/mod_imap.so
LoadModule actions_module modules/mod_actions.so
LoadModule speling_module modules/mod_speling.so
LoadModule userdir_module modules/mod_userdir.so
LoadModule alias_module modules/mod_alias.so
LoadModule rewrite_module modules/mod_rewrite.so
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_ftp_module modules/mod_proxy_ftp.so
LoadModule proxy_http_module modules/mod_proxy_http.so
LoadModule proxy_connect_module modules/mod_proxy_connect.so


#当使用内置模块perfork.c时动态加载cgi_module

<IFModule prefork.c>
LoadModule cgi_module modules/mod_cgi.so
</IfModule>
#ExtendedStatus On


### Section 2: 'Main' server configuration
#设置运行Apache服务器的用户和组

User apache
Group apache

#设置Apache服务器管理员的E_mail地址

ServerAdmin admin at astro dot bnu.edu.cn



ServerName www.livingelsewhere.net

#关闭此选项,当Apache服务器需要指向本身的连接时使用
#serverName:port作为主机名,例如www.livingelsewhere.net:80
#若打开此选项将使用www.livingelsewhere.net port 80作为主机名

UseCanonicalName Off


#设置根文档路径

#DocumentRoot "/var/www/html"
DocumentRoot "/home/httpd"


#设置apache服务器根的访问权限

<Directory />
#允许符号链接跟随,访问不在本目录下的文件
Options FollowSymLinks
#禁止读取.htaccess配置文件的内容
AllowOverride None
</Directory>


#设置根文档目录的访问权限

<Directory "/home/httpd">
#Indexes:当在目录中找不到DirectoryIndex列表中指定的文件
#就生成当前目录的文件列表
#FollowSymLinks允许符号链接跟随,访问不在本目录下的文件
Options Indexes FollowSymLinks

#禁止读取.htaccess配置文件的内容
AllowOverride None

#指定先执行Allow(允许)访问规则,在执行Deny访问规则
Order allow,deny
# Allow from 202.112.85.0/16
#设置Allow(允许)访问规则,允许所有连接
Allow from all
# Deny from all
</Directory>


#对Apache服务器的根的访问不生成目录列表,同时指定错误输出页面

<LocationMatch "^/$>
Options -Indexes
ErrorDocument 403 /error/noindex.ht


#不允许每用户的服务器配置

<IfModule mod_userdir.c>


#不允许每用户的服务器配置

UserDir disable


##基于安全考虑,禁止root用户使用自己的个人站点

#UserDir disable root


##配置对每个用户web站点目录的设置

#UserDir public_html
</IfModule>


#设置对每个用户web站点目录的访问权限

#<Directory /home/*/public_html>
# AllowOverride FileInfo AuthConfig Limit
# Options MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec
# <Limit GET POST OPTIONS>
# Order allow,deny
# Allow from all
# </Limit>
# <LimitExcept GET POST OPTIONS>
# Order deny,allow
# Deny from all
# </LimitExcept>
#</Directory>


#当访问服务器时,依次查找页面Index.html index.htm.var

DirectoryIndex index.html index.html.var


#指定保护目录配置文件的名称

AccessFileName .htaccess


#拒绝访问以.ht开头的文件,即保证.htaccess不被访问

<Files ~ "^.ht">
Order allow,deny
Deny from all
</Files>


#指定负责处理MIME对应格式的配置文件的存放位置

TypesConfig /etc/mime.types


#指定默认的MIME文件类型为纯文本或HTML文件

DefaultType text/plain


#当mod_mime_magic.c模块被加载时,指定magic信息码配置文件的存放位置

<IfModule mod_mime_magic.c>
# MIMEMagicFile /usr/share/magic.mime
MIMEMagicFile conf/magic
</IfModule>


#只记录连接Apache服务器的Ip地址,而不纪录主机名

HostnameLookups Off


#指定错误日志存放位置

ErrorLog logs/error_log


#指定记录的错误信息的详细等级为warn等级

LogLevel warn


#定义四中记录日志的格式

LogFormat "%h %l %u %t "%r" %>s %b "%{ Referer }i" "%{ User-Agent }i"" combined
LogFormat "%h %l %u %t "%r" %>s %b" common
LogFormat "%{ Referer }i -> %U" referer
LogFormat "%{ User-agent }i" agent


#指定访问日志的纪录格式为combined(混合型),并指定访问日志存放位置

# CustomLog logs/access_log common
CustomLog logs/access_log combined
#CustomLog logs/referer_log referer
#CustomLog logs/agent_log agent
#CustomLog logs/access_log combined


#设置apache自己产生的页面中使用apache服务器版本的签名

ServerSignature On


#设置内容协商目录的访问别名

Alias /icons/ "/var/www/icons/"


#设置/var/www/icons/的访问权限

<Directory "/var/www/icons">
#MultiViews 使用内容协商功决定被发送的网页的性质
Options Indexes MultiViews
AllowOverride None
Order allow,deny
Allow from all
</Directory>


#设置网页邮件服务

Alias /webmail "/usr/share/squirrelmail"



<Directory "/usr/share/squirrelmail">
Options Indexes MultiViews
AllowOverride None
Order allow,deny
Allow from all
</Directory>


#设置apache手册的访问别名

Alias /manual "/var/www/manual"



<Directory "/var/www/manual">
Options Indexes FollowSymLinks MultiViews
AllowOverride None
Order allow,deny
Allow from all
</Directory>


#设置浏览器匹配

BrowserMatch "Mozilla/2" nokeepalive
BrowserMatch "MSIE 4.0b2;" nokeepalive downgrade-1.0 force-response-1.0
BrowserMatch "RealPlayer 4.0" force-response-1.0
BrowserMatch "Java/1.0" force-response-1.0
BrowserMatch "JDK/1.0" force-response-1.0
BrowserMatch "Microsoft Data Access Internet Publishing Provider" redirect-carefully
BrowserMatch "^WebDrive" redirect-carefully


#
# Allow server status reports, with the URL of http:#servername/server-status
# Change the ".your-domain.com" to match your domain to enable.
#

#<Location /server-status>
# SetHandler server-status
# Order deny,allow
# Deny from all
# Allow from .your-domain.com
#</Location>


#
# Allow remote server configuration reports, with the URL of
# http:#servername/server-info (requires that mod_info.c be loaded).
# Change the ".your-domain.com" to match your domain to enable.
#

#<Location /server-info>
# SetHandler server-info
# Order deny,allow
# Deny from all
# Allow from .your-domain.com
#</Location>


#设置APache为代理服务器
# Proxy Server directives. Uncomment the following lines to
# enable the proxy server:
#

#<IfModule mod_proxy.c>
#ProxyRequests On

#

#<Proxy *>
# Order deny,allow
# Deny from all
# Allow from .your-domain.com
#</Proxy>


#
# Enable/disable the handling of HTTP/1.1 "Via:" headers.
# ("Full" adds the server version; "Block" removes all outgoing Via: headers)
# Set to one of: Off | On | Full | Block
#

#ProxyVia On

#
# To enable the cache as well, edit and uncomment the following lines:
# (no cacheing without CacheRoot)
#

#CacheRoot "/etc/httpd/proxy"
#CacheSize 5
#CacheGcInterval 4
#CacheMaxExpire 24
#CacheLastModifiedFactor 0.1
#CacheDefaultExpire 1
#NoCache a-domain.com another-domain.edu joes.garage-sale.com

#</IfModule>
# End of proxy directives.


#设置虚拟主机
### Section 3: Virtual Hosts
#
# VirtualHost: If you want to maintain multiple domains/hostnames on your
# machine you can setup VirtualHost containers for them. Most configurations
# use only name-based virtual hosts so the server doesn't need to worry about
# IP addresses. This is indicated by the asterisks in the directives below.
#
# Please see the documentation at
# <URL:http:#httpd.apache.org/docs-2.0/vhosts/>
# for further details before you try to setup virtual hosts.
#
# You may use the command line option '-S' to verify your virtual host
# configuration.

#
# Use name-based virtual hosting.
#指令监听本地计算机上所有的IP地址请求

#NameVirtualHost *


#
# VirtualHost example:
# Almost any Apache directive may go into a VirtualHost container.
# The first VirtualHost section is used for requests without a known
# server name.
#

#<VirtualHost *>
#定义虚拟主机的设置,此设置将覆盖前面有的的相同指令
# ServerAdmin webmaster at dummy-host dot example.com
# DocumentRoot /www/docs/dummy-host.example.com
# ServerName dummy-host.example.com
# ErrorLog logs/dummy-host.example.com-error_log
# CustomLog logs/dummy-host.example.com-access_log common
#</VirtualHost>


#指定DAV加锁数据库文件的存放位置

<IfModule mod_dav_fs.c>
# Location of the WebDAV lock database.
DAVLockDB /var/lib/dav/lockdb
</IfModule>



Alias /docs "/home/EMU/webmail/docs/"



<Directory "/home/EMU/webmail/docs">
Options Indexes FollowSymLinks MultiViews
AllowOverride None
Order allow,deny
Allow from all
</Directory>



<IfModule mod_dav_fs.c>
# Location of the WebDAV lock database.
DAVLockDB /var/lib/dav/lockdb
</IfModule>


#设置CGI目录的访问别名

ScriptAlias /cgi-bin/ "/var/www/cgi-bin/"


#由于red hat中不使用worker MPM运行方式,所以不加载mod_cgid.c模块

<IfModule mod_cgid.c>

#
# Additional to mod_cgid.c settings, mod_cgid has Scriptsock <path>
# for setting UNIX socket for communicating with cgid.
#

#Scriptsock logs/cgisock
</IfModule>


#设置CGI目录的访问权限

<Directory "/var/www/cgi-bin">
AllowOverride None
Options None
Order allow,deny
Allow from all
</Directory>


#重定向连接

# Redirect permanent /foo http:#www.example.com/bar


#设置自动生成目录列表的显示方式

#FancyIndexing 对每种类型的文件前加上一个小图标以示区别
#VersionSort 对同一个软件的多个版本进行排序
#NameWidth=* 文件名字段自动适应当前目录下的最长文件名
IndexOptions FancyIndexing VersionSort NameWidth=*


#当使用IndexOptions FancyIndexing之后,配置下面的参数
#用于告知服务器在遇到不同的文件类型或扩展名时采用MIME编码格式
#辨别文件类型并显示相应的图标

AddIconByEncoding (CMP,/icons/compressed.gif) x-compress x-gzip



AddIconByType (TXT,/icons/text.gif) text/*
AddIconByType (IMG,/icons/image2.gif) image/*
AddIconByType (SND,/icons/sound2.gif) audio/*
AddIconByType (VID,/icons/movie.gif) video/*

#当使用IndexOptions FancyIndexing之后,配置下面的参数
#用于告知服务器在遇到不同的文件类型或扩展名时采用所指定的格式
#并显示相应的图标

AddIcon /icons/binary.gif .bin .exe
AddIcon /icons/binhex.gif .hqx
AddIcon /icons/tar.gif .tar
AddIcon /icons/world2.gif .wrl .wrl.gz .vrml .vrm .iv
AddIcon /icons/compressed.gif .Z .z .tgz .gz .zip
AddIcon /icons/a.gif .ps .ai .eps
AddIcon /icons/layout.gif .html .shtml .htm .pdf
AddIcon /icons/text.gif .txt
AddIcon /icons/c.gif .c
AddIcon /icons/p.gif .pl .py
AddIcon /icons/f.gif .for
AddIcon /icons/dvi.gif .dvi
AddIcon /icons/uuencoded.gif .uu
AddIcon /icons/script.gif .conf .sh .shar .csh .ksh .tcl
AddIcon /icons/tex.gif .tex
AddIcon /icons/bomb.gif core

AddIcon /icons/back.gif ..
AddIcon /icons/hand.right.gif README
AddIcon /icons/folder.gif ^^DIRECTORY^^
AddIcon /icons/blank.gif ^^BLANKICON^^


#当使用IndexOptions FancyIndexing之后,且无法识别文件类型时
#显示此处定义的图标

DefaultIcon /icons/unknown.gif


#
# AddDescription allows you to place a short description after a file in
# server-generated indexes. These are only displayed for FancyIndexed
# directories.
# Format: AddDescription "description" filename
#

#AddDescription "GZIP compressed document" .gz
#AddDescription "tar archive" .tar
#AddDescription "GZIP compressed tar archive" .tgz


#当服务器自动列出目录列表时,在所生成的页面之后显示readme.html的内容

ReadmeName README.html


#当服务器自动列出目录列表时,在所生成的页面之前显示header.html的内容

HeaderName HEADER.html

#
# IndexIgnore is a set of filenames which directory indexing should ignore
# and not include in the listing. Shell-style wildcarding is permitted.
#
IndexIgnore .??* *~ *# HEADER* README* RCS CVS *,v *,t

#设置在线浏览用户可以实时解压缩.z .gz .tgz类型的文件
#并非所有浏览器都支持

AddEncoding x-compress Z
AddEncoding x-gzip gz tgz


#
# DefaultLanguage nl
# Danish (da) - Dutch (nl) - English (en) - Estonian (et)
# French (fr) - German (de) - Greek-Modern (el)
# Italian (it) - Norwegian (no) - Norwegian Nynorsk (nn) - Korean (kr)
# Portugese (pt) - Luxembourgeois* (ltz)
# Spanish (es) - Swedish (sv) - Catalan (ca) - Czech(cz)
# Polish (pl) - Brazilian Portuguese (pt-br) - Japanese (ja)
# Russian (ru) - Croatian (hr)
#
#设置网页内容的语言种类(浏览器要启用内容协商)
#对中文网页,此项无实际意义

AddLanguage da .dk
AddLanguage nl .nl
AddLanguage en .en
AddLanguage et .et
AddLanguage fr .fr
AddLanguage de .de
AddLanguage he .he
AddLanguage el .el
AddLanguage it .it
AddLanguage ja .ja
AddLanguage pl .po
AddLanguage kr .kr
AddLanguage pt .pt
AddLanguage nn .nn
AddLanguage no .no
AddLanguage pt-br .pt-br
AddLanguage ltz .ltz
AddLanguage ca .ca
AddLanguage es .es
AddLanguage sv .se
AddLanguage cz .cz
AddLanguage ru .ru
AddLanguage tw .tw
AddLanguage zh-tw .tw
AddLanguage hr .hr


#当启用内容协商时,设置语言的先后顺序
LanguagePriority en da nl et fr de el it ja kr no pl pt pt-br ltz ca es sv tw

#Prefer 当有多种语言可以匹配时,使用LanguagePriority 列表的第一项
#Fallback 当没有语言可以匹配时,使用LanguagePriority 列表的第一项

ForceLanguagePriority Prefer Fallback


#设置默认字符集

AddDefaultCharset ISO-8859-1

#设置各种字符集
AddCharset ISO-8859-1 .iso8859-1 .latin1
AddCharset ISO-8859-2 .iso8859-2 .latin2 .cen
AddCharset ISO-8859-3 .iso8859-3 .latin3
AddCharset ISO-8859-4 .iso8859-4 .latin4
AddCharset ISO-8859-5 .iso8859-5 .latin5 .cyr .iso-ru
AddCharset ISO-8859-6 .iso8859-6 .latin6 .arb
AddCharset ISO-8859-7 .iso8859-7 .latin7 .grk
AddCharset ISO-8859-8 .iso8859-8 .latin8 .heb
AddCharset ISO-8859-9 .iso8859-9 .latin9 .trk
AddCharset ISO-2022-JP .iso2022-jp .jis
AddCharset ISO-2022-KR .iso2022-kr .kis
AddCharset ISO-2022-CN .iso2022-cn .cis
AddCharset Big5 .Big5 .big5
# For russian, more than one charset is used (depends on client, mostly):
AddCharset WINDOWS-1251 .cp-1251 .win-1251
AddCharset CP866 .cp866
AddCharset KOI8-r .koi8-r .koi8-ru
AddCharset KOI8-ru .koi8-uk .ua
AddCharset ISO-10646-UCS-2 .ucs2
AddCharset ISO-10646-UCS-4 .ucs4
AddCharset UTF-8 .utf8

# The set below does not map to a specific (iso) standard
# but works on a fairly wide range of browsers. Note that
# capitalization actually matters (it should not, but it
# does for some browsers).
#
# See ftp:#ftp.isi.edu/in-notes/iana/assignments/character-sets
# for a list of sorts. But browsers support few.
#
AddCharset GB2312 .gb2312 .gb
AddCharset utf-7 .utf7
AddCharset utf-8 .utf8
AddCharset big5 .big5 .b5
AddCharset EUC-TW .euc-tw
AddCharset EUC-JP .euc-jp
AddCharset EUC-KR .euc-kr
AddCharset shift_jis .sjis


#添加新的MIME类型(避免用户编辑/etc/mime.types)

AddType application/x-tar .tgz

#
# AddHandler allows you to map certain file extensions to "handlers":
# actions unrelated to filetype. These can be either built into the server
# or added with the Action directive (see below)
#
# To use CGI scripts outside of ScriptAliased directories:
# (You will also need to add "ExecCGI" to the "Options" directive.)
#
#AddHandler cgi-script .cgi

#
# For files that include their own HTTP headers:
#
#AddHandler send-as-is asis

#设置apcche对某些扩展名的处理方式

AddHandler imap-file map
AddHandler type-map var


#使用过滤器执行SSI

AddOutputFilter INCLUDES .shtml


#设置错误页面目录的别名

Alias /error/ "/var/www/error/"

<IfModule mod_negotiation.c>
<IfModule mod_include.c>
<Directory "/var/www/error">
AllowOverride None
Options IncludesNoExec
AddOutputFilter Includes html
AddHandler type-map var
Order allow,deny
Allow from all
LanguagePriority en es de fr
ForceLanguagePriority Prefer Fallback
</Directory>
#设置错误输出页面
ErrorDocument 400 /error/HTTP_BAD_REQUEST.html.var
ErrorDocument 401 /error/HTTP_UNAUTHORIZED.html.var
ErrorDocument 403 /error/HTTP_FORBIDDEN.html.var
ErrorDocument 404 /error/HTTP_NOT_FOUND.html.var
ErrorDocument 405 /error/HTTP_METHOD_NOT_ALLOWED.html.var
ErrorDocument 408 /error/HTTP_REQUEST_TIME_OUT.html.var
ErrorDocument 410 /error/HTTP_GONE.html.var
ErrorDocument 411 /error/HTTP_LENGTH_REQUIRED.html.var
ErrorDocument 412 /error/HTTP_PRECONDITION_FAILED.html.var
ErrorDocument 413 /error/HTTP_REQUEST_ENTITY_TOO_LARGE.html.var
ErrorDocument 414 /error/HTTP_REQUEST_URI_TOO_LARGE.html.var
ErrorDocument 415 /error/HTTP_SERVICE_UNAVAILABLE.html.var
ErrorDocument 500 /error/HTTP_INTERNAL_SERVER_ERROR.html.var
ErrorDocument 501 /error/HTTP_NOT_IMPLEMENTED.html.var
ErrorDocument 502 /error/HTTP_BAD_GATEWAY.html.var
ErrorDocument 503 /error/HTTP_SERVICE_UNAVAILABLE.html.var
ErrorDocument 506 /error/HTTP_VARIANT_ALSO_VARIES.html.var

</IfModule>
</IfModule>

memcached完全剖析–1. memcached的基础

作者: charlee
网址: http://tech.idv2.com/2008/07/10/memcached-001

发表日:2008/7/2
作者:长野雅广(Masahiro Nagano)
原文链接:http://gihyo.jp/dev/feature/01/memcached/0001

memcached是什么?


memcached 是以LiveJournal 旗下Danga Interactive 公司的Brad Fitzpatric 为首开发的一款软件。现在已成为 mixihatenaFacebookVox、LiveJournal等众多服务中提高Web应用扩展性的重要因素。
许多Web应用都将数据保存到RDBMS中,应用服务器从中读取数据并在浏览器中显示。但随着数据量的增大、访问的集中,就会出现RDBMS的负担加重、数据库响应恶化、网站显示延迟等重大影响。
这时就该memcached大显身手了。memcached是高性能的分布式内存缓存服务器。一般的使用目的是,通过缓存数据库查询结果,减少数据库访问次数,以提高动态Web应用的速度、 提高可扩展性。
memcached-0001-01.png

图1 一般情况下memcached的用途

memcached的特征


memcached作为高速运行的分布式缓存服务器,具有以下的特点。

  • 协议简单

  • 基于libevent的事件处理

  • 内置内存存储方式

  • memcached不互相通信的分布式


协议简单


memcached的服务器客户端通信并不使用复杂的XML等格式, 而使用简单的基于文本行的协议。因此,通过telnet 也能在memcached上保存数据、取得数据。下面是例子。
[bash]
$ telnet localhost 11211
Trying 127.0.0.1...
Connected to localhost.localdomain (127.0.0.1).
Escape character is '^]'.
set foo 0 0 3 (保存命令)
bar (数据)
STORED (结果)
get foo (取得命令)
VALUE foo 0 3 (数据)
[/bash]
协议文档位于memcached的源代码内,也可以参考以下的URL。

基于libevent的事件处理


libevent是个程序库,它将Linux的epoll、BSD类操作系统的kqueue等事件处理功能 封装成统一的接口。即使对服务器的连接数增加,也能发挥O(1)的性能。memcached使用这个libevent库,因此能在Linux、BSD、Solaris等操作系统上发挥其高性能。关于事件处理这里就不再详细介绍,可以参考Dan Kegel的The C10K Problem。

内置内存存储方式


为了提高性能,memcached中保存的数据都存储在memcached内置的内存存储空间中。由于数据仅存在于内存中,因此重启memcached、重启操作系统会导致全部数据消失。另外,内容容量达到指定值之后,就基于LRU(Least Recently Used)算法自动删除不使用的缓存。memcached本身是为缓存而设计的服务器,因此并没有过多考虑数据的永久性问题。关于内存存储的详细信息,本连载的第二讲以后前坂会进行介绍,请届时参考。

memcached不互相通信的分布式


memcached尽管是“分布式”缓存服务器,但服务器端并没有分布式功能。各个memcached不会互相通信以共享信息。那么,怎样进行分布式呢? 这完全取决于客户端的实现。本连载也将介绍memcached的分布式。
memcached-0001-02.png

图2 memcached的分布式
接下来简单介绍一下memcached的使用方法。

安装memcached


memcached的安装比较简单,这里稍加说明。
memcached支持许多平台。

  • Linux

  • FreeBSD

  • Solaris (memcached 1.2.5以上版本)

  • Mac OS X


另外也能安装在Windows上。这里使用Fedora Core 8进行说明。

memcached的安装


运行memcached需要本文开头介绍的libevent库。Fedora 8中有现成的rpm包, 通过yum命令安装即可。
[bash]
$ sudo yum install libevent libevent-devel
[/bash]
memcached的源代码可以从memcached网站上下载。本文执笔时的最新版本为1.2.5。Fedora 8虽然也包含了memcached的rpm,但版本比较老。因为源代码安装并不困难, 这里就不使用rpm了。

memcached安装与一般应用程序相同,configure、make、make install就行了。
[bash]
$ wget http://www.danga.com/memcached/dist/memcached-1.2.5.tar.gz
$ tar zxf memcached-1.2.5.tar.gz
$ cd memcached-1.2.5
$ ./configure
[/bash]
默认情况下memcached安装到/usr/local/bin下。

memcached的启动


从终端输入以下命令,启动memcached。
[bash]
$ /usr/local/bin/memcached -p 11211 -m 64m -vv
slab class 1: chunk size 88 perslab 11915
slab class 2: chunk size 112 perslab 9362
slab class 3: chunk size 144 perslab 7281
中间省略
slab class 38: chunk size 391224 perslab 2
slab class 39: chunk size 489032 perslab 2
<23 server listening
<24 send buffer was 110592, now 268435456
<24 server listening (udp)
<24 server listening (udp)
<24 server listening (udp)
<24 server listening (udp)
[/bash]
这里显示了调试信息。这样就在前台启动了memcached,监听TCP端口11211 最大内存使用量为64M。调试信息的内容大部分是关于存储的信息, 下次连载时具体说明。
作为daemon后台启动时,只需
[bash]
$ /usr/local/bin/memcached -p 11211 -m 64m -d
[/bash]
这里使用的memcached启动选项的内容如下。





















选项 说明
-p 使用的TCP端口。默认为11211
-m 最大内存大小。默认为64M
-vv 用very vrebose模式启动,调试信息和错误输出到控制台
-d 作为daemon在后台启动

上面四个是常用的启动选项,其他还有很多,通过
[bash]
$ /usr/local/bin/memcached -h
[/bash]
命令可以显示。许多选项可以改变memcached的各种行为, 推荐读一读。

用客户端连接


许多语言都实现了连接memcached的客户端,其中以Perl、PHP为主。仅仅memcached网站上列出的语言就有

  • Perl

  • PHP

  • Python

  • Ruby

  • C#

  • C/C++

  • Lua


等等。

这里介绍通过mixi正在使用的Perl库链接memcached的方法。

使用Cache::Memcached


Perl的memcached客户端有

  • Cache::Memcached

  • Cache::Memcached::Fast

  • Cache::Memcached::libmemcached


等几个CPAN模块。这里介绍的Cache::Memcached是memcached的作者Brad Fitzpatric的作品, 应该算是memcached的客户端中应用最为广泛的模块了。

使用Cache::Memcached连接memcached


下面的源代码为通过Cache::Memcached连接刚才启动的memcached的例子。
[perl]
#!/usr/bin/perl
use strict;
use warnings;
use Cache::Memcached;
my $key = "foo";
my $value = "bar";
my $expires = 3600; # 1 hour
my $memcached = Cache::Memcached->new({
servers => ["127.0.0.1:11211"],
compress_threshold => 10_000 });
$memcached->add($key, $value, $expires);
my $ret = $memcached->get($key);
print "$ret\n";
[/perl]
在这里,为Cache::Memcached指定了memcached服务器的IP地址和一个选项,以生成实例。Cache::Memcached常用的选项如下所示。
















选项 说明
servers 用数组指定memcached服务器和端口
compress_threshold 数据压缩时使用的值
namespace 指定添加到键的前缀

另外,Cache::Memcached通过Storable模块可以将Perl的复杂数据序列化之后再保存, 因此散列、数组、对象等都可以直接保存到memcached中。

保存数据


向memcached保存数据的方法有

  • add

  • replace

  • set


它们的使用方法都相同:
[bash]
my $add = $memcached->add( '键', '值', '期限' );
my $replace = $memcached->replace( '键', '值', '期限' );
[/bash]
向memcached保存数据时可以指定期限(秒)。不指定期限时,memcached按照LRU算法保存数据。这三个方法的区别如下:

















选项 说明
add 仅当存储空间中不存在键相同的数据时才保存
replace 仅当存储空间中存在键相同的数据时才保存
set 与add和replace不同,无论何时都保存

获取数据


获取数据可以使用get和get_multi方法。
[perl]
my $val = $memcached->get('键');
my $val = $memcached->get_multi('键1', '键2', '键3', '键4', '键5');
[/perl]
一次取得多条数据时使用get_multi。get_multi可以非同步地同时取得多个键值,其速度要比循环调用get快数十倍。

删除数据


删除数据使用delete方法,不过它有个独特的功能。
[perl]
$memcached->delete('键', '阻塞时间(秒)');
[/perl]
删除第一个参数指定的键的数据。第二个参数指定一个时间值,可以禁止使用同样的键保存新数据。此功能可以用于防止缓存数据的不完整。但是要注意,set函数忽视该阻塞,照常保存数据

增一和减一操作


可以将memcached上特定的键值作为计数器使用。
[perl]
my $ret = $memcached->incr('键');
$memcached->add('键', 0) unless defined $ret;
[/perl]
增一和减一是原子操作,但未设置初始值时,不会自动赋成0。因此,应当进行错误检查,必要时加入初始化操作。而且,服务器端也不会对超过2 32时的行为进行检查。

总结


这次简单介绍了memcached,以及它的安装方法、Perl客户端Cache::Memcached的用法。只要知道,memcached的使用方法十分简单就足够了。
下次由前坂来说明memcached的内部结构。了解memcached的内部构造, 就能知道如何使用memcached才能使Web应用的速度更上一层楼。欢迎继续阅读下一章。

Google 推出DNS 服务器

今日看到一消息,Google推出DNS 服务器
首选: 8.8.8.8
备选: 8.8.4.4

这地址真是深谙中国人心理啊~

Mouse Event

鼠标事件,总是难以处理。mouseover, mouseout在 FF 下跑还比较正常,在 IE 下就像是患了抽搐症,根本就无法控制。

看了如下的文章,终其原因是 Javascript 的冒泡事件。
还是摘抄了过来~

TopExampleMousedown, mouseup, clickDblclickMousemoveMouseover and mouseoutrelatedTarget, fromElement, toElementCross–browser scriptsMousing out of a layerMouseenter and mouseleaveEnd


We’ll go through all mouse events: mousedown, mouseup and click, dblclick, mousemove and finally mouseover and mouseout. Then I explain the relatedTarget, fromElement and toElement event properties. Finally the Microsoft proprietary mouseenter and mouseleave events.

For browser compatibility, see the Event Compatibility Tables page.

Example

Here’s a small example. Try it to better understand the explanations below.
Mousedown, mouseup, click and dblclick are registered to the link. You’ll see the events that take place in the textarea, or in an alert, if you so choose.

The event handlers are registered on this link.
Clear textarea.


Show alert instead of writing to textarea



Mousedown, mouseup, click

If the user clicks on an element no less than three mouse events fire, in this order:

1. mousedown, user depresses the mouse button on this element
2. mouseup, user releases the mouse button on this element
3. click, one mousedown and one mouseup detected on this element

In general mousedown and mouseup are more useful than click. Some browsers don’t allow you to read out mouse button information onclick. Furthermore, sometimes the user does something with his mouse but no click event follows.

Suppose the user depresses the mouse button on a link, then moves his mouse off the link and then releases the mouse button. Now the link only registers a mousedown event. Similarly, if the user depresses the mouse button, then moves the mouse over a link and then releases the mouse button, the link only registers a mouseup. In neither case does a click event fire.

Whether or not this is a problem depends on the user interaction you want. But you should generally register your script onmousedown/up, unless you’re completely sure you want the click event and nothing else.

If you use alerts in handling these events, the browser may lose track of which event took place on which element and how many times it took place, messing up your scripts. So it’s best not to use alerts.

Dblclick


The dblclick event is rarely used. Even when you use it, you should be sure never to register both an onclick and an ondblclick event handler on the same HTML element. Finding out what the user has actually done is nearly impossible if you register both.

After all, when the user double–clicks on an element one click event takes place before the dblclick. Besides, in Netscape the second click event is also separately handled before the dblclick. Finally, alerts are dangerous here, too.

So keep your clicks and dblclicks well separated to avoid complications.

Mousemove

The mousemove event works fine, but you should be aware that it may take quite some system time to process all mousemove events. If the user moves the mouse one pixel, the mousemove event fires. Even when nothing actually happens, long and complicated functions take time and this may affect the usability of the site: everything goes very slowly, especially on old computers.

Therefore it’s best to register an onmousemove event handler only when you need it and to remove it as soon as it’s not needed any more:
[js]
element.onmousemove = doSomething;
// later
element.onmousemove = null;
[/js]

Mouseover and mouseout


Take another look at the example, switch the mouseovers on and try them. The example adds an onmouseover event handler to ev3 only. However, you’ll notice that a mouseover event takes place not only when the mouse enters ev3's area, but also when it enters the area of ev4 or the span. In Mozilla before 1.3, the event even fires when the mouse enters the area of a text!

The reason for this is of course event bubbling. The user causes a mouseover event on ev4. There is no onmouseover event handler on this element, but there is one on ev3. As soon as the event has bubbled up to this element, the event handler is executed.

Now this setup, though completely correct, gives us some problems. Our first problem is targeting. Suppose the mouse enters ev4:

-----------------------------------------
| This is div id="ev3" |
| ----------------------------- |
| | This is div id="ev4" | |
| | -------- <-------- |
| | | span | | |
| | | | | |
| | -------- | |
| ----------------------------- |
-----------------------------------------

<--------: mouse movement

Now the target/srcElement of this event is ev4: it’s the element the event took place on, since the user mouses over it. But when this happens:

-----------------------------------------
| This is div id="ev3" |
| ----------------------------- |
| | This is div id="ev4" | |
| | -------- | |
| | | span | | |
| | | --------> | |
| | -------- | |
| ----------------------------- |
-----------------------------------------

-------->: mouse movement

the event has exactly the same target/srcElement. Here, too, the mouse enters ev4. Nonetheless you might want do one thing when the mouse comes from ev3 and another thing when it comes from the SPAN. So we need to know where the mouse comes from.

relatedTarget, fromElement, toElement


W3C added the relatedTarget property to mouseover and mouseout events. This contains the element the mouse came from in case of mouseover, or the element it goes to in case of mouseout.

Microsoft created two properties to contain this information:

  • fromElement refers to the element the mouse comes from. This is interesting to know in case of mouseover.

  • toElement refers to the element the mouse goes to. This is interesting to know in case of mouseout.



In our first example, relatedTarget/fromElement contains a reference to ev3, in our second example to the SPAN. Now you know where the mouse came from.

Cross–browser scripts


So if you want to know where the mouse comes from in case of mouseover, do:
[js]
function doSomething(e) {
if (!e) var e = window.event;
var relTarg = e.relatedTarget || e.fromElement;
}
[/js]
If you want to know where the mouse goes to in case of mouseout, do:
[js]
function doSomething(e) {
if (!e) var e = window.event;
var relTarg = e.relatedTarget || e.toElement;
}
[/js]

Mousing out of a layer


In a layer-based navigation you may need to know when the mouse leaves a layer so that it can be closed. Therefore you register an onmouseout event handler to the layer. However, event bubbling causes this event handler to fire when the mouse leaves any element inside the layer, too.

--------------
| Layer |.onmouseout = doSomething;
| -------- |
| | Link | ----> We want to know about this mouseout
| | | |
| -------- |
| -------- |
| | Link | |
| | ----> | but not about this one
| -------- |
--------------
---->: mouse movement

Another show stopper is that when you move the mouse into the layer, and then onto a link, browsers register a mouseout event on the layer! It doesn't make much sense to me (the mouse is still in the layer), but all browsers agree on this one.

So how do we reject any mouseout that does not take place when the mouse actually leaves the layer?
[js]
function doSomething(e) {
if (!e) var e = window.event;
var tg = (window.event) ? e.srcElement : e.target;
if (tg.nodeName != 'DIV') return;
var reltg = (e.relatedTarget) ? e.relatedTarget : e.toElement;
while (reltg != tg && reltg.nodeName != 'BODY')
reltg= reltg.parentNode
if (reltg== tg) return;
// Mouseout took place when mouse actually left layer
// Handle event
}
[/js]
First get the event target, ie. the element the mouse moved out of. If the target is not the DIV (layer), end the function immediately, since the mouse has certainly not left the layer.

If the target is the layer, we're still not sure if the mouse left the layer or entered a link within the layer. Therefore we're going to check the relatedTarget/toElement of the event, ie. the element the mouse moved to.

We read out this element, and then we're going to move upwards through the DOM tree until we either encounter the target of the event (ie. the DIV), or the body element.

If we encounter the target the mouse moves towards a child element of the layer, so the mouse has not actually left the layer. We stop the function.

When the function has survived all these checks we're certain that the mouse has actually left the layer and we can take appropriate action (usually making the layer invisible).
Mouseenter and mouseleave

Microsoft has another solution. It has created two new events mouseenter and mouseleave. They are almost the same as mouseover and mouseout except that they don’t react to event bubbling. Therefore they see the entire HTML element they’re registered to as one solid block and don’t react to mouseovers and –outs taking place inside the block.

So using these events solves our problem too: they react only to mouseovers/outs on the element they’re registered to.

At the moment these events are only supported by Explorer 5.5 on Windows and higher. Maybe the other browser vendors will copy these events.


看完,用上上面所说的。结果还是不令人满意。
后面,还是找了jQuery 的一个插件,才算是基本解决问题。

2009年12月16日星期三

有趣的浏览器地址栏Javascript代码

FROM: http://www.kenengba.com/post/483.html/trackback

1 编辑网页


在地址栏输入下面的代码按 enter, 网页上所有元素都能变成可编辑状态, 你可以移动, 调整元素大小. 如果你只是讨厌某个网站想发泄一下, 我建议你使用 NetDisater.
[js]
javascript:document.body.contentEditable='true'; document.designMode='on'; void 0
[/js]

2 无敌风火轮


在地址栏运行下面的代码可使页面上所有图片元素一个接一个地转圈.

这种效果最好的实现地方就是图片搜索了, 改变代码里的 img 成任何网页上有的字符, 可以使这些字符做无敌风火轮运动.
[js]
javascript:R=0; x1=.1; y1=.05; x2=.25; y2=.24; x3=1.6; y3=.24; x4=300; y4=200; x5=300; y5=200; DI=document.getElementsByTagName("img"); DIL=DI.length; function A(){for(i=0; i-DIL; i++){DIS=DI[ i ].style; DIS.position='absolute'; DIS.left=(Math.sin(R*x1+i*x2+x3)*x4+x5)+"px"; DIS.top=(Math.cos(R*y1+i*y2+y3)*y4+y5)+"px"}R++}setInterval('A()',5); void(0);
[/js]

3 晃来晃去


不但是你, 浏览器也不是那么喜欢这个 Javascript. 在地址栏运行这个代码后, 浏览器会迅速地晃来晃去.
[js]

javascript:function flood(n) {if (self.moveBy) {for (i = 200; i &gt; 0;i--){for (j = n; j &gt; 0; j--) {self.moveBy(1,i); self.moveBy(i,0);self.moveBy(0,-i); self.moveBy(-i,0); } } }}flood(6);{ var inp = "D-X !msagro na dah tsuj resworb rouY"; var outp = ""; for (i = 0; i &lt;= inp.length; i++) {outp =inp.charAt (i) + outp ; } alert(outp) ;}; reverse
[/js]

4 计算器


在地址栏输入下面的代码, 可以实现简单的四则运算:
[js]
javascript: alert(34343+3434-222);
[/js]

5 防钓鱼验证


某些钓鱼网站提供的 URL 和网页本身的 URL 是不一致的, 可以用下面的代码进行验证, 当两个 URL 相差太大的时候, 就要稍加小心了:
[js]
javascript:alert("The actual URL is:tt" + location.protocol + "//" + location.hostname + "/" + "nThe address URL is:tt" + location.href + "n" + "nIf the server names do not match, this may be a spoof.");
[/js]

颜色渐变插件

FROM: Source Code
[js]
/*
* jQuery Color Animations
* Copyright 2007 John Resig
* Released under the MIT and GPL licenses.
*/

(function(jQuery){

// We override the animation for all of these color styles
jQuery.each(['backgroundColor', 'borderBottomColor', 'borderLeftColor', 'borderRightColor', 'borderTopColor', 'color', 'outlineColor'], function(i,attr){
jQuery.fx.step[attr] = function(fx){
if ( fx.state == 0 ) {
fx.start = getColor( fx.elem, attr );
fx.end = getRGB( fx.end );
}

fx.elem.style[attr] = "rgb(" + [
Math.max(Math.min( parseInt((fx.pos * (fx.end[0] - fx.start[0])) + fx.start[0]), 255), 0),
Math.max(Math.min( parseInt((fx.pos * (fx.end[1] - fx.start[1])) + fx.start[1]), 255), 0),
Math.max(Math.min( parseInt((fx.pos * (fx.end[2] - fx.start[2])) + fx.start[2]), 255), 0)
].join(",") + ")";
}
});

// Color Conversion functions from highlightFade
// By Blair Mitchelmore
// http://jquery.offput.ca/highlightFade/

// Parse strings looking for color tuples [255,255,255]
function getRGB(color) {
var result;

// Check if we're already dealing with an array of colors
if ( color && color.constructor == Array && color.length == 3 )
return color;

// Look for rgb(num,num,num)
if (result = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(color))
return [parseInt(result[1]), parseInt(result[2]), parseInt(result[3])];

// Look for rgb(num%,num%,num%)
if (result = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(color))
return [parseFloat(result[1])*2.55, parseFloat(result[2])*2.55, parseFloat(result[3])*2.55];

// Look for #a0b1c2
if (result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(color))
return [parseInt(result[1],16), parseInt(result[2],16), parseInt(result[3],16)];

// Look for #fff
if (result = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(color))
return [parseInt(result[1]+result[1],16), parseInt(result[2]+result[2],16), parseInt(result[3]+result[3],16)];

// Otherwise, we're most likely dealing with a named color
return colors[jQuery.trim(color).toLowerCase()];
}

function getColor(elem, attr) {
var color;

do {
color = jQuery.curCSS(elem, attr);

// Keep going until we find an element that has color, or we hit the body
if ( color != '' && color != 'transparent' || jQuery.nodeName(elem, "body") )
break;

attr = "backgroundColor";
} while ( elem = elem.parentNode );

return getRGB(color);
};

// Some named colors to work with
// From Interface by Stefan Petre
// http://interface.eyecon.ro/

var colors = {
aqua:[0,255,255],
azure:[240,255,255],
beige:[245,245,220],
black:[0,0,0],
blue:[0,0,255],
brown:[165,42,42],
cyan:[0,255,255],
darkblue:[0,0,139],
darkcyan:[0,139,139],
darkgrey:[169,169,169],
darkgreen:[0,100,0],
darkkhaki:[189,183,107],
darkmagenta:[139,0,139],
darkolivegreen:[85,107,47],
darkorange:[255,140,0],
darkorchid:[153,50,204],
darkred:[139,0,0],
darksalmon:[233,150,122],
darkviolet:[148,0,211],
fuchsia:[255,0,255],
gold:[255,215,0],
green:[0,128,0],
indigo:[75,0,130],
khaki:[240,230,140],
lightblue:[173,216,230],
lightcyan:[224,255,255],
lightgreen:[144,238,144],
lightgrey:[211,211,211],
lightpink:[255,182,193],
lightyellow:[255,255,224],
lime:[0,255,0],
magenta:[255,0,255],
maroon:[128,0,0],
navy:[0,0,128],
olive:[128,128,0],
orange:[255,165,0],
pink:[255,192,203],
purple:[128,0,128],
violet:[128,0,128],
red:[255,0,0],
silver:[192,192,192],
white:[255,255,255],
yellow:[255,255,0]
};

})(jQuery);
[/js]

复制到剪切板

web 开发中常常要实现 "复制到剪切板" 功能。这个功能很实用,但是由于安全问题,浏览器的限制越来越严,实现的方法也越来越有限了。Firefox 默认下不能直接通过 Javascript 操作剪切板,必须开启相关的设置才行。想只通过 Javascript 技术实现跨浏览器 的剪切板是行不通的。现在常用的方法是利用 JavaScript+Flash实现,普遍流传的办法是 _clipboard.swf,这是国外最早实现的(著名的 Clipboard Copy 解决方案: http://www.jeffothy.com/weblog/clipboard-copy/)。但是很可惜,_clipboard.swf 在新出来的 flash10 中无效,因为 Flash10 中规定了只有在 swf 上进行了实际的操作(比如鼠标点击)才能启动剪切板。而 _clipboard.swf 方法的 swf 是隐藏的,通过 JavaScript 来操作 flash 的剪切板,显然没有对 swf 进行实际的用户操作。
[js]
function copy(v_str , tip_obj) {
copy_clip(v_str);
}

function copy_clip(text2copy) {
if (window.clipboardData) {
window.clipboardData.setData("Text", text2copy);
} else {
var flashcopier = 'flashcopier';
if(!document.getElementById(flashcopier)) {
var divholder = document.createElement('div');
divholder.id = flashcopier;
document.body.appendChild(divholder);
}
document.getElementById(flashcopier).innerHTML = '';
var divinfo = '<embed src="/img/_clipboard.swf" FlashVars="clipboard='+escape(text2copy)+'" width="0" height="0" type="application/x-shockwave-flash"></embed>';
document.getElementById(flashcopier).innerHTML = divinfo;
}
return true;
}

function copyToClipboard(txt) {
if (window.clipboardData) {
window.clipboardData.clearData();
window.clipboardData.setData("Text", txt);
} else if (navigator.userAgent.indexOf("Opera") != -1) {
window.location = txt;
} else if (window.netscape) {
try {
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
} catch (e) {
alert("您的firefox安全限制限制您进行剪贴板操作,请打开'about:config'将signed.applets.codebase_principal_support'设置为true'之后重试");
return false;
}
var clip = Components.classes['@mozilla.org/widget/clipboard;1'].createInstance(Components.interfaces.nsIClipboard);
if (!clip)
return false;
var trans = Components.classes['@mozilla.org/widget/transferable;1'].createInstance(Components.interfaces.nsITransferable);
if (!trans)
return false;
trans.addDataFlavor('text/unicode');
var str = new Object();
var len = new Object();
var str = Components.classes['@mozilla.org/supports-string;1'].createInstance(Components.interfaces.nsISupportsString);
var copytext = txt;
str.data = copytext;
trans.setTransferData("text/unicode",str,copytext.length*2);
var clipid = Components.interfaces.nsIClipboard;
if (!clip)
return false;
clip.setData(trans,null,clipid.kGlobalClipboard);
}
return true;
}
[/js]

最近国外出现了一种新的方法,而且专门做了一个 JavaScript 库 Zero Clipboard ,它包含一个 flash 影片和一个 JavaScript 接口,这个 flash 是透明的(不是隐藏),用户不会察觉到它的存在。这个 flash 覆盖在一个 DOM元素上,比如 button,div之类,当点击这个 DOM 时,你实际点击的是这个 flash,这个作用在 flash 上的动作能够开启 flash 的剪切板。这实际上就是一种 clickjacking。

DEMO页面 : http://bowser.macminicolo.net/~jhuckaby/zeroclipboard/

Zero Clipboard项目主页: http://code.google.com/p/zeroclipboard/

固定位置CSS

TOP:
[css]
.toolbar{
position:fixed;
z-index:50;
top:0px;
left:0;
width:100%;
_top:expression(offsetParent.scrollTop);
_position:absolute;
}
[/css]
BOTTOM:
[css]
#bottombar {
position:fixed;
bottom:0;
z-index:50;
_position: absolute;
_bottom: auto;
_clear: both;
_top:expression(eval(document.compatMode && document.compatMode=='CSS1Compat') ? documentElement.scrollTop+(documentElement.clientHeight-this.clientHeight) - 6: document.body.scrollTop+(document.body.clientHeight-this.clientHeight) - 6);
[/css]

2009年12月11日星期五

万年历

[js]
var lunarInfo=new Array(
0x04bd8,0x04ae0,0x0a570,0x054d5,0x0d260,0x0d950,0x16554,0x056a0,0x09ad0,0x055d2,
0x04ae0,0x0a5b6,0x0a4d0,0x0d250,0x1d255,0x0b540,0x0d6a0,0x0ada2,0x095b0,0x14977,
0x04970,0x0a4b0,0x0b4b5,0x06a50,0x06d40,0x1ab54,0x02b60,0x09570,0x052f2,0x04970,
0x06566,0x0d4a0,0x0ea50,0x06e95,0x05ad0,0x02b60,0x186e3,0x092e0,0x1c8d7,0x0c950,
0x0d4a0,0x1d8a6,0x0b550,0x056a0,0x1a5b4,0x025d0,0x092d0,0x0d2b2,0x0a950,0x0b557,
0x06ca0,0x0b550,0x15355,0x04da0,0x0a5d0,0x14573,0x052d0,0x0a9a8,0x0e950,0x06aa0,
0x0aea6,0x0ab50,0x04b60,0x0aae4,0x0a570,0x05260,0x0f263,0x0d950,0x05b57,0x056a0,
0x096d0,0x04dd5,0x04ad0,0x0a4d0,0x0d4d4,0x0d250,0x0d558,0x0b540,0x0b5a0,0x195a6,
0x095b0,0x049b0,0x0a974,0x0a4b0,0x0b27a,0x06a50,0x06d40,0x0af46,0x0ab60,0x09570,
0x04af5,0x04970,0x064b0,0x074a3,0x0ea50,0x06b58,0x055c0,0x0ab60,0x096d5,0x092e0,
0x0c960,0x0d954,0x0d4a0,0x0da50,0x07552,0x056a0,0x0abb7,0x025d0,0x092d0,0x0cab5,
0x0a950,0x0b4a0,0x0baa4,0x0ad50,0x055d9,0x04ba0,0x0a5b0,0x15176,0x052b0,0x0a930,
0x07954,0x06aa0,0x0ad50,0x05b52,0x04b60,0x0a6e6,0x0a4e0,0x0d260,0x0ea65,0x0d530,
0x05aa0,0x076a3,0x096d0,0x04bd7,0x04ad0,0x0a4d0,0x1d0b6,0x0d250,0x0d520,0x0dd45,
0x0b5a0,0x056d0,0x055b2,0x049b0,0x0a577,0x0a4b0,0x0aa50,0x1b255,0x06d20,0x0ada0)
var solarMonth=new Array(31,28,31,30,31,30,31,31,30,31,30,31);
var Gan=new Array("甲","乙","丙","丁","戊","己","庚","辛","壬","癸");
var Zhi=new Array("子","丑","寅","卯","辰","巳","午","未","申","酉","戌","亥");
var Animals=new Array("鼠","牛","虎","兔","龙","蛇","马","羊","猴","鸡","狗","猪");
var solarTerm = new Array("小寒","大寒","立春","雨水","惊蛰","春分","清明","谷雨","立夏","小满","芒种","夏至","小暑","大暑","立秋","处暑","白露","秋分","寒露","霜降","立冬","小雪","大雪","冬至");
var sTermInfo = new Array(0,21208,42467,63836,85337,107014,128867,150921,173149,
195551,218072,240693,263343,285989,308563,331033,353350,375494,397447,419210,
440795,462224,483532,504758);
var nStr1 = new Array('日','一','二','三','四','五','六','七','八','九','十');
var nStr2 = new Array('初','十','廿','卅',' ');
//var monthName = new Array("JAN","FEB","MAR","APR","MAY","JUN","JUL","AUG","SEP","OCT","NOV","DEC");
var monthName = new Array("1","2","3","4","5","6","7","8","9","10","11","12");

var sFtv = new Array(
"0101*元旦",
"0115 成人节",
"0214 情人节",
"0303 女孩节",
"0308 妇女节",
"0312 植树节",
"0308 妇女节",
"0312 植树节",
"0315 消费者日",
"0401 愚人节",
"0407 卫生日",
"0413 宋干节",
"0417 食品节",
"0501 劳动节",
"0504 青年节",
"0505 男孩节",
"0512 护士节",
"0513 无烟日",
"0601 儿童节",
"0605 环境日",
"0701 香港回归",
"0801 建军节",
"0804 筷子节",
"0808 父亲节",
"0910 教师节",
"0915 敬老节",
"0928 孔子诞辰",
"1001*国庆节",
"1006 老人节",
"1010 啤酒节",
"1016 粮食日",
"1024 联合国日",
"1031 万圣节",
"1111 光棍节",
"1220 澳门回归",
"1224 平安夜",
"1225 圣诞节",
"1226 节礼日"
);

var lFtv = new Array(
"0101*春节",
"0115 元宵节",
"0505 端午节",
"0707 七夕",
"0715 中元节",
"0815 中秋节",
"0909 重阳节",
"1208 腊八节",
"1224 小年",
"0100*除夕"
);

var wFtv = new Array(
"0520 母亲节",
"0630 父亲节",
"0716 合作节",
"1144 感恩节"
);

var cM = new Array(
"正", "二", "三", "四", "五", "六", "七", "八", "九", "十", "十一", "十二");

function lYearDays(y) {
var i, sum = 348
for(i=0x8000; i>0x8; i>>=1) sum += (lunarInfo[y-1900] & i)? 1: 0
return(sum+leapDays(y))
}

function leapDays(y) {
if(leapMonth(y)) return((lunarInfo[y-1900] & 0x10000)? 30: 29)
else return(0)
}

function leapMonth(y) {
return(lunarInfo[y-1900] & 0xf)
}

function monthDays(y,m) {
return( (lunarInfo[y-1900] & (0x10000>>m))? 30: 29 )
}

function Lunar(objDate) {
var i, leap=0, temp=0;
var baseDate = new Date(1900, 0, 31);
var offset = (objDate - baseDate)/86400000;

this.dayCyl = offset + 40;
this.monCyl = 14;

for(i = 1900; i<2050 && offset>0; i++) {
temp = lYearDays(i)
offset -= temp
this.monCyl += 12
}

if(offset<0) {
offset += temp;
i--;
this.monCyl -= 12
}

this.year = i
this.yearCyl = i-1864

leap = leapMonth(i)
this.isLeap = false


for(i=1; i<13 && offset>0; i++) {

if(leap>0 && i==(leap+1) && this.isLeap==false) {
--i; this.isLeap = true; temp = leapDays(this.year);
} else {
temp = monthDays(this.year, i);
}

if(this.isLeap==true && i==(leap+1)) this.isLeap = false

offset -= temp
if(this.isLeap == false) this.monCyl++
}

if(offset==0 && leap>0 && i==leap+1)
if(this.isLeap) {
this.isLeap = false;
} else {
this.isLeap = true; --i; --this.monCyl;
}

if(offset<0) { offset += temp; --i; --this.monCyl; }

this.month = i;
this.day = offset + 1;
}

function solarDays(y,m) {
if(m==1)
return(((y%4 == 0) && (y%100 != 0) || (y%400 == 0))? 29: 28);
else
return(solarMonth[m]);
}

function cyclical(num) {
return(Gan[num%10]+Zhi[num%12])
}

function calElement(sYear,sMonth,sDay,week,lYear,lMonth,lDay,isLeap,cYear,cMonth,cDay) {

this.isToday = false;
this.sYear = sYear;
this.sMonth = sMonth;
this.sDay = sDay;
this.week = week;
this.lYear = lYear;
this.lMonth = lMonth;
this.lDay = lDay;
this.isLeap = isLeap;
this.cYear = cYear;
this.cMonth = cMonth;
this.cDay = cDay;

this.color = '';

this.lunarFestival = ''; //农历节日
this.solarFestival = ''; //国历节日
this.solarTerms = ''; //节气

}

function sTerm(y,n) {
var offDate = new Date( ( 31556925974.7*(y-1900) + sTermInfo[n]*60000 ) + Date.UTC(1900,0,6,2,5) )
return(offDate.getUTCDate())
}

function calendar(y,m) {
var sDObj, lDObj, lY, lM, lD=1, lL, lX=0, tmp1, tmp2;
var lDPOS = new Array(3);
var n = 0;
var firstLM = 0;

sDObj = new Date(y, m, 1); //当月一日日期
this.length = solarDays(y, m); //国历当月天数

this.firstWeek = sDObj.getDay(); //国历当月1日星期几

for(var i = 0;i < this.length; i++) {
if(lD > lX) {
sDObj = new Date(y, m, i + 1) //当月一日日期

lDObj = new Lunar(sDObj) //农历
lY = lDObj.year //农历年
lM = lDObj.month //农历月
lD = lDObj.day //农历日
lL = lDObj.isLeap //农历是否闰月
lX = lL? leapDays(lY): monthDays(lY,lM) //农历当月最後一天

if(n==0) firstLM = lM;
lDPOS[n++] = i-lD + 1
}

//sYear,sMonth,sDay,week,
//lYear,lMonth,lDay,isLeap,
//cYear,cMonth,cDay
this[i] = new calElement(y, m+1, i+1, nStr1[(i+this.firstWeek)%7],
lY, lM, lD++, lL,
cyclical(lDObj.yearCyl) ,cyclical(lDObj.monCyl), cyclical(lDObj.dayCyl++) )

if((i+this.firstWeek)%7==0) this[i].color = 'red' //周日颜色
if((i+this.firstWeek)%14==13) this[i].color = 'red' //周休二日颜色
}

tmp1=sTerm(y,m*2 )-1;
tmp2=sTerm(y,m*2+1)-1;
if (typeof(solarTerm[m * 2]) != 'undefined') {
this[tmp1].solarTerms = solarTerm[m*2];
}

if (typeof(solarTerm[m * 2 + 1]) != 'undefined') {
this[tmp2].solarTerms = solarTerm[m*2+1];
}

if(m==3) this[tmp1].color = 'red' //清明颜色

for(i in sFtv)
if(sFtv[i].match(/^(\d{2})(\d{2})([\s\*])(.+)$/))
if(Number(RegExp.$1)==(m+1)) {
this[Number(RegExp.$2)-1].solarFestival += RegExp.$4 + ' '
if(RegExp.$3=='*') this[Number(RegExp.$2)-1].color = 'red'
}

for(i in wFtv)
if(wFtv[i].match(/^(\d{2})(\d)(\d)([\s\*])(.+)$/))
if(Number(RegExp.$1)==(m+1)) {
tmp1=Number(RegExp.$2)
tmp2=Number(RegExp.$3)
this[((this.firstWeek>tmp2)?7:0) + 7*(tmp1-1) + tmp2 - this.firstWeek].solarFestival += RegExp.$5 + ' '
}

//农历节日
for(i in lFtv)
if(lFtv[i].match(/^(\d{2})(.{2})([\s\*])(.+)$/)) {
tmp1=Number(RegExp.$1)-firstLM
if(tmp1==-11) tmp1=1
if(tmp1 >=0 && tmp1<n) {
tmp2 = lDPOS[tmp1] + Number(RegExp.$2) -1
if( tmp2 >= 0 && tmp2<this.length) {
this[tmp2].lunarFestival += RegExp.$4 + ' '
if(RegExp.$3=='*') this[tmp2].color = 'red'
}
}
}

//黑色星期五
if((this.firstWeek+12)%7==5)
this[12].solarFestival += '黑色星期五 '

//今日
if(y==tY && m==tM && typeof(this[tD - 1]) != 'undefined') this[tD-1].isToday = true;

}

//====================== 中文日期
function cDay(d){
var s;
switch (d) {
case 10:
s = '初十'; break;
case 20:
s = '二十'; break;
break;
case 30:
s = '三十'; break;
break;
default :
s = nStr2[Math.floor(d/10)];
s += nStr1[d%10];
}
return(s);
}

var cld;
function drawCld(SY,SM) {
var i,sD,s,size;
cld = new calendar(SY,SM);

if(SY>1874 && SY<1909) yDisplay = '光绪' + (((SY-1874)==1)?'元':SY-1874)
if(SY>1908 && SY<1912) yDisplay = '宣统' + (((SY-1908)==1)?'元':SY-1908)
if(SY>1911 && SY<1950) yDisplay = '民国' + (((SY-1911)==1)?'元':SY-1911)
// if(SY>1949) yDisplay = '共和国' + (((SY-1949)==1)?'元':SY-1949)

// GZ.innerHTML = yDisplay +'年 农历' + cyclical(SY-1900+36) + '年('+Animals[(SY-4)%12]+')';

if (SY>1949) yDisplay = ''

GZ.innerHTML = yDisplay +' 农历' + cyclical(SY-1900+36) + '年('+Animals[(SY-4)%12]+')';

YMBG.innerHTML = SY + "年" + monthName[SM] + "月";
for(i=0;i<42;i++) {
sObj=eval('SD'+ i);
lObj=eval('LD'+ i);
sObj.className = '';
sD = i - cld.firstWeek;
if(sD>-1 && sD<cld.length) {
sObj.innerHTML = sD+1;
if(cld[sD].isToday) sObj.className = 'todyaColor';
sObj.style.color = cld[sD].color;
if(cld[sD].lDay==1)
lObj.innerHTML = '<b>'+(cld[sD].isLeap?'闰':'') + cMonth(cld[sD].lMonth)+ '月' + (monthDays(cld[sD].lYear,cld[sD].lMonth)==29?'小':'大')+'</b>';
else
lObj.innerHTML = cDay(cld[sD].lDay);

s=cld[sD].lunarFestival;

if(s.length>0) {
if(s.length>6) s = s.substr(0, 4)+'…';
s = s.fontcolor('red');
} else { //国历节日
s=cld[sD].solarFestival;
if(s.length>0) {
size = (s.charCodeAt(0)>0 && s.charCodeAt(0)<128)?8:4;
if(s.length>size+2) s = s.substr(0, size)+'…';
s = s.fontcolor('blue');
} else { //廿四节气
s=cld[sD].solarTerms;
if(s.length>0) s = s.fontcolor('limegreen');
}
}
if(s.length>0) lObj.innerHTML = s;

} else { //非日期
sObj.innerHTML = '';
lObj.innerHTML = '';
}
}
}

function cMonth(m) {
if (typeof(cM[parseInt(m) -1]) != 'undefined') {
return cM[parseInt(m) - 1];
} else {
return m;
}
}


var Today = new Date();
var tY = Today.getFullYear();
var tM = Today.getMonth();
var tD = Today.getDate();

tY = 2009;
tM = 4;

cld = new calendar(tY, tM);
$.each(cld, function(i,n ) {
if (i == 9) {
str = '';
$.each(n, function( j, k) {
str = str + j + ' ' + k + "\n";
});
alert(str);
}

});
[/js]

工作需要
时间紧张
只好到网上找了个
在此基础上做修改
很感谢写这万年历的原创者