当前位置:   article > 正文

Java——redis篇_java redis

java redis

目录

0、Redis常见数据类型及其应用场景

0.1、Redis有什么优点和缺点?

1、什么是缓存穿透 ? 怎么解决 ?

       解决方案:

2、什么是缓存击穿? 怎么解决 ?

        解决方案:

3、什么是缓存雪崩? 怎么解决 ?

       解决方案:

4、redis做为缓存,mysql的数据如何与redis进行同步呢?

排他锁是如何保证读写、读读互斥的呢?

延时双删

5、redis做为缓存,数据的持久化是怎么做的?

6、Redis的数据过期策略有哪些 ?

7、redis的数据淘汰策略

Redis支持8种不同策略来选择要删除的key:

LRU和LFU两种策略:

数据库有1000万数据 ,Redis只能缓存20w数据, 如何保证Redis中数据都是热点数据 ? 

Redis的内存用完了会发生什么?

8、Redis分布式锁的实现

Redisson实现分布式锁如何合理的控制锁的有效时长?

Redisson的这个锁,可以重入吗?

Redisson锁能解决主从数据一致的问题吗?

9、Redis的集群方案

主从复制

哨兵模式

10、怎么保证Redis的高并发高可用?

11、redis的分片集群

分片集群的作用:

Redis分片集群中数据是怎么存储和读取的?

12、Redis是单线程的,但是为什么还那么快?

13、I/O多路复用模型

​​​​14、什么是大Key问题?如何解决?


0、Redis常见数据类型及其应用场景

        Redis 中比较常见的数据类型有:String(字符串)、List(列表)、Set(集合)、Hash(散列)、Zset(有序集合)。  

  • String:一般用于存储一些常规数据,如Session、Token、序列化后的对象,还可以用来实现分布式锁
  • Hash:适合用于存储对象信息
  • List:底层为一个双向链表,支持从左右两边获取元素以及弹出元素,可以使用range命令查看范围内的元素。 List可用作消息队列,但一般不推荐使用。
  • Set:无序集合,集合内元素没有顺序且唯一。 基于set可以轻易的实现交集、并集、差集的操作,因此Set常用于实现共同关注、共同好友等功能
  • Zset:类似于Set,只是每个元素加了一个score字段,元素会根据score的大小进行排序,并且可以根据score值进行范围查询。常用于实现排行榜等功能。

        还有特殊的数据类型:

  • BitMap :底层是一个由0、1组成的二进制串,常用命令:setbit key offset value;getbit key offset; 通常用于用户签到等场景:key为[当前年份:用户id],offset为[当前天数]。

  • HyperLogLog 能够使用极少的内存来统计巨量的数据,如统计百万级访问量网页的用户点击次数。在 Redis 中实现的 HyperLogLog,只需要12K内存就能统计2^64个数据。 但是计数存在一定的误差,标准误差为 0.81% 。

  • Geospatial:主要用于存储地理位置信息,通过 GEO 我们可以轻松实现两个位置距离的计算、获取指定位置附近的元素等功能。 

0.1、Redis有什么优点和缺点?

优点:

  • 速度快:Redis是基于内存的,并且是单线程的,还采用了IO多路复用模型。
  • 支持丰富的数据类型。
  • 提供了持久化策略:RDB和AOF等持久化策略。
  • 提供高并发和高可用的Redis集群

缺点:

  • Redis是内存数据库,存储的数据量有限,需要制定数据过期策略定期删除数据。

1、什么是缓存穿透 ? 怎么解决 ?

        缓存穿透是指客户端请求的数据在缓存中和数据库中都不存在,这样缓存就形同虚设(只有数据库查到了,才会让redis缓存,但现在的问题是查不到),会频繁的去访问数据库。

       解决方案:

  • 缓存空对象:如果该数据在缓存和数据库中都不存在,就缓存一个空值到redis中,并且超时时间设置得短一点,如2分钟,以防占用太多redis空间。
  • 布隆过滤:布隆过滤器是由初始值都为 0 的位图数组和若干个个哈希函数组成的数据结构,通常用于从海量数据中判断某数字是否存在,应用场景如缓存穿透、垃圾邮件过滤。布隆过滤器会通过若干个哈希函数计算数据的哈希值并取模,在位图中对应位置设为1。客户端的所有请求都会先通过该过滤器进行过滤,由于哈希的性质,若该过滤器都查不到数据,则直接返回错误信息;若查到了则放行,但也不一定存在该数据(存在哈希冲突)。

2、什么是缓存击穿? 怎么解决 ?

        缓存击穿也叫热点Key问题,一个热点的Key,有大并发集中对其进行访问,突然间这个Key失效了,导致大并发全部打在数据库上,导致数据库压力剧增。

        解决方案:

  • 互斥锁:只有拿到锁才可以查询数据库,降低了在同一时刻打在数据库上的请求。
    • 优点:强一致性。
    • 缺点:性能差。
  • 逻辑过期:①在缓存中多设置一个逻辑过期字段,而不真正设置过期时间。②查询时,通过过期字段来判断当前key是否过期。③若过期,则另外开一个线程去数据库查询并同步缓存数据,当前线程则返回旧数据。
    • 优点:性能高,具有高可用性。
    • 缺点:无法保证数据绝对一致。

3、什么是缓存雪崩? 怎么解决 ?

        缓存雪崩是指在同一时间段,大量缓存的key同时失效,或者Redis服务宕机,导致大量请求到达数据库。

       解决方案:

  • 给不同的Key的TTL添加随机值,让其在不同时间段分批失效。
  • 利用Redis集群提高服务的可用性。(哨兵模式,集群模式)
  • 给缓存业务添加降级限流策略。(保底策略)
  • 给业务添加多级缓存。(可以理解为穿了好几件防弹衣)。

4、redis做为缓存,mysql的数据如何与redis进行同步呢?

        业务中有优惠券秒杀功能要求实时性比较高,因此采用读写锁保证redis和mysql的强一致性。 主要采用redisson实现的读写锁,读的时候添加共享锁(读锁),保证读读不互斥,读写互斥;更新数据的时候添加排他锁(写锁),读读、读写都互斥。 这样可以防止写数据的时候其他线程读数据,避免了脏数据

缓存的更新策略:当用户修改数据时,应该先删缓存还是先修改数据库?

  • 先删缓存:修改数据库的途中,有别的线程来访问该数据就会把脏数据读入缓存。
  • 先更新数据库:删完缓存还没提交事务的这段时间空隙,有别的线程来访问数据也会访问到脏数据并读入内存,但是发生的机率较小。

排他锁是如何保证读写、读读互斥的呢?

        排他锁底层使用的是setnx,保证了同时只能有一个线程操作锁住的方法。

延时双删

        延时双删也是分布式系统中保持redis和mysql一致性的常用策略,但不具有强一致性。

  • 延时双删:当前为写操作时,先删除redis中的缓存,再更新数据库,短暂延时之后再次删除redis中的缓存。
    • 为什么要两次删除redis缓存?
      • ​​​​​​防止​数据库还没更新完,有别的线程读取了数据库的脏数据,并更新redis缓存。
    • 为什么要延时删除?
      • 数据库一般是主从模式,需要给主节点一些时间同步数据到从节点中。

5、redis做为缓存,数据的持久化是怎么做的?

        Redis是内存数据库,宕机后数据会消失,需要提供持久化策略Redis中提供了两种数据持久化的方式:RDB 和 AOF

  • RDB(Redis DataBase):是一个快照文件,它把redis内存存储的数据写到磁盘上,当
    redis 宕机恢复数据的时候,方便从 RDB 的快照文件中恢复数据。
    • 优点:RDB是二进制压缩文件,占用空间小,便于传输,恢复数据速度较快。
    • 缺点:两次RDB期间有空档期,此期间若Redis宕机了可能会造成数据的丢失。
    • 创建RDB文件的两个命令:
      • SAVE:会阻塞Redis服务器进程,直到RDB文件创建完毕为止。
      • BGSAVE:会派生出一个子进程负责创建RDB文件,父进程继续处理命令请求。
  • AOF(append only file):当redis操作写命令的时候,都会存储这个文件中,当redis实例宕机恢复数据的时候,会从这个文件中再次执行一遍命令来恢复数据。(AOF会记录过程,RDB只管结果
    • 优点:数据的完整性较高。
    • 缺点:文件较大,恢复速度较慢。
  • RDB-AOF混合持久化:该模式会将生成相应的RDB数据,写入AOF文件中,重写后的新 AOF 文件前半段是 RDB 格式的全量数据,后半段是 AOF 格式的增量数据。这样用户可以同时获得RDB持久化和AOF持久化的优点。

6、Redis的数据过期策略有哪些 ?

        Redis的过期删除策略:惰性删除 + 定期删除两种策略进行配合使用。

  • 惰性删除:设置该key过期时间后,不去管它,当需要该key时,再检查其是否过期,如果过期,我们就删掉它,反之返回该key。
    • 优点:对CPU友好,只在使用该key时才进行过期检查,对于很多用不到的key不用浪费时间进行过期检查。
    • 缺点:对内存不友好,过期的key将一直存在于内存中不会释放。
  • 定期删除:每隔一段时间,我们就对一些key进行检查,删除里面过期的key。
    • 优点:可以通过限制删除操作执行的时长来减少对 CPU性能的影响。
    • 缺点:这个操作时长难以把控。

7、redis的数据淘汰策略

Redis支持8种不同策略来选择要删除的key:

  • noeviction: 不淘汰任何key,但是内存满时不允许写入新数据,默认就是这种策略。
  • volatile-ttl: 对设置了TTL的key,比较key的剩余TTL值,TTL越小越先被淘汰。
  • allkeys-random:对全体key ,随机进行淘汰。
  • volatile-random:对设置了TTL的key ,随机进行淘汰。
  • allkeys-lru: 对全体key,基于LRU算法进行淘汰。
  • volatile-lru: 对设置了TTL的key,基于LRU算法进行淘汰。
  • allkeys-lfu: 对全体key,基于LFU算法进行淘汰。
  • volatile-lfu: 对设置了TTL的key,基于LFU算法进行淘汰。

LRU和LFU两种策略:

  • LRU(Least Recently Used):最近最少使用。用当前时间减去最后一次访问时间,这个值越大则淘汰优先级越高。
  • LFU(Least Frequently Used):最少频率使用。会统计每个key的访问频率,值越小淘汰优先级越高。

数据库有1000万数据 ,Redis只能缓存20w数据, 如何保证Redis中数据都是热点数据 ? 

        使用allkeys-lru(最近最少使用)淘汰策略,留下来的都是经常访问的热点数据。

Redis的内存用完了会发生什么?

        看数据淘汰策略是什么,如果是默认的配置( noeviction ),会直接报错。

8、Redis分布式锁的实现

        项目中的优惠券秒杀抢单功能存在超卖问题,可以使用Synchronized锁解决。但如果是在集群模式下, 多台服务器会对应多个jvm, synchronized锁可以锁住单台服务器的多线程,多台服务器就锁不住了,此时需要有一个多服务器共享的锁监视器,即分布式锁

        Redis实现分布式锁主要利用Redis的setnx命令(SET if not exists) ,该命令需要设置锁的过期时间,以防止服务宕机从而导致锁永远无法释放的问题。这个过期时间设置长了会影响性能,设置短了又可能会提前释放锁导致线程安全问题,这就需要合理的控制锁的有效时长

Redisson实现分布式锁如何合理的控制锁的有效时长?

        于是我们使用采用redisson实现的分布式锁,底层是setnxlua脚本(保证原子性)。在redisson的分布式锁中,提供了一个WatchDog(看门狗)机制一个线程获取锁成功以后,会定期给锁续期(默认每10s续期一次)。

Redisson的这个锁,可以重入吗?

        可重入这样做是为了避免死锁的产生。多个锁重入需要判断是否是同一线程,在redis中进行存储的时候使用的hash结构,来存储线程信息重入的次数:大key可以按照自己的业务进行定制,其中小key是当前线程的唯一标识,value是当前线程重入的次数。

Redisson锁能解决主从数据一致的问题吗?

        不能解决,但可以使用redisson提供的红锁来解决,但是使用红锁性能太低了,如果业务中非要保证数据的强一致性,建议采用zookeeper实现的分布式锁。

9、Redis的集群方案

        Redis中提供的集群方案总共有三种:主从复制哨兵模式Redis 分片集群。

主从复制

        单节点Redis的并发能力是有上限的,要进一步提高Redis的并发能力,可以搭建主从集群,实现读写分离。一般都是一主多从主节点负责写数据,从节点负责读数据,主节点写入数据之后,需要把数据同步到从节点中。

主从同步数据的流程:分为全量同步增量同步。
        
  • 全量同步:从节点第一次与主节点建立连接的时候使用全量同步。
    • 从节点请求主节点同步数据,从节点会携带自己的replication idoffset偏移量。

    • 主节点判断是否是第一次请求,主要判断依据就是,主节点与从节点是否是同一个replication id,如果不是,就说明是第一次同步,那主节点就会把自己replication idoffset发送给从节点,让从节点与主节点的信息保持一致。 

    • 在同时主节点会执行bgsave生成rdb文件,发送给从节点去执行,从节点先把自己的数据清空,然后执行主节点发送过来的rdb文件,这样就保持了一致。

  • 增量同步:

    • 从节点请求主节点同步数据,主节点还是判断是不是第一次请求,不是第一次就获取从节点的offset值,然后主节点从命令日志中获取offset值之后的数据,发送给从节点进行数据同步。

哨兵模式

        Redis提供了哨兵(Sentinel)机制来实现主从集群的自动故障恢复。哨兵的结构和作用如下:

  • 监测:Sentinel 会基于心跳机制不断检查master和slave是否按预期工作:
    • 主观下线:如果某sentinel节点发现某实例未在规定时间响应,则认为该实例主观下线。
    • 客观下线:若超过一半的sentinel都认为该实例主观下线,则该实例客观下线。
  • 选主:如果master故障,Sentinel会将一个slave提升为master。当故障实例恢复后也以新的master为主。 下面是哨兵选举的规则:
    • 首先判断主与从节点断开时间长短,如断开时间太长则不选举该从节点。
    • 然后判断从节点的slave-priority值,越小优先级越高。
    • 如果优先值相等,则判断从节点的offset值,越大优先级越高
  • 通知:Sentinel充当Redis客户端的服务发现来源,当集群发生故障转移时,会将最新信息推送给Redis的客户端。

哨兵模式的脑裂问题:

        脑裂是由于网络等原因,使得哨兵无法心跳感知到主节点,于是通过选举的方式产生了一个新的主节点,于是就有了两个主节点,这样会导致客户端在老主节点那更新数据,新的主节点无法同步更新数据。

解决方案:

  • 配置参数:一个主节点至少需要有一个从节点,才允许写入。

10、怎么保证Redis的高并发高可用?

        首先可以搭建主从集群(解决高并发),再加上使用redis中的哨兵模式(解决高可用)哨兵模式可以实现主从集群的自动故障恢复,里面就包含了对主从服务的监控自动故障恢复通知

        如果master故障,Sentinel会将一个slave提升为master。 当故障实例恢复后也以新的master为主;同时Sentinel也充当Redis客户端的服务发现来源,当集群发生故障转移时,会将最新信息推送给Redis的客户端,所以一般项目都会采用哨兵的模式来保证redis的高并发高可用。

11、redis的分片集群

        主从和哨兵可以解决高可用、高并发读的问题。但是依然有两个问题没有解决: 海量数据存储问题 和 高并发写的问题。

分片集群的作用:

  • 集群中有多个master,每个master保存不同数据。(解决海量数据存储和高并发写的问题)
  • 每个master都可以有多个slave节点。(解决高并发读的问题)
  • master之间通过ping监测彼此健康状态。
  • 客户端请求可以访问集群任意节点,最终都会被转发到正确节点。(路由)

Redis分片集群中数据是怎么存储和读取的?

  • Redis 分片集群引入了哈希槽的概念,Redis 集群有 16384 个哈希槽。
  • 将16384个插槽分配到不同的master节点。
  • 读写数据:根据key的有效部分计算哈希值,对16384取余。(有效部分,如果key前面有大括号,大括号的内容就是有效部分,如果没有,则以key本身做为有效部分)余数做为插槽,寻找插槽所在的节点。

12、Redis是单线程的,但是为什么还那么快?

  • Redis是基于C语言编写,是基于内存操作,执行速度非常快。
  • 采用单线程,避免不必要的上下文切换,多线程还需要考虑线程安全问题。
  • 使用了I/O多路复用模型,非阻塞IO。

13、I/O多路复用模型

        Redis是纯内存操作,执行速度非常快,它的性能瓶颈是网络延迟和内存,而不是执行速度 I/O多路复用模型主要就是实现了高效的网络请求。 

  • 阻塞IO:用户区进程在“内核区准备数据”“拷贝数据”两个阶段都处于阻塞状态。
  • 非阻塞IO:第一个阶段是非阻塞,第二阶段阻塞。虽然是非阻塞,但都是忙等,没有提高性能,还会使CPU空转,使用率暴涨。
  • IO多路复用:是利用单个线程来同时监听多个Socket ,并在某个Socket可读、可写时得到通知,从而避免无效的等待,充分利用CPU资源。
    • 监听Socket的方式:
      • select:轮询监听各个Socket,监听个数上限为1024。
      • poll:也要轮询监听,但是监听上限大于select方法。
      • epoll:不必轮询监听,当socket准备好了通知一下即可。

redis使用I/O多路复用结合事件的处理器来应对多个Socket请求,

  • 连接应答处理器
  • 命令回复处理器,在Redis6.0之后,为了提升更好的性能,使用了多线程来处理回复事件
  • 命令请求处理器,在Redis6.0之后,将命令的转换使用了多线程,增加命令转换速度,在命令执行的时候,依然是单线程

​​​​14、什么是大Key问题?如何解决?

        BigKey的具体表现是Redis中的key对应的value很大,占用的redis空间比较大,本质上是大value问题。在读取大key的时候,由于读取的内容较多,占用较大的带宽,造成网络带宽的阻塞。 解决方案:
  • 针对BigKey进行拆分:将大key拆分成多个小key的键值对,读取的时候使用mget批量读取。
  • 压缩对应BigKey的value:可以通过序列化等方法对value进行压缩,使其变为较小的value。
  • 清理无效的数据:主要是针对像是listset这种类型,在使用的过程中,list和set中对应的内容不断增加,需要定时的对listset进行清理。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/盐析白兔/article/detail/875489
推荐阅读
相关标签
  

闽ICP备14008679号