Redis 基础和应用篇
🤗本博客参考的钱文品老师的《Redis深度历险核心原理和应用实战》书籍
0. 安装
Redis主要安装在Linux环境中,Redis有三种安装方式
-
使用Docker安装
> docker pull redis # 运行redis容器 > docker run --name myredis -d -p6379:6379 redis # 执行redis-cli > docker exec -it myredis redis-cli
-
Github源码编译方式
> git clone --branch 2.8 --depth 1 git@github.com:antirez/redis.git > cd redis > make # 编译 > cd src #运行服务器 > ./redis-server --daemonize yes # daemonize表示守护进程, 后端运行 # 运行客户端 > ./redis-cli
-
直接安装方式
# ubuntu > apt-get install redis # redhat > yum install redis # 运行客户段 > redis-cli
1. Redis 基础数据结构
1.1 5种数据结构
Redis有五种数据结构: string, list, hash, set, zset(有序集合)
⚠️Redis的相关指令非常多,本节只选取那些最常见的进行讲解
String
字符串的内部表示是一个字符数组。字符串的常见用途是缓存用户信息,将用户信息使用json序列化为字符串,然后将json字符串存储在redis中。
Redis的字符串是动态字符串,可以修改的。字符串的最大的长度为512M.
String的相关指令
- Redis可以对Key设置过期时间,到时间会被自动删除,这个功能常用来控制缓存的失效时间
# 设置过期
> set name codehole
> get name
"codehole"
> expire name 5 # 设置过期时间为5秒
> setex name 5 codehole # 相当于set + expire
> setnx name codehole # 如果name不存在就创建,如果已经存在则不创建
-
计数
Redis的自增自减是有范围的,符号范围在
signed long
的最大值和最小值之间, [ 2 31 − 1 , 2 31 ] [2^{31}-1, 2^{31}] [231−1,231]超过这个范围, Redis会报错。
> set age 30
OK
> incr age
(integet) 31
> incrby age 5
(integer) 36
list
Redis的list相当于Java的LinkedList
,底层的数据结构是双向链表,不是数组。
Redis的ui列常用来做异步队列使用。将需要延后处理的任务结构体序列化为字符串,放入list中,另一个线程从这个列表中沦陷数据进行处理。
list的相关指令
-
队列
> rpush books python java golang # key为books, value为列表 (integer) 3 > llen books (integer) 3 > lpop books "python" > rpop books "golang"
-
慢操作
lindex 相当于Java链表中的
get(int index)
方法,其中index
可以为负数,负数表示倒数第index
个值。该方法需要对链表进行遍历,时间复杂度为O(n)
。ltrim 有两个参数
start_index
和end_index
定义了一个区间,区间之内的值,ltrim要保留,区间之外的则统统砍掉。可以通过ltrim
来实现一个定长的链表。> rpush books python java golang > lindex books 1 # O(n) "java" > lrange books 0 -1 # 获取所有元素 O(n) 1) "python" 2) "java" 3) "golang" > ltrim books 1 -1 #保留第一个元素到最后一个元素 OK > lrange books 0 -1 1) "java" 2) "golang" > ltrim book 1 0 # 清空所有元素
-
快速列表
Redis的底层不是一个简单的linkedlist,而是一个快速链表
quicklist
.首先在列表元素较少的情况下,会使用一块连续的内存存储。这个结构是
ziplist
(压缩列表)。当数据量比较大的时候,才会改成quicklist
。因为普通的链表需要存储指针,浪费空间,所以Redis将链表和ziplist
结合起来,组成了quicklist
.即将多个ziplist
使用双向指针连接起来。
hash (unsorted_map)
Redis的hash
相当于Java中的HashMap
,它是无须列表,结构上与Java的HashMap一样,都是数组加链表的二维结构。当hash数组位置碰撞时,就会将碰撞的元素使用链表串接起来。
⚠️需要注意的是:Redis字典的值只能是字符串。 rehash的方式和HashMap不一样。因为Java的HashMap的字典很大时,rehash是一个耗时的操作,需要一次性全部rehash.Redis为了追求性能,不能阻塞服务,所以采用了渐近式rehash策略.
渐进式rehash会在rehash的同时,保留新旧两个hash结构,查询时会同时查询两个hash结构,然后在后续的定时任务以及hash操作指令中,循序渐进地将旧hash内容一点点迁移到新的hash结构中。
相关指令
> hset books java "think in java"
(integer) 1
> hset books golang "concurrency in go"
(integer) 1
> hgetall books # entries() key 和 value间隔出现
1) "java"
2) "think in java"
3) "golang"
4) "concurrency in go"
> hlen books # 获取set大小
(integer) 2
> hget books java
"think in java"
# 批量set
> hmset books java "think in java" golang "concurrency in go"
OK
同字符串一样,hash结构中的单个key也可以进行计数,指令和incr
, incrby
一样
> hset user age 1
(integer) 1
> hincrby user age 2
(integer) 3
set
Redis的set相当于Java里面的hashset
,Set结构可以用来存储在某个活动中中将的用户ID,因为set有去重功能,保证一个用户不能中将两次。
相关指令
> sadd books python
(integer) 1
# 重复添加
> sadd books python
(integer) 0 # 添加失败
> sadd books java golang
(integer) 2
> smembers books # 显示所有成员
1) "java"
2) "python"
3) "golang"
> sismember books java # 查询key时候在set中,相当于contains(key)
(integer) 1
> scard books # 获取长度 count()
(integer) 3
> spop books # 弹出一个
"java"
zset (sorted_set)
zset
是Redis提供的最有特色的数据结构,它类似于Java
的SortedSet
和HashMap
的结合体。一方面它是一个set,保证了内部value的唯一性,另一方面它可以给每一个value赋予一个score,代表这个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 # 升序排列
1) "java cookbook"
2) "java concurrency"
3) "think in java"
> zervrange books 0 -1 # 降序排序
1) "think in java"
2) "java concurrency"
3) "java cookbook"
> zscore books "java concurrency" # 获取指定值的score
8.9000000000004
> zrank books "java concurrency" # 获取值的排名, 按照升序排列返回排名,起使值为0
(integer) 1
> zrangebyscore books 0 8.91 # 根据分值区间遍历zset
1) "java cookbook"
2) "java concurrency"
> zrangebyscore books -inf 8.91 withscores # 根据分值区间(-inf, 8.91)遍历zset, 同时返回score
1) "java cookbook" 8.599999999999999
2) "java concurrency" 8.9000000000004
> zrem books "java concurrency" #删除value
跳跃列表
因为Zset要支持随机的插入和删除,所以不适合使用数组来表示,但是需要将这个链表按照score进行排序,也意味着当有新的元素需要插入时候,需要定位插入的位置,保证插入后的链表依旧是有序的。通常使用二分查找来定位插入点,但是二分查找的对象必须是数组。此时就需要跳跃列表来实现。
跳跃列表类似于一种层级制,最下面一层所有的元素都会串起来。然后每隔一个元素挑选一个代表,将这几个代表串起来,然后在这些代表中挑选出二级代表,串起来。最终形成金字塔结构。
跳跃列表采取一个随机策略来决定新元素可以兼职到第几层,首先位于L0
层的概率肯定是100%,兼职到L1
层的只有50%,到L2
层只有25%,以此类推,一直随机到最顶层31层。
1.2 过期时间
Redis的所有数据结构都可以设置过期时间,时间到了,Redis会自动删除相应的对象,但是需要注意的是:
-
过期是以对象为单位的,比如一个hash结构的过期是整个hash对象过期,而不是其中的某个子key
-
如果一个字符串已经设置了过期时间,然后调用
set
方法修改了它,它的过期时间就会消失> set codehole yoyo OK > expire codehole 600 # 过期时间600s (integer) 1 > ttl codehole (integer) 597 > set codehole yoyo OK > ttl codehole (integer) -1