开发小记(二)

关于Jedis的坑

今天分享一个白衣大大在微博中说明的关于Jedis的坑

“基础架构部之jedis中坑人的maxIdle参数”,jedis用的是apache commons pools做连接池。

连接池大小由minIdle,maxIdle,maxTotal三个参数控制。头一个和末一个好理解,就是连接池的最小和最大值,而maxIdle就比较坑人了,连接用完后,如果连接池小于maxIdle,就放回连接池,大于就直接扔掉。

又有这么一个应用,把三个值分别设为2,10,40,当压力来了,10个连接不够用了,就要频繁创连接,执行完一个命令就丢掉,下一个命令再创,再丢,cps(new connection per second) 的值,等于没有在原来10条连接里处理的qps,我们称为连接风暴。

这时候redis可遭罪了,单线程又要处理命令,又要处理连接创建,两头忙两头不是人(redis 6独立的io线程可以改善么?)

所以,maxIdle应该就和maxTotal同一个值,别开放出去让人配了。

另外一开始池里也没有minIdle所指定的最少连接数,可以应用启动时做一下预热。

当时看到这个以后,由于我们的项目中也在用Jedis于是去检查了一下参数是如何设置的,maxIdle确实和maxTotal同一个值,但是配置并不规范,连接池的参数都写在了代码中,而非配置文件。


Redis 6之I/O多线程

这里看到白衣大大提到,Redis 6的I/O多线程,也顺便了解一下。

Redis 6.0 之前为什么一直不使用多线程?

使用了单线程后,可维护性高。多线程模型虽然在某些方面表现优异,但是它却引入了程序执行顺序的不确定性,带来了并发读写的一系列问题,增加了系统复杂度、同时可能存在线程切换、甚至加锁解锁、死锁造成的性能损耗。

Redis 通过 AE 事件模型以及 IO 多路复用等技术,处理性能非常高,因此没有必要使用多线程。

单线程机制使得 Redis 内部实现的复杂度大大降低,Hash 的惰性 Rehash、Lpush 等等 “线程不安全” 的命令都可以无锁进行。

Redis 6.0 为什么要引入多线程呢?

之前的段落说了,Redis 的瓶颈并不在 CPU,而在内存和网络。

内存不够的话,可以加内存或者做数据结构优化和其他优化等,但网络的性能优化才是大头,网络 IO 的读写在 Redis 整个执行期间占用了大部分的 CPU 时间,如果把网络处理这部分做成多线程处理方式,那对整个 Redis 的性能会有很大的提升。

优化方向:

  • 提高网络 IO 性能,典型的实现比如使用 DPDK 来替代内核网络栈的方式。
  • 使用多线程充分利用多核,典型的实现比如 Memcached。

所以总结起来,Redis 支持多线程主要就是两个原因:

  • 可以充分利用服务器 CPU 资源,目前主线程只能利用一个核。
  • 多线程任务可以分摊 Redis 同步 IO 读写负荷。

Redis 6.0 默认是否开启了多线程?

否,在conf文件进行配置

io-threads-do-reads yes

io-threads 线程数

Redis 6.0 多线程的实现机制?

1712130-20200516174816219-1469215261.png

流程简述如下

  • 主线程负责接收建立连接请求,获取 Socket 放入全局等待读处理队列。
  • 主线程处理完读事件之后,通过 RR(Round Robin)将这些连接分配给这些 IO 线程。
  • 主线程阻塞等待 IO 线程读取 Socket 完毕。
  • 主线程通过单线程的方式执行请求命令,请求数据读取并解析完成,但并不执行。
  • 主线程阻塞等待 IO 线程将数据回写 Socket 完毕。
  • 解除绑定,清空等待队列。

1712130-20200516174905348-1186276910.png

该设计有如下特点:

  • IO 线程要么同时在读 Socket,要么同时在写,不会同时读或写。
  • IO 线程只负责读写 Socket 解析命令,不负责命令处理。

这样看来的话,还是不能解决Jedis配置在maxIdle<maxTotal时,导致的频繁创建和执行命令的问题。

参考