CreateArtTechnology
/ Blog
Login
最新文章
Java
语言相关
库相关
虚拟机相关
CreateArtTechnology
项目搭建
使用的工具
自研的工具
开源工具
ELK
ElasticSearch
Jenkins
Markdown
GraphQL
Arthas
生产工具
Linux
Nginx
VersionControl
Subversion
Git
Redis
Archiva
Maven
Zookeeper
Spring
SpringBoot
MySql
HBase
Cassandra
容器化
Docker
Kubernetes
服务容器化从零开始
未分类笔记
算法相关
概念相关
豆知识
机器学习
机器学习从零开始
Jedis集成与踩坑经历
26
2019-07-25 19:27:12
生产工具
Redis
## Jedis简介 Jedis是Redis的Java客户端实现,封装了对Redis的通信和命令处理等。 Jedis提供了资源池,可以很方便地实现对Redis的API调用。 ## Jedis集成 ### 目标 之前是通过组内对Jedis封装的Spring Bean来获取和使用Jedis的,现在希望自行实现类似功能,设计目标如下: 1. 封装为Spring FactoryBean 2. 集成目前自行实现的基于ZooKeeper的配置中心组件 3. 获取单个连接,避免每次调用需要从资源池中获取一个连接的额外操作(其实还有归还操作) 4. 实现在服务启动依赖注入后配置变更仍然能生效 ### 思路 具体思路就是针对设计目标而定的: 1. 实现FactoryBean 2. 接入Config组件 3. DCL单例生成资源池JedisPool,并通过动态代理提供Jedis连接,连接从JedisPool中获取 4. 配置变更后使用新的JedisPool替换旧的 ### 具体实现 由于需求比较基础,还没有太多应用场景,实现也没考虑太复杂。整体逻辑不到50行,可以在[我的GitHub](https://github.com/XuhuiLee/supporter/blob/master/src/main/java/com/createarttechnology/supporter/redis/JedisFactory.java)上大致看一下。 后续使用可以直接使用Spring将Bean注入。 ## Jedis踩坑 由于不按常规方法使用JedisPool可能背离了JedisPool设计的使用场景,因此在其中踩了不少坑。 其次,虽然平常常用组内的Jedis组件,但实际上对Jedis的API不了解,本次根据平常使用过程中的一些感受进行“黑盒临摹”,在爬坑过程中其实也学习了其他一些方面的经验,比如Guava Reflections等。 ### 坑#1 并发异常 **背景** 最开始通过FactoryBean提供的连接并未使用动态代理,也就是说仅提供了一个Jedis,所有线程使用同一个Jedis连接。 **现象** 业务中较频繁地报异常,异常信息为`java.lang.ClassCastException: java.util.ArrayList cannot be cast to [B`等,基本是`ClassCastException`,异常抛出位置为调用Jedis的位置。 - 初看以为是Jedis的泛型bug,但不是必现,可以排除这种猜测; - 有人认为是对于Redis的响应命令处理不完善,如`OK`、`NIL`等响应,但这种bug太低级了,不应该出现在Jedis这种久经考验的库中,排除; - 有人认为发生异常情况时Jedis的buffer未清空,导致后续使用中残存之前的错误数据,这个倒是有点道理,因为这种情况不是必现,但同上一条,不应该出现在这种库中; **原因** 最终在另一篇资料指引下来到jedis/issues,在[参考资料](https://github.com/xetorthio/jedis/issues/186)中发现了最可信最合理的原因:Jedis并非线程安全,不应当并发操作。 **正确使用** > Single connection. Single thread. 正如参考资料中回答提到的,每个线程(每次调用)都从JedisPool中获取一个连接,并在使用后归还。 也正是因为这一点跟最初的FactoryBean封装方式冲突了,后来才改用提供动态代理类的方式封装FactoryBean。 ### 坑#2 需要手动归还连接 **背景** 我使用的Jedis版本为3.0.1,网上的不少资料指出在使用连接后归还可以使用JedisPool的`void returnResource(Jedis resource)`方法,但在3.0.1版本中这个方法是protected可见的,没有间接调用方法。 另外Jedis源码中找不到注释,这有点奇怪,我想当然地认为版本升级后可以自动归还资源了,于是仅在设置最大连接数之后就部署到业务中了。 **现象** 业务线程启动后每访问一定次数(调用Jedis达到一定次数)后就完全不响应请求了: - 后续所有请求超时无响应 - CPU正常,内存正常 - Java线程有不少处于等待状态,GC正常,但从日志看没有任何请求到达Servlet - Nginx(用于反向代理)日志中响应状态码是**499** - **499是Nginx自定义的,对应的是client has closed connection即用户浏览器断开Http连接了** 还是在[参考资料](https://my.oschina.net/mojiewhy/blog/671840)的指引下查看Tomcat监听的端口,的确很多连接处于`CLOSE_WAIT`状态,表明客户端已断开连接(我自己测试的时候刷新页面太多,很多就中途断开了)。 **原因** 结合TCP四次挥手过程,应该是中间有资源释放不了导致没有进入`LAST_ACK`状态,推测是Jedis连接资源未归还而总连接数有限制,导致很多线程在等待获取Jedis资源。 **正确使用** 在Jedis连接使用完毕后,需要调用Jedis的`close()`方法将资源归还JedisPool,`close`方法是用于替代`returnResource`方法的。 这个方法语义比较奇怪,真实作用是“**归还或**关闭连接”,最开始其实就是考虑到资源复用的问题才没有考虑使用这个`close`方法的。 ## 可以优化的点 对比了一下组内的组件,思路差不多,还有以下的点能够扩展: 1. 多种资源池的兼容性 2. 接入监控上报 ## 参考资料 [ClassCastException - B cannot be cast to java.lang.Long · Issue #186 · xetorthio/jedis](https://github.com/xetorthio/jedis/issues/186) [nginx 499状态码 - 微信-大数据从业者 - 博客园](https://www.cnblogs.com/felixzh/p/8871959.html) [深入分析Tomcat无响应问题及解决方法 - 月下狼的个人页面 - OSCHINA](https://my.oschina.net/mojiewhy/blog/671840) [jedis:连接池(JedisPool)使用示例 - 10km的专栏 - CSDN博客](https://blog.csdn.net/10km/article/details/77852075) [Jedis使用|returnBrokenResource|returnResource废弃替代 - 诗人不写诗 - CSDN博客](https://blog.csdn.net/tales522/article/details/85942512)
发布文章 101
文章被阅读 1820
最近修改
什么是“丝滑”的曲线
2021-12-08 15:19:20
高效空间数据索引R树及其批量加载方法STR简介
2021-09-29 20:33:37
关于分库分表的一些事儿
2021-06-25 11:51:25
获得诺奖的稳定匹配理论之TTC算法与GS算法
2021-03-14 23:04:48
算法小白的机器学习入门实践,从零到上线
2021-01-13 14:28:27
分站宗旨
一站式资料平台,减少重复检索,减少重复采坑。