Serverless架构实践初探

随着云计算技术的进步,软件系统的架构方式也因此发生着一些变化,其中Serverless架构就是这里的一个典型的例子。

(图片来自:http://t.cn/RadEFqr)

什么是Serverless架构

目前关于Serverless架构的准确定义,业界并没有一个统一的标准。那么我们从字面上来分析,所谓Serverless架构,翻译过来也就是无服务器架构。那么似乎可以涵盖以下两个方面:

  1. BaaS(Backend as a Service)即后台即服务。后台即服务出现有很长一段的时间了,例如Parse,Firebase都是典型的代表。具体来说就是服务器端的逻辑和状态是完全依赖于云平台进行管理的。
  2. FaaS(Function as a Service)即函数即服务。函数即服务,意味着这些函数中的后台逻辑是由我们开发者自己实现的。但是这些函数是执行在一个无状态的计算容器中的,函数的执行是基于事件驱动的,关于这些函数的部署、执行、触发是由云平台来管理的。其最典型的例子就是AWS Lambda。

我们这篇文章中的所讨论的Serverless,是指的第二种,也就是FaaS。在我们Thoughtworks最新一期的技术雷达中,Serverless架构位于试验象限,下文就介绍下我们在Serverless架构下的一些实践经验。

数据处理业务的Serverless架构演进

所谓的数据处理业务,是指我们的系统需要每天定时获取一些外部数据与我们自身的数据结合,生成一些数据报表。那么最初我们是怎么设计技术方案的呢?

传统架构方式

我们将业务拆分为3个独立的服务,2个Data Collector,1个Data Loader,都分别部署在AWS服务器上,将中间数据存储在一个外部S3(AWS的数据存储)上。最后将数据保存在数据库中,在数据库之上使用专门的BI工具来制作报表。我们第一个数据服务就是按照这样的架构进行设计和实践的。当系统上线服务以后,我们发现了里边的一些问题。

在这套系统中,Data Collector 2每天的执行时间较长,需要1个小时左右的时间,而Data Collector 1每天的执行时间较短,通常执行时间不会超过1分钟,但是由于外部数据源的更新时间是不确定的,所以虽然我们服务实际有效时间只有仅仅一到两分钟,但是也不得不让服务器全天运行。

可以看到,这个系统每天的有效时间只有一个小时,其他23个小时实际上是在浪费资源,如何改善这样的情况呢?首先想到了让服务定点运行的方法。由于我们外服数据源的更新特点,虽然它的更新时间是不确定的,但是它在某个特定的时间点前是一定会更新的。基于这样的前提,我们将服务运行时间改为定点运行,这样是不是就能解决问题了呢?

然而现实并不总是那么美好,因为我们服务间是有依赖关系的,Data Loader是依赖于我们Data Collector的处理结果的,当我们把运行方式改为定点运行后,带来的问题是,一旦Data Collector的运行状态出现了问题,例如运行时间过长,运行中出现错误,那么Data Loader必然出错。同时改为定点运行后,我们的数据更新必然有延迟。

那么如何解决这些问题呢?

Serverless的系统架构

我们引入了Lambda,将Data Collector 和 Data Loader用Lambda进行了替换,带来了下面这些好处:

由于Lambda是由事件驱动的,S3上一个数据的变化可以触发一个事件,SNS的一条消息可以触发一个时间等等,在使用Lambda后,我们就可以讲原来基于时间的数据处理流程,转变为基于事件的数据处理流程,这样一方面可以保证我们数据更新的实时性,另一方面可以大大节省资源,由于Lambda是按照触发次数收费的,所以在我们的这个用例下,可以大大减少花费。

可能细心的读者想问为什么我们Data Collector 2没有使用Lambda进行替换呢?这是因为它的业务逻辑比较复杂,每次运行的时间较长,而Lambda的最长执行时间是5分钟,所以在这种情况下,就不适合使用Lambda进行替换了。

实时数据处理下的Serverless架构

在初识Serverless架构的好处之后,我们开始在其他方面的应用尝试,比较典型的一个例子就是在实时数据处理业务下的Serverless架构。在我们业务下,我们需要实时跟踪一个外部的数据源API,根据它的数据变化来实时更新我们的数据。

在我们的架构设计中,我们使用一个Lambda来跟踪外部数据源的数据变化,并将其推到AWS Kinesis Stream里,AWS Kinesis会触发第二个Lambda进行相应的数据处理,并把数据存储到数据库中,值得注意的是由于Lambda是可以根据需求自动伸缩的,所以Lambda会根据Kinesis的需求来自动扩展。这就体现了Serverless架构下的另一个好处,可以相对简单的,自动进行伸缩扩展。

Web系统的Serverless架构

对于Web系统这种我们最为熟悉和常见的IT系统来说,它能不能用Serverless的架构来实现呢?我们来看下边的例子。我们先来看看传统的例子。

在传统实现中,我们会利用Load Blancer来做负载均衡,然后后续的应用会部署在AutoScaling Group中,根据流量来做自动伸缩,这种模式已经是十分成熟了。那么在Serverless的架构下该如何设计呢?

在Serverless架构下,一般我们的前端应用的资源文件包括Html,JS,CSS,都是部署在S3(AWS的文件存储)上的。前端应用通过AJAX请求向后台请求数据。后台通过API GateWay定义对外的Endpoint,同时每个Endpoint会触发一个Lambda进行数据操作,例如图中的GET,和POST请求会触发两个不同Lambda。这样的Serverless架构可以让开发者不必担心水平扩展的问题。

Serverless架构的未来

目前AWS Lambda似乎已经成为了Serverless的代名词,为了帮助开发者更好的构建Serverless应用,市场上出现了一些工具和框架,例如Serverless Framework。但是同样我们还可以看到一些其他的云平台和开源框架也在提供类似的服务,例如webtaskOpenWhisk,以及其在IBM Bluemix上的实现。

Serverless架构作为一种新的架构方式,还在不断的发展中。希望本文能给您带来一些思考。


更多精彩洞见,请关注微信公众号:思特沃克

Share

小数据:理论和架构

大数据是当下最热门的IT主题之一。据麦肯锡的分析,大数据能使信息更透明、能让决策者获得更精确翔实的绩效信息、能针对客户群体提供更准确的定制、能提升组织决策能力、能帮助开发下一代产品和服务。新时代里与互联网联结的组织不论大小,都需要这些能力。

然而与此同时,大数据的“大”并非适用于所有组织。Gartner认为,大数据具有“3V”的特征:多样性(Variety),数据来自多种不同来源、具有多种不同形态;速度(Velocity),数据形态和呈现形式的变化快且频繁;量级(Volume),数据量非常巨大。然而对于众多的中小型企业及非营利组织而言,这三个特征有两个未必适用。很多中小型组织只有为数不多的几个IT系统,数据都保存在为数不多的几个关系型数据库中,数据量不超过数百万条记录。只有变化速度快这一特征,对于中小型组织仍然适用。从这个意义上,这些中小型组织需要的是一个“小数据”解决方案:

小数据:聚焦中小型组织和新兴业务,在数据量较小、数据来源较简单的情况下,提供非常灵活、非常简便易用、使用过程中对IT技能要求非常低的数据分析和商业智能,为应对多变且未知的外部环境提供决策支持。

传统上,很多小数据场景的分析和商业智能需求以“报表”的形式呈现在IT项目中:在开发OLTP系统的项目中列出一组报表需求,由交付OLTP系统的团队以直接SQL查询的形式实现报表。这种做法贴合了数据量小、数据来源简单的特征,但损失了灵活性,报表的定制和修改需要技术人员介入,因此又无法满足对速度的要求。为了赢得灵活性,小数据分析也同样需要首先建模OLAP Cube,然后通过不同维度的切片和钻取进行分析。

什么是Cube?按照维度建模方法,数据可以分为“事实”和“维度”两类。事实数据代表“发生了什么事”,维度数据则从各个角度描述这件事。如果以电商为例,事实数据是“销售记录”(卖了一个东西),常见的维度数据可能包括“产品”(卖的是什么)、“门店”(在哪里卖的)、“时间”(什么时候卖的)、“售货员”(谁卖的)、“顾客”(卖给了谁)等等。不难想象,事实数据表将只有一个主键、一个值、以及一大堆外键指向各个维度表;维度表也可能有外键再指向更多的描述性的子维度表(例如“产品”有外键指向“类别”)。于是我们就会得到一个星型表结构(或叫雪花型表结构)。

Star-schema-example

 

星型表结构的优势在于,分析操作会变得非常简单:你关心哪些信息,就直接用JOIN子句把这些维度表关联进来;只要在JOIN子句里指定WHERE条件,就可以快速缩减结果集。在星型表结构里,一个事实会被若干个维度修饰,因此可以把整个数据集想象成一个立方体(或超立方体,当维度多于三个时)。例如当只考虑“产品”、“城市”、“时间”这三个维度时,“销售记录”的数据集就可以被建模为一个立方体。

QQ20160126-0@2x

随后就可以在这个立方体上对数据进行各种分析。例如你可以锁定“城市”这一维度,从而得到“某城市各种产品历史销售报表”——“锁定某一维度取值”这一操作也叫“切片”(slice),因为它在这个例子中产生的效果就是从三维的立方体中切出一个二维的数据平面。同样的,我们也可以从“产品”维度切片,从而得到“某产品各市历史销售报表”。当维度具有“分级汇聚”的特性时,我们还可以进行“钻取”(drill)操作,例如当“地区”维度分为“市”和“省”两级时,我们就可以在“地区”维度上进行钻取:首先从产品维度切片得到“某产品各省历史销售报表”,然后选择一个省下钻得到“某产品在某省内各市历史销售报表”。

小数据系统设计原则1:建模一个Cube,就可以快速实现一系列分析操作(及对应的报表)。小数据系统应该支持简便且易于修改的Cube建模。

基于这个设计原则,我们可以大概推知小数据系统的架构:首先,根据指定的Cube描述信息,把业务数据建模成Cube;然后,通过RESTful API对Cube进行切片、钻取和聚合等操作,并取回二维平面表或透视表形式的结果集;最后,根据指定的报表定义信息,把结果集渲染成报表。

architecture

从上图不难看出,在这个架构中,必须由用户(不论是开发者或最终用户)提供的信息只有三项:(1)Cube的描述;(2)数据分析操作对应的URL;(3)呈现分析结果的报表定义。并且第三项信息(即报表定义)与具体业务是完全解耦的,因此理应可以用分别的开源软件组合形成轻量级的小数据解决方案。在下一篇文章中,我将具体介绍一个基于开源软件的小数据解决方案实现。

Share

基于微服务架构,改造企业核心系统之实践

1. 背景与挑战

随着公司国际化战略的推行以及本土业务的高速发展,后台支撑系统已经不堪重负。在吞吐量、稳定性以及可扩展性上都无法满足日益增长的业务需求。对于每10万元额度的合同,从销售团队准备材料、与客户签单、递交给合同部门,再到合同生效大概需要3.5人天。随着业务量的快速增长,签订合同的成本急剧增加。

合同管理系统是后台支撑系统中重要的一部分。当前的合同系统是5年前使用.NET基于SAGE CRM二次开发的产品。 一方面,系统架构过于陈旧,性能、可靠性无法满足现有的需求。另一方面,功能繁杂,结构混乱,定制的代码与SAGE CRM系统耦合度极高。由于是遗留系统,熟悉该代码的人早已离职多时,新团队对其望而却步,只能做些周边的修补工作。同时,还要承担着边补测试,边整理逻辑的工作。

1

在无法中断业务处理的情况下,为了解决当前面临的问题,团队制定了如下的策略:

  1. 在现有合同管理系统的外围,构建功能服务接口,将系统核心的功能分离出来。
  2. 利用这些功能服务接口作为代理,解耦原合同系统与其调用者之间的依赖;
  3. 通过不断构建功能服务接口,逐渐将原有系统分解成多个独立的服务。
  4. 摒弃原有的合同管理系统,使用全新构建的(微)服务接口替代。

2. 什么是微服务

多年来,我们一直在技术的浪潮中不断乘风破浪,扬帆奋进,寻找更好的方式构建IT系统。微服务架构(Micro Service Architect)是近一段时间在软件体系架构领域里出现的一个新名词。它通过将功能分解到多个独立的服务,以实现对解决方案或者复杂系统的解耦。

微服务的诞生并非偶然: 领域驱动设计指导我们如何分析并模型化复杂的业务;敏捷方法论帮助我们消除浪费,快速反馈;持续交付促使我们构建更快、更可靠、更频繁的软件部署和交付能力;虚拟化和基础设施自动化( Infrastructure As Code)则帮助我们简化环境的创建、安装;DevOps文化的流行以及特性团队的出现,使得小团队更加全功能化。这些都是推动微服务诞生的重要因素。

实际上,微服务本身并没有一个严格的定义。不过从业界的讨论来看,微服务通常有如下几个特征:

小,且专注于做一件事情

每个服务都是很小的应用,至于有多小,是一个非常有趣的话题。有人喜欢100行以内,有人赞成1000行以内。数字并不是最重要的。仁者见仁,智者见智,只要团队觉得合适就好。只关注一个业务功能,这一点和我们平常谈论的面向对象原则中的”单一职责原则”类似,每个服务只做一件事情,并且把它做好。

运行在独立的进程中

每个服务都运行在一个独立的操作系统进程中,这意味着不同的服务能被部署到不同的主机上。

轻量级的通信机制

服务和服务之间通过轻量级的机制实现彼此间的通信。所谓轻量级通信机制,通常指基于语言无关、平台无关的这类协议,例如XML、JSON,而不是传统我们熟知的Java RMI或者.Net Remoting等。

松耦合

不需要改变依赖,只更改当前服务本身,就可以独立部署。这意味着该服务和其他服务之间在部署和运行上呈现相互独立的状态。

综上所述,微服务架构采用多个服务间互相协作的方式构建传统应用。每个服务独立运行在不同的进程中,服务与服务之间通过轻量的通讯机制交互,并且每个服务可以通过自动化部署方式独立部署。

3.微服务的优势

相比传统的单块架构系统(monolithic),微服务在如下诸多方面有着显著的优势:

异构性

问题有其具体性,解决方案也应有其针对性。用最适合的技术方案去解决具体的问题,往往会事半功倍。传统的单块架构系统倾向采用统一的技术平台或方案来解决所有问题。而微服务的异构性,可以针对不同的业务特征选择不同的技术方案,有针对性的解决具体的业务问题。

对于单块架构的系统,初始的技术选型严重限制将来采用不同语言或框架的能力。如果想尝试新的编程语言或者框架,没有完备的功能测试集,很难平滑的完成替换,而且系统规模越大,风险越高。基于微服务架构,使我们更容易在遗留系统上尝试新的技术或解决方案。譬如说,可以先挑选风险最小的服务作为尝试,快速得到反馈后再决定是否试用于其他服务。这也意味着,即便对一项新技术的尝试失败,也可以抛弃这个方案,并不会对整个产品带来风险。

2

该图引用自Martin Fowler的Microservices一文

独立测试与部署

单块架构系统运行在一个进程中,因此系统中任何程序的改变,都需要对整个系统重新测试并部署。 而对于微服务架构而言,不同服务之间的打包、测试或者部署等,与其它服务都是完全独立的。对某个服务所做的改动,只需要关注该服务本身。从这个角度来说,使用微服务后,代码修改、测试、打包以及部署的成本和风险都比单块架构系统降低很多。

按需伸缩

单块架构系统由于单进程的局限性,水平扩展时只能基于整个系统进行扩展,无法针对某一个功能模块按需扩展。 而服务架构则可以完美地解决伸缩性的扩展问题。系统可以根据需要,实施细粒度的自由扩展。

错误隔离性

微服务架构同时也能提升故障的隔离性。例如,如果某个服务的内存泄露,只会影响自己,其他服务能够继续正常地工作。与之形成对比的是,单块架构中如果有一个不合格的组件发生异常,有可能会拖垮整个系统。

团队全功能化

康威定律(Conway’s law)指出:一个组织的设计成果,其结构往往对应于这个组织中的沟通结构(organizations which design systems are constrained to produce designs which are copies of the communication structures of these organizations)。传统的开发模式在分工时往往以技术为单位,比如UI团队、服务端团队和数据库团队,这样的分工可能会导致任何功能上的改变都需要跨团队沟通和协调。而微服务则倡导围绕服务来分工,团队需要具备服务设计、开发、测试到部署所需的所有技能。

4. 微服务快速开发实践

随着团队对业务的理解加深和对微服务实践的尝试,数个微服务程序已经成功构建出来。不过,问题同时也出现了:对于这些不同的微服务程序而言,虽然具体实现的代码细节不同,但其结构、开发方式、持续集成环境、测试策略、部署机制以及监控和告警等,都有着类似的实现方式。那么如何满足DRY原则并消除浪费呢?带着这个问题,经过团队的努力,Stencil诞生了。 Stencil是一个帮助快速构建Ruby微服务应用的开发框架,主要包括四部分:Stencil模板、代码生成工具,持续集成模板以及一键部署工具。

3

Stencil模板

Stencil模板是一个独立的Ruby代码工程库,主要包括代码模板以及一组配置文件模板。

代码模板使用Webmachine作为Web框架,RESTful和JSON构建服务之间的通信方式,RSpec作为测试框架。同时,代码模板还定义了一组Rake任务,譬如运行测试,查看测试报告,将当前的微服务生成RPM包,使用Koji给RPM包打标签等。

除此之外,该模板也提供了一组通用的URL,帮助使用者查看微服务的当前版本、配置信息以及检测该微服务程序是否健康运行等。

[
    {
        rel: "index",
        path: "/diagnostic/"
    },
    {
        rel: "version",
        path: "/diagnostic/version"
    },
    {
        rel: "config",
        path: "/diagnostic/config"
    },
    {
        rel: "hostname",
        path: "/diagnostic/hostname"
    },
    {
        rel: "heartbeat",
        path: "/diagnostic/status/heartbeat"
    },
    {
        rel: "nagios",
        path: "/diagnostic/status/nagios"
    }
]

配置文件模板主要包括NewRelic配置,Passenger配置、Nagios配置、Apache配置以及Splunk配置。通过定义这些配置文件模板,当把新的微服务程序部署到验收环境或者产品环境时,我们立刻就可以使用Nagios、NewRelic以及Splunk等第三方服务提供的功能,帮助我们有效的监控微服务,并在超过初始阈值时获得告警。

代码生成工具

借助Stencil代码生成工具,我们能在很短时间内就构建出一个可以立即运行的微服务应用程序。随着系统越来越复杂,微服务程序的不断增多,Stencil模板和代码生成工具帮助我们大大简化了创建微服务的流程,让开发人员更关注如何实现业务逻辑并快速验证。

Create a project from the stencil template (version 0.1.27)
        --name, -n <s>:   New project name. eg. things-and-stuff
        --git-owner, -g <s>:   Git owner (default: which team or owner)
        --database, -d:   Include database connection code
        --triggered-task, -t:   Include triggered task code
        --provider, -p:   Is it a service provider? (other services use this service)
        --consumer, -c:   Is it a service consumer? (it uses other services)
        --branch, -b <s>:   Specify a particular branch of Stencil
        --face-palm, -f:   Overide name validation 
        --help, -h:   Show this message

如上代码所示,通过指定不同参数,我们能创建具有数据库访问能力的微服务程序,或者是包含异步队列处理的微服务程序。同时,我们也可以标记该服务是数据消费者还是数据生产者,能帮助我们理解多个微服务之间的联系。

持续集成模板

基于持续集成服务器Bamboo,团队创建了针对Stencil的持续集成模板工程,并定义了三个主要阶段:

  • 打包:运行单元测试,集成测试,等待测试通过后生成RPM包。
  • 发布:将RPM包发布到Koji服务器上,并打上相应的Tag。然后使用Packer在亚马逊 AWS云环境中创建AMI,建好的AMI上已经安装了当前微服务程序的最新RPM包。
  • 部署:基于指定版本的AMI,将应用快速部署到验收环境或者产品环境上。

利用持续集成模板工程,团队仅需花费很少的时间,就可以针对新建的微应用程序,在Bamboo上快速定义其对应的持续集成环境。

一键部署工具

所有的微服务程序都部署并运行在亚马逊AWS云环境上。同时,我们使用Asgard对AWS云环境中的资源进行创建、部署和管理。 Asgard是一套功能强大的基于Web的AWS云部署和管理工具,由Netflix采用Groovy on Grails开发,其主要优点有:

  • 基于B/S的AWS部署及管理工具,使用户能通过浏览器直接访问AWS云资源,无需设置Secret Key和Access Key;
  • 定义了Application以及Cluster等逻辑概念,更清晰、有效地描述了应用程序在AWS云环境中对应的部署拓扑结构。
  • 在对应用的部署操作中,集成了AWS Elastic Load Balancer、AWS EC2以及AWS Autoscaling Group,并将这些资源自动关联起来。
  • 提供RESTful接口,能够方便地与其他系统集成。
  • 简洁易用的用户接口,提供可视化的方式完成一键部署以及流量切换。

由于Asgard对RESTful的良好支持,团队实现了一套基于Asgard的命令行部署工具,只需如下一条命令,提供应用程序的名称以及版本号,就可自动完成资源的创建、部署、流量切换、删除旧的应用等操作。

asgard-deploy [AppName] [AppVersion]

同时,基于命令行的部署工具,也可以很容易的将自动化部署集成到Bamboo持续集成环境。

通过使用微服务框架Stencil,大大缩短了团队开发微服务的周期。同时,基于Stencil,我们定义了一套团队内部的开发流程,帮助团队的每一位成员理解并快速构建微服务。

微服务架构下的新系统

经过5个月的努力,我们重新构建了合同管理系统,将之前的产品、价格、销售人员、合同签署、合同审查以及PDF生成都定义成了独立的服务接口。相比之前大而全、难以维护的合同管理系统而言,新的系统由不同功能的微服务组成,每个微服务程序只关注单一的功能。每个微服务应用都有相关的负责人,通过使用Page Duty建立消息通知机制。每当有监控出现告警的时候,责任人能立即收到消息并快速做出响应。

4

由于微服务具有高内聚,低耦合的特点,每个应用都是一个独立的个体。当出现问题时,很容易定位问题并解决问题,大大缩短了修复缺陷的周期。另外,通过使用不同功能的微服务接口提供数据,用户接口(UI)部分变成了一个非常简洁、轻量级的应用,更关注如何渲染页面以及表单提交等交互功能。

总结

通过使用微服务架构,在不影响现有业务运转的情况下,我们有效的将遗留的大系统逐渐分解成不同功能的微服务接口。同时,通过Stencil微服务开发框架,我们能够快速地构建不同功能的微服务接口,并能方便地将其部署到验收环境或者产品环境。最后,得益于微服务架构的灵活性以及扩展性,使得我们能够快速构建低耦合、易扩展、易伸缩性的应用系统。

参考文献

  1. http://martinfowler.com/articles/microservices.html
  2. http://jaxenter.com/cracking-microservices-practices-50692.html
  3. http://microservices.io/patterns/microservices.html
Share

大型网站技术架构的演进

摘要 最近我在阅读2本关于大型网站架构的书:《大型网站技术架构——核心原理与案例分析》李智慧、《大型网站系统与Java中间件实践》曾宪杰。 我期望从这些书中学习到大型网站是如何做架构的,这个过程会遇到什么问题。当看完这2本书后,我总结出两个大问题: 1. 网站技术架构为什么会演进?换个说法就是为什么网站会变大? 2. 演进的过程会遇到什么问题?或者说为了演进,会遇到什么问题?


最近我在阅读2本关于大型网站架构的书:《大型网站技术架构——核心原理与案例分析》李智慧、《大型网站系统与Java中间件实践》曾宪杰。

我期望从这些书中学习到大型网站是如何做架构的,这个过程会遇到什么问题。当看完这2本书后,我总结出两个大问题:

1. 网站技术架构为什么会演进?换个说法就是为什么网站会变大?

2. 演进的过程会遇到什么问题?或者说为了演进,会遇到什么问题?

网站技术架构为什么会演进

我个人总结出来我们的技术架构演进的两种驱动力,驱动着我们为什么演进网站的技术架构:

1. 内在驱动力:我们期望把当前的业务做得更好,开发更多新业务

2. 外在驱动力:用户量的上升、用户种类的多样化

这两种驱动力不是独立的,更多时候是并行的。我想淘宝就是两种驱动力并行驱动的结果。

演进的原因很简单。但是在什么时机我们就应该演进网站的技术架构了,以及如何演进?面对这些问题,说实话,我没有任何经验,再说现实中每家企业当时都面临的问题都不一样,所以,我很难从经验中总结出什么是演进的时机。

但是我可以从另一个角度切入这个问题:研究网站内外结构,找到这些结构可能出现的问题点,知道或者预见到问题点了,你当然就知道应该怎么演进了。类似于你了解了PC机的结构,你也就知道什么时候要加内存了,什么时候要加硬盘了。

那么我们先看看网站的外部结构:

Screen Shot 2015-03-12 at 12.20.27 PM

外部结构中,我们可以看由以下几个部分构成:

U:代表用户群。当用户群变了,我们的网站如何演进?用户群的分析,我目前能知道的维度有:数量,种类,地理位置(区域)。

N:代表网络环境。网络环境在每个地区都不同。你可以想像我们为什么需要CDN。当我们期望每个区域的用户都能得到好的体验,我们的网站如何演进?

S:代表安全。就是我们要安全到什么程度?这与网站当前所处阶段及你网站的性质有关。

C:代表我们的网站。属于内部结构

网站的内部结构:

Screen Shot 2015-03-12 at 6.18.20 PM

内部结构的组成:

A:应用服务。

D:数据服务

总结下来就是我们在考虑网站是否应该演进了或者如何演进时,这些组成部分为我们提供了考虑问题的基准。

那么我们为什么不一开始就把网站设计成“大型”的。李智慧在后记里写到:“不要企图去设计一个大型网站”,“原因是互联网发展运行有其自己的规律,短暂的互联网历史已经一再证明这种企图行不通”。还说了:“大型网站不是设计出来的,而是逐步演化出来的”。对于最后这句话,我需要提醒下:“不是设计出来的”并不代表“随意设计”。

对于“大型网站的设计”,我个人的看法是现在我们的有“云”了,计算是可以买的,只要我们的设计能适应“云”,我是不是就可以一开始就设计大型网站了?

演进的过程会遇到什么问题

– 最初

从一个小网站说起。一台服务器也就足够了。

Screen Shot 2015-03-08 at 8.07.03 PM

– 数据服务与应用服务分离

越来越多的用户代表着越来越多的数据,一台服务器已经满足不了。我们将数据服务和应用服务分离,给应用服务器配置更好的CPU,内存。而给数据服务器配置更好更大的硬盘。

Screen Shot 2015-03-08 at 8.44.17 PM

– 使用缓存

因为80%的业务访问都集中在20%的数据上,如果我们能将这部分数据缓存下来,性能一下子就上来了。而缓存又分为两种:本地缓存和远程分布式缓存。具体使用哪种?还是两种都用,我目前不知道。

Screen Shot 2015-03-08 at 8.48.20 PM

这里有一个问题,书没有提到:应该缓存哪些数据?应该有一些原则的吧。

– 使用服务器集群

当这台服务器的处理能力达到上限时,它就会成为瓶颈。虽然你是可以通过购买更强大的硬件,但总会有上限。这时,我们就需要服务器的集群。这时,就必须加个新东西:负载均衡调度服务器。

Screen Shot 2015-03-08 at 9.08.59 PM

但是,使用服务器集群时,需要考虑一个问题:Session的管理问题。Session的管理有以下几种方式:

* Session Sticky:打个比方就是如果我们每次吃饭都要保证我们用的是自己的碗筷,而只要我们在一家饭店里存着我们的碗筷,只要我们每次去这家饭店吃饭就好了。

Screen Shot 2015-03-08 at 9.12.37 PM

这种方式的问题:

1. 一台服务器重启,上面的session都没了

2. 负载均衡器成了有状态的机器,要实现容灾会有麻烦

* Session复制:就像我们在所有的饭店里都存一份自己的碗筷。不适合做大规模集群,适合机器不多的情况

Screen Shot 2015-03-08 at 9.22.34 PM

这种方案的问题:

1. 应用服务器间带宽问题

2. 大量用户在线时,占用内存过多

 

* 基于Cookie:类似于每次吃饭都把自己的碗筷带上

Screen Shot 2015-03-08 at 9.28.16 PM

这种方案的问题:

1. Cookie的长度限制

2. 安全性

3. 数据中心外部带宽的消耗

4. 性能影响,服务器处理每次的请求的内容又多了

* Session服务器:同样可以是集群的。这种方式适用于session数量及web服务器数量大的情况

Screen Shot 2015-03-08 at 9.35.35 PM

这种方案需要考虑的是:

1. 保证session服务器的可用性

2. 我们在写应用时需要做调整,我目前不知道应用服务器能否将这部分逻辑透明化

– 数据库读写分离

数据库的一部分读(未缓存、缓存过期)及所有的写操作都还需要经过数据库。当用户量达到一定量,数据库将会成为瓶颈。这边我们使用数据库提供的热备功能,将所有的读操作引入slave服务器。注意:读写分离解决的是读压力大的问题。

Screen Shot 2015-03-08 at 9.46.05 PM

因为数据库的读写分离了,所以,我们的应用程序也得做相应的变化。我们实现一个数据访问模块使上层写代码的人不知道读写分离的存在。这里,我很想知道如果我使用ORM模型时,如何实现读写的分离?

数据库读写分离会遇到如下问题:

* 数据复制问题: 考虑时延、数据库的支持、复制条件支持。不要忘了,分机房后,这个更是问题。

* 应用对于数据源的路由问题

– 使用反向代理和CDN加速网站响应

使用CDN可以很好的解决不同的地区的访问速度问题,反向代理则在服务器机房中缓存用户资源:

Screen Shot 2015-03-08 at 9.51.49 PM

– 使用分布式文件系统

Screen Shot 2015-03-08 at 10.18.57 PM

– 数据库专库专用:数据垂直拆分。这样可以解决部分数据写的问题

Screen Shot 2015-03-08 at 10.25.34 PM

垂直拆分数据库时,会遇到的问题:

* 跨业务的事务

* 应用的配置项多了

关于事务的问题,有两种办法:

* 使用分布式事务

* 去掉事务或不追求强事务

– 某个业务的数据表的数据量或者更新量达到了单个数据库的瓶颈:数据水平拆分

将同一个表的数据拆分到两个数据库中

Screen Shot 2015-03-08 at 10.34.06 PM

数据水平拆分会遇到的问题:

* SQL的路由问题,需要知道某个User在哪个数据库上。

* 主键的策略会有不同。

* 查询时的性能问题,如分页问题

– 使用搜索引擎:解决数据查询问题

– 部分场景可使用NoSQL提高性能

– 开发数据统一访问模块:解决上层应用开发的数据源问题

Screen Shot 2015-03-08 at 10.49.26 PM

– 业务拆分及应用拆分

网站的业务日益复杂,建立一个独立的大型应用来完成这所有的业务变得不实际。从管理角度来,也不方便管理。然而,业务的拆分很难找到一种通用的模式,这是一个企业管理问题和技术问题的混合问题。同时和每个企业的具体情况有关。

但是从这两本书来看,最终架构都走向服务化,也就是SOA。而如何实现SOA,是另一个很大的话题,不是本篇文章的范畴。

我从程立08年的演讲中截个图来说明SOA后的架构大概是怎样的:

Screen Shot 2015-03-09 at 8.55.28 AM

– 非功能性问题

     – 安全性问题、监控问题

     – 发布问题:新的架构意味着新的发布方式

     – 分机房

这两本书都没有说分机房的问题。我没有经验,可是也可以猜到如果要分机房了,所有上面的问题都可能要重新考虑。

 

     – 组织架构的变化 

我们的技术架构的变化,势必会引起我们的组织架构的变化,反之亦然。

这部分看似不应该由我们来管,但是,我觉得,我们技术人员也要参与一部分的组织架构的设计。举个例子,组织架构的设计会涉及绩效,而绩效有时很像一个国家的法律。如果一个国家的法律不健全,会发生什么?你懂的。

同时,我们还必须考虑人员对新架构的学习成本。

这部分我目前在看相关的书籍,还没有一个系统的认识。

总结:

– 关于演进的顺序

在现实中,技术架构的演进不一定就是按文章从头到尾这样列下来的,所以,要视具体情况来下决定。

– 关于传统演进与现代有“云”环境下的演进

很可惜,只有李智慧谈到云,而且只点了一下——“现在越来越多人的网站从建立之初就是搭建在大型网站提供的云计算服务基础之上,所需的一切资源:计算、存储、网络都可以按需购买线性伸缩,不需要自己一点一点地拼凑各种资源,综合使用各种技术方案逐步去完善自己的网站架构”。

因为我用“云”的时间也不长,还不能总结出有云架构与传统的无云架构在演进的时候有什么不同。

说回传统的架构演进,我自己总结和思考的结果是:

在对网站进行架构调整时,可以从两大的维度考虑:数据服务和应用服务。而这个调整的过程中,需要分清当前哪个点是瓶颈,需要知道哪个点优化的优先级最高。同时,最重要的一点:我们虽然作为技术人员,也应该去学习业务知识,这样我们在考虑问题时分清哪些是业务问题,哪些是技术问题,分清后才能对症下药。你要知道有些问题用技术手段并不比用业务手段更有效。12306的分时卖票就是一个典型例子。

以上总结及思考有不对,欢迎斧正。非常感谢。

Share

软件系统的稳定性

软件系统的稳定性,主要决定于整体的系统架构设计,然而也不可忽略编程的细节,正所谓“千里之堤,溃于蚁穴”,一旦考虑不周,看似无关紧要的代码片段可能会带来整体软件系统的崩溃。这正是我阅读Release It!的直接感受。究其原因,一方面是程序员对代码质量的追求不够,在项目进度的压力下,只考虑了功能实现,而不用过多的追求质量属性;第二则是对编程语言的正确编码方式不够了解,不知如何有效而正确的编码;第三则是知识量的不足,在编程时没有意识到实现会对哪些因素造成影响。

例如在Release It!一书中,给出了如下的Java代码片段:

package com.example.cf.flightsearch; 
//... 
public class FlightSearch implements SessionBean {
    private MonitoredDataSource connectionPool;
    public List lookupByCity(. . .) throws SQLException, RemoteException { 
        Connection conn = null; 
        Statement stmt = null;
        try { 
            conn = connectionPool.getConnection(); 
            stmt = conn.createStatement();

            // Do the lookup logic
            // return a list of results
        } finally { 
            if (stmt != null) {
                stmt.close();
            }
            if (conn != null) { 
                conn.close();
            }
        }
    }
}

正是这一小段代码,是造成Airline系统崩溃的罪魁祸首。程序员充分地考虑了资源的释放,但在这段代码中他却没有对多个资源的释放给予足够的重视,而是以释放单资源的做法去处理多资源。在finally语句块中,如果释放Statement资源的操作失败了,就可能抛出异常,因为在finally中并没有捕获这种异常,就会导致后面的conn.close()语句没有执行,从而导致Connection资源未能及时释放。最终导致连接池中存放了大量未能及时释放的Connection资源,却不能得到使用,直到连接池满。当后续请求lookupByCity()时,就会在调用connectionPool.getConnection()方法时被阻塞。这些被阻塞的请求会越来越多,最后导致资源耗尽,整个系统崩溃。

Release It!的作者对Java中同步方法的使用也提出了警告。同步方法虽然可以较好地解决并发问题,在一定程度上可以避免出现资源抢占、竟态条件和死锁的情况。但它的一个副作用同步锁可能导致线程阻塞。这就要求同步方法的执行时间不能太长。此外,Java的接口方法是不能标记synchronized关键字。当我们在调用封装好的第三方API时,基于“面向接口设计”的原理,可能调用者只知道公开的接口方法,却不知道实现类事实上将其实现为同步方法,这种未知性就可能存在隐患。

假设有这样的一个接口:

public interface GlobalObjectCache {
    public Object get(String id);
}

如果接口方法get()的实现如下:

public synchronized Object get(String id){
    Object obj = items.get(id); 
    if(obj == null) {
        obj = create(id); 
        items.put(id, obj);
    } 
    return obj;
}

protected Object create(String id) {
    //...
}

这段代码很简单,当调用者试图根据id获得目标对象时,首先会在Cache中寻找,如果有就直接返回;否则通过create()方法获得目标对象,然后再将它存储到Cache中。create()方法是该类定义的一个非final方法,它执行了DB的查询功能。现在,假设使用该类的用户对它进行了扩展,例如定义RemoteAvailabilityCache类派生该类,并重写create()方法,将原来的本地调用改为远程调用。问题出现了。由于采用create()方法是远程调用,当服务端比较繁忙时,发出的远程调用请求可能会被阻塞。由于get()方法是同步方法,在方法体内,每次只能有一个线程访问它,直到方法执行完毕释放锁。现在create()方法被阻塞,就会导致其他试图调用RemoteAvailabilityCache对象的get()方法的线程随之而被阻塞。进而可能导致系统崩溃。

当然,我们可以认为这种扩展本身是不合理的。但从设计的角度来看,它并没有违背Liskove替换原则。从接口的角度看,它的行为也没有发生任何改变,仅仅是实现发生了变化。如果不是同步方法,则一个调用线程的阻塞并不会影响到其他调用线程,问题就可以避免了。当然,这里的同步方法本身是合理的,因为只有采取同步的方式才能保证对Cache的读取是支持并发的。书中给出这个例子,无非是要说明同步方法潜在的危险,提示我们在编写代码时,需要考虑周全。

本文原文出自:http://zhangyi.farbox.com/post/stable-software-system

Share