霜天部落 | 专注PHP研发,研究LAMP高性能架构部署与优化

浅谈服务化架构

我在《可扩展架构设计的三个维度》一 文里,谈到服务化架构(SOA)在保证系统扩展性上,是一个比较好的架构设计实践。也谈到了通过服务网关的形式来进行多服务的注册与管理等。但困于篇幅, 并未展开讲关于服务化架构实现层面上的具体细节。本文就结合我这两年来,在服务化架构设计上的一些实践经验,谈谈一个服务化框架其应该具备的一些功能以及 其基本实现方式。

这里说到的“服务”,本质上来说,就是指“RPC”。单纯的RPC功能实现,其实很简单,无非就是client发起调用,中间某个组件(甚至就是 client本身)拦截调用信息,序列化后将信息传输到server端,server端收到调用请求后反序列化,根据请求详细发起实际调用后返回响应传输 回给client端。这样的RPC很常见,比如常见的存储过程调用就是一例。但是在一个复杂的业务环境,如何管理和协同这些大量的RPC才是最麻烦的事 情。所以,在此谈的“服务化”更多指的是对RPC的管理。

一个复杂业务环境下的大量RPC究竟会遇到哪些问题呢?换句话说,一个服务化管理框架究竟应该具备哪些功能特性才算基本完备呢?以下是我的一些看法

1.协议选型

数据序列化

为整个环境里的服务采用统一的数据序列化协议,其益处是显而易见的,能大大降低服务提供者和服务调用者之间的沟通成本,同时也可以为服务提供者减少 应对不同数据协议需求而带来的代码复杂性。所以,在开始设计一个服务化框架时,第一件重要的事情就是选定一个标准的数据序列化协议。如何选择合适的序列化 协议重点需要从扩展性,传输性能以及业界通用性(换句话说就是不同技术/语言的支持程度)三个因素里来协调选择。当前看来,在这三个方面都做的比较好,也是使用最广泛的就是JsonProtobuf了, 基于文本的Json在可读性和灵活性上占优,而基于二进制的Protobuf在传输性能生更胜一筹。而如果整个环境开发的技术栈比较统一,比如全是 Java/.NET,也可以选择对这一技术更加友好的序列化协议。我这一次选择的就是Json,因为从面对的业务情况来看,传输性能不是根本矛盾,而灵活 性要求较高,同时服务使用者使用的技术也较为多样化。

在序列化协议的选定上要避免的一个误区就是采用自定义协议而不是业界通用协议,自定义协议将很容易面临扩展性和使用推广方面的问题,同时,当有新的开发人员加入进来,其需要花费时间来学习与了解。

通讯协议选择

通讯协议上的选择上灵活性比较大,有多种选择,可以在基于HTTP或TCP链接上建立自己的通讯协议。比如可以设计一个简单的header(定长)+body(序列化的请求/响应)。如果采取json作序列化协议的情况下,可以跟我本次的选择一样,采取一个类似json-rpc, 完全基于json的通讯协议:

Resust:

1
2
3
4
5
{
	"ActionName":"Do",
	"AppId":"xxxxxx",
	"RequestContent":{}
}

Response:

1
2
3
4
5
{
	“RequestId”:“xxxxxxxxx”,
	“HasError”:false,
	"ResponseContent":{}
}

对于服务访问对象主要为企业内部的情况,不太建议采取与http完全绑定的restful协议,这将牺牲链接层选择的灵活性。

2.注册与授权管理

注册管理是解决系统交互复杂性的必备良药,我建议超过三个系统之间的系统交互,都应该具备注册管理功能。对于服务化架构来说,注册管理也是最为核心的一项功能。当服务数量和服务使用者数量爆发性增长时,最难回答的问题就是“服务被谁使用了?”以及“有哪些服务可供使用?”,注册管理就是解决这两个问题的最佳方式与实践。

注册管理的实现上其实也很简单,提供一个Config Server(配置中心),收集服务提供者的注册信息(包括服务名称,服务地址(可以多个),版本,超时时间控制等),我们称为服务的元信息。而当服务使用者需要调用相应的服务时,就可以利用这些元信息来查找和调用相应的服务了。

不过,在元信息的使用上,存在两者架构方式

1.服务使用者访问统一的服务中转器,由服务中转器按照注册信息以及负载情况将请求转发到相应的服务地址上。服务执行后,响应信息返回到服务中心,服务中心将响应回送给调用方。

service_2service_2

这种方式的优点是能比较好的控制所有请求的调度。当服务元信息发生变化时,能及时地调整请求转发(负载)与超时控制等。缺点是请求和响应均需要由中转中心负责转发,性能耗费较大。同时,中转中心的可用性也容易产生问题,必须通过集群的方式来解决。

2.服务使用者负责从配置中心获取服务地址等信息,然后有由服务使用者直接向相对应地址上的服务发送请求,请求也直接由服务提供者返回给服务调用者。同时,服务使用者本身可以缓存一定的服务元信息,防止每次访问都要从配置中心获取,以降低配置中心的负载,增强整个系统的可用性。当配置中心的服务元信息发生变化时,通过通知的方式告知服务使用者更新本地缓存。

service_1service_1

这种架构方式与第一种架构相比,能显著降低性能的损耗,以及服务使用者对中心节点的直接依赖。但代价是需要彻底改造服务使用者的调用方式,框架的代码必须侵入到客户端的开发中去。一般会针对不同的客户端提供clientLib,但当客户端实现方式多样化时,这种代价是非常大的。

由于我这次面对的客户端多样性,客户端开发也不在控制范围内,所以选择就是第一种方式。

关于授权,可以与注册管理相互结合,将授权信息同一保存到配置中心。对于企业内部访问的服务,做到通过IP+AppId授权应该就够了。这里有个经验是可以将授权和服务版本确认两者结合起来,即在授权的同时完成服务版本的确定,而不采取由客户端发起访问时指定版本的方式,这样做的好处是框架和服务提供者对于服务版本变更和灰度发布具有更高的可控制性。

3.路由与过载保护

《可扩展架构设计的三个维度》一文里谈到通过单元化架构以满足Z轴扩展,以满足差异性的需求或者做到安全隔离。而服务路由是实现这种单元化架构的基本保障,以保证能将来自不同访问者请求或者不同的请求内容,分发到不同的服务提供区域去,形成单元化架构的闭环。当然,路由功能并不一定需要框架来独立实现,业界许多通用的(软)负载均衡器可以协助实现,如Nginx/HAProxy/LVS这些。但是这类通用的负载均衡软件的问题是路由算法比较通用,当需要扩展到与业务逻辑相关的路由绑定时,比较麻烦,比如需要用户ID按权重分配路由。在此建议,可以采取通用的负载均衡软件当第一层接入,而在服务节点之间采取自己实现路由模块的方式。而在实现路由模块时,需要将扩展性上的考虑放在第一位。

对于服务化架构,保障提供服务提供者的业务系统不受“恶意”调用或突发性激增调用的破坏,过载保护功能至关重要,它能起到系统“保险丝”的效果。前文提到可用于接入的Nginx/HAProxy/LVS这些软件,也多少提供了过载保护的功能。如果自己实现过载保护模块,具体可参见我的《过载保护算法浅析》一文。对于过载保护的一个经验是:过载保护越靠近服务访问前端越好。

4.服务拆分与组合化

传统的SOA概念,指的是不同的应用系统之间相互通过大粒度服务的方式进行集成。而当今的服务化架构已经摆脱了这一概念的束缚,更多讲的是系统内部模块级甚至是功能级的服务化模式。也就是说服务实现的粒度更小了。这当然为应用和服务的实现带来了更强的灵活性,服务交付周期也大大缩短了。但这样的细粒度拆分服务,带来的问题是项功能的实现需要访问的服务数量成倍的增加。如下图所示:一个客户下订单的功能实现需要分别访问:客户信息服务,产品类别服务,库存服务,订单管理服务等。

service_3service_3

这将显著增加功能实现的复杂性。为了解决这一问题,我们只能再次使用那条永远有效的“中间层定律”:任何计算机问题都可以通过中间加一层来解决。 我们可以将相应的服务组合成一个新的服务提供出去,比如上面的例子,我们可以按以下方式组合:

service_4service_4

5.基于配置的服务运行时提供

前文已经概述了一个服务化框架应该具有的一些基本功能以及一些基本的架构实现方式。但这个服务框架究竟如何与业务开发相结合呢?也就是说业务逻辑代码与框架代码之间如何隔离,而不是让框架的功能代码侵入到业务逻辑代码的开发中来?这里通用的做法就是通过基于配置,由框架提供运行时,动态加载业务代码的方式。做到这点,只需要约束业务逻辑代码实现相应的接口/基类,然后打包成相应的组件(如jar/dll/so等)提供给框架加载运行即可,类似于java servlet的开发,业务开发完全不用关心服务化框架任何功能,专注开发业务逻辑即可。同时,对于既有代码的服务化也将变得简单,只需要稍加重构封装出实现相应的接口即可。

配置类似于:

1
2
3
4
<service serviceName="Customer.GetCustomer">
	<biz imp="com.customer.getCustomer">
	<biz>	
<service>

同时,这种基于组件配置的服务实现,对于组合组件实现服务也非常简单。只需要将上面的配置改为嵌套的方式既可以实现组合。比如对于订单生成服务只要组合如下:

1
2
3
4
5
6
7
8
<service serviceName="Order.CreateOrder">
	<biz imp="com.inventory.checkInventory">
		<biz imp="com.order.createOrder">
			<biz imp="com.inventory.updateInventory">
			</biz>
		</biz>
	<biz>	
<service>

总结:

基本来看,服务化架构已经在业界完成了落地,尤其是互联网公司,更是基于这一架构的领先者,有许多经验值得借鉴。当然,这个落地的服务化架构,与当年被各大商业公司用WS-*和ESB玩坏的SOA概念相去甚远。也再一次证明,那些被鼓吹出来的技术概念,只有当那些商业公司不再炒作之时,方是其真正落地之日(SOA如此,当今热炒的“大数据”,“云计算”这些概念又何尝不会是如此呢?)。在技术被鼓吹得风头正劲时,千万要保持冷静,别被那些商业公司所忽悠,你完全可以自己实现更轻量级更具有扩展性的架构。不信的话,可以去问问,那些当年花大价钱去买SOA商业组件的公司,他们还好吗?