Redis八股学习日记:数据结构;跳表的底层;Reids的事务机制

Redis中的常见数据结构由哪些

Redis中数据结构由五个部分组成:

  • String
  • List
  • Hash
  • Set
  • Zset
    接下来我们进行详细了解:

String 是最基础的类型,能存储文本数字,二进制,最大位512MB。典型场景:缓存用户会话,页面数据,计数器。像文章阅读量,点赞数,直接用INCR原子递增就行。
Hash 本质是键值对集合,适合存储对象属性 。像一个商品数据,ID作为key,价格,库存,名称都可以存储在一个hash中,改个字段不用整体覆盖。
List 是有序的字符串列表,底层是双向链表,支持两端操作。常见的场景是消息队列。LPUSH生产消息,RPOP消费消息。
Set 是无序不重复的集合,查找和去重效率很高 。适合做标签系统,记录某个页面的独立访客,共同关注等需要去重或集合运算的场景。
Zset 和Set类似,但每个元素自带 一个score分数来排序,底层用跳表来实现。适合用力啊创建一个排行榜,按score排序后直接取TopN。

拓展

随着Redis版本更新,又出现了四种高级的数据结构;

  • bitmap
  • hyperloglog
  • geo
  • stream

bitmap用位来存储数据,每个bit之占0或1,空间利用率极高。当统计1000万用户的签到状态,一个用户只占1bit。用setbit设置状态,用getbit读取状态:

java 复制代码
SETBIT user:sign:202409 12345 1  # 用户 12345 在 9 月签到
GETBIT user:sign:202409 12345    # 查询签到状态

HyperLogLog 是一种用来做"基数统计"(去重计数)的概率型数据结构,用极小的空间估算大量数据的去重数量。适合统计网站UV(独立访客数量)这种对精度要求不高但数据量大的场景

java 复制代码
PFADD page:uv user1 user2 user3  # 记录访问用户
PFCOUNT page:uv                  # 估算独立用户数

geo用来存地理位置信息,支持经纬度存储和 空间查询。底层是用Zset实现的,经纬度被编码成score。场景:附近的人,外卖配送距离。

java 复制代码
GEOADD riders 116.403 39.915 "rider001"   # 存骑手位置
GEORADIUS riders 116.4 39.9 5 km          # 查 5 公里内的骑手

stream是专门为消息队列设计的数据结构。相比list做队列;Stream多了两个关键性:自动生成全局唯一消息ID,支持消费组模式。

跳表的实现原理

在之前的学习中我们了解了Zset的底层是跳表并非红黑树。

我们现在来了解一下跳表的原理:

跳表本质上是一个多层链表 。底层链表保存所有元素,上层链表是下层的子集,通过这种分层索引结构把链表的O(n)查找优化到O(logn)
查询的过程:

查找的时候从最高层开始,先往右走,遇到比目标值大的节点就往下一层走,重复直到找到目标或者确定不存在。比如查找50,从顶层的10开始,跳到40发现比50小继续往右,发现下个目标是70比目标大就往下一层走,在第二层从40往右一步就找到50了。

插入过程:

先用查找的方式定位到插入位置,然后随机决定新节点要建几层索引。Redis用25%概率往上加一层。导致了大部分节点只在底层,少部分节点会出现在高层索引中。
删除过程

先找到节点,然后把这个节点在各层的前后指针都接上,跟普通链表删除一样,只是要在多层都操作一遍。

知道了跳表原理,我们来区分一下为什么Redis用跳表而不用红黑树

  1. 实现简单,红黑树的左旋右旋太复杂,跳表就是多层链表,好写好调试。
  2. 范围查询效率高,Zset通常要取排名10-20的数据,跳表在底层链表上顺着走就行,红黑树得中序遍历。
  3. 内存占用可靠,跳表每个节点平均1.33个指针,红黑树每个节点固定3个指针。

Redis的性能瓶颈时如何处理

当Redis出现性能瓶颈时,核心处理方法是:分摊压力,从单机到集群逐渐递进。

  • 垂直扩容:先看单机资源有没有用满,内存不够就加内存,CPU频率能提就提。
  • 读写分离:写请求还是走主节点,但把读请求分流到从节点。一主三从的架构,读QPS直接翻3倍。适合读多写少的场景,像商品详细页,用户信息查询。
  • Redis集群分片数据量超过单机内存上限,或者写请求压力太大,尝试分片集群。16384个槽位分散到多个主节点,写压力也能横向扩展。
  • 多级缓存 :在应用层加一层本地缓存,Caffeine,把热点数据兜在本地,压根不走网络。像秒杀场景,库存这种热key必须本地缓存顶着。

拓展

如何定位性能瓶颈在哪

用Redis自带的监控命令:

java 复制代码
# 查看实时性能指标
redis-cli info stats

# 慢查询日志,超过 10ms 的命令都记下来
config set slowlog-log-slower-than 10000
slowlog get 10

# 内存分析
memory doctor
memory stats

我们来看看常见的性能瓶颈右哪些:

  • 内存不足:used_memory超过maxmemory,开始频繁淘汰数据,响应时间飙升。
  • CPU打满:大key操作或者复杂命令把CPU吃满,其他请求只能排队等着。比如对一个百万元素的Set执行SMEMBERS,单次操作就能卡住几秒。
  • 网路带宽:单机网卡吞吐量优先,一旦数据量大,网络就成了瓶颈
  • 持久化阻塞:AOF重写或者RDB快照时,fork子进程拷贝页表,内存越大fork越慢。

Redis中的Hash是什么

Redis 的 Hash 是一种键值对集合,能把多个字段和值存在同一个 key下面,特别适合存对象的属性。比如存用户信息,用user:1001作为key,里面放name、age、city这些字段,改单个字段不用整体覆盖,比用String存JSON灵活多了。

Hash底层有两种编码方式。数据量小的适合用listpack存储,紧凑省内存;数据量大了自动切换成hashtable,查询效率O(1).切换阈值默认为512个字段或者单个值超过64字节

拓展

渐进式rehash

Redisd哈希表扩容不是一次搬完的,而是分多次慢慢搬,这就是渐进式rehash。

因为Redis是单线程,如果一次性搬几十万个key,这段事件其他请求都得等待,服务直接等待。

流程如下:

1)触发扩容后,给ht[1]分配空间,大小是第一个大于等于ht[O].used*2 的2次方幂。比如原表的值是1024,那个其扩容之后的新表大小就是 2048。

2)把 rehashidx的值从-1设成0,标记开始rehash

3)每次对 Hash 做增删改查,顺便把 rehashidx 对应的桶从 ht[O] 搬到ht[1],然后rehashidx++4)新插入的数据直接写 ht[11,查询的时候两个表都要查

5)搬完之后 ht[0] 和 ht[1]指针互换,rehashidx 重置为-1

Redis和Memcached有哪些区别

Redis 和 Memcached 都是内存级别的缓存系统,但 Redis 功能更丰富,Memcached 更轻量纯粹。数据结构方面,Redis 支持 String、List、Set、Sorted Set、Hash 五种基础结构,还有 HyperLogLog、Bitmap、Geo 等扩展类型;Memcached 只支持简单的 key-value 字符串存储。

持久化方面,Redis 提供 RDB 快照和 AOF日志两种方式,服务重启数据还在;Memcached 压根没有持久化,一重启数据全没了。

分布式方面,Redis 原生支持主从复制、哨兵和 Redis Cluster 集群模式;Memcached 服务端没有分布式逻辑,分片全靠客户端自己算hash 来分发。

功能方面,Redis 支持发布订阅、Lua脚本、事务、Stream消息队列等;Memcached 就是纯粹的缓存,啥扩展功能都没有。

Redis支持事务吗

我们都知道MySQL的事务机制,那Redis和MySQL的事务由什么区别呢?

Redis事务:只保证命令打包执行不被其他客户端插队,不支持回滚

Redis事务用MULTI,EXEC,WATCH,DISCARD四个命令实现:

  1. multi开启事务,之后发的命令不会立刻执行,而是进入一个队列排队
  2. 队列里可以塞任意多条命令,Redis会回复queued标识入队成功
  3. EXEC一次性把队列里的命令全部执行,期间不会被其他客户端的命令打断
  4. 如果中途不想执行了,用DISCARD丢弃整个队列
  5. WATCH可以在MULTI之前监视某些key,如果这些key在EXEC之前被别人改了,整个事务作废。

一句话总结:

Redis 事务通过 MULTI 开启,将命令入队,最后用 EXEC 一次性顺序执行,期间不会被其他命令打断。

注意的是:MySQL事务执行到一半出错可以回滚,Redis不行。Redis事务里某条命令执行失败剩下的命令照样继续跑,已经执行的也不会撤销

在实际工程中,一般不会使用Redis事务,选择lua脚本更好用。

拓展

Redis事务与MySQL事务的区分

特性 Redis 事务 MySQL 事务
原子性 打包执行不被打断,但不支持回滚 完整的原子性,支持回滚
隔离性 单线程串行执行,天然隔离 支持 4 种隔离级别
持久性 取决于 AOF 配置 通过 redo log 保证
一致性 不保证,部分失败不回滚 通过回滚保证
性能 极高,无额外开销 需要记录日志,有开销
相关推荐
xiaoliuliu123451 小时前
redis-windows-7.2.3安装步骤详解(附Redis配置与Windows服务注册)
数据库·windows·redis
田梓燊1 小时前
力扣:94.二叉树的中序遍历
数据结构·算法·leetcode
khalil10201 小时前
代码随想录算法训练营Day-38动态规划06 | 322. 零钱兑换、279.完全平方数、139.单词拆分、多重背包、总结
数据结构·c++·算法·leetcode·动态规划
自我意识的多元宇宙1 小时前
数据结构----希尔排序
数据结构·算法·排序算法
炽烈小老头1 小时前
【每天学习一点算法 2026/04/29】最长连续序列
学习·算法
库奇噜啦呼1 小时前
【iOS】源码学习-类与对象底层原理
学习·ios·cocoa
我不是懒洋洋1 小时前
【数据结构】二叉树OJ(单值二叉树、检查两棵树是否相同、对称二叉树、二叉树的前序遍历、另一颗树的子树)
c语言·数据结构·c++·经验分享·算法·leetcode·visual studio
yuzhiboyouye2 小时前
java redis(缓存)
java·redis·缓存
地球资源数据云2 小时前
2015年中国30米分辨率沼泽湿地空间分布数据集
大数据·数据结构·数据库·人工智能·机器学习