优点 开源的、基于内存的数据结构存储系统,可以⽤作数据库,缓存,消息中间件等; 性能⾼:单线程,读速度是110000次/s,写速度是81000次/s。数据存储在内存中,访问速度快; 数据类型丰富:⽀持字符串、列表、集合、有序集合、哈希表等数据类型; 操作原⼦性:Redis的所有操作都是原⼦性的,同时也⽀持事务; 可持久化:可以将内存中的数据保存到硬盘中,以防⽌数据丢失。 支持的数据结构 string 是⼆进制安全的,可以包含任何数据,⽐如 jpg 图⽚或者序列化的对象,最⼤能存储 512 MB。
常用的方法有以下几种:
常⽤⽅法:
set key value set key value ex 120(设置值的时候,同时设置过期时间120s) get key del key setnx key value(当 key 不存在时,为 key 设置的值。 成功返回 1 ,失败返回 0 ,常⽤于分布式锁的实现) hash 是⼀个 string 类型的 field 和 value 的映射表,特别适合⽤于存储对象。
常⽤⽅法:
hset key field value 向指定的键中添加⼀对 hash 类型的字段名和值。 hget key field 取出指定键的指定字段的值。 hmset key field1 value1 field2 value2... 向某个键中设置多个字段名和值。 hmget key field1 field2... 从指定的键中得到多个字段的值。 hmgetall key ⼀次性取出所有key的值。 hdel key field1 field2... 删除⼀个键中的⼀个或多个字段,返回删除成功的个数。 list 是按照插⼊顺序排序的 string 字符串链表,可以添加⼀个元素到列表的头部(左边)或者尾部(右边),是⼀个有序集合。底层使⽤双向链表实现,两端添加元素的时间复杂度为 O(1)。插⼊元素时,如果 key 不存在,redis 会为该 key 创建⼀个新的链表,如果链表中所有的元素都被移除,该 key 也会从 redis 中移除。列表最多可存储 2的32⽅ - 1 元素 (4294967295, 每个列表可存储40多亿)。
利⽤push和pop操作,可以⽅便元素放⼊和取出,在队列、任务池中⾮常⽅便。同时由于查询两端数据性能⾼,也适合⼀些需要获取最新数据的场景,⽐如最近n条评论。
常⽤⽅法:
lpush key value1 value2... 在列表的左边向指定的键中添加列表元素,如果该键并不存在,Redis将为该键创建⼀个新的链表,如果这个键已经存在,则是向list添加元素。 rpush key value1 value2... 从列表右边添加。 lpop key 从指定键中的左边弹出⼀个元素,列表中的元素同时被删除。 rpop 从指定键的右边弹出⼀个元素,列表中的元素同时被删除。 lrange key start end 从指定键的列表中取出指定范围的元素列表,区间以偏移量 start 和 end 指定。 其中 0 表示列表的第⼀个元素, 1 表示列表的第⼆个元素, 以此类推。 也可以使⽤负数下标,以 -1 表示列表的最后⼀个元素, -2 表示列表的倒数第⼆个元素。如果要取整个列表,开始是0,结束是-1 。 llen key 得到指定列表的⻓度。 set set是⽆序string类型集合,通过哈希表实现的,添加、删除、查找的复杂度都是 O(1),不允许数据重复,如果添加的数据在 set 中已经存在,将只保留⼀份,集合最多可存储 2的32⽅ - 1 元素 (4294967295, 每个列表可存储40多亿)。同时 set 提供了多个 set 之间的聚合运算,如求交集、并集、补集,可⽤于求如共同好友列表等场景。
常⽤⽅法:
sadd key v1 v2 向set集合中添加1个或多个元素,返回添加成功的元素个数,如果返回0表示添加失败。 smembers key 查询指定的集合中所有的元素。 scard key 查询key的元素个数。 sismember key v 判断指定的元素是否在某个集合中,如果存在返回1,否则返回0。 srem key v1 v2 删除指定的⼀个或多个元素,返回1表示删除成功,0表示删除失败。 sunion key1 key2 返回给定集合的并集,不存在的集合 key 被视为空集。 sinter key1 key2 返回给定集合的交集。 sdiff key1 key2 返回给定集合的差集。 zset zset 和 set ⼀样也是string类型元素的集合,不允许重复的成员,不同的是每个元素都会关联⼀个double类型的分数,redis正是通过分数来为集合中的成员进⾏从⼩到⼤的排序。
zset的成员是唯⼀的,但分数(score)却可以重复。可⽤于根据好友的亲密度排序显示、好友列表或直播间⾥粉丝打赏⾦额排序等场景。
常⽤命令:
zadd key s1 v1 s2 v2 向有序集合添加⼀个或多个成员,s为分数,v为值。 zrange key start end 通过索引区间返回有序集合中指定区间内的成员。 zrem key v1 v2 移除有序集合中的⼀个或多个成员。 zrank key v 返回有序集合中指定成员的索引位置。 zcard key 获取有序集合的成员数量。 zscore key v 得到指定成员的分数。 数据一致性问题 问题分析 先更新缓存,再更新数据库 若缓存更新成功,数据库更新失败,此时缓存中的数据是脏数据
先更新数据库,再更新缓存 若数据库更新成功,缓存更新失败,则在该缓存失效前,⼀直都访问的脏数据。
先删除缓存,再更新数据库 这种情况在没有⾼并发的情况下,是可能保持数据⼀致性的。但如果是处于读写并发的情况下,还是会出现数据不⼀致的情况:⽤户A读取,B更新,B先删缓存,此时A读缓存时发现不存在,去访问数据库,成功拿到旧值,随后B成功更新数据库。这之后在缓存失效的这段时间内,该缓存⼀直是错误的脏数据。
先更新数据库,再删除缓存 此时更新数据库成功了,⽽删除缓存失败了,那么数据库中就会是新数据,⽽缓存中是旧数据,数据就出现了不⼀致情况。
解决方案 延时双删 先清除缓存,再执⾏更新,最后延迟N秒再执⾏缓存清除。这种⽅式会缓解先删缓存后更新数据库这种⽅式出现不⼀致的情况,但还是避免不了。
消息队列异步处理 使⽤异步⽅式进⾏重试,因为消息队列可以保证消息的可靠性,消息不会丢失,也可以保证正确消费,所以可以保证数据的最终⼀致性。 该方案的优点有:
可以⼤幅减少接⼝的延迟返回的问题; MQ本身有重试机制,⽆需⼈⼯去写重试代码; 解耦,把数据库和缓存的操作完全分离,互不⼲扰。 缓存失效问题 缓存穿透 缓存穿透是指缓存和数据库中都没有的数据,⽤户还是源源不断的发起请求,导致每次请求都会到数据库,从⽽压垮数据库。 解决⽅法:
⽤户层拦截:⽤户发过来的请求,根据请求参数进⾏校验,对于明显错误的参数,直接拦截返回; 不存在的数据设置为null,过期时间设置短⼀些; 使⽤布隆过滤器拦截。 布隆过滤器简介
⾸先分配⼀块内存空间做 bit 数组,数组的 bit 位初始值全部设为 0。
加⼊元素时,采⽤ k 个相互独⽴的 Hash 函数计算,然后将元素 Hash 映射的 K 个位置全部设置为 1。
在检测 key 是否存在,仍然⽤这 k 个 Hash 函数计算出 k 个位置,如果位置全部为 1,则表明 key 存在,否则不存在。
如果布隆过滤器判断某个数据存在时,它可能不存在;但是当判定某个数据不存在时,它⼀定不存在。
注意布隆过滤器可以插⼊元素,但不可以删除已有元素。
因为可能不同key经过多次hash后的值是是⼀样的,如果去把这些位置值设为0,则可能影响到其他key。
缓存击穿 缓存击穿是指当 Redis 中⼀个热点 key 在失效的同时,⼤量的请求过来,从⽽会全部到达数据库,压垮数据库。在实际项目中,可采用以下几种方法进行避免:
设置热点数据永不过期(⽐较粗暴,慎⽤); 在热点数据过期前进⾏更新; 访问缓存时,⽤互斥锁进⾏控制。(当获取的 value 值为空时,先锁上,然后从数据库加载,加载完毕,释放锁。若其他线程也在请求该key时,发现获取锁失败,则睡眠⼀段时间后重试。) 缓存雪崩 缓存雪崩是指当 Redis 中缓存的数据⼤⾯积同时失效,或者 Redis 宕机,从⽽会导致⼤量请求直接到数据库,压垮数据库。 可以采用以下几种方法进行避免:
设置过期时间时随机增加⼀定时间,或统⼀规划有效期,使得过期时间均匀分布; 数据预热,对于即将来临的⼤量请求,提前将数据提前缓存在Redis中,并设置不同的过期时间; 保证 redis 服务⾼可⽤,采⽤哨兵或集群模式进⾏部署。 缓存过期策略 Redis缓存过期策略主要有两种:定时删除和惰性删除。
定时删除:Redis 定时去检查是否有过期的键,如果有,则删除。这种策略可以保证过期的键⽴即被删除,但是会消耗更多的 CPU 资源。 惰性删除:Redis 不主动删除过期的键,直到该键被访问时才去检查是否过期,如果已经过期,则删除。这种策略可以节省 CPU 资源,但可能会占⽤更多的内存。 在实际应⽤中,Redis 会结合两种策略来使⽤。当键过期时,Redis 会在键被访问时检查是否过期,如果已经过期,则删除。如果键没有被访问,就可能在⼀段时间内保留在内存中,直到下次访问或者被定时任务发现并删除(注意定时任务不会去检查所有键是否过期,而是抽查)。
缓存淘汰机制 当内存不够了,那么 redis 就需要进行一部分缓存淘汰了。缓存淘汰有几下几种方式:
noeviction:不进⾏淘汰的,当内存不⾜以容纳新写⼊数据时,新写⼊操作会报错。 allkeys-lru: 当内存不⾜以容纳新写⼊数据时,从所有键值对中移除最近最少使⽤的键。 allkeys-lfu:lfu是在Redis 4.0 时引⼊,当内存不⾜以容纳新写⼊数据时,它会从所有键值对中移除最近使⽤频率最⼩的键。 allkeys-randoms:当内存不⾜以容纳新写⼊数据时,从所有键值对选择并随机移除。 volatile-lru:当内存不⾜以容纳新写⼊数据时,会在设置了过期时间的键中,移除最近最少使⽤的键。 volatile-lfu: lfu是在Redis 4.0 时引⼊,当内存不⾜以容纳新写⼊数据时,它会在设置了过期时间的键中移除最近使⽤频率最⼩的键。 volatile-random: 在设置了过期时间的键值对中,进⾏随机移除。 volatile-ttl:是当内存不⾜以容纳新写⼊数据时,会在设置了过期时间的键空间中,移除即将过期的键。 Redis 持久化 RDB 通过快照(内存中数据在某⼀时刻的状态记录)的⽅式实现持久化,根据快照的触发条件,将内存的数据快照写⼊磁盘,以⼆进制的压缩⽂件进⾏存储。
优点:
⼤规模的数据恢复,并且对数据恢复的完整性要求不⾼,使⽤ RDB ⽐ AOF 更⾼效; 以⼆进制压缩⽂件的形式存储,占⽤内存更⼩; redis 使⽤ bgsave 命令进⾏持久化,基本不会影响主进程,保证了 redis 的⾼性能。 缺点:
在 fork 的时候,内存中的数据会被克隆⼀份,⼤致2倍的膨胀; 虽然 Redis 在 fork 的时候使⽤了写时拷⻉技术,但是如果数据庞⼤时还是⽐较消耗性能; 在⼀定间隔时间做⼀次备份,所以如果 redis 意外宕机的话,就会丢失最后⼀次快照后所有修改。 AOF 以独⽴⽇志的⽅式记录每次写的命令,重启时重新执⾏AOF⽂件中的命令恢复数据。在AOF文件过大时,redis 可以自动地在后台对AOF进行重写,将其中指令进⾏压缩。(如果有对于某个key多次的变更指令,则仅保留最新的数据指令)。重写流程:
当手动触发或自动触发时,判断是否当前有 bgfsave 或 bgrewriteaof 在运⾏,如果有,则等待该命令结束后再继续执⾏; 主进程 fork 出⼦进程执⾏重写操作; ⼦进程遍历 redis 内存中的数据到临时⽂件,客户端的写请求同时写⼊aof_buf 缓冲区和aof_rewrite_buf重写缓冲区保证原AOF⽂件完整性以及新AOF⽂件⽣成期间的新的数据修改动作不会丢失; ⼦进程写完新的AOF⽂件后,向主进程发送信号,⽗进程更新统计信息; 主进程把aof_rewrite_buf中的数据写⼊到新的AOF⽂件; 使⽤新的AOF⽂件覆盖旧的AOF⽂件,完成AOF重写。 优点:
备份机制更稳健,丢失数据概率更低 可读的⽇志⽂本,通过操作AOF⽂件,可以处理误操作 缺点:
⽐RDB占⽤更多的磁盘空间 恢复备份速度要慢 每次读写都同步的话,有⼀定的性能压⼒ 混合持久化 ⽣产环境中⼀般采⽤两种持久化机制混合使⽤。将内存中数据快照存储在AOF⽂件中(模拟RDB),后续再以AOF追加⽅式。
优点:
结合了 RDB 和 AOF 的优点,可以更快的启动,同时也降低了数据丢失的⻛险 缺点:
AOF ⽂件中添加了 RDB 格式的内容,可读性降低 如果开启混合持久化,那么⽀持次混合持久化 AOF ⽂件,不能⽤在redis 4.0 之前的版本 Redis 事务 Redis 通过 MULTI 和 EXEC 命令执⾏事务操作,在执⾏ EXEC提交事务之前,所有的命令都不会执⾏,会被暂存到队列中,当执⾏ EXEC 命令提交事务之后,才会从队列中⼀个个取出来执⾏。
Redis 不⽀持事务的回滚,但是允许在执⾏ EXEC 命令提交事务之前通过 DISCARD 命令放弃事务的执⾏,本质上这个命令就是把队列中等待执⾏的命令清空。
Redis 的事务操作主要有以下几种:
MULTI 命令:开启事务 EXEC 命令:提交事务 DISCARD 命令:放弃事务的执⾏ WATCH 命令:监视任意数量的键是否被其他客户端修改 原⼦性分析 Redis 事务对于原⼦性的保证需要分情况,不能⼀概⽽论,需要具体分析。
发⽣语法错误也能保证事务的原⼦性:语法错误指的是在 Redis 通过 MULTI 命令开启事务之后,提交到队列中的命令存在语法错误,那么 Redis 会⽴⻢返回错误并放弃事务的执⾏,即使在之前有语法正确的命令,也会放弃执⾏。这就保证了事务的原⼦性。 发⽣运⾏错误⽆法保证事务的原⼦性:各个命令都加⼊到队列中等待执⾏,当 Redis 通过 EXEC 命令提交事务时,执⾏到错误命令时就会报错,此时由于前⾯正确的命令已经执⾏了,⽆法放弃,所以就出现⼀个事务中正确的命令正常执⾏了,错误的命令⽆法执⾏,从⽽⽆法保证原⼦性。