1. Redis简介
redis全称 Remote Dictionary Service(远程字典服务)。Redis存储的都是K-V对,Redis里存放的任何一条数据都有唯一的key作为名称。Redis中,所有的key都存储在一个很大的字典中。一个Redis实例有多个库(database),可用过配置文件里的“databases”配置,库索引从0到databases-1,可以使用指令SELECT 切换到不通的库。
注意:在Redis命令行,Redis的指令不区分大小写,但是key区分大小写。
2. Redis基础数据结构
Redis有五种基础的数据结构:string、list、hash、set、zset。其中list、hash、set、zset称为容器型数据结构,容器型数据结构有两条通用规则:
如果容器不存在,就创建一个,再进行操作。
如果容器里没有数据了,就立即删除,回收内存。
2.1 string(字符串)
string是Redis最简单的数据结构,一个key对应一个value。Redis的string实际上是使用数组实现的。
string基本指令有:
SET:设置一个kv对,示例:SET name lily
GET:获取一个key对应的的value,示例:GET name
MSET:设置一个或多个kv对,示例:MSET name lily age 18
MGET:获取一个或多个key对应的value,示例:MGET name age
SETNX:设置一个kv对,如果该key不存在,设置成功;如果改成存在,设置失败,示例:SETNX name lilei
INCR:如果 value 是整数,还可以进行原子加一操作,示例:INCR age
DECR:如果 value 是整数,还可以进行原子减一操作,示例:DECR age
2.2 list(列表)
Redis的list是链表实现的,所以其插入删除操作非常快,时间复杂度都是O(1),但是索引定位很慢,时间复杂度是O(n)。当list中没有元素了,该数据结构会自动删除,内存被回收。
list的基本指令有:
LPUSH:从左边插入元素,可以插入多个。示例:LPUSH students lily lilei zhangsan
LPOP:从左边删除元素,默认删除一个元素。可以指定需要删除的个数,示例:LPOP students 5
RPUSH:从右边插入元素,用法同LPUSH
RPOP:从右边删除元素,用法同LPOP
LRANGE:列出指定范围里的元素(不会删除元素),示例:LRANGE students 0 -1
LINDEX:(时间复杂度O(n))获取指定位置的元素,示例:LINDEX students 1
LTRIM:(时间复杂度O(n))删除指定范围的元素,示例:LTRIM students 1 2
list常用场景:
队列/消息队列
从list的一边push数据,从另一边pop数据,就可以作为队列或者消息队列使用。如果list里面没有数据,LPOP和RPOP不会阻塞,都会立即返回空。如果用作消息队列,消费者需要处理LPOP或RPOP立即返回空的情况,一般的做法是如果返回为空,等待一段时间,再次获取,但是等待时间不好确定,等待时间太短会拉高CPU,造成浪费;等待时间太长会降低QPS,影响性能。Redis为list设计了阻塞读指令:BLPOP、BRPOP,指令用法是BLPOP key1 [key2 ...] timeout,这里BLPOP/BRPOP里的B是blocking的意思。阻塞读在list没有数据的时候,会立即休眠,一旦有数据到了,则立刻醒过来。
栈
在list的同一边push和pop数据,就可以作为栈使用
消息队列
LPOP或者RPOP操作不会阻塞,如果list里面没有数据,LPOP和RPOP都会立即返回空,如果
2.3 hash(字典)
Redis的hash的值只能是字符串。
hash的基本指令:
HMSET:写入数据,支持批量。示例: HMSET 001 name lily age 18,其中 001 是该条hash的key,name、age 是hash里面的field。
HMGET:获取指定hash的某个field的value。示例:HMGET 001 name
HGETALL:获取指定hash的所有数据。示例:HGETALL 001
HINCRBY :如果hash内部的value是整型,可以使用HINCRBY进行原子加减操作(只需讲操作数设置为负数就是减法操作),示例:HINCRBY 001 age 1,HINCRBY 001 age -1
2.4 set(集合)
Redis的set是无序的。
set的基本指令:
SADD:加入集合。示例:SADD students lily lilei
SMEMBERS:列出集合内成员。示例:SMEMBERS students
SISMEMBER:查询某个成员是否存在。示例:SISMEMBER students lily
SREM:删除某个成员。示例:SREM students lily
SCARD:获取集合长度。示例:SCARD students
SPOP:弹出成员,可以指定需要弹出的个数(不知道默认是1)。示例:SPOP students 5
2.5 zset(有序集合)
Redis的zset是一个很有特色的数据结构,一方面它是一个set,保证了内部value的唯一性,另一方面它可以给每个value赋予一个score,代表这个value的排序权重。它的内部实现采用的是”跳跃列表(skiplist)“。
zset基本指令:
ZADD:增加成员。示例:ZADD students 95 lily 85 lilei 70 zhangsan 55 lisi
ZRANGE:正序获取指定个数的成员。示例:ZRANGE students 0 -1
ZREVRANGE:倒序获取指定个数的成员。示例:ZREVRANGE statuents 0 -1
ZRANGEBYSCORE:根据score范围获取成员,正序排列。示例:ZRANGEBYSCORE students 0 60
ZREVRANGEBYSCORE:根据score范围获取成员,倒叙排列。示例:ZREVRANGEBYSCORE students 100 0
ZREM:删除指定成员。示例:ZREM students zhangsan
ZREMRANGEBYSCORE :按score范围删除成员。示例:ZREMRANGEBYSCORE students 0 60
ZSCORE:获取成员score。示例:ZSCORE students lilei
ZCARD:获取zset长度。示例:ZCARD students
3. Redis通用指令
3.1 EXPIRE
EXPIRE指令用于设置超时,某个key超时后会自动删除。示例:EXPIRE students 10,10秒后students将被自动删除。
超时相关指令有:
TTL:查看剩余过期时间,单位是秒。示例:TTL students
PTTL:查看剩余过期时间,单位是豪秒。示例:PTTL students
3.2 KEYS
KEYS获取当前库所有的或者特定的key列表,支持通配符”*“。示例:KEYS *查询全部key。
3.3 SCAN
KEYS可以获取key列表,但是有两个明显的缺点:
没有 offset、limit 参数,一次性返回所有满足条件的key。
KEYS算法是遍历算法,复杂度是O(n),如果当前库中key很多,使用KEYS会导致Redis卡顿。
Redis提供了SCAN指令,SCAN相比KEYS有一下特点:
复杂度虽然也是O(n),但是它是通过游标分步进行的,不会阻塞线程。
提供limit参数,可以控制每次返回结果的最大条数。
提供模式匹配功能。
服务器不需要为游标保存状态,游标的唯一状态就是SCAN返回给客户端的游标整数。
返回的结果可能会有重复,需要客户端去重。
遍历的过程中如果有数据修改,改动后的数据能不能遍历到是不确定的。
单词返回的结果是空的并不意味着遍历结束,而要看返回的游标值是否为零。
SCAN用法: SCAN cursor [MATCH pattern] [COUNT count] [TYPE type],示例:scan 0 match s* count 5 type string。SCAN有四个参数,第一个是cursor整数值(相当于offset),第二个是key的正则模式,第三个是count(相当于limit),第四个是key的类型。第一次遍历时cursor值为0,然后将返回结果中的第一个整数值作为下一次遍历的cursor,一直遍历到返回的cursor值为0时结束。需要注意的是,每次SCAN返回的个数不一定是count,因为这个count不是限定返回结果的数量,而是限定服务器单次遍历的字典槽位数量,只要返回的cursor不是0,就表示遍历还没结束。
4. Redis进阶篇
4.1 持久化
Redis提供了两种持久化机制:一种是RBD(Redis Database),即快照;另一种是AOF(Append Only File)日志追加。RBD是一次全量备份,AOF日志是连续的增量备份。RBD是内存数据的二进制序列化形式,在存储上非常紧凑,而AOF日志记录的是内存数据修改的指令记录文本。AOF日志会慢慢变大,所以需要定期进行AOF重写,给AOF日志进行瘦身。
4.1.1 RBD
Redis使用操作系统的多进程COW(Copy On Write,写时复制)机制来实现快照持久化。Redis在持久化时fork一个子进程,快照持久化完全交给子进程来处理,父进程继续处理客户端请求。子进程刚刚产生时,和父进程共享内存里面的代码段和数据段,当父进程接收到客户端请求需要修改数据时,会利用COW机制将需要修改的页复制一份进行修改,不会改变原来的页,所以子进程看到的还是它自己产生的那一瞬间的数据(这就是叫这种持久化机制为”快照“的原因),可以安心的遍历数据进行持久化。
4.1.2 AOF
AOF日志存储的是Redis服务器的顺序指令序列,AOF日志只记录对内存进行修改的指令记录。Redis恢复的时候,只需要根据AOF日志”重放“就行了。
Redis在长期运行过程中,AOF日志会越变越大,所以需要对AOF日志进行瘦身。Redis提供了bgrewirteaof指令用于对AOF日志进行瘦身。其原理就是开辟一个子进程对内存进行遍历转换成一系列Redis的操作指令,序列化到一个新的AOF日志文件中。序列化完毕后再将操作期间发生的增量AOF日志追加到这个新的AOF日志文件中,追加完毕后就立即替代旧的AOF日志文件,瘦身工作就完成了。
当Redis对AOF日志文件进行写操作时,实际上是将内容写到了内核为文件描述符分配的一个内存缓存中,然后内核会异步将脏数据刷回到磁盘。Redis提供了不同的刷盘策略:
定时刷盘,时间间隔可配。(如果需要开启持久化,一般采用这种方式。)
来一个指令就刷盘一次。(会影响性能)
永不主动刷盘,让操作系统决定何时刷盘。(很不安全)
4.1.3 混合持久化
将RDB文件的内容和增量的AOF日志文件存在一起,这里的AOF日志不是全量的日志,而是自快照持久化开始到持久化结束的这段时间发生的增量AOF日志。Redis重启的时候,先加载RBD的内容,再重放增量AOF日志。