云端自动化(上):使用Heat对OpenStack进行流程管理

对于云计算平台,涉及到虚拟化、存储、网络、监控等多种资源的管理和分配,以OpenStack为例,它由多个开源子项目组成,管理虚拟化资源的Nova、身份认证的KeyStone、仪表板的Horizon等。

Screen Shot 2015-01-21 at 3.33.25 PM

而对于一个虚拟机实例的启动、IP绑定、软件部署等一系列操作,如果完成由人工来完成,过程是非常繁杂和浪费时间的。AWS提供了一个好用的业务流程操作和资源分配工具CloudFormation,可以通过配置的模板来完成一系列的任务操作,如启动虚拟机实例、指定IP地址、开启端口、下载安装软件,最后成功完成一台Web服务器的搭建。OpenStack这边也提供了一个类似的框架Heat,来支持业务流程的管理。

Heat的基本概念包括StackTemplate。其中Stack是一组云基础设施的容器,被创建的虚拟机实例、网络资源和存储资源都会跟创建时Stack的ID相关联,当需要对这一批资源进行回收时,仅需要对这个Stack进行删除操作即可。所以Stack非常适合用于批量资源的创建和销毁。

Template则描述了这一批需要被创建的资源的细节,在Heat工作时,它会读取Template中的描述,向OpenStack发出请求完成操作,如下图所示:

Screen Shot 2015-01-21 at 3.42.33 PM

 

对Heat项目来说,由于它参考了AWS CloudFormation的实现,所以除了自己的格式之外,CloudFormation的Template它也是兼容的。

Heat Template

#
# This is a HOT template just defining a single compute server.
#
heat_template_version: 2013-05-23

description: >
  Defines a single server.

parameters:
  key_name:
    type: string
    description: Name of an existing key pair to use for the server
    default: ''
  flavor:
    type: string
    description: Flavor for the server to be created
    default: m1.large
  image:
    type: string
    description: Image ID or image name to use for the server
    default: a13349a4-fba5-4791-8db4-de82672311a6

resources:
  server:
    type: OS::Nova::Server
    properties:
      key_name: { get_param: key_name }
      image: { get_param: image }
      flavor: { get_param: flavor }
      networks: [{'network':'48afdbad-ba45-48d7-9b01-29ca6203cc01'}]

outputs:
  server_networks:
    description: The networks of the deployed server
    value: { get_attr: [server, networks] }
Heat Template文件后缀名为.yaml,parameters定义的是一组Key-Value pair,Key值可自由声明,Value的结构根据声明的Key实际类型来定义。声明的Key值可以在resources节点中进行调用。以上面的代码为例,image指定了镜像的ID和描述,在resources的server.properties.image节点,使用 get_param : image 对已定义的 image 进行了值的调用。 server.type指定的OS::Nova::Server,是由OpenStack提供的资源类型定义,使用时需要参考[HOT手册](http://www.justinablog.com/wp-content/uploads/2015/01/hot-reference.pdf)。 ### AWS CloudFormation Template
{
  "AWSTemplateFormatVersion" : "2010-09-09",
  "Parameters" : {
    "KeyName" : {
      "Description" : "Name of an existing EC2 KeyPair to enable SSH access to the instance",
      "Type" : "String",
      "Default" : "keypair-172"
    }
  },

  "Resources" : {
    "MyInstance" : {
      "Type" : "AWS::EC2::Instance",
      "Properties" : {
        "KeyName" : { "Ref" : "KeyName" },
        "ImageId" : "F17-x86_64-cfntools",
        "InstanceType": "m1.small",
        "UserData" : { "Fn::Base64" : "80" }
      }
    }
  },
  "Outputs" : {
    "InstanceIp" : {
      "Value" : { "Fn::Join" : ["", ["ssh ec2-user@",
                                     {"Fn::GetAtt": ["MyInstance",
                                     "PublicIp"]}]]},
      "Description" : "My ssh command"
    }
  }
}
CloudFormation Template后缀名为.template,如果企业使用私有云平台,通常会定义多个网络资源,使用CloudFormation Template创建基础设施时,会提示“寻找到多个网络资源,请指定”的错误。由于查找兼容的Template配置比较花费时间,在私有云平台上,我们更推荐使用Heat自己的Template进行资源描述管理。 ## Heat客户端管理工具 在OpenStack上安装并启用之后,可以通过管理界面上的“编配-栈”对Stack进行创建和删除。另外我们也可以使用heat-client提供的命令行工具进行管理。OpenStack使用Python进行开发,先需要安装Python的包管理工具pip,接下来使用pip安装heat-client.
pip install heat-client
如果使用的是Mac Yosemite并且没有为Python创建虚拟环境的话,需要删除 heat-client 并使用easy_install重新安装相关的依赖包,否则调用 heat 时会出现 no module named xmlrpc_client 的错误。 ### **命令行工具** 安装好后,先需要配置OpenStack的API认证信息到环境变量中,然后可以使用以下命令查看帮助、创建或删除Stack:
export OS_PASSWORD=password
export OS_TENANT_NAME=admin
export OS_AUTH_URL=http://openstack.example.org/v2.0
export OS_USERNAME=ADMIN

heat --help
heat list
heat stack-create -f hello_world.yaml mytest
heat stack-delete mytest
如果遇到找不到Controller机器的错误,还需要在本地机器hosts中添加Controller的DNS解析,这算是开源项目的一个Bug:
10.10.0.1    Controller
创建或删除Stack后Heat都会返回当前Stacks的状态,同时在OpenStack的仪表板中可以看到被创建的Stack和虚拟机实例:
+--------------------------------------+--------------+--------------------+----------------------+
| id                                   | stack_name   | stack_status       | creation_time        |
+--------------------------------------+--------------+--------------------+----------------------+
| 8d874f5c-6c6d-430a-8b9e-695be68e5972 | test-stack   | CREATE_IN_PROGRESS | 2015-01-21T06:21:19Z |
| bfbaea3b-185e-41d3-941b-f329aeb3425e | ansible-heat | DELETE_IN_PROGRESS | 2015-01-21T06:31:30Z |
+--------------------------------------+--------------+--------------------+----------------------+

使用编程方式访问API

Heat本身提供了Naive API可通过Http进行请求,默认端口是8004,在通过身份认证取得tenat_id后,通过以下Url进行请求:

http://heat.example.org:8004/v2/{tenant_id}
官方提供的Python Client类只提供了通过token的方式调用,先需要解决KeyStone身份认证的问题,在身份认证之后,可以在返回的Auth对象中取得Heat的API Url。示例代码如下:
from heatclient.client import Client as heatClient
from keystoneclient.v2_0 import client as keyStoneClient

username = 'admin'
password = 'password'
tenant_name = 'ADMIN'
auth_url = 'http://heat.example.org:35357/v2.0'
keystone = keyStoneClient.Client(username=username, password=password,
                                 tenant_name=tenant_name, auth_url=auth_url)

auth_token = keystone.auth_ref['token']['id']
heat_url = ''
services = keystone.auth_ref['serviceCatalog']
for service in services:
    if service['name'] == 'heat':
        heat_url = service['endpoints'][0]['publicURL']

heat = heatClient('1', endpoint=heat_url, token=auth_token)
创建一个Stack
from heatclient.common import template_utils
path = "/var/tmp/hello_world.yaml"
tpl_files, template = template_utils.get_template_contents(path)
create_fields = {
    'stack_name': 'test_stack',
    'disable_rollback': 'false',
    'parameters': '',
    'template': template,
    'files': dict(list(tpl_files.items()))
}
heat.stacks.create(**create_fields)
删除Stack
delete_fields = {
    'stack_id': 'test_stack'
}
heat.stacks.delete(**delete_fields)

数据挖掘在用户画像上的适用性

现在有不少数据挖掘的场景都是为用户画像,尤其是一些实时数据处理系统,设计亮点是自动从数据中提取规则,及时地为用户生成个性化产品。这其中有几个难点:

  1. 需要多少个“实时”的用户才能提取一个有效的用户特征?
  2. 用户特征和现有用户模型的关系,它对产品的“引爆点”在哪里?
  3. 对每一个细分市场,传统企业都是需要评估它的投资回报率再决定是否投入生产,对数据挖掘来说,只有发现环节,并没有评估环节,很难保证实时生成的产品是有效的,最简单的例子就是推荐系统,大部分的规则最终推荐的结果其实对用户是无效的。
    因为在这些场景下,影响决策的变量很多,数据挖掘和机器学习的特征库还难以覆盖完整。

也许在处理交通信号灯、网络流量、节点可用检测等负载问题上,自主学习加动态调整更适用一些。

网页正文抽取:基于行的Hash值模板算法

做搜索引擎,内容分析是一个核心环节,而要做内容分析,获得有效的内容——即网页的正文抽取是相当复杂的一个步骤。常见的正文抽取算法有基于DOM树的模板匹配法和基于内容密度算法,前者虽然精确,一旦网页的DOM结构发生改变就失效,后者结合机器学习能够对大量的网页快速自动抽取,能够满足搜索引擎的索引分类需求,但依然很难达到用户阅读级别的要求。

我在做Web挖掘时写过一种基于行的Hash值计算出模板的正文抽取算法,在精确度和自动化两方面都能兼顾。主要思路是获得整个网页的文本内容后,计算出每行的Hash值,与模板库中的模板进行匹配,如果能够匹配到模板,就进行正文抽取,如果无法匹配,则与样本库中的样本进行匹配,在样本匹配如能满足,计算出模板重新投取,如果找不到匹配的样本,则直接将整个文档的Hash值写入样本库等待下一个样本与它进行比较。

Untitled (4)

取得网页文本

有很多开源工具可以获取网页的文本,原理都是基于DOM树或XML路径解析。JSoup简洁精悍,Apache Tika比较全面,这里介绍Tika的获取方法,BodyContentHandler,它用于取出HTML中<body />节点的内容。

ContentHandler handler = new BodyContentHandler(outStream);
parser.parse(inStream, handler, metadata, context);
String text = handler.toString();

计算Hash模板

从Tika获取的内容默认是使用\n进行分行,可以使用Java的getHashCode()获取每行的Hash值。设计一个存储Hash模板的矩阵结构ArrayList<ArrayList<Integer>>,为了便于计算,我们需要一个正序排列的Hash值数组作为头部模板,一个倒序排列的Hash值数组作为尾部模板。

public class HashTemplete {

private ArrayList<ArrayList<Integer>> templates = new ArrayList<ArrayList<Integer>>();
private ArrayList<ArrayList<Integer>> docs = new ArrayList<ArrayList<Integer>>();
private int minY = 3;

public int compare (ArrayList<Integer> hashDoc) {
int x = compare(hashDoc, templates);
ArrayList<Integer> template = null;

if (x == -1) {
  x = compare(hashDoc, docs);
  if (x &gt; -1) {
    template = intersect(hashDoc, docs[x]);
    templates.add(template);
  } else {
    docs.add(hashDoc);
  }
} else {
  template = templates[x];
}

return template != null ? template.size() : 0;

}

private int compare (ArrayList<Integer> target, ArrayList<ArrayList<Integer>> sources) {
int x = -1, y = 0;

for (int i = 0; i &lt; sources.size(); i++) {
  for (int j = 0; j &lt; sources[i].size(); j++) {
    if (j == target.size() || sources[i][j] != target[j]) break;
  }
  if (j &gt; y) {
    x = i;
    y = j;
  }
}

return y &lt; minY ? -1 : x;

}
}

minY的作用是指定最小的模板匹配深度,避免数据计算在具体应用上的偏差。最后在存储模板时,还可以加入URL规则库,将模板按照URL键写入,这样在第一步就可以对目标网页进行快迅过滤。

成本、质量与效率

这是一个过程,记录了2009年我的工作总结,具体每篇的发布时间丢失了,放这里自己回顾一下。

(一)

从需求分析,需求用例、变更管理到过程管理,花了两个月时间终于有了比较透彻的想法,最终所有的一切都得落实到成本、质量和效率上面。分析、技术、过程,仅仅不过是可用的武器罢了。

当投身到一个项目之中时,我们只应该有一个目标,那就是赢利。人员的组织带来的是成本的分布及总和的变化,架构及其它设计、过程是质量和持续改进的保障,贯穿在这其中的是效率。

众所周知,很多软件项目都是不赚钱的,赚钱也是通过无偿加班获取。那么,这其中到底有没有一种适当的模式来创造更多的利润呢,我想是有的,并且,在这个市场上,这样的模式将会挽救很多困境中的亏损项目。

传统的CMMI甚至Agile,前者只能简单地拆分和统计过程的成本分布和偏差率,后者事实上是通过改进项目成员的工作思考方式来精化生产,两者都没有揭示出项目内部各种因素的相互影响程度,以及问题的根源在哪里?最合理的改进点又在哪里?

这是一个大的思考方向,接下来第一步,我决定切入项目内部,作一个成本跟踪,然后再设法去改善它的分布以观测总成本的变化和质量效率边际。

关于效率,由于在开发过程中,主要和效率挂勾的是代码编写活动,基于海量的编码经验,我先总结出几条高效原则:

  1. 消灭“代码个性”——一个团队只使用一种风格书写代码——我知道这很难,所以,请使用代码生成器(如 CodeSmith)或安装代码辅助工具(如 ReSharper)。一致的代码风格将使我们可以在一分钟内完成原本可能超过一周的代码返工工作。不用Ctrl+H是笨蛋,不能用Ctrl+H是超级笨蛋。
  2. 保障实现——在开工编码时,应保证让编码者获取到足够实现功能的技术支持。也就是说,不知道怎么去写,那么就不要写,因为你写出来也可能等于没写,把你心情写糟了只是小事,大事是还得耽误测试人员一次回归次数。
  3. 禁止调试,除非万不得已——挂上一个进程,F10、F11一整天,就为了看几个变量的值?得了吧!写一个单元测试在其中Assert.IsNullEmpty,不是十秒钟内就检测出来了吗?!
  4. 使用构建与集成——都什么年代了,还要十个人头挤一盘菜。分拆、构建,然后组装。不需要你保障提交的代码能完美地工作,只需要保障你能够按时提交代码,完工之后,我们再来看它能工作到什么程度。我们是操作源代码,不是捏泥坯;我们可以修正&amp;编译无数次,不是烧出来不好看就只得砸掉。
  5. 即时交流——一个项目中的所有编码者,都应该每三个小时集中交流一次各自的进展情况(重构之前,告诉组员一声会死人?报废别人的工作是可耻的),每天签入/合并一次代码。

(二)

本来关于第二章,这两天想法比较细化的,今天却遭遇三个小deadline,简直把我给整郁闷了,第一个:闷过去了;第二个:蒙过去了;第三个:挺过去了!

三个小时前,我用肯定的语气给同事们说,接下来的这个项目如果失败,一定是败在技术上。没想到三个小时后,我居然已经一知半解了……永远不要低估自己啊。。。

结果关于此系列的想法也给吓跑了一大半。先理个框框。

颠覆工时计划

项目开工之前,传统的做法是,估算工时。根据各个公司的规章制度、用人方式的不同,有“老板拍脑袋”、“项目经理豁客户”、“程序员干瞪眼”几种方式^_^ 不过,事实还是远没有那么的糟糕。比较科学的方法是需求基本确定(80%以上)的三点法估算,需求不太明确的情况下根据代码量估算,等等,由编码者操刀。
好了,现在我说:工时估算,就是项目失败的第一步。因为:\

  1. 需求在这时往往没有明确;
  2. 设计在这时往往没有开始。
    需求&设计才是决定实际工作时间的关键因素。在这两者都没有确定的时候,一个虚假的工时又有何意义呢?

有一个必须接受的事实就是:一个系统的程序员,他真正的工作时间是无法估算的。系统是一个有机的整体,任何一个新功能的加入和修改,如同器官移植一样,手术台上动刀子的结束,并不意味着移植的成功。

所以,我们必须放弃传统的工时估算计划。改为“成本估算计划”(本人独创,如有雷同,巧合之外实属抄袭)。

首先,根据客户的需求紧急程度,与客户协商一个deadline是必须的。请注意这点:根据deadline,增加项目组人员数目是应该的,但人员数目与产出存在着边际效应。根据大多数IT项目管理书籍的看法,超过12~15人的团队会很恐怖。沟通成本、代码独占、意见分歧……等等。最好维持在10人以内。

然后,我们需要制定的是关于这个项目的“赢利目标”,也就是成本估算。按照我的想法,可以分为A、B、C三个档次。A为赢利最高,C为最低,也就是最大能接受的成本开销。

成本大致包括:项目成员的工资、差旅补助、加班补助等人力成本,硬件折旧成本,公司管理费用及杂项支出。

根据这个成本估算,得出项目的计划时间。

接下来要做的就是评估这个时间的科学性,让它与需求数目、架构设计、编码量评估进行对比,如果有超出,至少,我们可以在此刻提前宣布:这个项目很难赢利——然后再想办法与客户进行协商。而不是超期半年之后宣布它已成为“问题项目”。

基于自己的经验和水平,我可以肯定地说,绝大多数项目在经过详尽的分析和设计,在过程中严密控制之后,是可以按期完成的。

那,是不是每次都把赢利目标定为A甚至A+、A++就更好呢?No!我们是受过高等教育的新一代,再去搞“人有多大胆,地有多大产”那一套,就贻笑大方了^_^

首先,两个概念,(我定的),“估算成本”,与“实际成本”,是两回事,最终的成本支出,不是估算成本而是实际成本;并且,“估算成本”越低,“实际成本”可能就越高。

这就牵涉到了标题中的第二个词语:质量。欲知后事如何,且听下回分解嘻嘻~

(三)

不得不说,因为了解了一个新的概念“成本动因分析法”,这系列的想法有一部分被抛弃,并且思维处于卡壳状态。在开始我试图使用最基本的微观经济学原理来阐述,现在发现在生产质量管理中的一些方法与软件项目管理有所交叉,而我对此的知识是完全地缺乏。

与此同时,一组敏捷专家也正在日本考察丰田汽车生产,想从中发掘出一些精益生产模式应用到软件开发之中,可惜,这些专家带回来的第一组消息都是些废话:日本精益实施情况考察

接下来需要花费一些时间学习一些生产作业方面的知识,当然,不会往这方面引得太深入,第一这是希望分享给大多数都是技术出身的项目管理者的,第二我也不专业,唯恐误导,阅读这方面的文档只是因为一般的程序员不会去读,也没有联系起来的意识,所以,引用+解释+对比权当参考。

基本上我希望在这系列文章中思考出一个形状的东西包括:

  1. 技术选择对成本、效率的影响
  2. 质量的标准
  3. 质量与成本的合理比例
  4. 重复操作对效率的利与弊
    计划花上一年的时间来研究这些问题,最好在业余时间开发一套软件来跟踪数据。首先,我想通过手上的一个新项目的记录,来获取一些成功或失败的经验。

这是一个 Wap 2.0 站点开发项目,上周拿到需求,很简单,但因为从来没做过此类项目,预计技术风险很大,并且,交付日期是7.30(7.3国内才有第一个 Wap 2.0 站点发布),上周四花了四个小时看资料,确定了基础开发技术为XHtml + .NET Framework 3.5,很侥幸因为多看了些资料,否则差一点选择了大家都不熟悉的 WML。随后与几个成员分享了 XHtml 语言特殊规范,比较简单,算是可以上手了。

说一下为什么选择 XHtml。因为 XHtml 与 HTML 相似,而 WML 有自己的标识符规则。虽然都比较简单能够马上上手,但里面关键的一点在于:初学和熟练是两回事。这体现在1000行 XHtml 代码因为我对 HTML 完全熟练所以错误为零,而初学的 WML 却可能出现“虽然只有个位数的错误但因为我不精通所以得花上数倍写代码的时间去排错”。这就是区别。

今天想到 GPRS 的速度,站点肯定不可能使用 ORM 不说,还必须有 Page Cache 支持才行,这一块我们也不太熟(其实我们都没有 Web 站点开发的经验),这是当前最大的一个风险。

成本,不说了,五个人一月薪水总和为主,不会出差,只要按期完成,就能保证赢利。所以,目标很简单,就是争取7.30交付。

质量,因为客户对质量要求不高(只图快,即使有些 Bugs 也可以容忍),那我们可以以“保障正常数据通过测试”为达到质量要求。前端的站点则需要保证很高的响应速度和适当的分辨率,这些是客户事前完全不能想到的,但事后绝对会崩溃的。需要引入压力测试,这我们以前也没有做过,常用的浏览器中只有 Opera 能访问 Wap 2.0 站点,手机模拟器还不知道如何做压力测试。这是第二个风险。

同事希望这能成为我们组第一个赢利的项目,目前我能想到最快的方式就是使用开源 CMS 系统改改直接拿来当后台……明天需求评审之后再说吧……

(四)

挂了三个项目,太多工作要做了,实在没空有什么想法。感慨一下今天的经历:

因为项目的缘故去MS面试,对方问我:“你们至今的项目都是亏损,你打算怎么改善?”我就照前段时间的想法谈了。

什么样的人,做什么样的事;信任,建立良好的团队关系,非常重要。我近段时间工作状态也不是很好,得检讨一下。

面完下来后和老Ben等人嗑了一通瓜子,又谈了我的想法,老Ben又开始唠叨“框架框架”,又开始怨我,然后竟然与Linda同学一起欢呼我天朝近期的海外投资,大有天朝人民真的站起来了之兴奋感,(很遗憾很快就被我严厉打击,唉,我真是太不爱国了)

一切都是模模糊糊的。有些想法是不是太超前太理想化了,都得实际检验才行。希望 7.30 我可以把这个项目交付出来吧。

God bless!(哈哈,写完这句想起,年初的时候Jeff与Ben两个家伙去文殊院烧香,现在连我也要喊基督了,哈哈哈)

最后竟然忘了记录项目的进度了,因为我挂的项目有点多,今天才开始做设计,组员们也卡在我这边,等着架构和数据库出来。SO,今天加班。下午和Ben讨论的时候,问了Ben在MS的其它项目组是怎么做的?Ben说他们使用的DAO-富血模型。这基本上和我的想法一样,即Domain Model(以前我们使用的是NET Tier,贫血模型即Table Model,本来当初我设计的Domain Model,因为没有ActiveRecord的开发经验,最后Jobs使用NET Tier解决了DAO层设计师),3月份做一个基础项目时我坚持使用ActiveRecord,感觉效果还不错,大家也马上上手了。这次的WAP项目,依然采用ActiveRecord,然后在CodePlex上找了一个Utilities程序集,基本上满足了我们所有Common操作。明天准备举行一次设计会议,给大家介绍ActiveRecord的关联(只有一名新来的应届生未使用过),所有的Domain Model代码使用我写的CodeSmith模板一次全部生成,省时,省力!

把CodeSmith的ActiveRecord模板share给大家,继续赶工去了:

(五)

至目前为止我们都处于实际进度与计划进度提前一天的工作状态。昨天上午开了一次设计会议10:00~11:30,下午花了20分钟进行了一个方案搭建的小会,随后完成了Domain Model的创建,通过了所有的构建和初始化测试,然后把4月份写的一个权限框架加进来时发现了问题,动态代理产生的新类,在ActiveRecord的NHibernate映射中提示找不到新类的名称。只得放弃AOP的框架。

所有的数据访问都创建了测试,所以不用在UI上进行测试了,这一块省了最多的时间。

今天上午直接找同事谈了新的UI设计方案,将他的工作减少到只做一个控件。另一个同事已经开始做模块了。
另外想说的是:从用户的角度出发,永远是对的;从程序员的角度出发,在开发周期内,都不一定是对的。
太忙了,有些事情到交付后再来总结。

(六)

至今为止,这个项目主要是卡在我手上。我的事情太多了。想把手上的工作分出去,遇到以下问题:

今天去看一个同事的代码,他对DAO不熟,还在Page上写数据访问,让我十分郁闷。准备明天花半个小时让他熟悉EA、CodeSmith,然后DAO的生成和修改可以让他做。

另一个同事虽然才毕业(88年12月生的,Faint哦),但是基础好多了,准备让他熟悉ActiveRecord,因为他手上的工作已经做完了,接下来就协助进行我们这边的Domain层的调试和单元测试编写了。哈哈,我正好是这个小兄弟的导师,以后会是我们组的一主力哦^O^

这样会快很多。

另外我们会举行Development Meeting,集中讨论开发中遇到的技术问题,并且使用统一的控件——这样解决一个问题,就解决了所有的问题。

最近事情一堆一堆的,感情上也有了新的目标,慢慢来吧。

(七)

这周有很多突破性的想法,实在没时间写。先列个提纲吧:

  1. 关于积累——很多时候项目组谈到积累就是指有源代码的框架或类库,其实,积累应该是各种开发的技术细节及技术选型。这个项目结束后我会制定一套覆盖所有技术细节和选择的开发指南,为每个程序员评价技术成熟度。以后的项目,就照着这个指南去勾选最成熟的技术,新的选择引入时,先在指南上加入,再评价。
  2. 领域模型的巨大优势——KAO,连抽象工厂都可以通过模型映射实现,华丽啊~~~有空更新到我那个技术博客上去。
  3. 约束需求的是测试——目前测试人员的观念还没转换过来,以为我在给他们找麻烦。今天给我报Bug说和需求不符,我问Test Case里写清楚了吗,如果是这样的话你为什么不写到Test Case里呢?他终于明白是怎么回事了。
    新看到的一篇文章,觉得这三位大大的话真是无力,也许这标志着,我更加的脚踏实地了。

于丹:40岁,我想明白一件事

送给三位大大的话,其实理想,就是成为被自己热爱的人。

(八)

今天早上我被彻彻底底地打击了!!!!唉!这确实不是我的问题!!太有戏剧性了!!!

(九)

真是天有不测风云,眼见着这个项目加点劲就可以按期交付了,结果因为一点非技术原因,公司宣布必须停止~~MMD,又长见识了

昨天中午很愤然地给死党打了一个小时的长途,死党正在家里等生孩子,说是个儿子~~然后又YY以后来成都工作的愉快团聚~唉,快一点过来吧~

唯一的好处就是现在手上只挂两个项目了。那个延期七个月的项目客户又有了新“想法”,今天和F讨论了一下,是关于工作流的,和我去年七月的想法完全一样,就是,客户希望在运行时刻改变流程的状态(原始需求是“到一定的时间自动结束”,F认为有些流程是需要选择下一个办理人的,所以与客户确定为“已经排定下一步办理人的流程可以设置自动结束时间”)。

今天我们又继续分析了一下,发现这个应该不会很简单。“时间上的自动结束”只是一个任务,参见我以前写的目标导向设计,客户的真实“目标”是“流程自动化”,时间只是“自动化的条件之一”,之所以提出时间,是因为他们目前只想到了时间——也就是说,就算我们今天完成了时间上的自动结束,明天他们也会提出以其它方式来结束流程,因为在未来他们又想到了新的方式,然后,我们又需要在成百上千个文件中修改我们的代码。

说到这里,大家不是要说,ops,难道我们就这样被客户牵着鼻子走吗?当然不是。只要分析出“有条件的自动结束”,并提供多条件结束,那么我们的程序就能轻易地适应未来的改变了。

进一步分析,“自动结束”也只是一个任务,除非整个业务活动完全结束,它的结束只是为下一步骤的开始做铺设。所以,这里隐含的目标是“让下一个办理人在条件许可的情况下开始工作”。那么,我们就认为“结束”这个动作事实上是不需要的(也正是因为如此,客户提出“自动结束”而不是“自动按结束按钮”),我们也不需要设计一个“结束功能”,而是,直接启动下一个办理人的工作入口。

造就这一系列问题的主要原因是这个工作入口(在工作流里称之为待办事项 Work Item)的创建逻辑是“上一步步骤结束之后”。

回过头来看看对象世界与真实世界的差异吧。对象世界里,在这个时刻,这个工作对象才能够参与到这个工作流程中来,事实上,他在真实世界中是一直存在的。假如没有工作流程序,办理请求人在Nn不在的时候,如果默认的规章告诉他可以直接去找下一个人Nn+1,这个流程依然是有效的,那么他就会去找Nn+1这个人把事情处理了,以此类推。然而,在对象的世界里,因为Nn这个人不存在,整个流程已经卡住在了Nn,事情再也无法继续了。

我的想法是,在整个流程发起时,就必须承认N系列的所有的人的“存在性”,这些待办事项都将被创建,然后,有一个“激活”的自动功能。

现在就很简单了,所谓的“自动结束上一步骤”就已经变为了“自动激活下一步骤”,只要在当前应处理人登录时检查他的待办事项是否应该激活,这些条件有可能是时间,也有可能是其它什么信息(只需要提供一个激活接口,就解决了)。

这样做的好处时,解决了我们不知道在何时去“结束”一个步骤。对工作流熟悉的朋友的会很快明白这个问题所在,不太熟悉的可以再看看:

  1. 在目前的所有工作流中,驱动因素是人,人的操作,驱动着流程的进行;
  2. 一旦某个流程无法继续(非系统故障),一定是某个点上的人离开了这个系统;
  3. 那么,如何把已经失去驱动因素的流程驱动起来呢?
    大部分的解决办法都是使用第三方的自动程序去定期询问,然后将它结束掉。这样效率很差,并且不同步。一旦下一个待办人在这个轮询程序的间隔期恰好需要工作了,那么他不得不等待,更多的实际情况时,他们根本就不知道有这样一个轮询程序的存在,所以,他们就彻底地迷惑了:时间到了,条件也满足了,为什么它无法点击(客户一般说:按钮是灰掉的^O^)?

直接把这个人的动作做为激活的因子,事情就全部解决了。你不进入系统性,就不激活,你进入了,就证明可以进行操作,那么就给你在这一刻创建操作所需的所有条件。

其实不是很想去做工作流相关的系统。只是把去年的想法总结了一下。

PS:网上看到的新的好文,又发现一个大牛叫胡健

建造无错软件:契约式设计引论

项目回顾:一个开发人员的观察与思考

新方法学(Martin Flower写的,敏捷的起源)

(十)

三天换了三个项目,这速度简直能把人给折腾疯。没想到这么快就把MS的项目接过来了,过去人家直接一句话“你们开始确定需求吧”就开工……这个效率……压力狂大,MS的一个EM亲自带项目,自称“十年来带项目从未出过问题,这次别给我翻了”。

关于成本控制,今天也学了不少,预算确定,时间确定,还可以在需求范围内控制总成本的支出——从MS的EM那学来的。接下来到八月第二周的主要任务就是确定需求范围。

今天给MS提出要做需求管理和变更管理,他们同意了,具体的,我希望用User Story的方式来进行需求描述。

现在狂疲惫,眼皮直打架。忙着写交接文档,手上前三个项目都积累了不少技术点,争取也能尽快整理出来——在这方面我觉得我有点强迫症,非笔记本、非Blog,好像我就下不了笔了…………应该是下不了键盘~
Tracy推荐的一款笔记本,Acer Timeline轻薄本,13.3英寸,1.6Kg,可持续导航8小时

(十一)

现在这个系列差不多是我的工作日记了,继续。

需求比我想象中要简单一些,Microsoft在我头上安排了一名Project Manager和一名Architect,所以我基本上只需要与他们确认需求和架构在旧系统PMS改造的可行性就OK了。

今天主要做的工作是理清了PMS(Project Management System)在新需求中与其它现有系统的关系。作为项目管理系统,去年做PMS最初是把所有的项目建设相关全部拉通,后来出了很多问题,一些做过的东西被砍掉,放到其它系统中,对我们形成了很大的浪费。所以,这次必须确定新需求的系统边界。

上午与PM开了个需求小会,因为去年离职的缘故,对PMS很多东西的现状我是不太清楚的,然后我发现了一个问题:在这边的三人维护团队只有一个同事清楚所有的需求变更,另外一名测试对现有系统的功能了解得不是很全面,还有一名开发人员现在几乎不能承担任何任务。所以,在那个同事忙的情况下,我们的讨论进展得很缓慢。于是我让测试参加了我们的需求讨论——我的原则是,验证功能完成的不是Use Case,而是Test Case。

由于不能确定很多细节的东西,这次我们只把所有新需求涉及到的系统理清了,并且与相关的负责人进行了沟通,与工程公文的负责人沟通时,才了解到当初不少流程被砍掉的原因:凡是具备法律效用的流程,必须形成公文(转公司领导审批),所以,子系统和部门级的系统是无权处理的,故砍掉——接下来我们就需要确认哪些流程会是法律效用的了,这可以避免后期又被砍掉……(结果是,需要访问的客户今天不在)。

简单说下这次的项目需求:我们以前的PMS仅提供了简单的项目流程管理和数据采集,现在,客户在业务上架设在项目管理之上的计划部门需要从资本的角度对整个项目进行掌控,比如所有项目的预算控制、变更,实际进度追踪,物流明细……等,目前最复杂的就在于,同样的预算变更操作会根据项目的实际进度深入造成几何级数的业务&amp;数据影响,比如说已签定金额的承包合同,未到货的采购合同等等~~暂时还没有想到好的办法来解决。

下午没什么事,研究了一下Mock测试技术,将Type Mock、Rhino Mock、Moq都下载了一份,写了基于Moq 3的测试,发现它实在太有用了,尤其是前期接口开发时,创建的模拟对象能够完成我们实际的操作测试,从而在接口设计期间就能发现错误。Mock可以广泛应用在数据库替代和工作流,希望它能给这个项目带来效率。

Moq,采用Lambda语法,相当的“简略”
http://code.google.com/p/moq/

(十二)

尝试使用用户故事(User Story)的方式撰写需求描述……用户故事的定义是:

  • a written description of the story used for planning and as a
    reminder
  • conversations about the story that serve to flesh out the
    details of the story
  • tests that convey and document details and that can be used to
    determine when a story is complete
    对于每一个用户故事,包括三个方面:
  1. 描述:对用户所需功能的描述,谁会使用,如何使用;
  2. 讨论:关于这个功能,与用户、分析师、团队成员的讨论记录;
  3. 确认:确定这个功能的验收标准,这个标准是让用户确定所需功能已经完成。
    每个用户故事都是独立、不依赖的。

第三点很重要,我们不但要明白用户的需求,“该做些什么”,还要再次确认一下“功能完成后,目标满足了吗?”

比如说这次的新需求,对进行项目额度预警、设计变更、请购到货明细查看这些功能实现之后,“项目资本控制”这一总体目标是否已达成?

不要惧怕用户会因为询问而(期望大增)将需求成倍的增加,我们可以从当前可行性的角度出发去拒绝一些需求点,或者将其延后到下一期开发计划中。做的事多了,整个工程都会延期,这是双方都明白并且不愿意看到的事情。重要的是保证所做的功能都是必须的、围绕着用户目标、不可替代的。

有意思的是我的工作被分割成两个方面:一方面是实际意义的、为团队开发提供支持的需求分析文档,另一方面是形式主义的、为客户讲解的幻灯片。

组里的一个同事Rusty每天都会有让大家神经崩溃的“事件”发生,以至于今天我把MSN签名换成了“Rusty是个人才,演郭靖的人才”。原因如下:

1.Rusty今天中午吃了一份Ben点的“青瓜皮蛋”,然后说:这不是黄瓜吗?它还有一个名字叫青瓜?

2.Rusty说:毛肚不是海产吗?

3.Rusty说:银耳不是黑色的吗?为什么这个银耳是白色的呢?

Ben:你到底是富家子弟还是官宦子弟?!

我:晋惠帝?

Ben:平常多读点书好不?

Rusty:我不喜欢读,听你们说挺好的。

Linda:都是常识啊常识!

Jerry:(无语向苍天)

Rusty:遇到事情不说,显得很高深,我明白了!

我现在是彻彻底底地折服于“个体的差异”了。今天上午我就花了一个小时给这位“郭靖”讲解了一下旧系统和新需求……

郭靖:我就是想听这个。

Mappings in Elasticsearch

虽然都是基于Lucene,Elasticsearch默认运行使用时,是不需要像Solr一样配置索引字段文件的,从开发者角度来看数据结构很灵活,只需要向数据库提交任意格式的JSON就行了,实际上,Elasticsearh对Lucene的索引文档映射进行了封装,每次提交时都是在原有数据结构上检查并增加,而且,并不是所有的数据结构改变都能对后续数据生效。这样一来,需要对数据结构进行更多灵活控制时,Elaticsearch远远比Solr复杂。这篇文章将给大家介绍在Elasticsearch中Mappings的主要类型和工作原理,以及如何使用Shell和API进行操作。

Elasticsearch的Mappings分为两种类型,索引文件(index)的根级别Mapping、文档类型(type)级别的Mapping,类型Mapping下又分为预置默认的Fields和自定义源(source)的Fields.

Index Mapping

根级别的Mapping会被此索引文件下所有的文档继承,在操作中,使用 “default“ 来识别。在创建Index时,可以同时创建默认的Mapping,注意的是,这个级别的Mapping只能在创建索引时指定才会生效。

curl -XPOST localhost:9200/myindex -d '{
    "mappings" : {
        "_default_" : {
            "_source" : { "enabled" : false }
        }
    }
}'
使用JavaScript API创建默认Mapping
client.indices.create({
    index: "myindex"
}).then(function(body){
    var mapping = {
        "_default_": {
            "_source": { "enabled": "false" }
        }
    };
    client.indices.putMapping({
        index: "myindex",
        body: mapping
    }).then(function(body){
        console.log("created");
    });
});
查询定义的Mapping
http://localhost:9200/_mapping?pretty
可以看到输出
"myindex" : {
  "mappings" : {
    "_default_" : {
      "_source" : { "enabled" : false }
    }
  }
}

Type Mapping : Default Fields

默认的映射字段的命名都是以下划线开始,具体可以参考Elasticsearch Fields文档。需要特别提到的是_timestamp字段,它可以为每个索引文档自动创建时间戳,但默认这个字段并未启用,在MongoDB中,ID其实就是TimeStamp,看来Elasticsearch不是很重视时间序列。

如果需要启用_timestamp,必须在创建索引时指定Mapping,否则,即使以后更改,新的数据也是无法加上时间戳的。下面为索引中所有文档类型启用时间戳:

curl -XPOST localhost:9200/myindex -d '{
    "mappings" : {
        "_default_" : {
            "_timestamp" : { "enabled" : true, "store" : true }
        }
    }
}'
其中的 "store" : true,如果对Lucene或Solr比较熟悉应该不陌生,启用store后索引文件中会保存原始值,如果只作查询范围不需要显示的话,可以让它保持默认值false. 如果只需要为某个文档类型启用_timestamp,将上面的 "_default_" 改为文档类型名称即可,但依然需要在创建第一个文档之前指定。
client.indices.create({
    index: "myindex"
}).then(function(body){
    var mapping = {
        "mydoc": {
            "_timestamp": { "enabled": "true", "store": "true" }
        }
    };
    client.indices.putMapping({
        index: "myindex",
        type: "mydoc",
        body: mapping
    }).then(function(body){
        console.log("created");
    });
});
查询指定文档的Mapping
http://localhost:9200/myindex/mydoc/_mapping?pretty
"myindex" : {
  "mappings" : {
    "mydoc" : {
      "_timestamp" : {
          "enabled" : true,
          "store" : true
        },
      "properties" : { }
    }
  }
}
对于默认的字段,也需要在索引时指定其值,否则无法创建索引文档。
client.index({
    index: 'myindex',
    type: 'mydoc',
    timestamp: new Date(),
    body: req.body
}).then(function (body) {
    res.send(200, "ok");
}, function (error) { });
查询_timestamp时需要在fileds中指定
http://localhost:9200/myindex/mydoc/1?pretty=1&fields=_source,_timestamp
默认类型是long
{
  "_index" : "myindex",
  "_type" : "mydoc",
  "_id" : 1,
  "_version" : 1,
  "found" : true,
  "_source":{ },
  "fields" : {
    "_timestamp" : 1412329642820
  }
}

Type Mapping : Custom Fields

自定义字段其实是全部放置在默认字段_source中的,在Mappings对应的是Properties节点。这类字段可以在任何时间进行更新,Elasticsearch可以处理类型冲突和合并。

$ curl -XPUT ‘http://localhost:9200/myindex/mydoc/_mapping' -d ‘
{
“mydoc” : {
“properties” : {
“message” : {“type” : “string”, “store” : true }
}
}
}

从这里我们可以看到,在数据结构上,即使是最灵活的Elasticsearch,也是需要预先对索引和文档类型级别的字段进行设计和定义,合并字段时是否需要重新索引,我并没有测试过,这点似乎还不及MongoDB的文档结构灵活。

easyXDM实现跨域请求

之前在使用JavaScript创建REST风格的应用程序一文中提到了前端JavaScript跨域请求REST API的解决方案,不过,那是在服务器端配置许可列表。

如果需要在客户端两个不同域名的窗口之间传送数据,采取的解决方案有两类,一是浏览器事件机制,包括window.postMessage接口,但IE8是不支持window.postMessage,只能借助Flash等浏览器插件事件。另外一类是通过向当前DOM注入一个iframe,刷新iframe的url调用回调函数取得跨域的值。很多广告平台的代码都是通过后者来实现用户cookie的共享。

easyXDM是一个实现了不同跨域字符串请求的开源框架,解决了各种浏览器的支持问题,它的简化版XDM正被Twitter使用,非常值得推荐。其核心对象是Socket和Rpc,区别在于是否为代理模式。

easyXDM.Socket

创建两个不同域名document的通讯通道。

easyXDM.Rpc

创建一个代理对象,在不同域名间传输数据。

内部的实现主要是根据浏览器的支持来判断和调用。

  • easyXDM.stack.SameOriginTransport 同域下的请求,一般可用来测试内部资源和外部资源的访问权限。
  • easyXDM.stack.FlashTransport Flash方式传播事件,原理是在浏览器中生成一个swf文件。
  • easyXDM.stack.PostMessageTransport 使用window.postMessage方式进行通讯。
  • easyXDM.stack.FrameElementTransport 通过向DOM插入frame的模式来触发事件。
  • easyXDM.stack.NameTransport 使用window.name来读取数据。
  • easyXDM.stack.HashTransport 最常见的方式,使用向DOM插入的iframe来进行通讯。
    easyXDM的主函数有onReady、onMessage和onDestory,分别对应初始化、发送消息和销毁几种场景。下面我们来看如何使用。

对easyXDM,我们需要为通讯的两端建立一个信息提供者(provider.html)和接收者(receiver.html),对提供者来说,不需要知道接收者的细节,只需要对事件作出响应,而接收者需要知道谁提供给它信息,并且广播出自己的状态。

provider.html

创建一个html文档,引入easyXDM.debug.js,在<body />中加入:

var socket = new easyXDM.Socket({
    onMessage: function(message, origin) {
        if (message == "open") {
            alert("Received '" + message + "' form '" + origin + "'");
        }
        socket.postMessage("Today is " + new Date().toGMTString());
    }
});
receiver.html 再创建一个html文档,同样需要引入easyXDM.dubug.js,在<body />中加入:
var socket = new easyXDM.Socket({
    remote: "http://www.domain1.com/easyXDM/provider.html",
    onMessage: function(message, origin) {
        alert("Received '" + message + "' from '" + origin + "'");
    },
    onReady: function() {
        socket.postMessage("open");
    }
});
测试时,可以修改本地hosts,就可以看到在不同域名之间的消息传递了。
127.0.0.1   www.domain1.com
127.0.0.1   www.domain2.com

跨域Cookie

如果不需要实时播放消息而是设置cookie,可以在provider的onMessage事件中写入cookie,由receiver事件来触发,经过测试是可行的。

var socket = new easyXDM.Socket({
    onMessage: function(message, origin) {
        if (message == "open") {
            document.cookie = "open=true";
            alert("Received '" + message + "' form '" + origin + "'");
        }
        socket.postMessage("Today is " + new Date().toGMTString());
    }
});

Node.js部署和安全

今天准备开发一个高性能的Web API,考虑之后认为Node.js是最好的技术选择,查阅了一些资料备用。

部署

Node.js本身是一个单线程无阻塞的服务器,但Cluster模块可以给应用程序增加多核运行的特性,在一些Linux操作系统中可以实现负载均衡。Node.js的部署没有特定的标准,比较流行的部署方式有forever和systemd,下面一些文章可作参考。

Deploying Node.js with systemd NodeJS Production Practices: Deploy How To Deploy Node.js Applications Using Systemd and Nginx

安全

和SQL注入一样,Node.js服务端也可能被客户端注入的JavaScript攻击。在进行任何处理之前,千万对用户输入和JSON对象进行检查。这份文档写得很详细:

Server-Side JavaScript Injection

另外Node.js经常和NoSQL搭配使用,NoSQL的安全性并未发展为SQL Database一样成熟,比如用户分级授权加密、数据加密等,这点可能要在Node.js应用程序中进行考虑。

在悉尼工作和生活(三)

来这座城市之前,考虑了不少工作上的事,但没想到最大的挑战是吃饭问题。在这个项目组,只有我一个人是中国人,第一天上班还好,附近有一家快餐店,只有几种最简单的rice, nuddle, beef, chicken的组合,上去指几个自己想要的就打好饭了,费用$8,算是很便宜了。

第二天Team Leader召集大家一起吃饭,大家纷纷就座时我才明白,虽然是Team Lunch,其实还是各点各的,这个习惯和中国太不一样了……每个座位上都有一份菜单,我一看,尼玛,一个字都不认识,当时那个囧哇,哈哈哈,幸好客户们都很善解人意,还说如果他们到中国也一样不认识中文菜单,啊哈。在大家的帮助下,我基本明白了这个菜单是按照不同国家的菜式来分类的,在悉尼貌似泰国菜很流行,菜单上有一栏全是泰国菜(Thai Food)。每一道菜名下面,会列出这道菜由哪些原料组成,比如说扁豆、鸡肉什么的;这顿饭学了一个单词:Prawn,对虾,囧。

接下来几天中午都和客户一块吃饭,努力学习各种菜名,否则啥也吃不到是很痛苦的……下面列举一些,也许对你有也用,哈哈:

咖啡
  • Black Coffee:不加糖和年奶的苦咖啡,除非你是咖啡精千万不要错点到这个了;
  • White Coffee:默认貌似是拿铁,但有些店没有这个说法;
  • Flat White:在悉尼最普及的,有大量的牛奶泡泡;
  • Cappuccino:撒了一层巧克力粉的牛奶咖啡,有时候你说White Coffee店员会问你Flat White or Cappuccino?
啤酒
  • Low Carb Beer:淡黄色口感也很淡的啤酒;
  • Light Beer:也是口感很清淡的一种。
    西式正餐

  • Fish & Chips:澳洲版牛肉饼加狼牙土豆?哈哈哈,其实别个这个Fish和Chips要厚道多了,一顿岂止是管饱,是胀翻的节奏啊〜

  • Salad:各种生菜叶黄瓜片蕃茄片豆子的组合,注意都是生的,而且有些菜的味道你绝对想不到……体会做牛做马的咀嚼感觉。PS:我们客户天天中午都是一大碗各种生菜叶,瀑布汗……
  • Pizza:肉菜饼,超市有各种卖,回来在烤箱里面烤烤就能吃,简单,哈〜
  • Pork Belly:德国菜式,很嫩的带皮烤五花肉,还有土豆块和洋葱丝混烤;
  • Beef Cheek:牛扒,又各种嫩,配菜就看菜单啦〜
  • Burger:汉堡包,和咖啡店一样在街边到处都是,你可以选择Egg, Bacon, Salad等一起组合起来,店员会问你要不要胡椒面儿,那个单词我一直没听明白〜
  • Cold Soup:怀疑Soup在西餐里就是糊糊一样的东西,总之你要点Soup,人家会问你Hot soup or cold soup,然后要么是热糊糊,要么是冷糊糊,而且奇难吃无比!
    类中式正餐,比如各种Rice什么的就是米饭配菜,这里的米很硬很难吃,放弃吧;面条什么的都称为Noodle Soup,是指有汤的面不是面汤……比如说牛肉面就是Beef Noodle Soup……当然,这里的面条又是很软很难吃。最好吃的就是Fish & Chips!现在简直有种被KFC坑了几十年的赶脚……

最后,除非是在正式的餐厅,店员都在结帐前问你Take away?意思是问是否打包。很多餐厅都有室内和室外的座位,店员也会问你Inside or outside?我也不明白对他而言有什么区别……

悉尼的建筑与艺术

此前只听说悉尼是一个金融都市,比较杂乱,文化氛围不如墨尔本。刚来第一天去参观了悉尼博物馆(Museum of Sydney),设计新颖,但比较小,藏品不多,大部分只是通过照片和文字来描述:毕竟在中国习惯了几千年历史,从石器时代到青铜时代,琳琅满目的出土文物充分展示着我们浩如烟海的过去。在历史上,悉尼什么也没有,只有海上的船支和破碎的瓷片来说明曾经这个欧洲来客的故事。第一批殖民者面临的是与大洋洲各类野生动物共存的环境。在悉尼博物馆中,看到的是简陋的木屋,草甸中婉延的泥土道路,鸵鸟在其中悠闲地散步。

但让这个城市引以为豪的是许多知名建筑师的到来;悉尼有举世无双的海边歌剧院(Opera House),这些屋顶的设计不只是从贝壳得到灵感,所有的贝壳片儿拼接起来,是严格遵循一个球面的解构。海港大桥(Harbor Bridge)不仅仅以世界最大的单拱桥梁而闻名,深黑的全钢架主体结构和灰黄色的四座桥礅更构成了坚如磐石的视觉冲击。

此外还有总督府(Government House),它是由白金汉宫的建筑设计师Edward Blore亲自操刀。还有哥特风格的圣玛丽大教堂(St Mary’s Cathedral)、混合多种建筑风格元素的维多利亚女王大厦(Queen Vitoria Building)。

圣玛丽大教堂

教堂的侧面,夜晚飞驰的车辆拉出一道道炫光

维多利亚女王大厦

悉尼医院(Sydney Hospital)是一座有些意大利风格的建筑,绿色的圆型拱顶和尖顶,和复杂的楼层间连接的走廊。

在游戏古墓丽影威尼斯城中,劳拉经常在这样的阳台上跳跃。

海德公园纪念馆(Hyde Park)是为第一次世界大战中,作为大英帝国成员参战的澳新军团阵亡将士而设。这是一座米黄色的建筑物,除了四方环绕的罗马风格的雕像,它看起来非常现代。纪念馆的前方是一个宽阔的水池,晚上可以将整个建筑物完全倒映在水中,你不得不惊叹设计师的灵动。

纪念馆内也采用了拱顶设计,正中圆型开口之下,是一个倒着被钉在十字架上的人的雕像,以基督受难的宗教意义作为象征,揭示着在战争中凡人的痛苦、奉献和牺牲。

下面金色的图案,似乎是溅出的鲜血,又像是燃烧的火焰,还有圣光的照耀。

死亡的时刻,只有面对苍穹诉说这一切……

更多图片请见Flickr像册。

在悉尼工作和生活(二)

繁忙的一周过去了,渐渐地从旅途疲劳状态调整到了正常。刚开始时我通过Google Maps和在机场拿的Sydney Guide熟悉周边和计划观光路线,然后是客户带着我在附近深度活动,介绍一些有趣的地方。

每天早上从Hyde Park出发,沿着Macquarie St跑步到Opera House,然后看着轮渡在海上缓缓地行驶,鸟儿们(它们的名字叫Sea Gull)自由地飞翔和降落。


海边是制造风景的地方,不管你是一个人:

还是一对儿:

日出时分,那一抹金色:

中午吃饭后,可以逛逛The Rocks,然后再去海边放松心情。这两张是手机拍摄,所以画质差了很多。

夜晚的QVB(Queen Vitoria Building)。它是悉尼非常著名的建筑,有着文艺复兴时代的绿色的圆型拱顶,罗马式的长柱,和哥特式的狭长窗户。

现在QVB是一个大型商场,后面的Market St全是各种名店,每幢楼的地下或二楼平台都有连通,你可以从这边WestField进入,然后从QVB出来,把Shopping的心情释放到爆。

© 2018 Silent River All Rights Reserved. 本站访客数人次 本站总访问量
Theme by hiero