文章列表
 
2006年08月27日 星期日 上午 11:05

欢迎访问火丁笔记:http://huoding.com/

火丁的订阅地址:http://huoding.com/feed

作者:老王

定义:

SSI(服务器端包含)提供了一种对现有HTML文档增加动态内容的方法。

作用:

一般出于效率的考虑,网站都会把内容尽可能的静态化成HTML文件,但是网站页面的布局往往比较复杂,各个部分的更新频率并不一致,比如说:一个显示“文章内容”的页面,很可能在旁边会有一个“每天最热文章TOP10”之类的列表,这个列表的内容是要每天更新的,而文章内容本身一般不会那么频繁的更新,对于这样的问题可以用Apache的SSI来解决,从感觉上讲,其作用和PHP中的inlucde文件类似。

配置:

配置SSI很简单,既可以把配置代码写在httpd.conf里,也可以写在.htaccess文件里,这里以httpd.conf文件为例来说明,打开你的httpd.conf文件,在相应的<Directory "......">里只要键入下面代码就可以激活SSI:

Options Includes
AddType text/html .shtml
AddOutputFilter INCLUDES .shtml

这样,你所有扩展名为shtml的文件都具有了SSI的能力,这里需要注意的是,如果我们不需要在shtml文件里执行exec指令,那么就应该尽可能的禁止它,以便获得最大的安全性,所以推荐的配置代码变成:

Options IncludesNOEXEC
AddType text/html .shtml
AddOutputFilter INCLUDES .shtml

注意:以上配置代码以Apache2.0.X版本为准,如果你的版本属于Apache1.3.X系列,则其中的“AddOutputFilter INCLUDES .shtml”应该换成“AddHandler server-parsed .shtm”。

另外还有一种XBitHack的配置方法,但是并不推荐,想了解的可以看手册。

应用:

下面看看具体页面应该如何编写,还是以开头说的情况为例,我们假设“每天最热文章TOP10”的代码每天都会由Crontab程序自动静态化成top10.html文件,那么在我们的文章显示页面的里,可以使用如下的代码来引用top10.html页面:

1. <!--#include file="top10.html"-->
2. <!--#include virtual="top10.html"-->

file和virtual的区别在于:file属性是一个相对于当前目录的文件路径,即不能是一个绝对路径以“/”开头或包含“../”的路径。virtual属性可能更有用,它是一个相对于被提供的文档的URL ,可以以“/”开头,但必须与被提供的文档位于同一服务器上。

当然,SSI的能力远不止于此,手册上对相关的指令有详细的介绍,这里只是介绍了SSI最常用的功能而已。

实战:

某公司有一个CMS(文章管理系统),首页,列表页,显示页都已经静态化,相应的头尾部分使用SSI搞定,大致如下:

<!--#include virtual="head.shtml"-->
<!--#include virtual="foot.shtml"-->

此时,业务部门和一些公司达成了许多合作意向,其中一条是我们的网站的每个页面都要加上对方网站的头和尾,粗看起来问题比较棘手,因为我们的网站都已经静态化了,难道我们要为每个合作公司再拷贝一份网站不成,那当然是不现实的,别忘了,我们网站原来的头尾都已经使用SSI分离出来了,而且使用的还是shtml格式的头尾,这就是说明我们还可以在头尾文件里包含合作公司的头尾来完成需求,等等,似乎还有疑问,合作公司那么多,我们怎么区分呢?很简单,为每个公司指定的一个子域名,如:A公司的合作页面域名为“a.domain.com”,类推,然后在已经有的SSI文件(head.shtml和foot.shtml)里的适当位置加入下面代码:

<!--#include virtual="${SERVER_NAME}.head.shtml"-->
<!--#include virtual="${SERVER_NAME}.foot.shtml"-->

其中“SERVER_NAME”是环境变量,说白了,就是你为公司指定的那个子域名。如果对应公司的头尾文件都准备好了,那就浏览吧,应该可以自动根据域名来动态加载相应的头尾文件了吧,^_^,搞定!

------------------------------------------------------------------

补充:今天一台服务器的httpd进程超多,感觉负载过大,所以决定干掉apache,网站迁移到lighttpd上,本来一切顺利,但是安装好后,浏览的时候发现页面的SSI没有正常加载,里面的代码和上面说的差不多,如下:

<!--#include virtual="${SERVER_NAME}.head.shtml"-->
<!--#include virtual="${SERVER_NAME}.foot.shtml"-->

查了很多,才搞清楚原来apache和lighttpd对SERVER_NAME的解释不同,换成HTTP_HOST就好了,所以以后写SSI的时候,还是应该在可移植性上考虑多点。最终代码如下:

<!--#include virtual="${HTTP_HOST}.head.shtml"-->
<!--#include virtual="${HTTP_HOST}.foot.shtml"-->

 
2006年08月19日 星期六 下午 12:34

欢迎访问火丁笔记:http://huoding.com/

火丁的订阅地址:http://huoding.com/feed

作者:老王

在传统的OOP(面向对象编程:Object-Oriented Programming)思想里,一般把应用程序分解成若干个的对象,强调高内聚,弱耦合,从而提高应用程序的模块化程度,但是在处理某些问题的时候,OOP会显得不够灵活,比如说,应用程序里很多业务逻辑都要在操作之初进行“权限检查”,在操作之后进行“日志记录”,如果直接把处理这些操作的代码加入到每个模块中,那么无疑破坏了OOP的“单一职责”原则,模块的可重用性会大大降低,这时候传统的OOP设计往往采取的策略是加入相应的代理(Proxy)层来完成系统的功能要求,但这样的处理明显使系统整体增加了一个层次的划分,复杂性也随之增加,从而给人过于厚重的感觉。正是为了处理这样的问题,AOP(面向方面编程:Aspect-Oriented Programming)思想应运而生了,假设把应用程序想成一个立体结构的话,OOP的利刃是纵向切入系统,把系统划分为很多个模块(如:用户模块,文章模块等等),而AOP的利刃是横向切入系统,提取各个模块可能都要重复操作的部分(如:权限检查,日志记录等等)。由此可见,AOP是OOP的一个有效补充。

就目前的PHP来说,还没有一个完整的AOP内置实现,虽然出现了RunKit,但一直都以BETA的状态呆在PECL项目里,估计很长时间内不太可能成为PHP的缺省设置。那是不是AOP在PHP里就破灭了呢?当然不是,因为我们有__get(),__set(),__call()等魔术方法,合理使用这些方法可以为我们实现某种程度的“准AOP”能力,之所以说是准AOP,是因为单单从实现上来看,称其为AOP有些牵强,但是从效果上来看,又部分实现了AOP的作用,虽然其实现方式并不完美,但对于一般的使用已经足够了。特别是从PHP4.3.0开始,这些魔术方法已经成为了PHP的缺省内置实现,扫除了PHP4/5兼容的顾虑,那么就更加没有理由不使用它们。这里要说明的是PHP4/5对这些魔术方法的实现有些许的不同,下面将分别举例说明:

先来看看PHP4的相应代码(下面代码只能运行在PHP4环境下):

<?php
//应用程序中某个业务逻辑类
class 
BIZ
{
    function 
foobar
()
    {
        echo 
'业务逻辑<br />'
;
    }
}

//业务逻辑类的包装类
class 
AOP
{
    var 
$instance
;

    function 
AOP($instance
)
    {
        
$this->instance $instance
;
    }

    function 
__call($method$argument$return
) 
    {
        if(! 
method_exists($this->instance$method
))
        {
            return 
false
;
        }

        echo 
'权限检查<br />'
;

        
$callBack = array($this->instance$method
);

       
$return call_user_func_array($callBack$argument);

        echo 
'日志记录<br />'
;

        return 
true
;
    }
}

//工厂方法
class 
Factory
{
    function 
getBizInstance
()
    {
        
//PHP4必须这样声明一下才可以使用overload相关方法
        
overload('AOP'
);

        return new 
AOP(new BIZ
());
    }
}

//客户端调用演示
header("Content-Type: text/html; charset=gbk"
);

$obj Factory::getBizInstance
();

$obj->foobar
();
?>

屏幕显示:

权限检查
业务逻辑
日志记录

再来看看PHP5的相应代码(下面代码只能运行在PHP5的环境下):

<?php
//应用程序中某个业务逻辑类
class BIZ
{
    public function 
foobar()
    {
        echo 
'业务逻辑<br />';
    }
}

//业务逻辑类的包装类
class AOP
{
    private 
$instance;

    public function
__construct($instance)
    {
        
$this->instance $instance;
    }

    public function 
__call($method$argument)
    {
        if(! 
method_exists($this->instance$method))
        {
            throw new 
Exception('未定义的方法:' $method);
        }

        echo 
'权限检查<br />';

        
$callBack = array($this->instance$method);

        
$return call_user_func_array($callBack$argument);

        echo 
'日志记录<br />';

        return 
$return;
    }
}

//工厂方法
class Factory
{
    public function 
getBizInstance()
    {
        return new 
AOP(new BIZ());
    }
}

//客户端调用演示
header("Content-Type: text/html; charset=gbk");

try
{
    
$obj Factory::getBizInstance();

    
$obj->foobar();
}
catch(
Exception $e)
{
    echo 
'Caught exception: ',  $e->getMessage();
}
?>

屏幕显示:

权限检查
业务逻辑
日志记录

总结:

代码中的粗体部分是表示PHP4/5有差异的地方,具体解释可以参考手册。整个的实现思路其实很简单,关键就是客户端请求的对象不能直接实例化出来,而是利用工厂方法返回一个请求对象的包装对象,在包装对象内利用魔术方法来处理权限处理,日志记录等公共操作。这既是巧妙的地方,也是最有可能出问题的地方,因为客户端使用对象并不是它想象中的对象,而是一个包装后的对象,比如说,客户端通过getBizInstance()方法以为得到的对象是BIZ,但实际上它得到的是一个BIZ的包装对象AOP,这样的话,如果客户端进行一些诸如get_class()之类和对象类型相关的操作就会出错,当然,大多数情况下,客户端似乎不太会做类似的操作,末了,再唠叨几句,为了脚本在PHP4/5都能运行,可以分别用两个脚本实现AOP类,然后用version_compare()方法来决定加载哪一个。

 
2006年08月05日 星期六 下午 2:23

欢迎访问火丁笔记:http://huoding.com/

火丁的订阅地址:http://huoding.com/feed

作者:老王

MySQL5.X都已经发布好久了,但是还有很多人认为MySQL是不支持事务处理的,这不得不怪他们是孤陋寡闻的,其实,只要你的MySQL版本支持BDB或InnoDB表类型,那么你的MySQL就具有事务处理的能力。这里面,又以InnoDB表类型用的最多,虽然后来发生了诸如Oracle收购InnoDB等令MySQL不爽的事情,但那些商业上的斗争与技术无关,下面以InnoDB表类型为例简单说一下MySQL中的事务。

先来明确一下事务涉及的相关知识:

事务都应该具备ACID特征。所谓ACID是Atomic(原子性),Consistent(一致性),Isolated(隔离性),Durable(持续性)四个词的首字母所写,下面以“银行转帐”为例来分别说明一下它们的含义:

原子性:组成事务处理的语句形成了一个逻辑单元,不能只执行其中的一部分。换句话说,事务是不可分割的最小单元。比如:银行转帐过程中,必须同时从一个帐户减去转帐金额,并加到另一个帐户中,只改变一个帐户是不合理的。

一致性:在事务处理执行前后,数据库是一致的。也就是说,事务应该正确的转换系统状态。比如:银行转帐过程中,要么转帐金额从一个帐户转入另一个帐户,要么两个帐户都不变,没有其他的情况。

隔离性:一个事务处理对另一个事务处理没有影响。就是说任何事务都不可能看到一个处在不完整状态下的事务。比如说,银行转帐过程中,在转帐事务没有提交之前,另一个转帐事务只能处于等待状态。

持续性:事务处理的效果能够被永久保存下来。反过来说,事务应当能够承受所有的失败,包括服务器、进程、通信以及媒体失败等等。比如:银行转帐过程中,转帐后帐户的状态要能被保存下来。

再来看看哪些问题会用到事务处理:

 

这里不说“银行转帐”的例子了,说一个大家实际更容易遇到的“网上购书”的例子。先假设一下问题的背景:网上购书,某书(数据库编号为123)只剩最后一本,而这个时候,两个用户对这本书几乎同时发出了购买请求,让我们看看整个过程:

在具体分析之前,先来看看数据表的定义:

-------------------------------------------------------------------------------

create table book
(
    book_id unsigned int(10) not null auto_increment,
    book_name varchar(100) not null,
    book_price float(5, 2) not null, #我假设每本书的价格不会超过999.99元
    book_number int(10) not null,
    primary key (book_id)
)
type = innodb; #engine = innodb也行

-------------------------------------------------------------------------------

对于用户甲来说,他的动作稍微比乙快一点点,其购买过程所触发的动作大致是这样的:

-------------------------------------------------------------------------------

1. SELECT book_number FROM book WHERE  book_id = 123;

book_number大于零,确认购买行为并更新book_number

2. UPDATE book SET book_number = book_number - 1 WHERE  book_id = 123;

购书成功

-------------------------------------------------------------------------------

而对于用户乙来说,他的动作稍微比甲慢一点点,其购买过程所触发的动作和甲相同:

-------------------------------------------------------------------------------

1. SELECT book_number FROM book WHERE  book_id = 123;

这个时候,甲刚刚进行完第一步的操作,还没来得及做第二步操作,所以book_number一定大于零

2. UPDATE book SET book_number = book_number - 1 WHERE  book_id = 123;

购书成功

-------------------------------------------------------------------------------

表面上看甲乙的操作都成功了,他们都买到了书,但是库存只有一本,他们怎么可能都成功呢?再看看数据表里book_number的内容,已经变成“-1”了,这当然是不能允许的(实际上,声明这样的列类型应该加上unsigned的属性,以保证其不能为负,这里是为了说明问题所以没有这样设置)

好了,问题陈述清楚了,再来看看怎么利用事务来解决这个问题,打开MySQL手册,可以看到想用事务来保护你的SQL正确执行其实很简单,基本就是三个语句:开始,提交,回滚。

-------------------------------------------------------------------------------

开始:START TRANSACTION或BEGIN语句可以开始一项新的事务

提交:COMMIT可以提交当前事务,是变更成为永久变更

回滚:ROLLBACK可以回滚当前事务,取消其变更

此外,SET AUTOCOMMIT = {0 | 1}可以禁用或启用默认的autocommit模式,用于当前连接。

-------------------------------------------------------------------------------

那是不是只要用事务语句包一下我们的SQL语句就能保证正确了呢?比如下面代码:

-------------------------------------------------------------------------------

BEGIN;

SELECT book_number FROM book WHERE  book_id = 123;

// ...

UPDATE book SET book_number = book_number - 1 WHERE  book_id = 123;

COMMIT;

-------------------------------------------------------------------------------

答案是否定了,这样依然不能避免问题的发生,如果想避免这样的情况,实际应该如下:

-------------------------------------------------------------------------------

BEGIN;

SELECT book_number FROM book WHERE  book_id = 123 FOR UPDATE;

// ...

UPDATE book SET book_number = book_number - 1 WHERE  book_id = 123;

COMMIT;

-------------------------------------------------------------------------------

由于加入了FOR UPDATE,所以会在此条记录上加上一个行锁,如果此事务没有完全结束,那么其他的事务在使用SELECT ... FOR UPDATE请求的时候就会处于等待状态,直到上一个事务结束,它才能继续,从而避免了问题的发生,需要注意的是,如果你其他的事务使用的是不带FOR UPDATE的SELECT语句,将得不到这种保护。

最后看看PHP + MySQL事务操作的代码演示:

实际LAMP应用中,一般PHP使用AdoDB操作MySQL,下面给出AdoDB相应的代码方便大家查阅:

-------------------------------------------------------------------------------

<?php
// ...

$adodb->startTrans
();

//实际,getOne所调用的查询也可以直接放到rowLock来进行,这里只是为了演示效果能更明显些。

$adodb->rowLock('book''book_id = 123'
);

$bookNumber $adodb->getOne("SELECT book_number FROM book WHERE  book_id = 123"
);

$adodb->execute("UPDATE book SET book_number = book_number - 1 WHERE  book_id = 123"
);

$adodb->completeTrans
();

// ...
?>

-------------------------------------------------------------------------------

其中,rowLock的方法就是调用的FOR UPDATE来实现的行锁,你可能会想把“FOR UPDATE”直接写到$adodb->getOne()调用的那条SQL语句里面去实现行锁的功能,不错,那样确实可以,但是并不是所有的数据库都使用“FOR UPDATE”语法来实现行锁功能,比如Sybase使用“HOLDLOCK”的语法来实现行锁功能,所以为了你的数据库抽象层保持可移植性,我还是劝你用rowLock来实现行锁功能,至于可移植性就交给AdoDB好了,嗯,有点扯远了,今儿就说到这里了。

-------------------------------------------------------------------------------

附:

AdoDB中存在一个setTransactionMode()方法,能够设置事务的隔离级别,如下:

SetTransactionMode allows you to pass in the transaction mode to use for all subsequent transactions for that connection session. Note: if you have persistent connections and using mysql or mssql, you might have to explicitly reset your transaction mode at the beginning of each page request. This is only supported in postgresql, mssql, mysql with InnoDB and oci8 currently. For example:

$db->SetTransactionMode("SERIALIZABLE");
$db->BeginTrans();
$db->Execute(...); $db->Execute(...);
$db->CommiTrans();

$db->SetTransactionMode(""); // restore to default
$db->StartTrans();
$db->Execute(...); $db->Execute(...);
$db->CompleteTrans();

Supported values to pass in:

    * READ UNCOMMITTED (allows dirty reads, but fastest)
    * READ COMMITTED (default postgres, mssql and oci8)
    * REPEATABLE READ (default mysql)
    * SERIALIZABLE (slowest and most restrictive)

You can also pass in database specific values such as 'SNAPSHOT' for mssql or 'READ ONLY' for oci8/postgres.

 
2006年07月26日 星期三 上午 11:29

欢迎访问火丁笔记:http://huoding.com/

火丁的订阅地址:http://huoding.com/feed

作者:老王

PHP程序员应该都知道连接MySQL数据库可以使用mysql_pconnect(永久连接)函数,使用数据库永久连接可以提高效率,但是实际应用中数据库永久连接往往会导致出现一些问题,通常的表现就是在大访问量的网站上时常发生断断续续的无法连接数据库的情况,出现类似"Too many connections in ..."的错误提示信息,重新启动服务器又正常了,但过不了一会儿又出现同样的故障。对于这些问题的成因,恐怕就不是每个人都能说清楚的了,虽然PHP文档里有一些相关资料,但是解释的并不浅显易懂,这里我厚着脸皮试图做一个简单的讨论,所述观点不见得全都正确,欢迎大家反馈意见。

首先看看数据库永久连接的定义:

永久的数据库连接是指在脚本结束运行时不关闭的连接。当收到一个永久连接的请求时。PHP 将检查是否已经存在一个(前面已经开启的)相同的永久连接。如果存在,将直接使用这个连接;如果不存在,则建立一个新的连接。所谓“相同”的连接是指用相同的用户名和密码到相同主机的连接。

PHP使用永久连接方式操作MySQL是有前提的:就是PHP必须安装为多线程或多进程Web服务器的插件或模块。最常见的形式是把PHP用作多进程Apache服务器的一个模块。对于一个多进程的服务器,其典型特征是有一个父进程和一组子进程协调运行,其中实际生成Web页面的是子进程。每当客户端向父进程提出请求时,该请求会被传递给还没有被其它的客户端请求占用的子进程。这也就是说当相同的客户端第二次向服务端提出请求时,它将有可能被一个不同的子进程来处理。在开启了一个永久连接后,所有不同子进程请求SQL服务的后继页面都能够重新使用这个已经建立的 SQL服务器连接。它使得每个子进程在其生命周期中只做一次连接操作,而非每次在处理一个页面时都要向 SQL 服务器提出连接请求。每个子进程将对服务器建立各自独立的永久连接。PHP本身并没有数据库连接池的概念,但是Apache有进程池的概念, 一个Apache子进程结束后会被放回进程池, 这也就使得用mysql_pconnect打开的的那个mysql连接资源可以不被释放,而是依附在相应的Apache子进程上保存到了进程池中。于是在下一个连接请求时它就可以被复用。一切看起来似乎都很正常,但是在Apache并发访问量大的时候,如果使用mysql_pconnect,会由于之前的Apache子进程占用的MySQL连接没有close, 很快使MySQL达到最大连接数,使得之后的请求可能得不到响应。

上面的部分文字是摘抄自PHP文档,看起来可能还是有些文绉绉的不好理解,那么我就用大白话再举一个例子来说明问题:

假设Apache配置最大连接数为1000,MySQL配置最大连接数为100,当Apache服务器接到200个并发访问的时候,其中100个涉及到数据库访问,剩下的100个不涉及数据库访问,因为这个时候还不存在可用的数据库连接,所以这里面涉及到数据库访问的100个并发会同时产生100个数据库永久连接,达到了数据库最大连接数,当这些操作没有结束的时候,任何其他的连接都无法再获得数据库连接,当这些操作结束了,相应的连接会被放入进程池,此时Apache的进程池里就有了200个空闲的子进程,其中100个是带有数据库连接的,由于Apache会为访问请求随机的挑选空闲子进程,所以你得到的子进程很可能是不包含数据库连接的那100个中的一个,而数据库连接已经达到了最大值,你也不可能成功的建立新的数据库连接,唉,你便只好不停的刷新页面,哪个时候运气好,碰巧分配到了带有数据库连接的子进程,才能正常浏览页面。如果是大访问量的网站来说,任何时候都可能存在大量的并发,所以浏览者可能就会不停的发现无法连接数据库的现象了。

或许你会说,我们把Apache和MySQL的最大连接数调成一样大不就可以了么?是的,合理的调整这个最大连接数某种程度上会避免这个问题的发生,但是Apache和MySQL的负载能力是不同的,如果按照Apache的负载能力来设置,对于MySQL来说,这个最大连接数就偏大,会产生大量的MySQL数据库永久连接,打个比方,就好像和平时代还要养活一个几百万的军队一样,其开销得不偿失;而如果按照Mysql的负载能力设置,对于Apache来说,这个最大连接数就偏小,有点杀鸡牛刀的感觉,无法发挥Apache的最大效率。

所以按照PHP手册上的介绍,只适合在并发访问不大的网站上使用数据库永久连接,但对于一个并发访问不大的网站来说,使用数据库永久连接带来的效率提高似乎没有太大的意义,从这个角度上来看,我觉得PHP中的数据库永久连接基本上是一个鸡肋的角色,如果你一定要使用数据库连接池的概念,可以尝试一下sqlrelay或者Apache本身提供的mod_dbd,说不定会有惊喜。

 
   
 
 
文章存档
 
     
 
最新文章评论
  

[表情]
 

不错!
 

linux大师之路,www.linuxmr.com
 

引导一直没有整明白说。
 

[表情]
   
帮助中心 | 空间客服 | 投诉中心 | 空间协议
©2012 Baidu