Tuesday, May 06, 2008

跳进python, Django

一直都想学习java以外的一门新语言,在学习java以前看过perl, php, 但是粗略的看一看,又没有地方来用,最终就淡忘了。 后来开始学python,也是一样的,有空看看书,但是用的机会不多,慢慢又模糊了, 真是浪费生命。 再以后开始用python写一些小工具来辅助开发,比如写了一个破解我老婆博客密码的小东东, 一个根据diff文件构建升级包的工具, 实践是最好的老师,一些模糊的记忆又慢慢开始清晰了。 java世界开始变得开放,比如jython的出现,这样python代码可以直接在java虚拟机上跑了。 更好的事情是grinder,一个开源的java性能测试工具, 它的测试脚本支持用jython来写, 于是就在项目里面小用了一下,很有意思, python可以在日常工作中发挥更大的作用了。
不过这里要写这篇blog是用来作为我学习python的笔记, 一些自己在使用python的过程经常出错的东西都要在这里记录下来,免得同一个石头绊我三次。

[内置的数据类型]
dictionary: key必须是常量 ,key大小写敏感,而且不能重复。 其中的元素是没有秩序的。
list::相当于php中的数组,可以保存不同类型的数据。 其中元素是有秩序的
tuple: 不可变的list, 可以用tuple()来将一个list转化为tuple; 反过来可以用list()将一个tuple转化为list

python是一种强类型的语言,就是说一旦将给一个变量赋值之后,这个值的类型是不能变化的,比如进行显式的强制转换,比如: int('1'), str(1); 对于弱类型的语言,这种转换可以自动完成,比如‘1’ + 2在python中会抛出异常,但是javascript中可以返回‘12’, 或者3,取决于你怎么使用。
python中有一个内置函数isinstance()可以检验某个值的数据类型(int, long, str, dict, list, tuple, float, Class etc),比如: isinstance(1, int) = True, isinstance('1', int) = False

python中有个types模块,其中定义了很多内置的数据类型:
>>> import types
>>> dir(types)

[内置函数]
[getattr]
使用 getattr 函数,可以得到一个直到运行时才知道名称的函数的引用。
>>> li = ["Larry", "Curly"]
>>> li.pop

>>> getattr(li, "pop")

>>> getattr(li, "append")("Moe")
>>> li
["Larry", "Curly", "Moe"]
[isinstance]
是否是某个类型的实例, isinstance(1, int) = true
[callable]
接收任何对象作为参数,如果参数对象是可调用的,返回 True;否则返回 False
[range ]
内置的 range 函数返回一个元素为整数的 list。这个函数的简化调用形式是接收一个上限值,然后返回一个初始值从 0 开始的 list,它依次递增,直到但不包含上限值。(如果您愿意,您可以传入其它的参数来指定一个非 0 的初始值和非 1 的步长。也可以使用 print range.__doc__ 来了解更多的细节。)
(MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY) = range(7)

[filter]

Python 有一个内建 filter 函数,它接受两个参数:一个函数和一个列表,返回一个列表。作为第一个参数传递给 filter 的函数本身应接受一个参数,filter 返回的列表将会包含被传入列表参数传递给 filter 所有可以令函数返回真 (true) 的元素。

>>> def odd(n):                 1
... return n % 2
...
>>> li = [1, 2, 3, 5, 9, 10, 256, -3]
>>> filter(odd, li) 2
[1, 3, 5, 9, -3]

[map]

使用内建 map 函数。它的工作机理和 filter 函数类似。

>>> def double(n):
... return n*2
...
>>> li = [1, 2, 3, 5, 9, 10, 256, -3]
>>> map(double, li) 1
[2, 4, 6, 10, 18, 20, 512, -6]

[other...]

获得定长字符串:

>>>s = 'xiix'

>>>s.ljust(8, '0')

'xiix0000'

>>>s.zfill(8)

'0000xiix'

[映射list]

Python 的强大特性之一是其对 list 的解析,它提供一种紧凑的方法,可以通过对 list 中的每个元素应用一个函数,从而将一个 list 映射为另一个 list。

>>> li = [1, 9, 8, 4]
>>> [elem*2 for elem in li] 1
[2, 18, 16, 8]
[过滤列表]
这种能力同过滤机制结合使用,使列表中的有些元素被映射的同时跳过另外一些元素。
[mapping-expression for element in source-list if filter-expression]
[使用lamba函数]
Python 支持一种有趣的语法,它允许你快速定义单行的最小函数。这些叫做 lambda 的函数,是从 Lisp 借用来的,可以用在任何需要函数的地方。
>>> (lambda x: x*2)(3) 2
6
lambda 函数可以接收任意多个参数 (包括可选参数) 并且返回单个表达式的值。lambda 函数不能包含命令,包含的表达式不能超过一个。

[多变量赋值]
>>>t = (1, 2, 3)
>>>(x, y, z) = t
x = 1, y =2, z =3
>>>l = [1, 2]
>>>[x,y] = l
x = 1, y = 2

[类的定义]
  • 当从你的类中调用一个父类的一个方法时,你必须包括 self 参数。
  • __init__ 方法是可选的,但是一旦你定义了,就必须记得显示调用父类的 __init__ 方法 (如果它定义了的话)。这样更是正确的:无论何时子类想扩展父类的行为,后代方法必须在适当的时机,使用适当的参数,显式调用父类方法。
class FileInfo(UserDict):
    "store file metadata"
def __init__(self, filename=None):
UserDict.__init__(self) 1
self["name"] = filename
2
  • 要在类的内部引用一个数据属性,我们使用 self 作为限定符。习惯上,所有的数据属性都在 __init__ 方法中初始化为有意义的值。然而,这并不是必须的,因为数据属性,像局部变量一样,当你首次赋给它值的时候突然产生
  • 专用类方法。Python 有一个与 __getitem__ 类似的 __setitem__ 专用方法,比如f['name']这个看上去就像你用来得到一个字典值的语法,事实上它返回你期望的值。下面是隐藏起来的一个环节:暗地里,Python 已经将这个语法转化为 f.__getitem__("name") 的方法调用。这就是为什么 __getitem__ 是一个专用类方法的原因,不仅仅是你可以自已调用它,还可以通过使用正确的语法让 Python 来替你调用。
  • 类属性和数据属性。 数据属性在__init__方法中通过'self.XXX'来定义,是一个特定类实例所拥有的变量;类属性是在class中直接定义, 是由类拥有的属性,当然所有该类的实例都拥有该属性。
[高级专用类方法]
  • __repr__ 是一个专用的方法,在当调用 repr(instance) 时被调用。repr 函数是一个内置函数,它返回一个对象的字符串表示。它可以用在任何对象上,不仅仅是类的实例。你已经对 repr 相当熟悉了,尽管你不知道它。在交互式窗口中,当你只敲入一个变量名,接着按ENTER,Python 使用 repr 来显示变量的值。自已用一些数据来创建一个字典 d ,然后用 print repr(d) 来看一看吧。(java.lang.Object#toString())
  • __cmp__ 在比较类实例时被调用。通常,你可以通过使用 == 比较任意两个 Python 对象,不只是类实例。有一些规则,定义了何时内置数据类型被认为是相等的,例如,字典在有着全部相同的关键字和值时是相等的。对于类实例,你可以定义 __cmp__ 方法,自已编写比较逻辑,然后你可以使用 == 来比较你的类,Python 将会替你调用你的 __cmp__ 专用方法。(java.lang.Object#equals(o, o)
  • __len__ 在调用 len(instance) 时被调用。len 是一个内置函数,可以返回一个对象的长度。它可以用于任何被认为理应有长度的对象。字符串的 len 是它的字符个数;字典的 len 是它的关键字的个数;列表或序列的 len 是元素的个数。对于类实例,定义 __len__ 方法,接着自已编写长度的计算,然后调用 len(instance),Python 将替你调用你的 __len__ 专用方法。
  • __delitem__ 在调用 del instance[key] 时调用 ,你可能记得它作为从字典中删除单个元素的方法。当你在类实例中使用 del 时,Python 替你调用 __delitem__ 专用方法。
这些其实就和java中的Object类相似, 因为java中所有类都是从Object继承的,那些方法都定义在Object中,而python没有这样的基类,通过这些专用方法来达到类似的效果。或者有些象设计模式中的模板方法(template method)

[xml解析]
  • 如果你打算在你的 Python 代码中保存非 ASCII 字符串,你需要在每个文件的顶端加入编码声明来指定每个 .py 文件的编码。这个声明定义了 .py 文件的编码为 UTF-8:
#!/usr/bin/env python
# -*- coding: UTF-8 -*-

  • 通过sys.getdefaultencoding()得到当前的默认编码形式, 在python启动以后就不能调用sys.setdefaultencoding('latin-1')来设定编码。 这里介绍一些简单的历史。ASCIi是英语字符的编码,只需要0-127, 但是欧洲,尤其是西欧有很多古怪的字符,比如西班牙语里有字符在n上面加一个波浪线,为了增加对这些字符的支持,出现了ISO-8859-1也就是Latin-1标准,所有那些字符都在128-255之间。 而unicode是一个想要统一天下所有字符的编码标准,地球上所有语言的任何一个字符都可以找到一个唯一的编码,使用两个字节,从0到65535(仅仅中日韩就不止65535个字符了吧,不够用)。 utf-8是对unicode进行的演化,因为对于很多iso-8859-1标准内定义的字符,它们用一个字符就可以表示了,如果用unicode来表示的话就相当于浪费一个字节,所以utf-8对ASCII等单字节的字符仍然只用一个字节表示,对其他的还是双字节。 而utf-16就是对所有字符都使用双字节。
  • iso-8859-1是ascii的超集,unicode是iso-8859-1的超集。 而中文又存在GBK和GB2312两个标准, gbk是gb2312的超集,其中增加了对繁体字,以及一些没有收录到gb2312中的汉字字符。 gb是中国的标准,在一定程度上,比如0-127段的定义和ascii是一致的。 更多信息可以参考维基(http://en.wikipedia.org/wiki/GBK)
[动态导入]
>>> sys = __import__('sys')           1
>>> os = __import__('os')
>>> re = __import__('re')
>>> unittest = __import__('unittest')
>>> sys 2
>>>
>>> os
>>>
1 内建 __import__ 函数与 import 语句的既定目标相同,但它是一个真正的函数,并接受一个字符串参数。
2 变量 sys 现在是 sys 模块,和 import sys 的结果完全相同。变量 os 现在是一个 os 模块,等等。

因此 __import__ 导入一个模块,但是是通过一个字符串参数来做到的。依此处讲,你用以导入的仅仅是一个硬编码性的字符串,但它可以是一个变量,或者一个函数调用的结果。并且你指向模块的变量也不必与模块名匹配。你可以导入一系列模块并把它们指派给一个列表。


[参考资源]

Monday, May 05, 2008

关于http协议中定义的缓存机制与网站性能

一直以来大家都在用http协议,但是有几个人真正的了解http协议?也许你会说,我只需要会用就可以了, 干吗要去读http协议规范? 某些情况下,我觉得这样说是对的, 甚至绝大多数情况下可能都是对的,在于你是要做什么。 但是, 我还是坚持,在你急急忙忙的完成了一个和http有关的功能之后,还是应该好好想想,你为什么要这样使用http协议,这个时候你应该静下心来好好读读http的协议规范, 作为一个真正的程序员, 你应该这么做。

这里有点搬起石头砸自己脚的意思,因为我自己也只是看了http协议规范中和cache有关的一部分,而且不敢保证是所有相关的部分都阅读过了,所以,下面说的东西仅供参考。

[caching mechanisms]
cache机制是分几个层次的,他们包括服务器端缓存,isp缓存,客户端缓存。
  • 服务端缓存,比如squid,还有本身很多web开发框架支持页面缓存, 这个缓存会影响所有访问这个网站的用户。
  • isp缓存,isp可能缓存网站的内容,这个缓存会影响所有通过这个isp接入的用户,可能所有通过这个isp接入,而且访问某个被缓存网站的用户的请求直接在isp就被返回了,不会到达请求的网站。
  • 客户端缓存,一般指浏览器缓存,这个缓存只影响单个用户, 这样用户直接从本地装载内容,或者只需要发送查询请求(if-modified-since)。
所有这些缓存机制可以极大的提高对用户的响应速度,同时也极大的降低了服务器的负荷,因为大量的请求还没有到达网站服务器就已经被处理了。

[Cache-Control]
http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9
cache-control是http1.1中定义的头,在http1.1之前(http1.0)是通过expire都来指定cache策略。就是说在http的request/response中指定的这些关于cache策略的头是用来影响前面提到的cache机制, 这些cache机制根据cache头来处理其中的cache内容。
具体的cache-control的写法就不详细说,可以自己参考http规范, 这里就说说几个比较常用的值。
[cache-control: public; max-age=21600]
public/private: public 表示这个响应可以被任何cache机制缓存下来,如果是private表示响应绝对不能被共享缓存(shared cache)cache下来,比如用户的登录进某个网站后看到的用户管理页面,如果这个页面被shared cache缓存下来,假设是被isp缓存下来了,那么所有通过这个isp访问该网页的用户都会得到某个其他用户的管理页面,这是绝对不允许的,这样泄漏了用户隐私信息。
max-age: 单位为秒,表示这个响应可以被缓存多长时间。 如果同时也指定了expire头,那么总是以max-age的值为准。 但是并不总是这样,因为有些缓存机制可能还不支持http1.1规范,那么它们就会忽略cache-control头,而仅仅采用expire头,所以在响应中总是同时指定expire头总是一个比较好的主意.
no-cache:

If the no-cache directive does not specify a field-name, then a cache MUST NOT use the response to satisfy a subsequent request without successful revalidation with the origin server.
就是说no-cache还必须指定可以被缓存的某个/某几个field-name,否则这个响应 不能被缓存下来。
no-store: 表示整个响应不能被保存下来,尤其是一些敏感信息。 请求和响应中都可以指定这个值。

[If-Modified-Since/Last-Modified]
last-modified: 表示请求的网页最后的修改时间(也许说是请求的资源最后修改时间更合适)。只会在响应中携带这个头。一般来说如果请求的是一个文件,这个头可能就是操作系统里面该文件的last-modified时间。
if-modified-since: 如果cache机制缓存了某个响应,这个响应中指定了last-modified头,那么下次有用户请求这个缓存的网页时, cache会向original server(产生这个资源的服务器)发送一个请求,请求中包含if-modified-since头,original server检查请求的资源,并且将服务器上该资源的last-modified和if-modified-since的值进行比较,如果服务器上的资源没有修改,那么返回304(这个响应只包括头),如果服务器上的资源已经有了修改,那么返回200,同时返回新修改过的资源。

如果想要弄清楚http的cache机制,动手实践是必不可少的。 我就是用firefox的一个tamper data的插件访问www.youtube.com来实验的, tamper data可以查看request/response中的头信息。 要说起来,如果做web开发,firefox真是个好东西,它的很多插件都很有意思,还可以推荐一个poster。
[ETag]
ETag 是实现与最近修改数据检查同样的功能的另一种方法:没有变化时不重新下载数据。其工作方式是:服务器发送你所请求的数据的同时,发送某种数据的 hash (在 ETag 头信息中给出)。hash 的确定完全取决于服务器。当第二次请求相同的数据时,你需要在 If-None-Match: 头信息中包含 ETag hash,如果数据没有改变,服务器将返回 304 状态代码。与最近修改数据检查相同,服务器仅仅 发送 304 状态代码;第二次将不为你发送相同的数据。在第二次请求时,通过包含 ETag hash,你告诉服务器:如果 hash 仍旧匹配就没有必要重新发送相同的数据,因为你还有上一次访问过的数据。

[压缩 (Compression)]

一个重要的 HTTP 特性是 gzip 压缩。 关于 HTTP web 服务的主题几乎总是会涉及在网络线路上传输的 XML。XML 是文本,而且还是相当冗长的文本,而文本通常可以被很好地压缩。当你通过 HTTP 请求一个资源时,可以告诉服务器,如果它有任何新数据要发送给我时,请以压缩的格式发送。在你的请求中包含 Accept-encoding: gzip 头信息,如果服务器支持压缩,它将返回由 gzip 压缩的数据并且使用 Content-encoding: gzip 头信息标记。




最后还说点和cache机制无关,但是也和网站性能有关的一个小话题。 用户访问一个网站,如何能做方便的到达他/她最关心的页面? 2次点击,4次点击? 似乎真是个小问题,但在我看来,至少涉及两个方面的问题,一个是易用性的问题,决定了用户是否会真正喜欢并且依赖你的站点; 另外一个是网站性能问题, 如果你把4次点击减少到2次点击,是不是意味着你的服务器承受的负荷也减少了一半? 如果你是在架构一个海量用户访问的站点,比如北京奥运的购票网站,那么这些问题绝对不是小问题。就算你在做小网站,这种努力也是很有价值的。

Thursday, January 03, 2008

关于负载均衡器

web应用服务器集群系统,是由一群同时运行同一个web应用的服务器组成的集群系统,在外界看来,就像是一个服务器一样。为了均衡集群服务器的负载,达 到优化系统性能的目的,集群服务器将众多的访问请求,分散到系统中的不同节点进行处理。从而实现了更高的有效性和稳定性,而这也正是基于Web的企业应用 所必须具备的特性。
  
  高可靠性可以看作为系统的一种冗余设定。对于一个特定的请求,如果所申请的服务器不能进行处理的话,那么其他的服务器能不能对之进行有效的处 理呢?对于一个高效的系统,如果一个Web服务器失败的话,其他的服务器可以马上取代它的位置,对所申请的请求进行处理,而且这一过程对用户来说,要尽可 能的透明,使用户察觉不到!
  
  稳定性决定了应用程序能否支持不断增长的用户请求数量,它是应用程序自身的一种能力。稳定性是影响系统性能的众多因素的一种有效的测量手段,包括机群系统所能支持的同时访问系统的最大用户数目以及处理一个请求所需要的时间。
  
  在现有众多的均衡服务器负载的方法中,广泛研究并使用的是以下两个方法:
  
  DNS负载平衡的方法RR-DNS(Round-Robin Domain Name System)
  负载均衡器
  以下,我们将就这两种方法进行讨论。
  
  DNS轮流排程 RR-DNS(Round-Robin Domain Name System)
  
  域名服务器(Domain Name Server)中的数据文件将主机名字映射到其IP地址。当你在浏览器中键入一个URL时(例如:www.loadbalancedsite.com), 浏览器则将请求发送到DNS,要求其返回相应站点的IP地址,这被称为DNS查询。当浏览器获得该站点的IP地址后,便通过该IP地址连接到所要访问的站 点,将页面展现在用户面前。
  
  域名服务器(DNS)通常包含一个单一的IP地址与该IP地址所映射的站点的名称的列表。在我们上面所假象的例子中,www.loadbalancedsite.com 这个站点的映射IP地址为203.24.23.3。
  
  为了利用DNS均衡服务器的负载,对于同一个站点来讲,在DNS服务器中同时拥有几个不同的IP地址。这几个IP地址代表集群中不同的机器, 并在逻辑上映射到同一个站点名。通过我们的例子可以更好的理解这一点,www.loadbalancedsite.com将通过下面的三个IP地址发布到 一个集群中的三台机器上:
  
  203.34.23.3
  
  203.34.23.4
  
  203.34.23.5
  
  在本例中,DNS服务器中包含下面的映射表:
  
  www.loadbalancedsite.com 203.34.23.3
  
  www.loadbalancedsite.com 203.34.23.4
  
  www.loadbalancedsite.com 203.34.23.5
  
  当第一个请求到达DNS服务器时,返回的是第一台机器的IP地址203.34.23.3;当第二个请求到达时,返回的是第二台机器的IP地址203.34.23.4,以此类推。当第四个请求到达时,第一台机器的IP地址将被再次返回,循环调用。
  
  利用上述的DNS Round Robin技术,对于某一个站点的所有请求将被平均的分配到及群中的机器上。因此,在这种技术中,集群中的所有的节点对于网络来说都是可见的。
  
  DNS 轮流排程的优势
  
   DNS Round Robin的最大的优点就是易于实现和代价低廉:
  
  代价低,易于建立。 为了支持轮流排程,系统管理员只需要在DNS服务器上作一些改动,而且在许多比较新的版本的DNS服务器上已经增加了这种功能。对于Web应用来说,不需要对代码作任何的修改;事实上,Web应用本身并不会意识到负载均衡配置,即使在它面前。
  简单. 不需要网络专家来对之进行设定,或在出现问题时对之进行维护。
  DNS 轮流排程的缺点
  
   这种基于软件的负载均衡方法主要存在两处不足,一是不实时支持服务期间的关联,一是不具有高可靠性。
[ refer to http://www.javaworld.com/cgi-bin/mailto/x_java.cgi]
  • No server affinity(姻亲关系:表示上一次请求送到了某个服务器,那么下个请求还是希望送到原来的那个服务器,否则有可能导致会话信息丢失) support. When a user receives an IP address, it is cached on the browser. Once the cache expires, the user makes another request for the IP address associated with a logical name. That second request can return the IP address of any other machine in the cluster, resulting in a lost session.
  • No HA support. Imagine a cluster of n servers. If one of those servers goes down, every nth request to the DNS server will go to the dead server.
  • Changes to the cluster take time to propagate to the rest of the Internet. Many corporations' and ISPs' DNS servers cache DNS lookups from their clients. Even if your DNS list of servers in the cluster could change dynamically, it would take time for the cached entries on other DNS servers to expire. For example, after a downed server is removed from your cluster's DNS list, AOL clients could still attempt to hit the downed server if AOL's DNS servers cached entries to the downed server. As a result, AOL users would not be able connect to your site even if other machines in the cluster were available.
  • No guarantee of equal client distribution across all servers in the cluster. If you don't configure cooperating DNS servers to support DNS load balancing, they could take only the first IP address returned from the initial lookup and use that for their client requests. Imagine a partner corporation with thousands of employees all pinned to a single server in your cluster!
  
   • 不支持服务器间的一致性。服务器一致性是负载均衡系统所应具备的一种能力,通过它,系统可以根据会话信息是属于服务器端的,还是底层数据库级别的,继而将 用户的请求导向相应的服务器。而DNS轮流排程则不具备这种智能化的特性。它是通过cookie、隐藏域、重写URL三种方法中的一种来进行相似的判断 的。当用户通过上述基于文本标志的方法与服务器建立连接之后,其所有的后续访问均是连接到同一个服务器上。问题是,服务器的IP是被浏览器暂时存放在缓存 中,一旦记录过期,则需要重新建立连接,那么同一个用户的请求很可能被不同的服务器进行处理,则先前的所有会话信息便会丢失。
  
  不支持高可靠性。设想一个具有N个节点的集群。如果其中的一个节点毁坏,那么所有的访问该节点的请求将不会有所回应,这是任何人都不愿意看到 的。比较先进的路由器可以通过每隔一定的时间间隔,对节点检查,如果有毁坏的节点,则将之从列表中去除的方法,解决这个问题。但是,由于在 Internet上,ISPs将众多的DNS存放在缓存中,以节省访问时间,因此,DNS的更新就会变得非常缓慢,以至于有的用户可能会访问一些已经不存 在的站点,或者一些新的站点得不到访问。所以,尽管DNS轮流排程在一定程度上解决了负载均衡问题,但这种状况的改变并不是十分乐观和有效的。
  除了上面介绍的轮流排程方法外,还有三种DNS负载均衡处理分配方法,将这四种方法列出如下:
  
  Ø Round robin (RRS): 将工作平均的分配到服务器 (用于实际服务主机性能一致)
  
  Ø Least-connections (LCS): 向较少连接的服务器分配较多的工作(IPVS 表存储了所有的活动的连接。用于实际服务主机性能一致。)
  
  Ø Weighted round robin (WRRS): 向较大容量的服务器分配较多的工作。可以根据负载信息动态的向上或向下调整。 (用于实际服务主机性能不一致时)
  
  Ø Weighted least-connections (WLC): 考虑它们的容量向较少连接的服务器分配较多的工作。容量通过用户指定的砝码来说明,可以根据装载信息动态的向上或向下调整。(用于实际服务主机性能不一致时)
  
  
  
  负载均衡器
  
  负载均衡器通过虚拟IP地址方法,解决了轮流排程所面临的许多问题。使用了负载均衡器集群系统,在外部看来,像是具有一个IP地址的单一服务 器一样,当然,这个IP地址是虚拟的,它映射了集群中的每一台机器的地址。所以,在某种程度上,负载均衡器是将整个集群的IP地址报漏给外部网络。
  
  当请求到达负载均衡器时,它会重写该请求的头文件,并将之指定到集群中的机器上。如果某台机器被从集群中移除了,请求不会别发往已经不存在的 服务器上,因为所有的机器表面上都具有同一个IP地址,即使集群中的某个节点被移除了,该地址也不会发生变化。而且,internet上缓存的DNS条目 也不再是问题了。当返回一个应答时,客户端看到的只是从负载均衡器上所返回的结果。也就是说,客户端操作的对象是负载均衡器,对于其更后端的操作,对客户 端来讲,是完全透明的。
  
  负载均衡器的优点
  
   • 服务器一致性. 负载均衡器读取客户端发出的每一个请求中所包含的cookies或url解释。基于所读出的这些信息,负载均衡器就可以重写报头并将请求发往集群中合适的 节点上,该节点维护着相应客户端请求的会话信息。在HTTP通信中,负载均衡器可以提供服务器一致性,但并不是通过一个安全的途径(例如:HTTPS)来 提供这种服务。当消息被加密后(SSL),负载均衡器就不能读出隐藏在其中的会话信息。
  
   • 通过故障恢复机制获得高可靠性. 故障恢复发生在当集群中某个节点不能处理请求,需将请求重新导向到其他节点时。主要有两种故障恢复:
  
  • 请求级故障恢复。当集群中的一个节点不能处理请求时(通常是由于down机),请求被发送到其他节点。当然,在导向到其他节点的同时,保存在原节点上的会话信息将会丢失。
  
  • 透明会话故障恢复。当一个引用失败后,负载均衡器会将之发送到集群中其他的节点上,以完成操作,这一点对用户来说是透明的。由于透明会话故障恢复需要节点 具备相应的操作信息,因此为了实现该功能,集群中的所有节点必须具有公共存储区域或通用数据库,存储会话信息数据,以提供每个节点在进行单独进程会话故障 恢复时所需要的操作信息。
  
   • 统计计量。既然所有的Web应用请求都必须经过负载均衡系统,那么系统就可以确定活动会话的数量,在任何实例访问中的活动会话的数目,应答的次数,高峰负 载次数,以及在高峰期和低谷期的会话的数目,还有其他更多的。所有的这些统计信息都可以被很好的用来调整整个系统的性能。
  
  负载均衡器的缺点
  
   硬件路由的缺点在于费用、复杂性以及单点失败的。由于所有的请求均是通过一个单一的硬件负载均衡器来传递,因此,负载均衡器上的任何故障都将导致整个站点的崩溃。
  
  HTTPS请求的负载均衡
  
   正如上面所提到的,很难在那些来自HTTPS的请求上进行负载均衡和会话信息维护处理。因为,这些请求中的信息已经被加密了。负载均衡器没有能力处理这类请求。不过,这里有两种方法可以解决这一问题:
  
  代理网络服务器
  硬件SSL解码器
   代理服务器位于服务器集群之前,首先由它接受所有的请求并对之进行解密,然后将这些处理后的请求根据头信息重新发往相应的节点上,这种方式不需要硬件上的支持,但会增加代理服务器的额外的负担。
  
   硬件SSL解码器,则是在请求到达负载均衡器之前,先经由它进行解密处理。这种方式比代理服务器的处理速度要快捷一些。但代价也高,而且实现比较复杂。

Reverse Proxy(就是对请求方显示单独的接口,对web server也是一样,但一般会携带原始请求的ip)

A reverse proxy or surrogate is a proxy server that is installed within the neighborhood of one or more servers. Typically, reverse proxies are used in front of Web servers. All connections coming from the Internet addressed to one of the Web servers are routed through the proxy server, which may either deal with the request itself or pass the request wholly or partially to the main web servers.

A reverse proxy dispatches in-bound network traffic to a set of servers, presenting a single interface to the caller. For example, a reverse proxy could be used for load balancing a cluster of web servers. In contrast, a forward proxy acts as a proxy for out-bound traffic. For example, an ISP may use a proxy to forward HTTP traffic from its clients to external web servers on the internet; it may also cache the results to improve performance.

Wednesday, January 02, 2008

(转)J2EE集群的神话

http://www.theserverside.com/tt/articles/article.tss?l=J2EEClustering

失效转移可以完全避免错误——否定

Jboss的文档中,整个章节都在警告你“你真的需要HTTP会话的复制吗?”。是的,有时没有失效转移的高可用性的解决方案也是可接受并且是廉价的。失效转移并不是你想象的那么强壮。

那么失效转移到底给你带来了什么?你可能想失效转移可以避免错误。你看,没有会话的失效转移,当一个服务器实例失效后,会话数据将丢失而导致错误。通过失效转移,会话可以从备份中恢复,而请求可以被其他服务器实例所处理,用户根本意识不到失效。这是事实,但这是有条件的!

回想一样我们定义的“失效转移”,我们定义了一个条件是失效转移是在“两个方法调用之间”发生的。这是说如果你有两个连续的对远程对象的方法调用,失效转移只会在第一调用成功后并且第二调用的请求发出前才能发生。

这样,当远程服务器在处理请求的过程中失效了会发生什么呢?答案是:多数情况处理将会停止而客户端将会看到错误信息。除非这个方法是等幂的(Idempotent),只有这个方法是等幂的,一些负载均衡器更智能,它会重试这些方法并将它失效转移到其他实例上。

为什么“等幂”重要呢,因为客户端不知道当失效发生的时候请求被执行到什么地方。是才刚刚初始化还是差不多就要完成了?客户端没法判断!如果方法不是等幂的,在相同方法上的两次调用可能会两次修改系统的状态,而使得系统出现不一致的情形。

你可能想所有在事务中的方法都是等幂的,毕竟,如果错误发生,事务将被回滚,事务状态的改变都将被复位。但事实上事务的边界可能不包括所有的远程方法调用过程。如果事务已经在服务器上提交了而返回给客户端时网络崩溃怎么办呢?客户端不知道服务器的事务是否是成功了。

在一些应用程序中,将所有的方法都做成等幂的是不可能的。这样,你只能通过失效转移减少错误,而不是避免它们。拿在线商店为例,假设每台服务器可以同时处理100个在线用户的请求,当一台服务器失效了,没有失效转移的解决方案将丢失100个用户的会话数据并激怒这些用户。而有失效转移的解决方案中,当失效发生的时候有20个用户正在处理请求,这样20个用户将被失效激怒。而其他80个用户正处于思考时间或在两个方法调用之间,这些用户可以透明地获得失效转移。这样,你就需做以下的考虑:

l 激怒20个用户和激怒100个用户造成影响的区别。

l 采用失效转移和不采用失效转移产品成本的区别

独立应用可以透明的迁移到集群结构中——否定

尽管一些供应商宣称他们的J2EE产品有这样的灵活性。不要相信他们!事实你要在开始系统设计时就要准备集群,而这将影响开发和测试的所有阶段。

Http Session

在集群环境中,如我前面提到的,使用HTTP Session有很多限制,这取决于你的应用程序服务器采用了那种会话失效转移的机制。第一个重要的限制就是所有保存的HTTP Session中的对象必须是可序列化的,这将限制设计和应用程序结构。一些设计模式和MVC框架会用HTTP Session保存一些不序列化的对象(如ServletContextEJB本地接口和WEB服 务引用),这样的设计不能在集群中工作。第二,对象的序列的反序列化对性能的影响很大,特别是数据库保存的方式。在这样的环境中,应该避免在会话中保存大 的或是众多的对象。如果你采用了内存复制的方式,如前所述你必须小心在会话中属性的交叉引用。其他在集群环境中的主要区别是在会话不管任何属性修改,你必 须调用“setAttribute()”方法。这个方法调用在独立的系统中是可选的。这个方法的目的是区别已修改的属性和那些没用到属性,这样系统可以只为失效转移备份必要的数据,从而提高性能。

缓存

我经历过的大多数J2EE项目都用了缓存来提高性能,同时流行的应用程序服务器也都提供了不同程度的缓存用来加快应用程序的速度。但这些缓存都是为那些典型的独立环境设计的,只能在单JVM实例中工作。我们需要缓存是因为一些对象很“重”,创建它需花费大量的时间和资源。因此我们维护了对象池用于重用这些对象,而不需要在后面创建。我们只有当维护缓存比创建对象更廉价时才能获得性能的提高。在集群环境,每个JVM实例都要维护一份缓存的拷贝,这些拷贝必须同步以维持所有服务器实例状态的一致性。有时这种类型的同步会比没有缓存带来更糟的性能。

Static变量

当我们设计J2EE应用程序时,在架构上经常会使用一些设计模式。这些如“Singleton”的设计模式会用到静态变量来在多对象之间共享状态。这种方式在单服务中工作得很好,但在集群环境将失效。集群中的每个实例都会在它的JVM实 例中维护一份静态变量的拷贝,这样就破坏了模式的机制。一个使用静态变量的例子就是用它来保持在线用户数。用静态变量来保存在线用户数是一个很简单的办 法,当用户进入或离开时就增加和减少它。这种方式在单服务器中绝对是好的,但在集群环境将失效。在集群中更好的方式是将所有状态保存到数据库。

外部资源

尽管J2EE规范不支持,但为各种目的仍然会用外部I/O的操作。例如,一些应用会使用文件系统来保存用户上传的文件,或是创建一个动态配置的XML文件。在集群环境是没有办法来在其他实例之间来复制这些文件的。为了在集群中工作,办法是用数据库作为外部文件的存放点,另外也可以使用SAN(存储区域网,Storage Area Network)作为存放点。

特殊服务

一 些特殊的服务只在独立的环境中才有意义,定时服务就一个很好例子,这种服务可以在一个固定的间隔时间有规律的触发。定时服务常用于执行一些自动化管理任 务。如日志文件滚动,系统数据备份,数据库一致性检查和冗余数据清理等。一些基于事件的服务也很难被迁移到集群环境中。初始化服务就是个好例子,它只在整 个系统启动时才发生。邮件通知服务也一样,它在一些警告条件下触发。

这些服务是被事件而不是被请求触发的,而且只被执行一次。这些服务使得负载均衡和失效转移在集群中没多少意义。

一些产品准备了这些服务,如Jboss使用了“集群单例设施”来协调所有实例,保证执行这些服务一次且仅有一次。基于你所选择的平台,一些特殊的服务可能会是把你的应用迁移到集群结构中的障碍。

分布式结构比并置结构更灵活——不一定

J2EE技术,尤其是EJB,天生就是用来做分布式计算。解耦业务功能,重用远程组件,这些使得多层应用非常流行。但是我们不能将所有的东西都分布。一些J2EE架构师认为Web层与EJB层并置得越近越好。这些计论后面会继续。先让我解释一下。

20 分布式结构

如图20所示,这是一个分布式结构。当请求来了,负载均衡器将请求分发到不同服务器中的不同WEB容器,如果请求包含了EJB调用,WEB容器将重发EJB调用到不同的EJB容器。这样,请求将被负载均衡和失效转移两次。

一些人看分布式结构,他们会指出:

l 第二次负载均衡没有必要,因为它不会使任务分配更平坦。每个服务器实例都有它们自己的WEB容器和EJB容器。把EJB容器用来处理来自其他实例WEB容器的请求比只在服务器内部调用并没有什么优势。

l 第二次失效转移没有必要,因为它不能是高可用性。多数供应商实现J2EE服务器都会在同一服务器中运行的WEB容器和EJB容器放在一个JVM实例中。如果EJB容器失效了,多数情况下在同一个JVM中的WEB容器也将同时失效。

l 性能将下降。想像一下对你的应用的一次调用包含一组对EJB的调用,如果你负载均衡了这些EJB,这将跨越每个服务器实例,导致许多不必要的服务器到服务器的交互。还有,如果这个方法在事务范围内,那么事务边界将包含许多服务器实例,这将严重影响性能。

实际上在运行期,多数的供应商(包括Sun JESWebLogicJboss)都会优化EJB调用机制,使请求首先选择同一个服务器中的EJB容器。这样,如图21所示,我们只在第一层(WEB层)做负载均衡,然后调用相同服务器上的服务。这种结构我们称之为并置结构。技术上说,并置结构是分布式结构的一种特例。

21 并置结构

一个有趣的问题是,既然多数的部署在运行期都演进成了并置结构,为什么不用本地接口代替远程接口,这将大提高性能。你当然可以,但是记住,当你使用本地接口后,WEB组件和EJB耦合得很紧,而方法调用也是直接的而不通过RMI/IIOP。负载均衡和失效转移分发器没有机会介入本地接口调用。“WEB+EJB”整体处理负载均衡和失效转移。

但不幸的是,在集群中使用本地接口在多数J2EE服务器中有局限性。使用本地接口的EJB是本地对象,是不可序列化的,这一个限制就使本地引用不能保存在HTTP Session中。一些产品,如Sun JES,会将本地接口区别看待,使它们可以序列化。这样就可以用在HTTP Session中。

另一个有趣的问题是,既然并置结构这么流行并且有好的性能,为什么还要分布式结构呢?这在多数情况下是有道理的,但有时分布式结构是不可替代的。

l EJB不仅被WEB容器使用,富客户端也会使用它。

l EJB组件和WEB组件需在不同的安全级别上,并需要物理分离。这样防火墙将被设置用于保护运行EJB的重要机器。

l WEB层和EJB层极端不对称使得分布式结构是更好的选择。比如,一些EJB组件非常复杂并且很消耗资源,它们只能运行在昂贵的大型服务器上,另一方面,WEB组件(HTMLJSPServlet)简单得只需廉价的PC服务器就能满足要求。在这种情况下,专门的WEB服务器可以用来接受客户端连接请求,很快处理静态数据(HTML和图像)和简单的WEB组件(JSPServlet)。大型服务器只被用来做复杂计算。这将更好的利用投资。

结论

集群与独立环境不同,J2EE供应商采用不同的方法来实现集群。如果你的项目为做到高伸缩性而使用集群,你应该在你的项目开始的时候就做准备。选择符合你的需求的正确的J2EE产品。选择正确的第三方软件和框架并确保它们能支持集群。最后设计正确的架构使得能从集群中受益而不是受害。


AppendixA: Classical cluster topology

The image “http://www.javaworld.com/jw-02-2001/images/jw-0223-extremescale7.gif” cannot be displayed, because it contains errors.


=================================================

ADDITIONAL INFORMATION

=================================================

垂直伸缩(scale vertically),应用部署在单个节点上,随着系统用户数的增多,可以增加系统的硬件资源,比如增加cpy,增加内存,增加带宽等。

水平伸缩(scale horizontally),应用可以部署在多个节点上,随着系统用户的增多,可以增加更多的处理节点,比如从一台节点增加到三台节点。 水平伸缩方式需要在每个节点上部署相同的应用,但是这种伸缩方式更加便宜,同时也提高了系统的可用性。

参考资料:http://www.theserverside.com/tt/articles/article.tss?l=ScalingYourJavaEEApplications

Thursday, November 01, 2007

Database Normalization And Design Techniques

One of the most important factors in dynamic web page development is database definition. If your tables are not set up properly, it can cause you a lot of headaches down the road when you have to perform miraculous SQL calls in your PHP code in order to extract the data you want. By understanding data relationships and the normalization of data, you will be better prepared to begin developing your application in PHP. ----
我们可以快速的开发一套系统,但是系统是否具有高可用性,是否支持大量用户的并发访问。前几天就有新闻,说是奥运门票的第二阶段销售开始,结果当天销售门票的网站就崩溃了,因为一下子有太多的用户去访问这个网站。 我想这种情况是可以预期的,谁都会知道抢购奥运门票的情形该有多火爆,所以应该是网站没有做好准备。 这里我主要关注系统的性能问题,即支持大并发量的用户访问,而所有决定系统性能的因素当中,数据库设计应该是最重要的,这是数据架构级别的问题,是决定性的,当然还有其他的因素,比如分布式系统的远程调用, 数据的缓存等等,这些也很重要。
我在网上找了一些资料,整理一下。
Basically, the Rules of Normalization are enforced by eliminating redundancy and inconsistent dependency in your table designs.

  1 引言
   数据库优化的目标无非是避免磁盘I/O瓶颈、减少CPU利用率和减少资源竞争。为了便于读者阅读和理解,笔者参阅了Sybase、Informix和 Oracle等大型数据库系统参考资料,基于多年的工程实践经验,从基本表设计、扩展设计和数据库表对象放置等角度进行讨论,着重讨论了如何避免磁盘 I/O瓶颈和减少资源竞争,相信读者会一目了然。

  2 基于第三范式的基本表设计

  在基于表驱动的信息管理系 统(MIS)中,基本表的设计规范是第三范式(3NF)。第三范式的基本特征是非主键属性只依赖于主键属性。基于第三范式的数据库表设计具有很多优点:一 是消除了冗余数据,节省了磁盘存储空间;二是有良好的数据完整性限制,即基于主外键的参照完整限制和基于主键的实体完整性限制,这使得数据容易维护,也容 易移植和更新;三是数据的可逆性好,在做连接(Join)查询或者合并表时不遗漏、也不重复;四是因消除了冗余数据(冗余列), 在查询(Select)时每个数据页存的数据行就多,这样就有效地减少了逻辑I/O,每个Cash存的页面就多,也减少物理I/O;五是对大多数事务 (Transaction)而言,运行性能好;六是物理设计(Physical Design)的机动性较大,能满足日益增长的用户需求。

  在基本表设计中,表的主键、外键、索引设计占有非常重要的地位,但系统设计人员往往只注重于满足用户要求,而没有从系统优化的高度来认识和重视它们。实际上,它们与系统的运行性能密切相关。现在从系统数据库优化角度讨论这些基本概念及其重要意义:

   (1)主键(Primary Key):主键被用于复杂的SQL语句时,频繁地在数据访问中被用到。一个表只有一个主键。主键应该有固定值(不能为Null或缺省值,要有相对稳定 性),不含代码信息,易访问。把常用(众所周知)的列作为主键才有意义。短主键最佳(小于25bytes),主键的长短影响索引的大小,索引的大小影响索 引页的大小,从而影响磁盘I/O。主键分为自然主键和人为主键。自然主键由实体的属性构成,自然主键可以是复合性的,在形成复合主键时,主键列不能太多, 复合主键使得Join*作复杂化、也增加了外键表的大小。人为主键是,在没有合适的自然属性键、或自然属性复杂或灵敏度高时,人为形成的。人为主键一般是 整型值(满足最小化要求),没有实际意义,也略微增加了表的大小;但减少了把它作为外键的表的大小。

OIDs Should Have No Business Meaning
A very critical issue that needs to be pointed out is that OIDs should have absolutely no business meaning
whatsoever. Nada. Zip. Zilch. Zero. Any column with a business meaning can potentially change, and if
there’s one thing that we learned over the years in the relational world it’s that it’s a fatal mistake to give
your keys meaning. If your users decide to change the business meaning, perhaps they want to add some
digits or make the number alphanumeric, you need to make changes to your database in every single spot
where you use that information. Anything that is used as a primary key in one table is virtually guaranteed
to be used in other tables as a foreign key. What should be a simple change, adding a digit to your
customer number, can be a huge maintenance nightmare. Yuck. In the relational database world, this OID
strategy is referred to as employing surrogate keys.


  (2)外键(Foreign Key):外键的作用是建立关系型数据库中表之间的关系(参照完整性),主键只能从独立的实体迁移到非独立的实体,成为后者的一个属性,被称为外键。

   (3)索引(Index):利用索引优化系统性能是显而易见的,对所有常用于查询中的Where子句的列和所有用于排序的列创建索引,可以避免整表扫描 或访问,在不改变表的物理结构的情况下,直接访问特定的数据列,这样减少数据存取时间;利用索引可以优化或排除耗时的分类*作;把数据分散到不同的页面 上,就分散了插入的数据;主键自动建立了唯一索引,因此唯一索引也能确保数据的唯一性(即实体完整性);索引码越小,定位就越直接;新建的索引效能最好, 因此定期更新索引非常必要。索引也有代价:有空间开销,建立它也要花费时间,在进行Insert、Delete和Update*作时,也有维护代价。索引 有两种:聚族索引和非聚族索引。一个表只能有一个聚族索引,可有多个非聚族索引。使用聚族索引查询数据要比使用非聚族索引快。在建索引前,应利用数据库系 统函数估算索引的大小。

  ① 聚族索引(Clustered Index):聚族索引的数据页按物理有序储存,占用空间小。选择策略是,被用于Where子句的列:包括范围查询、模糊查询或高度重复的列(连续磁盘扫 描);被用于连接Join*作的列;被用于Order by和Group by子句的列。聚族索引不利于插入*作,另外没有必要用主键建聚族索引。

   ② 非聚族索引(Nonclustered Index):与聚族索引相比,占用空间大,而且效率低。选择策略是,被用于Where子句的列:包括范围查询、模糊查询(在没有聚族索引时)、主键或外 键列、点(指针类)或小范围(返回的结果域小于整表数据的20%)查询;被用于连接Join*作的列、主键列(范围查询);被用于Order by和Group by子句的列;需要被覆盖的列。对只读表建多个非聚族索引有利。索引也有其弊端,一是创建索引要耗费时间,二是索引要占有大量磁盘空间,三是增加了维护代 价(在修改带索引的数据列时索引会减缓修改速度)。那么,在哪种情况下不建索引呢?对于小表(数据小于5页)、小到中表(不直接访问单行数据或结果集不用 排序)、单值域(返回值密集)、索引列值太长(大于20bitys)、容易变化的列、高度重复的列、Null值列,对没有被用于Where子语句和 Join查询的列都不能建索引。另外,对主要用于数据录入的,尽可能少建索引。当然,也要防止建立无效索引,当Where语句中多于5个条件时,维护索引 的开销大于索引的效益,这时,建立临时表存储有关数据更有效。

  批量导入数据时的注意事项:在实际应用中,大批量的计算(如电信话 单计费)用C语言程序做,这种基于主外键关系数据计算而得的批量数据(文本文件),可利用系统的自身功能函数(如Sybase的BCP命令)快速批量导 入,在导入数据库表时,可先删除相应库表的索引,这有利于加快导入速度,减少导入时间。在导入后再重建索引以便优化查询。

  (4) 锁:锁是并行处理的重要机制,能保持数据并发的一致性,即按事务进行处理;系统利用锁,保证数据完整性。因此,我们避免不了死锁,但在设计时可以充分考虑 如何避免长事务,减少排它锁时间,减少在事务中与用户的交互,杜绝让用户控制事务的长短;要避免批量数据同时执行,尤其是耗时并用到相同的数据表。锁的征 用:一个表同时只能有一个排它锁,一个用户用时,其它用户在等待。若用户数增加,则Server的性能下降,出现“假死”现象。如何避免死锁呢?从页级锁 到行级锁,减少了锁征用;给小表增加无效记录,从页级锁到行级锁没有影响,若在同一页内竞争有影响,可选择合适的聚族索引把数据分配到不同的页面;创建冗 余表;保持事务简短;同一批处理应该没有网络交互。

  (5)查询优化规则:在访问数据库表的数据(Access Data)时,要尽可能避免排序(Sort)、连接(Join)和相关子查询*作。经验告诉我们,在优化查询时,必须做到:
  ① 尽可能少的行;
  ② 避免排序或为尽可能少的行排序,若要做大量数据排序,最好将相关数据放在临时表中*作;用简单的键(列)排序,如整型或短字符串排序;
  ③ 避免表内的相关子查询;
  ④ 避免在Where子句中使用复杂的表达式或非起始的子字符串、用长字符串连接;
  ⑤ 在Where子句中多使用“与”(And)连接,少使用“或”(Or)连接;
  ⑥ 利用临时数据库。在查询多表、有多个连接、查询复杂、数据要过滤时,可以建临时表(索引)以减少I/O。但缺点是增加了空间开销。
除非每个列都有索引支持,否则在有连接的查询时分别找出两个动态索引,放在工作表中重新排序。

  3 基本表扩展设计
   基于第三范式设计的库表虽然有其优越性(见本文第一部分),然而在实际应用中有时不利于系统运行性能的优化:如需要部分数据时而要扫描整表,许多过程同 时竞争同一数据,反复用相同行计算相同的结果,过程从多表获取数据时引发大量的连接*作,当数据来源于多表时的连接*作;这都消耗了磁盘I/O和CPU时 间。

  尤其在遇到下列情形时,我们要对基本表进行扩展设计:许多过程要频繁访问一个表、子集数据访问、重复计算和冗余数据,有时用户要求一些过程优先或低的响应时间。

  如何避免这些不利因素呢?根据访问的频繁程度对相关表进行分割处理、存储冗余数据、存储衍生列、合并相关表处理,这些都是克服这些不利因素和优化系统运行的有效途径。

  3.1 分割表或储存冗余数据
  分割表分为水平分割表和垂直分割表两种。分割表增加了维护数据完整性的代价。
水 平分割表:一种是当多个过程频繁访问数据表的不同行时,水平分割表,并消除新表中的冗余数据列;若个别过程要访问整个数据,则要用连接*作,这也无妨分割 表;典型案例是电信话单按月分割存放。另一种是当主要过程要重复访问部分行时,最好将被重复访问的这些行单独形成子集表(冗余储存),这在不考虑磁盘空间 开销时显得十分重要;但在分割表以后,增加了维护难度,要用触发器立即更新、或存储过程或应用代码批量更新,这也会增加额外的磁盘I/O开销。

   垂直分割表(不破坏第三范式),一种是当多个过程频繁访问表的不同列时,可将表垂直分成几个表,减少磁盘I/O(每行的数据列少,每页存的数据行就多, 相应占用的页就少),更新时不必考虑锁,没有冗余数据。缺点是要在插入或删除数据时要考虑数据的完整性,用存储过程维护。另一种是当主要过程反复访问部分 列时,最好将这部分被频繁访问的列数据单独存为一个子集表(冗余储存),这在不考虑磁盘空间开销时显得十分重要;但这增加了重叠列的维护难度,要用触发器 立即更新、或存储过程或应用代码批量更新,这也会增加额外的磁盘I/O开销。垂直分割表可以达到最大化利用Cache的目的。

  总之,为主要过程分割表的方法适用于:各个过程需要表的不联结的子集,各个过程需要表的子集,访问频率高的主要过程不需要整表。在主要的、频繁访问的主表需要表的子集而其它主要频繁访问的过程需要整表时则产生冗余子集表。
注意,在分割表以后,要考虑重新建立索引。

  3.2 存储衍生数据
  对一些要做大量重复性计算的过程而言,若重复计算过程得到的结果相同(源列数据稳定,因此计算结果也不变),或计算牵扯多行数据需额外的磁盘I/O开销,或计算复杂需要大量的CPU时间,就考虑存储计算结果(冗余储存)。现予以分类说明:
  若在一行内重复计算,就在表内增加列存储结果。但若参与计算的列被更新时,必须要用触发器更新这个新列。

  若对表按类进行重复计算,就增加新表(一般而言,存放类和结果两列就可以了)存储相关结果。但若参与计算的列被更新时,就必须要用触发器立即更新、或存储过程或应用代码批量更新这个新表。

  若对多行进行重复性计算(如排名次),就在表内增加列存储结果。但若参与计算的列被更新时,必须要用触发器或存储过程更新这个新列。

  总之,存储冗余数据有利于加快访问速度;但违反了第三范式,这会增加维护数据完整性的代价,必须用触发器立即更新、或存储过程或应用代码批量更新,以维护数据的完整性。

  3.3 消除昂贵结合
   对于频繁同时访问多表的一些主要过程,考虑在主表内存储冗余数据,即存储冗余列或衍生列(它不依赖于主键),但破坏了第三范式,也增加了维护难度。在源 表的相关列发生变化时,必须要用触发器或存储过程更新这个冗余列。当主要过程总同时访问两个表时可以合并表,这样可以减少磁盘I/O*作,但破坏了第三范 式,也增加了维护难度。对父子表和1:1关系表合并方法不同:合并父子表后,产生冗余表;合并1:1关系表后,在表内产生冗余数据。

  4 数据库对象的放置策略
  数据库对象的放置策略是均匀地把数据分布在系统的磁盘中,平衡I/O访问,避免I/O瓶颈。

  ⑴ 访问分散到不同的磁盘,即使用户数据尽可能跨越多个设备,多个I/O运转,避免I/O竞争,克服访问瓶颈;分别放置随机访问和连续访问数据。
  ⑵ 分离系统数据库I/O和应用数据库I/O。把系统审计表和临时库表放在不忙的磁盘上。
  ⑶ 把事务日志放在单独的磁盘上,减少磁盘I/O开销,这还有利于在障碍后恢复,提高了系统的安全性。
  ⑷ 把频繁访问的“活性”表放在不同的磁盘上;把频繁用的表、频繁做Join*作的表分别放在单独的磁盘上,甚至把把频繁访问的表的字段放在不同的磁盘上,把访问分散到不同的磁盘上,避免I/O争夺;
   ⑸ 利用段分离频繁访问的表及其索引(非聚族的)、分离文本和图像数据。段的目的是平衡I/O,避免瓶颈,增加吞吐量,实现并行扫描,提高并发度,最大化磁盘 的吞吐量。利用逻辑段功能,分别放置“活性”表及其非聚族索引以平衡I/O。当然最好利用系统的默认段。另外,利用段可以使备份和恢复数据更加灵活,使系 统授权更加灵活。



Resources: