Go最新Redis原理详解(1),2024年阿里Golang面试题及答案

img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

Redis 基础详解

一、Redis 是什么

Redis 是一个使用 C 语言写成的,开源的、key-value 结构的、非关系型数据库。它支持存储的 value 类型相对更多,包括 String(字符串)、List(列表)、Set(集合)、Sorted Set(有序集合) 和 Hash(哈希),而且这些操作都是原子性的。在此基础上,Redis 支持各种不同方式的排序。为了保证效率,数据都是缓存在内存中。Redis 可以周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。

使用 Redis 有哪些好处?

· 速度快,因为数据存在内存中;

·  支持丰富数据类型,支持 string,list,set,sorted set,hash等;

· 支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行;

· 丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除;

二、单线程?

为什么 Redis 是单线程的?

Redis 的数据存储在内存中,如果数据全都在内存里,单线程的去操作就是效率最高的。

为什么呢?因为多线程的本质就是 CPU 模拟出来多个线程的情况,这种模拟出来的情况就有一个代价,就是上下文的切换,对于一个内存的系统来说,它没有上下文的切换就是效率最高的。因为上下文切换所花费的时间远大于直接从内存中读取数据所花费的时间。

Redis 用单个 CPU 绑定一块内存的数据,然后针对这块内存的数据进行多次读写的时候,都是在一个CPU上完成的,所以它是单线程处理这个事。在内存的情况下,这个方案就是最佳方案。

这里我们一直在强调的单线程,只是在处理我们的网络请求的时候只有一个线程来处理,一个正式的 Redis Server 运行的时候肯定是不止一个线程的,例如 Redis 进行持久化的时候会以子进程或者子线程的方式执行。

Redis 单线程如何处理那么多的并发客户端连接?

Redis的IO多路复用:redis利用epoll来实现IO多路复用,将连接信息和事件放到队列中,依次放到文件事件分派器,事件分派器将事件分发给事件处理器。

三、Redis 数据类型

1、String
String 数据结构是简单的 key-value 类型,value 其实不仅可以是 String,也可以是数字。需要注意是一个键值最大存储 512MB。

2、Hash(哈希)
Hash 是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象。 比如我们可以 Hash 数据结构来存储用户信息,商品信息等等。

3、List(列表)

是 Redis 的简单的字符串列表。list 的实现为一个双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销。

4、Set
Set 对外提供的功能与 list 类似是一个列表的功能,特殊之处在于 set 不允许重复元素,可以自动排重的,并且 set 提供了判断某个成员是否在一个 set 集合内的重要接口,这个也是 list 所不能提供的。

在微博应用中,可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。Redis 可以非常方便的实现如共同关注、共同喜好、二度好友等功能。

5、Sorted Set
与 set 相比,Sorted set 增加了一个权重参数 score,使得集合中的元素能够按 score 进行有序排列。

举例: 在直播系统中,实时排行信息包含直播间在线用户列表,各种礼物排行榜,弹幕消息(可以理解为按消息维度的消息排行榜)等信息,适合使用 Sorted set 结构进行存储。

**、Redis 数据持久化存储******

上面说了 Redis 是基于内存的数据库,一旦进程退出,数据就会丢失,所以我们需要它也可以把数据写到磁盘上,当 Redis 重启后,可以从磁盘中恢复数据。

Redis 提供了两种解决方案将内存中的数据保存到磁盘上。一种是 RDB 持久化,原理是将 Reids 在内存中的全部数据库记录定时 dump 到磁盘上的 RDB 持久化;另外一种是 AOF 持久化,原理是将 Reids 的操作日志以追加的方式写入文件。

******1、**RDB快照(snapshot)

RDB 是 Redis 用来进行持久化的一种方式,是把当前内存中的数据集快照写入磁盘,也就是 Snapshot 快照所有数据。恢复时是将快照文件直接读到内存里。

在默认情况下, Redis 将内存数据库快照保存在名字为dump.rdb的二进制文件中。
你可以对 Redis 进行设置, 让它在N秒内数据集至少有M个改动这一条件被满足时, 自动保存一次数据集。

save 60 1000

redis.conf文件里面有默认的3种情况,3种是或的关系

RDB 存储的劣势:

  1. RDB 方式数据没办法做到实时持久化,该方式是每间隔一段时间做一次备份。因为 bgsave 每次运行都要执行 fork 操作创建子进程,属于重量级操作,频繁执行成本过高。所以如果 Redis 意外挂掉,就会丢失最后一次快照后的所有修改;
  2. RDB 是内存数据的全量同步,数据量大会由于 I/O 而严重影响性能;

RDB 文件使用特定二进制格式保存,Redis 版本演进过程中有多个格式的 RDB 版本,旧版本无法兼容新版的格式。

2、AOF(Append Only File)

AOF 持久化:记录除了查询以外,所有变更数据库状态的指令,以 append 的形式追加保存到 AOF 文件中。AOF 文件通常会比 RDF 文件体积更大。

快照功能并不是非常耐久(durable): 如果 Redis 因为某些原因而造成故障停机, 那么服务器将丢失最近写入、且仍未保存到快照中的那些数据。从 1.1 版本开始, Redis 增加了一种完全耐久的持久化方式: AOF 持久化,将修改的每一条指令记录进文件
你可以通过修改配置文件来打开 AOF 功能:

appendonly yes

开启后,每当 Redis 执行一个改变数据集的命令时(比如SET), 这个命令就会被追加到 AOF 文件的末尾。
这样的话, 当 Redis 重新启时, 程序就可以通过重新执行 AOF 文件中的命令来达到重建数据集的目的。
你可以配置 Redis 多久才将数据fsync到磁盘一次。

有三个选项:

每次有新命令追加到 AOF 文件时就执行一次fsync:非常慢,也非常安全。
每秒fsync一次:足够快(和使用 RDB 持久化差不多),并且在故障时只会丢失 1 秒钟的数据。
从不fsync:将数据交给操作系统来处理。更快,也更不安全的选择。

推荐(并且也是默认)的措施为每秒fsync一次, 这种fsync策略可以兼顾速度和安全性。

RDB 和 AOF ,我应该用哪一个?

如果你非常关心你的数据, 但仍然可以承受数分钟以内的数据丢失, 那么你可以只使用 RDB 持久化。

有很多用户都只使用 AOF 持久化, 但我们并不推荐这种方式: 因为定时生成 RDB 快照(snapshot)非常便于进行数据库备份, 并且 RDB 恢复数据集的速度也要比 AOF 恢复的速度要快。

3、RDB-AOF 混合模式

混合模式是先使用 bgsave 以 RDB 形式将内存中的全部数据写入磁盘,之后当有新的数据时,再使用 AOF 的形式追加到文件中。

aof-use-rdb-preamble yes

******五、**Redis 应用

1、模糊查询

Redis 中有 1 亿个 key,其中有 10 万个 key 是已知的某个固定前缀,如何将找出这些 key。

当问题是:如何找出某一前缀的 key。这里需要注意数据规模是多大,再结合实际场景去解决。

如果数据量小,可以使用 keys 命令来查询。

但是,如果数据量大,而 Redis 正在线上运行,使用 keys 命令很可能会阻塞 Redis,使其不能提供服务。
这时可以使用 SCAN cursor [MATCH pattern] [COUNT count] 命令。需要注意的是条命令返回的数量是不可控的,只能大概符合 count。

cursor 从 0 开始,这里第一个返回值为 cursor 用于下一次迭代,当 cursor 再次为 0 时,说明迭代完成。

2、如何通过 Redis 实现分布式锁

首先想到的方法是使用 SETNX key value,如果 key 不存在,则创建并赋值,返回 1;若 key 已存在,则创建失败,返回 0。

当某一个线程通过 SETNX key value 设置成功后占用该资源,其他线程执行该命令就会设置失败,说明已经有线程占用该资源,通过这种方式来实现分布式锁。

同时,为了防止死锁,还需要设置 key 的过期时间 EXPIRE key second,设置 key 的过期时间,经过 second 秒后 key 会被删除。

虽然 Redis 中每个命令都满足原子性,但两个命令组合起来不满足了,例如若设置锁后,程序挂了,来不及设置过期时间,那么这个锁就无法释放了。

所以我们需要把上面两个命令结合起来的命令 SET key value [EX seconds] [PX milliseconds] [NX|XX],其中 NX 是 key 不存在时设置成功,XX 是 key 存在时设置成功。例如:

这里还需要注意,不要让大量的 key 在同一时间过期,因为删除大量的 key 很耗时,会出现卡顿现象。所以我们可以在设置 key 的过期时间时,加上一个随机值来避免。

3、使用 Redis 实现消息队列

使用 List 作为队列,RPUSH 生产消息,LPOP 消费消息。

但是这样有个缺点,就是只能一对一的消息通信,所以还可以使用 pub/sub 主题订阅者模式,发送者(pub)使用 PUBLISH channel message 发送消息,订阅者(sub)使用 SUBSCRIBE channel 接受消息,并且订阅者可以接收任意数量的 channel。

但是消息的发布是无状态的,无法保证可达性。

4、redis string与hash的数据差别

网路传输时候,必须要进行进行序列化,才可以进行网路传输,那么在使用string类型的类型的时候需要进行相关序列化,hash也是要进行相关的系列化,所以会存在很多序列化,在存储的时候hash是可以存储的更加丰富,但是在反序列化的时候,string的反序列化相对较低,而hash的序列化和返序列化是相对hash类更加复杂,所以看业务场景,如果是数据经常修改的那种,为了性能可以使用string,如果是数据不是经常改的那种就可以使用hash,由于hash,存储数据时比较丰富,可以存储多种数据类型

5、Redis主挂了怎么操作

redis提供了哨兵模式,当主挂了,可以选举其他的进行代替,哨兵模式的实现原理,就是三个定时任务监控,

1 每隔10s,每个S节点(哨兵节点)会向主节点和从节点发送info命令获取最新的拓扑结构

2 每隔2s,每个S节点会向某频道上发送该S节点对于主节点的判断以及当前Sl节点的信息,

同时每个Sentinel节点也会订阅该频道,来了解其他S节点以及它们对主节点的判断(做客观下线依据)

3 每隔1s,每个S节点会向主节点、从节点、其余S节点发送一条ping命令做一次心跳检测(心跳检测机制),来确认这些节点当前是否可达

当三次心跳检测之后,就会进行投票,当超过半数以上的时候就会将该节点当做主

6、Redis集群

redis集群在3.0以后提供了ruby脚本进行搭建,引入了糙的概念,

Redis集群内节点通过ping/pong消息实现节点通信,消息不但可以传播节点槽信息,还可以传播其他状态如:主从状态、节点故障等。因此故障发现也是通过消息传播机制实现的,主要环节包括:主观下线(pfail)和客观下线(fail)

主观下线:集群中每个节点都会定期向其他节点发送ping消息,接收节点回复pong消息作为响应。如果通信一直失败,则发                           送节点会把接收节点标记为主观下线(pfail)状态。

客观下线:超过半数,对该主节点做客观下线

主节点选举出某一主节点作为领导者,来进行故障转移。

故障转移(选举从节点作为新主节点)

7、内存淘汰策略

Redis的内存淘汰策略是指在Redis的用于缓存的内存不足时,怎么处理需要新写入且需要申请额外空间的数据。

noeviction:当内存不足以容纳新写入数据时,新写入操作会报错。

allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key。

allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key。

volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key。

volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key。

volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除。

8、缓存穿透(DB的key为空)****

业务系统要查询的数据根本就不存在!当业务系统发起查询时,按照上述流程,首先会前往缓存中查询,由于缓存中不存在,然后再前往数据库中查询。由于该数据压根就不存在,因此数据库也返回空。这就是缓存穿透。

综上所述:业务系统访问压根就不存在的数据,就称为缓存穿透。

危害:如果存在海量请求查询压根就不存在的数据,那么这些海量请求都会落到数据库中,数据库压力剧增,可能会导致系统崩溃

解决方案:

空值缓存

将数据库查询结果为空的key也存储在缓存中。当后续又出现该key的查询请求时,缓存直接返回null,而无需查询数据库,针对这类数据设置一个较短的过期时间,让其自动剔除。

缺点:空值做了缓存,意味着缓存层中存了更多的键,需要更多的内存空间 ( 如果是攻击,问题更严重 )

BloomFilter(布隆过滤器)

它需要在缓存之前再加一道屏障,里面存储目前数据库中存在的所有key。当业务系统有查询请求的时候,首先去BloomFilter中查询该key是否存在。若不存在,则说明数据库中也不存在该数据,因此缓存都不要查了,直接返回null。若存在,则继续执行后续的流程,先前往缓存中查询,缓存中没有的话再前往数据库中的查询。

缺点:这种方法适用于数据命中不高,数据相对固定实时性低(通常是数据集较大)的应用场景,代码维护较为复杂,但是缓存空间占用少。

9、缓存雪崩(服务宕机)

如果缓存因某种原因发生了宕机,那么原本被缓存抵挡的海量查询请求就会像疯狗一样涌向数据库。此时数据库如果抵挡不了这巨大的压力,它就会崩溃。

解决方法:

1 使用缓存集群,保证缓存高可用,redis集群,通过集群方式将数据放置

2 后端服务降级和限流****,专业工具使用Hystrix****,Hystrix是一款开源的“防雪崩工具”,它通过 熔断、降级、限流三个手段来降低雪崩发生后的损失。

Hystrix就是一个Java类库,它采用命令模式,每一项服务处理请求都有各自的处理器。所有的请求都要经过各自的处理器。处理器会记录当前服务的请求失败率。一旦发现当前服务的请求失败率达到预设的值,Hystrix将会拒绝随后该服务的所有请求,直接返回一个预设的结果。这就是所谓的“熔断”。当经过一段时间后,Hystrix会放行该服务的一部分请求,再次统计它的请求失败率。如果此时请求失败率符合预设值,则完全打开限流开关;如果请求失败率仍然很高,那么继续拒绝该服务的所有请求。这就是所谓的“限流”。而Hystrix向那些被拒绝的请求直接返回一个预设结果,被称为“降级”。

后端服务降级和限流:当一个接口请求次数过多,那么就会添加过多数据,可以对服务进行限流,限制访问的数量,这样就可以减少问题的出现

10、缓存击穿(热点数据集中失效)

热点数据集中失效,我们一般都会给缓存设定一个失效时间,过了失效时间后,该数据库会被缓存直接删除,从而一定程度上保证数据的实时性。

但是,对于一些请求量极高的热点数据而言,一旦过了有效时间,此刻将会有大量请求落在数据库上,从而可能会导致数据库崩溃。

解决方案:

互斥锁:此方法只允许一个线程重建缓存,其他线程等待重建缓存的线程执行完,重新从缓存获取数据即可。

永远不过期

此方法有效杜绝了热点 key 产生的问题,但唯一不足的就是重构缓存期间,会出现数据不一致的情况,这取决于应用方是否容忍这种不一致。

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

,其他线程等待重建缓存的线程执行完,重新从缓存获取数据即可。

永远不过期

此方法有效杜绝了热点 key 产生的问题,但唯一不足的就是重构缓存期间,会出现数据不一致的情况,这取决于应用方是否容忍这种不一致。

[外链图片转存中…(img-svaQCGnD-1715886462983)]
[外链图片转存中…(img-kvA8HmJT-1715886462983)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 25
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值