redis有5中基本数据类型:String(字符串)、list(集合)、set(集合)、hash(哈希)和zset(有序集合)
1.String字符串
redis的字符串是动态字符串,是可以修改的字符串,内部结构实现类似Java的ArrayList,采用预分配冗余空间的方式减少内存的频繁分配,如上图,内部为当前字符串实际分配空间capacity一般要高于实际字符串长度len。当字符串长度小于1M时,扩容都是加倍现有的空间,如果超过1M,扩容时一次只会多扩1M的空间。需要注意的是字符串最大长度是512M。
键值对操作:
127.0.0.1:6379> set name zhangsan
OK
127.0.0.1:6379> get name
"zhangsan"
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> set name1 zhangsan
OK
127.0.0.1:6379> set name2 list
OK
127.0.0.1:6379> mget name1 name2
1) "zhangsan"
2) "list"
127.0.0.1:6379> mget name1 name2 name3
1) "zhangsan"
2) "list"
3) (nil)
127.0.0.1:6379> mset name1 boy name2 girl name3 unknown
OK
127.0.0.1:6379> mget name1 name2 name3
1) "boy"
2) "girl"
3) "unknown"
过期和set命令扩展:
可以对key设置过期时间,到点自动删除,类似缓存失效时间。
127.0.0.1:6379> set name1 zhangsan
OK
127.0.0.1:6379> get name1
"zhangsan"
127.0.0.1:6379> expire name1 5 # 5s后过期
(integer) 1
127.0.0.1:6379> get name1
“zhangsan"
# wait for 5s
127.0.0.1:6379> get name1
(nil)
127.0.0.1:6379> setex name1 5 zhangsan # 5s后过期,等价于set+expire
OK
127.0.0.1:6379> get name1
"zhangsan"
127.0.0.1:6379> get name1
(nil)
127.0.0.1:6379> setnx name zhangsan # 如果name不存在就执行 set 创建
(integer) 1
127.0.0.1:6379> get name
"zhangsan"
127.0.0.1:6379> setnx name lisi # 因为name已经存在,所以 set 创建不成功
(integer) 0
127.0.0.1:6379> get name # 没有改变
"zhangsan"
计数
如果value值是一个整数,还可以对它进行自增操作。自增是有范围的,他的范围是 signed long的最大值和最小值,超过这个值,Redis会报错。
127.0.0.1:6379> set age 30
OK
127.0.0.1:6379> incr age
(integer) 31
127.0.0.1:6379> incrby age 5
(integer) 36
127.0.0.1:6379> incrby age -5
(integer) 31
127.0.0.1:6379> set size1 9223372036854775807 # Long.Max
OK
127.0.0.1:6379> incr size
(error) ERR value is not an integer or out of range
2.list(列表)
Redis的列表相当于Java语言中的LinkedList,注意它是链表而不是数组。这意味着list的插入和删除操作非常的快,时间复杂度为O(1),但是索引定位很慢,时间复制度为O(n),当列表弹出最后一个元素后,该数据结构会自动被删除,内存被回收。
Redis的列表结构常用来做异步队列使用。将需要延后处理的任务结构体序列化成字符串塞进Redis的列表,另一个线程从这个列表中轮询数据进行处理。
右边进左边出:队列(先进先出)
127.0.0.1:6379> rpush books python java golang
(integer) 3
127.0.0.1:6379> llen books
(integer) 3
127.0.0.1:6379> lpop books
"python"
127.0.0.1:6379> lpop books
"java"
127.0.0.1:6379> lpop books
"golang"
127.0.0.1:6379> lpop books
(nil)
右边进右边出:栈(先进后出)
127.0.0.1:6379> rpush books python java golang
(integer) 3
127.0.0.1:6379> rpop books
"golang"
127.0.0.1:6379> rpop books
"java"
127.0.0.1:6379> rpop books
"python"
127.0.0.1:6379> rpop books
(nil)
慢操作
lindex相当于Java链表的 get(int index) 方法,它需要对链表进行遍历,性能随着参数 index 增大而变差。
ltrim 它有两个参数,start_index 和 end_index 定义了一个区间,在这个区间的值保留,区间外的统统砍掉。我们可以通过ltrim来实现一个定长的链表,这一点非常有用。
index可以为负数,index=-1 表示倒数第一个元素,同样 index=-2 表示倒数第二个元素。
127.0.0.1:6379> rpush books python java golang
(integer) 3
127.0.0.1:6379> lindex books 1 # O(n) 慎用
"java"
127.0.0.1:6379> lrange books 0 -1 # 获取所有元素,O(n) 慎用
1) "python"
2) "java"
3) "golang"
127.0.0.1:6379> ltrim books 1 -1 # O(n) 慎用
OK
127.0.0.1:6379> lrange books 0 -1
1) "java"
2) "golang"
127.0.0.1:6379> ltrim books 1 0 # 这其实是清空了整个列表,因为区间范围长度为负
OK
127.0.0.1:6379> llen books
(integer) 0
快速列表
如果在深入一点,会发现Redis底层存储的还不是一个简单的 linkedlist ,而是称之为快速链表 quicklist 的一个结构。
首先在列表元素较少的情况下回使用一块连续的内存存储,这个结构是 ziplist ,即压缩列表。它将所有的元素紧挨着一起存储,分配的是一块连续的内存。当数据量较多的时候才会改成 quicklist 。因为普通的链表需要的附加指针空间太大,会比较浪费时间,而且会加重内存的碎片化。比如这个列表里存的只是 int 类型的数据,结构上还需要两个额外的指针 pre 和 next 。所以Redis将链表和 ziplist 结合起来组成了 quicklist 。也就是将多个 ziplist 使用双向指针串起来使用。这样既满足了快速的插入删除性能,也不会出现太大的空间冗余。
3.hash(字典)
Reids的字典相当于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结构的存储消耗要高于单个字符串,需要使用hash还是字符串,需要根据实际情况权衡。
127.0.0.1:6379> hset books java "think in java" # 命令行的字符串包含空格时,要用引号括起来
(integer) 1
127.0.0.1:6379> hset books golang "concurrency in go"
(integer) 1
127.0.0.1:6379> hset books python "python cookbook"
(integer) 1
127.0.0.1:6379> hgetall books # key value # 间隔出现
1) "java"
2) "think in java"
3) "golang"
4) "concurrency in go"
5) "python"
6) "python cookbook"
127.0.0.1:6379> entries books
(error) ERR unknown command 'entries'
127.0.0.1:6379> hlen books
(integer) 3
127.0.0.1:6379> hget books java
"think in java"
127.0.0.1:6379> hset books golang "learning go programming" # 因为是更新操作,所以返回 0
(integer) 0
127.0.0.1:6379> hget books golang
"learning go programming"
127.0.0.1:6379> hmset books java "effective java" python "learning python" golang "modern golang programming"
OK
127.0.0.1:6379> hgetall books
1) "java"
2) "effective java"
3) "golang"
4) "modern golang programming"
5) "python"
6) "learning python"
同字符串一样,hash结构中的单个key也可以进行计数,它对应的指令是 hincrby,和字符串的 incr。
127.0.0.1:6379> hincrby user age 1
(integer) 1
127.0.0.1:6379> hget user age
"1"
127.0.0.1:6379> hincrby user age 5
(integer) 6
127.0.0.1:6379> hget user age
"6"
4.set(集合)
Redis的集合相当于Java语言里面的HashSet,他内部的键值对是无序的唯一的。它的内部相对于实现了一个特殊的字典,字典中所有的value都是一个NULL。
当集合中最后一个元素移除之后,数据结构自动删除,内存被回收。
set结构可以用来存储活动中奖的用户ID,因为有去重功能,可以保证同一个用户不会中奖两次。
127.0.0.1:6379> hgetall books
(empty list or set)
127.0.0.1:6379> sadd books pythone
(integer) 1
127.0.0.1:6379> sadd books java
(integer) 1
127.0.0.1:6379> sadd books golang
(integer) 1
127.0.0.1:6379> sadd books java # 重复
(integer) 0
127.0.0.1:6379> smembers books # 这个顺序和插入的时候并不一致,因为set是无序的
1) "phthon"
2) "golang"
3) "java"
127.0.0.1:6379> sismember books java # 查询某个 value是否存在,相当于 contains(o)
(integer) 1
127.0.0.1:6379> sismember books php
(integer) 0
127.0.0.1:6379> scard books # 获取长度相当于 count()
(integer) 3
127.0.0.1:6379> spop books # 弹出一个
"phthon"
127.0.0.1:6379> spop books
"java"
127.0.0.1:6379> spop books
"golang"
127.0.0.1:6379> spop books
(nil)
5.zset(有序列表)
zset类似于java中的sortedSet和HashMap结合体,一方面它是一个set,保证了内部value的唯一性,另一方面它给每个value赋予一个score,代表这个value的排序权重。它的内部实现的是一种叫做跳跃列表的数据结构。
当zset中最后的一个value移除时,数据结构自动删除,内存被回收。
zset可以用来存放粉丝的列表,value值是粉丝的用户ID,score是关注时间。我们可以对粉丝列表按关注时间进行排序。
zset还可以用来存储学生的成绩,value值是学生的ID,score是他的成绩,我们可以按成绩对学生进行排序就可以得到名次。