共计 19182 个字符,预计需要花费 48 分钟才能阅读完成。
Redis
- 概述
- Redis 的数据结构和相干常用命令
- 数据长久化
- 内存治理与数据淘汰机制
- Pipelining
- 事务与 Scripting
- Redis 性能调优
- 主从复制与集群分片
- Redis Java 客户端的抉择
本文将从 Redis 的根本个性动手,通过讲述 Redis 的数据结构和次要命令对 Redis 的根本能力进行直观介绍。之后概览 Redis 提供的高级能力,并在部署、保护、性能调优等多个方面进行更深刻的介绍和领导。本文适宜应用 Redis 的一般开发人员,以及对 Redis 进行选型、架构设计和性能调优的架构设计人员。
概述
Redis 是一个开源的,基于内存的结构化数据存储媒介,能够作为数据库、缓存服务或音讯服务应用。Redis 反对多种数据结构,包含字符串、哈希表、链表、汇合、有序汇合、位图、Hyperloglogs 等。Redis 具备 LRU 淘汰、事务实现、以及不同级别的硬盘长久化等能力,并且反对正本集和通过 Redis Sentinel 实现的高可用计划,同时还反对通过 Redis Cluster 实现的数据主动分片能力。
Redis 的次要性能都基于单线程模型实现,也就是说 Redis 应用一个线程来服务所有的客户端申请,同时 Redis 采纳了非阻塞式 IO,并精密地优化各种命令的算法工夫复杂度,这些信息意味着:
- Redis 是线程平安的(因为只有一个线程),其所有操作都是原子的,不会因并发产生数据异样
- Redis 的速度十分快(因为应用非阻塞式 IO,且大部分命令的算法工夫复杂度都是 O(1))
- 应用高耗时的 Redis 命令是很危险的,会占用惟一的一个线程的大量解决工夫,导致所有的申请都被拖慢。(例如工夫复杂度为 O(N)的 KEYS 命令,严格禁止在生产环境中应用)
Redis 的数据结构和相干常用命令
本节中将介绍 Redis 反对的次要数据结构,以及相干的罕用 Redis 命令。本节只对 Redis 命令进行简要的介绍,且只列出了较罕用的命令。如果想要理解残缺的 Redis 命令集,或理解某个命令的具体应用办法,请参考官网文档:https://redis.io/commands
Key
Redis 采纳 Key-Value 型的根本数据结构,任何二进制序列都能够作为 Redis 的 Key 应用(例如一般的字符串或一张 JPEG 图片)对于 Key 的一些注意事项:
- 不要应用过长的 Key。例如应用一个 1024 字节的 key 就不是一个好主见,不仅会耗费更多的内存,还会导致查找的效率升高
- Key 短到缺失了可读性也是不好的,例如 ”u1000flw” 比起 ”user:1000:followers” 来说,节俭了寥寥的存储空间,却引发了可读性和可维护性上的麻烦
- 最好应用对立的标准来设计 Key,比方 ”object-typeattr”,以这一标准设计出的 Key 可能是 ”user:1000″ 或 ”commentreply-to”
- Redis 容许的最大 Key 长度是 512MB(对 Value 的长度限度也是 512MB)
String
String 是 Redis 的根底数据类型,Redis 没有 Int、Float、Boolean 等数据类型的概念,所有的根本类型在 Redis 中都以 String 体现。
与 String 相干的常用命令:
- SET:为一个 key 设置 value,能够配合 EX/PX 参数指定 key 的有效期,通过 NX/XX 参数针对 key 是否存在的状况进行区别操作,工夫复杂度 O(1)
- GET:获取某个 key 对应的 value,工夫复杂度 O(1)
- GETSET:为一个 key 设置 value,并返回该 key 的原 value,工夫复杂度 O(1)
- MSET:为多个 key 设置 value,工夫复杂度 O(N)
- MSETNX:同 MSET,如果指定的 key 中有任意一个已存在,则不进行任何操作,工夫复杂度 O(N)
- MGET:获取多个 key 对应的 value,工夫复杂度 O(N)
上文提到过,Redis 的根本数据类型只有 String,但 Redis 能够把 String 作为整型或浮点型数字来应用,次要体现在 INCR、DECR 类的命令上:
- INCR:将 key 对应的 value 值自增 1,并返回自增后的值。只对能够转换为整型的 String 数据起作用。工夫复杂度 O(1)
- INCRBY:将 key 对应的 value 值自增指定的整型数值,并返回自增后的值。只对能够转换为整型的 String 数据起作用。工夫复杂度 O(1)
- DECR/DECRBY:同 INCR/INCRBY,自增改为自减。
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 相干的常用命令:
- LPUSH:向指定 List 的左侧(即头部)插入 1 个或多个元素,返回插入后的 List 长度。工夫复杂度 O(N),N 为插入元素的数量
- RPUSH:同 LPUSH,向指定 List 的右侧(即尾部)插入 1 或多个元素
- LPOP:从指定 List 的左侧(即头部)移除一个元素并返回,工夫复杂度 O(1)
- RPOP:同 LPOP,从指定 List 的右侧(即尾部)移除 1 个元素并返回
- LPUSHX/RPUSHX:与 LPUSH/RPUSH 相似,区别在于,LPUSHX/RPUSHX 操作的 key 如果不存在,则不会进行任何操作
- LLEN:返回指定 List 的长度,工夫复杂度 O(1)
- LRANGE:返回指定 List 中指定范畴的元素(双端蕴含,即 LRANGE key 0 10 会返回 11 个元素),工夫复杂度 O(N)。应尽可能管制一次获取的元素数量,一次获取过大范畴的 List 元素会导致提早,同时对长度不可预知的 List,防止应用 LRANGE key 0 - 1 这样的残缺遍历操作。
应审慎应用的 List 相干命令:
- LINDEX:返回指定 List 指定 index 上的元素,如果 index 越界,返回 nil。index 数值是回环的,即 - 1 代表 List 最初一个地位,- 2 代表 List 倒数第二个地位。工夫复杂度 O(N)
- LSET:将指定 List 指定 index 上的元素设置为 value,如果 index 越界则返回谬误,工夫复杂度 O(N),如果操作的是头 / 尾部的元素,则工夫复杂度为 O(1)
- LINSERT:向指定 List 中指定元素之前 / 之后插入一个新元素,并返回操作后的 List 长度。如果指定的元素不存在,返回 -1。如果指定 key 不存在,不会进行任何操作,工夫复杂度 O(N)
因为 Redis 的 List 是链表构造的,上述的三个命令的算法效率较低,须要对 List 进行遍历,命令的耗时无奈预估,在 List 长度大的状况下耗时会明显增加,应审慎应用。
换句话说,Redis 的 List 理论是设计来用于实现队列,而不是用于实现相似 ArrayList 这样的列表的。如果你不是想要实现一个双端出入的队列,那么请尽量不要应用 Redis 的 List 数据结构。
为了更好反对队列的个性,Redis 还提供了一系列阻塞式的操作命令,如 BLPOP/BRPOP 等,可能实现相似于 BlockingQueue 的能力,即在 List 为空时,阻塞该连贯,直到 List 中有对象能够出队时再返回。针对阻塞类的命令,此处不做具体探讨,请参考官网文档(https://redis.io/topics/data-…)中 ”Blocking operations on lists” 一节。
Hash
Hash 即哈希表,Redis 的 Hash 和传统的哈希表一样,是一种 field-value 型的数据结构,能够了解成将 HashMap 搬入 Redis。Hash 非常适合用于表现对象类型的数据,用 Hash 中的 field 对应对象的 field 即可。Hash 的长处包含:
- 能够实现二元查找,如 ” 查找 ID 为 1000 的用户的年龄 ”
- 比起将整个对象序列化后作为 String 存储的办法,Hash 可能无效地缩小网络传输的耗费
- 当应用 Hash 保护一个汇合时,提供了比 List 效率高得多的随机拜访命令
与 Hash 相干的常用命令:
- HSET:将 key 对应的 Hash 中的 field 设置为 value。如果该 Hash 不存在,会主动创立一个。工夫复杂度 O(1)
- HGET:返回指定 Hash 中 field 字段的值,工夫复杂度 O(1)
- HMSET/HMGET:同 HSET 和 HGET,能够批量操作同一个 key 下的多个 field,工夫复杂度:O(N),N 为一次操作的 field 数量
- HSETNX:同 HSET,但如 field 曾经存在,HSETNX 不会进行任何操作,工夫复杂度 O(1)
- HEXISTS:判断指定 Hash 中 field 是否存在,存在返回 1,不存在返回 0,工夫复杂度 O(1)
- HDEL:删除指定 Hash 中的 field(1 个或多个),工夫复杂度:O(N),N 为操作的 field 数量
- HINCRBY:同 INCRBY 命令,对指定 Hash 中的一个 field 进行 INCRBY,工夫复杂度 O(1)
应审慎应用的 Hash 相干命令:
- HGETALL:返回指定 Hash 中所有的 field-value 对。返回后果为数组,数组中 field 和 value 交替呈现。工夫复杂度 O(N)
- HKEYS/HVALS:返回指定 Hash 中所有的 field/value,工夫复杂度 O(N)
上述三个命令都会对 Hash 进行残缺遍历,Hash 中的 field 数量与命令的耗时线性相关,对于尺寸不可预知的 Hash,应严格防止应用下面三个命令,而改为应用 HSCAN 命令进行游标式的遍历,具体请见 https://redis.io/commands/scan
Set
Redis Set 是无序的,不可反复的 String 汇合。
与 Set 相干的常用命令:
- SADD:向指定 Set 中增加 1 个或多个 member,如果指定 Set 不存在,会主动创立一个。工夫复杂度 O(N),N 为增加的 member 个数
- SREM:从指定 Set 中移除 1 个或多个 member,工夫复杂度 O(N),N 为移除的 member 个数
- SRANDMEMBER:从指定 Set 中随机返回 1 个或多个 member,工夫复杂度 O(N),N 为返回的 member 个数
- SPOP:从指定 Set 中随机移除并返回 count 个 member,工夫复杂度 O(N),N 为移除的 member 个数
- SCARD:返回指定 Set 中的 member 个数,工夫复杂度 O(1)
- SISMEMBER:判断指定的 value 是否存在于指定 Set 中,工夫复杂度 O(1)
- SMOVE:将指定 member 从一个 Set 移至另一个 Set
慎用的 Set 相干命令:
- SMEMBERS:返回指定 Hash 中所有的 member,工夫复杂度 O(N)
- SUNION/SUNIONSTORE:计算多个 Set 的并集并返回 / 存储至另一个 Set 中,工夫复杂度 O(N),N 为参加计算的所有汇合的总 member 数
- SINTER/SINTERSTORE:计算多个 Set 的交加并返回 / 存储至另一个 Set 中,工夫复杂度 O(N),N 为参加计算的所有汇合的总 member 数
- SDIFF/SDIFFSTORE:计算 1 个 Set 与 1 或多个 Set 的差集并返回 / 存储至另一个 Set 中,工夫复杂度 O(N),N 为参加计算的所有汇合的总 member 数
上述几个命令波及的计算量大,应审慎应用,特地是在参加计算的 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 的次要命令:
- ZADD:向指定 Sorted Set 中增加 1 个或多个 member,工夫复杂度 O(Mlog(N)),M 为增加的 member 数量,N 为 Sorted Set 中的 member 数量
- ZREM:从指定 Sorted Set 中删除 1 个或多个 member,工夫复杂度 O(Mlog(N)),M 为删除的 member 数量,N 为 Sorted Set 中的 member 数量
- ZCOUNT:返回指定 Sorted Set 中指定 score 范畴内的 member 数量,工夫复杂度:O(log(N))
- ZCARD:返回指定 Sorted Set 中的 member 数量,工夫复杂度 O(1)
- ZSCORE:返回指定 Sorted Set 中指定 member 的 score,工夫复杂度 O(1)
- ZRANK/ZREVRANK:返回指定 member 在 Sorted Set 中的排名,ZRANK 返回按升序排序的排名,ZREVRANK 则返回按降序排序的排名。工夫复杂度 O(log(N))
- ZINCRBY:同 INCRBY,对指定 Sorted Set 中的指定 member 的 score 进行自增,工夫复杂度 O(log(N))
慎用的 Sorted Set 相干命令:
- ZRANGE/ZREVRANGE:返回指定 Sorted Set 中指定排名范畴内的所有 member,ZRANGE 为按 score 升序排序,ZREVRANGE 为按 score 降序排序,工夫复杂度 O(log(N)+M),M 为本次返回的 member 数
- ZRANGEBYSCORE/ZREVRANGEBYSCORE:返回指定 Sorted Set 中指定 score 范畴内的所有 member,返回后果以升序 / 降序排序,min 和 max 能够指定为 -inf 和 +inf,代表返回所有的 member。工夫复杂度 O(log(N)+M)
- 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-… 中的相干章节
Bitmap 在 Redis 中不是一种理论的数据类型,而是一种将 String 作为 Bitmap 应用的办法。能够了解为将 String 转换为 bit 数组。应用 Bitmap 来存储 true/false 类型的简略数据极为节俭空间。
HyperLogLogs 是一种次要用于数量统计的数据结构,它和 Set 相似,保护一个不可反复的 String 汇合,然而 HyperLogLogs 并不保护具体的 member 内容,只保护 member 的个数。也就是说,HyperLogLogs 只能用于计算一个汇合中不反复的元素数量,所以它比 Set 要节俭很多内存空间。
其余常用命令
- EXISTS:判断指定的 key 是否存在,返回 1 代表存在,0 代表不存在,工夫复杂度 O(1)
- DEL:删除指定的 key 及其对应的 value,工夫复杂度 O(N),N 为删除的 key 数量
- EXPIRE/PEXPIRE:为一个 key 设置有效期,单位为秒或毫秒,工夫复杂度 O(1)
- TTL/PTTL:返回一个 key 残余的无效工夫,单位为秒或毫秒,工夫复杂度 O(1)
- RENAME/RENAMENX:将 key 重命名为 newkey。应用 RENAME 时,如果 newkey 曾经存在,其值会被笼罩;应用 RENAMENX 时,如果 newkey 曾经存在,则不会进行任何操作,工夫复杂度 O(1)
- TYPE:返回指定 key 的类型,string, list, set, zset, hash。工夫复杂度 O(1)
- CONFIG GET:取得 Redis 某配置项的以后值,能够应用 * 通配符,工夫复杂度 O(1)
- CONFIG SET:为 Redis 某个配置项设置新值,工夫复杂度 O(1)
- CONFIG REWRITE:让 Redis 从新加载 redis.conf 中的配置
数据长久化
Redis 提供了将数据定期主动长久化至硬盘的能力,包含 RDB 和 AOF 两种计划,两种计划别离有其短处和短板,能够配合起来同时运行,确保数据的稳定性。
必须应用数据长久化吗?
Redis 的数据长久化机制是能够敞开的。如果你只把 Redis 作为缓存服务应用,Redis 中存储的所有数据都不是该数据的主体而仅仅是同步过去的备份,那么能够敞开 Redis 的数据长久化机制。但通常来说,依然倡议至多开启 RDB 形式的数据长久化,因为:
- RDB 形式的长久化简直不损耗 Redis 自身的性能,在进行 RDB 长久化时,Redis 主过程惟一须要做的事件就是 fork 出一个子过程,所有长久化工作都由子过程实现
- Redis 无论因为什么起因 crash 掉之后,重启时可能主动复原到上一次 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 的长处:
- 对性能影响最小。如前文所述,Redis 在保留 RDB 快照时会 fork 出子过程进行,简直不影响 Redis 解决客户端申请的效率。
- 每次快照会生成一个残缺的数据快照文件,所以能够辅以其余伎俩保留多个工夫点的快照(例如把每天 0 点的快照备份至其余存储媒介中),作为十分牢靠的劫难复原伎俩。
- 应用 RDB 文件进行数据恢复比应用 AOF 要快很多。
RDB 的毛病:
- 快照是定期生成的,所以在 Redis crash 时或多或少会失落一部分数据。
- 如果数据集十分大且 CPU 不够强(比方单核 CPU),Redis 在 fork 子过程时可能会耗费绝对较长的工夫(长至 1 秒),影响这期间的客户端申请。
AOF
采纳 AOF 长久形式时,Redis 会把每一个写申请都记录在一个日志文件里。在 Redis 重启时,会把 AOF 文件中记录的所有写操作程序执行一遍,确保数据恢复到最新。
AOF 默认是敞开的,如要开启,进行如下配置:
appendonly yes
AOF 提供了三种 fsync 配置,always/everysec/no,通过配置项 [appendfsync] 指定:
- appendfsync no:不进行 fsync,将 flush 文件的机会交给 OS 决定,速度最快
- appendfsync always:每写入一条日志就进行一次 fsync 操作,数据安全性最高,但速度最慢
- appendfsync everysec:折中的做法,交由后盾线程每秒 fsync 一次
随着 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 的长处:
- 最平安,在启用 appendfsync always 时,任何已写入的数据都不会失落,应用在启用 appendfsync everysec 也至少只会失落 1 秒的数据。
- AOF 文件在产生断电等问题时也不会损坏,即便呈现了某条日志只写入了一半的状况,也能够应用 redis-check-aof 工具轻松修复。
- AOF 文件易读,可批改,在进行了某些谬误的数据革除操作后,只有 AOF 文件没有 rewrite,就能够把 AOF 文件备份进去,把谬误的命令删除,而后复原数据。
AOF 的毛病:
- AOF 文件通常比 RDB 文件更大
- 性能耗费比 RDB 高
- 数据恢复速度比 RDB 慢
内存治理与数据淘汰机制
最大内存设置
默认状况下,在 32 位 OS 中,Redis 最大应用 3GB 的内存,在 64 位 OS 中则没有限度。
在应用 Redis 时,应该对数据占用的最大空间有一个根本精确的预估,并为 Redis 设定最大应用的内存。否则在 64 位 OS 中 Redis 会无限度地占用内存(当物理内存被占满后会应用 swap 空间),容易引发各种各样的问题。
通过如下配置管制 Redis 应用的最大内存:
maxmemory 100mb
在内存占用达到了 maxmemory 后,再向 Redis 写入数据时,Redis 会:
- 依据配置的数据淘汰策略尝试淘汰数据,开释空间
- 如果没有数据能够淘汰,或者没有配置数据淘汰策略,那么 Redis 会对所有写申请返回谬误,但读申请依然能够失常执行
在为 Redis 设置 maxmemory 时,须要留神:
- 如果采纳了 Redis 的主从同步,主节点向从节点同步数据时,会占用掉一部分内存空间,如果 maxmemory 过于靠近主机的可用内存,导致数据同步时内存不足。所以设置的 maxmemory 不要过于靠近主机可用的内存,留出一部分预留用作主从同步。
数据淘汰机制
Redis 提供了 5 种数据淘汰策略:
- volatile-lru:应用 LRU 算法进行数据淘汰(淘汰上次应用工夫最早的,且应用次数起码的 key),只淘汰设定了有效期的 key
- allkeys-lru:应用 LRU 算法进行数据淘汰,所有的 key 都能够被淘汰
- volatile-random:随机淘汰数据,只淘汰设定了有效期的 key
- allkeys-random:随机淘汰数据,所有的 key 都能够被淘汰
- volatile-ttl:淘汰残余有效期最短的 key
最好为 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 的性能优化,次要从上面几个层面动手:
- 最后的也是最重要的,确保没有让 Redis 执行耗时长的命令
- 应用 pipelining 将间断执行的命令组合执行
-
操作系统的 Transparent huge pages 性能必须敞开:
echo never > /sys/kernel/mm/transparent_hugepage/enabled
- 如果在虚拟机中运行 Redis,可能人造就有虚拟机环境带来的固有提早。能够通过./redis-cli –intrinsic-latency 100 命令查看固有提早。同时如果对 Redis 的性能有较高要求的话,应尽可能在物理机上间接部署 Redis。
- 检查数据长久化策略
- 思考引入读写拆散机制
长耗时命令
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)命令时产生问题次要有几个方法:
- 不要把 List 当做列表应用,仅当做队列来应用
- 通过机制严格控制 Hash、Set、Sorted Set 的大小
- 可能的话,将排序、并集、交加等操作放在客户端执行
- 相对禁止应用 KEYS 命令
- 防止一次性遍历汇合类型的所有成员,而应应用 SCAN 类的命令进行分批的,游标式的遍历
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 的 number 条命令。应用SLOWLOG RESET 命令,能够重置 Slow Log
网络引发的提早
- 尽可能应用长连贯或连接池,防止频繁创立销毁连贯
- 客户端进行的批量数据操作,应应用 Pipeline 个性在一次交互中实现。具体请参照本文的 Pipelining 章节
数据长久化引发的提早
Redis 的数据长久化工作自身就会带来提早,须要依据数据的安全级别和性能要求制订正当的长久化策略:
- AOF + fsync always 的设置尽管可能相对确保数据安全,但每个操作都会触发一次 fsync,会对 Redis 的性能有比拟显著的影响
- AOF + fsync every second 是比拟好的折中计划,每秒 fsync 一次
- AOF + fsync never 会提供 AOF 长久化计划下的最优性能
- 应用 RDB 长久化通常会提供比应用 AOF 更高的性能,但须要留神 RDB 的策略配置
- 每一次 RDB 快照和 AOF Rewrite 都须要 Redis 主过程进行 fork 操作。fork 操作自身可能会产生较高的耗时,与 CPU 和 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 的主从复制,能够实现读写拆散和高可用:
- 实时性要求不是特地高的读申请,能够在 Slave 上实现,晋升效率。特地是一些周期性执行的统计工作,这些工作可能须要执行一些长耗时的 Redis 命令,能够专门布局出 1 个或几个 Slave 用于服务这些统计工作
- 借助 Redis Sentinel 能够实现高可用,当 Master crash 后,Redis Sentinel 可能主动将一个 Slave 晋升为 Master,持续提供服务
启用主从复制非常简单,只须要配置多个 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 实例以无奈承载
当上述两个问题呈现时,就必须要对 Redis 进行分片了。Redis 的分片计划有很多种,例如很多 Redis 的客户端都自行实现了分片性能,也有向 Twemproxy 这样的以代理形式实现的 Redis 分片计划。然而首选的计划还应该是 Redis 官网在 3.0 版本中推出的 Redis Cluster 分片计划。
本文不会对 Redis Cluster 的具体装置和部署细节进行介绍,重点介绍 Redis Cluster 带来的益处与弊病。
Redis Cluster 的能力
- 可能主动将数据扩散在多个节点上
- 当拜访的 key 不在以后分片上时,可能主动将申请转发至正确的分片
- 当集群中局部节点生效时仍能提供服务
其中第三点是基于主从复制来实现的,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 可能解决单节点拜访压力过大的问题
- Redis Cluster 蕴含了主从复制的能力
那是不是代表 Redis Cluster 永远是优于主从复制的抉择呢?
并不是。
软件架构永远不是越简单越好,简单的架构在带来显著益处的同时,肯定也会带来相应的弊病。采纳 Redis Cluster 的弊病包含:
- 保护难度减少。在应用 Redis Cluster 时,须要保护的 Redis 实例数倍增,须要监控的主机数量也相应减少,数据备份 / 长久化的复杂度也会减少。同时在进行分片的增减操作时,还须要进行 reshard 操作,远比主从模式下减少一个 Slave 的复杂度要高。
- 客户端资源耗费减少。当客户端应用连接池时,须要为每一个数据分片保护一个连接池,客户端同时须要放弃的连接数成倍增多,加大了客户端自身和操作系统资源的耗费。
- 性能优化难度减少。你可能须要在多个分片上查看 Slow Log 和 Swap 日志能力定位性能问题。
- 事务和 LUA Script 的应用成本增加。在 Redis Cluster 中应用事务和 LUA Script 个性有严格的限度条件,事务和 Script 中操作的 key 必须位于同一个分片上,这就使得在开发时必须对相应场景下波及的 key 进行额定的布局和标准要求。如果利用的场景中大量波及事务和 Script 的应用,如何在保障这两个性能的失常运作前提下把数据平均分到多个数据分片中就会成为难点。
所以说,在主从复制和集群分片两个计划中做出抉择时,应该从应用软件的性能个性、数据和拜访量级、将来倒退布局等方面综合思考,只在 的确有必要 引入数据分片时再应用 Redis Cluster。上面是一些倡议:
- 须要在 Redis 中存储的数据有多大?将来 2 年内可能倒退为多大?这些数据是否都须要长期保留?是否能够应用 LRU 算法进行非热点数据的淘汰?综合思考后面几个因素,评估出 Redis 须要应用的物理内存。
- 用于部署 Redis 的主机物理内存有多大?有多少能够调配给 Redis 应用?比照 (1) 中的内存需要评估,是否足够用?
- Redis 面临的并发写压力会有多大?在不应用 pipelining 时,Redis 的写性能能够超过 10 万次 / 秒(更多的 benchmark 能够参考 https://redis.io/topics/bench…)
- 在应用 Redis 时,是否会应用到 pipelining 和事务性能?应用的场景多不多?
综合下面几点思考,如果单台主机的可用物理内存齐全足以撑持对 Redis 的容量需要,且 Redis 面临的并发写压力间隔 Benchmark 值还尚有间隔,倡议采纳主从复制的架构,能够省去很多不必要的麻烦。同时,如果利用中大量应用 pipelining 和事务,也倡议尽可能抉择主从复制架构,能够缩小设计和开发时的复杂度。
Redis Java 客户端的抉择
Redis 的 Java 客户端很多,官网举荐的有三种:Jedis、Redisson 和 lettuce。
在这里对 Jedis 和 Redisson 进行比照介绍
Jedis:
- 轻量,简洁,便于集成和革新
- 反对连接池
- 反对 pipelining、事务、LUA Scripting、Redis Sentinel、Redis Cluster
- 不反对读写拆散,须要本人实现
- 文档差(真的很差,简直没有……)
Redisson:
- 基于 Netty 实现,采纳非阻塞 IO,性能高
- 反对异步申请
- 反对连接池
- 反对 pipelining、LUA Scripting、Redis Sentinel、Redis Cluster
- 不反对事务,官网倡议以 LUA Scripting 代替事务
- 反对在 Redis Cluster 架构下应用 pipelining
- 反对读写拆散,反对读负载平衡,在主从复制和 Redis Cluster 架构下都能够应用
- 内建 Tomcat Session Manager,为 Tomcat 6/7/ 8 提供了会话共享性能
- 能够与 Spring Session 集成,实现基于 Redis 的会话共享
- 文档较丰盛,有中文文档
对于 Jedis 和 Redisson 的抉择,同样应遵循前述的原理,只管 Jedis 比起 Redisson 有各种各样的有余,但也应该在须要应用 Redisson 的高级个性时再选用 Redisson,防止造成不必要的程序复杂度晋升。
Jedis:
github:https://github.com/xetorthio/…
文档:https://github.com/xetorthio/…
Redisson:
github:https://github.com/redisson/r…
文档:https://github.com/redisson/r…