写点什么

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:074644

评论

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

上亿数据怎么玩深度分页?兼容MySQL + ES + MongoDB

Kerwin

Java MySQL ES 深度分页

架构感悟 6- 平衡之美

旭东(Frank)

负载均衡

满山李子

Java这么优秀,我当然要深入啦

程序员小跃

Java Lambda

Redis进阶篇二——持久化

多选参数

redis redis6.0.0 redis集群 redis持久化

架构师训练营第 06 周——总结

李伟

极客大学架构师训练营

[架构师训练营]Week03 - 作业

谭方敏

并发业务中,线程安全与否很重要,来看看你懂多少?

Java小咖秀

Java 多线程与高并发

【week06】作业

chengjing

rdd序列化

InfoQ_6cf02607664f

By Experience的三个层次 -- 领域驱动设计的经验之谈

冯文辉

架构 领域驱动设计 DDD 架构设计

架构师训练营第六周学习总结

fenix

极客大学架构师训练营

黑鹰坠落

escray

极客时间架构师训练营 - week6 - 作业 2

jjn0703

极客大学架构师训练营

架构师训练营作业(6周)

Hanson

400GE燎原前夜,智能IP网络的核心路由器巅峰际会

脑极体

为了保存VuePress构建的网站为PDF,我竟然。。。

Leetao

Python python 爬虫 PDF vuepress pdfkit

朱嘉明:区块链对深入改革的意义何在?

CECBC

区块链技术 政策扶持 块链与经济 区块链功能 产业数字化

【week06】总结

chengjing

架构师训练营第六章总结

叮叮董董

架构师训练营第 06 周—— 练习

李伟

极客大学架构师训练营

架构师训练营第六章作业

叮叮董董

JVM详解之:java class文件的密码本

程序那些事

Java JVM class GC 密码

职业发展的迷茫与困境:你真的了解职级体系吗?

伴鱼技术团队

程序员 技术管理 人才培养 职业成长 技术人生

C、C++、Java到Python,编程入门学习什么语言好?

华为云开发者联盟

c c++ Python 编程语言 Java 分布式

计算机网络基础(三)---网络层-IP协议的转发流程

书旅

php laravel 网络协议 计算机基础 网络层

天猫小店、京东小店的问题分析

石云升

价值网络 新零售 天猫小店

redis系列之——数据类型bitmaps:今天你签到了吗?

诸葛小猿

redis bitmaps bloomfilter

CAP的原理

满山李子

架构师训练营(6周)

Hanson

从面试到入职到离职,我在B站工作的30天时光!!!

诸葛小猿

面试 B站 哔哩哔哩 收钱吧

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