Listening to the Words

开放API接口调用频次限制方案整理

描述:

对某个对外暴露的接口加一个限制:调用者一分钟之内调用次数不能超过100次,如果超过100次就直接返回给调用者失败的信息

Redis 与网络流量整形

文章出处: Redis 与网络流量整形 : http://blog.jobbole.com/88064/

摘要

我们希望服务器能在请求流量的控制上有一定的自动控制能力;本文通过简介令牌桶算法和讨论算法的 redis 实现给出流量整形(traffic shaping)的示例,来介绍网络流量整形。

令牌桶算法

令牌桶算法(token bucket) 并不是网络流量整形中的奇技淫巧,而是非常常用的算法,从百度百科上已经可以对它有一个概括的了解。对此算法的深入读者可自行查阅研究,这里我通俗化的来解释一下这个算法。

在令牌桶算法中,每一个访客都拥有一个独立的“令牌桶”,在这个“令牌桶”里放了一些“令牌”,访客每次来访都会消耗“令牌桶”中的“令牌”,如果“令牌桶”空了,将会对访客做特殊处理(如拒绝其继续访问以达到限流的目的)。

问题一:访客来访是一个持续的过程,如果最初的“令牌”数目固定,“令牌桶”中的令牌会慢慢被消耗殆尽,这样正常的访客也将无法访问—-所以我们需要以一个恒定的速率来向“令牌桶”中添加一定数量的“令牌”, 这样就可以让访客持续的访问。

问题二:我们以一个恒定速率向“令牌桶”中添加“令牌”, 那么如果访客一直没来访他的“令牌桶”岂不会累积大量“令牌”么?—-所以,我们设定“令牌桶”中“令牌”的最大数量,“令牌桶”满了就不需要再去添加了。这解决了“令牌”累积的问题,也使它更像一个“桶”。

如此,“令牌桶算法”中的重要的参数有:1. 给“令牌桶”添加“令牌”的速率(如果访客以这个速率消耗令牌,将一直不会被限流); 2. “令牌桶”的容量(如果消耗令牌的速率大于添加令牌的速率,将消耗桶中的存货,如果消耗速率过大,令牌会被消耗殆尽,访客将被限流)。注意:一般情况下“令牌桶”最初的状态是满的。

《开放API接口调用频次限制方案整理》

redis

作为优秀的内存数据库,redis 可以帮助我们在应用层次快速响应。本文不过多赘述 redis 的优劣,你可以用 redis 做很多事情,在网络流量整形方面,它是很好的实现方案, 下面我们来解析这样一个方案。

说明:这是一段 Python 代码,这段代码来自 GitHub 用户 justinfay。为了使逻辑更清楚,我修改了代码的部分内容和注释,以下是修改后的代码,我们用这段代码来看令牌桶算法的 redis 实现。

Show me the code

import redis
from redis import WatchError
import time

# 向令牌桶中添加令牌的速率
RATE = 0.1  
# 令牌桶的最大容量       
DEFAULT = 100 
# redis key 的过期时间
TIMEOUT = 60 * 60 

r = redis.Redis()

def token_bucket(tokens, key):

    pipe = r.pipeline()
    while 1:
        try:
            pipe.watch('%s:available' % key) 
            pipe.watch('%s:ts' % key)    

            current_ts = time.time()

            # 获取令牌桶中剩余令牌
            old_tokens = pipe.get('%s:available' % key)
            if old_tokens is None:
                current_tokens = DEFAULT
            else:
                old_ts = pipe.get('%s:ts' % key)
                # 通过时间戳计算这段时间内应该添加多少令牌,如果桶满,令牌数取桶满数。
                current_tokens = float(old_tokens) + min(
                    (current_ts - float(old_ts)) * RATE,
                    DEFAULT - float(old_tokens)  
                )
            # 判断剩余令牌是否足够
            if 0 <= tokens <= current_tokens: 
                current_tokens -= tokens 
                consumes = True 
            else: 
                consumes = False 

            # 以下动作为更新 redis 中key的值,并跳出循环返回结果。
            pipe.multi() 
            pipe.set('%s:available' % key, current_tokens) 
            pipe.expire('%s:available' % key, TIMEOUT) 
            pipe.set('%s:ts' % key, current_ts) 
            pipe.expire('%s:ts' % key, TIMEOUT) 
            pipe.execute() 
            break 
        except WatchError: 
            continue 
        finally: 
            pipe.reset() 
    return consumes 

if __name__ == "__main__": 
    tokens = 5 
    key = '192.168.1.1' 
    if token_bucket(tokens, key): 
        print 'haz tokens' 
    else: 
        print 'cant haz tokens'

需要说的几点

1.这段代码在网络流量整形策略中起到什么作用?

对访客的一次访问,我们通过以上代码可以来判断此次访问是否超过了我们的限制,通过返回的判断结果,我们将对此次访问选择正确的处理策略,比如你可以拒绝消耗完令牌的访客进行访问,从而控制他的访问速率,从而达到网络流量整形的目的。

2.redis 在其中如何工作?

对于每个独立的访客,redis 会为他建立两个 key,一个 key 保存了剩余令牌的数量,另外一个 key 保存了最近一次访问的时间戳。其中,最近一次访问时间戳在新访问到来时候用于计算时间间隔,从而计算在此时间间隔内应该向令牌桶中添加多少令牌,进而获得当前令牌桶的剩余令牌数。

3.redis pipe 起到什么作用?

我们看到代码中 while 循环,执行了 redis pipe 中的 watch 动作,这是对 redis 事务 的使用。 这使这里的算法能处理并发的来访。在 redis 中,事务执行是对 redis key 的一个加锁的操作,一个事务没有执行完,别的动作将无法操作这个 key ,代码中循环执行 watch 动作,就是去检查当前 key 是否有未执行完毕的事务,只有所有事务都执行的时候才可能进入执行体,完成令牌判断或者消耗。 —— 这样避免了并发的访问在 set redis key 时候的混乱。

4.如何调参

代码中 RATE 和 DEFAULT 为主要参数,分别代表每秒钟消耗令牌的速率,和令牌桶的容量。通过调整这两个参数来控制你想要的访问速率。

总结
这是一个实用的方式来完成网络流量整形,可以有效控制一些爆发式的流量访问,使访问更加平滑容易控制。

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

原文链接地址:https://my.oschina.net/feichexia/blog/312591

其他解决

1.

创建一个计数器,在cache中set一个key(根据用户标识)=0,设置timeout=60s,这样,新的请求到来,判断是否存在这个key,如果存在key,value>100,则拒绝请求,如果<100,则将其value+1,如果key不存在,则重新set一次,初始值为0,执行请求

这种方法不能控制一分钟内不均匀的访问,极端的例子是,最后59s时有98个请求进来,势必会对服务器造成压力

2.

维护一个List,其中的每个Object包含的内容有

{
secret:'ad822513232',
ip:'127.0.0.1',
timestamp:'231321321',
count:1
};

当一个请求来的时候,根据secret(或ip,依据需求)查找是否存在该对象,不存在则创建,timestamp为当前时间,count为1。如果存在对象obj,则先判断当前请求时间和obj.timestamp的时间差是否大于60s,大于就判断obj过期,然后重置它,obj.timestamp= currentTime,obj.count=1,小于则判断count和100的大小关系,大于则obj.count++,大于或等于100则废弃当前请求

待续>>>>

点赞