当前位置:   article > 正文

【面试准备】Redis_利用hash结构记录线程id和重入次数

利用hash结构记录线程id和重入次数
修订记录时间
首次发布2023.06

一、基础

1.1 数据结构

1. 常见Redis数据结构

String、Hash、List、Set、SortedSet(ZSet)。

2. String

底层实现:SDS。编码方式可能是RAW(大多数)、EMBSTR(长度小于44字节)和INT(整数值)。
使用场景:单值缓存、对象缓存、分布式锁、计数器(文章阅读量)。

3. Hash

底层实现:默认ZipList,以节省内存,当数据量大时会变成Dict(HT)。
使用场景:电商购物车。
问:知道渐进式rehash吗?

4. List

List结构类似于一个双端链表,可以从首、尾操作元素。
底层实现:3.2版本之后是QuickList。
使用场景:栈、消息队列。

5. Set

Set是Redis中的集合,元素唯一。
底层实现:Dict字典,采用HT编码,(即哈希表)。Dict中key存储元素,value为null。当存储的都是整数切不超过某一限值时采用IntSet编码,以节省内存。
使用场景:抽奖、朋友圈点赞、微博关注模型(共同关注、可能认识)。

6. SortedSet

SortedSet是一个可排序的Set集合,每个元素带有一个score属性,可以基于score进行排序。
特性:可排序、元素不重复、查询速度快。
底层实现:跳表SkipList + Dict(HT编码),即跳表+哈希表。
使用场景:排行榜。

1.2 缓存

1. 缓存击穿及解决方法

缓存击穿

给某一个key设置了过期时间,当key过期时恰好有对这个key的大量并发请求过来,这些请求都会直接查数据库,把数据库击垮。

解决方案
  1. 互斥锁
    发现缓存未命中之后,获取互斥锁(比如redis中的setnx),开始查询数据库并写入缓存,释放互斥锁。此时如果有另外一个请求查询缓存,则获取不到锁,定时重试get缓存的方法。这个方案可以保证数据的强一致性,但性能较差。
  2. 逻辑过期
    热点key不设置过期时间,但在存储内容中增加逻辑过期时间字段。查询缓存时,先校验逻辑过期时间,如果过期则获取互斥锁,返回过期数据。同时开启新线程查询数据库更新缓存,更新完后释放互斥锁。这个方案高可用,性能更好,但可能数据不一致。

2. 缓存穿透及解决方法

缓存穿透

查询一个不存在的数据,mysql中也查询不到,也不会写入缓存,导致每次请求都会查数据库。这种情况大概率是遭到了外部攻击。

解决方案
  1. 缓存空数据,查询返回为空,缓存空结果。
    优点:简单。
    缺点:消耗内存,可能后续会发生不一致的问题。
  2. 使用布隆过滤器
    查询布隆过滤器中不存在则直接返回,缓存预热时需要预热布隆过滤器。
    布隆过滤器使用bitmap位图,存储数据时会根据多个哈希函数计算哈希值,将bitmap上对应的位置改为1。查询数据时使用相同的多个哈希函数计算哈希值,去bitmap对应位置查看是否都为1,若不是则数据不存在。
    如果有哈希冲突,则可能存在误判。bitmap越小误判率越大,Redisson和Guava中都可以设置误判率,一般设置为5%。
    优点:内存占用较少,没有多余key。
    缺点:实现复杂,存在误判。

3. 缓存雪崩及解决方法

缓存雪崩

在同一段时间大量的缓存key同时过期或Redis服务宕机,大量的请求到达数据库,给数据库带来很大的压力。

解决方案
  1. 给不同key的过期时间上增加随机值
    添加1-5分钟随机值,避免同时过期。
  2. 利用Redis集群提高服务的可用性
    哨兵模式、集群模式
  3. 给缓存业务添加降级限流策略
    Nginx或Spring Cloud Gateway
  4. 给业务添加多级缓存
    Guava或Caffeine

1.3 持久化

RDB(Redis数据备份文件)、AOF。

1. RDB

RDB全称Redis Database Backup File,数据备份文件,也叫做数据快照。就是把内存中的所有数据记录到磁盘中,当实例故障重启后,从磁盘中读取快照文件,恢复数据。默认开启。

1.1 bgsave流程:
  1. 开始时会fork主进程,得到一个子进程,子进程共享主进程的内存数据。fork时会阻塞主进程。
  2. 完成fork之后子进程读取内存数据并写入新的RDB文件。
  3. 用新的RDB文件替换旧的RDB文件。
1.2 RDB在什么时候执行?
  1. 默认服务停止时执行一次save,即主进程进行持久化。
  2. 可以在redis.conf中配置save seconds changes参数,来修改RDB触发的频率。达到save条件时会自动触发bgsave后台进行异步持久化。例如save 60 1000是说在60s内至少执行1000次修改则会触发一次RDB。
1.3 RDB的缺点
  1. RDB执行间隔时间长,两次RDB之间写入数据有丢失的风险。
  2. fork子进程、压缩、写出RDB文件都比较耗时,可能十几秒或者几十秒。

2. AOF

AOF全称是Append Only File,追加文件。Redis处理的每一个写命令都会记录在AOF文件中,可以看成命令日志文件。默认关闭AOF,需要修改redis.conf中的appendonly来开启。还可以配置记录频率。
在这里插入图片描述
在这里插入图片描述
因为是记录命令,AOF文件会比RDB文件大很多,可能会记录对一个key的多次操作,但只有最后一次写操作才有意义。可以通过bBGREWRITEAOF命令来重写合并AOF文件。

RDB和AOF对比

在这里插入图片描述

1.4 内存淘汰

1. 数据过期策略

问:Redis的key过期之后,会立即删除吗?
不会立即删除,但数据过期以后需要从内存中删除,删除策略有惰性删除和定期删除两种。Redis是惰性删除和定期删除配合使用的。

惰性删除

设置key过期之后,不去管这个key,再次获取时如果发现过期,则删掉这个key。
对CPU友好,不需要额外检查key,但对内存不友好,过期key如果不被访问到就会一直存在在内存中。

定期删除

定期随机对一些key进行检查,删除里面过期的key。有SLOW和FAST两种模式,SLOW模式默认100ms执行一次,每次不超过25ms;FAST模式执行频率不固定,耗时不超过1ms。
减轻内存的压力。

2. 内存淘汰策略 maxmemory-policy

问:内存被占满了怎么办?
Redis支持8种不同的策略来删除key。

  1. noeviction:默认这种策略,不淘汰任何key,内存满后不允许新key写入。
  2. volatile-ttl
  3. allkeys-random
  4. volatile-random
  5. allkeys-lru
  6. volatile-lru
  7. allkeys-lfu
  8. volatile-lfu
    LRU最少最近使用,LFU最少频率使用。

问:数据库中有1000w数据,Redis中只能存20w数据,如何保证Redis中的数据都是热点数据?
答:可以把淘汰策略设置为allkeys-lru,那么留下来的就是热点数据。

1.5 双写一致性

问:MySQL数据如何与Redis进行同步?
先介绍业务背景:是一致性要求高,还是允许一定的延迟。
读操作:缓存命中,直接返回;缓存未命中则查询数据库,写入缓存,并设置过期时间。

方案一:延迟双删

写操作:采用延迟双删策略,先删除缓存,修改数据库,延时等待一段时间后再删除缓存。延时双删只能降低脏数据的风险,不能完全解决脏数据的问题。

  1. 为什么要删除两次缓存?
    因为无论先删缓存还是先修改数据库,都有可能存在脏数据,所以要删除两次。
  2. 为什么要延时删除?
    因为数据库一般是主从模式,读写分离的,需要延时让主节点把数据同步给从节点。

方案二:加分布式锁

性能会有损耗。

二、原理

2.1 高效

问:为什么Redis是单线程的,还那么快?

  1. 完全基于内存,采用C语言编写,更高效。
  2. 采用单线程,避免不必要的上下文切换和竞争条件。
  3. 使用了I/O多路复用模型,是非阻塞I/O。
    Redis的网络模型就是使用I/O多路复用结合事件处理器来处理多个socket请求,包括连接应答处理器、命令回复处理器、命令请求处理器。Redis的性能瓶颈主要在网络请求上,在Redis 6.0之后,采用了多线程来处理网络I/O,比如多线程转换命令、回复事件,但执行命令仍是单线程。

问:解释一下I/O多路复用模型。
I/O多路复用指的是利用单线程来监听多个socket,并在某个socket可读或可写时得到通知,避免了无效的等待,充分利用了CPU的资源。Redis中的I/O多路复用使用的是epoll模式,会在通知用户进程socket就绪的时候,将已就绪的socket写入用户空间。

2.2 底层数据结构

动态字符串SDS、IntSet、Dict、ZipList、QuickList、SkipList

三、应用 / 设计

3.1 分布式锁

场景:集群情况下的定时任务、抢单、幂等性场景。

1. 基础实现

  1. SET lock NX EX time
  2. 有一个单独的看门狗线程对锁进行续期
  3. 释放锁时判断是否为本线程的锁(lua脚本)
    锁的名字可以是UUID+线程名,加UUID是为了区别多个jvm运行应用的情况。

存在一些极低概率的问题:

  • 不可重入:同一个线程无法多次获取同一把锁。
  • 不可重试:需业务代码额外编写重试策略。
  • 主从一致性:如果Redis使用集群,主从同步之间存在延迟,极端情况下从主节点获取了锁,主宕机后从节点中锁是没有被获取的。

2. Redisson

Redisson是一个在Redis基础上实现的分布式工具的集合。
可重入:利用hash结构记录线程id和重入次数。
可重试:利用信号量和PubSub功能实现等待、唤醒,获取锁失败的重试机制。
超时续约:利用watchDog看门狗,每个一段时间重置超时时间。
在这里插入图片描述

3.2 Redis集群

主从复制、哨兵模式、分片集群

1. 主从复制

单机Redis并发8-10w,想进一步提升,需要构建集群。主从集群是一主多从,可以实现读写分离,写操作主节点,读操作从节点。

主从数据同步流程是什么?
  • 全量同步(首次或offset相差过大)
    • 从节点发送同步请求(含replication id和offset)。
    • 主节点对比replid不一致,则需要全量同步。返回master的eplication id、offset给从节点保存。
    • 主节点进行bgsave,生成一份rdb,发送给从节点。
    • 从节点收到rdb,将自己数据清空后,读取rdb。
    • 主节点生成rdb过程中的写操作会记录在rep_baklog中,作为增量数据发送给从节点。
      在这里插入图片描述
  • 增量同步(后续)
    • 从节点发送同步请求(含eplication id和offset)。
    • 主节点对比eplication id一致,只需要增量同步。
    • 主节点发送offset到最新操作的rep_baklog给从节点。
    • 一般是节点重启服务后。

2. 哨兵模式

主从模式下,如果主节点宕机,则会出现无法写入的情况。Redis提供了哨兵机制来实现主从集群的自动故障恢复

功能
  • 监控:Sentinel每秒会发ping指令来检查主从节点是否正常工作。
  • 自动故障恢复:如果主节点客观下线,Sentinel就会从从节点中选出新的主节点。
    • 客观下线:当多个Sentinel都认为某个节点下线了,则这个节点为客观下线。如果只是少数Sentinel认为则是主观下线。
    • 选出新的主节点
      • 首先判断这些从节点和主节点的断开时间,超过一定值则排除。
      • 按照slave_priority排序,越小优先级越高。
      • slave_priority一样的情况下,offset值大的优先,表明更近更新过,丢失数据更少。
      • 按照slave节点的运行id排序,越小优先级越高。
  • 通知变更:出现新的主节点后,Sentinel会将最新的信息推送给Redis客户端。
脑裂问题

当Sentinel认为主节点客观下线了,就会选出新的主节点,但原来的主节点恢复工作了,就存在了两个主节点。部分客户端往原来的主节点中写入了一部分数据,但原来的主节点会降级为从节点,将同步新的主节点的数据,所以会丢失部分数据。

  • 出现原因:节点故障,或者Sentinel和主节点在不同的网络分区。
  • 解决方法:
    • min-slaves-to-write 1 设置连接到master的最少slave数量(判断有多少个从服务器,达到了要求才发送信息,避免了主服务器失连后依旧写入数据)
    • min-slaves-max-lag 10 设置slave连接到master的最大延迟时间(减少同步间隔时间,在失连前同步完成)

问:怎么保证Redis的高并发高可用?
高并发:主从架构 / 分片集群
高可用:哨兵模式

3. 分片集群

  • 集群中有多个master,每个master保存不同的数据。
  • 每个master都可以有多个slave节点。
  • master之间通过ping监测彼此健康状态。
  • 客户端请求可以访问集群任意节点,最终都会转发到正确节点。

3.3 Redis限流

1. 固定窗口

2. 滑动窗口

3. 令牌桶

3.2 BigKey

1. 什么是BigKey

某一个key对应的value占用空间很大,那么就称为BigKey。
String类型下超过1M,其他类型下含有成员数上万个。

2. 为什么会产生BigKey?

业务分析不准确,实际业务中value值过大。List、Set中的无效数据没有及时删除。

3. BigKey的影响

占用内存增大、网络阻塞延迟变大、集群中迁移困难。

4. 怎么解决BigKey

  1. 根据业务将key拆分为多个key。
  2. 及时清理list、set中的无效数据。

3.3 Redis场景

  1. 查询数据缓存
  2. 分布式锁
  3. 延迟队列
    商品到时下架。
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/875483
推荐阅读
相关标签
  

闽ICP备14008679号