写点什么

Redis 集群中 Key 用法的最佳实践

  • 2019-10-24
  • 本文字数:2935 字

    阅读完需:约 10 分钟

Redis 集群中 Key 用法的最佳实践

在 Redis 中,究竟什么是 Key?Redis(或任意其它的 key-value 数据库)的初衷是为每块单独的数据提供一个 Key(或称之为标识符)。Redis 通过数据类型迅速扩充了这一概念,一个 key 可以引用到多块数据,甚至上百万块的数据。随着模块的引入,key 的概念进一步被拉伸,因为单独的一块数据已经可以横跨多个 key(例如 RediSearch)。因此,当有人问 Redis 是否是一个 Key-value 数据库时,我一般都回答“它是一个发源于 key-value 的数据库”,但需要注意的是,现在已经很难把 Redis 仅仅当做一个 key-value 数据库来看待了。


然而,在集群中,Redis 的 Key 依然是非常重要的。在 Redis 集群中,每个节点或分片都只有部分 key 和数据。当然了,如果 Redis 以高可用性方式运行,数据还可能会分布在从节点中。但不管怎么说,一个单独的 key 是不会分布到多个节点中的。


一个集群被分割成 16384 个 slot,这是集群能支持的最大节点数或分片数(如果你需要有更多的节点数或分片数,请与我们联系)。由于大多数集群都是由少量节点组成的,这些 slot 用来对 key 做逻辑划分。在以下 4 节点的简化集群实例中,我们有如下的 slot 分布:



举个例子,如果你知道一个 key 它在 slot 2000 上,那你就知道它在 Node #0 上。同样的,如果你知道一个 key 它在 slot 9000 上,那它就在 Node#02 上。实际上,现实中的集群比这要复杂得多,因为 slot 可能一直在节点间移动和重新平衡,但为了理解事务和 key,这种集群的简化概念理解就够了。


那么 key 和 slot 到底是怎么关联起来的呢?这些 slot 实际上都是散列 slot,每个 key 都先用一个散列函数进行计算(这个散列函数可接受任意长度的字符串),计算完成后得到一个散列数字。散列经常用在密码这个类似但又比这复杂得多的场合,一般来说密码都不是直接存储的,而是存储为密码的某个数学表示。在 Redis 中,你访问请求的 Key,本质上也是访问这个 key 的数学表示,只是在这个场景中,我们使用的是 crc16 散列函数。Crc16 散列函数返回的是一个 14bit 的整数,我们用这个返回的整数除于 16384 并取其余数。这刚好是 Redis 集群能支持的最大分片数,很有趣,不是吗?(译者注:作者可能想表达的意思是,crc16 返回的是一个 14bit 的整数,2 的 14 次方刚好是 16384,集群最大能支持的 slot 个数也是 16384,这两个数字刚好相等,所以有趣。)

这一切如何和 Redis 事务关联起来?

Redis 的事务仅支持单个 Slot,这可确保 Redis 的最大吞吐量。因为不需要节点/分片间的通信,这也避免了很多的故障情况。鉴于此,当你在 Redis 中执行事务的时候,你要确保所有 Key 都在同一个 slot 中。那么,怎么知道事务中一个 key 跟其它 key 是在同一个 slot 中呢(或者在同一个节点/分片中)?


虽然许多 key 可能是属于同一个 slot 的,但从 key 的命名角度来说这也是难于预测的,给 key 命名时每次都去检查下 key 是否同一个 slot 也不现实(Redis 开源版本或企业版本中,都支持 CLUSTER KEYSLOT 命令)。解决这个问题的最佳方式是对 key 进行规划和使用 hash tag 特性。在开源 Redis 中,花括号{}表示 hash tag,这个两个花括号中间的字符才会进行 CRC16 散列计算。让我们看几个例子:



在这些示例中,你可以看到,user-session:1234 和 user-profile:1234 这两个 key 是不允许在同一个事务中的,但 user-profile:{1234} 和 user-session:{1234} 是可以在同一个事务中的。


注意:你可能会想:“太好了,那我将所有 key 都放在同一个 slot 中,那就不用去担心集群的事务了”。很好,你并不孤单,因为我已经不是第一次听到这种不良导向了。Redis 不会阻止你干这种事情或者其它类似的事情,但你将会得到一个不均衡的集群,或者可能更糟糕的是,一个节点爆满了,但其他很多节点都是空的。只在有必要的时候使用 hash tag ,也同时要谨慎的使用它。


Redis 企业版也可以支持这种策略,但还增加了一个特性,使得数据分片更加透明。这个策略不是使用花括号,而是使用正则表达式来定义 key 中的哪部分进行 hash 运算。让我们用正则表达式“/user-.*:(?.+)/ ”来重新审视下上面的那个例子:



这个正则表达式足够灵活,以致于其它“user-”开头的 key 也可以被处理,所以我们也可以使用类似“user-image”或“user-pagecount”的 key。采用这个方案,每个用户的信息都保存在一个单独的 slot 中,可保证一个用户范围内的各种事务都可以被处理。


让我们来进一步扩展下这个样例。假设用户更改了他个人资料的一些信息,我们想要同时更新他的个人资料以及会话信息,并延长会话使会话不被过期。以下是一个典型(简化的)的事务实现版本:


>MULTIOK> HSET user-profile:1234username "king foo"QUEUED> HSET user-session:1234 username"king foo"QUEUED> EXPIRE user-session:12347200QUEUED> EXEC1) (integer) 12) (integer) 13) (integer) 1
复制代码


如果你要在 Redis 开源版本中运行这个样例,你必须保证这些 key 都有花括号,否则你会得到一个 CROSSSLOT 错误。这个错误的好处就是 Redis 会立刻通知你无效事务和跨 slot 违规。


>MULTIOK> HSET user-profile:1234username "king foo"QUEUED> HSET user-session:1234username "king foo"(error) ERR CROSSSLOT Keys inrequest don't hash to the same slot (command='HSET', key='user-session:1234')within 'MULTI'> EXPIRE user-session:12347200(error) ERR CROSSSLOT Keys inrequest don't hash to the same slot (command='EXPIRE', key='user-session:1234')within 'MULTI'> EXEC(error) EXECABORT Transactiondiscarded because of previous errors.
复制代码


请记住,slot 和 key 的问题并不局限于事务,其他场景下它们也是类似的,特别是一个命令需要操作多个 Key 的时候。举个例子:


>LPUSH my-list 1 2 3(integer) 3> RPOPLPUSH my-listmy-new-list(error) ERR CROSSSLOT Keys inrequest don't hash to the same slot (command='RPOPLPUSH', key='my-new-list')
复制代码


RPOPLPUSH 是个原子操作,它从一个 list 中取出元素,并将元素放入到另外一个 list 中,这个操作是原子性的。如果这两个 list 是两个不同的 slot 中(就像事务场景那样),你会得到一个 CROSSSLOT 错误。Redis 开源版本是严格限制这点的,任何命令同时操作两个 slot 都是禁止的。

充分利用集群

如果你已经是 Redis 单实例的高级用户,切换到集群后你会感觉到有些奇怪。你已经依赖的部分命令或事务,在集群中可能无法正常工作。如果你真的不走运,你之前设计的 keyspace 可能都会有问题。以下是让你的应用程序与 Redis 集群最佳工作的一些设计提示:


1、考虑 keyspace 设计。key 是否存在一些公共特征可将工作负载智能分散(按用户、按操作,按时间,或者其他)。使用 hashtag 或正则表达式将 key 分散到不同的 slot。


2、避免使用单个 key 来保存全局状态,而且这个操作还是事务性的,否则你很大可能会碰到 CROSSSLOTS 错误。


3、评估你的 MULTI/EXEC 事务。考虑下你是否真的需要事务,或者使用 pipeline 是否就能满足你要求。也不要忘记考虑多 key 的命令,这些多 key 操作的命令是否可以替换为多个命令来操作。


本文转载自公众号中间件小哥(ID:huawei_kevin)。


原文链接:


https://mp.weixin.qq.com/s/KKcd1LmVAvOF51jEx1Yvvw


2019-10-24 11:074931

评论

发布
暂无评论
发现更多内容

完美!字节3-1级别大佬把《数据结构与算法(1),mybatisorm原理

Java 程序员 后端

如何理解互斥锁、条件锁、读写锁以及自旋锁(1),mysql入门到精通电子书

Java 程序员 后端

字节跳动Java开放岗面经:14天快速面试,已拿offer,Java全套百度云

Java 程序员 后端

字节跳动,三面我败了!但是我把经验记录了下来,java编程思想第六版百度云

Java 程序员 后端

字节首席架构师整合面试痛点,成就399页Java框架核心宝典

Java 程序员 后端

如何正确使用Spring Cloud Zookeeper,不懂来学,java教程下载网盘

Java 程序员 后端

对Stream-API的用法鼓吹够多了,但性能到底怎么样呢?,mybatis和spring集成原理

Java 程序员 后端

完美!字节3-1级别大佬把《数据结构与算法,linux翻墙教程视频

Java 程序员 后端

Clickhouse技术分享

scalad

大数据 实时数仓 Clickhouse OLAP开源引擎

实现一个简单的“个人博客”项目,java基础大纲思维导图

Java 程序员 后端

如果当时这15道题能答好,现在应该已经被录取了(记一次面试的亲身经历 2020-7-20

Java 程序员 后端

学IT的人太多了,现在入行还有出路吗?,linux环境高级编程

Java 程序员 后端

华为云专家向宇:工欲善其事必先利其器,才能做数据的“管家”

华为云数据库小助手

GaussDB GaussDB(for Influx) 华为云数据库 华为云数据库创新Lab

学生管理系统(SSM简易版)总结,斗鱼Java开发二面被刷

Java 程序员 后端

学弟学妹们请不要错过自己的“黄金奋斗三年”,java实战项目代码

Java 程序员 后端

实习生想面阿里应该掌握掌握哪些知识点?给学弟学妹们支招

Java 程序员 后端

如何快速成长为技术大牛?阿里资深技术专家的总结亮了!

Java 程序员 后端

字节跳动一年一更的400多页算法刷题宝典已更新,力扣官网沸腾

Java 程序员 后端

学会RabbitMQ代理的连接,是一种怎样的体验?,mongodb教程

Java 程序员 后端

安利一款非常NICE的-API-敏捷开发工具,java注释快捷键视频

Java 程序员 后端

如何让阿三 Windows 10、11 的恢复分区(Recovery Partition

Java 程序员 后端

如果当时这16道题能答好,现在应该已经被录取了(记一次面试的亲身经历 2020-9-9

Java 程序员 后端

学习高并发的前置知识——Java中的线程基础,springcloud实战演练

Java 程序员 后端

完美!白嫖4份满分级“并发编程,java架构师技术栈

Java 程序员 后端

小白必看!结合实际实例,理解事务,多线程面试题java

Java 程序员 后端

完全没想到,他竟然靠这个拿到了40万年薪的大厂AI岗offer!

Java 程序员 后端

实现一个简单的HTTP,京东java面试问题大全及答案大全

Java 程序员 后端

如何让自己像打王者荣耀一样发了疯、拼了命、石乐志的学习

Java 程序员 后端

如何设计一个百万级用户的抽奖系统?,三面蚂蚁核心金融部

Java 程序员 后端

如何阅读一本书-读书笔记,java二到三年经验面试题

Java 程序员 后端

学透这份java进阶笔记,才知道为什么能一起斩获几十家大厂offer一定是有原因的

Java 程序员 后端

Redis 集群中 Key 用法的最佳实践_文化 & 方法_中间件小哥_InfoQ精选文章