Apr18

利用Enterprise Architect识别PHP输出UML图

Author: leeon  Click: 15775   Comments: 3 Category: 软件工程  Tag: Enterprise Architect,uml,类图
今天研究了如何将PHP代码快速转换成UML的类图,本想通过Zend的插件UML2来实现的,但是这龟速太慢了,没办法只能另辟蹊径。


Enterprise Architect是一个对于软件系统开发有着极好支持的CASE软件(Computer Aided Software Engineering)。EA不同于普通的UML画图工具(如VISIO),它将支撑系统开发的全过程。在需求分析阶段,系统分析与设计阶段,系统开发及部署等方面有着强大的支持,同时加上对10种编程语言的正反向工程,项目管理,文档生成,数据建模等方面。可以让系统开发中各个角色都获得最好的开发效率。


刚开始使用的时候发现在反向工程读取代码老出现乱码,严重影响识别代码。进过研究发现,第一次安装好Enterprise Architect后一定要设置一下读取代码的编码。我写的代码都是UTF-8的,因此整个设置过程如下:
1. 首先“工具->选项”设置页面编码,根据你的代码编码来设置


2. 设置后新建一个工程,然后根据你的需要新建新模型,我的是新建类图。

3. 选择class,并点击确定。在工程中右键点击Class Model。

4.选择导入的文件即可,注意箭头所示即可:

5.新图表配置可以按需配置即可。
Apr14

file_get_contents超时问题的解决方法

Author: leeon转载  Click: 10122   Comments: 1 Category: php  Tag: php,超时,timeout

一、增加超时的时间限制

这里需要注意:set_time_limit只是设置你的PHP程序的超时时间,而不是file_get_contents函数读取URL的超时时间。
我一开始以为set_time_limit也能影响到file_get_contents,后来经测试,是无效的。真正的修改file_get_contents延时可以用resource $context的timeout参数:

[code="php"]
$opts = array(
'http'=>array(
'method'=>"GET",
'timeout'=>60,
)
);
$context = stream_context_create($opts);
$html =file_get_contents('http://www.example.com', false, $context);
[/code]

二、一次有延时的话那就多试几次

有时候失败是因为网络等因素造成,没有解决办法,但是可以修改程序,失败时重试几次,仍然失败就放弃,因为file_get_contents()如果失败将返回 FALSE,所以可以下面这样编写代码:

[code="php"]
$cnt=0;
while($cnt < 3 && ($str=@file_get_contents('http...'))===FALSE) $cnt++;
[/code]

以上方法对付超时已经OK了。那么Post呢?细心点有人发现了'method'=>"GET", 对!是不是能设置成post呢?百度找了下相关资料,还真可以!而且有人写出了山寨版的post传值函数,如下:

[code="php"]
function Post($url, $post = null)
{
$context = array();

if (is_array($post))
{
ksort($post);

$context['http'] = array
(

'timeout'=>60,
'method' => 'POST',
'content' => http_build_query($post, '', '&'),
);
}

return file_get_contents($url, false, stream_context_create($context));
}

$data = array
(
'name' => 'test',
'email' => 'test@gmail.com',
'submit' => 'submit',
);

echo Post('http://www.yifu.info', $data);

[/code]

Apr3

Berkeley DB 由浅入深【转自架构师杨建】

Author: 杨建  Click: 29085   Comments: 1 Category: 数据库  Tag: Berkeley,DB,高性能数据库
在网上看到不少介绍Berkeley DB的文章,几乎所有的中文文章都是介绍完入门就再也没了。大都是个概括。最近做这个,所以想系统的由浅入深的介绍一下。不清楚的地方可以和我讨论,或参照官方网站sleepycat上的文档。我用的是最新版本 db-4.4.16.NC.tar.gz,这个包中含有详细的英文文档。
为什么要使用Berkeley DB,它适合什么场合应用?
Berkeley DB并不适合所有的应用,因为简单,专一所以高效。
嵌入式数据库,的“嵌入”是指它内嵌在程序中,而不是说他只应用在嵌入式系统上。它的特点很适合应用于嵌入式系统上。当然在我们的pc机集群或大型服务器上,也可以灵活的配置,完成更艰巨的任务。
它适合于管理海量的,简单的数据。Google用Berkeley DB HA (High Availability) 来管理他们的帐户信息. Motorola在他的无线产品中用Berkeley DB跟踪移动单元。hp,microsoft,Sun Microsystems...等也都是它的大客户。它不能完全取代关系数据库,但在某些方面,它却有他们望尘莫及的高效性。
性能测试,在如下的配置上:
Linux – SuSE Linux 9.1 running on an AMD Athlon 64 processor 3200+ at 1GHz system with 1GB of RAM。
每秒钟,单条记录读操作 1,002,200次。单条记录写操作 766,034次。 用bulk APIs能进行读操作 13,501,800次。当然这些都是发生在内存中的操作,因为bdb使用了cache。  性能测试具体数据可以参考官方网站的Performance Metrics & Benchmarks: Berkeley DB。
Berkeley DB简介
Berkeley DB可以说是一个专为程序员准备的数据库。我的文章中只针对c程序员介绍的。它还支持C++、Java、Perl、Tcl、Python和PHP等。原理和接口都差不多。它的安装很简单。
cd  build_unix
../dist/configure
make
make install
这几步就ok了,其实也就是把头文件和编译好的db库放到指特定位置。甚至可以不用make install,直接在编译你的程序时用-I -L -ldb 指定头文件和连接库的位置。可以完全把它当作一个函数库来用。由db库透明的来完成对数据的管理。无论是系统中的多个进程,或者是相同进程中的多个线程,都可以在同一时间调用访问数据库的函数。而底层的数据加锁、事务日志和存储管理等都在Berkeley DB函数库中实现。他不像传统的数据库那样有client和server,还专门跑几个进程。所以应用程序不需要事先同数据库服务建立起网络连接,而是通过内嵌在程序中的Berkeley DB函数库来完成对数据的保存、查询、修改和删除等操作。
Berkeley DB函数库本身虽然只有300KB左右,但却能够用来管理多达256TB的数据,并且在许多方面的性能还能够同商业级的数据库系统相抗衡。就拿对数据的并发操作来说,Berkeley DB能够很轻松地应付几千个用户同时访问同一个数据库的情况。因而,在资源受限的嵌入式系统上进行数据库管理,Berkeley DB也是一个不错的选择。
Berkeley DB为何高效?
Berkeley DB作为一种嵌入式数据库系统在许多方面有着独特的优势。首先,由于其应用程序和数据库管理系统运行在相同的进程空间当中,进行数据操作时可以避免繁琐的进程间通信包括建立socket连接等,因此耗费在通信上的开销自然也就降低到了极低程度。其次,Berkeley DB使用简单的函数调用接口来完成所有的数据库操作,而不是在数据库系统中经常用到的SQL语言。这样就避免了对结构化查询语言进行解析和处理所需的开销。
基本概念
关键字/数据 是Berkeley DB用来进行数据库管理的基础。每个 Key/Data 对构成一条记录。而整个数据库实际上就是由许多这样的结构单元所构成的。通过使用这种方式,开发人员在使用Berkeley DB提供的API来访问数据库时,只需提供关键字就能够访问到相应的数据。当然也可以也可以提供 Key 和部分Data来查询符合条件的相近数据。
一个例子来完成入门
使用过rdb的人相信都能看的懂下面的例子。简要的说一下下面持续完成的功能。作为一个简单的例子environment部分可以不是必要的,我把它的用法也一起加了进来。创建一个environment指明要把数据库文件创建到哪个目录下面。创建数据库,打开数据库,写一个记录进去,然后读出记录,然后将写入的记录删除,然后关闭environment和数据库。会了这些基本操作,你就可以使用bdb完成简单的应用了。
....................................................................

#include
#include
#include
#include
//only this head should include for use bdb.
#include   
#define DATABASE "yangjian.db"

int main()
{
        DB_ENV *myEnv;
        DB *dbp;
        DBT key, data;
        int ret,t_ret;
        u_int32_t env_flags;
        //........... Create an environment object and initialize it for error reporting
        ret = db_env_create(&myEnv, 0);
        if (ret != 0)
        {
                fprintf(stderr, "Error creating env handle: %s\n", db_strerror(ret));
                return -1;
        }
        //........If the environment does not exist create it. Initialize the in-memory cache.
        env_flags = DB_CREATE | DB_INIT_MPOOL;
        //........Open the environment.
        ret = myEnv->open(myEnv,"/home/yangbin1/yangjian/my/db/testevn",env_flags,0);
        if (ret != 0)
        {
                fprintf(stderr, "Environment open failed: %s", db_strerror(ret));
                return -1;
        }
        if ((ret = db_create(&dbp, myEnv, 0)) != 0)
        {
                fprintf(stderr, "db_create: %s\n", db_strerror(ret));
                exit (1);
        }

        if ((ret = dbp->open(dbp, NULL, DATABASE, NULL, DB_BTREE, DB_CREATE, 0664)) != 0)
        {
                dbp->err(dbp, ret, "%s", DATABASE);
                exit (1);
        }
        memset(&key, 0, sizeof(key));
        memset(&data, 0, sizeof(data)); key.data = "sport";
        key.size = sizeof("sport");
        data.data = "football";
        data.size = sizeof("football");
/*
        //......put data
        if ((ret = dbp->put(dbp, NULL, &key, &data, 0)) == 0)
        {
                printf("db: %s: key stored.\n", (char *)key.data);
        }
         else
        {
                dbp->err(dbp, ret, "DB->put");
        }
*/

        //........put data NOOVERWRITE
        if ((ret = dbp->put(dbp, NULL, &key, &data, DB_NOOVERWRITE)) == 0)
        printf("db: %s: key stored.\n", (char *)key.data);
        else dbp->err(dbp, ret, "DB->put");

        //.......get data
        if ((ret = dbp->get(dbp, NULL, &key, &data, 0)) == 0)
        printf("db: %s: key retrieved: data was %s.\n", (char *)key.data, (char *)data.data);
        else
        dbp->err(dbp, ret, "DB->get");

        //......del data
        if((ret = dbp->del(dbp, NULL, &key, 0)) == 0)
        printf("db: %s: key was deleted.\n", (char *)key.data);
        else
        dbp->err(dbp, ret, "DB->del");

        //.........close, only when the db successful closed,the data can real write to the disk.
        //if ((t_ret = dbp->close(dbp, 0)) != 0 && ret == 0)
        //ret = t_ret;
        //exit(ret);
        if (dbp != NULL)
        dbp->close(dbp, 0);
        //.........close evn
        //........When you are done with an environment, you must close it.
        //........Before you close an environment, make sure you close any opened databases
        if (myEnv != NULL)
        myEnv->close(myEnv, 0);

        return 0;
}


Hash or Btree?

Hash 和 Btree方法应该被用于当逻辑记录号不是用来做主键对数据访问的情况。(如果逻辑记录号是一个secondary key,用来对数据进行访问,Btree方法是一个可能的选择,因为它支持通过一个键和一个记录号来同时的访问。)

Btrees中的键是按一定的秩序来存放的。Btrees应该被用于那些keys存在某种关系的时候。例如用时间做keys,当现在访问8AM时间戳的时候,可能下一个就访问9AM时间戳。也就是在排列顺序中附近的(near)。再比如,用names做keys,我们也许要访问那些有相同last name的,Btrees仍然是一个不错的选择。

在小的数据设置上,Hash 和 Btree在性能表现上没什么差别。在那儿,所有的,或大部分数据设置被放在了cache里面。

尽管如此,当一个一数据设置足够大的时候,会有一些重要的数据页再也装不进cache里了。这种情况下,我们上面讨论的btree在性能表现上就很重要了。
例如,因为在hash中没有排列顺序中附近的机制。所以,cache在Btree中通常比Hash中更有效。Btree方法将产生更少的I/O调用。

尽管如此,当一个数据设置更大的时候,hash访问方法能赢过btree方法。原因是btree比hash数据库包含了更多的元数据页。
数据设置可以变的非常大,以至于元数据开始支配整个cache。如果这种事情发生,Btree将不得不对每次请求都进行一次I/O操作。Cache中几乎没有地方再放置那些真正的数据页了,失去了cache的意义。而因为hash有很少的元数据,可以它的cache照样可以用来放置那些数据页,起到cahche的作用。

当一个数据更更大的时候,以至于每个随机请求,hash和btree几乎都要进行一次I/O操作的时候。在这中情况下,实际上hash只要遍历少树几个内部页(internal pages)就差不多能找到,所以这也是hash在性能上的一个优势。
应用程序对数据的访问式样也极大的影响这些行为。例如,延着光标往下遍历的话,每次I/O操作到cache中的数据,将满足接下来的很多数据请求。

如果数据设置只是比cache大一点,我们还是条件使用Btree,如果你实在有太大的数据设置,hash也许会更好一些。db_stat公用程序是一个有用的工具,用来监视,你的cache表现的怎么样。
总结:
其实到这你应该能看出来,btree是在数据不是很大的时候是很优秀的,在更大的时候,由于元数据占用太多cache的原因,导致性能下降,落后与hash了,而不是说hash能超过它。所以能在元数据占用cache不是太多以前,也就是你的cache足够大,使用btree只最好的选择。当然,如果每次访问的数据都是随机的没有什么次序,也不是near的,那用btree也没什么优势了。

针对我们的应用我只讨论了 Hash or Btree?。Queue or Recno?我就不再讨论了。
选择一个页的大小:
太大了会产生很多不必要的i/o,而且影响并发性,因为Btree, Hash and Recn都是对页上锁。太小了会使用溢出页,大量使用溢出页会严重影响性能。所以一般
页的大小都选择和文件系统的I/O块,大小相等。
选择一个cache大小:
要设置的足够大,至少能满足一次操作的数据。如果你的cache设的太小,每个新页将要强迫换出least-recently-used page。
Berkeley DB将要重新读一次树的root page对于每次数据库请求。当然cache也不是越大越好,当cache大小增长到一个特定的点时,再增加就不会对性能有什么提高了。当到达这个点时,两件事情发生了。Cache足够大以至于,几乎所有的请求都不用再访问磁盘了就能从cache中得到信息。或则是你的应用程序做一些确实很随机的访问,因此再增加cache对于下一个请求也不会有什么性能上的提高了。第二种情况发生的概率很小,因为几乎所有的应用,都显示了一些,请求的相关联性。
如果cache设定的超过了操作系统的能力,将会使用交换分区,频繁换入换出,会很影响性能。


觉得有必要先把DBT结构放在这。方便后面看。
typedef struct {
 void *data;
 u_int32_t size;
 u_int32_t ulen;
 u_int32_t dlen;
 u_int32_t doff;
 u_int32_t flags;
} DBT;
1. 数据对齐
Berkeley DB没有为以DBT为参数的,返回的data/key对,或回调函数的字节对齐提供任何保证。
应用程序有责任对齐任何需要对齐的。DB_DBT_MALLOC, DB_DBT_REALLOC 和 DB_DBT_USERMEM标志可能被用来对齐存储在内存中的返回项。

2. 在bulk中取回数据
当从数据库中取回大量记录的时候,那些方法调用经常影响性能。Berkeley DB提供bulk取数据接口,它能有效的提高一些应用持续的性能要使用bulk,必须先为DB->get或DBcursor->c_get指定一个buffer。这个在c api中的实现是通过设置DBT结构的data和ulen域还有flag域被设为DB_DBT_USERMEM来引用应用程序的buffer。DB_MULTIPLE或DB_MULTIPLE_KEY 需要指定给DB->get或 DBcursor->c_get方法, 以使多条记录被返回到指定的buffer中。这两个标志的区别请看手册。
下面函数只看红色标出部分就可以了。示范如何使用bulk。
...................................................................................
int rec_display(DB *dbp)
{
 DBC *dbcp;
 DBT key, data;
 size_t retklen, retdlen;
 char *retkey, *retdata;
 int ret, t_ret;
 void *p;
 memset(&key, 0, sizeof(key));
 memset(&data, 0, sizeof(data));
 /* Review the database in 5MB chunks. */
#define BUFFER_LENGTH (5 * 1024 * 1024)
 if ((data.data = malloc(BUFFER_LENGTH)) == NULL)
  return (errno);
 data.ulen = BUFFER_LENGTH;
 data.flags = DB_DBT_USERMEM;
 /* Acquire a cursor for the database. */
 if ((ret = dbp->cursor(dbp, NULL, &dbcp, 0)) != 0) {
  dbp->err(dbp, ret, "DB->cursor");
  free(data.data);
  return (ret);
 }
 for (;;) {
  /*
   * Acquire the next set of key/data pairs.  This code does
   * not handle single key/data pairs that won't fit in a
   * BUFFER_LENGTH size buffer, instead returning DB_BUFFER_SMALL
   * to our caller.
   */
  if ((ret = dbcp->c_get(dbcp,
      &key, &data, DB_MULTIPLE_KEY | DB_NEXT)) != 0) {
   if (ret != DB_NOTFOUND)
    dbp->err(dbp, ret, "DBcursor->c_get");
   break;
  }
  for (DB_MULTIPLE_INIT(p, &data);;) {
   DB_MULTIPLE_KEY_NEXT(p,
       &data, retkey, retklen, retdata, retdlen);
   if (p == NULL)
    break;
   printf("key: %.*s, data: %.*s\n",
       (int)retklen, retkey, (int)retdlen, retdata);
  }
 }
 if ((t_ret = dbcp->c_close(dbcp)) != 0) {
  dbp->err(dbp, ret, "DBcursor->close");
  if (ret == 0)
   ret = t_ret;
 }
 free(data.data);
 return (ret);
}
................................................................................................
3. 记录的部分的存储和取回
在Berkeley DB的访问方法中,可以只存储或取回数据项的某一部分。这个通过设置DBT结构的DB_DBT_PARTIAL 标志来实现。
同时还要设置DBT的其他几个值。
doff 数据开始处
dlen 数据长度
例如,如果数据项是ABCDEFGHIJKL, doff的值为3是指从字节D开始。dlen为4,是指随后的4个字节DEFG。
取回记录:
当从一个数据库中取回一个数据项时,从doff位置开始的dlen字节,被返回。如果被指定的那些字节不存在,其他存在的字节将被返回。
存储记录:
下面的例子初始化数据项字节长度都是20: ABCDEFGHIJ0123456789
1,
size = 20
doff = 0
dlen = 20
data = abcdefghijabcdefghij
Result: The 20 bytes at offset 0 are replaced by the 20 bytes of data;
that is, the entire record is replaced.
ABCDEFGHIJ0123456789 -> abcdefghijabcdefghij
2,
size = 10
doff = 2
dlen = 15
data = abcdefghij
Result: The 15 bytes at offset 2 are replaced by the 10 bytes of data.
ABCDEFGHIJ0123456789 -> ABabcdefghij789
2,
size = 10
doff = 25
dlen = 0
data = abcdefghij
Result: The 0 bytes at offset 25 are replaced by the 10 bytes of data;
that is, 10 bytes are inserted into the record past the end of the
current data (\0 represents a nul byte).
ABCDEFGHIJ0123456789 -> ABCDEFGHIJ0123456789\0\0\0\0\0abcdefghij
其实就是字符串替换,把数据库中某条记录的某部分替换成指定的字符串,长度可以自动根据被替换的字符串大小进行调整。可伸缩的。


The big picture
前面几章讲了用访问方法快速的存储和取回数据。后面主要讲任何访问方法的应用,它们是线性的和可恢复的在面对系统故障时。
Berkeley DB 底层体系结构:
 
如上图,应用程序调用访问方法,而访问方法使用底层的共享内存cache放置最近用过的文件页面。
当应用程序需具备恢复能力的时候,它们调用的访问方法必须预先封装在事务字系统中。程序告诉bdb事务的开始和结束点。必须准备面对特殊情况下可能的失败,导致事务异常终止。
一个例子说明具有事务保护的代码的大致样子:
for (fail = 0;;) { /* Begin the transaction. */ if ((ret = dbenv->txn_begin(dbenv, NULL, &tid, 0)) != 0) { dbenv->err(dbenv, ret, "dbenv->txn_begin"); exit (1); } 

/* Store the key. */ switch (ret = dbp->put(dbp, tid, &key, &data, 0)) { case 0: /* Success: commit the change. */ printf("db: %s: key stored.\n", (char *)key.data); if ((ret = tid->commit(tid, 0)) != 0) { dbenv->err(dbenv, ret, "DB_TXN->commit"); exit (1); } return (0); case DB_LOCK_DEADLOCK: default: /* Failure: retry the operation. */ if ((t_ret = tid->abort(tid)) != 0) { dbenv->err(dbenv, t_ret, "DB_TXN->abort"); exit (1); } if (fail++ == MAXIMUM_RETRY) return (ret); continue; } }

 


    Berkeley DB由五个主要的子系统构成.包括: 存取管理子系统、内存池管理子系统、事务子系统、锁子系统以及日志子系统。其中存取管理子系统作为Berkeley DB数据库进程包内部核心组件,而其他子系统都存在于Berkeley DB数据库进程包的外部。每个子系统支持不同的应用级别。

1.数据存取子系统
  数据存取(Access Methods)子系统为创建和访问数据库文件提供了多种支持。Berkeley DB提供了以下四种文件存储方法:
哈希文件、B树、定长记录(队列)和变长记录(基于记录号的简单存储方式),应用程序可以从中选择最适合的文件组织结构。程序员创建表时可以使用任意一种结构,并且可以在同一个应用程序中对不同存储类型的文件进行混合操作。

    在没有事务管理的情况下,该子系统中的模块可单独使用,为应用程序提供快速高效的数据存取服务。
数据存取子系统适用于不需事务只需快速格式文件访问的应用。

 2.内存池管理子系统
    内存池(Memory pool)子系统对Berkeley DB所使用的共享缓冲区进行有效的管理。它允许同时访问数据库的多个进程或者
进程的多个线程共享一个高速缓存,负责将修改后的页写回文件和为新调入的页分配内存空间。它也可以独立于Berkeley DB系统之外,单独被应用程序使用,为其自己的文件和页分配内存空间。内存池管理子系统适用于需要灵活的、面向页的、缓冲的共享文件访问的应用。

 3.事务子系统
  事务(Transaction)子系统为Berkeley DB提供事务管理功能。它允许把一组对数据库的修改看作一个原子单位,这组操作要么全做,要么全不做。在默认的情况下,系统将提供严格的ACID事务属性,但是应用程序可以选择不使用系统所作的隔离保证。该子系统使用两段锁技术和先写日志策略来保证数据库数据的正确性和一致性。它也可以被应用程序单独使用来对其自身的数据更新进行事务保护。事务子系统适用于需要事务保证数据的修改的应用。
   
 4.锁子系统
    锁(Locking)子系统为Berkeley DB提供锁机制,为系统提供多用户读取和单用户修改同一对象的共享控制。
数据存取子系统可利用该子系统获得对页或记录的读写权限;事务子系统利用锁机制来实现多个事务的并发控制。
   
    该子系统也可被应用程序单独采用。锁子系统适用于一个灵活的、快速的、可设置的锁管理器。
   
5.日志子系统   
 日志(Logging)子系统采用的是先写日志的策略,用于支持事务子系统进行数据恢复,保证数据一致性。
它不大可能被应用程序单独使用,只能作为事务子系统的调用模块。

   以上几部分构成了整个Berkeley DB数据库系统。各部分的关系如下图所示:


   
    在这个模型中,应用程序直接调用的是数据存取子系统和事务管理子系统,这两个系统进而调用更下层的内存管理子系统、
锁子系统和日志子系统。
   
    由于几个子系统相对比较独立,所以应用程序在开始的时候可以指定哪些数据管理服务将被使用。可以全部使用,也可以只用其中的一部分。例如,如果一个应用程序需要支持多用户并发操作,但不需要进行事务管理,那它就可以只用锁子系统而不用事务。有些应用程序可能需要快速的、单用户、没有事务管理功能的B树存储结构,那么应用程序可以使锁子系统和事务子系统失效,这样就会减少开销。   

Programming model
它直接链接到应用程序中,与应用程序运行于同样的地址空间中。
Programmatic APIs
DB为多种编程语言提供了API接口,其中包括C、C++、Java。
值得一提的是bdb提供dbm样式的接口,以前使用unix Dbm/Ndbm的,只需要换个头文件#include ,重新编译一下,db效率将成倍的提高。当然我们也可以使用dbm样式的接口编写简单的应用程序,这种接口比较简洁。
也为脚本语言Perl、Tcl、Python和PHP提供了接口。
对apache也以module的方式提供了接口,安装后,可以在写apache api时候直接调用。除了几个函数不一样外,其他都相同。
bdb提供的公用程序:
db_archive
打印出不再使用的日志文件路径名
db_checkpoint
监视和检查数据库日志的守护进程
db_deadlock
当死锁发生时,退出锁定要求
db_dump
把数据库文件转换成db_load能认出的文本文件
db_load
从db_dump产生的文本文件中创建出数据库文件
db_printlog
把数据库日志文件转换成人能读懂的文本
db_recover
在发生错误后,把数据库恢复到一致的状态
db_stat
显示数据库环境统计
db_upgrade
把数据库文件转换成新版本的Berkley DB格式
db_verify
对数据库文件进行一致性检查
db库
许多程序中与db相关的函数都将使db库。



Database environment introduction

Berkeley DB 环境用来封装一个或多个数据库,日志文件和区域文件。区域文件是共享内存区,它里面包括数据库环境信息像内存池cache页等。只有数据库文件可以在不同的字节序机器间移动,日志文件只能在相同的字节序机器间移动。而区域文件(Region files)常常对于一个特定的机器来说是独一无二的,可能只能在指定的操作系统的某个版本上移动间移动。
一个环境可以被很多进程和线程共享。一个环境包含其它目录的资源也是可能的。应用程序经常选择把资源分布到其他目录或磁盘来提高性能或其他原因。尽管如此,默认的,数据库,共享区(锁,日志,内存池和事务共享内存区域)和日志文件将存储在同一个同层次目录中。
意识到所有应用程序共享一个数据库环境默认的相信彼此非常重要。他们能访问对方的数据,因为那些数据在同一个共享内存区,他们也共享资源像buffer空间和锁。与此同时,任何应用程序使用同一个数据库,必须共享一个环境,如果想在他们之间保持一致性的话。

Creating a database environment

环境对于bdb的可移植性和灵活性是非常重要的。为了增强可移植性,和快速灾难恢复,建议尽量使用相对路径。建议使用配置文件放置环境参数而不要直接写到程序里,可以避免每次移植时修改和编译源文件。

bdb环境由db_env_create 和 DB_ENV->open接口创建和描述,再需要定制的地方,比如把log文件存储到不同的磁盘驱动器里,或选择一个特殊cache大小, 应用程序描述这些定制信息通过创建配置文件,或者传参数给其他DB_ENV 处理函数。

 一旦一个环境被创建,被指定相对路径的数据库文件,都将相对与环境的home目录来创建。用相对目录允许整个环境轻易的移动。简化了在不同目录和不同系统中重建和恢复的步骤。

应用程序首先通过db_env_create方法获得一个环境句柄,然后调用DB_ENV->open来创建或合并数据库环境。这儿有很多选项你可以在调用DB_ENV->open时设置来定制你的环境。这些选项大致可以分为四类:

子系统初始化选项: 这些标志指明哪些bdb子系统将因为环境被初始化,和哪些操作将自动发生当数据库在环境中被访问的时候。这些标志包括DB_INIT_CDB, DB_INIT_LOCK, DB_INIT_LOG, DB_INIT_MPOOL, and DB_INIT_TXN。The DB_INIT_CDB标志为bdb并发数据存储做初始化工作。其他标志初始化单个子系统;也就是说,当DB_INIT_LOCK被指定,应用程序读写在这个环境中打开的数据库时,将使用locking子系统以确保它们不覆盖对方的对数据的改动。

恢复选项:这些包括DB_RECOVER 和 DB_RECOVER_FATAL选项,他们表明在环境被打开要作正常用途使用前,恢复(recovery)将要进行。

命名选项:这包括DB_USE_ENVIRON 和 DB_USE_ENVIRON_ROOT,修改如何在环境中给文件命名。
混杂选项:例如DB_CREATE选项使底层数据库文件被创建是必需的。更多的应用还指定
仅仅DB_INIT_MPOOL标志或者指定其它所有4个子系统的初始化标志(DB_INIT_MPOOL, DB_INIT_LOCK, DB_INIT_LOG, and DB_INIT_TXN)。
以前的配置只是想简单的用一些基本的访问方法接口用一个共享底层缓冲池,但是没有关心当应用程序或系统出现故障时的可恢复性。以后是一些需要提供可恢复性的应用。也有一些很稀少的情况下,其它的初始化标志组合成为可能。
DB_RECOVER在当应用程序想在运行的时做一些必需的数据库恢复的时候被指定。也就是说,是否在上次运行时,系统或应用程序出现了故障,想在再次运行前使数据恢复到可用状态。不过,在没有任何数据需要恢复的情况下,指定这个标志也不为错。
DB_RECOVER_FATAL标志有更特殊的用途。它执行灾难性的数据库恢复,通常需要做一些初始化的安排;也就是归档log文件被带回到文件系统。应用程序通常不指定这个标志,取而代之的是,在这种很稀有的情况下,db_recover 公用程序将会派上用场不用你自己写。
下面是一个简单的为事务程序打开一个数据库环境的例子:
DB_ENV *
db_setup(home, data_dir, errfp, progname)
 char *home, *data_dir, *progname;
 FILE *errfp;
{
 DB_ENV *dbenv;
 int ret;
 /*
  * Create an environment and initialize it for additional error
  * reporting.
  */
 if ((ret = db_env_create(&dbenv, 0)) != 0) {
  fprintf(errfp, "%s: %s\n", progname, db_strerror(ret));
  return (NULL);
 }
 dbenv->set_errfile(dbenv, errfp);
 dbenv->set_errpfx(dbenv, progname);
 /*
  * Specify the shared memory buffer pool cachesize: 5MB.
  * Databases are in a subdirectory of the environment home.
  */
 if ((ret = dbenv->set_cachesize(dbenv, 0, 5 * 1024 * 1024, 0)) != 0) {
  dbenv->err(dbenv, ret, "set_cachesize");
  goto err;
 }
 if ((ret = dbenv->set_data_dir(dbenv, data_dir)) != 0) {
  dbenv->err(dbenv, ret, "set_data_dir: %s", data_dir);
  goto err;
 }
 /* Open the environment with full transactional support. */
 if ((ret = dbenv->open(dbenv, home, DB_CREATE |
     DB_INIT_LOG | DB_INIT_LOCK | DB_INIT_MPOOL | DB_INIT_TXN, 0)) != 0) {
  dbenv->err(dbenv, ret, "environment open: %s", home);
  goto err;
 }
 return (dbenv);
err: (void)dbenv->close(dbenv, 0);
 return (NULL);
Opening databases within the environment

一旦环境被创建,数据库句柄将可能在这个环境中打开,这由db_create函数通过指定特定的环境作为参数来实现。
文件命名,数据库操作,和错误处理等都将因为这个指定的环境而被做。例如,如果DB_INIT_LOCK 或 DB_INIT_CDB 标志被指定,当环境被创建或被合并时,数据库操作将为应用程序自动的执行所有必要的锁操作。
下面是一个简单的例子,在一个环境中打开两个数据库:
DB_ENV *dbenv;
 DB *dbp1, *dbp2;
 int ret;
 dbenv = NULL;
 dbp1 = dbp2 = NULL;
 /*
  * Create an environment and initialize it for additional error
  * reporting.
  */
 if ((ret = db_env_create(&dbenv, 0)) != 0) {
  fprintf(errfp, "%s: %s\n", progname, db_strerror(ret));
  return (ret);
 }
 dbenv->set_errfile(dbenv, errfp);
 dbenv->set_errpfx(dbenv, progname);
 /* Open an environment with just a memory pool. */
 if ((ret =
     dbenv->open(dbenv, home, DB_CREATE | DB_INIT_MPOOL, 0)) != 0) {
  dbenv->err(dbenv, ret, "environment open: %s", home);
  goto err;
 }
 /* Open database #1. */
 if ((ret = db_create(&dbp1, dbenv, 0)) != 0) {
  dbenv->err(dbenv, ret, "database create");
  goto err;
 }
 if ((ret = dbp1->open(dbp1,
     NULL, DATABASE1, NULL, DB_BTREE, DB_CREATE, 0664)) != 0) {
  dbenv->err(dbenv, ret, "DB->open: %s", DATABASE1);
  goto err;
 }
 /* Open database #2. */
 if ((ret = db_create(&dbp2, dbenv, 0)) != 0) {
  dbenv->err(dbenv, ret, "database create");
  goto err;
 }
 if ((ret = dbp2->open(dbp2,
     NULL, DATABASE2, NULL, DB_HASH, DB_CREATE, 0664)) != 0) {
  dbenv->err(dbenv, ret, "DB->open: %s", DATABASE2);
  goto err;
 }
 return (0);
err: if (dbp2 != NULL)
  (void)dbp2->close(dbp2, 0);
 if (dbp1 != NULL)
  (void)dbp2->close(dbp1, 0);
 (void)dbenv->close(dbenv, 0);
 return (1);
}

Error support
db_strerror能根据一个bdb的一个错误返回值返回一个指向错误信息的指针。它可以处理系统的错误返回值也能处理bdb特有的返回值。
例如:
int ret;
if ((ret = dbenv->set_cachesize(dbenv, 0, 32 * 1024, 1)) != 0) {
 fprintf(stderr, "set_cachesize failed: %s\n", db_strerror(ret));
 return (1);
}

这儿也有两个附加的错误处理函数:DB_ENV->err 和 DB_ENV->errx。
DB_ENV->err函数追加标准错误字符串到已构造好的信息,而DB_ENV->errx不那样。
错误信息可以通过DB_ENV->set_errpfx被配置成总包含一个固定的东西,例如,应用程序名称。还可以把错误信息输入到一个指定的文件中,例如:
int ret;
dbenv->set_errfile(dbenv, errfp);
dbenv->set_errpfx(dbenv, program_name);
if ((ret = dbenv->open(dbenv, home,
    DB_CREATE | DB_INIT_LOG | DB_INIT_TXN | DB_USE_ENVIRON, 0))
    != 0) {
 dbenv->err(dbenv, ret, "open: %s", home);
 dbenv->errx(dbenv,"contact your system administrator:
   session ID was %d",session_id);
 return (1);
}
例如应用程序名为"my_app", 环境的home目录为 "/tmp/home",出错信息将是这样的:
my_app: open: /tmp/home: Permission denied.
my_app: contact your system administrator: session ID was 2

DB_CONFIG configuration file
几乎所有可以指定给DB_ENV那些方法的配置信息,也都能通过一个配置文件来指定。如果一被命名为DB_CONFIG的文件存在于数据库hone目录下,它将会一行行的按NAME VALUE的格式读入。
NAME和VALUE之间用一个或者多个空格来分割。凡是那一行的开头是空格或#的,都将被忽略为注释。
NAME VALUE具体值可以在对用的方法中查到例如DB_ENV->set_data_dir。

DB_CONFIG配置文件的目的是允许管理员定制不依赖于应用程序的环境。例如,可以移动数据库log文件和数据文件到不同的地方,而不用重新编译应用程序。另外,因为DB_CONFIG文件是当数据库环境被打开时读取的,它可以用来覆盖在那以前配置的规则。例如,可以定义一个更合理的cache大小,来覆盖以前已经编译到程序中的值。
File naming
下面介绍几种可能的为bdb指定文件命名信息的方法。
db_home: 为DB_ENV->open的db_home参数指定一个非NULL值,它的值将会用来作为数据库的home,以后的文件命名都是相对这个路径的。
DB_HOME:为环境变量DB_HOME指定值,DB_ENV->open被调用时候,读取这个值,把它作为数据库home,以后的文件命名都是相对这个路径的。
DB_ENV方法:这有三个方法可以影响文件命名。DB_ENV->set_data_dir可以为数据库文件指定一个目录。DB_ENV->set_lg_dir方法可以为log文件指定目录。DB_ENV->set_tmp_dir为创建的临时文件指定一个目录。例如,一个应用程序可以将数据文件,日志文件等分别放在不同的目录下。
DB_CONFIG文件:相同的指定给DB_ENV 方法的信息,也可以用DB_CONFIG 配置文件来指定。
我觉得指定的优先级从高到低应该是这样的:DB_ENV,DB_CONFIG,db_home,DB_HOME,default。如果以上的值为绝对路径,那么home就是那个绝对路径。如果以上的值为相对路径,那么将根据当前的工作目录算出home路径。如果什么都没指定,那么默认的是现在的工作目录为home。
例子:
情况一:把所有的文件都放在目录/a/database下:
dbenv->open(dbenv, "/a/database", flags, mode);
情况二:把临时文件放在/b/temporary,把所有其他文件放在/a/database:
dbenv->set_tmp_dir(dbenv, "/b/temporary");
dbenv->open(dbenv, "/a/database", flags, mode);
情况三:把数据文件放在/a/database/datadir,日志文件放在/a/database/logdir所有其他文件放在/a/database:
dbenv->set_lg_dir(dbenv, "logdir");
dbenv->set_data_dir(dbenv, "datadir");
dbenv->open(dbenv, "/a/database", flags, mode);
情况四:
把数据文件放在/a/database/data1和/b/data2,所有其他文件放在/a/database.
任何数据文件将被创建在/b/data2目录下,因为它是第一个被指定的数据目录:
dbenv->set_data_dir(dbenv, "/b/data2");
dbenv->set_data_dir(dbenv, "data1");
dbenv->open(dbenv, "/a/database", flags, mode);
。。。。。
Shared memory regions
每个在环境中的bdb子系统都被一个或多个区域(regions),或大块的内存来描述。区域包括所有的每进程和每线程共享信息,(包括互斥)
,这些组成了bdb环境。这些区域将下列在三种内存类型中的一种中被创建,这取决于指定给DB_ENV->open方法的标志:

DB_PRIVATE:如果这个标志被指定,区域将在每进程的堆内存中被创建;也就是说由malloc()返回的内存。
这个标志最好不要指定当一个以上的内存访问环境的时候。因为它很有可能引起数据库腐烂(corruption)和一些不可预知的行为,例如,当
server应用程序和bdb公用程序,(例如:db_archive, db_checkpoint or db_stat)都有可能访问这个环境的时候,B_PRIVATE标志最好别指
定。
DB_SYSTEM_MEM:如果这个标志被指定,共享区域将在系统内存中创建而不是在文件中。这是一个可选的机制,为了在多个进程和一个进程中的
多线程间共享bdb环境。bdb所使用的系统内存潜在地很有用,陪任何特殊的进程度过一生。因此附加的清除将是必要的当一个应用程序出现故
障后,因为bdb没有办法去确认,支撑共享内存区的系统资源是不是还给了系统。
系统内存的使用是根据计算机体系结构而定的。例如,在一个支持 X/Open样式共享内存的系统上,像UNIX系统,shmget(2) 和相近的
系统V IPC接口被使用。在VxWorks 系统中使用系统内存。在这些情况下,一个初始的段id必须在应用程序中被指定,以确保应用程序不互相覆
盖对方的数据库环境。因此,段创建的数量不是无限制的增长。可以参考DB_ENV->set_shm_key方法得到跟多的信息。
在windows平台上DB_SYSTEM_MEM标志问题多多,我就不说了.
default:如果没有内存相关的标志被指定给DB_ENV->open,被文件系统支撑的内存(觉得应该可以理解为虚拟内存)将用来存储这区域(regions)。在unix系统上,bdb库将将使用POSIX mmap接口。如果mmap不可用,那么unix shmget接口将可能被使用,如果它可用的话。
任何在文件系统中创建用来支撑区域的文件,将在环境的home目录下被创建。这些文件命名为__db.###(例如,_db.001, __db.002等等)。
当区域文件被文件系统来支撑的时候,每个区域对应一个文件被创建。当区域文件被系统内存来支撑的时候,只有一个文件将仍然被创建,因为这儿必须有一个熟知的名字在文件系统中,以便多进程能定位到环境所使用的系统共享内存。
统计在环境中的共享内存区域可以用db_stat的-e选项来显示。

Security
下面是当你在写bdb应用程序的时候需要考虑的安全问题:
数据库环境许可:
被bdb数据库环境使用的目录,应该有它自己的许可设置,以确保那些没有适当权限的用户不能访问环境里的文件。应用程序,那些添加到用户
的许可(例如,unix的setuid 或 setgid程序), 应该细心的检查,不允许违法的使用这些许可,例如访问在环境中的文件。
环境变量:
设置 DB_USE_ENVIRON 和 DB_USE_ENVIRON_ROOT标志 和允许在文件命名时使用环境变量都是危险的。在bdb应用程序中用附加的许可(例如,
unix的setuid 或 setgid程序)设置这些标志,将潜在地允许那些正常情况下没有权限的用户读写数据库。
文件许可:
默认地,bdb总是创建所有者和所在组可读写的文件(也就是,S_IRUSR, S_IWUSR, S_IRGRP 和 S_IWGRP; 或八进制模式 0660 在历史性的UNIX
系统上),创建文件的组的所有权,是基于系统和目录默认的,不被bdb进一步的指定。

临时支撑(backing)文件:
如果一个没有被命名的数据库被创建,而cache太小以至于不能在内存中控制这个数据库,bdb将创建一个临时的物理文件使它能把数据库的cache页放到磁盘上,当需要的时候。
在这种情况下,环境变量,像TMPDIR可能被用来指定用以定位那个临时文件。尽管临时支撑文件被创建被只有所有者可读写的。(S_IRUSR 和 S_IWUSR, 或八进制模式 0660 在历史性的UNIX系统上),一些文件系统可能不能充分的保护被创建在随机目录中的临时文件。为了绝对安全,应用程序存储敏感数据在未命名的数据库中,应该用DB_ENV->set_tmp_dir方法用已知的许可(known permissions)指定一个临时目录。
Encryption
bdb可选择的用Rijndael/AES算法支持加密和解密。这个加密只是文件级的,如果侵入者能够访问你系统的内存,那么这种加密就不能提供保障
了。与我的应用无关我就不多说了。自己看手册。
Remote filesystems
最好别使用远程文件系统,像nfs等。因为区域文件要映射到内存,远程文件系统不能很好的支持某些语义。数据库文件,的日志文件,临时文
件,还勉强可以放在远程文件系统上,如果远程文件系统完全支持标准POSIX文件系统语义的话。总之,不用最好。
Environment FAQ

我使用多进程访问bdb数据库环境,这儿有什么方法可以确保两个进程不同时执行数据恢复(recovery )操作吗?或者说,确保其他所有的进程都退出了,可以运行数据恢复了?
其实重点要说明的是,当执行数据恢复的时候要确保没有别的进程在使用这个环境。
很多应用程序组,写一个小的监视程序,来恢复数据库环境,然后执行那些实际上用数据库环境工作的进程。监视程序然后监视工作的进程,如果任何工作进程发生故障推出或其他原因,监视程序将kill所有仍然存活的其他进程,然后执行恢复任务,然后重新这个循环。


前面漏掉的一些东东。
腐烂数据的处理或者说数据库文件的瘦身:
当你从Btree或Hash数据库删除key/data对时,它并不把这个返回给文件系统,这使得数据重用成为可能。也就是说Btree和Hash数据库都是只增的。当你删除大量key/data对时,你可能想使数据库文件也缩减,你应该建立一个新的数据库文件,把记录从旧文件复制过去。应该是导入导出记录,而不是直接copy文件。
字节序的问题:
例如:数字254~257。在一个小数在前(little-endian)的系统上是:
254 fe 0 0 0
255 ff 0 0 0
256  0 1 0 0
257  1 1 0 0
如果你把他们当成字符串处理那么他们的排序是糟糕的:
256
257
254
255
在一个大数在前(big-endian)系统上是:
254 0 0 0 fe
255 0 0 0 ff
256 0 0 1 0
257 0 0 1 1
and so, if you treat them as strings they sort nicely. Which means, if you use steadily increasing integers as keys on a big-endian system Berkeley DB behaves well and you get compact trees, but on a little-endian system Berkeley DB produces much less compact trees. To avoid this problem, you may want to convert the keys to flat text or big-endian representations, or provide your own Btree comparison function.

Introduction

bdb包括对构建基于复制(replication)的高可用性应用程序的支持。bdb replication组由一些独立配置的数据库环境组成。
组里只有一个master数据库环境和一个或多个client环境。Master环境支持读和写,client环境支持只读。如果master环境倒掉了,应用程序将可能提升一个client为新的master。数据库环境可能在单独的计算机上,在单独的硬件分区上(partitions)一个不统一的内存访问系统上,或在一个单独的server的一个磁盘上。唯一的约束就是,replication组的所有的参与者必须在一个字节序(endianness)相同的机器上(都是大数再前或都是小数在前的操作系统)。我们期望这个约束在以后的版本中会去掉。因为总是用bdb环境,任何数量的并发进程或线程可能访问一个数据库环境。在master环境中,多个线程可能读写这个环境。在client环境中,多个线程可能要读这个环境。

应用程序可能被编写成在master和clients间提供不同程度的稳固性。系统能同步的运行以便复制品(replicas)能保证是最新的,对应于所有已提交的事务。但是这样做可能回招致性能上的很大的下降。高性能解决方案有考虑全局的稳固性,允许clients的数据过时一个应用程序可控制的一段时间。
尽管bdb包括必要的构建高可用性数据库环境的底层基础,应用程序仍然必须提供一些鉴定的(critical)组成部分:
应用程序有责任提供通信下部构造。应用程序可能用任何适当的通信协议。例如RPC, TCP/IP, UDP, VI或底板(backplane)消息传递。
应用程序有责任命名。bdb涉及到一个replication组成员的时候是靠一个应用程序提供的id,应用程序必须映射那个id到一个特殊的数据库环境中或通信通道中。
应用程序有责任监视master和clients的状态,和识别任何不可用的(unavailable)数据库环境。应用程序必须提供所有的需要的安全策略。
例如,应用程序可能选择去加密数据,用一个安全的套接层,或什么也不做。

最后,bdb replication实现还有一个附加的特性去增强可靠性。bdb中的replication实现成执行数据库更新用一个不同的编码路径而不是用标
准的。这意味着,有bug的软件的操作可能会毁坏replication master,但不会把clients也毁坏。
Replication和相近的方法的描述:
DB_ENV->rep_elect              举行一个replication竞选
DB_ENV->rep_process_message    处理一个replication消息
DB_ENV->rep_stat               Replication统计
DB_ENV->rep_sync               Replication同步
Replication 配置:
DB_ENV->rep_set_config        配置replication系统
DB_ENV->rep_start             为replication配置一个环境
DB_ENV->set_rep_limit         限制在响应但个消息时的数据发送
DB_ENV->set_rep_transport     配置replication传输

Replication environment IDs
每个在replication组中的数据库环境必须有一个独一无二的标识符,为它自己和其它replication组中的成员都分配一个不同标识符。这些标识符不必要是全局的,也就是说,每个数据库环境可以分配本地化的标识符给replication组的成员。就是在每数据库环境中都能区分出其他成员就行了,当然全局统一给指定标识符也不为错,只是非必要的。

应用程序有责任去标志每个进来的传递给DB_ENV->rep_process_message的有适当标识符的replication消息。随后,bdb将用这些相同的标识符去标志发送函数发出去的消息。
负标识符被bdb保留使用,不应该被应用程序指定给那些环境。有两个保留的标识符准备给应用程序使用的是:
DB_EID_BROADCAST:指定一个消息应该被广播给所有replication组中的成员。
DB_EID_INVALID:是一个无效的环境id,可能被用于初始化一些环境id变量,那些变量随后被检查合法性。

Replication environment priorities

每个replication组中的数据库环境变量必须有一个优先权,它指定了在replication组中不同环境间的一个相对的顺序。这个顺序在币桓鰉aster倒掉,在决定选举哪个环境作为新master的时候的一个重要因素。优先权必须是一个非负的整数,但不必要replication组中是独一无
Apr3

网站加速--Cache篇2【转自架构师杨建】

Author: 杨建  Click: 7538   Comments: 0 Category: 架构  Tag: cache,网站加速,系统架构
--提升性能的同时为你节约10倍以上成本
From: http://blog.sina.com.cn/iyangjian

一,Cache, 王道也
二,Cache 基本原理介绍
三,我划分的3个刷新级别
四,我对HTTP协议做的一点创新(?maxage=6000000)
五,Yslow优化网站性能的14条军规点评
六,上线了 !=  Finished
七,提速度同时节约成本方法汇总
-----------------------------------------------------------------------------------------

一,Cache,王道也

我觉得系统架构不应该仅仅是搭建一个强硬的能承受巨大并发压力的后台,前端页面也是需要架构的而且同等重要,不理解前台的的后台工程师是不合格的。中国人讲究钢柔相济,后台强硬只能说你内功深厚,前端用的巧,那叫四两拨千斤。

一般后台工程师很少关心前端如何使用自己的资源,而前端工程师,不知道自己的一个简单的用法会对后端造成多大影响。我会给出一些数据,来震撼下你的眼球。

二,Cache 基本原理介绍 (参考Caching Tutorial)

为什么使用Cache?
1,减少延迟,让你的网站更快,提高用户体验。
2,避免网络拥塞,减少请求量,减少输出带宽。
补充一个cache的原则:不更新的资源就不应该让它再次产生HTTP请求,如果强制产生了请求,那么就看看能否返回304。

Cache的种类?
浏览器Cache,代理Cache,网关Cache。
后端还有 disk cache ,server cache,php cache,不过不属于我们今天讨论范围。

Cache如何工作的?
1,如果响应头告诉cache别缓存它,cache不对它做缓存;
2,如果请求需要验证的或者是需要安全性的,它将不被缓存;
3,如果响应头里没有ETag或Last-Modifed header这类元素,而且也没有任何显式的信息告诉如何对数据保鲜,则它被认为不可缓存。
4,在下面情况下,一个缓存项被认为是新鲜的(即,不需到原server上检查就可直接发送给client):
    它设置了一个过期时间或age-controlling响应头,而且现在仍未过期。
    如果浏览器cache里有某个数据项,并且被被设置为每个会话(session)过程中只检查一次;
    如果一个代理cache里能找个某个数据项,并且它是在相对较长时间之前更新过的。
    以上情况会认为数据是新鲜的,就直接走cache,不再查询源server。
5,如果有一项过期了,它将会让原server去更新它,或者告诉cache这个拷贝是否还是可用的。

怎么控制你的Cache?
Meta tags :在html页面中指定,这个方法只被少数浏览器支持,Proxy一般不会读你html的具体内容然后再做cache决策的。

Pragma: no-cache : 一般被大家误用在http响应头中,这不会产生任何效果。而实际它仅仅应该用在请求头中。不过google的Server: GFE/1.3 响应中却这样用,难道人家也误用了呢。

Date: 当前主机GMT时间。

Last-Modified : 文件更新GMT时间,我在响应头中带上这个元素的时候,通常浏览器在cache时间内再发请求都会稍带上If-Modified-Since,让我们判断需要重新传输文件内容,还是仅仅返回个304告诉浏览器资源还没更新,需要缓存策略的服务器肯定都得支持的。有了这个请求,head请求在基本没太多用处了,除非在telnet上调试还能用上。

If-Modified-Since :  用在请求头里,见Last-Modified 。

Etag: 标识资源是否发生变化,etag的生成算法各是各样,通常是用文件的inode+size+LastModified进行Hash后得到的,可以根据应用选择适合自己的。Last-Modified 只能精确到秒的更新,如果一秒内做了多次更新,etag就能派上用场。貌似大家很少有这样精确的需求,浪费了http header的字节数,建议不要使用。
更正:Etag 其实在某种情况下可以很好的减少数据传输。在stonehuang的提醒下我才恍然大悟,转眼好几个月了也一直忘记更新。Etag应用场景。比如,数据为php的动态输出。每次请求把上一次Etag带来,跟本次计算的Etag进行比较,相等就可以避免一次数据传输。(最后修改时间 2009.12.07)

Expires :  指定缓存到期GMT的绝对时间,这个是http 1.0里就有的。这个元素有些缺点,一,服务器和浏览器端时间不一致时会有问题。二,一旦失效后如果忘记重新设置新的过期时间会导致cache失效。三,服务器端需要根据当前Date时间 + 应该cache的相对时间去计算这个值,需要cpu开销。 我不推荐使用。

Cache-Control:
这个是http 1.1中为了弥补 Expires 缺陷新加入的,现在不支持http 1.1的浏览器已经很少了。
max-age: 指定缓存过期的相对时间秒数,max-ag=0或者是负值,浏览器会在对应的缓存中把Expires设置为1970-01-01 08:00:00 ,虽然语义不够透明,但却是我最推荐使用的。
s-maxage: 类似于max-age,只用在共享缓存上,比如proxy.
public: 通常情况下需要http身份验证的情况,响应是不可cahce的,加上public可以使它被cache。
no-cache: 强制浏览器在使用cache拷贝之前先提交一个http请求到源服务器进行确认。这对身份验证来说是非常有用的,能比较好的遵守 (可以结合public进行考虑)。它对维持一个资源总是最新的也很有用,与此同时还不完全丧失cache带来的好处,因为它在本地是有拷贝的,但是在用之前都进行了确认,这样http请求并未减少,但可能会减少一个响应体。
no-store:  告诉浏览器在任何情况下都不要进行cache,不在本地保留拷贝。
must-revalidate: 强制浏览器严格遵守你设置的cache规则。
proxy-revalidate: 强制proxy严格遵守你设置的cache规则。
用法举例:  Cache-Control: max-age=3600, must-revalidate

其他一些使用cache需要注意的东西,不要使用post,不要使用ssl,因为他们不可被cache,另外保持url一致。只在必要的地方,通常是动态页面使用cookie,因为coolie很难cache。至于apache如何支持cache和php怎么用header函数设置cache,暂不做介绍,网上资料比较多。

如何设置合理的cache时间 ?
http://image2.sinajs.cn/newchart/min/n/sz000609.gif?1230015976759
拿我分时图举例,我们需要的更新频率是1分钟。但为了每次都拿到最新的资源,我们在后面加了个随机数,这个数在同一秒内的多次刷新都会变化。我们的js虽然能够很好的控制,一分钟只请求一次,但是如果用户点了刷新按纽呢?这样的调用是完全cache无关的,连返回304的机会都没有。

试想,如果很多人通过同一个代理出去的,那么所有的请求都会穿透代理,弄不好被网管封掉了。如果我们做只做一秒的cache,对直接访问源服务器的用户没太多影响,但对于代理服务器来说,他的请求可能会从10000 req/min 减少为 60 req/min ,这是160倍。

对于我们行情图片这样的情况,刷新频率为1分钟,比较好的做法是把后面的随机数(num)修改为 num=t-t%60 其中t是当前时间戳,这样你一分钟内刷这个url是不变的,下一分钟会增加1,会再次产生一个新请求。而我的max-age设置为默认59秒,即使设置120秒其实也没什么影响。可能你会说万一赶上临界点可能拿不到最新的数据,其实对用户来说,用那个多变的随即数和我这个分钟级的随即数,看到的效果是相同的下面我给你分析一下:如果用户打开了我们的分时间页面,当前随即数对他来说是新的,所以他会拿到一个当前最新的图片,然后他点了刷新按纽,用户会产生http请求,即使url没变,服务器有最新图片也一定会返回,否则返回304,一分钟后js刷新图片,分钟数加了1,会得到全新资源。这和那个随时变化的随即数效果有区别吗?都拿到了最新的数据,但是却另外收益了cache带来的好处,对后端减少很多压力。

三,我划分的3个刷新级别

名词解释 全新请求: url产生了变化,浏览器会把他当一个新的资源(发起新的请求中不带If-Modified-Since)。

更正:在firefox后来的版本中对此做了改进,倾向于更多的使用cache,曾经访问过的都会尽量捎带If-Modified-Since头。这些表现和IE一致。修改部分用红色标出。(最后修改时间 2009.12.07)

注: sports.sinajs.cn 在IE下的表现存在一个小bug,由于不是使用的strncpy,导致IE下难以返回304,
需要修改一行代码,把比较字符串长度设置为29即可解决。不过目前本人已不在职,难以修改。
情况一 FF 捎带的头: If-Modified-Since    Mon, 07 Dec 2009 10:54:43 GMT
情况二 IE 捎带的头: If-Modified-Since    Mon, 07 Dec 2009 10:54:43 GMT; length=6

1,在地址栏中输入http://sports.sinajs.cn/today.js?maxage=11地址按回车。重复n次,直到cache时间11秒过去后,才发起请求,这个请求会带If-Modified-Since

2,按F5刷新.  在你发起一个全新的请求以后,然后多次按F5都会产生一个带If-Modified-Since的请求。

3, ctrl+F5 ,总会发起一个全新的请求。

下面是按F5刷新的例子演示: http://sports.sinajs.cn/today.js?maxage=11
( 如果这个值大于浏览器最大cache时间maxage,将以浏览器最大cache为准)

----------------------------------------------------------发起一个全新请求
GET /today.js?maxage=11 HTTP/1.1
Host: sports.sinajs.cn
Connection: keep-alive

HTTP/1.x 200 OK
Server: Cloudia
Last-Modified: Mon, 24 Nov 2008 11:03:02 GMT
Cache-Control: max-age=11    (浏览器会cache这个页面内容,然后将cache过期时间设置为当前时间+11秒)
Content-Length: 312
Connection: Keep-Alive
---------------------------------------------------------- 按F5刷新
GET /today.js?maxage=11 HTTP/1.1
Host: sports.sinajs.cn
Connection: keep-alive
If-Modified-Since: Mon, 24 Nov 2008 11:03:02 GMT   (按F5刷新,If-Modified-Since将上次服务器传过来的Last-Modified时间带过来)
Cache-Control: max-age=0

HTTP/1.x 304 Not Modified   
Server: Cloudia
Connection: Keep-Alive
Cache-Control: max-age=11   (这个max-age有些多余,浏览器发现Not Modified,将使用本地cache数据,但不会重新设置本地过期时间)
----------------------------------------------------------
继续按F5刷新n次.......

这11秒内未产生http请求.直到11秒过去了...............
----------------------------------------------------------按F5刷新
GET /today.js?maxage=11 HTTP/1.1
Host: sports.sinajs.cn
Connection: keep-alive
If-Modified-Since: Mon, 24 Nov 2008 11:03:02 GMT (多次按F5都会产生一个带If-Modified-Since的请求)
Cache-Control: max-age=0

HTTP/1.x 304 Not Modified
Server: Cloudia
Connection: Keep-Alive
Cache-Control: max-age=11
----------------------------------------------------------按F5刷新
GET /today.js?maxage=11 HTTP/1.1
Host: sports.sinajs.cn
Connection: keep-alive
If-Modified-Since: Mon, 24 Nov 2008 11:03:02 GMT  (同上 ...)
Cache-Control: max-age=0

HTTP/1.x 304 Not Modified
Server: Cloudia
Connection: Keep-Alive
Cache-Control: max-age=11
----------------------------------------------------------


四,我对HTTP协议做的一点创新(?maxage=6000000)

上面看到了url后面有  ?maxage=xx  这样的用法,这不是一个普通的参数,作用也不仅仅是看起来那么简单。他至少有以下几个好处:

1,可以控制HTTP header的的 max-age 值。
2, 让用户为每个资源灵活定制精确的cache时间长度。
3, 可以代表资源版本号。

首先谈论对后端的影响:
服务器实现那块,不用再load类似mod_expires,mod_headers 这样额外的module,也不用去加载那些规则去比较,它属于什么目录,或者什么文件类型,应该cache多少时间,这样的操作是需要开销的。

再说说对前端的影响:
比如同一个分时行情图片,我们的分时页中需要1分钟更新,而某些首页中3分钟更新好。不用js控制的话,那我cache应该设置多少呢?   有了maxage就能满足这种个性化定制需求。

另一种情况是,我们为了cache,把某个图片设置了一个永久cache,但是由于需求,我必须更新这个图片,那怎么让用户访问到这个更新了的图片呢?从yahoo的资料和目前所有能找到的资料中都描述了同一种方法,更改文件名字,然后引用新的资源。 我觉得这方法太土, 改名后,老的还不能删除,可能还有地方在用,同一资源可能要存两份,再修改,又得改个名,存3份,不要不把inode当资源。我就不那样做,只需要把maxage=6000000 修改成 maxage=6000001 ,问题就解决了。

maxage=6000000 所产生的威力 (内存块消耗减少了250倍  ,请求数减少了37倍) :
体育那边要上一个新功能,一开始动态获取那些数据,我觉得那样太浪费动态池资源,就让他们把xml文件到转移到我的js池上来,为了方便,他们把那个84k的flash文件也放在了一起,而且是每个用户必须访问的。说实在的,我不欢迎这种大块头,因为它不可压缩,按正常来说,它应该代表一个3M的文件。我的服务器只这样设计的,如果一次发送不完的就暂存在内存里,每个内存块10k,如果不带参数默认maxage=120 。 我发现,由于这个文件,10w connections的时候,我消耗了10000个内存块。我自己写的申请连续内存的算法也是消耗cpu地,一个84k的文件,发送一次后,剩余的64k就应该能装的下,于是我把最小内存块大小改为64K。 这样消耗10w conn的时候消耗1500个左右内存快,虽然内存消耗总量没怎么变小,但是它能更快的拿到64K的连续内存资源,cpu也节约下来了。接下来我让meijun把所应用的flash资源后面加上maxage=6000000 (大概=79天,浏览器端最长cache能达到着个就不错了), 10w connections的时候,只消耗了不到40个内存块,也就是说内存块消耗减少了250倍  ,请求数减少了37倍。  35w+ connections, 5.67w req/s的时候也就消耗100块左右,比线性增加要少很多。也就是这点发现让我有了做这个技术分享的冲动,其他都是顺便讲讲。


五,Yslow优化网站性能的14条军规点评

其中黑色部分,跟后端是紧密相连的,在我们的内容中都已经涉及到了,而且做了更深入的讨论。兰色部分,5,6,7是相关页面执行速度的,构建前端页面的人应该注意的。 11属于避免使用的方法。 红色部分我着重说一下:

gzip 我不推荐使用,因为有些早期IE支持的不好,它的表现为直接用IE访问没问题,用js嵌进去,就不能正常解压。这样的用户占比应该在2%左右。这个问题我跟踪了近一个月,差点放弃使用压缩。后来发现我以前用deflate压缩的文件却能正常访问。 改用deflate问题解决。apache 1.x使用mod_gzip ,到了 2.x 改用cmod_deflate,不知道是否跟这个原因有关。 另外对于小文件压缩来说,deflate 可比 gzip 省不少字节。

减少 DNS 查询: 这里也是有个取舍的,一般浏览器最多只为一个域名创建两个连接通道。 如果我一个页面嵌了 image.xx.com 的很多图片,你就会发现,图片从上往下一张张显示出来这个过程。这造成了浏览器端的排队。 我们可以通过增加域名提高并发度,例如 image0.xx.com ,image1.xx.com ,image2.xx.com,image3.xx.com 这样并发度就提上去了,但是会造成很多cache失效,那很简单,假如我们对文件名相加,对4取mod,就能保证,某个图片只能通过某个域名进行访问。不过,我也很反对一页面请求了数十个域名,很多域名下只有一到两个资源的做法,这样的时间开销是不划算的。

另外,我在这里再添一个第15条:错开资源请求时间,避免浏览器端排队。
随着ajax的广泛使用,动态刷新无处不在,体育直播里有个页面调用了我一个域名下的6个文件,3个js,3个xml。刷新频率大致是两个10秒的,两个30秒的,两个一次性载入的。观察发现正常响应时间都在7ms,但是每过一会就会出现一次在100ms以上的,我就很奇怪,服务器负载很轻呢。meijun帮我把刷新时间错开,11秒的,9秒的,31秒的,这样响应在100ms以上的概率减少了好几倍,这就是所谓的细节决定成败吧。

1. 尽可能的减少 HTTP 的请求数     [content]
2. 使用 CDN(Content Delivery Network)     [server]
3. 添加 Expires 头(或者 Cache-control )     [server]
4. Gzip 组件     [server]
5. 将 CSS 样式放在页面的上方     [css]
6. 将脚本移动到底部(包括内联的)     [javascript]
7. 避免使用 CSS 中的 expression_r_r_r_rs     [css]
8. 将 JavaScript 和 CSS 独立成外部文件     [javascript] [css]
9. 减少 DNS 查询     [content]
10. 压缩 JavaScript 和 CSS (包括内联的)     [javascript] [css]
11. 避免重定向     [server]
12. 移除重复的脚本     [javascript]
13. 配置实体标签(ETags)     [css]
14. 使 AJAX 缓存    

六,上线了 !=  Finished

奥运期间我按1500w~2000w connections在线,设计了一套备用系统,现在看来,如果用户真达到了这个数目我会很危险,因为有部分服务器引入了32bit的centos 5未经实际线上检验,而我当时简单的认为它应该和centos 4表现出一样的特性。所以现在未经过完全测试的lib库和新版本,我都很谨慎的使用。没在真实环境中检验过,不能轻易下结论。

很多项目组好象不停的忙,做新项目,上线后又继续下个新项目,然后时不时的转过头去修理以前的bug。如果一个项目上线后,用户量持续上升,就应该考虑优化了,一个人访问,和100w人访问,微小的修改对后端影响是不能比较的,不该请求的资源就让它cache在用户的硬盘上,用户访问块了,你也省资源。上线仅仅代表可以交差了而已,对于技术人员来说持续的对一个重要项目进行跟踪和优化是必要的。


七,提速度同时节约成本方法汇总

1,编写节约的HTTP服务器 (高负载下速度明显提升,节约5~10倍服务器)
对一些重要的服务器量身定做。或者选用比较高效的开源软件进行优化。

2,不同服务混合使用  (节约1~2倍服务器)
如果我们一台服务器只支持30w conn的话,那么剩余的75% cpu资源,95%的内存资源,和几乎所有的磁盘资源都可以部署动态池系统,我觉得DB对网卡中断的消耗还是有限的,我也不用新买网卡了。

3,对于纯数据部分启用新的域名(速度有提升,上行带宽节约1倍以上)
比如我们另外购买了sinajs.cn 来做数据服务,以避免cookie,节约带宽. Cookie不但会浪费服务器端处理能力,而且它要上行数据,而通常情况上行比下行慢。

4, 使用长连接 (速度明显提升,节约带宽2倍以上,减少网络拥塞3~无数倍)
对于一次性请求多个资源,或在比较短的间隔内会有后续请求的应用,使用长连接能明显提升用户体验,减少网络拥塞,减少后端服务器建立新连接的开销。

5,数据和呈现分离,静态数据和动态数据分离 (速度明显提升,同时节约3倍带宽)
div+css 数据和呈现分离以后,据说文件大小能降到以前的1/3。
把页面中引用的js文件分离出来,把动态部分和静态部分也分离开来。

6,使用deflate压缩算法 (速度明显提升,节约3.33倍带宽)
一般来说压缩过的文件大小不到以前的30% 。
将上面分离出来的数据进行压缩(累计节约带宽10倍)。

7, 让用户尽可能多的Cache你的资源 (速度明显提升,节约3~50倍服务器资源和带宽资源)
将上面分离出来的css和不经常变动的js数据部分cache住合适的时间。(理想情况,累计节约带宽30~500倍) 。

以上改进可以让速度大幅度提升的同时,服务器资源节约 5~20 倍 ,减少网络拥塞3~无数倍, 上行带宽节约1倍以上,下行带宽节约30~500倍,甚至更多。

分类

标签

归档

最新评论

Abyss在00:04:28评论了
Linux中ramdisk,tmpfs,ramfs的介绍与性能测试
shallwe99在10:21:17评论了
【原创】如何在微信小程序开发中正确的使用vant ui组件
默一在09:04:53评论了
Berkeley DB 由浅入深【转自架构师杨建】
Memory在14:09:22评论了
【原创】最佳PHP框架选择(phalcon,yaf,laravel,thinkphp,yii)
leo在17:57:04评论了
shell中使用while循环ssh的注意事项

我看过的书

链接

其他

访问本站种子 本站平均热度:9360 c° 本站链接数:1 个 本站标签数:464 个 本站被评论次数:94 次