Koordinatör
webman, workerman tabanlı geliştirilmiştir, bu nedenle webman, workerman'ın kooperatif özelliklerini kullanabilir.
Koordinatör, Swoole
, Swow
ve Fiber
olmak üzere üç farklı sürümü destekler.
Gereksinimler
- PHP >= 8.1
- Workerman >= 5.1.0 (
composer require workerman/workerman ~v5.1
) - webman-framework >= 2.1 (
composer require workerman/webman-framework ~v2.1
) - swoole veya swow uzantısı kurulu olmalıdır, ya da
composer require revolt/event-loop
(Fiber) yüklenmelidir. - Koordinatör varsayılan olarak kapalıdır, eventLoop'un açık şekilde ayarlanması gerekir.
Açma Yöntemi
webman, farklı süreçler için farklı sürümleri açmayı destekler, bu yüzden config/process.php
dosyasında eventLoop
üzerinden koordinatör sürümünü yapılandırabilirsiniz:
return [
'webman' => [
'handler' => Http::class,
'listen' => 'http://0.0.0.0:8787',
'count' => 1,
'user' => '',
'group' => '',
'reusePort' => false,
'eventLoop' => '', // Varsayılan olarak boş, Select veya Event otomatik olarak seçilir, koordinatörü açmaz
'context' => [],
'constructor' => [
'requestClass' => Request::class,
'logger' => Log::channel('default'),
'appPath' => app_path(),
'publicPath' => public_path()
]
],
'my-coroutine' => [
'handler' => Http::class,
'listen' => 'http://0.0.0.0:8686',
'count' => 1,
'user' => '',
'group' => '',
'reusePort' => false,
// Koordinatörü açmak için Workerman\Events\Swoole::class veya Workerman\Events\Swow::class veya Workerman\Events\Fiber::class olarak ayarlayın
'eventLoop' => Workerman\Events\Swoole::class,
'context' => [],
'constructor' => [
'requestClass' => Request::class,
'logger' => Log::channel('default'),
'appPath' => app_path(),
'publicPath' => public_path()
]
]
// ... Diğer yapılandırmalar atlandı ...
];
İpucu
webman, farklı süreçler için farklı eventLoop ayarlayabilir, bu da belirli süreçler için seçerek koordinatör açmanızı sağlar.
Örneğin, yukarıdaki yapılandırmada 8787 portundaki hizmet koordinatörü açmamışken, 8686 portundaki hizmeti açmıştır, nginx ile yönlendirme ile koordinatör ve non-koordinatör karışık olarak dağıtım yapılabilir.
Koordinatör Örneği
<?php
namespace app\controller;
use support\Response;
use Workerman\Coroutine;
use Workerman\Timer;
class IndexController
{
public function index(): Response
{
Coroutine::create(function(){
Timer::sleep(1.5);
echo "hello coroutine\n";
});
return response('hello webman');
}
}
eventLoop
Swoole
, Swow
, Fiber
olduğunda, webman her istek için bir koordinatör oluşturur, istek işlenirken yeni koordinatörler oluşturmaya devam edebilir.
Koordinatör Kısıtlamaları
- Swoole veya Swow sürümü kullanıldığında, iş süreçleri bir blokaj IO ile karşılaştığında koordinatör otomatik olarak geçiş yapar, senkronize kodun asenkron çalışmasını sağlar.
- Fiber sürümü kullanıldığında, blokaj IO ile karşılaşıldığında koordinatör geçiş yapmaz, süreç blokaj durumuna girebilir.
- Koordinatör kullanırken, aynı kaynağa, örneğin veritabanı bağlantısı, dosya işlemleri vb. gibi birden çok koordinatör aynı anda işlem yapamaz; bu kaynak rekabetine neden olabilir, doğru kullanım, bağlantı havuzunu veya kilidi kullanarak kaynakları korumaktır.
- Koordinatör kullanırken, istek ile ilgili durum verilerini küresel değişkenlerde veya statik değişkenlerde saklayamazsınız; bu, küresel veri kirliliğine neden olabilir, doğru kullanım, koordinatör bağlamı
context
kullanarak depolamak veya erişmektir.
Diğer Dikkat Edilmesi Gerekenler
Swow, alt kısımda PHP'nin blokaj fonksiyonlarını otomatik olarak hook'lar, ancak bu hook, PHP'nin bazı varsayılan davranışlarını etkileyebileceğinden, Swow kullanılmadığında ve Swow yüklenmiş olduğunda bug'lara neden olabilir.
Bu nedenle önerilir:
- Projeniz Swow kullanmıyorsa, lütfen Swow uzantısını yüklemeyin.
- Projeniz Swow kullanıyorsa,
eventLoop
değeriniWorkerman\Events\Swow::class
olarak ayarlayın.
Koordinatör Bağlamı
Koordinatör ortamında, istek ile ilgili durum bilgilerini küresel değişkenlerde veya statik değişkenlerde saklamak yasaktır, çünkü bu küresel değişken kirliliğine yol açabilir, örneğin
<?php
namespace app\controller;
use support\Request;
use Workerman\Timer;
class TestController
{
protected static $name = '';
public function index(Request $request)
{
static::$name = $request->get('name');
Timer::sleep(5);
return static::$name;
}
}
Dikkat
Koordinatör ortamında küresel değişkenler veya statik değişkenlerin kullanımı yasak değildir, ancak istekle ilgili durum verilerini küresel değişkenler veya statik değişkenlerde saklamak yasaktır.
Örneğin, küresel yapılandırma, veritabanı bağlantısı, bazı sınıfların tekil örnekleri gibi küresel olarak paylaşılması gereken nesne verileri küresel değişkenler veya statik değişkenler içinde saklanması önerilir.
Süreç sayısını 1 olarak ayarladığımızda, ardışık iki istek gönderdiğimizde
http://127.0.0.1:8787/test?name=lilei
http://127.0.0.1:8787/test?name=hanmeimei
Her iki isteğin dönüş değerlerini sırasıyla lilei
ve hanmeimei
olarak almayı bekleriz, ancak aslında her ikisi de hanmeimei
dönüş değeri alır.
Bu, ikinci isteğin statik değişken $name
değerini ezmesinden kaynaklanmaktadır, birinci isteğin uyku süresi sona erdiğinde dönüşte statik değişken $name
zaten hanmeimei
olmuştur.
Doğru yöntem ise, istek durum verilerini context içinde saklamaktır.
<?php
namespace app\controller;
use support\Request;
use support\Context;
use Workerman\Timer;
class TestController
{
public function index(Request $request)
{
Context::set('name', $request->get('name'));
Timer::sleep(5);
return Context::get('name');
}
}
support\Context
sınıfı koordinatör bağlam verilerini depolamak için kullanılır, koordinatör işlemi tamamlandığında ilgili context verileri otomatik olarak silinir.
Koordinatör ortamında her istek ayrı bir koordinatör olduğundan, istek tamamlandığında context verileri otomatik olarak yok edilir.
Koordinatör olmayan ortamda, context istek sona erdiğinde otomatik olarak yok olacaktır.
Yerel değişkenler veri kirliliğine neden olmaz.
<?php
namespace app\controller;
use support\Request;
use support\Context;
use Workerman\Timer;
class TestController
{
public function index(Request $request)
{
$name = $request->get('name');
Timer::sleep(5);
return $name;
}
}
Çünkü $name
yerel bir değişkendir, koordinatörler arasında yerel değişkenlere erişim yoktur, bu nedenle yerel değişkenlerin kullanımı koordinatör açısından güvenlidir.
Locker Kilidi
Bazen bazı bileşenler veya iş süreçleri koordinatör ortamını dikkate almaz, bu durumda kaynak rekabeti veya atomiklik sorunları ortaya çıkabilir, bu durumda işlem sırası sağlamak ve eşzamanlılık sorunlarını önlemek için Workerman\Locker
ile kilitleme yapılabilir.
<?php
namespace app\controller;
use Redis;
use support\Response;
use Workerman\Coroutine\Locker;
class IndexController
{
public function index(): Response
{
static $redis;
if (!$redis) {
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
}
// Kilit eklenmediğinde, Swoole altında "Socket#10 has already been bound to another coroutine#10" hatasına neden olabilir
// Swow altında coredump tetikleyebilir
// Fiber altında Redis uzantısı senkron blokaj IO olduğu için sorun çıkmayacaktır
Locker::lock('redis');
$time = $redis->time();
Locker::unlock('redis');
return json($time);
}
}
Paralel Eşzamanlı Çalışma
Birden fazla görevi eşzamanlı yürütmek ve sonuç almak istediğimizde, Workerman\Parallel
kullanabiliriz.
<?php
namespace app\controller;
use support\Response;
use Workerman\Coroutine\Parallel;
class IndexController
{
public function index(): Response
{
$parallel = new Parallel();
for ($i=1; $i<5; $i++) {
$parallel->add(function () use ($i) {
// Bir şey yap
return $i;
});
}
$results = $parallel->wait();
return json($results); // Yanıt: [1,2,3,4]
}
}
Havuz Bağlantı Havuzu
Birden fazla koordinatör aynı bağlantıyı paylaştığında veri karışıklığına neden olabileceğinden, veritabanı, redis gibi bağlantı kaynaklarını yönetmek için bağlantı havuzu kullanılması gerekir.
webman, webman/database, webman/redis, webman/cache, webman/think-orm, webman/think-cache gibi bileşenleri sağlayarak, bu bileşenler hem koordinatör hem de koordinatör olmayan ortamda kullanılabilen bağlantı havuzunu entegre eder.
Bağlantı havuzu olmayan bir bileşeni değiştirmek isterseniz, Workerman\Pool
kullanarak bunu gerçekleştirebilirsiniz, aşağıdaki kod örneğine bakınız.
Veritabanı Bileşeni
<?php
namespace app;
use Workerman\Coroutine\Context;
use Workerman\Coroutine;
use Workerman\Coroutine\Pool;
class Db
{
private static ?Pool $pool = null;
public static function __callStatic($name, $arguments)
{
if (self::$pool === null) {
self::initializePool();
}
// Koordinatör bağlamından bağlantıyı al, aynı koordinatörün aynı bağlantıyı kullandığından emin ol
$pdo = Context::get('pdo');
if (!$pdo) {
// Bağlantı havuzundan bağlantıyı al
$pdo = self::$pool->get();
Context::set('pdo', $pdo);
// Koordinatör sonlandığında bağlantı otomatik olarak iade edilir
Coroutine::defer(function () use ($pdo) {
self::$pool->put($pdo);
});
}
return call_user_func_array([$pdo, $name], $arguments);
}
private static function initializePool(): void
{
// Bağlantı havuzu oluştur, maksimum bağlantı sayısı 10
self::$pool = new Pool(10);
// Bağlantı oluşturucu ayarla (kısalık için yapılandırma dosyası okuma atlandı)
self::$pool->setConnectionCreator(function () {
return new \PDO('mysql:host=127.0.0.1;dbname=your_database', 'your_username', 'your_password');
});
// Bağlantı kapatıcı ayarla
self::$pool->setConnectionCloser(function ($pdo) {
$pdo = null;
});
// Kalp atışı kontrol cihazı ayarla
self::$pool->setHeartbeatChecker(function ($pdo) {
$pdo->query('SELECT 1');
});
}
}
Kullanım
<?php
namespace app\controller;
use support\Response;
use app\Db;
class IndexController
{
public function index(): Response
{
$value = Db::query('SELECT NOW() as now')->fetchAll();
return json($value); // [{"now":"2025-02-06 23:41:03","0":"2025-02-06 23:41:03"}]
}
}
Daha Fazla Koordinatör ve İlgili Bileşenler Hakkında Bilgi
Daha fazla bilgi için workerman koordinatör belgelerine bakabilirsiniz.
Koordinatör ve Non-Koordinatör Karışık Dağıtım
webman, koordinatör ve non-koordinatör karışık dağılmayı destekler, örneğin non-koordinatör normal işleyişi işlerken, koordinatör yavaş IO iş akışını işler, nginx ile farklı hizmetlere yönlendirme yapılabilir.
Örneğin config/process.php
return [
'webman' => [
'handler' => Http::class,
'listen' => 'http://0.0.0.0:8787',
'count' => 1,
'user' => '',
'group' => '',
'reusePort' => false,
'eventLoop' => '', // Varsayılan olarak boş, Select veya Event otomatik olarak seçilir, koordinatörü açmaz
'context' => [],
'constructor' => [
'requestClass' => Request::class,
'logger' => Log::channel('default'),
'appPath' => app_path(),
'publicPath' => public_path()
]
],
'my-coroutine' => [
'handler' => Http::class,
'listen' => 'http://0.0.0.0:8686',
'count' => 1,
'user' => '',
'group' => '',
'reusePort' => false,
// Koordinatörü açmak için Workerman\Events\Swoole::class veya Workerman\Events\Swow::class veya Workerman\Events\Fiber::class olarak ayarlayın
'eventLoop' => Workerman\Events\Swoole::class,
'context' => [],
'constructor' => [
'requestClass' => Request::class,
'logger' => Log::channel('default'),
'appPath' => app_path(),
'publicPath' => public_path()
]
],
// ... Diğer yapılandırmalar atlandı ...
];
Daha sonra nginx yapılandırmasını kullanarak talepleri farklı hizmetlere yönlendirin.
upstream webman {
server 127.0.0.1:8787;
keepalive 10240;
}
# 8686 için yeni bir upstream ekleyin
upstream task {
server 127.0.0.1:8686;
keepalive 10240;
}
server {
server_name webman.com;
listen 80;
access_log off;
root /path/webman/public;
# /tast ile başlayan talepler 8686 portuna gider, lütfen ihtiyaçlarınıza göre /tast'ı değiştirin
location /tast {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_pass http://task;
}
# Diğer talepler ise orijinal 8787 portuna gider
location / {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_http_version 1.1;
proxy_set_header Connection "";
if (!-f $request_filename){
proxy_pass http://webman;
}
}
}