这两年来,在服务化架构设计上的实践比较多,在此对关于服务化设计一些经验稍作总结, 知识经验水平有限,如有欠缺和不准确的地方,还请指出修正! 我在《可扩展架构设计的三个维度》一文里,谈到服务化架构(SOA)在保证系统扩展性上,是一个比较好的架构设计实践。也谈到了通过服务网关的形式来进行多服务的注册与管理等。但困于篇幅,并未展开讲关于服务化架构实现层面上的具体细节。本文就结合我这两年来,在服务化架构设计上的一些实践经验,谈谈一个服务化框架其应该具备的一些功能以及其基本实现方式。 这里说到的“服务”,本质上来说,就是指“RPC”。单纯的RPC功能实现,其实很简单,无非就是client发起调用,中间某个组件(甚至就是client本身)拦截调用信息,序列化后将信息传输到server端,server端收到调用请求后反序列化,根据请求详细发起实际调用后返回响应传输回给client端。这样的RPC很常见,比如常见的存储过程调用就是一例。但是在一个复杂的业务环境,如何管理和协同这些大量的RPC才是最麻烦的事情。所以,在此谈的“服务化”更多指的是对RPC的管理。 一个复杂业务环境下的大量RPC究竟会遇到哪些问题呢?换句话说,一个服务化管理框架究竟应该具备哪些功能特性才算基本完备呢?以下是我的一些看法 1.协议选型 数据序列化 为整个环境里的服务采用统一的数据序列化协议,其益处是显而易见的,能大大降低服务提供者和服务调用者之间的沟通成本,同时也可以为服务提供者减少应对不同数据协议需求而带来的代码复杂性。所以,在开始设计一个服务化框架时,第一件重要的事情就是选定一个标准的数据序列化协议。如何选择合适的序列化协议重点需要从扩展性,传输性能以及业界通用性(换句话说就是不同技术/语言的支持程度)三个因素里来协调选择。当前看来,在这三个方面都做的比较好,也是使用最广泛的就是Json和Protobuf了,基于文本的Json在可读性和灵活性上占优,而基于二进制的Protobuf在传输性能生更胜一筹。而如果整个环境开发的技术栈比较统一,比如全是Java/.NET,也可以选择对这一技术更加友好的序列化协议。我这一次选择的就是Json,因为从面对的业务情况来看,传输性能不是根本矛盾,而灵活性要求较高,同时服务使用者使用的技术也较为多样化。 在序列化协议的选定上要避免的一个误区就是采用自定义协议而不是业界通用协议,自定义协议将很容易面临扩展性和使用推广方面的问题,同时,当有新的开发人员加入进来,其需要花费时间来学习与了解。 通讯协议选择 通讯协议上的选择上灵活性比较大,有多种选择,可以在基于HTTP或TCP链接上建立自己的通讯协议。比如可以设计一个简单的header(定长)+body(序列化的请求/响应)。如果采取json作序列化协议的情况下,可以跟我本次的选择一样,采取一个类似json-rpc, 完全基于json的通讯协议: Resust: { 'ActionName':'Do', 'AppId':'xxxxxx', 'RequestContent':{} } 2.注册与授权管理 注册管理是解决系统交互复杂性的必备良药,我建议超过三个系统之间的系统交互,都应该具备注册管理功能。对于服务化架构来说,注册管理也是最为核心的一项功能。当服务数量和服务使用者数量爆发性增长时,最难回答的问题就是“服务被谁使用了?”以及“有哪些服务可供使用?”,注册管理就是解决这两个问题的最佳方式与实践。 注册管理的实现上其实也很简单,提供一个Config Server(配置中心),收集服务提供者的注册信息(包括服务名称,服务地址(可以多个),版本,超时时间控制等),我们称为服务的元信息。而当服务使用者需要调用相应的服务时,就可以利用这些元信息来查找和调用相应的服务了。 不过,在元信息的使用上,存在两者架构方式 1.服务使用者访问统一的服务中转器,由服务中转器按照注册信息以及负载情况将请求转发到相应的服务地址上。服务执行后,响应信息返回到服务中心,服务中心将响应回送给调用方。 这种方式的优点是能比较好的控制所有请求的调度。当服务元信息发生变化时,能及时地调整请求转发(负载)与超时控制等。缺点是请求和响应均需要由中转中心负责转发,性能耗费较大。同时,中转中心的可用性也容易产生问题,必须通过集群的方式来解决。 2.服务使用者负责从配置中心获取服务地址等信息,然后有由服务使用者直接向相对应地址上的服务发送请求,请求也直接由服务提供者返回给服务调用者。同时,服务使用者本身可以缓存一定的服务元信息,防止每次访问都要从配置中心获取,以降低配置中心的负载,增强整个系统的可用性。当配置中心的服务元信息发生变化时,通过通知的方式告知服务使用者更新本地缓存。 这种架构方式与第一种架构相比,能显著降低性能的损耗,以及服务使用者对中心节点的直接依赖。但代价是需要彻底改造服务使用者的调用方式,框架的代码必须侵入到客户端的开发中去。一般会针对不同的客户端提供clientLib,但当客户端实现方式多样化时,这种代价是非常大的。 由于我这次面对的客户端多样性,客户端开发也不在控制范围内,所以选择就是第一种方式。 关于授权,可以与注册管理相互结合,将授权信息同一保存到配置中心。对于企业内部访问的服务,做到通过IP+AppId授权应该就够了。这里有个经验是可以将授权和服务版本确认两者结合起来,即在授权的同时完成服务版本的确定,而不采取由客户端发起访问时指定版本的方式,这样做的好处是框架和服务提供者对于服务版本变更和灰度发布具有更高的可控制性。 3.路由与过载保护 在《可扩展架构设计的三个维度》一文里谈到通过单元化架构以满足Z轴扩展,以满足差异性的需求或者做到安全隔离。而服务路由是实现这种单元化架构的基本保障,以保证能将来自不同访问者请求或者不同的请求内容,分发到不同的服务提供区域去,形成单元化架构的闭环。当然,路由功能并不一定需要框架来独立实现,业界许多通用的(软)负载均衡器可以协助实现,如Nginx/HAProxy/LVS这些。但是这类通用的负载均衡软件的问题是路由算法比较通用,当需要扩展到与业务逻辑相关的路由绑定时,比较麻烦,比如需要用户ID按权重分配路由。在此建议,可以采取通用的负载均衡软件当第一层接入,而在服务节点之间采取自己实现路由模块的方式。而在实现路由模块时,需要将扩展性上的考虑放在第一位。 对于服务化架构,保障提供服务提供者的业务系统不受“恶意”调用或突发性激增调用的破坏,过载保护功能至关重要,它能起到系统“保险丝”的效果。前文提到可用于接入的Nginx/HAProxy/LVS这些软件,也多少提供了过载保护的功能。如果自己实现过载保护模块,具体可参见我的《过载保护算法浅析》一文。对于过载保护的一个经验是:过载保护越靠近服务访问前端越好。
|
|
来自: openlabzeng > 《待分类》