@LaravelVueConf Taiwan 2022
source code
slides
SSID: LaravelVueConfTaiwan 2022
Password: lovelaravelvue
installation
Since 2012
Open source based on Apache-2.0 License
17.6k stars on Dec. 2022
More than 130 contributors
Adopted by many enterprises in China
Laravel Octane was released in 2021
Not a new language, but an extension for PHP
Provides many new features for traditional PHP
New lifecycle in Swoole
Server patterns, TCP, UDP, Coroutine, Async I/O, Process Management, etc.
It's still under active development
Stable for production environment
High performance
v4.4.x: LTS version
v4.8.x: Stable version with new features
Followed PHP's official supported schedule
Branch | Initial Release | Active Support Until | Security Support Until |
---|---|---|---|
7.4 | 28 Nov 2019 | 28 Nov 2021 | 28 Nov 2022 |
8.0 | 26 Nov 2020 | 26 Nov 2022 | 26 Nov 2023 |
8.1 | 25 Nov 2021 | 25 Nov 2023 | 25 Nov 2024 |
(https://www.php.net/supported-versions.php)
Supported on Linux、FreeBSD、MacOS
Windows needs CygWin or WSL
Requeirements for compliation:
GCC >= 4.8
4.8: >= PHP 7.2
5.0: >= PHP 8
Compile manually
mkdir -p ~/build && \
cd ~/build && \
rm -rf ./swoole-src && \
curl -o ./tmp/swoole.tar.gz https://github.com/swoole/swoole-src/archive/master.tar.gz -L && \
tar zxvf ./tmp/swoole.tar.gz && \
mv swoole-src* swoole-src && \
cd swoole-src && \
phpize && \
./configure \
--enable-openssl \
--enable-http2 && \
make && sudo make install
Via pecl
pecl install swoole
pecl install -D 'enable-openssl="yes" enable-http2="yes" swoole
extension=swoole.so
Append extension to php.ini
Via docker
docker run --rm phpswoole/swoole "php --ri swoole"
swoole
Swoole => enabled
Author => Swoole Team <team@swoole.com>
Version => 4.8.10
Built => May 19 2022 04:26:27
coroutine => enabled with boost asm context
kqueue => enabled
rwlock => enabled
pcre => enabled
zlib => 1.2.11
brotli => E16777225/D16777225
async_redis => enabled
Directive => Local Value => Master Value
swoole.enable_coroutine => On => On
swoole.enable_library => On => On
swoole.enable_preemptive_scheduler => Off => Off
swoole.display_errors => On => On
swoole.use_shortname => On => On
swoole.unixsock_buffer_size => 262144 => 262144
swoole-cli
a standalone binary (like NodeJS)
integrated with PHP and swoole
if other extensions are needed, you need to compile it manually
php.ini is not applied by default (with -d or -c)
wget https://wenda-1252906962.file.myqcloud.com/dist/swoole-cli-v5.0.1-macos-x64.tar.xz
tar zxvf swoole-cli-v5.0.1-macos-x64.tar.xz
mv swoole-cli /usr/local/bin/composer
swoole-cli --ri swoole
(https://mp.weixin.qq.com/s/y1tvbEzNuSbSc3mID0Zv5A)
How do we serve PHP today?
PHP-FPM
mod_php for Apache
Both patterns are all stateless
Pros
Easy scaling
Simple, less risk causing memory leaks
Cons
States can't be shared between requests
States must rely on external storages
Resources can't be reused efficiently
Connection cost is expensive (like database)
Not good for high performance
FPM (FastCGI Process Manager)
SAPI: Adapters like PHP-Cli, PHP-Fpm or embeded
ZendVM: Composed with Compiler and Executor, like JVM in Java
Extension: Including PHP Extension and Zend Extension
Process Mode
Master process to manage connections
Connection won't be disconnected if worker process failed
Base Mode
Better performance
Other connections will be effected if worker process failed
Communication Between Processes
Master <==> Worker
Worker <==> Worker
Worker <==> Task Worker
Server Protocols
TCP
HTTP
Websocket
UDP
UnixSocket
Callbacks for HTTP Server
onStart
onManagerStart
onWorkerStart
onRequest
onBeforeShutdown (SISGTERM 15)
Swoole
<?php
$server = new Swoole\HTTP\Server('0.0.0.0', 9501);
$server->on('request', function ($request, $response) {
$response->end('Hello Swoole!');
});
echo "Server is starting...\n";
$server->start();
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
fmt.Println("Server is starting...")
http.HandleFunc("/", handle)
http.ListenAndServe("0.0.0.0:9501", nil)
}
func handle(rw http.ResponseWriter, r *http.Request) {
rw.Header().Set("Content-Type", "text/html")
io.WriteString(rw, "Hello Go!")
}
Go
var http = require('http');
http.createServer(function (request, response) {
response.writeHead(200, {'Content-Type': 'text/plain'});
response.end('Hello NodeJS!');
}).listen(9501);
console.log('Server is starting...');
NodeJS
Watch Processes
pstree | grep http.php
pstree | grep php
| | \-+= 70392 Albert php http.php
| | \-+- 70393 Albert php http.php
| | |--- 70394 Albert php http.php
| | |--- 70395 Albert php http.php
| | |--- 70396 Albert php http.php
| | |--- 70397 Albert php http.php
| | |--- 70398 Albert php http.php
| | |--- 70399 Albert php http.php
| | |--- 70400 Albert php http.php
| | \--- 70401 Albert php http.php
Master
Manager
Workers
Watch Threads
ps M 70392
Albert 70392 s006 0.0 S 31T 0:00.02 0:00.06 php http.php
70392 0.0 S 31T 0:00.00 0:00.00
70392 0.0 S 31T 0:00.00 0:00.00
70392 0.0 S 31T 0:00.00 0:00.00
70392 0.0 S 31T 0:00.00 0:00.00
Master
Threads
Callbacks for TCP Server
onConnect
onReceive
onClose
TCP Server
<?php
$server = new Swoole\Server('0.0.0.0', 9501, SWOOLE_PROCESS, SWOOLE_SOCK_TCP);
$server->on('connect', function ($server, $fd){
echo "Client {$fd} connected.\n";
});
$server->on('receive', function ($server, $fd, $reactorId, $data) {
if (trim($data) === 'exit') {
$server->close($fd);
return;
}
$server->send($fd, "Swoole Response: {$data}");
});
$server->on('close', function ($server, $fd) {
echo "Client {$fd} closed.\n";
});
echo "Server is starting...\n";
$server->start();
array(10) {
["server_port"]=>
int(9501)
["server_fd"]=>
int(4)
["socket_fd"]=>
int(18)
["socket_type"]=>
int(1)
["remote_port"]=>
int(58485)
["remote_ip"]=>
string(9) "127.0.0.1"
["reactor_id"]=>
int(2)
["connect_time"]=>
int(1567943830)
["last_time"]=>
int(1567943830)
["close_errno"]=>
int(0)
}
TCP Handshake
$server->set([
'heartbeat_check_interval' => 5,
'heartbeat_idle_time' => 10
]);
Heartbeat
Disconnect idle connections
Server and clients need to implement their ping-pong policy
Exercise
Implement a chatroom server based on TCP server
Support multi-users chat on the same channel
Swoole\Server->getClientList(int $start_fd = 0, int $pageSize = 10): bool|array
Websocket Protocol
Request
GET / HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Host: example.com
Origin: http://example.com
Sec-WebSocket-Key: sN9cRrP/n9NdMgdcy2VJFQ==
Sec-WebSocket-Version: 13
Response
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: fFBooB7FAkLlXgRSz0BT3v4hq5s=
Sec-WebSocket-Location: ws://example.com/
Websocket Server
<?php
$server = new Swoole\WebSocket\Server('0.0.0.0', 9501);
$server->on('open', function (Swoole\Websocket\Server $server, Swoole\Http\Request $request) {
echo "handshake succeeded with fd {$request->fd}\n";
});
$server->on('message', function (Swoole\Websocket\Server $server, Swoole\Websocket\Frame $frame) {
echo "received from {$frame->fd}:{$frame->data}, opcode:{$frame->opcode}, fin:{$frame->finish}\n";
$server->push($frame->fd, $frame->data);
});
$server->on('close', function (Swoole\Websocket\Server $server, $fd) {
echo "client {$fd} closed\n";
});
echo "Server is starting...\n";
$server->start();
Online Websocket Client
https://websocketking.com
http://coolaf.com/tool/chattest
Task Wokers
Handle jobs from workers
Workers are dispatched through unix socket, no I/O consuming
Simple message queue
Task Woker
<?php
$server->set([
'worker_num' => 2,
'task_worker_num' => 4
]);
$server->on('task', function (Swoole\Server $server, $taskId, $fromId, $data) {
// ...
});
$server->on('finish', function (Swoole\Server $server, $taskId, $data) {
// ...
});
What's the count?
<?php
$server = new Swoole\HTTP\Server('0.0.0.0', 9501);
$count = 0;
$server->on('request', function ($request, $response) use (&$count) {
$response->end($count++);
});
echo "Server is starting...\n";
$server->start();
Each worker has its own memory space
Use Swoole Table for Sharing Data
Atomic
<?php
$server = new Swoole\Http\Server('127.0.0.1', 9501);
$atomic = new Swoole\Atomic(1);
$server->on('request', function ($request, $response) use ($atomic) {
$response->end($atomic->add(1));
});
echo "Server is starting...\n";
$server->start();
Swoole Table
Based on shared memory (hash table)
Data can be shared within processes
Row lock instead of global lock while concurrent access to the same row
Extremely high performance
Table size can't be extended once allocated
Swoole Table
Initialize table
Configure column types
Create table
$table = new \Swoole\Table(1024, 0.2);
$table->column('id', \Swoole\Table::TYPE_INT, 8);
$table->column('name', \Swoole\Table::TYPE_STRING, 32);
$table->create();
Swoole Table
$table->set('a', ['id' => 1, 'name' => 'foo']);
$table->set('b', ['id' => 2, 'name' => 'bar']);
$table->get('a', 'name');
$table->exist('a');
foreach ($table as $key => $value) {
$table->del($key);
}
Swoole\Timer::tick(1000, function() {
echo "hello\n";
});
Exercise
interface CacheInterface
{
public function __construct(array $options = []);
public function getCount(): int;
public function put(string $key, string $value, int $ttl = 60): void;
public function get(string $key): ?string;
public function forget(string $key): void;
public function flush(): void;
public function recycle(): void;
}
Inter-Process Lock
Lock shared in processes
$lock = new Swoole\Lock(SWOOLE_MUTEX);
// blocking
$lock->lock();
// non-blocking
$lock->trylock();
$lock->unlock();
Simplified Matrix of Linux I/O Models
Execute PHP Code
Initialize PHP Code
Database Request
HTTP API Request
Response
Include PHP Files
According to Donald Knuth, the term coroutine was coined by Melvin Conway in 1958, after he applied it to construction of an assembly program.The first published explanation of the coroutine appeared later, in 1963. - wiki
run(function() {
go(function () {
Coroutine::sleep(2);
echo "Hello world!\n";
});
go(function () {
Coroutine::sleep(1);
echo "Swoole is awesome!\n";
});
echo "PHP is the best!\n";
});
echo "Coroutine to the moon~\n";
Runtime::enableCoroutine(false);
run(function() {
go(function () {
sleep(2);
echo "Hello world!\n";
});
go(function () {
sleep(1);
echo "Swoole is awesome!\n";
});
echo "PHP is the best!\n";
});
echo "Coroutine to the moon~\n";
run(function() {
$variable = null;
$cid = go(function () {
global $variable;
$variable = 'a';
Coroutine::yield();
echo $variable . "\n";
});
go(function () use ($cid) {
global $variable;
$variable = 'b';
Coroutine::resume($cid);
echo $variable . "\n";
});
});
run(function() {
$cid = go(function () {
$context = Coroutine::getContext();
$context['a'] = 'b';
Coroutine::yield();
var_dump($context);
});
go(function () use ($cid) {
$context = Coroutine::getContext();
$context['a'] = 'c';
Coroutine::resume($cid);
var_dump($context);
});
});
Decoupled by channel
Pub/Sub Pattern
Shared memory and lock are implemented in channel
Only one active consumer can access data
Processes are anonymous
run(function() {
$channel = new Swoole\Coroutine\Channel(1);
go(function () use ($channel) {
for ($i = 1; $i <= 5; $i++) {
echo "push {$i}\n";
$channel->push($i);
Swoole\Coroutine::sleep(1);
}
});
go(function () use ($channel) {
while ($result = $channel->pop(1.2)) {
if (! $result) {
break;
}
echo "pop {$result}\n";
}
});
});
run(function() {
$amount = 100;
$lock = new Swoole\Lock;
echo "remaining amount: {$amount}\n";
go(function () use (&$amount, $lock) {
$lock->lock();
Coroutine::sleep(1);
$amount -= 10;
$lock->unlock();
echo "remaining amount: {$amount}\n";
});
go(function () use (&$amount, $lock) {
$lock->lock();
Coroutine::sleep(1);
$amount -= 20;
$lock->unlock();
echo "remaining amount: {$amount}\n";
});
});
<?php
use function Swoole\Coroutine\run;
run(function() {
go(function () {
$a = 0;
while (true) {
$a++;
}
});
go(function () {
echo "hello world!\n";
});
});
Coroutine::set([
'enable_preemptive_scheduler' => true
]);
run(function() {
go(function () {
$a = 0;
while (true) {
$a++;
}
});
go(function () {
echo "hello world!\n";
});
});
Published in April 2021
Maintained by official Laravel team
Supports Swoole and Roadrunner
Requires Laravel 8 and PHP 8
Becoming more friendly in long-lived app
Hot reload support
Brings additional features
Built with Golang
A replacement for web server and PHP-FPM
Works on both Linux and Windows
HTTPS and HTTP/2 support (including HTTP/2 Push, H2C)
No external PHP dependencies
composer require laravel/octane
php artisan octane:install
PHP_BINARY=/usr/local/bin/swoole-cli php artisan octane:start
npm install --save-dev chokidar
php artisan octane:start --watch
php artisan octane:start --workers=4 --task-workers=6
php artisan octane:start --max-requests=250
php artisan octane:reload
php artisan octane:stop
php artisan octane:status
$this->app->singleton(Service::class, function ($app) {
return new Service($app);
});
$this->app->bind(Service::class, function ($app) {
return new Service($app);
});
$this->app->singleton(Service::class, function () {
return new Service(fn () => Container::getInstance());
});
'listeners' => [
RequestReceived::class => [
...Octane::prepareApplicationForNextOperation(),
...Octane::prepareApplicationForNextRequest(),
//
],
RequestHandled::class => [
//
],
RequestTerminated::class => [
// FlushUploadedFiles::class,
],
......
]
public static function defaultServicesToWarm(): array
{
return [
'auth','cache','cache.store','config','cookie',
'db','db.factory','db.transactions','encrypter',
'files','hash','log','router','routes','session','session.store',
'translator','url','view',
];
}
class Counter
{
public static $count = 0;
}
Counter::$count++;
use App\Service;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
public function index(Request $request)
{
Service::$data[] = Str::random(10);
// ...
}
Octane::tick('simple-ticker', fn () => ray('Ticking...'))
->seconds(10)
->immediate();
Cache::store('octane')->interval('random', function () {
return Str::random(10);
}, seconds: 5);
Octane::table('example')->set('uuid', [
'name' => 'Nuno Maduro',
'votes' => 1000,
]);
Octane::table('example')->get('uuid');
use App\User;
use App\Server;
use Laravel\Octane\Facades\Octane;
[$users, $servers] = Octane::concurrently([
fn () => User::all(),
fn () => Server::all(),
]);