微服务分布式架构之redis篇

本文深入探讨了Redis的慢操作及其解决方案,包括哈希冲突、集合操作和列表时间复杂度。介绍了Redis单线程快速的原因,如内存数据库、非阻塞IO多路复用和hash表使用。讨论了数据持久化方法RDB和AOF,以及如何在RDB和AOF之间做出选择。此外,文章还涉及Redis的主从复制、哨兵模式、数据恢复和故障切换。对于数据增长,提出了分片集群的解决方案,讨论了缓存满、击穿、穿透和雪崩问题的处理。最后,文章提到了Redis的分布式锁、并发竞争、布隆过滤器和Redis 6.0的新特性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  1. 快速的redis有哪些慢操作?
  • 哈希冲突导致时间复杂度向链表倾斜,拉低查询效率。解决方案:哈希桶rehash。
  • 一次性rehash会导致redis线程阻塞。解决方案:渐进式rehash,其实也是分步、分摊、分治思想。
  • 集合类型的范围操作时间复杂度降为O(n)。解决方案:用SCAN命令代替,渐进式遍历,每次只返回部分数据。
  • list类型由压缩列表和双向链表实现,时间复杂度都是O(n),但是这二者会记录表头和表尾的偏移量,适合用做先进先出的队列使用。
  • 压缩列表与数组的区别是:压缩列表会允许存储数据的每一个元素的容量大小不同。
  1. 为什么单线程的redis能那么快?
  • 内存数据库,内存操作
  • 单线程,无线程上下文切换开销,无锁资源性能开销
  • 采用非阻塞IO多路复用技术
  • hash表的大量使用
  1. 宕机了,redis如何避免数据丢失?
  • 数据持久化,两种方式:RDB和AOF
  • RDB可以在指定的时间段内对数据进行快照存储,方便传输,适用于灾难备份和灾难恢复。fork子进程,备份的工作由子进程来做。redis意外中断时,会丢失数据。数据集大的时候,fork会比较耗时。
  • AOF将每次写的操作追加到文件末尾。AOF更加耐久,fsync策略可以自己设置。文件体积大,速度可能会慢。
  • 在RDB和AOF之间如何抉择?看业务需求,看中什么优点,能接受什么缺点,或者两种方式结合起来用。
  • 可以调用save或者是bgsave命令进行手动备份。
  • 写时复制机制,fork子进程进行修改的过程中使用了写时复制技术。
  • AOF方式可以进行日志重写,优化,简化。
  1. 宕机后,redis如何实现快速恢复?
  • 有了RDB和AOF两种持久化的方式,可以使用备份的文件对redis数据进行恢复。
  1. 主从库如何实现数据一致?
  • redis默认使用异步复制
  • redis复制对于master而言,是非阻塞的。对于slave而言,加载新数据集是阻塞的。
  • 当master关闭了持久化和被允许自动重启的时候,主从复制会面临一个暗坑。master挂机,自动重启,数据集为空,slave同步master的数据集后也变为空数据集。“天下武功,唯快不破”,master的重启可以快到让sentinel检测不到。故即使配置了sentinel高可用,依然避免不了这个坑。所以在master关闭持久化的时候,需要禁止其自动重启。
  • Replication ID, offset,主从之间同步靠的是这两个参数。
  1. 主库挂了,如何不间断服务?
  • 哨兵模式,主库挂了,能实现自动故障转移,从从库中选出一个库升级为主库。
  1. 哨兵挂了,主从库还能切换吗?
  • 如果哨兵只有一个节点,确实存在单点故障问题,一旦这个哨兵挂了,就不实现自动主从切换了
  • 但是哨兵一般也是需要配置多个节点,实现高可用的
  1. 数据增多了,是该加内存还是加实例?
  • 数据量增多了,可以考虑分片集群,其实也是一种分而治之的思想。分而治之是处理大数据量一种很常见的思路。
  • 纵向扩展(scale up):升级单个 Redis 实例的资源配置,好理解,加内存,加磁盘,升CPU。纵向扩展的思路简单粗暴,但是有明显的缺点,面临硬件成本,fork子进程会阻塞主线程,除非不要求持久化。
  • 横向扩展(scale out):增加Redis实例的个数。横向扩展没有纵向扩展的问题,但是也会面临其他的挑战,多个实例如何管理?
  • 多个实例,如何决定某些数据入哪个实例?哈希槽(Hash Slot)。在 Redis Cluster 方案中,一个切片集群共有 16384 个哈希槽,每个键值对都会根据它的 key,经过哈希算法(CRC16 算法,然后对16384取模),被映射到一个哈希槽中。
  • cluster create 命令创建集群,redis自动把16384个槽平均分配在所有实例上。
  • cluster meet 命令手动建立实例间的连接,然后cluster addslots指定每个实例上的哈希槽个数。
  • 这么多实例,客户端怎么怎么直到它某次需要操作哪个实例?redis多个实例相互连接之后,会同步各个实例上的哈希槽信息,使得每个redis实例都有集群全量的哈希槽信息,然后,当客户端和集群建立连接后,集群会把哈希槽的分配信息发送给客户端,客户端通过同样的算法,最后定位到它要操作哪个实例。
  1. 缓存满了怎么办?
  • 通过内存缓存淘汰算法进行删除一部分数据
  1. 如何解决缓存击穿、穿透和雪崩难题?
  • 缓存击穿:缓存中没有,但是数据库中有。若并发用户多,数据库压力瞬间增大。原因:热点key过期。解决方案:设置热点key永不过期;加互斥锁,当线程A发现缓存中数据没有时会去数据库中取,此时其他线程要取相同的key会等待,而不是重复取请求数据库,再重复更新缓存。
  • 缓存穿透:缓存和数据库中都没有数据,而用户不断发起请求,此时可能是恶意攻击。大量请求导致数据库压力增大。原因:极有可能是恶意攻击。解决方案:接口层加校验,尽量将恶意攻击的请求数据尽早识别,尽早拦截,比较典型的方法是使用布隆过滤器对其进行过滤;将所请求的key的value值设置为null,可以将key的过期时间设置短一点,设置太长的话会导致该key在正常的情况下也无法使用,也就是误杀,这样可以防止恶意用户反复用同一条数据进行暴力攻击。
  • 缓存雪崩:缓存中大量数据同时失效,导致大量请求直接请求数据库,导致数据库压力增大。与击穿区别:击穿的场景是多个请求并发请求同一条数据,而雪崩指的是多个请求大量请求多条数据。原因:缓存中大量数据同时过期。解决方案:各个key的过期时间随机设置,避免多个key的过期时间设置为同一时间;设置热点数据永不过期;分布式缓存情况下,热点数据均匀分布。
  1. redis如何实现分布式锁?
  • setnx命令加过期时间
  • 用redis做分布式锁为什么要用lua脚本来释放锁?为了保证原子性。在释放锁的时候,需要考虑锁释放的锁的value是不是当前线程设置的锁的value,所有取value,然后比较,在然后删除对应的key,这三步是需要作为一个原子操作的,而lua脚本执行的时候就是单线程,就能保证这三个操作的原子性。
  1. 如何应对数据倾斜?
  • 针对hot key造成的数据倾斜,可以对hot key进行打散,可以对hot key加上前缀或者是后缀,把一个key变成多个key,再通过分片算法将数据存储到不同的实例上,将访问量分摊到各个不同的实例上。此时需要注意各个temp key的过期时间需要在原hot key的过期时间的基础上,加上一个小的随机正整数,这是为了防止多个key同时过期造成雪崩。
  • 针对big key造成的数据倾斜,可以进行拆分,如果big key对应的big value是个大json的形式,可以通过mset 的方式将这个 key 的内容打散分布到各个实例中。
  1. redis支持哪些数据类型?
  • 字符串、哈希表、列表、集合、有序集合
  1. redis有哪些架构模式?各有什么特点?
  • 单机,特点是容量受限、处理能力受限、单点故障、没做到高可用
  • 主从复制,特点是可以在一定的程序上解决了容量问题和处理能力,读压力转移给从库了,但是还是存在单点故障问题,master写的压力问题还是没有解决。
  • 哨兵模式,特点:sentinel监控各redis节点,当某个节点出问题,sentinel会通知其他节点,当主节点出问题时,sentinel会自动进行故障转移操作,保障了高可用。缺点是切换时会需要时间并且丢数据,还是没有解决master的写压力问题
  • 代理式集群,对业务方而言,屏蔽后端多个redis复杂业务逻辑,但是针对代理层需要考虑高可用方案,扩缩容需要手动干预。
  • 3.0版本后,直连式集群,Redis-Cluster采用无中心结构模式。优点:无中心节点模式,不会因为某个节点影响整体性能;节点间数据共享,可动态调整数据分布;实现了可扩展性,节点可动态添加或删除;实现了高可用;实现了故障自动转移。缺点:各节点之间资源隔离性差,容易相互影响;数据之间异步复制,不能保证强一致性。
  1. 使用redis做异步队列,有什么缺点?
  • 使用List做异步队列,RPUSH生产消息,LPOP消费消息。生产者消费者模式,LPOP不等待队列有值就直接消费,没有消费到数据就认为是消息被消费完毕。解决方案:应用层引入sleep机制,调用LPOP进行重试。
  • BLPOP会在没有消息的时候阻塞,直到消息到来或者是超时。缺点:只能供一个消费者消费
  • pub/sub模式,发布订阅模式,缺点:消息的发布是无状态的,无法保证消息的可达。
  1. redis的内存淘汰策略有哪些?
  • noeviction:不删除策略,达到最大内存限制时,再申请内存时,直接返回错误信息
  • allkeys-lru:所有的key使用lru算法,优先删除最近最少使用的key
  • volatile-lru:对于设置了expire的key,使用lru算法进行优先删除
  • allkeys-random:对于所有的key,随机删除一部分key
  • volatile-random:对于设置了expire的key,随机删除一部分key
  • volatile-ttl:对于设置了expire的key, 优先删除剩余时间(time to live,TTL) 短的key
  • 如果没有设置expire的key,volatile-lru、volatile-random、volatile-ttl和noeviction一样
  • 具体选择哪一种驱逐策略,需要根据业务场景来判断。
  1. redis的并发竞争问题如何解决?
  • 解决并发竞争问题的总体思路就是加锁,将并发执行变成顺序执行,并行变成串行。
  • 业务客户端加锁,synchronized
  • 使用redis自带的incr命令,保证原子性
  • 使用redis的watch命令,构建乐观锁
  • 使用redis的setnx实现内置的锁
  • 分布式锁,分布式锁的实现方式有很多种
  1. 布隆过滤器是如何解决缓存穿透的问题的?
  • 首先,布隆过滤器是什么?布隆过滤器的作用是迅速地判断一个key是否在某个集合中,类似于HashSet,但是与HashSet不同的是,使用布隆过滤器,不需要存储key的值。
  • 布隆过滤器的原理是什么?对一个key进行k个hash算法,得到k个值。然后需要一个各位初始值为0的位图,把位图中这k个位置都置为1。在查询的时候,用同样的算法,校验这k个位置是否都为1,如果是,就说明该key存在,否则则认为该key不存在。
  • 布隆过滤器的优缺点是什么?优点:不需要存储key,节省空间,运算都是用hash函数,运算速度快,避免了无谓地查询redis和数据库的开销;缺点:可能误判,可能把不存在的key判定为存在,但是概率比较小。redis的位图只支持2的32方大小,对应于内存中也就是512MB,误判率为万分之一。另外,布隆过滤器不支持删除操作。
  • 布隆过滤器是如何解决缓存穿透的问题的?流程:查询key请求过来,先去redis里面查,如果查不到,用布隆过滤器判定该key是否存在,不存在直接返回null,如果存在就去数据库取,取到后写回redis,然后返回给客户端。
  • 布隆过滤器还有其他的应用场景吗?有的:网页爬虫对URL去重;反垃圾邮件;反垃圾短信;消息推送去重;推荐系统去重。
  • 布隆过滤器在代码层面如何用?google的guava包已经有相关的API。
  1. redis的红锁是有什么作用?
  • 用来实现分布式锁。
  • 有什么优点?高可用
  • 红锁的加锁流程是怎么样的?有2n + 1个节点,这些节点之间是没有主从也没有集群关系,客户端使用相同的key和随机值在这2n + 1个节点中请求锁,注意请求锁的超时时间应该小于锁自动释放的时间,当超过半数请求到锁的时候,才算是真正加锁成功,如果没有加锁成功,则回滚释放已经锁定的节点。
  1. redisson是干什么用的?有什么优点?
  • redisson是一个在Redis的基础上实现的Java驻内存数据网格,功能非常强大。
  • 可以用redisson实现分布式锁,可以用watch dog机制来避免业务执行时间超过锁过期时间而产生的锁释放的问题。
  1. redis存在线程安全的问题吗?
  • redis本身是不存在安全问题的,redis的命令都是单线程执行。
  • redis6.0的新特性会支持多线程,但是只是用来处理网络数据的读写和协议解析。
  • 当然,使用redis实现复杂业务的时候,可能会存在业务层面的非原子性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值