Redis安装
在Linux或者Mac环境可以直接安装使用
Docker 方式
# 拉取 redis 镜像
> docker pull redis
# 运行 redis 容器
> docker run --name myredis -d -p6379:6379 redis
# 执行容器中的 redis-cli,可以直接使用命令行操作 redis
> 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
# 运行服务器,daemonize表示在后台运行
> ./redis-server --daemonize yes
# 运行命令行
> ./redis-cli
直接安装方式
# mac
> brew install redis
# ubuntu
> apt-get install redis
# redhat
> yum install redis
# 运行客户端
> redis-cli
Redis基础数据结构
Redis有5种基础数据结构,分别为:string(字符串)、list(列表)、set(集合)、hash(哈希)和zset(有序集合)
string(字符串)
Redis所有的数据结构都是以唯一的key字符串作为名称,然后通过这个唯一的key值来获取响应的value数据。不同类型的数据结构差异在于value的结构不同。
Redis的字符串是可以修改的动态字符串,采用预分配冗余空间的方式来减少内存的频繁分配(如图:内部为当前字符串分配的空间capacity一般要高于实际字符串长度len),当字符串小于1M时,扩容都是加倍现有的空间,如果超过1M,扩容时一次只会多扩1M的空间,字符串最大长度为512M。
键值对
127.0.0.1:6379> set name xiaoyige
OK
127.0.0.1:6379> get name
"xiaoyige"
127.0.0.1:6379> exists name
(integer) 1
127.0.0.1:6379> del name
(integer) 1
127.0.0.1:6379> get name
(nil)
批量键值对
127.0.0.1:6379> mset name1 mingzi1 name2 mingzi2
OK
127.0.0.1:6379> mget name1 name2 name3
1) "mingzi1"
2) "mingzi2"
3) (nil)
过期和set命令扩展
可以对key设置过期时间,到点自动删除,这个功能常用来控制缓存失效时间。
127.0.0.1:6379> set name mingzi
OK
127.0.0.1:6379> get name
"mingzi"
127.0.0.1:6379> expire name 5 # 5s后过期
(integer) 1
127.0.0.1:6379> get name
"mingzi"
# 5s内能获取到
127.0.0.1:6379> get name
(nil)
# 5s后缓存失效
127.0.0.1:6379> setex name 5 mingzi 5s后过期,等价于set+expire
OK
127.0.0.1:6379> get name
"mingzi"
127.0.0.1:6379> get name
(nil)
127.0.0.1:6379> setnx name mingzi # 当key不存在时执行set创建
(integer) 1
127.0.0.1:6379> get name
"mingzi"
127.0.0.1:6379> setnx name mingzi2 # 因为key已经存在所有创建不成功
(integer) 0
127.0.0.1:6379> get name
"mingzi"
注意setnx(SET if Not eXists),命令在指定的key不存在时,为key设置指定的值。
计数
如果value是个证书,还可以对它进行自增操作。但是自增是有范围的,是**signed long的最大最小值(±),超过了redis会报错
127.0.0.1:6379> set num 100
OK
127.0.0.1:6379> incr num # 默认+1
(integer) 101
127.0.0.1:6379> incrby num 4
(integer) 105
127.0.0.1:6379> incrby num -4
(integer) 101
127.0.0.1:6379> set num 9223372036854775807 # Long.Max
OK
127.0.0.1:6379> incr num
(error) ERR increment or decrement would overflow
字符串是由很多个字节组成,每个字节又是由8个bit组成,如此便可以将一个字符串看成很多bit的组合,这便是bitmap「位图」数据结构
list(列表)
Redis的列表是链表,这意味着list的插入和删除操作非常快,时间复杂度为O(1),但是索引定位很慢,时间复杂度为O(n)。
当列表弹出了最后一个元素后,该数据结构自动被删除,内存被回收
Redis的列表结构常用来做异步队列使用。将需要延后处理的任务结构体序列化成字符串塞进Redis的列表,另一个线程从这个列表中轮询数据进行处理。
127.0.0.1:6379> rpush books golang java python # golang <-> java <-> python
(integer) 3
127.0.0.1:6379> llen books
(integer) 5
127.0.0.1:6379> lpush books C++ C#
(integer) 5
127.0.0.1:6379> lpop books # C# <-> C++ <-> golang <-> java <-> python
"C#"
127.0.0.1:6379> rpop books # C++ <-> golang <-> java <-> python
"python"
127.0.0.1:6379> lpop books # C++ <-> golang <-> java
"C++"
127.0.0.1:6379> rpop books # golang <-> java
"java"
127.0.0.1:6379> lpop books # golang
"golang"
127.0.0.1:6379> rpop books
(nil)
慢操作lindex
127.0.0.1:6379> rpush books golang java python
(integer) 3
127.0.0.1:6379> lindex books 1 # 定位找到下标为1的元素
"java"
127.0.0.1:6379> lrange books 0 -1 # 遍历books
1) "golang"
2) "java"
3) "python"
127.0.0.1:6379> ltrim books 1 -1 # O(n) 保留从下标1开始到最后的元素
OK
127.0.0.1:6379> lrange books 0 -1
1) "java"
2) "python"
127.0.0.1:6379> ltrim books 0 0 # 保留下标为0的元素
OK
127.0.0.1:6379> lrange books 0 -1
1) "java"
127.0.0.1:6379> ltrim books 1 0 # 清空了整个列表,因为区间范围长度为负
OK
127.0.0.1:6379> llen books
(integer) 0
127.0.0.1:6379> rpush books golang java python
(integer) 3
127.0.0.1:6379> rpush books golang java python
(integer) 6
127.0.0.1:6379> rpush books golang java python
(integer) 9
127.0.0.1:6379> lrange books 0 -1
1) "golang"
2) "java"
3) "python"
4) "golang"
5) "java"
6) "python"
7) "golang"
8) "java"
9) "python"
127.0.0.1:6379> lrem books 2 golang # 从表头遍历删除值为golang的元素并删除,删除两个结束,如果是负数,则从表尾向表头遍历
(integer) 2
127.0.0.1:6379> lrange books 0 -1
1) "java"
2) "python"
3) "java"
4) "python"
5) "golang"
6) "java"
7) "python"
lindex相当于链表中get(int index)方法,它需要对链表进行遍历,性能随着参数index增大而变差
快速列表
Redis底层存储的不是一个简单的linkedlist,而是称之为快速链表quicklist的一个结构。
在列表元素较少的情况下会使用一块连续的内存存储,这个结构是压缩列表(ziplist)。当数据量比较多的时候才会改成quicklist。原因是普通的链表需要的附加指针空间太大,会比较浪费空间,而且会加重内存的碎片化。所以Redis将链表和ziplist结合起来组成了quicklist。将多个ziplist使用双向指针串起来使用。这样的优点是满足了快速的插入删除功能,而且不回出现太大的空间冗余。
hash(字典)
Redis中的字典是无序字典。使用的数组+链表二维结构。第一维hash的组位置碰撞时,就会将碰撞的元素使用链表串起来。
Redis的字典的值只能是字符串。
Rehash:h[0]为原hash表,当h[0]在装载数据到一定成都的时候触发扩容和rehash过程
不同于Java的rehash,redis为了高性能,不能堵塞服务器,所以采用了渐进式rehash策略。
渐进式rehash会在rehash的同时,保留新旧两个hash结构,循序渐进地将旧hash的内容一点点前一到新的hash结构中。当迁移完成后,就会使用新的hash结构取而代之。当hash移除了最后一个元素后,该数据结构自动被删除,内存被回收。
当hash结构的存储消耗要高于单个字符串,到底该使用hash还是字符串,需要根据实际情况权衡。
127.0.0.1:6379> hset guowei height 177
(integer) 1
127.0.0.1:6379> hset guowei weight 127
(integer) 1
127.0.0.1:6379> hset guowei friends gaopeng
(integer) 1
127.0.0.1:6379> hgetall guowei
1) "height"
2) "177"
3) "weight"
4) "127"
5) "friends"
6) "gaopeng"
127.0.0.1:6379> hlen guowei
(integer) 3
127.0.0.1:6379> hget guowei height
"177"
127.0.0.1:6379> hmset guowei sister guohuan mom yangfengrong dad guoshujun
OK
127.0.0.1:6379> hget guowei sister
"guohuan"
127.0.0.1:6379> hmget guowei sister mom dad
1) "guohuan"
2) "yangfengrong"
3) "guoshujun"
127.0.0.1:6379> hset guohuan legs 2
(integer) 1
127.0.0.1:6379> hset guohuan arms 2
(integer) 1
127.0.0.1:6379> hdel guohuan arms # 删除key为arms的元素
(integer) 1
127.0.0.1:6379> hgetall guohuan
1) "legs"
2) "2"
hash结构中单个key也可以进行计数,对应的指令是hincrby、hincr
127.0.0.1:6379> hset guowei age 26
(integer) 1
127.0.0.1:6379> hincrby guowei age 1
(integer) 27
set(集合)
Redis set的键值是无序的唯一的。它的内部实现相当于一个特殊的字典,字典中所有的value都是一个值NULL。
当集合中最后一个元素移除之后,数据结构自动删除,内存被回收。
127.0.0.1:6379> sadd guowei leftleg # 可以添加一个或者多个值
(integer) 1
127.0.0.1:6379> sadd guowei rightleg
(integer) 1
127.0.0.1:6379> sadd guowei leftleg
(integer) 0 # 元素重复,添加失败
127.0.0.1:6379> sadd guowei leftarm
(integer) 1
127.0.0.1:6379> sadd guowei rightarm
(integer) 1
127.0.0.1:6379> smembers guowei
1) "rightleg"
2) "leftleg"
3) "leftarm"
4) "rightarm"
127.0.0.1:6379> scard guowei # 获取集合元素个数
(integer) 4
127.0.0.1:6379> spop guowei # 瞎级把弹
"leftarm"
zset(有序集合)
一方面zset是一个set,保证了内部value的唯一性,另一方main可以给每个value赋予一个score,代表这个value的排序权重。它的内部实现方式是「跳跃列表」的数据结构。
zset中最后一个value被移除后,数据结构自动删除,内存被回收。
127.0.0.1:6379> zadd guowei 9.0 "I hava a head"
(integer) 1
127.0.0.1:6379> zadd guowei 8.0 "I hava arms"
(integer) 1
127.0.0.1:6379> zadd guowei 7.0 "I hava legs"
(integer) 1
127.0.0.1:6379> zrange guowei 0 -1 # 按照score从小到大输出
1) "I hava legs"
2) "I hava arms"
3) "I hava a head"
127.0.0.1:6379> zrevrange guowei 0 -1 # 按照score从大到小输出
1) "I hava a head"
2) "I hava arms"
3) "I hava legs"
127.0.0.1:6379> zcard guowei
(integer) 3
127.0.0.1:6379> zscore guowei "I hava a head" # 指定value获取score
"9"
127.0.0.1:6379> zrangebyscore guowei 0 8.7 # 指定范围
1) "I hava legs"
2) "I hava arms"
127.0.0.1:6379> zrangebyscore guowei 8.7 inf # inf为ininfinite,无穷大的意思,-inf为负无穷大
1) "I hava a head"
127.0.0.1:6379> zrangebyscore guowei 8.0 inf withscores # inf为ininfinite,无穷大的意思,-inf为负无穷大,所以该命令是根据分值区间(-∞,8.0)遍历zset,同时返回分值
1) "I hava arms"
2) "8"
3) "I hava a head"
4) "9"
127.0.0.1:6379> zrem guowei "I hava a head" # 删除元素
(integer) 1
127.0.0.1:6379> zrange guowei 0 -1
1) "I hava legs"
2) "I hava arms"
跳跃列表
因为zset需要支持随机的插入和删除,所以不好使用数组来表示
链表可以支持随机的插入和删除,但是需要按照score值进行排序。当有新元素要插入时,需要定位到特定位置的插入点,这样才可以保证链表是有序的,但是如果线性比较的话耗时太久,所以通常我们使用二分法来查找插入点,这就产生了跳表。
跳表类似于层级制,最下面一层所有元素都会串起来。然后每隔几个挑选出一个代表,再将这几个代表使用另外一级指针串起来。然后在这些代表里再选出二级代表,再串起来。最终形成了金字塔结构。
在定位插入点时,先在顶层进行定位,然后下潜到下一级定位。一直下潜到最底层找到合适到位置,将新元素插进去。
容器型数据结构的通用规则
list/set/hash/zset 这四种数据结构是容器型数据结构,他们共享两条通用规则:
- create if not exists:如果容器不存在,那就创建一个,再进行操作
- drop if not elements:如果容器里元素没有了,那么立即删除容器释放内存。
过期时间
Redis所有的数据结构都可以设置过期时间,时间到了,Redis会自动该删除相应的对象。如果一个字符串已经设置了过期时间,然后你又调用了set方法修改了它,则它的过期时间会消失
127.0.0.1:6379> set guowei ll
OK
127.0.0.1:6379> expire guowei 1000
(integer) 1
127.0.0.1:6379> get guowei
"ll"
127.0.0.1:6379> ttl guowei
(integer) 990
127.0.0.1:6379> set guowei ff
OK
127.0.0.1:6379> ttl guowei
(integer) -1