您现在的位置是:首页 > 文章详情

探索分布式服务框架Dubbo2:以RPC为基础的微服务

日期:2018-06-19点击:314

为什么说要搞定微服务架构,先搞定RPC框架?

原创:  58沈剑  架构师之路  2016-08-25

第一章聊了【“为什么要进行服务化,服务化究竟解决什么问题”

第二章聊了【“微服务的服务粒度选型”

今天开始聊一些微服务的实践,第一块,RPC框架的原理及实践,为什么说要搞定微服务架构,先搞定RPC框架呢?


一、需求缘起

服务化的一个好处就是,不限定服务的提供方使用什么技术选型,能够实现大公司跨团队的技术解耦,如下图:


服务A是欧洲团队提供服务,欧洲团队的技术背景是Java,可以用Java实现服务;

服务B是美洲团队提供服务,可以用C++实现服务;

服务C是中国团队提供服务,可以用Go实现服务;

服务的上游调用方,按照接口、协议即可完成对远端服务的调用。

 

但实际上,99.9%的公司的团队规模有限,技术团队人数也有限,基本是使用同一套技术体系来调用和提供服务的:


这样的话,如果没有统一的服务框架,RPC框架各个团队的服务提供方就需要各自实现一套序列化、反序列化、网络框架、连接池、收发线程、超时处理、状态机等“业务之外”的重复技术劳动,造成整体的低效。所以,统一RPC框架把上述“业务之外”的技术劳动统一处理,是服务化首要解决的问题

 

在达成【“使用统一的RPC框架”是正确的道路】这个一致的前提下,本文期望用简单通俗的言语简述一下一个通用RPC框架的技术点与实现。

 

二、RPC背景与过程

什么是RPCRemote Procedure Call Protocol),远程过程调用?

先来看下什么是本地函数调用,当我们写下:

int result = Add(1, 2);


这段代码的时候,我们知道,我们传入了12两个入参数,调用了本地代码段中的一个Add函数,得到了result出参。此时,传入数据,传出数据,代码段在同一个进程空间里,这是本地函数调用

 

那有没有办法,我们能够调用一个跨进程(所以叫远程,典型的,这个进程部署在另一台服务器上)的函数呢


 

最容易想到的,两个进程约定一个协议格式,使用Socket通信,来传输【入参】【调用哪个函数】【出参】

假设请求报文协议是一个11字节的字节流:


1)前3个字节填入函数名

2)中间4个字节填入第一个参数

3)末尾4个字节填入第二个参数

同时可以设计响应报文协议是一个4字节的字节流:


即处理结果。

 

调用方的代码可能变为:

request = MakePacket(“add”, 1, 2);

SendRequest_ToService_B(request);

response = RecieveRespnse_FromService_B();

int result = unMakePacket(respnse);

简单解释一下:

1)讲传入参数变为字节流

2)将字节流发给服务B

3)从服务B接受返回字节流

4)将返回字节流变为传出参数

 

服务方的代码可能变为:

request = RecieveRequest();

args/function = unMakePacket(request);

result = Add(1, 2);

response = MakePacket(result);

SendResponse(response);

这个过程也很好理解:

1)服务端收到字节流

2)将字节流转为函数名与参数

3)本地调用函数得到结果

4)将结果转变为字节流

5)将字节流发送给调用方

 


这个过程用一张图描述如上,调用方与服务方的处理步骤都是非常清晰的。这个过程存在最大的问题是什么呢?

回答:调用方太麻烦了,每次都要关注很多底层细节

1)入参到字节流的转化,即序列化应用层协议细节

2socket发送,即网络传输协议细节

3socket接受

4)字节流到出参的转化,即反序列化应用层协议细节

 

能不能调用层不关注这个细节呢?

回答:可以,RPC框架就是解决这个问题的,它能够让调用方“像调用本地函数一样调用远端的函数(服务)”

 

三、RPC框架职责

通过上面的讨论,RPC框架要向调用方屏蔽各种复杂性,要向服务提供方也屏蔽各类复杂性

1)调用方感觉就像调用本地函数一样

2)服务提供方感觉就像实现一个本地函数一样来实现服务

所以整个RPC框架又分为client部分与server部分,负责把整个非(1)(2)的各类复杂性屏蔽,这些复杂性就是RPC框架的职责。


再细化一些,client端又包含:序列化、反序列化、连接池管理、负载均衡、故障转移、队列管理,超时管理、异步管理等等等等职责。

server端包含:服务端组件、服务端收发包队列、io线程、工作线程、序列化反序列化、上下文管理器、超时管理、异步回调等等等等职责。

 

however,因为篇幅有限,这些细节不做深入展开。

 

四、结论

1RPC框架是架构微服务化的首要基础组件,它能大大降低架构微服务化的成本,提高调用方与服务提供方的研发效率,屏蔽跨进程调用函数(服务)的各类复杂细节

2RPC框架的职责是:让调用方感觉就像调用本地函数一样调用远端函数、让服务提供方感觉就像实现一个本地函数一样来实现


从SOA到微服务的演化

原创:  老刘  码农翻身  2017-05-22

小明毕业后为了户口,进入了一家大型国企的信息部门工作, 这个国企不差钱, 几十年来随着IT系统的发展, 也与时俱进地兴建了多个信息系统,只不过自家开发的极少, 从外边购买的极多, 虽然信息部也有开发能力, 但是当甲方的感觉是最妙的, 何况出了问题还可以把责任推出去。  


在这些系统当中, 小一点儿的有自动化办公系统(OA) ,  休假系统,车辆管理系统, 薪水支付系统, 大点儿的有客户关系管理系统, ERP系统 ..... 等等, 可以说是琳琅满目,让人目不暇接,几十年来IT发展的技术,几乎都能在公司的IT环境中找到。


小明的工作之一就是维护现在的IT系统博物馆, 博物馆中大部分都是遗留系统, 能工作,但是非常的老旧。硬件平台, 软件环境,开发语言各部相同,都是异构的。


就说那个休假系统吧,还是用上个世纪流行的Delphi 写的。 还有那个OA系统, 也是上个世纪的ASP,运行在IIS上。  虽然界面丑陋,勉强能用。


也有一点新东西,比如上周上线的那个维修系统不就用了最新的前端技术嘛, 小明也着实激动了一阵,看了两天的React。


有一天有个著名外企的销售来到了小明的公司,请信息部门的老大吃了饭,K了歌。。。好像还搞了些小明这些小兵不知道的秘密活动。


第二天老大给国企老总做了汇报, 过了一段时间, 公司发文了:  

为了提升IT效率,打破各个信息孤岛,实现各个信息系统之间的互联互通, 让业务和IT进行对齐, 达到业务敏捷性, 公司经慎重研究决定,邀请xxx公司作为咨询顾问, 从即日起开始实施SOA战略


小明看了一遍,愣住了,上面的字全都认识, 但连起来就不知道是什么意思, 虚头巴脑的, 唯一确定的是,咨询顾问要来了,要开始什么SOA了。


顾问果然来了, 先给小明他们的信息部门洗了一次脑, 小明的脑海中被各种新式的名词所充斥: SOA, ESB, SCA, BEPL.... 下了课, 小明和同事们讨论了很久, 模模糊糊的明白了要做什么事情。  


好像是把这些遗留的异构系统包装成粗粒度的服务, 还是 Web 服务,可以通过Http来访问,  然后呢, 让大家互相调用, 甚至可以把这些服务进行编排,形成一个大的业务流程, 完成更高层次的业务,  听起来挺有意思的。


外企的销售非常精明, 趁势卖了一大批硬件和软件,  他们的技术团队还确实帮着做了一个小的验证系统, 实现了一个业务场景,展示给公司老总看, 老总非常满意: 不错, 我们公司又一次站到了IT技术的前沿!


然后就没有下文了, 领导们似乎忘记了, SOA似乎没有发生过,互联互通呢? 业务敏捷呢?


小明很困惑,周末约着同学张大胖去吃饭。


张大胖在一个互联网公司工作, 主要是做网上约车系统,  用的都是最前沿的技术, 他说: 听起来你们要把这些遗留的异构系统做数据/信息的集成啊, 只是没有做下去而已, 国企嘛可以理解。 你知道我们公司在干什么事儿吗?


小明说:“不会和我们一样吧?”


“完全不同!  我们公司才成立几年啊, 最重要的就是这个约车系统, 当然我们现在发展的很快,这三年以来系统已经快变成一个巨无霸了, 代码已经达到百万行级别, 没人能搞明白了, 代码库非常难于管理, 冲突不断。 系统部署也非常困难, 一点点小改动都需要巨无霸式的整体部署, 你能想象得到吗, 我们系统重启一次得15分钟!”


“我赛, 这么慢? 不可思议,我用过你们的打车软件, 用起来还可以啊?”


“唉, 金玉其外,败絮其中, 你不知道我们每次发布有多痛苦, 但是竞争激烈, 我们还得频繁发布。 所以我们做的事情和你们相反, 不是集成, 而是拆分!  把一个巨无霸变成一些小的组件, 让这些小组件能完全独立的开发, 测试和部署。”


“那你们的开发团队怎么办?” 小明问。


“我们的组织结构也要随着这些小组件来重构啊,你看我们分成了“乘客管理”,“司机管理”,“旅程管理”,“支付管理”等好多组, 每个组只负责他们特定的一块儿功能, 并且每个组里边都有设计,开发,测试,部署等人员,一应俱全, 他们从头到尾全程负责。”


“有意思啊,这些小组件都是独立的,每个组件实例都是一个进程吧, 那这些小组件怎么交互? 难道也是通过我们公司所用的Web service ?”


“不不, 我们不用那重量级的Web service , 什么WSDL, 什么SOAP, 我们统统不用, 我们只用最轻量级的、基于Http 的Restful 来对外提供接口”


小明突然想到一个问题:“你们每个部门负责一个特定功能,那数据怎么办?还用统一的数据库吗?”


“这是个老大难问题,我们得做数据库的拆分啊,唉, 一言难尽。”


小明说 :“可以理解,不过这样以来确实是更加敏捷了。”


大胖说:“这还不是最厉害的,最厉害的是我们能快速的自动化的部署这些小组件,并且能为他们创建很多实例来运行,有一个挂掉了也没关系,别人可以调用那些还在运行的。”


“所以关键点就是这些小组件对外提供的服务是无状态的,对吧?”


“没错” 大胖说, “这一点和你们的SOA是一样的,  对了, 告诉你一个小秘密,我们在生产环境会做一些‘猴子测试’,通过写脚本随机的停掉一些实例,看看我们的系统运行的怎么样”


小明说:“厉害啊,你们都玩的可都是心跳啊。”


“木有办法,只有在生产环境才能发现真正的问题啊”


“难道你们的这种方式没有缺点吗?”


“当然有了, 就拿数据库来说吧, 数据做了分区, 一致性怎么保证啊?   选择分布式事务非常麻烦, 有时候不得不选择最终一致性来妥协; 还有服务多了, 客户调用起来非常的麻烦, 所以经常得把多个接口API封装,对外提供一个简单的接口 ; 当然这种基于HTTP的调用远没有原来的在一个进程内的方式效率高。 还有一个要命的问题就是监控,你想想这么多运行的实例, 互相之间有调用关系,一个地方出错了, 怎么追踪啊,很麻烦。”


“不管如何, 你们这种把系统拆分,让一个独立的组织负责独立的部分还是很敏捷啊, 对了,你说的小组件,难道没有一个像SOA这样的高大上名称吗?”


“当然有了, 业界把这种方式叫做微服务!  虽然这个词不能完整的表达我们做的事情。 我现在很期望Martin Flower 给它起个更贴切的名称,就像Dependency Injection 那样, 比之前的IoC强多了。 ”


吃过饭回去的路上,小明心想:天下大势,真是分久必合,合久必分啊, 我们在零散系统的集成, 大胖他们又在搞巨无霸应用的拆分。


相比而言,小明还是羡慕大胖,羡慕他们公司的朝气蓬勃。 他虽然明白自己所在国企的信息系统和大胖做的不一样, 但是暮气沉沉的感觉让人看不到希望,  再这么混下去, 热爱的技术可就真的废了。


过了年,小明已经在国企服务了3年了,顺利的拿到了帝都的户口, 然后毫不迟疑的跳槽到了大胖的公司,去搞微服务去了。



微服务中的RPC

2017-02-24 07:48      娱乐     来自: 牛人趣事

在支付系统的微服务架构中,基础服务的构建是重中之重, 本文重点分析如何使用Apache Thrift + Google Protocol Buffer来构建基础服务。

一、RPC vs Restful

在微服务中,使用什么协议来构建服务体系,一直是个热门话题。 争论的焦点集中在两个候选技术:(Binary)RPC or Restful。

  1. 以Apache Thrift为代表的二进制RPC,支持多种语言(但不是所有语言),四层通讯协议,性能高,节省带宽。相对Restful协议,使用Thrifpt RPC,在同等硬件条件下,带宽使用率仅为前者的20%,性能却提升一个数量级。但是这种协议最大的问题在于,无法穿透防火墙。

  2. 以Spring Cloud为代表所支持的Restful 协议,优势在于能够穿透防火墙,使用方便,语言无关,基本上可以使用各种开发语言实现的系统,都可以接受Restful 的请求。 但性能和带宽占用上有劣势。

所以,业内对微服务的实现,基本是确定一个组织边界,在该边界内,使用RPC; 边界外,使用Restful。这个边界,可以是业务、部门,甚至是全公司。

二、 RPC技术选型


RPC技术选型上,原则也是选择自己熟悉的,或者公司内部内定的框架。 如果是新业务,则现在可选的框架其实也不多,却也足够让人纠结。

Apache Thrift

国外用的多,源于Facebook,后捐献给Apache基金。是Apache的顶级项目 Apache Thrift。使用者包括Facebook、Evernote、Uber、Pinterest等大型互联网公司。 而在开源界,Apache Hadoop/HBase也在使用Thrift作为内部通讯协议。 这是目前最为成熟的框架,优点在于稳定、高性能。缺点在于它仅提供RPC服务,其他的功能,包括限流、熔断、服务治理等,都需要自己实现,或者使用第三方软件。

Dubbo

国内用的多,源于阿里公司。 性能上略逊于Apache Thrift,但自身集成了大量的微服务治理功能,使用起来相当方便。 Dubbo的问题在于,该系统目前已经很长时间没有维护更新了。 官网显示最近一次的更新也是8个月前。

Google Protobuf

和Apache Thrift类似,Google Protobuf也包括数据定义和服务定义两部分。问题是,Google Protobuf一直只有数据模型的实现,没有官方的RPC服务的实现。 直到2015年才推出gRPC,作为RPC服务的官方实现。但缺乏重量级的用户。

以上仅做定性比较。定量的对比,网上有不少资料,可自行查阅。 此外,还有一些不错的RPC框架,比如Zeroc ICE等,不在本文的比较范围。

Thrift 提供多种高性能的传输协议,但在数据定义上,不如Protobuf强大。

  1. 同等格式数据, Protobuf压缩率和序列化/反序列化性能都略高。 Protobuf支持对数据进行自定义标注,并可以通过API来访问这些标注,这使得Protobuf在数据操控上非常灵活。比如可以通过option来定义Protobuf定义的属性和数据库列的映射关系,实现数据存取。

  2. 数据结构升级是常见的需求,Protobuf在支持数据向下兼容上做的非常不错。只要实现上处理得当,接口在升级时,老版本的用户不会受到影响。

  3. 而Protobuf的劣势在于其RPC服务的实现性能不佳(gRPC)。为此,Apache Thrift + Protobuf的RPC实现,成为不少公司的选择。

三、Apache Thrift + Protobuf


如上所述,利用Protobuf在灵活数据定义、高性能的序列化/反序列化、兼容性上的优势,以及Thrift在传输上的成熟实现,将两者结合起来使用,是不少互联网公司的选择。

服务定义:

service HelloService{binary hello(1: binary hello_request);}

协议定义:

message HelloRequest{optional string user_name = 1; //访问这个接口的用户optional string password = 2; //访问这个接口的密码optional string hello_word = 3; //其他参数;}message HelloResponse{optional string hello_word = 1; //访问这个接口的用户}

想对于纯的Thrift实现,这种方式虽然看起来繁琐,但其在可扩展性、可维护性和服务治理上,可以带来不少便利。

四、服务注册与发现


Spring Cloud提供了服务注册和发现功能,如果需要自己实现,可以考虑使用Apache ZooKeeper作为注册表,使用Apache Curator 来管理ZooKeeper的链接,它实现如下功能:

  • 侦听注册表项的变化,一旦有更新,可以重新加载注册表。

  • 管理到ZooKeeper的链接,如果出现问题,则进行重试。

Curator的重试策略是可配置的,提供如下策略:

BoundedExponentialBackoffRetryExponentialBackoffRetryRetryForeverRetryNTimesRetryOneTimeRetryUntilElapsed

一般使用指数延迟策略,比如重试时间间隔为1s、2s、4s、8s……指数增加,避免把服务器打死。

对服务注册来说,注册表结构需要详细设计,一个参考的注册表结构按照如下方式组织:

机房区域-部门-服务类型-服务名称-服务器地址


由于在ZooKeeper上的注册和发现有一定的延迟,所以在实现上也得注意,当服务启动成功后,才能注册到ZooKeeper上;当服务要下线或者重启前,需要先断开同ZooKeeper的连接,再停止服务。

五、连接池


RPC服务访问和数据库类似,建立链接是一个耗时的过程,连接池是服务调用的标配。目前还没有成熟的开源Apache Thrift链接池,一般互联网公司都会开发内部自用的链接池。自己实现可以基于JDBC链接池做改进,比如参考Apache commons DBCP链接池,使用Apache Pools来管理链接。 在接口设计上,连接池需要管理的是RPC 的Transport:

public interface TransportPool {/*** 获取一个transport* @return* @throws TException*/public TTransport getTransport() throws TException;}

连接池实现的主要难点在于如何从多个服务器中选举出来为当前调用提供服务的连接。比如目前有10台机器在提供服务,上一次分配的是第4台服务器,本次应该分配哪一台?在实现上,需要收集每台机器的QOS以及当前的负担,分配一个最佳的连接。

六、API网关


随着公司业务的增长,RPC服务越来越多,这也为服务调用带来挑战。如果有一个应用需要调用多个服务,对这个应用来说,就需要维护和多个服务器之间的链接。服务的重启,都会对连接池以及客户端的访问带来影响。为此,在微服务中,广泛会使用到API网关。API网关可以认为是一系列服务集合的访问入口。从面向对象设计的角度看,它与外观模式类似,实现对所提供服务的封装。

网关作用

API网关本身不提供服务的具体实现,它根据请求,将服务分发到具体的实现上。 其主要作用:

  1. API路由: 接受到请求时,将请求转发到具体实现的worker机器上。避免使用方建立大量的连接。

  2. 协议转换: 原API可能使用http或者其他的协议来实现的,统一封装为rpc协议。注意,这里的转换,是批量转换。也就是说,原来这一组的API是使用http实现的,现在要转换为RPC,于是引入网关来统一处理。对于单个服务的转换,还是单独开发一个Adapter服务来执行。

  3. 封装公共功能: 将微服务治理相关功能封装到网关上,简化微服务的开发,这包括熔断、限流、身份验证、监控、负载均衡、缓存等。

  4. 分流:通过控制API网关的分发策略,可以很容易实现访问的分流,这在灰度测试和AB测试时特别有用。 

解耦合

RPC API网关在实现上,难点在于如何做到服务无关。我们知道使用Nginx实现HTTP的路由网关,可以实现和服务无关。而RPC网关由于实现上的不规范,很难实现和服务无关。统一使用Thrift + Protobuf 来开发RPC服务可以简化API网关的开发,避免为每个服务上线而带来的网关的调整,使得网关和具体的服务解耦合:

  • 每个服务实现的worker机器将服务注册到ZooKeeper上;

  • API网关接收到ZooKeeper的变更,更新本地的路由表,记录服务和worker(连接池)的映射关系。

  • 当请求被提交到网关上时,网关可以从rpc请求中提取出服务名称,之后根据这个名称,找到对应的worker机(连接池),调用该worker上的服务,接受到结果后,将结果返回给调用方。

权限和其他

Protobuf的一个重要特性是,数据的序列化和名称无关,只和属性类型、编号有关。 这种方式,间接实现了类的继承关系。如下所示,我们可以通过Person类来解析Girl和Boy的反序列化流:

message Person {optional string user_name = 1; optional string password = 2; }message Girl {optional string user_name = 1; optional string password = 2; optional string favorite_toys = 3; }message Boy {optional string user_name = 1; optional string password = 2; optional int32  favorite_club_count = 3; optional string favorite_sports = 4; }

我们只要对服务的输入参数做合理的编排,将常用的属性使用固定的编号来表示,既可以使用通用的基础类来解析输入参数。比如我们要求所有输入的第一个和第二个元素必须是user_name和password,则我们就可以使用Person来解析这个输入,从而可以实现对服务的统一身份验证,并基于验证结果来实施QPS控制等工作。

七、熔断与限流


Netflix Hystrix提供不错的熔断和限流的实现,参考其在GitHub(https://github.com/Netflix/Hystrix/)上的项目介绍。这里简单说下熔断和限流实现原理。

熔断一般采用电路熔断器模式(Circuit Breaker Patten)。当某个服务发生错误,每秒错误次数达到阈值时,不再响应请求,直接返回服务器忙的错误给调用方。 延迟一段时间后,尝试开放50%的访问,如果错误还是高,则继续熔断;否则恢复到正常情况。


限流指按照访问方、IP地址或者域名等方式对服务访问进行限制,一旦超过给定额度,则禁止其访问。 除了使用Hystrix,如果要自己实现,可以考虑使用使用Guava RateLimiter。

八、服务演化


随着服务访问量的增加,服务的实现也会不断演化以提升性能。主要的方法有读写分离、缓存等。

读写分离

针对实体服务,读写分离是提升性能的第一步。 实现读写分离一般有两种方式:

  1. 在同构数据库上使用主从复制的方式: 一般数据库,比如MySQL、HBase、Mongodb等,都提供主从复制功能。数据写入主库,读取、检索等操作都从从库上执行,实现读写分离。这种方式实现简单,无需额外开发数据同步程序。一般来说,对写入有事务要求的数据库,在读取上的性能会比较差。虽然可以通过增加从库的方式来sharding请求,但这也会导致成本增加。

  2. 在异构数据库上进行读写分离。发挥不同数据库的优势,通过消息机制或者其他方式,将数据从主库同步到从库。 比如使用MySQL作为主库来写入,数据写入时投递消息到消息服务器,同步程序接收到消息后,将数据更新到读库中。可以使用Redis,Couchbase等内存数据库作为读库,用来支持根据ID来读取;使用Elastic作为从库,支持搜索。

     

缓存使用

如果数据量大,使用从库也会导致从库成本非常高。对大部分数据来说,比如订单库,一般需要的只是一段时间,比如三个月内的数据。更长时间的数据访问量就非常低了。 这种情况下,没有必要将所有数据加载到成本高昂的读库中,即这时候,读库是缓存模式。 在缓存模式下,数据更新策略是一个大问题。

  • 对于实时性要求不高的数据,可以考虑采用被动更新的策略。即数据加载到缓存的时候,设置过期时间。一般内存数据库,包括Redis,Couchbase等,都支持这个特性。到过期时间后,数据将失效,再次被访问时,系统将触发从主库读写数据的流程。

  • 对实时性要求高的数据,需要采用主动更新的策略,也就是接受Message后,立即更新缓存数据。

当然,在服务演化后,对原有服务的实现也会产生影响。 考虑到微服务的一个实现原则,即一个服务仅管一个存储库,原有的服务就被分裂成多个服务了。 为了保持使用方的稳定,原有服务被重新实现为服务网关,作为各个子服务的代理来提供服务。


微信公众号【黄小斜】大厂程序员,互联网行业新知,终身学习践行者。关注后回复「Java」、「Python」、「C++」、「大数据」、「机器学习」、「算法」、「AI」、「Android」、「前端」、「iOS」、「考研」、「BAT」、「校招」、「笔试」、「面试」、「面经」、「计算机基础」、「LeetCode」 等关键字可以获取对应的免费学习资料。 


                     wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

原文链接:https://yq.aliyun.com/articles/639893
关注公众号

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。

持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。

转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。

文章评论

共有0条评论来说两句吧...

文章二维码

扫描即可查看该文章

点击排行

推荐阅读

最新文章