Redis基础数据类型
基本数据结构包含:字符串(strings)、 散列(hashes)、 列表(lists)、 集合(sets)、 有序集合(sorted sets)五种。这五种数据结构在我们工作中经常使用到,面试过程中经常被问到,因此熟练掌握这5种基本数据结构的使用和应用场景是Redis知识最基础也是最重要的部分。
字符串(strings)类型
字符串是Redis最简单的储存类型,它存储的值可以是字符串、整数或浮点数,对整个字符串或字符串的其中一部分执行操作;对整数或者浮点数执行自增(increment)或者自减(decrement)操作。
Redis的字符串是一个由字节组成的序列,跟java里面的ArrayList有点类似,采用预分配冗余空间的方式来减少内存的频繁分配,如图中所示,内部为当前字符串实际分配的空间capacity一般要高于实际串长度len。当字符串长度小于1M时,扩容加倍成现有的空间,如果超过1M,扩容时一次只会多扩1M的空间。需要注意的是字符串最大长度为512M。
应用场景
字符串类型在工作中使用广泛,主要用于缓存数据,提高查询属性。比如存储登录信息,电商中存储商品信息、可以做计数器等等。
- set key value: 添加一条String类型数据
- get key: 获取一条String类型数据
- mset key1 value1 key2 value2: 添加多条String类型数据
- mget key1 key2: 获取多条String类型数据
- incr key: 自增(+1)
- incrby key step: 按照步长(step)自增
- decr key: 自减(-1)
- decrby key step: 按照步长(step)递减
实操
插入字符串
java
> set username zhangsan
"OK"
获取字符串
> get username
"zhangsan"
插入多个字符串
>mset age 18 address bj
"OK"
获取多个字符串
>mget username age
1) "zhangsan"
2) "18"
自增
>incr num
"1"
>incr num
"2"
自减
>decr num
"1"
指定步长自增
>incrby num 2
"3"
>incrby num 2
"5"
指定步长自减
>decrby num 3
"2"
删除
>del num
"1"
hash散列
散列相当于Java中的HashMap,内部是无序字典。实现原理跟HashMap一致。一个哈希表有多个节点,每个节点保存一个键值对。
与Java中HashMap不同的是,rehash的方式不一样,因为Java在HashMap在字典很大时,redhash是个耗时的操作,需要一次性全部rehash。Redis为了高性能,不能堵塞服务,所以采用了渐进式rehash策略。
渐进式rehash会有rehash的同时,保留新旧两个hash结构,查询时会同时查询两个 hash结构,然后在后续的定时任务中以及hash操作指令中,循序渐进地将旧hash的内容一占占迁移到新的hash结构中。当搬迁完成了,就会使用新的hash结构取而代之。
当hash移除了最后一个元素之后,该数据结构自动被删除,内存被回收。
应用场景
Hash也可以同于对象存储,比如存储用户信息,与字符串不一样的是,字符串是需要将对象进行序列化(比如json序列化)之后才能保存,而Hash则可以讲用户对象的每个字段单独存储,这样就能节省序列化和反序列的时间。如下:
此外还可以保存用户的购买记录,比如key为用户id,field为商品id,value为商品数量。同样还可以用于购物车数据的存储,比如key为用户id,field为商品id,value为购买数量等等。
操作指令
# 设置属性
hset keyname field1 value1 field2 value2
# 获取某个属性值
hget keyname field
# 获取所有属性值
hgetall keyname
# 删除某个属性
hdel keyname field
# 获取属性个数
hlen keyname
# 按照步长自增/自减某个属性(该属性必须是数字)
hincrby keyname field step
实操
# 插入 hash 数据
>hset userInfo username zhangsan age 18 address bj
"3"
# 获取 hash 单条 field 数据
>hget userInfo username
"zhangsan"
>hget userInfo age
"18"
# 获取 hash 多个 field 数据
>hmget userInfo username age
1) "zhangsan"
2) "18"
# 获取 hash 所有 field 数据
>hgetall userInfo
1) "username"
2) "zhangsan"
3) "age"
4) "18"
5) "address"
6) "bj"
# 获取 hash 的 field 个数
>hlen userInfo
"3"
# 自增 hash 的某个 field
>hincrby userInfo age 2
"20"
>hincrby userInfo age 2
"22"
# 自减 hahs 的某个 field(通过自增负步长达到)
>hincrby userInfo age -2
"20"
# 删除 hash 的某个 field
>hdel userInfo age
"1"
# 删除 hash 所有数据
>del userInfo
"1"
列表(lists)
Redis中的lists相当于Java中的LinkedList,实现原理是一个双向链表(其底层是一个快速列表),即可以支持反向查找和遍历,更方便操作。插入和删除操作非常快,时间复杂度为 O(1),但是索引定位很慢,时间复杂度为 O(n)。
应用场景
lists的应用场景非常多,可以利用它轻松实习热销榜;可以实现工作队列(利用lists的Push操作,将任务存在list中,然后工作线程再用POP操作将任务取出进行执行);可以实现最新列表,最热评论等。
操作指令
# 左进
lpush key value1 value2 value3...
# 左出
lpop key
# 右进
rpush key value1 value2 value3...
# 右出
rpop key
# 从左往右读取 start和end是下标
lrange key start end
实操
# 从 list 左边依次插入
>lpush student zhangsan lisi wangwu
"3"
# 从 list 右边插入
>rpush student tianqi
"4"
# 从 list 左边弹出一个
>lpop liangshan
"wangwu"
# 从 list 右边弹出一个
>rpop liangshan
"tianqi"
# 获取 list 下标 0 ~ 1 的数据(左闭右闭)
>lrange liangshan 0 1
1) "lisi"
2) "zhangsan"
注意:blpop阻塞版获取
为什么要阻塞版本的pop呢,主要是为了避免轮询。举个简单的例子如果我们用list来实现一个工作队列。执行任务的thread可以调用阻塞版本的pop去获取任务这样就可以避免轮询去检查是否有任务存在。当任务来时候工作线程可以立即返回,也可以避免轮询带来的延迟。