随机记事 http://diggex.net 一个记事本 Mon, 23 Aug 2010 02:53:45 +0000http://wordpress.org/?v=2.7.1 en hourly 1 Web Services框架XINS http://diggex.net/?p=666 http://diggex.net/?p=666#comments Mon, 23 Aug 2010 02:53:45 +0000 admin http://diggex.net/?p=666 XINS是个开源的Web Services框架,支持REST、SOAP、XML-RPC、JSON以及JSON-RPC等。它基于契约优先的开发模式,因此可以根据API规范生成代码与文档

XINS的官方站点深入了解XINS,也可以在这里查看XINS的全部特性,这里还有一个XINS的使用示例能帮助各位读者快速上手。最后,想要系统学习XINS的读者可以参考XINS用户指南

]]>
http://diggex.net/?feed=rss2&p=666
[语录]产品化,商业化,市场化 http://diggex.net/?p=665 http://diggex.net/?p=665#comments Wed, 28 Jul 2010 00:53:15 +0000 admin http://diggex.net/?p=665 产品化,商业化,市场化

]]>
http://diggex.net/?feed=rss2&p=665
SOAP协议规范 http://diggex.net/?p=664 http://diggex.net/?p=664#comments Mon, 26 Jul 2010 02:53:10 +0000 admin http://diggex.net/?p=664 SOAP协议规范

1. 简介

SOAP以XML形式提供了一个简单、轻量的用于在分散或分布环境中交换结构化和类型信息的机制。SOAP本身并没有定义任何应用程序语 义,如编程模型或特定语义的实现;实际上它通过提供一个有标准组件的包模型和在模块中编码数据的机制,定义了一个简单的表示应用程序语义的机制。这使 SOAP能够被用于从消息传递到RPC的各种系统。

SOAP包括三个部分

  • SOAP封装(见第4节)结构定义了一个整体框架用来表示消息中包含什么内容,谁来处理这些内容以及这些内容是可选的或是必需的。
  • SOAP编码规则(见第5节)定义了用以交换应用程序定义的数据类型的实例的一系列机制。
  • SOAP RPC表示(见第7节)定义了一个用来表示远程过程调用和应答的协定。

虽然这三个部分都作为SOAP的一部分一起描述,但它们在功能上是相交的。特别的,封装和编码规则是在不同的名域中定义的,这种模块性的定 义方法增加了简单性在SOAP封装,SOAP编码规则和SOAPRPC协定之外,这个规范还定义了两个协议的绑定,描述了在有或没有HTTP扩展框架 [6]的情况下,SOAP消息如何包含在HTTP消息[5]中被传送。

1.1 设计目标

SOAP的主要设计目标是简单性和可扩展性,这意味着传统的消息系统和分布对象系统的某些性质不是SOAP规范的一部分。这些性质包括:

  • 分布式碎片收集
  • 成批传送消息
  • 对象引用(要求分布式碎片收集)
  • 激活机制(要求对象引用)

1.2 符号约定

这篇文章中的关键字 “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”,”SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, 和”OPTIONAL”的解释在RFC-2119 [2]中。 这篇文章中用到的名域前缀 “SOAP-ENV” 和”SOAP-ENC”分别与”http://schemas.xmlsoap.org/soap/envelope/” 和”http://schemas.xmlsoap.org/soap/encoding/”关联。整篇文档中,名域前缀“xsi”被假定为与 URI”http://www.w3.org/1999/XMLSchema-instance“(在XMLSchema规范[11]定义)相连。类似 的,名域前缀”xsd“被假定为与URI”http://www.w3.org/1999/XMLSchema”(在[10]中定义)相连。名域前 缀”tns“用来表示任意名域。所有其它的名域前缀都只是例子。
名域URI的基本形式”some-URI“表示某些依赖于应用程序或上下文的URI[4]。这个规范用扩展BNF(在RFC-2616[5] 描述)描述某些结构。

1.3 SOAP消息举例

在这个例子中,GetLastTradePrice SOAP 请求被发往StockQuote服务。这个请求携带一个字符串参数和ticker符号,在SOAP应答中返回一个浮点数。XML名域用来区分SOAP标志 符和应用程序特定的标志符。这个例子说明了在第6节中定义的HTTP绑定。如果SOAP中管理XML负载的规则完全独立于HTTP是没有意义的,因为事实 上该负载是由HTTP携带的。在Appendix A中有更多的例子。

例1 在HTTP请求中嵌入SOAP消息

POST /StockQuote HTTP/1.1
Host:
www.stockquoteserver.com
Content-Type: text/xml;
charset=”utf-8″
Content-Length: nnnn
SOAPAction:
“Some-URI”
<SOAP-ENV:Envelope
xmlns:SOAP-ENV=”http://schemas.xmlsoap.org/soap/envelope/”
SOAP-ENV:encodingStyle=”http://schemas.xmlsoap.org/soap/encoding/”>
<SOAP-ENV:Body>
<m:GetLastTradePrice xmlns:m=”Some-URI”>
<symbol>DIS</symbol>
</m:GetLastTradePrice>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

下面是一条应答消息,包括HTTP消息,SOAP消息是其具体内容 :

例2 在HTTP应答中嵌入SOAP消息

HTTP/1.1 200 OK
Content-Type: text/xml;
charset=”utf-8″
Content-Length:
nnnn
<SOAP-ENV:Envelope
xmlns:SOAP-ENV=”http://schemas.xmlsoap.org/soap/envelope/”
SOAP-ENV:encodingStyle=”http://schemas.xmlsoap.org/soap/encoding/”/>
<SOAP-ENV:Body>
<m:GetLastTradePriceResponse xmlns:m=”Some-URI”>
<Price>34.5</Price>
</m:GetLastTradePriceResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

2. SOAP消息交换模型

SOAP消息从发送方到接收方是单向传送,但正如上面显示的,SOAP消息经常以请求/应答的方式实现。SOAP实现可以通过开发特定网络 系统的特性来优化。例如,HTTP绑定(见第6节)使SOAP应答消息以HTTP应答的方式传输,并使用同一个连接返回请求。不管SOAP被绑定到哪个协 议,SOAP消息采用所谓的”消息路径“发送,这使在终节点之外的中间节点可以处理消息。一个接收SOAP消息的SOAP应用程序必须按顺序执行以下的动 作来处理消息:识别应用程序想要的SOAP消息的所有部分 (见4.2.2节)检验应用程序是否支持第一步中识别的消息中所有必需部分并处理它。如果不支持,则丢弃消息(见4.4节)。在不影响处理结果的情况下, 处理器可能忽略第一步中识别出的可选部分。如果这个SOAP应用程序不是这个消息的最终目的地,则在转发消息之前删除第一步中识别出来的所有部分。为了正 确处理一条消息或者消息的一部分,SOAP处理器需要理解:所用的交换方式(单向,请求/应答,多路发送等等),这种方式下接收者的任务,RPC机制(如 果有的话)的使用(如第7节中所述),数据的表现方法或编码,还有其它必需的语义。尽管属性(比如SOAP encodingstyle,见4.1.1节)可以用于描述一个消息的某些方面,但这个规范并不 强制所有的接收方也必须有同样的属性并取同样的属性值。举个例子,某一特定的应用可能知道一个元素表示一条遵循第7节约定的RPC请求,但是另外一些应用 可能认为指向该元素的所有消息都用单向传输,而不是类似第7节的请求应答模式。
(译者注:交互双方的SOAP消息并不一定要遵循同样的格式设定,而只需要以一种双方可理解的格式交换信息就可以了)

3. 与XML的关系

所有的SOAP消息都使用XML形式编码(更多有关XML的信息请见[7])一个SOAP应用程序产生的消息中,所有由SOAP定义的元素 和属性中必须包括正确的名域。SOAP应用程序必须能够处理它接收到的消息中的SOAP名域(见4.4节),并且它可以处理没有SOAP名域的SOAP消 息,就象它们有正确的名域一样。SOAP定义了两个名域(更多有关XML名域的信息请见[8])

  • SOAP封装的名域标志符是”http://schemas.xmlsoap.org/soap/envelope/”
  • SOAP的编码规则的名域标志符是”http://schemas.xmlsoap.org/soap/encoding/”

SOAP消息中不能包含文档类型声明,也不能包括消息处理指令。[7] SOAP使用”ID”类型”id”属性来指定一个元素的唯一的标志符,同时该属性是局部的和无需校验的。SOAP使用”uri-reference”类型 的”href”属性指定对这个值的引用,同时该属性是局部的和无需校验的。这样就遵从了XML规范[7],XMLSchema规范[11]和XML连接语 言规范[9]的风格。除了SOAP mustUnderstand 属性(见4.2.3节)和SOAPactor属性(见4.2.2节)之外,一般允许属性和它们的值出现在XML文档实例或Schema中(两者效果相 同)。也就是说,在DTD或Schema中声明一个缺省值或固定值和在XML文档实例中设置它的值在语义上相同。

4. SOAP封装

SOAP消息是一个XML文档,包括一个必需的SOAP封装,一个可选的SOAP头和一个必需的SOAP体。在这篇规范剩余部分中,提到SOAP消息时就是指这个XML文档。这一节中定义的元素和属性的名域标志符为:

“http://schemas.xmlsoap.org/soap/envelope/” 。一个SOAP消息包括以下部分:1.在表示这个消息的XML文档中,封装是顶层元素。2.应用SOAP交换信息的各方是分散的且没有预先协定,SOAP 头提供了向SOAP消息中添加关于这条SOAP消息的某些要素(feature)的机制。SOAP定义了少量的属性用来表明这项要素(feature)是 否可选以及由谁来处理。(见4.2节)3.SOAP体是包含消息的最终接收者想要的信息的容器(见4.3节)。SOAP为SOAP体定义了一个Fault 元素用来报告错误信息。语法规则如下所示:

封装

  1. 元素名是 “Envelope”
  2. 在SOAP消息中必须出现。
  3. 可以包含名域声明和附加属性。如果包含附加属性,这些属性必须限定名域。类似的,”Envelope”可以包含附加子元素,这些也必须限定名域且跟在SOAP体元素之后。

SOAP头 (见4.2节)

  1. 元素名是”Header”
  2. 在SOAP消息中可能出现。如果出现的话,必须是SOAP封装元素的第一个直接子元素。
  3. SOAP头可以包含多个条目,每个都是SOAP头元素的直接子元素。所有SOAP头的直接子元素都必须限定名域。

SOAP体 (见4.3节)

  1. 元素名是”Body”
  2. 在SOAP消息中必须出现且必须是SOAP封装元素的直接子元素。它必须直接跟在SOAP头元素(如果有)之后。否则它必须是SOAP封装元素的第一个直接子元素。
  3. SOAP体可以包括多个条目,每个条目必须是SOAP体元素的直接子元素。SOAP体元素的直接子元素可以限定名域。SOAP定义了SOAPFault元素来表示错误信息。

4.1.1 SOAP encodingStyle属性

EncodingStyle全局属性用来表示SOAP消息的序列化规则。这个属性可以在任何元素中出现,作用范围与名域声明的作用范围很相 似,为这个元素的内容和它的所有没有重载此属性的子元素。SOAP消息没有定义缺省编码。属性值是一个或多个URI的顺序列表,每个URI确定了一种或多 种序列化规则,用来不同程度反序列化SOAP消息,举例如下:

“http://schemas.xmlsoap.org/soap/encoding/”
“http://my.host/encoding/restricted http://my.host/encoding/”
“”

第5节中定义的序列化规则由URI”http://schemas.xmlsoap.org/soap/encoding/” 确定。使用这个特定序列化规则的消息应该用encodingStyle属性说明这一点。另外,所有 以”http://schemas.xmlsoap.org/soap/encoding/”开头的URI中的序列化规则与第5节中定义的SOAP编码规 则相一致。一个零长度的URI(”")明确显示所含元素没有任何编码形式。这可以用来取消上一级元素的所有编码声明。

4.1.2 封装版本模型

SOAP没有定义常规的基于主版本号和辅版本号的版本形式。SOAP消息必须有一个封装元素与名 域”http://schemas.xmlsoap.org/soap/envelope/”关联。如果SOAP应用程序接收到的SOAP消息中的 SOAP封装元素与其他的名域关联,则视为版本错误,应用程序必须丢弃这个消息。如果消息是通过HTTP之类的请求/应答协议收到的,应用程序必须回答一 个SOAP VersionMismatch 错误信息(见4.4节)。

4.2 SOAP头

SOAP为相互通信的团体之间提供了一种很灵活的机制:在无须预先协定的情况下,以分散但标准的方式扩展消息。可以在SOAP头中添加条目 实现这种扩展,典型的例子有认证,事务管理,支付等等。头元素编码为SOAP封装元素的第一个直接子元素。头元素的所有直接子元素称作条目。条目的编码规 则如下:

一个条目有它的完整的元素名(包括名域URI和局部名)确定。SOAP头的直接子元素必须有名域限制。
SOAP encodingStyle属性可以用来指示条目所用的编码形式(见4.1.1节)
SOAP mustUnderstand属性(见4.2.3节)和SOAPactor属性(见4.2.2节)可以用来指示如何处理这个条目以及由谁来处理。(见4.2.1节)

4.2.1 使用头属性

这一节中定义的SOAP头属性确定了SOAP消息的接收者应该怎样按第2节中所述的方式处理消息。产生SOAP消息的SOAP应用程序,应 该仅仅在SOAP头元素的直接子元素中使用这些SOAP头属性。SOAP消息的接收者必须忽略所有不在SOAP头元素的直接子元素中SOAP头属性。下面 的例子是一个SOAP头,包括一个元素标志符”Transaction”,”mustUnderstand”取值为”1″和数值5。这应该以如下方式编 码:

<SOAP-ENV:Header>
<t:Transaction
xmlns:t=”some-URI” SOAP-ENV:mustUnderstand=”1″>
5
</t:Transaction>
</SOAP-ENV:Header>

4.2.2 SOAP actor属性

一个SOAP消息从始节点到终节点的过程中,可能沿着消息路径经过一系列SOAP中间节点。一个SOAP中间节点是一个可以接收转发 SOAP消息的应用程序。中间节点和终节点由URI区分。可能SOAP消息的终节点并不需要所有部分,而在消息路径上的一个和几个中间节点可能需要这些内 容。头元素的接收者扮演的角色类似于一个过滤器,防止这些只发给本接受者的消息部分扩散到其它节点。即一个头元素的接收者必须不转发这些头元素到SOAP 消息路径上的下一个应用程序。同样的,接收者可能插入一个相似的头元素。SOAP actor全局属性可以用于指示头元素的接收者。SOAP actor属性的值是一个URI。

URI “http://schemas.xmlsoap.org/soap/actor/next”指出了第一个处理这个消息的SOAP应用程序需要这个头元素。这类似于HTTP头中用Connection域表示hop-by-hop范围模型。省略SOAP actor属性表示接收者是SOAP消息的终节点。如果这个属性要生效,它必须出现在SOAP消息实例中。(见第3节和4.2.1节)

4.2.3 SOAP mustUnderstand属性

SOAP mustUnderstand全局属性用来指示接受者在处理消息时这个条目是否必须处理。条目的接收者由SOAP actor属性定义(见4.2.2节)。MustUnderstand属性的值是”1″ 或 “0″。缺少SOAP mustUnderstand属性在语义上等同于它的值为”0″。如果一个头元素的SOAP mustUnderstand属性的值是”1″,那么条目的接受者必须或者遵守语义(如以元素的全名传送)并按照语义正确的处理,或者放弃处理消息(见4.4节)。SOAP mustUnderstand 属性考虑了消息演变的准确性(robust evolution)。必须假定包含SOAP mustUnderstand属性且值为”1″的元素以某种方式修改了它们的父元素或同层元素的语义。以这种方式连接元素确保了语义上的变化不会被那些不能完全理解它的接收者忽略。如果这个属性要生效,它必须出现在SOAP消息实例中。(见第3节和4.2.1节)

4.3 SOAP体

SOAP体元素提供了一个简单的机制,使消息的最终接收者能交换必要的信息。使用体元素的典型情况包括配置RPC请求和错误报告。体元素编 码为SOAP封装元素的直接子元素。如果已经有一个头元素,那么体元素必须紧跟在头元素之后,否则它必须是SOAP封装元素的第一个直接子元素。体元素的 所有直接子元素称作体条目,每个体条目在SOAP体元素中编码为一个独立的元素。条目的编码规则如下:

  • 一个条目由它的元素全名(包括名域URI和局部名)确定。SOAP体元素的直接子元素可能是名域限制的。
  • SOAP encodingStyle属性可能用来指示条目(见4.1.1节)的编码方式。
  • SOAP定义了一个Fault条目用来报告错误信息。(见4.4节)

4.3.1 SOAP头和体的关系

虽然头和体定义为独立的元素,它们实际上是有关系的。体条目和头条目的关系如下:体条目在语义上等同于actor属性为缺省值且mustUnderstand属性值为”1″的头条目。不使用actor属性则表示缺省的actor。(见4.2.2节)

4.4 SOAP错误

SOAP错误元素用于在SOAP消息中携带错误和(或)状态信息。如果有SOAP错误元素,它必须以以体条目的方式出现,并且在一个体元素中最多出现一次。SOAP错误元素定义了以下四个子元素:

  • faultcode
    faultcode元素给软件提供了一个识别此错误的算法机制。SOAP错误元素必须有faultcode子元素,并且它的值必须是一个合法的名(在[8]节定义)。SOAP定义一些SOAP faultcode描述基本的SOAP错误(见4.4.1节)。
  • faultstring
    faultstring元素提供了一个错误解释,而不是为了软件处理。faultstring元素类似于HTTP中定义(见[5],第6.1节)的 ‘Reason-Phrase’。SOAP错误元素必须有faultstring子元素,并且它应该提供一些错误本质的解释信息。
  • faultactor
    faultactor元素提供了在消息路径上是谁导致了错误发生的信息(见第2节)。它类似于SOAP actor属性(见4.2.2节),只是SOAP actor指的是头条目的目的地,faultactor指的是错误的来源。faultactor属性的值是用来区分错误来源的URI。不是SOAP消息的最终目的地的应用程序必须在SOAP Fault元素中包含faultactor元素。消息的最终目的地可以使用faultactor元素明确的指示是它产生了这个错误(参见下面的detail元素)
  • detail
    detail元素用来携带与Body元素有关的应用程序所要的错误信息。如果Body元素的内容不能被成功的处理,则必须包含detail子元素。它不能 用来携带属于头条目的错误信息。头条目的详细出错信息必须由头条目携带。Fault元素中没有detail元素表示这个错误与Body元素的处理无关。在 有错误的时候,这可以用来区分Body元素有没有被正确的处理。detail元素的所有直接子元素称作detail条目,并且每个detail条目在 detail元素中编码为独立的元素。detail条目的编码规则如下(参见例10): 一个detail条目由它的元素全名(包括名域URI和局部名)确定。SOAP体元素的直接子元素可能是名域限制的。SOAP encodingStyle属性可能用来指示detail条目(见4.1.1节)的编码方式。也可以有其它的Fault子元素,只要它们是名域限制的。

4.4.1 SOAP 错误代码

在描述这个规范中定义的错误时,这一节中定义的Faultcode值必须用在faultcode元素中。这些faultcode值得名域标 志符为”http://schemas.xmlsoap.org/soap/envelope/”。定义这个规范之外的方法时推荐(不要求)使用这个名 域。缺省的SOAP faultcode值以可扩展的方式定义,允许定义新的SOAP faultcode值,并与现有的faultcode值向后兼容。使用的机制类似于HTTP中定义的1xx, 2xx,3xx等基本的状态类(见[5]第10节),不过,它们定义为XML合法名(见 [8] 第3节 ),而不是整数。 字符”.”(点)作为faultcode的分隔符,点左边的错误代码比右边的错误代码更为普通。如:

Client.Authentication

这篇文档中定义的faultcode值是:

名称 含义
VersionMismatch 处理方发现SOAP封装元素有不合法的名域(见4.1.2节)
MustUnderstand 处理方不理解或者不服从一个包含值为”1″的
mustUnderstand 属性的 SOAP头元素的直接子元素。(见4.2.3节)

Client

Client错误类表示消息的格式错误或者不包含适当的正确信息。例如,消息可能缺少正确的认证和支付信息。一般地,它表示消息不能不作修改就重发。参见4.4节

SOAP Fault detail子元素的描述。

Server

Server错误类表示由于消息的处理过程而不是消息的内容本身使得消息消息不能正确的处理。例如,处理消息时可能要与其它处理器通信,但它没有响应。这个消息可能在迟一点的时间处理成功。 SOAP Fault子元素的详细信息参见4.4节

5. SOAP编码

SOAP编码格式基于一个简单的类型系统,概括了程序语言,数据库和半结构化数据等类型系统的共同特性。一个类型或者是一个简单的(标量 的)类型,或者是由几个部分组合而成的复合类型,其中每个部分都有自己的类型。以下将详细描述这些类型。这一节定义了类型化对象的序列化规则。它分两个层 次。首先,给定一个与类型系统的符号系统一致的Schema(译者注:这里的schema不是符合XML语法的schema,而仅仅表示广义的用于表示消 息结构的定义方式),就构造了XML语法的Schema。然后,给定一个类型系统的Schema和与这个Schema一致的特定的值,就构造了一个XML 文档实例。反之,给定一个依照这些规则产生的XML文档实例和初始的Schema,就可以构造初始值的一个副本。这一节中定义的元素和属性的名域标志符 为”http://schemas.xmlsoap.org/soap/encoding/”。下面的例子都假定在上一层的元素中声明了名域。
鼓励使用这一节中描述的数据模型和编码方式,但也可以在SOAP中使用其他的数据模型和编码方式。(见4.1.1节)

5.1 XML中的编码类型规则

XML允许非常灵活的数据编码方式。SOAP定义了一个较小的规则集合。这一节在总的层次上定义了这些编码规则,下一节将描述特定类型的编码规则的细节。这一节定义的编码规则可以与第7节中所述的RPC调用和应答映射结合使用。下面的术语用来描述编码规则:

  • 一个”value”是一个字符串,类型(数字,日期,枚举等等)的名或是几个简单值的组合。所有的值都有特定的类型。
  • 一个”simple value”没有名部分, 如特定的字符串,整数,枚举值等等。
  • 一个”compound value”是相关的值的结合,如定单,股票报表,街道地址等等。在”compound value”中,每个相关的值都潜在的以名,序数或这两者来区分。这叫作”a ccessor”。复合值的例子有定单和股票报表等等。数组也是复合值。在复合值中,多个accessor有相同的名是允许的,例如RDF就是这样做的。
  • 一个”array”是一个复合值,成员值按照在数组中的位置相互区分。
  • 一个”struct”也是一个复合值,成员值之间的唯一区别是accessor名,accessor名互不相同。
  • 一个”simple type”是简单值的类,如叫做”string” “integer”的类,还有枚举类等等。
  • 一个”compound type”是复合值的类。复合类型的例子有定单类,它们有相同的accessor名(shipTo, totalCost等),但可能会有不同的值(可能以后被设置为确定的值)。

在复合类型中,如果类型内的accessor名互不相同,但是可能与其他类型中的accessor名相同,即,accessor名加上类型 名形成一个唯一的标志符,这个名叫作”局部范围名”。如果名是直接或间接的基于URI的一部分,那么不管它出现在什么类型中,这个名本身就可以唯一标志这 个accessor,这样的名叫作”全局范围名”。给定了schema中相关的值的序列化信息,就可能确定某些值只与某个accessor的一个实例有 关。其它情况下则无法确定。当且仅当一个accessor引用一个值,这个值才能被视为”single-reference”,如果有不止一个 accessor引用它,那么就将它视为”multi-reference”。注意,可能一个确定的值在一个schema中是”single- reference”,而在另一个schema中是”multi-reference”。在语句构成上,一个元素可能是”independent” 或 “embedded”。一个独立的元素指出现在序列化最顶层的任何元素。所有其它元素都是嵌入元素。虽然用xsi:type属性可以使值的结构和类型变为 自描述的,但是序列化规则允许值的类型仅仅参照schema而定。这样的schema可能使用”XML Schema Part 1: Structures” [10]和”XML Schema Part 2: Datatypes” [11]中描述的符号系统,也可能使用其它符号系统。注意,虽然序列化规则可以用于除了数组和结构之外的复合类型,但是许多schema仅仅包含数组和结 构类型。序列化规则如下:

所有的值以元素内容的形式表示。一个multi-reference值必须表示为一个独立元素的内容,而一个single-reference值最好不要这样表示(也可以这样表示)。对于每个具有值的元素,值的类型时必须用下述三种方式之一描述:

  • 所属元素实例有xsi:type属性
  • 所属元素是一个有SOAP-ENC:arrayType 属性(该属性可能是缺省的)的元素的子元素,或者
  • 所属元素的名具有特定的类型,类型可以由schema确定。

一个简单值表示为字符数据,即没有任何子元素。每个简单值必须具有一个类型,这个类型或者是XML Schemas Specification, part 2 [11]有的类型,或者具有源类型(参见5.2节)。一个复合值编码成一个元素的序列,每个accessor用一个嵌入元素表示,该元素的元素名和 accessor的名一致。如果accessor的名是局部于其所属的类型的,则该元素的元素名不是合格的,否则对应的元素名是合格的。(参见5.4节)
一个multi-reference的简单值或复合值编码成一个独立的元素,这个元素包含一个局部的无需校验的属性,属性名为”id”,类型为”ID” (依照XML Specification [7])。值的每个accessor对应一个空元素,该元素有一个局部的,无需校验的属性,属性名为”href”,类型为” uri-reference “(依照XML Schema Specification [11]),”href”属性的值引用了相对应的独立元素的URI标志符。字符串和字符数组表示为multi-reference的简单类型,但是特殊的 规则使它们在普通的情况下能被更有效的表示(参见5.2.1节和5.2.3节)。字符串和字符数组值的accessor可能有一个名字为”id”,类型 为”ID”(依照XML Specification [7])的属性。如果这样,所有这个值的所有其它accessor编码成一个空元素,这个元素有一个局部的,无需校验的属性,属性名为”href”,类型 为” uri-reference “(依照XML Schema Specification [11]),”href”属性的值引用了包含这个值的元素的URI标志符。编码时允许一个值有多个引用,就像多个不同的值有多个引用一样,但这仅在从上下 文可以知道这个XML文档实例的含义没有改变时才可使用。数组是复合值(参见5.4.2节)。SOAP数组定义为具有类型”SOAP- ENC:Array”或从它衍生的类型.

SOAP数组可以时一维或多维,它们的成员以序数位置相互区分。一个数组值表示为反映这个数组的一系列元素,数组成员按升序出现。对多维数组来说,右边的这一维变化最快。每个成员元素命名为一个独立元素。(见规则2)SOAP数组可以是single-reference 或multi-reference值,因此可以表示为嵌入元素或独立元素的内容。SOAP数组必须包含一个”SOAP-ENC:arrayType”属性,它的值指定了包含元素的类型和数组的维数。”SOAP-ENC:arrayType”属性的值定义如下:

arrayTypeValue = atype asize
atype = QName *( rank )
rank = “[" *( "," ) "]”
asize = “[" #length "]”
length = 1*DIGIT

  • “atype”结构是被包含元素的类型名,它表示为QName并且作为类型限制在XML元素声明的
  • “type”属性中出现(这意味着被包含元素的所有值都要与该类型一致,即在SOAP-ENC:a rrayType中引用的类型必须是每个数组成员的类型或超类型)。在arrays of arrays or “jagged arrays”的情况下,类型组件编码为”innermost”类型且在从第一层开始的嵌套数组的每一层中,类型名后都跟随一个rank结构。多维数组编码时从第一维起,每一维之间用逗号隔开。
  • “asize”结构包含一个以逗号分隔的列表,数值0,1或其它整数表示数组每一维的长度。整数0表示没有指定详细的大小,但是可能 在检查数组实际成员的大小后确定。例如,一个5个成员的整型数组的arrayTypeValue值为”int[][5]“,它的atype值是 int[]“,asize值是”[5]“。同样,一个3个成员的两维整型数组的arrayTypeValue值为”int[,][3]“,它的atype 值是int[,]“,asize值是”[3]“。

一个SOAP数组成员可能包含一个”SOAP-ENC:offset”属性表示这一项在整个数组中的位置偏移值。这被用来指示一个部分储值 数组(见5.4.2.1节)的位置偏移值。同样,一个数组成员可能包含一个”SOAP-ENC:position”属性表示这一项在整个数组中的位置,这 被用来描述稀疏数组(见5.4.2.2节)的成员。”SOAP-ENC:offset” 和”SOAP-ENC:position”属性值的定义如下:

arrayPoint = “[" #length "]”
偏移值和位置从0开始
NULL值或缺省值可能通过省略accssor元素来表示。NULL值也可能通过一个包含值为’1′的xsi:null属性的accssor元素来表示, 其它的依赖于应用程序的属性和值也可能用来表示NULL值。注意,规则2允许独立的元素和数组成员名不同于值类型的元素。

5.2 简单类型

SOAP采用了”XML Schema Part 2: Datatypes”规范[11]“Built-in datatypes”节中的所有类型作为简单类型,包括值和取值范围。例如:

类型 举例
int 58502
float 314159265358979E+1
negativeInteger -32768
string Louis “Satchmo” Armstrong

在XML Schema规范中声明的数据类型可以直接用在元素schema中,也可以使用从这些类型衍生的新类型。一个schema和对应的具有这些类型的元素的数据实例的例子如下所示:

<element name=”age” type=”int”/>
<element name=”height” type=”float”/>
<element name=”displacement” type=”negativeInteger”/>
<element name=”color”>
<simpleType base=”xsd:string”>
<enumeration value=”Green”/>
<enumeration value=”Blue”/>
</simpleType>
</element>
<age>45</age>
<height>5.9</height>
<displacement>-450</displacement>
<color>Blue</color>

所有简单值必须编码为元素的内容,它的类型或者在”XML Schema Part 2: Datatypes”规范[11]中定义过,或者是基于一个用XML Schema规范提供的机制能推衍生出的类型。如果一个简单值编码为独立元素或异质数组成员,那么有一个对应于数据类型的元素声明将会很方便。因为”XML Schema Part 2: Datatypes”规范[11]包括了类型定义,但是不包括对应的元素声明,SOAP-ENC schema和名域为每个简单数据类型声明了一个元素,如<SOAP-ENC:int id=”int1″>45</SOAP-ENC:int>

5.2.1 字符串

字符串数据类型的定义在”XML Schema Part 2: Datatypes”规范[11]中。注意,这不同于许多数据库和程序语言中的”string”类型,特别的,字符串数据类型可能禁止某些在那些语言中允 许的字符。(这些值必须用xsd:string之外的数据类型表示)一个字符串可能编码为一个single-reference 或 multi-reference值。包含字符串值的元素可能有一个”id”属性。附加的accessor元素可能有对应的”href”属性。
例如,同一字符串的两个accessor可能以如下形式出现:

<greeting id=”String-0″>Hello</greeting>
<salutation href=”#String-0″/>

但是,如果两个accessor参考同一字符串实例(或字符串的子类型),这不是一个实质问题,它们可以编码为两个single-reference值,如下所示:

<greeting>Hello</greeting>
<salutation>Hello</salutation>

这个例子的schema片断如下所示:

<element name=”greeting” type=”SOAP-ENC:string”/>
<element name=”salutation” type=”SOAP-ENC:string”/>

在这个例子中,SOAP-ENC:string类型用作元素的类型,这是声明数据类型是”xsd:string”且允许”id” 和”href”属性的元素的简便方法。精确定义参见SOAP编码schema。Schemas可以使用这些源自SOAP编码schema的声明,但也可以不这样做。

5.2.2 Enumerations

“XML Schema Part 2: Datatypes”规范 [11] 定义了”enumeration.”机制。SOAP数据模型直接采用了这种机制。但是,由于程序语言和其它语言在定义枚举时通常有些不同,所以我们在这里 详细阐述了它的概念并描述了一个列表成员的可能取的值是如何编码的。”Enumeration”作为一个概念表示不同的名字的集合。一个特定的枚举就是对 应于特定的基类型的不同的值的列表。例如,颜色集合(”Green”, “Blue”, “Brown”)可以定义为基于字符串类型的枚举,(”1″, “3″, “5″)可能是一个基于整型数的枚举,等等。”XML Schema Part 2: Datatypes” [11]支持除了布尔型以外所有简单类型的枚举。”XML Schema Part 1: Structures”规范[10]的语言可以用来定义枚举类型。如果schema由另一个没有特定基类型适用的符号系统生成,就使用”string”。 在下面schema的例子中,”EyeColor”定义为字符串,可能的值是”Green”, “Blue”, 或”Brown”的枚举,数据实例按照schema显示如下。

<element name=”EyeColor” type=”tns:EyeColor”/>
<simpleType name=”EyeColor” base=”xsd:string”>
<enumeration value=”Green”/>
<enumeration value=”Blue”/>
<enumeration value=”Brown”/>
</simpleType>
<Person>
<Name>Henry Ford</Name>
<Age>32</Age>
<EyeColor>Brown</EyeColor>
</Person>

5.2.3 字符数组

一个字符数组可能编码为single-reference 或multi-reference值。字符数组的编码规则与字符串的编码规则类似。特别的,包含字符数组的元素值可能由一个”id”属性,附加的 accssor元素可能有相应的”href”属性。推荐使用定义在XML Schemas [10][11]中的’base64′编码(使用在2045 [13]中定义的base64编码算法)表示模糊字符数组。不过,由于行长度(line length)的限制,通常在MIME中应用base64编码,SOAP中一般不应用base64编码。但是提供了”SOAP-ENC:base64″子 类型使之能用于SOAP。

<picture xsi:type=”SOAP-ENC:base64″>
aG93IG5vDyBicm73biBjb3cNCg==
</picture>

5.3 多态accessor

许多语言允许能够多态访问多种类型值的accessor,每种类型在运行时可用。一个多态accessor实例必须包含一个”xsi:type”属性描述实际值的类型。例如,一个名为”cost”类型值为”xsd:float”的多态accessor编码如下:

<cost xsi:type=”xsd:float”>29.95</cost>与之对比,类型值不变的accessor编码如下:

<cost>29.95</cost>

5.4 Compound types复合类型

SOAP定义了与下列常在程序语言中出现的结构性模式对应的类型:

  • 结构:一个”struct”是一个复合值,它的成员值的唯一区别是accessor名称,任意两个accessor名称都不相同。
  • 数组:一个”array”是一个复合值,它的成员值的唯一区别是序数位置。

SOAP也允许结构和数组之外的其它数据的序列化,例如Directed-Labeled-Graph Data Model之类的数据中,单个节点有许多不同的accssor,有些不止出现一次。SOAP序列化规则不要求底层的数据模型在accssor之间区分次序,但如果有这样的次序的话,这些accssor必须按照这个顺序编码。

5.4.1 复合值,结构和值引用

复合值的成员编码为accessor元素。当accessor由名区分时(如结构),accessor名即作为元素名。名局部于类型的accessor有不受限的名,其它的accessor则有受限的名。下面的例子是类型为”Book”的结构:

<e:Book>
<author>Henry Ford</author>
<preface>Prefatory text</preface>
<intro>This is a book.</intro>
</e:Book>

以下是描述上面结构的schema片断:

<element name=”Book”>
<complexType>
<element name=”author” type=”xsd:string”/>
<element name=”preface” type=”xsd:string”/>
<element name=”intro” type=”xsd:string”/>
</complexType>
</e:Book>

以下是一个同时具有简单和复杂成员类型的例子。它显示两层引用。注意”Author”accssor元素的”href”属性是对相应具有”id”属性的值的引用。”Address”与之类似。

<e:Book>
<title>My Life and Work</title>
<author href=”#Person-1″/>
</e:Book>
<e:Person id=”Person-1″>
<name>Henry Ford</name>
<address href=”#Address-2″/>
</e:Person>
<e:Address id=”Address-2″>
<email>mailto:henryford@hotmail.com</email>
<web>http://www.henryford.com</web>
</e:Address>

当”Person”的值和”Address”的值是multi-reference时,上面的形式是正确的。如果它
们是single-reference,就必须用嵌入的形式,如下所示:

<e:Book>
<title>My Life and Work</title>
<author>
<name>Henry Ford</name>
<address>
<email>mailto:henryford@hotmail.com</email>
<web>http://www.henryford.com</web>
</address>
</author>
</e:Book>

如果添加一个限制,任意两个人都不会有相同的地址,并且地址可以是街道或Email地址,一本书可以有两个作者,编码如下:

<e:Book>
<title>My Life and Work</title>
<firstauthor href=”#Person-1″/>
<secondauthor href=”#Person-2″/>
</e:Book>
<e:Person id=”Person-1″>
<name>Henry Ford</name>
<address xsi:type=”m:Electronic-address”>
<email>mailto:henryford@hotmail.com</email>
<web>http://www.henryford.com</web>
</address>
</e:Person>
<e:Person id=”Person-2″>
<name>Samuel Crowther</name>
<address xsi:type=”n:Street-address”>
<street>Martin Luther King Rd</street>
<city>Raleigh</city>
<state>North Carolina</state>
</address>
</e:Person>

序列化可以包含对不在同一个资源的值的引用:

<e:Book>
<title>Paradise Lost</title>
<firstauthor href=”http://www.dartmouth.edu/~milton/”/>
</e:Book>

以下是描述上面结构的schema片断:

<element name=”Book” type=”tns:Book”/>
<complexType name=”Book”>
<!– Either the following group must occur or else the
href attribute must appear, but not both. –>
<sequence minOccurs=”0″ maxOccurs=”1″>
<element name=”title” type=”xsd:string”/>
<element name=”firstauthor” type=”tns:Person”/>
<element name=”secondauthor” type=”tns:Person”/>
</sequence>
<attribute name=”href” type=”uriReference”/>
<attribute name=”id” type=”ID”/>
<anyAttribute namespace=”##other”/>
</complexType>
<element name=”Person” base=”tns:Person”/>
<complexType name=”Person”>
<!– Either the following group must occur or else the
href attribute must appear, but not both. –>
<sequence minOccurs=”0″ maxOccurs=”1″>
<element name=”name” type=”xsd:string”/>
<element name=”address” type=”tns:Address”/>
</sequence>
<attribute name=”href” type=”uriReference”/>
<attribute name=”id” type=”ID”/>
<anyAttribute namespace=”##other”/>
</complexType>
<element name=”Address” base=”tns:Address”/>
<complexType name=”Address”>
<!– Either the following group must occur or else the
href attribute must appear, but not both. –>
<sequence minOccurs=”0″ maxOccurs=”1″>
<element name=”street” type=”xsd:string”/>
<element name=”city” type=”xsd:string”/>
<element name=”state” type=”xsd:string”/>
</sequence>
<attribute name=”href” type=”uriReference”/>
<attribute name=”id” type=”ID”/>
<anyAttribute namespace=”##other”/>
</complexType>

5.4.2 数组

SOAP数组定义为具有”SOAP-ENC:Array”类型或一个从”SOAP-ENC:Array”衍生的类型(参见规则8)。数组表 示为元素值,对元素的名没有特别的约束(正如元素值并不约束它们所属的元素)。数组可以包含任意类型的元素,包括嵌套数组。可以创建新的类型(受 SOAP-ENC:Array
类型限制)来表示数组,如整数数组或某些用户定义的枚举。数组值表示为组成这个数组的项的元素的规则序列。在数组值中,元素名对于区分accesor并不 重要。元素可以有任意的名。实际上,元素常常用它们在schema中暗示或确定的数组类型来命名元素。并且一般情况下对于复合值来说,如果数组中数组项的 值是single-reference值,则这个数组项包含它的值,否则,该数组项通过”href”属性引用这个值。下面的例子是一个整型数组的 schema片断:

<element name=”myFavoriteNumbers”
type=”SOAP-ENC:Array”/>
<myFavoriteNumbers
SOAP-ENC:arrayType=”xsd:int[2]“>
<number>3</number>
<number>4</number>
</myFavoriteNumbers>

在这个例子中,数组”myFavoriteNumbers”包括几个成员,每个成员是一个类型为SOAP-ENC:int的值。注意 SOAP-ENC:Array允许不受限制的元素名,它们不传达任何类型信息,所以在使用时,或者它们有xsi:type属性,或者它们所属的元素有 SOAP-ENC:arrayType属性。自然,由SOAP-ENC:Array衍生的类型可以声明局部元素,但这种情况下要包括类型信息。上面已经提 到,SOAP-ENC schema包含了元素的声明,元素名与”XML Schema Part 2: Datatypes”规范[11]中的简单类型一致。其中包括了对”Array”的声明。于是,我们可以这样写:

<SOAP-ENC:Array SOAP-ENC:arrayType=”xsd:int[2]“>
<SOAP-ENC:int>3</SOAP-ENC:int>
<SOAP-ENC:int>4</SOAP-ENC:int>
</SOAP-ENC:Array>

数组可以包含特定arrayType的任意子类型的实例。即,数组成员可以是arryType属性值指定的类型的任意子类型,这个类型对于 arrayType属性中指定的类型来说是可替换的(根据schema中的替换规则)。例如,一个整型数组可以包含从整型衍生的任意类型(如”int”或 任意用户定义的从整型衍生的类型)。同样,一个”address”数组可能包含一个address的受限类型或扩展类型 如”internationalAddress”。因为提供的SOAP-ENC:Array类型允许任意类型的成员,所以可以包含任意类型的混合除非使用 arrayType属性加以特别的限制。在实例中,可以使用xsi:type指定成员元素的类型,或通过schema中成员元素的声明来指定。下面是两个 例子。

<SOAP-ENC:Array SOAP-ENC:arrayType=”SOAP-ENC:ur-type[4]“>
<thing xsi:type=”xsd:int”>12345</thing>
<thing xsi:type=”xsd:decimal”>6.789</thing>
<thing xsi:type=”xsd:string”>
Of Mans First Disobedience, and the Fruit
Of that Forbidden Tree, whose mortal tast
Brought Death into the World, and all our woe,
</thing>
<thing xsi:type=”xsd:uriReference”> http://www.dartmouth.edu/~milton/reading_room/ </thing>
</SOAP-ENC:Array>
<SOAP-ENC:Array SOAP-ENC:arrayType=”SOAP-ENC:ur-type[4]“>
<SOAP-ENC:int>12345</SOAP-ENC:int>
<SOAP-ENC:decimal>6.789</SOAP-ENC:decimal>
<xsd:string>
Of Mans First Disobedience, and the Fruit
Of that Forbidden Tree, whose mortal tast
Brought Death into the World, and all our woe,
</xsd:string>
<SOAP-ENC:uriReference> http://www.dartmouth.edu/~milton/reading_room/ </SOAP-ENC:uriReference >
</SOAP-ENC:Array>

数组值可以是结构或其它复合值。例如”xyz:Order”结构数组:

<SOAP-ENC:Array SOAP-ENC:arrayType=”xyz:Order[2]“>
<Order>
<Product>Apple</Product>
<Price>1.56</Price>
</Order>
<Order>
<Product>Peach</Product>
<Price>1.48</Price>
</Order>
</SOAP-ENC:Array>

数组成员值也可以是数组。下例是两个字符串数组组成的数组:

<SOAP-ENC:Array SOAP-ENC:arrayType=”xsd:string[][2]“>
<item href=”#array-1″/>
<item href=”#array-2″/>
</SOAP-ENC:Array>
<SOAP-ENC:Array id=”array-1″ SOAP-ENC:arrayType=”xsd:string[2]“>
<item>r1c1</item>
<item>r1c2</item>
<item>r1c3</item>
</SOAP-ENC:Array>
<SOAP-ENC:Array id=”array-2″ SOAP-ENC:arrayType=”xsd:string[2]“>
<item>r2c1</item>
<item>r2c2</item>
</SOAP-ENC:Array>

包含数组的元素无需命名为”SOAP-ENC:Array”。它可以有任意的名,只要元素的类型是SOAP-ENC:Array或由之衍生的类型。例如,下面是一个schema片断和与之一致的数组实例。

<simpleType name=”phoneNumber” base=”string”/>
<element name=”ArrayOfPhoneNumbers”>
<complexType base=”SOAP-ENC:Array”>
<element name=”phoneNumber” type=”tns:phoneNumber” maxOccurs=”unbounded” />
</complexType>
<anyAttribute/>
</element>
<xyz:ArrayOfPhoneNumbers SOAP-ENC:arrayType=”xyz:phoneNumber[2]“>
<phoneNumber>206-555-1212</phoneNumber>
<phoneNumber>1-888-123-4567</phoneNumber>
</xyz:ArrayOfPhoneNumbers>

数组可能是多维的。在这种情况下,在arrayType属性的asize部分将不止有一个值:

<SOAP-ENC:Array SOAP-ENC:arrayType=”xsd:string[2,3]“>
<item>r1c1</item>
<item>r1c2</item>
<item>r1c3</item>
<item>r2c1</item>
<item>r2c2</item>
<item>r2c3</item>
</SOAP-ENC:Array>

虽然上面的例子把数组编码为独立的元素,但元素值也可以是嵌入形式,而且若元素值是single reference时,必须编码为嵌入形式。下例是一个schema片断,电话号码数组嵌入到一个类型为”Person”的结构中,并且通过accessor “phone-numbers”访问它:

<simpleType name=”phoneNumber” base=”string”/>
<element name=”ArrayOfPhoneNumbers”>
<complexType base=”SOAP-ENC:Array”>
<element name=”phoneNumber” type=”tns:phoneNumber” maxOccurs=”unbounded”/>
</complexType>
<anyAttribute/>
</element>
<element name=”Person”>
<complexType>
<element name=”name” type=”string”/>
<element name=”phoneNumbers” type=”tns:ArrayOfPhoneNumbers”/>
</complexType>
</element>
<xyz:Person>
<name>John Hancock</name>
<phoneNumbers SOAP-ENC:arrayType=”xyz:phoneNumber[2]“>
<phoneNumber>206-555-1212</phoneNumber>
<phoneNumber>1-888-123-4567</phoneNumber>
</phoneNumbers>
</xyz:Person>

下面的例子中,数组值为single-reference,被编码为嵌入元素,包含它的元素名即为入口名:

<xyz:PurchaseOrder>
<CustomerName>Henry Ford</CustomerName>
<ShipTo>
<Street>5th Ave</Street>
<City>New York</City>
<State>NY</State>
<Zip>10010</Zip>
</ShipTo>
<PurchaseLineItems SOAP-ENC:arrayType=”Order[2]“>
<Order>
<Product>Apple</Product>
<Price>1.56</Price>
</Order>
<Order>
<Product>Peach</Product>
<Price>1.48</Price>
</Order>
</PurchaseLineItems>
</xyz:PurchaseOrder>

5.4.2.1 部分储值(partially transmitted)数组

SOAP提供了对部分储值(partially transmitted)数组的支持,如某些上下文中的可变数组。一个partially transmitted 数组由一个”SOAP-ENC:offset”属性(从第一个transmitted的元素开始的偏移量,基于0)指示。如果省略,偏移量取0。下面的例子中数组的大小为5,但只有从0起,第三和第四个元素被储值。

<SOAP-ENC:Array ;SOAP-ENC:arrayType=”xsd:string[5]” ;SOAP-ENC:offset=”[2]“>
<item>The third element</item>
<item>The fourth element</item>
</SOAP-ENC:Array>

5.4.2.2 稀疏数组Sparse Arrays

SOAP提供了对稀疏数组的支持。每个表示成员值的元素包含一个”SOAP-ENC:position”属性,用来指示它在数组中的位置。下例是两维字符串稀疏数组的例子,数组大小是4,但只用到第2个。

<SOAP-ENC:Array SOAP-ENC:arrayType=”xsd:string[,][4]“>
<SOAP-ENC:Array href=”#array-1″ SOAP-ENC:position=”[2]“/>
</SOAP-ENC:Array>
<SOAP-ENC:Array id=”array-1″ SOAP-ENC:arrayType=”xsd:string[10,10]“>
<item SOAP-ENC:position=”[2,2]“>Third row, third col</item>
<item SOAP-ENC:position=”[7,2]“>Eighth row, third col</item>
</SOAP-ENC:Array>

如果对array-1的引用仅发生在数组内部,上例也可以编码如下:

<SOAP-ENC:Array SOAP-ENC:arrayType=”xsd:string[,][4]“>
<SOAP-ENC:Array SOAP-ENC:position=”[2]” SOAP-ENC:arrayType=”xsd:string[10, 10]>
<item SOAP-ENC:position=”[2,2]“>Third row, third col</item>
<item SOAP-ENC:position=”[7,2]“>Eighth row, third col</item>
</SOAP-ENC:Array>
</SOAP-ENC:Array>

5.4.3 一般复合类型

在这里提到的编码规则不仅仅限于accessor名已知的情况,如果accessor名是运行环境下实时获得的,编码规则同样适用,也就是 说accessor编码成一个元素名与accessor名匹配的元素,同时accessor可能包含或者引用该元素的值。如果accessor包含类型不 能事先确定的值,它必须包含一个合适的属性xsi:type 。类似地,上述引用的规则已经足够用于复合类型的序列化,这些复合类型可能包含用名区分的accessors(结构)和用名及序数位置区分的 accessors。(可能包含重复的accessor) 实际上这并不要求任何schema模式包含这些类型,但更为准确的说法是:一个类型模型(type-model)schema如果有这些类型,就可以构造 一个符合XML句法规则的schema和XML文档实例。

<xyz:PurchaseOrder>
<CustomerName>Henry Ford</CustomerName>
<ShipTo>
<Street>5th Ave</Street>
<City>New York</City>
<State>NY</State>
<Zip>10010</Zip>
</ShipTo>
<PurchaseLineItems>
<Order>
<Product>Apple</Product>
<Price>1.56</Price>
</Order>
<Order>
<Product>Peach</Product>
<Price>1.48</Price>
</Order>
</PurchaseLineItems>
</xyz:PurchaseOrder>

类似地,将一个结构上类似数组但实际上不是一个 SOAP-ENC:Array类型或者 SOAP-ENC:Array子类型的复合值序列化同样是允许的,例如:

<PurchaseLineItems>
<Order>
<Product>Apple</Product>
<Price>1.56</Price>
</Order>
<Order>
<Product>Peach</Product>
<Price>1.48</Price>
</Order>
</PurchaseLineItems>

5.5 缺省值

省略accessor元素意味着或者有一个缺省值或者值不知道。具体细节依靠这个accessor,方法和上下文。例如,对于多态 accessor,省略accessor一般意味着一个Null值。同样,省略布尔accessor一般意味着False值或者值不知道,省略数字 accessor一般意味着值为零或者值不知道。

5.6 SOAP root属性

SOAP root 属性可用于标记一个序列化root,从而一个对象可以反序列化(deserialized),而实际上该root并不是真正的对象root。这个属性有两个可选值”1″ or “0″。对象真正的roots属性值为“1” ,序列化root但不是真正的root属性值也为“1”,元素如果要显式地指定不能为序列化root,只需将该属性设置为“0” SOAP root属性可以出现在SOAP头和SOAP体元素的任意子元素中。(译者注:SOAP root属性为0的元素不是一个独立的实体,外部的应用不能访问到该元素,但该元素可以被SOAP文档本身的其它元素访问到)SOAP root属性可以出现在SOAP头和SOAP体元素的任意子元素中。这个属性没有缺省值。

6. 在HTTP中使用SOAP

这一节讲述了如何在HTTP中使用SOAP。把SOAP绑定到HTTP,无论使用或不用HTTP扩展框架,都有很大的好处:在利用SOAP 的形式化和灵活性的同时,使用HTTP种种丰富的特性。在HTTP中携带SOAP消息,并不意味着SOAP改写了HTTP已有的语义,而是将构建在 HTTP之上SOAP语义自然地对应到HTTP语义。SOAP自然地遵循HTTP的请求/应答消息模型使得SOAP的请求和应答参数可以包含在HTTP请 求和应答中。注意,SOAP的中间节点与HTTP的中间节点并不等同,即,不要期望一个根据HTTP连接头中的域寻址到的HTTP中间节点能够检查或处理 HTTP请求中的SOAP消息。
在HTTP消息中包含SOAP实体时,按照RFC2376[3] HTTP应用程序必须使用媒体类型 “text/xml”。

6.1 SOAP HTTP请求

虽然SOAP可能与各种HTTP请求方式相结合,但是绑定仅定义了在HTTP POST请求中包含SOAP消息。(第7节中描述了如何在RPC中使用SOAP,第6.3节描述了如何使用HTTP扩展框架)

6.1.1 HTTP头中SOAPAction域

一个HTTP请求头中的SOAPAction域用来指出这是一个SOAP HTTP请求,它的值是所要的URI。在格式、URI的特性和可解析性上没有任何限制。当HTTP客户发出SOAP HTTP请求时必须使用在HTTP头中使用这个域。

soapaction = “SOAPAction” “:” [ <"> URI-reference <"> ]
URI-reference = <as defined in RFC 2396 [4]>

HTTP头中SOAPAction域使服务器(如防火墙)能正确的过滤HTTP中SOAP请求消息。如果这个域的值是空字符串(”"),表示SOAP消息的目标就是HTTP请求的URI。这个域没有值表示没有SOAP消息的目标的信息。例子:

SOAPAction: “http://electrocommerce.org/abc#MyMessage”
SOAPAction: “myapp.sdl”
SOAPAction: “”
SOAPAction:

6.2 SOAP HTTP应答

SOAP HTTP遵循HTTP 中表示通信状态信息的HTTP状态码的语义。例如,2xx状态码表示这个包含了SOAP组件的客户请求已经被成功的收到,理解和接受。在处理请求时如果发生错误,SOAP HTTP服务器必须发出应答HTTP 500 “Internal Server Error”,并在这个应答中包含一个SOAP Fault元素(见4.4节)表示这个SOAP处理错误。

6.3 HTTP扩展框架

一个SOAP消息可以与HTTP扩展框架 [6]一起使用以区分是否有SOAP HTTP请求和它的目标。是使用扩展框架或是普通的HTTP关系到通信各方的策略和能力。通过使用一个必需的扩展声明和”M-”HTTP方法名前缀,客户 可以强制使用HTTP扩展框架。服务器可以使用HTTP状态码510 “Not Extended”强制使用HTTP扩展框架。也就是说,使用一个额外的来回,任何一方都可以发现另一方的策略并依照执行。用来表示SOAP使用了扩展框 架的扩展标志符是:http://schemas.xmlsoap.org/soap/envelope/

6.4 SOAP HTTP举例

例3 使用POST的SOAP HTTP

POST /StockQuote HTTP/1.1
Content-Type: text/xml; charset=”utf-8″
Content-Length: nnnn
SOAPAction: “http://electrocommerce.org/abc#MyMessage”
<SOAP-ENV:Envelope…
HTTP/1.1 200 OK
Content-Type: text/xml; charset=”utf-8″
Content-Length: nnnn
<SOAP-ENV:Envelope…

例4 使用扩展框架的SOAP HTTP

M-POST /StockQuote HTTP/1.1
Man: “http://schemas.xmlsoap.org/soap/envelope/”; ns=NNNN
Content-Type: text/xml; charset=”utf-8″
Content-Length: nnnn
NNNN-SOAPAction: “http://electrocommerce.org/abc#MyMessage”
<SOAP-ENV:Envelope…
HTTP/1.1 200 OK

Ext:
Content-Type: text/xml; charset=”utf-8″
Content-Length: nnnn
<SOAP-ENV:Envelope…

7. 在RPC中使用SOAP

设计SOAP的目的之一就是利用XML的扩展性和灵活性来封装和交换RPC调用。这一节定义了远程过程调用和应答的统一表示形式。虽然可以 预计到这种表示形式最可能被用于与第5节中定义的编码方式相结合,但也可能有其它的表示形式。SOAP的encodingStyle属性(见4.3.2 节)可以用来表明方法调用和应答都使用这一节所指定的表示方式。在RPC中使用SOAP和SOAP协议绑定(见第6节)是紧密相关的。在使用HTTP作为 绑定协议时,一个RPC调用自然地映射到一个HTTP请求,RPC应答同样映射到HTTP应答。但是,在RPC中使用SOAP并不限于绑定HTTP协议。
要进行方法调用,以下的信息是必需的:

  • 目标对象的URI
  • 方法名
  • 方法signature(可选)
  • 方法的参数
  • 头数据(可选)

SOAP依靠协议绑定提供传送URI的机制。例如,对HTTP来说,请求的URI指出了调用的来源 。除了必须是一个合法的URI之外,SOAP对一个地址的格式没有任何限制。(更多URI的信息参见 [4])

7.1 RPC和SOAP体

RPC方法调用和应答都包含在SOAP Body元素中(见4.3节),它们使用如下的表示形式:

  • 一个方法调用用一个结构表示
  • 一个方法调用被看作一个单个的结构,每个[in]和[in/out]参数有一个accessor。结构的名和类型与方法相同。每个 [in]和[in/out]参数都被看作一个accessor,这个accessor的名和类型与参数的名和类型相对应。它们的出现顺序和方法中定义的参 数顺序相同。
  • 一个方法应答用一个结构表示。
  • 一个方法应答被看作一个单个的结构,返回值和每个[in]和[in/out]参数有一个accessor。第一个accessor是 返回值,之后是参数accessor,参数accessor的出现顺序和方法中定义的参数顺序相同。每个参数accessor的名称和类型与参数的名称和 类型相对应。返回值accessor的名称并不重要。同样,结构的名称也不重要,不过,通常在方法名称的后面加上字符串”Response”作为结构的名 称。

方法错误使用SOAP Fault元素(见4.4节)表示。如果绑定的协议有额外的规则表示错误,则这些规则也必须要遵从。正如上面所述,方法调用和应答结构可以按照第5节中规 则编码,或者用encodingStyle属性(见4.1.1节)指定编码方式。应用程序可以处理缺少参数的请求,但是可能返回一个错误。因为返回结果表 示调用成功,错误表示调用失败,所以,在方法应答中同时包含返回结果和错误是错误的。

7.2 RPC和SOAP头

在RPC编码中,可能会有与方法请求有关但不是正规的方法signature的附加信息。如果这样,它必须作为SOAP头元素的子元素。使 用这种头元素的一个例子是在消息中传递事务ID。由于事务ID不是方法signature的一部分,通常由底层的组件而不是应用程序代码控制,所以没有一 种直接的方法在调用中传递这个必要的信息。通过在头中添加一个给定名字的条目,接收方的事务管理器就可以析取这个事务ID,而且不影响远程过程调用的代 码。

8. 安全性考虑

这篇文档中没有涉及完整性和保密性,这些问题将在以后的版本中描述。

9. 参考文献

[1] S. Bradner, “The Internet Standards Process — Revision 3″, RFC2026, Harvard University, October 1996
[2] S. Bradner, “Key words for use in RFCs to Indicate Requirement Levels”, RFC 2119, Harvard University, March 1997
[3] E. Whitehead, M. Murata, “XML Media Types”, RFC2376, UC Irvine, Fuji Xerox Info. Systems, July 1998
[4] T. Berners-Lee, R. Fielding, L. Masinter, “Uniform Resource Identifiers (URI): Generic Syntax”, RFC 2396, MIT/LCS, U.C.Irvine, Xerox Corporation, A ugust 1998.
[5] R. Fielding, J. Gettys, J. C. Mogul, H. Frystyk, T. Berners-Lee, “Hypert ext Transfer Protocol — HTTP/1.1″, RFC 2616, U.C. Irvine, DEC W3C/MIT, DEC,W3C/MIT, W3C/MIT, January 1997
[6] H. Nielsen, P. Leach, S. Lawrence, “An HTTP Extension Framework”, RFC 2774, Microsoft, Microsoft, Agranat Systems
[7] W3C Recommendation “The XML Specification”
[8] W3C Recommendation “Namespaces in XML”
[9] W3C Working Draft “XML Linking Language”. This is work in progress.
[10] W3C Working Draft “XML Schema Part 1: Structures”. This is work in progress.
[11] W3C Working Draft “XML Schema Part 2: Datatypes”. This is work in progress.
[12] Transfer Syntax NDR, in “DCE 1.1: Remote Procedure Call”
[13] N. Freed, N. Borenstein, “Multipurpose Internet Mail Extensions (MIME)Part One: Format of Internet Message Bodies”, RFC2045, Innosoft, First Virtu al, November 1996

10。 附录

A. SOAP封装举例

A.1 请求编码举例

例5 类似于例1,但有一个必要的头

POST /StockQuote HTTP/1.1
Host: www.stockquoteserver.com
Content-Type: text/xml; charset=”utf-8″
Content-Length: nnnn
SOAPAction: “Some-URI”
<SOAP-ENV:Envelope
xmlns:SOAP-ENV=”http://schemas.xmlsoap.org/soap/envelope/”
SOAP-ENV:encodingStyle=”http://schemas.xmlsoap.org/soap/encoding/”/>
<SOAP-ENV:Header>
<t:Transaction
xmlns:t=”some-URI”
SOAP-ENV:mustUnderstand=”1″>
5
</t:Transaction>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
<m:GetLastTradePrice xmlns:m=”Some-URI”>
<symbol>DEF</symbol>
</m:GetLastTradePrice>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

例6 类似于例1,但有多个请求参数

POST /StockQuote HTTP/1.1
Host: www.stockquoteserver.com
Content-Type: text/xml; charset=”utf-8″
Content-Length: nnnn
SOAPAction: “Some-URI”
<SOAP-ENV:Envelope
xmlns:SOAP-ENV=”http://schemas.xmlsoap.org/soap/envelope/”
SOAP-ENV:encodingStyle=”http://schemas.xmlsoap.org/soap/encoding/”/>
<SOAP-ENV:Body>
<m:GetLastTradePriceDetailed
xmlns:m=”Some-URI”>
<Symbol>DEF</Symbol>
<Company>DEF Corp</Company>
<Price>34.1</Price>
</m:GetLastTradePriceDetailed>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

A.2 应答编码举例

例7 与例2类似,但有必要的头部

HTTP/1.1 200 OK
Content-Type: text/xml; charset=”utf-8″
Content-Length: nnnn
<SOAP-ENV:Envelope
xmlns:SOAP-ENV=”http://schemas.xmlsoap.org/soap/envelope/”
SOAP-ENV:encodingStyle=”http://schemas.xmlsoap.org/soap/encoding/”/>
<SOAP-ENV:Header>
<t:Transaction xmlns:t=”some-URI” xsi:type=”xsd:int” mustUnderstand=”1″> 5 </t:Transaction>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
<m:GetLastTradePriceResponse xmlns:m=”Some-URI”>
<Price>34.5</Price>
</m:GetLastTradePriceResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

例8 与例2类似,但有一个结构

HTTP/1.1 200 OK
Content-Type: text/xml; charset=”utf-8″
Content-Length: nnnn
<SOAP-ENV:Envelope
xmlns:SOAP-ENV=”http://schemas.xmlsoap.org/soap/envelope/”
SOAP-ENV:encodingStyle=”http://schemas.xmlsoap.org/soap/encoding/”/>
<SOAP-ENV:Body>
<m:GetLastTradePriceResponse
xmlns:m=”Some-URI”>
<PriceAndVolume>
<LastTradePrice> 34.5 </LastTradePrice>
<DayVolume> 10000 </DayVolume>
</PriceAndVolume>
</m:GetLastTradePriceResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

例9 与例2类似,但处理必要的头出错

HTTP/1.1 500 Internal Server Error
Content-Type: text/xml; charset=”utf-8″
Content-Length: nnnn
<SOAP-ENV:Envelope
xmlns:SOAP-ENV=”http://schemas.xmlsoap.org/soap/envelope/”>
<SOAP-ENV:Body>
<SOAP-ENV:Fault>
<faultcode>SOAP-ENV:MustUnderstand</faultcode>
<faultstring>SOAP Must Understand Error</faultstring>
</SOAP-ENV:Fault>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

例10 与例2类似,但处理Body出错

HTTP/1.1 500 Internal Server Error
Content-Type: text/xml; charset=”utf-8″
Content-Length: nnnn
<SOAP-ENV:Envelope
xmlns:SOAP-ENV=”http://schemas.xmlsoap.org/soap/envelope/”>
<SOAP-ENV:Body>
<SOAP-ENV:Fault>
<faultcode>SOAP-ENV:Server</faultcode>
<faultstring>Server Error</faultstring>
<detail>
<e:myfaultdetails xmlns:e=”Some-URI”>
<message>
My application didn’t work
</message>
<errorcode> 1001 </errorcode>
</e:myfaultdetails>
</detail>
</SOAP-ENV:Fault>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

]]>
http://diggex.net/?feed=rss2&p=664
linux下打字软件 http://diggex.net/?p=663 http://diggex.net/?p=663#comments Mon, 26 Jul 2010 02:44:35 +0000 admin http://diggex.net/?p=663 “打字训练软件是为了帮助用户在不看键盘的情况下根据触摸按键,此类软件通常要用户使用所用手指,而不是几个手指。盲打能帮助用户提高打字速度。Linuxlinks的这篇文章列举了五款优秀的Linux打字训练自由软件,帮助用户改进他们的打字技巧。包括:KTouchKlavaroGNU TypistTypingWeb

]]>
http://diggex.net/?feed=rss2&p=663
笑话 http://diggex.net/?p=662 http://diggex.net/?p=662#comments Mon, 19 Jul 2010 10:24:27 +0000 admin http://diggex.net/?p=662 三男子去女方提亲,家长:说说各自情况。A:我有1000万;B:我有一栋豪宅,价值2000万;女方家长很满意,就问C,你家有什么?C答:我什么都没 有,只有一个孩子。现在孩子在你女儿的肚子里。A、B无语,走了。这个案例告诉了我们一个浅显的道理,核心竞争力不是钱和房子,是在关键的岗位上,要有自 己的人

]]>
http://diggex.net/?feed=rss2&p=662
对话 linux: shell高手的重大秘密 http://diggex.net/?p=661 http://diggex.net/?p=661#comments Wed, 07 Jul 2010 02:29:45 +0000 admin http://diggex.net/?p=661 保存环境变量

大多数 UNIX 用户在 .bashrc(针对 Bash shell)和 .zshrc(针对 Z shell)等 shell 启动文件中塞满大量用户设置,以便一次又一次地重建钟爱的 shell 环境。启动文件能够创建别名、设置 shell 选项、创建函数、以及设置环境变量。关键的环境变量包括 HOME(指向您的主目录)、PATH(列举从中搜索应用程序的目录)和 MANPATH(列举从中搜索手册页的目录)。要查看您的 shell 中设置了哪些环境变量,键入 printenv 命令。查阅 shell 手册页,获取可用环境变量的完整列表。

与 shell 一样,可以通过环境变量定制其他许多 UNIX 应用程序。例如,Java™ 子系统要求定义 JAVA_HOME 来指向 Java 运行时的根。同样,Amazon Web Services (AWS) 实用程序套件强制使用 AWS_CREDENTIAL_FILE 来指向一个包含有效私匙凭证的文件。单独的应用程序也提供环境变量,关键是如何发现这些变量。幸运的是,这种工作不需要非法入侵;相反,只需查询手边的实用工具手册页,查找标题为 “Environment Variables” 的章节即可。

例如,分页实用程序 less 定义了几个有用的环境变量:

* 环境变量 LESS 存储一些命令行选项,以在您每次调用该分页程序时减少键入量。例如,如果您需要阅读大量日志文件,可将以下语句添加到一个 shell 启动文件中:

export LESS=’–RAW-CONTROL-CHARACTERS –squeeze-lines –ignore-case’

`

上述选项将分别解译控制字符(通常是语法着色),将多个空行压缩为一行,并忽略字符串匹配中的大小写。如果您使用代码,可尝试以下选项:

export LESS=’–LINE-NUMBERS –quit-if-one-screen –quit-on-intr’

* 名为 LESSKEY 的环境设置指向一个密匙绑定文件。可以使用密匙绑定来定制 less 的行为,比如,匹配另一个页面或编辑器的行为。
* 与 shell 一样,less 能保留多个调用之间的历史。设置 LESSHISTFILE 和 LESSHISTSIZE 分别指向一个持久命令文件和设置要记录的命令的最大条数。

GNU Compiler Collection (GCC) 是另一个典型的环境变量应用示例。GCC 定义各种环境变量来定制其操作。LIBRARY_PATH,顾名思义,是一个目录列表,用于搜索要链接到的库;COMPILER_PATH 的工作方式与 shell 的 PATH 非常相似,但是由 GCC 在内部使用,用于查找编译过程中使用的子程序。

如果您针对单个平台写代码并构建二进制文件,您可能永远也不会用到这些环境变量,但是,如果您跨平台交叉编译相同的代码,那么这些变量对于访问每个平台的不同的头部和库至关重要。您可以将这些变量设置为不同的值集合,一个集合针对一种机器,而另一个集合针对另一种风格的系统。

事实上,您可以从 GCC 获得一个暗示:可以为每个应用程序维护多个环境变量集合,根据手边的工作从一个集合切换到另一个集合。一种方法是在每个项目目录中保存一个环境初始化文件并根据需要 source 它。例如,许多 Ruby 开发人员使用这种方法来在不同的 Ruby 版本间切换,根据需要更改环境变量 PATH、GEM_HOME 和 GEM_PATH,从一个版本跳到另一个版本。

“点缀” 环境

与环境变量非常相似的是,许多 Linux® 和 UNIX 应用程序都提供一个点 文件 — 文件名以圆点开始的小文件 — 来进行定制。与环境变量不同的是:环境变量采集少量标记和相对较少的信息量,而点文件可能更广泛、更复杂,拥有自己独特的语法规则、甚至自己的编程语言。点文件是保存选项和设置的理想位置,因为(根据 UNIX 传统)以一个圆点开始的文件名不会出现在标准的目录清单中。(使用 ls -a 来查看这些所谓的隐藏文件。)点文件是纯文本文件,只是文件名比较特别而已。

点文件通常位于您的主目录内,但有些实用程序也在当前工作目录中查找点文件。如果一个应用程序支持多个点文件,则该程序通常应用于优先规则,来表明一个文件比另一个文件优先。通常,“本地” 点文件 — 位于当前工作目录 — 优先级最高,然后是主目录中的点文件,最后是一个系统范围配置文件。这些文件可以全部存在,也可以存在一个,或者都不存在,这取决于应用程序将这些文件视为互斥的还是递增的。在第一种情况下,优先链中第一个点文件的优先权是不容置疑的。在后一种情况下,配置可以级联或融解到最终结果中。

less 的密匙绑定文件是一个简单点文件示例,位于 $HOME/.lesskey 中。文件中的每一行都是一对(一个按键和一条命令),如下所示:

\r forw-line
\n forw-line
e forw-line
j forw-line
^E forw-line
^N forw-line
k back-line
y back-line
^Y back-line

fetchmail 是比较复杂的点文件示例。这个实用程序在本地从多个远程源提取电子邮件并传送消息。这个实用程序的操作只通过 $HOME/.fetchmailrc 控制。(参见手册页了解它的众多选项。)cron、git、vi,以及其他许多命令都能识别点文件。同样,请阅读这个应用程序的手册页,了解可以在点文件中配置的内容。有些点文件内容丰富,足以占用一个单独的手册页,比如 crontab。

嘘……关于 SSH 的秘密

Secure Shell (SSH) 是一个功能强大的子系统,用于安全地登录到远程系统、复制文件并穿越防火墙。由于 SSH 是一个子系统,它提供大量选项来定制和简化其操作。事实上,SSH 提供名为 $HOME/.ssh 的整个 “点目录” 来包含其所有数据。(您的 .ssh 目录必须是模式 600,以阻止他人访问。非 600 模式将干扰正常的操作。)特别是,文件 $HOME/.ssh/config 可以定义大量快捷方式,包括机器名称的别名、每主机访问控制等。

下面是位于 $HOME/.ssh/config 中的一个典型代码块,用于定制一个特定主机的 SSH:

Host worker
HostName worker.example.com
IdentityFile ~/.ssh/id_rsa_worker
User joeuser

~/.ssh/config 中的每个块配置一个或多个主机。不同的块使用一个空行分隔。这个块使用 4 个选项:Host、HostName、IdentityFile 和 User。Host 为 HostName 指定的机器创建一个昵称。昵称允许您键入 ssh worker,而不是 ssh worker.example.com。另外,IdentityFile 和 User 选项指定如何登录到 worker。前者指向此主机使用的一个私匙,后者提供登录 ID。这样,这个代码块就等同于以下命令:

ssh joeuser@worker.example.com -i ~/.ssh/id_rsa_worker

ControlMaster 是一个鲜为人知的强大选项。如果设置,同一个主机的多个 SSH 会话将共享单个连接。一旦第一个连接建立,后续连接就不再需要凭证,从而消除了每次连接同一机器都需要键入密码的麻烦。ControlMaster 非常方便,您可能愿意为每台机器启用它。启用方法非常简单,只需使用主机通配符 *:

Host *
ControlMaster auto
ControlPath ~/.ssh/master-%r@%h:%p

如您所料,标记了 Host * 的块将应用到每个主机,甚至是那些在配置文件中没有明确指定的主机。ControlMaster auto 尝试使用一个现有连接,并在没有发现共享连接时创建一个新连接。ControlPath 指向一个文件,以便持久化一个控制套接字以供共享。%r 用远程登录用户名替换,%h 用目标主机名替换,%p 代替连接使用的端口。(您还可以使用 %l,它使用本地主机名替换。)上述规范使用类似于下面的文件名创建控制套接字:

master-joeuser@worker.example.com:22

当到远程主机的所有连接都被切断时,每个控制套接字都就会被移除。如果您想随时了解连接到了哪些主机,只需键入 ls ~/.ssh 并查看控制套接字的主机名部分(%h)。

SSH 配置文件非常大,它也有自己的手册页。键入 man ssh_config 查看所有可能的选项。这里有一个巧妙的 SSH 技巧:可以通过 SSH 从本地系统进入远程系统。要用到的命令行如下所示:

$ ssh example.com -L 5000:localhost:3306

这条命令的意思是:通过 example.com 进行连接,并在本地机器上的端口 5000 和名为 “localhost” 的机器上的端口 3306(MySQL 服务器端口)之间建立一条通道。由于 localhost 在 example.com 上解释(因为通道已建立),因此 localhost 就是 example.com。由于出站通道 — 以前称为本地转发(local forward)— 已建立,本地客户端能够连接到端口 5000,并与 example.com 上运行的 MySQL 服务器通信。

通道创建的常规形式如下:

$ ssh proxyhost
localport:targethost:targetport

其中,proxyhost 是可以通过 SSH 访问的机器,并且拥有一个到 targethost 的网络连接(不通过 SSH)。localport 是您的本地系统上的一个非特权端口(1024 以上的任一未用端口),targetport 是您要连接到的服务的端口。

前面的命令从您的机器发送出去,到达外部世界。 也可以使用 SSH 发送进来,或者从外部世界连接到您的本地系统。入站通道的常规形式如下:

$ ssh user@proxyhost -R proxyport:targethosttargetport

建立一条入站通道 — 以前称为远程转发 — 时,proxyhost 和 targethost 的角色将被反转:目标是您的本地机器,代理是远程机器。user 是您在代理上的登录名。以下命令提供了一个具体示例:

$ ssh joe@example.com -R 8080:localhost:80

这条命令的意思是:用户 Joe 连接到 example.com,并将远程端口连接到本地端口 80。这条命令向 example.com 上的用户提供一个通道,以连接到 Joe 的机器上。远程用户能够连接到 8080,以便连接 Joe 机器上的 Web 服务器。

除了分别用于本地和远程转发的 -L 和 -R 之外,SSH 还提供 -D 参数来在远程机器上创建一个 HTTP 代理。请参见 SSH 手册页了解正确语法。

使用历史记录重写

如果您经常在 shell 提示符中花费大量时间,保存 shell 历史记录可以节约时间和输入。但是如果历史记录不能被修改,就会导致一些麻烦:记录重复的命令,且多个 shell 实例可能会干扰各自的历史记录。这两个问题很容易解决,只需在您的 .bashrc 中添加两行:

export HISTCONTROL=ignoreboth
shopt -s histappend

第一行将移除您的 shell 历史记录中连续的重复命令。如果您想移除所有零散的副本,可将 ignoreboth 更改为 erasedups。第二行在 shell 退出时将 shell 的历史记录附加到您的记录文件。默认情况下,Bash 记录文件命名为 ~/~/.bash_history (不错,这是一个点文件)。可以通过设置 HISTFILE(不错,这是一个环境变量)来更改它的位置。如果您想将一个 shell 的最近 10,000 命令保存在一个包含 100,000 条目的记录文件中,将 export HISTSIZE=10000 HISTFILESIZE=100000 添加到您的 shell 启动文件中。要查看一个 shell 的历史记录,在任意提示处键入 history 即可。

如果不能调用,那么保存的命令历史记录就用处不大。而这正是 shell !(或 bang)操作符的作用所在:

* !! (”bang bang”) 完整地重复最后一条命令。
* !:0 是前一条命令的名称。
* !^ 是前一条命令的第一个参数。!:2、!:3 … !$ 等命令是前一条命令的第二、第三……以及最后一个参数。
* !* 是最后一条命令的所有参数,命令名除外。
* !n 重复历史中编号为 n 的命令。
* !handle 重复以 handle 中的字符开始的最后一条命令。例如,!ca 将重复以字符 ca 开始的最后一条命令,如 cat README。
* !?handle 重复包含 handle 中的字符组成的字符串的最后一条命令。例如,!?READ 还会匹配 cat README。
* ^original^substitution 使用 substitution 替换 original 的第一个 实例。例如,如果前一条命令是 cat README,,命令 ^README^license.txt 将生成一条新命令 cat license.txt。
* !:gs/original/substitution 将使用 substitution 替换 original 的所有 实例(!:gs 表示 “全局替换[global substitution]”)。
* !-2 是倒数第二条命令,!-3 是倒数第三条命令,以此类推。

您甚至可以合并历史表达式来生成 !-2:0 -R !^ !-3:2 这样的 “魔汤”,该命令将扩展为倒数第二条命令的名称,加上 -R,再加上前一条命令的第一个参数,以及倒数第三条命令的第二个参数。要使这样的神秘命令更具可读性,可以在键入时扩展历史参考。在任意提示符键入命令 bind Space:magic-space ,或者将其添加到一个启动文件,从而将空格键绑定到函数 magic-space,该函数将扩展内联历史替换。

与扩展名无关的自动解压

鉴于 Internet 上有如此众多的代码,您可能每天都会下载数十个文件。可能会出现这样的情况:所有那些文件都使用不同的方式打包 — 有的是 ZIP 文件,有的是 RAR 文件,还有很多是 tarball 文件,尽管每个包都使用不同的实用程序压缩。记住如何解压缩和扩展每种包格式将会使人精疲力尽。那么,为何不在单个命令中完成所有那些任务呢?下面这个函数在许多样例点文件中广泛可用:

ex () {
if [ -f $1 ] ; then
case $1 in
*.tar.bz2) tar xjf $1 ;;
*.tar.gz) tar xzf $1 ;;
*.bz2) bunzip2 $1 ;;
*.rar) rar x $1 ;;
*.gz) gunzip $1 ;;
*.tar) tar xf $1 ;;
*.tbz2) tar xjf $1 ;;
*.tgz) tar xzf $1 ;;
*.zip) unzip $1 ;;
*.Z) uncompress $1 ;;
*.7z) 7z x $1 ;;
*) echo “‘$1′ cannot be extracted via extract()” ;;
esac
else
echo “‘$1′ is not a valid file”
fi
}

这个函数 ex 扩展了 11 种文件格式;如果要处理其他包类型,该函数还可以扩展。一旦定义 — 例如,在一个 shell 启动文件中 — 就可以简单地键入 ex somefile,其中 somefile 以以下一种已命名扩展结束:

$ ls
source
$ tar czf source.tgz source
$ ls -1
source
source.tgz
$ rm -rf source
$ ex source.tgz
$ ls -1
source
source.tgz

顺便说一下,如果您将今天下载的文件放错了位置,可以运行 find 来查找它:

$ find ~ -type f -mtime 0

命令 -type f 查找纯文本文件,-mtime 0 查找自当天午夜以来创建的文件。

更多秘密

需要揭开的专家秘密还有很多。在 Web 上搜索 “shell auto-complete”,进一步了解自动完成特性,该特性用于在您键入一条命令时提供上下文敏感的扩展。另外,搜索 “shell prompts” 以了解如何定制您的 shell 提示:可以将其设置为彩色,可以设置您的当前工作目录或 Git 分支,还可以显示历史数目 — 如果经常调用历史,这是一个方便的参考信息。要查看工作示例,可在 Github 中搜索 “dot files”。许多专家都将他们的 shell 配置张贴在 Github 上。

]]>
http://diggex.net/?feed=rss2&p=661
研究驱动型创业公司 http://diggex.net/?p=660 http://diggex.net/?p=660#comments Mon, 05 Jul 2010 02:33:30 +0000 admin http://diggex.net/?p=660 作者认为硅谷正在重归以研发为重的传统,并认为创业公司应放弃追求完美的开局,从尽早发布可用的产品做起,待获得用户和风险投资后,再深入研发不迟。

硅谷的根基是研究,而 Web 热潮已将它改变为消费媒体软件。像 Facebook 和 Twitter 这样的公司从简单的程序造起,获得投资后再招募研究人员。

不过,我想我们可能正处在数据与研究复兴的启蒙时期。

我们正看到越来越多的研究驱动的数据类创业公司。我们正看到越来越多的类似 Facebook 和 Twitter 这样的创业公司,它们初期不以研究和数据为重,但之后便需要通过研究或数据支撑其业务的个性化、精准广告投送、产品推荐、高级产品或其他形式的情报以转化成收入。

如果该假设是正确的,那么就需要了解如何做好这样的创业公司。

通过产品解决问题,以研究为基础,借数据处理推动

该类创业公司的存在价值一目了然。

收集并处理数据,接着从中提取信息,并通过研究获得有用的情报,再以此开发能够解决某个问题的产品。

问题 <- 产品 <- 情报 <- 研究 <- 信息 <- 加工 <- 数据

某些时候,我们只想到「信息」阶段,没有关系,「情报」的获得仍需深入的工作。

技巧

我们需要仰赖三大技能。

* 研究人员:机器学习,统计学,数学,计算机科学。
* 系统黑客:专注于数据储存、传送、队列、处理的计算机科学家和工程师,在许多情况下还需要分布式系统的技能。
* 前端工程师:设计师、交互设计师、JavaScript 高手、用户体验。

研究人员和前端工程师需专注于产品,而三者都需要专注于数据。

通过产品化消解复杂度

有一条数据护城河是远远不够的。

如果你是 B2B,你可以通过 API 将数据丢给用户,乐得清闲。如果你是 B2C,消费者才不想要什么数据,他们只希望问题能获得解决。

即使大多数 B2B,用户需要的也不是原始数据,而是经过加工后的数据。他们要的是何时采取行动的信号,而不是晦涩的概率分布。

人们通常玩不转概率论。见卡尼曼和特沃斯基的研究,以及他们的预期理论。

对于 B2C 来说,你得有一款可爱的产品。然后你需要从数据中获得信息,并自信满满的期待自己能够将信息加工成有用的情报。

那么这个领域内的大赢家,就是能把海量的数据和复杂度装入极简的界面之中。见下图 :p

先找到「足够好」的模型

研究驱动的数据型创业公司的资源往往重度受限。于是,先开发出一个「足够好」的而又简单的模型,借助这个模型发现问题,然后获得顾客的关注和投资者的融资,以及诸如此类的玩意。

对于这类创业公司,应该做到,在最优的解决方案出炉之前就要拿出产品来。有了足够好的方案,才能继续深入研究,并逐步成为最好的方案。

这是重要的观念,宜尽早考虑为妙。如果在同样的风险下,仅凭较小的边际收益便能击败标普 500 指数,你将会成为一个英雄。对于其他问题,只要你足够努力,自然能够解决。

如果结果必须精准无误,那么也许你想要解决的问题并不适合研究驱动型创业公司。如果你的项目只需稍作改进就能产生效益,那么正适合用来启动创业公司。

日复一日,你总会做的更好 — 完美的开局不足以让你押上全部的赌注。

从单个数据源开始

如果你把许多不同的数据源集成到单视图中用于创建你的特征向量,那么接着你也许会想以某个单一的数据源作为模型的基础,然后将其他数据源分次装入。

许多问题的模式相通:一个主要的密集数据源和几个增入主源的系数稀疏数据源。

如果一次整合过多数据,你也许会发现自己已被数据处理和转换的复杂度压倒,而这亦会伤及你的研究能力。也可能会限制你从数据源中提取信息的能力,因为不断的修改数据,你便很难专注于从每个数据源中提取信息。

软件开发的经验教训

早发布、常发布、多评估。没错 — 在做这些的时候你都可以继续研究。

那种认为可以肆意制定未来研究目标的看法纯属胡说八道。一步一个脚印 — 这种方法更好。

步伐可能会有所不同,时间也可能会有不同,而且也许有很多困难,但你仍可以步步为营。

是研究带我走入敏捷和 TDD(编者:测试驱动型开发,Test-Driven Development)。自从 2003 年以来,我一直这么做研究。TDD 是一门科学 — 提出你的假设,找到测试的办法,然后便去测试。

选定你的度量以及测试的方法。什么才是足够好呢?什么时候才能达到收益递减点?

不要想那些你死了后都完不成的东西。你要不断的告诫自己。

假设检验

请记住,在创业公司, 一切都是假设,而你的工作就是测试假设。

* 你可以从数据源中提取多少数据?也许你有大量的数据,但杂讯过多,价值过少。
* 数据的稀疏程度如何?那种极为详实但却罕有的数据源亦无助于事。
* 你能找到一个足够简单模型,能够迅速启动,并也能招徕用户吗?
* 你能利用那些从早期的「足够好」的模式所获得的收入来扩展业务或精进研究吗?
* 你的模型可以产品化或装入某个人们非常在意的服务之中吗?

创造解决问题的办法

回想一下,研究驱动的数据型创业公司获取数据,通过处理数据得到信息,接着在此之上研究出有用的情报,然后将它做成产品,解决一个问题。

此外,还请记得,在某些情况下,我们仅能得到信息,但这已足够。我们还有许多问题。有时候,情报部分不是十分必要,宁可早一些做出产品,不妨将解决的办法挪到今后。

为人们解决问题,请做一些研究吧。

]]>
http://diggex.net/?feed=rss2&p=660
smart http://diggex.net/?p=659 http://diggex.net/?p=659#comments Sun, 04 Jul 2010 00:22:05 +0000 admin http://diggex.net/?p=659

]]>
http://diggex.net/?feed=rss2&p=659
英语绕口令 http://diggex.net/?p=656 http://diggex.net/?p=656#comments Fri, 02 Jul 2010 16:21:21 +0000 admin http://diggex.net/?p=656 英语绕口令:A tidy tiger tied a tie tighter to tidy her tiny tail(一只老虎将领带系紧,清洁它的尾巴)A big black bug bit a big black bear, made the big black bear bleed blood(大黑虫咬大黑熊,大黑熊流血了)

]]>
http://diggex.net/?feed=rss2&p=656
敏捷软件开发12原则 http://diggex.net/?p=654 http://diggex.net/?p=654#comments Thu, 01 Jul 2010 13:45:41 +0000 admin http://diggex.net/?p=654 1. 我们最优先要做的是通过尽早、持续地交付有价值的软件来使客户满意。

2. 在项目的整个开发期间,业务人员和开发人员必须天天在一起工作。

3. 即使到了开发后期,也欢迎需求变化。

4. 经常性地交付可以工作的软件。

5. 可以工作的软件是主要的进度度量标准。

6. 围绕被激励起的个体来构建项目。为他们提供所需的环境和支持,并信任他们能胜任工作。

7. 最好的架构、需求和设计来自于自组织的团队。

8. 在团队内部,最有效果和最有效率的传递信息的方法是面对面地交流。

9. 敏捷过程提倡可持续的开发速度。

10. 不断地关注最优秀的技术和良好的设计能增强敏捷能力。

11. 简单是根本的。

12. 开发团队每隔一定时间,都会对如何能有效地工作进行反省,然后相应地对自己的行为进行调整。

Principles behind the Agile Manifesto

We follow these principles:

Our highest priority is to satisfy the customer
through early and continuous delivery
of valuable software.

Welcome changing requirements, even late in
development. Agile processes harness change for
the customer’s competitive advantage.

Deliver working software frequently, from a
couple of weeks to a couple of months, with a
preference to the shorter timescale.

Business people and developers must work
together daily throughout the project.

Build projects around motivated individuals.
Give them the environment and support they need,
and trust them to get the job done.

The most efficient and effective method of
conveying information to and within a development
team is face-to-face conversation.

Working software is the primary measure of progress.

Agile processes promote sustainable development.
The sponsors, developers, and users should be able
to maintain a constant pace indefinitely.

Continuous attention to technical excellence
and good design enhances agility.

Simplicity–the art of maximizing the amount
of work not done–is essential.

The best architectures, requirements, and designs
emerge from self-organizing teams.

At regular intervals, the team reflects on how
to become more effective, then tunes and adjusts
its behavior accordingly.

http://www.agilemanifesto.org/principles.html

]]>
http://diggex.net/?feed=rss2&p=654
中国互联网 http://diggex.net/?p=653 http://diggex.net/?p=653#comments Thu, 01 Jul 2010 13:25:57 +0000 admin http://diggex.net/?p=653 新浪:一直在装逼 总是被攻击…腾讯:一直在山寨 垄断不失败…搜狐:一直在抄袭 从来未成功…网易:一直在个性 评论已关闭…百度:一直在搜索 其实总和谐…谷歌:一直在海外 根本没进来…淘宝:一直在秒杀 骗的就是你…猫扑:一直在色情 人肉很流行…盛大:一直在免费 玩家高消费…『转』

]]>
http://diggex.net/?feed=rss2&p=653
使用 Apache Solr 实现更加灵巧的搜索 http://diggex.net/?p=650 http://diggex.net/?p=650#comments Thu, 01 Jul 2010 07:09:50 +0000 admin http://diggex.net/?p=650

Solr 是一种可供企业使用的、基于 Lucene 的搜索服务器,它支持层面搜索、命中醒目显示和多种输出格式。在这篇分两部分的文章中,Lucene Java™ 的提交人 Grant Ingersoll 将介绍 Solr 并向您展示如何轻松地将其表现优异的全文本搜索功能加入到 Web 应用程序中。

一旦用户需要某种信息,就可以立即搜索到这些信息,这种要求再也不是可有可无的了。随着 Google 和类似的复杂搜索引擎的出现,用户希望得到高质量的搜索结果,帮助他们快速、轻易地找到所需的信息。经理对您的在线购物站点同样抱有很高的期望,要求它能 够提供一个可伸缩、高度可用且易于维护的搜索解决方案,并且安装这个解决方案不应太昂贵。对于您而言,只是希望事业进步,让老板和客户满意,以及保持头脑 清醒。

使用 Apache Solr 可以满足所有的这些要求,它是一种开放源码的、基于 Lucene Java 的搜索服务器,易于加入到 Web 应用程序中。Solr 提供了层面搜索、命中醒目显示并且支持多种输出格式(包括 XML/XSLT 和 JSON 格式)。它易于安装和配置,而且附带了一个基于 HTTP 的管理界面。您可以坚持使用 Solr 的表现优异的基本搜索功能,也可以对它进行扩展从而满足企业的需要。Solr 还拥有一个活跃的开发者群体,如有需要,您可以随时向他们寻求帮助。

这篇分为两部分的文章将介绍 Solr,展示其特性并举例说明如何将其完全加入到 Web 应用程序中。 我们将首先提供一些 Solr 的基本介绍,包括安装和配置的说明。然后引入一个示例应用程序(博客界面),您可以通过该程序让自己熟悉一下 Solr 的各种特性。您将学习如何使用 Solr 来索引和搜索内容并探索 Solr 对层面浏览的支持。第 1 部分的最后将简要介绍一下 Solr 的模式并解释如何针对示例应用程序的索引结构配置模式

安装:

可以通过以下三种方式之一设置   Solr 的主位置:

1。设置   java   系统属性   solr.solr.home   (没错,就是   solr.solr.home)。
2。配置   java:comp/env/solr/home   的一个   JNDI   查找指向   solr 目录。
3。在包含   solr 目录的目录中启动   servlet   容器。(默认的   Solr 主目录是当前工作目录下的   solr。)

我采用的是第二种(2。配置   java:comp/env/solr/home   的一个   JNDI   查找指向   solr 目录.。)

安装步骤:

下载solrzip包后解压缩,将dist目录下的war文件改名为solr.war直接复制到tomcatwebapps目录即可。

注意一定要设置solr的主位置。有三种方法。我采用的是在tomcat里配置java:comp/env/solr/home 的一个JNDI指向solr的主目录(example目录下),建立/tomcat安装目录 /conf/Catalina/localhost/solr.xml文件。

<Context docBase=”D:/solr.war” debug=”0″ crossContext=”true” >
<Environment name=”solr/home” type=”java.lang.String” value=”D:/solr/solr override=”true” />
</Context>

我主要问题出在 Environment  value,name设置问题。

变量1:这里解释一下Context docBase=“solr.war的目录,如果放在了/tomcat安装目录/webapps下,则是这么/solr.war”。

变量2:而Environment name=”solr/home”必须这么写,可能solr默认 为solr的home目录(记住必须这么写)。

变量3:Environment value=”D:/solr/solr 值,是solr目录, 就是包括bin,conf等目录,一定要有.

我吃亏就吃亏在这三个变量上了,搞了半天.记住几个变量的设置。

最后,启动tomcat.输入 http://localhost:8080/solr/admin/出现管理页面。

http://www.ibm.com/developerworks/cn/java/j-solr1/

http://www.ibm.com/developerworks/cn/java/j-solr2/index.html

分词:

http://java.chinaitlab.com/advance/732532.html

http://blog.chenlb.com/

]]>
http://diggex.net/?feed=rss2&p=650
又拍网架构中的分库设计 http://diggex.net/?p=649 http://diggex.net/?p=649#comments Sat, 12 Jun 2010 05:46:48 +0000 admin http://diggex.net/?p=649 又拍网和大多数Web2.0站点一样,构建于大量开源软件之上,包括MySQLPHPnginxPythonmemcachedredisSolrHadoopRabbitMQ等等。又拍网的服务器端开发语言主要是PHPPython, 其中PHP用于编写Web逻辑(通过HTTP和用户直接打交道), 而Python则主要用于开发内部服务和后台任务。在客户端则使用了大量的 Javascript, 这里要感谢一下MooTools这个JS框架,它使得 我们很享受前端开发过程。 另外,我们把图片处理过程从PHP进程里独立出来变成 一个服务。这个服务基于nginx,但是是作为nginx的一个模块而开放REST API。

开发语言

图1:开发语言

由于PHP的单线程模型,我们把耗时较久的运算和I/O操作从HTTP请求 周期中分离出来, 交给由Python实现的任务进程来完成,以保证请求响 应速度。这些任务主要包括:邮件发送、数据索引、数据聚合和好友动态推送(稍候会有介绍)等等。通常这些任务由用户触发,并且,用户的一个行为可能会触发 多种任务的执行。 比如,用户上传了一张新的照片,我们需要更新索引,也需要向他的朋友推送一条新的动态。PHP通过消息队列(我们用的是RabbitMQ)来触发任务执行。

PHP和Python的协作

图2:PHP和Python的协作

数据库一向是网站架构中最具挑战性的,瓶颈通常出现在这里。又拍网的照片数据量很大,数据库也几度出现严重的压力问题。 因此,这里我主要介绍一下又拍网在分库设计这方面的一些尝试。

分库设计

和很多使用MySQL的2.0站点一样,又拍网的MySQL集群经历了从最初的一个主库一个从库、到一个主库多个从库、 然后到多个主库多个从库的一个发展过程。

数据库的进化过程

最初是由一台主库和一台从库组成,当时从库只用作备份和容灾,当主库出现故障时,从库就手动变成主库,一般情况下,从库不作读写操作(同步除外)。 随着压力的增加,我们加上了memcached,当时只 用其缓存单行数据。 但是,单行数据的缓存并不能很好地解决压力问题,因为单行数据的查询通常很快。所以我们把一些实时性要求不高的Query放到从库去执行。后面又通过添加 多个从库来分流查询压力,不过随着数据量的增加,主库的写压力也越来越大。

在参考了一些相关产品和其它网站的做法后,我们决定进行数据库拆分。也就是将数据存放到不同的数据库服务器中,一般可以按两个纬度来拆分数据:

垂直拆分:是指按功能模块拆分,比如可以将群组相关表和照片相关表存放在不同的数据库中,这种方式多个数据库之间的表结构不同

水平拆分:而水平拆分是将同一个表的数据进行分块保存到不同的数据库中,这些数据库中的表结构完全相同

拆分方式

一般都会先进行垂直拆分,因为这种方式拆分方式实现起来比较简单,根据表名访问不同的数据库就可以了。但是垂直拆分方式并不能彻底解决所有压力问 题,另外,也要看应用类型是否合适这种拆分方式。如果合适的话,也能很好的起到分散数据库压力的作用。比如对于豆瓣我觉得比较适合采用垂直拆分, 因为豆瓣的各核心业务/模块(书籍、电影、音乐)相对独立,数据的增加速度也比较平 稳。不同的是,又拍网的核心业务对象是用户上传的照片,而照片数据的增加速度随着用户量的增加越来越快。压力基本上都在照片表上,显然垂直拆分并不能从根 本上解决我们的问题,所以,我们采用水平拆分的方式。

拆分规则

水平拆分实现起来相对复杂,我们要先确定一个拆分规则,也就是按什么条件将数据进行切分。 一般2.0网站都以用户为中心,数据基本都跟随用户,比如用户的照片、朋友和评论等等。因此一个比较自然的选择是根据用户来切分。每个用户都对应一个数据 库,访问某个用户的数据时, 我们要先确定他/她所对应的数据库,然后连接到该数据库进行实际的数据读写。

那么,怎么样对应用户和数据库呢?我们有这些选择:

按算法对应

最简单的算法是按用户ID的奇偶性来对应,将奇数ID的用户对应到数据库A,而偶数ID的用户则对应到数据库B。这个方法的最大问题是,只能分成两 个库。另一个算法是按用户ID所在区间对应,比如ID在0-10000之间的用户对应到数据库A, ID在10000-20000这个范围的对应到数据库B,以此类推。按算法分实现起来比较方便,也比较高效,但是不能满足后续的伸缩性要求,如果需要增加 数据库节点,必需调整算法或移动很大的数据集, 比较难做到在不停止服务的前提下进行扩充数据库节点。

按索引/映射表对应

这种方法是指建立一个索引表,保存每个用户的ID和数据库ID的对应关系,每次读写用户数据时先从这个表获取对应数据库。新用户注册后,在所有可用 的数据库中随机挑选一个为其建立索引。这种方法比较灵活,有很好的伸缩性。一个缺点是增加了一次数据库访问,所以性能上没有按算法对应好。

比较之后,我们采用的是索引表的方式,我们愿意为其灵活性损失一些性能,更何况我们还有memcached, 因为索引数据基本不会改变的缘故,缓存命中率非常高。所以能很大程度上减少了性能损失。

数据访问过程

图4:数据访问过程

索引表的方式能够比较方便地添加数据库节点,在增加节点时,只要将其添加到可用数据库列表里即可。 当然如果需要平衡各个节点的压力的话,还是需要进行数据的迁移,但是这个时候的迁移是少量的,可以逐步进行。要迁移用户A的数据,首先要将其状态置为迁 移数据中,这个状态的用户不能进行写操作,并在页面上进行提示。 然后将用户A的数据全部复制到新增加的节点上后,更新映射表,然后将用户A的状态置为正常,最后将原来对应的数据库上的数据删除。这个过程 通常会在临晨进行,所以,所以很少会有用户碰到迁移数据中的情况。

当然,有些数据是不属于某个用户的,比如系统消息、配置等等,我们把这些数据保存在一个全局库中。

问题

分库会给你在应用的开发和部署上都带来很多麻烦。

不能执行跨库的关联查询

如果我们需要查询的数据分布于不同的数据库,我们没办法通过JOIN的方式查询获得。比如要获得好友的最新照片,你不能保证所有好友的数据都在同一 个数据库里。一个解决办法是通过多次查询,再进行聚合的方式。我们需要尽量避免类似的需求。有些需求可以通过保存多份数据来解决,比如User-A和 User-B的数据库分别是DB-1和DB-2, 当User-A评论了User-B的照片时,我们会同时在DB-1和DB-2中保存这条评论信息,我们首先在DB-2中的photo_comments表 中插入一条新的记录,然后在DB-1中的user_comments表中插入一条新的记录。这两个表的结构如下图所示。这样我们可以通过查询 photo_comments表得到User-B的某张照片的所有评论, 也可以通过查询user_comments表获得User-A的所有评论。另外可以考虑使用全文检索工具来解决某些需求, 我们使用Solr来提供全站标签检索和照片搜索服务。

评论表结构

图5:评论表结构

不能保证数据的一致/完整性

跨库的数据没有外键约束,也没有事务保证。比如上面的评论照片的例子, 很可能出现成功插入photo_comments表,但是插入user_comments表时却出错了。一个办法是在两个库上都开启事务,然后先插入 photo_comments,再插入user_comments, 然后提交两个事务。这个办法也不能完全保证这个操作的原子性。

所有查询必须提供数据库线索

比如要查看一张照片,仅凭一个照片ID是不够的,还必须提供上传这张照片的用户的ID(也就是数据库线索),才能找到它实际的存放位置。因此,我们 必须重新设计很多URL地址,而有些老的地址我们又必须保证其仍然有效。我们把照片地址改成/photos/{username}/{photo_id} /的形式,然后对于系统升级前上传的照片ID, 我们又增加一张映射表,保存photo_id和user_id的对应关系。当访问老的照片地址时,我们通过查询这张表获得用户信息, 然后再重定向到新的地址。

自增ID

如果要在节点数据库上使用自增字段,那么我们就不能保证全局唯一。这倒不是很严重的问题,但是当节点之间的数据发生关系时,就会使得问题变得比较麻 烦。我们可以再来看看上面提到的评论的例子。如果photo_comments表中的comment_id的自增字段,当我们在DB- 2.photo_comments表插入新的评论时, 得到一个新的comment_id,假如值为101,而User-A的ID为1,那么我们还需要在DB-1.user_comments表中插入(1, 101 …)。 User-A是个很活跃的用户,他又评论了User-C的照片,而User-C的数据库是DB-3。 很巧的是这条新评论的ID也是101,这种情况很用可能发生。那么我们又在DB-1.user_comments表中插入一行像这样(1, 101 …)的数据。 那么我们要怎么设置user_comments表的主键呢(标识一行数据)?可以不设啊,不幸的是有的时候(框架、缓存等原因)必需设置。那么可以以 user_id、 comment_id和photo_id为组合主键,但是photo_id也有可能一样(的确很巧)。看来只能再加上photo_owner_id了, 但是这个结果又让我们实在有点无法接受,太复杂的组合键在写入时会带来一定的性能影响,这样的自然键看起来也很不自然。所以,我们放弃了在节点上使用自增 字段,想办法让这些ID变成全局唯一。为此增加了一个专门用来生成ID的数据库,这个库中的表结构都很简单,只有一个自增字段id。 当我们要插入新的评论时,我们先在ID库的photo_comments表里插入一条空的记录,以获得一个唯一的评论ID。 当然这些逻辑都已经封装在我们的框架里了,对于开发人员是透明的。 为什么不用其它方案呢,比如一些支持incr操作的Key-Value数据库。我们还是比较放心把数据放在MySQL里。 另外,我们会定期清理ID库的数据,以保证获取新ID的效率。

实现

我们称前面提到的一个数据库节点为Shard,一个Shard由两个台物理服务器组成, 我们称它们为Node-A和Node-B,Node-A和Node-B之间是配置成Master-Master相互复制的。 虽然是Master-Master的部署方式,但是同一时间我们还是只使用其中一个,原因是复制的延迟问题, 当然在Web应用里,我们可以在用户会话里放置一个A或B来保证同一用户一次会话里只访问一个数据库, 这样可以避免一些延迟问题。但是我们的Python任务是没有任何状态的,不能保证和PHP应用读写相同的数据库。那么为什么不配置成Master-Slave呢?我们觉得 只用一台太浪费了,所以我们在每台服务器上都创建多个逻辑数据库。 如下图所示,在Node-A和Node-B上我们都建立了shard_001和shard_002两个逻辑数据库, Node-A上的shard_001和Node-B上的shard_001组成一个Shard,而同一时间只有一个逻辑数据库处于Active状态。 这个时候如果需要访问Shard-001的数据时,我们连接的是Node-A上的shard_001, 而访问Shard-002的数据则是连接Node-B上的shard_002。以这种交叉的方式将压力分散到每台物理服务器上。 以Master-Master方式部署的另一个好处是,我们可以不停止服务的情况下进行表结构升级, 升级前先停止复制,升级Inactive的库,然后升级应用,再将已经升级好的数据库切换成Active状态, 原来的Active数据库切换成Inactive状态,然后升级它的表结构,最后恢复复制。 当然这个步骤不一定适合所有升级过程,如果表结构的更改会导致数据复制失败,那么还是需要停止服务再升级的。

Database  Layout

图6:数据库布局

前面提到过添加服务器时,为了保证负载的平衡,我们需要迁移一部分数据到新的服务器上。为了避免短期内迁移的必要,我们在实际部署的时候,每台机器 上部署了8个逻辑数据库, 添加服务器后,我们只要将这些逻辑数据库迁移到新服务器就可以了。最好是每次添加一倍的服务器, 然后将每台的1/2逻辑数据迁移到一台新服务器上,这样能很好的平衡负载。当然,最后到了每台上只有一个逻辑库时,迁移就无法避免了,不过那应该是比较久 远的事情了。

我们把分库逻辑都封装在我们的PHP框架里了,开发人员基本上不需要被这些繁琐的事情困扰。下面是使用我们的框架进行照片数据的读写的一些例子:

<?php
    $Photos = new ShardedDBTable('Photos', 'yp_photos', 'user_id', array(
                'photo_id'    => array('type' => 'long', 'primary' => true, 'global_auto_increment' => true),
                'user_id'     => array('type' => 'long'),
                'title'       => array('type' => 'string'),
                'posted_date' => array('type' => 'date'),
            ));

    $photo = $Photos->new_object(array('user_id' => 1, 'title' => 'Workforme'));
    $photo->insert();

    // 加载ID为10001的照片,注意第一个参数为用户ID
    $photo = $Photos->load(1, 10001);

    // 更改照片属性
    $photo->title = 'Database Sharding';
    $photo->update();

    // 删除照片
    $photo->delete();

    // 获取ID为1的用户在2010-06-01之后上传的照片
    $photos = $Photos->fetch(array('user_id' => 1, 'posted_date__gt' => '2010-06-01'));
?>

首先要定义一个ShardedDBTable对象,所有的API都是通过这个对象开放。第一个参数是对象类型名称, 如果这个名称已经存在,那么将返回之前定义的对象。你也可以通过get_table(’Photos’)这个函数来获取之前定义的Table对象。 第二个参数是对应的数据库表名,而第三个参数是数据库线索字段,你会发现在后面的所有API中全部需要指定这个字段的值。 第四个参数是字段定义,其中photo_id字段的global_auto_increment属性被置为true,这就是前面所说的全局自增ID, 只要指定了这个属性,框架会处理好ID的事情。

如果我们要访问全局库中的数据,我们需要定义一个DBTable对象。

<?php
    $Users = new DBTable('Users', 'yp_users', array(
                'user_id'  => array('type' => 'long', 'primary' => true, 'auto_increment' => true),
                'username' => array('type' => 'string'),
            ));
?>

DBTable是ShardedDBTable的父类,除了定义时参数有些不同(DBTable不需要指定数据库线索字段),它们提供一样的 API。

缓存

我们的框架提供了缓存功能,对开发人员是透明的。

<?php
    $photo = $Photos->load(1, 10001);
?>

比如上面的方法调用,框架先尝试以Photos-1-10001为Key在缓存中查找,未找到的话再执行数据库查询并放入缓存。当更改照片属性或删 除照片时,框架负责从缓存中删除该照片。这种单个对象的缓存实现起来比较简单。稍微麻烦的是像下面这样的列表查询结果的缓存。

<?php
    $photos = $Photos->fetch(array('user_id' => 1, 'posted_date__gt' => '2010-06-01'));
?>

我们把这个查询分成两步,第一步先查出符合条件的照片ID,然后再根据照片ID分别查找具体的照片信息。 这么做可以更好的利用缓存。第一个查询的缓存Key为Photos-list-{shard_key}-{md5(查询条件SQL语句)}, Value是照片ID列表(逗号间隔)。其中shard_key为user_id的值1。目前来看,列表缓存也不麻烦。 但是如果用户修改了某张照片的上传时间呢,这个时候缓存中的数据就不一定符合条件了。所以,我们需要一个机制来保证我们不会从缓存中得到过期的列表数据。 我们为每张表设置了一个revision,当该表的数据发生变化时(调用insert/update/delete方法), 我们就更新它的revision,所以我们把列表的缓存Key改为Photos-list-{shard_key}-{md5(查询条件SQL语 句)}-{revision}, 这样我们就不会再得到过期列表了。

revision信息也是存放在缓存里的,Key为Photos-revision。这样做看起来不错,但是好像列表缓存的利用率不会太高。因为我 们是以整个数据类型的revision为缓存Key的后缀,显然这个revision更新的非常频繁,任何一个用户修改或上传了照片都会导致它的更新,哪 怕那个用户根本不在我们要查询的Shard里。要隔离用户的动作对其他用户的影响,我们可以通过缩小revision的作用范围来达到这个目的。 所以revision的缓存Key变成Photos-{shard_key}-revision,这样的话当ID为1的用户修改了他的照片信息时, 只会更新Photos-1-revision这个Key所对应的revision。

因为全局库没有shard_key,所以修改了全局库中的表的一行数据,还是会导致整个表的缓存失效。 但是大部分情况下,数据都是有区域范围的,比如我们的帮助论坛的主题帖子, 帖子属于主题。修改了其中一个主题的一个帖子,没必要使所有主题的帖子缓存都失效。 所以我们在DBTable上增加了一个叫isolate_key的属性。

<?php
$GLOBALS['Posts'] = new DBTable('Posts', 'yp_posts', array(
        'topic_id'    => array('type' => 'long', 'primary' => true),
        'post_id'     => array('type' => 'long', 'primary' => true, 'auto_increment' => true),
        'author_id'   => array('type' => 'long'),
        'content'     => array('type' => 'string'),
        'posted_at'   => array('type' => 'datetime'),
        'modified_at' => array('type' => 'datetime'),
        'modified_by' => array('type' => 'long'),
    ), 'topic_id');
?>

注意构造函数的最后一个参数topic_id就是指以字段topic_id作为isolate_key,它的作用和shard_key一样用于隔离 revision的作用范围。

ShardedDBTable继承自DBTable,所以也可以指定isolate_key。 ShardedDBTable指定了isolate_key的话,能够更大幅度缩小revision的作用范围。 比如相册和照片的关联表yp_album_photos,当用户往他的其中一个相册里添加了新的照片时, 会导致其它相册的照片列表缓存也失效。如果我指定这张表的isolate_key为album_id的话, 我们就把这种影响限制在了本相册内。

我们的缓存分为两级,第一级只是一个PHP数组,有效范围是Request。而第二级是memcached。这么做的原因是,很多数据在一个 Request周期内需要加载多次,这样可以减少memcached的网络请求。另外我们的框架也会尽可能的发送memcached的gets命令来获取 数据, 从而减少网络请求。

总结

这个架构使得我们在很长一段时间内都不必再为数据库压力所困扰。我们的设计很多地方参考了netlogflickr的 实现,因此非常感谢他们将一些实现细节发布出来。

]]>
http://diggex.net/?feed=rss2&p=649
g++命令行详解 http://diggex.net/?p=648 http://diggex.net/?p=648#comments Fri, 28 May 2010 09:18:20 +0000 admin http://diggex.net/?p=648 gcc/g++参数详解
gcc and g++分别是gnu的c & c++编译器 gcc/g++在执行编译工作的时候,总共需要4步

1.预处理,生成.i的文件[预处理器cpp]
2.将预处理后的文件不转换成汇编语言,生成文件.s[编译器egcs]
3.有汇编变为目标代码(机器代码)生成.o的文件[汇编器as]
4.连接目标代码,生成可执行程序[链接器ld]

[参数详解]
-x language filename
设定文件所使用的语言,使后缀名无效,对以后的多个有效.也就是根据约定C语言的后缀名称是.c的,而C++的后缀名是.C或者.cpp,如果你很个性, 决定你的C代码文件的后缀名是.pig 哈哈,那你就要用这个参数,这个参数对他后面的文件名都起作用,除非到了下一个参数的使用。
可以使用的参数吗有下面的这些
`c’, `objective-c’, `c-header’, `c++’, `cpp-output’, `assembler’, and `assembler-with-cpp’.
看到英文,应该可以理解的。
例子用法:
gcc -x c hello.pig

-x none filename
关掉上一个选项,也就是让gcc根据文件名后缀,自动识别文件类型
例子用法:
gcc -x c hello.pig -x none hello2.c

-c
只激活预处理,编译,和汇编,也就是他只把程序做成obj文件
例子用法:
gcc -c hello.c
他将生成.o的obj文件

-S
只激活预处理和编译,就是指把文件编译成为汇编代码。
例子用法
gcc -S hello.c
他将生成.s的汇编代码,你可以用文本编辑器察看

-E
只激活预处理,这个不生成文件,你需要把它重定向到一个输出文件里面.
例子用法:
gcc -E hello.c > pianoapan.txt
gcc -E hello.c | more
慢慢看吧,一个hello word 也要与处理成800行的代码

-o
制定目标名称,缺省的时候,gcc 编译出来的文件是a.out,很难听,如果你和我有同感,改掉它,哈哈
例子用法
gcc -o hello.exe hello.c (哦,windows用习惯了)
gcc -o hello.asm -S hello.c

-pipe
使用管道代替编译中临时文件,在使用非gnu汇编工具的时候,可能有些问题
gcc -pipe -o hello.exe hello.c

-ansi
关闭gnu c中与ansi c不兼容的特性,激活ansi c的专有特性(包括禁止一些asm inline typeof关键字,以及UNIX,vax等预处理宏,

-fno-asm
此选项实现ansi选项的功能的一部分,它禁止将asm,inline和typeof用作关键字。
-fno-strict-prototype
只对g++起作用,使用这个选项,g++将对不带参数的函数,都认为是没有显式的对参数的个数和类型说明,而不是没有参数.
而gcc无论是否使用这个参数,都将对没有带参数的函数,认为城没有显式说明的类型

-fthis-is-varialble
就是向传统c++看齐,可以使用this当一般变量使用.

-fcond-mismatch
允许条件表达式的第二和第三参数类型不匹配,表达式的值将为void类型

-funsigned-char
-fno-signed-char
-fsigned-char
-fno-unsigned-char
这四个参数是对char类型进行设置,决定将char类型设置成unsigned char(前两个参数)或者 signed char(后两个参数)

-include file
包含某个代码,简单来说,就是便以某个文件,需要另一个文件的时候,就可以用它设定,功能就相当于在代码中使 用#include<filename>
例子用法:
gcc hello.c -include /root/pianopan.h

-imacros file
将file文件的宏,扩展到gcc/g++的输入文件,宏定义本身并不出现在输入文件中

-Dmacro
相当于C语言中的#define macro

-Dmacro=defn
相当于C语言中的#define macro=defn

-Umacro
相当于C语言中的#undef macro

-undef
取消对任何非标准宏的定义

-Idir
在你是用#include”file”的时候,gcc/g++会先在当前目录查找你所制定的头文件,如果没有找到,他回到缺省的头文件目录找,如果使 用-I制定了目录,他
回先在你所制定的目录查找,然后再按常规的顺序去找.
对于#include<file>,gcc/g++会到-I制定的目录查找,查找不到,然后将到系统的缺省的头文件目录查找

-I-
就是取消前一个参数的功能,所以一般在-Idir之后使用

-idirafter dir
在-I的目录里面查找失败,讲到这个目录里面查找.

-iprefix prefix
-iwithprefix dir
一般一起使用,当-I的目录查找失败,会到prefix+dir下查找

-nostdinc
使编译器不再系统缺省的头文件目录里面找头文件,一般和-I联合使用,明确限定头文件的位置

-nostdin C++
规定不在g++指定的标准路经中搜索,但仍在其他路径中搜索,.此选项在创libg++库使用

-C
在预处理的时候,不删除注释信息,一般和-E使用,有时候分析程序,用这个很方便的

-M
生成文件关联的信息。包含目标文件所依赖的所有源代码你可以用gcc -M hello.c来测试一下,很简单。

-MM
和上面的那个一样,但是它将忽略由#include<file>造成的依赖关系。

-MD
和-M相同,但是输出将导入到.d的文件里面

-MMD
和-MM相同,但是输出将导入到.d的文件里面

-Wa,option
此选项传递option给汇编程序;如果option中间有逗号,就将option分成多个选项,然后传递给会汇编程序

-Wl.option
此选项传递option给连接程序;如果option中间有逗号,就将option分成多个选项,然后传递给会连接程序.

-llibrary
制定编译的时候使用的库
例子用法
gcc -lcurses hello.c
使用ncurses库编译程序

-Ldir
制定编译的时候,搜索库的路径。比如你自己的库,可以用它制定目录,不然
编译器将只在标准库的目录找。这个dir就是目录的名称。

-O0
-O1
-O2
-O3
编译器的优化选项的4个级别,-O0表示没有优化,-O1为缺省值,-O3优化级别最高
-g
只是编译器,在编译的时候,产生调试信息。

-gstabs
此选项以stabs格式声称调试信息,但是不包括gdb调试信息.

-gstabs+
此选项以stabs格式声称调试信息,并且包含仅供gdb使用的额外调试信息.

-ggdb
此选项将尽可能的生成gdb的可以使用的调试信息.

-static
此选项将禁止使用动态库,所以,编译出来的东西,一般都很大,也不需要什么
动态连接库,就可以运行.

-share
此选项将尽量使用动态库,所以生成文件比较小,但是需要系统由动态库.

-traditional
试图让编译器支持传统的C语言特性

[参考资料]
-Linux/UNIX高级编程
中科红旗软件技术有限公司编著.清华大学出版社出版
-Gcc man page

[ChangeLog]
-2002-08-10
ver 0.1 发布最初的文档
-2002-08-11
ver 0.11 修改文档格式
-2002-08-12
ver 0.12 加入了对静态库,动态库的参数
-2002-08-16
ver 0.16 增加了gcc编译的4个阶段的命令

运行 gcc/egcs

**********运行 gcc/egcs***********************
GCC 是 GNU 的 C 和 C++ 编译器。实际上,GCC 能够编译三种语言:C、C++ 和 Object C(C 语言的一种面向对象扩展)。利用 gcc 命令可同时编译并连接 C 和 C++ 源程序。
如果你有两个或少数几个 C 源文件,也可以方便地利用 GCC 编译、连接并生成可执行文件。例如,假设你有两个源文件 main.c 和 factorial.c 两个源文件,现在要编译生成一个计算阶乘的程序。
代码:

———————–
清单 factorial.c
———————–
int factorial (int n)
{
if (n <= 1)
return 1;
else
return factorial (n - 1) * n;
}
———————–
清单 main.c
———————–
#include <stdio.h>
#include <unistd.h>

int factorial (int n);
int main (int argc, char **argv)
{
int n;

if (argc < 2)
{
printf (”Usage: %s n\n”, argv [0]);
return -1;
}
else
{
n = atoi (argv[1]);
printf (”Factorial of %d is %d.\n”, n, factorial (n));
}
return 0;
}

———————–
利用如下的命令可编译生成可执行文件,并执行程序:
$ gcc -o factorial main.c factorial.c
$ ./factorial 5
Factorial of 5 is 120.

GCC 可同时用来编译 C 程序和 C++ 程序。一般来说,C 编译器通过源文件的后缀名来判断是 C 程序还是 C++ 程序。在 Linux 中,C 源文件的后缀名为 .c,而 C++ 源文件的后缀名为 .C 或 .cpp。但是,gcc 命令只能编译 C++ 源文件,而不能自动和 C++ 程序使用的库连接。因此,通常使用 g++ 命令来完成 C++ 程序的编译和连接,该程序会自动调用 gcc 实现编译。假设我们有一个如下的 C++ 源文件(hello.C):
#include <iostream>
void main (void)
{
cout << “Hello, world!” << endl;
}

则可以如下调用 g++ 命令编译、连接并生成可执行文件:
$ g++ -o hello hello.C
$ ./hello
Hello, world!

**********************gcc/egcs 的主要选项*********
gcc 命令的常用选项
选项 解释
-ansi 只支持 ANSI 标准的 C 语法。这一选项将禁止 GNU C 的某些特色,
例如 asm 或 typeof 关键词。
-c 只编译并生成目标文件。
-DMACRO 以字符串“1”定义 MACRO 宏。
-DMACRO=DEFN 以字符串“DEFN”定义 MACRO 宏。
-E 只运行 C 预编译器。
-g 生成调试信息。GNU 调试器可利用该信息。
-IDIRECTORY 指定额外的头文件搜索路径DIRECTORY。
-LDIRECTORY 指定额外的函数库搜索路径DIRECTORY。
-lLIBRARY 连接时搜索指定的函数库LIBRARY。
-m486 针对 486 进行代码优化。
-o FILE 生成指定的输出文件。用在生成可执行文件时。
-O0 不进行优化处理。
-O 或 -O1 优化生成代码。
-O2 进一步优化。
-O3 比 -O2 更进一步优化,包括 inline 函数。
-shared 生成共享目标文件。通常用在建立共享库时。
-static 禁止使用共享连接。
-UMACRO 取消对 MACRO 宏的定义。
-w 不生成任何警告信息。
-Wall 生成所有警告信息。

]]>
http://diggex.net/?feed=rss2&p=648
学习的方法 http://diggex.net/?p=646 http://diggex.net/?p=646#comments Tue, 25 May 2010 14:19:16 +0000 admin http://diggex.net/?p=646 为了进步,我们必须忍受一定的未知

“暂时不用去问为什么”可能更划算

有问题解决不了,暂时先把它记下来,而后继续前行。

]]>
http://diggex.net/?feed=rss2&p=646
从Reddit学到的七条经验 http://diggex.net/?p=645 http://diggex.net/?p=645#comments Mon, 24 May 2010 07:09:08 +0000 admin http://diggex.net/?p=645 Steve Huffman,Reddit的共同创始人,分享了将Reddit从一个小型Web应用程序发展为大型社交网站过程中学到的主要经验。

Steve Huffman和Alexis Ohanian在2005年创建了Reddit,当时在一台机器上运行Web应用程序、应用服务器和数据库。发展至今,Reddit已经成长为每月750 万用户、2亿7千万PV的站点。Huffman在一次演讲中谈到 Reddit发展过程中学到的经验,他们犯的很多错误,以及他们是如何修复这些错误的。

1、宕机是家常便饭

起初他们经常宕机,Huffman常常睡在笔记本电脑旁边,每隔几个小时就醒一下,看看网站是否仍在运行。当时的解决方案是使用 Supervise,那是一个能重启崩溃应用程序的守护进程。这带来了一种很有趣的运行应用程序的方式:如果应用程序有内存泄露,或者消耗了太多的内存, 只需终止它随后重启即可。这只是一个临时方案,而非最佳方案,最终是基于日志内容修复了应用程序。

2、服务分离

Huffman建议将类似的进程集中在一台机器或一组机器上,这样可以避免频繁的上下文切换,减少资源消耗。他还提供了一个最佳实践——在一个数据 库中处理类似的数据,以此避免频繁的索引缓存切换,将其他类型的数据移到别的机器上去。

Huffman强烈建议避免使用线程,在Python中这就是“死亡之吻,缓慢之道”。如果多个任务被分配到独立的进程而非线程上,那在请求量上 升、需要更多资源的时候,就可以方便地将它们移到不同的机器上。这种做法的唯一问题就是进程间通信,除此之外都比使用线程要好,因为这样的架构能更平滑地 进行扩展。

3、开放Schema

随着数据库的发展,每个要更新Schema的新特性都会带来更多的问题。向一个有1千万行数据的表中增加一个新字段需要很多时间,尤其是有备份 (backup)和复制(replication)时。他们当时虽然没有备份,但也花了好多天,因为他们构建了一个副本(replica)。

解决方案是使用开放Schema或实体-属性值,Key-Value存储。现在每个数据类型有两张表:

image

Thing可以是用户、链接、评论等,共享相同的Schema。Data表由大量数据构成,但里面只有3个字 段:ID、 Key和Value。在新的Schema中添加新特性并不涉及Schema的变更,也不需要创建新表。此外,再也没有数据库的join操作,这也易于数据 库的拆分。

4、保持无状态

所有Web应用程序都有一个共同的目标,它的每台应用服务器都能处理任意请求。这个目标在只有一台机器时很容易达成(这是显而易见的),但当使用多 台服务器并缓存应用状态时,情况就变得复杂了。每台服务器在访问缓存数据时的复杂性都增加了,而且还加入了更多的缓存冗余。

此处的解决方案是切换到memcache并在所有应用服务器上不再使用状态。一个立竿见影的效果是一台应用程序服务器宕机时不会影响其他服务器。此 外,可以简单地通过增加更多服务器来进行扩展。

将缓存服务器与其他服务器隔离开是很重要的,这能避免资源争夺。

5、Memcache所有内容

Reddit的所有内容都使用了memcache:数据库的数据、会话数据、渲染的页面、存储的内部函数、预先计算的页面、全局锁。它们还用 memcachedb进行数据持久化。

6、存储冗余数据

“在你需要前,数据都保持正规化”会降低性能。当用户需要以特定格式来展现数据时,获取原始数据随即进行处理会延长响应时间,以至于用户放弃等待结 果。解决方案是在内存和硬盘中保存数据的所有格式。这样做对磁盘和内存有些影响,但对用户请求的快速响应很有帮助。

对Reddit而言,速度的关键是“预先计算所有内容并放入memcache。”

7、脱机工作

当用户发起请求时,系统要执行用于返回适当响应的必要工作,其他事情都放到队列任务中脱机执行。例如,脱机执行的工作包括:预先计算列表、获取缩略 图、检测欺骗行为、删除垃圾信息、计算奖励以及更新搜索索引。当用户给某个链接投票时,他并不需要等待所有索引和列表更新完毕,这些任务可以在响应用户后 再去执行。

下图中的蓝色箭头表示为响应用户请求所执行的活动,粉色箭头表示脱机执行的活动:

image

查看英文原文:7 Lessons Learned at Reddit

]]>
http://diggex.net/?feed=rss2&p=645
十条不错的编程观点 http://diggex.net/?p=644 http://diggex.net/?p=644#comments Thu, 20 May 2010 02:23:31 +0000 admin http://diggex.net/?p=644 Stack Overflow上有这样的一个贴子《What’s your most controversial programming opinion?》,翻译成中文就是“你认为最有争议的编程观点是什么?”,不过,在400多个主回贴,以及 千把个子回贴中,好像并不是很有争议,而是令人相当的茅塞顿开,下面罗列一些,并通过我自己的经历和理解发挥了一些,希望对你有帮助。

1) The only “best practice” you should be using all the time is “Use Your Brain”.

唯一的“Best Practice”并不是使用各种各样被前人总结过的各种设计方法、模式,框架,那些著名的方法、模式、框架只代码赞同他们的人多,并不代表他们适合你, 你应该更多的去使用你的大脑,独立地思考那些方法、模式、框架出现的原因和其背后的想法和思想,那才是“best practice”。事实上来说,那些所谓的“Best Practice”只不过是限制那些糟糕的程序员们的破坏力。

2)Programmers who don’t code in their spare time for fun will never become as good as those that do.

如果你对编程没有感到一种快乐,没有在你空闲的时候去以一种的娱乐方式去生活,无论是编程,还是运动,还是去旅游,那么你只不过是在应付你的工作, 无时无刻不扎在程序堆中,这样下来,就算是你是一个非常聪明,非常有才华的人,你也不会成为一个优秀的编程员,要么只会平平凡凡,要么只会整天扎在技术中 成为书呆子。当然,这个观点是有争议,热情和能力的差距也是很大的。不过我们可以从中汲取其正面的观点。

3)Most comments in code are in fact a pernicious form of code duplication.

注释应该是注释Why,而不是How和What,参看《惹恼程序员的十件事》,代码告诉你 How,而注释应该告诉你Why。但大多数的程序并不知道什么是好的注释,那些注释其实和code是重复的,毫无意义。

4)XML is highly overrated

XML可能被高估了。XML对于Web上的应用是不错的,但是我们把其用到了各种地方,好像没有XML,我们都不会编程了。

5)Not all programmers are created equal

这是那些junior经理或是流程爱犯的错,他们总是认为,DeveloperA == DeveloperB,只要他们的title一样,他们以为他们的能力、工作速度、解决问题的方法,掌握的技能等等都是一样的。呵呵。更扯的是,在某些时 候,就算是最差的程序员,他们也会认为其比别人强十倍,这就是现代的SB管理。

6)”Googling it” is okay!

Google只会给你知识,并不会教给你技能。那里只有“鱼”,没有“渔”,过度的使用Google,只会让你越来越离不开他,你越来越去要去立马 告诉你答案,而你越来越不会自己去思考,自己去探索,去专研。如果KFC快餐是垃圾食品对我们的身体没有好处,那么使用Google也一种快餐文化对我们 的智力发展大大的没有好处。

7)If you only know one language, no matter how well you know it, you’re not a great programmer.

如果你只懂一种语言,准确的说,如果你只懂一类语类,如:Java和C#,PHP和Perl,那么,你将会被局限起来,只有了解了各种各样的语言, 了解了不同语言的不同方法 ,你才会有比较,只有了比较,你才会明白各种语言的长处和短处,才会让你有更为成熟的观点,而且不整天和别的程序在网上斗嘴争论是Windows好还是 Unix好,是C好还是C++好,有这点工夫能干好多事了。世界因为不同而精彩,只知道事物的一面是有害的。

8)Your job is to put yourself out of work.

你的工作不是保守,那种教会徒弟,饿死师父的想法,不但是相当短浅的,而且还是相当脑残的。因为,在计算机世界里,你掌握的老技术越多,你就越没 用,因为技术更新的太快。你对工作越保守,这个工作就越来越离不开你,你就越不越不能抽身去学新的东西,你也就越来越OUT了。记住:If you can’t be replaced then you can’t be promoted!

9)Design patterns are hurting good design more than they’re helping it.

很多程序员把设计模式奉为天神,他们过度的追求设计模式以至都都忘了需求是什么,结果整个系统设计被设计模式搞得乱七八糟,我们叫这种编程为“设计模式驱动编程”,正如第一点所 说,如果你不懂得用自己的大脑思考的话,知其然,不知所以然的话,那么你不但得不到其好处,反而受其所累。

10)Unit Testing won’t help you write good code

准确地说,我们可以认为这是Test-Driven开发,其实,这种开发就是先写unit test case,这样的开发方式的主要目的是,为了防止你不会因为一个改动而引入Bug,但这并不会让你能写出更好的代码。这只会让你写出不会出错的代码。同第 一点,这样的方法,只不过是防止糟糕的 程序员,而并不是让程序员或代码质量更有长进。反而,通过Unit Test会为程序员的为自己代码做辩解的一种托辞。

最后,顺便说一下,以前去那个敏捷的公司面试,发现那个公司的某些技术人员中毒不浅,具体表现在上述的1)9)10)观点上。

]]>
http://diggex.net/?feed=rss2&p=644
RESTful OAuth认证全过程 http://diggex.net/?p=643 http://diggex.net/?p=643#comments Wed, 19 May 2010 03:08:27 +0000 admin http://diggex.net/?p=643 为了保护Myspace用户的数据,当挂件(widget)需要通过RESTful API访问或修改受保护的用户数据(例如修改用户心情状态)时,需要用户认证并授权挂件(widget)拥有相关权限。

Myspace RESTful API认证授权机制遵循 OAuth规 范。 限于篇幅本文档没有包含该规范的全部信息。如果使用Myspace提供的Actionscript3 API Library进行开发,那么通常你不必通读整个规范。 如果需要手工编程进行认证(例如开发其他语言版本的Myspace RESTful API客户端),那么建议你通读OAuth规范。 此外如果你发现本文档的某些部分难以理解,也建议你阅读OAuth规 范以获取更多信息。

API Key

每个使用Myspace RESTful API认证授权机制的挂件(widget)必须拥有唯一的Myspace RESTful API Key。获得API Key的同时也获得一个对应该API Key的私钥。他们是在创建挂件时自动生成的:

参数名

参数值

Consumer Key (挂件URI)

http://www.myspace.cn/1305688195

Consumer Secret (Security Key)

dd8a4e4e99fd4bca84f8c3f2c0cd7fe1

API Key与私钥都将被用于Myspace RESTful API的认证过程。 作为 第三方开发者,你需要保护好API Key对应的私钥,一旦私钥泄露,其对应的API Key则可能被他人滥用。

请求参数与返回值

Myspace RESTful API认证请求的参数必须转义且转义之前的参数必须为utf-8编码。 认证请求支持以以下三种形式进行传递参数

l URL参数

l content-typeapplication/x-www-form-urlencodedHTTP PUT请求体

l OAuth HTTP认证方案中定义的HTTP Authorization首部

认证请求的返回值位于HTTP响应的消息体内。 返 回值的格式类似URL参数(‘=’连接的名值对,中间以‘&’连接),例如

oauth_signature=kB4j6FEcpfXlai1RUa3XI0kHZLI%3D&oauth_signature_method=HMAC-SHA1

认证流程

Myspace RESTful API认证通过以下三个步骤完成

l 获取未授权的 Request Token

l 请求用户授权 Request Token

l 使用授权后的 Request Token 换取 Access Token

:通过访问http://developer.myspace.cn/modules/apis/pages/accessdelegationtool.aspx;您可以在线测试获取认证Token的全部过程。

获取未授权的Request Token

通过访问以下 URL 获取未授权的 Request Token

http://api.myspace.cn/v1/request_token

该请求需要包含如下参数:

参数

意义

oauth_consumer_key

Consumer Key

oauth_nonce

单次 值,一个随机字符串,用于防止重放攻击(18)

oauth_signature_method

签名 方法,Myspace支持OAuth中定义的HMAC-SHA1, RSA-SHA1PLAINTEXT三种签名方式

oauth_timestamp

时间 戳,用格林威治时间197011000秒起的秒数表示

oauth_token

oauth_version

目前 为1.0(v1)

oauth_signature

签名 值

返回值包括未授权的Request Token, 例如:

KzT+/yAvSJu8kA2gwXhn/8+7w+/bpFTfNgM4xMGeCp2+DvHsHXF+grZf+x+Z4KYyPVdU0Z3LUq3eFfuXaDOLvw==

请求用户授权Request Token

获得Request Token之后,需要请求用户授权该Request Token

挂件(widget)需要将浏览器跳转到如下URL(如果无法自动跳转,则需要提示用户手 工跳转) :

http://api.myspace.cn/authorize

跳转后用户会看到请求授权的页面,用户可以选择同意或者拒绝授权

该请求包含两个可选参数以及若干附加参数:

参数

意义

oauth_token

上一 步中获得的Request Token

如果 不存在用户会被要求填写Request Token

oauth_callback

如果 包含这个参数,认证成功后浏览器会被重定向到形如http://callback?oauth_token=ab3cd9j4ks73hf7gurl,其中oauth_tokenRequest Token

否则 需要用户手工通知挂件(widget)以完成授权

使用授权后的Request Token换取Access Token

用户完成授权后,挂件(widget)可以通过访问如下url换取Access Token:

http://api.myspace.cn/v1/access_token

该请求需要包含如下参数:

参数

意义

oauth_consumer_key

Consumer Key

oauth_nonce

单次 值,一个随机字符串,用于防止重放攻击(18)

oauth_signature_method

签名 方法,Myspace支持OAuth中定义的HMAC-SHA1, RSA-SHA1PLAINTEXT三种签名方式

oauth_timestamp

时间 戳,用格林威治时间197011000秒起的秒数表示

oauth_token

第一 步中获得的Request Token

oauth_version

目前 为1.0(v1)

oauth_signature

签名 值

返回值包括授权的Access Token,例如

oauth_token=KzT%2b%2fyAvSJu8kA2gwXhn%2f8%2b7w%2b%2fbpFTfNgM4xMGeCp2%2bDvHsHXF%2bgrZf%2bx%2bZ4KYyPVdU0Z3LUq3eFfuXaDOLvw%3d%3d

访问或修改受保护资源

获得Access Token之后,挂件(widget)可以使用该Access Token访问或修改受保护的 资源

每次操作受保护资源时,请求参数中都必须包含以下参数:

参数

意义

oauth_consumer_key

Consumer Key

oauth_nonce

单次 值,一个随机字符串,用于防止重放攻击(18)

oauth_signature_method

签名 方法,Myspace支持OAuth中定义的HMAC-SHA1, RSA-SHA1PLAINTEXT三种签名方式

oauth_timestamp

时间 戳,用格林威治时间197011000秒起的秒数表示

oauth_token

GET操作时,需要使用Access Token,负责置空

oauth_version

目前 为1.0(v1)

oauth_signature

签名 值

]]>
http://diggex.net/?feed=rss2&p=643
互联网常见Open API文档资源 http://diggex.net/?p=642 http://diggex.net/?p=642#comments Tue, 13 Apr 2010 04:07:47 +0000 admin http://diggex.net/?p=642 所谓的开放API(OpenAPI)是服务型网站 常见的一种应用,网站的服务商将自己的网站服务封装成一系列API(Application Programming Interface,应用编程接口)开放出去,供第三方开发者使用,这种行为就叫做开放网站的API,所开放的API就被称作OpenAPI(开放 API)。

网站提供开放平台的API后,可以吸引一些第三方的开发人员在该平台上开发商业应用,平台提供商可以获得更多的流量与市场份 额,第三方开发者不需要庞大的硬件与技术投资就可以轻松快捷的创业,从而达到双赢的目的,开放API是大平台发展、共享的途径,让开发者开发一个有价值应 用,付出的成本更少,成功的机会更多。今天,OpenAPI作为互联网在线服务的发展基础,已经成为越来越多互联网企业发展服务的必然选择。下面我就列举 一些常见网站服务的Open API文档资源索引。

SNS类网站API

Facebook - http://developers.facebook.com/

人人网开放平台 - http://dev.renren.com/

51.com开放平台 - http://developers.51.com/

MySpace开发者平台 - http://developer.myspace.cn/

Opensocial - http://wiki.opensocial.org/

Google Gadgets 小工具 API 开发人员指南 - http://www.google.com/intl/zh-TW/apis/gadgets/docs-home.html

Gadgets API 开发人员指南 - http://code.google.com/intl/zh-CN/apis/gadgets/docs/dev_guide.html

Gadgets API - http://code.google.com/intl/zh-CN/apis/gadgets/

电子商务类

Amazon API - http://aws.amazon.com/

eBay API - http://developer.ebay.com/

淘宝开放平台 - http://www.taobao.com/theme/tao_source/

微博API

Twitter API - http://apiwiki.twitter.com/Twitter-API-Documentation

Status.Net(Laconica) API - http://status.net/wiki/Twitter-compatible_API

新浪微博开发者平台 - http://open.t.sina.com.cn

注:需要授权的开发者才能访问,其API调用格式类似Twitter,但需要一个API Key用于认证管理。

搜狐博客开放平台 - http://ow.blog.sohu.com/

Follow5 API - http://www.follow5.com/f5/jsp/other/api/api.jsp

嘀咕API - http://code.google.com/p/digu-api/wiki/DiguApi

做啥API - http://code.google.com/p/zuosa-api/wiki/ZuosaApiDoc

人间网API - http://renjian.com/api.html

9911微博API - http://www.9911.com/api.php

Google Maps API

Google Maps API Developer Guide - http://code.google.com/intl/en/apis/maps/documentation/

Google Maps API Tutorial - http://econym.org.uk/gmap/extensions.htm

GMaps Utility Library - http://code.google.com/p/gmaps-utility-library-dev/wiki/Libraries

GMaps Utility Examples - http://gmaps-utility-library.googlecode.com/svn/trunk/labeledmarker/release/examples/

Saving User-Added Form Data - http://code.google.com/intl/zh-CN/apis/maps/articles/phpsqlinfo.html

Firefox类

Mozilla 开发者中心的扩展开发专题 - https://developer.mozilla.org/en/Extensions

XUL 1.0 规范 - http://www-archive.mozilla.org/projects/xul/xul.html

更多地了解这种基于 XML 的用户界面语言,它可以构建各种富跨平台应用程序。

Mozilla Development Center 的 XUL 教程 - http://developer.mozilla.org/en/docs/XUL_Tutorial

Getting started with extension development 编写一个最简单的Firefox扩展 - http://kb.mozillazine.org/Getting_started_with_extension_development

Setting up extension development environment - http://developer.mozilla.org/en/docs/Setting_up_extension_development_environment

实战 Firefox 扩展开发 - http://www.ibm.com/developerworks/cn/web/wa-lo-firefox-ext/

使用 XUL 实现浏览器扩展 (1) - http://www.ibm.com/developerworks/cn/web/wa-xul1/

使用 XUL 实现浏览器扩展 (2) - http://www.ibm.com/developerworks/cn/web/wa-xul2/

应用类

豆瓣API - http://www.douban.com/service/apidoc/

Flickr API - http://www.flickr.com/services/api/

Last.fm API - http://www.last.fm/api

Box.net API - http://developers.box.net/

Delicious API - http://delicious.com/help/api

API统计 - http://www.programmableweb.com/apis

]]>
http://diggex.net/?feed=rss2&p=642
google go 语言 基础 http://diggex.net/?p=641 http://diggex.net/?p=641#comments Fri, 09 Apr 2010 04:19:58 +0000 admin http://diggex.net/?p=641 Google最近发布新型的编程语言,Go。它被设计为将现代编程语言的先进 性带入到目前仍由C语言占统治地位的系统层面。然而,这一语言仍在试验阶段并在不断演变。

Go语言的设计者计划设计一门简单、高效、安全和 并发的语言。这门语言简单到甚至不需要有一个符号表来进行词法分析。它可以快速地编译;整个工程的编译时间在秒以下的情况是常事。它具备垃圾回收功能,因 此从内存的角度是安全的。它进行静态类型检查,并且不允许强制类型转换,因而对于类型而言是安全的。同时语言还内建了强大的并发实现机制。

阅读Go

Go的语法传承了与C一样的风格。程序由函数组成,而函数体是一系列的语句序列。一段代码块用花括号括起来。这门语言保留有限的关键字。表达式使用 同样的中缀运算符。语法上并无 太多出奇之处。

Go语言的作者在设计这一语言时坚持一个单一的指导原则:简单明了至上。一些新的语法构件提供了简明地表达一些约定俗成的概 念的方式,相较之下用C表达显得冗长。而其他方面则是针对几十年的使用所呈现出来的一些不合理的语言选择作出了改进。

变量声明

变量是如下声明的:

var sum int // 简单声明
var total int = 42 // 声明并初始化

最值得注意的是,这些声明里的类型跟在变量名的后面。乍一看有点怪,但这更清晰明了。比如,以下面这个C片段来说:

int* a, b;

它并明了,但这里实际的意思是a是一个指针,但b不是。如果要将两者都声明为指针,必须要重复星号。然后在Go语言里,通过如下方式可以将两者都 声明为指针:

var a, b *int

如果一个变量初始化了,编译器通常能推断它的类型,所以程序员不必显式的敲出来:

var label = "name"

然而,在这种情况下var几乎显得是多余了。因此,Go的作者引入了一个新的运算符来 声明和初始化一个新的变量:

name := "Samuel"

条件语句

Go语言当中的条件句与C当中所熟知的if-else构造一样,但条件不需要被打包在括号内。这样可以减少阅读代码时的视觉上的混乱。

括号并不是唯一被移去的视觉干扰。在条件之间可以包括一个简单的语句,所以如下的代码:

result := someFunc();
if result > 0 {
	/* Do something */
} else {
	/* Handle error */
}

可以被精简成:

if result := someFunc(); result > 0 {
	/* Do something */
} else {
	/* Handle error */
}

然而,在后面这个例子当中,result只在条件块内部有效——而前者 中,它在整个包含它的上下文中都是可存取的。

分支语句

分支语句同样是似曾相识,但也有增强。像条件语句一样,它允许一个简单的语句位于分支的表达式之前。然而,他们相对于在C语言中的分支而言走得更远。

首先,为了让分支跳转更简明,作了两个修改。情况可以是逗号分隔的列表,而fall-throuth也不再是默认的行为。

因此,如下的C代码:

int result;
switch (byte) {
 case 'a':
 case 'b':
   {
     result = 1
     break
   }

 default:
   result = 0
}

在Go里就变成了这样:

var result int
switch byte {
case 'a', 'b':
  result = 1
default:
  result = 0
}

第二点,Go的分支跳转可以匹配比整数和字符更多的内容,任何有效的表达式都可以作为跳转语句值。只要它与分支条件的类型是一样的。

因此如下的C代码:

int result = calculate();
if (result < 0) {
  /* negative */
} else if (result > 0) {
  /* positive */
} else {
  /* zero */
}

在Go里可以这样表达:

switch result := calculate(); true {
case result < 0:
  /* negative */
case result > 0:
  /* positive */
default:
  /* zero */
}

这些都是公共的约定俗成,比如如果分支值省略了,就是默认为真,所以上面的代码可以这样写:

switch result := calculate(); {
case result < 0:
  /* negative */
case result > 0:
  /* positive */
default:
  /* zero */
}

循环

Go只有一个关键字用于引入循环。但它提供了除do-while外C语言当中所有可用的循环方式。

条件

for a > b { /* ... */ }

初始,条件和步进

for i := 0; i < 10; i++ { /* ... */ }

范围

range语句右边的表达式必须是arrayslicestring或者map, 或是指向array的指针,也可以是channel

for i := range "hello" { /* ... */ }

无限循环

for { /* ever */ }

函数

声明函数的语法与C不同。就像变量声明一样,类型是在它们所描述的术语之后声明的。在C语言中:

int add(int a, b) { return a + b }

在Go里面是这样描述的:

func add(a, b int) int { return a + b }

多返回值

在C语言当中常见的做法是保留一个返回值来表示错误(比如,read()返回0),或 者保留返回值来通知状态,并将传递存储结果的内存地址的指针。这容易产生了不安全的编程实践,因此在像Go语言这样有良好管理的语言中是不可行的。

认识到这一问题的影响已超出了函数结果与错误通讯的简单需求的范畴,Go的作者们在语言中内建了函数返回多个值的能力。

作为例子,这个函数将返回整数除法的两个部分:

func divide(a, b int) (int, int) {
  quotient := a / b
  remainder := a % b
  return quotient, remainder
}

有了多个返回值,有良好的代码文档会更好——而Go允许你给返回值命名,就像参数一样。你可以对这些返回的变量赋值,就像其它的变量一样。所以我们可以重写divide

func divide(a, b int) (quotient, remainder int) {
  quotient = a / b
  remainder = a % b
  return
}

多返回值的出现促进了”comma-ok”的模式。有可能失败的函数可以返回第二个布尔结果来表示成功。作为替代,也可以返回一个错误对象,因此像下面这样的代码也就不见怪了:

if result, ok := moreMagic(); ok {
  /* Do something with result */
}

匿名函数

有了垃圾收集器意味着为许多不同的特性敞开了大门——其中就包括匿名函数。Go为声明匿名函数提供了简单的语法。像许多动态语言一样,这些函数在它们被定义的范围内创建了词法闭包。

考虑如下的程序:

func makeAdder(x int) (func(int) int) {
  return func(y int) int { return x + y }
}

func main() {
  add5 := makeAdder(5)
  add36 := makeAdder(36)
  fmt.Println("The answer:", add5(add36(1))) //=> The answer: 42
}

基本类型

像C语言一样,Go提供了一系列的基本类型,常见的布尔,整数和浮点数类型都具备。它有一个Unicode的字符串类型和数组类型。同时该语言还引入了两 种新的类型:slicemap

数组和切片

Go语言当中的数组不是像C语言那样动态的。它们的大小是类型的一部分,在编译时就决定了。数组的索引还是使用的熟悉的C语法(如 a[i]),并且与C一样,索引是由0开始的。编译器提供了内建的功能在编译时求得一个数组的长度 (如 len(a))。如果试图超过数组界限写入,会产生一个运行时错误。

Go还提供了切片(slices),作为数组的变形。一个切片(slice)表示一个数组内的连续分段,支持程序员指定底层存储的明确部分。构建一个切片 的语法与访问一个数组元素类似:

/* Construct a slice on ary that starts at s and is len elements long */
s1 := ary[s:len]

/* Omit the length to create a slice to the end of ary */
s2 := ary[s:]

/* Slices behave just like arrays */
s[0] == ary[s] //=> true

// Changing the value in a slice changes it in the array
ary[s] = 1
s[0] = 42
ary[s] == 42 //=> true

该切片所引用的数组分段可以通过将新的切片赋值给同一变量来更改:

/* Move the start of the slice forward by one, but do not move the end */
s2 = s2[1:]

/* Slices can only move forward */
s2 = s2[-1:] // this is a compile error

切片的长度可以更改,只要不超出切片的容量。切片s的容量是数组从s[0]到数组尾端的大小,并由内建的cap()函数返回。一个切片的长度永远不能超出它的容量。

这里有一个展示长度和容量交互的例子:

a := [...]int{1,2,3,4,5} // The ... means "whatever length the initializer has"
len(a) //=> 5

/* Slice from the middle */
s := a[2:4] //=> [3 4]
len(s), cap(s) //=> 2, 3

/* Grow the slice */
s = s[0:3] //=> [3 4 5]
len(s), cap(s) //=> 3, 3

/* Cannot grow it past its capacity */
s = s[0:4] // this is a compile error

通常,一个切片就是一个程序所需要的全部了,在这种情况下,程序员根本用不着一个数组,Go有两种方式直接创建切片而不用引用底层存储:

/* literal */
s1 := []int{1,2,3,4,5}

/* empty (all zero values) */
s2 := make([]int, 10) // cap(s2) == len(s2) == 10

Map类型

几乎每个现在流行的动态语言都有的数据类型,但在C中不具备的,就是dictionary。Go提供了一个基本的dictionary类型叫做map。下 面的例子展示了如何创建和使用Go map:

m := make(map[string] int) // A mapping of strings to ints

/* Store some values */
m["foo"] = 42
m["bar"] = 30

/* Read, and exit program with a runtime error if key is not present. */
x := m["foo"]

/* Read, with comma-ok check; ok will be false if key was not present. */
x, ok := m["bar"]

/* Check for presence of key, _ means "I don't care about this value." */
_, ok := m["baz"] // ok == false

/* Assign zero as a valid value */
m["foo"] = 0;
_, ok := m["foo"] // ok == true

/* Delete a key */
m["bar"] = 0, false
_, ok := m["bar"] // ok == false

面向对象

Go语言支持类似于C语言中使用的面向对象风格。数据被组织成structs,然后定义操作这些structs的函数。类似于Python,Go语言提供 了定义函数并调用它们的方式,因此语法并不会笨拙。

Struct类型

定义一个新的struct类型很简单:

type Point struct {
  x, y float64
}

现在这一类型的值可以通过内建的函数new来分配,这将返回一个指针,指向一块内存单元,其所占内存槽初始化为零。

var p *Point = new(Point)
p.x = 3
p.y = 4

这显得很冗长,而Go语言的一个目标是尽可能的简明扼要。所以提供了一个同时分配和初始化struct的语法:

var p1 Point = Point{3,4}  // Value
var p2 *Point = &Point{3,4} // Pointer

方法

一旦声明了类型,就可以将该类型显式的作为第一个参数来声明函数:

func (self Point) Length() float {
  return math.Sqrt(self.x*self.x + self.y*self.y);
}

这些函数之后可作为struct的方法而被调用:

p := Point{3,4}
d := p.Length() //=> 5

方法实际上既可以声明为值也可以声明为指针类型。Go将会适当的处理引用或解引用对象,所以既可以对类型T,也可以对类型*T声明方式,并合理地使用它们。

让我们为Point扩展一个变换器:

/* Note the receiver is *Point */
func (self *Point) Scale(factor float64) {
  self.x = self.x * factor
  self.y = self.y * factor
}

然后我们可以像这样调用:

p.Scale(2);
d = p.Length() //=> 10

很重要的一点是理解传递给MoveToXYself和其它的参数一样,并且是传递,而不是引用传递。如果它被声明为Point,那么在方法内修改的struct就不再跟调用方的一样——值在它们传递给方法的时候被 拷贝,并在调用结束后被丢弃。

接口

像Ruby这样的动态语言所强调面向对象编程的风格认为对象的行为比哪种对象是动态类型duck typing)更为重要。Go所 带来的一个最强大的特性之一就是提供了可以在编程时运用动态类型的思想而把行为定义的合法性检查的工作推到编译时。这一行为的名字被称作接口

定义一个接口很简单:

type Writer interface {
  Write(p []byte) (n int, err os.Error)
}

这里定义了一个接口和一个写字节缓冲的方法。任何实现了这一方法的对象也实现了这一接口。不需要像Java一样进行声明,编译器能推断出来。这既给予了动态类型的表达能力又保留了静态类型检查的安全。

Go当中接口的运作方式支持开发者在编写程序的时候发现程序的类型。如果几个对象间存在公共行为,而开发者想要抽象这种行为,那么它就可以创建一个接口并使用它。

考虑如下的代码:

// Somewhere in some code:
type Widget struct {}
func (Widget) Frob() { /* do something */ }

// Somewhere else in the code:
type Sprocket struct {}
func (Sprocket) Frob() { /* do something else */ }

/* New code, and we want to take both Widgets and Sprockets and Frob them */
type Frobber interface {
  Frob()
}

func frobtastic(f Frobber) { f.Frob() }

需要特别指出的很重要的一点就是所有的对象都实现了这个空接口:

interface {}

继承

Go语言不支持继承,至少与大多数语言的继承不一样。并不存在类型的层次结构。相较于继承,Go鼓励使用组合和委派,并为此提供了相应的语法甜点使其更容易接受。

有了这样的定义:

type Engine interface {
  Start()
  Stop()
}

type Car struct {
  Engine
}

于是我可以像下面这样编写:

func GoToWorkIn(c Car) {
  /* get in car */

  c.Start();

  /* drive to work */

  c.Stop();

  /* get out of car */
}

当我声明Car这个struct的时候,我定义了一个匿名成员。这是一 个只能被其类型识别的成员。匿名成员与其它的成员一样,并有着和类型一样的名字。因此我还可以写成c.Engine.Start()。 如果Car并没有其自身方法可以满足调用的话,编译器自动的会将在Car上的调用委派给它的Engine上面的方法。

由匿名成员提供的分离方法的规则是保守的。如果为一个类型定义了一个方法,就使用它。如果不是,就使用为匿名成员定义的方法。如果有两个匿名成员都提供一 个方法,编译器将会报错,但只在该方法被调用的情况下。

这种组合是通过委派来实现的,而不是继承。一旦匿名成员的方法被调用,控制流整个都被委派给了该方法。所以你无法做到和下面的例子一样来模拟类型层次:

type Base struct {}
func (Base) Magic() { fmt.Print("base magic") }
func (self Base) MoreMagic() {
  self.Magic()
  self.Magic()
}

type Foo struct {
  Base
}
func (Foo) Magic() { fmt.Print("foo magic") }

当你创建一个Foo对象时,它将会影响Base的两个方法。然而,当你调用MoreMagic时, 你将得不到期望的结果:

f := new(Foo)
f.Magic() //=> foo magic
f.MoreMagic() //=> base magic base magic

并发

Go的作者选择了消息传递模型来作为推荐的并发编程方法。该语言同样支持共享内存,然后作者自有道理:

不要通过共享内存来通信,相反,通过通信来共享内存。

该语言提供了两个基本的构件来支持这一范型:goroutineschannels

Go例程

Goroutine是轻量级的并行程序执行路径,与线程,coroutine或者进程类似。然而,它们彼此相当不同,因此Go作者决定给它一个新的名字并 放弃其它术语可能隐含的意义。

创建一个goroutine来运行名为DoThis的函数十分简单:

go DoThis() // but do not wait for it to complete

匿名的函数可以这样使用:

go func() {
  for { /* do something forever */ }
}() // Note that the function must be invoked

这些goroutine将会通过Go运行时而映射到适当的操作系统原语(比如,POSIX线程)。

通道类型

有了goroutine,代码的并行执行就容易了。然而,它们之间仍然需要通讯机制。Channel提供一个FIFO通信队列刚好能达到这一目的。

以下是使用channel的语法:

/* Creating a channel uses make(), not new - it was also used for map creation */
ch := make(chan int)

/* Sending a value blocks until the value is read */
ch <- 4

/* Reading a value blocks until a value is available */
i := <-ch

举例来说,如果我们想要进行长时间运行的数值计算,我们可以这样做:

ch := make(chan int)

go func() {
  result := 0
  for i := 0; i < 100000000; i++ {
    result = result + i
  }
  ch <- result
}()

/* Do something for a while */

sum := <-ch // This will block if the calculation is not done yet
fmt.Println("The sum is:", sum)

channel的阻塞行为并非永远是最佳的。该语言提供了两种对其进行定制的方式:

  1. 程序员可以指定缓冲大小——想缓冲的channel发送消息不会阻塞,除非缓冲已满,同样从缓冲的channel读取也不会阻塞,除非缓冲是空的。
  2. 该语言同时还提供了不会被阻塞的发送和接收的能力,而操作成功是仍然要报告。
/* Create a channel with buffer size 5 */
ch := make(chan int, 5)

/* Send without blocking, ok will be true if value was buffered */
ok := ch <- 42

/* Read without blocking, ok will be true if a value was read */
val, ok := <-ch

Go提供了一种简单的机制来组织代码:包。每个文件开头都会声明它属于哪一个包,每个文件也可以引入它所用到的包。任何首字母大写的名字是由包导出的,并可以被其它的包所使用。

以下是一个完整的源文件:

package geometry

import "math"

/* Point is capitalized, so it is visible outside the package. */

type Point struct {

  /* the fields are not capitalized, so they are not visible
     outside of the package */

  x, y float64
}

/* These functions are visible outside of the package */

func (self Point) Length() float64 {
  /* This uses a function in the math package */
  return math.Sqrt(self.x*self.x + self.y*self.y)
}

func (self *Point) Scale(factor float64) {
  self.setX(self.x * factor)
  self.setY(self.y * factor)
}

/* These functions are not visible outside of the package, but can be
   used inside the package */

func (self *Point) setX(x float64) { self.x = x }
func (self *Point) setY(y float64) { self.y = y }

缺失

Go语言的作者试图将代码的清晰明确作为设计该语言作出所有决定的指导思想。第二个目标是生产一个编译速度很快的语言。有了这两个标准作为方向,来 自其它语言的许多特性就不那么适合了。许多程序员会发现他们最爱的语言特性在Go当中不存在,确实,有很多人也许会觉得Go语言由于缺乏其它语言所共有的 一些特性,还不太可用。

这当中两个缺失的特性就是异常和泛型,两者在其它语言当中都是非常有用的。而它们目前都不是Go的一分子。但因为该 语言仍处于试验阶段,它们有可能最终会加入到语言里。然而,如果将Go与其它语言作比较的话,我们应当记住Go是打算在系统编程层面作为C语言的替代。明 白这一点的话,那么缺失的这许多特性倒也不是很大的问题了。

最后,因为这一语言才刚刚发布,因此它没有什么类库或工具可以用,也没有Go语 言的集成编程环境。Go语言标准库有些有用的代码,但这与更为成熟的语言比 起来仍还是很少的。

查看英文原文Google Go: A Primer

]]>
http://diggex.net/?feed=rss2&p=641