文档章节

开放平台API接口调用频率控制系统设计浅谈

优雅先生
 优雅先生
发布于 2014/09/11 19:52
字数 3093
阅读 6086
收藏 138

   先描述下基本场景:

系统API接口日均调用次数预计1亿次,提供5台服务器。

需要做两种层面的控制:

> 单IP、单应用每小时调用次数不超过10000次

> 单应用、单用户、单接口每小时调用次数不超过1000次

要求每次对频控系统的调用的平均响应时间在1ms内。

此外,应用开发者和开放平台所属公司关心调用次数统计数据,如当天某应用所有接口被调用总次数、当天某应用某接口被调用次数、当天某应用用户使用数等。

    根据上面,我们可以直接得到系统响应度要求和计算得到系统吞吐量要求,计算公式如下:

频控系统吞吐量(系统每秒能够处理的请求数) 
   = 80% * 1亿 / (24小时 * 60分钟 * 60秒 * 40% * 5) = 4630tps

    80%、40%是指一天中有80%的请求发生在40%的时间内,是粗略的估算值。5是服务器数量。所以得到吞吐量要求为4630tps。前期设计系统时必须参考这些性能指标,后期压测系统时必须根据这些指标设计测试计划。

    总结下系统设计需要达成的目标:

  • 请求的响应足够快

  • 能支撑4630tps

  • 占用的CPU、内存等硬件资源不能太夸张(隐性设计目标)


A、数据结构设计初步尝试

    计数是典型的key-value数据结构。

    可能想到的最简单最自然的方式是下面这样的:

K(app_id, ip) => V(count, startTime, lastTime)
K(app_id, uid, interface_id) => V(count, startTime, lastTime)

    startTime记录的是第一次调用的发生时刻,lastTime记录的是最近一次调用的发生时刻,它们用来判断是否应该重置计数值count和是否该拒绝调用。startTime和lastTime都只精确到秒级

    为了节省内存,有必要对key和value做特殊设计,最次的方案当然是直接拼接上面各个字段。但这是非常不合理的,我们来简单估算下:

假设应用有10,000个,平均每个应用的用户数为100,000,接口数为50,独立访问IP地址为1,000,000,那么数据项总共为:

10,000 * 1,000,000 + 10,000 * 100,000 * 50 = 600亿

那么如果每个数据项节省1个字节,能够节省的总数据存储是600G,这是非常可观的。

    对于Key,一种更优方案是先拼接Key的字符串,然后MD5得到16位定长字符串作为Key,Key定长的话或许对性能提升也会有一定帮助。

    对于Value,count、startTime、lastTime信息不能丢失,那么或许可以考虑下面两种优化方案:

  • 无损压缩Value字符串,比如使用Snappy字符串压缩算法,但压缩和解压缩会带来额外的CPU计算消耗,需要权衡

  • 计数不需要太精确,所以可以牺牲一定精确度换取空间节省。或许我们可以利用CountingBloomFilter?Key需要重新设计为:MD5(app_id, interface_id, 现在距离1970年1月1号的小时数),Value就是CountingBloomFilter数据结构了,每个调用先根据“app_id、interface_id和现在距离1970年1月1号的小时数”计算16位MD5值,然后得到所属的CountingBloomFilter(如果没有就创建),然后每次先检查是否已达到最大插入次数,如果是则直接返回,如果不是才插入。但是我们别忘了一点:CountingBloomFilter支持最大重复插入次数为15,远小于这里的1000次和10000次。所以很遗憾,CountingBloomFilter不适合这种方案。但这是一个很好的起点,Value的数据结构虽然不能用CountingBloomFilter,但或许可以用其他的优化数据结构,可以参考:

    http://blog.csdn.net/hguisu/article/details/7856239 

    还有一篇文章标题大概是用1k内存来排序亿级数组,找不到了。另外频率控制数据结构模型还可以参考“令牌桶算法”,这里不再深入,详情看:http://en.wikipedia.org/wiki/Token_bucket


B、进一步的数据存储和数据结构设计

    考虑到性能要求,肯定需要用到Cache,这里打算选用Redis做Cache。再根据上面的估算,数据项总共有600亿,所以不可能把所有数据项全部放到Redis Cache中(假设每个Cache项占100个字节,估算下需要多少内存,嘿嘿)。

    所以我这里采用冷热数据分离方案。有这么三类数据:

  • 冷数据存放在MySQL数据库,按照app_id、uid进行水平Shard

  • 不冷不热数据采用Redis Hash结构存储(这种内存结构更加紧凑)

  • 热数据放在另外的Redis库中,并且“展开式”存储以改善访问性能

    先简单说下不冷不热数据的Redis Hash结构,Key是app_id,Field是MD5(ip, interface_id, uid, 现在距离1970年1月1号的小时数),Value包括两个计数值,即单IP、单应用已调用次数和单应用、单接口、单用户已调用次数。这样设计相当于把本来应该存成两项的数据合并到了一个缓存数据项中。

    热数据的所谓“展开式”结构是指将上面两个维度的计数分开,即存成类似下面这两种结构:

K:MD5(app_id, ip, 现在距离1970年1月1号的小时数),V:count
K:MD5(app_id, interface_id, uid, 现在距离1970年1月1号的小时数),V:count

    这种方案有一个小缺陷,那就是小时区间的划分不太符合用户的直觉。用户的直觉是认为真正第一次调用的时刻才是计时起点。为了满足这个需求,可以回归到A节的数据结构上,变成如下:

K:MD5(app_id, ip),V:count、startTime、endTime的优化数据结构
K:MD5(app_id, interface_id, uid),V:count、startTime、endTime的优化数据结构

    另外,我后面仔细思考过,不冷不热数据采用Redis Hash和热数据展开存储,是否真的会像我推断的那样展开存储更快?

    答案是不一定。因为Redis Hash存储的话,只需要一次Redis访问外加一些解析计算开销,而展开存储需要两次Redis访问。应该不是绝对的,需实际进行测量后才能下结论。

    Redis Cache失效时间:

    如果采用的数据结构是:

K:MD5(app_id, ip, 现在距离1970年1月1号的小时数),V:count
K:MD5(app_id, interface_id, uid, 现在距离1970年1月1号的小时数),V:count

    设置缓存失效时间为1小时。

    如果采用的数据结构是:

K:MD5(app_id, ip),V:count、startTime、endTime的优化数据结构
K:MD5(app_id, interface_id, uid),V:count、startTime、endTime的优化数据结构

    那么建议设置缓存永不失效。

    冷热数据迁移过程:

    数据的冷热一直在发生着改变,所以冷热数据之间需要进行迁移。包括下面四种:

  • 热数据中符合不冷不热数据标准的数据移动到不冷不热数据缓存

  • 将不冷不热数据中符合热数据标准的数据迁移到热数据缓存

  • 将不冷不热数据中符合冷数据标准的数据迁移到MySQL数据库

  • 将MySQL数据库中符合不冷不热数据标准的数据迁移到不冷不热数据缓存

    判断冷热的标准可以基于下面一种或者几种:

  • 每天计算一次的历史平均每小时调用次数:单应用、单IP和单应用、单接口、单用户

  • 根据应用的用户量级来区分冷热,也就是区分热应用、不热不冷应用和冷应用。比如某个用户量级超过20万,则该应用下的接口调用数据存为热数据。然后用户量级在5万到20万之间的应用的接口调用数据存为不热不冷数据。至于用户量级低于5万的应用的接口调用数据就存为冷数据了。这种是我比较推荐的方案,因为简单科学,并且只需要控制到应用粒度

  • 基于最近50次调用的平均时间间隔来判断数据冷热,也就是对于每一个数据项还要存储一个它最近50次调用的平均时间间隔(可以借鉴下文提到的滑动窗口算法)

    比如我采用第二种划分标准。每天跑一次后台脚本将热应用、不热不冷应用和冷应用区分出来,然后缓存到Redis Cache中(应用数不会太多,百万级就了不起了,所以甚至可以存为Local Cache),类似这样:

app_id => 1或者2或者3 # 1表示是热应用,2表示是不热不冷应用,3表示是冷应用

    然后API接口调用时,先根据app_id去这个Cache里找出应用的热度级别,然后就知道该访问前面哪一种存储了。当然可能出现访问未命中的情况,在那种情况需要依次访问热缓存数据 => 不冷不热缓存数据 => 冷数据来确定是因为数据存储中还没有相应的Key或者是还没有及时迁移数据到正确的存储,但那种情况毕竟是很少的。


C、借债机制

    比如限制某应用某个接口单用户每小时调用次数不能超过1000次,那么是否严格按照1000次的标准来哪?

    答案是否。因为用户调用接口的频率可能并不均匀,比如像下面这样:

第1小时 第2小时 第3小时
1200 700 950

    如果严格按照1000次标准来的话,那么第1小时内就有200次调用被拒绝。而如果采用简单的借债机制,即如允许超出1000次往上300次,那么在第一小时内的1200次调用都能成功,但欠下200的债,需要在第2小时偿还,第2小时的调用额度就变成800了,以此类推


D、RPC框架选择

    频率控制系统是作为一个独立的系统供其他系统调用,所以需要一个高性能的RPC框架来保证能满足并发要求。选择Thrift,并且采用TNonblockingServer非阻塞模式。


E、滑动窗口算法

    我最早听到滑动窗口算法是在大学的计算机网络课程上,它是TCP协议用来保证数据帧顺序和限制流量的一个算法。Storm中貌似也用到了滑动窗口算法来限制调用频率。所以或许可以迁移应用到这个系统的设计中。

    滑动窗口算法可以参考:http://blog.csdn.net/thisispan/article/details/7545785


F、服务降级

    虽然调用频率控制系统是开放平台中的一个重要系统,但我们也不希望在调用频率控制系统不可用的情况下正常的数据API调用都不可用。所以频控系统应该增加一个服务开关,以实现服务降级。


2014年11月04日22:59更新:另外API接口调用频率控制比较适合用Storm这种实时事件处理框架来处理。

2015年03月10日22:14更新:

数据结构可以用位运算优化,用一个长整数同时包含count、startTime、endTime信息。

此外如果用Redis等带缓存自动失效的系统可以省略“现在距离1970年1月1日的小时数”字段,每次调用都只需要GET-COMPARE-SET。

© 著作权归作者所有

共有 人打赏支持
优雅先生
粉丝 364
博文 36
码字总数 46290
作品 0
浦东
技术主管
私信 提问
加载中

评论(17)

AAABBBQA
AAABBBQA
使用
K:MD5(app_id, ip),V:count、startTime、endTime的优化数据结构
K:MD5(app_id, interface_id, uid),V:count、startTime、endTime的优化数据结构
这种数据结构如何控制60分钟内的访问限制?
JerryLin
JerryLin
NAT过来的请求怎么控制,可能后面有多个正常请求
omeweb
omeweb
用redis是OK的,但是本机的计数也是有必要的
n
newnoder
LuckyWiky
LuckyWiky

引用来自“风行”的评论

没看明白。 在你的设计中,平台 =》 应用 =》 用户 之间什么关系啊?

用户 使用 应用 来间接地使用 平台提供的API,这里会涉及到用户的IP地址吗? 应用 本身是代理 用户来访问平台的API,接口频率应该是控制应用访问API吧,跟用户有什么关系呢?

我觉得楼主应该先说明一下领域模型。
有些应用直接是client side , 就要IP控制了。
优雅先生
优雅先生

引用来自“陈海天”的评论

想问问,为啥设置缓存时间1小时59秒节省内存??谢谢
提早一秒过期Redis就可以提早释放掉一些内存啊。虽然实际不一定能全部立即释放,但应该会有一些内存节省的。
陈海天
陈海天
想问问,为啥设置缓存时间1小时59秒节省内存??谢谢
陈海天
陈海天
稻草鸟人
稻草鸟人
来点实例最好
优雅先生
优雅先生

引用来自“风行”的评论

没看明白。 在你的设计中,平台 =》 应用 =》 用户 之间什么关系啊?

用户 使用 应用 来间接地使用 平台提供的API,这里会涉及到用户的IP地址吗? 应用 本身是代理 用户来访问平台的API,接口频率应该是控制应用访问API吧,跟用户有什么关系呢?

我觉得楼主应该先说明一下领域模型。
某些API是用于获取用户数据的,比如评论、用户个人信息等,所以会有uid。另外所有API都是通过HTTP API开放给外部调用的,可以获取到IP地址,然后做基于IP地址的简单频率控制。
浅谈如何设计一个可用的企业级API网关

在上一篇《浅谈微服务架构下的API网关》文章中, 我们介绍了API网关的概念、优势、应用场景和选型要素, 本文我们将从API网关的架构设计与功能要素两个方面介绍如何设计一个企业级API网关。 ...

garyond
08/05
0
0
浅谈国内取得突破的AGV几项应用技术

  1、移动机器人车体控制技术   在移动机器人的实际应用中,由于用户所需要运载的货物不同,经常需要更改传感器、驱动器的类型及数量。为了提高机器人研发设计的工作效率,增强产品的核心...

中国机器人
01/02
0
0
浅谈Hybrid技术的设计与实现

随着移动浪潮的兴起,各种APP层出不穷,极速的业务扩展提升了团队对开发效率的要求,这个时候使用IOS&Andriod开发一个APP似乎成本有点过高了,而H5的低成本、高效率、跨平台等特性马上被利用...

黄金林
2016/12/21
10
0
高并发系统设计之开放平台API接口调用频率控制系统

先描述下基本场景: 系统API接口日均调用次数预计1亿次,提供5台服务器。 需要做两种层面的控制: > 单IP、单应用每小时调用次数不超过10000次 > 单应用、单用户、单接口每小时调用次数不超过...

李景枫
2016/04/17
405
0
浅谈 PHP 与手机 APP 开发(API 接口开发)

浅谈 PHP 与手机 APP 开发(API 接口开发) 浏览:28008 发布日期:2013/08/22 分类:技术分享 关键字: PHP APP API Q群 MySQL://Nginx:PHP@Linux => 开发交流Q群:12349137 复制代码 推荐阅...

thinkyoung
2015/05/22
0
0

没有更多内容

加载失败,请刷新页面

加载更多

fabric增删改查Mac

备份1.3版本,重新下载1.1版本到fabric文件夹 /opt/gopath/src/github.com/hyperledger/fabric -> /opt/gopath/src/github.com/hyperledger/fabric1.3 新建/opt/gopath/src/github.com/hype......

八戒八戒八戒
13分钟前
1
0
盘点愚人节各大网站彩蛋,谁最爱恶搞?

如今的愚人节俨然已是各品牌宣传了一个重要节日,同时,也成为了各大互联网科技企业凑热闹,比拼创意和策划的节日。跟小编一起看看有哪些有趣的策划吧! Google地图变成吃豆人游戏 每年愚人节...

临江仙卜算子
37分钟前
3
0
Java7/8 中的 HashMap 和 ConcurrentHashMap 全解析

本文分析的是源码,所以至少读者要熟悉它们的接口使用,同时,对于并发,读者至少要知道 CAS、ReentrantLock、UNSAFE 操作这几个基本的知识,文中不会对这些知识进行介绍。Java8 用到了红黑树...

java菜分享
38分钟前
3
0
玩手机与做实验

看过这样一个故事:说的是在二十世纪二十年代初的一个深夜,担任英国剑桥大学卡文迪许实验室主任的卢瑟福来实验室检查,发现一位学生还在做实验。卢瑟福就问他:“你上午做什么了?”学生回答...

Bob2100
今天
5
0
Kafka流式处理

Kafka Streams 初识流式处理 什么是数据流 数据流(也叫事件流)是无边界数据集的抽象表示。无边界意味着无限和持续增长。无边界数据集之所以是无限的,是因为随着时间的推移,新记录会不断加...

东都大狼狗
今天
9
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部