禅与计算机 禅与计算机
首页
  • 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中的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技术原理
  • 深入理解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后端开发环境搭建与实践
    • 基于提示词工程的Redis签到功能开发实践
    • 基于Vibe Coding的Redis分页查询实现
    • 告别AI无效对话:资深工程师的提示词设计最佳实践
  • 实用技巧与配置

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

    • 写好技术博客的5大核心原则:从认知科学到AI工具的全流程指南
  • 开发工具

    • IDEA配置详解与高效使用指南
  • 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

    • 从Lucene到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 有序集合指令复刻探索
  • 项目编排

    • 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插件评测:祖传代码重构与接口优化实战
关于
收藏
  • 分类
  • 标签
  • 归档
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中的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技术原理
  • 深入理解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后端开发环境搭建与实践
    • 基于提示词工程的Redis签到功能开发实践
    • 基于Vibe Coding的Redis分页查询实现
    • 告别AI无效对话:资深工程师的提示词设计最佳实践
  • 实用技巧与配置

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

    • 写好技术博客的5大核心原则:从认知科学到AI工具的全流程指南
  • 开发工具

    • IDEA配置详解与高效使用指南
  • 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

    • 从Lucene到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 有序集合指令复刻探索
  • 项目编排

    • 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插件评测:祖传代码重构与接口优化实战
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Redis

    • Redis核心知识小结
    • Redis源码与实战剖析小结
    • Redis系列文章全汇总
    • 解锁Redis发布订阅模式:通过实践演示挖掘消息通信潜能
    • 掌握 Redis 事务,提升数据处理效率的必备秘籍
    • 基于Jedis来探讨池化技术
      • 为什么需要池化技术
      • 池化技术实现的三种方式
        • xxxPool和xxxConnection
        • xxxClient和xxxPool
        • xxxConnection对象
      • Jedis使用不当导致的线程安全问题
        • 前置步骤
        • 问题重现
        • 阅读源码了解究竟
        • 基于debug剖析原因
        • 解决方案
        • 小结
      • 使用和不使用池化技术性能之间的差距
        • 问题简介
        • 前置步骤
        • 错误的池化使用示例
        • 正确的池化使用示例
        • 小结池化技术性能压测实验
      • 池化配置问题
        • 最大连接数的配置可能造成的问题
        • 基于数据库连接池复现问题
        • 解决思路
      • 参考文献
    • 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

  • 数据库
  • Redis
sharkchili
2022-12-14
目录

基于Jedis来探讨池化技术

# 为什么需要池化技术

系统运行时必然是需要数据库连接、线程等一些重量级对象,频繁的创建这种对象对性能有着不小的开销,所以为了减少没必要的创建和开销,我们就用到了池化技术。 通过创建一个资源池来保存这些资源便于后续的复用,从而提升系统性能。

在这里插入图片描述

# 池化技术实现的三种方式

一般来说使用池化技术的客户端有以下三种形式:

# xxxPool和xxxConnection

这种池化方式的实现即我们从xxxPool获取到xxxConnection对象,我们在使用xxxConnection对象之后必须手动调用某些API将资源归还,否则可能会导致内存泄漏之类的问题。并且xxxConnection对象一般都是非线程安全的,多线程操作这类对象很可能导致一些意外情况发生。

在这里插入图片描述

# xxxClient和xxxPool

xxxClient这种形式用法一般都是从xxxPool获取这个对象,我们使用完成后无需手动将资源归还,xxxClient会自动完成这种操作,一般来说这种对象都是线程安全的。

# xxxConnection对象

xxxConnection就是一个单独的连接对象,是一个完全的单一连接对象,不仅性能一般,还需要手动获取和释放,使用不当极可能导致线程安全问题。

# Jedis使用不当导致的线程安全问题

# 前置步骤

在完成这个实验之前我们必须完成对实验环境的搭建,首先肯定是下载并启动redis服务端,由于笔者使用的是Windows系统,所以这里就简单的下载了一个Windows环境下的redis。下载地址为:

https://github.com/tporadowski/redis/releases (opens new window)

首先自然是在spring boot项目中引入jedis的依赖:

 <!--jedis依赖-->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>3.2.0</version>
        </dependency>
1
2
3
4
5
6

完成这些步骤之后,我们编写一个简单的测试用例测试一下连通性

public class JedisTest {


    private static Logger logger = LoggerFactory.getLogger(JedisTest.class);

    /**
     * 测试ping
     */
    @Test
    public void testPing() {
        Jedis jedis = null;
        try {
            jedis = new Jedis("127.0.0.1");
            String result = jedis.ping();
            Assert.isTrue("PONG".equals(result),"unable to connect redis");
        } finally {
            jedis.close();

        }

    }

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

从输出结果来看,环境准备完成了,接下来就可以开始实验了。

在这里插入图片描述

# 问题重现

为了方便实验,我们首先基于redis客户端往redis中设置两个值,分别是是(a,1)和(b,2)

  try (Jedis jedis = new Jedis("127.0.0.1", 6379)) {
            Assert.isTrue("OK".equals(jedis.set("a", "1")), "set a=1 return ok");
            Assert.isTrue("OK".equals(jedis.set("b", "2")), "set b=2 return ok");
        }
1
2
3
4

然后我们再编写下面这样一段代码,可以看到两个线程都是用同一个redis客户端获取对象,线程1获取a的值,如果不为1,则输出一段警告。线程不断获取b的值,如果b的value不为2则也输出一段警告。

@GetMapping("wrong")
    public void wrong() throws InterruptedException {
        Jedis jedis = new Jedis("127.0.0.1");
        CountDownLatch countDownLatch = new CountDownLatch(2);
        new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                String result = jedis.get("a");
                if (!"1".equals(result)) {
                    log.warn("expect to be 1 but found {}", result);
                    return;
                }
            }
            countDownLatch.countDown();
        }, "t1").start();


        new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                String result = jedis.get("b");
                if (!"2".equals(result)) {
                    log.warn("expect to be 2 but found {}", result);
                    return;
                }
            }
            countDownLatch.countDown();
        }, "t2").start();


        countDownLatch.await();

    }
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

我们将项目进行启动测试并测试该接口,可以看到该接口报错了。

在这里插入图片描述

经过笔者不断点击调试,获取到的a的值变为2,出现了线程安全问题。

在这里插入图片描述

# 阅读源码了解究竟

我们的问题发生在get所以我们不妨从get方法中查看究竟。

从源码中可以看到get方法获取值是通过一个client对象的get方法获取的,我们不妨步入看看具体逻辑。

@Override
  public String get(final String key) {
    checkIsInMultiOrPipeline();
    client.get(key);
    return client.getBulkReply();
  }
1
2
3
4
5
6

可以看到client的get方法还是通过一个get方法完成value的获取,我们再步入看看

@Override
  public void get(final String key) {
    get(SafeEncoder.encode(key));
  }
1
2
3
4

再次步入后,我们来到了BinaryClient类的get方法,它的逻辑是发送一个get的命令,以key作为参数来完成值的获取的,我们不妨在步入看看细节。

public void get(final byte[] key) {
    sendCommand(GET, key);
  }
1
2
3

最终,我们来到了Connection对象了,可以看到在多线程情况下,多个线程获取的值会写到同一个outputStream中,这就是造成我们线程安全的原因所在。


//Connection共用一个outputStream对象
private RedisOutputStream outputStream;

public void sendCommand(final ProtocolCommand cmd, final byte[]... args) {
    try {
    //建立连接
      connect();
      //向服务端发送cmd命令,参数为args,将结果写入outputStream中
      Protocol.sendCommand(outputStream, cmd, args);
    } catch (JedisConnectionException ex) {
    
      broken = true;
      throw ex;
    }
  }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

除了这一处之外,还有一个地方,我们回到get方法,可以看到return语句后面跟着一个getBulkReply方法,我们步入看看。

 @Override
  public String get(final String key) {
    checkIsInMultiOrPipeline();
    client.get(key);
    return client.getBulkReply();
  }
1
2
3
4
5
6

可以看到redis的结果是从getBinaryBulkReply中获取,再通过SafeEncoder.encode转码返回给客户端的,所以我不妨看看getBinaryBulkReply是如何完成值的获取的。

public String getBulkReply() {
    final byte[] result = getBinaryBulkReply();
    if (null != result) {
      return SafeEncoder.encode(result);
    } else {
      return null;
    }
  }
1
2
3
4
5
6
7
8

重点来了,可以看到结果是通过flush将结果写入某个地方,然后再通过readProtocolWithCheckingBroken返回的,我们再次步入看看flush做了什么。

public byte[] getBinaryBulkReply() {
    flush();
    return (byte[]) readProtocolWithCheckingBroken();
  }
1
2
3
4

可以看到这个flush方法就是将上文的outputStream的值刷到缓冲区中,试想一下,我们线程1执行完flush就被挂起,然后线程2执行flush的话,线程1是不是就可以拿到线程2的结果呢?

 protected void flush() {
    try {
      outputStream.flush();
    } catch (IOException ex) {
      broken = true;
      throw new JedisConnectionException(ex);
    }
  }
1
2
3
4
5
6
7
8

# 基于debug剖析原因

为了印证上文中说到的线程1调用flush后线程2再次flush将导致线程1取到线程2的猜想,我们不妨通过debug的方式来重现这个问题。

我们不妨在jedis的get方法上使用thread模式打个断点。

在这里插入图片描述

然后启动项目开始debug,首先将线程1执行到flush后切换到线程2

在这里插入图片描述

然后让线程2也执行完flush,并将readProtocolWithCheckingBroken走完,让结果存到inputStream中。

在这里插入图片描述

最后我们将线程切回线程1,可以看到key为a却得到了2,线程安全问题重现。

在这里插入图片描述

最终代码输出了警告

在这里插入图片描述

# 解决方案

从源码中我们可以看出每一个jedis都相当于一个connection对象,它是属于我们说的第一种连接池,从pool中获取xxxConnection的情况,获取后需要我们手动归还。连接池声明代码如下:

 private static JedisPool jedisPool = new JedisPool("127.0.0.1", 6379);

1
2

因为jedisPool中获取的都是jedis对象,我们想了解一下为什么无论连接池还是单jedis对象都能通过close方法关闭或者归还对象,我们查看了一下close方法:

可以看到源码中又做了判断,如果是线程池中的对象,则调用returnBrokenResource或returnResource归还,如果是我们自己创建的Jedis对象,则调用close关闭资源。

 @Override
  public void close() {
	//如果是线程池中的对象,则调用returnBrokenResource或returnResource归还
    if (dataSource != null) {
      JedisPoolAbstract pool = this.dataSource;
      this.dataSource = null;
      if (client.isBroken()) {
        pool.returnBrokenResource(this);
      } else {
        pool.returnResource(this);
      }
    } else {
    //如果是我们自己创建的Jedis对象,则调用close
      super.close();
    }
  }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

为了保证项目关闭之后连接池能够正确关闭,我们可以使用PostConstruct确保在bean完成依赖注入之后,添加一个销毁jedisPool 的钩子方法。

@PostConstruct
    public void init() {
     
        Runtime.getRuntime().addShutdownHook(new Thread(()->{
            jedisPool.close();
        }));
    }
1
2
3
4
5
6
7

然后就可以修改我们的接口了,这样一来每一个线程使用的都是从连接池中获取的对象,因为Jedis继承了Closeable,所以我们可以用JDK的try语法完成close逻辑。就不会有线程安全问题了。

@GetMapping("right")
    public void right() throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(2);
        new Thread(() -> {
            try (Jedis jedis=jedisPool.getResource()) {
                for (int i = 0; i < 1000; i++) {
                    String result = jedis.get("a");
                    if (!"1".equals(result)) {
                        log.warn("expect to be 1 but found {}", result);
                        return;
                    }
                }
                countDownLatch.countDown();
            }

        }, "t1").start();


        new Thread(() -> {
            try (Jedis jedis=jedisPool.getResource()) {
                for (int i = 0; i < 1000; i++) {
                    String result = jedis.get("b");
                    if (!"2".equals(result)) {
                        log.warn("expect to be 2 but found {}", result);
                        return;
                    }
                }
                countDownLatch.countDown();
            }

        }, "t2").start();


        countDownLatch.await();

    }
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

# 小结

从本例子中可以看到笔者使用池化技术时的几个注意点:

  1. 根据源码推测池化技术的设计方式和使用方法。
  2. 基于多线程模式debug排查和印证,并使用池化技术解决问题。
  3. 时刻保持对池化资源的归还和关闭工作。

# 使用和不使用池化技术性能之间的差距

# 问题简介

上文我们提到了池化技术的重要性,接下来我们就基于apache客户端来了解一下不同的池化技术运用方式之间的差距。

# 前置步骤

为了更好的完成实验,我们可以必须在服务器中安装一个轻量级压测工具wrk,读者可以参考笔者写的这篇文章:

极简的wrk安装和使用教程 (opens new window)

为了更好查看池化技术之间网络交互过程,我们同样还需要安装一下Wireshark,读者可以参考这篇文章:

网络分析工具——WireShark的使用(超详细) (opens new window)

完成好这些步骤之后,我们就可以开始进行实践了,首先打开我们的spring boot项目(端口号设置为45678),引入httpclient的依赖:

  <!--httpclient依赖-->
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.13</version>
        </dependency>
1
2
3
4
5
6

# 错误的池化使用示例

先来看看错误的示例,可以看到笔者的使用方式可以说是非常低级的,每次请求进来都去创建一个连接池,然后从连接池中获取一个连接调用test接口。


    @GetMapping("wrong2")
    public String wrong2() {

        //每次请求进来都创建一个连接池
        try (
                CloseableHttpClient client = HttpClients.custom()
                        //连接池的连接数为1
                        .setMaxConnTotal(1)
                        .setConnectionManager(new PoolingHttpClientConnectionManager())
                        .evictIdleConnections(60, TimeUnit.SECONDS)
                        .build();
                //使用连接池中的连接调用同一个项目下的test方法
                CloseableHttpResponse response = client.execute(new HttpGet("http://127.0.0.1:45678/httpclientnotreuse/test"))) {
            return EntityUtils.toString(response.getEntity());
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }

@GetMapping("/test")
    public String test() {
        return "OK";
    }
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

编写完成之后,我们不妨在服务器中将项目启动,并使用wrk进行压测。

如下所示,我们使用一个线程和一个连接对接口压测10s。

wrk -t1 -c1 -d10s --latency http://localhost:45678/httpclientnotreuse/wrong2
1

压测结果如下,可以看到qps为414.01。

Running 10s test @ http://localhost:45678/httpclientnotreuse/wrong2
  1 threads and 1 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     2.49ms    1.23ms  12.12ms   89.16%
    Req/Sec   415.93     88.45   620.00     70.00%
  Latency Distribution
     50%    2.06ms
     75%    2.96ms
     90%    3.80ms
     99%    7.65ms
  4144 requests in 10.01s, 466.15KB read
Requests/sec:    414.01
Transfer/sec:     46.57KB

1
2
3
4
5
6
7
8
9
10
11
12
13
14

然后我们再通过服务器进行抓包了解一下压测通信过程详情。

如下命令所示,笔者使用tcpdump对45678端口(即我们的web项目端口)进行抓包,并将结果写到wrong2.pcap文件中。

tcpdump -i any tcp   and port 45678 -w wrong2.pcap
1

开启抓包命令之后,我们再打开一个终端对接口进行压测,完成后将pcap文件下载到本地用Wireshark打开。

如下所示,我们在Wireshark过滤一栏键入下面的指令。

tcp.port == 45678 && http
1

并将源端口号应用为列以便观察数据。

在这里插入图片描述

可以看到每一个请求test接口的源端口号都不一样,很明显,每次发起请求时都是发起test请求时,都进行了TCP三次握手再请求的过程。

在这里插入图片描述

我们不妨将过滤条件删除,可以看到,抓包记录中大量的SYN包,很明显大量的HTTP请求都是先进行TCP三次握手了。

在这里插入图片描述

# 正确的池化使用示例

接下来我们再来看看正确池化的效果。首先我们会在类中声明一个全局静态连接池

private static CloseableHttpClient client = null;

    static {
        client = HttpClients.custom()
                .setMaxConnTotal(1)
                .setConnectionManager(new PoolingHttpClientConnectionManager())
                .evictIdleConnections(60, TimeUnit.SECONDS)
                .build();

        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            try {
                client.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }));
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

然后我们的连接池则是直接使用这个连接池发起http请求的。

 @GetMapping("right")
    public String right() {


        try (CloseableHttpResponse response = client.execute(new HttpGet("http://127.0.0.1:45678/httpclientnotreuse/test"))) {
            return EntityUtils.toString(response.getEntity());
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }
1
2
3
4
5
6
7
8
9
10
11
12

然后我们使用同样的参数对这个接口进行压测

wrk -t1 -c1 -d10s --latency http://localhost:45678/httpclientnotreuse/right
1

从压测结果来看,qps为3337.56,很明显正确的使用池化技术对性能是有着质的提高。

Running 10s test @ http://localhost:45678/httpclientnotreuse/right
  1 threads and 1 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   764.22us    4.52ms  82.06ms   98.54%
    Req/Sec     3.37k   478.07     4.16k    73.00%
  Latency Distribution
     50%  274.00us
     75%  316.00us
     90%  379.00us
     99%   15.98ms
  33637 requests in 10.08s, 3.70MB read
Requests/sec:   3337.56
Transfer/sec:    375.44KB

1
2
3
4
5
6
7
8
9
10
11
12
13
14

我们使用同样的方式进行抓包

tcpdump -i any tcp   and port 45678 -w right.pcap
1

从抓包记录来看,很明显发起http请求的接口都是47402,都是复用同一个TCP连接。

在这里插入图片描述

# 小结池化技术性能压测实验

这里我们通过wrk压测和Wireshark抓包对池化技术使用细节进行了进一步了解,很明显正确的使用池化技术可以复用资源,避免资源没必要的创建和销毁,进而提高服务器每秒处理请求数即QPS。

# 池化配置问题

# 最大连接数的配置可能造成的问题

既然聊到池化技术,那么我们就来了解一下池化技术中关于连接池的配置问题。我们使用连接池时都会设置连接池参数的配置,这里面可能会涉及一个比较常用的配置,最大连接数。 这个参数我们必须谨慎,如果连接参数过小,极可能导致大量客户端等待连接,导致吞吐量下降。

在这里插入图片描述

而连接数设置过大又可能会增加服务端维护连接池的开销,以及大量客户端通过这些连接和服务端建立远程交互进而造成大量线程切换的开销。

在这里插入图片描述

# 基于数据库连接池复现问题

我们不妨基于spring boot jpa写一段保存用户信息的功能来模拟一下数据库连接池被打满的情况。

首先我们引入JPA和MySQL的依赖。

<!-- jpa -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <!-- myql驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
1
2
3
4
5
6
7
8
9
10
11

建立一张user表

-- wiki.`user` definition

CREATE TABLE `user` (
  `id` char(8) NOT NULL DEFAULT '' COMMENT 'id',
  `login_name` varchar(50) NOT NULL COMMENT '登陆名',
  `name` varchar(50) DEFAULT NULL COMMENT '昵称',
  `password` char(32) NOT NULL COMMENT '密码',
  `pass_word` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `login_name_unique` (`login_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户';
1
2
3
4
5
6
7
8
9
10
11

编写user表对应的实体类

@Entity
@Data
public class User {
    @Id
    @GeneratedValue(strategy = AUTO)
    private Long id;

    private String name;

    private String loginName;

    private String password;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14

编写user表持久层代码,代码很简单,集成JPA类即可,具体的逻辑该类都为我们实现了,我们只需指定泛型即可。

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
}

1
2
3
4

服务层代码如下所示,逻辑很简单,创建一个实体类调用持久层UserRepository 保存即可,注意我们为了模拟真实环境的某些复杂业务,这个功能我们加了事务并且还让其休眠500ms。

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    @Transactional
    public User register() {
        User user = new User();
        user.setName("new-user-" + System.currentTimeMillis());
        user.setLoginName("new-user-" + System.currentTimeMillis());
        user.setPassword("123");
        userRepository.save(user);
        try {
            TimeUnit.MILLISECONDS.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return user;
    }

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

最后是控制层代码。

@Autowired
    private UserService userService;

    @GetMapping("test")
    public Object test() {
        return userService.register();
    }
1
2
3
4
5
6
7

因为我们后续会用到jconsole,所以我们会在配置文件中添加下面这段配置,使得我们可以在jconsole的mbean中看到这个信息。

spring.datasource.hikari.register-mbeans=true
1

为了保证文章完整性,这里也把数据库连接配置贴上:




spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update
spring.datasource.url=jdbc:mysql://xxxxxxx
spring.datasource.username = xxxxxxx
spring.datasource.password = xxxx
spring.datasource.platform=mysql
spring.jpa.open-in-view=false
1
2
3
4
5
6
7
8
9
10

然后我们会将这个项目打包到服务器上,为了能让本地jconsole监控到该服务信息我们启动时会添加下面几个参数,

java 
-Djava.rmi.server.hostname=xxxx   #远程服务器ip
-Dcom.sun.management.jmxremote  #允许JMX远程调用
-Dcom.sun.management.jmxremote.port=3214  #允许JMX远程调用的端口号,后续我们的jmx就可以通过该ip和该端口和应用建立连接
-Dcom.sun.management.jmxremote.ssl=false  # 是否开启ssl
-Dcom.sun.management.jmxremote.authenticate=false # 是否开启ssl
 -jar 
 java-common-mistakes.jar
1
2
3
4
5
6
7
8

项目启动后我们可能还需要进行这样一步操作,以笔者为例项目启动时JVM远程调用端口号为3214,但是放行该端口后还是无法连接,查阅网上资料了解到还有别的端口需要放行,所以我们需要键入下面这条命令

netstat -apn |grep java
1

最终输出这样结果,可以看到除了应用端口和JMX远程调用端口以外,还有34751和36709两个端口,所以我们一并将其放行。

在这里插入图片描述

完成后我们就可以使用jconsole进行远程连接了,打开JConsole输入应用的ip+JMX远程调用端口号即可,以笔者为例则是:i

ip:3214
1

在这里插入图片描述

接下来我们就可以在JConsole中看到连接池的信息了,可以看到默认情况下,最大连接数为10:

在这里插入图片描述

我们不妨基于wrk进行一次压测,因为笔者的服务器为双核,所以使用了20个线程进行压测:

wrk -t20 -c20 -d30s --latency http://localhost:45678/improperdatasourcepoolsize/test
1

可以看到在现有连接池都被占用,大量连接处于等待状态,导致大量请求报错:

在这里插入图片描述

报错内容基本都为超时

unable to obtain isolated JDBC connection] with root cause

java.sql.SQLTransientConnectionException: HikariPool-1 - Connection is not available, request timed out after 30000ms.

1
2
3
4

# 解决思路

所以我们需要对连接池进行调整,为了直观压测获取服务器最大并发数,我们将连接池最大值调整为50:

spring.datasource.hikari.maximum-pool-size=50
1

然后再次进行压测,最终我们得到服务器的最大并发数为30,为了保证有一半的冗余,连接池数量调整为并发数*2即60

在这里插入图片描述

小结一下,对于连接池参数问题建议做到以下几点:

  1. 做好配置确保可以实时监控,确保自己的配置是生效的。
  2. 进行压测确保配置参数符合预期效果。
  3. 配置连接参数要确保留有一半的余量,并保证我们的监控工具能够在剩余不到一半的情况下发出预警。

# 参考文献

Redis开发与运维 (opens new window)

Java 业务开发常见错误 100 例 (opens new window)

wireshark添加列:端口号等 (opens new window)

JConsole连接远程服务器方法 (opens new window)

使用JConsole链接远程服务器 (opens new window)

jconsole远程连接失败 (opens new window)

编辑 (opens new window)
上次更新: 2026/03/26, 01:05:31
掌握 Redis 事务,提升数据处理效率的必备秘籍
Redis主从复制技术:理论基础、运行逻辑与应用场景

← 掌握 Redis 事务,提升数据处理效率的必备秘籍 Redis主从复制技术:理论基础、运行逻辑与应用场景→

最近更新
01
基于EasyExcel实现高效导出
03-25
02
从开源框架中学习那些实用的位运算技巧
03-25
03
浅谈分布式架构设计思想和常见优化手段
03-25
更多文章>
Theme by Vdoing | Copyright © 2025-2026 Evan Xu | MIT License | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式
×
×