draveness / blog-comments

面向信仰编程
https://draveness.me
140 stars 6 forks source link

为什么 Redis 选择单线程模型 · Why's THE Design? · /whys-the-design-redis-single-thread #155

Closed draveness closed 2 years ago

draveness commented 4 years ago

https://draveness.me/whys-the-design-redis-single-thread

Zingphoy commented 4 years ago

文章说,CPU 资源往往都不是 Redis 服务器的性能瓶颈,这是不使用多线程的理由之一,没弄明白是什么意思

  1. 如果不是cpu-bound,那是否代表添加一定的线程数是没问题的?
  2. 如果是network I/O-bound,那为什么不通过添加线程来减少阻塞等待呢? 懵了
draveness commented 4 years ago

文章说,CPU 资源往往都不是 Redis 服务器的性能瓶颈,这是不使用多线程的理由之一,没弄明白是什么意思

  1. 如果不是cpu-bound,那是否代表添加一定的线程数是没问题的?
  2. 如果是network I/O-bound,那为什么不通过添加线程来减少阻塞等待呢? 懵了

引入 IO 多路复用可以同时监听多个连接的可读和可写,不需要多个线程来做这个事情,引入了不必要的复杂性

zheng-bobo commented 4 years ago

大佬能探讨下Golang为什么要使用Plan 9的汇编器么?这篇文章的观点把Plan 9贬得一无是处? 实际上真的是这样么

draveness commented 4 years ago

大佬能探讨下Golang为什么要使用Plan 9的汇编器么?这篇文章的观点把Plan 9贬得一无是处? 实际上真的是这样么

可以的,我看一下这个

polunzh commented 4 years ago

DEL这样的IO操作是异步执行的,那么怎么判断这些操作是否执行完成了呢?

draveness commented 4 years ago

DEL这样的IO操作是异步执行的,那么怎么判断这些操作是否执行完成了呢?

你说的是 UNLINK 吧,我觉得调用方不会在乎这个操作什么时候会执行成功,如果在乎的话就会调用 DEL 了

wenmoxiao commented 4 years ago

不是因为作者不想过多考虑多线程引入的复杂性么?~~将部分问题丢给用户了

draveness commented 4 years ago

不是因为作者不想过多考虑多线程引入的复杂性么?~~将部分问题丢给用户了

还是因为纯内存服务够快了,不然处理能力很差,我们也不会用 Redis

yuanjize commented 4 years ago

你在做基础架构么

moshiale commented 4 years ago

大佬,请问你的画图工具是什么?

OrdinaryYZH commented 4 years ago

看了这篇文章,说到Redis6.0引入了多线程,是为了解决网络的 IO 消耗,能解释下这个具体是什么意思吗?还有这个跟IO多路复用要解决的问题有什么关系呢?

draveness commented 4 years ago

看了这篇文章,说到Redis6.0引入了多线程,是为了解决网络的 IO 消耗,能解释下这个具体是什么意思吗?还有这个跟IO多路复用要解决的问题有什么关系呢?

这是一个非常好的问题,I/O 多路复用的主要作用是让我们可以使用一个线程来监控多个连接是否可读或者可写,但是从网络另一头发的数据包需要先解序列化成 Redis 内部其他模块可以理解的命令,这个过程就是 Redis 6.0 引入多线程来并发处理的。

I/O 多路复用模块收到数据包之后将其丢给后面多个 I/O 线程进行解析,I/O 线程处理结束后,主线程会负责串行的执行这些命令,由于向客户端发回数据包的过程也是比较耗时的,所以执行之后的结果也会交给多个 I/O 线程发送回客户端。

chinaran commented 4 years ago

文章写得很棒!

liangdas commented 4 years ago

@polunzh DEL这样的IO操作是异步执行的,那么怎么判断这些操作是否执行完成了呢?

只要从元数据删除是在主线程,后续的读取操作就都认为这个key已经被删除了

tanteng commented 4 years ago

@moshiale 大佬,请问你的画图工具是什么?

同问

hudyseu commented 4 years ago

@tanteng

@moshiale 大佬,请问你的画图工具是什么?

同问

文章末尾有说明,“图片使用 Sketch 进行绘制”。

lichongsw commented 4 years ago

在性能不是瓶颈的情况下怎么简单怎么来。内存跟CPU频率是有两个数量级的差异,但已经很快

  1. 内存访问是100ns的时间级别,随机请求预估每次请求平均读写8次
  2. CPU cache看似命中率还挺高,但对随机读写的帮助真的没有那么明显。毕竟缓存与内存的容量差距太大,假设cache miss按照50%计算

忽略cache命中部分,单核随机读写理论上QPS=1/(100ns 8 0.5)=250w。云主机单实例benchmark在开启pipeline后测试结果就接近100w了。作者的实现跟理论值都在一个数量级,非常优秀了

shaojunda commented 4 years ago

and then two they hav erpoblesms.

应该是 and then two they have problesms. 吧?

draveness commented 4 years ago

and then two they hav erpoblesms.

应该是 and then two they have problesms. 吧?

这个都可以吧

johnli1029 commented 4 years ago

@shaojunda

and then two they hav erpoblesms.

应该是 and then two they have problesms. 吧?

这笑话的意思不就是多线程造成的动作执行顺序的不确定性嘛

shaojunda commented 4 years ago

@shaojunda

and then two they hav erpoblesms.

应该是 and then two they have problesms. 吧?

这笑话的意思不就是多线程造成的动作执行顺序的不确定性嘛

之前没 Get 到这个点。

Crearns commented 4 years ago

你好,想请问一下,这种模型是不是类似Java的Netty中负责处理连接的BossEventLoopGroup和负责网络I/O的WorkEventLoopGroup,BossEventLoopGroup线程数一般为1,而WorkEventLoopGroup一般为多线程?

draveness commented 4 years ago

你好,想请问一下,这种模型是不是类似Java的Netty中负责处理连接的BossEventLoopGroup和负责网络I/O的WorkEventLoopGroup,BossEventLoopGroup线程数一般为1,而WorkEventLoopGroup一般为多线程?

听起来应该是的,但是我没写过 Java 不能确定..

dawncold commented 4 years ago

一旦受到网络请求


2020-05-15 UPDATES: 已修复

dawncold commented 4 years ago

多线程技术的能够


2020-05-15 UPDATES: 已修复

donnior commented 4 years ago

@Crearns 你好,想请问一下,这种模型是不是类似Java的Netty中负责处理连接的BossEventLoopGroup和负责网络I/O的WorkEventLoopGroup,BossEventLoopGroup线程数一般为1,而WorkEventLoopGroup一般为多线程?

还是不一样的,只能说两者都用到了io复用的技术;但是Netty采用的是一种reactor模式,你说的这个主线程来负责处理连接,另外一个线程池来处理读写事件,所以netty在整个处理io的时候实际上是使用了多个线程的(当然第二个线程池你也可以选择继续用来处理自己的业务);而redis在不一样,它虽然使用了io复用,但是并不区分连接和读写,所有的io操作都变成事件由一个线程来处理,甚至于后面的命令执行也可以在这个线程中。

redis之所以可以采用这种模型的原因完全是因为它可以非常快,因为它的数据都在内存中,操作都是纳秒级的,同时它的网络协议也会简单,读写解码都很快;而netty作为一个通用的网络框架并不能假定用户在上面执行什么操作,所以需要用这种线程模型来达到极致的效率; 而实际上,在redis这种模型下,如果你在高并发场景下来测试读写一些大数据(增加io的读写时间),或者执行一些耗时的命令,是可以看到比较明显的性能差异的。

smartisanyyh commented 4 years ago

@shaojunda

and then two they hav erpoblesms.

应该是 and then two they have problesms. 吧?

@draveness

看了这篇文章,说到Redis6.0引入了多线程,是为了解决网络的 IO 消耗,能解释下这个具体是什么意思吗?还有这个跟IO多路复用要解决的问题有什么关系呢?

这是一个非常好的问题,I/O 多路复用的主要作用是让我们可以使用一个线程来监控多个连接是否可读或者可写,但是从网络另一头发的数据包需要先解序列化成 Redis 内部其他模块可以理解的命令,这个过程就是 Redis 6.0 引入多线程来并发处理的。

I/O 多路复用模块收到数据包之后将其丢给后面多个 I/O 线程进行解析,I/O 线程处理结束后,主线程会负责串行的执行这些命令,由于向客户端发回数据包的过程也是比较耗时的,所以执行之后的结果也会交给多个 I/O 线程发送回客户端。

也就是说6.0以前 只是用了多路复用 select返回可读的fd 然后主线程挨着 读->计算->写 读->计算->写 读->计算->写 6.0以后可以将读和写交给其他线程去做 主线程只负责计算

smartisanyyh commented 4 years ago

@draveness select 返回fd 还是 可读句柄个数呀 要是个数的话 他怎么知道哪些句柄是可读的了?

draveness commented 4 years ago

@draveness select 返回fd 还是 可读句柄个数呀 要是个数的话 他怎么知道哪些句柄是可读的了?

要重新遍历 fd 检查所有文件描述符的可读或者可写

Hello-World-0X commented 4 years ago

既然6.0之后已经有多个io线程去做反序列化和发送数据的io操作了,为啥不直接在这些线程里面读内存阿?都已经引入多线程,复杂性也带来了,上下文开销也带来了。 我理解是因为单个线程中循环执行命令,不用做上下文开销,这种情况下的吞吐比多个线程中读内存的吞吐要高,因为多个线程不光会上下文切换,而且每个线程里面要做的事情还有网络IO。 不知道理解的对不对哈,求指教

raoxuanxuan commented 4 years ago

@Hello-World-0X 既然6.0之后已经有多个io线程去做反序列化和发送数据的io操作了,为啥不直接在这些线程里面读内存阿?都已经引入多线程,复杂性也带来了,上下文开销也带来了。 我理解是因为单个线程中循环执行命令,不用做上下文开销,这种情况下的吞吐比多个线程中读内存的吞吐要高,因为多个线程不光会上下文切换,而且每个线程里面要做的事情还有网络IO。 不知道理解的对不对哈,求指教

主要应该是多线程要引入因为锁吧,至少是个读写锁。

Cenyol commented 4 years ago

@Zingphoy 文章说,CPU 资源往往都不是 Redis 服务器的性能瓶颈,这是不使用多线程的理由之一,没弄明白是什么意思

  1. 如果不是cpu-bound,那是否代表添加一定的线程数是没问题的?
  2. 如果是network I/O-bound,那为什么不通过添加线程来减少阻塞等待呢? 懵了

用多线程来做网络连接处理,在请求很多的时候,会生成相当多的线程,给内核造成压力。而且大部分请求是有八二定律的,就是大部分时间内只有少部分线程是活跃的,所以这种情况来看,IO Multiplex更适合处理网络请求。Netty和Nginx也都是用了Reactor反应模型

Aiome commented 3 years ago

你文章解释性能瓶颈感觉解释的有问题。 开启多线程是为了提高CPU利用率。CPU密集型的操作不需要开启多线程提高CPU利用率,CPU本来就忙啊,线程数与CPU核心数相同就好了。IO密集型才需要开启多线程,A线程IO操作时CPU需要等待时间很长,这时切换到B线程进行计算操作,提高CPU利用率。 我感觉Reids设计应该是认为单线程IO操作时我CPU等会就等会了,比切换其他线程进行计算还要快。

draveness commented 3 years ago

你文章解释性能瓶颈感觉解释的有问题。 开启多线程是为了提高CPU利用率。CPU密集型的操作不需要开启多线程提高CPU利用率,CPU本来就忙啊,线程数与CPU核心数相同就好了。IO密集型才需要开启多线程,A线程IO操作时CPU需要等待时间很长,这时切换到B线程进行计算操作,提高CPU利用率。 我感觉Reids设计应该是认为单线程IO操作时我CPU等会就等会了,比切换其他线程进行计算还要快。

没理解你的疑问

liuyanghui commented 3 years ago

文章说redis的处理请求能力在一百万每秒应该是笔误吧,官方给的数据是读写分别在十万/八万每秒

draveness commented 3 years ago

@liuyanghui 文章说redis的处理请求能力在一百万每秒应该是笔误吧,官方给的数据是读写分别在十万/八万每秒

这里可能有误解,我这里的意思是使用 Redis 可以抗住百万级别的 QPS 不是说单机的 Redis..

wujunze commented 3 years ago

最新的 Redis 6.0 采用了多线程 博主能不能分析一下为什么这样做 谢谢

ermazi commented 3 years ago

@wujunze 最新的 Redis 6.0 采用了多线程 博主能不能分析一下为什么这样做 谢谢

老阴阳了

mugbya commented 3 years ago

@liuyanghui 文章说redis的处理请求能力在一百万每秒应该是笔误吧,官方给的数据是读写分别在十万/八万每秒

你在哪儿看的 ? https://redis.io/topics/faq#what39s-the-redis-memory-footprint

这里写的是 1 million requests per second

fenghaojiang commented 3 years ago

Some people, when confronted with a problem, think, “I know, I’ll use threads,” and then two they hav erpoblesms.

typo

draveness commented 3 years ago

@fenghaojiang Some people, when confronted with a problem, think, “I know, I’ll use threads,” and then two they hav erpoblesms.

typo

这并不是 typo

dawncold commented 3 years ago

因为用了threads

Sincerely, Zhen Tian

On Tue, Dec 8, 2020 at 6:26 PM Draven notifications@github.com wrote:

@fenghaojiang https://github.com/fenghaojiang Some people, when confronted with a problem, think, “I know, I’ll use threads,” and then two they hav erpoblesms.

typo

这并不是 typo

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/draveness/blog-comments/issues/155#issuecomment-740531811, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAD2YSANCLTO2I4SSFI4QSDSTX5L7ANCNFSM4JCBRB2A .

ZMbiubiubiu commented 3 years ago

@Aiome 你文章解释性能瓶颈感觉解释的有问题。 开启多线程是为了提高CPU利用率。CPU密集型的操作不需要开启多线程提高CPU利用率,CPU本来就忙啊,线程数与CPU核心数相同就好了。IO密集型才需要开启多线程,A线程IO操作时CPU需要等待时间很长,这时切换到B线程进行计算操作,提高CPU利用率。 我感觉Reids设计应该是认为单线程IO操作时我CPU等会就等会了,比切换其他线程进行计算还要快。

我大概理解你意思,我理解的意思:Redis主处理程序主要与内存打交道,没有什么I/O处理,所以多线程并不适用,也就是说Redis的瓶颈不在于I/O,Redis不是I/O-bound吧,我读到原文,也感觉怪怪的。

SpikeWong commented 3 years ago

{% include related/whys-the-design.md %} 你的 template 失效了

mlbjay commented 3 years ago

虽然 看过Redis文档,虽然 也写过 IO多路复用,但是 这一篇文章 真是把这个问题讲透了!!!

Jessezhao920 commented 3 years ago

在这里,我认为与其和多线程的去做对比,不如直接搞明白NIO和IO的,因为redis既然是一种缓存,直接操作与内存中,而我们一直在那多线程的复杂性,是初看上去,与单线程对应的就是多线程,但我认为并不是提升速度的核心,这是我的一点见解。

Jigsawk commented 3 years ago

@Aiome 你文章解释性能瓶颈感觉解释的有问题。 开启多线程是为了提高CPU利用率。CPU密集型的操作不需要开启多线程提高CPU利用率,CPU本来就忙啊,线程数与CPU核心数相同就好了。IO密集型才需要开启多线程,A线程IO操作时CPU需要等待时间很长,这时切换到B线程进行计算操作,提高CPU利用率。 我感觉Reids设计应该是认为单线程IO操作时我CPU等会就等会了,比切换其他线程进行计算还要快。

我大致也看懂了你的意思,刚读完文章我也觉得作者由“Redis 服务中运行的绝大多数操作的性能瓶颈都不是 CPU”得出“没必要开启多线程”的这个说法有点问题。诚然可以通过selector来处理网络连接,但是如果开启多线程处理指令,应该依然能够进一步提升效率(提升了CPU的利用率)

我觉得因为redis在单线程处理连接与指令方面已经很快了,如果要引入多线程处理指令,虽然也可能一定程度上提升效率,但是却要考虑共享数据的线程安全问题,以及引入事务等概念?这在开发维护上带来了很高的复杂度。redis可能并不想做一个大而全的东西,只要能够在自己专注的场景中,获得高性能就可以了。如果对读写有更高要求,那就横向拓展。

Johnson-jjy commented 3 years ago

很喜欢作者的文章,看了都觉得受益匪浅。。以前我还会忽略评论,今天发现评论也是满满的精华

887412 commented 3 years ago

@Zingphoy 文章说,CPU 资源往往都不是 Redis 服务器的性能瓶颈,这是不使用多线程的理由之一,没弄明白是什么意思

  1. 如果不是cpu-bound,那是否代表添加一定的线程数是没问题的?
  2. 如果是network I/O-bound,那为什么不通过添加线程来减少阻塞等待呢? 懵了
我们想一下木桶效应,最终的性能是由哪个短木板决定的;cpu的处理速度远高于内存,而redis的父进程运行在内存中不执行IO操作(AOF和RDB的持久化IO是由子进程来完成的);所以此时内存就是木桶效应中的短木板,就是瓶颈;   
我们知道引入多线程的目的是为了把比较耗时的任务非阻塞式处理来提高利用率,在内存中处理速度都非常快,比较耗时的任务比较少,没有必要全面引入多线程,只需要像垃圾处理的时候来进行部分引入优化即可; 
redis的主要性能瓶颈是内存和网络IO, 我们不会因为引入了多线程,内存和网络IO的处理速度就会变快,比较好的方式就是利用集群,分摊压力到是更有效直接的方式。
887412 commented 3 years ago

@Jigsawk

@Aiome 你文章解释性能瓶颈感觉解释的有问题。 开启多线程是为了提高CPU利用率。CPU密集型的操作不需要开启多线程提高CPU利用率,CPU本来就忙啊,线程数与CPU核心数相同就好了。IO密集型才需要开启多线程,A线程IO操作时CPU需要等待时间很长,这时切换到B线程进行计算操作,提高CPU利用率。 我感觉Reids设计应该是认为单线程IO操作时我CPU等会就等会了,比切换其他线程进行计算还要快。

我大致也看懂了你的意思,刚读完文章我也觉得作者由“Redis 服务中运行的绝大多数操作的性能瓶颈都不是 CPU”得出“没必要开启多线程”的这个说法有点问题。诚然可以通过selector来处理网络连接,但是如果开启多线程处理指令,应该依然能够进一步提升效率(提升了CPU的利用率)

我觉得因为redis在单线程处理连接与指令方面已经很快了,如果要引入多线程处理指令,虽然也可能一定程度上提升效率,但是却要考虑共享数据的线程安全问题,以及引入事务等概念?这在开发维护上带来了很高的复杂度。redis可能并不想做一个大而全的东西,只要能够在自己专注的场景中,获得高性能就可以了。如果对读写有更高要求,那就横向拓展。

因为引入多线程是可以达到部分优化的,比如redis4.0的对垃圾的处理和redis6.0对命令的处理,但是redis的性能瓶颈是内存和网络IO,引入再多的线程也没办法解决根本矛盾,所以注定redis中多线程只能用于优化非阻塞式处理比较耗时的任务,但是根本上还是沿着单线程的思路,即使是redis6.0,它命令的执行采用单线程,只有在命令的处理上使用多线程。