redis基础数据结构
- string(字符串)
- list(列表)
- set(无序集合)
- hash(哈希)
- zset(有序集合)
string
底层实现:类于Java的ArrayList,预分配冗余空间来减少扩容带来的频繁内存分配
小于1m时扩容为两倍,大于1m时扩容1m,最大长度为512m
常见使用场景:缓存用户信息,将用户信息序列化成json字符串,需要使用时再进行反序列化
list
类似于Java中的LinkedList,是链表而不是数组,插入和删除操作非常快,索引定位很慢
当列表弹出最后一个元素之后,数据结构自动被删除,内存被回收
组成:快速链表(quickList)
底层实现:快速链表
元素较少时用一块连续内存存储,也就是ziplist压缩列表,数据量大时会改为quicklist,
ziplist间维护双向指针连接
常见使用场景:做异步队列使用,将需要延后处理的任务结构体序列化成字符串塞进Redis列表,另一个线程从这个列表中轮询数据处理
hash(字典)
底层实现:类似hashmap,数组+链表,hash碰撞时用链表连接起来
与hashmap的区别:hashmap扩容resize时,需要一次性全部rehash,而Redis为了高性能,不能堵塞服务,采用了渐进式rehash策略
渐进式 rehash 会在 rehash 的同时,保留新旧两个 hash 结构,查询时会同时查询两个 hash 结构,然后在后续的定时任务中以及 hash 操作指令中,循序渐进地将旧 hash 的内容一点点迁移到新的 hash 结构中。当搬迁完成了,就会使用新的hash结构取而代之。
常见使用场景:也可以用来做用户信息存储,不同于string类型,不需要序列化对象,直接用键值对存储属性,缺点是存储消耗要高于字符串
set
底层结构:类似hashset,内部有一个特殊字典,字典中的value值都是null
使用场景:可以用来存储中奖用户ID,因为有去重功能,可以保证同一用户不会中奖两次
zset
底层结构:跳跃链表,zset的排序功能就是靠跳跃链表实现的
我们需要这个链表按照 score 值进行排序。这意味着当有新元素需要插入时,要定位到特定位置的插入点,这样才可以继续保证链表是有序的。通常我们会通过二分查找来找到插入点,但是二分查找的对象必须是数组,只有数组才可以支持快速位置定位,链表做不到,那该怎么办?
「跳跃列表」之所以「跳跃」,是因为内部的元素可能「身兼数职」,比如上图中间的这个元素,同时处于 L0、L1 和 L2 层,可以快速在不同层次之间进行「跳跃」。
定位插入点时,先在顶层进行定位,然后下潜到下一级定位,一直下潜到最底层找到合适的位置,将新元素插进去。
容器型数据结构的通用规则
list/set/hash/zset 这四种数据结构是容器型数据结构,它们共享下面两条通用规则:
- create if not exists
如果容器不存在,那就创建一个,再进行操作。比如 rpush 操作刚开始是没有列表的,Redis 就会自动创建一个,然后再 rpush 进去新元素。 - drop if no elements
如果容器里元素没有了,那么立即删除元素,释放内存。这意味着 lpop 操作到最后一个元素,列表就消失了。
过期时间
Redis 所有的数据结构都可以设置过期时间,时间到了,Redis 会自动删除相应的对象。需要注意的是过期是以对象为单位,比如一个 hash 结构的过期是整个 hash 对象的过期,而不是其中的某个子 key。
还有一个需要特别注意的地方是如果一个字符串已经设置了过期时间,然后你调用了 set 方法修改了它,它的过期时间会消失。