@yufan
2015-12-11T12:02:46.000000Z
字数 7134
阅读 847
API
在软件行业快速发展的今天,传统的软件授权已经不能足以满足一个IT类的公司的发展。虽然在大部分公司里,它还是现金池的直接源头。但是在可遇见的未来,受摩尔根理论的失效、物联网的发展等影响,应用的架构会越来越趋于简单化,架构越来越倾向于分布式水平扩展,对外的服务提供也会越来越SaaS化。在这种大背景下,很多公司都开始提供所谓的开放平台。
查阅各个大公司的开放平台,我们不难发现,都是Rest API,都是HTTP请求,响应报文都是大同小异的XML或者是JSON等众多雷同的特点。这是为什么呢?让我们唠唠API平台的那些事。
查看历史,我们惊讶地发现,其实Rest的概念早在2000年就被人提出。用一句话描述它,就是用固定的URI和可变的参数访问某个服务,来完成一系列业务请求。
Rest API,无论它的名字多么高大上,它本质还是一个HTTP请求,POST也好,GET也罢,都是不同的数据提交方式。所以,能够决定一个Rest API的也就:URI、参数、请求方式、请求头等。
我们一般用URI
来定义希望对外暴露的服务。结构基本类似 schema://yourCompanyDomain/rest/{version}/{application}/{someService}
。schema
可以是http
,也可以是https
,version
指的是你这个API的版本,application
一般会指向底层的某个子系统,someService
就是这个子系统对外提供的服务。当然,如果按照业务为边界划分,也可将业务维度相同但隶属于底层不同的系统的服务定义为一个application
。
对于这种Rest请求,常见的响应结果就是XML或者是JSON形式,往往结果中会包含请求状态,和时间戳,业务系统响应结果。
具体的格式约定,可以看底部的参考文献。
在 URI
的格式定义中,我们包含了version这个字段,这在早期,其实被认为是不优雅的方式。阮一峰有一篇文章就专门抨击这种设计,后面他又自己打脸说还是拼接version的好。(不知道是不是因为Github的设计缘故)
API
设计时常会考虑版本这个概念,无论是在URI还是在请求参数里面,至少有一个地方得指明含版本,为什么呢?
这很简单,就和系统迭代一样,API也是快速迭代开发的。也许初期,你的老大脑袋一热,我们要上API,于是你们就加班加点做,设定了一版请求协议。当时你们想,写完就能赚钱,必然带来一堆问题,比如说,代码难以维护,功能单一。后面这个API
的第二版,你们要基于它去做一些新的变更,比如请求参数多了,返回内容多了,必然就有了第二版。但是又不能影响现有的业务。这个API
基本的URI
没变,但是会同时存在两个版本,老用户继续请求旧版,没问题,你无需动旧版?有需求的新用户,你要请求新版才能提供服务。这样通过版本来区分了不同的服务,便于以后的升级和维护。(就像BAE那货可以毫无压力地废弃掉2.0的API)
所以一开始设计API时,就要定义好版本。其次,版本化可以骗钱。我们可以满怀恶意地猜测,1.0为了抢用户,免费。2.0老牛逼了,你用的爽了,想更爽,付费。233333333
API平台的架构,其实和底层公司的业务系统架构有着密切的关系,基于一个好的系统架构写一个Rest API平台基本是水到渠成的。我们先从业务系统的架构变迁说起,再来分析上面的API平台。
基本的软件公司的业务系统,一开始都是单机,单节点,单库
。慢慢随着业务量的增加。这个系统越来越复杂,机器性能越来越不能满足需求。于是,第一种可能,领导说,换机器。上一台牛逼机器。但是机器性能有限,越牛逼的机器价格越贵,有时候都能买3~5台现有配置机器。于是就有了方案二,三个臭皮匠赛过一个诸葛亮。单应用,多机器,多节点
。
但是慢慢过了几年。你发现,这代码写的越来越屎,越来越复杂。基本上新来的开发要熟悉好几个月的业务。就代码因为快速上线。一堆坑,无法改。于是,大家现在都在做的事情,就是拆分。也就是现在常说的SOA
。拆分,也有拆的好的,拆的不好的。不好的,就是一个大的恶心系统,变成了一堆恶心的小系统,互相调用,成一团乱码。小系统看似很好。但是某个不起眼的小系统。一挂,那么全部的系统都瘫了。这个时候这个万年不维护的小系统还找不到负责人,他么早就滚了。这就是拆分的技术欠债,你无法避免。
那么,就谈到拆分的架构设计。其实这块分两个架构,技术架构和业务架构
。技术架构,就是要分清技术系统和业务系统
。技术系统也可能是一个业务系统,但它一定是一个通用的服务组件。它提供的服务无任何定制需求,就是纯简单服务。比如,发短信发邮件发微信的通知系统,它就是通知。你业务有何特殊需求,就在上面自己实现一个XXX通知系统,那么业务系统的拆分,才是最关键的。就是要划清边界
。
这个边界问题很可怕,什么你该做,什么你不该做。每个系统的职责都要明确。不要你也实现一个他也实现一个,然后相互调。这种可怕性就是在两个服务都瘫痪的时候,完全都无法启用。最后你的系统架构变成了一个通用技术组件系统,完成各个基础服务,每个产品线,业务端,基于你的技术系统包装出业务定制化服务系统,然后最上层就是业务子系统。业务子系统组合在一起,就是一个大的业务系统,也叫服务化平台。
这个时候,你需要做开放平台。暴露一套Restful API,就是水到渠成了。
PS,实际的架构远比这个复杂,截图选自《大型网站系统与Java中间件实践》。
1、演变
初期的API平台往往是上图左侧那种,某个庞大的业务系统希望暴露一套API,于是大家就在这个系统上做,直接设计一套协议。但是,这样子带来的缺点十分明显,第一,它与业务联系太重,理想的API平台是通用的,不是只给你设计一个。第二,它不好扩展,每次变更都得和业务系统一同上线,糟糕的情况下代码还会影响原有正常的业务。第三,性能问题,理论上会降低原有应用的性能。
这种情况下,如果应用部署了多台机器,多个节点,我们就可以独立出来。也就是右边所示的API Gateway,它做的事情本质上就是反向代理,将外部的请求校验完合法性之后反代至内部实际想要对外暴露服务的服务集群上。
所以,这种场景下,API Gateway也就如名称所说,就是一个入口。实际的Rest API的东西还是建立在各个业务子系统上,只是只需要提供最简单的服务,无需考虑授权等东西。用户管理,API注册发布,调用统计等,均由API Gateway实现处理。对于想要快速上线的开发人员而言,实在是一个不错的福音。
然而,当系统应用拆分到了SOA化之后,API的架构由有了新的变革,我们有了注册中心的概念。因为SOA化,所以每个业务子系统其实都有了对外的统一接口,有了ESB(注册中心)。实际的内部系统间请求也有了较好的路由、熔断等策略。
在这种大背景下,API平台对外暴露的Rest API无需底层的业务专门开发了。直接使用现有的内部接口,选择性暴露即可。问题点就在于,如何根据定义的Rest API请求,实际模拟内部的RPC协议请求。
某种程度上,这时候的API平台,已经不仅仅是HTTP Rest请求了。我们完全可以实现相同RPC协议的透传,比如你就是一个Hessian接口想对外暴露,我只需包上一层认证,直接注册于API Gateway,外部Hessian请求直接透传至内部子系统。
在这个基础上的 Rest API 平台,才是灵活的,可扩展的,易于维护的。然而有得必有失,Mock请求必然会有性能上的损耗,但是这个架构的公司,已经不在乎钱了,上10台虚机,不够加呗。
2、特点
API平台的背后,就是庞大的各个业务子系统。每个API,就相当于一个业务子系统。API平台要做的事,就非常清晰和简单。就是业务子系统注册发布API,对外部请求校验计费,模拟请求内部业务子系统,对子系统结果包装序列化为JSON
返回。
上面是一个简单的交互,简单显示了外部系统和内部系统通过Rest API的交互过程:开发者(企业)注册,申请APP_KEY,开通API。按照开发接入,请求签名。转发至后端调用返回结果,API平台计费(预付费或者后收费),统计调用情况。
外部通信本质上还是HTTP
,那么必然存在了授权问题,生产的API平台是直接暴露于公网的,如果认证授权策略出现纰漏,影响是可怕的。
比如,这个API是群发短信,你要是没有好的授权体系,允许人随意推送。某个人想搞你,调用发布反共信息,你整个公司都会跨。HTTP
协议本质上没有这一块的内容,所以我们必然要在这上面考虑安全策略的内容。
如果单纯考虑加解密,或者签名方式来保证请求合法,其实是远远不够的。事实上,一个安全的API平台往往需要多方面一起考虑,保证请求安全合法。
1、是不是实际客户端的请求?
2、API平台是否允许你调用(访问控制)?
访问控制,主要是授权调用部分。API都对外暴露,但是某些公共API可以直接请求,某些,需要授权请求。本质的目的,都是为了验证发起用户合法,且对用户能标识统计计费。
以HMac Auth为例,我们简单设计一个签名算法。开发者注册时获取App Key、App Secret,然后申请部分API的访问权限,发起请求时:
服务端先验证是不是实际客户端的请求
,然后按照App Key查找对应App Secret,执行签名算法,比较签名是否一致。签名一致后查看此App Key对应的用户是否有访问此API的权限,有则放行。
执行成功后包装返回指定格式的结果,进行统计计费。
1、API管理
所有API可后台查询管理,包括动态发布、参数映射配置、后端服务接口配置、API禁用、启用,多版本、分组、分级别等。
2、应用管理
后台管理开放平台接入的应用(第三方应用),包括查询、禁用、启用、审核。
3、API鉴权&授权
4、计费统计
5、开发者平台
1、外部用户
用户 | 做什么 | 使用目的 |
---|---|---|
API平台接入方 | 接入API平台 | 使用XXXX提供的开放平台服务 |
2、各个业务产品线
用户 | 做什么 | 使用目的 |
---|---|---|
各个业务产品线 | 作为外部应用接入API平台 | 使用XXXX提供的开放平台服务 |
各个业务产品线 | 提供服务 | 提供后端服务,发布到API平台供外部应用接入 |
公司后端应用 | 提供服务 | 提供后端服务,发布到API平台供外部应用接入 |
API平台 | API治理 | 运营,管理API、第三方应用等 |
API 的所有服务请求域名是相同的,区别在于Request Path等。请求参数分为系统级参数和业务级参数两部分,系统级参数是所有 API 都拥有的参数,而业务级参数由具体服务 API 定义。
建立API Gateway接受所有请求,按照Request Path,Request Method,Request Head分发所有的请求。
1、 通用统一URL
格式:schema://<API Gateway URI>/DispatcherServlet?method=XXService.xxMethod?xxxObj.xxxParam=xxxValue。
说明:所有请求直接走DispatcherServlet
分发,所有内容均定义于URL参数中。method
为后端某个子系统的某个方法。xxxObj.xxxParam
为方法参数实体的某个属性的值定义。
示例:
http://api.xxxxx.com/router?method=SMSService.sendSMS&user.phoneNumber=18888888888&sign=ds234324sdsad&date=20151229231232
2、Rest类型URL
格式:schema://<API Gateway URI>/rest/{version}/{service}/{method}/{params}
说明:请求按照Gateway定义的Rest地址匹配,动态映射至具体系统具体方法,模拟调用。请求中包含version
字段。
示例:
http://api.yeepay.com/rest/v1/XXService/xxMethod/{xxParam}
http://api.yeepay.com/rest/v1/XXService/xxMethod?xxxParam=xxxValue
1、系统级参数
系统级参数是由 API 平台定义的一组参数,每个服务都拥有这些参数,用以传送框架级的参数信息。如我们前面提到的 method 就是一个系统级参数,使用该参数指定服务的名称。
2、业务级参数
业务级参数,顾名思义是由业务逻辑需要自行定义的,每个服务 API 都可以定义若干个自己的业务级参数。API Getaway 根据参数名和请求属性名相等的契约,将业务级参数具体的方法请求对象中。
跨平台,管你是Java
,还是PHP
,还是Node.js
还是Go
,你丫都得支持HTTP
请求。我API
平台只需要提供这个语言的SDK
,保证能按照消息协议调用就好。
将复杂的内部业务系统抽象为通用调用请求。包装了复杂的业务逻辑,对外提供统一的,好管理的接口。并可以定制化设计,计费,授权一类的容易管理。
协议描述能力弱化,Restful
的URI
无法完全对请求参数做强格式校验。最后的方法参数绑定,模拟内部请求时往往容易出问题,尤其是以Java等强格式语言的系统。不能像WebService
一样清晰描述请求报文。
同样的道理,响应结果为了是JSON
、XML
。这当中,编码,正反序列化,等操作,往往就会有性能瓶颈。而且,Java在这块资源消耗极大。以Github的ROP
这个框架为例,当年测试时,它在并发请求过高的时候就会有一个内存泄漏问题。