सहयोगी कार्य
webman कार्यकर्ता पर आधारित है, इसलिए webman कार्यकर्ता की सहयोगी कार्य सुविधाएँ का उपयोग कर सकता है।
सहयोगी कार्य Swoole
Swow
और Fiber
तीन प्रकार के ड्राइवरों का समर्थन करता है।
पूर्व शर्तें
- 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 या swow एक्सटेंशन स्थापित किया गया है, या
composer require revolt/event-loop
(Fiber) स्थापित किया गया है - सहयोगी कार्य डिफ़ॉल्ट रूप से बंद है, इसे eventLoop को सक्रिय करने के लिए अलग से सेट करना होगा।
सक्रिय करने की विधि
webman विभिन्न प्रक्रियाओं के लिए विभिन्न ड्राइवर संचार स्थापित कर सकता है, इसलिए आप config/process.php
में eventLoop
के माध्यम से सहयोगी कार्य ड्राइवर कॉन्फ़िगर कर सकते हैं:
return [
'webman' => [
'handler' => Http::class,
'listen' => 'http://0.0.0.0:8787',
'count' => 1,
'user' => '',
'group' => '',
'reusePort' => false,
'eventLoop' => '', // डिफ़ॉल्ट रूप से खाली, Select या Event का स्वचालित चयन, सहयोगी कार्य को सक्रिय नहीं किया गया है
'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,
// सहयोगी कार्य को सक्रिय करने के लिए इसे Workerman\Events\Swoole::class या Workerman\Events\Swow::class या Workerman\Events\Fiber::class पर सेट करना आवश्यक है
'eventLoop' => Workerman\Events\Swoole::class,
'context' => [],
'constructor' => [
'requestClass' => Request::class,
'logger' => Log::channel('default'),
'appPath' => app_path(),
'publicPath' => public_path()
]
]
// ... अन्य कॉन्फ़िगरेशन छोड़ दिए गए हैं ...
];
नोटिस
webman विभिन्न प्रक्रियाओं के लिए विभिन्न eventLoop सेट कर सकता है, जिसका अर्थ है कि आप विशिष्ट प्रक्रियाओं के लिए सहयोगी कार्य को सक्रिय करने का चयन कर सकते हैं।
उदाहरण के लिए, ऊपर दिए गए कॉन्फ़िगरेशन में 8787 पोर्ट की सेवा में सहयोगी कार्य सक्रिय नहीं है, जबकि 8686 पोर्ट की सेवा में सहयोगी कार्य सक्रिय है, और nginx रीडायरेक्ट का उपयोग करके सहयोगी और गैर-सहयोगी डिप्लॉयमेंट को प्राप्त किया जा सकता है।
सहयोगी कार्य उदाहरण
<?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
होता है, तो webman प्रत्येक अनुरोध के लिए एक सहयोगी कार्य बनाएगा, ताकि अनुरोध को संसाधित करते समय नए सहयोगी कार्यों को बनाने के लिए व्यवसाय कोड को निष्पादित किया जा सके।
सहयोगी कार्य सीमाएँ
- जब Swoole Swow को ड्राइवर के रूप में उपयोग किया जाता है, तो व्यवसाय अवरोधक IO का सामना करने पर सहयोगी कार्य ऑटोमेटिक रूप से स्विच होते हैं, जो समकालीन कोड को असमय निष्पादन करने में सक्षम बनाता है।
- जब Fiber ड्राइवर का उपयोग किया जाता है, तब अवरोधक IO का सामना करते समय सहयोगी कार्य नहीं होते हैं, और प्रक्रिया अवरुद्ध स्थिति में चली जाती है।
- सहयोगी कार्य का उपयोग करते समय, किसी एक संसाधन पर कई सहयोगी कार्यों का एक साथ संचालन नहीं किया जा सकता है, जैसे डेटाबेस कनेक्शन, फ़ाइल संचालन आदि, इससे संसाधन प्रतिस्पर्धा हो सकती है; सही तरीका यह है कि संसाधनों की सुरक्षा के लिए कनेक्शन पूल या लॉक का उपयोग किया जाए।
- सहयोगी कार्य का उपयोग करते समय, अनुरोध से संबंधित स्थिति डेटा को वैश्विक चर या स्थैतिक चर में संग्रहीत नहीं किया जा सकता है, इससे वैश्विक डेटा प्रदूषण हो सकता है; सही तरीका यह है कि उन्हें सहयोगी कार्य संदर्भ
context
का उपयोग करके संग्रहीत और पुनर्प्राप्त किया जाए।
अन्य ध्यान देने योग्य बातें
Swow का निचला स्तर PHP के अवरोधक कार्यों को स्वचालित रूप से हुक करता है, लेकिन इस प्रकार का हुक PHP के कुछ डिफ़ॉल्ट व्यवहार को प्रभावित कर सकता है, इसलिए यदि आप Swow का उपयोग नहीं कर रहे हैं लेकिन Swow स्थापित है तो यह बग उत्पन्न कर सकता है।
इसलिए सुझाव दिया गया है:
- यदि आपके प्रोजेक्ट में Swow का उपयोग नहीं किया जा रहा है, तो कृपया Swow एक्सटेंशन स्थापित न करें।
- यदि आपके प्रोजेक्ट में Swow का उपयोग किया गया है, तो कृपया
eventLoop
कोWorkerman\Events\Swow::class
पर सेट करें।
सहयोगी कार्य संदर्भ
सहयोगी कार्य वातावरण में अनुरोध से संबंधित स्थिति जानकारी को वैश्विक चर या स्थैतिक चर में संग्रहीत करने की अनुमति नहीं है, क्योंकि इससे वैश्विक चर प्रदूषण हो सकता है। उदाहरण के लिए
<?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;
}
}
नोटिस
सहयोगी कार्य वातावरण में वैश्विक चर या स्थैतिक चर का उपयोग करने पर कोई प्रतिबंध नहीं है, लेकिन वैश्विक चर या स्थैतिक चर में अनुरोध से संबंधित स्थिति डेटा को संग्रहीत करने पर प्रतिबंध है।
उदाहरण के लिए, वैश्विक कॉन्फ़िगरेशन, डेटाबेस कनेक्शन, कुछ कक्षाओं के सिंगलेट्स आदि, जिन्हें वैश्विक रूप से साझा करने की आवश्यकता होती है, को वैश्विक चर या स्थैतिक चर में संग्रहीत करने की अनुशंसा की जाती है।
जब हम 1 की प्रक्रिया की संख्या सेट करते हैं और लगातार दो अनुरोध भेजते हैं
http://127.0.0.1:8787/test?name=lilei
http://127.0.0.1:8787/test?name=hanmeimei
हम अपेक्षा करते हैं कि दो अनुरोधों का परिणाम क्रमशः lilei
और hanmeimei
होगा, लेकिन वास्तव में, दोनों का परिणाम hanmeimei
होता है।
यह इस वजह से है कि दूसरा अनुरोध स्थैतिक चर $name
को ओवरराइट करता है, पहले अनुरोध की नींद समाप्त होने के बाद $name
स्थैतिक चर पहले से ही hanmeimei
हो गया है।
सही तरीका है कि अनुरोध स्थिति डेटा को context में संग्रहीत किया जाए
<?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
वर्ग सहयोगी कार्य संदर्भ डेटा को संग्रहीत करने के लिए उपयोग किया जाता है, जब सहयोगी कार्य समाप्त होता है, तो संबंधित context डेटा स्वचालित रूप से हटा दिया जाएगा।
सहयोगी कार्य वातावरण में, क्योंकि प्रत्येक अनुरोध एक अलग सहयोगी कार्य होता है, अतः अनुरोध पूरा होने पर context डेटा स्वचालित रूप से नष्ट हो जाएगा।
गैर-सहयोगी कार्य वातावरण में, request के समाप्त होने पर context स्वचालित रूप से नष्ट हो जाएगा।
स्थानीय चर डेटा प्रदूषण का कारण नहीं बनते
<?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;
}
}
क्योंकि $name
एक स्थानीय चर है, सहयोगी कार्यों के बीच स्थानीय चर साझा नहीं किया जा सकता, इसलिए स्थानीय चर का उपयोग सहयोगी रूप से सुरक्षित है।
लॉक Locker
कभी-कभी कुछ घटक या व्यवसाय सहयोगी कार्य वातावरण का विचार नहीं करते हैं, जिससे संसाधन प्रतिस्पर्धा या एटॉमिकिटी समस्या उत्पन्न हो सकती है, तब आप Workerman\Locker
को लॉक करके कतार में संसाधनों को संसाधित कर सकते हैं, ताकि प्रतियोगिता समस्याओं को रोका जा सके।
<?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);
}
// यदि लॉक नहीं लगाया गया, Swoole के तहत "Socket#10 has already been bound to another coroutine#10" जैसी त्रुटि उत्पन्न होगी
// Swow के अंतर्गत coredump उत्पन्न हो सकता है
// Fiber में Redis एक्सटेंशन समकालिक अवरुद्ध IO है, इसलिए समस्याएँ नहीं होंगी
Locker::lock('redis');
$time = $redis->time();
Locker::unlock('redis');
return json($time);
}
}
समानांतर Parallel निष्पादन
जब हम कई कार्यों को समानांतर में निष्पादित करने और परिणाम प्राप्त करने की आवश्यकता होती है, तो आप Workerman\Parallel
का उपयोग कर सकते हैं।
<?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) {
// कुछ करना
return $i;
});
}
$results = $parallel->wait();
return json($results); // प्रतिक्रिया: [1,2,3,4]
}
}
पूल Connection Pool
कई सहयोगी कार्यों के एक ही कनेक्शन का उपयोग करने से डेटा भ्रमित हो सकता है, इसलिए डेटाबेस, रेडिस आदि कनेक्शन संसाधनों का प्रबंधन करने के लिए कनेक्शन पूल का उपयोग करना आवश्यक है।
webman ने पहले ही webman/database webman/redis webman/cache webman/think-orm webman/think-cache जैसे घटक प्रदान किए हैं, जो सभी कनेक्शन पूल को एकीकृत करते हैं, और सहयोगी और गैर-सहयोगी कार्य वातावरण में उपयोग का समर्थन करते हैं।
यदि आप कनेक्शन पूल के बिना किसी घटक को संशोधित करना चाहते हैं, तो आप Workerman\Pool
का उपयोग कर सकते हैं, जैसे नीचे दिए गए कोड के संदर्भ में।
डेटाबेस घटक
<?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();
}
// सहयोगी कार्य संदर्भ से कनेक्शन प्राप्त करें, यह सुनिश्चित करें कि एक ही सहयोगी कार्य एक ही कनेक्शन का उपयोग कर रहा है
$pdo = Context::get('pdo');
if (!$pdo) {
// कनेक्शन पूल से कनेक्शन प्राप्त करें
$pdo = self::$pool->get();
Context::set('pdo', $pdo);
// जब सहयोगी कार्य समाप्त होता है, तो कनेक्शन स्वचालित रूप से वापस किया जाएगा
Coroutine::defer(function () use ($pdo) {
self::$pool->put($pdo);
});
}
return call_user_func_array([$pdo, $name], $arguments);
}
private static function initializePool(): void
{
// एक कनेक्शन पूल बनाएं, अधिकतम कनेक्शन संख्या 10 है
self::$pool = new Pool(10);
// कनेक्शन निर्माता सेट करें (सरलता के लिए, कॉन्फ़िगरेशन फ़ाइल पढ़ना छोड़ दिया गया है)
self::$pool->setConnectionCreator(function () {
return new \PDO('mysql:host=127.0.0.1;dbname=your_database', 'your_username', 'your_password');
});
// कनेक्शन क्लोजर सेट करें
self::$pool->setConnectionCloser(function ($pdo) {
$pdo = null;
});
// हृदय की जाँच करने वाला सेट करें
self::$pool->setHeartbeatChecker(function ($pdo) {
$pdo->query('SELECT 1');
});
}
}
उपयोग
<?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"}]
}
}
अधिक सहयोगी कार्य और संबंधित घटकों का परिचय
संदर्भित करें workerman सहयोगी कार्य दस्तावेज़
सहयोगी कार्य और गैर-सहयोगी कार्य मिश्रण परिनियोजन
webman सहयोगी कार्य और गैर-सहयोगी कार्य मिश्रण परिनियोजन का समर्थन करता है, जैसे कि सामान्य व्यवसाय को गैर-सहयोगी रूप से संसाधित करना और धीमी IO व्यवसाय को सहयोगी कार्य के रूप में संसाधित करना, अनुरोधों को विभिन्न सेवाओं पर भेजने के लिए nginx का उपयोग करना।
उदाहरण के लिए config/process.php
return [
'webman' => [
'handler' => Http::class,
'listen' => 'http://0.0.0.0:8787',
'count' => 1,
'user' => '',
'group' => '',
'reusePort' => false,
'eventLoop' => '', // डिफ़ॉल्ट रूप से खाली, Select या Event का स्वचालित चयन, सहयोगी कार्य को सक्रिय नहीं किया गया है
'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,
// सहयोगी कार्य को सक्रिय करने के लिए इसे Workerman\Events\Swoole::class या Workerman\Events\Swow::class या Workerman\Events\Fiber::class पर सेट करना आवश्यक है
'eventLoop' => Workerman\Events\Swoole::class,
'context' => [],
'constructor' => [
'requestClass' => Request::class,
'logger' => Log::channel('default'),
'appPath' => app_path(),
'publicPath' => public_path()
]
],
// ... अन्य कॉन्फ़िगरेशन छोड़ दिए गए हैं ...
];
फिर nginx कॉन्फ़िगरेशन के माध्यम से विभिन्न सेवाओं पर अनुरोधों को रीडायरेक्ट करें
upstream webman {
server 127.0.0.1:8787;
keepalive 10240;
}
# एक नया 8686 upstream जोड़ें
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 से शुरू होने वाले अनुरोध 8686 पोर्ट पर जाएं, कृपया वास्तविक स्थिति में /tast को उस पूर्ववर्ती में बदलें जिसकी आपको आवश्यकता है
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;
}
# अन्य अनुरोध मूल 8787 पोर्ट पर जाते हैं
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;
}
}
}