注:本文来源于我给公司内部发的邮件中,所以背景都是基于我们现在的应用,而且思路也很混乱,请大家见谅。
自05年开始接触到分布式架构,06年在原先的基础上从头开始设计了一套分布式架构,当时SOA这个概念也没这么火。整个大平台的开发、性能和可扩展性都得到了考验,觉得有一些东西想和大家一起分享。
我不知道我所说的这些算不算真正的SOA,我也没读过什么SOA的书籍,我觉得SOA这个概念非常抽象,任何概念的产生都是由原因的。因此,我也不会说一些抽象的原则,只是想说一些在过去几年实施“SOA”过程中的一些心得和一些细节,希望对大家有用。
不说什么是SOA,先来说说我们现有架构遇到的一些问题:
l 同样一个逻辑,A系统(ASP)使用COM实现一份(我们现在的JAVA库),B系统(.NET)在开发的时候觉得调用JAVA也是很麻烦的,索性自己实现一份逻辑,可能是存储过程,也可能是在代码中硬编码SQL,C系统(由系统部门开发的.NET程序)也用到了相同的逻辑,由于和产品部门缺乏必要的沟通,也实现了一份逻辑。最后,如果这个相同的逻辑需要修改,则需要修改A、B、C三个系统,一份逻辑在三个地方实施有几个缺点,一来是因为如果需要修改要改三个地方,二来是增加了工作量,三来是占用了不必要的资源(因为大家可能都实现了自己的缓存)。
l 所有网站全部部署在一个WEB服务器上,直接实现负载均衡。这样的方案有几个缺点,一是几乎没一个网站程序都有自己的缓存,每台服务器的缓存是冗余的,而且甚至每个网站中的缓存都有重复,二是如果一个网站有问题会影响到其它网站(比如进程回收,应用程序池不能100%解决这个问题,而且我们现在并没有注重应用程序池的划分,又比如某些应用是长请求、过长时间占用线程),三是不能有效利用服务器资源,这是因为不是每一个网站都具有相同的访问量,不是每一个网站都需要相同的资源(有些需要特别多IO、有些需要特别多CPU、有些甚至是内存)。
虽然说我们现在是使用了三层架构,但并没有什么重用,而且所有的层还是部署在相同的服务器上的。为了解决前面的2大问题,我们首先想到了:
l 是不是有什么方法可以让相同的逻辑被其它系统重用?
l 是不是考虑把逻辑以服务的形式对外部(其他模块)公开?
由此引入面向服务架构的概念,我们通过这些公开的服务进行逻辑的重用,提高系统性能也降低了模块之间的耦合性。架构图见我以前写的文章http://www.cnblogs.com/lovecherry/archive/2008/06/18/1224496.html
如果确实采用这种架构,我们的开发方式会有什么改变呢?
l 在一般情况下,我们一般认为A系统对应A数据库,B系统对应B数据库,每一个系统都有自己的数据库。传统的方式是A系统和B系统在数据库端直接使用JOIN进行交叉耦合。如果实施SOA的话,最佳实践应该是对于大多数系统来说,禁止A系统直接访问B系统的数据库,反之亦然。我要你的数据,就必须调用你公开的服务。而且这个服务或者说接口或者说契约,尽量是粗粒度的。比如一个逻辑包括X和Y和Z三 个过程,如果独立提供三个方法的话,每一次方法调用都是网络调用的过程,性能比较低,而且更重要的原因,这个模块提供了这么细粒度的三个方法,如果这三个 构成了一定逻辑的话,很有可能这个逻辑就在调用方和提供方两个模块都实现了相同的逻辑。虽然说确实是提供了服务,但是没有达到封装逻辑这个重要的目的,而 且也产生了性能的下降,这种服务就显得很不值得。
l 一 个大系统有许多模块或者说子系统,每一个都有自己的复杂逻辑和存储结构。开发人员可能只熟悉自己的那一部分,如果我的系统确实要用到其它系统的数据,按道 理是应该想到调用它提供的程序集。很多时候我们并没有这么做,是因为我们并不知道对方有没有提供我需要的功能,即使知道提供了也不知道怎么去使用,如果要 去用的话可能熟悉对方系统的时间会比直接在数据库中进行JOIN需要的时间还要长。这是一种恶性循环,因为这样又导致了一个逻辑在多处出现,一份数据在多处取得、保存和缓存。有了SOA,我们应该能在SHAREPOINT或WIKI上看到一份清单,在哪个HTTP或TCP端 点上具有什么服务,服务中有哪些方法,这些方法是干什么的,返回什么,传入什么,使用的注意事项,如果我开发的系统需要用到外部的数据,我第一时间想到的 不应该是去看数据库中我需要的数据在哪里,而是应该是去看看是否对方系统提供了这样的服务,如果没有提供的话则和服务的提供者进行一些沟通。你可能会问, 我作为系统的开发者,我也不知道要对外提供哪些服务,我也不知道别人需要用到什么,确实是,但是至少我们现在可以做的是把内部使用到的一些逻辑以服务形式 对外公开,一般来说自己系统的这些函数如果能满足自己要求的话从功能上来说可以满足别人要求,只不过别人可能需要的并不是这么多罢了。
l 我们现在做TECH SPEC的时候可能关注内部的实现,如果是SOA的话,我们就需要关注现在在做的这个系统会用到别人的哪些接口,别人可能会用到我什么接口,我需要公开出来。可能还会考虑,我用了ABC三 个系统的接口,是否需要把这个逻辑以粗粒度服务公开出来。每一个公开接口的参数、返回都需要仔细考虑,把这些都列入到架构设计中,和架构师一起完成服务的 定义。开发人员可能只对自己系统的接口和逻辑比较熟悉,架构师的作用是给开发人员建议,哪些接口你可能需要,哪些接口你可能需要对外提供,是否需要做缓 存。
l 我们现在的部署是非常简单的,如果实施了SOA, 很有可能一个系统需要调用十几个外部子系统的接口,每一个接口都需要制定地址、调用策略以及契约。地址和调用策略需要是可配置的,一般定义在配置文件中或 者数据库中,这样一个系统的部署可能非常复杂,打个比方就像芯片的引脚一样,有很多,一个引脚没有接到合适的地方,系统就不能工作。虽然部署负责了,但是 系统之间的耦合非常小的,大家只是依赖于某个网络环境中的地址,依赖于某个契约。这样的话,系统的伸缩性就很强,有些服务需要很高的资源,就给独立的服务 器,有些服务占用资源很小,可以合并在一起。当然,也可以根据服务的性质,比如特别需要IO、特别需要CPU来分配到合适的服务器上。服务器并不一定是一样的,有的服务器内存特别大,有的服务器CPU特别好。
l 还 会遇到一个问题,就是开发人员不太愿意调用其它的接口。一是对别人的东西往往默认会觉得是实现糟糕的,性能很差的,二是觉得不放心,会不会到时候你没给我 正确的数据,影响我的开发,产生相互推卸责任的问题。其实,这种想法不对,一个人不可能开发系统的全部,作为使用者来说要使用别人的接口信任别人的接口, 作为接口提供者来说需要积极对自己的接口负责,进行完善的单元测试,如果调用者有特别的需求在讨论后进行改进。这也就是说引入SOA的话,我们需要更多的沟通。
说的有点乱,接下来想说说我在实施过程中遇到的一些细节问题,很多时候SOA实施的失败都是因为一些细节。
l SOA的 接口设计必须基于业务的。架构师应该是一个总导演,对所有系统的业务都有一个认识,理解业务之间的关系,和开发人员一起定义合理的接口。这包括,接口是否 代表了业务、是否是合适的粒度、是否会有性能问题,别小看接口设计,一旦确定以后很难修改,接口的好坏决定成败,我列为第一要素。
l 在实施中,管理很关键,有许多要点是需要有强制的。比如除非特殊需要不能直接引用其它数据库,即使是本系统也应该引用本系统的服务,也就是说网站项目里面没有连接字符串、没有数据访问逻辑,只有端点的配置。还比如是A系统的开发人员有这个责任为A系统对外的所有接口进行后续的维护和功能扩展,不是说A系统结束了,我的数据我也不管,不行,别人如果需要的数据确实是我的数据的话,我就要管。
l 从性能角度考虑,一般内网中的服务通讯采用TCP(二进制序列化),公网的走HTTP。还有一种方式是IPC方式,进程间通讯,我们以前也用的挺多的,虽然说缓存应该在一个地方建立。但很多时候,某个方法的调用是每次访问页面都需要进行一次或多次的,如果再进行网络调用的话性能很成问题,比如论坛上的脏话过滤,如果某个脏话服务提供了脏话过滤的接口,如果这个这个接口需要TCP调用的话性能不高,这个时候我们会考虑把这个服务部署在WEB服务器上,而不是APP服务器上,IPC方式进行进程间通讯。
l 到最后我们会发现我们有20个子系统,也就至少有20个服务(一般是以Windows服务部署在APP服务器上)。而且如果服务部署多份做负载均衡的话,可能就有上百个网络地址。一个网站如果引用了5个服务,就需要一个一个IP地 址(或者说端点)进行配置,如果将来服务迁移,那么这些网站的配置文件修改是一个大问题。虽然说我们会有一个拓扑图来描述网站之间服务的依赖情况,但是配 置文件的修改工作量不小而且容易出错。推荐的做法是有一个数据库来存放所有服务的端点定义、描述,使用一个单独的服务来提供所有服务的端点信息,在网站中 只需要配置这个服务的端点信息,然后引用各种服务的契约就可以了。考虑到效率关系,在每一个WEB服务器上都安装有这个配置服务,以IPC方式提供所有网站进行调用,当然,其中的端点信息都会做缓存。
l 前面说的是网站引用服务的端点信息配置问题,还有一个问题就是服务的健康监视问题,服务是以Windows服务形式运行的,我们需要检测Windows服务的状态,占用的CPU和内存信息。我们在每一个APP服务器上又装有一个AGENT服务,专门用于监视这些服务进程的情况,一旦发现问题会第一时间通知网络部门甚至是开发人员,当然服务无响应的情况比较少,一般常见的情况是服务没有正确部署(缺少DLL不能启动),或者是占用了过多的内存。这个AGENT的另外一个作用就是收集服务器上的错误日志,并且负责更新和重启服务。也就是说所有服务的更新是自动化的更新,我们发布的时候发布到指定的RELEASE服务器,由专门的管理工具进行服务的自动更新,服务如果做负载均衡的话手动更新很麻烦。而且很多情况下相同的服务需要在多个服务器部署,比如IPC服务 。
l 服务的实例管理问题,一般有SINGLETON和SINGLECALL等方式。推荐采用SINGLECALL方式,如果服务具有状态的话会大大增加服务的开发难度,一是需要考虑线程同步问题,二是需要考虑生命周期。其实,我一直觉得有状态的服务配合ORM数据访问层的使用是很好的,但做起来可能会遇到很多问题。
l 在存储过程中做事务还是很简单的,SOA如 果要做分布式事务的话,实现不是打大问题,但性能是很大的问题,很容易引起数据库对象的死锁。大多数情况下我们对分布式事务的替代方式是采用队列,放到队 列中的东西就认为是一定可以成功的,对于不使用队列的情况,如果调用失败了则记录日志,不会进行回滚。除非涉及到钱的地方才做分布式事务。
l 版本问题也是需要考虑的。传统的实现方式是,如果新增接口的话以前的程序是没有影响的,如果万不得已需要修改接口的话就大吼一声,我这个接口要改了,然后给大家发一份,请调用者按照我的要求修改一下,连同我的服务一起重新发布。对于WCF的话,有MEX元数据交换,这样就不用手动引用新的契约,只需要从HTTP地 址上更新新的服务即可。但有一个不得不考虑的问题就是万一在调用其它服务的时候发生错误了,我们怎么知道是哪个服务的版本出现问题了呢。我们是这样做的, 调用服务的代理有一层封装,如果网络调用发生错误的话,会记录详细的错误信息,哪个服务(服务的版本)、哪个方法在哪个端点上的调用、传入的参数是什么, 返回的是什么。这样,在部署的过程中发生错误或是版本问题,我们就很容易知道错误的原因了。那么怎么知道服务的版本呢?没一个服务都约定有一个GetVersion方法,这个方法两个作用,一是给管理工具测试这个服务是不是还有效,二是给配置服务获取服务的版本号。
l 如果引入SOA的话,不可避免的是大大增加了整个系统的部署复杂性。想一下,在LIVE上,我们可能有20个WEB服务器,10个APP服务器(我们以前是2:1这样进行配置的,服务按照类型和资源不同放在不同的APP服务器,WEB服务器CPU特别强,APP服务器是廉价服务器,有足够大的内存,对于WEB程序来说确实没什么大的CPU计算,服务也就是缓存比较厉害)。10个APP服务器上放100个服务,就产生了100个端点地址。开发人员其实不用考虑服务在LIVE上 怎么部署的,需要架构师在部署的时候协调部署团队一起完成部署工作,架构师知道哪个服务部署在哪个服务器最合理,以哪种信道进行部署最合适。部署团队需要 对各个服务器的结构很清楚,也要学会使用健康监控工具(我前面提到的监控服务运行状态的工具和异常管理工具来发现部署上的问题),这就对部署团队的要求更 高了。实在不行的话可以要求开发人员参与,开发人员应该没权限直接进行服务的部署,但是有权使用两个工具的,一来用于排查问题,二来也会有很大的成就感。
l 服务监控工具以一个拓扑图形式展现整个网络上WEB服务器、APP服务器的IP地址、CPU内存使用,以及服务的引用情况和每一个服务对CPU和内存的占用,以及服务的健康情况。监控人员24小时值班负责监控,出问题的时候及时重新启动相关服务。异常管理工具每天收集各个系统的异常,开发人员每天上班和下班的时候查看一次这个工具,知道自己负责的模块产生了哪个异常(异常有几种,一种是程序有BUG,一种是外部有人在攻击造成的,还有一种是前面说的服务的版本和运行问题产生的异常)。当然,架构师也应该时刻关注这2个工具,知道整个系统的健康情况。
l 从网络上来说,APP服务器应该跨2个网段,DB服务器应该外部不能直接访问,所有数据库操作都是通过APP服务器提供的服务访问DB的,网站不具有任何连接字符串不能直接访问DB服务器(不在同一网段)。WEB服务器和APP服务器之间应该走TCP,如果是跨国的话应该有VPN链路。
l 从软件架构上来说,我们以前使用的是.NET Remoting对内,Web服务对外的方式,中间层以Windows服务作为载体。对于.NET 3.5时代,可以采用WCF+Windows服务承载的方式。WCF支持元数据交换是一个优点,另一个优点是方便TCP/HTTP各种绑定的切换,当然也可以同时提供多份绑定。如果是细粒度服务的话,可以使用ADO.NET DATA SERVICE+ADO.NET EF的方式直接进行提供。表现层可以直接使用ADO.NET DATA SERVICE也可以使用自定义的JSON/XML数据,不属于SOA的范畴就不扩展了。
总结一下:
l 对于ETOWN的应用,SOA架构是绝对适合的。
l 实施SOA,技术不是问题,最大的问题还是管理和沟通(协作)。
l 实施SOA的话,我们需要重新构建一套基础架构,提供统一的端点配置管理、异常管理、健康监控、契约协作平台等。不然到最后的配置和管理将会很混乱。
l 我们不需要更多的服务器,但我们需要更合理的分配服务器。而且整个系统的部署架构可能随着时间的迁移不断调整的,合并压力小的服务,拆散压力大的服务。怎么做负载均衡,这都是一直调整的。
l 实施SOA需要开发人员有更好的意识,包括协作意识,包括架构意识。当然,也需要架构师能够参与到每个项目中去,心中对业务有大局观。
l 配置可能是复杂的,需要有工具来确保这个复杂的过程。服务和网站怎么配置是很重要的事情,需要架构师和开发人员讨论决定,不是说随便放上去能用就可以。
l 在做架构的时候要站在较高的角度来看,眼光要长远,暂时的性能问题不一定是问题。按照网络/磁盘/内存/CPU的层次来考虑。很多时候SOA和性能是有冲突的,就像不能一味觉得ORM性能不好就不去使用。考虑80/20原则,任何东西满足了80的应用就达到了目的,剩下的20进行特殊处理。
补充几点:
l 从服务契约设计上来说,应该让这个契约尽可能独立,不依赖于其它的契约。服务的调用过程不能依赖于服务本身的状态,应该在任何情况下服务的作用不会由环境所变。
l 虽然说是内部实施,但最好数据契约的类型是标准类型,通用类型,原生类型,方便以后做开放API的时候真正实现对外的服务。其实SOA强调松耦合性,因异构平台协作的需求产生,强调一切基于消息,无关服务平台。
l SOA的实现包括业务分析、契约规划、服务管理、消费者管理、文档管理、配置管理、迭代整合等重要步骤,不是说我要建立A契约就建立,我要消费A契约就消费,要体现在管理中。我一直觉得SOA不是组件升级到服务这么简单,软件开发流程、管理流程、设计分析方法、配置管理都会有巨大的改变,前期准备要充分。
l 在建立前期需要制作完成一个基础的架构供开发人员使用。比如说是不是需要扩展VS内建的WEB引用,使得生成的客户端代理包括异常处理、端点配置(从配置服务中获取)等内容。非业务相关异常的收集和发送等工作应该是自动的,无需编码。由于调用的复杂性,服务可能还会调用服务,在部署的时候配置问题很难察觉,所以其中的异常处理非常重要。
l 项目完成后的代码审阅需要对项目中用到的服务进行逐一检查,如果有改变需要在拓扑图(推荐有专门管理软件来维护这个拓扑图,并且还能检查各个节点的健康状态)中进行修改。必要的话还可以为每一个节点实现信任机制,得不到信任的消费者将不能消费。甚至这个工具可以和SHAREPOINT结合,也提供文档的管理。
l 最理想的方式是实现服务的自动部署,全部依靠管理工具来完成,因为在服务器上安装Windows服务,需要复制文件、卸载原来的服务、安装新的服务、服务运行账户的配置,如果服务达到上百个的话,部署的压力也比较大。
l 最后,什么是SOA?个人觉得SOA是提供了系统级的松散组合和重用的基于消息的整合方案。
推荐阅读: 《可伸缩性最佳实践:来自eBay的经验》
看图说话:
1、web服务器做负载均衡。即使是做负载均衡也要按功能分割,只有分割了才能有伸缩性。
2、app服务器说实话压力应该不会很大,主要是内存占的多一点。所以一般情况下没有必要做负载均衡,而且一般情况下一个物理
服务器上安装多个服务。比如APP1
如果性能不行的话可以一个物理服务器安装一个服务,比如APP2。如果要做负载均衡的话可以随机选择端点、可以按照主键范围选
择服务等方案。
3、主DB采用SAN和磁盘阵列。其它还有一些类型的服务器,比如外部资源(CDN、google搜索)、文件服务器(不是所有的资源都
需要传到CDN,CDN延时比较大),一般的图片转到专门的文件服务器即可,开放FTP供上传,对外开放HTTP。还有比如邮件服务器
等。
这和SOA没什么关系,只不过提一下物理上的架构。
好,现在拿一个网站做例子,比如认证网站,提供了用户的注册,用户信息向其它区域服务器(比如游戏服务器)激活等功能。上
图想体现的几点是:
1、前面说的,对性能要求特别高的服务,可以以IPC方式进程间通讯。服务当然也就要安装在本地了,无论是WEB服务器还是APP服
务器。
比如说用户在注册的时候需要对某些关键词进行过滤,这个关键词的数据保存在keyword DB,由keyword Service提供关键词的过
滤服务,虽然说在这个服务里面会缓存所有关键词的列表,但是如果需要TCP调用服务的话,跨越网络的性能还是很差的。所以哪
里主机需要,服务就安装在哪个主机上。
2、服务有两种类型,一种是业务服务,比如Passport Service,其余绿色的全部属于基础服务。比如说每一个机器都会有
endpoint service,提供了所有服务的端点信息,我们根据key去定位到服务,如果服务地址需要改,只需要在endpoint db中改变
端点信息,然后重新启动所有机器上的endpoint service即可(为了刷新缓存)。说到缓存刷新方式,一般从来不变的数据可以采
取被动刷新,变化慢的数据可以采取定时主动刷新。那么,重启每一个机器上的基础服务,比如前面说的endpoint服务也很麻烦,
怎么办?每一个安装服务的机器还提供了service management agent服务,这个服务前面说过,一是用于管理机器上的各种服务,
二是用于监控它们,三是这些数据可以汇总到管理C/S程序上(第一个蓝色方块),提供统一的服务管理。还有一种必要的基础服
务就是exception service,提供了统一的记录异常的功能,当然也会有一个异常管理的B/S系统来管理这些异常。
3、每一个服务中都有一个粉红色小方块代表缓存。我想是的说缓存可以在各个层次去做,不一定只有app服务器可以做缓存。sql
server 你给它多点内存就是缓存多点,app上可以做数据的缓存,web服务器上可以做页面的缓存。你可能奇怪,记录异常的服务
不就是把异常插入到数据库吗,缓存什么呢。其实很多异常是重复的,比如有人在攻击你产生的异常,我们在内存中维护不同类型
的异常,相同类型的异常在短时间内比如1小时,不会提交到数据库去记录。
4、再次要提一下一个原则,一个数据库上的服务就像一个罩子,罩在了DB上,其它服务不能穿透这个罩子直接访问下面的数据库
,要访问它的数据只能通过这个服务提供的接口。比如这里的passport service,一来是给passport website去用的,而其它很多
网站也需要会员信息的数据啊?怎么办,就从passport service去拿,不能直接到数据库中去拿。
5、图中棕色的部门是一个异步队列。这里体现了《可伸缩性最佳实践:来自eBay的经验
》中的第四点那就是用异步策略去解耦程序。按照前面一个原则,假设有这么一个场景。用户在中心数据库的数据需要插入到各个
区域的数据库(比如用户激活游戏的过程就是把用户注册时的信息,插入到游戏的数据库中,游戏的数据库在全国各地的)。要访
问游戏数据库,我们肯定是通过专有的game service去做的,不会去直接连接游戏数据库。问题就来了,如果local game db不可
用,game service也不用,而passport service会调用game service去激活用户,那么
passport service这个功能也不可用吗?这2个服务之间产生了很大的依赖,如果在数据库中有一个队列,passport service只需
要把任务信息提交到这个队列表中,这样就可以认为完成了激活任务。那么passport service和game service之间没有直接的依赖
。而game service作为一个队列服务,只需要定时从对列表中获取需要激活的信息,然后真正操作game db去做这个激活就可以了
。完成后再去队列表更新状态就可以了。通过一个异步过程来解除服务之间的耦合。