কোঅর্টিন

webman হলো workerman-এর ভিত্তিতে তৈরি, তাই webman workerman-এর কোঅর্টিন বৈশিষ্ট্য ব্যবহার করতে পারে।
কোঅর্টিন 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' => '', // ডিফল্টভাবে খালি সিলেক্ট বা ইভেন্ট নির্বাচন করা হয়, কোঅর্টিন চালু করা হয় না
        '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 হ্যান্ডেলিংয়ের তলায় পিএইচপির ব্লকিং ফাংশনগুলো স্বয়ংক্রিয়ভাবে হুক করবে, তবে এই হুক কিছু ডিফল্ট পিএইচপি আচরণকে প্রভাবিত করে, তাই আপনি যদি 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 ক্লাস কোঅর্টিন কনটেক্সট ডেটা সংরক্ষণ করার জন্য ব্যবহৃত হয়, যখন কোঅর্টিন সম্পন্ন হয়, ম্যাচিং কনটেক্সট ডেটা স্বয়ংক্রিয়ভাবে মুছে ফেলা হয়।
কোঅর্টিন পরিবেশে, প্রতিটি অনুরোধ আলাদাভাবে কোঅর্টিন, তাই অনুরোধ শেষ হলে কনটেক্সট ডেটা স্বয়ংক্রিয়ভাবে ধ্বংস হবে।
নন-কোঅর্টিন পরিবেশে, কনটেক্সট অনুরোধ শেষ হওয়ার সময় স্বয়ংক্রিয়ভাবে ধ্বংস হবে।

স্থানীয় ভেরিয়েবল ডেটা দূষণ সৃষ্টি করবে না

<?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); // Response: [1,2,3,4]
    }

}

Pool সংযোগ পুল

একাধিক কোঅর্টিন একই সংযোগ শেয়ার করলে ডেটা বিশৃঙ্খলা সৃষ্টি করতে পারে, তাই আমাদের ডেটাবেজ, redis ইত্যাদির সংযোগ সম্পদ পরিচালনার জন্য সংযোগ পুল ব্যবহার করতে হবে।

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' => '', // ডিফল্টভাবে খালি সিলেক্ট বা ইভেন্ট নির্বাচন করা হয়, কোঅর্টিন চালু করা হয় না
        '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;
      }
  }
}