Posted: November 15, 2006 at 3:54 pm | Tags: cache, class, debug, html, java, linux, php, python, ruby, server, web, 优化, 测试, 类, 缓存
一、memcached 简介
在很多场合,我们都会听到 memcached 这个名字,但很多同学只是听过,并没有用过或实际了解过,只知道它是一个很不错的东东。这里简单介绍一下,memcached 是高效、快速的分布式内存对象缓存系统,主要用于加速 WEB 动态应用程序。
二、memcached 安装
首先是下载 memcached 了,目前最新版本是 1.1.12,直接从官方网站即可下载到 memcached-1.1.12.tar.gz。除此之外,memcached 用到了 libevent,我下载的是 libevent-1.1a.tar.gz。
接下来是分别将 libevent-1.1a.tar.gz 和 memcached-1.1.12.tar.gz 解开包、编译、安装:
# tar -xzf libevent-1.1a.tar.gz# cd libevent-1.1a# ./configure --prefix=/usr# make# make install# cd ..# tar -xzf memcached-1.1.12.tar.gz# cd memcached-1.1.12# ./configure --prefix=/usr# make# make install
安装完成之后,memcached 应该在 /usr/bin/memcached。
三、运行 memcached 守护程序
运行 memcached 守护程序很简单,只需一个命令行即可,不需要修改任何配置文件(也没有配置文件给你修改
):
/usr/bin/memcached -d -m 128 -l 192.168.1.1 -p 11211 -u httpd
参数解释:
-d 以守护程序(daemon)方式运行 memcached;-m 设置 memcached 可以使用的内存大小,单位为 M;-l 设置监听的 IP 地址,如果是本机的话,通常可以不设置此参数;-p 设置监听的端口,默认为 11211,所以也可以不设置此参数;-u 指定用户,如果当前为 root 的话,需要使用此参数指定用户。
当然,还有其它参数可以用,man memcached 一下就可以看到了。
四、memcached 的工作原理
首先 memcached 是以守护程序方式运行于一个或多个服务器中,随时接受客户端的连接操作,客户端可以由各种语言编写,目前已知的客户端 API 包括 Perl/PHP/Python/Ruby/Java/C#/C 等等。PHP 等客户端在与 memcached 服务建立连接之后,接下来的事情就是存取对象了,每个被存取的对象都有一个唯一的标识符 key,存取操作均通过这个 key 进行,保存到 memcached 中的对象实际上是放置内存中的,并不是保存在 cache 文件中的,这也是为什么 memcached 能够如此高效快速的原因。注意,这些对象并不是持久的,服务停止之后,里边的数据就会丢失。
三、PHP 如何作为 memcached 客户端
有两种方法可以使 PHP 作为 memcached 客户端,调用 memcached 的服务进行对象存取操作。
第一种,PHP 有一个叫做 memcache 的扩展,Linux 下编译时需要带上 –enable-memcache[=DIR] 选项,Window 下则在 php.ini 中去掉 php_memcache.dll 前边的注释符,使其可用。
除此之外,还有一种方法,可以避开扩展、重新编译所带来的麻烦,那就是直接使用 php-memcached-client。
本文选用第二种方式,虽然效率会比扩展库稍差一些,但问题不大。
四、PHP memcached 应用示例
首先 下载 memcached-client.php,在下载了 memcached-client.php 之后,就可以通过这个文件中的类“memcached”对 memcached 服务进行操作了。其实代码调用非常简单,主要会用到的方法有 add()、get()、replace() 和 delete(),方法说明如下:
add ($key, $val, $exp = 0)
往 memcached 中写入对象,$key 是对象的唯一标识符,$val 是写入的对象数据,$exp 为过期时间,单位为秒,默认为不限时间;
get ($key)
从 memcached 中获取对象数据,通过对象的唯一标识符 $key 获取;
replace ($key, $value, $exp=0)
使用 $value 替换 memcached 中标识符为 $key 的对象内容,参数与 add() 方法一样,只有 $key 对象存在的情况下才会起作用;
delete ($key, $time = 0)
删除 memcached 中标识符为 $key 的对象,$time 为可选参数,表示删除之前需要等待多长时间。
下面是一段简单的测试代码,代码中对标识符为 ‘mykey’ 的对象数据进行存取操作:
<pre><?php// 包含 memcached 类文件require_once(‘memcached-client.php’);// 选项设置$options = array( ’servers’ => array(‘192.168.1.1:11211′), //memcached 服务的地址、端口,可用多个数组元素表示多个 memcached 服务 ‘debug’ => true, //是否打开 debug ‘compress_threshold’ => 10240, //超过多少字节的数据时进行压缩 ‘persistant’ => false //是否使用持久连接 );// 创建 memcached 对象实例$mc = new memcached($options);// 设置此脚本使用的唯一标识符$key = ‘mykey’;// 往 memcached 中写入对象$mc->add($key, ’some random strings’);$val = $mc->get($key);echo “n”.str_pad(‘$mc->add() ’, 60, ‘_’).“n”;var_dump($val);// 替换已写入的对象数据值$mc->replace($key, array(’some’=>‘haha’, ‘array’=>‘xxx’));$val = $mc->get($key);echo “n”.str_pad(‘$mc->replace() ’, 60, ‘_’).“n”;var_dump($val);// 删除 memcached 中的对象$mc->delete($key);$val = $mc->get($key);echo “n”.str_pad(‘$mc->delete() ’, 60, ‘_’).“n”;var_dump($val);?></pre>
是不是很简单,在实际应用中,通常会把数据库查询的结果集保存到 memcached 中,下次访问时直接从 memcached 中获取,而不再做数据库查询操作,这样可以在很大程度上减轻数据库的负担。通常会将 SQL 语句 md5() 之后的值作为唯一标识符 key。下边是一个利用 memcached 来缓存数据库查询结果集的示例(此代码片段紧接上边的示例代码):
<?php$sql = ‘SELECT * FROM users’;$key = md5($sql); //memcached 对象标识符if ( !($datas = $mc->get($key)) ) { // 在 memcached 中未获取到缓存数据,则使用数据库查询获取记录集。 echo “n”.str_pad(‘Read datas from MySQL.’, 60, ‘_’).“n”; $conn = mysql_connect(‘localhost’, ‘test’, ‘test’); mysql_select_db(‘test’); $result = mysql_query($sql); while ($row = mysql_fetch_object($result)) $datas[] = $row; // 将数据库中获取到的结果集数据保存到 memcached 中,以供下次访问时使用。 $mc->add($key, $datas);} else { echo “n”.str_pad(‘Read datas from memcached.’, 60, ‘_’).“n”;}var_dump($datas);?>
可以看出,使用 memcached 之后,可以减少数据库连接、查询操作,数据库负载下来了,脚本的运行速度也提高了。
之前我曾经写过一篇名为《PHP 实现多服务器共享 SESSION 数据》文章,文中的 SESSION 是使用数据库保存的,在并发访问量大的时候,服务器的负载会很大,经常会超出 MySQL 最大连接数,利用 memcached,我们可以很好地解决这个问题,工作原理如下:
- 用户访问网页时,查看 memcached 中是否有当前用户的 SESSION 数据,使用 session_id() 作为唯一标识符;如果数据存在,则直接返回,如果不存在,再进行数据库连接,获取 SESSION 数据,并将此数据保存到 memcached 中,供下次使用;
- 当前的 PHP 运行结束(或使用了 session_write_close())时,会调用 My_Sess::write() 方法,将数据写入数据库,这样的话,每次仍然会有数据库操作,对于这个方法,也需要进行优化。使用一个全局变量,记录用户进入页面时的 SESSION 数据,然后在 write() 方法内比较此数据与想要写入的 SESSION 数据是否相同,不同才进行数据库连接、写入数据库,同时将 memcached 中对应的对象删除,如果相同的话,则表示 SESSION 数据未改变,那么就可以不做任何操作,直接返回了;
- 那么用户 SESSION 过期时间怎么解决呢?记得 memcached 的 add() 方法有个过期时间参数 $exp 吗?把这个参数值设置成小于 SESSION 最大存活时间即可。另外别忘了给那些一直在线的用户延续 SESSION 时长,这个可以在 write() 方法中解决,通过判断时间,符合条件则更新数据库数据。
五、相关资源
肖理达 (KrazyNio AT hotmail.com), 2006.04. 06, 转载请注明出处
转载自:http://nio.infor96.com/php-memcached/
Posted: November 15, 2006 at 3:24 pm | Tags: cache, class, debug, html, linux, php, server, web, 开发, 类, 缓存
通常的网页缓存方式有动态缓存和静态缓存等几种,在ASP.NET中已经可以实现对页面局部进行缓存,而使用memcached的缓存比ASP.NET的局部缓存更加灵活,可以缓存任意的对象,不管是否在页面上输出。而memcached最大的优点是可以分布式的部署,这对于大规模应用来说也是必不可少的要求。
LiveJournal.com使用了memcached在前端进行缓存,取得了良好的效果,而像wikipedia,sourceforge等也采用了或即将采用memcached作为缓存工具。memcached可以大规模网站应用发挥巨大的作用。
Memcached是什么?
Memcached是高性能的,分布式的内存对象缓存系统,用于在动态应用中减少数据库负载,提升访问速度。
Memcached由Danga Interactive开发,用于提升LiveJournal.com访问速度的。LJ每秒动态页面访问量几千次,用户700万。Memcached将数据库负载大幅度降低,更好的分配资源,更快速访问。
如何使用memcached-Server端?
在服务端运行:
# ./memcached -d -m 2048 -l 10.0.0.40 -p 11211
这将会启动一个占用2G内存的进程,并打开11211端口用于接收请求。由于32位系统只能处理4G内存的寻址,所以在大于4G内存使用PAE的32位服务器上可以运行2-3个进程,并在不同端口进行监听。
如何使用memcached-Client端?
在应用端包含一个用于描述Client的Class后,就可以直接使用,非常简单。
PHP Example:
$options["servers"] = array("192.168.1.41:11211", "192.168.1.42:11212");
$options["debug"] = false;
$memc = new MemCachedClient($options);
$myarr = array("one","two", 3);
$memc->set("key_one", $myarr);
$val = $memc->get("key_one");
print $val[0]."\n"; // prints ‘one‘
print $val[1]."\n"; // prints ‘two‘
print $val[2]."\n"; // prints 3
为什么不使用数据库做这些?
暂且不考虑使用什么样的数据库(MS-SQL, Oracle, Postgres, MysQL-InnoDB, etc..), 实现事务(ACID,Atomicity, Consistency, Isolation, and Durability )需要大量开销,特别当使用到硬盘的时候,这就意味着查询可能会阻塞。当使用不包含事务的数据库(例如Mysql-MyISAM),上面的开销不存在,但读线程又可能会被写线程阻塞。
Memcached从不阻塞,速度非常快。
为什么不使用共享内存?
最初的缓存做法是在线程内对对象进行缓存,但这样进程间就无法共享缓存,命中率非常低,导致缓存效率极低。后来出现了共享内存的缓存,多个进程或者线程共享同一块缓存,但毕竟还是只能局限在一台机器上,多台机器做相同的缓存同样是一种资源的浪费,而且命中率也比较低。
Memcached Server和Clients共同工作,实现跨服务器分布式的全局的缓存。并且可以与Web Server共同工作,Web Server对CPU要求高,对内存要求低,Memcached Server对CPU要求低,对内存要求高,所以可以搭配使用。
Mysql 4.x的缓存怎么样?
Mysql查询缓存不是很理想,因为以下几点:
当指定的表发生更新后,查询缓存会被清空。在一个大负载的系统上这样的事情发生的非常频繁,导致查询缓存效率非常低,有的情况下甚至还不如不开,因为它对cache的管理还是会有开销。
在32位机器上,Mysql对内存的操作还是被限制在4G以内,但memcached可以分布开,内存规模理论上不受限制。
Mysql上的是查询缓存,而不是对象缓存,如果在查询后还需要大量其它操作,查询缓存就帮不上忙了。
如果要缓存的数据不大,并且查询的不是非常频繁,这样的情况下可以用Mysql 查询缓存,不然的话memcached更好。
数据库同步怎么样?
这里的数据库同步是指的类似Mysql Master-Slave模式的靠日志同步实现数据库同步的机制。
你可以分布读操作,但无法分布写操作,但写操作的同步需要消耗大量的资源,而且这个开销是随着slave服务器的增长而不断增长的。
下一步是要对数据库进行水平切分,从而让不同的数据分布到不同的数据库服务器组上,从而实现分布的读写,这需要在应用中实现根据不同的数据连接不同的数据库。
当这一模式工作后(我们也推荐这样做),更多的数据库导致更多的让人头疼的硬件错误。
Memcached可以有效的降低对数据库的访问,让数据库用主要的精力来做不频繁的写操作,而这是数据库自己控制的,很少会自己阻塞 自己。
Memcached快吗?
非常快,它使用libevent,可以应付任意数量打开的连接(使用epoll,而非poll),使用非阻塞网络IO,分布式散列对象到不同的服务器,查询复杂度是O(1)。
参考资料:
Distributed Caching with Memcached | Linux Journal
http://www.danga.com/
http://www.linuxjournal.com/article/7451
转载自:http://www.example.net.cn/archives/2006/01/eoamemcachedoea.html
Posted: November 5, 2006 at 6:57 pm | Tags: class, html, 优化, 开发, 技术, 类, 缓存
Andrei Alexandrescu
想象本篇你正要读的“泛型<编程>”部分的开头是:“本文关于怎样用C++处理内存缓冲”。
当你轻率地关掉浏览器时,如果竖起耳朵,你还会听到成千上万的鼠标在做和你一样的事情。因为谁会对处理内存缓冲这样的小事感兴趣呢?
但本文确实关于怎样在C++中处理内存缓冲,但这里有两个特殊之处。首先,缓冲是泛型的,这意味着缓冲里面能放置类型化数据(相反于原始字节)。第二,我们要讨论的缓存的效率和存放在里面的类型及所在操作系统容许的最高效率相当,这种效率是指在任何方面的效率——包括分配策略,和数据操作。
你会很快看到,写一个泛型缓存是个关于模板的小小练习。写一个高效的的缓存也不是一个复杂的任务。写一个意外安全的缓存难一些,但还不是很难,特别在你读过关于意外安全的书[1]之后。但写一个同时具备泛型特征和高效率的缓存就象爬一个险峰。就象在C++中常常发生的,你在山顶欣赏到的景色使你的努力获得足够回报——只要你能克服路上遇到困难。
真空地带
有些人总会时不时在Usenet新闻组comp.lang.c++.moderated中张贴这样的问题:为什么autp_ptr不能给你在析构函数中使用delete[]而不是delete的机会?接下去的讨论通常会这样:
“你不应该使用C风格的数组而应该用std::vector,它高效又强大”
“但std::vector在我要用到的地方不够高效。”
“为什么?”等等
事实是,std::vector确实是个用来处理连续对象序列的设计精良的类。当我首次看到std::vector的优良设计时,我根本没有把它扔掉而自己来写一个的需要(当我看到,比如说,某些MFC的东东时我感觉到了这种冲动)然而。std::vector是有些不够高效之处,而这可能会严重影响到某些使用者。
* 不必要的数据初始化。通常情况,你需要创建一个大小合适的vector,带有基本类型(比如char)并把它传递给低层C函数来用一个socket或文件中读取的数据来填充它。问题在于,尽管你不需要,整个vector都会通过vector的构造函数或resize函数初始化。如果你必须和C函数打夹道,你就没办法避免这个开销。
* 指数增长。标准vector规则要求快速的(平均为常量时间)的增长策略。这就要求std::vector以乘法来增长——大多数vector实现当需要自动增长时分配倍增的内存。比如如果你要在一个包含一百万对象的vector后面添加一个对象,这个vector为了将来短时间内不再增长会分配能容纳三百万对象的空间,但这可能不是你想要的。
* “强制性”内存分配策略。标准vector永远不会收缩而只会增长。如果你集中了三百万数据样本,然后你觉得做插值计算的话只需要保留一百万数据样本,你就会有能存放二百万对象的空闲空间。处理这种情况建议使用的常用手段是把你的vector拷到一个新建的vector中然后互换这两个vector
std::vector<double> data;
…处理数据…
//收缩数据
std::vector<double>(data.begin(), data.end()).swap(data);
这个常用法不是很优化,因为你必须拷贝里面的数据。更糟的是,就是这样一个常用法也不能保证对内存百分之百的利用率。因为std::vector会总是分配比需要多的内存。如果你能够“就地”收缩vector会更好,也就是说,这需要告诉内存分配器它可以使用你的vector尾端之后的内存。许多内存分配器允许你这样做。
* 低效的对象移动。std::vector对拷贝和移动做无差别处理。对一个std::vector来说,一个移动操作就是一个拷贝操作后面跟一个对源对象的析构操作。所以如果你有一个保存字符串的vector并且为它重新分配,你没办法写一个函数来快速地移动几个指针(译注:此处指针指的是比如指向所管理内存的开始和结尾等这些地方的指针)到新的内存来使std::vector使用它。每个单个的字符串都毫无必要地拷贝到一个新的字符串。当被移动对象不是CRC[2]成员,那这将会是个低效率的巨大来源。这也是常常说到的怎样的std::vector<std::vector<T> >会被禁止的原因。
* 数据拷贝和移动中更多的低效之处。一些STL的实现不能区分包含旧数据类型的vector和包含用户自定类型的vector。(Metrowerks是个例外)这意味着它们使用更通用的结构来拷贝数据,比如for循环。你有权认为一个好的编译器应该把拷贝一系列整数的loop替换为更高效的memcpy调用。传说中两个能产生高效代码的编译器,Microsoft Visual C++和Metrowerks CodeWarrior,在完全速度优化方式下,使用loop来拷贝整型数据产生的代码比对应的memcpy调用明显要慢。所以你要执行效率,你需要在你自己的代码中调用memcpy。
* 不必要的意外安全。许多std::vector实现(但不是全部)假设所存类型的构造函数,拷贝构造函数,和赋值操作符重载函数可能抛出意外。但这对基本类型和其他许多类型都不会发生。所以这些实现对这些类型产生的代码可能会不必要的更大和更慢。
在许多应用中,这些问题的部分或全部根本没什么影响。但当你有苛刻的性能要求时
std::vector就不那么吸引人了。你会转而寻找一个更优化也给你更多控制权的结构。问题是你无法在C++标准中的std::vector下层找到任何容器——除了C风格数组。你要么开卡迪拉克,要么骑黄鱼车。
要证据?证据就在你面前:STL在至少三处使用连续内存缓冲(除了std::vector本身以外):std::basic_string,std::deque的内存块管理部分和std::deque的内存块本身。然而。我没看到有STL实现使用vector作为这些结构的后端。如果说std::vector是你应用中实现连续内存唯一能用的工具,那就是说STL实现有其他特别工具来作为它们的后端。
有一个空隙,在std::vector和C风格数组中有一个真空地带。我们定位于这个位置。我们会开发一个包含下列特性的连续内存缓冲:
* 泛型化的—可以存放任意类型序列
* 亲和的—支持std::vector语法和语义的子集
* 提供完全控制——提供细粒度的基本操作加上高层次的函数
* 产生的代码对基本类型和选定的用户定义类型尽最大可能优化
* 允许用户控制的优化
* 支持高级内存分配功能,例如就地扩展和快速重分配
* 最最重要的,能够被用做更高层连续内存容器的后端——特别是,你能用它实现vector,string或者deque.
buffer:第一线曙光
我们来为buffer模类定义一个接口。buffer只保存两个指针——所控制内存块的头和尾。对一个buffer来说,尺寸和容积(size and capacity)没有区别。我们就从std::vector开始,下手去除和容积有关的函数,我们剩下下列成员函数集:
template <class T, class Allocater = std::allocator<T> >
class buffer
{
public:
….所有std::vector的类型定义和函数,除了:
//size_type capacity() const;
//void reserve(size_type n);
private:
T* beg_;
T* end_;
};
有趣的是,尽管buffer没有容积概念,但它能够实现std::vector的所有功能,除了capacity和reserve,还有buffer不能满足所有这些函数的性能要求。比如说,buffer<T>::push_back有O(N)的时间复杂度,但std::vector<T>::push_back综合于大量的调用时有O(1)的时间复杂度。(这是标准所说的“摊分常量时间(amortized constant)”)你看到后面就知道能够在某些情况下提高buffer<T>::push_back的性能而仍旧不必支持容积概念。
实现buffer的接口并不很复杂,两个地方需要注意:意外安全和正确地摧毁对象。标准函数std::uninitialized_copy和std::uninitialized_fill是两个很有用的工具。
为了允许用户分配一块缓存而不初始化它,我们需要一个特别的构造函数和几个辅助函数。然后,我们需要一个grow_noinit工具来扩展缓存而不调用构造函数。对应的,我们需要shrink_nodestroy函数来收缩缓存而不调用析构函数,最后,需要一个稍微有些多余的函数clear_nodestroy,它清空缓存,回收内存而不调用析构函数。
template <class T, class Allocator = std::allocator<T> >
class buffer
{
….同上….
public:
enum noinit_t { noinit };
buffer(size_type n, noinit_t,
const Allocator& a = Allocator());
void grow_noinit (size_type growBy);
void shrink_nodestroy(size_type shrinkBy);
void clear_nodestroy();
};
这个扩展的接口给了你对缓存储存在内数据的完全控制。但是注意不要不假思索地直接使用这些扩展函数。比如说你象下面那样使用grow_noinit:
void Fun()
{
buffer<Widget> someWidgets;
….
//加10个Widget的空间
someWidgets.grow_noinit(10);
//初始化这些Widget
ConstructWidgets(
SomeWidgets.end() – 10, someWidgets.end());
….
};
这里的问题在于如果10个Widget的构造函数如果以任意形式失败,所有事情都会是一团糟。当Fun返回,someWidgets的析构函数会摧毁它里面包含对象——它不会知道哪些Widget构造成功哪些没有,这也是因为buffer不象std::vector有容积的概念,如果还有buffer内还有没初始化的内存,对那些内存使用Widget的析构函数很明显会导致未定义后果。
类型特性(Type Traits)
一项优化泛型代码的关键技术是获取泛型代码操作的类型的信息。这样,泛型代码能够在编译时分配工作给指定的代码,这些代码都对专门类型执行操作。
比如在我们的例子中,一个很重要的信息是buffer内的类型的拷贝构造函数是否抛出意外。对一个拷贝时不抛意外的类型,代码就会因不用处理意外安全而变得简单。此外,一些编译器如没有用try块会产生更好的代码。
类型特性是一个用来推导类型信息的有名技术。Boost[4]有个库实现类型特性,Loki[5]也有(在buffer中,我们会用到的类型特性机制会和Boost和Loki的都稍有不同,这是由Kavonen通过电邮建议我的)。
我们来看怎样推导一个类型的拷贝函数是否抛意外。坦白地说,C++中不可能知道所有类型的拷贝函数是否抛意外。但是,我们做一些工作并让用户当他们觉得需要优化时来帮我们做到。
你不必成为歇洛克?福尔摩斯就能够知道任何一个基本类型的拷贝构造函数不会抛出意外。这样,一个保守的假设是任何除了基本类型的类型的拷贝构造函数可能抛出意外。下面是对应代码:
namespace TypeTraits
{
//列举所有基本类型
typedef TYPELIST_14(
const bool,
const char,
const signed char,
const unsigned char,
const wchar_t,
const short int,
const unsigned short int,
const int,
const unsigned int,
const long int,
const unsigned long int,
const float,
const double,
const long double)
PrimitiveTypes;
template <typename T> struct IsPrimitive
{
enum { value =
Loki::TL::IndexOf<PrimitiveTypes,
Const T<::value >= 0};
};
template <typename T> struct IsPrimitive<T*>
{
enum {value = true };
};
template <typename T> struct CopyThrows
{
enum {value = !IsPrimitive<T>::value };
};
}
为了简明,上面的代码使用了Loki提供的两个工具:typelists和indexOf。typellists让你创建和操作类型串,Loki::TL::IndexOf在类型串中查找一个单独类型并且返回它在其中的索引。如果类型串中没有找到这个类型,返回的索引为负。最终,TypeTraits::CopyThrow<T>::value包含了所需信息。
通过这个机制来获取类型信息非常非常灵活,假设你在一个应用程序里定义了下列类型:
struct Point
{
int x;
int y;
….操作函数….
};
上面这个Point不是基本类型,但它拷贝时也不会抛出意外。你可以用类型特性这个机制来告知这个信息。你所要做的就是再次打开TypeTraits命名空间然后在里面放入一个CopyThrows的显式实例化(explicit instantiation)版本
//在file"Pint.h"文件中,Point的定义后
namespace TypeTraits
{
template <> struct CopyThrows<Point>
{
enum {value = false };
};
}
更好的是,你可以为一整个类型种类定制CopyThrows,这通过部分模板特化(partial template specialization)来实现,考虑一下标准复数类型,std::complex<T>,你可以用基本算术类型实例化std::complex,但也可以用用户定义算术类型,比如Rational或BrigInt。现在,因为拷贝一个std::complex<T>对象包括拷贝两个T对象(实数部分和虚数部分),由此可知std::complex<T>有着和T一样的CopyThrows特性。你能够通过下面代码表示这一点:
namespace TypeTraits
{
template <class T> struct CopyThrows<std::complex<T> >
{
enum { value = CopyThrows<T>::value };
};
}
我们回到buffer。buffer如何使用CopyThrows的信息?通过使用Int2Type模板类[5][6]在编译时分派一个布尔值是非常简单的。回忆一下,Int2Type的定义有着简单的表象。
Template <int v>
Struct Int2Type
{
enum { value = v };
};
下面是buffer的构造函数怎样使用Int2Type来分派调用一个意外安全或意外无关的初始化函数的例子:
template <class T, class Allocator>
std::allocator<T> >
class buffer : private Allocator
{
private:
enum { copyThrow =
TypeTraits::CopyThrows<T>::value != 0 };
//无意外初始化
void Init(size_type n, const T& value,
Loki::Int2Type<false>)
{
….
}
//有可能出现意外的初始化
void Init(size_type n, const T& value,
Loki::Int2Type<true>)
{
….
}
public
explicit buffer(size_type m,
const T& value = T(),
const Allocator& a = Allocator())
{
Init(n, value,
Loki::Int2Type(copyThrows>());
}
};
其它buffer可能会需要的信息包括:
* 类型是否为MemCopyable,就是说,拷贝一个对象和memcpy这个对象的字节结果一样。显然,基本类型和POD结构(简单旧数据,C风格)属于这种情况。
* 类型是否为MemMoveable,就是说,在内存中从一个地方拷贝一个对象到另一个地方并且摧毁源对象,结果和memcpy对象的字节而且没有摧毁源对象一致。再一次的,基本类型和POD属于这种情况。但是,你会很快看到,有相当多的用户定义类型是MemMoveable的。
“ 下一部分的“泛型<编程>”会定义MemCopyable和MemMoveable并且用和CopyThrows类似的方式使用它们。关于MemCopyable和MemMoveable是否就此结束了?根本不是。它们和供下载代码中的reallocate和inplace_reallocate一样,都意味着我们面临内存分配的挑战。到那时——会有详尽说明!
鸣谢:
对Tim Sharrock的详尽检查非常感谢。
参考数目和注释
[1] Herb Sutter. Exceptional C++ (Addison-Wesley, 2000).
[2] COW 爱好者俱乐部的缩写. COW 是写时拷贝的缩写. COW是可以使std::vector所用的用拷贝实现移动原则开销较小的策略。但是,许多库的作者正在去掉基于COW的实现,因为这在多线程程序中会引起问题。
[3] 总的来说, buffer必须和vector有同样级别的意外。参看Bjarne Stroustrup, Appendix E: Standard-Library Exception Safety 在 <www.research.att.com/~bs/3rd_safe0.html>.
[4] Boost 是尖端的C++库集, 参看 <www.boost.org>.
[5] Loki 是Modern C++ Design (Addison-Wesley, 2001)一书附带库,实际上是你们大家参与一起写成的.你能从 <www.moderncppdesign.com>下载下载Loki.
[6] Andrei Alexandrescu. "Generic<Programming>: Mappings between Types and Values," C/C++ Users Journal C++ Experts Forum, October 2000, <www.cuj.com/experts/1810/alexandr.htm>.
Andrei Alexandrescu 是位于西雅图的华盛顿大学的博士生,也是受到好评的《Modern C++ Design》一书的作者。可以通过www.moderncppdesign.com. 来联系他。Andrei同时也是C++研讨会 (<www.gotw.ca/cpp_seminar>).的一名有号召力的讲师。
你可以从CUJ网站或http://merced.go.nease.net/code/buffer.zip获得本文源代码。
Posted: October 26, 2006 at 5:34 pm | Tags: cache, html, java, linux, php, server, web, 优化, 开发, 技术, 类, 缓存
一个小型的网站,比如个人网站,可以使用最简单的html静态页面就实现了,配合一些图片达到美化效果,所有的页面均存放在一个目录下,这样的网站对系统架构、性能的要求都很简单,随着互联网业务的不断丰富,网站相关的技术经过这些年的发展,已经细分到很细的方方面面,尤其对于大型网站来说,所采用的技术更是涉及面非常广,从硬件到软件、编程语言、数据库、WebServer、防火墙等各个领域都有了很高的要求,已经不是原来简单的html静态网站所能比拟的。
大型网站,比如门户网站。在面对大量用户访问、高并发请求方面,基本的解决方案集中在这样几个环节:使用高性能的服务器、高性能的数据库、高效率的编程语言、还有高性能的Web容器。但是除了这几个方面,还没法根本解决大型网站面临的高负载和高并发问题。
上面提供的几个解决思路在一定程度上也意味着更大的投入,并且这样的解决思路具备瓶颈,没有很好的扩展性,下面我从低成本、高性能和高扩张性的角度来说说我的一些经验。
1、HTML静态化
其实大家都知道,效率最高、消耗最小的就是纯静态化的html页面,所以我们尽可能使我们的网站上的页面采用静态页面来实现,这个最简单的方法其实也是最有效的方法。但是对于大量内容并且频繁更新的网站,我们无法全部手动去挨个实现,于是出现了我们常见的信息发布系统CMS,像我们常访问的各个门户站点的新闻频道,甚至他们的其他频道,都是通过信息发布系统来管理和实现的,信息发布系统可以实现最简单的信息录入自动生成静态页面,还能具备频道管理、权限管理、自动抓取等功能,对于一个大型网站来说,拥有一套高效、可管理的CMS是必不可少的。
除了门户和信息发布类型的网站,对于交互性要求很高的社区类型网站来说,尽可能的静态化也是提高性能的必要手段,将社区内的帖子、文章进行实时的静态化,有更新的时候再重新静态化也是大量使用的策略,像Mop的大杂烩就是使用了这样的策略,网易社区等也是如此。
同时,html静态化也是某些缓存策略使用的手段,对于系统中频繁使用数据库查询但是内容更新很小的应用,可以考虑使用html静态化来实现,比如论坛中论坛的公用设置信息,这些信息目前的主流论坛都可以进行后台管理并且存储再数据库中,这些信息其实大量被前台程序调用,但是更新频率很小,可以考虑将这部分内容进行后台更新的时候进行静态化,这样避免了大量的数据库访问请求。
2、图片服务器分离
大家知道,对于Web服务器来说,不管是Apache、IIS还是其他容器,图片是最消耗资源的,于是我们有必要将图片与页面进行分离,这是基本上大型网站都会采用的策略,他们都有独立的图片服务器,甚至很多台图片服务器。这样的架构可以降低提供页面访问请求的服务器系统压力,并且可以保证系统不会因为图片问题而崩溃,在应用服务器和图片服务器上,可以进行不同的配置优化,比如apache在配置ContentType的时候可以尽量少支持,尽可能少的LoadModule,保证更高的系统消耗和执行效率。
3、数据库集群和库表散列
大型网站都有复杂的应用,这些应用必须使用数据库,那么在面对大量访问的时候,数据库的瓶颈很快就能显现出来,这时一台数据库将很快无法满足应用,于是我们需要使用数据库集群或者库表散列。
在数据库集群方面,很多数据库都有自己的解决方案,Oracle、Sybase等都有很好的方案,常用的MySQL提供的Master/Slave也是类似的方案,您使用了什么样的DB,就参考相应的解决方案来实施即可。
上面提到的数据库集群由于在架构、成本、扩张性方面都会受到所采用DB类型的限制,于是我们需要从应用程序的角度来考虑改善系统架构,库表散列是常用并且最有效的解决方案。我们在应用程序中安装业务和应用或者功能模块将数据库进行分离,不同的模块对应不同的数据库或者表,再按照一定的策略对某个页面或者功能进行更小的数据库散列,比如用户表,按照用户ID进行表散列,这样就能够低成本的提升系统的性能并且有很好的扩展性。sohu的论坛就是采用了这样的架构,将论坛的用户、设置、帖子等信息进行数据库分离,然后对帖子、用户按照板块和ID进行散列数据库和表,最终可以在配置文件中进行简单的配置便能让系统随时增加一台低成本的数据库进来补充系统性能。
4、缓存
缓存一词搞技术的都接触过,很多地方用到缓存。网站架构和网站开发中的缓存也是非常重要。这里先讲述最基本的两种缓存。高级和分布式的缓存在后面讲述。
架构方面的缓存,对Apache比较熟悉的人都能知道Apache提供了自己的缓存模块,也可以使用外加的Squid模块进行缓存,这两种方式均可以有效的提高Apache的访问响应能力。
网站程序开发方面的缓存,Linux上提供的Memory Cache是常用的缓存接口,可以在web开发中使用,比如用Java开发的时候就可以调用MemoryCache对一些数据进行缓存和通讯共享,一些大型社区使用了这样的架构。另外,在使用web语言开发的时候,各种语言基本都有自己的缓存模块和方法,PHP有Pear的Cache模块,Java就更多了,.net不是很熟悉,相信也肯定有。
5、镜像
镜像是大型网站常采用的提高性能和数据安全性的方式,镜像的技术可以解决不同网络接入商和地域带来的用户访问速度差异,比如ChinaNet和EduNet之间的差异就促使了很多网站在教育网内搭建镜像站点,数据进行定时更新或者实时更新。在镜像的细节技术方面,这里不阐述太深,有很多专业的现成的解决架构和产品可选。也有廉价的通过软件实现的思路,比如Linux上的rsync等工具。
6、负载均衡
负载均衡将是大型网站解决高负荷访问和大量并发请求采用的终极解决办法。
负载均衡技术发展了多年,有很多专业的服务提供商和产品可以选择,我个人接触过一些解决方法,其中有两个架构可以给大家做参考。
硬件四层交换
第四层交换使用第三层和第四层信息包的报头信息,根据应用区间识别业务流,将整个区间段的业务流分配到合适的应用服务器进行处理。 第四层交换功能就象是虚IP,指向物理服务器。它传输的业务服从的协议多种多样,有HTTP、FTP、NFS、Telnet或其他协议。这些业务在物理服务器基础上,需要复杂的载量平衡算法。在IP世界,业务类型由终端TCP或UDP端口地址来决定,在第四层交换中的应用区间则由源端和终端IP地址、TCP和UDP端口共同决定。
在硬件四层交换产品领域,有一些知名的产品可以选择,比如Alteon、F5等,这些产品很昂贵,但是物有所值,能够提供非常优秀的性能和很灵活的管理能力。Yahoo中国当初接近2000台服务器使用了三四台Alteon就搞定了。
软件四层交换
大家知道了硬件四层交换机的原理后,基于OSI模型来实现的软件四层交换也就应运而生,这样的解决方案实现的原理一致,不过性能稍差。但是满足一定量的压力还是游刃有余的,有人说软件实现方式其实更灵活,处理能力完全看你配置的熟悉能力。
软件四层交换我们可以使用Linux上常用的LVS来解决,LVS就是Linux Virtual Server,他提供了基于心跳线heartbeat的实时灾难应对解决方案,提高系统的鲁棒性,同时可供了灵活的虚拟VIP配置和管理功能,可以同时满足多种应用需求,这对于分布式的系统来说必不可少。
一个典型的使用负载均衡的策略就是,在软件或者硬件四层交换的基础上搭建squid集群,这种思路在很多大型网站包括搜索引擎上被采用,这样的架构低成本、高性能还有很强的扩张性,随时往架构里面增减节点都非常容易。这样的架构我准备空了专门详细整理一下和大家探讨。
对于大型网站来说,前面提到的每个方法可能都会被同时使用到,我这里介绍得比较浅显,具体实现过程中很多细节还需要大家慢慢熟悉和体会,有时一个很小的squid参数或者apache参数设置,对于系统性能的影响就会很大,希望大家一起讨论,达到抛砖引玉之效。
Posted: October 26, 2006 at 4:10 pm | Tags: cache, html, linux, server, unix, windows, 优化, 开发, 技术, 类, 缓存
应用加速(Application Acceleration)是当前信息产业中流行的一个新的时髦词语,但它到底是什么含义呢?
缓慢的应用软件响应时间可能会让用户感到失望并且失去生产力,从而对公司的核心实力造成影响,所以,应用软件加速成为信息产业中的一个新的时髦词语也就不足为奇了,但 它到底是什么含义呢?您是否需要它呢?如果需要的话,那么最具扩展性的实现方式是什么呢?让我们来看看不同的应用软件加速技术以及如何创建应用软件加速策略来与您的公 司一同成长。
本地VS网络应用软件
在过去,大部分应用软件都是安装并运行在本地计算机,现在的小型公司也通常是这样。对于本地应用软件而言,性能主要取决于本地系统资源,它受到处理器速度、处理器数量 、物理和虚拟内存数量和共享这些资源的其他应用软件(多任务)的影响。
对于这种类型的系统环境,提高应用软件性能是在单独的机器上通过升级硬件、关闭消耗宝贵资源的视觉效果,禁用不必要的与您的应用软件争夺处理器时间和内存的后台服务, 使用预取(prefetching)技术来提高应用软件的加载时间,定期整理磁盘碎片,优化各种设置比如设定进程的优先级、调整与性能相关的注册表条目等等。
然而,在当今的大中型企业中,很多应用软件都是通过局域网、城域网或互联网传递的,这对原有的方法带来了新的问题,同时也为网络带宽和网络协议带来了新的问题。
应用软件转移到了互联网上
越来越多的应用软件正在通过互联网来传递,从便利性的角度来看,这很有意义,因为几乎所有与互联网相连的计算机都有网络浏览器,这就排除了在用户系统上安装任何软件的 需要,HTTP是最普遍的网络协议中的一个,这种应用软件的“互联网化”为用户创建了一个通用界面和环境,同时也是系统管理员的通用传递系统。
在基于网络的应用软件加速的问题上,一个关键因素是使用一个逆向代理(reverse proxy)来缓存静态对象,我们在这个栏目里所讨论的缓冲方案在以往更多地被应用于正向缓存 ,这是一种加速内部用户访问互联网内容的方法:让代理服务器在本地存储从互联网站点上下载的对象。
逆向代理,正如名字所暗示的,是反方向工作的,逆向代理服务器存储了在您的内部服务器上被访问过的对象,然后将它们返回给请求者,这样请求者不必到服务器上去检索这些 数据,对于外部用户而言就意味着性能的提高。
扩展一个逆向代理的解决方案
互联网应用软件加速经常是在企业环境下讨论的,但即使是小型公司也可以使用逆向服务器来加速基于互联网的应用软件,您不必花费很多钱来实现这一点,免费的和廉价的逆向 代理软件包括:
Squid:一个基于GPL许可的开源的代理服务器,它可以从ftp://ftp.squid-cache.org/pub/上免费下载,它 可以在Linux、FreeBSD、Mac OS X和很多版本的UNIX上运行,您可以使用Cygwin开发环境(Cygwin development environment)在Windows上编译和运行Squid。
IIS逆向代理:一个用于互联网信息服务(IIS)的开源逆向代理软件,它是基于MIT许可的,可以安装在运行IIS的Windows2000、XP或Windows Server2003上,您可以在http://www.saltypickle.com/Home/16上下载到这一软件。
Orenosp安全逆向代理:一个逆向代理的共享软件,还具有平衡负载和安全端口的转发功能,它可以运行在Windows NT、2000、XP和2003,以及Linux和Mac OS X上,如果需要 更多的信息可以参见http://hp.vector.co.jp/authors/VA027031/orenosp/index_en.html
通常,随着公司的发展,您将会需要对互联网应用软件进行加速,功能不复杂的逆向代理服务器会缓存所有的数据,而高级一些的会允许您像外科手术般地来操作哪些数据需要缓 存,哪些不需要。您可能还希望考虑可扩展性的协议支持:您需要缓存的可能不仅仅是HTTP对象。
从免费的和低廉的解决方案中提升一步,您可以发现一些适合大中型公司需求的中等价位的产品,例如:
微软的ISA服务器:ISA可以作为正向的或逆向代理服务器和应用层过滤防火墙,所以您可以应用很多相关的功能。标准版可以运行在Windows 2000 Server或Windows Server 2003上,每个处理器的价格为1499美元,企业版可以部署在服务器阵列上,因而具备更强的灵活性与可扩展性,每个处理器价格为5999美元。需要更多的信息,请点击此处。
Vigos AG网站加速器:对于仅仅需要一个逆向代理而不需要ISA的附加功能的用户而言,这是一个提高他们网络站点性能的选择。Vigos还可以实时压缩发送的数据,“简易” 版(支持单一域)的价格是499美元,标准版(支持最多十个域)的价格是999美元,企业版(支持无限个域)的价格为1999美元。需要更多的信息,请点击此处。在企业级,逆向代理允许您对多个前端互联网服务器实施逆向代理方案,并提供SSL的安全连接保障。为企业级市场设计的高端逆向代理解决方案需要花费30000美元或更多,一些 可选方案包括:
Blue Coat Proxy SG:作为一套配置齐全立即可用的设备,Proxy SG有很多产品系列(400, 800和8000)以满足不通的预算和需求,您可以对上游的互联网服务器实施正向缓存 ,甚至对上游的服务器进行状态检查,高端的8000系列可以支持超过50000接入用户并支持最高300Mbps的城域网吞吐量,型号为8000-4的价格为99000美元,需要更多的信息,请点击此处。
思科内容引擎(Content Engine):这是Cisco的缓存与内容过滤设备,可以部署在您的互联网站点之前来缓存或分流请求,以及卸载互联网服务器的数据流量,以此来增加网 络应用软件的性能。内容引擎7325的价格在40000美元到50000之间。
Posted: August 23, 2006 at 1:43 pm | Tags: class, html, java, linux, unix, web, 优化, 平台, 开发, 技术, 测试, 类, 缓存
一、ACE综述
ACE自适配通信环境(ADAPTIVE Communication Environment)是可以自由使用、开放源码的面向对象(OO)框架(Framework),在其中实现了许多用于并发通信软件的核心模式。ACE提供了一组丰富的可复用C++ Wrapper Facade(包装外观)和框架组件,可跨越多种平台完成通用的通信软件任务,其中包括:事件多路分离和事件处理器分派、信号处理、服务初始化、进程间通信、共享内存管理、消息路由、分布式服务动态(重)配置、并发执行和同步,等等。
ACE的目标用户是高性能和实时通信服务和应用的开发者。它简化了使用进程间通信、事件多路分离、显式动态链接和并发的OO网络应用和服务的开发。此外,通过服务在运行时与应用的动态链接,ACE还使系统的配置和重配置得以自动化。
ACE正在进行持续的改进。Riverace公司(http://www.riverace.com)采用开放源码商业模式对ACE进行商业支持。此外,ACE开发组的许多成员目前正在进行The ACE ORB(TAO,http://www.cs.wustl.edu/~schmidt/TAO.html)的开发工作。
二、使用ACE的好处
使用ACE的好处有:
l 增强可移植性:在ACE组件的帮助下,很容易在一种OS平台上编写并发网络应用,然后快速地将它们移植到各种其他的OS平台上。而且,因为ACE是开放源码的自由软件,你无需担心被锁定在特定的操作系统平台或编译器上。
l 更好的软件质量:ACE的设计使用了许多可提高软件质量的关键模式,这些质量因素包括通信软件灵活性、可扩展性、可复用性和模块性。
l 更高的效率和可预测性:ACE经仔细设计,支持广泛的应用服务质量(QoS)需求,包括延迟敏感应用的低响应等待时间、高带宽应用的高性能,以及实时应用的可预测性。
l 更容易转换到标准的高级中间件:TAO使用了ACE提供的可复用组件和模式。它是CORBA的开发源码、遵循标准的实现,并为高性能和实时系统作了优化。为此,ACE和TAO被设计为能良好地协同工作,以提供全面的中间件解决方案。
三、ACE的结构和功能
下图显示了ACE中的关键组件以及它们的层次关系:

图中的结构和各层的组成部分描述如下。
四、ACE OS适配层
该层直接位于用C写成的本地OS API之上。它提供轻型的类POSIX OS适配层,将ACE中的其他层及组件和以下与OS API相关联的平台专有特性屏蔽开来:
l 并发和同步:ACE的适配层封装了用于多线程、多进程和同步的OS API。
l 进程间通信(IPC)和共享内存:ACE的适配层封装了用于本地和远地IPC、以及共享内存的OS API。
l 事件多路分离机制:ACE的适配层封装了用于对基于I/O、定时器、信号和同步的事件进行同步和异步多路分离的OS API。
l 显式动态链接:ACE的适配层封装了用于显式动态链接的OS API。显式动态链接允许在安装时或运行时对应用服务进行配置。
l 文件系统机制:ACE的适配层封装了用于操作文件和目录的OS文件系统API。
ACE OS适配层的可移植性使得ACE可运行在许多操作系统上。ACE已在广泛的OS平台上进行了移植和测试,包括Win32(也就是,在Intel和Alpha平台,使用MSVC++、Borland C++ Builder和IBM Visual Age的WinNT 3.5.x、4.x、2000、Win95/98和WinCE)、Mac OS X、大多数版本的UNIX(例如,SPARC和Intel上的Solaris 1.x和2.x、SGI IRIX 5.x和6.x、DG/UX、HP-UX 9.x、10.x和11.x、DEC/Compaq UNIX 3.x和4.x、AIX 3.x和4.x、UnixWare、SCO,以及可自由使用的UNIX实现,比如Debian Linux 2.x、RedHat Linux 5.2、6.x和7.x、FreeBSD和NetBSD)、实时操作系统(比如,LynxOS、VxWorks、Chorus ClassiX 4.0、QnX Neutrino、RTEMS和PSoS)、MVS OpenEdition和CRAY UNICOS。
由于ACE的OS适配层所提供的抽象,所有这些平台使用同一棵代码树。这样的设计极大地增强了ACE的可移植性和可维护性。此外,还有Java版本的ACE可用(http://www.cs.wustl.edu/~eea1/JACE.html)。
五、OS接口的C++ Wrapper Facade
可以直接在ACE OS适配层之上编写高度可移植的C++应用。但是,大多数ACE开发者使用的是上图中所示的C++ Wrapper Facade层。通过提供类型安全的C++接口(这些接口封装并增强本地的OS并发、通信、内存管理、事件多路分离、动态链接和文件系统API),ACE Wrapper Facade简化了应用的开发。应用可以通过有选择地继承、聚合和/或实例化下面的组件来组合和使用这些包装:
l 并发和同步组件:ACE对像互斥体和信号量这样的本地OS多线程和多进程机制进行抽象,以创建高级的OO并发抽象,像主动对象(Active Object)和多态期货(Polymorphic Future)。
l IPC和文件系统组件:ACE C++包装对本地和/或远地IPC机制进行封装,比如socket、TLI、UNIX FIFO和STREAM管道,以及Win32命名管道。此外,ACE C++包装还封装了OS文件系统API。
l 内存管理组件:ACE内存管理组件为管理进程间共享内存和进程内堆内存的动态分配和释放提供了灵活和可扩展的抽象。
ACE C++包装提供了许多与ACE OS适配层一样的特性。但是,这些特性是采用C++类和对象、而不是独立的C函数来构造的。这样的OO包装有助于减少正确地学习和使用ACE所需的努力。
例如,C++的使用提高了应用的健壮性,因为C++包装是强类型的。所以,编译器可在编译时、而不是运行时检测类型系统违例。相反,不到运行时,不可能检测像socket或文件系统I/O这样的C一级OS API的类型系统违例。
ACE采用了许多技术来降低或消除额外的性能开销。例如,ACE大量地使用C++内联来消除额外的方法调用开销;这样的开销可由OS适配层和C++包装所提供的额外的类型安全和抽象层次带来。此外,对于性能要求很高的包装,比如socket和文件I/O的send/recv方法,ACE会避免使用虚函数。
六、框架
ACE还含有一个高级的网络编程框架,集成并增强了较低层次的C++ Wrapper Facade。该框架支持将并发分布式服务动态配置进应用。ACE的框架部分包含以下组件:
l 事件多路分离组件:ACE Reactor(反应器)和Proactor(前摄器)是可扩展的面向对象多路分离器,它们分派应用特有的处理器,以响应多种类型的基于I/O、定时器、信号和同步的事件。
l 服务初始化组件:ACE Acceptor(接受器)和Connector(连接器)组件分别使主动和被动的初始化任务与初始化一旦完成后通信服务所执行的应用特有的任务去耦合。
l 服务配置组件:ACE Service Configurator(服务配置器)支持应用的配置,这些应用的服务可在安装时和/或运行时动态装配。
l 分层的流组件:ACE Stream组件简化了像用户级协议栈这样的由分层服务组成的通信软件应用的开发。
l ORB适配器组件:通过ORB适配器,ACE可以与单线程和多线程CORBA实现进行无缝集成。
ACE框架组件便利了通信软件的开发,它们无需修改、重编译、重链接,或频繁地重启运行中的应用,就可被更新和扩展。在ACE中,这样的灵活性是通过结合以下要素来获得的:(1)C++语言特性,比如模板、继承和动态绑定,(2)设计模式,比如抽象工厂、策略和服务配置器,以及(3)OS机制,比如显式动态链接和多线程。
七、分布式服务和组件
除了OS适配层、C++ Wrapper Facade和框架组件,ACE还提供了包装成自包含组件的标准分布式服务库。尽管这些服务组件并不是ACE框架库的严格组成部分,它们在ACE中扮演了两种角色:
1. 分解出可复用分布式应用的“积木”:这些服务组件提供通用的分布式应用任务的可复用实现,比如名字服务、事件路由、日志、时间同步和网络锁定。
2. 演示ACE组件的常见用例:这些分布式服务还演示了怎样用像Reactor、Service Configurator、Acceptor和Connector、Active Object,以及IPC包装这样的ACE组件来有效地开发灵活、高效和可靠的通信软件。
八、高级分布式计算中间件组件
即使使用像ACE这样的通信框架,开发健壮、可扩展和高效的通信应用仍富有挑战性。特别是,开发者必须掌握许多复杂的OS和通信的概念,比如:
l 网络寻址和服务标识。
l 表示转换,比如加密、压缩和在异种终端系统间的字节序转换。
l 进程和线程的创建和同步。
l 本地和远地进程间通信(IPC)机制的系统调用和库例程。
通过采用像CORBA、DCOM或Java RMI这样的高级分布式计算中间件,可以降低开发通信应用的复杂性。高级分布式计算中间件驻留在客户端和服务器之间,可自动完成分布式应用开发的许多麻烦而易错的方面,包括:
l 认证、授权和数据安全。
l 服务定位和绑定。
l 服务注册和启用。
l 事件多路分离和分派。
l 在像TCP这样的面向字节流的通信协议之上实现消息帧。
l 涉及网络字节序和参数整编(marshaling)的表示转换问题。
为给通信软件的开发者提供这些特性,在ACE中绑定了下面的高级中间件应用:
1. The ACE ORB(TAO):TAO是使用ACE提供的框架组件和模式构建的CORBA实时实现,包含有网络接口、OS、通信协议和CORBA中间件组件等特性。TAO基于标准的OMG CORBA参考模型,并进行了增强的设计,以克服传统的用于高性能和实时应用的ORB的缺点。TAO像ACE一样,也是可自由使用的开放源码软件。
2. JAWS:JAWS是高性能、自适配的Web服务器,使用ACE提供的框架组件和模式构建。JAWS被构造成“框架的框架”。JAWS的总体框架含有以下组件和框架:事件多路分派器、并发策略、I/O策略、协议管道、协议处理器和缓存虚拟文件系统。每个框架都被构造成一组协作对象,通过组合和扩展ACE中的组件来实现。JAWS也是可自由使用的开放源码软件。
九、主页
ACE的主页为:http://www.cs.wustl.edu/~schmidt/ACE.html,在这里可获得最新版本的ACE以及其他相关资源。
Posted: August 22, 2006 at 7:56 pm | Tags: cache, class, linux, php, 技术, 缓存
mplayer是linux下播放速度最快,支持媒体格式最多的播放器,它几乎能播放所有的win媒体文件!下面介绍它的安装方法:
本文大部分参考了本站主席发于ch2000技术论坛的文章,http://www.ch2000.com.cn/lbbbs/cgi-bin/topic.cgi?forum=19&topic=974&start=0&show=0,本人仅做一些补充
使用nvdia显卡的朋友请务必安装nvidia的for linux驱动,否则xv模式不能支持,虽然可以用sdl视频模式播放,但效果不理想。
1、在本站下载mplayer的最新版本及安装所需软件包的集合包http://www.linuxfans.org/nuke/modules.php?name=Downloads&d_op=getit&lid=23
2、unzip mplayer.zip
得到mplayer目录,cd mplayer,运行ls命令,应该可以看见divx4linux-20020418,arial-18,gcc3,default这四个目录(三个目录下各有一些文件,这里就不列出来了),及MPlayer-0.90pre5.tar,w32codec-0.60.tar,SDL-1.2.3-1.i686.rpm,SDL-devel-1.2.3-1.i686.rpm这四个文件
3、升级gcc:
检查你的gcc版本,运行gcc -v看输出是多少,如果是3.0以下的话,则建议安装gcc3.0以上(根据mplayer的官方说明,3.0以下的gcc可以用./configre –disable-gcc-checking参数编译,但可能不稳定),安装gcc3的方法是:进入gcc3目录,运行rpm -ivh *.rpm –nodeps,全部百分百安装后,开一个终端窗口,运行mv /usr/bin/gcc /usr/bin/gcc2和mv /usr/bin/gcc3 /usr/bin/gcc,再运行gcc -v,看输出是否为3.0.2,如果是,则ok,不是,则检查安装步骤。
4、安装sdl及sdl-devel:
检查你的系统有没有安装了sdl及相同版本(一定要相同版本的)的sdl-devel库:运行“软件包管理器”,按“查找软件包”按钮,输入sdl,查找,如果能找到sdl及sdl-devel包,则表示都已经安装,如果版本比1.2.0高,那么跳到第5步;如果没安装或版本比较低(小于1.2.0),则安装本站提供的sdl包,命令rpm -ivh SDL*.rpm –nodeps或rpm -Uvh SDL*.rpm –nodeps,安装完后运行一下sdl-config –version[/color],看输出是不是1.2.3,如果正确,则ok;不正确或有问题,检查步骤,或发帖子问。
5、安装win32 for linux的dll库:(必须,只有安装它才可以播放win下的媒体文件,包括asf,wmv,mp3,avi,dat,mpg等)
运行命令:tar vxf w32codec-0.60.tar,得到w32codec-0.60.tar这个目录,建立/usr/lib/win32目录(mkdir /usr/lib/win32),将w32codec-0.60.tar目录下所有文件拷贝到/usr/lib/win32目录下(cp w32codec-0.60/*.* /usr/lib/win32)
6、安装divx5 for linux:(如果你想播放divx格式的电影文件的话必须安装这个,安装了这个后xine也可以利用,推荐)
进入divx4linux-20020418目录(cd divx4linux-20020418),运行./install.sh
7、解压编译安装mplayer:
tar vxf MPlayer-0.90pre5.tar,cd MPlayer-0.90pre5.tar,./configure (如果你想要gui的操作界面,则换用这个命令./configrue –enable-gui,但似乎这个版本的gui编译有错,我和linuxCN都无法成功,pre4版本则没问题),make,make install,都成功运行一下mplayer(这是为了让它在家目录下建立.mplayer这个配置目录,好进行下一步操作)
8、拷贝配置文件:(注意:下面这个etc目录是上一步中解压出来的MPlayer-0.90pre5目录下的,非/etc)
cp etc/codecs.conf ~/.mplayer
cp etc/example.conf ~/.mplayer/config
9、安装font和skin:(如果你选择了./configure –enable-gui那才需要skin,否则skin不用安装)
进入自己的home目录,显示隐藏文件,进入.mplayer文件夹,创2个建文件夹:font和Skin。然后把arial-18内的所有文件拷入到font内,把default整个文件夹(注意:是连同文件夹一起)拷到Skin内!
10、运行方式
两种:(1)mpalyer /路径/要播放的媒体文件名;(2)gmplayer(只有用./configure –enable-gui编译的这个命令才有效)
11、关于~/.mplayer/config文件的一些配置说明
vo=xv(选择默认的播放模式,xv模式是支持显卡硬加速功能的模式,对cpu的占用很小,推荐;更多的显示模式可以用命令mplayer -vo help看,其中的vesa模式可以在不启动xwin界面时播放,有点象当年DOS下的金山影霸)
ao=oss(选择声音的播放模式,oss是兼容性最好的播放模式,推荐;你也可以试试sdl模式)
fs=yes(全屏模式,yes的话默认以全屏播放,no或前面加#号注释掉这行的话则默认是1:1播放模式)
zoom=yes(即时改变电影画面大小,yes的话则可以用鼠标随意将播放画面拉大拉小,no 的话不能)
gui = yes(默认是图形化操作界面,这需要你先前用./configure –enable-gui来编译,否则的话就算yes也没有gui操作界面)
skin = default(设置默认的skin,将等号右边的值换成skin所放目录就可以设置想要的skin为默认了,这个参数在用gui操作界面才有效)
cache = 8192(默认的播放缓存大小,如果机器内存够大,则将它加大一点,如果不是很大,譬如128以下,还是用默认的算了)
framedrop = yes(允许跳帧,如果你的机器CPU速度非常非常慢的话请将这个yes,默认是no或前面加#号将这行禁止;对于366MHZ以上的机器,建议都设成no或前面加#号)
其他设置用默认就行
一些小技巧:
标准播放模式时一般按F键可以切换到全屏模式,再按一下复原,按Q可以退出;播放时如果发现声音和画面不同步,可以按+号(shift+=)或-号调整;播放divx的avi时如果想支持中文字幕,则你的电影文件目录下必须要有和电影同名的idx和sub这两种格式的字幕文件存在;支持网络播放流式文件,譬如输入mplayer http://IP/1.asf可以在线播放1.asf
附贴图:./configure后的输出窗口,请注意Enabled optional drivers:的下面:
Codecs: divx5linux libavcodec directshow win32这行,它代表了支持电影格式包括divx,avi,win32dll里的所有媒体格式;如果你缺少了divx,则检查你的divx4linux安装有没有问题,如果你缺少了win32,则检查你的win32codec安装正确了没有。
Audio output: arts oss sdl mpegpes(file)这行,它表示音频的输出模式支持arts,oss,sdl和mpegpes模式,其中的mpegpes模式是将音频导出生成mpg文件;如果缺少sdl模式,则检查你的sdl及sdl-devel安装了没有;安装完后可以通过修改config文件设置默认的音频输出模式,或用mplayer -ao 模式 要播放的文件名即时改变播放模式,譬如mplayer -ao sdl 1.asf就是用sdl的音频模式播放1.asf
Video output: xvidix sdl vesa gif89a jpeg png mpegpes(file) fbdev svga opengl dga xv x11这行,它表示可以支持的视频输出模式;如果缺少sdl模式,则检查你的sdl及sdl-devel安装了没有;安装完后可以通过修改config文件设置默认的视频输出模式,或用mplayer -vo 模式 要播放的文件名即时改变播放模式,譬如mplayer -vo sdl 1.asf就是用sdl的视频模式播放1.asf,mplayer -vo sdl -ao sdl 1.asf就是音频视频都选用sdl模式来播放1.asf’
Posted: August 17, 2006 at 11:48 am | Tags: cache, php, 缓存, 页面
<?php
# 让它在过去就“失效”
header(“Expires: Mon, 26 Jul 1997 05:00:00 GMT”);
# 永远是改动过的
header(“Last-Modified: “.gmdate(“D, d M Y H:i:s”).” GMT”);
# HTTP/1.1
header(“Cache-Control: no-store, no-cache, must-revalidate”);
header(“Cache-Control: post-check=0, pre-check=0″, false);
# HTTP/1.0
header(“Pragma: no-cache”);
?>
自己记录一下
Posted: August 17, 2006 at 11:26 am | Tags: blog, cache, flickr, html, java, javascript, php, unix, web, windows, 优化, 开发, 技巧, 技术, 类, 缓存, 网站, 翻译
在一个讨论web技术的网站vitamin上发现这篇《Serving JavaScript Fast》,读过之后大有收获,茅塞顿开。于是就有了翻译过来的念头——我这人有个毛病,看到有意思的英文文章,就想自己翻过来(虽然英文水平很烂)。先在网上查了查,已经有blog谈到这篇文章(我算是后知后觉了),有总结要点的《Flickr 的开发者的 Web 应用优化技巧》,也有延伸开来的《接着讲Flickr的八卦》,但似乎没有全文翻译的(这下就好,不会忙了半天发现是无用功)。之后,就写信问作者可不可以,作者一口答应:“sure – i’d love you to translate it”,只是要求我翻好之后给他一个链接地址。得到准许,心里就有底了。
先介绍一下作者。Cal Henderson,伦敦人,现居加利福尼亚的旧金山。PHP,MySQL和Perl专家,现任flickr架构师(flickr被收购后就在yahoo了),同时也是vitamin的特聘顾问(写些技术性文章)。
既然他是架构师,flickr用的应该就是文中谈到的这些技术,于是参照文章,再对比网站,种种迹象表明确实如此。虽然在中国访问flickr速度不敢恭维,加速效果不得而知,但其用了n多css和javascript资源却似乎从没出过什么问题,也从侧面印证了这些技术的有效性。
仔细的看完文章,还有个强烈的感觉:这老兄也太能卖关子了,一句话非分成三句说,摆事实讲道理是够透彻,就是有点太@#$%了…… 算了,他怎么说我怎么翻吧,忠实于原著嘛,要不就成篡改了。经过几天努力,加上同事thincat兄倾力援手(小弟不胜感激啊),终于完工(@_@ 真是苦力活啊,我再也不想干了~)。
全文翻译如下:
让javascript跑得更快
作者:Cal Henderson
下一代web应用让javascript和css得堪大用。我们会告诉你怎样使这些应用又快又灵。
建立了号称“Web 2.0”的应用,也实现了富内容(rich content)和交互,我们期待着css和javascript扮演更加重要的角色。为使应用干净利落,我们需要完善那些渲染页面的文件,优化其大小和形态,以确保提供最好的用户体验——在实践中,这就意味着一种结合:使内容尽可能小、下载尽可能快,同时避免对未改动资源不必要的重新获取。
由于css和js文件的形态,情况有点复杂。跟图片相比,其源代码很有可能频繁改动。而一旦改动,就需要客户端重新下载,使本地缓存无效(保存在其他缓存里的版本也是如此)。在这篇文章里,我们将着重探讨怎样使用户体验最快:包括初始页面的下载,随后页面的下载,以及随着应用渐进、内容变化而进行的资源下载。
我始终坚信这一点:对开发者来说,应该尽可能让事情变得简单。所以我们青睐于那些能让系统自动处理优化难题的方法。只需少许工作量,我们就能建立一举多得的环境:它使开发变得简单,有极佳的终端性能,也不会改变现有的工作方式。
好大一沱
老的思路是,为优化性能,可以把多个css和js文件合并成极少数大文件。跟十个5k的js文件相比,合并成一个50k的文件更好。虽然代码总字节数没变,却避免了多个HTTP请求造成的开销。每个请求都会在客户端和服务器两边有个建立和消除的过程,导致请求和响应header带来开销,还有服务器端更多的进程和线程资源消耗(可能还有为压缩内容耗费的cpu时间)。
(除了HTTP请求,)并发问题也很重要。默认情况下,在使用持久连接(persistent connections)时,ie和firefox在同一域名内只会同时下载两个资源(在HTTP 1.1规格书中第8.1.4节的建议)(htmlor注:可以通过修改注册表等方法改变这一默认配置)。这就意味着,在我们等待下载2个js文件的同时,将无法下载图片资源。也就是说,这段时间内用户在页面上看不到图片。
(虽然合并文件能解决以上两个问题,)可是,这个方法有两个缺点。第一,把所有资源一起打包,将强制用户一次下载完所有资源。如果(不这么做,而是)把大块内容变成多个文件,下载开销就分散到了多个页面,同时缓解了会话中的速度压力(或完全避免了某些开销,这取决于用户选择的路径)。如果为了随后页面下载得更快而让初始页面下载得很慢,我们将发现更多用户根本不会傻等着再去打开下一个页面。
第二(这个影响更大,一直以来却没怎么被考虑过),在一个文件改动很频繁的环境里,如果采用单文件系统,那么每次改动文件都需要客户端把所有css和js重新下载一遍。假如我们的应用有个100k的合成的js大文件,任何微小的改动都将强制客户端把这100k再消化一遍。
分解之道
(看来合并成大文件不太合适。)替代方案是个折中的办法:把css和js资源分散成多个子文件,按功能划分、保持文件个数尽可能少。这个方案也是有代价的,虽说开发时代码分散成逻辑块(logical chunks)能提高效率,可在下载时为提高性能还得合并文件。不过,只要给build系统(把开发代码变成产品代码的工具集,是为部署准备的)加点东西,就没什么问题了。
对于有着不同开发和产品环境的应用来说,用些简单的技术可以让代码更好管理。在开发环境下,为使条理清晰,代码可以分散为多个逻辑部分(logical components)。可以在Smarty(一种php模板语言)里建立一个简单的函数来管理javascript的下载:
SMARTY:{insert_js files="foo.js,bar.js,baz.js"}PHP:function smarty_insert_js($args){ foreach (explode(',', $args['files']) as $file){ echo "<script type=\"text/javascript\" SOURCE=\"/javascript/$file\"></script>\n"; }}OUTPUT:<script type="text/javascript" SOURCE="/javascript/foo.js"></script><script type="text/javascript" SOURCE="/javascript/bar.js"></script><script type="text/javascript" SOURCE="/javascript/baz.js"></script>
(htmlor注:wordpress中会把“src”替换成不知所谓的字符,因此这里只有写成“SOURCE”,使用代码时请注意替换,下同)
就这么简单。然后我们就命令build过程(build process)去把确定的文件合并起来。这个例子里,合并的是foo.js和bar.js,因为它们几乎总是一起下载。我们能让应用配置记住这一点,并修改模板函数去使用它。(代码如下:)
SMARTY:{insert_js files="foo.js,bar.js,baz.js"}PHP:# 源文件映射图。在build过程合并文件之后用这个图找到js的源文件。$GLOBALS['config']['js_source_map'] = array( 'foo.js' => 'foobar.js', 'bar.js' => 'foobar.js', 'baz.js' => 'baz.js',);function smarty_insert_js($args){ if ($GLOBALS['config']['is_dev_site']){ $files = explode(',', $args['files']); }else{ $files = array(); foreach (explode(',', $args['files']) as $file){ $files[$GLOBALS['config']['js_source_map'][$file]]++; } $files = array_keys($files); } foreach ($files as $file){ echo "<script type=\"text/javascript\" SOURCE=\"/javascript/$file\"></script>\n"; }}OUTPUT:<script type="text/javascript" SOURCE="/javascript/foobar.js"></script><script type="text/javascript" SOURCE="/javascript/baz.js"></script>
模板里的源代码没必要为了分别适应开发和产品阶段而改动,它帮助我们在开发时保持文件分散,发布成产品时把文件合并。想更进一步的话,可以把合并过程(merge process)写在php里,然后使用同一个(合并文件的)配置去执行。这样就只有一个配置文件,避免了同步问题。为了做的更加完美,我们还可以分析css和js文件在页面中同时出现的几率,以此决定合并哪些文件最合理(几乎总是同时出现的文件是合并的首选)。
对css来说,可以先建立一个主从关系的模型,它很有用。一个主样式表控制应用的所有样式表,多个子样式表控制不同的应用区域。采用这个方法,大多数页面只需下载两个css文件,而其中一个(指主样式表)在页面第一次请求时就会缓存。
对没有太多css和js资源的应用来说,这个方法在第一次请求时可能比单个大文件慢,但如果保持文件数量很少的话,你会发现其实它更快,因为每个页面的数据量更小。让人头疼的下载花销被分散到不同的应用区域,因此并发下载数保持在一个最小值,同时也使得页面的平均下载数据量很小。
压缩
谈到资源压缩,大多数人马上会想到mod_gzip(但要当心,mod_gzip实际上是个魔鬼,至少能让人做恶梦)。它的原理很简单:浏览器请求资源时,会发送一个header表明自己能接受的内容编码。就像这样:
Accept-Encoding: gzip,deflate
服务器遇到这样的header请求时,就用gzip或deflate压缩内容发往客户端,然后客户端解压缩。这过程减少了数据传输量,同时消耗了客户端和服务器的cpu时间。也算差强人意。但是,mod_gzip的工作方式是这样的:先在磁盘上创建一个临时文件,然后发送(给客户端),最后删除这个文件。在高容量的系统中,由于磁盘io问题,很快就会达到极限。要避免这种情况,可以改用mod_deflate(apache 2才支持)。它采用更合理的方式:在内存里做压缩。对于apache 1的用户来说,可以建立一块ram磁盘,让mod_gzip在它上面写临时文件。虽然没有纯内存方式快,但也不会比往磁盘上写文件慢。
话虽如此,其实还是有办法完全避免压缩开销的,那就是预压缩相关静态资源,下载时由mod_gzip提供合适的压缩版本。如果把压缩添加在build过程,它就很透明了。需要压缩的文件通常很少(用不着压缩图片,因为并不能减小更多体积),只有css和js文件(和其他未压缩的静态内容)。
配置选项会告诉mod_gzip去哪里找到预压缩过的文件。
mod_gzip_can_negotiate Yesmod_gzip_static_suffix .gzAddEncoding gzip .gz
新一点的mod_gzip版本(从1.3.26.1a开始)添加一个额外的配置选项后,就能自动预压缩文件。不过在此之前,必须确认apache有正确的权限去创建和覆盖压缩文件。
mod_gzip_update_static Yes
可惜,事情没那么简单。某些Netscape 4的版本(尤其是4.06-4.08)认为自己能够解释压缩内容(它们发送一个header这么说来着),但其实它们不能正确的解压缩。大多数其他版本的Netscape 4在下载压缩内容时也有各种各样的问题。所以要在服务器端探测代理类型,(如果是Netscape 4,就要)让它们得到未压缩的版本。这还算简单的。ie(版本4-6)有些更有意思的问题:当下载压缩的javascript时,有时候ie会不正确的解压缩文件,或者解压缩到一半中断,然后把这半个文件显示在客户端。如果你的应用对javascript的依赖比较大(htmlor注:比如ajax应用),那么就得避免发送压缩文件给ie。在某些情况下,一些更老的5.x版本的ie倒是能正确的收到压缩的javascript,可它们会忽略这个文件的etag header,不缓存它。(thincat友情提示:尽管压缩存在一些浏览器不兼容的现象,由于这些不能很好的支持压缩的浏览器数量现在已经非常少了,我认为这种由于浏览器导致的压缩不正常的情况可以忽略不计。这些过时的浏览器还能不能在现在流行的windows或unix环境下面安装都存在不小的问题)
既然gzip压缩有这么多问题,我们不妨把注意力转到另一边:不改变文件格式的压缩。现在有很多这样的javascript压缩脚本可用,大多数都用一个正则表达式驱动的语句集来减小源代码的体积。它们做的不外乎几件事:去掉注释,压缩空格,缩短私有变量名和去掉可省略的语法。
不幸的是,大多数脚本效果并不理想,要么压缩率相当低,要么某种情形下会把代码搞得一团糟(或者两者兼而有之)。由于对解析树的理解不完整,压缩器很难区分一句注释和一句看似注释的引用字符串。因为闭合结构的混合使用,要用正则表达式发现哪些变量是私有的并不容易,因此一些缩短变量名的技术会打乱某些闭合代码。
还好有个压缩器能避免这些问题:dojo压缩器(现成的版本在这里)。它使用rhino(mozilla的javascript引擎,是用java实现的)建立一个解析树,然后将其提交给文件。它能很好的减小代码体积,仅用很小的成本:因为只在build时压缩一次。由于压缩是在build过程中实现的,所以一清二楚。(既然压缩没有问题了,)我们可以在源代码里随心所欲的添加空格和注释,而不必担心影响到产品代码。
与javascript相比,css文件的压缩相对简单一些。由于css语法里不会有太多引用字符串(通常是url路径跟字体名),我们可以用正则表达式大刀阔斧的干掉空格(htmlor注:这句翻的最爽,哈哈)。如果确实有引用字符串的话,我们总可以把一串空格合成一个(因为不需要在url路径和字体名里查找多个空格和tab)。这样的话,一个简单的perl脚本就够了:
#!/usr/bin/perlmy $data = '';open F, $ARGV[0] or die "Can't open source file: $!";$data .= $_ while <F>;close F;$data =~ s!/*(.*?)*/!!g; # 去掉注释$data =~ s!s+! !g; # 压缩空格$data =~ s!} !}\n!g; # 在结束大括号后添加换行$data =~ s!\n$!!; # 删除最后一个换行$data =~ s! { ! {!g; # 去除开始大括号后的空格$data =~ s!; }!}!g; # 去除结束大括号前的空格print $data;
然后,就可以把单个的css文件传给脚本去压缩了。命令如下:
perl compress.pl site.source.css > site.compress.css
做完这些简单的纯文本优化工作后,我们就能减少数据传输量多达50%了(这个量取决于你的代码格式,可能更多)。这带来了更快的用户体验。不过我们真正想做的是,尽可能避免用户请求的发生——除非确实有必要。这下HTTP缓存知识派上用场了。
缓存是好东西
当用户代理(如浏览器)向服务器请求一个资源时,第一次请求过后它就会缓存服务器的响应,以避免重复之后的相同请求。缓存时间的长短取决于两个因素:代理的配置和服务器的缓存控制header。所有浏览器都有不同的配置选项和处理方式,但大多数都会把一个资源至少缓存到会话结束(除非被明确告知)。
为了不让浏览器缓存改动频繁的页面,你很可能已经发送过header不缓存动态内容。在php中,以下两行命令可以做到:
<?phpheader("Cache-Control: private");header("Cache-Control: no-cache", false);?>
听起来太简单了?确实如此——因为有些代理(浏览器)在某些环境下将忽略这些header。要确保浏览器不缓存文档,应该更强硬一些:
<?php# 让它在过去就“失效”header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");# 永远是改动过的header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT");# HTTP/1.1header("Cache-Control: no-store, no-cache, must-revalidate");header("Cache-Control: post-check=0, pre-check=0", false);# HTTP/1.0header("Pragma: no-cache");?>
这样,对于我们不想缓存的内容来说已经行了。但对于那些不会每次请求时都有改动的内容,应该鼓励浏览器更霸道的缓存它。“If-Modified-Since”请求header能够做到这点。如果客户端在请求中发送一个“If-Modified-Since”header,apache(或其他服务器)会以状态代码304(没改过)响应,告诉浏览器缓存已经是最新的。使用这个机制,能够避免重复发送文件给浏览器,不过仍然导致了一个HTTP请求的消耗。嗯,再想想。
与If-Modified-Since机制类似的是实体标记(entity tags)。在apache环境下,每个对静态文件的响应都会发出一个“ETag”header,它包含了一个由文件修改时间、文件大小和inode号生成的校验和(checksum)。在下载文件之前,浏览器会发送一个HEAD请求去检查文件的etag。可ETag跟If-Modified-Since有同样的问题:客户端仍旧需要执行HTTP请求来验证本地缓存是否有效。
此外,如果你使用多台服务器提供内容,得小心使用if-modified-since和etags。在两台负载平衡的服务器环境下,对一个代理(浏览器)来说,一个资源可以这次从A服务器得到,下次从B服务器得到(htmlor注:lvs负载平衡系统就是个典型的例子)。这很好,也是采用平衡负载的原因。可是,如果两台服务器给同一个文件生成了不同的etag或者文件修改日期,浏览器就无所适从了(每次都会重新下载)。默认情况下,etag是由文件的inode号生成的,而多台服务器之间文件的inode号是不同的。可以使用apache的配置选项关掉它:
FileETag MTime Size
使用这个选项,apache将只用文件修改日期和文件大小来决定etag。很不幸,这导致了另一个问题(一样能影响if-modified-since)。既然etag依赖于修改时间,就得让时间同步。可往多台服务器上传文件时,上传时间差个一到两秒是常有的事。这样一来,两台服务器生成的etag还是不一样。当然,我们还可以改变配置,让etag的生成只取决于文件大小,但这就意味着如果文件内容变了而大小没变,etag也不会变。这可不行。
缓存真是个好东西
看来我们正从错误的方向入手解决问题。(现在的问题是,)这些可能的缓存策略导致了一件事情反复发生,那就是:客户端向服务器查询本地缓存是否最新。假如服务器在改动文件的时候通知客户端,客户端不就知道它的缓存是最新的了(直到接到下一次通知)?可惜天公不做美——(事实)是客户端向服务器发出请求。
其实,也不尽然。在获取js或css文件之前,客户端会用<script>或<link>标记向服务器发送一个请求,说明哪个页面要加载这些文件。这时候就可以用服务器的响应来通知客户端这些文件有了改动。有点含糊,说得再详细点就是:如果改变css和js文件内容的同时,也改变它们的文件名,就可以告诉客户端对url全都永久缓存——因为每个url都是唯一的。
假如能确定一个资源永不更改,我们就可以发出一些霸气十足的缓存header(htmlor注:这句也很有气势吧)。在php里,两行就好:
<?phpheader("Expires: ".gmdate("D, d M Y H:i:s", time()+315360000)." GMT");header("Cache-Control: max-age=315360000");?>
我们告诉浏览器这个内容在10年后(10年大概会有315,360,000秒,或多或少)过期,浏览器将会保留它10年。当然,很有可能不用php输出css和js文件(因此就不能发出header),这种情况将在稍后说明。
人力有时而穷
当文件内容更改时,手动去改文件名是很危险的。假如你改了文件名,模板却没有指向它?假如你改了一些模板另一些却没改?假如你改了模板却没改文件名?还有最糟的,假如你改动了文件却忘了改名或者忘了改变对它的引用?最好的结果,是用户看到老的而看不到新的内容。最坏的结果,是找不到文件,网站没法运转了。听起来这(指改动文件内容时修改url)似乎是个馊主意。
幸运的是,计算机做这类事情——当某种变化发生,需要相当准确地完成的、重复重复再重复的(htmlor注:番茄鸡蛋伺候~)、枯燥乏味的工作——总是十分在行。
这个过程(改变文件的url)没那么痛苦,因为我们根本不需要改文件名。资源的url和磁盘上文件的位置也没必要保持一致。使用apache的mod_rewrite模块,可以建立简单的规则,让确定的url重定向到确定的文件。
RewriteEngine onRewriteRule ^/(.*.)v[0-9.]+.(css|js|gif|png|jpg)$ /$1$2 [L]
这条规则匹配任何带有指定扩展名同时含有“版本”信息(version nugget)的url,它会把这些url重定向到一个不含版本信息的路径。如下所示:
URL Path/images/foo.v2.gif -> /images/foo.gif/css/main.v1.27.css -> /css/main.css/javascript/md5.v6.js -> /javascript/md5.js
使用这条规则,就可以做到不改变文件路径而更改url(因为版本号变了)。由于url变了,浏览器就认为它是另一个资源(会重新下载)。想更进一步的话,可以把我们之前说的脚本编组函数结合起来,根据需要生成一个带有版本号的<script>标记列表。
说到这里,你可能会问我,为什么不在url结尾加一个查询字符串(query string)呢(如/css/main.css?v=4)?根据HTTP缓存规格书所说,用户代理对含有查询字符串的url永不缓存。虽然ie跟firefox忽略了这点,opera和safari却没有——为了确保所有浏览器都缓存你的资源,还是不要在url里用查询字符串的好。
现在不移动文件就能更改url了,如果能让url自动更新就更好了。在小型的产品环境下(如果有大型的产品环境,就是开发环境了),使用模板功能可以很轻易的实现这点。这里用的是smarty,用其他模板引擎也行。
SMARTY:<link xhref="{version xsrc='/css/group.css'}" rel="stylesheet" type="text/css" />PHP:function smarty_version($args){ $stat = stat($GLOBALS['config']['site_root'].$args['src']); $version = $stat['mtime']; echo preg_replace('!.([a-z]+?)$!', ".v$version.$1", $args['src']);}OUTPUT:<link xhref="/css/group.v1234567890.css" rel="stylesheet" type="text/css" />
对每个链接到的资源文件,我们得到它在磁盘上的路径,检查它的mtime(文件最后修改的日期和时间),然后把这个时间当作版本号插入到url中。对于低流量的站点(它们的stat操作开销不大)或者开发环境来说,这个方案不错,但对于高容量的环境就不适用了——因为每次stat操作都要磁盘读取(导致服务器负载升高)。
解决方案相当简单。在大型系统中每个资源都已经有了一个版本号,就是版本控制的修订号(你们应该使用了版本控制,对吧?)。当我们建立站点准备部署的时候,可以轻易的查到每个文件的修订号,写在一个静态配置文件里。
<?php$GLOBALS['config']['resource_versions'] = array( '/images/foo.gif' => '2.1', '/css/main.css' => '1.27', '/javascript/md5.js' => '6.1.4',);?>
当我们发布产品时,可以修改模板函数来使用版本号。
<?phpfunction smarty_version($args){ if ($GLOBALS['config']['is_dev_site']){ $stat = stat($GLOBALS['config']['site_root'].$args['src']); $version = $stat['mtime']; }else{ $version = $GLOBALS['config']['resource_versions'][$args['src']]; } echo preg_replace('!.([a-z]+?)$!', ".v$version.$1", $args['src']);}?>
就这样,不需要改文件名,也不需要记住改了哪些文件——当文件有新版本发布时它的url就会自动更新——有意思吧?我们就快搞定了。
只欠东风
之前谈到为静态文件发送超长周期(very-long-period)的缓存header时曾说过,如果不用php输出,就不能轻易的发送缓存header。很显然,有两个办法可以解决:用php输出,或者让apache来做。
php出马,手到擒来。我们要做的仅仅是改变rewrite规则,把静态文件指向php脚本,用php在输出文件内容之前发送header。
Apache:RewriteRule ^/(.*.)v[0-9.]+.(css|js|gif|png|jpg)$ /redir.php?path=$1$2 [L]PHP:header("Expires: ".gmdate("D, d M Y H:i:s", time()+315360000)." GMT");header("Cache-Control: max-age=315360000");# 忽略带有“..”的路径if (preg_match('!..!', $_GET[path])){ go_404(); }# 保证路径开头是确定的目录if (!preg_match('!^(javascript|css|images)!', $_GET[path])){ go_404(); }# 文件不存在?if (!file_exists($_GET[path])){ go_404(); }# 发出一个文件类型header$ext = array_pop(explode('.', $_GET[path]));switch ($ext){ case 'css': header("Content-type: text/css"); break; case 'js' : header("Content-type: text/javascript"); break; case 'gif': header("Content-type: image/gif"); break; case 'jpg': header("Content-type: image/jpeg"); break; case 'png': header("Content-type: image/png"); break; default: header("Content-type: text/plain");}# 输出文件内容echo implode('', file($_GET[path]));function go_404(){ header("HTTP/1.0 404 File not found"); exit;}
这个方案有效,但并不出色。(因为)跟apache相比,php需要更多内存和执行时间。另外,我们还得小心防止可能由path参数传递伪造值引起的exploits。为避免这些问题,应该用apache直接发送header。rewrite规则语句允许当规则匹配时设置环境变量(environment variable),当给定的环境变量设置后,Header命令就可以添加header。结合以下两条语句,我们就把rewrite规则和header设置绑定在了一起:
RewriteEngine onRewriteRule ^/(.*.)v[0-9.]+.(css|js|gif|png|jpg)$ /$1$2 [L,E=VERSIONED_FILE:1]Header add "Expires" "Mon, 28 Jul 2014 23:30:00 GMT" env=VERSIONED_FILEHeader add "Cache-Control" "max-age=315360000" env=VERSIONED_FILE
考虑到apache的执行顺序,应该把rewrite规则加在主配置文件(httpd.conf)而不是目录配置文件(.htaccess)中。否则在环境变量设置之前,header行会先执行(就那没意义了)。至于header行,则可以放在两文件任何一个当中,没什么区别。
眼观六路
(htmlor注:多谢tchaikov告知“skinning rabbits”的含义,但我不想翻的太正式,眼下的这个应该不算太离谱吧。)
通过结合使用以上技术,我们可以建立一个灵活的开发环境和一个快速又高性能的产品环境。当然,这离终极目标“速度”还有一段距离。有许多更深层的技术(比如分离伺服静态内容,用多域名提升并发量等)值得我们关注,包括与我们谈到的方法(建立apache过滤器,修改资源url,加上版本信息)殊途同归的其他路子。你可以留下评论,告诉我们那些你正在使用的卓有成效的技术和方法。
(完)
Next Page