├── _config.yml ├── test ├── stop.sh ├── dump.rdb ├── reload.sh ├── democlient_reload.php ├── client.conf.php ├── client.conf.php.examples ├── democlient_stat.php ├── setparam.sh ├── demomonitor.php ├── democlient_spcip.php ├── demohttp.php ├── demoserver.php └── democlient.php ├── .gitignore ├── .travis.yml ├── composer.json ├── src ├── Common.php ├── DoraConst.php ├── Packet.php ├── LogAgent.php ├── Monitor.php ├── Client.php └── BackEndServer.php ├── ChangeLog.md └── README.md /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-slate -------------------------------------------------------------------------------- /test/stop.sh: -------------------------------------------------------------------------------- 1 | cat /tmp/dorarpc.pid|xargs kill -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | /.idea 3 | *.pid -------------------------------------------------------------------------------- /test/dump.rdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wujunze/Dora-RPC/master/test/dump.rdb -------------------------------------------------------------------------------- /test/reload.sh: -------------------------------------------------------------------------------- 1 | # 根据当前目录的pid,发送kill信号,重启task和worker 2 | cat /tmp/dorarpcmanager.pid|xargs kill -USR1 -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.4 5 | - 5.5 6 | - 5.6 7 | - 7.0 8 | 9 | script: 10 | - exit 0 11 | 12 | -------------------------------------------------------------------------------- /test/democlient_reload.php: -------------------------------------------------------------------------------- 1 | "2.0.0.1", "port" => 9567), 8 | ); 9 | 10 | //define the mode 11 | $mode = array("type" => 2, "ip" => "1.0.0.1", "port" => 9567); 12 | 13 | $obj = new \DoraRPC\Client($config); 14 | $obj->changeMode($mode); 15 | 16 | $ret = $obj->reloadServerTask("127.0.0.1", 9567); 17 | var_dump($ret); 18 | 19 | -------------------------------------------------------------------------------- /test/client.conf.php: -------------------------------------------------------------------------------- 1 | 5 | array ( 6 | '10.0.2.15_9567' => 7 | array ( 8 | 'ip' => '10.0.2.15', 9 | 'port' => 9567, 10 | 'updatetime' => '1482239138', 11 | ), 12 | ), 13 | 'group2' => 14 | array ( 15 | '10.0.2.15_9567' => 16 | array ( 17 | 'ip' => '10.0.2.15', 18 | 'port' => 9567, 19 | 'updatetime' => '1482239138', 20 | ), 21 | ), 22 | ); -------------------------------------------------------------------------------- /test/client.conf.php.examples: -------------------------------------------------------------------------------- 1 | 5 | array ( 6 | '10.0.2.15_9567' => 7 | array ( 8 | 'ip' => '10.0.2.15', 9 | 'port' => 9567, 10 | 'updatetime' => '1466433170', 11 | ), 12 | ), 13 | 'group2' => 14 | array ( 15 | '10.0.2.15_9567' => 16 | array ( 17 | 'ip' => '10.0.2.15', 18 | 'port' => 9567, 19 | 'updatetime' => '1466433170', 20 | ), 21 | ), 22 | ); -------------------------------------------------------------------------------- /test/democlient_stat.php: -------------------------------------------------------------------------------- 1 | "2.0.0.1", "port" => 9567), 8 | ); 9 | //获取服务器的状态 10 | //会使用getstat指定的ip进行工作 11 | //define the mode 12 | $mode = array("type" => 2, "ip" => "1.0.0.1", "port" => 9567); 13 | 14 | $obj = new \DoraRPC\Client($config); 15 | $obj->changeMode($mode); 16 | 17 | $ret = $obj->getStat("127.0.0.1", 9567); 18 | var_dump($ret); 19 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "xcl3721/dora-rpc", 3 | "description": "Dora RPC is an Basic Swoole Fixed Header TCP Proctol tiny RPC", 4 | "keywords": [ 5 | "RPC", 6 | "Dora-RPC" 7 | ], 8 | "license": "Apache-2.0", 9 | "homepage": "https://github.com/xcl3721/dora-rpc", 10 | "require": { 11 | "php": ">=5.4.30", 12 | "ext-swoole" : "1.9.*" 13 | }, 14 | "autoload": { 15 | "psr-4": { 16 | "DoraRPC\\": "src" 17 | } 18 | }, 19 | "authors": [ 20 | { 21 | "name": "ChangLong.Xu", 22 | "email": "xcl_rockman@qq.com" 23 | } 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /test/setparam.sh: -------------------------------------------------------------------------------- 1 | # 这个只是我自己测试的改配置脚本(有错误),线上根据需要自行配制,具体参考swoole官方文档 2 | ulimit -n 65535 3 | sysctl net.unix.max_dgram_qlen=100 4 | sysctl net.core.wmem_default=8388608 5 | sysctl net.core.rmem_default=8388608 6 | sysctl net.core.rmem_max=16777216 7 | sysctl net.core.wmem_max=16777216 8 | sysctl kernel.msgmnb=65536 9 | 10 | 11 | sysctl net.ipv4.tcp_syncookies=1 12 | 13 | sysctl net.ipv4.tcp_max_syn_backlog=81920 14 | 15 | sysctl net.ipv4.tcp_synack_retries=3 16 | 17 | sysctl net.ipv4.tcp_syn_retries=3 18 | 19 | sysctl net.ipv4.tcp_fin_timeout=30 20 | 21 | sysctl net.ipv4.tcp_keepalive_time=300 22 | 23 | sysctl net.ipv4.tcp_tw_reuse=1 24 | 25 | sysctl net.ipv4.tcp_tw_recycle=1 26 | 27 | sysctl net.ipv4.ip_local_port_range=20000 65000 28 | 29 | sysctl net.ipv4.tcp_max_tw_buckets=200000 30 | 31 | sysctl net.ipv4.route.max_size=5242880 32 | 33 | -------------------------------------------------------------------------------- /src/Common.php: -------------------------------------------------------------------------------- 1 | array( 12 | //first reporter 13 | array( 14 | "ip" => "127.0.0.1", 15 | "port" => "6379", 16 | ), 17 | //next reporter 18 | array( 19 | "ip" => "127.0.0.1", 20 | "port" => "6379", 21 | ), 22 | ), 23 | //general config path for client 24 | "config" => "./client.conf.php", 25 | 26 | //log monitor path 27 | "log" => array( 28 | "tag1" => array("tag" => "", "path" => "./log/"), 29 | "tag2" => array("tag" => "", "path" => "./log2/"), 30 | ), 31 | ); 32 | 33 | //ok start server 34 | $monitor = new \DoraRPC\Monitor("0.0.0.0", 9569, $config); 35 | 36 | $monitor->start(); 37 | -------------------------------------------------------------------------------- /test/democlient_spcip.php: -------------------------------------------------------------------------------- 1 | array( 9 | array("ip" => "127.0.0.1", "port" => 9567), 10 | //array("ip"=>"127.0.0.1","port"=>9567), you can set more ,the client will random select one,to increase High availability 11 | ), 12 | );*/ 13 | //or 14 | $config = include("client.conf.php"); 15 | 16 | //define the mode 17 | $mode = array("type" => 2, "ip" => "127.0.0.1", "port" => 9567); 18 | //这里会使用127.0.0.1指定的配置,而非include内的配置 19 | $obj = new \DoraRPC\Client($config); 20 | $obj->changeMode($mode); 21 | 22 | for ($i = 0; $i < 100000; $i++) { 23 | //single && sync 24 | $ret = $obj->singleAPI("abc", array(234, $i), \DoraRPC\DoraConst::SW_MODE_WAITRESULT, 1); 25 | var_dump($ret); 26 | //multi && async 27 | $data = array( 28 | "oak" => array("name" => "oakdf", "param" => array("dsaf" => "321321")), 29 | "cd" => array("name" => "oakdfff", "param" => array("codo" => "fds")), 30 | ); 31 | $ret = $obj->multiAPI($data, \DoraRPC\DoraConst::SW_MODE_WAITRESULT, 1, "127.1.0.1", 9567); 32 | var_dump($ret); 33 | } 34 | -------------------------------------------------------------------------------- /src/DoraConst.php: -------------------------------------------------------------------------------- 1 | $guid, 11 | 12 | "api" => array( 13 | "oak" => array("name" => "/module_d/oakdf", "param" => array("dsaf" => "32111321")), 14 | "cd" => array("name" => "/module_e/oakdfff", "param" => array("codo" => "f11ds")), 15 | ) 16 | , 17 | ); 18 | 19 | $data_string = "params=" . urlencode(json_encode($data)) . "&guid=" . $guid; 20 | 21 | $ch = curl_init('http://127.0.0.1:9566/api/multisync'); 22 | curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST"); 23 | curl_setopt($ch, CURLOPT_POSTFIELDS, $data_string); 24 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 25 | curl_setopt($ch, CURLOPT_HTTPHEADER, array( 26 | 'Connection: Keep-Alive', 27 | 'Keep-Alive: 300', 28 | ) 29 | ); 30 | 31 | $result = curl_exec($ch); 32 | var_dump(json_decode($result, true)); 33 | 34 | 35 | //multi call no wait result 36 | $guid = md5(mt_rand(1000000, 9999999) . mt_rand(1000000, 9999999) . microtime(true)); 37 | $data = array( 38 | "guid" => $guid, 39 | 40 | "api" => array( 41 | "oak" => array("name" => "/module_d/oakdf", "param" => array("dsaf" => "32111321")), 42 | "cd" => array("name" => "/module_e/oakdfff", "param" => array("codo" => "f11ds")), 43 | ) 44 | , 45 | ); 46 | $data_string = "params=" . urlencode(json_encode($data)) . "&guid=" . $guid; 47 | 48 | $ch = curl_init('http://127.0.0.1:9566/api/multinoresult'); 49 | curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST"); 50 | curl_setopt($ch, CURLOPT_POSTFIELDS, $data_string); 51 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 52 | curl_setopt($ch, CURLOPT_HTTPHEADER, array( 53 | 'Connection: Keep-Alive', 54 | 'Keep-Alive: 300', 55 | ) 56 | ); 57 | 58 | $result = curl_exec($ch); 59 | var_dump(json_decode($result, true)); 60 | 61 | $time = bcsub(microtime(true), $time, 5); 62 | if ($time > $maxrequest) { 63 | $maxrequest = $time; 64 | } 65 | echo $i . " cost:" . $time . PHP_EOL; 66 | //var_dump($ret); 67 | } 68 | echo "max:" . $maxrequest . PHP_EOL; 69 | -------------------------------------------------------------------------------- /ChangeLog.md: -------------------------------------------------------------------------------- 1 | ##更新历史(ChangeLog) 2 | 3 | --------- 4 | > * 2017-01-15 支持channel归并日志到同一个process内落地,改guid到json外面,防止解序列化失败导致guid丢失 5 | > * 2016-07-24 黄总支援了帮忙改进了配置的传递方式及服务启动方式 6 | > * 2016-06-11 增加group连接方式,将原有groupclient方式整合到client内,不再提供非分组配置客户端 7 | > * 2016-06-08 增加新的异步调用方式,并且修复低几率调用参数覆盖问题,groupclient还未更新,请稍后 8 | > * 2016-06-06 客户端增加分组配置客户端,可以用客户端调用多组隔离开的业务。另外之前加了服务发现在test/demomonitor .去掉unique函数服务性能翻倍,gzcompress包压缩 9 | > * 2015-09-30 客户端增加ip和port选项,常量放到一个文件内统一管理 10 | > * 2015-09-29 added to composer. 11 | > * 2015-09-20 psr2检测,去掉非规范写法,增加namespace为composer做准备,将demo移到test内.修复task返回结果超过8k导致超时问题 12 | > * 2015-07-27 客户端单个连接配置扩展为多个,每次初始化客户端的时候会自动从配置内随机选一个配置进行连接,如果连接失败自动切换另外一个,用于提高高可用。 13 | > * 2015-07-24 增加两个抽象函数 initTask 当task进程启动的时候初始化使用 ,initServer 服务启动前附加启动时会调用这个,用于一些服务的初始化.增加请求失败重试指定次数功能 14 | > * 2015-06-23 修复client链接多个ip或端口导致的错误(#2) 15 | > * 2015-06-24 客户端服务端都增加了SW_DATASIGEN_FLAG及SW_DATASIGEN_SALT参数,如果开启则支持消息数据签名,可以强化安全性,打开会有一点性能损耗,建议SALT每个人自定义一个 16 | 17 | ---------- 18 | > * 2017-01-15 support use channel for collect the log and dump by process,change guid outside packet to solve the guid lost when unserialize fail 19 | > * 2016-07-24 JanHuang optimize the config define,and server startup 20 | > * 2016-06-11 remove the groupclient.php and combine groupclient function to the client.php now only support group config client 21 | > * 2016-06-08 add new async result get and fixed the running stack overwrite bug,groupclient was not update under construction 22 | > * 2016-06-06 client have new group config client that for test/demomonitor .(and service discovery demomonitor)。performance 2X when remove unique。gzcompress packet compress 23 | > * 2015-09-30 client can set ip and port you want 24 | > * 2015-09-29 added to composer. 25 | > * 2015-09-20 psr2 leve check and add namespace for composer,move the demo to test folder,fixed when task result 8k+ was timeout bug 26 | > * 2015-07-27 client support multi config item.each of item is the server infomation.to improve the high availability.when the connect fail will try another config item 27 | > * 2015-07-24 add two abstract function: server start init(fn initServer) . task threads start init(fn initTask).and add retry parameter on the request 28 | > * 2015-06-23 Repair client link multiple ip or port error(#2); 29 | > * 2015-06024 Client Server have added SW_DATASIGEN_FLAG and SW_DATASIGEN_SALT parameters, if enabled supports message data signature, can strengthen security, there will increase a little performance loss, it is recommended everyone to customize a SALT 30 | -------------------------------------------------------------------------------- /test/demoserver.php: -------------------------------------------------------------------------------- 1 | "ohyes123"); 23 | } 24 | 25 | function initTask($server, $worker_id) 26 | { 27 | //require_once() 你要加载的处理方法函数等 what's you want load (such as framework init) 28 | } 29 | } 30 | 31 | //ok start server 32 | $server = new APIServer("0.0.0.0", 9567, 9566); 33 | 34 | $server->configure(array( 35 | 'tcp' => array(), 36 | 'http' => array( 37 | //to improve the accept performance ,suggest the number of cpu X 2 38 | //如果想提高请求接收能力,更改这个,推荐cpu个数x2 39 | 'reactor_num' => 8, 40 | 41 | //packet decode process,change by condition 42 | //包处理进程,根据情况调整数量,推荐cpu个数x2 43 | 'worker_num' => 16, 44 | 45 | //the number of task logical process progcessor run you business code 46 | //实际业务处理进程,根据需要进行调整 47 | 'task_worker_num' => 100, 48 | 49 | 'daemonize' => false, 50 | 51 | 'log_file' => '/tmp/sw_server.log', 52 | 53 | 'task_tmpdir' => '/tmp/swtasktmp/', 54 | 55 | ), 56 | 'dora' => array( 57 | 'pid_path' => '/tmp/',//dora 自定义变量,用来保存pid文件 58 | //'response_header' => array('Content_Type' => 'application/json; charset=utf-8'), 59 | 'master_pid' => 'doramaster.pid', //dora master pid 保存文件 60 | 'manager_pid' => 'doramanager.pid',//manager pid 保存文件 61 | 'log_path' => '/tmp/bizlog/', //业务日志 62 | ), 63 | )); 64 | 65 | //redis for service discovery register 66 | //when you on product env please prepare more redis to registe service for high available 67 | $server->discovery( 68 | array( 69 | 'group1', 'group2' 70 | ), 71 | array( 72 | 73 | array( 74 | array(//first reporter 75 | "ip" => "127.0.0.1", 76 | "port" => "6379", 77 | ), 78 | array(//next reporter 79 | "ip" => "127.0.0.1", 80 | "port" => "6379", 81 | ), 82 | ), 83 | )); 84 | 85 | $server->start(); 86 | -------------------------------------------------------------------------------- /src/Packet.php: -------------------------------------------------------------------------------- 1 | $guid, 11 | "code" => $code, 12 | "msg" => $msg, 13 | "data" => $data, 14 | ); 15 | 16 | return $pack; 17 | } 18 | 19 | public static function packEncode($data, $type = "tcp") 20 | { 21 | 22 | if ($type == "tcp") { 23 | $guid = $data["guid"]; 24 | $sendStr = serialize($data); 25 | 26 | //if compress the packet 27 | if (DoraConst::SW_DATACOMPRESS_FLAG == true) { 28 | $sendStr = gzencode($sendStr, 4); 29 | } 30 | 31 | if (DoraConst::SW_DATASIGEN_FLAG == true) { 32 | $signedcode = pack('N', crc32($sendStr . DoraConst::SW_DATASIGEN_SALT)); 33 | $sendStr = pack('N', strlen($sendStr) + 4 + 32) . $signedcode . $guid . $sendStr; 34 | } else { 35 | $sendStr = pack('N', strlen($sendStr) + 32) . $guid . $sendStr; 36 | } 37 | 38 | return $sendStr; 39 | } else if ($type == "http") { 40 | $sendStr = json_encode($data); 41 | return $sendStr; 42 | } else { 43 | return self::packFormat($data["guid"], "packet type wrong", 100006); 44 | } 45 | 46 | } 47 | 48 | public static function packDecode($str) 49 | { 50 | $header = substr($str, 0, 4); 51 | $len = unpack("Nlen", $header); 52 | $len = $len["len"]; 53 | 54 | if (DoraConst::SW_DATASIGEN_FLAG == true) { 55 | 56 | $signedcode = substr($str, 4, 4); 57 | $guid = substr($str, 8, 32); 58 | $result = substr($str, 40); 59 | 60 | //check signed 61 | if (pack("N", crc32($result . DoraConst::SW_DATASIGEN_SALT)) != $signedcode) { 62 | return self::packFormat($guid, "Signed check error!", 100005); 63 | } 64 | 65 | $len = $len - 4 - 32; 66 | 67 | } else { 68 | $guid = substr($str, 4, 32); 69 | $result = substr($str, 36); 70 | $len = $len - 32; 71 | } 72 | if ($len != strlen($result)) { 73 | //结果长度不对 74 | return self::packFormat($guid, "packet length invalid 包长度非法", 100007); 75 | } 76 | //if compress the packet 77 | if (DoraConst::SW_DATACOMPRESS_FLAG == true) { 78 | $result = gzdecode($result); 79 | } 80 | $result = unserialize($result); 81 | 82 | return self::packFormat($guid, "OK", 0, $result); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/LogAgent.php: -------------------------------------------------------------------------------- 1 | 9 || $level < 1) { 33 | return; 34 | } else { 35 | //init other parameter from config 36 | self::$table->set("log_level", array('value' => $level)); 37 | } 38 | } 39 | 40 | public static function getQueueStat() 41 | { 42 | //get queue stat 43 | return self::$logagent->stats(); 44 | } 45 | 46 | public static function recordLog($level, $tag, $file, $line, $msg) 47 | { 48 | $loglevel = self::$table->get("log_level"); 49 | $loglevel = $loglevel["value"]; 50 | 51 | //ignore the level log 52 | if ($loglevel < $level) { 53 | return; 54 | } 55 | 56 | //t type ,p path,l line, m msg,g tag,e time,c cost 57 | $log = array( 58 | 'v' => $level, 59 | 'e' => microtime(true), 60 | 'g' => $tag, 61 | 'p' => $file, 62 | 'l' => $line, 63 | 'm' => $msg, 64 | ); 65 | 66 | //send log 67 | self::$logagent->push($log); 68 | } 69 | 70 | public static function threadDumpLog() 71 | { 72 | swoole_set_process_name("dora: logdumper"); 73 | 74 | //dump the log to the local 75 | $logcount = 0; 76 | $logstr = ""; 77 | $startime = microtime(true); 78 | 79 | while (true) { 80 | $log = self::$logagent->pop(); 81 | 82 | //ok add the log 83 | if ($log !== false) { 84 | $log = json_encode($log); 85 | 86 | $logstr = $logstr . "\n" . $log; 87 | $logcount++; 88 | }else{ 89 | usleep(10000); 90 | } 91 | 92 | //logcount大于100条,过去时间3秒 dump日志 93 | if ($logcount > 100 || microtime(true) - $startime > 3) { 94 | //todo:dump log 95 | $logcount = 0; 96 | $logstr = ""; 97 | $startime = microtime(true); 98 | } 99 | 100 | } 101 | 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /test/democlient.php: -------------------------------------------------------------------------------- 1 | array( 9 | array("ip" => "127.0.0.1", "port" => 9567), 10 | //array("ip"=>"127.0.0.1","port"=>9567), you can set more ,the client will random select one,to increase High availability 11 | ), 12 | );*/ 13 | //or 14 | $config = include("client.conf.php"); 15 | 16 | //define the mode 17 | $mode = array("type" => 1, "group" => "group1"); 18 | 19 | $maxrequest = 0; 20 | 21 | //new obj 22 | $obj = new \DoraRPC\Client($config); 23 | 24 | //change connect mode 25 | $obj->changeMode($mode); 26 | 27 | for ($i = 0; $i < 10000; $i++) { 28 | //echo $i . PHP_EOL; 29 | 30 | //---------single 31 | $time = microtime(true); 32 | 33 | //single && sync 34 | $ret = $obj->singleAPI("/module_a/abc" . $i, array("mark" => 234, "foo" => $i), \DoraRPC\DoraConst::SW_MODE_WAITRESULT, 1); 35 | var_dump("single sync", $ret); 36 | 37 | //single call && async 38 | $ret = $obj->singleAPI("/module_b/abc" . $i, array("yes" => 21321, "foo" => $i), \DoraRPC\DoraConst::SW_MODE_NORESULT, 1); 39 | var_dump("single async", $ret); 40 | 41 | //single call && async 42 | $ret = $obj->singleAPI("/module_c/abd" . $i, array("yes" => 233, "foo" => $i), \DoraRPC\DoraConst::SW_MODE_ASYNCRESULT, 1); 43 | var_dump("single async result", $ret); 44 | 45 | //---------multi 46 | 47 | //multi && sync 48 | $data = array( 49 | "oak" => array("name" => "/module_c/dd" . $i, "param" => array("uid" => "ff")), 50 | "cd" => array("name" => "/module_f/ef" . $i, "param" => array("pathid" => "fds")), 51 | ); 52 | $ret = $obj->multiAPI($data, \DoraRPC\DoraConst::SW_MODE_WAITRESULT, 1); 53 | var_dump("multi sync", $ret); 54 | 55 | //multi && async 56 | $data = array( 57 | "oak" => array("name" => "/module_d/oakdf" . $i, "param" => array("dsaf" => "32111321")), 58 | "cd" => array("name" => "/module_e/oakdfff" . $i, "param" => array("codo" => "f11ds")), 59 | ); 60 | $ret = $obj->multiAPI($data, \DoraRPC\DoraConst::SW_MODE_NORESULT, 1); 61 | var_dump("multi async", $ret); 62 | 63 | //multi && async 64 | $data = array( 65 | "oak" => array("name" => "/module_a/oakdf" . $i, "param" => array("dsaf" => "11")), 66 | "cd" => array("name" => "/module_b/oakdfff" . $i, "param" => array("codo" => "f11ds")), 67 | ); 68 | $ret = $obj->multiAPI($data, \DoraRPC\DoraConst::SW_MODE_ASYNCRESULT, 1); 69 | var_dump("multi async result", $ret); 70 | 71 | //get all the async result 72 | $data = $obj->getAsyncData(); 73 | var_dump("allresult", $data); 74 | //compare each request 75 | $time = bcsub(microtime(true), $time, 5); 76 | if ($time > $maxrequest) { 77 | $maxrequest = $time; 78 | } 79 | echo $i . " cost:" . $time . PHP_EOL; 80 | //var_dump($ret); 81 | } 82 | echo "max:" . $maxrequest . PHP_EOL; 83 | -------------------------------------------------------------------------------- /src/Monitor.php: -------------------------------------------------------------------------------- 1 | _server->addProcess(new \swoole_process(function () use ($config, $self) { 19 | 20 | swoole_set_process_name("dora: monitor service"); 21 | 22 | static $_redisObj = array(); 23 | 24 | while (true) { 25 | //for result list 26 | $serverListResult = array(); 27 | 28 | //get redis config 29 | $redisConfig = $self->_config["redis"]; 30 | 31 | //connect all redis 32 | foreach ($redisConfig as $redisItem) { 33 | //validate redis ip and port 34 | if (trim($redisItem["ip"]) && $redisItem["port"] > 0) { 35 | $key = $redisItem["ip"] . "_" . $redisItem["port"]; 36 | try { 37 | //connecte redis 38 | if (!isset($_redisObj[$key])) { 39 | //if not connect 40 | $_redisObj[$key] = new \Redis(); 41 | $_redisObj[$key]->connect($redisItem["ip"], $redisItem["port"]); 42 | } 43 | 44 | //get register node server 45 | $serverList = $_redisObj[$key]->smembers("dora.serverlist"); 46 | if ($serverList) { 47 | foreach ($serverList as $sitem) { 48 | $info = json_decode($sitem, true); 49 | //decode success 50 | if ($info) { 51 | //get last report time 52 | $lastTimeKey = "dora.servertime." . $info["node"]["ip"] . "." . $info["node"]["port"] . ".time"; 53 | $lastUpdatTime = $_redisObj[$key]->get($lastTimeKey); 54 | 55 | //timeout ignore 56 | if (time() - $lastUpdatTime > 20) { 57 | continue; 58 | } 59 | 60 | if (is_array($info["group"])) { 61 | foreach ($info["group"] as $groupname) { 62 | $clientkey = $info["node"]["ip"] . "_" . $info["node"]["port"]; 63 | $serverListResult[$groupname][$clientkey] = array("ip" => $info["node"]["ip"], "port" => $info["node"]["port"]); 64 | $serverListResult[$groupname][$clientkey]["updatetime"] = $lastUpdatTime; 65 | } 66 | } 67 | //foreach group and record this info 68 | }//decode info if 69 | }// foreach 70 | }//if got server list from redis 71 | 72 | } catch (\Exception $ex) { 73 | //var_dump($ex); 74 | $_redisObj[$key] = null; 75 | echo "get redis server error" . PHP_EOL; 76 | } 77 | } 78 | } 79 | 80 | if (count($serverListResult) > 0) { 81 | 82 | $configString = var_export($serverListResult, true); 83 | $ret = file_put_contents($this->_config["export_path"], "_config["export_path"] . PHP_EOL; 88 | } 89 | } else { 90 | echo "Error there is no Config get..." . PHP_EOL; 91 | } 92 | 93 | //sleep 10 sec 94 | sleep(10); 95 | } 96 | })); 97 | } 98 | 99 | public function __construct($ip = "0.0.0.0", $port = 9569, $config = array()) 100 | { 101 | //record ip:port 102 | $this->_ip = $ip; 103 | $this->_port = $port; 104 | 105 | //create server object 106 | $this->_server = new \swoole_server($ip, $port, \SWOOLE_PROCESS, \SWOOLE_SOCK_UDP); 107 | //set config 108 | $this->_server->set(array( 109 | 'open_length_check' => 1, 110 | 'dispatch_mode' => 3, 111 | 'package_length_type' => 'N', 112 | 'package_length_offset' => 0, 113 | 'package_body_offset' => 4, 114 | 'package_max_length' => 1024 * 1024 * 2, 115 | 'buffer_output_size' => 1024 * 1024 * 3, 116 | 'pipe_buffer_size' => 1024 * 1024 * 32, 117 | 'open_tcp_nodelay' => 1, 118 | 'heartbeat_check_interval' => 5, 119 | 'heartbeat_idle_time' => 10, 120 | 121 | 'reactor_num' => 1, 122 | 'worker_num' => 2, 123 | 'task_worker_num' => 0, 124 | 125 | 'max_request' => 0, //必须设置为0否则并发任务容易丢,don't change this number 126 | 'task_max_request' => 4000, 127 | 128 | 'backlog' => 2000, 129 | 'log_file' => '/tmp/sw_monitor.log', 130 | 'task_tmpdir' => '/tmp/swmonitor/', 131 | 'daemonize' => 0,//product env is 1 132 | )); 133 | 134 | //register the event 135 | $this->_server->on('Packet', array($this, 'onPacket')); 136 | 137 | echo "Start Init Server udp://" . $ip . ":" . $port . PHP_EOL; 138 | 139 | //store the list of redis 140 | $this->_config["redis"] = $config["discovery"]; 141 | 142 | //store the avaliable node list to this config file 143 | $this->_config["export_path"] = $config["config"]; 144 | 145 | //log monitor path 146 | $this->_config["log_path"] = $config["log"]; 147 | 148 | $this->discovery($this->_config["redis"]); 149 | } 150 | 151 | public function start() 152 | { 153 | $this->_server->start(); 154 | } 155 | 156 | public function onPacket(\swoole_server $server, $data, $client_info) 157 | { 158 | //$data = \DoraDRPC\Base\Packet::packDecode($data); 159 | //$server->sendto($client_info['address'], $client_info['port'], \DoraDRPC\Base\Packet::packEncode(array())); 160 | 161 | //var_dump($server, $data); 162 | } 163 | 164 | } 165 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dora RPC 2 | 3 | [![Build Status](https://travis-ci.org/xcl3721/Dora-RPC.svg?branch=master)](https://travis-ci.org/xcl3721/Dora-RPC) [![Latest Stable Version](https://poser.pugx.org/xcl3721/dora-rpc/v/stable)](https://packagist.org/packages/xcl3721/dora-rpc) [![Latest Unstable Version](https://poser.pugx.org/xcl3721/dora-rpc/v/unstable)](https://packagist.org/packages/xcl3721/dora-rpc) [![License](https://poser.pugx.org/xcl3721/dora-rpc/license)](https://packagist.org/packages/xcl3721/dora-rpc) 4 | ## 简介(Introduction) 5 | 6 | Dora RPC 是一款基础于Swoole定长包头通讯协议的最精简的RPC, 用于复杂项目前后端分离,分离后项目都通过API工作可更好的跟踪、升级、维护及管理。 7 | 8 | 问题提交: [Issue](https://github.com/xcl3721/Dora-RPC/issues) 9 | 10 | For complex projects separation, the project can be better maintained by the API project management. 11 | > * Dora RPC is an Basic Swoole Fixed Header TCP Proctol tiny RPC 12 | > * Now support an simple PHP version 13 | > * If you find something wrong,please submit an issue 14 | > * add the http protocol and KeepAlive for the other program language 15 | 16 | 17 | # 设计思路(Design) 18 | > * http://blog.sina.com.cn/s/blog_54ef39890102vs3h.html 架构设计图 19 | > * http://blog.sina.com.cn/s/blog_54ef39890102w8ff.html 端午升级介绍 20 | > * http://wenku.baidu.com/view/ae8adf90e518964bce847c43.html Dora-RPC 思想介绍 21 | 22 | # 功能支持(Function) 23 | > * 支持单API调用,多API并发调用 24 | > * 支持同步调用,异步任务下发不等待结果,异步任务下发统一拿回结果 25 | > * 其他相关知识请参考Swoole扩展 26 | > * 客户端长链接,请求完毕后仍旧保留,减少握手消耗 27 | > * guid收发一致性检测,避免发送和接收数据不一致 28 | > * 基于redis制作的服务发现 29 | 30 | > * Single API RPC \ Multi API Concurrent RPC 31 | > * Asynchronous,synchronization no need result, synchronization get result by manual 32 | > * Please visit Swoole official for further infomation 33 | > * keep the connection of client after the request finishe 34 | > * check the guid when the send<->recive 35 | > * service discovery. 36 | > * base on Redis. Service discovery for High available 37 | 38 | ## 请安装依赖(depend) 39 | > * Swoole 1.8.x+ 40 | > * PHP 5.4+ 41 | > * zlib for compress packet 42 | 43 | ## Installation 44 | ``` 45 | composer require "xcl3721/dora-rpc" 46 | ``` 47 | 48 | ## 文件功能简介(File) 49 | ### dora-rpc/src/Client.php 50 | > * 使用最简单的方式实现的客户端,通过这个框架可以轻松实现PHP的伪多线程,通过分布式加快接口响应速度及高可用 51 | > * an simple client,it's easy adn simply to implement the multi fake thread,you can speed up you API by this distribute RPC 52 | 53 | ### dora-rpc/src/BackEndServer.php 54 | > * API服务端 55 | > * 目前需要继承才能使用,继承后请实现dowork,这个函数是实际处理任务的函数参数为提交参数 56 | > * 做这个只是为了减少大家启用RPC的开发时间 57 | > * 开启服务发现功能,服务端在启动的时候,如果指定redis配置则会自动将当前服务器信息注册到Redis上 58 | > * 返回结果是一个数组 分两部分,第一层是通讯状态code,第二层是处理状态 code 59 | 60 | > * a powerful API server 61 | > * you must extends the swserver and implement dowork function 62 | > * it's use for decrease the dev cycle 63 | > * when you setup the redis config the server will register this server to the redis for service discovery 64 | > * the result will be a two-level arrayfirst is communicate state 'code field' ,second is dowork state 65 | 66 | ### dora-rpc/src/Monitor.php 67 | > * 服务发现客户端,通过扫描Redis获取到所有可用后端服务列表,并生成配置到指定路径 68 | > * an discovery controller client that:scan all the redis and get the list of available service and general config file to special path 69 | 70 | ### dora-rpc/src/groupclient.php (combined to client.php) 71 | > * 服务发现monitor进程产生的配置可以用这个客户端直接引用,请求时可以指定使用哪个组的服务 72 | > * an client for service discovery (monitor general the config from redis) that you can use the config directly 73 | 74 | ## 使用方法(Example) 75 | ### 任务下发模式介绍(task deploy mode) 76 | > * 0 sync wait result 同步下发任务阻塞等待结果返回 77 | > * 1 async no need result 下发异步任务,下发成功返回下发成功提示,不等待任务处理结果 78 | > * 2 async get result by getAsyncData function 下发异步任务,下发成功返回下发成功提示,可以在后续调用getAsyncData 获取所有下发的异步结果 79 | 80 | ### TCP客户端(TCP Client) 81 | ```PHP 82 | 83 | $config = include("client.conf.php"); 84 | //define the mode 85 | $mode = array("type" => 1, "group" => "group1"); 86 | 87 | $maxrequest = 0; 88 | 89 | //new obj 90 | $obj = new \DoraRPC\Client($config); 91 | 92 | //change connect mode 93 | $obj->changeMode($mode); 94 | 95 | for ($i = 0; $i < 10000; $i++) { 96 | //echo $i . PHP_EOL; 97 | 98 | //single 99 | $time = microtime(true); 100 | 101 | //single && sync 102 | $ret = $obj->singleAPI("/module_a/abc" . $i, array("mark" => 234, "foo" => $i), \DoraRPC\DoraConst::SW_MODE_WAITRESULT, 1); 103 | var_dump("single sync", $ret); 104 | 105 | //single call && async 106 | $ret = $obj->singleAPI("/module_b/abc" . $i, array("yes" => 21321, "foo" => $i), \DoraRPC\DoraConst::SW_MODE_NORESULT, 1); 107 | var_dump("single async", $ret); 108 | 109 | //single call && async 110 | $ret = $obj->singleAPI("/module_c/abd" . $i, array("yes" => 233, "foo" => $i), \DoraRPC\DoraConst::SW_MODE_ASYNCRESULT, 1); 111 | var_dump("single async result", $ret); 112 | 113 | //multi 114 | 115 | //multi && sync 116 | $data = array( 117 | "oak" => array("name" => "/module_c/dd" . $i, "param" => array("uid" => "ff")), 118 | "cd" => array("name" => "/module_f/ef" . $i, "param" => array("pathid" => "fds")), 119 | ); 120 | $ret = $obj->multiAPI($data, \DoraRPC\DoraConst::SW_MODE_WAITRESULT, 1); 121 | var_dump("multi sync", $ret); 122 | 123 | //multi && async 124 | $data = array( 125 | "oak" => array("name" => "/module_d/oakdf" . $i, "param" => array("dsaf" => "32111321")), 126 | "cd" => array("name" => "/module_e/oakdfff" . $i, "param" => array("codo" => "f11ds")), 127 | ); 128 | $ret = $obj->multiAPI($data, \DoraRPC\DoraConst::SW_MODE_NORESULT, 1); 129 | var_dump("multi async", $ret); 130 | 131 | //multi && async 132 | $data = array( 133 | "oak" => array("name" => "/module_a/oakdf" . $i, "param" => array("dsaf" => "11")), 134 | "cd" => array("name" => "/module_b/oakdfff" . $i, "param" => array("codo" => "f11ds")), 135 | ); 136 | $ret = $obj->multiAPI($data, \DoraRPC\DoraConst::SW_MODE_ASYNCRESULT, 1); 137 | var_dump("multi async result", $ret); 138 | 139 | //get all the async result 140 | $data = $obj->getAsyncData(); 141 | var_dump("allresult", $data); 142 | 143 | //compare each request 144 | $time = bcsub(microtime(true), $time, 5); 145 | if ($time > $maxrequest) { 146 | $maxrequest = $time; 147 | } 148 | echo $i . " cost:" . $time . PHP_EOL; 149 | } 150 | echo "max:" . $maxrequest . PHP_EOL; 151 | 152 | ``` 153 | 154 | ### HTTP客户端(Http Client) 155 | 156 | http protocol for the other language use performance is common.suggest used tcp client 157 | ```PHP 158 | 159 | for ($i = 0; $i < 10000; $i++) { 160 | $time = microtime(true); 161 | 162 | //mutil call sync wait result 163 | $data = array( 164 | "guid" => md5(mt_rand(1000000, 9999999) . mt_rand(1000000, 9999999) . microtime(true)), 165 | 166 | "api" => array( 167 | "oak" => array("name" => "/module_d/oakdf", "param" => array("dsaf" => "32111321")), 168 | "cd" => array("name" => "/module_e/oakdfff", "param" => array("codo" => "f11ds")), 169 | ) 170 | , 171 | ); 172 | 173 | $data_string = "params=" . urlencode(json_encode($data)); 174 | 175 | $ch = curl_init('http://127.0.0.1:9566/api/multisync'); 176 | curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST"); 177 | curl_setopt($ch, CURLOPT_POSTFIELDS, $data_string); 178 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 179 | curl_setopt($ch, CURLOPT_HTTPHEADER, array( 180 | 'Connection: Keep-Alive', 181 | 'Keep-Alive: 300', 182 | ) 183 | ); 184 | 185 | $result = curl_exec($ch); 186 | var_dump(json_decode($result, true)); 187 | 188 | 189 | //multi call no wait result 190 | $data = array( 191 | "guid" => md5(mt_rand(1000000, 9999999) . mt_rand(1000000, 9999999) . microtime(true)), 192 | 193 | "api" => array( 194 | "oak" => array("name" => "/module_d/oakdf", "param" => array("dsaf" => "32111321")), 195 | "cd" => array("name" => "/module_e/oakdfff", "param" => array("codo" => "f11ds")), 196 | ) 197 | , 198 | ); 199 | 200 | $data_string = "params=" . urlencode(json_encode($data)); 201 | 202 | $ch = curl_init('http://127.0.0.1:9566/api/multinoresult'); 203 | curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST"); 204 | curl_setopt($ch, CURLOPT_POSTFIELDS, $data_string); 205 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 206 | curl_setopt($ch, CURLOPT_HTTPHEADER, array( 207 | 'Connection: Keep-Alive', 208 | 'Keep-Alive: 300', 209 | ) 210 | ); 211 | 212 | $result = curl_exec($ch); 213 | var_dump(json_decode($result, true)); 214 | 215 | 216 | $time = bcsub(microtime(true), $time, 5); 217 | if ($time > $maxrequest) { 218 | $maxrequest = $time; 219 | } 220 | echo $i . " cost:" . $time . PHP_EOL; 221 | //var_dump($ret); 222 | } 223 | echo "max:" . $maxrequest . PHP_EOL; 224 | 225 | ``` 226 | 227 | ### 服务端(Server) 228 | ```PHP 229 | 230 | class Server extends DoraRPCServer { 231 | 232 | //all of this config for optimize performance 233 | //以下配置为优化服务性能用,请实际压测调试 234 | protected $externalConfig = array( 235 | 236 | //to improve the accept performance ,suggest the number of cpu X 2 237 | //如果想提高请求接收能力,更改这个,推荐cpu个数x2 238 | 'reactor_num' => 32, 239 | 240 | //packet decode process,change by condition 241 | //包处理进程,根据情况调整数量 242 | 'worker_num' => 40, 243 | 244 | //the number of task logical process progcessor run you business code 245 | //实际业务处理进程,根据需要进行调整 246 | 'task_worker_num' => 20, 247 | ); 248 | 249 | function initServer($server){ 250 | //the callback of the server init 附加服务初始化 251 | //such as swoole atomic table or buffer 可以放置swoole的计数器,table等 252 | } 253 | function doWork($param){ 254 | //process you logical 业务实际处理代码仍这里 255 | //return the result 使用return返回处理结果 256 | return array("hehe"=>"ohyes"); 257 | } 258 | 259 | function initTask($server, $worker_id){ 260 | //require_once() 你要加载的处理方法函数等 what's you want load (such as framework init) 261 | } 262 | } 263 | 264 | $res = new Server(); 265 | ``` 266 | ### 客户端监控器(Client Local Monitor) 267 | ```PHP 268 | include "src/Doraconst.php"; 269 | include "src/Packet.php"; 270 | include "src/Monitor.php"; 271 | 272 | 273 | //redis for service discovery register 274 | //when you on product env please prepare more redis to registe service for high available 275 | $redisconfig = array( 276 | array(//first reporter 277 | "ip" => "127.0.0.1", 278 | "port" => "6379", 279 | ), 280 | array(//next reporter 281 | "ip" => "127.0.0.1", 282 | "port" => "6379", 283 | ), 284 | ); 285 | 286 | //ok start server 287 | $res = new \DoraRPC\Monitor("0.0.0.0", 9569, $redisconfig, "./client.conf.php"); 288 | //this server will auto get the node server list from redis and general the client config on special path 289 | ``` 290 | 291 | ### 以上代码测试方法 292 | include以上两个文件,使用命令行启动即可(客户端支持在apache nginx fpm内执行,服务端只支持命令行启动) 293 | > * php democlient.php 294 | > * php demoserver.php 295 | 296 | ## 错误码及含义(Error Code) 297 | > * 0 Success work 298 | > * 100001 async task success 299 | > * 100002 unknow task type 300 | > * 100003 you must fill the api parameter on you request 301 | > * 100005 Signed check error 302 | > * 100006 Pack decode type wrong 303 | > * 100007 socket error the recive packet length is wrong 304 | > * 100008 the return guid wrong may be the socket trasfer wrong data 305 | > * 100009 the recive wrong or timeout 306 | > * 100010 there is no server can connect 307 | > * 100011 unknow cmd of controlle 308 | > * 100012 Get Async Result Fail: Client Closed. 309 | > * 100099 unknow communicate mode have been set 310 | > * 100100 guid wront please retry.. 311 | 312 | ## 性能(Performance) 313 | > * Mac I7 Intel 2.2Mhz 314 | > * Vagrant with Vm 1 Core 315 | > * 1G Memory 316 | > * with example code (loop forever) 317 | 318 | ### 测试结果Result 319 | > * Network Cost:0.002~0.004/sec Per Request 320 | > * CPU 10~25% 321 | 以上还有很大优化空间 322 | There is still a lot of optimization space 323 | 324 | ### Optimize performance性能优化 325 | ``` 326 | vim demoserver.php 327 | to see $externalConfig var 328 | and swoole offcial document 329 | 330 | 如果想优化性能请参考以上文件的$externalConfig配置 331 | ``` 332 | 333 | ### Server Config Optimize 334 | > * http://wiki.swoole.com/wiki/page/p-server/sysctl.html 335 | 336 | ### License授权 337 | Apache 338 | 339 | ### QQ Group 340 | QQ Group:346840633 341 | -------------------------------------------------------------------------------- /src/Client.php: -------------------------------------------------------------------------------- 1 | serverConfig = $serverConfig; 56 | } 57 | 58 | //$param = array("type"=>1,"group"=>"group1"); 59 | //$param = array("type"=>2,"ip"=>"127.0.0.1","port"=>9567); 60 | 61 | /** 62 | * 更换连接模式,用于指定ip请求和普通请求切换 63 | * @param $param 64 | * @throws \Exception unknow mode parameter 65 | */ 66 | public function changeMode($param) 67 | { 68 | switch ($param["type"]) { 69 | case 1: 70 | if ($param["group"] == "") { 71 | throw new \Exception("change mode parameter is wrong", -1); 72 | } 73 | $this->connectMode = 1; 74 | $this->connectGroup = $param["group"]; 75 | $this->connectIp = ""; 76 | $this->connectPort = ""; 77 | $this->currentClientKey = ""; 78 | break; 79 | case 2: 80 | if ($param["ip"] == "" || $param["port"] == "") { 81 | throw new \Exception("change mode parameter is wrong", -1); 82 | } 83 | $this->connectMode = 2; 84 | $this->connectGroup = "default"; 85 | $this->connectIp = $param["ip"]; 86 | $this->connectPort = $param["port"]; 87 | $this->currentClientKey = ""; 88 | break; 89 | default: 90 | throw new \Exception("change mode parameter is wrong", -1); 91 | break; 92 | } 93 | } 94 | 95 | /** 96 | * 返回当前连接模式及相关信息 97 | * @return array 98 | * @throws \Exception unknow mode 99 | */ 100 | public function getConnectMode() 101 | { 102 | switch ($this->connectMode) { 103 | case 1: 104 | return array("type" => 1, "group" => $this->connectGroup, "ip" => $this->connectIp, "port" => $this->connectPort); 105 | break; 106 | case 2: 107 | return array("type" => 2, "group" => "", "ip" => $this->connectIp, "port" => $this->connectPort); 108 | break; 109 | default: 110 | throw new \Exception("current connect mode is unknow", -1); 111 | break; 112 | } 113 | } 114 | 115 | //random get config key 116 | private function getConfigObjKey() 117 | { 118 | 119 | if (!isset($this->serverConfig[$this->connectGroup])) { 120 | throw new \Exception("there is no one server can connect", 100010); 121 | } 122 | // if there is no config can use clean up the block list 123 | if (isset($this->serverConfigBlock[$this->connectGroup]) && 124 | count($this->serverConfig[$this->connectGroup]) <= count($this->serverConfigBlock[$this->connectGroup]) 125 | ) { 126 | //clean up the block list 127 | $this->serverConfigBlock[$this->connectGroup] = array(); 128 | } 129 | 130 | //if not specified the ip and port random get one 131 | do { 132 | //get one config by random 133 | $key = array_rand($this->serverConfig[$this->connectGroup]); 134 | 135 | //if not on the block list. 136 | if (!isset($this->serverConfigBlock[$this->connectGroup][$key])) { 137 | return $key; 138 | } 139 | 140 | } while (count($this->serverConfig[$this->connectGroup]) > count($this->serverConfigBlock)); 141 | 142 | throw new \Exception("there is no one server can connect", 100010); 143 | 144 | } 145 | 146 | //get current client 147 | private function getClientObj() 148 | { 149 | //config obj key 150 | $key = ""; 151 | 152 | //if not spc will random 153 | switch ($this->connectMode) { 154 | case 1: 155 | $key = $this->getConfigObjKey(); 156 | $clientKey = $this->serverConfig[$this->connectGroup][$key]["ip"] . "_" . $this->serverConfig[$this->connectGroup][$key]["port"]; 157 | //set the current client key 158 | $this->currentClientKey = $clientKey; 159 | $connectHost = $this->serverConfig[$this->connectGroup][$key]["ip"]; 160 | $connectPort = $this->serverConfig[$this->connectGroup][$key]["port"]; 161 | break; 162 | case 2: 163 | //using spec 164 | $clientKey = trim($this->connectIp) . "_" . trim($this->connectPort); 165 | //set the current client key 166 | $this->currentClientKey = $clientKey; 167 | $connectHost = $this->connectIp; 168 | $connectPort = $this->connectPort; 169 | break; 170 | default: 171 | throw new \Exception("current connect mode is unknow", -1); 172 | break; 173 | } 174 | 175 | if (!isset(self::$client[$clientKey])) { 176 | $client = new \swoole_client(SWOOLE_SOCK_TCP | SWOOLE_KEEP); 177 | $client->set(array( 178 | 'open_length_check' => 1, 179 | 'package_length_type' => 'N', 180 | 'package_length_offset' => 0, 181 | 'package_body_offset' => 4, 182 | 'package_max_length' => 1024 * 1024 * 2, 183 | 'open_tcp_nodelay' => 1, 184 | 'socket_buffer_size' => 1024 * 1024 * 4, 185 | )); 186 | 187 | if (!$client->connect($connectHost, $connectPort, DoraConst::SW_RECIVE_TIMEOUT)) { 188 | //connect fail 189 | $errorCode = $client->errCode; 190 | if ($errorCode == 0) { 191 | $msg = "connect fail.check host dns."; 192 | $errorCode = -1; 193 | } else { 194 | $msg = \socket_strerror($errorCode); 195 | } 196 | 197 | if ($key !== "") { 198 | //put the fail connect config to block list 199 | $this->serverConfigBlock[$this->connectGroup][$key] = 1; 200 | } 201 | 202 | throw new \Exception($msg . " " . $clientKey, $errorCode); 203 | } 204 | 205 | self::$client[$clientKey] = $client; 206 | } 207 | 208 | //success 209 | return self::$client[$clientKey]; 210 | } 211 | 212 | /** 213 | * 获取应用服务器信息 get the backend service stat 214 | * @param string $ip 215 | * @param string $port 216 | * @return array 217 | */ 218 | 219 | public function getStat($ip = "", $port = "") 220 | { 221 | $beformode = $this->getConnectMode(); 222 | 223 | if ($ip != "" && $port != "") { 224 | $mode = array("type" => 2, "ip" => $ip, "port" => $port); 225 | $this->changeMode($mode); 226 | } 227 | 228 | $this->guid = $this->generateGuid(); 229 | 230 | $packet = array( 231 | 'api' => array( 232 | "cmd" => array( 233 | 'name' => "getStat", 234 | 'param' => array(), 235 | ), 236 | ), 237 | 'type' => DoraConst::SW_CONTROL_CMD, 238 | 'guid' => $this->guid, 239 | ); 240 | 241 | $sendData = Packet::packEncode($packet); 242 | $result = $this->doRequest($sendData, DoraConst::SW_MODE_WAITRESULT_SINGLE); 243 | 244 | if ($this->guid != $result["guid"]) { 245 | return Packet::packFormat($this->guid, "guid wront please retry..", 100100, $result["data"]); 246 | } 247 | 248 | //revert befor connect mode 249 | if ($ip != "" && $port != "") { 250 | //revert befor mode 251 | $this->changeMode($beformode); 252 | } 253 | return $result["data"]; 254 | } 255 | 256 | /** 257 | * reload 指定服务器的代码 258 | * @param string $ip 259 | * @param string $port 260 | * @return array 261 | */ 262 | public function reloadServerTask($ip = "", $port = "") 263 | { 264 | $beformode = $this->getConnectMode(); 265 | 266 | if ($ip != "" && $port != "") { 267 | $mode = array("type" => 2, "ip" => $ip, "port" => $port); 268 | $this->changeMode($mode); 269 | } 270 | 271 | $this->guid = $this->generateGuid(); 272 | 273 | $packet = array( 274 | 'api' => array( 275 | "cmd" => array( 276 | 'name' => "reloadTask", 277 | 'param' => array(), 278 | ), 279 | ), 280 | 'type' => DoraConst::SW_CONTROL_CMD, 281 | 'guid' => $this->guid, 282 | ); 283 | 284 | $sendData = Packet::packEncode($packet); 285 | $result = $this->doRequest($sendData, DoraConst::SW_MODE_WAITRESULT_SINGLE); 286 | 287 | if ($this->guid != $result["guid"]) { 288 | return Packet::packFormat($this->guid, "guid wront please retry..", 100100, $result["data"]); 289 | } 290 | 291 | //revert befor connect mode 292 | if ($ip != "" && $port != "") { 293 | //revert befor mode 294 | $this->changeMode($beformode); 295 | } 296 | return $result["data"]; 297 | } 298 | 299 | /* 300 | * mode 参数更改说明,以前版本只是sync参数不是mode 301 | * sync : 302 | * true 代表是否阻塞等待结果, 303 | * false 下发任务成功后就返回不等待结果,用于对接口返回没有影响的操作提速 304 | * 改版后---- 305 | * mode : 306 | * 0 代表阻塞等待任务执行完毕拿到结果 ; 307 | * 1 代表下发任务成功后就返回不等待结果 ; 308 | * 2 代表下发任务成功后直接返回guid 然后稍晚通过调用阻塞接收函数拿到所有结果 309 | */ 310 | /** 311 | * 单api请求 312 | * @param string $name api地址 313 | * @param array $param 参数 314 | * @param int $mode 315 | * @param int $retry 通讯错误时重试次数 316 | * @param string $ip 要连得ip地址,如果不指定从现有配置随机个 317 | * @param string $port 要连得port地址,如果不指定从现有配置找一个 318 | * @return mixed 返回单个请求结果 319 | * @throws \Exception unknow mode type 320 | */ 321 | public function singleAPI($name, $param, $mode = DoraConst::SW_MODE_WAITRESULT, $retry = 0, $ip = "", $port = "") 322 | { 323 | //get guid 324 | $this->guid = $this->generateGuid(); 325 | 326 | $packet = array( 327 | 'api' => array( 328 | "one" => array( 329 | 'name' => $name, 330 | 'param' => $param, 331 | ) 332 | ), 333 | 'guid' => $this->guid, 334 | ); 335 | 336 | switch ($mode) { 337 | case DoraConst::SW_MODE_WAITRESULT: 338 | $packet["type"] = DoraConst::SW_MODE_WAITRESULT_SINGLE; 339 | break; 340 | case DoraConst::SW_MODE_NORESULT: 341 | $packet["type"] = DoraConst::SW_MODE_NORESULT_SINGLE; 342 | break; 343 | case DoraConst::SW_MODE_ASYNCRESULT: 344 | $packet["type"] = DoraConst::SW_MODE_ASYNCRESULT_SINGLE; 345 | break; 346 | default: 347 | throw new \Exception("unknow mode have been set", 100099); 348 | break; 349 | } 350 | 351 | $sendData = Packet::packEncode($packet); 352 | 353 | $result = $this->doRequest($sendData, $packet["type"]); 354 | 355 | //retry when the send fail 356 | while ((!isset($result["code"]) || $result["code"] != 0) && $retry > 0) { 357 | $result = $this->doRequest($sendData, $packet["type"]); 358 | $retry--; 359 | } 360 | 361 | if ($this->guid != $result["guid"]) { 362 | return Packet::packFormat($this->guid, "guid wront please retry..", 100100, $result["data"]); 363 | } 364 | 365 | return $result; 366 | } 367 | 368 | /** 369 | * 并发请求api,使用方法如 370 | * $params = array( 371 | * "api_1117"=>array("name"=>"apiname1",“param”=>array("id"=>1117)), 372 | * "api_2"=>array("name"=>"apiname2","param"=>array("id"=>2)), 373 | * ) 374 | * @param array $params 提交参数 请指定key好方便区分对应结果,注意考虑到硬件资源有限并发请求不要超过50个 375 | * @param int $mode 376 | * @param int $retry 通讯错误时重试次数 377 | * @param string $ip 要连得ip地址,如果不指定从现有配置随机个 378 | * @param string $port 要连得port地址,如果不指定从现有配置找一个 379 | * @return mixed 返回指定key结果 380 | * @throws \Exception unknow mode type 381 | */ 382 | public function multiAPI($params, $mode = DoraConst::SW_MODE_WAITRESULT, $retry = 0, $ip = "", $port = "") 383 | { 384 | //get guid 385 | $this->guid = $this->generateGuid(); 386 | 387 | $packet = array( 388 | 'api' => $params, 389 | 'guid' => $this->guid, 390 | ); 391 | 392 | switch ($mode) { 393 | case DoraConst::SW_MODE_WAITRESULT: 394 | $packet["type"] = DoraConst::SW_MODE_WAITRESULT_MULTI; 395 | break; 396 | case DoraConst::SW_MODE_NORESULT: 397 | $packet["type"] = DoraConst::SW_MODE_NORESULT_MULTI; 398 | break; 399 | case DoraConst::SW_MODE_ASYNCRESULT: 400 | $packet["type"] = DoraConst::SW_MODE_ASYNCRESULT_MULTI; 401 | break; 402 | default: 403 | throw new \Exception("unknow mode have been set", 100099); 404 | break; 405 | } 406 | 407 | $sendData = Packet::packEncode($packet); 408 | 409 | $result = $this->doRequest($sendData, $packet["type"]); 410 | 411 | //retry when the send fail 412 | while ((!isset($result["code"]) || $result["code"] != 0) && $retry > 0) { 413 | $result = $this->doRequest($sendData, $packet["type"]); 414 | $retry--; 415 | } 416 | 417 | if ($this->guid != $result["guid"]) { 418 | return Packet::packFormat($this->guid, "guid wront please retry..", 100100, $result["data"]); 419 | } 420 | 421 | return $result; 422 | } 423 | 424 | 425 | private function doRequest($sendData, $type) 426 | { 427 | //get client obj 428 | try { 429 | $client = $this->getClientObj(); 430 | } catch (\Exception $e) { 431 | $data = Packet::packFormat($this->guid, $e->getMessage(), $e->getCode()); 432 | return $data; 433 | } 434 | 435 | $ret = $client->send($sendData); 436 | 437 | //ok fail 438 | if (!$ret) { 439 | $errorcode = $client->errCode; 440 | 441 | //destroy error client obj to make reconncet 442 | self::$client[$this->currentClientKey]->close(true); 443 | unset(self::$client[$this->currentClientKey]); 444 | // mark the current connection cannot be used, try another channel 445 | $this->serverConfigBlock[$this->connectGroup][$this->currentClientKey] = 1; 446 | 447 | if ($errorcode == 0) { 448 | $msg = "connect fail.check host dns."; 449 | $errorcode = -1; 450 | $packet = Packet::packFormat($this->guid, $msg, $errorcode); 451 | } else { 452 | $msg = \socket_strerror($errorcode); 453 | $packet = Packet::packFormat($this->guid, $msg, $errorcode); 454 | } 455 | 456 | return $packet; 457 | } 458 | 459 | //if the type is async result will record the guid and client handle 460 | if ($type == DoraConst::SW_MODE_ASYNCRESULT_MULTI || $type == DoraConst::SW_MODE_ASYNCRESULT_SINGLE) { 461 | self::$asynclist[$this->guid] = $client; 462 | } 463 | 464 | //recive the response 465 | $data = $this->waitResult($client); 466 | $data["guid"] = $this->guid; 467 | return $data; 468 | } 469 | 470 | //for the loop find the right result 471 | //save the async result to the asyncresult static var 472 | //return the right guid request 473 | private function waitResult($client) 474 | { 475 | while (1) { 476 | $result = $client->recv(); 477 | 478 | if ($result !== false && $result != "") { 479 | $data = Packet::packDecode($result); 480 | //if the async result first deploy success will 481 | if ($data["data"]["guid"] != $this->guid) { 482 | 483 | // this data was not we want 484 | //it's may the async result 485 | //when the guid on the asynclist and have isresult =1 on data is async result 486 | //when the guid on the asynclist not have isresult field ond data is first success deploy msg 487 | 488 | if (isset(self::$asynclist[$data["data"]["guid"]]) && isset($data["data"]["isresult"]) && $data["data"]["isresult"] == 1) { 489 | 490 | //ok recive an async result 491 | //remove the guid on the asynclist 492 | unset(self::$asynclist[$data["data"]["guid"]]); 493 | 494 | //add result to async result 495 | self::$asynresult[$data["data"]["guid"]] = $data["data"]; 496 | self::$asynresult[$data["data"]["guid"]]["fromwait"] = 1; 497 | } else { 498 | //not in the asynclist drop this packet 499 | continue; 500 | } 501 | } else { 502 | //founded right data 503 | return $data; 504 | } 505 | } else { 506 | //time out 507 | $packet = Packet::packFormat($this->guid, "the recive wrong or timeout", 100009); 508 | return $packet; 509 | } 510 | } 511 | } 512 | 513 | public function getAsyncData() 514 | { 515 | //wait all the async result 516 | //when timeout all the error will return 517 | //这里有个坑,我不知道具体哪个client需要recive 518 | while (1) { 519 | 520 | if (count(self::$asynclist) > 0) { 521 | foreach (self::$asynclist as $k => $client) { 522 | if ($client->isConnected()) { 523 | $data = $client->recv(); 524 | if ($data !== false && $data != "") { 525 | $data = Packet::packDecode($data); 526 | 527 | if (isset(self::$asynclist[$data["data"]["guid"]]) && isset($data["data"]["isresult"]) && $data["data"]["isresult"] == 1) { 528 | 529 | //ok recive an async result 530 | //remove the guid on the asynclist 531 | unset(self::$asynclist[$data["data"]["guid"]]); 532 | 533 | //add result to async result 534 | self::$asynresult[$data["data"]["guid"]] = $data["data"]; 535 | self::$asynresult[$data["data"]["guid"]]["fromwait"] = 0; 536 | continue; 537 | } else { 538 | //not in the asynclist drop this packet 539 | continue; 540 | } 541 | } else { 542 | //remove the result 543 | unset(self::$asynclist[$k]); 544 | self::$asynresult[$k] = Packet::packFormat($this->guid, "the recive wrong or timeout", 100009); 545 | continue; 546 | } 547 | } else { 548 | //remove the result 549 | unset(self::$asynclist[$k]); 550 | self::$asynresult[$k] = Packet::packFormat($this->guid, "Get Async Result Fail: Client Closed.", 100012); 551 | continue; 552 | } 553 | } // foreach the list 554 | } else { 555 | break; 556 | } 557 | }//while 558 | 559 | $result = self::$asynresult; 560 | self::$asynresult = array(); 561 | return Packet::packFormat($this->guid, "OK", 0, $result); 562 | } 563 | 564 | //clean up the async list and result 565 | public function clearAsyncData() 566 | { 567 | self::$asynresult = array(); 568 | self::$asynclist = array(); 569 | } 570 | 571 | private function generateGuid() 572 | { 573 | //to make sure the guid is unique for the async result 574 | while (1) { 575 | $guid = md5(microtime(true) . mt_rand(1, 1000000) . mt_rand(1, 1000000)); 576 | //prevent the guid on the async list 577 | if (!isset(self::$asynclist[$guid])) { 578 | return $guid; 579 | } 580 | } 581 | } 582 | 583 | 584 | public function __destruct() 585 | { 586 | 587 | } 588 | } 589 | -------------------------------------------------------------------------------- /src/BackEndServer.php: -------------------------------------------------------------------------------- 1 | 3, 25 | 26 | 'package_max_length' => 2097152, // 1024 * 1024 * 2, 27 | 'buffer_output_size' => 3145728, //1024 * 1024 * 3, 28 | 'pipe_buffer_size' => 33554432, //1024 * 1024 * 32, 29 | 'open_tcp_nodelay' => 1, 30 | 31 | 'heartbeat_check_interval' => 5, 32 | 'heartbeat_idle_time' => 10, 33 | 'open_cpu_affinity' => 1, 34 | 35 | //'reactor_num' => 32,//建议设置为CPU核数 x 2 新版会自动设置 cpu个数 36 | 'worker_num' => 40, 37 | 'task_worker_num' => 20,//生产环境请加大,建议1000 38 | 39 | 'max_request' => 0, //必须设置为0,否则会导致并发任务超时,don't change this number 40 | 'task_max_request' => 4000, 41 | 42 | 'log_level' => 2, //swoole 日志级别 Info 43 | 'backlog' => 3000, 44 | 'log_file' => '/tmp/sw_server.log',//swoole 系统日志,任何代码内echo都会在这里输出 45 | 'task_tmpdir' => '/dev/shm/swtask/',//task 投递内容过长时,会临时保存在这里,请将tmp设置使用内存 46 | ); 47 | 48 | protected $tcpConfig = array( 49 | 'open_length_check' => 1, 50 | 'package_length_type' => 'N', 51 | 'package_length_offset' => 0, 52 | 'package_body_offset' => 4, 53 | 54 | 'package_max_length' => 2097152, // 1024 * 1024 * 2, 55 | 'buffer_output_size' => 3145728, //1024 * 1024 * 3, 56 | 'pipe_buffer_size' => 33554432, // 1024 * 1024 * 32, 57 | 58 | 'open_tcp_nodelay' => 1, 59 | 60 | 'backlog' => 3000, 61 | ); 62 | 63 | protected $doraConfig = array( 64 | //自定义配置 65 | 'pid_path' => '/tmp/',//dora 自定义变量,用来保存pid文件 66 | //'response_header' => array('Content_Type' => 'application/json; charset=utf-8'), 67 | 'master_pid' => 'doramaster.pid', //dora master pid 保存文件 68 | 'manager_pid' => 'doramanager.pid',//manager pid 保存文件 69 | 'log_level' => DoraConst::LOG_TYPE_INFO,//设置默认日志等级 70 | 'log_dump_type' => 'file',//file|logserver 71 | 'log_path' => '/tmp/bizlog/', //业务日志 dump path 72 | 73 | //const MASTER_PID = './dorarpc.pid'; 74 | //const MANAGER_PID = './dorarpcmanager.pid'; 75 | ); 76 | 77 | abstract public function initServer($server); 78 | 79 | final public function __construct($ip = "0.0.0.0", $port = 9567, $httpport = 9566) 80 | { 81 | $this->server = new \swoole_http_server($ip, $httpport); 82 | //tcp server 83 | $this->tcpserver = $this->server->addListener($ip, $port, \SWOOLE_TCP); 84 | //tcp只使用这个事件 85 | $this->tcpserver->on('Receive', array($this, 'onReceive')); 86 | //init http server 87 | $this->server->on('Start', array($this, 'onStart')); 88 | $this->server->on('ManagerStart', array($this, 'onManagerStart')); 89 | $this->server->on('ManagerStop', array($this, 'onManagerStop')); 90 | 91 | $this->server->on('Request', array($this, 'onRequest')); 92 | $this->server->on('WorkerStart', array($this, 'onWorkerStart')); 93 | $this->server->on('WorkerError', array($this, 'onWorkerError')); 94 | $this->server->on('Task', array($this, 'onTask')); 95 | $this->server->on('Finish', array($this, 'onFinish')); 96 | 97 | //invoke the start 98 | $this->initServer($this->server); 99 | 100 | //store current ip port 101 | $this->serverIP = $ip; 102 | $this->serverPort = $port; 103 | 104 | } 105 | 106 | /** 107 | * Configuration Server.必须在start之前执行 108 | * 109 | * @param array $config 110 | * @return $this 111 | */ 112 | public function configure(array $config) 113 | { 114 | if (isset($config['http'])) { 115 | //if (isset($config['http']['response_header'])) { 116 | // $config['http']['response_header'] = array_merge($this->httpConfig['response_header'], $config['http']['response_header']); 117 | //} 118 | 119 | $this->httpConfig = array_merge($this->httpConfig, $config['http']); 120 | } 121 | 122 | if (isset($config['tcp'])) { 123 | $this->tcpConfig = array_merge($this->tcpConfig, $config['tcp']); 124 | } 125 | 126 | if (isset($config['dora'])) { 127 | $this->doraConfig = array_merge($this->doraConfig, $config['dora']); 128 | } 129 | return $this; 130 | } 131 | 132 | /** 133 | * 启动服务发现服务 134 | * @param array $group 135 | * @param array $report 136 | */ 137 | public function discovery(array $group, array $report) 138 | { 139 | $self = $this; 140 | $this->monitorProcess = new \swoole_process(function () use ($group, $report, $self) { 141 | while (true) { 142 | // 上报的服务器IP 143 | $reportServerIP = $self->getLocalIp(); 144 | swoole_set_process_name("dora: monitor (" . $reportServerIP . ")"); 145 | 146 | foreach ($report as $discovery) { 147 | foreach ($discovery as $config) { 148 | if (trim($config["ip"]) && $config["port"] > 0) { 149 | $key = $config["ip"] . "_" . $config["port"]; 150 | try { 151 | if (!isset($_redisObj[$key])) { 152 | //if not connect 153 | $_redisObj[$key] = new \Redis(); 154 | $_redisObj[$key]->connect($config["ip"], $config["port"]); 155 | } 156 | //register this server 157 | $_redisObj[$key]->sadd("dora.serverlist", json_encode(array( 158 | "node" => array( 159 | "ip" => $reportServerIP, 160 | "port" => $self->serverPort 161 | ), 162 | "group" => $group, 163 | ))); 164 | //set time out 165 | $_redisObj[$key]->set("dora.servertime." . $reportServerIP . "." . $self->serverPort . ".time", time()); 166 | echo "Reported Service Discovery:" . $config["ip"] . ":" . $config["port"] . PHP_EOL; 167 | 168 | } catch (\Exception $ex) { 169 | $_redisObj[$key] = null; 170 | echo "connect to Service Discovery error:" . $config["ip"] . ":" . $config["port"] . PHP_EOL; 171 | } 172 | } 173 | 174 | sleep(10); 175 | //sleep 10 sec and report again 176 | }// config foreach 177 | }//discover foreach 178 | } 179 | }); 180 | $this->server->addProcess($this->monitorProcess); 181 | 182 | } 183 | 184 | /** 185 | * Start Server. 186 | * 187 | * @return void; 188 | */ 189 | public function start() 190 | { 191 | //config the server config 192 | $this->server->set($this->httpConfig); 193 | $this->tcpserver->set($this->tcpConfig); 194 | 195 | $this->table = new \swoole_table(1024); 196 | $this->table->column('value', \swoole_table::TYPE_STRING, 64); 197 | $this->table->create(); 198 | 199 | //log agent init first 200 | LogAgent::init($this->doraConfig["log_path"], $this->table); 201 | LogAgent::setLogLevel($this->doraConfig["log_level"]); 202 | 203 | $this->server->addProcess(new \swoole_process(function () { 204 | LogAgent::threadDumpLog(); 205 | })); 206 | 207 | 208 | $this->server->start(); 209 | } 210 | 211 | //http request process 212 | final public function onRequest(\swoole_http_request $request, \swoole_http_response $response) 213 | { 214 | //return the json 215 | $response->header('Content_Type', 'application/json; charset=utf-8'); 216 | 217 | //forever http 200 ,when the error json code decide 218 | $response->status(200); 219 | 220 | //chenck post error 221 | if (!isset($request->post["params"]) || !isset($request->post["guid"])) { 222 | $response->end(json_encode(Packet::packFormat($request->post["guid"], "Parameter was not set or wrong", 100003))); 223 | return; 224 | } 225 | //get the post parameter 226 | $params = $request->post; 227 | $params = json_decode($params["params"], true); 228 | 229 | //check the parameter need field 230 | if (!isset($params["guid"]) || !isset($params["api"]) || count($params["api"]) == 0) { 231 | $response->end(json_encode(Packet::packFormat($params["guid"], "Parameter was not set or wrong", 100004))); 232 | return; 233 | } 234 | 235 | //task base info 236 | $task = array( 237 | "guid" => $params["guid"], 238 | "fd" => $request->fd, 239 | "protocol" => "http", 240 | ); 241 | 242 | $url = trim($request->server["request_uri"], "\r\n/ "); 243 | 244 | switch ($url) { 245 | case "api/multisync": 246 | $task["type"] = DoraConst::SW_MODE_WAITRESULT_MULTI; 247 | foreach ($params["api"] as $k => $v) { 248 | $task["api"] = $params["api"][$k]; 249 | $taskid = $this->server->task($task, -1, function ($serv, $task_id, $data) use ($response) { 250 | $this->onHttpFinished($serv, $task_id, $data, $response); 251 | }); 252 | $this->taskInfo[$task["fd"]][$task["guid"]]["taskkey"][$taskid] = $k; 253 | } 254 | break; 255 | case "api/multinoresult": 256 | $task["type"] = DoraConst::SW_MODE_NORESULT_MULTI; 257 | 258 | foreach ($params["api"] as $k => $v) { 259 | $task["api"] = $params["api"][$k]; 260 | $this->server->task($task); 261 | } 262 | $pack = Packet::packFormat($task["guid"], "transfer success.已经成功投递", 100001); 263 | $response->end(json_encode($pack)); 264 | 265 | break; 266 | case "server/cmd": 267 | $task["type"] = DoraConst::SW_CONTROL_CMD; 268 | 269 | if ($params["api"]["cmd"]["name"] == "getStat") { 270 | $pack = Packet::packFormat($params["guid"], "OK", 0, array("server" => $this->server->stats(), "logqueue" => LogAgent::getQueueStat())); 271 | $pack["guid"] = $task["guid"]; 272 | $response->end(json_encode($pack)); 273 | return; 274 | } 275 | if ($params["api"]["cmd"]["name"] == "reloadTask") { 276 | $pack = Packet::packFormat($params["guid"], "OK", 0, array('server' => $this->server->stats(), "logqueue" => LogAgent::getQueueStat())); 277 | $this->server->reload(true); 278 | $pack["guid"] = $task["guid"]; 279 | $response->end(json_encode($pack)); 280 | return; 281 | } 282 | break; 283 | default: 284 | $response->end(json_encode(Packet::packFormat($params["guid"], "unknow task type.未知类型任务", 100002))); 285 | unset($this->taskInfo[$task["fd"]]); 286 | return; 287 | } 288 | 289 | } 290 | 291 | //application server first start 292 | final public function onStart(\swoole_server $serv) 293 | { 294 | swoole_set_process_name("dora: master"); 295 | 296 | echo "MasterPid={$serv->master_pid}\n"; 297 | echo "ManagerPid={$serv->manager_pid}\n"; 298 | echo "Server: start.Swoole version is [" . SWOOLE_VERSION . "]\n"; 299 | 300 | file_put_contents($this->doraConfig["pid_path"] . "/" . $this->doraConfig["master_pid"], $serv->master_pid); 301 | file_put_contents($this->doraConfig["pid_path"] . "/" . $this->doraConfig["manager_pid"], $serv->manager_pid); 302 | 303 | } 304 | 305 | //application server first start 306 | final public function onManagerStart(\swoole_server $serv) 307 | { 308 | swoole_set_process_name("dora: manager"); 309 | } 310 | 311 | final public function onManagerStop(\swoole_server $serv) 312 | { 313 | //echo "Manager Stop , shutdown server\n"; 314 | //$serv->shutdown(); 315 | } 316 | 317 | //worker and task init 318 | final public function onWorkerStart($server, $worker_id) 319 | { 320 | $istask = $server->taskworker; 321 | if (!$istask) { 322 | //worker 323 | swoole_set_process_name("dora: worker {$worker_id}"); 324 | } else { 325 | //task 326 | swoole_set_process_name("dora: task {$worker_id}"); 327 | $this->initTask($server, $worker_id); 328 | } 329 | 330 | } 331 | 332 | abstract public function initTask($server, $worker_id); 333 | 334 | //tcp request process 335 | final public function onReceive(\swoole_server $serv, $fd, $from_id, $data) 336 | { 337 | $requestInfo = Packet::packDecode($data); 338 | 339 | #decode error 340 | if ($requestInfo["code"] != 0) { 341 | $req = Packet::packEncode($requestInfo); 342 | $serv->send($fd, $req); 343 | 344 | return true; 345 | } else { 346 | $requestInfo = $requestInfo["data"]; 347 | } 348 | 349 | #api was not set will fail 350 | if (!is_array($requestInfo["api"]) && count($requestInfo["api"])== 0) { 351 | $pack = Packet::packFormat($requestInfo["guid"], "param api is empty", 100003); 352 | $pack = Packet::packEncode($pack); 353 | $serv->send($fd, $pack); 354 | 355 | return true; 356 | } 357 | $guid = $requestInfo["guid"]; 358 | 359 | //prepare the task parameter 360 | $task = array( 361 | "type" => $requestInfo["type"], 362 | "guid" => $requestInfo["guid"], 363 | "fd" => $fd, 364 | "protocol" => "tcp", 365 | ); 366 | 367 | //different task type process 368 | switch ($requestInfo["type"]) { 369 | 370 | case DoraConst::SW_MODE_WAITRESULT_SINGLE: 371 | $task["api"] = $requestInfo["api"]["one"]; 372 | $taskid = $serv->task($task); 373 | 374 | //result with task key 375 | $this->taskInfo[$fd][$guid]["taskkey"][$taskid] = "one"; 376 | 377 | return true; 378 | break; 379 | case DoraConst::SW_MODE_NORESULT_SINGLE: 380 | $task["api"] = $requestInfo["api"]["one"]; 381 | $serv->task($task); 382 | 383 | //return success deploy 384 | $pack = Packet::packFormat($guid, "transfer success.已经成功投递", 100001); 385 | $pack = Packet::packEncode($pack); 386 | $serv->send($fd, $pack); 387 | 388 | return true; 389 | 390 | break; 391 | 392 | case DoraConst::SW_MODE_WAITRESULT_MULTI: 393 | foreach ($requestInfo["api"] as $k => $v) { 394 | $task["api"] = $requestInfo["api"][$k]; 395 | $taskid = $serv->task($task); 396 | $this->taskInfo[$fd][$guid]["taskkey"][$taskid] = $k; 397 | } 398 | 399 | return true; 400 | break; 401 | case DoraConst::SW_MODE_NORESULT_MULTI: 402 | foreach ($requestInfo["api"] as $k => $v) { 403 | $task["api"] = $requestInfo["api"][$k]; 404 | $serv->task($task); 405 | } 406 | 407 | $pack = Packet::packFormat($guid, "transfer success.已经成功投递", 100001); 408 | $pack["guid"] = $task["guid"]; 409 | $pack = Packet::packEncode($pack); 410 | 411 | $serv->send($fd, $pack); 412 | 413 | return true; 414 | break; 415 | case DoraConst::SW_CONTROL_CMD: 416 | switch ($requestInfo["api"]["cmd"]["name"]) { 417 | case "getStat": 418 | $pack = Packet::packFormat($guid, "OK", 0, array("server" => $serv->stats(), "logqueue" => LogAgent::getQueueStat())); 419 | $pack = Packet::packEncode($pack); 420 | $serv->send($fd, $pack); 421 | return true; 422 | 423 | break; 424 | case "reloadTask": 425 | $pack = Packet::packFormat($guid, "OK", 0, array("server" => $serv->stats(), "logqueue" => LogAgent::getQueueStat())); 426 | $pack = Packet::packEncode($pack); 427 | $serv->send($fd, $pack); 428 | $serv->reload(true); 429 | return true; 430 | 431 | break; 432 | default: 433 | $pack = Packet::packFormat($guid, "unknow cmd", 100011); 434 | $pack = Packet::packEncode($pack); 435 | 436 | $serv->send($fd, $pack); 437 | unset($this->taskInfo[$fd]); 438 | break; 439 | } 440 | break; 441 | 442 | case DoraConst::SW_MODE_ASYNCRESULT_SINGLE: 443 | $task["api"] = $requestInfo["api"]["one"]; 444 | $taskid = $serv->task($task); 445 | $this->taskInfo[$fd][$guid]["taskkey"][$taskid] = "one"; 446 | 447 | //return success 448 | $pack = Packet::packFormat($guid, "transfer success.已经成功投递", 100001); 449 | $pack = Packet::packEncode($pack); 450 | $serv->send($fd, $pack); 451 | 452 | return true; 453 | break; 454 | case DoraConst::SW_MODE_ASYNCRESULT_MULTI: 455 | foreach ($requestInfo["api"] as $k => $v) { 456 | $task["api"] = $requestInfo["api"][$k]; 457 | $taskid = $serv->task($task); 458 | $this->taskInfo[$fd][$guid]["taskkey"][$taskid] = $k; 459 | } 460 | 461 | //return success 462 | $pack = Packet::packFormat($guid, "transfer success.已经成功投递", 100001); 463 | $pack = Packet::packEncode($pack); 464 | 465 | $serv->send($fd, $pack); 466 | break; 467 | default: 468 | $pack = Packet::packFormat($guid, "unknow task type.未知类型任务", 100002); 469 | $pack = Packet::packEncode($pack); 470 | 471 | $serv->send($fd, $pack); 472 | //unset($this->taskInfo[$fd]); 473 | 474 | return true; 475 | } 476 | 477 | return true; 478 | } 479 | 480 | final public function onTask($serv, $task_id, $from_id, $data) 481 | { 482 | try { 483 | $data["result"] = Packet::packFormat($data["guid"], "OK", 0, $this->doWork($data)); 484 | } catch (\Exception $e) { 485 | $data["result"] = Packet::packFormat($data["guid"], $e->getMessage(), $e->getCode()); 486 | } 487 | 488 | return $data; 489 | } 490 | 491 | abstract public function doWork($param); 492 | 493 | 494 | final public function onWorkerError(\swoole_server $serv, $worker_id, $worker_pid, $exit_code) 495 | { 496 | //using the swoole error log output the error this will output to the swtmp log 497 | var_dump("workererror", array($this->taskInfo, $serv, $worker_id, $worker_pid, $exit_code)); 498 | LogAgent::recordLog(DoraConst::LOG_TYPE_ERROR, "worker_error", __FILE__, __LINE__, array($this->taskInfo, $serv, $worker_id, $worker_pid, $exit_code)); 499 | } 500 | 501 | /** 502 | * 获取当前服务器ip,用于服务发现上报IP 503 | * 504 | * @return string 505 | */ 506 | protected function getLocalIp() 507 | { 508 | if ($this->serverIP == '0.0.0.0' || $this->serverIP == '127.0.0.1') { 509 | $serverIps = swoole_get_local_ip(); 510 | $patternArray = array( 511 | '10\.', 512 | '172\.1[6-9]\.', 513 | '172\.2[0-9]\.', 514 | '172\.31\.', 515 | '192\.168\.' 516 | ); 517 | foreach ($serverIps as $serverIp) { 518 | // 匹配内网IP 519 | if (preg_match('#^' . implode('|', $patternArray) . '#', $serverIp)) { 520 | return $serverIp; 521 | } 522 | } 523 | } 524 | 525 | return $this->serverIP; 526 | } 527 | 528 | //task process finished 529 | final public function onFinish($serv, $task_id, $data) 530 | { 531 | $fd = $data["fd"]; 532 | $guid = $data["guid"]; 533 | 534 | //if the guid not exists .it's mean the api no need return result 535 | if (!isset($this->taskInfo[$fd][$guid])) { 536 | return true; 537 | } 538 | 539 | //get the api key 540 | $key = $this->taskInfo[$fd][$guid]["taskkey"][$task_id]; 541 | 542 | //save the result 543 | $this->taskInfo[$fd][$guid]["result"][$key] = $data["result"]; 544 | 545 | //remove the used taskid 546 | unset($this->taskInfo[$fd][$guid]["taskkey"][$task_id]); 547 | 548 | switch ($data["type"]) { 549 | 550 | case DoraConst::SW_MODE_WAITRESULT_SINGLE: 551 | $packet = Packet::packFormat($guid, "OK", 0, $data["result"]); 552 | $packet = Packet::packEncode($packet, $data["protocol"]); 553 | 554 | $serv->send($fd, $packet); 555 | unset($this->taskInfo[$fd][$guid]); 556 | 557 | return true; 558 | break; 559 | 560 | case DoraConst::SW_MODE_WAITRESULT_MULTI: 561 | if (count($this->taskInfo[$fd][$guid]["taskkey"]) == 0) { 562 | $packet = Packet::packFormat($guid, "OK", 0, $this->taskInfo[$fd][$guid]["result"]); 563 | $packet = Packet::packEncode($packet, $data["protocol"]); 564 | $serv->send($fd, $packet); 565 | //$serv->close($fd); 566 | unset($this->taskInfo[$fd][$guid]); 567 | 568 | return true; 569 | } else { 570 | //multi call task 571 | //not finished 572 | //waiting other result 573 | return true; 574 | } 575 | break; 576 | 577 | case DoraConst::SW_MODE_ASYNCRESULT_SINGLE: 578 | $packet = Packet::packFormat($guid, "OK", 0, $data["result"]); 579 | //flag this is result 580 | $packet["isresult"] = 1; 581 | $packet = Packet::packEncode($packet, $data["protocol"]); 582 | 583 | //sys_get_temp_dir 584 | $serv->send($fd, $packet); 585 | unset($this->taskInfo[$fd][$guid]); 586 | 587 | return true; 588 | break; 589 | case DoraConst::SW_MODE_ASYNCRESULT_MULTI: 590 | if (count($this->taskInfo[$fd][$guid]["taskkey"]) == 0) { 591 | $packet = Packet::packFormat($guid, "OK", 0, $this->taskInfo[$fd][$guid]["result"]); 592 | $packet["isresult"] = 1; 593 | $packet = Packet::packEncode($packet, $data["protocol"]); 594 | $serv->send($fd, $packet); 595 | 596 | unset($this->taskInfo[$fd][$guid]); 597 | 598 | return true; 599 | } else { 600 | //multi call task 601 | //not finished 602 | //waiting other result 603 | return true; 604 | } 605 | break; 606 | default: 607 | // 608 | return true; 609 | break; 610 | } 611 | 612 | } 613 | 614 | //http task finished process 615 | final public function onHttpFinished($serv, $task_id, $data, $response) 616 | { 617 | $fd = $data["fd"]; 618 | $guid = $data["guid"]; 619 | 620 | //if the guid not exists .it's mean the api no need return result 621 | if (!isset($this->taskInfo[$fd][$guid])) { 622 | return true; 623 | } 624 | 625 | //get the api key 626 | $key = $this->taskInfo[$fd][$guid]["taskkey"][$task_id]; 627 | 628 | //save the result 629 | $this->taskInfo[$fd][$guid]["result"][$key] = $data["result"]; 630 | 631 | //remove the used taskid 632 | unset($this->taskInfo[$fd][$guid]["taskkey"][$task_id]); 633 | 634 | switch ($data["type"]) { 635 | case DoraConst::SW_MODE_WAITRESULT_MULTI: 636 | //all task finished 637 | if (count($this->taskInfo[$fd][$guid]["taskkey"]) == 0) { 638 | $packet = Packet::packFormat($guid, "OK", 0, $this->taskInfo[$fd][$guid]["result"]); 639 | $packet = Packet::packEncode($packet, $data["protocol"]); 640 | unset($this->taskInfo[$fd][$guid]); 641 | $response->end($packet); 642 | return true; 643 | } else { 644 | //multi call task 645 | //not finished 646 | //waiting other result 647 | return true; 648 | } 649 | break; 650 | default: 651 | 652 | return true; 653 | break; 654 | } 655 | } 656 | 657 | final public function __destruct() 658 | { 659 | echo "Server Was Shutdown..." . PHP_EOL; 660 | //shutdown 661 | $this->server->shutdown(); 662 | } 663 | 664 | } 665 | --------------------------------------------------------------------------------