admin管理员组文章数量:1487745
Redis缓存基础
优点
- 开源的、基于内存的数据结构存储系统,可以⽤作数据库,缓存,消息中间件等;
- 性能⾼:单线程,读速度是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 命令提交事务时,执⾏到错误命令时就会报错,此时由于前⾯正确的命令已经执⾏了,⽆法放弃,所以就出现⼀个事务中正确的命令正常执⾏了,错误的命令⽆法执⾏,从⽽⽆法保证原⼦性。
本文标签: Redis缓存基础
版权声明:本文标题:Redis缓存基础 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/shuma/1754876141a3180706.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论