本文从 Redis 基本特性入手,通过讲述 Redis 的数据结构和主要命令对 Redis 的基本能力进行直观介绍。之后概览 Redis 提供的高级能力,并在部署、维护、性能调优等多个方面深入介绍和指导。
十余年建站经验, 网站设计制作、网站建设客户的见证与正确选择。创新互联提供完善的营销型网页建站明细报价表。后期开发更加便捷高效,我们致力于追求更美、更快、更规范。
本文适合使用 Redis 的普通开发人员,以及对 Redis 进行选型、架构设计和性能调优的架构设计人员:
Redis 是一个开源的,基于内存的结构化数据存储媒介,可以作为数据库、缓存服务或消息服务使用。
Redis 支持多种数据结构,包括字符串、哈希表、链表、集合、有序集合、位图、Hyperloglogs 等。
Redis 具备 LRU 淘汰、事务实现、以及不同级别的硬盘持久化等能力,并且支持副本集和通过 Redis Sentinel 实现的高可用方案,同时还支持通过 Redis Cluster 实现的数据自动分片能力。
Redis 的主要功能都基于单线程模型实现,也就是说 Redis 使用一个线程来服务所有的客户端请求,同时 Redis 采用了非阻塞式 IO,并精细地优化各种命令的算法时间复杂度。
这些信息意味着:
Redis 的数据结构和相关常用命令
本节中将介绍 Redis 支持的主要数据结构,以及相关的常用 Redis 命令。本节只对 Redis 命令进行扼要的介绍,且只列出了较常用的命令。
如果想要了解完整的 Redis 命令集,或了解某个命令的详细使用方法,请参考官方文档:
- https://redis.io/commands
Key
Redis 采用 Key-Value 型的基本数据结构,任何二进制序列都可以作为 Redis 的 Key 使用(例如普通的字符串或一张 JPEG 图片)。
关于 Key 的一些注意事项:
String
String 是 Redis 的基础数据类型,Redis 没有 Int、Float、Boolean 等数据类型的概念,所有的基本类型在 Redis 中都以 String 体现。
与 String 相关的常用命令:
上文提到过,Redis 的基本数据类型只有 String,但 Redis 可以把 String 作为整型或浮点型数字来使用,主要体现在 INCR、DECR 类的命令上:
INCR/DECR 系列命令要求操作的 Value 类型为 String,并可以转换为 64 位带符号的整型数字,否则会返回错误。
也就是说,进行 INCR/DECR 系列命令的 Value,必须在 [-2^63 ~ 2^63 – 1] 范围内。
前文提到过,Redis 采用单线程模型,天然是线程安全的,这使得 INCR/DECR 命令可以非常便利的实现高并发场景下的精确控制。
例 1:库存控制
在高并发场景下实现库存余量的精准校验,确保不出现超卖的情况。设置库存总量:
- SET inv:remain "100"
库存扣减+余量校验:
- DECR inv:remain
当 DECR 命令返回值大于等于 0 时,说明库存余量校验通过,如果返回小于 0 的值,则说明库存已耗尽。
假设同时有 300 个并发请求进行库存扣减,Redis 能够确保这 300 个请求分别得到 99 到 -200 的返回值,每个请求得到的返回值都是唯一的,绝对不会出现两个请求得到一样的返回值的情况。
例 2:自增序列生成
实现类似于 RDBMS 的 Sequence 功能,生成一系列唯一的序列号。设置序列起始值:
- SET sequence "10000"
获取一个序列值:
- INCR sequence
直接将返回值作为序列使用即可。获取一批(如100个)序列值:
- INCRBY sequence 100
假设返回值为 N,那么 [N – 99 ~ N] 的数值都是可用的序列值。
当多个客户端同时向 Redis 申请自增序列时,Redis 能够确保每个客户端得到的序列值或序列范围都是全局唯一的,绝对不会出现不同客户端得到了重复的序列值的情况。
List
Redis 的 List 是链表型的数据结构,可以使用 LPUSH/RPUSH/LPOP/RPOP 等命令在 List 的两端执行插入元素和弹出元素的操作。
虽然 List 也支持在特定 Index 上插入和读取元素的功能,但其时间复杂度较高(O(N)),应小心使用。
与 List 相关的常用命令:
应尽可能控制一次获取的元素数量,一次获取过大范围的 List 元素会导致延迟,同时对长度不可预知的 List,避免使用 LRANGE key 0 -1 这样的完整遍历操作。
应谨慎使用的 List 相关命令:
如果指定的元素不存在,返回 -1。如果指定 Key 不存在,不会进行任何操作,时间复杂度 O(N)。
由于 Redis 的 List 是链表结构的,上述的三个命令的算法效率较低,需要对 List 进行遍历,命令的耗时无法预估,在 List 长度大的情况下耗时会明显增加,应谨慎使用。
换句话说,Redis 的 List 实际是设计来用于实现队列,而不是用于实现类似 ArrayList 这样的列表的。
如果你不是想要实现一个双端出入的队列,那么请尽量不要使用 Redis 的 List 数据结构。
为了更好支持队列的特性,Redis 还提供了一系列阻塞式的操作命令,如 BLPOP/BRPOP 等,能够实现类似于 BlockingQueue 的能力,即在 List 为空时,阻塞该连接,直到 List 中有对象可以出队时再返回。
针对阻塞类的命令,此处不做详细探讨,请参考官方文档中”Blocking operations on lists”一节:
- https://redis.io/topics/data-types-intro
Hash
Hash 即哈希表,Redis 的 Hash 和传统的哈希表一样,是一种 field-value 型的数据结构,可以理解成将 HashMap 搬入 Redis。
Hash 非常适合用于表现对象类型的数据,用 Hash 中的 field 对应对象的 field 即可。
Hash 的优点包括:
应谨慎使用的 Hash 相关命令:
上述三个命令都会对 Hash 进行完整遍历,Hash 中的 field 数量与命令的耗时线性相关,对于尺寸不可预知的 Hash,应严格避免使用上面三个命令。
而改为使用 HSCAN 命令进行游标式的遍历,具体请见:
- https://redis.io/commands/scan
Set
Redis Set 是无序的,不可重复的 String 集合。与 Set 相关的常用命令:
慎用的 Set 相关命令:
上述几个命令涉及的计算量大,应谨慎使用,特别是在参与计算的 Set 尺寸不可知的情况下,应严格避免使用。
可以考虑通过 SSCAN 命令遍历获取相关 Set 的全部 Member,具体请见:
- https://redis.io/commands/scan
如果需要做并集/交集/差集计算,可以在客户端进行,或在不服务实时查询请求的 Slave 上进行。
Sorted Set
Redis Sorted Set 是有序的、不可重复的 String 集合。Sorted Set 中的每个元素都需要指派一个分数(score),Sorted Set 会根据 Score 对元素进行升序排序。
如果多个 Member 拥有相同的 Score,则以字典序进行升序排序。Sorted Set 非常适合用于实现排名。
Sorted Set 的主要命令:
慎用的 Sorted Set 相关命令:
ZREMRANGEBYRANK/ZREMRANGEBYSCORE:移除 Sorted Set 中指定排名范围/指定 Score 范围内的所有 Member。时间复杂度 O(log(N)+M)。
上述几个命令,应尽量避免传递 [0 -1] 或 [-inf +inf] 这样的参数,来对 Sorted Set 做一次性的完整遍历,特别是在 Sorted Set 的尺寸不可预知的情况下。
可以通过 ZSCAN 命令来进行游标式的遍历,具体请见:
- https://redis.io/commands/scan
或通过 LIMIT 参数来限制返回 Member 的数量(适用于 ZRANGEBYSCORE 和 ZREVRANGEBYSCORE 命令),以实现游标式的遍历。
Bitmap 和 HyperLogLog
Redis 的这两种数据结构相较之前的并不常用,在本文中只做简要介绍,如想要详细了解这两种数据结构与其相关的命令,请参考官方文档中的相关章节:
- https://redis.io/topics/data-types-intro
Bitmap 在 Redis 中不是一种实际的数据类型,而是一种将 String 作为 Bitmap 使用的方法。
可以理解为将 String 转换为 bit 数组。使用 Bitmap 来存储 true/false 类型的简单数据极为节省空间。
HyperLogLogs 是一种主要用于数量统计的数据结构,它和 Set 类似,维护一个不可重复的 String 集合,但是 HyperLogLogs 并不维护具体的 Member 内容,只维护 Member 的个数。
也就是说,HyperLogLogs 只能用于计算一个集合中不重复的元素数量,所以它比 Set 要节省很多内存空间。
其他常用命令:
使用 RENAMENX 时,如果 Newkey 已经存在,则不会进行任何操作,时间复杂度 O(1)。
数据持久化
Redis 提供了将数据定期自动持久化至硬盘的能力,包括 RDB 和 AOF 两种方案,两种方案分别有其长处和短板,可以配合起来同时运行,确保数据的稳定性。
必须使用数据持久化吗?Redis 的数据持久化机制是可以关闭的。如果你只把 Redis 作为缓存服务使用,Redis 中存储的所有数据都不是该数据的主体而仅仅是同步过来的备份,那么可以关闭 Redis 的数据持久化机制。
但通常来说,仍然建议至少开启 RDB 方式的数据持久化,因为:
这省去了手工从其他数据源(如 DB)同步数据的过程,而且要比其他任何的数据恢复方式都要快。
RDB
采用 RDB 持久方式,Redis 会定期保存数据快照至一个 RBD 文件中,并在启动时自动加载 RDB 文件,恢复之前保存的数据。
可以在配置文件中配置 Redis 进行快照保存的时机:
- save [seconds] [changes]
意为在 [seconds] 秒内如果发生了 [changes] 次数据修改,则进行一次 RDB 快照保存,例如:
- save 60 100
会让 Redis 每 60 秒检查一次数据变更情况,如果发生了 100 次或以上的数据变更,则进行 RDB 快照保存。可以配置多条 Save 指令,让 Redis 执行多级的快照保存策略。
Redis 默认开启 RDB 快照,默认的 RDB 策略如下:
- save 900 1
- save 300 10
- save 60 10000
也可以通过 BGSAVE 命令手工触发 RDB 快照保存。
RDB 的优点:
RDB 的缺点:
AOF
采用 AOF 持久方式时,Redis 会把每一个写请求都记录在一个日志文件里。在 Redis 重启时,会把 AOF 文件中记录的所有写操作顺序执行一遍,确保数据恢复到最新。
AOF 默认是关闭的,如要开启,进行如下配置:
- appendonly yes
AOF 提供了三种 Fsync 配置,always/everysec/no,通过配置项 [appendfsync] 指定:
随着 AOF 不断地记录写操作日志,必定会出现一些无用的日志,例如某个时间点执行了命令 SET key1 “abc”,在之后某个时间点又执行了 SET key1 “bcd”,那么第一条命令很显然是没有用的。
大量的无用日志会让 AOF 文件过大,也会让数据恢复的时间过长。所以 Redis 提供了 AOF Rewrite 功能,可以重写 AOF 文件,只保留能够把数据恢复到最新状态的最小写操作集。
AOF Rewrite 可以通过 BGREWRITEAOF 命令触发,也可以配置 Redis 定期自动进行:
- auto-aof-rewrite-percentage 100
- auto-aof-rewrite-min-size 64mb
上面两行配置的含义是,Redis 在每次 AOF Rewrite 时,会记录完成 Rewrite 后的 AOF 日志大小,当 AOF 日志大小在该基础上增长了 100% 后,自动进行 AOF Rewrite。
同时如果增长的大小没有达到 64MB,则不会进行 Rewrite。
AOF 的优点:
AOF 的缺点:
内存管理与数据淘汰机制
最大内存设置
默认情况下,在 32 位 OS 中,Redis 最大使用 3GB 的内存,在 64 位 OS 中则没有限制。
在使用 Redis 时,应该对数据占用的最大空间有一个基本准确的预估,并为 Redis 设定最大使用的内存。
否则在 64 位 OS 中 Redis 会无限制地占用内存(当物理内存被占满后会使用 Swap 空间),容易引发各种各样的问题。
通过如下配置控制 Redis 使用的最大内存:
- maxmemory 100mb
在内存占用达到了 maxmemory 后,再向 Redis 写入数据时,Redis 会:
在为 Redis 设置 maxmemory 时,需要注意:如果采用了 Redis 的主从同步,主节点向从节点同步数据时,会占用掉一部分内存空间;如果 maxmemory 过于接近主机的可用内存,导致数据同步时内存不足。
所以设置的 maxmemory 不要过于接近主机可用的内存,留出一部分预留用作主从同步。
数据淘汰机制
Redis 提供了 5 种数据淘汰策略:
最好为 Redis 指定一种有效的数据淘汰策略以配合 maxmemory 设置,避免在内存使用满后发生写入失败的情况。
一般来说,推荐使用的策略是 volatile-lru,并辨识 Redis 中保存的数据的重要性。
对于那些重要的,绝对不能丢弃的数据(如配置类数据等),应不设置有效期,这样 Redis 就永远不会淘汰这些数据。
对于那些相对不是那么重要的,并且能够热加载的数据(比如缓存最近登录的用户信息,当在 Redis 中找不到时,程序会去 DB 中读取),可以设置上有效期,这样在内存不够时 Redis 就会淘汰这部分数据。
配置方法:
- maxmemory-policy volatile-lru #默认是noeviction,即不进行数据淘汰
Pipelining
Redis 提供许多批量操作的命令,如 MSET/MGET/HMSET/HMGET 等等,这些命令存在的意义是减少维护网络连接和传输数据所消耗的资源和时间。
例如连续使用 5 次 SET 命令设置 5 个不同的 Key,比起使用一次 MSET 命令设置 5 个不同的 Key,效果是一样的,但前者会消耗更多的 RTT(Round Trip Time)时长,永远应优先使用后者。
然而,如果客户端要连续执行的多次操作无法通过 Redis 命令组合在一起,例如:
- SET a "abc"
- INCR b
- HSET c name "hi"
此时便可以使用 Redis 提供的 Pipelining 功能来实现在一次交互中执行多条命令。
使用 Pipelining 时,只需要从客户端一次向 Redis 发送多条命令(以 rn)分隔,Redis 就会依次执行这些命令,并且把每个命令的返回按顺序组装在一起一次返回,比如:
- $ (printf "PINGrnPINGrnPINGrn"; sleep 1) | nc localhost 6379
- +PONG
- +PONG
- +PONG
大部分的 Redis 客户端都对 Pipelining 提供支持,所以开发者通常并不需要自己手工拼装命令列表。
Pipelining 的局限性
Pipelining 只能用于执行连续且无相关性的命令,当某个命令的生成需要依赖于前一个命令的返回时,就无法使用 Pipelining 了。通过 Scripting 功能,可以规避这一局限性。
事务与 Scripting
Pipelining 能够让 Redis 在一次交互中处理多条命令,然而在一些场景下,我们可能需要在此基础上确保这一组命令是连续执行的。
比如获取当前累计的 PV 数并将其清 0:
- > GET vCount
- 12384
- > SET vCount 0
- OK
如果在 GET 和 SET 命令之间插进来一个 INCR vCount,就会使客户端拿到的 vCount 不准确。
Redis 的事务可以确保复数命令执行时的原子性。也就是说 Redis 能够保证:一个事务中的一组命令是绝对连续执行的,在这些命令执行完成之前,绝对不会有来自于其他连接的其他命令插进去执行。
通过 MULTI 和 EXEC 命令来把这两个命令加入一个事务中:
- > MULTI
- OK
- > GET vCount
- QUEUED
- > SET vCount 0
- QUEUED
- > EXEC
- 1) 12384
- 2) OK
Redis 在接收到 MULTI 命令后便会开启一个事务,这之后的所有读写命令都会保存在队列中但并不执行,直到接收到 EXEC 命令后,Redis 会把队列中的所有命令连续顺序执行,并以数组形式返回每个命令的返回结果。
可以使用 DISCARD 命令放弃当前的事务,将保存的命令队列清空。需要注意的是,Redis 事务不支持回滚。
如果一个事务中的命令出现了语法错误,大部分客户端驱动会返回错误,2.6.5 版本以上的 Redis 也会在执行 EXEC 时检查队列中的命令是否存在语法错误,如果存在,则会自动放弃事务并返回错误。
但如果一个事务中的命令有非语法类的错误(比如对 String 执行 HSET 操作),无论客户端驱动还是 Redis 都无法在真正执行这条命令之前发现,所以事务中的所有命令仍然会被依次执行。
在这种情况下,会出现一个事务中部分命令成功部分命令失败的情况,然而与 RDBMS 不同,Redis 不提供事务回滚的功能,所以只能通过其他方法进行数据的回滚。
通过事务实现 CAS
Redis 提供了 WATCH 命令与事务搭配使用,实现 CAS 乐观锁的机制。
假设要实现将某个商品的状态改为已售:
- if(exec(HGET stock:1001 state) == "in stock")
- exec(HSET stock:1001 state "sold");
这一伪代码执行时,无法确保并发安全性,有可能多个客户端都获取到了”in stock”的状态,导致一个库存被售卖多次。
使用 WATCH 命令和事务可以解决这一问题:
- exec(WATCH stock:1001);
- if(exec(HGET stock:1001 state) == "in stock") {
- exec(MULTI);
- exec(HSET stock:1001 state "sold");
- exec(EXEC);
- }
WATCH 的机制是:在事务 EXEC 命令执行时,Redis 会检查被 WATCH 的 Key,只有被 WATCH 的 Key 从 WATCH 起始时至今没有发生过变更,EXEC 才会被执行。
如果 WATCH 的 Key 在 WATCH 命令到 EXEC 命令之间发生过变化,则 EXEC 命令会返回失败。
Scripting
通过 EVAL 与 EVALSHA 命令,可以让 Redis 执行 LUA 脚本。这就类似于 RDBMS 的存储过程一样,可以把客户端与 Redis 之间密集的读/写交互放在服务端进行,避免过多的数据交互,提升性能。
Scripting 功能是作为事务功能的替代者诞生的,事务提供的所有能力 Scripting 都可以做到。Redis 官方推荐使用 LUA Script 来代替事务,前者的效率和便利性都超过了事务。
关于 Scripting 的具体使用,本文不做详细介绍,请参考官方文档:
- https://redis.io/commands/eval
Redis 性能调优
尽管 Redis 是一个非常快速的内存数据存储媒介,也并不代表 Redis 不会产生性能问题。
前文中提到过,Redis 采用单线程模型,所有的命令都是由一个线程串行执行的,所以当某个命令执行耗时较长时,会拖慢其后的所有命令,这使得 Redis 对每个任务的执行效率更加敏感。
针对 Redis 的性能优化,主要从下面几个层面入手:
- echo never > /sys/kernel/mm/transparent_hugepage/enabled
考虑引入读写分离机制。
长耗时命令
Redis 绝大多数读写命令的时间复杂度都在 O(1) 到 O(N) 之间,在文本和官方文档中均对每个命令的时间复杂度有说明。
通常来说,O(1) 的命令是安全的,O(N) 命令在使用时需要注意,如果 N 的数量级不可预知,则应避免使用。
例如对一个 field 数未知的 Hash 数据执行 HGETALL/HKEYS/HVALS 命令,通常来说这些命令执行的很快,但如果这个 Hash 中的 field 数量极多,耗时就会成倍增长。
又如使用 SUNION 对两个 Set 执行 Union 操作,或使用 SORT 对 List/Set 执行排序操作等时,都应该严加注意。
避免在使用这些 O(N) 命令时发生问题主要有几个办法:
Redis 提供了 SCAN 命令,可以对 Redis 中存储的所有 Key 进行游标式的遍历,避免使用 Keys 命令带来的性能问题。
同时还有 SSCAN/HSCAN/ZSCAN 等命令,分别用于对 Set/Hash/Sorted Set 中的元素进行游标式遍历。
SCAN 类命令的使用请参考官方文档:
- https://redis.io/commands/scan
Redis 提供了 Slow Log 功能,可以自动记录耗时较长的命令。相关的配置参数有两个:
- slowlog-log-slower-than xxxms #执行时间慢于xxx毫秒的命令计入Slow Log
- slowlog-max-len xxx #Slow Log的长度,即最大纪录多少条Slow Log
使用 SLOWLOG GET [number] 命令,可以输出最近进入 Slow Log 的 Mumber 条命令。使用 SLOWLOG RESET 命令,可以重置 Slow Log。
网络引发的延迟
尽可能使用长连接或连接池,避免频繁创建销毁连接,客户端进行的批量数据操作,应使用 Pipeline 特性在一次交互中完成。具体请参照本文的 Pipelining 章节。
数据持久化引发的延迟
Redis 的数据持久化工作本身就会带来延迟,需要根据数据的安全级别和性能要求制定合理的持久化策略:
根据具体的情况合理配置 RDB 快照和 AOF Rewrite 时机,避免过于频繁的 Fork 带来的延迟。
Redis 在 Fork 子进程时需要将内存分页表拷贝至子进程,以占用了 24GB 内存的 Redis 实例为例,共需要拷贝 24GB / 4KB * 8 = 48MB 的数据。
在使用单 Xeon 2.27Ghz 的物理机上,这一 Fork 操作耗时 216ms。可以通过 INFO 命令返回的 latest_fork_usec 字段查看上一次 Fork 操作的耗时(微秒)。
Swap 引发的延迟
当 Linux 将 Redis 所用的内存分页移至 Swap 空间时,将会阻塞 Redis 进程,导致 Redis 出现不正常的延迟。
Swap 通常在物理内存不足或一些进程在进行大量 I/O 操作时发生,应尽可能避免上述两种情况的出现。
/proc/ /smaps 文件中会保存进程的 Swap 记录,通过查看这个文件,能够判断 Redis 的延迟是否由 Swap 产生。
如果这个文件中记录了较大的 Swap size,则说明延迟很有可能是 Swap 造成的。
数据淘汰引发的延迟
当同一秒内有大量 Key 过期时,也会引发 Redis 的延迟。在使用时应尽量将 Key 的失效时间错开。
引入读写分离机制
Redis 的主从复制能力可以实现一主多从的多节点架构,在这一架构下,主节点接收所有写请求,并将数据同步给多个从节点。
在这一基础上,我们可以让从节点提供对实时性要求不高的读请求服务,以减小主节点的压力。
尤其是针对一些使用了长耗时命令的统计类任务,完全可以指定在一个或多个从节点上执行,避免这些长耗时命令影响其他请求的响应。
主从复制与集群分片
主从复制
Redis 支持一主多从的主从复制架构。一个 Master 实例负责处理所有的写请求,Master 将写操作同步至所有 Slave。
借助 Redis 的主从复制,可以实现读写分离和高可用:
启用主从复制非常简单,只需要配置多个 Redis 实例,在作为 Slave 的 Redis 实例中配置:
- slaveof 192.168.1.1 6379 #指定Master的IP和端口
当 Slave 启动后,会从 Master 进行一次冷启动数据同步,由 Master 触发 BGSAVE 生成 RDB 文件推送给 Slave 进行导入。
导入完成后 Master 再将增量数据通过 Redis Protocol 同步给 Slave。之后主从之间的数据便一直以 Redis Protocol 进行同步。
使用 Sentinel 做自动 Failover:Redis 的主从复制功能本身只是做数据同步,并不提供监控和自动 Failover 能力,要通过主从复制功能来实现 Redis 的高可用,还需要引入一个组件:Redis Sentinel。
Redis Sentinel 是 Redis 官方开发的监控组件,可以监控 Redis 实例的状态,通过 Master 节点自动发现 Slave 节点,并在监测到 Master 节点失效时选举出一个新的 Master,并向所有 Redis 实例推送新的主从配置。
Redis Sentinel 需要至少部署 3 个实例才能形成选举关系。关键配置:
- sentinel monitor mymaster 127.0.0.1 6379 2 #Master实例的IP、端口,以及选举需要的赞成票数
- sentinel down-after-milliseconds mymaster 60000 #多长时间没有响应视为Master失效
- sentinel failover-timeout mymaster 180000 #两次failover尝试间的间隔时长
- sentinel parallel-syncs mymaster 1 #如果有多个Slave,可以通过此配置指定同时从新Master进行数据同步的Slave数,避免所有Slave同时进行数据同步导致查询服务也不可用
另外需要注意的是,Redis Sentinel 实现的自动 Failover 不是在同一个 IP 和端口上完成的。
也就是说自动 Failover 产生的新 Master 提供服务的 IP 和端口与之前的 Master 是不一样的。
所以要实现 HA,还要求客户端必须支持 Sentinel,能够与 Sentinel 交互获得新 Master 的信息才行。
集群分片
为何要做集群分片?原因如下:
当上述两个问题出现时,就必须要对 Redis 进行分片了。Redis 的分片方案有很多种,例如很多 Redis 的客户端都自行实现了分片功能,也有向 Twemproxy 这样的以代理方式实现的 Redis 分片方案。
然而选择的方案还应该是 Redis 官方在 3.0 版本中推出的 Redis Cluster 分片方案。
本文不会对 Redis Cluster 的具体安装和部署细节进行介绍,重点介绍 Redis Cluster 带来的好处与弊端。
Redis Cluster 的能力:
其中第三点是基于主从复制来实现的,Redis Cluster 的每个数据分片都采用了主从复制的结构,原理和前文所述的主从复制完全一致。
唯一的区别是省去了 Redis Sentinel 这一额外的组件,由 Redis Cluster 负责进行一个分片内部的节点监控和自动 Failover。
Redis Cluster 分片原理:Redis Cluster 中共有 16384 个 hash slot,Redis 会计算每个 Key 的 CRC16,将结果与 16384 取模,来决定该 Key 存储在哪一个 hash slot 中。
同时需要指定 Redis Cluster 中每个数据分片负责的 Slot 数。Slot 的分配在任何时间点都可以进行重新分配。
客户端在对 Key 进行读写操作时,可以连接 Cluster 中的任意一个分片,如果操作的 Key 不在此分片负责的 Slot 范围内,Redis Cluster 会自动将请求重定向到正确的分片上。
Hash Tags:在基础的分片原则上,Redis 还支持 hash tags 功能,以 hash tags 要求的格式明明的 Key,将会确保进入同一个 Slot 中。
例如:{uiv}user:1000 和 {uiv}user:1001 拥有同样的 hash tag {uiv},会保存在同一个 Slot 中。
使用 Redis Cluster 时,Pipelining、事务和 LUA Script 功能涉及的 Key 必须在同一个数据分片上,否则将会返回错误。
如要在 Redis Cluster 中使用上述功能,就必须通过 hash tags 来确保一个 Pipeline 或一个事务中操作的所有 Key 都位于同一个 Slot 中。
有一些客户端(如 Redisson)实现了集群化的 Pipelining 操作,可以自动将一个 Pipeline 里的命令按 Key 所在的分片进行分组,分别发到不同的分片上执行。
但是 Redis 不支持跨分片的事务,事务和 LUA Script 还是必须遵循所有 Key 在一个分片上的规则要求。
主从复制 VS 集群分片
在设计软件架构时,要如何在主从复制和集群分片两种部署方案中取舍呢?从各个方面看,Redis Cluster 都是优于主从复制的方案:
那是不是代表 Redis Cluster 永远是优于主从复制的选择呢?并不是。软件架构永远不是越复杂越好,复杂的架构在带来显著好处的同时,一定也会带来相应的弊端。
采用 Redis Cluster 的弊端包括:
同时在进行分片的增减操作时,还需要进行 Reshard 操作,远比主从模式下增加一个 Slave 的复杂度要高。
这就使得在开发时必须对相应场景下涉及的 Key 进行额外的规划和规范要求。如果应用的场景中大量涉及事务和 Script 的使用,如何在保证这两个功能的正常运作前提下把数据平均分到多个数据分片中就会成为难点。
所以说,在主从复制和集群分片两个方案中做出选择时,应该从应用软件的功能特性、数据和访问量级、未来发展规划等方面综合考虑,只在确实有必要引入数据分片时再使用 Redis Cluster。
下面是一些建议:
综合上面几点考虑,如果单台主机的可用物理内存完全足以支撑对 Redis 的容量需求,且 Redis 面临的并发写压力距离 Benchmark 值还尚有距离,建议采用主从复制的架构,可以省去很多不必要的麻烦。
同时,如果应用中大量使用 Pipelining 和事务,也建议尽可能选择主从复制架构,可以减少设计和开发时的复杂度。
Redis Java 客户端的选择
Redis 的 Java 客户端很多,官方推荐的有三种:
在这里对 Jedis 和 Redisson 进行对比介绍。
Jedis:
Redisson:
对于 Jedis 和 Redisson 的选择,同样应遵循前述的原理,尽管 Jedis 比起 Redisson 有各种各样的不足,但也应该在需要使用 Redisson 的高级特性时再选用 Redisson,避免造成不必要的程序复杂度提升。
Jedis:
- github:https://github.com/xetorthio/jedis
- 文档:https://github.com/xetorthio/jedis/wiki
Redisson:
- github:https://github.com/redisson/redisson
- 文档:https://github.com/redisson/redisson/wiki
网页名称:一文搞定Redis高级特性与性能调优
当前地址:http://www.shufengxianlan.com/qtweb/news33/110833.html
网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联