5种基础数据结构
  • Redis有5种基础数据结构,分别是:String(字符串),list(列表),hash(字典),set(集合),zset(有序集合),这五种是我们开发种经常用的到的,是Redis种最基础,最重要的部分。
string(字符串)
  • 字符串string是Redis最简单的一个数据结构,他的内部表示是一个字符数组,Redis所有的数据结构都以唯一的key字符串作为名称,然后通过这个唯一的key获取对应的value数据,不同类型数据结构差异其实就在value上而已。如下图对应String类型字符串示意图:
  • Redis的字符串底层实现其实是动态的字符串,是可以修改的字符串,内部结构实现类似java种的ArrayList,采用预分配的方式冗余了一部分空的内存空间来减少频繁的内存重新分配的环节,如下图,内部为当前字符串分配的实际空间Capacity,一般比我们字符串长度len更多,当字符串长度小于1M时候,扩容都是加倍现有空间,如果字符串长度超过1M,扩容的时候,只会扩容1M内存(生产上一个1M的字符也比较大了,不建议这么搞)。注意,字符串最大长度512M。
  • 如果这个string类型的数据value是一个整数,Redis还提供来一种自增的操作,他的自增范围是signed long的最大值最小值之间,超过这个范围,Redis会报错。
set age 30
incr age //自增
list(列表)
  • Redis的list相当于Java中的LinkedList,他是链表而不是数组。这个意味着list的插入和删除是很快的,事件复杂度是O(1),但是下标索引定位慢,事件复杂度O(n),Redis中实现是一个双向链表结构的保证顺序性,串起来可以同时支持前后的方向便利,当列表弹出最后一个元素,该数据结构被自动删除,内存回收。如下图
  • Redis的列表结构可以用来做简单的异步队列使用,将需要延后处理的任务结构化为一个字符串,放到Redis列表中,另一个线程在列表中逐个弹出。
右近左出:队列
  • 队列是先进先出的数据结构,用于消费队列和异步的逻辑处理,他会确保元素的访问顺序性。如下图
右边进右边出:栈
  • 栈是先进后出的数据结构,跟队列正好相反,我们用Redis的列表数据结构来做栈使用如下:
警惕慢操作
  • lindex相当于java链表的get方法,他需要遍历整个链表,性能O(n),随着参数index的增加而增加
  • ltrim 与字面上意思不一致,保留一段期间内的值,删掉区间以外的,可以通过这个来获取一个定长列表,index可以是负数,-1是倒数第一个,-2 倒数第二个
ltrim key start_index end_index // 删除start_index到 end_index以外的所有元素
内部存储:快速列表
  • Redis的底层存储的不是一个简单的LinkedList,而是一个快速列表的一个结构
  • 首先元素较少的时候,会使用一块连续的内存存储,这个结构叫做ziplist,压缩列表。他所有的元素彼此紧挨在一起存储,分配的是一块连续的内存(可以看作一个数组)
  • 当数据量比较多的时候才会改成quicklist。因为普通列表需要的附加指针占用空间太大比如我们list中都是int类型的数据,那么每一个节点还需要维护两个指针prev,next,指针所占用的内存比实际数据还要多
  • 所以Redis将链表和ziplist结合使用,组成quicklist,也就是将多个ziplist用双向指针串起来,就可以节省很多指针空间。如下图。
hash(字典)
  • Redis的字典相当于Java中的HashMap,,他是无顺序的字典结构,内存存储多个key,value,
  • 实现结构与Java中HashMap也类似,都是数组+链表的而为结构,如下图,第一纬度的hash结构数组会发生重合的情况,也就是两个key的hash值一样,就会使用链表的形式将元素串起来。
  • 不同地方Redis的字典值只能是字符串,另外他们的rehash的方式也不一样,因为Java的HashMap字典很大时候,rehash的耗时很多,而且一次性全部完成,Redis为追求高性能,不能堵塞服务,所以采用渐进式rehash策略
  • 渐进式rehash会在rehash的同时,保留新旧两个hash结构,查询时候同时查询两个字典结构,后续Redis会在定时任务以及hash操作指令中,逐渐的将旧的hash内容一点点迁移到新的hash结构,完成后才用新的。(空间换时间)
  • 当hash结构最后一个元素被移除,该数据结构会自动删掉,内存自动回收。
  • 同字符串一样,hash结构也有单个key的计数功能,他对于指令是hincrby,和incr使用方法基本一致
127.0.0.1:6379> hset user age 29
(integer) 1
127.0.0.1:6379> hset user height 171
(integer) 1
127.0.0.1:6379> hincrby user height
(error) ERR wrong number of arguments for 'hincrby' command
127.0.0.1:6379> hincrby user height 1
(integer) 172
127.0.0.1:6379> hget user height
"172"
set(集合)
  • Redis的集合相当于java中的HashSet,内部的键值对是无序的,唯一的。他的内部实现相当于一个特殊的字典,字典中的所有value都是一个NULL而已,同样当集合最后一个元素被移除也会被删除,自动回收内存。
  • set结构可以用来存储中奖用户id,因为有去重的功能,保证唯一性。
zset(有序列表)
  • zset是Redis提供的最有特色的一个数据结构,类似java的sortedSet和HashMap的结合体,应为他有两者特点,首先他是一个set,所以保证内部每个value的唯一性,另外他给每个value的排序赋予一个权重score,代表这个value的排序权重,他内部实现是一个叫”跳跃表“的数据结构。
跳跃列表
  • zset的内部排序功能是通过跳跃表数据结构来实现的,他的结构非常特殊,因为zset既要支持随机的插入和删除,所以他不能用数组,数组需要遍历
  • Zset要支持按照score排序,这意味着有新元素插入,需要定位特定的位置插入点,这样才可以保证有序性,通常我们用二分查找(平均时间复杂度最小),但是二分查找对象都是数组,链表无法做到这样就两个条件矛盾来用如下的结构来解决这个问题:变异形态的链表
  • 比如我们平时公司的结构中,研发人员平等,都有一个组长,各个组长平等,组长上有leader,各个leader之间平等,leader上没有总监,依次向上到最后的CEO,跳跃表就类似这样的一个层级结构
  • 最下面的一层是所有元素都串起来,然后每隔几个元素挑选出一个代表,再将这几个代表用另外一个指针串联。接着在这些代表中继续挑选出二级代表,形成新的金字塔结构。如下:
  • redis中怎么把key分组 redis组成_redis

  • 解释:
  • 类比二分法,当我们用二分发时候,先找到middle位置,比较中间位置的value和将要insert进去元素的value,如果小,则将0~middle作为新的链表,如果大middle-max作为新的链表重新以上步骤,来到第二层。
  • 如上图,我们定位插入点时候,先从顶层开始,我们将最顶层的一个节点就相当于中间位置,只不过他是我们通过特殊的数据结构暴露出来的一个中间位置,然后类比上面步骤,我们进入到第二层中,比较中间位置的L2和将要insert进去元素的value,如果小,将L1层级中中0~L2位置元素作为新的链表,我们通过这样逐层的比较,直到最底层找到合适的位置,将新的元素insert进去。
  • 这种做法就相当于将二分法的步骤拆开后,得到N个链表,每个链表都是我们二分法中每个步骤中需要查找的那个半个数组而已,通过比较每个数组中的中间value值来得出最后的位置。用冗余的方式来避免链表的遍历(替换数组的下标索引功能)
  • 跳跃表中节点可以在多个层级有多个身份,跳跃表采用随机策略决定新元素可以到第几层,L0层概率是1 ,L1 只有50%对半分,L2 再次25%,L3只有12.5%,依次,一直随机到顶层L31,每次对半,到顶层基本不会有很多数据。
容器类型数据结构的通用规则
  • list,set, hash, zset四种数据结构是容器型数据结构,他们共享下面几个规则:
  • create if not exists:如果容器不存在,那就创建一个,子啊进行操作,比如rpush操作刚开始没有列表,会新建一个列表,然后在rpush元素
  • drop if no elements:如果容器中没有元素,会立即删除容器,释放内存。意味着lpop操作完最后一个元素后列表就会消失。
  • 过期时间设置,到期自动删除,注意如果一个key设置来过期时间后调用set方法修改,过期时间会消失。

上一篇Redis数据结构以及对应存储策略下一篇Redis基础数据结构之二