easyswoole - 基于swoole扩展实现的一款高性能php框架

Actor

提供Actor模式支持,助力游戲行業(yè)開發(fā)。EasySwooleActor采用自定義Process作為存儲載體,以協(xié)程作為最小調(diào)度單位,利用協(xié)程Channelmail box,而客戶端與Process之間的通訊,采用UnixSocket實現(xiàn),并且借助TCP實現(xiàn)分布式的ActorClient,超高并發(fā)下也能輕松應(yīng)對。

工作流程

一般來說有兩種策略用來在并發(fā)線程中進(jìn)行通信:共享數(shù)據(jù)和消息傳遞。使用共享數(shù)據(jù)方式的并發(fā)編程面臨的最大的一個問題就是數(shù)據(jù)條件競爭,當(dāng)兩個實例需要訪問同一個數(shù)據(jù)時,為了保證數(shù)據(jù)的一致性,通常需要為數(shù)據(jù)加鎖,而Actor模型采用消息傳遞機制來避免數(shù)據(jù)競爭,無需復(fù)雜的加鎖操作,各個實例只需要關(guān)注自身的狀態(tài)以及處理收到的消息。

Actor是完全面向?qū)ο蟆o鎖、異步、實例隔離、分布式的并發(fā)開發(fā)模式。Actor實例之間互相隔離,Actor實例擁有自己獨立的狀態(tài),各個Actor之間不能直接訪問對方的狀態(tài),需要通過消息投遞機制來通知對方改變狀態(tài)。由于每個實例的狀態(tài)是獨立的,沒有數(shù)據(jù)被共享,所以不會發(fā)生數(shù)據(jù)競爭,從而避免了并發(fā)下的加鎖問題。

舉一個游戲場景的例子,在一個游戲房間中,有5個玩家,每個玩家都是一個PlayerActor,擁有自己的屬性,比如角色I(xiàn)D,昵稱,當(dāng)前血量,攻擊力等。游戲房間本身也是一個RoomActor,房間也擁有屬性,比如當(dāng)前在線的玩家,當(dāng)前場景的怪物數(shù)量,怪物血量等。此時玩家A攻擊某個怪物,則PlayerActor-ARoomActor發(fā)送一個攻擊怪物的指令,RoomActor經(jīng)過計算,得出玩家A對怪物的傷害值,并給房間內(nèi)的所有PlayerActor發(fā)送一個消息(玩家A攻擊怪物A,造成175點傷害,怪物A剩余血量1200點),類似此過程,每個PlayerActor都可以得知房間內(nèi)發(fā)生了什么事情,但又不會造成同時訪問怪物A的屬性,導(dǎo)致的共享加鎖問題。

安裝

Actor并沒有作為內(nèi)置組件,需要先引入包并進(jìn)行基礎(chǔ)配置才能夠使用。

composer require easyswoole/actor

使用

建立一個Actor

每一種對象(玩家、房間、甚至是日志服務(wù)也可以作為一種Actor對象)都建立一個Actor來進(jìn)行管理,一個對象可以擁有多個實例(Client)并且可以互相通過信箱發(fā)送消息來處理業(yè)務(wù)。

<?php

namespace App\Player;

use EasySwoole\Actor\AbstractActor;
use EasySwoole\Actor\ActorConfig;

/**
 * 玩家Actor
 * Class PlayerActor
 * @package App\Player
 */
class PlayerActor extends AbstractActor
{
    /**
     * 配置當(dāng)前的Actor
     * @param ActorConfig $actorConfig
     */
    public static function configure(ActorConfig $actorConfig)
    {
        $actorConfig->setActorName('PlayerActor');
        $actorConfig->setWorkerNum(3);
    }

    /**
     * Actor首次啟動時
     */
    protected function onStart()
    {
        $actorId = $this->actorId();
        echo "Player Actor {$actorId} onStart\n";
    }

    /**
     * Actor收到消息時
     * @param $msg
     */
    protected function onMessage($msg)
    {
        $actorId = $this->actorId();
        echo "Player Actor {$actorId} onMessage\n";
    }

    /**
     * Actor即將退出前
     * @param $arg
     */
    protected function onExit($arg)
    {
        $actorId = $this->actorId();
        echo "Player Actor {$actorId} onExit\n";
    }

    /**
     * Actor發(fā)生異常時
     * @param \Throwable $throwable
     */
    protected function onException(\Throwable $throwable)
    {
        $actorId = $this->actorId();
        echo "Player Actor {$actorId} onException\n";
    }

}

注冊Actor服務(wù)

可以使用setListenAddresssetListenPort指定本機對外監(jiān)聽的端口,其他機器可以通過該端口向本機的Actor發(fā)送消息。


public static function mainServerCreate(EventRegister $register) {

    // 注冊Actor管理器
    $server = \EasySwoole\EasySwoole\ServerManager::getInstance()->getSwooleServer();
    \EasySwoole\Actor\Actor::getInstance()->register(PlayerActor::class);
    \EasySwoole\Actor\Actor::getInstance()->setTempDir(EASYSWOOLE_TEMP_DIR)
        ->setListenAddress('0.0.0.0')->setListenPort('9900')->attachServer($server);

}

Actor實例管理

服務(wù)啟動后就可以進(jìn)行Actor的操作,管理本機的Client實例,則不需要給client傳入$node參數(shù),默認(rèn)的node為本機,管理其他機器時需要傳入。


    // 管理本機的Actor則不需要聲明節(jié)點
    $node = new \EasySwoole\Actor\ActorNode();
    $node->setIp('127.0.0.1');
    $node->setListenPort(9900);

    // 啟動一個Actor并得到ActorId 后續(xù)操作需要依賴ActorId
    $actorId = PlayerActor::client($node)->create(['time' => time()]);   // 00101000000000000000001
    // 給某個Actor發(fā)消息
    PlayerActor::client($node)->send($actorId, ['data' => 'data']);
    // 給該類型的全部Actor發(fā)消息
    PlayerActor::client($node)->sendAll(['data' => 'data']);
    // 退出某個Actor
    PlayerActor::client($node)->exit($actorId, ['arg' => 'arg']);
    // 退出全部Actor
    PlayerActor::client($node)->exitAll(['arg' => 'arg']);

架構(gòu)解讀

Actor

應(yīng)該叫ActorManager更確切點,它用來注冊Actor啟動ProxyActorWorker進(jìn)程。

當(dāng)你在業(yè)務(wù)邏輯里定義了幾種Actor,比如RoomActorPlayerActor,需要在SwooleServer啟動時注冊它們。

具體就是在EasySwooleEvent.mainServerCreate方法中添加如下代碼。

$actor = Actor::getInstance();
$actor->register(RoomActor::class);
$actor->register(PlayerActor::class);
$actorConf = Config::getInstance()->getConf('ACTOR_SERVER');
$actor->setMachineId($actorConf['MACHINE_ID'])
    ->setListenAddress($actorConf['LISTEN_ADDRESS'])
    ->setListenPort($actorConf['PORT'])
    ->attachServer($server);

其中ListenAddressListenPortProxy進(jìn)程的監(jiān)聽地址端口,MachineIdActorWorker進(jìn)程的機器碼。

MachineIdIP:PORT對應(yīng)。

attachServer將開啟相應(yīng)數(shù)量的Proxy進(jìn)程,以及前邊registerActorWorker進(jìn)程。

工作原理

Proxy進(jìn)程做消息中轉(zhuǎn),Worker進(jìn)程做消息分發(fā)推送。來看個具體的例子:

游戲中玩家P請求進(jìn)入房間R,抽象成Actor模型就是PlayerActor需要往RoomActor發(fā)送請求加入的命令。

那么這時候需要這樣寫:

\EasySwoole\Actor\Test\RoomActor::client($node)->send($roomActorId, [
    'user_actor_id' => $userActorId,
    'data'  => '其他進(jìn)入房間的參數(shù)'
])

其中$roomActorId$userActorId是事先xxActor::client()->create()出來的。

上面那段代碼的意思就是往$roomActorIdRoomActor實例推送了一條$userActorId玩家的UserActor實例要加入房間的消息。

參數(shù)$node用來尋址Proxy,它由目標(biāo)Actor實例的Worker.MachineId決定,在本例中就是$roomActorId被創(chuàng)建在了哪個MachineIdWorkerProcess

通過$roomActorId中的機器碼找到IP:PORT,生成$node

send時會創(chuàng)建一個協(xié)程TcpClient,將消息發(fā)送給Proxy,然后Proxy將消息轉(zhuǎn)發(fā)(UnixClient)至本機WorkerProcessWorkerProcess收到消息,推送到具體的Actor實例。

這樣就完成了從PlayerActorRoomActor的請求通訊,RoomActor收到請求消息并處理完成后,向PlayerActor回發(fā)處理結(jié)果,用的是同樣的通訊流程。

如果是單機部署,可以忽略$node參數(shù),因為所有通訊都是在本機進(jìn)行。

多機的話,需要自己根據(jù)業(yè)務(wù)來實現(xiàn)Actor如何分布和定位。

主要屬性

machineId 機器碼

proxyNum 啟動幾個ProxyProcess

listenPort 監(jiān)聽port

listenAddress 監(jiān)聽ip

AbstractActor

Actor實例的基類,所有業(yè)務(wù)中用到的Actor都將繼承于`AbstractActor。例如游戲場景中的房間,你可以:

class RoomActor extends AbstractActor

工作原理

每個Actor實例都維護一份獨立的數(shù)據(jù)和狀態(tài),當(dāng)一個Actor實例通過client()->create()后,會開啟協(xié)程循環(huán),接收mailbox pop的消息,進(jìn)而處理業(yè)務(wù)邏輯,更新自己的數(shù)據(jù)及狀態(tài)。具體實現(xiàn)就是__run()這個方法。

靜態(tài)方法 configure

用來配置ActorConfig,只需要在具體的Actor(如RoomActor)去重寫這個方法就行。

關(guān)于ActorConfig具體屬性可以看下邊ActorConfig部分。

幾個虛擬方法

以下幾個虛擬方法需要在Actor子類中實現(xiàn),這幾個方法被用在__run()中來完成Actor的運行周期。

onStart() 在協(xié)程開啟前執(zhí)行,你可以在此進(jìn)行Actor初始化的一些操作,比如獲取房間的基礎(chǔ)屬性等。

onMessage() 當(dāng)接收到消息時執(zhí)行,一個Actor實例的生命周期基本上就是在收消息-處理-發(fā)消息,你需要在這里對消息進(jìn)行解析處理。

onExit() 當(dāng)接收到退出命令時執(zhí)行。比如你希望在一個Actor實例退出的時候,同時通知某些關(guān)聯(lián)的其他Actor,可以在此處理。

其它

exit() 用于實例自己退出操作,會向自己發(fā)一條退出的命令。

tick()、after() 兩個定時器,用于Actor實例的定時任務(wù),比如游戲房間的定時刷怪(tick);掉線后多長時間自動踢出(after)

static client() 用于創(chuàng)建一個ActorClient來進(jìn)行對應(yīng)Actor(實例)的通訊。

ActorClient

Actor通訊客戶端,調(diào)用xxActor::client()來創(chuàng)建一個ActorClient進(jìn)行Actor通訊。

上邊已經(jīng)大概講過了Actor的通訊流程,本質(zhì)就是TcpClient->ProxyProcess->UnixClient->ActorWorkerProcess->xxActor

看下它實現(xiàn)了哪些方法:

create() 創(chuàng)建一個xxActor實例,返回actorId,在之后你可以使用這個actorId與此實例進(jìn)行通訊。

send() 指定actorId,向其發(fā)送消息。

exit() 通知xxActor退出指定actorId的實例。

sendAll() 向所有的xxActor實例發(fā)送消息。

exitAll() 退出所有xxActor實例。

exist() 當(dāng)前是否存在指定actorIdxxActor實例。

status() 當(dāng)前ActorWorkerxxActor的分布狀態(tài)。

ActorConfig

具體Actor的配置項,比如RoomActorPlayerActor都有自己的配置。

actorName 一般用類名就可以,注意在同一個服務(wù)中這個是不能重復(fù)的。

actorClassActor->register()會將對應(yīng)的類名寫入。

workerNumActor開啟幾個進(jìn)程,Actor->attachServer()時會根據(jù)這個參數(shù)為相應(yīng)Actor啟動WorkerNumWorker進(jìn)程。

ActorNode

上邊提到過,xxActor::client($node),這個$node就是ActorNode對象,屬性為IpPort,用于尋址Proxy

WorkerConfig

WorkerProcess的配置項,WorkerProcess啟動時用到。

workerId worker進(jìn)程Idcreate Actor的時候用于生成actorId

machineId worker進(jìn)程機器碼,create Actor的時候用于生成actorId

trigger 異常觸發(fā)處理接口

WorkerProcess

Actor的重點在這里,每個注冊的Actor(類)會啟動相應(yīng)數(shù)量的WorkerProcess

比如你注冊了RoomActorPlayerActorworkerNum都配置的是3,那么系統(tǒng)將啟動3個RoomActorWorker進(jìn)程和3個PlayerActorWorker進(jìn)程。

每個WorkerProcess維護一個ActorList,你通過client()->create()Actor將分布在不同Worker進(jìn)程里,由它的ActorList進(jìn)行管理。

WorkerProcess通過協(xié)程接收client(這個client就是Proxy做轉(zhuǎn)發(fā)時的UnixClient)消息,區(qū)分消息類型,然后分發(fā)給對應(yīng)的Actor實例。

請仔細(xì)閱讀下WorkerProcess的源碼,它繼承于AbstractUnixProcess

UnixClient

UnixStream Socket,自行了解。Proxy轉(zhuǎn)發(fā)消息給本機Actor所使用的Client

Protocol

數(shù)據(jù)封包協(xié)議。

ProxyCommand

消息命令對象,Actor2將不同類型的消息封裝成格式化的命令,最終傳給WorkerProcess

你可以在ActorClient中了解一下方法和命令的對應(yīng)關(guān)系,但這個不需要在業(yè)務(wù)層去更改。

ProxyConfig

消息代理的配置項。

actorList 注冊的actor列表。

machineId 機器碼

tempDir 臨時目錄

trigger 錯誤觸發(fā)處理接口

ProxyProcess

Actor->attachServer()會啟動proxyNumProxyProcess

用于在Actor實例和WorkerProcess做消息中轉(zhuǎn)。

主站蜘蛛池模板: 江苏广分检测技术有限公司、电力安全工具检测、苏州绝缘工具检测、昆山电力安全工具检测-广分检测技术(苏州)有限公司 | 江西食用油批发_江西食用油厂家_菜籽油厂家-江西省家泰粮油科技有限公司 | 营销型网站建设_网站设计_网页制作_找北京恒基建网站公司 | 自建房外墙砖|地砖|墙砖,农村|别墅瓷砖-佛山燊陶丰 | 中国江苏国际经济技术合作集团有限公司-致力于做大做强国际工程、国内工程、国际贸易和城镇投资 中国建材信息总网-中国建材行业权威的信息资讯平台 | 绝缘油介电强度测试仪|d33压电测试仪|准静态d33压电测量仪厂家直销-上海蓝巢电气有限公司【官网】 | 新洲际教育-一站式留学解决方案领航者【官网】 | 长沙变频器维修,变频器维修,ABB变频器维修,西门子变频器维修,施耐德变频器维修,伺服驱动器维修,工业机器人维修,20年专业工控电气维修,长沙文铖电气设备有限公司_长沙文铖电气设备有限公司 | 木工圆锯片,进口锯片厂家,合金锯片生产厂家,木工合金锯片,BAK(百恪)刀具有限公司 | 青州东威机械有限公司,洗沙机,脱水筛、细沙回收机,淘金设备,洗石机,砂石分离机,筛沙机,采沙船,清淤船,破碎制砂机,海沙淡化设备 | 万通汽车学校,汽车学校,汽修学校,汽修培训学校,汽车美容学校,汽车维修学校,学汽修-武汉万通汽车学校官方网站 | 木马交互设计研究中心 ,专注于用户体验与人机交互设计 - 首页 | 长沙变频器维修,变频器维修,ABB变频器维修,西门子变频器维修,施耐德变频器维修,伺服驱动器维修,工业机器人维修,20年专业工控电气维修,长沙文铖电气设备有限公司_长沙文铖电气设备有限公司 | 铁氟龙膜/板/棒-铁氟龙网带/胶带/胶布-[东莞华氟]专注铁氟龙及特氟龙制品生产 | 木雕红木家具网_红木家具厂_专业的红木家具批发网 | 小地磅,钢瓶秤,叉车称,轮椅秤,倒桶秤,畜牧秤,轴重仪,称重模块——上海实干实业有限公司-网站首页 | 浙江创洁卫生消杀有限公司-浙江杀虫公司,温州消杀公司,温州灭鼠公司,灭蟑螂,灭蚊蝇,灭跳蚤,灭书虱,灭臭虫,灭螨虫,白蚁防治,房间消毒除味等专业服务 | 通道闸-人行通道闸|通道闸机系统厂家-深圳伊帕克智能科技有限公司 | 网带输送机_皮带_滚筒_链板输送机_不锈钢输送链条生产厂家-宁津县鸿昶机械设备有限公司 | 箱式污泥采样器-全自动旋转振荡器-恒温石墨电热板-常州亿通分析仪器制造有限公司 | 实木全屋定制|整木定制|整木家装|实木护墙板-浩冠家具官网 | 金亨木业建筑模板_清水模板_覆膜板_金亨木业建筑模板厂家批发 | 网络公关公司_舆情监测_危机公关_品牌公关_一夜红传媒 | 耐磨焊丝厂-堆焊焊材研发-修复工程-天津舜荣焊材官网 | 太原万通汽车学校[官网]-太原好的汽修培训学校,学新能源汽车技术,学汽修,学汽车检测与维修技术 | 捷胜通运|超大件双清专线|欧美专线|订舱|订柜|订船|双清包税|纯电池运输|液体粉末专线| | 商城网站建设_商城系统_响应式商城_小程序商城_FwShop | 全彩LED显示屏厂家_室内户外电子屏-深圳华邦瀛光电有限公司 | 江苏吉宏特专用汽车制造有限公司_联合吸污车-下水道管道清洗疏通车-综合养护吸排车 | 天津韬艺科技有限公司,天津网站建设公司,天津网站设计公司,品牌网站策划公司 | 篮球场围网|网球场围网|球场围网|体育场围网_安平县炎煌丝网制品有限公司 | 网咖网址导航,网咖分类目录,网址目录,免费收录国内外、各行业优秀网站网站。 | 河南专升本-河南省统招专升本| 直膨式空调机组_风冷恒温恒湿_转轮式热回收_屋顶式空调机组_德州瑞尼森环保科技有限公司 | 商用厨具|商用厨房设备|商用电磁灶-鲁宝厨业官方网站 | 潍坊沃林机械设备有限公司-牵引式风送果园打药机,悬挂式风送果园喷雾机,自走式果树喷药机,车载式风送远程喷雾机-潍坊沃林机械设备有限公司-牵引式风送果园打药机,悬挂式风送果园喷雾机,自走式果树喷药机,车载式风送远程喷雾机 潍坊网络推广,临沂360推广,东营360推广,枣庄360推广,潍坊网站建设,潍坊网络公司,潍坊360搜索,潍坊APP开发,潍坊360推广,潍坊360代理,潍坊点睛网络科技有限公司 | 文学素材,好词好句,正能量句子,百科知识-素文网 | 亚洲一区日韩一区欧美一区a,中文字幕乱妇无码AV在线,欧美日韩免费在线观看,国产精品一区二区三区免费,日韩精品免费一线在线观看,日韩一本在线,国产呦精品一区二区三区下载,国产日韩精品一区二区在线观看,欧美日韩高清一区二区三区,日韩在线免费观看视频,欧美日韩一区在线观看 | 全通径焊接球阀_全焊接球阀「生产厂家」批发-浙江信合阀门有限公司 | 南山荔枝,深圳南荔农业荔枝园自销-质保优放心选购 | 上海便携式液体_日本理音液体_HACH液体颗粒计数器,metone尘埃粒子计数器-上海翰森科学仪器有限公司 |