在Redis数据库中实现分布式速率限制的方法

问题

在许多应用中,对昂贵的资源的访问必须加以限制,此时速率限制是必不可少的。许多现代网络应用程序在多个进程和服务器上运行,状态需要被共享。一个理想的解决方案应该是高效、 快捷的,而不是依赖于被绑定到特定客户端的单个应用程序服务器(由于负载平衡) 或本身持有任何状态。
解决方案

实现这一目标的一个简单有效的方法就是使用 Redis, 它有很多有用的数据结构和功能, 尽管实现速率限制只需要2个功能用: 一、在某个具体的键值上递增一个整数,二、给这个键值设置过期时间。

因为redis 有个单一的事件循环系统 (每个人每次在同一个时间只能执行一个操作),这是个原子操作, 也就是说无论有多少个客户端同时交互操作,对于同一个键值总有一个确定的数值。

这在对同一个资源进行多个速率限制的情况下通常是有利的, 因为这允许少量的破裂,以及更长的期限限制。例如每秒钟请求3次,没分钟请求20次。因为每个限制都是相对独立的,这就需要与其它限制分开进行单独的递增。

因为速率限制通常用在响应时间比较重要的资源(比如网页应用),所以尽量缩短速率限制的使用时间是非常有必要的。redis的最基本的应用就是发出命令,等待响应,然后发出另一个命令,如此往复。 这个花费是昂贵的,因为需要通过网络在应用程序和redis服务器之间多次往返。由于在这个用例中,没有命令依赖其它命令的执行结果,这使得redis的一个叫做流水线技术的使用成为可能。这就是客户端缓存所有redis请求,然后把这写请求发送给redis,redis一次性返回所有的结果。

Redis不会维护客户端需要的限制的,因为redis会根据客户端设置的过期时间删除旧的记数。这消除了客户端统筹协调的需要,和删除竞争条件的可能性。

The Code

import redis
import time

def rate_limit_check(r, key, limits):
  period_lengths = [_[0] for _ in sorted(limits.items())]
  period_limits = [_[1] for _ in sorted(limits.items())]
  pipe = r.pipeline()
  for period_length in period_lengths:
     current_period = int(time.time() / period_length)
     redis_key = 'rate_limit:{key}:{period_length}:{current_period}'.format(key=key, period_length=period_length, current_period=current_period)
     pipe.incr(redis_key).expire(redis_key, period_length*3)
  return not any(hits > period_limit for period_limit, hits in zip(period_limits, pipe.execute()[::2]))

if __name__ == '__main__':
  r = redis.Redis()
  print rate_limit_check(r, '127.0.0.1', {1: 3, 60: 20})

{1: 3, 60: 20} 意味着每秒钟3次的命中率是允许的,在任何限制下,都允许20次的命中。'127.0.0.1'在这里用作键值,尽管在真实的情况下,可能作为IP地址。更高级的用例将有一个全应用程序的速率限制,键值只有客户端的IP地址,以及一个为昂贵的终结点设置的特定终结点限制,这将用到客户端的IP地址和终结点,例如127.0.0.1+/login/。这些限制可以独立地设置。

return rate_limit_check(r, '127.0.0.1', {1: 3, 60: 20}) and rate_limit_check(r, '127.0.0.1+/login/', {1: 2, 60: 5})

这是一个用Python写的例子,它可以简单地移植到任何语言,只要这门语言包含Redis客户端库。

(0)

相关推荐

  • php中使用redis队列操作实例代码

    例1,入队操作: 复制代码 代码如下: <?php$redis = new Redis();$redis->connect('127.0.0.1',6379);while(True){  try{    $value = 'value_'.date('Y-m-d H:i:s');    $redis->LPUSH('key1',$value);    sleep(rand()%3);    echo $value."\n";  }catch(Exception $e)

  • redis 队列操作的例子(php)

    入队操作 复制代码 代码如下: <?php $redis = new Redis(); $redis->connect('127.0.0.1',6379); while(True){ try{ $value = 'value_'.date('Y-m-d H:i:s'); $redis->LPUSH('key1',$value); sleep(rand()%3); echo $value."\n"; }catch(Exception $e){ echo $e->g

  • Redis数据库中实现分布式锁的方法

    分布式锁是一个在很多环境中非常有用的原语,它是不同进程互斥操作共享资源的唯一方法.有很多的开发库和博客描述如何使用Redis实现DLM(Distributed Lock Manager),但是每个开发库使用不同的方式,而且相比更复杂的设计与实现,很多库使用一些简单低可靠的方式来实现. 这篇文章尝试提供更标准的算法来使用Redis实现分布式锁.我们提出一种算法,叫做Relock,它实现了我们认为比vanilla单一实例方式更安全的DLM(分布式锁管理).我们希望社区分析它并提供反馈,以做为更加复杂

  • 基于Redis实现分布式锁以及任务队列

    一.前言 双十一刚过不久,大家都知道在天猫.京东.苏宁等等电商网站上有很多秒杀活动,例如在某一个时刻抢购一个原价1999现在秒杀价只要999的手机时,会迎来一个用户请求的高峰期,可能会有几十万几百万的并发量,来抢这个手机,在高并发的情形下会对数据库服务器或者是文件服务器应用服务器造成巨大的压力,严重时说不定就宕机了,另一个问题是,秒杀的东西都是有量的,例如一款手机只有10台的量秒杀,那么,在高并发的情况下,成千上万条数据更新数据库(例如10台的量被人抢一台就会在数据集某些记录下 减1),那次这个

  • 详解Java如何实现基于Redis的分布式锁

    前言 单JVM内同步好办, 直接用JDK提供的锁就可以了,但是跨进程同步靠这个肯定是不可能的,这种情况下肯定要借助第三方,我这里实现用Redis,当然还有很多其他的实现方式.其实基于Redis实现的原理还算比较简单的,在看代码之前建议大家先去看看原理,看懂了之后看代码应该就容易理解了. 我这里不实现JDK的java.util.concurrent.locks.Lock接口,而是自定义一个,因为JDK的有个newCondition方法我这里暂时没实现.这个Lock提供了5个lock方法的变体,可以

  • 浅谈Redis在分布式系统中的协调性运用

    在分布式系统中,各个进程(本文使用进程来描述分布式系统中的运行主体,它们可以在同一个物理节点上也可以在不同的物理节点上)相互之间通常是需要协调进行运作的,有时是不同进程所处理的数据有依赖关系,必须按照一定的次序进行处理,有时是在一些特定的时间需要某个进程处理某些事务等等,人们通常会使用分布式锁.选举算法等技术来协调各个进程之间的行为.因为分布式系统本身的复杂特性,以及对于容错性的要求,这些技术通常是重量级的,比如 Paxos 算法,欺负选举算法,ZooKeeper 等,侧重于消息的通信而不是共享

  • 详解Redis用链表实现消息队列

    前言 Redis链表经常会被用于消息队列的服务,以完成多程序之间的消息交换.个人认为redis消息队列有一个好处,就是可以实现分布式和共享,就和memcache作为mysql的缓存和mysql自带的缓存一样. 链表实现消息队列 Redis链表支持前后插入以及前后取出,所以如果往尾部插入元素,往头部取出元素,这就是一种消息队列,也可以说是消费者/生产者模型.可以利用lpush和rpop来实现.但是有一个问题,如果链表中没有数据,那么消费者将要在while循环中调用rpop,这样以来就浪费cpu资源

  • redis中使用java脚本实现分布式锁

    redis被大量用在分布式的环境中,自然而然分布式环境下的锁如何解决,立马成为一个问题.例如我们当前的手游项目,服务器端是按业务模块划分服务器的,有应用服,战斗服等,但是这两个vm都有可能同时改变玩家的属性,这如果在同一个vm下面,就很容易加锁,但如果在分布式环境下就没那么容易了,当然利用redis现有的功能也有解决办法,比如redis的脚本. redis在2.6以后的版本中增加了Lua脚本的功能,可以通过eval命令,直接在RedisServer环境中执行Lua脚本,并且可以在Lua脚本中调用

  • Redis实现分布式队列浅析

    Redis是什么? Redis是一个简单的,高效的,分布式的,基于内存的缓存工具. 假设好服务器后,通过网络连接(类似数据库),提供Key-Value式缓存服务. 简单,是Redis突出的特色. 简单可以保证核心功能的稳定和优异. redis的安装和配置 Linux系统下: apt-get install redis-server Windows下: 下载链接 下载安装msi文件就好了 配置主从同步 需要实现分布式队列,至少要有一个master(192.168.45.1)和一个slave(192

  • Redis上实现分布式锁以提高性能的方案研究

    背景: 在很多互联网产品应用中,有些场景需要加锁处理,比如:秒杀,全局递增ID,楼层生成等等.大部分是解决方案基于DB实现的,Redis为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对Redis的连接并不存在竞争关系. 项目实践 任务队列用到分布式锁的情况比较多,在将业务逻辑中可以异步处理的操作放入队列,在其他线程中处理后出队,此时队列中使用了分布式锁,保证入队和出队的一致性.关于redis队列这块的逻辑分析,我将在下一次对其进行总结,此处先略过. 接下来对redis实现的分

随机推荐