使用 Redis 实现 SQL 伸缩 【已翻译100%】

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介:

我喜欢Redis。这是目前的技术当中唯一让你奇怪为什么需要这么长时间编译它的技术。可预测的,高性能并且适应性强,这是我过去几年越来越多使用它的原因。Sentry主要在PostgreSQL上运行已经不是秘密(尽管目前它还依赖于一系列其它技术)

一个多星期前,我在 Python Nordeste 上作了主题演讲。某种程度上而言我只能作一些快速的总结,我决定去找一些黑客来探讨大量使用Sentry,特别是Redis技术。这篇文章是一个5分钟讨论的扩充。

缓解行竞争

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

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

1.创建一个绑定到传入实体的哈希键(hash key)

2.使用HINCRBY使计数器值增加

3.HSET所有的LWW数据(比如 "最后一次见到的")

4.用当前时间戳ZADD哈希键(hash key)到一个"挂起" set

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

1.使用ZRANGE获取所有的key

2.为每一个挂起的key发起一个作业到RabbitMQ

3.ZREM所有传入的key

现在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无疑是很适合的。它的文档是那样让人惊讶,那是因为(阅读)其文档的门槛非常的低。虽然他也有折衷(主要是如果你使用持久化),但是他们工作地很好并且比较直观。

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

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
SQL XML Oracle
Mybatis动态SQL语句查询,实现一个参数 可查询多个字段。
Mybatis动态SQL语句查询,实现一个参数 可查询多个字段。
583 0
Mybatis动态SQL语句查询,实现一个参数 可查询多个字段。
|
6月前
|
网络协议 NoSQL 网络安全
【Azure 应用服务】由Web App“无法连接数据库”而逐步分析到解析内网地址的办法(SQL和Redis开启private endpoint,只能通过内网访问,无法从公网访问的情况下)
【Azure 应用服务】由Web App“无法连接数据库”而逐步分析到解析内网地址的办法(SQL和Redis开启private endpoint,只能通过内网访问,无法从公网访问的情况下)
|
8月前
|
存储 监控 NoSQL
Redis系列学习文章分享---第十二篇(搭建哨兵集群+RedisTemplate连接哨兵+搭建分片集群+-散列插槽+集群伸缩 +故障转移+RedisTemplate访问分片集群)
Redis系列学习文章分享---第十二篇(搭建哨兵集群+RedisTemplate连接哨兵+搭建分片集群+-散列插槽+集群伸缩 +故障转移+RedisTemplate访问分片集群)
315 0
|
9月前
|
SQL 存储 NoSQL
SQL实践篇(三):什么是Redis
SQL实践篇(三):什么是Redis
81 1
|
9月前
|
NoSQL Redis
Redis集群伸缩,转移插槽失败
Redis集群伸缩,转移插槽失败
|
9月前
|
SQL 关系型数据库 MySQL
SQL编程【MySQL 01】拆分列字段为行并根据类型翻译字段 > 1305 - FUNCTION x.help_topic_id does not exist 报错问题
SQL编程【MySQL 01】拆分列字段为行并根据类型翻译字段 > 1305 - FUNCTION x.help_topic_id does not exist 报错问题
109 0
|
NoSQL Redis
Redis集群伸缩,转移插槽失败
关于Redis集群伸缩,转移插槽失败
88 0
优质!从Sql到Nosql,redis+mysql从架构到优化全覆盖
Redis是一个远程内存数据库,它不仅性能强劲,而且还具有复制特性以及为解决问题而生的独一无二的数据模型。Redis 提供了5种不同类型的数据结构,各式各样的问题都可以很自然地映射到这些数据结构上:Redis的数据结构致力于帮助用户解决问题,而不会像其他数据库那样,要求用户扭曲问题来适应数据库。除此之外,通过复制、持久化( persistence )和客户端分片( client side sharding )等特性,用户可以很方便地将Redis扩展成一个能够包含数百GB数据、每秒处理上百万次请求的系统。
|
SQL JSON 关系型数据库
「PostgreSQL」PostgreSQL 和SQL SERVER(性能和可伸缩性)
「PostgreSQL」PostgreSQL 和SQL SERVER(性能和可伸缩性)
|
存储 SQL 缓存
Redis(一)入门:NoSQL OR SQL,看完这篇你就懂了
非结构数据,根据定义是指数据结构不规则或不完整,没有任何预定义的数据模型,不方便用二维逻辑表来表现数据,例如网页日志、文本文档、图像、视频和音频文件等。
565 0
Redis(一)入门:NoSQL OR SQL,看完这篇你就懂了