redis的数据结构与类型
数据结构
RedisObject结构
type
- 0-字符串
- 1-列表
- 2-集合
- 3-有序集
- 4-哈希表
encoding(编码类型)
可以通过Object encoding命令查看对象编码
- OBJ_ENCODING_RAW 0
- OBJ_ENCODING_INT 1
- OBJ_ENCODING_HT 2
- OBJ_ENCODING_ZIPMAP 3
- OBJ_ENCODING_LINKEDLIST 4
- OBJ_ENCODING_ZIPLIST 5
- OBJ_ENCODING_INTSET 6
- OBJ_ENCODING_SKIPLIST 7
- OBJ_ENCODING_EMBSTR 8
- OBJ_ENCODING_QUICKLIST 9
- OBJ_ENCODING_STREAM 10
lru
- 记录了对象最后一次被命令程序访问的时间
- 可以通过Object IdleTime命令查看键的空闲时间
refcount
引用计数
可以通过object refcount命令查看对象引用数验证是否启用整数对象池技术
ptr
指针,指向实际保存值的数据结构
这个数据结构由type和encoding属性决定
数据分类
SDS(动态字符串)
- 常数复杂度获取字符串长度
- 杜绝缓冲区溢出
- 减少修改字符串的内存重新分配次数
- 二进制安全
- 兼容部分 C 字符串函数
ZipList(压缩列表)
- ZipList是为了提高存储效率而设计的一种特殊编码的双向链表。它能在O(1)的时间复杂度下完成list两端的push和pop操作
- 可以存储字符串或者整数,存储整数时是采用整数的二进制而不是字符串形式存储
- 每次操作都需要重新分配ziplist的内存,所以实际复杂度和ziplist的内存使用量相关
QuickList(快速表)
- 是以ziplist为结点的双端链表结构
- 宏观上, quicklist是一个链表
- 微观上, 链表中的每个结点都是一个ziplist
Dict(字典/哈希表)
- 解决哈希冲突
- 扩容和收缩
- 当哈希表保存的键值对太多或者太少时,就要通过 rerehash(重新散列)来对哈希表进行相应的扩展或者收缩
- 扩容方式有:触发扩容的条件、渐进式rehash
IntSet(整数集)
- 整数集合(intset)是集合类型的底层实现之一,当一个集合只包含整数值元素,并且这个集合的元素数量不多时,Redis 就会使用整数集合作为集合键的底层实现
ZSkipList(跳表)
- 跳跃表结构在 Redis 中的运用场景只有一个,那就是作为有序列表 (Zset) 的使用。跳跃表的性能可以保证在查找,删除,添加等操作的时候在对数期望时间内完成,这个性能是可以和平衡树来相比较的,而且在实现方面比平衡树要优雅,这就是跳跃表的长处。跳跃表的缺点就是需要的存储空间比较大,属于利用空间来换取时间的数据结构
对象管理
对象共享
简述
- Redis会在初始化服务器时,会创建0-9999字符串数值对象,即共享对象。当新建的对象值在等于共享对象中的任意一个时,不需要创建新的对象,而是将RedisObject中的ptr指针指向共享对象
- 在字符串键值中可以使用到共享对象,以及使用LinkedList、HashTable、ZSet编码的RedisObject对象也可以使用共享对象。使用ZipList编码的RedisObject不能使用共享对象
- 当设置maxmemory并启用LRU相关淘汰策略如:volatile-lru,allkeys-lru时,Redis禁止使用共享对象池
不共享包含字符串的对象
- 如果共享对象是保存整数值的字符串对象,那么验证操作的复杂度为O(1)
- 如果共享对象是保存字符串值的字符串对象,那么验证操作的复杂度为O(N)
- 如果共享对象是包含了多个值(或者对象的)对象,比如列表对象或者哈希对象,那么验证操作的复杂度将会是O(N 2)
对象销毁机制
采用的是引用计数法
redisObject中有refcount属性,是对象的引用计数,显然计数0那么就是可以回收
数据类型
String(字符串)
简述
- 字符串是Redis最基本的数据类型,可以是字符串、整数或浮点数。字符串的长度不能超过512M
- 所有key都是字符串类型
- 其它的数据类型构成的元素也是字符串
- 支持对整个字符串或字符串的一部分进行操作;对整数或浮点数进行自增或自减操作
编码方式
int(用于保存整数值)
- 保存的是可以用 long 类型表示的整数值
embstr(用于保存短字符串)
- 保存长度小于44字节的字符串(redis3.2版本之前是39字节,之后是44字节)
- 只分配一次内存空间(因此redisObject和sds是连续的),对象的与数据是连续存储的
- embstr的数据为只读,如果值进行修改,则会重新分配内存。当数据超过44字节之后,会升级成raw(不可逆)
raw(用于保存长字符串)
- 保存长度大于44字节的字符串(redis3.2版本之前是39字节,之后是44字节)
- 分配两次内存空间(分别为redisObject和sds分配空间)
- 只进行修改,不需要重新分配内存
操作
基本命令
#设置键值对
set key value
#获取key对应的value
get key
#先get再set,返回旧值,如果没有旧值返回nil
getset key value
#向指定的key的value后追加字符串
append key value
#删除key
del key
#获取key对应值的字符串长度
strlen key
数值处理命令
#值自增
incr key
#值自减
decr key
#将键存储的值加上整数
incrby key number
#将键存储的值减去整数
decrby key number
范围处理命令
#获取指定下标范围内的值,如果是(0),(1)就是获取所有
getrange key begin end
#从begin下标开始设置value值,将原有的替换掉
setrange key begin value
批量处理命令
#批量设置键值
met (key1) (value1) (key2) (value2)
#批量获取键值
mget (key1) (key2)
#当所有key都成功设置,返回1。如果有一个key设置失败,所有的key设置都会失败,返回0。原子操作
msetnx(key1) (value1) (key2) (value2)
拓展命令选项
格式:set key value [ex seconds] [px minlliseconds] [nx|xx]
- ex
- 为键设置秒级过期时间
- 等同于setex命令。如:setex key seconds value
- px
- 为键设置毫秒级过期时间
- 等同于psetex命令。如:psetex key millisecond value
- nx
- 键必须不存在,才能设置成功,用于新增
- 等同于setnx命令。如:setnx key value
- xx
- 键必须存在,才能设置成功,用于更新
List(列表)
数据结构为两端链表,链表节点类型为字符串,按照插入顺序排列节点,支持链表的相关操作。例如:push、pop等
编码方式
- QuickList
操作
#将给定值推入到列表左端
lpush key value
#将给定值推入到列表左端
rpush key value
#从列表的左端弹出一个值,并返回被弹出的值
lpop key
#从列表的右端弹出一个值,并返回被弹出的值
rpop key
#获取列表在给定范围上的所有值
lrange key begin end
#通过索引获取列表中的元素。你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推
lindex key index
使用场景
- lpush+lpop=Stack(栈)
- lpush+rpop=Queue(队列)
- lpush+ltrim=Capped Collection(有限集合)
- lpush+brpop=Message Queue(消息队列)
Set(集合)
简述
- 数据结构为集合,集合中的节点类型为字符串,是一个无序的集合,且元素不允许重复
- 支持集合的增删改查等常见命令,并提供交集、并集、差集、是否存在、统计总数量等命令
- Redis中的集合是通过哈希表实现的,在增删改查的时间复杂度为O(1)
编码方式
- IntSet
- 集合对象中所有元素都是整数
- 集合对象所有元素数量不超过512
- HashTable
- 满足intset条件时,使用intset,否则使用另一种
操作
#向集合添加一个或多个成员
sadd key value
#获取集合的成员数
scard key
#返回集合中的所有成员
smember key
#判断item是否在集合中
sismember key item
Hash(散列)
简述
- 数据类型为散列表,散列表中的节点类型是字符串键值对(Key,Value),是一个无序散列表
- 支持散列表的增删改查等常见命令
编码方式
- ZipList
- 列表保存元素个数小于512个
- 每个元素长度小于64字节
- HashTable
- 满足ziplist的条件时,使用ziplist,否则使用hashtable
操作
#在note集合里面添加键值
hset (note) (key) (value)
#在note集合里面查询指定键的值
hget (note) (key)
#获取note集合中的所有键值
hgetall (note)
#删除note集合中的指定键的键值信息
hdel (note) (key)
ZSet(有序集合)
概念
- 数据结构为集合,元素类型为键值对(Key,Value),是一个有序的集合,通过Score(分数)来进行排序
- 支持集合的增伤改查等常见命令,并提共区间查询、汇总统计、排序、比较等命令
编码方式
- ZipList
- 保存的元素数量小于128
- 保存的所有元素长度都小于64字节
- SkipList与Dict
- 满足ZipList条件是,使用ZipList,否则使用另一种
操作
-------------基本命令-------------
#将一个带有给定分值的成员添加到有序集合里面
zadd key score value
#移除有序集中的一个或多个成员
zrem
-------------排序命令-------------
#返回有序集中指定成员的排名。其中有序集成员按分数值递增(从小到大)顺序排列
zrank key begin end
#返回有序集中成员的排名。其中有序集成员按分数值递减(从大到小)排序
zrevrank key begin end
-------------其它命令-------------
#获取指定范围内的元素
zrange key begin end
#返回有序集合中指定分数区间的成员列表。有序集成员按分数值递增(从小到大)
zrangebyscore key begin end
#命令用于计算集合中元素的数量
zcard key
#查看分数
zrank key value
#查看集合元素的值与分数
zrank key begin end withscores
HyperLogLogs
简述
- HyperLogLog 是用来做基数统计的算法,是一种概率性的统计算法,统计数值有一定的误差比例
- HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定 的、并且是很小的,每个对象最大占用空间为 12KB
- 提供了三个操作:PFADD、PFCOUNT、PFMERGE,分别用于添加、计数与合并
补充说明
- 什么是基数
- 比如数据集 {1, 3, 5, 7, 5, 7, 8}, 那么这个数据集的基数集为 {1, 3, 5 ,7, 8}, 基数(不重复元素)为5。 基数估计就是在误差可接受的范围内,快速计算基数
使用场景
- 这个结构可以非常省内存的去统计各种计数,比如注册 IP 数、每日访问 IP 数、页面实时UV、在线用户数,共同好友数等
操作
#添加指定元素到 HyperLogLog 中,O(1) 的复杂度添加一个元素
PFADD key element [element ...]
#返回给定 HyperLogLog 的基数估算值,针对一个 key 是 O(1) 常量时间,如果是 N 个 key 就是 O(N)
PFCOUNT key [key ...]
#将多个 HyperLogLog 合并为一个 HyperLogLog,合并 N 个 HyperLogLog 对象,时间复杂度是 O(N),通常会有更高的时间消耗
PFMERGE destkey sourcekey [sourcekey ...]
Bitmap(位存储)
简述
- Bitmap 即位图数据结构,都是操作二进制位来进行记录,只有0 和 1 两个状态
- 支持SETBIT、GETBIT、BITCOUNT、BITOP等命令用于处理二进制位数组
操作
#设置二进制位的值。设置bit值时,其中offset的值不能为负数
SETBIT KEY OFFSET VALUE
#获取二进制位的值
GETBIT KEY OFFSET
#统计被设置的二进制位数量
BITCOUNT KEY START END
#对一个或多个保存二进制位的字符串 key 进行位元操作
#操作符包括AND、OR、NOT、XOR
BITOP AND KEY1 KEY2
使用场景
- 统计用户信息,活跃,不活跃! 登录,未登录! 打卡,不打卡! 两个状态的,都可以使用 Bitmaps
geospatial(地理位置)
简述
- 主要用于存储地理位置信息,并对存储的信息进行操作
- 提供添加、获取地理位置坐标,以及提供计算位置空间距离、统计范围内的位置集合等
- 底层使用的是ZSet实现的,因此也支持ZSet的相关命令
操作
-------------基本命令-------------
#用于存储指定的地理空间位置
#可以将一个或多个经度(longitude)、纬度(latitude)、位置名称(member)添加到指定的 key 中
GEOADD key longitude latitude membe
#从给定的 key 里返回所有指定名称(member)的位置(经度和纬度),不存在的返回 nil
GEOPOS key member [member ...]
-------------统计命令-------------
#返回两个给定位置之间的距离
GEODIST key member1 member2 [m|km|ft|mi]
#以给定的经纬度为中心, 返回键包含的位置元素当中, 与中心的距离不超过给定最大距离的所有位置元素
GEORADIUS key longitude latitude radius
#以给定的member坐标为中心, 返回键包含的位置元素当中, 与中心的距离不超过给定最大距离的所有位置元素
GEORADIUSBYMEMBER key member radius
-------------其它命令-------------
#用于获取一个或多个位置元素的 geohash 值
GEOHASH key member [member ...]
Stream
概念
- 是一个支持多channl(通道)的可持久化的消息队列模块
消息ID生成方式
- 时间戳+序号
- Stream类型数据都维护一个latest_generated_id属性,用于记录最后一个消息的ID
时间回拨消息编号的问题
- 若发现当前时间戳退后(小于latest_generated_id所记录的),则采用时间戳不变而序号递增的方案来作为新消息ID(这也是序号为什么使用int64的原因,保证有足够多的的序号),从而保证ID的单调递增性质
死信(坏消息)问题
- 通过delivery counter字段累加,当超过阈值之后,就认为是死信,可以通过xdel语法进行删除
消费者崩溃带来的会不会消息丢失问题
KEY键的管理
简述
- 每当我们设置一个键的过期时间时,Redis就会将该键带上过期时间存放到一个过期字典(expires)中
- 当我们查询一个键时,Redis便首先检查该键是否存在过期字典中,如果存在,那就获取其过期时间。然后将过期时间和当前系统时间进行比对,比系统时间大,那就没有过期;反之判定该键过期
常用命令
- TTL KEY
- 返回键 key 的剩余生存时间(单位:秒)
- -1:永久保存的数据,-2:已经过期的数据或被删除的数据或未被定义的数据
- PTTL KEY
- 返回键 key 的剩余生存时间(单位:毫秒)
- EXPIRE KEY TTL
- 设置KEY的生存时间为TTL秒
- PEXPIRE KEY TTL
- 设置KEY的生存时间为TTL毫秒
- EXPIREAT KEY timestamp
- 设置KEY的生存时间到指定的时间戳(秒)
- PEXPIREAT KEY timestamp
- 设置KEY的生存时间到指定的时间戳(毫秒)
- PERSIST KEY
- 将KEY的过期时间移除
- setex key seconds expire
- 设置键过期时间
- ttl key
- 查看key剩余存活时间