目录
一、redis常用的全局命令
对于键来说Redis的5种数据结构有一些通用的命令
1.查看所有键:keys * (不建议使用,redis单线程模式会造成堵塞)
将所有的键输出keys命令 会遍历所有键,所以它的时间复杂度是O(n),当Redis保存了大量键 时,线上环境禁止使用。
2.查看键总数:dbsize
将返回当前数据库中键的总数,不会遍历所有键,而是直接获取Redis内置的键总数变量,所以dbsize命令的时间复杂度是O(1)
3.检查键是否存在:exists key
键存在则返回1,不存在则返回0
4.删除键:del key [key ...]
返回结果为成功删除键的个数,支持一条命令删除多个,删除不存在的键就会返 回0
5.键设置过期时间:expire key seconds
超过过期时间后,会自动删除 键,例如为键hello设置了10秒过期时间,十秒之后自动删除:
> set hello world #设置键
> expire hello 10 #给键添加过期时间
6.查看键剩余过期时间:ttl key
返回键的剩余过期时间,三种返回值类型:
·大于等于0的整数:键剩余的过期时间
·-1:键没设置过期时间
·-2:键不存在
7.键的数据结构类型:type key
如果键不存在,则返回none,存在返回键的数据结构类型,五种数据结构的其中一种
在文章开头我们简单讲键的几个通用命令,在下面文章里我们将会按照单个键、遍历键、数据库管理三个维度对剩余的一些通用命令进行介绍。
二、Redis数据结构对应的内部编码以及使用场景
1.五种数据结构类型
string(字符串)、hash(哈希)、list(列表)、set(集合)、zset(有序集合)
2.五种数据结构对应的内部编码
type命令查询键的数据类型实际返回的是当前键对外的数据结构类型:string(字符串)、hash(哈希)、list(列表)、set(集合)、zset(有 序集合),实际上每种数据结构都有自己底层的内部编码实现,而且是多种实现;
Redis会在合适的场景选择合适的内部编码,每种数据结构都有两种以上的内部编码实现,同时有些内部编码,例如 ziplist,可以作为多种外部数据结构的内部实现,可以通过object encoding命令查询键的内部编码:object encoding key;
Redis这样设计有两个好处:
- 1、当redis改进内部编码进行内部编码的升级时,对外的数据结构和命令是没有影响的,对外部用户来说基本是无感知的;
- 2、多种内部编码实现可以在不同场景下发挥各自的优势,例如ziplist比较节省内存,但是在列表元素比较多的情况下,性能会有所下降,这时候Redis会根据配置选项将 列表类型的内部实现转换为linkedlist
3.五种数据结构常见的使用场景
string(字符串):缓存功能、计数、共享Session、限速
hash(哈希):存储用户信息(数据库)
list(列表):消息队列、文章列表(分页查询)
set(集合):标签、社交
zset(有序集合):排行榜系统、社交
三、Redis的单线程架构
Redis使用了单线程架构和I/O多路复用模型来实现高性能的内存数据库服务
1.redis的命令执行流程
因为Redis是单线程来处理命令的,所以一条命令从客户端达到服务端不会立刻被执行,所有命令都会进入一 个队列中,然后逐个被执行。
命令执行流程:
所以上面3个客户端命令的执行顺序是不确定的,但是可以确定不会有两条命令被同时执行 ;所以两条incr命令无论怎么执行最终结果都是2,不会产生并发问题,这就是Redis单线程的基本模型。
2.redis的单线程如何做到快速响应的
redis单线程还能这么快的原因总结以下三点:
第一,纯内存访问,Redis将所有数据放在内存中,内存的响应时 长大约为100纳秒,这是Redis达到每秒万级别访问的重要基础。
第二,非阻塞I/O,Redis使用epoll作为I/O多路复用技术的实现,再 加上Redis自身的事件处理模型将epoll中的连接、读写、关闭都转换为 事件,不在网络I/O上浪费过多的时间
第三,单线程避免了线程切换和竞态产生的消耗
- 单线程可以简化数据结构和算法的实现;
- 单线程避免了线程切换和竞态产生的消耗,对于服务端开发来说,锁和线程切换通常是性能杀手;但是单线程会有一个问题:对于每个命令的执行时间是有要求的。 如果某个命令执行过长,会造成其他命令的阻塞,对于Redis这种高性 能的服务来说是致命的,所以Redis是面向快速执行场景的数据库。
四、string(字符串)类型
字符串类型是Redis最基础的数据结构,redis中键都是字符串类型;字符串类型的值实际可以是字符串(简单的字符串、复杂的字符串(例如JSON、 XML))、数字(整数、浮点数),甚至是二进制(图片、音频、视 频),但是值最大不能超过512MB。
1.常用命令
设置值:set key value [ex seconds] [px milliseconds] [nx|xx]
·ex seconds:为键设置秒级过期时间。
·px milliseconds:为键设置毫秒级过期时间。
·nx:键必须不存在,才可以设置成功,用于添加。
·xx:与nx相反,键必须存在,才可以设置成功,用于更新。
Redis还提供了setex和setnx两个命令,它们的作用和ex和nx选项是一样的;
在实际使用中有什么应用场景吗?以setnx命令为例 子,由于Redis的单线程命令处理机制,如果有多个客户端同时执行 setnx key value,根据setnx的特性只有一个客户端能设置成功,setnx可 以作为分布式锁的一种实现方案,Redis官方给出了使用setnx实现分布 式锁的方法:http://redis.io/topics/distlock 。
获取值:get key
如果要获取的键不存在,则返回nil(空)
批量设置值:mset key value [key value ...]
> mset a 1 b 2 c 3 d 4
批量获取值:mget key [key ...]
> mget a b c d
如果有些键不存在,那么它的值为nil(空)
计数:incr key
incr命令用于对值做自增操作,返回结果分为三种情况:
·值不是整数,返回错误。
·值是整数,返回自增后的结果。
·键不存在,按照值为0自增,返回结果为1。
Redis提供了decr(自减)、incrby(自增指定数字)、decrby(自减指定数字)、incrbyfloat(自增浮点数):
decr key
incrby key increment
decrby key decrement
incrbyfloat key increment
追加值:append key value
字符串长度:strlen key(每个中文占用3个字节)
设置并返回原值:getset key value(set一样会设置值,但是它同时会返回键原来的值)
设置指定位置的字符:setrange key offeset value
获取部分字符串:getrange key start end
各命令的时间复杂度:
2.批量处理命令的优点
批量操作命令可以有效提高开发效率,假如没有mget这样的命令, 要执行n次get命令需要按照下图的方式来执行:
使用mget命令后,要执行n次get命令操作方式:
Redis可以支撑每秒数万的读写操作,但是这指的是Redis服务端的处理能力,对于客户端来说,一次命令除了命令时间还是有网络时间, 假设网络时间为1毫秒,命令时间为0.1毫秒(按照每秒处理1万条命令 算),那么执行1000次get命令和1次mget命令的区别如表2-1,因为 Redis的处理能力已经足够高,对于开发人员来说,网络可能会成为性 能的瓶颈。
学会使用批量操作,有助于提高业务处理效率,但是要注意的是每次批量操作所发送的命令数不是无节制的,如果数量过多可能造成 Redis阻塞或者网络拥塞;
3.内部编码
Redis会根据当前值的类型和长度决定使用哪种内部编码实现,字符串类型的内部编码有3种:
·int:8个字节的长整型。
·embstr:小于等于39个字节的字符串。
·raw:大于39个字节的字符串。
4.常见使用场景
- 1.缓存功能:
- 1.收到请求后首先从Redis获取需要的信息
- 2.如果没有从Redis获取到信息,需要从MySQL中进行获取,并将结果回写到Redis,添加过期时间;
Redis作为缓存层, MySQL作为存储层,绝大部分请求的数据都是从Redis中获取。由于 Redis具有支撑高并发的特性,所以缓存通常能起到加速读写和降低后 端压力的作用。
- 2.计数
许多应用都会使用Redis作为计数的基础工具,它可以实现快速计数、查询缓存的功能,同时数据可以异步落地到其他数据源。例如笔者 所在团队的视频播放数系统就是使用Redis作为视频播放数计数的基础 组件,用户每播放一次视频,相应的视频播放数就会自增1;一个真实的计数系统要考虑的问题会很多:防作弊、按照不 同维度计数,数据持久化到底层数据源等。
- 3.共享Session
一个分布式Web服务将用户的Session信息(例如用 户登录信息)保存在各自服务器中,这样会造成一个问题,出于负载均衡的考虑,分布式服务会将用户的访问均衡到不同服务器上,用户刷新 一次访问可能会发现需要重新登录,这个问题是用户无法容忍的。
为了解决这个问题,可以使用Redis将用户的Session进行集中管 理,如图所示,在这种模式下只要保证Redis是高可用和扩展性的, 每次用户更新或者查询登录信息都直接从Redis中集中获取;
- 4.限速
例如很多应用出于安全的考虑,会在每次进行登录时,让用户输入手机 验证码,从而确定是否是用户本人。但是为了短信接口不被频繁访问, 会限制用户每分钟获取验证码的频率,例如一分钟不能超过5次;
phoneNum = "138xxxxxxxx";
key = "shortMsg:limit:" + phoneNum;
// SET key value EX 60 NX
isExists = redis.set(key,1,"EX 60","NX");
if(isExists != null || redis.incr(key) <=5){
// 通过
}else{
// 限速
}
通过手机号设置一个key加上60秒的过期时间,每次收到发送请求后做一个key的自增并比较当前值是否大于等于5,如果大于等于5就不发送;
例如一些网站限制一个IP地址 不能在一秒钟之内访问超过n次也可以采用类似的思路;
五、hash(哈希)类型
在Redis中,哈希类型是指键值本身又是一 个键值对结构,形如value={{field1,value1},...{fieldN,valueN}}
哈希类型中的映射关系叫作field-value,注意这里的value是指field,对应的值,不是键对应的值,请注意value在不同上下文的作用;
1.常用命令
- 设置值:hset key field value
如果设置成功会返回1,反之会返回0。此外Redis提供了hsetnx命 令,它们的关系就像set和setnx命令一样,只不过作用域由键变为field
- 获取值:hget key field
如果键或field不存在,会返回nil
- 删除field:hdel key field [field ...]
hdel会删除一个或多个field,返回结果为成功删除field的个数
- 计算field个数:hlen key
- 批量设置field-value:hmset key field value [field value ...]
- 批量获取field-value:hmget key field [field ...]
- 判断field是否存在:hexists key field
包含域,所以返回结果为1,不包含时返回0
- 获取所有field:hkeys key
hkeys命令应该叫hfields更为恰当,它返回指定哈希键所有的field
- 获取所有value:hvals key
- 获取所有的field-value:hgetall key
注意:在使用hgetall时,如果哈希元素个数比较多,会存在阻塞Redis的可 能。如果开发人员只需要获取部分field,可以使用hmget,如果一定要 获取全部field-value,可以使用hscan命令,该命令会渐进式遍历哈希类型
- field自增指定数字:hincrby key field和hincrbyfloat key field
hincrby和hincrbyfloat,就像incrby和incrbyfloat命令一样,但是它们 的作用域是filed
- 计算value的字符串长度:hstrlen key field
各命令时间复杂度:
2.内部编码
哈希类型的内部编码有两种:
- ziplist(压缩列表):当哈希类型元素个数小于hash-max-ziplistentries配置(默认512个)、同时所有值都小于hash-max-ziplist-value配 置(默认64字节)时,Redis会使用ziplist作为哈希的内部实现,ziplist使 用更加紧凑的结构实现多个元素的连续存储,所以在节省内存方面比 hashtable更加优秀。
- hashtable(哈希表):当哈希类型无法满足ziplist的条件时,Redis 会使用hashtable作为哈希的内部实现,因为此时ziplist的读写效率会下 降,而hashtable的读写时间复杂度为O(1)
1)当field个数比较少且没有大的value时,内部编码为ziplist
2)当有value大于64字节,内部编码会由ziplist变为hashtable
3)当field个数超过512,内部编码也会由ziplist变为hashtable
3.常见使用场景
关系型数据表记录的两条用户信息,用户的属性作为表的 列,每条用户信息作为行
将其用哈希类型存储
相比于使用字符串序列化缓存用户信息,哈希类型变得更加直观, 并且在更新操作上会更加便捷。可以将每个用户的id定义为键后缀,多 对field-value对应每个用户的属性
哈希类型和关系型数据库有两点不同之处:
- ·哈希类型是稀疏的,而关系型数据库是完全结构化的,例如哈希 类型每个键可以有不同的field,而关系型数据库一旦添加新的列,所有 行都要为其设置值(即使为NULL),如图2-17所示。
- ·关系型数据库可以做复杂的关系查询,而Redis去模拟关系型复杂 查询开发困难,维护成本高
4.三种方法缓存用户信息,实现方法和优缺点分析:
1)原生字符串类型:每个属性一个键。
set user:1:name tom
set user:1:age 23
set user:1:city beijing
优点:简单直观,每个属性都支持更新操作。
缺点:占用过多的键,内存占用量较大,同时用户信息内聚性比较 差,所以此种方案一般不会在生产环境使用。
2)序列化字符串类型:将用户信息序列化后用一个键保存。
set user:1 serialize(userInfo)
优点:简化编程,如果合理的使用序列化可以提高内存的使用效 率。
缺点:序列化和反序列化有一定的开销,同时每次更新属性都需要 把全部数据取出进行反序列化,更新后再序列化到Redis中。
3)哈希类型:每个用户属性使用一对field-value,但是只用一个键 保存。
hmset user:1 name tomage 23 city beijing
优点:简单直观,如果使用合理可以减少内存空间的使用。
缺点:要控制哈希在ziplist和hashtable两种内部编码的转换, hashtable会消耗更多内存
六、list(列表)类型
列表(list)类型是用来存储多个有序的字符串,列表中的每个字符串称为元素(element),一个列表最多可以存储(2 的32次幂 -1个)元素,在Redis中,可以对列表两端插入(push)和弹出(pop),还可以获取 指定范围的元素列表、获取指定索引下标的元素等;
它可以充当栈和队列的角色,在实际开发上有很多应用场景;
1.常用命令
列表两端插入和弹出操作:
子列表获取、删除等操作:
列表中可以包含重复元素:
列表类型有两个特点:
1、列表中的元素是有序的,通过索引下标获取某个元素或者某个范围内的元素列表
2、列表中的元素可以是重复的
- 1.添加操作
(1)从右边插入元素:rpush key value [value ...]
(2)从左边插入元素:lpush key value [value ...]
(3)向某个元素前或者后插入元素:linsert key before|after pivot value
- 2.查找
(1)获取指定范围内的元素列表:lrange key start end
从左到右获取列表的所有元素:lrange listkey 0 -1
(2)获取列表指定索引下标的元素:lindex key index
(3)获取列表长度:llen key
- 3.删除
(1)从列表左侧弹出元素:lpop key
将列表最左侧的元素c会被弹出
(2)从列表右侧弹出:rpop key
它的使用方法和lpop是一样的,只不过从列表右侧弹出
(3)删除指定元素:lrem key count value
lrem命令会从列表中找到等于value的元素进行删除,根据count的 不同分为三种情况: ·count>0,从左到右,删除最多count个元素。
·count<0,从右到左,删除最多count绝对值个元素。
·count=0,删除所有
(4)按照索引范围修剪列表:ltrim key start end
只保留列表listkey第start个到第end个元素
- 4.修改
修改指定索引下标的元素:lset key index newValue
- 5.阻塞操作
阻塞式弹出如下:
blpop key [key ...] timeout
brpop key [key ...] timeout
blpop和brpop是lpop和rpop的阻塞版本,阻塞弹出时的两个参数:
key[key...]:多个列表的键。
timeout:阻塞时间(单位:秒),只在列表为空时生效
1)列表为空:如果timeout=3,那么客户端要等到3秒后返回,如果 timeout=0,那么客户端一直阻塞等下去;
如果等待期间列表中添加了数据,客户端立即返回;
2)列表不为空:客户端会立即返回。
在使用brpop时,有两点需要注意:
1)如果是brpop多个键,那么brpop会从左至右遍历键,一旦有一 个键能弹出元素,客户端立即返回
2)如果多个客户端对同一个键执行brpop,那么最先执行 brpop命令的客户端可以获取到弹出的值,而其他客户端继续阻塞;
各命令的时间复杂度:
2.内部编码
列表类型的内部编码有两种:
- ·ziplist(压缩列表):当列表的元素个数小于list-max-ziplist-entries 配置(默认512个),同时列表中每个元素的值都小于list-max-ziplist-value配置时(默认64字节),Redis会选用ziplist来作为列表的内部实现 来减少内存的使用。
- ·linkedlist(链表):当列表类型无法满足ziplist的条件时,Redis会 使用linkedlist作为列表的内部实现。
127.0.0.1:6379> rpush listkey e1 e2 e3
(integer) 3
127.0.0.1:6379> object encoding listkey
"ziplist"
Redis3.2版本提供了quicklist内部编码,简单地说它是以一个ziplist 为节点的linkedlist,它结合了ziplist和linkedlist两者的优势,为列表类型 提供了一种更为优秀的内部编码实现,它的设计原理可以参考Redis的 另一个作者Matt Stancliff的博客:https://matt.sh/redis-quicklist 。
3.常见使用场景
- 1.消息队列
Redis的lpush+brpop命令组合即可实现阻塞队列,生 产者客户端使用lrpush从列表左侧插入元素,多个消费者客户端使用 brpop命令阻塞式的“抢”列表尾部的元素,多个客户端保证了消费的负 载均衡和高可用性。
- 2.文章列表
每个用户有属于自己的文章列表,现需要分页展示文章列表。此时 可以考虑使用列表,因为列表不但是有序的,同时支持按照索引范围获 取元素。
1)每篇文章使用哈希结构存储,例如每篇文章有3个属性title、 timestamp、content:
hmset acticle:1 title xx timestamp 1476536196 content xxxx
...
hmset acticle:k title yy timestamp 1476512536 content yyyy
...
2)向用户文章列表添加文章,user:{id}:articles作为用户文章列 表的键:
lpush user:1:acticles article:1 article3
...
lpush user:k:acticles article:5
...
3)分页获取用户文章列表,例如下面伪代码获取用户id=1的前10 篇文章:
articles = lrange user:1:articles 0 9
for article in {articles}
hgetall {article}
使用列表类型保存和获取文章列表会存在两个问题:
第一,如果每 次分页获取的文章个数较多,需要执行多次hgetall操作,此时可以考虑 使用Pipeline批量获取,或者考虑将文章数据序列化为字符串类型,使用mget批量获取。
第二,分页获取文章列表时,lrange 命令在列表两端性能较好,但是如果列表较大,获取列表中间范围的元 素性能会变差,此时可以考虑将列表做二级拆分,或者使用Redis3.2的quicklist内部编码实现,它结合ziplist和linkedlist的特点,获取列表中间范围的元素时也可以高效完成。
列表的使用场景很多,在选择时可以参考以下口诀:
·lpush+lpop=Stack(栈)
·lpush+rpop=Queue(队列)
·lpsh+ltrim=Capped Collection(有限集合)
·lpush+brpop=Message Queue(消息队列)
七、set(集合)类型
集合(set)类型也是用来保存多个的字符串元素,但和列表类型不一样的是,集合中不允许有重复元素,并且集合中的元素是无序的,不能通过索引下标获取元素。Redis除了支持集合内的增删改查,同时还支持多个集合取交 集、并集、差集;最多可以存储2的 32次幂 -1 个元素,这里和hash有点类似,下面我们通过两张图来看一下hash和set的区别:
hash:哈希类型是指键值本身又是一 个键值对结构,形如value={{field1,value1},...{fieldN,valueN}}
set:保存多个的字符串元素,和列表类型类似,不一样的是集合中不允许有重复元素,并且集合中的元素是无序的
通过上边的两张图我们就能清晰的看出set和hash的区别了,下面我们来看一下set的常用命令和该数据结构的特点;
1.常用命令
一、集合内操作
- (1)添加元素:sadd key element [element ...]
返回结果为添加成功的元素个数
- (2)删除元素:srem key element [element ...]
返回结果为成功删除元素个数
- (3)计算元素个数:scard key
scard的时间复杂度为O(1),它不会遍历集合所有元素,而是直 接用Redis内部的变量
- (4)判断元素是否在集合中: sismember key element
如果给定元素element在集合内返回1,反之返回0
- (5)随机从集合返回指定个数元素: srandmember key [count]
[count]是可选参数,如果不写默认为1,只返回不删除该元素
- (6)从集合随机弹出元素 spop key
spop操作可以从集合中随机弹出一个元素,随机抽出一个元素返回并删除该元素
需要注意的是Redis从3.2版本开始,spop也支持[count]参数;srandmember和spop都是随机从集合选出元素,两者不同的是spop 命令执行后,元素会从集合中删除,而srandmember不会;
- (7)获取所有元素:smembers key
代码获取集合key所有元素,并且返回结果是无序的
smembers和lrange、hgetall都属于比较重的命令,如果元素过多存 在阻塞Redis的可能性,这时候可以使用sscan来完成,有关sscan命令下面会介绍;
二、不同集合之间的操作
user:1:follow和user:2:follow:
127.0.0.1:6379> sadd user:1:follow it music his sports
(integer) 4
127.0.0.1:6379> sadd user:2:follow it news ent sports
(integer) 4
- (1)求多个集合的交集:sinter key [key ...]
是求user:1:follow和user:2:follow两个集合的交集,返回结果是sports、it;
- (2)求多个集合的并集:suinon key [key ...]
是求user:1:follow和user:2:follow两个集合的并 集,返回结果是sports、it、his、news、music、ent
- (3)求多个集合的差集 sdiff key [key ...]
求user:1:follow和user:2:follow两个集合的差 集,返回结果是music和his
- (4)将交集、并集、差集的结果保存:
sinterstore destination key [key ...]
suionstore destination key [key ...]
sdiffstore destination key [key ...]
原命令+store将集合间交集、并集、差集的结果保存 在destination key中
127.0.0.1:6379> sinterstore user:1_2:inter user:1:follow user:2:follow
(integer) 2
127.0.0.1:6379> type user:1_2:inter
set
127.0.0.1:6379> smembers user:1_2:inter
1) "it"
2) "sports"
各命令的时间复杂度:
2.内部编码
集合类型的内部编码有两种:
- ·intset(整数集合):当集合中的元素都是整数且元素个数小于set-max-intset-entries配置(默认512个)时,Redis会选用intset来作为集合的 内部实现,从而减少内存的使用。
- ·hashtable(哈希表):当集合类型无法满足intset的条件时,Redis 会使用hashtable作为集合的内部实现。
3.常见使用场景
集合类型比较典型的使用场景是标签(tag)
例如一个用户可能 对娱乐、体育比较感兴趣,另一个用户可能对历史、新闻比较感兴趣, 这些兴趣点就是标签。有了这些数据就可以得到喜欢同一个标签的人, 以及用户的共同喜好的标签,这些数据对于用户体验以及增强用户黏度 比较重要。
(1)给用户添加标签
sadd user:1:tags tag1 tag2 tag5
sadd user:2:tags tag2 tag3 tag5
...
sadd user:k:tags tag1 tag2 tag4
(2)给标签添加用户
sadd tag1:users user:1 user:3
sadd tag2:users user:1 user:2 user:3
...
sadd tagk:users user:1 user:2
注意:用户和标签的关系维护应该在一个事务内执行,防止部分命令失败 造成的数据不一致,(3)和(4)也是尽量放在一个事务执行;
(3)删除用户下的标签
srem user:1:tags tag1 tag5
(4)删除标签下的用户
srem tag1:users user:1
srem tag5:users user:1
(5)计算用户共同感兴趣的标签 (交集)
sinter user:1:tags user:2:tags
·sadd=Tagging(标签)
·spop/srandmember=Random item(生成随机数,比如抽奖)
·sadd+sinter=Social Graph(社交需求)
八、zset(有序集合)类型
zset保留了集合不能有重复成员的特性,但不同的是,有序集合中的元素可以排序;是它和列表使用 索引下标作为排序依据不同的是,它给每个元素设置一个分数(score) 作为排序的依据。有序集合中的元素不能重复,但是score可以重复,就和一个班里的 同学学号不能重复,但是考试成绩可以相同。
列表、集合和有序集合三者的异同点:
1.常用命令
一、集合内:
- (1)添加成员: zadd key score member [score member ...]
返回结果代表成功添加成员的个数
·Redis3.2为zadd命令添加了nx、xx、ch、incr四个选项:
·nx:member必须不存在,才可以设置成功,用于添加。
·xx:member必须存在,才可以设置成功,用于更新。
·ch:返回此次操作后,有序集合元素和分数发生变化的个数
·incr:对score做增加,相当于后面介绍的zincrby。
·有序集合相比集合提供了排序字段,但是也产生了代价,zadd的 时间复杂度为O(log(n)),sadd的时间复杂度为O(1)。
- (2)计算成员个数: zcard key
zcard的时间复杂度为O(1)
- (3)计算某个成员的分数: zscore key member
如果成员不存在则返回nil
- (4)计算成员的排名: zrank key member(正序排名) zrevrank key member(反序排名)
例如下面操作 中,tom在zrank和zrevrank分别排名第5和第0(排名从0开始计算)
127.0.0.1:6379> zrank user:ranking tom
(integer) 5
127.0.0.1:6379> zrevrank user:ranking tom
(integer) 0
- (5)删除成员 zrem key member [member ...]
返回结果为成功删除的个数
- (6)增加成员的分数 zincrby key increment member
- (7)返回指定排名范围的成员
zrange key start end [withscores]
zrevrange key start end [withscores]
有序集合是按照分值排名的,zrange是从低到高返回,zrevrange反 之。下面代码返回排名最低的是三个成员,如果加上withscores选项, 同时会返回成员的分数:
127.0.0.1:6379> zrange user:ranking 0 2 withscores
1) "kris"
2) "1"
3) "frank"
4) "200"
5) "tim"
6) "220"
127.0.0.1:6379> zrevrange user:ranking 0 2 withscores
1) "tom"
2) "260"
3) "martin"
4) "250"
5) "tim"
6) "220"
- (8)返回指定分数范围的成员
zrangebyscore key min max [withscores] [limit offset count]
zrevrangebyscore key max min [withscores] [limit offset count]
其中zrangebyscore按照分数从低到高返回,zrevrangebyscore反之;withscores选项会同时 返回每个成员的分数;[limit offset count]选项可以限制输出的起始位置和个数;同时min和max还支持开区间(小括号)和闭区间(中括号),-inf 和+inf分别代表无限小和无限大;
127.0.0.1:6379> zrangebyscore user:ranking (200 +inf withscores
1) "tim"
2) "220"
3) "martin"
4) "250"
5) "tom"
6) "260"
- (9)返回指定分数范围成员个数 zcount key min max
- (10)删除指定排名内的升序元素 zremrangebyrank key start end
- (11)删除指定分数范围的成员 zremrangebyscore key min max
二、集合间的操作
- (1)交集
zinterstore destination numkeys key [key ...] [weights weight [weight ...]]
[aggregate sum|min|max]
命令参数:
·destination:交集计算结果保存到这个键。
·numkeys:需要做交集计算键的个数。
·key[key...]:需要做交集计算的键。
·weights weight[weight...]:每个键的权重,在做交集计算时,每个 键中的每个member会将自己分数乘以这个权重,每个键的权重默认是 1。
·aggregate sum|min|max:计算成员交集后,分值可以按照 sum(和)、min(最小值)、max(最大值)做汇总,默认值是sum。
- (2)并集、
zunionstore destination numkeys key [key ...] [weights weight [weight ...]]
[aggregate sum|min|max]
该命令的所有参数和zinterstore是一致的,只不过是做并集计算;
各命令时间复杂度:
2.内部编码
有序集合类型的内部编码有两种:
- ·ziplist(压缩列表):当有序集合的元素个数小于zset-max-ziplist-entries配置(默认128个),同时每个元素的值都小于zset-max-ziplist-value配置(默认64字节)时,Redis会用ziplist来作为有序集合的内部实 现,ziplist可以有效减少内存的使用。
- ·skiplist(跳跃表):当ziplist条件不满足时,有序集合会使用 skiplist作为内部实现,因为此时ziplist的读写效率会下降。
3.常见使用场景
有序集合比较典型的使用场景就是排行榜系统。例如视频网站需要 对用户上传的视频做排行榜,榜单的维度可能是多个方面的:按照时 间、按照播放数量、按照获得的赞数。本节使用赞数这个维度,记录每 天用户上传视频的排行榜。
(1)添加用户赞数
例如用户mike上传了一个视频,并获得了3个赞,可以使用有序集 合的zadd和zincrby功能:
zadd user:ranking:2016_03_15 mike 3
如果之后再获得一个赞,可以使用zincrby:
zincrby user:ranking:2016_03_15 mike 1
(2)取消用户赞数
由于各种原因(例如用户注销、用户作弊)需要将用户删除,此时 需要将用户从榜单中删除掉,可以使用zrem。例如删除成员tom
zrem user:ranking:2016_03_15 mike
(3)展示获取赞数最多的十个用户
此功能使用zrevrange命令实现:
zrevrangebyrank user:ranking:2016_03_15 0 9
(4)展示用户信息以及用户分数
此功能将用户名作为键后缀,将用户信息保存在哈希类型中,至于 用户的分数和排名可以使用zscore和zrank两个功能:
hgetall user:info:tom
zscore user:ranking:2016_03_15 mike
zrank user:ranking:2016_03_15 mike
总结回顾:
- 1)Redis提供5种数据结构,每种数据结构都有多种内部编码实 现。
- 2)纯内存存储、IO多路复用技术、单线程架构是造就Redis高性能 的三个因素。
- 3)由于Redis的单线程架构,所以需要每个命令能被快速执行完, 否则会存在阻塞Redis的可能,理解Redis单线程命令处理机制是开发和 运维Redis的核心之一。
- 4)批量操作(例如mget、mset、hmset等)能够有效提高命令执行 的效率,但要注意每次批量操作的个数和字节数。
- 5)了解每个命令的时间复杂度在开发中至关重要,例如在使用 keys、hgetall、smembers、zrange等时间复杂度较高的命令时,需要考 虑数据规模对于Redis的影响。