安装
- Docker
docker pull redis
docker run --name myredis -d -p6379:6379 redis
docker exec -it myredis redis-cli
- 直接安装
brew install redis
# ubuntu
apt-get install redis
# redhat
yum install redis
# 运行客户端
redis-cli
- 源码启动
windows版:
https://github.com/MSOpenTech/redis/releases
解压:Redis-x64-3.2.100.zip
在解压目录下
启动服务
redis-server.exe
启动客户端,连接服务器
redis-cli.exe
安装为windows服务
redis-server.exe --service-install redis.windows.conf
卸载服务:
redis-server --service-uninstall
开启服务:
redis-server --service-start
停止服务:
redis-server --service-stop
重命名服务:
redis-server --service-name name
应用场景
- 记录帖子点赞数,评论数,点击数
- 缓存用户行为历史
- 有关id
数据结构
五种数据结构
- string
- list
- set
- hash
- zset
1. 字符串string
string 是redis最简单的数据结构,所有数据结构的key都是以唯一的string作为名称,然后通过key去找到value。不同类型的数据结构的差异都是value的结构不一样。
字符串的一个常见的用处就是缓存用户信息,将用户信息结构体使用json序列化成字符串,然后将序列化的字符串存到redis中。同样取出用户信息,也要进行反序列化。
redis的字符串是动态字符串,可以修改的字符串,内部结构类似与ArrayList,采用预分配空间来减少内存的频分分配,内部当前的字符串空间要大于实际字符串长度。当字符串小于1m的时候,扩容都是加倍现有的空间,如果超过1m,每次扩用只会扩1m。字符串的最大长度是512m
设置
#设置键值对
set key1 value1
#根据键获取值
get key1
#判断是否存在
exists key1
#删除
del key1
批量操作
#设置
mset key1 value1 key2 value2
#获取
mget key1 key2
对key设置过期时间
#key1 5秒过期
expire key1 5
#expire 等价 setex
setex key2 5 value2
#如果 name 不存在就执行 set 创建
setnx key1 value1
计数
set age 30
#自增
incr age
#减少6
incrby age -6
#自增是有范围的 在signed long之间
字符串有多个字节组成,每个字节由8个bit组成,可将字符串看成多个bit组成,这边是bitmap数据结构[位图]
list
list相当于linkedList,它是链表结构,插入和删除快,索引定位慢
当弹出最后一个元素,该数据结构被删除,内存自动回收。
list常用来做异步队列使用
- 队列 左进右出
rpush key value1 value2
llen key
lpop key #value1
lpop key # value2
- 栈 右进右出
rpush key value1 value2
rpop key #value2
rpop key #value1
- 慢操作
相当于get(index),需要对链表进行遍历,随着index的增大性能变慢。ltrim跟着两个参数(start_index,end_index)定义了一个区间,区间内的保留,区间外的全部砍掉。可以通过ltrim实现一个定长的链表。index可以为负数,-1是倒数第一个元素,-2是倒数第二个元素
rpush books python java golang
lindex books 1 # O(n) 慎用
lrange books 0 -1 # 获取所有元素,O(n) 慎用
ltrim books 1 -1 # O(n) 慎用
lrange books 0 -1
ltrim books 1 0 # 这其实是清空了整个列表,因为区间范围长度为负
- 快速列表
redis底层存储不是简单的LinkedList ,而是一个快速列表quicklist的一个结构
首先,在列表元素较少的情况下,会使用一块连续的内存存储,称之为ziplist,即压缩列表。将所有的元素紧挨着存储,分配的是一块连续的内存。当数据量大的时候会使用quicklist。因为普通的链表需要附件的指针空间太大,比较浪费空间,加重内存的碎片化。
比如一个列表中只存int类型的数据,但是需要prev和next两个额外的指针,所以将列表和ziplist结合起来组成了quiklist。就是将多个ziplist使用双向指针穿起来使用。满足了插入和删除快,又不会出现太大的空间 冗余
字典hash
相当于HashMap。是无序字典。内部结构和HashMap一致。是数组+链表的二维结构。第一维数组的hash碰撞的时候,就会将碰撞的hash用链表存储起来。
但是redis的字典只能是string 。另外和HashMap的rehash不一样,reahs是一个很耗时的操作,HashMap在的字典很大的时候,需要一次全部rehash。而redis为可高性能,不能堵塞服务,采用了渐进式rehash
渐进式rehash,会在rehash的同时,保留新旧两个hash,查询时会查询两个hash结构,然后在后续的定时任务以及hash的子指令中,将旧hash的内容迁移到新hash中。当旧hash移除最后一个元素,该数据结构被删除,内存被回收。
hash结构也可以存用户信息,不同于字符串一次性需要全部 序列化整个对象,hash可以对用户结构的每个字段进行单独存储,当需要获取用户信息可以进行部分获取,而以字符串的形式进行存储用户信息只能一次性全部读取完,比较浪费网络流量。
hash也有缺点,hash结构的存储消耗要高于单个字符串,使用hash还是字符串根据实际情况来定。
> hset books java "think in java" # 命令行的字符串如果包含空格,要用引号括起来
(integer) 1
> hset books golang "concurrency in go"
(integer) 1
> hset books python "python cookbook"
(integer) 1
> hgetall books # entries(),key 和 value 间隔出现
1) "java"
2) "think in java"
3) "golang"
4) "concurrency in go"
5) "python"
6) "python cookbook"
> hlen books
(integer) 3
> hget books java
"think in java"
> hset books golang "learning go programming" # 因为是更新操作,所以返回 0
(integer) 0
> hget books golang "learning go programming"
> hmset books java "effective java" python "learning python" golang "modern golang
programming" # 批量 set
OK
同字符串一样,hash 结构中的单个子 key 也可以进行计数,它对应的指令是 hincrby,
和 incr 使用基本一样。
> hincrby user-laoqian age 1
(integer) 30
Set集合
相当于HashSet,内部的键值对是无序唯一的。内部实现相当于一个特殊的字典。字典中所有的value都有一个值NULL
当集合中最后一个元素移除之后,数据结构自动删除,内存被回收。 set 结构可以用来存储活动中奖的用户 ID,因为有去重功能,可以保证同一个用户不会中奖两次。
> sadd books python
(integer) 1
> sadd bookspython # 重复
(integer) 0
> sadd books java golang
(integer) 2
> smembers books # 注意顺序,和插入的并不一致,因为 set 是无序的
1) "java"
2) "python"
3) "golang"
> sismember books java # 查询某个 value 是否存在,相当于 contains(o)
(integer) 1
> sismember books rust
(integer) 0
> scard books # 获取长度相当于 count()
(integer) 3
> spop books # 弹出一个
"java"
zset (有序列表)
类似与HashMap和SortSet的结合。它是一个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"
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 0 8.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"
跳跃列表
因为zset支持随机插入和删除,所以不好使用数组表示,采用跳跃列表。
跳跃列表中的元素可以身兼数职,如上图中间的元素,同时处于L0,L1,L2中,可以快速在不同层级进行跳跃。
定位插入点时,先在顶层进行定位,然后下潜到下一级定位,一直下潜到最底层找到合适的位置,将新元素插进去。你也许会问,那新插入的元素如何才有机会「身兼数职」呢?
跳跃列表采取一个随机策略来决定新元素可以兼职到第几层。
首先 L0 层肯定是 100% 了,L1 层只有 50% 的概率,L2 层只有 25% 的概率,L3 层只有 12.5% 的概率,一直随机到最顶层 L31 层。绝大多数元素都过不了几层,只有极少数元素可以深入到顶层。列表中的元素越多,能够深入的层次就越深,能进入到顶层的概率就会越大。