Swoole
重新定義 PHP
@ PHP 也有 Day # 33
By Albert Chen
About Me
{
"data": {
"human": {
"name": "Albert Chen",
"occupation": "Software Engineer",
"company": "Unisharp Techonology",
"website": "http://albert-chen.com",
"interests": [
"traveling",
"programming",
"cooking"
],
"age": 25,
"relationship_status": "single"
}
}
}
Programmer Hierarchy
What is Swoole?
Swoole is...
a C extension for
Open Source
Based on Apache 2.0 Liscense
Swoole is a
Multi-process & Multi-thread
framework
Author
姓名:韓天峰 (Rango)
經歷:阿里巴巴集團
車輪互聯 · 總架構師
簡介:資深 PHP 程序員
Swoole 開源項目創始人
PHP官方擴展開發组成員
+
=
Naming of Swoole
Features
-
Written in C, extremely high performance
-
Event driven, non-blocking I/O
-
Supports TCP /UDP / UnixSock
-
Supports Async / Sync / Coroutine
-
Supports IPv4 / IPv6 network
- High CPU affinity
- Multi-process, multi-thread structure
Use Cases
-
TCP / UDP server and client
-
Task worker
-
Database connection pool
-
Millisecond timer
-
Async I/O client
-
Async Http / WebSocket client
-
Async Redis / MySQL client
Use Cases
PHP is awesome
But can it be even faster?
PHP 7
PHP Lifecycle
PHP Execution
High Concurrency
Matters
PHP vs Node JS
PHP
Node JS
Sync vs. Async
Async Solutions
Wokerman
Kraken PHP
Let's get started
with Swoole!
Prerequisites
-
Operation system: Linux, FreeBSD or MacOS
-
Linux kernel version >= 2.3.32
-
PHP version >= 5.3.10
-
GCC version >= 4.4 or Clang
-
Cmake version >= 2.4 (Cmake is required to compile libswoole as C/C++ library)
Installation
Linux
pecl install swoole
MacOS X
brew install php71-swoole
Manually Compile
# enter the directory of swoole source code
cd swoole
# prepare the build environment for a PHP extension
phpize
# add configuration paramaters as needed
./configure
# a successful result of make is swoole/module/swoole.so
make
# install the swoole into the PHP extensions directory
sudo make install
Enable Swoole
# check the php.ini file location
php -i | grep php.ini
# add the extension=swoole.so to the end of php.ini
sudo echo "extension=swoole.so" >> php.ini
# check if the swoole extension has been enabled
php -m | grep swoole
# check swoole version and modules
php --ri swoole
After install the swoole extension to the PHP extensions directory, you will need to adjust php.ini and add an extension=swoole.so line before you can use the extension.
Hello World
<?php
$http = new Swoole\Http\Server("127.0.0.1", 9501);
$http->on("start", function ($server) {
echo "Swoole http server is started at http://127.0.0.1:9501\n";
});
$http->on("request", function ($request, $response) {
$response->header("Content-Type", "text/plain");
$response->end("Hello World\n");
});
$http->start();
vs.
Swoole vs. NodeJS
Benchmark
<?php
$http = new Swoole\Http\Server("127.0.0.1", 9501);
$http->on("start", function ($server) {
echo "Swoole http server is started at http://127.0.0.1:9501\n";
});
$http->on("request", function ($request, $response) {
$response->header("Content-Type", "text/html");
$response->end("hello world");
});
$http->start();
Benchmark
var http = require('http');
var app = function (req, res) {
res.writeHead(200, {
'Content-Type': 'text/html'
});
res.end('hello world');
};
var server = http.createServer(app);
server.listen(9502, function() {
console.log("Server running at http://127.0.0.1:9502");
});
Benchmark
wrk -t4 -c400 -d10s http://127.0.0.1:9501
Running 10s test @ http://127.0.0.1:9501
4 threads and 400 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 17.16ms 26.41ms 199.98ms 86.94%
Req/Sec 12.53k 1.59k 17.51k 72.75%
499976 requests in 10.03s, 78.20MB read
Socket errors: connect 0, read 235, write 0, timeout 0
Requests/sec: 49853.41
Transfer/sec: 7.80MB
Running 10s test @ http://127.0.0.1:9502
4 threads and 400 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 30.62ms 5.28ms 144.94ms 92.94%
Req/Sec 3.14k 645.97 4.37k 85.50%
125051 requests in 10.03s, 19.20MB read
Socket errors: connect 0, read 164, write 19, timeout 0
Requests/sec: 12462.35
Transfer/sec: 1.91MB
What happend behind the scenes?
Work Flow
Swoole Server Model
Reactor Model
Requests arrive main reactor
Main reactor binds request to reactor
Reactor dispatches socket to worker
Worker handles the main logic
Callback to correspondent reactor
Reactor returns result to client
Sequence Diagram
Swoole Basics
HTTP Server
HTTP Server
<?php
$http = new Swoole\Http\Server("127.0.0.1", 9501);
$http->set([
'daemonize' => true,
'reactor_num' => 4,
'worker_num' => 4,
'task_worker_num' => 4,
'max_request' => 10000,
'task_max_request' => 10000,
'open_cpu_affinity' => true,
'log_file' => __DIR__ . '/swoole.log',
'ssl_cert_file' => $key_dir . '/ssl.crt',
'ssl_key_file' => $key_dir . '/ssl.key'
]);
HTTP Server
<?php
$http->on("start", function ($server) {
echo "Swoole http server is started at http://127.0.0.1:9501\n";
// don't define any global variables here
});
In the callback function registered for start, it only allows to log record and modify the name of process.
The event start and workerstart happen concurrently in different processes and have no precedence.
HTTP Server
<?php
$http->on("workerStart", function (Server $server, int $worker_id) {
echo "worker start id: {$worker_id}\n";
echo 'taskworker: ' . json_encode($server->taskworker) . "\n";
// not allowed in osx
// swoole_set_process_name('process name');
});
The event workerstart happens when the worker process or task worker process starts.
HTTP Server
<?php
$http->on("connect", function (Server $server, int $fd, int $from_id) {
// echo json_encode($server) . "\n";
});
This event connect happens when the new connection comes. And the worker process will call the callback function registered for the event connect.
In the mode UDP, there is only receive event and there is no connect and close event.
$fd the id number of client
$from_id the id number of reactor thread
HTTP Server
<?php
$http->on("request", function ($request, $response) {
echo json_encode($request);
// forget php magic functions
// echo json_encode($_GET);
$response->header("Content-Type", "text/html");
$response->end('Hello World!');
});
You can get the information of request here (GET, POST, etc.) and handle your response
HTTP Server
<?php
$http->set([
'task_worker_num' => 4
]);
Send data to the task worker processes.To call this method, it needs to set the configuration of task_worker_num and the callback function of onTask and onFinish.
This method is non-blocking and return immediately after sending the task data to the pool of task worker.
HTTP Server
<?php
$http->on("task", function (Server $server, $task_id, $from_id, $data) {
// task worker received data
echo "task received:" . json_encode($data) . "\n";
return $data;
});
$http->on("finish", function (Server $server, $task_id, $data) {
// task worker callback
echo "task finished:" . json_encode($data) . "\n";
});
global $globalServer;
$data = [
'code' => 'ok',
'error' => false,
'payload' => 'Hello World'
];
$globalServer->task($data);
Swoole Basics
Websocket Server
Websocket Server
Websocket Server
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: http://example.com
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat
Websocket Server
The swoole_websocket_server class inherits from the class swoole_server.
If the swoole_websocket_server has been setted the callback function of event request, it can be used as Http server.
If the swoole_websocket_server hasn't been setted the callback function of event request and received http request, it would repond http 400 error.
Websocket Server
<?php
use Swoole\Websocket\Server;
$server = new Server("127.0.0.1", 9503);
$server->on('open', function (Server $server, $request) {
// echo json_encode($request) . "\n";
echo "server: handshake success with fd{$request->fd}\n";
});
$server->on('message', function (Server $server, $frame) {
echo "receive from {$frame->fd}:{$frame->data}, opcode:{$frame->opcode}, fin:{$frame->finish}\n";
$server->push($frame->fd, "this is server");
});
$server->on('close', function (Server $server, $fd) {
echo "client {$fd} closed\n";
});
$server->start();
Websocket Server
<?php
$server->on('handshake', function (Request $request, Response $response) {
// you can choose to implement your handshake method
$secWebSocketKey = $request->header['sec-websocket-key'];
$patten = '#^[+/0-9A-Za-z]{21}[AQgw]==$#';
if (0 === preg_match($patten, $secWebSocketKey) || 16 !== strlen(base64_decode($secWebSocketKey))) {
$response->end();
return false;
}
echo $request->header['sec-websocket-key'];
$key = base64_encode(sha1(
$request->header['sec-websocket-key'] . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11',
true
));
$headers = [
'Upgrade' => 'websocket',
'Connection' => 'Upgrade',
'Sec-WebSocket-Accept' => $key,
'Sec-WebSocket-Version' => '13',
];
if (isset($request->header['sec-websocket-protocol'])) {
$headers['Sec-WebSocket-Protocol'] = $request->header['sec-websocket-protocol'];
}
foreach ($headers as $key => $val) {
$response->header($key, $val);
}
$response->status(101);
$response->end();
echo "connected!" . PHP_EOL;
return true;
});
Websocket Server
<?php
public function broadcast($server, $data)
{
$connections = $server->connections;
$sender = $data['sender'] ?? 0;
foreach($connections as $fd) {
if ($sender !== (integer) $fd) {
$server->push($fd, $message, $opcode);
}
}
}
There's no socket.io library for swoole yet, that means you need to handle every detail of websocket on your own.
Swoole Basics
Async IO
Async File IO
<?php
use Swoole\Async;
// you can configure your async settings here.
Async::set([
'aio_mode' => SWOOLE_AIO_LINUX,
'thread_num' => 4,
]);
Async::readfile(__DIR__ . "/file.txt", function($filename, $content) {
echo "$filename: $content";
});
Read files in the async way. When the operation of reading content from file finished, the callback registered is callback automatically.
The max length of file is 4M.
Async File IO
<?php
use Swoole\Async;
// If the aio mode setted by swoole_async_set is SWOOLE_AIO_BASE,
// the method can't support append content to the end of file and
// must set the value of $fileContent to integer multiples of 4096.
Async::writeFile(__DIR__ . "/file.log", "test\n", function ($filename) {
echo "wirte ok.\n";
}, $append = false);
Write file in the async way. When the operation of writing content to file finished, the callback registered is callback automatically.
Async File IO
<?php
use Swoole\Async;
Async::read(__DIR__ . "/file.txt", function ($filename, $content) {
echo "file: $filename\ncontent-length: " . strlen($content) . "\nContent: $content\n";
if (empty($content)) {
echo "file is end.\n";
return false;
} else {
return true;
}
}, 8192);
Read the file content stream in the async way. When the operation of reading content from file finished, the callback registered is callback automatically.
Async File IO
<?php
use Swoole\Async;
for ($i = 0; $i < 10; $i++) {
Async::write(__DIR__ . "/file.log", "A\n", -1, function ($file, $writen) {
echo "write $file [$writen]\n";
//return true: write contine. return false: close the file.
return true;
});
}
Write file stream in the async way. When the operation of writing content to file finished, the callback registered is callback automatically.
Async IO
-
Async MySQL Client
-
Async Redis Client
-
Async HTTP Client
-
Async Websocket Client
-
Async HTTP 2.0 Client
Swoole Basics
Coroutine
Coroutine
Coroutine
<?php
$a = sum(1, 2);
$b = sum($a, 3);
$c = sum($b, 4);
var_dump(array($a, $b, $c));
<?php
async_sum(1, 2, function($a) {
async_sum($a, 3, function($b) use ($a) {
async_sum($b, 4, function($c) use ($a, $b) {
var_dump(array($a, $b, $c));
});
});
});
Coroutine
Coroutine
Coroutine
Coroutine
<?php
use Swoole\Coroutine\MySQL;
$mysql = new Swoole\Coroutine\MySQL();
$mysql->connect([
'host' => '127.0.0.1',
'user' => 'user',
'password' => 'pass',
'database' => 'test',
]);
$result = $mysql->query('select sleep(1)');
// your business logic
Coroutine
<?php
use Swoole\Coroutine\MySQL;
use Swoole\Coroutine\Redis;
$redis = new Redis;
$redis->connect('127.0.0.1', 6379);
$redis->setDefer();
$redis->get('key');
$mysql = new MySQL;
$mysql->connect(['host' => '127.0.0.1', 'user' => 'user', 'password' => 'pass', 'database' => 'test']);
$mysql->setDefer();
$mysql->query('select sleep(1)');
$redis_res = $redis->recv();
$mysql_res = $mysql->recv();
// your business logic
Coroutine
Redis sent packet
Redis received packet
MySQL sent packet
MySQL received packet
Redis sent packet
Redis received packet
MySQL sent packet
MySQL received packet
Communication in
Different Processes and Threads
Communication Issue
<?php
use Swoole\Http\Server;
$http = new Server("127.0.0.1", 9501);
$http->on("start", function ($server) {
echo "Swoole http server is started at http://127.0.0.1:9501\n";
});
$i = 1;
$http->on("request", function ($request, $response) {
global $i;
$response->header("Content-Type", "text/html");
$response->end($i);
$i++;
});
$http->start();
Process Model
Communication
-
I/O (File, DB)
-
Pipe
-
Message Queue
-
Memory Table
-
Channel
Swoole Table
Swoole table is a high performance memory management module, implemented based on shared memory and spin lock.
- High performance, the single thread read/write speed is more than 2 millions per second.
- Can be used by multiple threads or processes.
In order to keep the data synchronization, you have to use Swoole\Lock if the memory is modified and accessed by multiple threads or processes.
Swoole Table
<?php
use Swoole\Table;
// create table
// columns size
$table = new Table(1024);
// 1, 2, 4, 8
$table->column('id', swoole_table::TYPE_INT, 4);
$table->column('name', swoole_table::TYPE_STRING, 64);
$table->column('num', swoole_table::TYPE_FLOAT);
$table->create();
// set column
$table->set('tianfenghan@qq.com', array('id' => 145, 'name' => 'rango', 'num' => 3.1415));
$table->set('350749960@qq.com', array('id' => 358, 'name' => "Rango1234", 'num' => 3.1415));
// get column
$ret1 = $table->get('350749960@qq.com');
// delete column
$table->del('350749960@qq.com');
Notices
- You need to allocate memory size for Swoole Table beforehand.
- Swoole Table can't be expanded once the memory is allocated.
- Column key can not be greater than 64 bytes.
- Swoole Table is based on the structure of hash table, there's collision possibility.
- You can cutomize your pool size(20 % as default).
- Insufficient memory will cause an error while allocating swoole table.
Database
Connection Pooling
Async IO
Connection Pooling
Connection Pooling
<?php
$count = 0;
$pool = new SplQueue();
$server = new Swoole\Http\Server('127.0.0.1', 9501, SWOOLE_BASE);
$server->on('Request', function($request, $response) use(&$count, $pool) {
if (count($pool) == 0) {
$redis = new Swoole\Coroutine\Redis();
$res = $redis->connect('127.0.0.1', 6379);
if ($res == false) {
$response->end("redis connect fail!");
return;
}
$pool->push($redis);
$count++;
}
$redis = $pool->pop();
$ret = $redis->set('key', 'value');
$response->end("swoole response is ok, count = $count, result=" . var_export($ret, true));
$pool->push($redis);
});
$server->start();
Connection Pooling
swoole/php-cp
- A powerful local connection pool proxy server
- Implement mysql/redis database connection pool
- Provide max/min pool size setting
- Support connection waiting queue
- Support async mysql/redis query
pool_server start
pool_server stop
pool_server restart
pool_server status
Connection Pooling
<?php
// redis
$obj = new redisProxy();
$rs = $obj->connect("192.168.20.130");
$obj->select(5);
$obj->set("test", '1111');
var_dump($obj->get("test"));
$obj->release();
// pdo
$obj1 = new pdoProxy('mysql:host=192.168.20.131;dbname=db1', "admin", "admin");
$rs = $obj1->query("show tables");
var_dump($rs->fetchAll());
$obj1->release();
// aync pdo
$obj2 = new asyncPdoProxy('mysql:host=192.168.1.19;dbname=mz_db', "public_user", "1qa2ws3ed");
$stmt = $obj2->prepare("select * from mz_account where user_id=:user_id");
$stmt->bindParam(':user_id', "311");
$stmt->execute(function($stmt, $ret) {
$data = $stmt->fetchAll();
var_dump($data);
$stmt->release();
});
Speed up your
Laravel App
with Swoole
Laravel with Swoole
- scil/LaravelFly
- garveen/laravoole
- chongyi/swoole-laravel-framework
- huang-yi/swoole-laravel
Current related projects on github
- Purely run laravel with swoole server
- Restore laravel snapshot every request
Implementations
Laravel with Swoole
Test with Lumen 5.1 and
huang-yi/swoole-laravel
public function onRequest(Request $request, Response $response)
{
$illuminateRequest = IlluminateRequest::swooleCapture($request);
$illuminateResponse = $this->runApplication($illuminateRequest);
$swooleResponse = ResponseFactory::createFromIlluminate(
$response,
$illuminateResponse
);
$swooleResponse->send();
}
Laravel with Swoole
wrk -t4 -c400 -d10s http://lumen51.app:9999
Running 10s test @ http://lumen51.app:9999
4 threads and 400 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 692.27ms 147.64ms 1.51s 85.77%
Req/Sec 44.84 28.67 160.00 72.78%
1679 requests in 10.05s, 2.52MB read
Socket errors: connect 0, read 300, write 0, timeout 21
Non-2xx or 3xx responses: 41
Requests/sec: 167.04
Transfer/sec: 256.68KB
Running 10s test @ http://localhost:1215
4 threads and 400 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 22.38ms 5.88ms 116.88ms 77.94%
Req/Sec 4.18k 446.00 5.10k 74.25%
167130 requests in 10.06s, 247.37MB read
Socket errors: connect 0, read 226, write 0, timeout 0
Requests/sec: 16616.24
Transfer/sec: 24.59MB
Notices
- Most of those packages are not maintained
- Blocked IO breaks everything
- It's easier for stateless usage
- You need to reset some objects status by yourself
- It's not suitable for production usage yet
Swoole Frameworks
-
Swoole Framework
-
shenzhe/zphp
-
keaixiaou/zapi
-
hprose/hprose-php
-
bixuehujin/blink
-
swoft-cloud/swoft
-
tmtbe/SwooleDistributed
Q&A
Swoole 台灣
PHP 也有 Day # 33 - Swoole 重新定義 PHP
By Albert Chen
PHP 也有 Day # 33 - Swoole 重新定義 PHP
- 5,555