用过redis哪些数据类型?Redis String 类型的底层实现是什么?

Redis 数据类型有哪些?

详细可以查看:数据类型及其应用场景

基本数据类型

  1. String:最常用的一种数据类型,String类型的值可以是字符串、数字或者二进制,但值最大不能超过512MB。一般用于 缓存和计数器
  2. Hash:Hash 是一个键值对集合。存储商品的各个属性
  3. Set:无序去重的集合。Set 提供了交集、并集等方法,对于实现共同好友、共同关注等功能特别方便。
  4. List:有序可重复的集合,底层是依赖双向链表实现的。用于消息队列
  5. SortedSet :有序Set。内部维护了一个score的参数来实现。适用于排行榜和带权重的消息队列等场景。

特殊的数据类型

  1. Bitmap:位图,可以认为是一个以位为单位数组,数组中的每个单元只能存0或者1,数组的下标在 Bitmap 中叫做偏移量。Bitmap的长度与集合中元素个数无关,而是与基数的上限有关。
  2. Hyperloglog。HyperLogLog 是用来做基数统计的算法,其优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且是很小的。典型的使用场景是统计独立访客。
  3. Geospatial :主要用于存储地理位置信息,并对存储的信息进行操作,适用场景如定位、附近的人等。
  4. Stream :一种日志数据结构,适合于存储时间序列数据或消息流。支持高效的消息生产和消费模式,具有持久性和序列化特性。

SortedSet和List异同点?

相同点

  1. 都是有序的;
  2. 都可以获得某个范围内的元素。

不同点:

  1. 列表基于链表实现,获取两端元素速度快,访问中间元素速度慢;
  2. 有序集合基于散列表和跳跃表实现,访问中间元素时间复杂度是OlogN;
  3. 列表不能简单的调整某个元素的位置,有序列表可以(更改元素的分数);
  4. 有序集合更耗内存。

Redis 怎么实现消息队列?

java 复制代码
BLPOP queue 0  //0表示不限制等待时间

BLPOP和LPOP命令相似,唯一的区别就是当列表没有元素时BLPOP命令会一直阻塞连接,直到有新元素加入。

redis可以通过pub/sub主题订阅模式实现一个生产者,多个消费者,当然也存在一定的缺点,当消费者下线时,生产的消息会丢失。

java 复制代码
PUBLISH channel1 hi
SUBSCRIBE channel1
UNSUBSCRIBE channel1 //退订通过SUBSCRIBE命令订阅的频道。

PSUBSCRIBE channel?* 按照规则订阅。 PUNSUBSCRIBE channel?* 退订通过PSUBSCRIBE命令按照某种规则订阅的频道。其中订阅规则要进行严格的字符串匹配,PUNSUBSCRIBE *无法退订channel?*规则。

如何在 Redis 中实现队列和栈数据结构?

可以通过 List 类型 来实现 队列 和 栈

实现队列(FIFO):队列是一种 先进先出(FIFO)的数据结构。在Redis中,可以使用 PUSH 和 RPOP命令组合来实现队列。LPUSH 向列表的左侧推入元素,而 RPOP从列表的右侧弹出元素,这样可以保证最先进入的元素最先被弹出

实现栈(LIFO):栈是一种 后进先出(LIFO)的数据结构。在Redis 中,可以使用 LPUSH和 LPoP命令组合来实现栈。LPUSH 向列表的左侧推入元素,而 LPoP从列表的左侧弹出元素,这样可以保证最后进入的元素最先被弹出。

Redis 怎么实现延时队列

使用sortedset,拿时间戳作为score,消息内容作为key,调用zadd来生产消息,消费者用zrangebyscore指令获取N秒之前的数据轮询进行处理。

如何使用 Redis 快速实现排行榜?

使用 Redis 实现排行榜的方式主要利用 Sorted Set(有序集合),它可以高效地存储、更新、以及获取排名数据。实现排行榜的主要步骤:

  1. 使用 Sorted Set 存储分数和成员:使用 Redis 的 ADD命令,将用户和对应的分数添加到有序集合中。例如:add leaderboard 1000 user1,将用户 user1 的分数设置为 1000。
  2. 获取排名:使用 ZRANK命令获取某个用户的排名。例如:zrank leaderboard user1,返回用户user1 的排名(从0开始)。
  3. 获取前 N 名:使用 ZREVRANGE 命令获取分数最高的前N名。例如:REVRANGE leaderboard 0 9 WITHSCORES ,获取排行榜前 10 名用户及其分数。
  4. 更新分数:如果用户的分数需要更新,可以使用 ZINCRBY 命令对其分数进行加减操作。例如:ZINCRBY leaderboard 500 user1,将用户 user1 的分数增加 500。

如何使用 Redis 快速实现布隆过滤器?

可以通过使用 位图(Bitmap)或使用 Redis 模块 RedisBloom。

  • 使用位图实现布隆过滤器:使用 Redis 的位图结构 SETBIT 和 GETBIT 操作来实现布隆过滤器。位图本质上是一个比特数组,用于标识元素是否存在对于给定的数据,通过多个 哈希函数 计算位置索引,将位图中的相应位置设置为 1,表示该元素可能存在。
  • 使用 RedisBloom 模块:Redis 提供了一个官方模块 RedisBloom,封装了哈希函数、位图大小等操作,可以直接用于创建和管理布隆过滤器。使用 BF.ADD 来向布隆过滤器添加元素,使用 BF.EXISTS 来检查某个元素是否可能存在,

如何使用 Redis 统计大量用户唯一访问量(UV)?

Redis 中 HyperLogLog 结构,可以快速实现网页UV、PV 等统计场景。它是一种基数估算算法的概率性数据结构,可以用极少的内存统计海量用户唯一访问量的近似值。

Set 也可以实现,用于精确统计唯一用户访问量,但是但当用户数非常大时,内存开销较高。

Redis 中的 Geo 数据结构是什么?

Redis中的 Geo(Geoloaton的简写形式,代表地理坐标) 数据结构主要用于地理位置信息的存储,通过这个结构,可以方便地进行地理位置的存储、检索、以及计算地理距离等课作,GeO 数据结内存层使用了 Sorted set, 并结合了Geohash 编码算法来对地理位置进行处理。

Redis String 类型的底层实现是什么?(SDS)

Redis 中的 Sting 类型底层实现主要基于 SDS(Simple Dynamic string 简单动态字符串)结构,并结合 int、embstr、raw 等不同的编码方式进行优化存储。

详情请看这篇文章:Redis是如何高效存储与访问数据?

Redis 中的 Ziplist 和 Quicklist 数据结构的特点是什么?

Ziplist:

  • 简单、紧凑、连续存储,适用于小数据量场景,但对大量数据或频繁的修改操作不太友好。
  • 适合小数据量场景,例如短列表、小哈希表等,因为它的内存紧凑,可以大幅减少内存使用

Quicklist:

  • 通过将链表和 Ziplist 结合,既实现了链表的灵活操作,又能节省内存,在 Redis 3.2 之后成为 List 的默认实现。
  • Quicklist是为了替代纯而设计的,适用于需要频繁对列表进行插入、删除、查找等提作的场景,并目数据量可能较大,它在存储多个元素时,既保留了链表的灵活性,又具备压缩列表的内存优势

Redis Zset 的实现原理是什么?

Redis 中的Zset(有序集合,Sorted set)是一种由 跳表 (Skip List)和哈希表 (Hash Table)组成的数据结构,Zset 结合了集合 (Set)的特性和排序功能,能够存储具有唯一性的成员,并根据成员的分数 (score) 进行排序

ZSet 的实现由两个核心数据结构组成:

  1. 跳表(Skip List):用于存储数据的排序和快速查找。
  2. 哈希表(Hash Table):用于存储成员与其分数的映射,提供快速查找

当 Zset 元素数量较少时,Redis 会使用压缩列表(Zip List)来节省内存

  • 即元素个数≤ zset-max-ziplist-entries(默认 128)
  • 元素成员名和分值的长度 ≤ zset-max-ziplist-value(默认 64 字节)

如果任何一个条件不满足,Zset 将使用 跳表 +哈希表 作为底层实现,

Redis 的有序集合底层为什么要用跳表,而不用平衡树、红黑树或者 B+树?

这道面试题很多大厂比较喜欢问,难度还是有点大的。

  • 平衡树 vs 跳表:平衡树的插入、删除和查询的时间复杂度和跳表一样都是 O(log n)。对于范围查询来说,平衡树也可以通过中序遍历的方式达到和跳表一样的效果。但是它的每一次插入或者删除操作都需要保证整颗树左右节点的绝对平衡,只要不平衡就要通过旋转操作来保持平衡,这个过程是比较耗时的。跳表诞生的初衷就是为了克服平衡树的一些缺点。跳表使用概率平衡而不是严格强制的平衡,因此,跳表中的插入和删除算法比平衡树的等效算法简单得多,速度也快得多。

  • 红黑树 vs 跳表:相比较于红黑树来说,跳表的实现也更简单一些,不需要通过旋转和染色(红黑变换)来保证黑平衡。并且,按照区间来查找数据这个操作,红黑树的效率没有跳表高。

  • B+树 vs 跳表:B+树更适合作为数据库和文件系统中常用的索引结构之一,它的核心思想是通过尽可能少的 IO 定位到尽可能多的索引来获得查询数据。对于 Redis 这种内存数据库来说,它对这些并不感冒,因为 Redis 作为内存数据库它不可能存储大量的数据,所以对于索引不需要通过 B+树这种方式进行维护,只需按照概率进行随机维护即可,节约内存。而且使用跳表实现 zset 时相较前者来说更简单一些,在进行插入时只需通过索引将数据插入到链表中合适的位置再随机维护一定高度的索引即可,也不需要像 B+树那样插入时发现失衡时还需要对节点分裂与合并。

Redis 中跳表的实现原理是什么?

跳表主要是通过多层链表来实现,底层链表保存所有元素,而每一层链表都是下一层的子集。

插入时,首先从最高层开始查找插入位置,然后随机决定新节点的层数,最后在相应的层中插入节点并更新指针

删除时,同样从最高层开始查找要删除的节点,并在各层中更新指针,以保持跳表的结构。

查找时,从最高层开始,逐层向下,直到找到目标元素或确定元素不存在。查找效率高,时间复杂度为 O(logn)

Redis中的跳表是两步两步跳的吗?

如果采用新增节点或者删除节点时,来调整跳表节点以维持比例2:1的方法的话,显然是会带来额外开销的。

跳表在创建节点时候,会生成范围为[0-1]的一个随机数,如果这个随机数小于 0.25(相当于概率 25%),那么层数就增加 1 层,然后继续生成下一个随机数,直到随机数的结果大于 0.25 结束,最终确定该节点的层数。因为随机数取值在[0,0.25)范围内概率不会超过25%,所以这也说明了增加一层的概率不会超过25%。这样的话,当插入一个新结点时,只需修改前后结点的指针,而其它结点的层数就不需要随之改变了,这样就降低插入操作的复杂度。

c 复制代码
// #define ZSKIPLIST_P 0.25
int zslRandomLevel(void) {
    static const int threshold = ZSKIPLIST_P*RAND_MAX;
    int level = 1; //初始化为一级索引
    while (random() < threshold)
        level += 1;//随机数小于 0.25就增加一层
	//如果level 没有超过最大层数就返回,否则就返回最大层数
    return (level<ZSKIPLIST_MAXLEVEL) ? level : ZSKIPLIST_MAXLEVEL;
}

Redis遇到哈希冲突怎么办?

当有两个或以上数量的键被分配到了哈希表数组的同一个索引上面时, 我们称这些键发生了冲突(collision)。

关于解决hash冲突问题可以看这篇文章:解决哈希冲突的三种方法

而redis是先通过拉链法 解决,再通过rehash来解决hash冲突问题的,即再hash法,只不过redis的hash使渐进式hash

rehash原理?

渐进式 rehash 步骤如下:

  1. 先给哈希表 2分配空间;
  2. 在 rehash 进行期间,每次哈希表元素进行新增、删除、查找或者更新操作时,Redis 除了会执行对应的操作之外,还会顺序将哈希表 1中索引位置上的所有 key-value 迁移到哈希表 2上;
  3. 随着处理客户端发起的哈希表操作请求数量越多,最终在某个时间点会把哈希表 1的所有 key-value 迁移到哈希表 2,从而完成 rehash 操作。

这样就把一次性大量数据迁移工作的开销,分摊到了多次处理请求的过程中,避免了一次性 rehash 的耗时操作。

在进行渐进式 rehash 的过程中,会有两个哈希表,所以在渐进式 rehash 进行期间,哈希表元素的删除、查找、更新等操作都会在这两个哈希表进行。比如,在渐进式 rehash 进行期间,查找一个 key 的值的话,先会在哈希表 1里面进行查找,如果没找到,就会继续到哈希表 2 里面进行找到。新增一个 key-value 时,会被保存到哈希表 2里面,而哈希表 1则不再进行任何添加操作,这样保证了哈希表 1的 key-value 数量只会减少,随着 rehash 操作的完成,最终哈希表 1就会变成空表。

rehash的触发条件?

负载因子 = 哈希表已保存节点数量/哈希表大小

触发 rehash 操作的条件,主要有两个:

  • 当负载因子大于等于 1 ,并且 Redis 没有在执行 bgsave 命令或者 bgrewiteaof 命令,也就是没有执行 RDB 快照或没有进行 AOF 重写的时候,就会进行 rehash 操作。
  • 当负载因子大于等于 5 时,此时说明哈希冲突非常严重了,不管有没有有在执行 RDB 快照或 AOF 重写,都会强制进行 rehash 操作

一个REDIS实例最多能存放多少KEYS

redis 的每个实例最多可以存放约 2^32 - 1 个keys,即大约 42 亿个keys。这是由 Redis 内部使用的哈希表实现决定的,它使用 32 位有符号整数作为索引。Redis 使用的哈希函数和负载因子等因素也会影响实际可存放键的数量。

需要注意的是,尽管 Redis 允许存储数量庞大的键,但在实践中,存储过多的键可能会导致性能下降和内存消耗增加。因此,在设计应用程序时,需要根据实际需求和硬件资源来合理规划键的数量,避免过度使用 Redis 实例造成负担。如果需要存储更多的键值对,可以考虑使用 Redis 集群或分片技术,以扩展整体存储容量。

相关推荐
Momentary_SixthSense2 小时前
RESP协议
java·开发语言·javascript·redis·后端·python·mysql
努力的小郑2 小时前
放弃使用 Redis 事务!这才是它正确的打开方式!
数据库·redis
.Shu.15 小时前
Redis Reactor 模型详解【基本架构、事件循环机制、结合源码详细追踪读写请求从客户端连接到命令执行的完整流程】
数据库·redis·架构
lssjzmn1 天前
🚀如何基于Redis的ZSet数据结构设计一个通用的,简单的,可靠的延迟消息队列,以RedisTemplate为例
redis
jakeswang1 天前
应用缓存不止是Redis!——亿级流量系统架构设计系列
redis·分布式·后端·缓存
.Shu.1 天前
Redis zset 渐进式rehash 实现原理、触发条件、执行流程以及数据一致性保障机制【分步源码解析】
数据库·redis·缓存
君不见,青丝成雪1 天前
大数据技术栈 —— Redis与Kafka
数据库·redis·kafka
悟能不能悟1 天前
排查Redis数据倾斜引发的性能瓶颈
java·数据库·redis
切糕师学AI1 天前
.net core web程序如何设置redis预热?
redis·.netcore