滴滴passport设计之道:帐号体系高可用的7条经验(含PPT)

运维派——国内最大的IT运维社区,欢迎关注运维派微信公众号(ID: yunweipai),获取更多资讯

导读:应对高可用及极端峰值,每个技术团队都有自己的优秀经验,但是这些方法远没有得到体系化的讨论。高可用架构在 6 月 25 日举办了『高压下的架构演进』专题活动,进行了闭门私董会研讨及对外开放的四个专题的演讲,期望能促进业界对应对峰值的方法及工具的讨论,本文是洪泽国介绍滴滴在 passport 设计的高可用经验。

作者:洪泽国,2007 年硕士毕业于中科大,先后在 Oracle、腾讯等公司就职,主要关注在线服务的高可用、高性能和易扩展。

大家好,我来自滴滴出行,今天其他老师分享的内容覆盖内容比较大,我分享的话题相对小一些,讲一个具体的应用。我们在 passport 设计时候踩过很多坑,后来在可用性方面做了很多优化实践,今天给大家分享其中的 7 个小优化。

我的题目就指出了 Passport 设计的一切都是为了高可用。Passport 主要有两个功能,第一登录;第二,授权或者鉴权,每一个请求过来,我这边都会做一个校验,校验量是比较大的。再考虑到滴滴的场景,我们在座的大家可能是乘客端,但是我们还有司机端、代驾端等,司机端每一秒都会发请求过来,请求方就会到 Passport 请求一下,所以是一个典型的高并发高可用场景。

业务场景

先简单介绍一下业务场景,我来自滴滴平台部门,平台是一个业务支撑部门,支付、账号、消息等功能都会在我们平台里。今天主要给大家介绍账号子系统,我们设计 Passport,有很多优化的规则,比如大系统做小,做服务拆分,力度拆得非常小,目的是为了高可用。

Passport 的应用场景,工作之一就是登录。登录成功之后返回 ticket,之后每一个业务请求都会把 ticket 传过来,如果合法,则返回给调用方用户真实的信息。

passport

Passport 简单理解,它是三元组。登录的凭证是手机号码、密码、UID,可以简单理解为 Passport 只维护了三元组。

在我们开始设计一个账户,用户其他资料一开始揉在一起设计,后来我们发现这个问题非常麻烦,可用性会存在一些瓶颈,因此把大系统做小,把 Passport 单独拆出来,只包括这三元组。

一切为了高可用

我的第二个分享内容是一切为了高可用,我们做了什么?我们会从编程语言上,最早用 PHP 写的现在用 golang。最小闭环,柔性降级,异地多活,访问控制,接口拆分等。

1. 选用什么编程语言

我们编程语言是 PHP,现在账号系统用 golang,提升非常明显。有一个例子,一个乘客的用户服务,在线上布了 45 个实例,司机端或者乘客端都有心跳,每一个端有点像 ddos 攻击一样,不停的轮询,司机要不停上报他的状态或者坐标等等信息,访问量非常大。一开始用 45 台 PHP,后面用 golang 重写了一下,只用 6 台机解决了这个问题

2. 最小闭环(大系统小做)

刚才也说到用户的资料包括 count、UID、密码、名字等信息,我们把它做了一个拆分。拆分有什么作用?和我之前在腾讯的经历有关系。之前在腾讯的时候,老大一直说 QQ 永远不能存在不登录情况下,即时登录进去都是空白都能接受。这个的确有很大的差异,当用户不能登录,他以为他的账户被盗了,这会形成很大的惶恐,但登录之后什么都没有,他知道肯定是系统挂了,不会有恐慌的心理。因此对于帐号系统来说,需要永远要保证它是能登录的。

最小闭环刚才说了,passport 只包括三个最主要的属性。我们乘客端刚刚上了密码登录,司机端都是用密码登录。在 QQ 时登录量是非常大的,校验量非常大需要做很多细致的工作。腾讯包括微信的架构都有一条经典的经验,大系统小做,当你把系统做小之后,高可用性最容易做,每一个功能比如用户存储的信息越多,这个事情就越难一些。

(小编注:在上一篇大促系统全流量压测及稳定性保证——京东交易架构分享文章中,京东商城的杨超将这种设计方法叫异构

3. 柔性及可降级之 Ticket 设计

很多公司都需要降级,在柔性降级里面举几个例子跟大家分享。

在移动客户端应用,登录时间通常是很长的。比如大家用微信,不需要经常登录,但是服务端需要有踢出用户的能力。踢出是什么概念?登录后,可以用另外一个手机登录就把前一个踢出,这样应用就会更安全。就因为可踢出,实现就会稍微复杂一点。

我加入滴滴之前已经存在一个 Passport,最早是 PHP 语言写的。在滴滴合并快的,我也了解快的那边的情况,大家在设计 ticket 时比较简单和类似,一登录,生成一个 ticket,业务来请求提供认证,认证服务和 ticket 进行对比,对的就通过,不对就让用户踢出。

passport

我相信很多帐号系统都是这样实现,但这里面隐藏比较严重的问题。ticket 是无语义的,里面没有任何信息。其次如果 ticket 服务不稳定校验就会不通过,所有的业务请求第一步就是来校验,它对系统的要求,第一是低延时,你得足够稳定足够快。第二,不能有故障,一旦你个服务失败,用户端就会请求失败,就叫不到车。

在滴滴,不管基础组件比如 MySQL 都需要考虑失败的情况,和滴滴快速成长有一定的关系,所有业务系统,在实现时就需要充分考虑系统的不可靠性。

于是我们对 ticket 重新进行设计,下图是目前的设计。第一我们 ticket 增加了语义,里面是有内容进行了加密。这里面提一点,加解密尽量不要用 RSA 非对称算法,那会是一个灾难。ticket 里面包含一些信息,包括手机号、UID、密码。

滴滴

图上面有一个 seq,可能是我们这边比较独特的设计,你要实现可踢除,就像前图更多是对比两个 ticket 是不是对等,ticket 比较大是一个串,放在一个存储里面,空间挺大,并且不停地变,我们想把它改造成 seq,一个四字节的 int,通过 seq 达到 ticket 踢除的目的。

在用户登录的时候发 ticket,ticket 有 seq,跟手机号加密在里面,每登录一次我会 seq + 1,我们有状态的是 seq,由很长的存储变成 int。我们验证 ticket 是否有效?只需要解密,把 seq 拿出来,跟数据库 seq 对比是不是一样?一样,就过了。

这个项目我说了几点。第一,我用 token 的概念,实际上是没用,把它干掉,通过有状态的 seq 做到。另外,ticket 里面是自己包含内容是有语义的,这为我们降级各方面做了很多的探索,我们在降级的情况下会牺牲一点点的安全性。刚才说 Seq,由 ticket 变成 seq,存储下降非常多。

这里实例说的还是柔性降级,假设 seq 存在 cache 里面,cache 这一级挂了,我们还是能够做到验证 seq,能解密,seq 判断符合要求,在降级的情况下也是可以过的。当然这也牺牲了一点安全性。

4. 柔性及可降级之短信验证码

我们最早大部分登录使用验证码,另外我们系统有很多的入口,我们在腾讯的微信、支付宝里面都有访问入口。在 Web 环境下系统很容易被攻击,后面我会讲攻击的事情。

登录时候,输手机,发验证码,输入验证码,然后到我这边服务端做校验。通常做法也是用户点获取验证码,验证码有效期几分钟,系统存储一个手机号跟 code(验证码)的关系,登录的时候把手机号跟 code 传进来。

滴滴

验证时候根据手机号找到存储里面 code,两个一比,相同就通过了。但是也有问题,假如 cache 挂了,登录不了就会很被动,如何实现高可用?

Cache 复制永远高可用是另外一个话题,我们尝试了另外一种柔性可用的方法。我们的需求是验证码能够在几分钟内有效,我们也可以计算,手机号加上当前时间戳,实际是 unix seconds 变成 unix minutes,算当前是多少秒分钟。通过手机号加时间,在它发的时候算 code,输入就是手机号加 unit,输出给它一个 code。

高可用架构

第二步,用户输入手机号传过来手机号加 code,假设配置是 5 分钟有效,计算其中的时间,拿手机号加上当前的时间,假设是 5 分钟,递减 5 分钟,当前的分钟数和手机号算一个值与 code 对比,不对的话最多循环五次(当然这个也有优化之处)。

当极端情况 cache 不可用的时候,我们可以手机号以及时间,通过内部的算法算出验证码是否基本可符,降级之后安全级别会一定降低,在可用性和安全性方面取得一个折衷。(编者:假如在系统正常情况下,cache 的验证码可以通过算法再加一个随机因子,严格符合才能通过,这样正常时候安全级别是可以有保证。)

攻击者有两种攻击,一种拿着手机号,换不同的 code,这是一种攻击。另外一种攻击,拿固定的 code 换不同的手机号,我们现在结合的方式,我们现在还是采用第一种,当我们后端服务不可用,还有兜底方,当然牺牲了一点点安全性。当我挂掉那段时间,我还是照样可以登录。

5. 高可用与异地多活

讲一下异地多活,保证系统永远可登录。在滴滴,由于业务发展太快,下图是当前业务分布的情况,它带给我们的一些麻烦。

高可用架构

上面是当前 Passport 简单的图解,我们现在是有 3 IDC,每一个 IDC 里面部署不同的业务,我们滴滴还没有做到业务异地多活。可能 IDC1 有专车快车,IDC2 有顺风车,IDC3 里面有代驾。

我们现在是租用的机房,一个 IDC 如果机器不够用,就从别的机房匀出一些,导致我们的业务非常分散,这也给 Passport 和账号团队的服务提出挑战,我们要提前业务做异地多活。但是现在业务本身并没有做到异地多活。

我们把登录实现了多活,注册还没做,但是目前已经足够满足我们要求,如果一个机房挂掉只是影响新注册的用户,在一定程度是可以接受的,所有其它的服务可保证正常使用。

刚才提到不同的 IDC 存在不同的业务。一个人登录进来,先用快车在 IDC1,点开顺风车在 IDC2。这里面就有一些很细节的东西,也就是刚才说 ticket 问题。用户用户在 IDC1 登录,IDC1 给他一个 ticket,这个时候 IDC2 里的 ticket 并没有更新,因为我们所有的请求都是在同机房完成。这时候切到另外一个 IDC 校验,如果当前的 seq 比它传过来,而且发现比他当前小的情况,可以考虑放行。这是由于有可能同步的延迟,seq 还没同步过来。通过这个柔性可用策略,一定程度解决了多机房数据同步不一致的问题。

6. 独立的访问控制层——Argus

我们部门所有的服务都是平台级的业务,比如账号支付,所有业务线都要访问,一般都是通过内网来访问。

为什么要做过载保护?当公司业务部门增多后,会碰到不同的业务拿到线上做压力测试的情况,我们现在所有的公共业务部署,不是按业务线多地部署,我们是大池子大集群,每个业务线都来混合访问。账号访问由于容量比较大,一般压测并没有引起问题。但是在支付的时候,做压力测试在线上支付,就可能会直接把支付拖挂。

我们想必须有一种机制,不能相信任何的业务方,它随时能犯错误,需要通过技术手段去解决。因此需要有过载保护,包括权限控制等一系列机制等。

高可用架构

如上图,防控就是 Argus 系统,承载了过载保护,白名单、安全策略等等职责。它是独立的服务,所有的业务流量打过来,都需要通过它做过滤。

上面提到现在业务并没有多机房的部署,因此如果需要对调用方进行 QPS 的限制?只需要通过在 cache 里设置一个配额,每调一次检验一次。

但这样有个问题,调用量太大。比如说快车有千万级别调用,调用量比较大,我给快车的某一个核心业务一个配额,如果都放在单个实例是支撑不住。因此可以增加一个简单的散列的方式,比如每个调用方调用的时通过 hash 到不同的 Argus 节点上。比如配额是十万 QPS,则可以部署 10 个节点,Argus 每一个就是一万,这样访问就比较可控了。

7. 接口拆分

刚才说的核心登录功能,不经常变,我们希望最稳定的接口独立出来,目的是让稳定不变更的部分故障率降低,所以需要考虑进行拆分。

核心的接口包括登录这一块,其实不经常变,但是像一些小逻辑,策略会经常跟着去上线,但大部分事故都是上线引起的。

分享一个 Pass-TT 的案例。当时跟快的合并时候,快的所有业务在阿里,滴滴所有的服务在深圳腾讯机房,ticket 服务在内网,两个机房跨公网,改造太大了,并且延时不可以接受。

所以我们设计了一个方式,简单说,登录从我们这边登录,访问快的服务的时候再给它 ticket,token。这个 token 专门为代驾用的,但是设计时候犯了一个错误,就是 RSA 方式进行加密。因为有一个远程校验,为了不想 key 泄漏,所以用 RSA 的方式,他们那边部署了一个公钥,我们这边是私钥,token 用我们的私钥加密,然后到它那边进行解密就 OK 了。

高可用架构

这些 token 失效是通过有效期,就是几个小时,失效后就用 ticket 来换。

结果一上线,由于端上有一个 bug,然后所有的等于 ddos 攻击一样,不停闪。当时 Passport 服务器还比较少,10 台左右的服务,正常 CPU 利用率大概在 30% 不到,那个接口一上一下子到 70%,眼看全要挂了,然后连夜我们赶快把那样一个接口单独拆出来,打到不同的单元上,然后重新部署。

所以给大家一个建议,当用 SOA 的时候,如果你的量特别大的时候,你一定要记得把它的 CPU 占用非常高那些功能及接口提前单独拆分出来。

高可用的效果

更多得益于 golang 本身,我们去年在线单机 QPS 峰值超过一万,现在我们有更多的机房,滴滴整个的订单去年和今年都是有数倍增长。

我们响应时间,我们的所有接口无论获取用户信息或者是校验,其实是非常小的,目前高并发接口小于 5 毫秒。密码相关的接口是在 200 毫秒,这主要加解密本身的耗时。

总结

滴滴跟前面分享的今日头条非常相似,发展太快,四年的时间,滴滴的技术规范完全没有统一,文嵩加入滴滴之后,有机会再做服务治理包括框架统一,但是这件事可能会比较挑战,目前滴滴的技术体系特别异构,PHP、Java、Golang 都有,因此目前也不能太多从系统层面给大家做分享,以后应该有这样的机会。

后面有一句话,什么是一切为了高可用?针对 Passport 账号这种系统,需要有柔性的降级,可能需要一些巧妙设计,包括多机房。

one more thing

被攻击的问题

再补充一个被攻击的问题。其它的系统被攻击相对好一点,账号给你挡了,登录后再攻击,至少有 ticket 给你挡了。

发短信攻击,这个维护费用是非常巨大的,攻击者目的你并不知道,他就是要你不停发短信,曾经被这个东西搞得焦头烂额。

当然可以做一些蜜罐的机制,当发现有异常,返回正常值,让攻击者觉得正常,其实服务端什么也没做,这是蜜罐机制。要真正解决这类问题,跟安全部一起做了试点,端上做这种是比较简单,认证比较容易。但是在 app 上不好做,通过 JavaScript 算,其实比较麻烦,并且容易别识破。另外还可以做一些人机识别机制。

Q&A

Q:token 什么时候更新?

洪泽国:token 只有两种失效机制,一个是用户重新登录,第二个踢掉,现在参照微信的做法,端上定期会拿着来换一次。

Q:密码更新,所有的 token 都失效 如何解决?

洪泽国:密钥一更新,登录都踢出?这个问题之前的确有讨论过,有两种方式,第一,让它失效,所有人重新登录,并不是完全不能接受,对用户来讲就是重新登录,密钥泄漏是一次事故。第二,看紧急程度,当前哪些人登录,比如先把没有登录的踢掉,后端算出来,对于已经有的,然后慢慢踢。新的在入口的时候,就要给它打到新的一套服务里面去,你要做新老两套密钥服务之间的切换,并且在上线是知道。

Q:HttpDNS 作用?

洪泽国:HttpDNS 从入口层面上就决定了所有的这一个请求全部落在这个机房,而不会存在两个 IDC 之间内部错峰的一切交替,不会你这个故障某一个服务我就打到那边。HttpDNS 比较简单,维护的信息比较少。无非就是一个服务对应一个IP列表,这是动态,这是很容易做的,信息不会太多。

Q:切换 IDC 怎么做,数据你怎么复制?

洪泽国:我们的确在讨论这样的方式,我不太确认是不是能够最完美回答你的问题,我们现在方案通过 HttpDNS 做中转,通过它我能够完成这件事情,第二,怎么发现故障,这里面包括两个问题,一个是怎么发现 IDC 挂了。第二,发现后怎么做,发现后 HttpDNS 很容易给一个新地址。第一个问题怎么发现,我们内部也在尝试一些方法,可能更多通过手段的方式,或者通过检测链路故障,无非触发时机,所有的链路故障都是从系统,网络层面上触发的。只要 HttpDNS 提供一个接口,一旦故障给它打个标记,A 故障,HttpDNS 就返回 B,这是很容易做到。但是怎么判断它是故障,这是一个问题。这个更多从网络层面去触发。

Q:刚才说如果其它服务链路问题导致 Web 失效,把用户踢下去,对于这种情况你们怎么处理,不是你本身的问题。

洪泽国:这个是调用方有约束的,调网络错误不应该是提的,更多是要从业务层面上,端和业务系统的配合形成。它调一个业务,业务调我,业务调我超时了,现在好多业务处理不是那么完美,出错也踢掉这是不合理的,我们跟他梳理,只有失效踢出是最合理。如果没有的话就踢掉,短信服务提供商那边压力是比较大的,做柔性降级。

本文相关 PPT 链接如下

http://pan.baidu.com/s/1eRMW4zK

文章出处:高可用架构

高可用架构

网友评论comments

发表评论

电子邮件地址不会被公开。 必填项已用*标注

暂无评论

Copyright © 2012-2017 YUNWEIPAI.COM - 运维派 - 粤ICP备14090526号-3
扫二维码
扫二维码
返回顶部