目录
 ;
基础数据类型及其使用场景
redis是键值数据库,存储的数据都是键值对的形式,key只能是string类型,value可以为不同的类型,redis的5种基础数据类型如下
1、string 字符串
- 最简单的数据类型,用于简单的键值存储,常用于实现分布式锁、分布式session、缓存、限流、计数器等。
- 对象都可以转换为json字符串进行存储,理论上string可以存储所有类型的对象,但不推荐这样做,尽量选择合适的类型
2、hash 哈希
- 存储对象,字段名不能重复,可以直接操作hash中的键值对,常用于存储需要直接操作字段的对象(这个对象没有嵌套其它对象)
3、list 列表
- 元素有序、可重复,常用于实现消息队列、有序列表、存储有序的事件
4、set 集合
- 元素无序、不可重复,常用于社交领域的去重、求交集
- eg. 存储好友列表、存储点赞了这条朋友圈的好友,列出共同好友
5、sorted set 有序集合,也叫做zset
- 有序、元素不能重复、分数可以重复,常用于实现排行榜、带权重的无序队列
- eg. 微博热度排行、热销商品、歌曲排行榜、阅读排行榜、评论排行榜,带优先级的抢票
说明
- string是基础数据类型,其它4种都是数据结构,根据类型特点来使用
- hash可存储 2^32 - 1 (42亿+)个字段,list、set、zset均可存储 2^32 - 1 (42亿+)个元素
- hash的字段值,list、set、zset的元素都只能是string,redis中只能使用这些数据类型。string虽然只能存储字符串,但二进制数据可以序列化为字符串,也就说string其实可以存储所有类型。使用hash存储对象时需要先序列化对象,从redis获取对象后需要反序列化得到对象。
- set、zset,集合中的元素都是不可重复的
常用命令
string
set name "chy" #单双引均可
set age 20 #redis没有数值类型,值默认是字符串类型,引不引都行。习惯上,数值型不引,其它字符串引一下
strlen name #返回字符串(值)长度
append name "01" #在值的末尾添加字符串,返回操作后字符串(值)的长度。若key不存在,会先创建初始化为空串
incr age #值自增+1,返回操作后的值。如果key不存在,会先新建初始化为0。常用于主键自增,比如订单编号。
incrby age 5 #指定增量,+5,增量只能是整数
incrbyfloat age 5.0 #增量可以是浮点数,
decr age #值自减-1
decrby age 5 #指定减量,即-5
getrange name 0 2 #返回值在[0,2]上的字符串。redis的区间都是闭区间,支持负数索引
incr、decr常用于自增、自减的统计,比如访问量,点赞、收藏数等
hash
hset user name "chy" #若user不存在,会自动创建。若name字段不存在,添加name字段并返回1;若name已存在,则更新其值,返回0
hsetnx user name "chy" #若name字段已存在,则不进行任何操作
hmset user name "chy" age 20 #同时添加|更新多个字段
hget user name #获取字段的值
hmget user name age #获取多个字段的值
hkeys user #获取所有的字段名
hvals user #获取所有的字段值
hdel user name #删除指定字段,返回实际删除的字段个数
hdel user name age #删除多个字段
hincrby user age 5 #值自增,指定增量,+5
hincrby user age -2 #负值表示值自减,-2
hincrbyfloat user age 5.0 #增量是浮点数
hincrbyfloat user age -2.0
hexists user name #判断指定字段是否存在,存在返回1,不存在返回0
hlen user #获取字段数
list
lpush users "zhangsan" "lisi" #在list的头部插入一个或多个元素,l即left。若users不存在,会先自动创建。先插入"zhangsan",再插入"lisi",每次都是在最前面插入,list第一个元素是lisi
lpushx users "zhangsan" "lisi" #如果users不存在,不作任何操作,不会自动创建
rpush users "wangwu" "zhaoliu" #在list的尾部插入一个或多个元素,r即right,每次都是在最后面插入,list的最后一个元素是zhaoliu
rpushx users "wangwu" "zhaoliu" #如果users不存在,不作任何操作,不会自动创建
lpop users #弹出list的第一个元素,弹出是指返回并删除
rpop users #弹出list的最后一个元素
blpop users 60 #弹出第一个元素,若list是空的,会阻塞等待60s,这60s内list内有了元素就弹出。60是超时时间。b即block,阻塞
brpop users 60 #最后一个元素
rpoplpush list1 list2 #弹出list1的最后一个元素,并在list2的头部插入该元素。即把列表的最后一个元素剪切到另一个列表的开头。
brpoplpush list1 list2 60 #如果list1是空的,阻塞等待60s
llen users #返回list的长度(元素个数),第一个l即list
lrange users 0 -1 #返回[0,-1]上的所有元素
lindex users 1 #返回指定位置上元素
lset users 1 "zhangwei" #修改指定位置上元素,该位置要已存在元素,才能修改,否则报错
lrem users 0 "zhangsan" #移除list中所有值为zhangsan的元素。0表示删除匹配到的所有元素
lrem users 2 "zhangsan" #从前往后搜索,删除匹配到的前2个元素
lrem users -2 "zhangsan" #从后往前搜索,删除匹配到的前2个元素。正负表示搜索方向,数值表示删除个数。
ltrim users 1 3 #修剪list,只保留[1,3]上的元素,删除其它元素
linsert users before "zhangsan" "lisi" #从前往后搜索"zhangsan",在匹配的第一个"zhangsan"前面插入元素lisi"。若"zhangsan"不存在,不做任何操作,返回-1;若users不存在,不做任何操作,返回0
lisert users after "zhangsan" "lisi" #后面插入
set
sadd users "zhangsan" "lisi" #向集合中添加一个或多个元素,若users不存在,会自动创建。若set中已有该元素,则不添加该元素,返回本次操作添加的元素个数
srem users "zhangsan" "lisi" #删除set中的一个或多个元素,若set中没有该元素,自动跳过
smembers users #返会set中的所有元素
sismember users "zhangsan" #检测指定元素是否是users的成员,是返回1,不是返回0
scard users #返回set中的元素个数
spop users #随机弹出一个元素。set是无序的,只能随机弹,不能指定弹出哪个元素
srandmember users #随机返回一个元素。是返回,不是弹出,不会删除该元素
srandmember users 3 #随机返回3个元素。随机返回一个元素,再从剩下的元素中随机返回一个,再从剩下的元素中随机返回一个,以此类推。就是说返回的元素各不相同。若数值大于集合的元素总数,则返回集合中所有的元素
srandmember users -3 #负数表示每次都从整个集合中选,不是从剩下的里面选,即返回的元素可能有相同的
smove set1 set2 "zhangsan" #把set1中的元素"zhangsan"剪切到set2中
sinter set1 set2 set3 #返回这些集合的交集(共同元素)
sinterstore destset set1 set2 set3 #求交集,并把交集中的元素存储到集合destset中。返回的是交集中的元素个数
sunion set1 set2 set3 #并集(合并到一起)
sunionstore destset set1 set2 set3
sdiff set1 set2 #差集(set1中有、set2中没有的元素)
sdiffstore destset set1 set2
zset
zset中的每个元素都会关联一个分数,分数可用于实现榜单等排序相关功能,比如头条热度、歌曲热榜。
zset底层使用压缩表+跳表实现,所谓表指的是双向链表,按分数升序排列,插入元素时根据分数确定在链表中的存储位置。压缩表包含了全部元素,在压缩表的基础同时维护了多个跳表,需要在指定分数区间上操作时,通过跳表可快速转到指定区间上。
zadd grade 90 "zhangsan" 98.5 "lisi" #添加一个或多个元素。分数在前,元素在后。分数支持浮点型、负数。如果元素已存在,则更新对应的分数。返回此次操作添加的元素个数
zrangebyscore grade 90 100 #返回分数在[90,100]上的所有元素,返回默认不带分数
zrangebyscore grade 90 100 withscores #返回时带上对应的分数
zscore grade "zhangsan" #返回关联的分数
zincrby grade 5 "zhangsan" #将指定元素关联的分数+5
zincrby grade -5 "zhangsan" #负数即减,-5
zrange grade 0 9 #返回[0,9]上的所有元素(取前十名),默认按分数升序排列
zrange grade 0 9 withscores #返回时带上对应的分数
#zrevrange用法同上,只不过是按分数降序排列
zrank grade "zhangsan" #返回该元素所在位置的下标(查询排名),默认按分数升序排列。
#zrevrank用法同上,只不过按分数降序排列
zcount grade 90 100 #返回分数在[90,100]上的元素个数
zcard grade #返回元素总数
zrem grade "zhangsan" "lisi" #删除一个或多个元素
zremrangebyrank grade 0 2 #删除[0,2]上的所有元素,默认按分数升序排雷
zremrangebyscore grade 90 100 #删除分数在[90,100]上的所有元素
不带rev的是按分数升序排列,带rev的表示反序,按分数降序排列。
数据类型选择的建议
理论上所有的值都可以序列化为string进行存储,但不要全都存储为string类型,尽量结合类型特点、业务场景选择合适的数据类型。
尽量不要设置大量的key,大量的key不好管理、维护,且容易出现key命名冲突,比如尽量不要一个user对象对应一个key。
底层数据结构 | 基础数据类型的实现原理
常见数据类型的底层结构
数据类型 | 限制 | 底层数据结构 |
---|---|---|
string | 大小上限 512 MB | SDS |
hash | 键值对最大个数2^32-1 | dict、ziplist(小集合) |
list | 元素最大个数2^32-1 | quicklist |
set | 元素最大个数2^32-1 | dict、intset(小整数集) |
zset | 元素最大个数2^32-1 | dict+skiplist、ziplist(小集合) |
bitmap | 大小上限 512 MB | SDS |
SDS
redis把c语言传统的字符串只用于字符串字面量,用在一些无须修改字符串本身的地方, 比如打印日志。redis构建了一种简单动态字符串(simple dynamic string,SDS),作为redis的字符串类型。
C字符串 | SDS |
---|---|
获取字符串长度的复杂度为 O(n) | 获取字符串长度的复杂度为 O(1) |
API 是不安全的,可能会造成缓冲区溢出 | API 是安全的,不会造成缓冲区溢出 |
修改字符串长度 n 次必然要执行 n 次内存重分配 | 修改字符串长度 n 次最多需要执行 n 次内存重分配 |
只能保存文本数据 | 可以保存文本或二进制数据 |
SDS进行修改操作时,会先检查SDS的空间是否满足修改后所需的空间,空间不足时会自动扩容, 然后才执行修改操作,不存在应用操作时SDS所需空间不足的情况,即不会出现SDS缓冲区溢出。
SDS采用空间预分配、惰性空间释放,减少了修改字符串带来的内存重分配次数。
dict
dict是基于哈希表算法实现的一个数据结构,和传统哈希算法类似,dict使用哈希函数对key进行计算,得到在哈希表中的存储位置,采用拉链法解决hash冲突,在负载达到阈值(容量*负载因子)时进行重哈希扩容。
dict最显著的特点是重哈希采用增量式重哈希(incremental rehashing),在扩容时避免一次性对所有的 key 进行重哈希,而是将重哈希操作分散到对dict 的各个增删改查操作中去,每次只对一小部分 key 进行重哈希,而每次重哈希之间不影响 dict 的操作,避免了重哈希期间处理单个请求耗时长的问题。
为了实现增量式重哈希,dict中包含两个哈希表,在重哈希期间,数据从一个哈希表迁移到另一个哈希表。
可以把dict看成redis中的哈希表实现,哈希表的一大特点是尽量不存储重复元素,以减少hash冲突、提高性能,hash键不重复,set、zset元素不重复,这3种数据类型的实现都用到了dict。
ziplist
ziplist是一个经过特殊设计的双向链表,设计目标是为了节省存储占用的内存空间。
普通的双向链表,链表中每个节点都占用单独的一块内存,节点之间用指针(或引用)连接,这种方式会产生大量的内存碎片,且存储地址指针也会占用额外的内存空间。
ziplist 是list,但其实并不是linked list(链表),ziplist
- 使用连续的地址空间来存储节点,本身能维护节点插入顺序,每个节点无需存储前后节点的指针,减少了内存占用;
- 一个ziplist占用一大块内存,减少了内存碎片的产生;
- 但增删节点需要移动很多节点,尤其在节点数量很多的情况下,增删节点效率低下,不适合频繁增删节点的情景。
ziplist可用于存储字符串或整数,其中整数是按二进制形式进行存储,并非存储为字符串。ziplist采用变长编码的方式储存值,存储节点值占用的空间大小不相同,大的整数会多用一些字节来存储,小的整数会少用一些字节来存储。
quicklist
quicklist 是由 ziplist 为节点组成的双向链表,这样设计是空间、时间(性能)上的折中,ziplist减少内存占用,双向链表可快速进行元素的增删操作。
skiplist 跳跃表
在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的。