Redis 宝典 | 基础、高级特性与性能调优

本文由 DevOpsDays 本文由简书做者kelgon供稿,高效运维社区致力于陪伴您的职业生涯,与您一块儿愉快的成长。git

bb38b85a2e420a3534ff107c322e03a7.jpeg

做者:kelgon
连接:http://www.jianshu.com/p/2f14bc570563
來源:简书
github

本文将从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网络

经常使用命令1、Key

Redis采用Key-Value型的基本数据结构,任何二进制序列均可以做为Redis的Key使用(例如普通的字符串或一张JPEG图片)
关于Key的一些注意事项:

不要使用过长的Key。例如使用一个1024字节的key就不是一个好主意,不只会消耗更多的内存,还会致使查找的效率下降

Key短到缺失了可读性也是很差的,例如”u1000flw”比起”user:1000:followers”来讲,节省了寥寥的存储空间,却引起了可读性和可维护性上的麻烦

最好使用统一的规范来设计Key,好比”object-type:id:attr”,以这一规范设计出的Key多是”user:1000”或”comment:1234:reply-to”

Redis容许的最大Key长度是512MB(对Value的长度限制也是512MB)

经常使用命令2、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可以确保每一个客户端获得的序列值或序列范围都是全局惟一的,绝对不会出现不一样客户端获得了重复的序列值的状况。

经常使用命令3、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-types-intro) 中”Blocking operations on lists”一节。

经常使用命令4、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

经常使用命令5、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上进行。

经常使用命令6、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命令),以实现游标式的遍历。

经常使用命令7、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要节省不少内存空间。

其余经常使用命令

  • 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性能调优

尽管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。
下面是一些建议:

  1. 须要在Redis中存储的数据有多大?将来2年内可能发展为多大?这些数据是否都须要长期保存?是否可使用LRU算法进行非热点数据的淘汰?综合考虑前面几个因素,评估出Redis须要使用的物理内存。

  2. 用于部署Redis的主机物理内存有多大?有多少能够分配给Redis使用?对比(1)中的内存需求评估,是否足够用?

  3. Redis面临的并发写压力会有多大?在不使用pipelining时,Redis的写性能能够超过10万次/秒(更多的benchmark能够参考 https://redis.io/topics/benchmarks )

在使用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/jedis
文档:https://github.com/xetorthio/jedis/wiki

Redisson:github:https://github.com/redisson/redisson文档:https://github.com/redisson/redisson/wiki

相关文章
相关标签/搜索