当前位置: 首页 > article >正文

redis高级(面试题二)

目录

一、redis的五大数据结构有哪些?zset底层是什么结构?

1、redis五大数据结构有哪些?

2、什么是skiplist?

3、zset底层是什么结构?

 二、Redis的内存过期策略是什么?Redis的内存淘汰策略有哪些?

1、redis如何判断key是否过期?

2、redis的内存过期策略是什么?

3、redis的内存淘汰策略有哪些?

三、Redis缓存和Mysql数据库怎么保证数据一致性?Redis的缓存穿透、缓存雪崩、缓存击穿分别是什么意思?怎么解决?

 1、Redis缓存和Mysql数据库怎么保证数据一致性?

2、如何解决缓存穿透问题?

3、如何解决缓存雪崩问题?

4、如何解决缓存击穿问题?


一、redis的五大数据结构有哪些?zset底层是什么结构?

1、redis五大数据结构有哪些?

  • String

  • List

  • Set

  • SortedSet

  • Hash

2、什么是skiplist?

skiplist(跳表)首先是链表,与传统链表相比有几点差异:

  • 元素按照升序排列存储

  • 节点可能包含多个指针,指针跨度不同。

        传统链表只有指向前后的指针,因此只能顺序一次访问。如果查找的元素在链表中间,查询的效率会比较低。而skiplist则不同,它内部包含跨度不同的多级指针,可以让我们条约查找链表中间的元素,效率非常高。

其结构如图所示:

        我们可以看到1号元素就有指向3、5、10的多个指针,查询时就可以跳跃查找。例如我们要找大小为14的元素,查找的流程是这样的:

  • 首先找元素1节点最高级指针,也就是4级指针,起始元素大小为1,指针跨度为9,可以判断出目标元素大小为10。由于14比10大,肯定要从10这个元素向下接着找。

  • 找到10这个元素,发现10这个元素的最高级指针跨度为5,判断出目标元素大小为15,大于14,需要判断下级指针

  • 10这个元素的2级指针跨度为3,判断出目标元素为13,小于14,因此要基于元素13接着找

  • 13这个元素最高级级指针跨度为2,判断出目标元素为15,比14大,需要判断下级指针。

  • 13的下级指针跨度为1,因此目标元素是14,刚好于目标一致,找到。

        这种多级指针的查询方式就避免了传统链表的逐个遍历导致的查询效率下降问题。在对有序数据做随机查询和排序时效率非常高。

3、zset底层是什么结构?

        zset底层是哈希表加上跳表实现的。zset是有序集合,底层存储的每个数据都包含element和score两个值。score是得分,element则是字符串值。zset会根据每个element的score值排序,形成有序集合。

        它支持的操作很多,比如:根据element查询score的值、按照score值升序或降序查询element。

        要实现element查询对应的score值,就必须实现element与score之间的键值映射。zset底层是基于hashtable实现的。

        要实现对score值排序,并且查询效率还高,就需要一种高效的有序数据结构,zset是基于跳表实现的。

加分项:因为zset底层需要用到两种数据结构,堆内存的占用比较高 。因此redis底层会对zset中的元素大小做判断。如果元素大小小于128且每个元素都小于64字节,zset底层会采用ziplist,也就是压缩列表来代替hashtable和skiplist,不过,ziplist存在连锁更新问题,因此在redis7.0版本之后ziplist又被替换位listpack(紧凑列表)。

Redis源码中zset,也就是SortedSet的结构体如下:

typedef struct zset {
    dict *dict; // dict,底层就是HashTable
    zskiplist *zsl; // 跳表
} zset;

 其内存结构如图:

 二、Redis的内存过期策略是什么?Redis的内存淘汰策略有哪些?

1、redis如何判断key是否过期?

        在redis中会有两个dict,也就是hashtable,其中一个记录key-value键值对,另一个记录key和过期时间。要判断一个key是否过期,只需要到记录过期时间的dict中根据key查询即可。

redis是合适删除过期key得呢?

        redis并不会在key过期的时候立刻删除key,因为要实现这样得效果就必须给每一个过期的key设置时钟,并监控这些key的过期状态。无论对cpu还是内存都会带来极大的负担。

2、redis的内存过期策略是什么?

惰性删除:顾名思义就是果期七之后不会立刻删除。redis会在灭磁访问key的时候判断当前key有没有设置过期时间,如果有,判断过期时间是否已经到期,如果已经过期并不会立刻删除,而是当下次再次访问该key时候才会删除。

周期删除:顾名思义就是通过一个定时任务,周期性的抽样部分过期的key然后删除。

周期删除有两种模式:

  • SLOW模式:通过一个定时任务,定期的抽样部分带有TTL的KEY,判断其是否过期。默认情况下定时任务的执行频率是每秒10次,但每次执行不能超过25毫秒。如果执行抽样后发现时间还有剩余,并且过期KEY的比例较高,则会多次抽样。

  • FAST模式:在Redis每次处理NIO事件之前,都会抽样部分带有TTL的KEY,判断是否过期,因此执行频率较高。但是每次执行时长不能超过1ms,如果时间充足并且过期KEY比例过高,也会多次抽样

3、redis的内存淘汰策略有哪些?

Redis支持8种不同的内存淘汰策略:

  • noeviction: 不淘汰任何key,但是内存满时不允许写入新数据,默认就是这种策略。

  • volatile-ttl: 对设置了TTL的key,比较key的剩余TTL值,TTL越小越先被淘汰

  • allkeys-random:对全体key ,随机进行淘汰。也就是直接从db->dict中随机挑选

  • volatile-random:对设置了TTL的key ,随机进行淘汰。也就是从db->expires中随机挑选。

  • allkeys-lru: 对全体key,基于LRU算法进行淘汰

  • volatile-lru: 对设置了TTL的key,基于LRU算法进行淘汰

  • allkeys-lfu: 对全体key,基于LFU算法进行淘汰

  • volatile-lfu: 对设置了TTL的key,基于LFI算法进行淘汰

比较容易混淆的有两个算法:

  • LRULeast Recently Used),最近最久未使用。用当前时间减去最后一次访问时间,这个值越大则淘汰优先级越高。

  • LFULeast Frequently Used),最少频率使用。会统计每个key的访问频率,值越小淘汰优先级越高。

三、Redis缓存和Mysql数据库怎么保证数据一致性?Redis的缓存穿透、缓存雪崩、缓存击穿分别是什么意思?怎么解决?

 1、Redis缓存和Mysql数据库怎么保证数据一致性?

        答:缓存的双写一致性很难保证强一致,只能尽可能降低不一致的概率,确保最终一致。我们项目中采用的是Cache Aside模式。简单来说,就是在更新数据库之后删除缓存;在查询时先查询缓存,如果未命中则查询数据库并写入缓存。同时我们会给缓存设置过期时间作为兜底方案,如果真的出现了不一致的情况,也可以通过缓存过期来保证最终一致。

2、如何解决缓存穿透问题

        答:缓存穿透也可以说是穿透攻击,具体来说是因为请求访问到了数据库不存在的值,这样缓存无法命中,必然访问数据库。如果高并发的访问这样的接口,会给数据库带来巨大压力。

        我们项目中都是基于布隆过滤器来解决缓存穿透问题的,当缓存未命中时基于布隆过滤器判断数据是否存在。如果不存在则不去访问数据库。当然,也可以使用缓存空值的方式解决,不过这种方案比较浪费内存。

3、如何解决缓存雪崩问题

        答:缓存雪崩的常见原因有两个,第一是因为大量key同时过期。针对问这个题我们可以可以给缓存key设置不同的TTL值,避免key同时过期。

        第二个原因是Redis宕机导致缓存不可用。针对这个问题我们可以利用集群提高Redis的可用性。也可以添加多级缓存,当Redis宕机时还有本地缓存可用。

4、如何解决缓存击穿问题

        答:缓存击穿往往是由热点Key引起的,当热点Key过期时,大量请求涌入同时查询,发现缓存未命中都会去访问数据库,导致数据库压力激增。解决这个问题的主要思路就是避免多线程并发去重建缓存,因此方案有两种。

        第一种是基于互斥锁,当发现缓存未命中时需要先获取互斥锁,再重建缓存,缓存重建完成释放锁。这样就可以保证缓存重建同一时刻只会有一个线程执行。不过这种做法会导致缓存重建时性能下降严重。

        第二种是基于逻辑过期,也就是不给热点Key设置过期时间,而是给数据添加一个过期时间的字段。这样热点Key就不会过期,缓存中永远有数据。

        查询到数据时基于其中的过期时间判断key是否过期,如果过期开启独立新线程异步的重建缓存,而查询请求先返回旧数据即可。当然,这个过程也要加互斥锁,但由于重建缓存是异步的,而且获取锁失败也无需等待,而是返回旧数据,这样性能几乎不受影响。

        需要注意的是,无论是采用哪种方式,在获取互斥锁后一定要再次判断缓存是否命中,做double check. 因为当你获取锁成功时,可能是在你之前有其它线程已经重建缓存了。


http://www.kler.cn/news/339820.html

相关文章:

  • Redis介绍及整合Spring
  • FPGA学习(4)-时序逻辑电路实现D触发器与计数器,LED灯闪烁
  • C#将部分Controls数据导入对象并存入ini中
  • node配置swagger
  • c语言中的有关“sizeof”和“strlen”在“数组”以及“指针”中应用的举例
  • ubuntu的useradd和adduser命令
  • LeetCode题练习与总结:生命游戏--289
  • EventSource和websocket该用哪种技术
  • 图示详解OpenEuler下 DNS安装、配置与测试
  • CleverPDF是一款专业的pdf转换器-强大的PDF表格识别能够将PDF中的表格提取到Excel或者其他格式-供大家学习研究参考
  • STM32移植RT-thread实现IIC与AT24C02的通信功能(含软件包和软件模拟IIC两种方法)
  • linux常用命令记录
  • Spring Cloud微服务详解
  • 三角形面积 python
  • 【优选算法】(第二十七篇)
  • Springboot Web
  • 路由:ReactRouter
  • 数据驱动投资:AI在股票市场的应用
  • 未来已来:探索日常小发明背后的专利革命
  • C++语言学习(3): type 的概念