禅与计算机 禅与计算机
首页
  • 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中的字典设计与实现
  • 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最佳方案
    • 从开源框架中学习那些实用的位运算技巧
  • 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中的字典设计与实现
  • 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最佳方案
    • 从开源框架中学习那些实用的位运算技巧
  • 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)
  • MySQL

  • ElasticSearch

  • StarRocks

  • 数据库
  • ElasticSearch
sharkchili
2024-10-10
目录

Elasticsearch核心原理与架构设计

# 写在文章开头

在日常业务开发中,你是否遇到过这样的困扰:

  1. 当数据量达到千万甚至亿级别时,模糊查询变得异常缓慢,传统关系型数据库力不从心
  2. 分词器琳琅满目,IK、jieba、ansj...究竟该如何选择,它们底层有什么不同
  3. 为什么 Elasticsearch 能实现秒级返回,而你的 SQL 查询却要等待数秒
  4. 对 Lucene 和 Elasticsearch 的关系一头雾水,不知道它们各自负责什么

本文将从 lucene 讲起,逐步深入 elasticsearch 的设计理念和工作机制:

  1. 理解倒排索引、term dictionary、posting list 等核心概念,这是 Lucene 的立身之本
  2. 探讨从正排索引到倒排索引的演进,以及如何通过排序和二分查找实现从线性扫描到对数级查询的性能跃升
  3. 看到单机索引如何走向分布式,理解 Sharding、Replication、Consistency 等机制背后的设计哲学
  4. 最终理解 Elasticsearch 为什么能够实现秒级全文检索

通过本文的学习,你将:

  1. 深入理解 Lucene 的底层数据结构和工作原理
  2. 掌握倒排索引的设计思路和优化策略
  3. 理解从单机 lucene 到分布式 elasticsearch 的演进逻辑
  4. 能够根据业务场景合理设计索引结构和选择分词策略

你好,我是 SharkChili ,Java Guide 核心维护者之一,对 Redis、Nightingale 等知名开源项目有深度源码研究经验。熟悉 Java、Go 等多语言技术栈,现任某知名黑厂高级研发。

🌟 开源项目贡献

  • mini-redis:教学级 Redis 精简实现,助力分布式缓存原理学习
    🔗 https://github.com/shark-ctrl/mini-redis (opens new window)(欢迎 Star & Contribute)

📚 公众号价值 分享企业级架构设计、性能优化、源码解析等核心技术干货,涵盖分布式系统、微服务治理、大数据处理等实战领域,并探索面向AI的vibe coding等现代开发范式。

👥 加入技术社群 关注公众号,回复 【加群】 获取联系方式,与众多技术爱好者交流分布式架构、微服务等前沿技术!

# Lucene 核心概念

# Lucene 如何实现快速检索

假设我们现在有如下简单示例数据:

hello world
Elasticsearch in action
redis in action
effective java
i am sharkchili
1
2
3
4
5

我们希望从中找到elasticSearch这个关键词的数据,若按照原有的关系型数据库查询思路,就需要遍历并逐一比对文本关键词才能获得目标文本。然而这种遍历查询效率很低。

考虑到查询效率,Lucene采用了空间换时间的思想,提出了类似字典目录的索引方案:以单词为单位生成多个词项,也就是term,以这些词项作为索引与文本id进行关联。查询时我们只需匹配到对应词项,即可快速定位到document_id。这就像查字典时,我们通过拼音索引(term)快速找到目标页码(document_id),而非MySQL查询那样退化为全表扫描。

基于这个document_id,我们又可以快速定位到对应的文本文档数据。需要补充的是,Lucene中,除了倒排索引外,还通过 stored fields 按需单独存储文档的原始字段值。当需要高性能获取特定字段时,可以将字段设置为 store: true,从 stored fields 中直接获取,而无需解析整个 _source。我们将倒排索引、stored fields 等数据结构组合在一起,就构成下图中这个便于检索的索引结构:

例如,当需要单独获取某篇文章的标题用于高亮展示时,可以将该字段设置为 store: true。

首先创建索引,在 mapping 中声明 title 字段需要单独存储:

curl -X PUT 'http://localhost:9200/blog_index' -H 'Content-Type: application/json' -d'
{
  "mappings": {
    "properties": {
      "title": {
        "type": "text",
        "store": true
      },
      "content": {
        "type": "text"
      }
    }
  }
}'
1
2
3
4
5
6
7
8
9
10
11
12
13
14

然后插入一条文档测试数据:

curl -X POST 'http://localhost:9200/blog_index/_doc/1' -H 'Content-Type: application/json' -d'
{
  "title": "Elasticsearch 入门教程",
  "content": "本文介绍 Elasticsearch 的基本使用方法"
}'
1
2
3
4
5

搜索时,通过 stored_fields 参数可以直接获取标题,而无需解析整个 _source:

curl -X GET 'http://localhost:9200/blog_index/_search' -H 'Content-Type: application/json' -d'
{
  "query": { "match": { "content": "Elasticsearch" } },
  "stored_fields": ["title"]
}'
1
2
3
4
5

返回结果中,各字段含义如下:

字段 含义
took 查询耗时(毫秒),本例耗时 47ms
timed_out 是否超时
_shards 命中的分片信息,successful: 1 表示查询成功
hits.total.value 匹配的文档数量
hits.max_score 相关性得分最大值
hits.hits[*]._score 当前文档的相关性得分
fields 通过 stored_fields 参数获取的字段值
{
  "took": 47,                    // 查询耗时 47ms
  "timed_out": false,            // 未超时
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 1,                 // 匹配到 1 条文档
      "relation": "eq"
    },
    "max_score": 0.2876821,      // 最大相关性得分
    "hits": [
      {
        "_index": "blog_index",
        "_type": "_doc",
        "_id": "1",
        "_score": 0.2876821,      // 该文档得分
        "fields": {
          "title": ["Elasticsearch 入门教程"]  // 直接获取的 stored field
        }
      }
    ]
  }
}
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

# 倒排索引:词项检索的基石

# 倒排索引核心概念

我们的文本远远不止这些词项,随着时间的推移需要维护的词项越来越多。当前场景下,我们查询词项时需要进行扫描遍历,平均时间复杂度为O(n)。于是Lucene提出将这些词项按照字典序从小到大进行排序,通过二分查找法将时间复杂度从O(n)提升为O(logN)。

我们整理一下这些概念:

  1. 排序的字典统称为term dictionary
  2. 字典匹配的文本文档统称为posting list

这几个部分共同构成Lucene的核心——倒排索引(Inverted Index)

然而,随着词项数量持续增长,即使二分查找,查询性能仍会下降。term dictionary 规模过于庞大时,无法完全加载到内存中进行查询处理。

于是就有了term index。它基于 FST(Finite State Transducer,有限状态转换器)实现,这是一种基于前缀共享的有限状态自动机结构。FST 将 term dictionary 中具有相同前缀的词项压缩存储在同一个路径上,大幅减少存储空间。term index 占用少量内存,通过它可以快速定位到term dictionary在磁盘中的物理地址,从而高效检索文本文档:

# 倒排索引与正排索引对比

正排索引是与倒排索引相反的概念。它以文档为维度,存储每个文档包含的所有词项,即 doc_id → [term1, term2, ...]。这种结构的优势在于更新文档时只需修改该文档的词项列表,无需维护全局的词项到文档的映射关系,因此维护成本较低。

以一个文档更新为例,假设我们有两篇文档:

doc_id title content
doc_1 Elasticsearch 入门 介绍 ES 基本用法
doc_2 Redis 实战 讲解 Redis 缓存

对应的正排索引结构为:

{
  "doc_1": {
    "title": ["Elasticsearch", "入门"],
    "content": ["介绍", "ES", "基本", "用法"]
  },
  "doc_2": {
    "title": ["Redis", "实战"],
    "content": ["讲解", "Redis", "缓存"]
  }
}
1
2
3
4
5
6
7
8
9
10

假设我们把 doc_1 的 title 从 "Elasticsearch 入门" 改为 "Elasticsearch 进阶",只需更新 doc_1 的 title 字段:

{
  "doc_1": {
    "title": ["Elasticsearch", "进阶"],
    "content": ["介绍", "ES", "基本", "用法"]
  }
}
1
2
3
4
5
6

整个过程只涉及 doc_1 内部的数据变更,无需触碰其他文档或全局的词项映射。相比之下,倒排索引如果修改一个词项,需要更新该词项对应的整个 posting list,影响范围更大。

然而,正排索引在关键词检索时必须扫描每个文档的词项列表才能找到目标关键词,检索性能远不如倒排索引。

以搜索 "Redis" 为例,检索过程如下:

步骤 操作 结果
1 扫描 doc_1 的 title 和 content 未找到 "Redis"
2 扫描 doc_2 的 title 找到!title 中包含 "Redis"
3 返回 doc_2 搜索完成

随着文档数量从 2 增长到 100 万,扫描次数也从 2 次增长到 100 万次,时间复杂度为 O(n)。相比之下,倒排索引只需一步定位:"Redis" → [doc_2],时间复杂度为 O(1)(不考虑 posting list 遍历)。

正排索引与倒排索引结构对比

# Doc Values:排序性能优化

有时候我们还希望检索的数据按特定规则进行排序,例如搜索 Elasticsearch 相关文本后,按插入日期排序。尽管可以将查询结果拉到内存中进行排序,但为了提升排序性能,Lucene 采用空间换时间的思想,通过 doc values 将需要排序的字段值以列式存储整合。这样在检索到相应数据后,可以基于 doc values 一次性取出排序字段进行排序。

Doc Values 的底层实现本质上是列式存储,假设我们有 3 篇文档需要按日期排序:

doc_id title date(时间戳)
doc_1 Elasticsearch 入门 2024-01-15
doc_2 Redis 实战 2024-03-20
doc_3 MySQL 优化 2024-02-10

传统的行式存储需要逐行读取并解析才能获取 date 字段,而 Doc Values 以列式存储:

{
  "doc_id": ["doc_1", "doc_2", "doc_3"],
  "title": ["Elasticsearch 入门", "Redis 实战", "MySQL 优化"],
  "date": ["2024-01-15", "2024-03-20", "2024-02-10"]
}
1
2
3
4
5

排序时,只需读取 date 列即可,无需解析整行数据。同时,Doc Values 还会对数值型字段进行预排序和压缩,进一步提升排序效率。

对比项 无 Doc Values 有 Doc Values
获取排序字段 需要解析每行数据 直接读取排好序的列
时间复杂度 O(n) O(1) 读取列
内存占用 临时排序开销大 预计算,固定空间

实验验证:创建一个包含 keyword、date、long 类型字段的索引,这些字段默认启用 Doc Values:

# 1. 创建索引
curl -X PUT 'http://localhost:9200/test_docvalues' -H 'Content-Type: application/json' -d'
{
  "mappings": {
    "properties": {
      "title": { "type": "text" },
      "author": { "type": "keyword" },
      "publish_date": { "type": "date" },
      "views": { "type": "long" }
    }
  }
}'
1
2
3
4
5
6
7
8
9
10
11
12

插入 3 条测试数据:

curl -X POST 'http://localhost:9200/test_docvalues/_doc/1' -H 'Content-Type: application/json' -d'
{ "title": "Elasticsearch 入门", "author": "张三", "publish_date": "2024-01-15", "views": 1000 }'

curl -X POST 'http://localhost:9200/test_docvalues/_doc/2' -H 'Content-Type: application/json' -d'
{ "title": "Redis 缓存实战", "author": "李四", "publish_date": "2024-03-20", "views": 2500 }'

curl -X POST 'http://localhost:9200/test_docvalues/_doc/3' -H 'Content-Type: application/json' -d'
{ "title": "MySQL 优化指南", "author": "王五", "publish_date": "2024-02-10", "views": 1800 }'
1
2
3
4
5
6
7
8

执行按时间戳升序排序的查询:

curl -X GET 'http://localhost:9200/test_docvalues/_search' -H 'Content-Type: application/json' -d'
{
  "sort": [
    { "publish_date": { "order": "asc" } }
  ]
}'
1
2
3
4
5
6

返回结果中,sort 字段直接返回排好序的时间戳值,证明 Doc Values 已生效:

{
  "took": 29,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 3,
      "relation": "eq"
    },
    "max_score": null,
    "hits": [
      {
        "_index": "test_docvalues",
        "_type": "_doc",
        "_id": "1",
        "_score": null,
        "_source": {
          "title": "Elasticsearch 入门",
          "author": "张三",
          "publish_date": "2024-01-15",
          "views": 1000
        },
        "sort": [1705276800000]
      },
      {
        "_index": "test_docvalues",
        "_type": "_doc",
        "_id": "3",
        "_score": null,
        "_source": {
          "title": "MySQL 优化指南",
          "author": "王五",
          "publish_date": "2024-02-10",
          "views": 1800
        },
        "sort": [1707523200000]
      },
      {
        "_index": "test_docvalues",
        "_type": "_doc",
        "_id": "2",
        "_score": null,
        "_source": {
          "title": "Redis 缓存实战",
          "author": "李四",
          "publish_date": "2024-03-20",
          "views": 2500
        },
        "sort": [1710892800000]
      }
    ]
  }
}
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

可以看到返回结果按 publish_date 升序排列(从 1月15日 → 2月10日 → 3月20日),sort 字段中的时间戳值也已预排序好。

# Segment:数据读写的核心机制

基于上述概念,我们将Inverted Index、term index、doc values、stored fields等组合起来,就构成Lucene中一个非常重要的文件——segment。Lucene支持将多份文档写入一个segment中,为了保证并发写入的效率,Lucene提出一旦segment生成后就不可修改,所有新文档都写入新的segment中。

随着文档增加,segment也会不断增加,此时Lucene底层会将这些小segment合并成更大的segment(类似 Leveled Compaction 策略)。这种设计导致要检索的数据可能分布在不同的segment上,所以Lucene在查询时往往会并发查询多个segment并聚合结果:

即使如此,高并发场景下仍可能出现资源争抢导致的阻塞。为此,Elasticsearch按业务划分不同的index,每个index由多个shard(Lucene 索引实例)组成,数据隔离到不同的shard中,从而实现资源的隔离和负载均衡:

按业务划分 Index 和 Shard

在此基础上,再将每个Lucene内部进行分片,通过shard将数据切片后分摊到多台服务器上,从而缓解单台服务器的压力:

# Elasticsearch 数据类型

# 常见数据类型概览

# Keyword 类型

Elasticsearch 常见的关键词类型有:keyword、constant_keyword 和 wildcard。

keyword 类型用于精准匹配和聚合查询,该类型在查询时以整个词项为单位,无需像 text 类型那样进行分词。

精准匹配即精确查找数据,例如创建一个 product_index 索引,包含 product_id 和 product_category 两个 keyword 类型字段。查询时 ES 不会进行分词,而是基于完整的词项进行匹配:

# 创建索引
curl -X PUT 'http://localhost:9200/product_index' -H 'Content-Type: application/json' -d'
{
  "mappings": {
    "properties": {
      "product_id": { "type": "keyword" },
      "product_category": { "type": "keyword" }
    }
  }
}'

# 插入文档
curl -X POST 'http://localhost:9200/product_index/_doc/1' -H 'Content-Type: application/json' -d'
{
  "product_id": "P001",
  "product_category": "Electronics"
}'

# 查询(使用 term 精准匹配)
curl -X GET 'http://localhost:9200/product_index/_search' -H 'Content-Type: application/json' -d'
{
  "query": {
    "term": { "product_id": "P001" }
  }
}'
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

因为 keyword 类型是精准匹配的,在进行分类字段的聚合统计时可以高效完成。


constant_keyword 用于索引中所有文档该字段值必须相同的场景,适用于日志级别、订单状态等固定枚举值。一旦设置后,所有文档的该字段值必须与预设值一致,否则写入失败:

# constant_keyword 适用于固定枚举值场景
curl -X PUT 'http://localhost:9200/logs_index' -H 'Content-Type: application/json' -d'
{
  "mappings": {
    "properties": {
      "log_level": {
        "type": "constant_keyword",
        "value": "INFO"
      },
      "message": { "type": "text" }
    }
  }
}'
1
2
3
4
5
6
7
8
9
10
11
12
13

因为天然不可变且精准匹配,检索效率比 keyword 更高。


wildcard 用于通配符匹配,适用于数据量小且查询方式多样的场景:

curl -X GET 'http://localhost:9200/books/_search' -H 'Content-Type: application/json' -d'
{
  "query": {
    "wildcard": { "title": "*search*" }
  }
}'
1
2
3
4
5
6

由于需要遍历匹配,性能远不如精准匹配,仅适用于特殊查询场景。

# 基本数值类型

ES 的基本数值类型与编程语言类似:

  1. long、integer、short、byte
  2. double、float、half_float、scaled_float
  3. boolean(布尔型)
  4. date、date_nanos(日期型)
  5. binary(二进制)

# 结构化数据类型

结构化数据类型相对少见,常见的有范围类型和 IP 地址类型。

范围类型通过指明文档字段的数值区间,查询时也是通过区间进行匹配,只要文档的区间与查询区间存在交集就会返回。例如创建一个 price_range 索引,文档区间为 10~20,查询区间为 15~25 时,该文档会被返回:

Range 类型区间匹配示意

# 创建索引
curl -X PUT 'http://localhost:9200/range_index' -H 'Content-Type: application/json' -d'
{
  "mappings": {
    "properties": {
      "price_range": { "type": "integer_range" }
    }
  }
}'

# 插入区间为 10~20 的文档
curl -X POST 'http://localhost:9200/range_index/_doc/1' -H 'Content-Type: application/json' -d'
{
  "price_range": { "gte": 10, "lte": 20 }
}'

# 查询 15~25 区间的数值
curl -X GET 'http://localhost:9200/range_index/_search' -H 'Content-Type: application/json' -d'
{
  "query": {
    "range": {
      "price_range": { "gte": 15, "lte": 25 }
    }
  }
}'
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

ES 除了 integer_range 以外还有:

  1. float_range
  2. long_range
  3. double_range
  4. date_range

IP 地址类型用于记录 IPv4 和 IPv6 两种地址,支持范围查询,同时进行格式校验,对于非法的 IP 地址,ES 会明确抛出错误并禁止插入。

# 文本搜索类型

文本类型是 ES 中最常用的类型之一,它基于分词器对检索词进行切词,然后匹配合适结果。主要包括:

  1. text
  2. annotated-text(Elasticsearch 7.8.0 引入)
  3. completion

text 类型是常规的文本类型,支持全文搜索,允许在大量文本中进行模糊匹配和相关性搜索。传入的检索词会按照分词器的规则进行切词,通过 match 或 match_phrase 等手段实现不同程度的文本匹配:

Text 类型分词匹配示意

# 创建索引
curl -X PUT 'http://localhost:9200/text_index' -H 'Content-Type: application/json' -d'
{
  "mappings": {
    "properties": {
      "article_content": { "type": "text" }
    }
  }
}'

# 插入文档
curl -X POST 'http://localhost:9200/text_index/_doc/1' -H 'Content-Type: application/json' -d'
{ "article_content": "hello world" }'

curl -X POST 'http://localhost:9200/text_index/_doc/2' -H 'Content-Type: application/json' -d'
{ "article_content": "redis server" }'

# 查询 "hello" 会被分词,可能匹配到 "hello world"
curl -X GET 'http://localhost:9200/text_index/_search' -H 'Content-Type: application/json' -d'
{
  "query": {
    "match": { "article_content": "hello" }
  }
}'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

annotated-text 是 text 的特殊变种,用于结构化注释文本。通过 annotations 数组为文本中的实体添加类型、位置等信息,支持根据注释进行过滤、分组、排序:

curl -X POST 'http://localhost:9200/annotated_text_index/_doc/1' -H 'Content-Type: application/json' -d'
{
  "annotated_text_field": {
    "text": "Apple is looking at buying U.K. startup for $1 billion",
    "annotations": [
      { "type": "entity", "start": 0, "end": 5, "value": "Apple", "category": "company" },
      { "type": "entity", "start": 23, "end": 31, "value": "U.K.", "category": "country" },
      { "type": "entity", "start": 34, "end": 40, "value": "startup", "category": "organization" }
    ]
  }
}'
1
2
3
4
5
6
7
8
9
10
11

completion 用于前缀搜索和自动补全,常用于搜索引擎的关键词推荐功能:

curl -X POST 'http://localhost:9200/completion_index/_doc/1' -H 'Content-Type: application/json' -d'
{
  "suggestions": ["apple", "application", "banana", "cherry"]
}'
1
2
3
4

搜索 "ap" 时,会返回 "apple" 和 "application" 等前缀匹配的建议。

# 对象关系类型

对象关系类型强调对象间的组合关系,常见的两种对象关系大类有:

  1. 嵌套类型:nested、join
  2. 对象类型:object、flattened

object 类型即 JSON 对象类型,这里不做赘述。我们以 nested 类型为例讨论:假设需要通过 reviews 字段记录评论者信息,默认使用 object 类型时,ES 底层的 Lucene 并不会针对该类型进行结构化,存储时 reviews 会按照字段的方式归类并构建成一个个字段数组,导致查询时可能出现错误的跨对象匹配:

Object 类型数组扁平化示意

为了保证结构化数据的完整性,ES 提出了 nested 类型:

# 创建索引
curl -X PUT 'http://localhost:9200/nested_example_index' -H 'Content-Type: application/json' -d'
{
  "mappings": {
    "properties": {
      "product_name": { "type": "text" },
      "reviews": {
        "type": "nested",
        "properties": {
          "reviewer_name": { "type": "text" },
          "rating": { "type": "integer" },
          "comment": { "type": "text" }
        }
      }
    }
  }
}'

# 插入文档(包含两条评论)
curl -X POST 'http://localhost:9200/nested_example_index/_doc/1' -H 'Content-Type: application/json' -d'
{
  "product_name": "Sample Product",
  "reviews": [
    { "reviewer_name": "Alice", "rating": 4, "comment": "This product is great!" },
    { "reviewer_name": "Bob", "rating": 3, "comment": "It's okay, but could be better." }
  ]
}'

# 查询评分 4 且评论者名称为 Alice 的文档
curl -X GET 'http://localhost:9200/nested_example_index/_search' -H 'Content-Type: application/json' -d'
{
  "query": {
    "nested": {
      "path": "reviews",
      "query": {
        "bool": {
          "must": [
            { "match": { "reviews.rating": 4 } },
            { "match": { "reviews.reviewer_name": "Alice" } }
          ]
        }
      }
    }
  }
}'
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

nested 类型将每个对象单独结构化,针对单个对象的字段使用数组的方式维护,保证对象的独立性:

Nested 类型独立对象示意


join 类型用于文档间的父子关系,通过 join 可以使不同文档通过父子 ID 构成关联,常用于问答系统等需要父子结构化的场景:

Join 类型父子关系示意

# 创建父子索引
curl -X PUT 'http://localhost:9200/my_index' -H 'Content-Type: application/json' -d'
{
  "mappings": {
    "properties": {
      "text": { "type": "keyword" },
      "my_join_field": {
        "type": "join",
        "relations": { "question": "answer" }
      }
    }
  }
}'

# 创建父文档(question)
curl -X PUT 'http://localhost:9200/my_index/_doc/1?refresh' -H 'Content-Type: application/json' -d'
{
  "text": "我是第一个问题",
  "my_join_field": { "name": "question" }
}'

# 创建子文档(answer),通过 parent 关联到 id=1 的问题
curl -X PUT 'http://localhost:9200/my_index/_doc/3?routing=1&refresh' -H 'Content-Type: application/json' -d'
{
  "text": "问题一的答案1",
  "my_join_field": { "name": "answer", "parent": "1" }
}'

# 查询问题 1 的所有答案
curl -X GET 'http://localhost:9200/my_index/_search' -H 'Content-Type: application/json' -d'
{
  "query": {
    "parent_id": { "type": "answer", "id": "1" }
  }
}'
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

需要注意的是,has_child 和 has_parent 查询对性能影响较大,查询耗时随子文档数量增加而上升。根据压测数据,在 5 个分片的情况下,十万级父表和千万级子表关联耗时基本可以在 100ms 内完成,对于大部分 B 端业务是可接受的。

应谨慎使用 join 类型。在业务允许的情况下,更推荐通过宽表冗余的非规范化方案解决问题。


flattened 类型用于处理动态 JSON 对象。ES 在没有预先创建 mapping 的情况下插入文档时,会根据字段推断类型完成 mapping 创建。但如果 JSON 对象中的键值对是动态变化的,无法预先统计类型,此时可以将字段声明为 flattened 类型:

# 创建索引
curl -X PUT 'http://localhost:9200/bug_reports' -H 'Content-Type: application/json' -d'
{
  "mappings": {
    "properties": {
      "title": { "type": "text" },
      "labels": { "type": "flattened" }
    }
  }
}'

# 动态插入 JSON 数据
curl -X POST 'http://localhost:9200/bug_reports/_doc/1' -H 'Content-Type: application/json' -d'
{
  "title": "Results are not sorted correctly.",
  "labels": {
    "priority": "urgent",
    "release": ["v1.2.5", "v1.3.0"],
    "timestamp": { "created": 1541458026, "closed": 1541457010 }
  }
}'

# 查询
curl -X GET 'http://localhost:9200/bug_reports/_search' -H 'Content-Type: application/json' -d'
{
  "query": {
    "term": { "labels": "v1.2.5" }
  }
}'
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

flattened 类型支持精准查询,但不支持数字范围查询和高亮显示。

# 空间类型

空间类型用于存储地理坐标等数据,常见的有:

  1. 地理坐标类型:geo_point
  2. 地理形状类型:geo_shape

使用场景较少,了解即可。


# Keyword 与 Text 的区别

text 类型进行数据检索时会经过分词的步骤,通过分词得到词项后再进行数据检索;而 keyword 类型直接作为检索词项进行查询,所以 keyword 查询效率更高:

Keyword 与 Text 分词对比


# Nested 类型详解

传统 object 类型传入对象数组进行查询时会因数组扁平化导致多字段笛卡尔积检索问题:

Object 类型数组扁平化问题

而 nested 类型本质上就是保持对象数组中各个对象的独立性,底层会将对象数组中每个对象独立划分,保证查询维度能够精准定位到数组中各个独立的对象,避免上述问题:

Nested 类型独立对象模型


# ES 数组类型的处理方式

ES 没有明确定义数组类型,但每个字段可以用数组形式记录多个值,间接实现了数组类型。例如 tags 字段可以同时存储多个标签值:

curl -X POST 'http://localhost:9200/articles/_doc/1' -H 'Content-Type: application/json' -d'
{
  "title": "Elasticsearch 入门",
  "tags": ["教程", "搜索", "数据库"],
  "views": [100, 200, 300]
}'
1
2
3
4
5
6

ES 会自动将数组中所有值索引,无需额外配置。


# Mapping 字段类型修改

不支持修改。ES 的 mapping 可类比于 MySQL 的 schema,字段类型在存在数据的情况下不能修改,但可以增加字段。如果需要修改,只能通过 reindex 等方式重建索引。


# 防止 Mapping 字段膨胀

可以参考 ES 官网给出的配置加以限定: https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-settings-limit.html (opens new window)

# Mapping 映射机制

# Mapping 是什么

Mapping 本质上是对索引中字段名称、数据类型、优化信息(是否索引)等结构的定义,相当于数据表的 schema。一个 index 对应一个 mapping。

Mapping 分为动态 mapping和静态 mapping:

  • 动态 mapping:用户提交索引数据时自动创建 mapping
  • 静态 mapping:通过 PUT 请求显式创建

# 插入数据为何无需指定 Mapping

ES 接受插入数据请求时会检查该索引是否存在,如果不存在则会基于参数推断并自动创建相应的 mapping。但自动推断的类型往往与预期不符,所以一般不建议依赖自动创建。

# 字段索引控制

将 index 设置为 false 即可使字段不可搜索:

# 创建索引时指定字段不可搜索
curl -X PUT 'http://localhost:9200/my_index' -H 'Content-Type: application/json' -d'
{
  "mappings": {
    "properties": {
      "title": { "type": "text" },
      "description": { 
        "type": "text",
        "index": false
      }
    }
  }
}'

# 插入文档
curl -X POST 'http://localhost:9200/my_index/_doc/1' -H 'Content-Type: application/json' -d'
{
  "title": "示例文档标题",
  "description": "这是一段示例描述"
}'

# 查询 description 字段时会抛出异常
curl -X GET 'http://localhost:9200/my_index/_search' -H 'Content-Type: application/json' -d'
{
  "query": {
    "match": { "description": "示例" }
  }
}'
# 错误:Cannot search on field [description] since it is not indexed.
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

index: false 的字段不会建立倒排索引,无法被搜索,但字段值仍会存储在 _source 中。

# Elasticsearch 设计理念

# ES 解决的问题与使用场景

Elasticsearch 简称 ES,是一款基于 Lucene 实现的开源搜索引擎和分析引擎,主要用于以下场景:

  1. 全文检索:对海量文本进行快速搜索
  2. 结构化查询:支持精确匹配、范围查询、布尔查询等
  3. 数据分析:聚合统计、实时分析

ES 对外提供 RESTful Web API,可以非常方便地完成数据查询与检索。


# 从关系型数据库理解 ES 核心概念

初学 ES 的读者可能对某些概念比较陌生,下面以关系型数据库作为类比:

ES 概念 关系型数据库 说明
索引(index) 数据表 具有相似特征文档的集合
文档(document) 行 搜索的最小单位,由多个字段构成
类型(type) - ES 7.x 后已废弃(ES 6.x 支持多 type,ES 8.x 完全移除)
映射(mapping) schema 字段名称、类型、分词器、是否索引等定义
倒排索引(Inverted Index) 索引 提升检索速度的核心数据结构
字段(field) 列 文档的基本组成单元
DSL SQL ES 数据查询检索语法
节点(node) - ES 实例运行的单个进程
集群(cluster) - 多个节点组成的分布式系统
分片(shard) - 索引的水平拆分,分散读写压力
副本(replica) - 分片的冗余备份,保证高可用

# 自定义路由的优势

默认路由使用 MurmurHash3 算法对 doc_id 取模,均匀分发数据到每个分片。这导致某些查询需要向多个分片发送请求,再进行过滤、聚合、归并,在数据量较大时查询性能较差。

通过自定义路由,使用 routing 参数指定路由值(如 user_id),保证同一用户的数据都存放到同一个分片下,从而减少跨分片查询,提升检索效率。

# 同一用户的所有数据路由到同一分片
curl -X POST 'http://localhost:9200/orders/_doc?routing=user_123' -H 'Content-Type: application/json' -d'
{
  "user_id": "user_123",
  "product": "iPhone",
  "amount": 8999
}'
1
2
3
4
5
6
7

使用场景:用户中心场景下,同一用户的订单、收藏等数据应路由到同一分片,避免跨分片关联查询。


# 查看集群健康状态

curl -X GET 'http://localhost:9200/_cluster/health'
1
状态 说明
green 所有主分片和副本分片都已分配,理想状态
yellow 所有主分片已分配,但副本分片因节点故障或刚启动等原因未分配
red 有分片未分配,通常发生在集群刚启动时

假设启动一个单节点集群,有一个针对 student 的索引,配置 3 个主分片和 3 个副本分片。启动时这些分片尚未分配,状态为红色。

单节点启动后,所有主分片完成分配,此时状态为黄色,副本尚未分配。由于所有分片在同一节点存在单点故障风险,我们新增一个节点加入集群。此时 Elasticsearch 集群会触发重平衡操作,将分片重新分布。但状态仍为黄色,因为副本分片仍未分配到新节点。

新节点加入并完成副本分配后,基于高可用原则,每个主分片的副本会创建在不同的节点上。此时状态变为绿色。

硬件故障风险始终存在。假设节点 1 突然崩溃,集群会经历以下过程:

  1. 主分片 1 和分片 2、分片 3 副本下线,状态变为红色
  2. 节点 2 的副本分片晋升为主分片,所有主分片都存在,状态变为黄色

Elasticsearch 通过副本冗余实现重平衡和故障恢复,后续只需恢复节点 1,集群就会重新分配分片,最终恢复为绿色状态。


# 高可用设计

为避免单个分片宕机导致服务不可用,ES 通过主从复制保证分片间数据同步。当主分片不可用时,从分片直接取代主分片对外提供服务:

主从复制架构

副本数可在创建索引时设置:number_of_replicas: 2。与 MySQL 主从复制的区别:ES 是异步复制。


# 角色化节点设计

ES 不同的工作由不同角色承担:

角色 说明
主节点(master node) 负责集群管理,如创建/删除索引、节点加入/离开、分片分配
协调节点(coordinate node) 接收客户端请求,分发到数据节点,聚合结果(所有节点默认都是协调节点)
数据节点(data node) 存储数据,执行 CRUD、搜索、聚合(默认所有节点都是数据节点)

每个角色可独立扩展,按需配置资源:

角色化节点架构

# elasticsearch.yml 配置示例
node.master: true   # 可参与主节点选举
node.data: true     # 可存储数据
node.ingest: true   # 可处理预处理管道
1
2
3
4

# 主节点选举机制

ES 并未采用中心化选举,而是通过 Raft 协议实现节点间通信,通过节点间数据信息交换完成实时状态更新和主节点选举:

Raft 选举机制

Raft 协议三个核心机制:

  1. Leader Election:通过 term 和投票机制选主
  2. Log Replication:主节点写入后同步到从节点
  3. Safety:term 更大的节点才能成为主节点

防止脑裂:minimum_master_nodes = (num_nodes/2)+1 确保只有多数派节点能组成集群。


# 数据写入流程

基于上述架构,数据写入流程如下:

  1. 客户端发起写请求,到达协调节点
  2. 协调节点根据 routing 参数计算 hash 值,定位主分片
  3. 主分片将数据写入内存 Buffer,同时写入 Translog(类似 MySQL redo log,保证宕机数据不丢失)
  4. 同步:将数据转发到副本分片,等待副本确认
  5. 返回确认给客户端
  6. 异步:Buffer → Lucene Segment(Refresh,默认 1 秒一次,这就是 ES 近实时搜索的原理)

数据写入流程


# 数据查询流程

  1. 客户端发起查询请求,到达协调节点
  2. 协调节点根据 index 获取所有相关分片信息
  3. 并发查询所有分片,利用倒排索引定位匹配的 doc_id + 评分
  4. 各分片返回 doc_id + score,协调节点合并结果
  5. 协调节点执行全局 Sort → Pagination
  6. 根据 doc_id 到数据节点批量获取完整文档(_source 或 stored fields),返回结果

数据查询流程

# 分片数量设计策略

针对分片数量的设计,建议从以下几个问题入手:

  1. 如何设计索引?
  2. 单个分片大小多少合适?
  3. 一个索引设置多少个主分片合适?
  4. 一个索引副本分片设置多少合适?
  5. 单个节点允许多少个分片合适?
  6. 设置多少个节点合适?

# 如何设计索引

设计索引时,需要避免创建过多的索引和分片,以减少查询时归并与聚合的开销。推荐采用时序性的方式创建索引,按照日、周、月等周期管理索引,以便根据不同业务时期灵活调整分片数量。

# 单个分片大小

分片是由一个个 Lucene 索引实例构成,每个 Lucene 实例都是持有文件句柄的单独文件。分片过小会导致段过小,增加开销;分片过大则带来以下问题:

  • 堆内存压力:Segment 元数据、Field Data Cache、Query Cache 等组件占用堆内存,分片越大占用越多,可能导致 OOM 或频繁 GC
  • 故障恢复:节点故障时,集群需要从副本复制数据重建分片。分片越大(如 100GB),恢复所需复制的数据量越大,集群处于 yellow/red 状态的时间越长
  • Segment 合并开销:Lucene 的合并流程为"读取多个小 Segment → 内存排序 → 写入新的大 Segment → 删除旧 Segment"。合并时需要同时持有被合并的所有 Segment,分片越大,合并涉及的数据量越大,磁盘 I/O 和内存占用越高,且会与查询竞争资源

按照 Elasticsearch 官方建议,单分片大小应控制在 20GB 至 40GB 之间。这个建议更多是官方基于通用场景的经验值,实际生产中应根据业务特点灵活调整。例如 GitHub 的 Elasticsearch 集群采用 128 个分片,每个分片约 120GB。官方建议与大规模生产环境的实践存在差异,说明具体配置需要结合数据量、查询模式、硬件资源等因素综合考量。

# 主分片数量

结合堆内存分配法则:

  • 物理内存的 50%:堆内存不应超过物理内存的 50%(剩余内存供 Lucene 缓存文件系统数据)
  • 绝对上限 32GB:即使物理内存超过 64GB,堆内存也不应超过 32GB(与 JVM 指针压缩技术有关)

Elasticsearch 官方建议:每 GB 堆内存对应少于 20 个分片。

假设服务器 16G 内存,堆空间约 8G,单节点最多可维护 160 个分片。结合单个分片 40G 的经验值,若每日数据量约 200G,则 5 个主分片即可满足需求。

# 副本分片数量

ES 6 默认配置为 5 主分片 + 1 副本,ES 7 改为 1 主分片 + 1 副本。在分布式场景下,ES 7 的策略足够满足大多数需求;若对高可用要求较高,建议设置 2 个副本。

PUT /testindex
{
   "settings" : {
      "number_of_shards" : 5,
      "number_of_replicas" : 1
   }
}
1
2
3
4
5
6
7

# 节点数量

根据主分片和副本分片的规划,计算所需的数据节点数量。副本应分布在不同节点上以保证高可用。

注:上述为理论参考,具体建议结合业务场景进行基准测试后调整。

# 实际评估示例

假设业务场景:企业级日志分析平台

参数 数值
每月数据量 500GB
保留周期 12 个月
单节点内存 64GB
单节点堆内存 30GB(留 50% 给 Lucene 页缓存)
高可用要求 支持 1 个节点故障

步骤 1:确定单分片大小

根据官方建议和 GitHub 实践,取中间值 50GB 作为单分片目标大小。

步骤 2:计算主分片数量

由于采用时序性索引按月管理,每月生成一个新索引,因此索引大小应与单月数据量匹配:

每月数据量 / 单分片大小 = 500GB / 50GB ≈ 10 个主分片
1

总数据量 6TB(500GB × 12 个月)分散在 12 个索引中,每个索引约 500GB。

步骤 3:确定副本分片数量

基于上述推算,单月索引需要 10 个主分片。高可用要求支持 1 个节点故障,至少需要 1 个副本:

总分片数 = 主分片 × (1 + 副本数) = 10 × (1 + 1) = 20 个分片
1

步骤 4:验证节点分片容量

验证单节点能否承载总分片数(考虑 6TB 总数据量分散在 12 个索引中):

单节点最大分片数 = 堆内存 30GB × 20 = 600 个分片
单索引总分片数 = 10 × 2 = 20 个分片
1
2

每个索引 20 个分片,12 个索引共 240 个分片(主 + 副本),远低于单节点上限 600 个,节点分片容量充足。

步骤 5:确定节点数量

考虑高可用,副本应分布在不同节点:

  • 数据节点:至少 2 个(主分片 + 副本分片分布)
  • 考虑 1 个节点故障的场景,建议配置 3 个数据节点

最终配置:

PUT /logs-2024-03-01
{
   "settings" : {
      "number_of_shards" : 10,
      "number_of_replicas" : 1
   }
}
1
2
3
4
5
6
7

集群拓扑:

节点 1:P0-P3 + R4-R9
节点 2:P4-P6 + R0-R3
节点 3:P7-P9 + R0-R3
1
2
3

任意 1 个节点故障,仍有完整数据副本,集群保持绿色状态。

# 小结

本文从 Lucene 底层引擎出发,逐步展开介绍了 Elasticsearch 的核心概念与架构设计:

  • Lucene 基础:通过倒排索引、Doc Values 和 Segment 机制,理解 ES 高效检索的底层原理
  • 数据类型:掌握 ES 常见数据类型(Keyword/Text/Nested/Geo 等)及其适用场景
  • Mapping 映射:理解 Mapping 的定义、动态映射机制及字段索引控制
  • 分布式架构:了解集群健康状态、副本高可用、角色化节点设计及主节点选举机制
  • 数据流:掌握数据写入(Buffer → Translog → Segment)与查询(协调节点 → 分片合并 → 聚合排序)的完整流程
  • 分片设计:学会基于数据量、堆内存、高可用要求设计合理的分片数量与副本策略

你好,我是 SharkChili ,Java Guide 核心维护者之一,对 Redis、Nightingale 等知名开源项目有深度源码研究经验。熟悉 Java、Go 等多语言技术栈,现任某知名黑厂高级研发。

🌟 开源项目贡献

  • mini-redis:教学级 Redis 精简实现,助力分布式缓存原理学习
    🔗 https://github.com/shark-ctrl/mini-redis (opens new window)(欢迎 Star & Contribute)

📚 公众号价值 分享企业级架构设计、性能优化、源码解析等核心技术干货,涵盖分布式系统、微服务治理、大数据处理等实战领域,并探索面向AI的vibe coding等现代开发范式。

👥 加入技术社群 关注公众号,回复 【加群】 获取联系方式,与众多技术爱好者交流分布式架构、微服务等前沿技术!

# 参考

ES详解 - 认知:ElasticSearch基础概念:https://www.pdai.tech/md/db/nosql-es/elasticsearch-x-introduce-1.html (opens new window)

elasticSearch 是什么?工作原理是怎么样的?:https://mp.weixin.qq.com/s/RUQXIyN95hvi2wM3CyPI9w (opens new window)

【图解】IO趣事|图文结合让你搞透Elasticsearch:https://www.cnblogs.com/-wenli/p/12763887.html (opens new window)

深度剖析 Elasticsearch 集群是如何分片的,一文讲透:https://blog.csdn.net/laoyang360/article/details/82950393 (opens new window)

ES 索引分片内部原理,看完这篇就懂了:https://blog.csdn.net/ubuntutouch/article/details/103713730 (opens new window)

Elasticsearch 面试题第三弹:https://www.cnblogs.com/vincentfhr/p/14006855.html (opens new window)

我的 Elasticsearch 集群应该有多少个分片?:https://www.elastic.co/cn/blog/how-many-shards-should-i-have-in-my-elasticsearch-cluster (opens new window)

Elasticsearch面试题汇总:https://www.cnblogs.com/c9999/p/13686126.html (opens new window)

《Elasticsearch 实战》第二版:https://book.douban.com/subject/34972209/ (opens new window)

编辑 (opens new window)
上次更新: 2026/04/01, 11:20:53
最近更新
01
Windows 10 下的 Maven 安装配置教程
05-11
02
基于 Claude Code 复刻 Redis 慢查询指令实践
05-11
03
VSCode与Claude Code后端开发环境搭建与AI编程工作流实践
05-09
更多文章>
Theme by Vdoing | Copyright © 2025-2026 Evan Xu | MIT License | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式
×
×