大型互联网企业平台开放技术实践

本篇文章一共分为四个部分,分别是开放生态、开放网关、开放授权和开放安全。为什么要做开放,开放的技术实现有哪些,主要是开放网关和授权,同时我们开放了以后肯定还需要安全,需要开放的安全保障。

开放生态

首先是开放生态,当提到生态的时候,我们可以想象一下,一片大大的草原,有羊群,有野兔,有飞鸟,可能还有狼群,它是一个多样性的。那么开放的生态也是这样,打造一个多样化的平台生态。对于大型的企业,尤其是大型的互联网企业,这个大,具体就是用户量大,数据量大,如下图所示,当用户和数据的规模都在平台上起来以后,企业不会独享这些资源,企业会想办法把这些资源共享出去,怎么个方式来实现呢,就是通过开放平台。比如当用户越来越多,他们的一些个性化的需求也会增多,企业为了满足日益增多的这类个性化需求就引入了第三方开发者,由这些优质的第三方开发者来为企业的用户提供个性化的服务。

大型互联网企业平台开放技术实践_第1张图片

我们在接下来的叙述中,要有一个开放的业务模型来作为基础,这点很重要不然不太好理解开放生态里面包括的这些角色。现在,下面这幅图展示的这个业务模型中的角色有企业、独立软件服务商(ISV)、商家,商家在企业平台上面开一个店铺,比如京东商城上的第三方卖家,他们的店铺。这里面的流程是这样的,商家会在他们的店铺上发布商品,最终用户,就是比如我们这些个体最终的消费者来购买商家的商品,这样的行为都会产生数据到企业平台上面;ISV会通过企业平台的开放接口来开发商家所需要的软件并发布到服务市场上面售卖;商家从服务市场去购买这些软件,比如购买了一款管理商品的软件,那么后续商家就可以使用这款软件更有效率的来管理他的店铺里面的商品,上新、更改图片、更改描述、上下架等等操作。

 在开放的环境中,商家、ISV、企业在绝大部分情况下是三赢的状态,如下图所示,为什么说绝大部分呢,在后面介绍安全的部分我们会再细说。通过上面那张图我们已经能够知道ISV通过企业的开放平台可以开发出商家所需要的SAAS软件,商家使用这些SAAS软件来提高自己店铺的运营能力从而更好的服务最终消费者用户,用户的各方面体验都有提升从而给企业带来更好的发展增长,这是一个优性的循环,实际就是我们说的一个三赢的局面。三赢,没有什么比这个更美好的了。

 

开放网关 

打造一个开放平台需要从三个方面着手,分别是开放网关、开放授权、开放安全。其中开放网关和开放授权之于开放平台好比汽车的车轮负责高速奔跑,而开放安全对于开放平台来说则好比汽车的底盘来负责行车的安稳。我们先介绍构建开放网关的几个重要的知识。我们在网关技术架构演进中一共经历过我认为比较严重的三个问题有雪崩,就是所有的服务器都不再能够接受请求了,全部拒绝服务,这个是非常可怕的场景,相当于停服了。再一个就是降级失效,本来我们提供了降级功能就是为了有限度的保护我们的依赖服务或者是黄金功能,但降级未生效还是给核心功能造成了损失,还有就是问题延时,以前当发生问题的时候我们总是不能够迅速的止损并解决,以至造成了很坏的结果。在经历过一系列的技术架构演进之后,我们现在的技术架构大致长成了下面这个图所示的样子,限于篇幅这篇文章介绍相关几个技术点,如需更多内容可参考《架构修炼之道》一书中的第一章【网关之道】。

 

大型互联网企业平台开放技术实践_第2张图片

选取一种IO线程模型,一般情况下我们通过WEB服务器接收一个请求,然后由一个线程来处理解析REQUEST请求的参数,继而继续进行后续的业务逻辑处理,类似下图所示。比如默认的WEB服务器中的SERVLERT机制下也是这样处理的,这样会带来一个不好的方面就是,现在这个接收REQUEST的请求线程和处理业务逻辑的线程是同一个,业务处理简单或者业务耗时极快没有问题,当然这都是理想状态下,真实的生产环境中,我们基本不会有这样简单的事情发生的,都会需要去继续做一次RPC调用,比如请求数据库,请求第三方接口服务等等。那么在IO请求线程和业务处理线程是同一个的这样的情况下,如果业务处理突然变得很慢了,前方的用户请求量又继续增加,就会不断的创建线程,极端情况下就会堆满整个CPU,也就是上面我们说过的可能会发生雪崩。我们利用servlet3的异步化机制,就可以实现IO请求线程和业务处理线程分开,这样会直接带来一个最大的好处是,使得我们可以将业务线程去隔离分组,接下来还会重点介绍。我们还是先继续说servlet3异步的使用,异步了之后会带来吞吐量的提升吗?想当然的理解会,其实不然,这还少要看接收请求之后是如何处理的,如果仍然是同步调用的RPC,那么吞吐量在servlet3异步和不适用异步是没有多大区别的,因为响应的时间要取决于这段同步的RPC的时延。另外我们可能还会遇到threadlocal的问题,如果原先的系统中有使用threadlocal来传递上下文参数,在我们使用servlet3异步化之后就失效了,因为发生了线程切换了,这个时候可以有两种方法来解决,一种是做代码的改造通过传递参数的方式来实现,另外一种方法是将一些通用变量放在HttpServletRequest的Attribute里,异步上下文中保持了对HttpServletRequest的引用,然后通过一些工具类直接从HttpServletRequest提取所需要的参数值。 

大型互联网企业平台开放技术实践_第3张图片

上文也提到了通过servlet3异步之后,使得我们有机会可以将业务线程去做分组,为什么要为线程分组呢,最主要的原因是不让各自不同的业务处理相互影响,比如上文说的因为一个业务处理变慢而导致整个服务不可用的情况发生。那么我们一般有两种方式,如下图所示,一种是我们建造一个大的线程池,提前为这个线程池分配固定数量的线程比如30个,当为我们的业务处理分配一定数量的线程,这个时候又有两种形式,限制性和保守型,限制型是说为某个业务处理分配的最多线程数量,比如10个;保守型是说至少你要给该业务处理分配10个。具体的实现可以通过count计数来做。还有一种是,每种类型的业务处理分配一个线程池,这个就比较好理解了,比如订单处理的分配一个10个线程大小的线程池,商品处理分配一个20个线程大小的线程池,这个从某种意义上来讲是真实的一个线程池隔离,具体实现可以通过构造ConcurrentHashMap来实现利用它的key-value结构来存储业务类型和线程池的对应关系。线程池隔离的这种方式的优点就是我们已经提到过的不同的业务处理使用线程的时候不会相互影响,缺点呢也很容易想到,就是会涉及线程调度的上线文切换,不过还是要评估一下这个缺点带来的影响是否会阻止你来使用线程池隔离的方式,比较它的优点更突出。

 再来说下管道,我们这里用的是管道技术这个词语,这样可以方便表现它的实现的技术特点,实际上它更是一种思想,一种管道的思想,管道我们很容易理解像图中展示的这样,一段接着一段的连接起来的。每一个请求进入网关系统开始都会经过这样每一段的管道,比如处理黑白名单的,处理降级限流的,可以看出所谓的管道实际上是我们把这样的功能处理形象化成了管道,如下图所示,具体实现上则可以将这样的功能定义为一个Pipe类,然后将这个Pipe类在包装成一个实现了Runnable的Task,再将这个Task交给事先定义好的ThreadPool线程池来处理。这些Pipe类都是提前开发好的,事先都会给每一个API方法分配一定数量的管道,通过使用Map对象来存储API的method名称跟管道的对应关系,这样当每个API方法被请求的时候都会经过这些管道来处理,如果想去掉某个管道直接通过配置来卸载掉就可以了。所以我们也看出来管道技术的特点一个是顺序性,另外一个是具备热插拔特性,当然这些的前提是我们事先要定义并实现好这些管道。

 

流控对于一个网关系统来讲是核心的核心,因为网关系统的特点一个是访问量大,一个是依赖系统多。在开放网关里面我们把流控进行多维度的划分,如下图所示,按照流控类型划分包括基本流控、运营流控、大促流控;按照流控场景划分包括API流控、应用和API的组合流控、应用访问API的权重流控;按照流控手段来划分有分布流控和单机流控。将这些维度组合展示就想图中呈现的这样。还有一点比较重要当分布式流控出现压力的时候,比如分布式流控一般都是通过redis来实现,那么当有热点压力的时候,就好降级到单机流控,单机流控的实现一般是我们常说的令牌桶等实现方式。通过这样组合流控的形式最大化的保证流控的有效性。

 开放授权

上文,有提到过,开放平台的两条腿,一条是开放网关,另外一条腿就是开放授权了。这里的授权特指Oauth2。我们在授权方式上犯过不严谨的错误,比如Oauth2的code换取token的过程,下面还会详细讲到,本来是两步操作,在以前的实现中我们有些环节下做成了一步。token过期之后如果继续使用则会一样造成访问安全风险,如果要让用户频繁重新授权,则又影响了体验。还有授权的粒度我们也会在openid这页去详细介绍。

我们已经知道在我们今天讲述的开放业务模型中有三个角色分别是ISV或者称为第三方开发者、商家、平台。授权要解决的问题是第三方开发者要能够在没有最终商家的用户名和密码的情况下有权限获取到商家的店铺数据,注意是在没有用户名和密码的前提下。我们再对照下面这个图来讲,资源拥有者就是我们说的商家,商家拥有资源包括店铺的数据,比如商品、订单等;用户的数据比如哪个用户在什么时间购买了什么商品,用户的收货地址等;第三方软件,就是ISV或者第三方软件开发者开发的共商家购买使用的软件;资源服务,就是在第三方软件获取到授权以后去访问该服务最终获取上面说到的商家的资源;授权服务,就是本节重点要讲述的,如何通过授权服务获取授权。

 

在整个Oauth2的授权流程中,有两个关键词一个是code授权码,就是最开始的时候拿到这个授权码以后,你才能继续往下;另外一个是accestoken访问码,就是必须通过这个访问码才可以获取到用户的资源。在整个Oauth2的环境下一共有四种授权类型,今天我们着重要讲述的是步骤最多也是最安全的一种授权码方式,详细步骤如下图所示。资源拥有者去使用ISV的软件,ISV的软件需要通过重定向的方式跳转到平台所提供的授权页面,这也是我们常说的发生了第一次重定向,此时如果该用户没有登录则需要登录,如果已经登录那么点击授权动作之后会请求平台的授权服务器,授权服务器会再次重定向到ISV软件的URL地址上,此时这个URL会带着一个code码返回,紧接着ISV会通过后端post的方式把code码重新传回授权服务器,授权服务器会返回一个accestoken,这样经过图中所示的6个步骤的操作,ISV软件便拿到了accestoken访问码,继而可以继续访问资源服务器了,这便是整个Oauth2下的授权码流程,我们日常生活中在大型的平台上所使用的第三方软件,比如微信里的小程序,它们的工作方式几乎都是采用的Oauth2的流程。详细Oauth2介绍可参看这篇文章

《大话Oauth2.0(二)、标准流程下的Oauth2组件及通信》

 为什么需要一个openid呢,所有的根源都来自于用户名,为什么用户名不能开放出去呢,最容易想到也是最根本的原因之一是,用户名具有一定的安全风险,因为如果被黑产拿到用户名就可以提高撞库的成功率,毕竟黑产手里有大量的其他网站上的用户名和密码。因此安全上的考虑,如果只是暴露OpenID和UnionID并不能对用户的数据安全造成危害,因为平台上的登录操作不需要OpenID。OpenID表示的是验证,也就是是说,该用户是不是此企业平台下的用户,是一种“是不是”的关系。Oauth表示的是授权,是第三方开发者可不可以使用或者获取该用户下的数据,是一种“可不可以” 的关系。举个例子,你正驾驶一辆汽车行驶在马路上,突然遇到了交警,让你拿出行驶证(OpenID),但是你除了拿出行驶证你还拿出了车钥匙(Oauth),结果车被交警开走了。^_^,当然交警是不会这么干的。无论使用OpenID还是unionID最终都是要换成用户pin,因为底层接口业务只能识别pin,OpenID和unionID仅仅是开放场景下的用户标识。在这样的情况下就需要OpenIDunionID和pin的互换。我们可以将这个关系存储到redis集群,他们对应redis存储的key分别为OpenID+appkey+isv账号、unionID+isv账号。也就是当调用请求传递过来的时候通过传入的OpenID和unionID换取到pin。

 

大型互联网企业平台开放技术实践_第4张图片

开放安全 

开放和安全历来都是对立的,企业逐渐扩大业务开放规模的同时也带来的日趋严重的开放安全的威胁。我们这里会阐述两种具有代表性的两种安全问题,分别是越权访问和敏感数据。

首先是越权访问,越权又分为两种,一种是垂直越权,也称为权限提升,造成这种类型越权的原因主要是因为压根没有做权限控制,或者仅仅是通过前端JavaScript的方式做了简单的权限判断,导致攻击者绕过了权限控制直接拿到访问权限。我们这里重点说的是另外一种越权,叫做水平越权,比如根据订单ID查询订单详情信息,后台服务只要接收到订单ID就返回订单详情信息,那么这样肯定是有问题的。在解决越权问题上第一个要注意获取用户信息的方式,不能通过GET、Post参数、Header头中获取当前用户的登录信息。另外一个就是做数据归属判断,比如我们正在举例的订单信息查询,需要判断这个订单归属到哪个商家下面,如下图所示。

 

大型互联网企业平台开放技术实践_第5张图片

有了Oauth2就能解决越权的问题吗,答案是,否。授权在开放平台的环境下只是解决了ISV软件获取数据的"合法性",并不能够解决越权的问题,因为上文我们也提到了开放网关和开放授权,其中开放授权从技术的落地上来讲是放在了开放网关里面去验证的,但是开放网关所依赖的服务,比如订单服务、商品服务等,如果这些服务没有做数据归属的判断,仍然无法解决越权的问题。举个例子来讲,还是通过订单ID获取订单数据,ISV的软件传入了订单ID和accestoken两个字段,在经过开放网关的时候开放网关将accestoken置换成了pin,传给了订单服务层,但是订单服务层如果不去做验证这个订单ID对应的订单信息是否是该pin下的,那么就发生了水平越权,如果此漏洞被利用则直接能够获取到所有商家的订单,因此订单服务还需要做数据归属判断才可以避免该安全问题。整个流程如下图所示。

 

大型互联网企业平台开放技术实践_第6张图片

 

鉴于第三方开发者的技术能力参差不齐,企业一方必须要做一层数据加解密的服务,通过运营规则要求第三方开发者需要按照平台的要求将数据加密存储到自己的数据库中,以防止万一的情况下遭到拖库而带来严重的安全问题。因为加解密服务对计算性能要求较高,不再像大部分业务场景下的纯IO操作,消耗CPU资源比较严重,因此对于解密的操作就放在了提供给开发者的SDK中。同时还需要注意秘钥的粒度是建立在APPKEY上面还是用户上面,针对粒度的不同安全等级也会相应有差别,粒度越细安全等级会越高,但同时对开发者一方的数据存储结构要求也高,因此加密粒度的选择需要综合平衡来考虑。另外我们还需要注意秘钥的时效性问题,不会让秘钥一直有效,这样一旦秘钥被泄露造成的时间安全成本就会增大,因此还需要对秘钥的时效进行管理。

本文首发于个人公众号【程序架道】 

你可能感兴趣的:(程序架构,互联网架构实践)