禅与计算机 禅与计算机
首页
  • Java基础

    • 一文搞懂Java核心技术
    • Java面向对象知识点大总结,建议收藏
    • 聊聊Java中的异常
    • 聊聊Java中的常用类String
    • 万字长文带你细聊Java注解本质
    • 来聊聊Java的反射机制
    • 深入解析Java泛型的魅力与机制
    • Java集合框架深度解析与面试指南
    • Java常用集合类HashMap深度解析
    • LinkedHashMap源码到面试题的全解析
    • 深入解析CopyOnWriteArrayList的工作机制
    • Java基础IO总结
    • Java三大IO模型小结
    • Java BIO NIO AIO详解
    • Java进阶NIO之IO多路复用详解
    • Java8流式编程入门
    • 一文速通lambda与函数式编程
    • Java8函数式方法引用最佳实践
    • Java异常:从原理到实践
  • Java并发编程

    • Java并发编程基础小结
    • 深入理解Java中的final关键字
    • 浅谈Java并发安全发布技术
    • 浅谈Java并发编程中断的哲学
    • Java线程池知识点小结
    • 浅谈Java线程池中拒绝策略与流控的艺术
    • synchronized关键字使用指南
    • 深入源码解析synchronized关键字
    • 详解JUC包下的锁
    • 详解并发编程中的CAS原子类
    • LongAdder源码分析
    • AQS源码解析
    • 深入剖析Java并发编程中的死锁问题
    • Java并发容器总结
    • 详解Java并发编程volatile关键字
    • 并发编程ThreadLocal必知必会
    • CompletableFuture基础实践小结
    • CompletableFuture异步多任务最佳实践
    • 硬核详解FutureTask设计与实现
    • 线程池大小设置的底层逻辑与场景化方案
    • 来聊一个有趣的限流器RateLimiter
  • JVM相关

    • 从零开始掌握 JVM
    • JVM核心知识点小结
    • JVM指令集概览:基础与应用
    • JVM类加载器深度解析
    • JVM方法区深度解析
    • Java内存模型JMM详解
    • Java对象大小的精确计算方法
    • 逃逸分析在Java中的应用与优化
    • 从零开始理解JVM的JIT编译机制
    • G1垃圾回收器:原理详解与调优指南
    • JVM故障排查实战指南
    • JVM内存问题排错最佳实践
    • JVM内存溢出排查指南
    • 简明的Arthas使用教程
    • 简明的Arthas配置及基础运维教程
    • 基于Arthas Idea的JVM故障排查与指令生成
    • 基于arthas量化监控诊断java应用方法论与实践
    • 深入剖析arthas技术原理
    • 探索JVM的隐秘角落:元空间详解
  • 深入理解Spring框架

    • Spring 核心知识点全面解析
    • Spring核心功能IOC详解
    • Spring AOP 深度剖析与实践
    • Spring 三级缓存机制深度解析
    • 深入 Spring 源码,剖析设计模式的落地实践
    • 探索 Spring 事务的奥秘
    • 深入解析Spring Bean的生命周期管理
    • 解读 Spring Boot 核心知识点
    • Spring Boot 启动优化实战:1分钟到13秒的排查与优化之路
    • Spring Boot自动装配原理及实践
    • 一文快速上手Sharding-JDBC
    • sharding-jdbc如何实现分页查询
    • 基于DynamicDataSource整合分库分表框架Shardingsphere
  • 计算机组成原理

    • 计算机硬件知识小结
    • CPU核心知识点小结
    • 浅谈CPU流水线的艺术
    • 从Java程序员视角聊聊CPU缓存
    • CPU任务调度和伪共享问题小结
    • CPU MESI缓存一致性协议
    • CPU内存管理机制
    • 内存深度解析
    • 磁盘存储原理
    • 详解计算机启动步骤
    • CPU南北桥架构与发展史
    • CPU中断机制与硬件交互详解
  • 操作系统

    • 如何实现一个高性能服务器
    • Linux文件结构与文件权限
    • Linux常见压缩指令小结
    • Linux核心系统调用详解
    • Linux进程管理
    • Linux线程管理
    • 进程与线程深度解析
    • Linux进程间通信机制
    • 零拷贝技术原理与实践
    • CPU缓存一致性问题深度解析
    • IO任务与CPU调度艺术
  • 计算机网络

    • 网卡通信原理详解
    • 网卡数据包处理指南
    • 基于抓包详解TCP协议
  • 编码最佳实践

    • 浅谈现代软件工程TDD最佳实践
    • 浅谈TDD模式下并发程序设计与实现
    • 面向AI编程新范式Trae后端开发环境搭建与实践
    • 基于Vibe Coding的Redis分页查询实现
    • 告别AI无效对话:资深工程师的提示词设计最佳实践
  • 实用技巧与配置

    • Mac常用快捷键与效率插件指南
    • Keynote技术科普短视频制作全攻略
  • 元认知

    • 摩擦感:AI时代的写作自省
    • 从断墨寻径浅谈程序员的元学习能力
    • AI时代专注力培养
    • 如何阅读一本书:技术书籍的读书笔记方法论
  • 开发工具

    • IDEA配置详解与高效使用指南
    • Windows环境下JDK安装与环境变量配置
    • Windows 10 下的 Maven 安装配置教程
  • Nodejs
  • 博客搭建
  • Redis

    • Redis核心知识小结
    • 解锁Redis发布订阅模式
    • 掌握Redis事务
    • Redis主从复制技术
    • Redis的哨兵模式详解
    • 深度剖析Redisson分布式锁
    • 详解redis单线程设计思路
    • 来聊聊Redis所实现的Reactor模型
    • Redis RDB持久化源码深度解析
    • 来聊聊redis的AOF写入
    • 来聊聊Redis持久化AOF管道通信的设计
    • 来聊聊redis集群数据迁移
    • Redis SDS动态字符串深度解析
    • 高效索引的秘密:redis跳表设计与实现
    • 聊聊redis中的字典设计与实现
    • 感觉你因为AI退化了?用Redis SCAN源码打脸AI时代的孔乙己
  • MySQL

    • MySQL基础知识点小结
    • 解读MySQL 索引基础
    • MySQL 索引进阶指南
    • 解读MySQL Explain关键字
    • 探秘 MySQL 锁:原理与实践
    • 详解MySQL重做日志redolog
    • 详解undoLog在MySQL MVCC中的运用
    • MySQL二进制日志binlog核心知识点
    • MySQL高效插入数据的最佳实践
    • MySQL分页查询优化指南
    • MySQL流式查询的奥秘与应用解析
    • 来聊聊分库分表
    • 来聊聊大厂常用的分布式ID生成方案
  • ElasticSearch

    • Elasticsearch核心原理与架构设计
    • ES 基础使用指南
    • ElasticSearch如何写入一篇文档
    • 深入剖析Elasticsearch文档读取原理
    • 聊聊ElasticSearch性能调优
    • Spring借助Easy-Es操作ES
  • Netty

    • 一文快速了解高性能网络通信框架Netty
    • Netty网络传输简记
    • 来聊聊Netty的ByteBuf
    • 来聊聊Netty消息发送的那些事
    • 解密Netty高性能之谜:NioEventLoop线程池阻塞分析
    • 详解Netty中的责任链Pipeline如何管理ChannelHandler
    • Netty Reactor模型常见知识点小结
    • Netty如何驾驭TCP流式传输?粘包拆包问题全解
    • Netty解码器源码解析
  • 消息队列

    • 一文快速入门消息队列
    • 消息队列RocketMQ入门指南
    • 基于RocketMQ实现分布式事务
    • RocketMQ容器化最佳实践
    • RocketMQ常见问题与深度解析
    • Kafka快速安装与使用指南
  • Nginx

    • Linux下的nginx安装
    • Nginx基础入门总结
    • Nginx核心指令小结
    • Nginx进程结构与核心模块初探
    • Nginx应用进阶HTTP核心模块配置
    • Nginx缓存及HTTPS配置小记
    • nginx高可用实践简记
    • Nginx性能优化
  • 微服务基础

    • 微服务基础知识小结
    • 分布式事务核心概念小结
    • OpenFeign核心知识小结
    • 微服务组件Gateway核心使用小结
    • 分布式事务Seata实践
    • 用 Docker Compose 完成 Seata 的整合部署
  • Nacos

    • Nacos服务注册原理全解析
    • Nacos服务订阅流程全解析
    • Nacos服务变更推送流程全解析
    • 深入解析SpringCloud负载均衡器Loadbalancer
    • Nacos源码环境搭建与调试指南
  • Seata

    • 深度剖析Seata源码
  • Docker部署

    • 一文快速掌握docker的理念和基本使用
    • 使用docker编排容器
    • 基于docker-compose部署微服务基本环境
    • 基于docker容器化部署微服务
    • Gateway全局异常处理及请求响应监控
    • Docker图形化界面工具Portainer最佳实践
  • Go基础

    • 一文带你速通Go语言基础语法
    • 一文快速掌握Go语言切片
    • 来聊聊go语言的hashMap
    • 一文速通go语言类型系统
    • 浅谈Go语言中的面向对象
    • go语言是如何实现协程的
    • 聊聊go语言中的GMP模型
    • 极简的go语言channel入门
    • 聊聊go语言基于epoll的网络并发实现
    • 写给Java开发的Go语言协程实践
  • mini-redis实战

    • 来聊聊我用go手写redis这件事
    • mini-redis如何解析处理客户端请求
    • 实现mini-redis字符串操作
    • 硬核复刻redis底层双向链表核心实现
    • 动手复刻redis之go语言下的字典的设计与落地
    • Go 语言下的 Redis 跳表设计与实现
    • Go 语言版 Redis 有序集合指令复刻探索
    • 基于 Claude Code 复刻 Redis 慢查询指令实践
  • 项目编排

    • Spring脚手架创建简记
    • Spring脚手架集成分页插件
    • Spring脚手架集成校验框架
    • maven父子模块两种搭建方式简记
    • SpringBoot+Vue3前后端快速整合入门
    • 来聊聊Java项目分层规范
  • 场景设计

    • Java实现文件分片上传
    • 基于时间缓存优化浏览器轮询阻塞问题
    • 基于EasyExcel实现高效导出
    • 10亿数据高效插入MySQL最佳方案
    • 从开源框架中学习那些实用的位运算技巧
    • 对账核销场景设计与重构实践
    • 千万级交易流水慢查询综合治理实践
    • 记一次StarRocks源码级排错和既有架构优化实践
  • CI/CD

    • 基于NETAPP实现内网穿透
    • 基于Gitee实现Jenkins自动化部署SpringBoot项目
    • Jenkins离线安装部署教程简记
    • 基于Nexus搭建Maven私服基础入门
    • 基于内网的Jenkins整合gitlab综合方案简记
  • 监控方法论

    • SpringBoot集成Prometheus与Grafana监控
    • Java监控度量Micrometer全解析
    • 从 micrometer计量器角度快速上手promQL
    • 硬核安利一个监控告警开源项目Nightingale
  • Spring AI

    • Spring AI Alibaba深度实战:一文掌握智能体开发全流程
    • Spring AI Alibaba实战:JVM监控诊断Arthas Agent的工程化构建与最佳实践
  • 大模型评测

    • M2.7 真能打!我用两个真实场景测了测,结果有点意外
    • Qoder JetBrains插件评测:祖传代码重构与接口优化实战
  • AI工具链

    • Claude Code 实战指南:从安装配置到企业级开发流程
    • 一次 Claude Code 启动失败的 AI 辅助排查复盘
    • 基于提示词工程与KITE框架的Redis签到功能开发实践
    • Claude Code 记忆管理:CLAUDE.md 最佳实践
    • Claude Code 规则管理:Rules 拆分编排与迭代实践(文末送书)
    • VSCode与Claude Code后端开发环境搭建与AI编程工作流实践
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

sharkchili

计算机禅修者
首页
  • Java基础

    • 一文搞懂Java核心技术
    • Java面向对象知识点大总结,建议收藏
    • 聊聊Java中的异常
    • 聊聊Java中的常用类String
    • 万字长文带你细聊Java注解本质
    • 来聊聊Java的反射机制
    • 深入解析Java泛型的魅力与机制
    • Java集合框架深度解析与面试指南
    • Java常用集合类HashMap深度解析
    • LinkedHashMap源码到面试题的全解析
    • 深入解析CopyOnWriteArrayList的工作机制
    • Java基础IO总结
    • Java三大IO模型小结
    • Java BIO NIO AIO详解
    • Java进阶NIO之IO多路复用详解
    • Java8流式编程入门
    • 一文速通lambda与函数式编程
    • Java8函数式方法引用最佳实践
    • Java异常:从原理到实践
  • Java并发编程

    • Java并发编程基础小结
    • 深入理解Java中的final关键字
    • 浅谈Java并发安全发布技术
    • 浅谈Java并发编程中断的哲学
    • Java线程池知识点小结
    • 浅谈Java线程池中拒绝策略与流控的艺术
    • synchronized关键字使用指南
    • 深入源码解析synchronized关键字
    • 详解JUC包下的锁
    • 详解并发编程中的CAS原子类
    • LongAdder源码分析
    • AQS源码解析
    • 深入剖析Java并发编程中的死锁问题
    • Java并发容器总结
    • 详解Java并发编程volatile关键字
    • 并发编程ThreadLocal必知必会
    • CompletableFuture基础实践小结
    • CompletableFuture异步多任务最佳实践
    • 硬核详解FutureTask设计与实现
    • 线程池大小设置的底层逻辑与场景化方案
    • 来聊一个有趣的限流器RateLimiter
  • JVM相关

    • 从零开始掌握 JVM
    • JVM核心知识点小结
    • JVM指令集概览:基础与应用
    • JVM类加载器深度解析
    • JVM方法区深度解析
    • Java内存模型JMM详解
    • Java对象大小的精确计算方法
    • 逃逸分析在Java中的应用与优化
    • 从零开始理解JVM的JIT编译机制
    • G1垃圾回收器:原理详解与调优指南
    • JVM故障排查实战指南
    • JVM内存问题排错最佳实践
    • JVM内存溢出排查指南
    • 简明的Arthas使用教程
    • 简明的Arthas配置及基础运维教程
    • 基于Arthas Idea的JVM故障排查与指令生成
    • 基于arthas量化监控诊断java应用方法论与实践
    • 深入剖析arthas技术原理
    • 探索JVM的隐秘角落:元空间详解
  • 深入理解Spring框架

    • Spring 核心知识点全面解析
    • Spring核心功能IOC详解
    • Spring AOP 深度剖析与实践
    • Spring 三级缓存机制深度解析
    • 深入 Spring 源码,剖析设计模式的落地实践
    • 探索 Spring 事务的奥秘
    • 深入解析Spring Bean的生命周期管理
    • 解读 Spring Boot 核心知识点
    • Spring Boot 启动优化实战:1分钟到13秒的排查与优化之路
    • Spring Boot自动装配原理及实践
    • 一文快速上手Sharding-JDBC
    • sharding-jdbc如何实现分页查询
    • 基于DynamicDataSource整合分库分表框架Shardingsphere
  • 计算机组成原理

    • 计算机硬件知识小结
    • CPU核心知识点小结
    • 浅谈CPU流水线的艺术
    • 从Java程序员视角聊聊CPU缓存
    • CPU任务调度和伪共享问题小结
    • CPU MESI缓存一致性协议
    • CPU内存管理机制
    • 内存深度解析
    • 磁盘存储原理
    • 详解计算机启动步骤
    • CPU南北桥架构与发展史
    • CPU中断机制与硬件交互详解
  • 操作系统

    • 如何实现一个高性能服务器
    • Linux文件结构与文件权限
    • Linux常见压缩指令小结
    • Linux核心系统调用详解
    • Linux进程管理
    • Linux线程管理
    • 进程与线程深度解析
    • Linux进程间通信机制
    • 零拷贝技术原理与实践
    • CPU缓存一致性问题深度解析
    • IO任务与CPU调度艺术
  • 计算机网络

    • 网卡通信原理详解
    • 网卡数据包处理指南
    • 基于抓包详解TCP协议
  • 编码最佳实践

    • 浅谈现代软件工程TDD最佳实践
    • 浅谈TDD模式下并发程序设计与实现
    • 面向AI编程新范式Trae后端开发环境搭建与实践
    • 基于Vibe Coding的Redis分页查询实现
    • 告别AI无效对话:资深工程师的提示词设计最佳实践
  • 实用技巧与配置

    • Mac常用快捷键与效率插件指南
    • Keynote技术科普短视频制作全攻略
  • 元认知

    • 摩擦感:AI时代的写作自省
    • 从断墨寻径浅谈程序员的元学习能力
    • AI时代专注力培养
    • 如何阅读一本书:技术书籍的读书笔记方法论
  • 开发工具

    • IDEA配置详解与高效使用指南
    • Windows环境下JDK安装与环境变量配置
    • Windows 10 下的 Maven 安装配置教程
  • Nodejs
  • 博客搭建
  • Redis

    • Redis核心知识小结
    • 解锁Redis发布订阅模式
    • 掌握Redis事务
    • Redis主从复制技术
    • Redis的哨兵模式详解
    • 深度剖析Redisson分布式锁
    • 详解redis单线程设计思路
    • 来聊聊Redis所实现的Reactor模型
    • Redis RDB持久化源码深度解析
    • 来聊聊redis的AOF写入
    • 来聊聊Redis持久化AOF管道通信的设计
    • 来聊聊redis集群数据迁移
    • Redis SDS动态字符串深度解析
    • 高效索引的秘密:redis跳表设计与实现
    • 聊聊redis中的字典设计与实现
    • 感觉你因为AI退化了?用Redis SCAN源码打脸AI时代的孔乙己
  • MySQL

    • MySQL基础知识点小结
    • 解读MySQL 索引基础
    • MySQL 索引进阶指南
    • 解读MySQL Explain关键字
    • 探秘 MySQL 锁:原理与实践
    • 详解MySQL重做日志redolog
    • 详解undoLog在MySQL MVCC中的运用
    • MySQL二进制日志binlog核心知识点
    • MySQL高效插入数据的最佳实践
    • MySQL分页查询优化指南
    • MySQL流式查询的奥秘与应用解析
    • 来聊聊分库分表
    • 来聊聊大厂常用的分布式ID生成方案
  • ElasticSearch

    • Elasticsearch核心原理与架构设计
    • ES 基础使用指南
    • ElasticSearch如何写入一篇文档
    • 深入剖析Elasticsearch文档读取原理
    • 聊聊ElasticSearch性能调优
    • Spring借助Easy-Es操作ES
  • Netty

    • 一文快速了解高性能网络通信框架Netty
    • Netty网络传输简记
    • 来聊聊Netty的ByteBuf
    • 来聊聊Netty消息发送的那些事
    • 解密Netty高性能之谜:NioEventLoop线程池阻塞分析
    • 详解Netty中的责任链Pipeline如何管理ChannelHandler
    • Netty Reactor模型常见知识点小结
    • Netty如何驾驭TCP流式传输?粘包拆包问题全解
    • Netty解码器源码解析
  • 消息队列

    • 一文快速入门消息队列
    • 消息队列RocketMQ入门指南
    • 基于RocketMQ实现分布式事务
    • RocketMQ容器化最佳实践
    • RocketMQ常见问题与深度解析
    • Kafka快速安装与使用指南
  • Nginx

    • Linux下的nginx安装
    • Nginx基础入门总结
    • Nginx核心指令小结
    • Nginx进程结构与核心模块初探
    • Nginx应用进阶HTTP核心模块配置
    • Nginx缓存及HTTPS配置小记
    • nginx高可用实践简记
    • Nginx性能优化
  • 微服务基础

    • 微服务基础知识小结
    • 分布式事务核心概念小结
    • OpenFeign核心知识小结
    • 微服务组件Gateway核心使用小结
    • 分布式事务Seata实践
    • 用 Docker Compose 完成 Seata 的整合部署
  • Nacos

    • Nacos服务注册原理全解析
    • Nacos服务订阅流程全解析
    • Nacos服务变更推送流程全解析
    • 深入解析SpringCloud负载均衡器Loadbalancer
    • Nacos源码环境搭建与调试指南
  • Seata

    • 深度剖析Seata源码
  • Docker部署

    • 一文快速掌握docker的理念和基本使用
    • 使用docker编排容器
    • 基于docker-compose部署微服务基本环境
    • 基于docker容器化部署微服务
    • Gateway全局异常处理及请求响应监控
    • Docker图形化界面工具Portainer最佳实践
  • Go基础

    • 一文带你速通Go语言基础语法
    • 一文快速掌握Go语言切片
    • 来聊聊go语言的hashMap
    • 一文速通go语言类型系统
    • 浅谈Go语言中的面向对象
    • go语言是如何实现协程的
    • 聊聊go语言中的GMP模型
    • 极简的go语言channel入门
    • 聊聊go语言基于epoll的网络并发实现
    • 写给Java开发的Go语言协程实践
  • mini-redis实战

    • 来聊聊我用go手写redis这件事
    • mini-redis如何解析处理客户端请求
    • 实现mini-redis字符串操作
    • 硬核复刻redis底层双向链表核心实现
    • 动手复刻redis之go语言下的字典的设计与落地
    • Go 语言下的 Redis 跳表设计与实现
    • Go 语言版 Redis 有序集合指令复刻探索
    • 基于 Claude Code 复刻 Redis 慢查询指令实践
  • 项目编排

    • Spring脚手架创建简记
    • Spring脚手架集成分页插件
    • Spring脚手架集成校验框架
    • maven父子模块两种搭建方式简记
    • SpringBoot+Vue3前后端快速整合入门
    • 来聊聊Java项目分层规范
  • 场景设计

    • Java实现文件分片上传
    • 基于时间缓存优化浏览器轮询阻塞问题
    • 基于EasyExcel实现高效导出
    • 10亿数据高效插入MySQL最佳方案
    • 从开源框架中学习那些实用的位运算技巧
    • 对账核销场景设计与重构实践
    • 千万级交易流水慢查询综合治理实践
    • 记一次StarRocks源码级排错和既有架构优化实践
  • CI/CD

    • 基于NETAPP实现内网穿透
    • 基于Gitee实现Jenkins自动化部署SpringBoot项目
    • Jenkins离线安装部署教程简记
    • 基于Nexus搭建Maven私服基础入门
    • 基于内网的Jenkins整合gitlab综合方案简记
  • 监控方法论

    • SpringBoot集成Prometheus与Grafana监控
    • Java监控度量Micrometer全解析
    • 从 micrometer计量器角度快速上手promQL
    • 硬核安利一个监控告警开源项目Nightingale
  • Spring AI

    • Spring AI Alibaba深度实战:一文掌握智能体开发全流程
    • Spring AI Alibaba实战:JVM监控诊断Arthas Agent的工程化构建与最佳实践
  • 大模型评测

    • M2.7 真能打!我用两个真实场景测了测,结果有点意外
    • Qoder JetBrains插件评测:祖传代码重构与接口优化实战
  • AI工具链

    • Claude Code 实战指南:从安装配置到企业级开发流程
    • 一次 Claude Code 启动失败的 AI 辅助排查复盘
    • 基于提示词工程与KITE框架的Redis签到功能开发实践
    • Claude Code 记忆管理:CLAUDE.md 最佳实践
    • Claude Code 规则管理:Rules 拆分编排与迭代实践(文末送书)
    • VSCode与Claude Code后端开发环境搭建与AI编程工作流实践
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Redis

    • Redis核心知识小结
    • Redis源码与实战剖析小结
    • Redis系列文章全汇总
    • 解锁Redis发布订阅模式:通过实践演示挖掘消息通信潜能
    • 掌握 Redis 事务,提升数据处理效率的必备秘籍
    • 基于Jedis来探讨池化技术
    • Redis主从复制技术:理论基础、运行逻辑与应用场景
    • 聊聊Redis主从复制
    • Redis的哨兵模式详解
    • 深度剖析 Redisson 分布式锁:原理、实现与应用实践
    • 来聊聊Redis中的字符串对象的设计
    • 详解redis单线程设计思路
    • 基于Gdb快速上手调试Redis
    • 聊聊redis中的有序集合
    • 来聊聊redis文件事件驱动的设计
    • 如何理解redis是单线程的
    • 来聊聊Redis所实现的Reactor模型
    • 来聊聊Redis客户端的概念
    • 来聊聊redis数据库的设计与实现
    • 来聊聊Redis定期删除策略的设计与实现
    • 聊聊Redis中缓存淘汰算法的实现
    • Redis RDB持久化源码深度解析:从原理到实现
    • 一文读懂Redis RDB持久化:策略、配置与应用
    • 来聊聊redis的AOF写入
    • 来聊聊Redis的AOF重写机制
    • 来聊聊Redis持久化AOF管道通信的设计
    • Redis如何高效实现定时任务
    • 以从节点的角度看看Redis主从复制的实现
    • Redis哨兵是如何完成初始化的
    • 聊聊Redis哨兵选举与故障转移的实现
    • 来聊聊Redis哨兵如何主观认定下线
    • 来聊聊redis的发布订阅设计与实现
    • 来聊聊去中心化Redis集群节点如何完成通信
    • redis集群中如何处理非本节点的slot
    • 来聊聊redis集群数据迁移
    • 硬核详解redis客户端指令与服务端传输协议RESP
    • 从redis源码了解双向链表的设计与实现
    • 能不能给我讲讲redis中的列表
    • 聊聊redis中的字典设计与实现
    • 聊聊redis字典指令操作
    • 高效索引的秘密:redis跳表设计与实现
    • 探索数据结构之美——有序集合的内部机制
    • Redis SDS动态字符串深度解析
    • Redis核心数据结构字典操作实践与解析
    • Redis持久化技术AOF要点与详细解答
    • Redisson全面解析从使用方法到工作原理的深度探索
    • 基于VSCode调试Redis源码指南
    • Redis持久化技术AOF要点与详细解答(2)
    • 感觉你因为AI退化了?用Redis SCAN源码打脸AI时代的孔乙己
      • 写在文章开头
      • 前置步骤说明
        • redis编译调试环境配置
        • scan指令快速扫盲
      • 详解scan指令的设计与实现
        • scan指令执行流程
        • 游标参数解析
        • 参数解析
        • 反向迭代算法遍历元素
        • 过滤结果集
        • 输出游标和结果
      • 小结
      • 参考
  • MySQL

  • ElasticSearch

  • StarRocks

  • 数据库
  • Redis
sharkchili
2026-05-17
目录

感觉你因为AI退化了?用Redis SCAN源码打脸AI时代的孔乙己

# 写在文章开头

笔者对于AI的使用态度一直是保持理性态度,所以对于AI协作编程一直是秉持理性看待和合理利用的 态度。

很遗憾,很多所谓"技术专家"站出来以"过来人"和所谓的"学习、成长"经验过来教育笔者,你现在AI用多,人有些退步了,你要学会:

  1. 不要调试代码,主动阅读每一行代码
  2. 保持理解,后续能够快速定位问题
  3. 不要指望AI,后续生产遇到问题,你没法用AI帮到你
  4. AI不完全靠谱的,只有自己手写的才能放心,打到线上不会翻车

对此,我也感到遗憾,我无法成为一个AI时代下的传统编程信徒。所以,我只能通过这篇解读redis底层核心算法的文章,来告知那些所谓的"老前辈"——书架和浏览器收藏夹的内容该更新了。

本文是笔者早期传统编程阶段所整理的一篇关于redis scan指令的解读文章。由于算法设计的巧妙以及当时个人认知不足,导致当时的解读表述不足以让读者正确了解该指令的设计理念并学习内化。

所以,笔者借着近期传统编程训练模式之机,以此文作为论证,将传统编程调测技巧与AI深度分析相结合,给出scan指令深度教程和编程最佳实践,告诉那些所谓的"过来人"——talk is cheap,show me the code。

SharkChili · 禅与计算机程序设计的艺术

开源贡献

  • mini-redis:教学级 Redis 精简实现 · https://github.com/shark-ctrl/mini-redis

关注公众号,回复 【加群】 加入技术社群

# 前置步骤说明

# redis编译调试环境配置

大型开源项目设计架构是非常复杂的,很多情况下我们无法通过肉眼调试的方式将流程梳理清楚,所以当我们需要阅读源码的时候,首要做的就是将源码环境拉下来并运行,以确保一个可实际观测理解,精确调测梳理完成研发任务。

我相信大部分人都是因为这一步而被劝退,原因很简单: 人是有惰性的

搭建环境时或多或少会因为各种未知的报错和繁琐的配置,笔者还是坚定的认为既有阶段,我们这些常人所遇到的问题,势必有最终的答案,我们还是要学会沉下心来,检索推测不断的实践,然后复盘内化。

以本文为例,笔者是一名主力开发语言为Java的软件从业者,对于c语言的基础也仅仅是停留在大学阶段,所以对于企业级c语言项目的了解也不是特别熟悉,在正式阅读redis源码时笔者也是详细的阅读了redis官网中对于源码环境的说明。

如下图所示,这是笔者近期阅读的redis 3.2.8的源码的readme文档,可以看到该文档中详尽地介绍redis项目拉取、编译、启动运行的所有步骤,我们完全可以沉下心去阅读了解大体步骤后,结合网上的一些关于c语言调试的开发工具得出一套综合的环境搭建过程将项目跑起来:

# scan指令快速扫盲

学习的前提是目标,以本文为例即阅读源码一定要清晰明确了解:

  • 阅读的源码段解决问题
  • 源码对应的功能如何使用

一切学习的前提都需要具备明确的输入和输出,只有明确自己的why,才能进一步execute,从而完成知识点之间的关联,构建一次完整的学习闭环,提升自己的学习能力和知识储备。

回过头来,本次所要了解的就是redis中scan指令的实现细节,在正式的了解之前,笔者对该指令做了一定的了解,对其作用、输入输出、运用场景、使用注意事项等都有了深入的扫盲,以确保读者能够连贯的阅读这篇文章并加以学习内化。

本质上,scan指令就是针对当前客户端操作的指定的redis内存库(默认是db 0),默认我们执行scan 0 进行迭代,对应的执行和工作流程为:

  1. 基于游标定位到对应的redis字典底层的桶也称bucket,将当前bucket中的元素返回,默认情况下提示返回10个(实际可能多一些)
  2. 返回下一次的游标值n
  3. 客户端再次通过scan n进行下一轮的迭代扫描

例如,笔者当前db 0库存在四个元素a、b、c、d,执行scan 0就会输出返回所有的元素,同时给出下一次游标0。

127.0.0.1:6379> scan 0
1) "0"
2) 1) "b"
   2) "c"
   3) "a"
   4) "d"
1
2
3
4
5
6

这里笔者也特殊说明一下,游标0的含义,我们都知道使用scan指令第一次指定的游标值就是0,redis为了让用户感知游标迭代完结,同样采用游标0作为终止标识,告知用户已完成迭代闭环。

此外,scan指令不保证返回结果的唯一性,即在迭代过程中可能会返回重复元素。这在字典缩容场景下尤为常见,因此客户端需要对scan返回的结果进行去重处理。

scan还支持优先count输出,如下所示,通过count指定参数值为1,即提示redis输出1个元素,对应输出结果如下,可以看到redis竟意外的输出两个元素?是bug嘛?

127.0.0.1:6379> scan 0 count 1
1) "2"
2) 1) "b"
   2) "c"
1
2
3
4

软件系统的设计是一门在性能和标准之间权衡的艺术,以表象直接定义bug是一种非常主观且武断的做法。redis底层采用自定义字典存储键值对,并通过拉链法解决散列冲突。

由此就出现上述的问题,假设我们指定scan的游标为0,定位到了桶0,与之对应存在2个元素,按照scan底层一个游标对应一个bucket的做法,scan扫描到两个当前桶中所有元素后,会直接写入到链表中,而对应的count检测是在bucket扫描完成之后的逻辑判断。这就使得条件count为1输出结果却是2个:

实际上redis也可以杜绝这一点,即看bucket维度的扫荡中加一个count过滤就好了,但这种做法会增加实现上的复杂度。原因我也可以通过不同实现维度的评估推导:

  • 基于bucket维度扫描,每次扫荡都能完成全桶扫描,下一次扫荡可直接从全新的游标开始迭代
  • 基于bucket维度上进行count过滤,虽然保证count的准确性,却需要server端进行更细粒度的游标控制,即返回的游标要精确到桶内部的某个键值对,这一点回报的性价比对于一个非要求高精确性的scan来说是得不偿失的。

这也是为什么,笔者一直认为redis是程序设计艺术的典范,它很好的在业务和软件程序设计上做了非常出色的折中。

scan指令也支持match参数,即支持模糊匹配,例如笔者希望找到c开头的key,对应的查询指令和输出指令如下所示:

127.0.0.1:6379> scan 0 match c* count 10000
1) "0"
2) 1) "c"
1
2
3

# 详解scan指令的设计与实现

# scan指令执行流程

有了上述的使用经验的基础,我们就可以正式阅读和分析源码了,首先自然是定位到指令核心实现的入口,对应笔者本次要阅读了解的源码位置即db.c下的scanCommand,因为知道scan的作用,所以我们可以宏观的介绍一下源码了解其对于单库扫描的整体流程:

  1. 解析scan参数:如果有count和match则解析这些参数后面的值,并校验合法性。
  2. 定位到客户端使用的数据库,并基于游标开始获取元素,将结果链表中
  3. 如果有match参数则基于步骤2的链表完成过滤
  4. 返回有效元素和下一次的迭代的游标

例如,我们键入scan 0 match a* count 5,对应的执行步骤为:

  1. 解析游标参数有效性,感知到是无符号整数0,返回成功
  2. 判断count和match参数是否存在,若存在则按需解析构造参数
  3. 基于高位优先迭代算法,到bucket中获取元素,例如:本次迭代在桶0拿到ab、aa、cc等
  4. 按照match匹配过滤出aa、ab
  5. 返回输出元素,并告知下一次迭代的游标为2

我们给出scan指令对应源代码实现入口,可以看到拿到参数的第一步就是调用parseScanCursorOrReply,并传入第一个参数即游标,查看参数有效性。然后在调用scanGenericCommand执行后续迭代步骤:

void scanCommand(client *c) {
    unsigned long cursor;
    //判断是否可以转为无符号整数,如果不行则直接返回错误
    if (parseScanCursorOrReply(c,c->argv[1],&cursor) == C_ERR) return;
    scanGenericCommand(c,NULL,cursor);
}
1
2
3
4
5
6

继续步入scanGenericCommand 即可看到上述概括的逻辑,即:

  1. 解析count或者match参数值
  2. 基于游标定位bucket解析元素存入链表中
  3. 基于match表达式过滤有效元素
  4. 返回下一个游标Cursor和元素
void scanGenericCommand(client *c, robj *o, unsigned long cursor) {
    int i, j;
    //创建初始化链表
    list *keys = listCreate();
    listNode *node, *nextnode;
    //默认情况下count取10个
    long count = 10;
    sds pat = NULL;
    int patlen = 0, use_pattern = 0;
    dict *ht;

    /* Object must be NULL (to iterate keys names), or the type of the object
     * must be Set, Sorted Set, or Hash. */
    serverAssert(o == NULL || o->type == OBJ_SET || o->type == OBJ_HASH ||
                o->type == OBJ_ZSET);

    //默认情况下是为2的,因为默认传入的obj是空的
    i = (o == NULL) ? 2 : 3; /* Skip the key argument if needed. */

    //默认情况下,从索引2开始参数解析
    while (i < c->argc) {
        //代表当前参数索引到总参数个数的剩余参数个数
        j = c->argc - i;
        //查看是否为count然后转换值存入count变量中,若报错直接进入goto语句块做后置的清理工作
        if (!strcasecmp(c->argv[i]->ptr, "count") && j >= 2) {
            //解析count参数
        } else if (!strcasecmp(c->argv[i]->ptr, "match") && j >= 2) {
        	//......
        	//解析match参数
        } else {
            addReply(c,shared.syntaxerr);
            goto cleanup;
        }
    }

    //......

    if (ht) {
       	//......
        do {
        		//扫描元素存入链表
            cursor = dictScan(ht, cursor, scanCallback, privdata);
        } while (cursor &&
              maxiterations-- &&
              listLength(keys) < (unsigned long)count);//游标非0且还在最大尝试次数且链表未达到要求的count就可以继续循环
    } else if (o->type == OBJ_SET) {
       	//.......
    } else if (o->type == OBJ_HASH || o->type == OBJ_ZSET) {
       //.......
    } else {
        serverPanic("Not handled encoding in SCAN.");
    }
		
		//基于match过滤有效元素
    node = listFirst(keys);
    while (node) {
       //......
    }

   //返回下一次的游标和结果集


    //说明长度为2
    addReplyMultiBulkLen(c, 2);
    //告知下一次的游标的值
    addReplyBulkLongLong(c,cursor);
    //告知链表的长度
    addReplyMultiBulkLen(c, listLength(keys));
    //从头开始遍历链表
    while ((node = listFirst(keys)) != NULL) {
        robj *kobj = listNodeValue(node);
        addReplyBulk(c, kobj);
        decrRefCount(kobj);
        listDelNode(keys, node);
    }
		//......
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77

# 游标参数解析

明确宏观的执行流程之后,我们逐步细化拆解scan的每一个核心步骤,先是游标解析,逻辑比较简单,将传入的robj也就是游标数值对象传入,再调用strtoul查看是否可以正确转为无符号整数,若失败则直接响应invalid cursor 异常:

int parseScanCursorOrReply(client *c, robj *o, unsigned long *cursor) {
    char *eptr;

   
    errno = 0;
   /**
    * 通过strtoul将o对象的ptr成员转换为无符号长整数存储在cursor中,若失败则直接输出异常
    */
    *cursor = strtoul(o->ptr, &eptr, 10);
    if (isspace(((char*)o->ptr)[0]) || eptr[0] != '\0' || errno == ERANGE)
    {
        addReplyError(c, "invalid cursor");
        return C_ERR;
    }
    return C_OK;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 参数解析

有了宏观的流程的基础之后,我们就可以针对性的去了解细节,上文中我们看到scan指令的首要核心步骤是解析参数,对应的就是基于索引的迭代,这里我们也可以看到redis设计者在这种细节上的优化,对于一般的开发者而言,拿到scan match count这套组合参数之后,一般都是采用顺序遍历的方式进行参数解析,即顺序遍历:

  1. 看到match解析match后面的参数
  2. 看到count解析count后面跟随的参数

而redis在循环迭代这方面就有了微观的操作,直接从索引2开始(scan游标后面的值),查看当前字符串是否是match,如果是则直接解析其参数并验证合法性,然后索引直接+2直接尝试去解析count的值:

scan-command-2.drawio

同时参数解析阶段,也考虑到用户不规范的用法,即match后面直接跟个*导致基于全库扫荡的数据执行了过滤逻辑,进而造成非必要的耗时,所以redis在参数解析阶段,也会判断match后面的值是否是*,如果不是才执行过滤匹配。

对应的我们给出参数解析这段代码实现的细节,读者可以基于笔者的说法自行参阅了解一下:

//默认情况下,从索引2开始参数解析
    while (i < c->argc) {
        //计算剩余参数的数量
        j = c->argc - i;
        //查看是否为count然后转换值存入count中,若报错或j小于2,则说明剩余参数不足两个无法凑成一对,直接进入goto语句块做后置的清理工作
        if (!strcasecmp(c->argv[i]->ptr, "count") && j >= 2) {
            if (getLongFromObjectOrReply(c, c->argv[i+1], &count, NULL)
                != C_OK)
            {
                goto cleanup;
            }

            if (count < 1) {
                addReply(c,shared.syntaxerr);
                goto cleanup;
            }
						//直接跳两步,避免非必要的遍历
            i += 2;
        } else if (!strcasecmp(c->argv[i]->ptr, "match") && j >= 2) {//如果参数是match则将下一个参数存入pat变量中,并且计算长度存入patlen变量中
            pat = c->argv[i+1]->ptr;
            patlen = sdslen(pat);

            //避免非必要的过滤逻辑,看到匹配单词是*且长度为1,则use_pattern为0,后续不执行过滤逻辑
            use_pattern = !(pat[0] == '*' && patlen == 1);
						//直接跳两步,避免非必要的遍历
            i += 2;
        } else {
            addReply(c,shared.syntaxerr);
            goto cleanup;
        }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

# 反向迭代算法遍历元素

通过上述步骤,我们完成count和match的参数解析,接下来就到了最重要的一个步骤——迭代字典元素。

我们都知道redis字典采用双数组+链表的形式,完成渐进式哈希和拉链法避免极端冲突,从而保证redis读写性能。因为双数组的元素,若scan采用顺序遍历就可以存在一个致命问题——重复迭代。

假设我们初始化一个长度为4的数组,存放元素,通过顺序迭代算法,我们完成桶2的元素迭代,随后redis为避免极端的冲突导致读写性能下降,进行了一次渐进式rehash,构建一个长度为8的数组,逐步迁移桶2的元素到桶4中。

如下图所示,试想一下,若按照顺序迭代的方式,当游标指向桶4时,我们又再次扫描到了桶2中迭代的元素,造成了大量非必要的迭代开销:

所以redis提出了一种巧妙的高位优先和反向迭代算法,确保能够迭代所有桶元素的同时,还能避免重复扫描。例如:我们字典底层的数组长度为4,对应的哈希计算的掩码为3也就是二进制011,苏处结果是0、2、1、3,对应二进制为00、10、01、11,如下图所示,相信读者也已看出,该算法看着像是一种反向的递增算法,即高位+1向低位不断进位的反向迭代算法:

算法的原理比较简单,但是实现却相对复杂一些,所以笔者还是通过例子的方式进行演示。例如,我们现在有一个长度为4的字典数组,完成游标0迭代后,对应计算步骤为:

  1. 将掩码取反,即0000 0011变为1111 1100,将高位置为1,便于后续反向递增运算
  2. 将游标值也就是0000 0000与掩码进行按位或,得到1111 1100,获得高位全为1,低位(用于计算桶位置的2位)保留原样的二进制数。
  3. 二进制位翻转获得0011 1111,为递增做铺垫
  4. 递增加一,将翻转为高位的原低位区间(即两个00),完成反向递增,也就是0100 0000
  5. 再翻转回来,获得0000 0010,也就是元素2

同理对应数值2的反向递增也是同理,通过上述的算法说明我们可知对应二进制10会因为反向递增变为01,也就是二进制1,对应的计算步骤为:

  1. 掩码取反获得1111 1100
  2. 游标值和掩码按位或,将高位置1,低位即有效计算桶为10保留,为反向递增做铺垫
  3. 将二进制翻转变为0111 1111
  4. 执行递增,完成反向递增,变为1000 0000
  5. 翻转回来获得二进制1

通过将掩码高位置1,利用按位或保留所有有效桶位二进制,使得翻转后的递增操作实现反向递增的效果同时,保证最终游标值都控制在桶区间以内且不重复,例如数组为4的字典,它可以确保不重复的情况下完成一轮有效的遍历:0 → 2→ 1→ 3

通过一个简单例子,印证了该算法如何保证完整的有效扫荡,但这还不是该算法的巧妙之处,相较于顺序扫荡,它能够有效避免非必要的大量重复扫荡。

按照redis字典的扩容算法,数组为4的元素扩容后会变为8,对应的桶之间的迁移关系如下图所示。由于扩容后掩码多了一位,原桶中的每个元素会根据其hash值新增的最高位被拆分到两个新桶中。

例如:

  • 桶0中的元素,根据hash的最高位是0或1,分别留在桶0或迁移到桶4;
  • 桶1中的元素分别留在桶1或迁移到桶5;
  • 桶2中的元素分别留在桶2或迁移到桶6;
  • 桶3中的元素分别留在桶3或迁移到桶7:

假设按照顺序迭代算法,完成桶2迭代后,我们可能会经过几次迭代之后才会接触到游标6。极端情况下,redis已将桶2元素迁移到桶6时,对于游标6的扫荡就可能带来大量的重复扫描。

再来看看反向迭代算法,假设我们完成2也就是二进制10的迭代。此时,因为扩容的原因,掩码变为7也就是二进制的0111,对应计算步骤也是按照高位置1、翻转、递增、再翻转,也就是最终得到二进制6,对应运算过程如下图所示:

同理,我们继续计算后的结果为:0 → 4 → 2 → 6 → 1 → 5 → 3 → 7 ,不难看出,反向迭代算法有效确保同模桶也就是存在扩容迁移关系的bucket紧挨着遍历,例如:

  • 0紧挨着扩容桶4
  • 2紧挨着扩容桶6

该算法可确保在扩容期间执行的scan操作时,能够尽可能快速的完成同模桶的迭代,避免大量元素重复扫描,避免非必要的资源开销,保证redis scan的执行效率:

对应我们也给出scan迭代操作核心实现,对应算法逻辑和注释如下,如上文解析所说,整体算法通过rev完成二进制翻转,递增、再翻转的方式实现反向递增,确保完全覆盖同时,减少非必要的重试扫荡的开销:

unsigned long dictScan(dict *d,
                       unsigned long v,
                       dictScanFunction *fn,
                       void *privdata)
{
   	//......

    if (dictSize(d) == 0) return 0;

    if (!dictIsRehashing(d)) {
        t0 = &(d->ht[0]);
        //m为hash取模的掩码,假如size为4那么掩码就是获取0~3索引的某个位置,对应的掩码就是0011,同理size为8的情况下掩码就是0111
        m0 = t0->sizemask;

        /* Emit entries at cursor */
        de = t0->table[v & m0];
        while (de) {
            fn(privdata, de);
            de = de->next;
        }

        
        //掩码取反并和游标进行按位或,获得一个可支持反向递增的二进制数
        v |= ~m0;

      	//通过游标翻转、累加、再翻转的方式完成反向递增
        v = rev(v);
        v++;
        v = rev(v);

    } else {
       //......
    }

    return v;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

# 过滤结果集

最后我们再来聊聊match参数的优化,相比于扫荡的算法这个实现就比较容易了,对应步骤为:

  1. 获取上一轮迭代后的元素链表
  2. 基于表达式进行匹配,若不符合要求则将filter设置为1
  3. 根据步骤2 生成的filter标识判断是否将元素从结果集中删除

对应我们也给出匹配过滤的核心逻辑,即位于scanGenericCommand的step 3代码段,对应逻辑也和笔者说明的一致,即通过match 参数解析获取的pat(以上图为例则是a*)和patlen 即也就是2,遍历链表进行过滤匹配,将不符合要求的从链表中删除:

  /* Step 3: Filter elements. */
    //获取反向迭代后的链表首元素
    node = listFirst(keys);
    while (node) {
        robj *kobj = listNodeValue(node);
        nextnode = listNextNode(node);
        //初始化过滤标识为0,代表不过滤
        int filter = 0;

        /* Filter element if it does not match the pattern. */

        if (!filter && use_pattern) {
            if (sdsEncodedObject(kobj)) {
                //结合match表达式参数pat和patlen判断当前元素是否匹配,若不匹配则过滤标识设置为1
                if (!stringmatchlen(pat, patlen, kobj->ptr, sdslen(kobj->ptr), 0))
                    filter = 1;
            } else {
                char buf[LONG_STR_SIZE];
                int len;

                serverAssert(kobj->encoding == OBJ_ENCODING_INT);
                len = ll2string(buf,sizeof(buf),(long)kobj->ptr);
                if (!stringmatchlen(pat, patlen, buf, len, 0)) filter = 1;
            }
        }

        /* Filter element if it is an expired key. */
        //判断元素是否过期,若过期,也将其设置为1,
        if (!filter && o == NULL && expireIfNeeded(c->db, kobj)) filter = 1;

        /* Remove the element and its associted value if needed. */
        //如果过滤标识为1则将这个节点删除
        if (filter) {
            decrRefCount(kobj);
            listDelNode(keys, node);
        }

        /* If this is a hash or a sorted set, we have a flat list of
         * key-value elements, so if this element was filtered, remove the
         * value, or skip it if it was not filtered: we only match keys. */
        if (o && (o->type == OBJ_ZSET || o->type == OBJ_HASH)) {
            node = nextnode;
            nextnode = listNextNode(node);
            if (filter) {
                kobj = listNodeValue(node);
                decrRefCount(kobj);
                listDelNode(keys, node);
            }
        }
        node = nextnode;
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

# 输出游标和结果

结合反向迭代算法和链表过滤,我们得到下一次的游标cursor和keys,redis会严格按照redis resp协议进行组装和输出,对应步骤也很清晰:

  1. 输出数组长度为2,1个数组存放游标、1个数组存放结果集
  2. 输出数组1,即下一次的游标
  3. 输出数组2,即下一个数组也就是链表结果集
//说明长度为2
    addReplyMultiBulkLen(c, 2);
    //告知下一次的游标的值
    addReplyBulkLongLong(c,cursor);
    //告知链表的长度
    addReplyMultiBulkLen(c, listLength(keys));
    //从头开始遍历链表
    while ((node = listFirst(keys)) != NULL) {
        robj *kobj = listNodeValue(node);
        addReplyBulk(c, kobj);
        decrRefCount(kobj);
        listDelNode(keys, node);
    }
1
2
3
4
5
6
7
8
9
10
11
12
13

例如:笔者在redis中存放user:1和user:2两个key,对应键入SCAN 0 COUNT 2 MATCH user:*后的输出结果如下:

# 小结

自此,笔者基于redis的scan指令完成了源码解析过程技巧的介绍,总的来说阅读源码时,我们要遵循:

  1. 搭建起调测环境,例如笔者本次的redis源码环境搭建
  2. 明确要调测的源码的输入和输出,对其使用有所感知,例如笔者本次的scan指令的使用场景和效果
  3. 带着阅读的目的去调试源码,以宏观了解流程再逐步了解核心逻辑的方式进行理解学习
  4. 针对不了解的算法可通过搜索引擎或AI理解工作理念,并进行笔算理解调测,必要时可通过反证法推断算法的合理性
  5. 图文梳理理解进行复盘总结

笔者也是一个与时俱进的软件研发者,从底层原理至应用层架构设计,笔者一直以来都是以自己的工作理念探索和推进,我始终无条件坚定自己学习和工作态度,不接受任何反驳,对于那些秉持老旧的工作理念,以过来人方式主观推测与时俱进的方法论的人,我只能说:只有互联网的浪潮退去,那波以红利作为当能力才会知道谁在裸泳。

SharkChili · 禅与计算机程序设计的艺术

开源贡献

  • mini-redis:教学级 Redis 精简实现 · https://github.com/shark-ctrl/mini-redis

关注公众号,回复 【加群】 加入技术社群

# 参考

Redis SCAN 命令 递增地遍历key空间:https://redis.com.cn/commands/scan.html (opens new window)

Redis中的数据库切换:从DB0到DB1的写操作详解-百度开发者中心:https://developer.baidu.com/article/details/3192420 (opens new window)

Redis Scan 原理解析与踩坑:https://www.lixueduan.com/posts/redis/redis-scan (opens new window)

让人爱恨交加的Redis Scan遍历操作原理:http://chenzhenianqing.com/articles/1410.html (opens new window)

一次 Scan 竟耗时上百秒?Redis Scan 原理解析与踩坑:https://blog.csdn.net/java_1996/article/details/122509155 (opens new window)

巴菲特:潮水退了,才知道谁在裸泳。没经历过几个社会经济周期的人,体会不到这句话的含义:https://zhuanlan.zhihu.com/p/2011787421348540777 (opens new window)

编辑 (opens new window)
上次更新: 2026/05/18, 09:28:44
Redis持久化技术AOF要点与详细解答(2)
MySQL基础知识点小结

← Redis持久化技术AOF要点与详细解答(2) MySQL基础知识点小结→

最近更新
01
千万级交易流水慢查询综合治理实践
05-14
02
记一次StarRocks源码级排错和既有架构优化实践
05-13
03
对账核销场景设计与重构实践
05-12
更多文章>
Theme by Vdoing | Copyright © 2025-2026 Evan Xu | MIT License | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式
×
×