0%

redis常见数据结构

[toc]


String字符串#

string 不是简单的字符串的意思
而是key -> value(string)
即value是一个string ,可以用一个key去对应到,key也可以理解成变量名。

  • 这个string可以当成数字使用。
    set count 1
    incr count // count+1
    incrby count 100 // count+100
    但是当你get count的时候,得到的是string的count
    23ec1c8f14dda8d7d967db125bf3b0d3b12c6182

  • 也可以是,jpg图片或者序列化的对象。
    因此string本质上是二进制安全的,不会因为编码不同发生变化


Q: redis-string的key可以是中文吗?#

A:
key可以是中文,不过redis在存储的时候会将key进行序列化,在redis中存储的是字节码。

不推荐使用中文Key,内耗更大并且出现乱码的可能性也是有的。编码字节存储等都是不一样的占用空间。


Q: 那么redis string的value可以存中文吗?什么样子的?要不要指定编码?#

A:

  • 在保存到redis中时以utf8的编码方式保存的,实际上时等价于正常的这种十六进制字符串
  • 默认看到的是乱码\xe4\xb8\xaa\xe4\xba\xba\xe8\xb5\x9b
  • 启动cli时多加一个参数就行了,redis-cli --raw这种方式,就可以查看中文了

Q: redis-string的用途随便说几个#

A:

  • 缓存。把常用信息,字符串,图片或者视频等信息放到redis中,redis作为缓存层,mysql做持久化层,降低mysql的读写压力
  • 计数器,单节点redis的计数是同步的,不用担心并发问题。调用incr等方法即可
  • session缓存

Q: string的底层实现有了解吗?和C语言的字符串有什么区别?#

A:
redis的字符串类型是由一种叫做简单动态字符串(SDS)的数据类型来实现
其实就是多了当前长度和剩余容量

1
2
3
4
5
struct sdshdr {
int len; //buf中已占用空间的长度
int free; //buf中剩余空间的长度
char buf[]; //数据空间
}

SDC和C语言字符串的区别:

  1. SDS保存了字符串的长度,而C语言不保存,C语言只能遍历找到第一个\0的结束符才能确定字符串的长度
  2. 修改SDS,会检查空间是否足够,不足会先扩展空间,防止缓冲区溢出,C字符串不会检查
  3. SDS的预分配空间机制,可以减少为字符串重新分配空间的次数

备注:重新分配空间方式,小于1M的数据 翻倍+1,例如:13K+13K+1,如果大于1M,每次多分配1M,例如:10M+1M+1,如果字符串变短,并不会立即缩短,而是采用惰性空间释放,有专门的API可以释放多余空间

List列表#

  • list的本质就是一个双端链表!
  • 用于消息排队、最新消息更新、 消息消费队列

Q: 用redis的list可以实现哪些数据结构?怎么实现?#

A:

  • 栈:lpush + lpop (只在一个方向上做push和pop)
  • 队列:lpush + rpop(一个方向入, 一个方向出)
  • 有限集合:lpush + ltrim(trim是截断,这样可以限制长度)
  • 阻塞的消费者消息队列:lpush+brpop(b就是block的意思,没有就阻塞)

Q: 消费者消息队列能设置超时吗?#

A:
可以的
BLPOP key1 [key2 ] timeout


Q: list里的存储结构是什么?#

A:
链表。 链表里存的是string。


Set集合#

  • set本质上就是实现了一个哈希表, 以至于不能在这个set成员中放入相同的key
  • 除了简单的sadd、smember外,还有很多求交集的关键方法。

**

Q: 为什么标签可以用set来做?而不用list#

A:
因为set允许任意的增删, 而list只能对前后操作。 标签可能会被随时增加删除。
因此被某某某点赞/取消的系统也可以用redis的set做缓存


Hash散列#

  • 和set的区别, 就是key-value, 而set里只有key
  • 命令都是hset、hget、hxxx等

Q: hash存储有什么限制?#

A:

  • 哈希对象保存的所有键值对的键和值的字符串长度都小于64字节
  • 哈希对象保存的键值对数量小于512个

Q: redis如何解决哈希冲突的?#

A:
链表法(挂链法),后入的放到最前面


Q: 散列表容量不足怎么办?#

A:
容量不足时的rehash:
键值数据量变动时,为了让哈希表的负载因子(load factor)维持在一个合理的范围之内, 当哈希表保存的键值对数量太多或者太少时, 程序需要对哈希表的大小进行相应的扩展或者收缩。

  • 如果是扩充,新数组的空间大小为 大于2*used的2的n次方,比如:used=5,则去大于10的第一个2的n次方,为16
  • 如果是缩小,新数组的空间大小为第一个不大于used的2的n次方,比如:used=5,则新大小为4
    即永远2的n次方
    Q:

Zset有序集合#

  • zset不是java中的treeMap。
    treeMap是根据key进行排序
    而zset是根据scope排序, ke是唯一的, key对应的scope不是唯一的。

  • 关键命令: 可以用ZRANGE拿到前几个。


Q: zset的原理是什么?#

A:
底层分别使用ziplist(压缩链表)和skiplist(跳表)实现。

当zset满足以下两个条件的时候,使用ziplist:

  1. 保存的元素少于128个
  2. 保存的所有元素大小都小于64字节
    不满足这两个条件则使用skiplist。
    (注意:这两个数值是可以通过redis.conf的zset-max-ziplist-entries 和 zset-max-ziplist-value选项 进行修改。)处。

  1. 跳表:
    skiplist实现原理
  • 插入时,随机一个层数,然后找到那一层的对应位置插入,并更新前后节点
  • get时,先从最高层找,比要找的大时,再去下一层。
  • 随机时不是完全随机,会根据当前分布情况,修改概率p,并设置某一层的最大限制,防止比别的层过多。
    b148147baa7475ed77e0525aa6fb624c7f822861

Q: redis 3.0里zset有什么改变?#

A:
zset的底层实现变成了quicklist。

  • quicklist宏观上是一个双向链表,因此,它具有一个双向链表的有点,进行插入或删除操作时非常方便,虽然复杂度为O(n),但是不需要内存的复制,提高了效率,而且访问两端元素复杂度为O(1)。
  • quicklist微观上是一片片entry节点,每一片entry节点内存连续且顺序存储,可以通过二分查找以 log2(n)log2(n) 的复杂度进行定位。
    快速列表(quicklist)源码解读

HyperLogLogs基数统计#

Q: 基数用来解决什么问题?#

A:
解决海量计数问题, 例如每天访问的ip数量, 如果真的存每个ip,会很大。
而基数利用位运算来统计, 限制死了每个键的内存大小, 虽然有一定误差(即位冲突的情况),但是在海量的情况下不影响粗略统计。
4c43aab1de89a220066b3d858353d798100969b3


基数每个键占用的内容都是 12K,理论存储近似接近 2^64 个值, 因为位冲突问题可能会偏少。


BitMap 位存储#

用于统计 ”是“或者”否“的 这种一堆人的状态
比如是否在线的缓存, 可以用bitmap。

如果存储一年的打卡状态需要多少内存呢?
365 天 = 365 bit 1字节 = 8bit 46 个字节左右,非常节省内存


geospatial 地理信息#

求解一些经纬度信息的。略

stream#

是一种更可靠的消息队列实现,redis5.0后实现的。
具体见后面的stream详解