前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Redis缓存基础

Redis缓存基础

原创
作者头像
羽毛球初学者
修改2024-10-13 09:54:30
1120
修改2024-10-13 09:54:30
举报
文章被收录于专栏:JAVA基础知识

优点

  • 开源的、基于内存的数据结构存储系统,可以⽤作数据库,缓存,消息中间件等;
  • 性能⾼:单线程,读速度是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多亿)。

redis 中 list 的数据结构
redis 中 list 的数据结构

利⽤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,过期时间设置短⼀些;
  • 使⽤布隆过滤器拦截。
代码语言:txt
复制
布隆过滤器简介
⾸先分配⼀块内存空间做 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 命令提交事务时,执⾏到错误命令时就会报错,此时由于前⾯正确的命令已经执⾏了,⽆法放弃,所以就出现⼀个事务中正确的命令正常执⾏了,错误的命令⽆法执⾏,从⽽⽆法保证原⼦性。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 优点
  • 支持的数据结构
    • string
      • hash
        • list
          • set
            • zset
            • 数据一致性问题
              • 问题分析
                • 先更新缓存,再更新数据库
                • 先更新数据库,再更新缓存
                • 先删除缓存,再更新数据库
                • 先更新数据库,再删除缓存
              • 解决方案
                • 延时双删
                • 消息队列异步处理
            • 缓存失效问题
              • 缓存穿透
                • 缓存击穿
                  • 缓存雪崩
                  • 缓存过期策略
                  • 缓存淘汰机制
                  • Redis 持久化
                    • RDB
                      • AOF
                        • 混合持久化
                        • Redis 事务
                          • 原⼦性分析
                          相关产品与服务
                          云数据库 Redis
                          腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
                          领券
                          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档