非阻塞网络服务器如何工作?
How does non blocking web server work?
例如繁体:
[client] 0s GET /test
[server] 1s got request
[server] 2s query db
[server] 3s db return
[server] 4s return response
[client] 5s get response
非阻塞网络服务器如何工作?例如,节点?
[client] 0s GET /test
[server] 1s got request
/ \
[server] 2s return response [server] 2s query db
| [server] 3s db return
| |
| |
| |
[client] 3s get fake response[status] [client] 4s get real response[data]
所以传统的要花费 5s 而非阻塞的只需要 3s 或 4s ??
具有使用非阻塞 I/O 的请求处理程序的非阻塞 Web 服务器可以在任何给定请求等待非阻塞 I/O 请求的结果时交错处理其他请求,并且它只需一个线程即可完成所有这些。
因此,如果传入的第一个请求具有以下事件序列:
5ms [r1 - sync: receive incoming request, send read request to database]
100ms [r1 - async: read request from database returns data]
5ms [r1 - sync: convert data to proper form for the response]
5ms [r1 - async: send response back to client]
上面标记"sync:"的操作是非阻塞服务器主线程忙于工作的操作。如果这个非阻塞服务器是单线程的,那么此时没有其他操作正在服务。如果在此期间有另一个请求进来,那么它将被排队 - 等待主线程不忙的时候。
上述序列中排队等待处理的新请求的第一个机会是在 100 毫秒异步数据库读取操作中。
因此,我们假设在上述请求之后 1 毫秒内出现另一个请求,这次是一个写入请求,具有以下正常进程:
5ms [r2 - sync: receive incoming request, send write request to database]
500ms [r2 - async: write request returns result]
5ms [r2 - async: send response back to client]
然后,这两个请求将交织在一起:
r1 将被接收,初始处理将在其上完成,然后它将其读取请求发送到数据库。此时,r1 已完成,直到读取请求从数据库返回,因此主服务器线程现在可用于处理其他请求。
如果r2在队列中等待,则启动r2请求。初始处理在其上完成,并将它的写请求发送到数据库。
此时,有两个数据库操作运行并行,一个从 r1 读取,一个从 r2 写入,全部来自一个服务器线程。
现在,这两个请求都在处理中,服务器线程暂时无事可做(如果有的话,它可以处理其他传入请求)。
然后,在 r1 请求发送其读取请求 100 毫秒后,数据库触发其回调以指示数据可用。因此,该回调从 运行 开始,然后执行 r1 请求的最后两个步骤并将响应发送回其客户端。
然后,在 r2 请求发送其写入请求后 500 毫秒,数据库触发其回调以指示写入操作已完成。因此回调开始 运行 然后执行 r2 请求的最后两个步骤并将响应发送回其客户端。
这里的操作要点是没有什么是先发制人的。一个给定的请求 运行 是同步的,直到它触发一个异步操作。那时它已经完成了当时它可以处理的任何事情,然后服务器线程可以自由地为在其队列中等待的其他事件提供服务(其他新请求进入或其他等待 运行 的请求的异步回调) .下一个项目从队列中拉出,它 运行s 直到它完成。下一个项目从队列中拉出,它 运行s 直到它完成,等等...
像node.js这样的非阻塞单线程服务器有几个优点。
他们可以非常有效地同时处理一堆正在运行的请求,I/O。这是因为它们可以用一个线程处理所有主要处理,即使同时有一堆请求都在运行。非阻塞服务器必须同时为每个正在运行的请求创建系统线程,这在服务器端会占用更多资源,并且多线程多任务处理的额外开销也会导致整体效率降低如果您只查看服务器可以处理的 request/sec 的数量,则操作,因为一些服务器 CPU 用于在线程之间进行任务切换。
使用单线程 node.js 设计,线程之间的同步问题要少得多,因为代码都是非抢占式的。从一个请求处理到另一个请求的请求切换只发生在请求发出非阻塞 I/O 请求的时候。这可以极大地简化许多编程,例如访问共享资源,因为在 node.js 模型中,您不必使用互斥锁来保护共享数据。这不仅大大减少了编写代码的复杂性,而且大大减少了出现讨厌的并发错误的机会。 node.js 程序中仍然可能存在一些并发问题,但它们的数量要少得多,而且通常更易于编写代码。
非阻塞单线程风格的缺点有时必须解决:
长时间的 运行ning 同步计算将 "hog" 主线程和其他请求在此期间不会获得任何周期。虽然很少有长时间的 运行ning 计算没有同时执行 I/O,但它可能会发生。一个常见的解决方法是将这些长时间的 运行ning 计算移到另一个进程中,在该进程中它将被 OS 分片,以便主线程可以做其他事情。
非阻塞 I/O 代码需要一些时间来学习如何编写,并且编写起来比阻塞 I/O 代码更复杂。 Promises 通常有助于使此代码健壮、可读且更易于编写(尤其是在处理错误时)。
如果您的服务器有多个 CPU,单个 node.js 线程不会利用它们,因为它主要只使用一个 CPU。这通常通过使用集群来解决,集群将为每个 CPU 运行 一个单独的 node.js 进程,并将传入请求传递给任何可用的集群服务器。
例如繁体:
[client] 0s GET /test
[server] 1s got request
[server] 2s query db
[server] 3s db return
[server] 4s return response
[client] 5s get response
非阻塞网络服务器如何工作?例如,节点?
[client] 0s GET /test
[server] 1s got request
/ \
[server] 2s return response [server] 2s query db
| [server] 3s db return
| |
| |
| |
[client] 3s get fake response[status] [client] 4s get real response[data]
所以传统的要花费 5s 而非阻塞的只需要 3s 或 4s ??
具有使用非阻塞 I/O 的请求处理程序的非阻塞 Web 服务器可以在任何给定请求等待非阻塞 I/O 请求的结果时交错处理其他请求,并且它只需一个线程即可完成所有这些。
因此,如果传入的第一个请求具有以下事件序列:
5ms [r1 - sync: receive incoming request, send read request to database]
100ms [r1 - async: read request from database returns data]
5ms [r1 - sync: convert data to proper form for the response]
5ms [r1 - async: send response back to client]
上面标记"sync:"的操作是非阻塞服务器主线程忙于工作的操作。如果这个非阻塞服务器是单线程的,那么此时没有其他操作正在服务。如果在此期间有另一个请求进来,那么它将被排队 - 等待主线程不忙的时候。
上述序列中排队等待处理的新请求的第一个机会是在 100 毫秒异步数据库读取操作中。
因此,我们假设在上述请求之后 1 毫秒内出现另一个请求,这次是一个写入请求,具有以下正常进程:
5ms [r2 - sync: receive incoming request, send write request to database]
500ms [r2 - async: write request returns result]
5ms [r2 - async: send response back to client]
然后,这两个请求将交织在一起:
r1 将被接收,初始处理将在其上完成,然后它将其读取请求发送到数据库。此时,r1 已完成,直到读取请求从数据库返回,因此主服务器线程现在可用于处理其他请求。
如果r2在队列中等待,则启动r2请求。初始处理在其上完成,并将它的写请求发送到数据库。
此时,有两个数据库操作运行并行,一个从 r1 读取,一个从 r2 写入,全部来自一个服务器线程。
现在,这两个请求都在处理中,服务器线程暂时无事可做(如果有的话,它可以处理其他传入请求)。
然后,在 r1 请求发送其读取请求 100 毫秒后,数据库触发其回调以指示数据可用。因此,该回调从 运行 开始,然后执行 r1 请求的最后两个步骤并将响应发送回其客户端。
然后,在 r2 请求发送其写入请求后 500 毫秒,数据库触发其回调以指示写入操作已完成。因此回调开始 运行 然后执行 r2 请求的最后两个步骤并将响应发送回其客户端。
这里的操作要点是没有什么是先发制人的。一个给定的请求 运行 是同步的,直到它触发一个异步操作。那时它已经完成了当时它可以处理的任何事情,然后服务器线程可以自由地为在其队列中等待的其他事件提供服务(其他新请求进入或其他等待 运行 的请求的异步回调) .下一个项目从队列中拉出,它 运行s 直到它完成。下一个项目从队列中拉出,它 运行s 直到它完成,等等...
像node.js这样的非阻塞单线程服务器有几个优点。
他们可以非常有效地同时处理一堆正在运行的请求,I/O。这是因为它们可以用一个线程处理所有主要处理,即使同时有一堆请求都在运行。非阻塞服务器必须同时为每个正在运行的请求创建系统线程,这在服务器端会占用更多资源,并且多线程多任务处理的额外开销也会导致整体效率降低如果您只查看服务器可以处理的 request/sec 的数量,则操作,因为一些服务器 CPU 用于在线程之间进行任务切换。
使用单线程 node.js 设计,线程之间的同步问题要少得多,因为代码都是非抢占式的。从一个请求处理到另一个请求的请求切换只发生在请求发出非阻塞 I/O 请求的时候。这可以极大地简化许多编程,例如访问共享资源,因为在 node.js 模型中,您不必使用互斥锁来保护共享数据。这不仅大大减少了编写代码的复杂性,而且大大减少了出现讨厌的并发错误的机会。 node.js 程序中仍然可能存在一些并发问题,但它们的数量要少得多,而且通常更易于编写代码。
非阻塞单线程风格的缺点有时必须解决:
长时间的 运行ning 同步计算将 "hog" 主线程和其他请求在此期间不会获得任何周期。虽然很少有长时间的 运行ning 计算没有同时执行 I/O,但它可能会发生。一个常见的解决方法是将这些长时间的 运行ning 计算移到另一个进程中,在该进程中它将被 OS 分片,以便主线程可以做其他事情。
非阻塞 I/O 代码需要一些时间来学习如何编写,并且编写起来比阻塞 I/O 代码更复杂。 Promises 通常有助于使此代码健壮、可读且更易于编写(尤其是在处理错误时)。
如果您的服务器有多个 CPU,单个 node.js 线程不会利用它们,因为它主要只使用一个 CPU。这通常通过使用集群来解决,集群将为每个 CPU 运行 一个单独的 node.js 进程,并将传入请求传递给任何可用的集群服务器。