初识Redis

 

简略的记录一下学过的Redis的基础知识.

在当初看Netty框架的时候, 我被Netty简约而高效的设计给震撼到了, (现在Netty却忘个差不多了, 尴尬), 在前段时间学习Redis的时候, 我又被这种设计风格给震撼到了, Redis作为一个键值对存储数据系统, 它运行在内存中, 仅仅有5种数据类型, 命令也相当地简洁. 而且Redis是单线程, 如果在我没有用过Redis之前, 我估计会怀疑Redis为什么会这么火, 但用过了之后, 真香.

真香

Redis的数据类型

Redis 一共5中数据结构, string(字符串), list(列表), hash(字典), set(集合), zset(有序集合)

redis是一种key-value数据库, 所以你在创建一个基本数据结构的同时, 必须给他命名一个key, 当你想要取出指定的元素的时候, 也是通过这个key来取出.

string (字符串)

redis中的字符串的长度是可变的, 它采用预分配冗余的空间来减少每次实际长度的变化带来的频繁的内存分配, 为redis分配的长度, 一般要大于实际字符串的长度.

redis的扩容机制是当字符串占用的内存空间小于1M时候, 扩容都是将字符串的长度扩容到它原来容量的两倍, 当所占用的内存空间大于1M时候, 那么就会每次申请1M的空间进行扩容. 字符串的最大占用内存空间是512MB.

redis 127.0.0.1:6379> set name tom
OK
redis 127.0.0.1:6379> get name
"tom"
redis 127.0.0.1:6379> del name
(integer) 1
redis 127.0.0.1:6379> get name
(nil)
# 当然, 也可以多项一起添加
redis 127.0.0.1:6379> mset name tom age 21 sex male
OK
redis 127.0.0.1:6379> mget name age sex
1) "tom"
2) "21"
3) "male"

你以为就字符串就这么简单? 那么计算数字用什么?

也是用string啦, 当string存入的数据是整数形式的时候, 可以进行具体的运算. (不支持浮点数)

redis 127.0.0.1:6379> set num 7
OK
redis 127.0.0.1:6379> get num
"7"
# incr key  如果key所指的value是数值形式. 则+1
redis 127.0.0.1:6379> incr num  
(integer) 8  
# incrby num 可以进行具体的相加
redis 127.0.0.1:6379> incrby num 7
(integer) 15
redis 127.0.0.1:6379> get num
"15"
redis 127.0.0.1:6379> incrby num -15
(integer) 0
redis 127.0.0.1:6379> incrby num -1
(integer) -1

整数的范围根据你所使用的redis服务器有关, 如果是64位, 那么范围就是-(2^63)-(2^63-1)

如果我们不知道一个值是否存在, 难道我们需要先get key再进行设置?

不需要, setnx key value 当已经存在指定的key的时候, 会返回失败, 也就是返回0

redis 127.0.0.1:6379> setnx num 5
(integer) 0

缓存都是有有效期的, redis也提供了这样的命令. expire key [秒数]

redis 127.0.0.1:6379> set num 7
OK
redis 127.0.0.1:6379> expire num 60
(integer) 1
redis 127.0.0.1:6379> ttl num
(integer) 57
# 等待一会
redis 127.0.0.1:6379> get num
(nil)

在添加一个key的时候, 也可以同时设置该key的生存时间. setex key [秒数] [value]

redis 127.0.0.1:6379> setex num 60 10010
OK
redis 127.0.0.1:6379> ttl num
(integer) 56
redis 127.0.0.1:6379> get num
"10010"

list(列表)

list是用链表来实现的, 所以插入和删除非常快O(1), 但是定位很慢O(n).

当list的长度为0的时候, list会被自动删除.

list提供的功能非常丰富, 所以我们可以把它当作一个队列或者栈来使用.

FIFO

添加进入队列. rpush key [value---] 查看队列长度. llen key 从队列尾部弹出. rpop key

127.0.0.1:6379> rpush jvmlangs java scale kotlin
(integer) 3
127.0.0.1:6379> llen jvmlangs
(integer) 3
127.0.0.1:6379> lpop jvmlangs
"java"
127.0.0.1:6379> lpop jvmlangs
"scale"
127.0.0.1:6379> lpop jvmlangs
"kotlin"
127.0.0.1:6379> lpop jvmlangs
(nil)
127.0.0.1:6379> llen jvmlanges
(integer) 0

添加的方法和队列是一样的, 其实语义都相同, 不同的是弹出的时候.

从队列头部弹出元素. rpop key

127.0.0.1:6379> get jvmlangs
(nil)
127.0.0.1:6379> rpush jvmlangs java scale kotlin
(integer) 3
127.0.0.1:6379> rpop jvmlangs
"kotlin"
127.0.0.1:6379> rpop jvmlanges
(nil)
127.0.0.1:6379> rpop jvmlangs
"scale"
127.0.0.1:6379> llen jvmlangs
(integer) 1
127.0.0.1:6379> rpop jvmlangs
"java"
127.0.0.1:6379> rpop jvmlangs
(nil)

当然既然是列表, redis也提供了来获取指定元素的方法.lindex key [下标] (注意O(n))

获取指定范围的元素.lrange key [起始下标] [末尾下标] , 这里的包括范围是起始下标<= node <=末尾下标

删除范围之外元素. ltrim key [起始] [末尾]

下标index可以为负数, 代表倒数第几个元素.

127.0.0.1:6379> rpush books java scale kotlin
(integer) 3
127.0.0.1:6379> lindex books 1
"scale"
127.0.0.1:6379> lrange books 0 -1
1) "java"
2) "scale"
3) "kotlin"
127.0.0.1:6379> lrange books 0 1
1) "java"
2) "scale"
127.0.0.1:6379> ltrim books 0 -1
OK
127.0.0.1:6379> lrange 0 -1
(error) ERR wrong number of arguments for 'lrange' command

hash(字典)

hash, 你可以理解为java中的HashMap, 也是采用的数组+链表, 但是在扩容时候采用rehash的方式不一样, redis采用的是渐进式rehash, 而不是像java中, 停下来全部进行rehash.

存入一个字典. hset key [key value]

批量加入. hmset key [[key value] [key value] --]

获取整个字典 hgetall key

获取字典中的指定元素 hget key [元素key]

查看字典中长度 hlen

127.0.0.1:6379> hset books java "think in java"
(integer) 1
127.0.0.1:6379> hset books python "python cookbook"
(integer) 1
127.0.0.1:6379> hgetall books
1) "java"
2) "think in java"
3) "python"
4) "python cookbook"
127.0.0.1:6379> hlen books
(integer) 2
127.0.0.1:6379> hmset books java "effective java" python "another python book" c "programing c"
OK
127.0.0.1:6379> hgetall books
1) "java"
2) "effective java"
3) "python"
4) "another python book"
5) "c"
6) "programing c"
127.0.0.1:6379> hget books java
"effective java"

set(集合)

set相当于java中的HashSet, set中的元素是不重复.

添加一个元素 sadd key [value]

批量添加和添加一个元素时候是一样的. sadd key [value] [value]--

查看set中的所有元素. smembers key

查看元素是否在set中. sismember key [value] 返回0 or 1

查看set的长度 scard key

移除并返回集合中的一个随机元素 spop key

127.0.0.1:6379> sadd books java
(integer) 1
127.0.0.1:6379> sadd books python golang
(integer) 2
127.0.0.1:6379> smembers books
1) "python"
2) "java"
3) "golang"
127.0.0.1:6379> scard
(error) ERR wrong number of arguments for 'scard' command
127.0.0.1:6379> scard books
(integer) 3
127.0.0.1:6379> spop books
"golang"
127.0.0.1:6379> sismember books golang
(integer) 0

zset(有序集合)

再往zset中添加元素的时候, 需要给这个元素一个权值, 这个权值作为这个元素排序的根据.

添加元素 zadd key score value

查看范围内的元素zrange key [起始] [末尾] 当然是排好序的. 逆序的话是 zrevrange key [起始] [末尾]

获取指定value的score , zscore key [value]

根据分值获取区间元素 zrangebyscore key [起始分值] [末尾分值]

删除value. zrem key [value]

查看元素个数 zcard key

127.0.0.1:6379> zadd langs 9.0 java
(integer) 1
127.0.0.1:6379> zadd langs 8.9 c++
(integer) 1
127.0.0.1:6379> zadd langs 8.8 php
(integer) 1
127.0.0.1:6379> zrange langs 0 -1
1) "php"
2) "c++"
3) "java"
127.0.0.1:6379> zrevrange langs 0 -1
1) "java"
2) "c++"
3) "php"
127.0.0.1:6379> zscore langs java
"9"
127.0.0.1:6379> zscore langs c++
"8.9000000000000004"
127.0.0.1:6379> zrem langs php
(integer) 1
127.0.0.1:6379> zcard langs
(integer) 2

现在redis可以加入模块, 又在这些结构之上添加了其他的数据结构.

数据结构总览

数据结构与编码

具体的分析, 有时间再总结.

我看的教程是老钱的«Redis 深度历险:核心原理与应用实践», 很棒, 这里推荐大家一下.