Microservices陷阱: 切割单一系统

前面已经提到,构建microservices的第一步即划分服务边界,而对新系统直接进行边界划分具有相当的风险,且通常是不被鼓励的。我们建议选择一个较成熟的codebase再进行服务切割。

1.辨识服务边界

面对已有系统,首先需要做的就是辨识其中隐藏的服务边界。在《Working Effectively with Legacy Code》中,提到架构“缝隙”的概念。我们几乎可以把“缝隙”和“边界”等同起来,实际上DDD中的服务边界即一种好的“缝隙”。那么如何找到“缝隙”则是面对遗留代码所需的第一站。 许多编程语言提供一种自然的封装方式,比如namespace,java的package也拥有类似的概念,当然并非所有语言如此(如javascript)。 在进入代码之前,首先应该至少理解高级别的边界上下文抽象O,这将有利于我们进行逐渐向下的切割。当拥有O后(当然O可能并不完美,但这不是必须的),我们可以逐步把代码移动至相应的O’中。在这一过程中通常就能发现其中存在的问题,比如更细粒度级别的边界、错误的依赖、以及其它,借助代码分析工具可能会提高一部分工作效率。 当然,面对小型系统,你可以指望在数天、甚至一天内完成全部分析工作,但是多数情况下这可能是数周、或数月的付出。因此还应注意任务分解、确定优先级、循序渐进地切割新服务。

切割单一系统的好处

尽管前文已经无数次提到,但这里还是简单做一下总结:相互独立的自治单元更易实现变更、根据所属服务划分团队职责、针对特定服务的安全性增强、技术更新更加频繁…所有以上会是单一系统经过合理切割之后带来的好处,然而如果你不确定这些是否真的对你有“帮助”,还是谨慎一些比较好。

2.解决依赖缠绕

当切割服务边界时,如何处理新旧系统之间的依赖缠绕则成为随之而来的问题。前文提到过建模工具,它能更容易地让你发现设计是否基于有向无环图从而避免了明显的缠绕关系。当然,实践表明几乎所有的缠绕都和数据库模式相关。事实上到目前为止,切割服务就像是纸上谈兵,数据库才是大boss。 基于之前的实践,首先尝试依照服务把repository层进行垂直切割,也就是分表。SchemaSpy等类似的工具能够根据代码生成类UML的可视化分析结果,从而提高理解程度。

解除外键关系

如果确实存在外键依赖,唯一能做的就是自己维护一个与外键类似的字段,然后按需要手动解决一致性问题(这通常与业务有关,也就是说,有时需求根本不要求强一致性)。

静态共享数据

例如国家代码、区划等数据,一般来说有三种解决途径:在多个服务间实现冗余拷贝;通过静态配置文件消除代码和数据;或者直接建立独立服务,并在代码中内嵌静态数据。多数情况下,静态文件会是一种最简便的解决方法。

动态共享数据

此类数据比外键关系更为复杂,实际上出现的几率也更高。对于此类情况,通常是由于数据库中隐含了某种领域概念,而实际上这种领域概念理应由代码直接表示出来。当抽象出该概念模型后,就可以把共享数据转变成独立服务了。

动态共享表

为了降低数据冗余,有时会把分属于不同领域概念的数据放在一张表中,这就是动态共享表。为了进一步划分服务边界,应允许适当的冗余,也即垂直分表,但这里的分表并非是抽象出独立服务,而是分配到不同服务中。

3.重构数据库

正如前文所述,切割服务确实需要重构数据库,而这一领域又少有人涉及,如《Refactoring Databases: Evolutionary Database Design》。而在预上线环境,应尽可能先部署包含两套schema的但仍然保持单一的版本,然后逐渐分离服务代码,直至成为两个不同的codebase。之所以要循序渐进,是因为隔离schema带来的第一个问题就是影响了后台数据读取操作,由原先的若干查询语句,成为API请求-查询数据库-响应模式,这就直接破坏了系统原有的事务集成特性。因此,应当在处理好这一部分之前,尽量不要独立上线新服务.

事务边界

在单一schema系统中,事务的好处是能轻而易举实现操作的一致性。而对分布式系统而言,事务边界被一分为众,数据一致性将面临挑战。

稍后重试

一致性的首要目标是处理中间出错的情况,在许多场景中,“强一致”是非必要的,而通常我们仅要求“最终一致性”。在这种情况下,可以设置一个队列或日志文件,存储数据库操作及其状态,并对出错操作进行特殊标记,稍后重试该操作,直到数据成功写入。该方法的前提是假设重试操作必然有效。

全局操作中断

数据回滚是另一种解决方案,但需要另维护一份回滚代码,从而保证错误数据被及时清除。问题在于保证回滚代码能够正确执行,可能需要采用前一种不断重试的方法。然而随着回滚数据的增多,该方法的有效性就会降低,成本则会不断增加。

分布式事务

分布式事务通过设置一个中央协调进程,监控所有执行节点的状态。一旦接收到事务请求,中央进程会向所有执行节点发出投票通知,执行节点在接到通知后会检查自己的数据提交状态,成功则返回赞成票,否则反对。只有当中央进程接收到所有节点的赞成票时,才会再向所有节点发出执行通知,每个节点分别执行数据提交,这就是朴素的分布式事务“两段提交”算法。 然而分布式事务算法始终不是“绝对正确”的,比如执行节点在投“赞成”之后出错。此外,中央协调进程的存在会使得所有资源被加锁,进而存在资源竞争行为,降低系统可用性。 目前已经有一些针对分布式事务的实现,如Java的事务API。但是,分布式事务带来的副作用是必须要考虑的问题。

小结

保持一致性会带来更多的复杂因素,例如分布式事务会降低可用性和伸缩性。当一致性需求发生时,首先应思考它的必要性,或者采用局部事务、最终一致性加以替代。如果确实是强一致,应尽量避免切割。如果一定要切割,也可以选择设计一种混合抽象来代表事务本身,从而降低分布式事务的复杂度,进而保证可用性(例如在销售系统中,分离出一种“处理中”的订单服务,从而降低销售和库存服务之间的耦合)。

4.审计报表

审计报表系统是企业应用里常见的需求,而随着微服务架构的演进,该类型的系统将面临重构。在单一系统中,报表很可能只是意味着若干SQL语句的集合,顶多在架构层面增设一个读库专供报表使用,读库和主库之间采用定时同步策略。 对微服务而言,上述方法存在一定缺陷:首先,数据库schema将以服务与报表系统之间共享的形式存在,这就导致任何变更需要格外小心;其次,许多数据库提供优化能力以提高读写性能,然而当处于微服务环境时,由于数据结构的不确定性,很难在报表数据库中实现任何优化;此外,随着异构数据库架构越来越普遍,SQL、NOSQL的混合使用将使得数据融合成为新挑战。

采用服务调用获取数据

假设一个商业系统报表,常见需求是列出最近15分钟产生的订单。在微服务中,这可能需要跨服务的数据调用。然而,当数据体量过大时,这种方法就会变的效率低下,例如条件变为过去24个月的订单信息查询,如果采用备份数据的形式存放在报表系统中,可能会导致非一致的结果。 一种有效方案是定时从主库中提取数据到报表库(通常是一个关系型数据库),仍然有几个问题需要解决:首先,不同微服务暴露的接口可能并非报表友好的,如果硬要基于现有API,可能会导致一些其它问题,例如数据的额外处理、cache失效等等。因此在针对可能存在的大数据传输问题,一个好的办法是目标服务并不通过HTTP直接返回数据内容,而是以文件的形式转存至第三方位置,这样主服务就可以通过轮询文件生成状态从而实现HTTP的低负载通信。

数据泵

除了拉取数据这种方式,也可以尝试采用数据推送的办法。传统HTTP的缺点是链接耗费较高,一方面是底层协议原因,另一方面是报表系统请求的API次数较高(有时该API甚至只为报表提供服务)。一种高效方案是设立一个第三方的数据泵,其同时拥有主数据库和报表数据库的访问权限,定时把主库的更新数据同步至报表库中。该方法唯一要解决的问题就是schema的管理,而事实上,报表库的schema可以看作是published api,泵程序最好和目标服务共同由一支团队开发,这就尽可能保证了schema的同步。

事件数据泵

数据泵是一种有效的数据同步方案,但同步时机缺乏验证。对微服务而言,当某个服务产生状态迁移时,可通过发出一个特殊事件通知泵程序,使后者能够订阅、接收并处理相关事件,从而实现事件驱动的数据同步。此外,为了减小通信压力,报表服务可以只提取差异数据,而非全部,这就尽可能避免了广播报表更新事件与相关数据所带来的副作用。然而,这是一种有效的近实时同步报表解决方案。

备份数据泵

该方法被用于Netflix的报表系统中,基于现有的备份方案。Netflix采用Cassandra作为其主数据库,并在此基础上构建了许多备份服务应用(详见github)。为了备份Cassandra,常规做法是生成一份数据文件(SSTables)拷贝并存放至第三方,例如Amazon S3。报表服务是基于Hadoop实现的大数据处理框架Aegisthus,其作用与本文中数据泵的概念类似。

5.小结

面向实时

由于业务需求的不同,许多服务,比如dashboard、alerting、financial reports甚至user analytics都具有相互不同的时效性要求,这就导致各个服务可建立在不同的技术选项上。微服务为此提供了良好的前提条件。

变更代价

微服务是面向需求变更友好的,但走向微服务的过程可能是极不友好的…例如切割服务、重构数据库等工作,都存在一定的风险。我们唯一能做的就是尽量使风险最小、可控。采用白板分析设计是一个常用的办法。此外,class-responsibility-collaboration卡是个“好”工具。

理解根源

问题的关键在于理解为何我们需要微服务架构。在实践中,我们经常会遇到某个服务迅速变的臃肿,尽管我们可能知道这么做所带来的不良后果。改善的第一步是找准下手位置,这正是本文的目的。当你熟练于此,随后就会自然陷入到微服务庞杂且无序的内部细节中。但是相信我,第一步才是最难的。

Sessioncam: Introduction&Principle

Recently we used an online user session recording service in one of our project, which caused my interests and push me to do some spike on such tool —— sessioncam. In this article, I will give an introduction as simple as I can, then try to dig deep into its design and implementation.

1.What is sessioncam in one sentence?

SessionCam is a leading “software as a service” session replay product used by clients to record and replay website sessions. you can visit http://www.sessioncam.com/ for more detail.

2.Basic features list about sessioncam.

i.Session replay

Almost any behaviour of customers will be recorded and can be replayed by developers, including mouse clicks, movements, scrolling, form inputs, or any other actions on page, similar to specific actions on mobile devices. Developers can replay the recordings like playing videos by their sessioncam account with various speed.

ii.Heatmaps

User can generate website heatmaps for mouse movement(M), mouse clicks©, page scrolling(S) and browser attention(A).

iii.Funnels, Form Analytics, Field Drop-Off

A kind of analytics tool to filter out useless data, improve conversion through assessing drop-off of users behaviour, such as page accessing or form inputing.

iv.API and integration

User can integrate sessioncam with their own analytics tool, such as Google Analytics, Campaign Monitor, CheetahMail and Olark.

3.How to start?

You must register an account on sessioncam http://www.sessioncam.com/plans/ , there are also free plan http://www.sessioncam.com/free-account-request/ , with limited page count per month.

About which plan to choose, from our experience you can hardly use non-enterprise edition if all site visitors are planned to be recorded. Therefore, it depends on the scale of your site. If you only have hundreds users and each users will take hundreds page views on your site per month, that means medium edition is better for your situation.

There are many ways to deploy sessioncam to your website, most of them are very easy work with only a few javascript needed on your page. Refer to https://help.sessioncam.com/hc/en-gb/articles/200863126-Adding-SessionCam-to-your-website .

Login to your sessioncam account console and turn on recording, https://console.sessioncam.com/Dashboard/Manage/HostnameManagement.aspx?accountAdmin sessioncam will start working.

4. How does it work?

Step 1. IO initialisation

Reading and executing script from //d2oh4tlt9mrke9.cloudfront.net/Record/js/sessioncam.recorder.js, through which it will request for https://ws.sessioncam.com/Record/config.aspx to initialise a sessionCamRecorder object, with tracking configurations from sessioncam server.

Step 2. Run

(Please refer to sessioncam.recorder.js when you reading this part)

Sessioncam will capture and clone the whole document inner html at line 5920(function SessionCamRecorder.prototype.ml), during this a pre-processing job should be done at line 6568. Below shows basic workflow:

  1. Replace script, object, image, or any useless target element with short strings. The processed document content will be kept in SessionCamRecorder.hK.

  2. Check whether sessionCamDebug is opened .

  3. Initialise data structure which stores form data at line 5847.

  4. Config for mobile device.

  5. Check XHttpRequest configuration, if disabled by browser it will try swfhttprequest.

  6. Bind any document interactive events in sessionCamRecorder.cU, line 7631.

  7. Once any event triggered, sessioncam will collect event data and push them into IO pool, in SessionCamRecorder.prototype.gG, line 7279.

  8. Looping scan all doms of page per 250ms, record any dom and its xpath value who is different from 250ms before into IO pool. line 6855-6878.

  9. Looping check IO pool and send existed data flow by time to sessioncam server, in SessionCamRecorder.prototype.jM, line 8106.

  10. Send data to sessioncam server per second, in SessionCamRecorder.prototype.lh, line 8584.

Step 3. Replay

According to step 2, sessioncam almost collects all the user’s browser data at milliseconds level, it’s easy to replay the session by time flow. Actually, for session replay there is an iframe element which always figuring to remote getpage.aspx, with parameters like PageId, SessionId, starttime, EventId, HostnameId, etc. Javascript can play this iframe just as animation easily.

5.Miscellaneous

Sessioncam identify data only by website domain, which means you must register sessioncam account by a domain, and once such domain has been taken, new account can not set same domain unless the first user permits it.

Session data may contain some user privacy data, but sessioncam will ignore most common item like password, credentials. But admin can still config some privacy fields to be ignored.

Comments

Microservices陷阱: 实践篇(服务建模和集成)

总的来说,微服务并不是指某一个具体的技术。推崇者认为它得益于Eric Evans的领域驱动设计、持续交付、Alistair Cockburn的六边形架构、基础设施可视平台和小型全功能团队等众多近年内不断涌现的高效实践。ThoughtWorks的Sam Newman认为,微服务包含两方面含义:小且只专注做一件事,自治性。

1.服务建模

在概念篇中我们已经提到,服务其实就是系统中的组件。容易理解的是,好的服务设计也就是常说的“高内聚、松耦合”。意味着多个服务之间的低依赖性、以及服务内部的行为紧相关性。这种特征使得代码变更带来的影响面尽可能小,同时实现快速简单部署。那么服务建模的目的就在于寻找问题域中的边界,划分出相关行为,同时保证边界间通信的低依赖性。 微服务推荐DDD的设计思想,鼓励在构建系统时参考真实世界中的领域,而不是套用分层架构。其中最重要的概念就是边界上下文——即任何领域都可以被认为是由许多边界上下文构成,其内部容纳着具体的物(模型),其中有的物无需和外界进行通信,有的物则与其它边界上下文共享。每个边界上下文都包含一个显式接口,决定哪些物共享出去的。

边界上下文的另一个简单解释就是“一种被显式边界强化的特定职责”。当你想和边界上下文内部进行通信,或是请求其内部的某些功能时,你需要携带模型和它的显式边界进行通信。Evan把边界上下文比喻为细胞,而决定物质进出的细胞膜就是显式边界。微服务把边界上下文和服务紧密联系在一起,尽管边界上下文可以是任何一种组件或模块,其可以存在于单一系统中,也可以存在于独立进程中。而后者也就是微服务的例子。

然而过早确定边界是不明智的,特别是在初期,一旦边界不稳定,就会造成更多的修改和成本增加。比较好的方法是在一个成熟codebase上划分边界,而非一开始就这么做。

在划分边界上下文时,需要注意该上下文对领域内其余部分提供的业务能力,而这种业务能力通过共享模型实现信息交换。而如果对单纯的CRUD操作划分边界,失去了它的业务意义,则完全没有必要。

在实践中,一般是自顶向下划分出边界上下文,粒度也应当是由粗到细。然而在演进的过程中,是否独立出内嵌的上下文,则取决于设计和团队等因素,如果某个团队同时负责若干个上下文,则可能会把它们组合成一个较大的上下文而非全部分离出来。这种内嵌上下文的另一个好处在于,较粗的架构能简化测试,特别是针对庞大复杂的end-to-end测试。

2.服务集成

服务集成是微服务架构中最重要、技术最相关的部分。一般存在两种风格:编配和编排,编配是指通过一个中心服务以同步通信的方式管理其它服务,编排则通过事件队列实现异步通信,相关服务自己监听相应事件。前者的优点是结构明确、易于开发,缺点是变更成本高、难以维护;后者则实现了松耦合,缺点是难以调试,需要额外的监控代价。 无论采用何种风格,都避免不了集成方法的选择。一般可用的集成方式有SOAP、XML-RPC、REST、Protocol Buffer等,但这些方法有一定的针对性。值得注意的是,编配风格的架构更易于构建,特别是带语义的请求/响应类型的通信,同步方式是最简洁的实现。相比之下,编排风格的架构就需要额外的callback机制处理响应数据。

请求/响应类

目前请求/响应类通信存在两种流行的方式:RPC和REST,其特点各不相同。

RPC包括多种技术,如SOAP、Java RMI、Thrift、Protocol Buffer等。其中SOAP采用XML格式的协议构建消息,其余则为纯二进制形式。 一些RPC技术仅限于某种特定领域,如Java RMI,必须运行在JVM中。尽管有些不限定语言或平台,但依然会带来或多或少的集成限制。 RPC的另一个缺点是过于“底层”,除SOAP外,几乎都直接基于TCP/UDP实现,然而由于网络环境的限制,应用不得不考虑可靠性、容错性、语义性。 在设计上,RPC方式如Java RMI过度依赖于服务接口和Model实现,任意变更都很可能影响服务端、客户端以及相关测试代码,并继而影响后续的测试和部署,这就是lock-step release。当然Thrift和Protocol Buffer为此改进了许多。

与RPC从底层构建不同的是,现代REST提供了更为抽象的架构方式。一般来说,REST over HTTP是最易于实现的一种REST,但不是唯一途径,本文只就REST over HTTP展开讨论。

REST over HTTP的好处在于,二者拥有许多能够相互对应的概念,如动词GET、POST和PUT,其含义是十分明确的。此外,HTTP拥有极为完整的生态系统,从缓存代理Varnish、负载均衡modproxy、以及监控系统,这就使得构建系统变得十分高效。另外,HTTP还支持较完整的安全控制机制,从基本验证到客户端证书等等。 值得一提的是,SOAP同样基于HTTP,然而却抛弃了大量HTTP中极富内涵的概念,使得学习成本、系统复杂度都大大增加。 REST over HTTP也存在缺点,一、生成客户端stub的成本较高,超媒体控制客户端很可能以共享库的方式在微服务中广泛使用,从而导致变更困难;二、HTTP动词并没有得到服务器端的良好支持,但是通常有绕过的办法解决该问题;三、HTTP连接是基于TCP的,因此即使是JSON或二进制数据传输,性能也比不上Thrift这种纯二进制协议,对于低延迟的应用就更是如此。

异步事件类

事件处理需要考虑两方面内容:服务端发送和客户端接收。 传统的消息队列如RabbitMQ,试图一次解决上述问题。发送者通过API向队列中发送事件,队列处理针对这些事件的订阅,向接收者发出通知,甚至还能处理接收者状态,例如帮助跟踪历史消息。然而类似的中间件系统目前变的越来越臃肿,厂商向其注入了更多智能化组件,这不得不让人联想到ESB的情形。因此,时刻保持哑管道和智能终端,是引入中间件技术时必须要考虑的问题。 除此之外还有一个特殊选择:ATOM。ATOM本身就是基于REST的服务发布协议,由于开发库较完备,事实上也可被当作事件发布/订阅工具。同样,基于HTTP的架构具有良好的扩展性,却不适用低延迟应用。

尽管事件驱动架构具有诸如松耦合、可扩展等优点,但其带来的系统复杂性也随之大幅提高。或者至少应当着重注意监控过程,特别是跟踪跨服务边界的请求。Enterprise Integration Patterns是目前服务集成领域的权威著作。

3. 服务集成需要注意的问题

服务即状态机

无论是否采用REST,服务即状态机都是一种强大的思维模式。其核心在于,构建具有尽可能丰富功能的服务,而非将部分功能划分至服务外,例如管道甚至消费者一边,这会丧失高内聚的特性。举个例子,当消费者向提供者发送更新请求时,应由提供者一方决定是否接受这次修改,而非由消费者决定,从而保证数据和行为的一致性。

重新思考DRY

我们都清楚DRY的含义,并且了解DRY带来的好处。然而,在微服务架构中,服务间的代码共享可能会导致修改灾难,一般而言,应当允许一定的重复代码存在于不同服务中,而非使用统一的共享库。当然,微服务内部应遵循DRY原则。 一个例外是对客户端库的设计,客户端库能够方便保持整个系统的可靠性和可扩展性,例如引入服务发现、请求失败、日志等通用特性,这种DRY是有益的。但应注意保证客户端库不被具体的服务逻辑污染这一原则。

引用访问

当在服务请求间传递领域实体时,需要考虑值传递或引用传递两种方式。一般而言,消费者并不需要保证自己获取的资源是最新的。但某些特殊需求可能会有此限制,这时就需要在获取资源时同时取得该资源的引用方式,从而使消费者时刻能获取其最新的状态。当然,频繁的资源请求也会导致系统负载增加,这里我们需要考虑是否一定要实现实时更新,如果不是,则可以利用HTTP自身的缓存控制机制减轻负载;或者根据不同需求只传递需要的资源属性。

版本化

版本化会引入更多复杂性,因此在对服务接口进行变更时,尽量避免采用版本机制。如果一定要采用,应分配语义化版本,一般遵循MAJOR.MINOR.PATCH即可。这种令不同版本接口共存的做法,能够尽量减轻消费者的迁移压力。当然如果项目足够小,只需要保持新旧接口同时存在就好,版本化就太过了。

用户界面集成

用户界面集成几乎是任何系统都必须要考虑的问题,微服务中尤为如此。传统上,不同服务可能会有各自的UI组件,并嵌入在页面相应位置。然而该方法需要考虑用户体验的一致性,同时,由于要适应不同设备的需求,各UI组件还需要考虑响应式的问题。此外,有些服务并不能以单个UI组件的方式提供给用户,而可能存在与其它服务协作的情况。因此,用户界面最有效的构建方法就是前端负责UI,后端负责提供API,互不干涉。 整块前端的做法并不利于降低开发的复杂度。为了解决这一问题,采用API网关技术,针对不同设备的请求返回不同的内容聚集。然而API网关可能会导致该层过于复杂,有时会适得其反。一种轻量的解决方法是前端+特定后端技术,即不采用整块API网关,而是针对不同设备构建特定的后端支持系统,尽量减轻复杂度。

集成第三方应用

采用第三方应用有时会显著降低成本,最简单的就是直接集成到现有微服务系统中,通过暴露应用API从而向其它角色提供服务。更进一步,如果能够抽象出系统角色,就能借助角色层连接微服务和该第三方应用。

Microservices陷阱:概念篇

2011年5月,microservice一词由一次在威尼斯举行的软件架构工作坊中被提出,直到次年的同次会议中被正式命名为microservices(微服务)。 James Lewis, Martin Folwer

通常,微服务被用于描述一种架构风格。在这种风格下,单个应用软件被设计成一系列可独立部署的服务。尽管业界在微服务的具体定义上还有争议,但其基本上代表了自动化部署、智能终端、语言和数据的去中心化等特征。

1.基本概念

Martin是这样用一句话描述微服务的:

对于每一个小型服务,其拥有独立的进程、轻量化通信机制(通常是http请求)、这些服务围绕着业务上下文构建,并可以实现独立的自动化部署。而这种控制的去中心化允许不同服务可以采用不同的语言和数据库技术实现。

微服务的拥趸把与之相反的设计风格称为monolithic,也就是单一系统。在他们看来,凡是不符合微服务特征的架构,都属于单一系统。

企业web应用中的单一系统和微服务架构

基本的企业web应用架构包含三层,分别是用户界面层(页面)、数据层、以及服务端应用层。其中服务端应用层负责接收请求,处理领域逻辑,操作数据库,然后选择响应的视图。单一系统风格一般是指服务端应用层的设计,任何修改都需要对整个系统进行重新部署。

单一系统风格的缺点是:1.小的更新需要对整个系统进行重新构建和部署;2.单一系统内的模块化设计通常很难维持,事实上很难做到完全的修改隔离;3.扩展时需要考虑整个系统,而不是其中拥有真正需求的一小部分。

针对单一系统的上述问题,微服务提供了自己的解决方案:应用软件被设计为一系列服务,这些服务允许独立部署和扩展。每个服务需要提供一个稳固的模块边界,允许由不同语言和团队实现。

2.微服务的基本特征

利用服务实现组件化

在微服务中,组件化是目的,分割服务是方法。这里的组件是指实现替换和更新独立的软件单位。其中,在不同的场景中软件组件的定义可能不同。一种情况是采用库实现组件,这里的库是链接程序代码、并且直接在内存中调用的一种组件化形式。而对微服务而言,由于服务是进程隔离的,通常采用web service或者rpc实现相互通信的组件化。

基于服务的好处在于,1.允许独立部署,对整体变化更轻量,尽管有些改动不可避免地影响到接口层面,从而产生协同变化,但可以通过聚合服务边界和利用服务契约实现优化演进。2.更加明确的发布接口,对于语言层面来说,发布接口可能没有什么太好的办法,通常只能借助文档并且约束用户破坏组件的封装性,导致组件间的紧耦合。

当然服务也有缺点,首先rpc的效率远不及进程内调用,意味着远程api需要是粗粒度的,但使用起来并不方便。如果需要跨组件的职责变更,跨进程边界通常并不容易。再次,服务内的进程完全独立于外界,包括开发和部署,也就意味着应用服务器和数据库也应当是独立并仅为该服务使用的。

围绕业务能力构建组织

为了避免康威法则,应转变固有的以功能划分团队的方式,尽可能组建全功能团队。对于大型单一系统应用,几乎都可以围绕业务能力进行模块化,但这通常需要团队按照业务线划分,这里遇到的主要问题是,通常这些团队的组织都基于复杂的上下文。如果单一系统跨越了多个模块边界,那么对团队中的个体成员来说消化成本是非常高的。此外,模块线需要一个强有力的约定去保证实施。而服务组件能够提供更加明确的分割,从而使团队边界更清晰。

产品而非项目

绝大多数的应用开发采用项目模型的方式运作,即完成软件并交付给运维团队,然后开发团队解散。而微服务建议摈弃这种项目形式,而是让团队拥有产品的整个生命周期。这种做法的好处是令整个开发团队能够更进一步和用户接触,承担一定的用户支持任务,并理解用户的业务需求。而对于单一系统而言,该做法几乎很难实现。

智能终端和哑管道

当构建不同过程间的通信框架时,多数做法是将业务智能整合进通信机制中,例如Enterprise Service Bus,ESB。ESB产品通常包括了消息路由、编排、变换、乃至业务规则的制定等复杂功能。智能终端和哑管道则相反,由于解耦的需求,服务应当拥有其自身的逻辑,采用类似REST的通信协议,而非WS-Choreography,BPEL或其它采用集中式工具编写的复杂协议。两个好的例子分别是Http API(或protobuf)和轻量级消息总线。后者通常的特点也是“哑”的,如RabbitMQ或ZeroMQ,它们仅仅提供一套异步纤程,智能仍掌握在终端服务手中。

当你试图从单一系统转移至微服务时,最大的难点在于解决内部消息机制。而朴素的转移可能会导致不良通信行为,这里可能需要采用粗粒度方法解决细粒度通信的问题。

去中心化的控制

控制中心化的一个结果是使单一的系统平台成为标准,这么做的缺点在于,并非每个问题都是钉子,也并不是每个问题都是锤子,更恰当的方法是在具体情况下采用正确的工具。

微服务的理念是条条大路通罗马式的,也就是说强调服务的独立性。实际上,你会发现越来越多的开发人员开始从自己的实现中分离出实用工具,并分享至社区(工具之间可能拥有类似的功能,却存在不同的实现形式)。

然而这种自由并不意味着微服务并不遵守服务契约。恰恰相反,像是Tolerant Reader和Consumer-Driven Contracts这种契约模式经常被用于微服务实现,帮助服务能够独立发展。同时契约能够保证新服务开发的精简性,保证YAGNI原则,提高开发效率。

去中心化的数据管理

目前存在许多方法来解决数据管理中心化的问题。在最抽象级,就意味着划分出系统间不同的概念模型。例如,在大型企业级应用中,顾客的销售视图和支持视图之间存在差异,在销售视图中的顾客信息并不会存在于所有支持视图中,而这又会由于不同语义间的细微差别而存在不同或相同情况。

基于上述原因,如果试图把单一系统划分为多个分离的组件,采用领域驱动设计DDD会是一个不错的方法。DDD能够把复杂的领域分解成多个带边界的上下文,并建立其相互之间的映射关系。对于单一系统和微服务来说DDD都很有效,而后者在概念上更加符合DDD增加隔离的思想。

当基于概念模型进行去中心化时,微服务同样要求把数据存储去中心,而单一系统则采用单独的逻辑数据库进行持久化,企业甚至倾向于使用同一个数据库覆盖多种应用(这在很大程度上取决于供应商的许可证业务)。微服务则更加极端,既可以采用同一数据库技术的多个实例,或者采用完全不同的数据库技术实现(Polyglot Persistence,同样可适用于单一系统)。

这里最大的问题是变更管理,因为在许多应用场景中,单一系统采用事务处理的方式保证多个资源间的一致性。而在分布式系统中,事务无疑变得十分困难,这令微服务面临两难的问题:既要保持分割独立,又要保证一致性。而在实际上,微服务的设计思想倾向于强调服务间的弱事务性。这就使得一致性只可能是最终一致性,并采用弥补操作解决相关出现的问题。

这种非一致性管理的问题对多数团队都是一项挑战,但在实际业务中却时有发生。有时具体业务会要求将不一致性保持在一定程度以内,从而快速响应需求,同时拥有一些类似于回退的过程处理错误。一旦修复这些错误的耗费小于保证业务的强一致性,这种权衡就是值得的。

基础设施自动化

近年来,基础设施自动化技术取得了巨大的进步,特别是类似AWS这种公有云服务的兴起,降低了构建、部署、以及操作微服务的门槛。

一般来说,采用微服务的团队通常都能熟练应用持续集成CI、甚至持续交付CD技术,拥有丰富的基础设施自动化背景。CI/CD的目标是令构建和部署变得“无趣”,一旦目标达成,任何扩展就变得非常容易实现,而这在单一系统上已经得到了充分验证。对于微服务来说,该项技术与单一系统相比没有太大区别,但两者的操作域存在明显区别。

高融错设计

当采用服务作为应用组件以后,该应用就需要被设计成面向服务的高容错系统。而在实际情况中,任何服务都有可能发生错误而中止服务,客户端必须据此给出合理的响应。与单一系统相比,这是一个存在于微服务系统中的额外复杂度。

首先是测试,包括在生产环境中执行自动测试,以及当某些服务或者数据中心出错的意外情况恢复和监控。然而对习惯于单一系统的运维团队来说,这种方式可能是一时难以接受。 微服务特别强调服务的实时监控,无论是架构方面,还是业务方面,这种语义化监控能够提早给出出错警告并同时开发团队。对于单一系统,可以把单个应用视为一个微服务,区别是你需要在不同进程中的服务出错时得到警告。而由于单一系统通常采用库实现组件化,相同进程内的服务出错可能并不会得到明确的警告。

总而言之,微服务要求较为完善的监控和日志系统,同时拥有一个实时的dashboard随时通知开发团队,重要的测量单位有断路器状态、吞吐量、延迟等等。

演进化设计

微服务的实践者通常拥有演进化设计背景,他们把服务分解当作未来控制变更的工具之一。控制变更的目的并非是要降低变更频率,而是保证更好、更快地控制变更对软件的影响。 无论何时对现有系统进行组件化分解,你都需要遵守一个分解原则:组件的关键属性之一是独立可替换和升级。这就意味着我们需要寻找一个点,能够通过重写这部分的组件而不会影响其它组件。而实际上,很多团队选择直接将服务碎片化,而非进行长期演进。

在模块化设计中,一个通用的准则是强调可替换性,这样就可以保证接受变更。当你发现你需要重复的修改两个服务时,那就意味着它们应当被合并。

微服务的优势是降低变更发生时的构建和部署时间,缺点是你需要考虑服务变更对消费者带来的影响。一个传统的解决方式是采用服务版本管理策略,但对于微服务来说,应当尽量不要进行版本管理。在许多情况下,我们可以通过设计使得服务尽可能接受不同的消费者请求。

2. 总结

上文几乎列出了微服务架构到目前为止被发现的全部优点,这里列出其缺陷或尚未解决的问题:

组件边界的确定

由于微服务本质上是采用服务实现系统组件化,那么组件边界就成为衡量微服务设计优劣的重要参考价值。一旦设计完成,任何代码级重构、借口变更、向下兼容、以及测试架构的复杂度都会提升。

组件的组成

当组件的组成方式存在瑕疵,剩下来能做的就是把组件内的复杂性转移至组件连接部分。当你关注组件内部时,应当注意组件间的组成方式设计。

团队技能

任何新技术都倾向于更适合拥有中高级能力的团队,但并非对其它团队完全无用。即使是采用单一系统,某些团队依然做的一团糟,而微服务表现如何犹未可知。

最后给出Martin对何时向微服务架构迈进的评论: One reasonable argument we’ve heard is that you shouldn’t start with a microservices architecture. Instead begin with a monolith, keep it modular, and split it into microservices once the monolith becomes a problem. (Although this advice isn’t ideal, since a good in-process interface is usually not a good service interface.)

喜讯:热烈祝贺本站正式被OOXX!!

经向有关部门了解,本站已被GFW屏蔽,具体原因不明。大家今后VPN见!百度的朋友就对不住啦……

有关秋雨、毕业、软件测试、痛苦根源、《金刚经》和妄念的一切

须菩提白佛言:“世尊!佛得阿耨多罗三藐三菩提,为无所得耶?”佛言:“如是,如是。须菩提!我于阿耨多罗三藐三菩提乃至无有少法可得,是名阿耨多罗三藐三菩提。”

——《金刚般若波罗蜜经》,第二十二品 无法可得分

趁着雨天赶上了入夜前的最后一班校车,结果遇到喜闻乐见的大堵车,折腾回家天已全黑。待饭毕,终于不愿去碰什么C++、数据结构与算法的书——最近整天泡在图书馆里读各种经典,恨不能把本科和研究生几年来西大欠我的一座图书馆一下子搬走。结局是书读了不少,却毫无长进:恐怕是因为我这种随众的心态,毕竟眼见人人都揣着本面试宝典之类的畅销书,再淡定也终不能自持了。但这真称得上是所谓的奋斗么?却未必尽然。

现在回头看一眼前一篇文章的日期:写于大约一个半月之前。在那又之前的很长一段时间里,我始终以为未来将在那之后笃定,我甚至认为自己将与心仪的导师和朝气蓬勃的团队会面,大家相谈甚欢,以至于令我觉得能够在自己钟爱的领域大干十年,人生便没有遗憾了。然而当青岛会议结束后,却发现情况和当初预想的完全不同:首先,如果稍了解国内的科研现状,你会发现好的导师和团队真是凤毛菱角,即便遇到了,准入门槛也极高,甚至不完全取决于主观因素,有时真得称得上是可遇而不可求;其次,我发现自己现有的心智模式中可能缺少非常重要的一块,尽管多年来不时遇到好心人提醒,却很少真的思考这个问题。换言之,如果我选择并得以“再干十年”,难道就真能得到自己想要的东西么?

这是一个矛盾,而且以我仅有的经验而言根本无法解决之,也无法获得能够真正说服自己的第三方的帮助。再加上七八月份的火气:令我不得不选择逃离,去到一个能够给自己降温的圣地,也是为了给自己留一点时间:我明智地选择了一人出发,在青藏高原起伏的草场上,试图短暂忘却平均海拔4000米下的一切,而有时候,甚至真的已经感到有些“乐不思蜀”了。有意思的是,等我回到家,发现命运已经把自己落下了一截。

我不知道它是如何发生,实话说也不想知道。大概是因为我有一种固有的计算思维,在提前制定测试用例时,我会像是强迫症似地列举出每条分支和可能性,然后从自己的角度做出对结果的评估。也就是说,我已经意识到这会是结局之一,的确就不很讶异了。但这种所谓的“淡定”存在明显的缺陷,即如果我们只一味做黑盒测试,无法真正从编码的角度去解释问题的原因,那么作为此生唯一的程序员,怎么保证自己能谱下一首生命之歌呢?然而关于这个问题,我至今没有想太明白。

后来有机会和朋友讨论这件事,还是不到两周前的事。谁知道大家听了都很不屑:“这简直太显而易见了吧?”我很惊奇他们是如何熟练掌握这种技能的,更有可能是道听途说吧?但如今世界的规则不就是由道听途说演变而来的么?唯一能确定的,就这是一切都没有超出合理的范围之外,如果连这些都不合理,那人类恐怕早都灭亡了也说不定。

于是我觉得自己又面临着一次需求跟踪与评估,值变了,维度却没变,复杂度依然毫无降低。唯一能做的,就是像众人一样,去啃那些本不值得在此时去啃的书。也就是说,虽然自己的算法bug了,换成朴素的一样好用,当然前提是你不是一个追求完美的狂热份子。我显然在这又悲剧了一把。

所以好不容易回趟家,绵绵秋雨,好时节当然不能全浪费了,便去读大乘经典。本文开头的《金刚经》第二十二分,大意为“须菩提问佛说:世尊,佛所得至高无上、大彻大悟大智慧,也就是什么也没得到吗?佛回答道:正是这样,正是这样!须菩提,我于阿耨多罗三藐三菩提,是无所得,(须菩提)一点法都没得到,只是说我成就了至高无上、大彻大悟大智慧。”另文中的名词可参见关于心经的注,在此就不讨论佛学。南怀瑾亦有关于本品的偈颂:“多年行脚觅归途,入室知为道路愚。检点旧时新衣钵,了无一物可提扶。”或许可当做送给自己的安慰作?仔细想了下,自己恐怕永远也到不了那个境界……

尽管我崇尚绝对自由,甚至可以说如果没有自由,我就不再是我。但是另一方面,如果未来我选择妥协,用自己的自由去换取世界观,我就变成了另外一个我,一个背叛了原始的我的我,到那时,我该怎么看待自己?我只能确定,本文完成后还剩了一摞的书等着我去啃。至少通过这种方法,我就有机会去变成一个不一样的我,然后告诉原始的我:原来我是这样的我!

最后,如果读者您完全看懂了本文,那么也请尽量告诉我,让我能够佩服一下自己 ,谢谢。

Comments

网络里的隐形蝴蝶

取道天津回家的路上,回想起在研讨班上介绍的近期发表在《Science》上的一篇文章——《Network Interventions》,恰好随身u盘里存有该期的full text,为了打发时间,便试读了这篇Review(很少去碰《Science》上的Review,往往感觉高贵但又不知所云)。结合最近、特别是这几天会议期间的经历,觉得有所感触,于是就想写文章,无奈下了动车就找不到一个能接到电源的地方,于是只好回家后将其完善。事实上,南加州大学Thomas W. Valente博士通过这篇Review列举了一些针对社会网络的最新研究成果,并向我们展示了一个看待现实问题的新思路。

原文作者Thomas W. Valente是USC的传播学博士,现任该校预防医学系副教授,由于数学学士的背景,其研究兴趣主要集中于网络分析和基于社会网络与程序评估的社会影响研究。Network Interventions的字面意思是网络介入,指利用社会网络数据加速行为变更或改善组织效率的一种新方法。近年来,研究人员逐渐发现通过分析已有的社会网络数据,并采用合适的方法就能得到一种具体且有效的Network Intervention方案,而一旦应用该方案就有可能引发强烈的正面或负面效应(Interventions并不是本文的重点,但却是原文的核心主题)。应当注意的是,Network Interventions之所以可被看作一门科学,是因为前期已有大量有关社会网络分析的研究,并取得了不乏瞩目的成果,而Network Interventions实际上是建立在前述成果基础上的一系列应用。那么,随着时间的推移,社会网络的规模和复杂度不断更迭,其本质是否已发生了无数次巨变呢?

同一篇文章的原作者著有一本《Social Networks and Health: Models, Methods, and Applications》,别出心裁地将社会网络分析的理论和方法应用于公共卫生和其它社会科学领域,其基本观点是:不同人类分组之间相互交流和传播形成了个体的健康行为。这里的“分组”可以指一个族群,也可以指一个个体。通过对社会网络的数据、特别是近年出现的基于互联网的虚拟社会网络所带来的庞大数据进行网络分析,研究人员发现个体行为和精神状态不同程度地受到了网络中相邻节点的影响,影响力最大的节点被称为“变更代理”。目前已有许多判别网络中“变更代理”的方法,而采用Borgatti提出的最关键节点方法可以找到网络中的局部Key player,即所谓的Leader。通常情况下Leader影响了网络中大部分节点的行为属性,但对于类似存在桥节点(且Leader非该类型节点)的网络,传播效率就很大程度上受到非Leader因素的影响。具体到公共卫生领域,这种影响可能是饮食性超重在节点间的传染,其根源则是Key player对其它节点施加了影响,使节点的固有行为、如饮食习惯等发生变化,从而导致超重节点数量的增加。另一个典型案例就是某种抑郁症的网络传播,这里会另文介绍。进一步,研究人员为了度量个体行为在网络中的传播速度和冲量(动量的变化量),通常采用一种“低阈值变更代理”的方法,即通过分析邻接Key Player数量与所有相邻的节点数,从而得到节点的曝光度,其值实际上就是每个节点的阈值。低阈值携带节点有时也会成为变更代理,但确定这部分则需要获取行为学意义上的先验数据加以支持。

总的来说,社会网络可被看作是一个个体相互间施加行为影响的媒介,如同一只隐形的蝴蝶,有意无意将花粉传播到网络中的各个角落。而随着电子信息工业的发展,互联网上的社会网络逐渐崛起,非常丰富和高质量的社会网络数据直接被用于网络分析,从而为底层的基础理论研究提供数据保障,使这第一手资料变得弥足珍贵。但是,研究人员仍需要进一步明确传统社会网络和所谓SNS的本质区别,由于网络传播的即时性和爆炸性,行为影响传播在广度上会有极大的促进,但也未必完全意味着好消息。毕竟,从植物学的角度来说,错误的花粉被授予柱头,将导致花粉和胚珠的双重浪费,这显然不是进化论给予我们的答案。

即使是在传统社会网络里,这种情况发生的概率也非常之低。

Comments

整站迁移完成

最近一直在考虑是不是该升级到vps了?毕竟单纯的空间已经远远无法满足现在互联网DIY的需求。然而思考再三,vps需要花更多时间去照顾,恰好在未来一段时间内又不会很轻松,只好先忍一忍。于是决定趁早先拿下新域名hanyi.name。结果问题就来了,国内的空间商要求新绑定域名必须重新备案,否则按规定不予解析。还好我对这一套比较熟悉,屁颠儿跑去XX部网站办备案,结果被告知个人只允许备案1次,机构最多也只允许备案5次。而我前次备案是在08年,当时还是因为他们无故删除了我在06年就提交并审核通过的备案信息,悲剧的是他们这次居然成功保存了4年前的信息!回来找空间商商量,最简单的方法还是换别人的真实信息重新提交。顿时无语,原来早前CN域名开放是纯属坑人呢?进而有了将整站迁移至国外空间的想法。

域名问题很好解决,许多国外知名域名商支持通过支付宝进行国际支付,加上pending共花了一天时间完成。缺点是DNS初解析较慢,平时用的CN域名的解析在国内几乎是实时更新的。另一方面采用了原空间商在国外的主机业务,用电信线路看主机的ping不错,不过迁移后发现明显还是慢了一个级别,目前看来只好通过在今后优化页面内容来改善了。好处是空间容量是以前的三倍,不过现在看来用处似乎不大。

迁移工作还是颇费周折,现在看来wordpress迁移唯一必然的成功办法还是数据库内容链接的全文替换,否则只改几个字段可能会出现无法进入后台的情况。再加上周中实验室网络欠费,用了几天坑爹的“西大国内网”,很难顺利完成整站内容的迁移。于是只好把多余的时间用来进行一些反思:多年以来我做了许多自己感兴趣的工作,尽管无甚成果,但确实也学习了不少东西,总体上始终循着当初所谓“自由学习,自由思想”的理念,也从未遇过大的挫折。然而,出于一些忧虑和回避,自己有意无意忽视了平等交流:所谓平等交流,在我看来就是用自己的想法去换取别人的想法,并且所有人都认同且遵守唯一的道德准则。事实上,我对周围真实环境的了解程度非常低,某种程度上是基于以上原因。这也就导致了自己对身边事物的看法具有很强的不确定性,难以在自身问题上做出有效判断。另一方面也导致自己至今缺乏一些常识……有些事在别人看来是显而易见,自己却不容易想明白。

考虑到有些外链是指向原mp77.cn,本站仍会对继续支持这个域名,并持续至少1年时间。而hanyi.name会是我的永久性域名,今后也不会再向站外自动导出文章。

手嶌葵拯救世界

如果说,手嶌葵是六年前的《地海战记》迄今为止给人们留下的唯一记忆的话,同样的评价送给《虞美人盛开的山坡》恐怕也没多少人会提出异议。然而必须承认,宫崎吾朗的二次登场的确令人耳目一新,看似俗套的少女漫画情节,影片最终展现出的信息却远超出这个范畴。毫无疑问,我们都被影片和她的主题歌声完全治愈了。

仅从观影的角度来说,影片的主线情节可以说毫无新意,即使单是吉卜力在近二十年里也已拍了不少类似的主题,观者可能会感到比较乏味。不过如果换一个方向,会发现影片丝毫不含蓄地表达着一种积极向上的氛围,这一点却是同类型片中所少见的。正如《萤火虫之墓》展现给我们一个焦灼的日本一样,《虞美人盛开的山坡》所展现的是短短二十年后一个全新的社会。许多中国人都对五六十年代的日本感兴趣,以至于有关彼国六十年代经济腾飞的著述迄今都颇受欢迎,然而恐怕许多人(包括我)至今都为此感到困惑,如今幸好一部2011年本土票房冠军的影片能够给我们以管中窥豹的机会。

故事原设于公元1963年,恰好是东京第十八届夏季奥运会的前一年。阿海和阿俊几乎在同一时间内都遇到了“人生的重要挑战”,一是“拉丁楼事件”,二是关于自身的身世之谜。然而二人自始至终都未言放弃,一方面成功使得作为历史见证的活动大楼免遭校方为了“迎接奥运”而进行的拆毁重建;另一方面,同时也颇具戏剧性的是,历史真相的唯一见证人恰好在片末出现,吐露出了阿俊身世的秘密,并且终于卸下了重压在两人身上的历史包袱。如果仅仅从青春爱情故事的视角去鉴赏影片,有些情节可能经不起推敲,于是宁愿称之为一种朦胧的、尚未成熟的情感加以附会,但叙事手法上就显然比不上早年的《侧耳倾听》。事实上,从《地海战记》中我们就可以发现,宫崎吾朗的思想深度其实不亚于老爹,广度上则可能要更甚一些。然而他的劣势在讲故事的能力上,导演没能像上一辈的几部经典作品(当然宫老也并非只是传说)那样能够抓住观众,这种小遗憾现在看来恐怕仍然是不可避免的。

不过,导演真正希望传递的信息可能不止于此。其实,真正令人惊讶的是片中人物所共有的思维方式。例如,作为全剧线索的“拉丁楼事件”,即使在争辩双方矛盾非常尖锐的情况下,依然能够齐心协力维护大家得来不易的自由。学生们甚至喊出了“少数服从多数,即是多数人对少数人的暴力”这样颇具智慧的政治口号:这些桥段无一不体现着当时日本社会的整体风貌。另一方面,阿海和阿俊都是非常严肃地看待两人之间的微妙关系,哪怕是后来所谓的“兄妹疑云”,也没有发生任何情绪化事件,相反二人都在尽力追求真正的谜底:相比之下,在五十年后的现代社会,这样的“死理性派”是否太少了点呢?

如果对故事的所有事件追根溯源,则要归结于立花洋和泽村雄一郎两位长辈在战时的先后离世。阿俊每天驾着拖船驶过阿海家门前的一片水域,不经意间发现少女每天准时升起祈愿平安的U.W信号旗,于是撰下诗篇并发表在校园刊物上:

少女扬起了旗帜  何故  要将思念寄语晨风  呼唤著远方  伴著一时心血来潮的老鸦  少女啊  那藏青色围绕下红白相间的旗帜今日也在风中飞扬

命运就那样巧合。阿海偶然读到诗文,从此记住了松间俊这个名字,也为二人相识奠定了命运的基础。最后,唯一历史亲历者小野寺善雄的出现,使得尘封十余年的故事最终浮出水面,拖船缓缓驶向岸边,夕阳下的阿海和阿俊终于如释重负:对阿俊而言当然是得知了自己的真实身世;另一方面则包含了阿海对父亲的怀念;而当下乃至未来的意义则是不言自明了。片末,阿海完成了人生中迄今为止最为宁静的一次升旗,镜头回到姐姐的那副画作,正是不远处水域上航行的那艘拖船,船头也挂起了“答语U.W”。对导演来说,战后的日本社会长期受到历史遗留问题的困扰,而同样作为战争受害者的普通民众则受之更甚:人们需要通过某些方式得到彻底解脱,才能坦然面对未来并开始新的生活。实际上,六十年代的日本国民不仅做到了,而且还比其他亚洲国家要出色的多。或许这才是导演的真正意图:唤醒国民对美好时代的回忆与憧憬,而且不应仅仅是怀念而已。

手嶌葵的歌声在片尾响起,其实是翻唱自森山良子在七十年代的一首老歌《さよならの夏(告别之夏)》,宫氏招牌式的完美结局,在手MM的伴奏下逾显动人了(也彻底填补了影片末尾与《侧耳倾听》之间存在的思想差距)。

附歌词:

光る海に かすむ船は

さよならの汽笛 のこします

ゆるい坂を おりてゆけば

夏色の风に あえるかしら

わたしの爱 それはメロディー

たかく ひくく 歌うの

わたしの爱 それはカモメ

たかく ひくく 飞ぶの

夕阳のなか 呼んでみたら

やさしいあなたに 逢えるかしら

だれかが弾く ピアノの音

海鸣りみたいに きこえます

おそい午後を 往き交うひと

夏色の梦を はこぶかしら

わたしの爱 それはダイアリー

日々のページ つづるの

わたしの爱 それは小舟

空の海をゆくの

夕阳のなか 降り返れば

あなたはわたしを 探すかしら

散歩道に ゆれる木々は

さよならの影を おとします

古いチャペル 风见の鶏(とり)

夏色の街は みえるかしら

きのうの爱 それは涙

やがて かわき 消えるの

あしたの爱 それはルフラン

おわりのない言叶

夕阳のなか めぐり逢えば

あなたはわたしを 抱くかしら

(完)

Comments

Kinect的三维重建(1)

有关Kinect应用开发正日新月异,稍有懈怠就会被远远甩在身后。不过,Kinect目前带给我们的仍只是一个充满无限可能的远景,正如App store能吸引年仅11岁的开发者一样,Kinect未来将对“全民开发者”产生重要推动作用。另一方面,一些基于Kinect的应用研究仍颇复杂,主要是因为一些关键环节的滞后而导致的。2011年的siggraph talks上,KinectFusion首次展示了实时、廉价、轻便的室内场景三维重建,使得我们向着“无处不在的数字化”迈进了一大步。该项目主要由微软剑桥研究院的研究人员发起实施,研究小组目前公开发表了两篇文章:

Shahram Izadi, David Kim, Otmar Hilliges, David Molyneaux, Richard Newcombe, Pushmeet Kohli, Jamie Shotton, Steve Hodges, Dustin Freeman, Andrew Davison, and Andrew Fitzgibbon, KinectFusion: Real-time 3D Reconstruction and Interaction Using a Moving Depth Camera, ACM Symposium on User Interface Software and Technology, October 2011

Richard A. Newcombe, Shahram Izadi, Otmar Hilliges, David Molyneaux, David Kim, Andrew J. Davison, Pushmeet Kohli, Jamie Shotton, Steve Hodges, and Andrew Fitzgibbon, KinectFusion: Real-Time Dense Surface Mapping and Tracking, in IEEE ISMAR, IEEE, October 2011

除了Microsoft Research外,国内外大量机构都借助Kinect进行相关研究。仅就实时场景三维重建这项应用而言,由开源机器人研发公司Willow Garage在2011年发起的Point Cloud Libary (PCL)就吸引了全世界数十家著名科研机构参与、十余家公司提供经济支持。PCL是一项集点云获取与处理、滤波、特征提取、关键点标定、表面重建和点云配准以及点云融合等功能为一体的开源点云处理库,PCL使用OpenNI作为系统IO接口,实际成为KinectFusion的开源实现项目,该项目的相关论文发表在ICRA2011上。

上述文献可以说是迄今为止有关Kinect的三维重建应用的最佳切入点了。应注意的是这两篇文章讨论重点的区别,前者先是对KinectFusion整个项目进行了流水式说明,随后解释了文中采用的经典算法的GPU实现,开发者可能会比较感兴趣;而后一篇文章则重点讨论了新的并行算法的形式化描述和性能分析,这部分可能会更吸引一部分研究者。我们将采用一种自上而下的方法先对这两篇文章进行简要介绍。

 概述

有关Kinect的结构光技术读者可以参考这篇文章或直接去Google下PrimeSense的专利,这里不再赘述。不过,尽管Kinect在获取深度图方面几乎是达到了性能与质量的完美结合,但仍无法避免深度图中大量抖动的产生,如果我们直接对单张深度图进行bilateral filter双边滤波然后重建出网格模型,会发现重建出的模型表面质量较低,甚至出现孔洞的情况。更重要的是,单张深度图能够获得的只是模型在某个视角下所展现出的一部分,而非表示完整的模型。因此,如何改善基于Kinect深度图的模型重建质量,并实现物体的完整重建成为该项技术的关键。

KinectFusion的首要任务就是克服该难题,它允许用户手持Kinect设备在室内场景自由移动,并实现场景的高质量三维重建。同时,KinectFusion还提供了精彩的AR应用,包括前景、背景和人体的分割与重建、以及AR世界中的多点触摸识别技术。

场景重建

基于上述需求,KinectFusion允许用户手持Kinect设备自由探索室内环境,系统将自动跟踪Kinect摄像头的6DOF姿态,然后融合不同时序的深度图数据并重建出场景的全局模型。由于视角的不断变化,Kinect反馈的深度图也会发生改变,这里采用了类似图像超分辨率技术对深度图进行了细节优化,从而提高模型的重建质量。最后利用Kinect自带的RGB数据进行场景纹理映射。

首先如何利用单Kinect摄像头去跟踪它自己的6DOF姿态?这里采用点云模型的刚性配准对齐来计算摄像头在空间中的6DOF变换,点云配准首选经典的ICP[Besl92, RusinKiewicz01]。之前需要将深度图像数据变换至摄像机的空间坐标系中,并计算其对应点的法线信息;然后逐帧采用基于GPU的ICP实现进行模型配准和6DOF计算;在重建部分,KinectFusion并没有直接融合点云或生成网格模型,而是采用了[Curless96]的体集成算法,最终在全局坐标系中生成一个三维体素网格并进行不断更新,每个体素内最终保存了一段时间内从该体素到物理表面上某一点的平均距离。最后使用Raycast给出隐式表面的渲染结果,另一方面,以摄像机位置作为视点做Raycasting,同样能够得到一个具有真实细节的高质量的合成深度图,然后再对其进行下一轮ICP迭代。这就允许我们能够利用合成深度图与下一帧的深度图进行配准,使重建结果的精度不断提高。

具体算法实现采用了CUDA并行架构,算法基本流程如下:

1)深度坐标变换,对于每个像素启用一只CUDA线程,给定一个Kinect红外摄像头的内部校准矩阵K,使用如下公式

计算坐标变换;相应的顶点法线则直接取其右、下邻向量的外积,并进行归一化。时间i时的6DOF摄像机姿态使用一个刚性变换矩阵T表示,其中T包括了摄像机的旋转矩阵R和平移矩阵t。变换后的顶点和法线通过T即可再变换至全局坐标系中。

2)摄像机跟踪,该部分的主要目的就是计算6DOF姿态。核心的ICP即迭代最近点算法,首要是需要逐帧计算不同朝向的点集的相关度。这里采用了projective data association方法计算相关度。

3)体集成,针对已配准的点云数据,需要执行后续的融合处理,这里采用了经典的[Curless96]体集成方法融合这些点云数据。

4)光线投射渲染,采用光线投射渲染前步生成的隐式表面。

上述整个管线均采用了并行GPU实现,在下文中,我们将重点解析上述算法的CUDA实现细节。

Comments