PHP concurrent IO programming

The problem of concurrent IO has always been a technical challenge in back-end programming, from the earliest synchronous blocking Fork process, to multi-process / multi-thread, to the current asynchronous IO, coroutine. Because PHP programmers have a powerful LAMP framework, they know very little about the underlying knowledge. The purpose of this article is to introduce PHP's various attempts for concurrent IO programming in detail, and finally introduce the use of Swoole to explain the comprehensive IO problem.

Multi-process / multi-thread synchronous blocking

The earliest server-side programs used multiple processes and threads to solve the problem of concurrent IO. The process model first appeared, and the concept of process has been in existence since the birth of the Unix system. The earliest server-side programs generally accepted Accept to create a process after a client connection, and then the child process entered the loop to synchronously block and interact with the client connection, send and receive data.

Multi-threaded mode appears later, threads are more lightweight than processes, and threads share memory stacks, so interaction between different threads is very easy to achieve. For programs such as chat rooms, client connections can interact, and players in the chat room can send messages to anyone else. It is very simple to implement in multi-threaded mode, you can directly read and write a client connection in the thread. The multi-process model requires the use of pipes, message queues, and shared memory to achieve data interaction, and can be realized by a complex technology called inter-process communication (IPC).

Code example:

The process of the multi-process / thread model is

  1. Create a socket, bind the server port (bind), listen port (listen), use PHP stream_socket_server a function to complete the above three steps, of course, you can also use php sockets extension to achieve separately.
  2. Enter the while loop, block on the accept operation, and wait for the client connection to enter. At this point, the program will enter a sleep state until a new client initiates a connection to the server, and the operating system will wake up the process. The accept function returns the socket to which the client connects
  3. The main process uses the fork (php: pcntl_fork) to create child processes in the multi-process model, and pthread_create (php: new Thread) to create child threads in the multi-thread model. If there is no special statement below, process will be used to mean process / thread.
  4. After the child process is successfully created, it enters a while loop, blocking on the recv (php: fread) call, waiting for the client to send data to the server. After receiving the data, the server program processes it and then uses send (php: fwrite) to send a response to the client. Long-connected services will continue to interact with clients, while short-connected services generally close when they receive a response.
  5. When the client connection is closed, the child process exits and destroys all resources. The main process will recycle this child process.

 

The biggest problem with this model is that the cost of process / thread creation and destruction is high. So the above pattern cannot be applied to very busy server programs. The corresponding improved version solves this problem, which is the classic Leader-Follower model.

Code example:

Its characteristic is that N processes will be created after the program starts. Each child process enters Accept, waiting for a new connection to enter. When the client connects to the server, one of the child processes will be woken up to start processing client requests and no longer accept new TCP connections. When this connection is closed, the child process will release, re-enter Accept, and participate in processing the new connection.

The advantage of this model is that it can fully reuse processes without additional consumption, and the performance is very good. Many common server programs are based on this model, such as Apache, PHP-FPM.

The multi-process model also has some disadvantages.

  1. This model relies heavily on the number of processes to solve the concurrency problem. A client connection needs to occupy one process, and the number of worker processes has as many concurrent processing capabilities as possible. The number of processes that the operating system can create is limited.
  2. Starting a large number of processes will cause additional process scheduling costs. When hundreds of processes may consume less than 1% of the CPU in the process context switching scheduling, it can be ignored. If thousands or even tens of thousands of processes are started, the consumption will rise linearly. The scheduling consumption may account for tens or even 100% of the CPU.

In addition, there are some scenarios where the multi-process model cannot be solved. For example, an instant chat program (IM), where a server needs to maintain tens of thousands or even hundreds of thousands of millions of connections at the same time (the classic C10K problem), the multi-process model is unable to handle it.

Another scenario is the weakness of the multi-process model. Usually the Web server starts 100 processes. If a request consumes 100ms, 100 processes can provide 1000qps. This processing capability is not bad. However, if you want to call the external Http interface in the request, such as QQ and Weibo login, it will take a long time, and a request takes 10s. That process can only process 0.1 requests per second, and 100 processes can only reach 10qps, which is too poor.

Is there a technology that can handle all concurrent IO in one process? The answer is yes, this is IO multiplexing technology.

IO multiplexing / event loop / asynchronous non-blocking

In fact, the history of IO reuse is as long as multi-process. Linux has provided a select system call very early, which can maintain 1024 connections in a process. Later, the poll system call was added, and poll made some improvements to solve the 1024 limit problem, which can maintain any number of connections. But another problem with select / poll is that it needs to loop to detect whether there is an event on the connection. This is where the problem comes. If the server has 1 million connections and only one connection sends data to the server at a time, select / poll needs to loop 1 million times, of which only 1 is hit, and the remaining 990,000 9999 times are invalid, wasting CPU resources in vain.

Until the Linux 2.6 kernel provides a new epoll system call, which can maintain an unlimited number of connections, and without polling, this really solves the C10K problem. Now all kinds of high concurrent asynchronous IO server programs are based on epoll, such as Nginx, Node.js, Erlang, Golang. Single-process single-threaded programs like Node.js can maintain more than 1 million TCP connections, all thanks to epoll technology.

IO multiplexed asynchronous non-blocking programs use the classic Reactor model. Reactor, as the name implies, means the reactor. It does not handle any data sending and receiving. It is only possible to monitor the event changes of a socket handle.

Reactor has 4 core operations:

  1. add add socket to listen to reactor, which can be listen socket or client socket, can also be pipe, eventfd, signal, etc.
  2. set modify event monitoring, you can set the type of monitoring, such as readable and writable. It is readable and easy to understand. For listen socket, a new client connection needs to be accepted. For client connection is to receive data, recv is required. Writable events are more difficult to understand. A SOCKET has a cache area. If you want to send 2M of data to the client connection, it cannot be sent at one time. The operating system default TCP cache area is only 256K. You can only send 256K at a time. After the buffer is full, send will return an EAGAIN error. At this time, it is necessary to monitor the writable event. In purely asynchronous programming, you must monitor the writable to ensure that the send operation is completely non-blocking.
  3. del is removed from reactor and no longer listens to events
  4. callback is the corresponding processing logic after the event, which is generally formulated at add / set. C language is implemented with function pointers, JS can use anonymous functions, PHP can use anonymous functions, object method arrays, and string function names.

Reactor is just an event generator. The actual operations on socket handles, such as connect / accept, send / recv, and close are done in the callback. The specific coding can refer to the following pseudo code:

The Reactor model can also be used in conjunction with multi-process and multi-thread, both to achieve asynchronous non-blocking IO, but also to use multi-core. The current popular asynchronous server programs are all in this way: such as

  • Nginx: Multi-process Reactor
  • Nginx + Lua: multi-process Reactor + coroutine
  • Golang: single-threaded Reactor + multi-threaded coroutine
  • Swoole: Multi-threaded Reactor + multi-process Worker

What is coroutine

The coroutine is actually an asynchronous IO Reactor model from the perspective of the underlying technology. The application layer implements task scheduling by itself, and uses Reactor to switch each currently executing user mode thread, but the user code is completely unaware of the existence of Reactor.

PHP concurrent IO programming practice

PHP related extensions

  • Stream: socket package provided by PHP kernel
  • Sockets: encapsulation of the underlying Socket API
  • Libevent: Encapsulation of libevent library
  • Event: Based on Libevent's more advanced encapsulation, it provides support for object-oriented interfaces, timers, and signal processing
  • Pcntl / Posix: multi-process, signal, process management support
  • Pthread: Multithreading, thread management, lock support
  • PHP also has related extensions for shared memory, semaphores, and message queues
  • PECL: PHP's extended library, including the bottom of the system, data analysis, algorithms, drivers, scientific computing, graphics, etc. If it is not found in the PHP standard library, you can find the desired function in PECL.

Advantages and disadvantages of PHP language

Advantages of PHP:

  1. The first one is simple. PHP is simpler than any other language. PHP can really be started in a week. C ++ has a book called "21-day in-depth study of C ++". In fact, it is impossible to learn in 21 days. It can even be said that C ++ cannot be mastered in depth in 3-5 years. But PHP can definitely get started in 7 days. So the number of PHP programmers is very large, and recruitment is easier than other languages.
  2. PHP's function is very powerful, because the official standard library and extension library of PHP provide 99% of the things that can be used for server programming. PHP PECL extension library any function you want.

In addition, PHP has a history of more than 20 years, the ecosystem is very large, and a lot of code can be found on Github.

Disadvantages of PHP:

  1. The performance is relatively poor, because it is a dynamic script after all, and is not suitable for intensive operations. If it is also written in PHP and then written in c ++, the PHP version is one hundred times worse.
  2. The function naming convention is poor. Everyone knows this. PHP is more practical and has no specifications. The naming of some functions is very confusing, so every time you have to go through the PHP manual.
  3. The interface granularity of the provided data structures and functions is relatively coarse. PHP has only one Array data structure, and the bottom layer is based on HashTable. PHP's Array is a collection of functions such as Map, Set, Vector, Queue, Stack, Heap and other data structures. In addition, PHP has an SPL that provides class encapsulation of other data structures.

So PHP

  1. PHP is more suitable for practical application-level programs, a tool for business development and rapid implementation
  2. PHP is not suitable for developing low-level software
  3. Use C / C ++, JAVA, Golang and other static compiled languages ​​as a supplement to PHP, combining dynamic and static
  4. Using IDE tools to achieve automatic completion, grammar prompts  

PHP Swoole Extension

Based on the above extensions, using pure PHP can fully implement asynchronous web server and client programs. But if you want to implement a multi-IO thread, there are still many tedious programming tasks to be done, including how to manage the connection, how to ensure the principle of data transmission and reception, and the processing of network protocols. In addition, the performance of the PHP code in the protocol processing part is relatively poor, so I started a new open source project Swoole, using C language and PHP to complete this work. The flexible business module uses PHP to develop with high efficiency, and the underlying bottom layer and protocol processing part are implemented in C language, which ensures high performance. It is loaded into PHP in an extended manner, providing a complete network communication framework, and then PHP code to write some business. Its model is based on multi-threaded Reactor + multi-process Worker, which supports both fully asynchronous and semi-asynchronous and semi-synchronous.

Some features of Swoole:

  • Accept thread to solve Accept performance bottlenecks and shock group problems
  • Multiple IO threads can make better use of multiple cores
  • Provides two modes: fully asynchronous and semi-synchronous and semi-asynchronous
  • Asynchronous mode is used for parts with high concurrent IO
  • Synchronous mode for complex business logic
  • The bottom layer supports traversing all connections, sending data between each other, automatically merging and splitting data packets, and data transmission atomicity.

Swoole's process / thread model:

Swoole program execution flow:

Using PHP + Swoole extension to realize asynchronous communication programming

The example code can be viewed on the https://github.com/swoole/swoole-src homepage.

TCP server and client

Asynchronous TCP server:

 

Here the new swoole_server object, then the parameters are passed into the listening HOST and PORT, and then set up three callback functions, respectively, onConnect has a new connection to enter, onReceive received a client's data, onClose a client closed the connection . Finally, call start to start the server program. The bottom layer of swoole will start a corresponding number of Reactor threads and Worker processes according to how many CPU cores the current machine has.

Asynchronous client:

The usage method of the client is similar to the server except that there are 4 callback events. OnConnect successfully connects to the server, and then you can send data to the server. onError failed to connect to the server. The onReceive server sends data to the client connection. onClose connection closed.

After setting the event callback, initiate a connect to the server, the parameters are the server's IP, PORT and timeout.

 

Synchronization client:

The sync client does not need to set any event callback, it does not have Reactor monitoring, and it is blocking the serial. Wait for IO to complete before proceeding to the next step.

Asynchronous tasks:

The asynchronous task function is used to execute a time-consuming or blocking function in a purely asynchronous Server program. The underlying implementation uses a process pool, onFinish will be triggered after the task is completed, and the results of the task processing can be obtained in the program. For example, an IM needs to be broadcast. If it is broadcast directly in asynchronous code, it may affect the processing of other events. In addition, file reading and writing can also be achieved using asynchronous tasks, because file handles cannot use Reactor monitoring like sockets. Because the file handle is always readable, reading the file directly may block the server program, and using asynchronous tasks is a very good choice.

Asynchronous millisecond timer

These two interfaces implement the setInterval and setTimeout functions similar to JS, and can be set to implement a function at n millisecond intervals or execute a function after n milliseconds.

Asynchronous MySQL client

swoole also provides a MySQL asynchronous client with a built-in connection pool, you can set the maximum number of MySQL connections. Concurrent SQL requests can reuse these connections instead of creating them repeatedly, which protects MySQL from being exhausted by connection resources.

Asynchronous Redis client

Asynchronous web program

The logic of the program is to read a data from Redis and then display the HTML page. The performance of pressure measurement using ab is as follows:

The performance test results of the same logic under php-fpm are as follows:

WebSocket program

Swoole has a built-in websocket server, which can implement the function of actively pushing Web pages, such as WebIM. There is an open source project that can be used as a reference. https://github.com/matyhtf/php-webim

PHP + Swoole coroutine

Asynchronous programming generally uses the callback method. If you encounter very complicated logic, you may nest callback functions layer by layer. Coroutines can solve this problem. You can write code sequentially, but the runtime is asynchronous and non-blocking. Tencent engineers based on Swoole extension and PHP5.5's Yield / Generator syntax to achieve Golang-like coroutines, the project name is TSF (Tencent Server Framework), open source project address: https://github.com/tencent-php/tsf . At present, there are large-scale applications in Tencent's enterprise QQ, QQ public account project and the illegal project of neglecting wheels.

The use of TSF is also very simple. The following calls 3 IO operations, which are completely serial writing. But it is actually executed asynchronously and non-blocking. The TSF bottom scheduler takes over the execution of the program, and will continue to execute after the corresponding IO is completed.

Use PHP + Swoole on Raspberry Pi

Both PHP and Swoole can be compiled and run on the ARM platform, so PHP + Swoole can also be used to develop network communication programs on the Raspberry Pi system.

Published 23 original articles · won praise 2 · Views 5240

Guess you like

Origin blog.csdn.net/bianlitongcn/article/details/104990539