一、9种数据类型

概述
我们先通过一张图了解下 Redis 内部内存管理中是如何描述这些不同数据类型的:
image.png

首先Redis内部使用一个redisObject对象来表示所有的key和value,redisObject最主要的信息如上图所示:type代表一个value对象具体是何种数据类型,encoding是不同数据类型在redis内部的存储方式。

redis支持丰富的数据类型,不同的场景使用合适的数据类型可以有效的优化内存数据的存放空间:

  1. string:最基本的数据类型,二进制安全的字符串,最大512M。
  2. list:按照添加顺序保持顺序的字符串列表。
  3. set:无序的字符串集合,不存在重复的元素。
  4. sorted set:已排序的字符串集合。
  5. hash:key-value对的一种集合。
  6. bitmap:更细化的一种操作,以bit为单位。
  7. hyperloglog:基于概率的数据结构。 # 2.8.9新增
  8. Geo:地理位置信息储存起来, 并对这些信息进行操作 # 3.2新增
  9. 流(Stream)# 5.0新增

String 字符串

常用命令:
setnx,set,get,decr,incr,mget 等。

应用场景:

字符串是最常用的数据类型,他能够存储任何类型的字符串,当然也包括二进制、JSON化的对象、甚至是Base64编码之后的图片。在Redis中一个字符串最大的容量为512MB,可以说是无所不能了。redis的key和string类型value限制均为512MB。

  • 缓存,热点数据
  • 分布式session
  • 分布式锁
  • INCR计数器
  • 文章的阅读量,微博点赞数,允许一定的延迟,先写入 Redis 再定时同步到数据库
  • 全局ID

    INT 类型,INCRBY,利用原子性

  • INCR 限流
    以访问者的 IP 和其他信息作为 key,访问一次增加一次计数,超过次数则返回 false。
  • setbit 位操作

    内部编码:

  • int:8 个字节的长整型(long,2^63-1)
  • embstr:小于等于44个字节的字符串,embstr格式的SDS(Simple Dynamic String)
  • raw:SDS大于 44 个字节的字符串

redis 为什么要自己写一个SDS的数据类型,主要是为了解决C语言 char[] 的四个问题

  1. 字符数组必须先给目标变量分配足够的空间,否则可能会溢出
  2. 查询字符数组长度 时间复杂度O(n)
  3. 长度变化,需要重新分配内存
  4. 通过从字符串开始到结尾碰到的第一个\0来标记字符串的结束,因此不能保存图片、音频、视频、压缩文件等二进制(bytes)保存的内容,二进制不安全

redis SDS

  1. 不用担心内存溢出问题,如果需要会对 SDS 进行扩容
  2. 因为定义了 len 属性,查询数组长度时间复杂度O(1) 固定长度
  3. 空间预分配,惰性空间释放
  4. 根据长度 len来判断是结束,而不是 \0

Hash 哈希表

常用命令:
hget,hsetnx,hset,hvals,hgetall,hmset,hmget 等。

应用场景:

我们简单举个实例来描述下 Hash 的应用场景,比如我们要存储一个用户信息对象数据,包含以下信息:用户 ID 为查找的 key,存储的 value 用户对象包含姓名,年龄,生日等
image.png

内部编码:

  • ziplist(压缩列表):当哈希类型中元素个数小于 hash-max-ziplist-entries 配置(默认 512 个),同时所有值都小于 hash-max-ziplist-value 配置(默认 64 字节)时,Redis 会使用 ziplist 作为哈希的内部实现。
  • hashtable(哈希表):当上述条件不满足时,Redis 则会采用 hashtable 作为哈希的内部实现。

List 列表

常用命令:
lpush,rpush,lpop,rpop,lrange等。

列表(list)用来存储多个有序的字符串,每个字符串称为元素;一个列表可以存储2^32-1个元素。Redis中的列表支持两端插入和弹出,并可以获得指定位置(或范围)的元素,可以充当数组、队列、栈等

应用场景

比如 twitter 的关注列表,粉丝列表等都可以用 Redis 的 list 结构来实现,可以利用lrange命令,做基于Redis的分页功能,性能极佳,用户体验好。

消息队列
列表类型可以使用 rpush 实现先进先出的功能,同时又可以使用 lpop 轻松的弹出(查询并删除)第一个元素,所以列表类型可以用来实现消息队列

发红包的场景
在发红包的场景中,假设发一个10元,10个红包,需要保证抢红包的人不会多抢到,也不会少抢到.
image.png
下面我们通过下图来看一下 Redis 中列表类型的插入和弹出操作:
image.png
下面我们看一下 Redis 中列表类型的获取与删除操作:
image.png
Redis 列表类型的特点如下:

  • 列表中所有的元素都是有序的,所以它们是可以通过索引获取的lindex 命令。并且在 Redis 中列表类型的索引是从 0 开始的。
  • 列表中的元素是可以重复的,也就是说在 Redis 列表类型中,可以保存同名元素

内部编码:

  • ziplist(压缩列表):当列表中元素个数小于 512(默认)个,并且列表中每个元素的值都小于 64(默认)个字节时,Redis 会选择用 ziplist 来作为列表的内部实现以减少内存的使用。当然上述默认值也可以通过相关参数修改:list-max-ziplist-entried(元素个数)、list-max-ziplist-value(元素值)。
  • linkedlist(链表):当列表类型无法满足 ziplist 条件时,Redis 会选择用 linkedlist 作为列表的内部实现。

Set 集合

常用命令:
sadd,spop,smembers,sunion,scard,sscan,sismember等。

应用场景:

Redis set 对外提供的功能与 list 类似是一个列表的功能,特殊之处在于 set 是可以自动排重的,当你需要存储一个列表数据,又不希望出现重复数据时,set 是一个很好的选择,并且 set 提供了判断某个成员是否在一个 set 集合内的重要接口,这个也是 list 所不能提供的。

1.知乎点赞数
image.png

2.存储社交关系

用户(编号user001)关注
sadd focus:user001 user003
sadd focus:user002 user003 user004

相互关注

sadd focus:user001 user002
sadd focus:user002 user001

实现方式:

set 的内部实现是一个 value 永远为 null 的 HashMap,实际就是通过计算 hash 的方式来快速排重的,这也是 set 能提供判断一个成员是否在集合内的原因。

Redis 中的集合类型,也就是 set。在 Redis 中 set 也是可以保存多个字符串的,经常有人会分不清 list 与 set,下面我们重点介绍一下它们之间的不同:

set 中的元素是不可以重复的,而 list 是可以保存重复元素的。
set 中的元素是无序的,而 list 中的元素是有序的。
set 中的元素不能通过索引下标获取元素,而 list 中的元素则可以通过索引下标获取元素。
除此之外 
set 还支持更高级的功能,例如多个 set 取交集、并集、差集等

内部编码

  • intset(整数集合):当集合中的元素都是整数,并且集合中的元素个数小于 512 个时,Redis 会选用 intset 作为底层内部实现。
  • hashtable(哈希表):当上述条件不满足时,Redis 会采用 hashtable 作为底层实现。

备注:我们可以通过 set-max-intset-entries 参数来设置上述中的默认参数。

Sorted set 有序集合

常用命令:
zadd,zrange,zrem,zcard,zscore,zcount,zlexcount等

下面先看一下列表、集合、有序集合三种数据类型之间的区别:
image.png

内部编码:

  • ziplist(压缩列表):当有序集合的元素个数小于 128 个(默认设置),同时每个元素的值都小于 64 字节(默认设置),Redis 会采用 ziplist 作为有序集合的内部实现。
  • skiplist(跳跃表):当上述条件不满足时,Redis 会采用 skiplist 作为内部编码。
    image.png
    备注:上述中的默认值,也可以通过以下参数设置:zset-max-ziplist-entries 和 zset-max-ziplist-value。

应用场景

Redis sorted set 的使用场景与 set 类似,区别是 set 不是自动有序的,而 sorted set 可以通过用户额外提供一个优先级(score)的参数来为成员排序,并且是插入有序的,即自动排序。当你需要一个有序的并且不重复的集合列表,那么可以选择 sorted set 数据结构,比如 twitter 的 public timeline 可以以发表时间作为 score 来存储,这样获取时就是自动按时间排好序的。点击数做出排行榜。

1.商品的评价标签,可以记录商品的标签,统计标签次数,增加标签次数,按标签的分值进行排序
image.png

BitMap 位图

在应用场景中,有一些数据只有两个属性,比如是否是学生,是否是党员等等,对于这些数据,最节约内存的方式就是用bit去记录,以是否是学生为例,1代表是学生,0代表不是学生。那么1000110就代表7个人中3个是学生,这就是BitMaps的存储需求。Bitmaps是一个可以对位进行操作的字符串,我们可以把Bitmaps想象成是一串二进制数字,每个位置只存储0和1。下标是Bitmaps的偏移量。BitMap 就是通过一个 bit 位来表示某个元素对应的值或者状态, 其中的 key 就是对应元素本身,实际上底层也是通过对字符串的操作来实现。Redis从2.2.0版本开始新增了setbit,getbit,bitcount等几个bitmap相关命令。虽然是新命令,但是并没有新增新的数据类型,因为setbit等命令只不过是在set上的扩展。

使用场景

1.用户签到
很多网站都提供了签到功能(这里不考虑数据落地事宜),并且需要展示最近一个月的签到情况

2.统计活跃用户
使用时间作为cacheKey,然后用户ID为offset,如果当日活跃过就设置为1
那么我该如果计算某几天/月/年的活跃用户呢(暂且约定,统计时间内只有有一天在线就称为活跃),有请下一个redis的命令
命令 BITOP operation destkey key [key ...]
说明:对一个或多个保存二进制位的字符串 key 进行位元操作,并将结果保存到 destkey 上。
说明:BITOP 命令支持 AND 、 OR 、 NOT 、 XOR 这四种操作中的任意一种参数

3.用户在线状态
前段时间开发一个项目,对方给我提供了一个查询当前用户是否在线的接口。不了解对方是怎么做的,自己考虑了一下,使用bitmap是一个节约空间效率又高的一种方法,只需要一个key,然后用户ID为offset,如果在线就设置为1,不在线就设置为0,和上面的场景一样,5000W用户只需要6MB的空间。

内部编码

这个就是Redis实现的BloomFilter,BloomFilter非常简单,如下图所示,假设已经有3个元素a、b和c,分别通过3个hash算法h1()、h2()和h2()计算然后对一个bit进行赋值,接下来假设需要判断d是否已经存在,那么也需要使用3个hash算法h1()、h2()和h2()对d进行计算,然后得到3个bit的值,恰好这3个bit的值为1,这就能够说明:d可能存在集合中。再判断e,由于h1(e)算出来的bit之前的值是0,那么说明:e一定不存在集合中:
image.png
需要说明的是,bitmap并不是一种真实的数据结构,它本质上是String数据结构,只不过操作的粒度变成了位,即bit。因为String类型最大长度为512MB,所以bitmap最多可以存储2^32个bit。

HyperLogLog 基数统计、Geo 地理位置、Streams 流(5.5引入)

这三种不常用,后续再讲


一个废柴程序猿
1 声望1 粉丝

一个废柴程序猿的自我反省...