关键词
非关系型数据库
内存型数据库
常用于缓存
单线程
非阻塞的IO多路复用机制
常用的五种数据结构 String
常用命令:set,get,strlen,exists,decr,incr,expire,setex
应用场景:
普通的key/value存储
decr/incr实现计数器
mget/mset批量读写 。
实现方式:实现方式为String,被redisObject所引用;当遇到incr、decr等操作时会转成数值型进行计算,此时redisObject的encoding字段为int
基本操作 1 2 3 4 5 6 7 8 9 10 11 12 13 127.0.0.1:6379> set key value #设置 key-value 类型的值 OK 127.0.0.1:6379> get key # 根据 key 获得对应的 value "value" 127.0.0.1:6379> exists key # 判断某个 key 是否存在 (integer) 1 127.0.0.1:6379> strlen key # 返回 key 所储存的字符串值的长度。 (integer) 5 127.0.0.1:6379> del key # 删除某个 key 对应的值 (integer) 1 127.0.0.1:6379> get key (nil)
批量操作 1 2 3 4 5 127.0.0.1:6379> mset key1 value1 key2 value2 # 批量设置 key-value 类型的值 OK 127.0.0.1:6379> mget key1 key2 # 批量获取多个 key 对应的 value 1) "value1" 2) "value2"
计数器(仅限整数值) 1 2 3 4 5 6 7 8 9 10 127.0.0.1:6379> set number 1 OK 127.0.0.1:6379> incr number # 将 key 中储存的数字值增一 (integer) 2 127.0.0.1:6379> get number "2" 127.0.0.1:6379> decr number # 将 key 中储存的数字值减一 (integer) 1 127.0.0.1:6379> get number "1"
过期时间设置(默认永久) 1 2 3 4 5 6 7 127.0.0.1:6379> expire key 60 # 数据在 60s 后过期 (integer) 1 127.0.0.1:6379> setex key 60 value # 数据在 60s 后过期 相当于set+expire OK 127.0.0.1:6379> ttl key # 查看数据还有多久过期 (integer) 56
List
常用命令:rpush,lpop,lpush,rpop,lrange,llen
应用场景:
存储各种列表数据。
lpush和rpop配合实现队列
lpush和lpop配合实现栈
lrange实现分页
实现方式: 实现为双向链表 ,支持反向查找和遍历,但带来了额外的内存开销 ;Redis内部的缓冲队列用的就是该数据结构
实现队列 1 2 3 4 5 6 7 8 9 10 11 12 13 127.0.0.1:6379> rpush myList value1 # 向 list 的头部(右边)添加元素 (integer) 1 127.0.0.1:6379> rpush myList value2 value3 # 向list的头部(最右边)添加多个元素 (integer) 3 127.0.0.1:6379> lpop myList # 将 list的尾部(最左边)元素取出 "value1" 127.0.0.1:6379> lrange myList 0 1 # 查看对应下标的list列表, 0 为 start,1为 end 1) "value2" 2) "value3" 127.0.0.1:6379> lrange myList 0 -1 # 查看列表中的所有元素,-1表示倒数第一 1) "value2" 2) "value3"
实现栈 1 2 3 4 5 127.0.0.1:6379> rpush myList2 value1 value2 value3 (integer) 3 127.0.0.1:6379> rpop myList2 # 将 list的头部(最右边)元素取出 "value3"
Hash
常用命令:hset,hmset,hexists,hget,hgetall,hkeys,hvals
应用场景:存储对象数据
实现方式: 成员较少时 采用类似一维数组的方式来紧凑存储 ,此时redis对象的encoding为zipmap;成员数量较多时 采用真正的HashMap ,此时redis对象的encoding为ht
基本操作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 127.0.0.1:6379> hmset userInfoKey name "guide" description "dev" age "24" OK 127.0.0.1:6379> hexists userInfoKey name # 查看 key 对应的 value中指定的字段是否存在。 (integer) 1 127.0.0.1:6379> hget userInfoKey name # 获取存储在哈希表中指定字段的值。 "guide" 127.0.0.1:6379> hget userInfoKey age "24" 127.0.0.1:6379> hgetall userInfoKey # 获取在哈希表中指定 key 的所有字段和值 1) "name" 2) "guide" 3) "description" 4) "dev" 5) "age" 6) "24" 127.0.0.1:6379> hkeys userInfoKey # 获取 key 列表 1) "name" 2) "description" 3) "age" 127.0.0.1:6379> hvals userInfoKey # 获取 value 列表 1) "guide" 2) "dev" 3) "24" 127.0.0.1:6379> hset userInfoKey name "GuideGeGe" # 修改某个字段对应的值 127.0.0.1:6379> hget userInfoKey name "GuideGeGe"
Set
常用命令:sadd,spop,smembers,sismember,scard,sinterstore,sunion
应用场景:
类似List,不同之处是可以自动去重
交集、并集、差集,可以实现共同好友、共同关注等
实现方式:内部其实是value永远为null的HashMap ,通过计算hash实现快速去重
基本操作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 127.0.0.1:6379> sadd mySet value1 value2 # 添加元素进去 (integer) 2 127.0.0.1:6379> sadd mySet value1 # 不允许有重复元素 (integer) 0 127.0.0.1:6379> smembers mySet # 查看 set 中所有的元素 1) "value1" 2) "value2" 127.0.0.1:6379> scard mySet # 查看 set 的长度 (integer) 2 127.0.0.1:6379> sismember mySet value1 # 检查某个元素是否存在set 中,只能接收单个元素 (integer) 1 127.0.0.1:6379> sadd mySet2 value2 value3 (integer) 2 127.0.0.1:6379> sinterstore mySet3 mySet mySet2 # 获取 mySet 和 mySet2 的交集并存放在 mySet3 中 (integer) 1 127.0.0.1:6379> smembers mySet3 1) "value2"
Sorted Set
常用命令:zadd,zcard,zscore,zrange,zrevrange,zrem
应用场景:
与Set类似;Sorted Set可以通过额外的参数score 来为成员排序
排行榜
实现方式:Sorted Set的内部使用HashMap 和**跳跃表(SkipList)**来保证数据的存储和有序,HashMap里放的是成员到score的映射,而跳跃表里存放的是所有的成员,排序依据是HashMap里存的score,使用跳跃表的结构可以获得比较高的查找效率,并且在实现上比较简单。
基本操作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 127.0.0.1:6379> zadd myZset 3.0 value1 # 添加元素到 sorted set 中 3.0 为权重 (integer) 1 127.0.0.1:6379> zadd myZset 2.0 value2 1.0 value3 # 一次添加多个元素 (integer) 2 127.0.0.1:6379> zcard myZset # 查看 sorted set 中的元素数量 (integer) 3 127.0.0.1:6379> zscore myZset value1 # 查看某个 value 的权重 "3" 127.0.0.1:6379> zrange myZset 0 -1 # 顺序输出某个范围区间的元素,0 -1 表示输出所有元素 1) "value3" 2) "value2" 3) "value1" 127.0.0.1:6379> zrange myZset 0 1 # 顺序输出某个范围区间的元素,0 为 start 1 为 stop 1) "value3" 2) "value2" 127.0.0.1:6379> zrevrange myZset 0 1 # 逆序输出某个范围区间的元素,0 为 start 1 为 stop 1) "value1" 2) "value2"
bitmap
常用命令:setbit 、getbit 、bitcount、bitop
应用场景:
适合需要保存状态信息(比如是否签到、是否登录…)并需要进一步对这些信息进行分析的场景。
用户签到情况
活跃用户情况
用户行为统计(比如是否点赞过某个视频)。
实现方式:bitmap 存储的是连续的二进制数字(0 和 1),通过 bitmap, 只需要一个 bit 位来表示某个元素对应的值或者状态,key 就是对应元素本身 。我们知道 8 个 bit 可以组成一个 byte,所以 bitmap 本身会极大的节省储存空间。
基本操作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 # SETBIT 会返回之前位的值(默认是 0)这里会生成 7 个位 127.0.0.1:6379> setbit mykey 7 1 (integer) 0 127.0.0.1:6379> setbit mykey 7 0 (integer) 1 127.0.0.1:6379> getbit mykey 7 (integer) 0 127.0.0.1:6379> setbit mykey 6 1 (integer) 0 127.0.0.1:6379> setbit mykey 8 1 (integer) 0 # 通过 bitcount 统计被被设置为 1 的位的数量。 127.0.0.1:6379> bitcount mykey (integer) 2
用户行为分析 1 2 # 记录你喜欢过 001 号小姐姐 127.0.0.1:6379> setbit beauty_girl_001 uid 1
统计用户在线状态 只需要一个 key,然后用户 ID 为 offset,如果在线就设置为 1,不在线就设置为 0。
统计活跃用户 命令 1 2 3 # 对一个或多个保存二进制位的字符串 key 进行位元操作,并将结果保存到 destkey 上。 # BITOP 命令支持 AND 、 OR 、 NOT 、 XOR 这四种操作中的任意一种参数 BITOP operation destkey key [key ...]
初始化数据 1 2 3 4 5 6 127.0.0.1:6379> setbit 20210308 1 1 (integer) 0 127.0.0.1:6379> setbit 20210308 2 1 (integer) 0 127.0.0.1:6379> setbit 20210309 1 1 (integer) 0
统计20210308~20210309 总活跃用户数: 1 1 2 3 4 127.0.0.1:6379> bitop and desk1 20210308 20210309 (integer) 1 127.0.0.1:6379> bitcount desk1 (integer) 1
统计 20210308~20210309 在线活跃用户数: 2 1 2 3 4 127.0.0.1:6379> bitop or desk2 20210308 20210309 (integer) 1 127.0.0.1:6379> bitcount desk2 (integer) 2
内存淘汰策略
noeviction:返回错误,不会删除任何键值
allkeys-lru:使用LRU算法删除最近最少使用的键值
volatile-lru:使用LRU算法从设置了过期时间的键集合中删除最近最少使用的键值
allkeys-random:从所有key随机删除
volatile-random:从设置了过期时间的键的集合中随机删除
volatile-ttl:从设置了过期时间的键中删除剩余时间最短的键
volatile-lfu:从配置了过期时间的键中删除使用频率最少的键
allkeys-lfu:从所有键中删除使用频率最少的键
数据持久化 RDB快照 Redis支持将当前数据的快照存成一个数据文件的持久化机制,即RDB快照。但是一个持续写入的数据库如何生成快照呢?Redis借助了fork命令的copy on write机制。在生成快照时,将当前进程fork出一个子进程,然后在子进程中循环所有的数据,将数据写成为RDB文件。我们可以通过Redis的save指令来配置RDB快照生成的时机,比如配置10分钟就生成快照,也可以配置有1000次写入就生成快照,也可以多个规则一起实施。这些规则的定义就在Redis的配置文件中,你也可以通过Redis的CONFIG SET命令在Redis运行时设置规则,不需要重启Redis。
Redis的RDB文件不会坏掉,因为其写操作是在一个新进程中进行的,当生成一个新的RDB文件时,Redis生成的子进程会先将数据写到一个临时文件中,然后通过原子性rename系统调用将临时文件重命名为RDB文件,这样在任何时候出现故障,Redis的RDB文件都总是可用的。同时,Redis的RDB文件也是Redis主从同步内部实现中的一环。RDB有他的不足,就是一旦数据库出现问题,那么我们的RDB文件中保存的数据并不是全新的,从上次RDB文件生成到Redis停机这段时间的数据全部丢掉了。在某些业务下,这是可以忍受的。
AOF日志 AOF日志的全称是append only file,它是一个追加写入的日志文件。与一般数据库的binlog不同的是,AOF文件是可识别的纯文本,它的内容就是一个个的Redis标准命令。只有那些会导致数据发生修改的命令才会追加到AOF文件。每一条修改数据的命令都生成一条日志,AOF文件会越来越大,所以Redis又提供了一个功能,叫做AOF rewrite。其功能就是重新生成一份AOF文件,新的AOF文件中一条记录的操作只会有一次,而不像一份老文件那样,可能记录了对同一个值的多次操作。其生成过程和RDB类似,也是fork一个进程,直接遍历数据,写入新的AOF临时文件。在写入新文件的过程中,所有的写操作日志还是会写到原来老的AOF文件中,同时还会记录在内存缓冲区中。当重完操作完成后,会将所有缓冲区中的日志一次性写入到临时文件中。然后调用原子性的rename命令用新的AOF文件取代老的AOF文件。
AOF是一个写文件操作,其目的是将操作日志写到磁盘上,所以它也同样会遇到我们上面说的写操作的流程。在Redis中对AOF调用write写入后,通过appendfsync选项来控制调用fsync将其写到磁盘上的时间,下面appendfsync的三个设置项,安全强度逐渐变强。
appendfsync no 当设置appendfsync为no的时候,Redis不会主动调用fsync去将AOF日志内容同步到磁盘,所以这一切就完全依赖于操作系统的调试了。对大多数Linux操作系统,是每30秒进行一次fsync,将缓冲区中的数据写到磁盘上。
appendfsync everysec 当设置appendfsync为everysec的时候,Redis会默认每隔一秒进行一次fsync调用,将缓冲区中的数据写到磁盘。但是当这一次的fsync调用时长超过1秒时。Redis会采取延迟fsync的策略,再等一秒钟。也就是在两秒后再进行fsync,这一次的fsync就不管会执行多长时间都会进行。这时候由于在fsync时文件描述符会被阻塞,所以当前的写操作就会阻塞。所以结论就是,在绝大多数情况下,Redis会每隔一秒进行一次fsync。在最坏的情况下,两秒钟会进行一次fsync操作。这一操作在大多数数据库系统中被称为group commit,就是组合多次写操作的数据,一次性将日志写到磁盘。
appednfsync always 当设置appendfsync为always时,每一次写操作都会调用一次fsync,这时数据是最安全的,当然,由于每次都会执行fsync,所以其性能也会受到影响。
对于一般性的业务需求,建议使用RDB的方式进行持久化,原因是RDB的开销并相比AOF日志要低很多,对于那些无法忍数据丢失的应用,建议使用AOF日志。