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)。


String

String是Redis的基础数据类型,Redis没有Lnt、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中有对象可以出队时再返回。


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,应严格避免使用上面三个命令。


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尺寸不可知的情况下,应严格避免使用。


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的尺寸不可预知的情况下。


Bitmap和HyperLogLog

Redis的这两种数据结构相较之前的并不常用,在本文中只做简要介绍。

Bitmap在Redis中不是一种实际的数据类型,而是一种将String作为Bitmap使用的方法。可以理解为将String转换为bit数组。使用Bitmap来存储true/false类型的简单数据极为节省空间。

HyperLogLogs是一种主要用于数量统计的数据结构,它和Set类似,维护一个不可重复的String集合,但是HyperLogLogs并不维护具体的member内容,只维护member的个数。也就是说,HyperLogLogs只能用于计算一个集合中不重复的元素数量,所以它比Set要节省很多内存空间。