Redis详解
Redis 详解
1. 什么是 Redis
Redis 是⼀个基于内存的⾼性能 key-value 数据库。是完全开源免费的,⽤C语⾔编写的,遵守BSD协议。
Redis 特点:
Redis 是基于内存操作的,吞吐量⾮常⾼,可以在 1s内完成⼗万次读写操作
Redis 的读写模块是单线程,每个操作都具原⼦性
Redis ⽀持数据的持久化,可以将内存中的数据保存在磁盘中,重启可以再次加载,但可能会有极短时间内数据丢失
Redis ⽀持多种数据结构,String,list,set,zset,hash等
2. Redis 基本使⽤
3. Redis 数据结构
2.1 String
字符串 string 是 Redis 最简单的数据结构。Redis 所有的数据结构都是以唯⼀的 key 字符串作为名称,然后通过这个唯⼀ key 值来获取相应的 value 数据。不同类型的数据结构的差异就在于 value 的结构不⼀样。Redis 的 string 可以包含任何数据,⽐如 jpg图⽚或者序列化的对象(java 中对象序列化函数 serialize)
内部实现,其本质是⼀个byte数组
struct sdshdr {
long len;//buf数组的长度
long free;//buf数组中剩余可⽤字节数
char buf[];//存储实际字符串内容
}
String 采⽤预分配冗余空间的⽅式来减少内存的频繁分配,内部为当前字符串实际分配的空间 capacity ⼀般要⾼于实际字符串长度len。当字符串长度⼩于 1M 时,扩容都是加倍现有的空间,如果超过 1M,扩容时⼀次只会多扩 1M 的空间。需要注意的是字符串最⼤长度为 512M。
键值对
> set name codehole
OK
> get name
"codehole"
> exists name
(integer)1
> del name
(integer)1
> get name
(nil)
批量键值对
可以批量对多个字符串进⾏读写,节省⽹络耗时开销
> set name1 codehole
OK
> set name2 holycoder
OK
> mget name1 name2 name3 # 返回⼀个列表
1)"codehole"
2)"holycoder"
3)(nil)
> mset name1 boy name2 girl name3 unknow
> mget name1 name2 name3
1)"boy"
2)"girl"
3)"unknown"
过期和 set 命令扩展
可以对 key 设置过期时间,到点⾃动删除,这个功能常⽤来控制缓存的失效时间。不过这个 [⾃动删除] 的机制是⽐较复杂的。
> set name codehole
> get name
"codehole"
> expire name 5 # 5s 后过期
... # wait for5s
> get name
(nil)
> setex name 5 codehole # 5s 后过期,等价于 set + expire
> get name
"codehole"
... # wait for5s
> get name
(nil)
> setnx name codehole # set 和 expire 原⼦执⾏,因为 name 不存在就执⾏创建成功
(integer)1
> get name
"codehole"
> setnx name holycoder # set 和 expire 原⼦执⾏,因为 name 存在 set 创建不成功
(integer)0
> get name
"codehole"
计数
如果 value 值是⼀个整数,还可以对它进⾏⾃增操作。⾃增是有范围的,它的范围是 signed long 的最⼤最⼩值,超过了这个
值,Redis 会报错。
> set age 30
OK
> incr age
(integer)31
> incrby age 5
(integer)36
> incrby age -5
(integer)31
set codehole 9223372036854775807
# Long.Max
Ok
字符串是由多个字节组成,每个字节⼜是由 8 个 bit 组成,如此便可以将⼀个字符串看成很多 bit 的组合,这便是 bitmap「位图」数据结构
2.2 List
Redis 的列表相当于 Java 语⾔⾥⾯的 LinkedList,注意它是链表⽽不是数组,⽽且是双向链表。这意味着 list 的插⼊和删除操作⾮常快,时间复杂度为 O(1),但是索引定位很慢,时间复杂度为
O(n),这点让⼈⾮常意外。
当列表弹出了最后⼀个元素之后,该数据结构⾃动被删除,内存被回收。
Redis 的列表结构常⽤来做异步队列使⽤。将需要延后处理的任务结构体序列化成字符
串塞进 Redis 的列表,另⼀个线程从这个列表中轮询数据进⾏处理。
右边进左边出:队列
> rpush books python java golang
(integer)3
> llen books
(integer)3
> lpop books
"python"
> lpop books
"java"
> lpop books
"golang"
> lpop books
(nil)
右边进右边出:栈
> rpush books python java golang
(integer)3
> rpop books
"golang"
> rpop books
"java"
> rpop books
"python"
> rpop books
(nil)
慢操作
lindex 相当于 java 链表的 get(int index)⽅法,它需要对链表进⾏遍历,性能随着参数 index 增⼤⽽变差。ltrim 和字⾯上的含义不太⼀样,个⼈觉得它叫 lretain(保留) 更适合⼀些,因为 ltrim 跟两个参数 start_index 和 end_index 定义了⼀个区间,在这个区间内的
值,ltrim 是要保留,区间之外统统砍掉。我们可以通过 ltrim 来实现⼀个定长的链表,这⼀点⾮常有⽤。index 可以为负数,index=-1 表⽰倒数第⼀个元素,同样 index=-2 表⽰倒数第⼆个元素。
> rpush books python java golang
(integer)3
> lindex books 1 # O(n)慎⽤,并不会删除"java"
"java"
> lrange books 1-1 # 获取从1开始到最后⼀个元素,O(n)慎⽤,并不会删除
1)"python"
2)"java"
3)"golang"
> ltrim books 1-1 # O(n)慎⽤
OK
> lrange books 0-1 # 获取所有元素
1)"java"
2)"golang"
> ltrim books 10 # 这其实是清空了整个列表,因为区间范围长度为负
OK
> llen books
(integer)0
2.3 Hash(字典)
Redis 的字典相当于 Java 语⾔⾥⾯的 HashMap,它是⽆序字典。内部实现结构上同 Java 的 HashMap 也是⼀致的,同样的数组 +链表⼆维结构。第⼀维 hash 的数组位置碰撞时,就会将碰撞的元素使⽤链表串接起来。
不同的是,Redis 的字典的值只能是字符串,另外它们 rehash 的⽅式不⼀样,因为Java 的 HashMap 在字典很⼤时,rehash 是个耗时的操作,需要⼀次性全部 rehash。Redis为了⾼性能,不能堵塞服务,所以采⽤了渐进式 rehash 策略。
渐进式 rehash 会在 rehash 的同时,保留新旧两个 hash 结构,查询时会同时查询两个 hash 结构,然后在后续的定时任务中以及hash 的⼦指令中,循序渐进地将旧 hash 的内容⼀点点迁移到新的 hash 结构中。
当 hash 移除了最后⼀个元素之后,该数据结构⾃动被删除,内存被回收。
hash 结构也可以⽤来存储⽤户信息,不同于字符串⼀次性需要全部序列化整个对象,hash 以对⽤户结构中的每个字段单独存储。这样当我们需要获取⽤户信息时可以进⾏部分获取。⽽以整个符串的形式去保存⽤户信息的话就只能⼀次性全部读取,这样就会⽐较浪费⽹络流量。
hash 也有缺点,hash 结构的存储消耗要⾼于单个字符串,到底该使⽤ hash 还是字符串,需要根据实际情况再三权衡。
> hset books java "thinks in java" # books 是 key,Java 是 hash中的 key
# 如果字符串包含空格要⽤引号括起来
(integer) 1
> hset books golang "concurrency in go"
(integer) 1
> hgetall books # entries(),key 和 value 间隔出现
1) "java"
2) "thinks in java"
3) "golang"
4) "concurrency in go"
> hlen books
(integer) 2
> hget books golang
"concurrency in go"
> hset books golang "learning go programming" # 因为是更新操作,所以返回 0
(integer) 0
> hget bools golang
"learning go programming"
> hmset books java "effective java" golang "modern golang
programming" # 批量 set
OK
同字符串⼀样,hash 结构中的单个⼦ key 也可以进⾏计数,它对应的指令是 hincrby,和 incr 使⽤基本⼀样。
> hincrby user-laoqian age 1
(integer)30
2.4 Set(集合)
Redis 的集合相当于 Java 语⾔⾥⾯的 HashSet,它内部的键值对是⽆序的唯⼀的。它的内部实现相当于⼀个特殊的字典,字典中所有的 value 都是⼀个值 NULL。
当集合中最后⼀个元素移除之后,数据结构⾃动删除,内存被回收。 set 结构可以⽤来存储活动中奖的⽤户 ID,因为有去重功能,可以保证同⼀个⽤户不会中奖两次。
> sadd books python
(integer)1
> sadd books python # 重复
(integer)0
> sadd books golang
(integer)1
> smembers books # 注意顺序,和插⼊的并不⼀致,因为 set 是⽆序的
1)"golang"
2)"python"
> sismember books python # 查询某个 value 是否存在,相当于contains(o)
(integer)1
> sismember books rust
(integer)0
> scard books # 获取长度相当于count()
(integer)2
> spop books # 弹出⼀个
"python"
2.5 zset(有序集合)
zset 可能是 Redis 提供的最为特⾊的数据结构,它也是在⾯试中⾯试官最爱问的数据结构。它类似于 Java 的 SortedSet 和HashMap 的结合体,⼀⽅⾯它是⼀个 set,保证了内部value 的唯⼀性,另⼀⽅⾯它可以给每个 value 赋予⼀个 score,代表这个 value 的排序权重。它的内部实现⽤的是⼀种叫着「跳跃列表」的数据结构。
zset 中最后⼀个 value 被移除后,数据结构⾃动删除,内存被回收。 zset 可以⽤来存粉丝列表,value 值是粉丝的⽤户 ID,score 是关注时间。我们可以对粉丝列表按关注时间进⾏排序。
zset 还可以⽤来存储学⽣的成绩,value 值是学⽣的 ID,score 是他的考试成绩。我们可以对成绩按分数进⾏排序就可以得到他的名次。
> zadd books 9.0"think in java"
(integer)1
> zadd books 8.9"java concurrency"
(integer)1
> zadd books 8.6"java cookbook"
(integer)1
> zrange books 0-1 # 按 score 排序列出,参数区间为排名范围
1)"java cookbook"
2)"java concurrency"
redis支持的数据结构3)"think in java"
> zrevrange books 0-1 # 按 score 逆序列出,参数区间为排名范围
1)"think in java"
2)"java concurrency"
3)"java cookbook"
> zcard books # 相当于count()
(integer)3
> zscore books "java concurrency" # 获取指定 value 的 score
"8.9000000000000004" # 内部 score 使⽤double类型进⾏存储,所以存在⼩数点精度问题
> zrank books "java concurrency" # 排名
(integer)1
> zrangebyscore books 08.91 # 根据分值区间遍历 zset
1)"java cookbook"
2)"java concurrency"
> zrangebyscore books -inf 8.91 withscores # 根据分值区间(-∞,8.91]遍历 zset,同时返回分值。inf 代表 infinite,⽆穷⼤的意思。
1)"java cookbook"
2)"8.5999999999999996"
3)"java concurrency"
4)"8.9000000000000004"
> zrem books "java concurrency" # 删除 value
(integer)1
> zrange books 0-1
1)"java cookbook"
2)"think in java"
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论