Redis有5种基础数据结构:string字符串
、list列表
、set集合
、hash哈希
和zset有序集合
。
Redis的所有数据结构都是一个唯一的字符串类型key作为名称,然后通过这个key值获取相应的value,Redis中5种数据结构就是体现在value上的。
string(字符串)
string字符串时Redis最简单的数据结构,它的使用非常广泛;
常见场景:比如用于缓存用户信息,我们将用户信息结构体通过JSON序列化成字符串放进Redis中缓存,取用户信息时会经过一个反序列化。
单个键值对操作 set、get:
127.0.0.1:6379> set desc "today is 2019-01-26"
OK
127.0.0.1:6379> get desc
"today is 2019-01-26"
127.0.0.1:6379> exists desc
(integer) 1
127.0.0.1:6379> del desc
(integer) 1
127.0.0.1:6379> get desc
(nil)
127.0.0.1:6379>
批量键值对操作 mset、mget:可以批量对多个字符串进行读写,节省网络耗时开销
127.0.0.1:6379> set lang1 chinese
OK
127.0.0.1:6379> set lang2 english
OK
127.0.0.1:6379> mget lang1 lang2 lang3
1) "chinese"
2) "english"
3) (nil)
127.0.0.1:6379> mset book1 java book2 c++ book3 python
OK
127.0.0.1:6379> mget book1 book2 book3
1) "java"
2) "c++"
3) "python"
过期命令expire和setex,可以对key设置过期时间,到点后自动删除,这个功能经常用于设置缓存失效时间:
expire key secords
secords为过期时间,单位时秒
127.0.0.1:6379> set city guangzhou
OK
127.0.0.1:6379> get city
"guangzhou"
127.0.0.1:6379> expire city 5
(integer) 1
# 等待5s过后
127.0.0.1:6379> get city
(nil)
setex key secords value
secords为过期时间,单位时秒,等价于set + expire
127.0.0.1:6379> setex city 5 guangzhou
OK
127.0.0.1:6379> get city
"guangzhou"
# 等待5s过后
127.0.0.1:6379> get city
(nil)
set的其他扩展命令
setnx key value
当key不存在时执行set操作
127.0.0.1:6379> setnx num 001
(integer) 1
127.0.0.1:6379> get num
"001"
127.0.0.1:6379> setnx num 002
(integer) 0 # 因为num已经存在,所以此处设置不成功
127.0.0.1:6379> get num
"001" # num的值没有改变
原子计数:如果value值设置成一个整数,Redis还支持对它记性自增、自减操作,自增自减时有范围的,它的范围时signed long的最大最小值,超过这个范围Redis会报错。
127.0.0.1:6379> set age 25
OK
127.0.0.1:6379> incr age
(integer) 26
127.0.0.1:6379> incrby age 10
(integer) 36
127.0.0.1:6379> incrby age -5
(integer) 31
127.0.0.1:6379> set max 9223372036854775807
OK
127.0.0.1:6379> incr max
(error) ERR increment or decrement would overflow
list(列表)
Redis的list相当于Java中的LinkedList
,它是一个链表结构而不是数组结构,因此它具备链表的优、缺点;
优点
:list的插入和删除操作效率高,时间复杂度喂O(1);
缺点
:list的查询或索引定位比较慢,时间复杂度喂O(n);
此外,列表的特性就是先进先出
,当list弹出最后一个元素之后该数据结构自动被删除,同时内存被回收。
常见场景:Redis的列表结构常用于作为异步队列使用,线程1将需要延后处理的任务结构体序列化喂字符串放进list中,线程2从这个list中轮询取出数据进行处理。
- 右进左出:队列
127.0.0.1:6379> RPUSH books java golang python
(integer) 3
127.0.0.1:6379> LLEN books
(integer) 3
127.0.0.1:6379> LPOP books
"java"
127.0.0.1:6379> LPOP books
"golang"
127.0.0.1:6379> LPOP books
"python"
127.0.0.1:6379> LPOP books
(nil)
- 右进右出:栈
127.0.0.1:6379> RPUSH books java golang python
(integer) 3
127.0.0.1:6379> RPOP books
"python"
127.0.0.1:6379> RPOP books
"golang"
127.0.0.1:6379> RPOP books
"java"
127.0.0.1:6379> RPOP books
(nil)
set(集合)
Redis的set相当于Java中的HashSet,它存储的键值对是无序且唯一的;内部实现相当于一个特殊的Hash,一个所有value都为NULL的特殊Hash,当set中的最后一个元素被移除后,数据结构会自动删除,内存被回收。
127.0.0.1:6379> SADD books java
(integer) 1
127.0.0.1:6379> SADD books java
(integer) 0 # 这里重复插入失败,证明Set结构元素是唯一的
127.0.0.1:6379> SADD books golang python # 批量add
(integer) 2
127.0.0.1:6379> SMEMBERS books # 结果元素顺序跟插入时不一致,证明Set内元素是无序的
1) "python"
2) "java"
3) "golang"
127.0.0.1:6379> SISMEMBER books java # 查询某个元素是否存在,相当于contains(element)
(integer) 1
127.0.0.1:6379> SISMEMBER books c++
(integer) 0
127.0.0.1:6379> SCARD books # 获取集合长度,相当于count()
(integer) 3
127.0.0.1:6379> SPOP books # 弹出一个元素
"python"
hash(哈希)
- Redis中的hash相当于Java 1.8之前中的HashMap,它是无序字典在内部结构的实现上跟HashMap是一样的,都是数组+链表的二维结构;在第一维hash的数组位置碰撞时,就会使用链表将碰撞的元素串联起来。
- hash结构可用于存储用户信息,不同于字符串需要一次性对整个对象序列化,hash结构可以对用户信息中的每个字段单独存储,这样方便我们在获取用户信息时进行部分获取,减少网络传输消耗。
- hash的缺点是它的储存消耗要高于单个字符串结构,至于在实际应用中使用哪种结构,需要大家根据实际情况进行选择。
127.0.0.1:6379> HSET books java "Thinking 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 # entries(),key和value间隔出现
1) "java"
2) "Thinking in Java"
3) "golang"
4) "Concurrency in go"
5) "python"
6) "Python cookbook"
127.0.0.1:6379> HLEN books
(integer) 3
127.0.0.1:6379> HGET books java
"Thinking in Java"
127.0.0.1:6379> HSET books golang "Learning go programming"
(integer) 0 # 因为是更新操作,所以这里返回0
127.0.0.1:6379> HGET books golang
"Learning go programming"
127.0.0.1:6379> HMSET books java "Effective java" golang "Modern golang programming" python "Learning python" # 批量set操作
OK
zset(有序集合)
- Redis中的zset结构类似于Java中的SortedSet和HashMap的结合体,一方面它是一个set,保证了内部元素value的唯一性;另一方面,它可以给每个元素value赋予一个score,表示这个value的排序权重。
- zset在实际应用中可以用来存储排行榜数据,value值存储用户名称或ID,score存储分数,我们可以按照按照分数正/反序对用户列表进行排序。
127.0.0.1:6379> ZADD ranking 22.22 "zhangsan"
(integer) 1
127.0.0.1:6379> ZADD ranking 33.33 "lisi"
(integer) 1
127.0.0.1:6379> ZADD ranking 11.11 "wangwu"
(integer) 1
127.0.0.1:6379> ZRANGE ranking 0 -1 # 按score正序列出,参数区间为排名范围
1) "wangwu"
2) "zhangsan"
3) "lisi"
127.0.0.1:6379> ZREVRANGE ranking 0 -1 # 按score逆序列出,参数区间为排名范围
1) "lisi"
2) "zhangsan"
3) "wangwu"
127.0.0.1:6379> ZCARD ranking # 等同于count()
(integer) 3
127.0.0.1:6379> ZSCORE ranking zhangsan # 获取指定value的score值
"22.219999999999999" # 内部score使用double类型存储,所以存储小数点精度问题
127.0.0.1:6379> ZRANK ranking "zhangsan" # 获取指定value的排名,从0开始
(integer) 1
127.0.0.1:6379> ZRANK ranking "wangwu"
(integer) 0
127.0.0.1:6379> ZRANK ranking "lisi"
(integer) 2
127.0.0.1:6379> ZRANGEBYSCORE ranking 0 30 # 根据score区间列出zset元素
1) "wangwu"
2) "zhangsan"
127.0.0.1:6379> ZRANGEBYSCORE ranking -inf 30 withscores # inf为infinite缩写,-inf为负无穷,withscores参数表示将score一同列出
1) "wangwu"
2) "11.109999999999999"
3) "zhangsan"
4) "22.219999999999999"
127.0.0.1:6379> ZREM ranking "zhangsan" # 删除value
(integer) 1
127.0.0.1:6379> ZRANGE ranking 0 -1
1) "wangwu"
2) "lisi"
其他高级命令
1. KEYS:全局遍历键
KEYS pattern
查找所有符合给定模式pattern
的key;特殊符号用\
隔开;keys命令的速度非常快,但在一个大的数据库中使用它仍然可能造成性能问题,要尽量避免使用。
127.0.0.1:6379> MSET one 1 two 2 three 3 four 4 # 一次设置 4 个 key
OK
127.0.0.1:6379> KEYS *o*
1) "four"
2) "two"
3) "one"
127.0.0.1:6379> KEYS t??
1) "two"
127.0.0.1:6379> KEYS t[w]*
1) "two"
127.0.0.1:6379> KEYS * # 匹配数据库内所有key
1) "four"
2) "three"
3) "two"
4) "one"
2. SCAN:增量/渐进式遍历键
SCAN cursor [MATCH pattern] [COUNT count]
- cursor:游标,整型值。
- MATCH pattern:正则表达式,对redis的所有key过滤,可选。
需要注意的是, 对元素的模式匹配工作是在命令从数据集中取出元素之后, 向客户端返回元素之前的这段时间内进行的, 所以如果被迭代的数据集中只有少量元素和模式相匹配, 那么迭代命令或许会在多次执行中都不返回任何元素。 - COUNT count:每次迭代遍历多少个key,输出结果的key个数不保证与等于count。
SCAN 命令是一个基于游标的迭代器(cursor based iterator): SCAN 命令每次被调用之后, 都会向用户返回一个新的游标cursor, 用户在下次迭代时需要使用这个新游标作为 SCAN 命令的游标参数cursor, 以此来延续之前的迭代过程。
当 SCAN 命令的游标参数cursor被设置为 0 时, 服务器将开始一次新的迭代, 而当服务器向用户返回值为 0 的游标时, 表示迭代已结束。
以下是一个 SCAN 命令的迭代过程示例:
127.0.0.1:6379> scan 0 # 游标设置为0
1) "17" # 返回新的游标,作为下次迭代的cursor参数
2) 1) "key:12"
2) "key:8"
3) "key:4"
4) "key:14"
5) "key:16"
6) "key:17"
7) "key:15"
8) "key:10"
9) "key:3"
10) "key:7"
11) "key:1"
127.0.0.1:6379> scan 17 # 上轮返回的游标
1) "0" # 当迭代返回的游标值为0时,说明redis中的所有key已经遍历完成
2) 1) "key:5"
2) "key:18"
3) "key:0"
4) "key:2"
5) "key:19"
6) "key:13"
7) "key:6"
8) "key:9"
9) "key:11"
Redis存储键值对实际使用的是HashTable的数据结构
3. INFO:查看redis服务运行信息
INFO [section]:通过给定可选的参数 section ,可以让命令只返回某一部分的信息。
-
server : 一般 Redis 服务器信息,包含以下域:
- redis_version : Redis 服务器版本
- redis_git_sha1 : Git SHA1
- redis_git_dirty : Git dirty flag
- os : Redis 服务器的宿主操作系统
- arch_bits : 架构(32 或 64 位)
- multiplexing_api : Redis 所使用的事件处理机制
- gcc_version : 编译 Redis 时所使用的 GCC 版本
- process_id : 服务器进程的 PID
- run_id : Redis 服务器的随机标识符(用于 Sentinel 和集群)
- tcp_port : TCP/IP 监听端口
- uptime_in_seconds : 自 Redis 服务器启动以来,经过的秒数
- uptime_in_days : 自 Redis 服务器启动以来,经过的天数
- lru_clock : 以分钟为单位进行自增的时钟,用于 LRU 管理
-
clients : 已连接客户端信息,包含以下域:
- connected_clients : 已连接客户端的数量(不包括通过从属服务器连接的客户端)
- client_longest_output_list : 当前连接的客户端当中,最长的输出列表
- client_longest_input_buf : 当前连接的客户端当中,最大输入缓存
- blocked_clients : 正在等待阻塞命令(BLPOP、BRPOP、BRPOPLPUSH)的客户端的数量
-
memory : 内存信息,包含以下域:
- used_memory : 由 Redis 分配器分配的内存总量,以字节(byte)为单位
- used_memory_human : 以人类可读的格式返回 Redis 分配的内存总量
- used_memory_rss : 从操作系统的角度,返回 Redis 已分配的内存总量(俗称常驻集大小)。这个值和 top 、 ps 等命令的输出一致。
- used_memory_peak : Redis 的内存消耗峰值(以字节为单位)
- used_memory_peak_human : 以人类可读的格式返回 Redis 的内存消耗峰值
- used_memory_lua : Lua 引擎所使用的内存大小(以字节为单位)
- mem_fragmentation_ratio : used_memory_rss 和 used_memory 之间的比率
- mem_allocator : 在编译时指定的, Redis 所使用的内存分配器。可以是 libc 、 jemalloc 或者 tcmalloc 。
在理想情况下, used_memory_rss 的值应该只比 used_memory 稍微高一点儿。
当 rss > used ,且两者的值相差较大时,表示存在(内部或外部的)内存碎片。
内存碎片的比率可以通过 mem_fragmentation_ratio 的值看出。
当 used > rss 时,表示 Redis 的部分内存被操作系统换出到交换空间了,在这种情况下,操作可能会产生明显的延迟。
Because Redis does not have control over how its allocations are mapped to memory pages, high used_memory_rss is often the result of a spike in memory usage.当 Redis 释放内存时,分配器可能会,也可能不会,将内存返还给操作系统。
如果 Redis 释放了内存,却没有将内存返还给操作系统,那么 used_memory 的值可能和操作系统显示的 Redis 内存占用并不一致。
查看 used_memory_peak 的值可以验证这种情况是否发生。 -
persistence : RDB 和 AOF 的相关信息
-
stats : 一般统计信息
-
replication : 主/从复制信息
-
cpu : CPU 计算量统计信息
-
commandstats : Redis 命令统计信息
-
cluster : Redis 集群信息
-
keyspace : 数据库相关的统计信息
更多命令详解请参考