利用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

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

  1. 我们可以将所有的键设为一次性的(这也可能产生负面影响,但是目前为止是稳定的)
  2. 大幅压缩键值,这是相当重要的处理

此外,离散的数字键允许我们在将虚拟的离散键值映射到固定数目的键值上,并在此分配单一存储区(我们可以使用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无疑是很适合的。它的文档是那样让人惊讶,那是因为(阅读)其文档的门槛非常的低。虽然他也有折衷(主要是如果你使用持久化),但是他们工作地很好并且比较直观。

那么Redis为您解决什么问题呢?

(0)

相关推荐

  • 简介Redis中的showlog功能

    Redis 有一个实用的slowlog功能,正如你可以猜到的,可以让你检查运行缓慢的查询. Slowlog 将会记录运行时间超过Y微秒的最后X条查询. X 和 Y 可以在 redis.conf 或者在运行时通过 CONFIG 命令:     复制代码 代码如下: CONFIG SET slowlog-log-slower-than 5000 CONFIG SET slowlog-max-len 25 进行设置. slowlog-log-slower-than 是用来设置微秒数的, 因此上面的设置

  • 简介Lua脚本与Redis数据库的结合使用

    可能你已经听说过Redis 中嵌入了脚本语言,但是你还没有亲自去尝试吧?  这个入门教程会让你学会在你的Redis 服务器上使用强大的lua语言. Hello, Lua! 我们的第一个Redis Lua 脚本仅仅返回一个字符串,而不会去与redis 以任何有意义的方式交互. 复制代码 代码如下: local msg = "Hello, world!" return msg 这是非常简单的,第一行代码定义了一个本地变量msg存储我们的信息, 第二行代码表示 从redis 服务端返回msg

  • NoSQL和Redis简介及Redis在Windows下的安装和使用教程

    NoSQL简介 介绍redis前,我想还是先认识下NoSQL,即not only sql, 是一种非关系型的数据存储,key/value键值对存储.现有Nosql DB 产品: Redis/MongoDB/Memcached/Hbase/Cassandra/ Tokyo Cabinet/Voldemort/Dynomite/Riak/ CouchDB/Hypertable/Flare/Tin/Lightcloud/ KiokuDB/Scalaris/Kai/ThruDB, 等等~~~ 为什么需要

  • 在Node.js应用中使用Redis的方法简介

    在开始本文之前请确保安装好 Redis 和 Node.js 以及 Node.js 的 Redis 扩展 -- node_redis 首先创建一个新文件夹并新建文本文件 app.js 文件内容如下: var redis = require("redis") , client = redis.createClient(); client.on("error", function (err) { console.log("Error " + err);

  • Redis简介

    Redis官方网网站是:http://www.redis.io/ . Redis是一个开源,高级的键值存储和一个适用的解决方案,用于构建高性能,可扩展的Web应用程序. Redis有三个主要特点,使它优越于其它键值数据存储系统 : a.Redis将其数据库完全保存在内存中,仅使用磁盘进行持久化. b.与其它键值数据存储相比,Redis有一组相对丰富的数据类型. c.Redis可以将数据复制到任意数量的从机中. Redis的优点 a.异常快:Redis非常快,每秒可执行大约110000次的设置(S

  • Redis的11种Web应用场景简介

    下面列出11种Web应用场景,在这些场景下可以充分的利用Redis的特性,大大提高效率. 1.在主页中显示最新的项目列表 Redis使用的是常驻内存的缓存,速度非常快.LPUSH用来插入一个内容ID,作为关键字存储在列表头部.LTRIM用来限制列表中的项目数最多为5000.如果用户需要的检索的数据量超越这个缓存容量,这时才需要把请求发送到数据库. 2.删除和过滤 如果一篇文章被删除,可以使用LREM从缓存中彻底清除掉. 3.排行榜及相关问题 排行榜(leader board)按照得分进行排序.Z

  • Redis教程(一):Redis简介

    一.简介: 在过去的几年中,NoSQL数据库一度成为高并发.海量数据存储解决方案的代名词,与之相应的产品也呈现出雨后春笋般的生机.然而在众多产品中能够脱颖而出的却屈指可数,如Redis.MongoDB.BerkeleyDB和CouchDB等.由于每种产品所拥有的特征不同,因此它们的应用场景也存在着一定的差异,下面仅给出简单的说明: 1). BerkeleyDB是一种极为流行的开源嵌入式数据库,在更多情况下可用于存储引擎,比如BerkeleyDB在被Oracle收购之前曾作为MySQL的存储引擎,

  • 利用Redis实现SQL伸缩的方法简介

    缓解行竞争 我们在Sentry开发的早起采用的是sentry.buffers. 这是一个简单的系统,它允许我们以简单的Last Write Wins策略来实现非常有效的缓冲计数器. 重要的是,我们借助它完全消除了任何形式的耐久性 (这是Sentry工作的一个非常可接受的方式). 操作非常简单,每当一个更新进来我们就做如下几步: 创建一个绑定到传入实体的哈希键(hash key) 使用HINCRBY使计数器值增加 HSET所有的LWW数据(比如 "最后一次见到的") 用当前时间戳ZADD

  • 利用Redis实现SQL伸缩的方法

    这篇文章主要介绍了利用Redis实现SQL伸缩的方法,包括讲到了锁和时间序列等方面来提升传统数据库的性能,需要的朋友可以参考下. 缓解行竞争 我们在Sentry开发的早起采用的是sentry.buffers. 这是一个简单的系统,它允许我们以简单的Last Write Wins策略来实现非常有效的缓冲计数器. 重要的是,我们借助它完全消除了任何形式的耐久性 (这是Sentry工作的一个非常可接受的方式). 操作非常简单,每当一个更新进来我们就做如下几步: 创建一个绑定到传入实体的哈希键(hash

  • SpringBoot利用redis集成消息队列的方法

    一.pom文件依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> 二.创建消息接收者 变量.方法及构造函数进行标注,完成自动装配的工作. 通过 @Autowired的使用来消除 set ,get方法. @Autowired pub

  • Windows系统下Java连接SQL Server的方法简介

    使用JDBC连接SQL Server 设置SQL Server服务器 我使用的是SQL Server 2005标准版SP2,这些都默认即可,一般不用配置.如果需要配置端口请看下面. 1."开始"→"程序"→"Microsoft SQL Server 2005"→"配置工具"→"SQL Server 配置管理器"→"SQL Server 2005 网络配置"→"MSSQLSERV

  • 利用Redis实现延时处理的方法实例

    背景 在开发中,往往会遇到一些关于延时任务的需求.例如 •生成订单30分钟未支付,则自动取消 •生成订单60秒后,给用户发短信 对上述的任务,我们给一个专业的名字来形容,那就是延时任务. 最近需要做一个延时处理的功能,主要是从kafka中消费消息后根据消息中的某个延时字段来进行延时处理,在实际的实现过程中有一些需要注意的地方,记录如下. 实现过程 说到java中的定时功能,首先想到的Timer和ScheduledThreadPoolExecutor,但是相比之下Timer可以排除,主要原因有以下

  • node.js利用redis数据库缓存数据的方法

    一.运行redis Redis服务器默认使用6379端口 redis-server 自定义端口 redis-server –port 6390 客户端 redis-cli 指定ip和端口连接 redis-cli -h 127.0.0.1 -p 6390 测试客户端和服务器是否连通 ping 二.Nodejs连接redis 通过redis.createClient(port,host,options)来连接redis服务器 var redis = require("redis") var

  • 利用python在excel里面直接使用sql函数的方法

    我们一般在Excel里面是使用数据连接属性里面写sql语句,或者vba里面利用ado组件执行sql语句. 新版的Excel里面带上了Power query的功能也可以使用Odbc.DataSource()和Odbc.Query()函数写sql语句. 今天讲一下利用Python直接在excel里面使用xlwings addin 里的一个名为sql的函数. 首先我们需要在python里面安装好xlwings模块. 执行pip/conda install xlwings命令 即可完成安装. 然后继续在

  • 利用Redis实现访问次数限流的方法详解

    假设我们要做一个业务需求,这个需求就是限制用户的访问频次.比如1分钟内只能访问20次,10分钟内只能访问200次.因为是用户维度的场景,性能肯定是要首先考虑,那么适合这个场景的非Redis莫属. 最简单的实现,莫过于只是用incr进行计数操作,于是有了下面的代码: long count = redisTemplate.opsForValue().increment("user:1:60"); if (count > maxLimitCount) { throw new Limit

  • 解说mysql之binlog日志以及利用binlog日志恢复数据的方法

    众所周知,binlog日志对于mysql数据库来说是十分重要的.在数据丢失的紧急情况下,我们往往会想到用binlog日志功能进行数据恢复(定时全备份+binlog日志恢复增量数据部分),化险为夷! 废话不多说,下面是梳理的binlog日志操作解说: 一.初步了解binlog MySQL的二进制日志binlog可以说是MySQL最重要的日志,它记录了所有的DDL和DML语句(除了数据查询语句select),以事件形式记录,还包含语句所执行的消耗的时间,MySQL的二进制日志是事务安全型的. ---

  • Java利用Redis实现消息队列的示例代码

    本文介绍了Java利用Redis实现消息队列的示例代码,分享给大家,具体如下: 应用场景 为什么要用redis? 二进制存储.java序列化传输.IO连接数高.连接频繁 一.序列化 这里编写了一个java序列化的工具,主要是将对象转化为byte数组,和根据byte数组反序列化成java对象; 主要是用到了ByteArrayOutputStream和ByteArrayInputStream; 注意:每个需要序列化的对象都要实现Serializable接口; 其代码如下: package Utils

随机推荐