分享好友 数据库首页 频道列表

利用Redis实现SQL伸缩的方法

Redis教程  2015-09-21 10:190

这篇文章主要介绍了利用Redis实现SQL伸缩的方法,包括讲到了锁和时间序列等方面来提升传统数据库的性能,需要的朋友可以参考下。

缓解行竞争

我们在Sentry开发的早起采用的是sentry.buffers。 这是一个简单的系统,它允许我们以简单的Last Write Wins策略来实现非常有效的缓冲计数器。 重要的是,我们借助它完全消除了任何形式的耐久性 (这是Sentry工作的一个非常可接受的方式)。

操作非常简单,每当一个更新进来我们就做如下几步:

  • 创建一个绑定到传入实体的哈希键(hash key)
  • 使用HINCRBY使计数器值增加
  • HSET所有的LWW数据(比如 "最后一次见到的")
  • 用当前时间戳ZADD哈希键(hash key)到一个"挂起" set

现在每一个时间刻度 (在Sentry中为10秒钟) 我们要转储(dump)这些缓冲区并且扇出写道(fanout the writes)。 看起来像下面这样:

  • 使用ZRANGE获取所有的key
  • 为每一个挂起的key发起一个作业到RabbitMQ

现在RabbitMQ作业将能够读取和清除哈希表,和“悬而未决”更新已经弹出了一套。有几件事情需要注意:

  • 在下面我们想要只弹出一个设置的数量的例子中我们将使用一组排序(举例来说我们需要那100个旧集合)。
  • 假使我们为了处理一个键值来结束多道排序的作业,这个人会得到no-oped由于另一个已经存在的处理和清空哈希的过程。
  • 该系统能够在许多Redis节点上不断扩展下去仅仅是通过在每个节点上安置把一个'悬置'主键来实现。

我们有了这个处理问题的模型之后,能够确保“大部分情况下”每次在SQL中只有一行能够被马上更新,而这样的处理方式减轻了我们能够预见到的锁问题。考虑到将会处理一个突然产生且所有最终组合在一起进入同一个计数器的数据的场景,这种策略对Sentry用处很多。

速度限制

出于哨兵的局限性,我们必须终结持续的拒绝服务攻击。我们通过限制连接速度来应对这种问题,其中一项是通过Redis支持的。这无疑是在sentry.quotas范围内更直接的实现。

它的逻辑相当直接,如同下面展示的那般:

def incr_and_check_limit(user_id, limit): 
 key = '{user_id}:{epoch}'.format(user_id, int(time() / 60)) 
  
 pipe = redis.pipeline() 
 pipe.incr(key) 
 pipe.expire(key, 60) 
 current_rate, _ = pipe.execute() 
  
 return int(current_rate) > limit 

我们所阐明的限制速率的方法是 Redis在高速缓存服务上最基本的功能之一:增加空的键字。在高速缓存服务中实现同样的行为可能最终使用这种方法:

def incr_and_check_limit_memcache(user_id, limit): 
 key = '{user_id}:{epoch}'.format(user_id, int(time() / 60)) 
  
 if cache.add(key, 0, 60): 
  return False 
  
 current_rate = cache.incr(key) 
  
 return current_rate > limit 

事实上我们最终采取这种策略可以使哨兵追踪不同事件的短期数据。在这种情况下,我们通常对用户数据进行排序以便可以在最短的时间内找到最活跃用户的数据。

基本锁

虽然Redis的是可用性不高,我们的用例锁,使其成为工作的好工具。我们没有使用这些在哨兵的核心了,但一个示例用例是,我们希望尽量减少并发性和简单无操作的操作,如果事情似乎是已经在运行。这对于可能需要执行每隔一段时间类似cron任务非常有用,但不具备较强的协调。

在Redis的这样使用SETNX操作是相当简单的:

from contextlib import contextmanagerr = Redis()@contextmanagerdef lock(key, nowait=True): 
 while not r.setnx(key, '1'): 
  if nowait: 
   raise Locked('try again soon!') 
  sleep(0.01) 
  
 # limit lock time to 10 seconds 
 r.expire(key, 10) 
  
 # do something crazy 
 yield 
  
 # explicitly unlock 
 r.delete(key) 

而锁()内的哨兵利用的memcached的,但绝对没有理由我们不能在其切换到Redis。
时间序列数据

近来我们创造一个新的机制在Sentry(包含在sentry.tsdb中) 存储时间序列数据。这是受RRD模型启发,特别是Graphite。我们期望一个快速简单的方式存储短期(比如一个月)时间序列数,以便于处理高速写入数据,特别是在极端情况下计算潜在的短期速率。尽管这是第一个模型,我们依旧期望在Redis存储数据,它也是使用计数器的简单范例。

在目前的模型中,我们使用单一的hash map来存储全部时间序列数据。例如,这意味所有数据项在都将同一个哈希键拥有一个数据类型和1秒的生命周期。如下所示:

{ 
 
  "<type enum>:<epoch>:<shard number>": { 
 
    "<id>": <count> 
 
  }} 

因此在这种状况,我们需要追踪事件的数目。事件类型映射到枚举类型"1".该判断的时间是1s,因此我们的处理时间需要以秒计。散列最终看起来是这样的:

 { 
 
  "1:1399958363:0": { 
 
    "1": 53, 
 
    "2": 72, 
 
  }} 

一个可修改模型可能仅使用简单的键并且仅在存储区上增加一些增量寄存器。

"1:1399958363:0:1": 53 

我们选择哈希映射模型基于以下两个原因:

我们可以将所有的键设为一次性的(这也可能产生负面影响,但是目前为止是稳定的)

大幅压缩键值,这是相当重要的处理

此外,离散的数字键允许我们在将虚拟的离散键值映射到固定数目的键值上,并在此分配单一存储区(我们可以使用64,映射到32个物理结点上)

现在通过使用 Nydus和它的map()(依赖于一个工作区)(),数据查询已经完成。这次操作的代码是相当健壮的,但幸好它并不庞大。

def get_range(self, model, keys, start, end, rollup=None): 
 """ To get a range of data for group ID=[1, 2, 3]: Start and end are both inclusive. >>> now = timezone.now() >>> get_keys(tsdb.models.group, [1, 2, 3], >>>   start=now - timedelta(days=1), >>>   end=now) """ 
 normalize_to_epoch = self.normalize_to_epoch 
 normalize_to_rollup = self.normalize_to_rollup 
 make_key = self.make_key 
  
 if rollup is None: 
  rollup = self.get_optimal_rollup(start, end) 
  
 results = [] 
 timestamp = end 
 with self.conn.map() as conn: 
  while timestamp >= start: 
   real_epoch = normalize_to_epoch(timestamp, rollup) 
   norm_epoch = normalize_to_rollup(timestamp, rollup) 
  
   for key in keys: 
    model_key = self.get_model_key(key) 
    hash_key = make_key(model, norm_epoch, model_key) 
    results.append((real_epoch, key, conn.hget(hash_key, model_key))) 
  
   timestamp = timestamp - timedelta(seconds=rollup) 
  
 results_by_key = defaultdict(dict) 
 for epoch, key, count in results: 
  results_by_key[key][epoch] = int(count or 0) 
  
 for key, points in results_by_key.iteritems(): 
  results_by_key[key] = sorted(points.items()) 
 return dict(results_by_key) 

归结如下:

  • 生成所必须的键。
  • 使用工作区,提取所有连接操作的最小结果集(Nydus负责这些)。
  • 给出结果,并且基于指定的时间间隔内和给定的键值将它们映射到当前的存储区内。

以上就是如何利用Redis实现SQL伸缩的方法,希望对大家的学习有所帮助。

查看更多关于【Redis教程】的文章

展开全文
相关推荐
反对 0
举报 0
评论 0
图文资讯
热门推荐
优选好物
更多热点专题
更多推荐文章
CentOS系统中Redis数据库的安装配置指南
Redis是一个基于主存存储的数据库,性能很强,这里我们就来看一下CentOS系统中Redis数据库的安装配置指南,包括将Redis作为系统服务运行的技巧等,需要的朋友可以参考下

0评论2016-06-26545

Python的Flask框架使用Redis做数据缓存的配置方法
Redis数据库依赖于主存,在关系型数据库以外再配套Redis管理缓存数据将对性能会有很大的提升,这里我们就来看一下Python的Flask框架使用Redis做数据缓存的配置方法

1评论2016-06-26984

Windows下Redis的安装使用教程
这篇文章主要以图文结合的方式为大家详细介绍了Windows下Redis的安装使用,Redis的出现,很大程度补偿了memcached这类key/value存储的不足,在部分场合可以对关系数据库起到很好的补充作用,对Redis感兴趣的小伙伴们可以参考一下

0评论2016-05-26270

win 7 安装redis服务【笔记】
Redis是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。

0评论2016-05-26179

在redhat6.4安装redis集群【教程】
这篇文章主要介绍了在redhat6.4安装redis集群【教程】,需要的朋友可以参考下

0评论2016-05-26215

Redis Stat的安装指南
这篇文章主要介绍了Redis Stat的安装指南的相关资料,需要的朋友可以参考下

0评论2016-05-17199

Redis实现信息已读未读状态提示
这篇文章主要介绍了Redis实现信息已读未读状态提示的相关资料,需要的朋友可以参考下

0评论2016-04-27420

windows环境下Redis+Spring缓存实例讲解
这篇文章主要为大家详细介绍了windows环境下Redis+Spring缓存实例教程,感兴趣的小伙伴们可以参考一下

0评论2016-04-27193

redis的hGetAll函数的性能问题(记Redis那坑人的HGETALL)
这篇文章主要介绍了redis的hGetAll函数的性能问题,需要的朋友可以参考下

0评论2016-04-15272

浅谈Redis在分布式系统中的协调性运用
这篇文章主要介绍了Redis在分布式系统中的协调性运用,讲解了Redis在进程和线程的调度上以及消息队列中的作用,需要的朋友可以参考下

0评论2016-04-15173

在CenOS系统下安装和配置Redis数据库的教程
这篇文章主要介绍了在CenOS系统下安装和配置Redis数据库的教程,Redis是一个可基于内存的高性能NoSQL数据库,需要的朋友可以参考下

0评论2015-11-16172

Redis正确使用的十个技巧
Redis已经走过了很长的一段路,随之而来的一系列最佳实践,使得大多数人可以正确地使用Redis,下面我们将探索正确使用 Redis 的10个技巧。

0评论2015-10-23111

Redis的11种Web应用场景简介
一些Redis原语命令比如LPUSH、LTRIM和 LREM等等能够用来帮助开发者完成需要的任务——这些任务在传统的数据库存储中非常困难或缓慢。这是一篇非常有用并且实际的文章。那么要如何在你的框架中完成这些任务呢?

0评论2015-09-21131

详解Redis中的双链表结构
这篇文章主要介绍了Redis中的双链表结构,包括listNode结构的API,需要的朋友可以参考下

0评论2015-09-01114

Redis的Python客户端redis-py安装使用说明文档
这篇文章主要介绍了Redis的Python客户端redis-py安装使用说明文档,本文讲解了安装方法、入门使用实例、API参考和详细说明,需要的朋友可以参考下

0评论2015-08-12163

更多推荐