回答重点Redis 中的 ZSet(有序集合,Sorted Set) 是一种由 跳表(Skip List) 和 哈希表(Hash Table) 组成的数据结构。ZSet 结合了集合(Set)的特性和排序功能,能够存储具有唯一性的成员,并根据成员的分数(score)进行排序。
ZSet 的实现由两个核心数据结构组成:
跳表(Skip List):用于存储数据的排序和快速查找。
哈希表(Has...
回答重点为什么不用红黑树?1)相比红黑树而言实现简单
跳表基于多层链表实现,通过概率算法动态生成索引层级,没有左旋右旋等操作,逻辑理解上更为简单。而红黑树需要复杂的平衡操作(旋转)来维护结构,代码实现复杂度较高,理解门槛更高。
2)范围查询更高效
范围查询跳表可以通过 O(logn) 的时间复杂度定位起点,然后在原始的链表中往后遍历即可。
红黑树从结构上不支持范围查询。
3)结构更灵活
跳表...
回答重点Redisson 的看门狗(watchdog)主要用来避免 Redis 中的锁在超时后业务逻辑还未执行完毕,锁却被自动释放的情况。它通过定期刷新锁的过期时间来实现自动续期。
主要原理:
定时刷新:如果当前分布式锁未设置过期时间,Redisson 基于 Netty 时间轮启动一个定时任务,定期向 Redis 发送命令更新锁的过期时间,默认每 10s 发送一次请求,每次续期 30s。
...
回答重点Redis 作为一款高性能的内存数据库,其源码中包含了许多巧妙的设计。这些设计不仅体现了高效的数据处理和管理能力,也为后续的系统扩展和性能优化奠定了基础。
巧妙的设计主要包括:线程模型、数据结构、共享对象池、过期设计、数据持久化设计等。
线程模型Redis 使用单线程模型来处理所有的客户端请求。
虽然看似单线程简单,但这种设计减少了上下文切换和锁的开销,避免了多线程编程中的复杂性。
...
回答重点Redisson 是基于 Redis 实现的分布式锁,实际上是使用 Redis 的原子操作来确保多线程、多进程或多节点系统中,只有一个线程能获得锁,避免并发操作导致的数据不一致问题。
1)锁的获取:
Redisson 使用 Lua 脚本,利用 exists + hexists + hincrby 命令来保证只有一个线程能成功设置键(表示获得锁)。
同时,Redisson 会通过 p...
回答重点若锁在未完成逻辑前就过期,此时可能会产生数据不一致的问题。因为锁过期了,此时如果再出现一个客户端争抢锁,即可拿到锁然后同时进行业务操作,这等于锁失效了。
此时可以在逻辑执行过程中定期续期锁,确保锁在处理过程中不会过期。
扩展知识看门狗机制业界出了一个看门狗机制来防止这种情况的产生。
理论很简单,在抢到锁之后,后台会有一个任务,定时向 redis 进行锁的续期。比如锁的过期时间是...
回答重点Redis 的订阅发布功能(Publish/Subscribe,简称 Pub/Sub),是一种消息通信机制,用于在不同客户端之间实现消息的实时传递和广播。使用 Pub/Sub 模型,客户端可以订阅一个或多个频道,当有其他客户端向这些频道发布消息时,所有订阅了该频道的客户端都会立即收到消息。
主要功能包括:
发布(Publish):某个客户端向指定的频道...
回答重点Red Lock,又称为红锁,是一种分布式锁的实现方案,旨在解决在分布式环境中使用 Redis 实现分布式锁时的安全性问题。
一般情况下,我们在生产环境会使用主从+哨兵方式来部署 Redis。
如果我们正在使用 redis 分布式锁,此时发生了主从切换,但从节点上不一定已经同步了主节点的锁信息。
所以新的主节点上可能没有锁的信息。此时另一个业务去加锁,一看锁还没被占,于是抢到了锁开始...
回答重点Redis 中的 String 类型底层实现主要基于 SDS(Simple Dynamic String 简单动态字符串)结构,并结合 int、embstr、raw 等不同的编码方式进行优化存储。
扩展知识C 语言字符串的缺陷Redis 为什么没有使用 C 标准库提供的字符串,而是实现了一种动态字符串?因为 C 语言的字符串本质上就是 char* 的字符数组,存在一定缺陷:
C 语...
回答重点Redis 中 HyperLogLog 结构,可以快速实现网页 UV 、PV 等统计场景。它是一种基数估算算法的概率性数据结构,可以用极少的内存统计海量用户唯一访问量的近似值。
Set 也可以实现,用于精确统计唯一用户访问量,但是但当用户数非常大时,内存开销较高。
扩展知识HyperLogLog 使用介绍HyperLogLog 具有极小的内存占用(每个 HyperLogLog 结...
回答重点Redis 字符串能存储的最大容量是 512 MB 的数据,可以查看官方文档:
无论是网络传输、内存分配还是字符串操作,大字符串都会增加 Redis 服务器的负载。
且过大的字符串在 GET、SET、APPEND、STRLEN 等操作都会导致性能瓶颈。
所以官方给字符串的大小做了限制,防止单个键值对占用过多的内存,影响整体性能和稳定性。
扩展知识
661. Redis Strin...
回答重点Redis 中的 Geo(Geolocation 的简写形式,代表地理坐标) 数据结构主要用于地理位置信息的存储。通过这个结构,可以方便地进行地理位置的存储、检索、以及计算地理距离等操作。Geo 数据结构底层使用了 Sorted Set,并且结合了 Geohash 编码算法来对地理位置进行处理。
它是 Redis 2.2 版本后新增的数据类型
扩展知识Geo 常用命令GEOADD...
回答重点Redis 主从的几种常见拓扑结构如下(忽略哨兵):
1)一主多从:
这是最基本的拓扑结构,包含一个主节点和多个从节点。所有写操作都在主节点上执行,而读操作可以在从节点上进行,以提高读取速度和负载均衡。
2)树状主从结构(级联):
从节点也可以作为其他从节点的主节点。这样形成了一个层次结构,主节点负责写操作,而从节点负责读操作,并将数据再次复制到更下一级的从节点。
...
为什么 EMBSTR 的阈值大小是 44 个字节?这个问题有几个关键点:
1)Redis 使用的是 jemalloc 作为内存分配器。
2)jemalloc 是以 64 字节作为内存单位进行内存分配的,如果超过了 64 字节,即超过了一个内存单元,使用的就是 raw 编码,反之使用的就是 EMBSTR 编码。
3)核心就是这个 64 字节,围绕 64 字节这个关键点来分析。Redis 的字符...
回答重点原生批处理命令(MSET、MGET) 和 Pipeline 都可以用于一次性处理多个命令,但它们在实现方式和应用场景上有所不同:
1)MSET / MGET(原生批处理命令):
MSET 和 MGET 是 Redis 提供的原生批处理命令,用于批量设置和获取多个键值。
它们是 单个命令,可以一次操作多个键值对,因此只需要进行一次网络往返,适合对多个键值进行原子性的读写操作...
回答重点Ziplist:
简单、紧凑、连续存储,适用于小数据量场景,但对大量数据或频繁的修改操作不太友好。
适合小数据量场景,例如短列表、小哈希表等,因为它的内存紧凑,可以大幅减少内存使用
Quicklist:
通过将链表和 Ziplist 结合,既实现了链表的灵活操作,又能节省内存,在 Redis 3.2 之后成为 List 的默认实现。
Quicklist 是为了替代纯链表而设计的...
回答重点可以通过 List 类型 来实现 队列 和 栈 :
实现队列(FIFO):
队列是一种 先进先出(FIFO) 的数据结构。在 Redis 中,可以使用 LPUSH 和 RPOP 命令组合来实现队列。
LPUSH 向列表的左侧推入元素,而 RPOP 从列表的右侧弹出元素,这样可以保证最先进入的元素最先被弹出。
实现栈(LIFO):
栈是一种 后进先出(LIFO) 的数据结构。在 ...
回答重点
Redis 的复制延迟是指从节点同步主节点数据时可能出现时间延迟。在读写分离场景,这个延迟会导致明明写入了数据,但是去从节点查的时候没查到。
可能原因如下:
1)网络原因
可能是带宽不足,或者网络抖动导致同步的延迟。
不过一般内网情况下不会产生这个问题。
2)主节点负载过高
主节点接收到大量的写操作,在处理客户端请求的同时,还需向从节点发送复制数据。如果主节点负载较高时,来不及处...
回答重点1)Redis Cluster 是 Redis 集群,提供自动分片功能,将数据自动分布在多个节点上,支持自动故障转移。如果一个节点失败,集群会自动重新配置和平衡,不需要外部介入,因为它内置了哨兵逻辑。
2)Sentinel 是哨兵,主要用于管理多个 Redis 服务器实例来提高数据的高可用性。当主节点宕机,哨兵会将从节点提升为主节点,它并不提供数据分片功能。
如果需要处理大量数据并进...
回答重点Redis 的事务跟严格意义上的关系型数据库事务不一样,先复习下数据库事务 ACID 的定义:
原子性(Atomicity):事务中的所有操作要么全部成功,要么全部失败,且可以回滚到事务开始前的状态。
一致性(Consistency):事务执行前后,数据库必须保持一致的状态。
隔离性(Isolation):事务的执行是隔离的,事务之间不会相互干扰。支持不同的隔离级别(如读未提交、读...
回答重点Redis 的内存碎片化是指内存使用中出现小块空间被闲置,无法被有效利用的现象。
Redis 默认使用 jemalloc 作为内存分配器,它是按照固定大小来分配内存的,比如实际需要 8kb 的内存,分配器给了 12kb。
那么多余的 4kb 其实就无法被利用上了,它就叫内存碎片。
且频繁创建和删除大量数据的时候,会导致内存块大小和位置不连续,内存碎片会变多。
可以通过 INFO m...
回答重点Redis 的 VM 机制(Virtual Memory)曾经是 Redis 早期版本(2.0 之前)的一部分,用于将部分数据存储在磁盘上,以扩展内存数据库的容量。当内存不足时,Redis 会将冷数据(不经常访问的数据)移到磁盘,并将热数据(经常访问的数据)保留在内存中。
通过这种方式,Redis 可以处理比实际物理内存更多的数据。
虽然能使用的数据变多了,但是数据存到磁盘在获取会显...
回答重点ListPack 是 Redis 内部的一种数据结构,用于高效存储短小的字符串或整数集合。它是一种紧凑型的序列化数据结构,旨在减少内存占用和提升性能。为了尽可能紧凑地存储数据,因此它没有使用 Redis 常见的对象模型,而是直接以字节序列的形式存储数据。
ListPack 是 Redis 6.0 引入的新数据类型,在 List、Hash 和 ZSet 的内部实现中使用。
ListPa...
回答重点Redis 集群将数据分布到 16384 个哈希槽(slots) 中,每个键通过哈希函数计算出一个槽位编号,然后根据槽位编号定位到具体的节点,具体是使用 CRC16 哈希函数计算键的哈希值,然后对 16384 取模,得到哈希槽编号(范围是 0 到 16383)。
扩展知识Redis 集群节点的通信Redis 集群节点在一开始的时候,只知道自己的槽位,不知道其他节点槽位的,然后它们之间...
这种题目都是开放性的,面试过程中也不奢望聊出所有的设计细节,仅仅需要抛出一些大致的设计需求与要点,然后简单的方案实现思路即可。
关于文件上传系统有几个最主要的核心点需要解决:
1)如何支持超大文件上传2)避免重复文件存储,节省空间3)限流问题
大文件上传假设有个 10 G 的文件需要上传,正常情况下是将文件转成流传到后端,如果不做任何处理,前端一直传,后端存储这些流直到传递完成,后端非常容易...
这个问题我觉得可以从 HashMap 的一些关键点入手,例如 hash函数、如何处理冲突、如何扩容。
可以先说下你对 HashMap 的理解。
比如:HashMap 无非就是一个存储 <key,value> 格式的集合,用于通过 key 就能快速查找到 value。
基本原理就是将 key 经过 hash 函数进行散列得到散列值,然后通过散列值对数组取模找到对应的 index 。...
一般在分库分表场景,就会有分布式 ID 的需求,因为需要有一个唯一标识来标记一个订单或者其他类似的数据等。
全局唯一 ID 有很多种实现,例如 UUID ,优势就是本地生成,很简单。但它是无序的,如果需要将唯一 ID 作为主键,则 UUID 不合适,首先是太长了,其次无序插入会导致数据页频繁分裂,性能不好。
在回答这个面试题的时候可以先提下 UUID,简单说下优缺点(UUID 的缺点其实侧面...
这种设计类问题还是一样,先说下理解,表明你是知道这个东西的用处和原理的,然后开始 阐述。
基本上就是按照现有的设计来说,再添加一些个人见解。
线程池讲白了就是存储线程的一个容器,池内保存之前建立过的线程来重复执行任务,减少创建和销毁线程的开销,提高任务的响应速度,并便于线程的管理。
我个人觉得如果要设计一个线程池的话得考虑池内工作线程的管理、任务编排执行、线程池超负荷处理方案、监控。
初始化...
设计类题目要先从大局上讲出需要设计的东西的重点,然后再等待面试官的继续提问,深挖。
回答:
首先我们需要明确地提出消息中间件的几个重要角色,分别是生产者、消费者、Broker、注册中心。
简述下消息中间件数据流转过程,无非就是生产者生成消息,发送至 Broker,Broker 可以暂缓消息,然后消费者再从 Broker 获取消息,用于消费。
而注册中心用于服务的发现包括:Broker 的发现...
业务场景一般在即时通讯项目(比如聊天室)中,我们会采用下拉分页的方式让用户加载历史消息记录。
区别于标准分页每次只展示当前页面的数据,下拉分页加载是 增量加载 的模式,每次下拉时会请求加载一小部分新数据,并放到已加载的数据列表中,从而形成无限滚动的效果,确保用户体验流畅。
比如用户有 10 条消息记录,以 5 条为单位进行分页,刚进入房间时只会加载最新的 5 条消息:
下拉后,会加载历史...