-
-
Notifications
You must be signed in to change notification settings - Fork 27
Document
使用两种语言来编写一个项目具有天生的复杂性。为了易于使用,GoTask项目的目标之一就是极力降低引入Go语言的成本。
建议将Go的构建方式打包到Hyperf或其他Swoole框架的启动命令中。这样便不会出现Go语言和PHP不同步的现象。缺点是启动会变慢一些。
为了方便进行Demo演示,也可以在PHP脚本开始处利用PHP的exec
函数进行打包。
对于Hyperf用户,GoTask已经集成了自动构建Go项目的选项。
//config/autoload/gotask.php
'go_build' => [
'enable' => false,
'workdir' => BASE_PATH . '/gotask',
'command' => 'go build -o ../bin/app cmd/app.go',
],
在将enable
设为true
后启动项目时将会在默认路径进行自动打包。
不建议生产环境使用该选项。
通过Swoole进程管理Go边车存在几个好处,首先,Go项目的生命周期将完全由Swoole接管。Swoole启动Go启动,Swoole停则Go停。当Go崩溃时,Swoole会将Go拉起。其次,Go将继承Swoole的环境变量,这使得我们可以共享配置信息。
在非Hyperf项目中,可以手动启动Go边车或使用其他框架推荐的方式封装。
$process = new Process(function (Process $process) {
$process->exec(__DIR__ . '/app', ['-address', ADDR]);
});
$process->start();
在Hyperf的项目中,只要配置gotask.enable
处于开启状态,边车就是自动启停的。在0.2.0版本以后,Go边车的stdout也默认重定向到PHP侧,由PHP统一输出,这使得Go边车可以复用PHP的日志配置,比如通过monolog对日志进行分页等。
相关配置:
//config/autoload/gotask.php
'go_log' => [
'redirect' => true,
'level' => 'info',
],
PHP将数据用json序列化后传递给Go。Go将json反序列化成指定方法中约束的类型。Go运算完成后,通过同样的方式将结果序列化投递给PHP,PHP反序列化拿到数组。
通过添加PAYLOAD_RAW
flag,也可以不序列化,直接投递字节。
PHP侧一共有四种GoTask实现,具有共同的interface,可以根据情况选择。
interface GoTask
{
function call($method, $payload = null, $flag = 0);
}
限单协程使用 | 跨协程使用 | |
---|---|---|
通过Socket投递 | SocketIPCSender | SocketGoTask |
通过标准输入输出投递 | PipeIPCSender | PipeGoTask |
在Hyperf中,通过容器注入GoTask,注入的实例是SocketGoTask,当做单例使用即可。
$socketIPCSender = new SocketIPCSender('127.0.0.1:6001');
$pipeIPCSender = new PipeIPCSender($process); //Swoole\Process
$socketGoTask = new SocketGoTask($connectionPool); //Hyperf\GoTask\GoTaskConnectionPool
$pipeGoTask = new PipeGoTask($process); //Swoole\Process
/** @var SocketGoTask $gotask (Hyperf only) */
$goTask = ApplicationContext::getContainer()->get(GoTask::class);
go侧需要至少一个net/rpc风格的Struct作为服务容器,
type T struct {}
// net/rpc structs must have method like this:
func (t *T) MethodName(argType T1, replyType *T2) error
调用gotask.Register(new(T))
将其注册,然后执行gotast.Run()
启动服务。
// Register a net/rpc compatible service
func Register(receiver interface{}) error
// Run the sidecar, receive any fatal errors.
func Run() error
文档: https://pkg.go.dev/github.com/hyperf/gotask/v2/pkg/gotask?tab=doc
interface GoTask
{
function call($method, $payload = null, $flag = 0);
}
- $method: IPC函数名,形如'Struct.Func'。
- $payload: 需要传递的信息。任何可以JSON序列化的值均可。
- $flag: IPC协议的Flag。
- Hyperf\GoTask\GoTask::PAYLOAD_RAW:不自动序列化payload,作为[]bytes传递给Go
func (app *App) HelloString(payload interface{}, result *interface{}) err error
- app: go注册的服务struct
- payload:接收的信息。可以约束类型。
- result:返回的信息。
- err: 如果不为nil,则会转化为PHP侧的异常抛出
- 服务interface与net/rpc兼容。
$task->call('App.HelloStruct', [
'firstName' => 'LeBron',
'lastName' => 'James',
'id' => 23,
]));
type Name struct {
Id int `json:"id"`
FirstName string `json:"firstName"`
LastName string `json:"lastName"`
}
func (a *App) HelloStruct(name Name, r *interface{}) error {
*r = map[string]Name{
"hello": name,
}
return nil
}
$task->call('App.HelloBytes', base64_encode('My Bytes'), GoTask::PAYLOAD_RAW);
func (a *App) HelloBytes(name []byte, r *[]byte) error {
reader := base64.NewDecoder(base64.StdEncoding, bytes.NewReader(name))
*r, _ = ioutil.ReadAll(reader)
return nil
}
PHP通过call($method, $payload = null, $flag = 0)
方法调用Go服务时,由于$method只是一个字符串,这意味着无法提供IDE提示和静态分析。我们可以对PHP类进行进一步的封装,比如:
<?php
declare(strict_types=1);
namespace App\GoTask;
use Hyperf\GoTask\GoTask;
use Hyperf\GoTask\GoTaskProxy;
class Worker extends GoTaskProxy
{
/**
* @param string $payload
* @return bool
*/
public function test(string $payload) : bool
{
return parent::call("Worker.Test", $payload, 0);
}
/**
* @param $payload
* @return mixed
*/
public function debug($payload)
{
return parent::call("Worker.Debug", $payload, GoTask::PAYLOAD_RAW);
}
}`
1.0.0后,支持通过go语言反射自动生成上述PHP代码封装。
执行
./bin/app -reflect
即可在标准输出生成GoTask对应的PHP代码。
如需直接生成到代码目录,可以在Go服务上加PHPPath和PHPNamespace两个公开变量。
type App struct{
PHPPath string
PHPNamespace string
}
v2.1.0 起,Hyperf用户如果需要在命令行中向Go进程投递,只需实现WithGoTask接口。
class MyCommand extends HyperfCommand implements WithGoTask {}
在某些时候,Go可能也需要调用PHP的函数。在0.2.0版本以后加入了go2php的支持。
非Hyperf项目中,请在Go的启动参数中传入go2php的socket地址。
$process = new Process(function (Process $process) {
sleep(1);
$process->exec(__DIR__ . '/app', ['-go2php-address', ADDR]);
}, false, 0, true);
$process->start();
run(function () {
$server = new SocketIPCReceiver(ADDR);
$server->start();
});
在Hyperf项目中,只要配置一下就可以了。
'go2php' => [
'enable' => false,
'address' => \Hyperf\GoTask\ConfigProvider::address(),
],
在Go边车中,可以通过FQCN调用PHP对象的方法。
client, err := gotask.NewAutoClient()
if err != nil {
log.Fatalln(err)
}
defer client.Close()
var res []byte
err = client.Call("Namespace\\Example::HelloString", "Hyperf", &res)
if err != nil {
log.Fatalln(err)
}
fmt.Println(string(res))
这里因为PHP的socket address已经通过启动参数传入,所以
NewAutoClient()
就不用再传地址了。如果不知道地址,可以使用NewClient(conn net.Conn)
来创建客户端。
<?php
namespace Namespace;
class Example
{
public function HelloString(string $payload)
{
return "Hello, {$payload}!";
}
}
在Hyperf框架中,该对象从容器中获取的且为长生命周期,可以使用依赖注入。
go2php使用的协议与php2go完全一致,不再赘述。
在 0.3.0 后go语言新增"github.com/hyperf/gotask/v2/pkg/log"包。Hyperf下开启gotask.go2php.enable
后可用。
log模块与默认的日志stdout重定向不同,是通过socket将日志投递给PHP。遵循PSR-3规范,功能更为丰富。可以理解为Hyperf日志组件的Go实现。
文档:https://pkg.go.dev/github.com/hyperf/gotask/v2/pkg/log?tab=doc
在 0.3.0 后go语言新增"github.com/hyperf/gotask/v2/pkg/config"包。Hyperf下开启gotask.go2php.enable
后可用。
用于从Go边车获取PHP的Config配置。可以理解为Hyperf配置组件的Go实现。
文档:https://pkg.go.dev/github.com/hyperf/gotask/v2/pkg/config?tab=doc
在 2.1.0 后新增"github.com/hyperf/gotask/v2/pkg/mongo_client"包。通过该模块封装Mongo查询,使得PHP侧获得如下API:
<?php
namespace App\Controller;
use Hyperf\GoTask\MongoClient\MongoClient;
class IndexController
{
public function index(MongoClient $client)
{
$col = $client->my_database->my_col;
$col->insertOne(['gender' => 'male', 'age' => 18]);
$col->insertMany([['gender' => 'male', 'age' => 20], ['gender' => 'female', 'age' => 18]]);
$col->countDocuments();
$col->findOne(['gender' => 'male']);
$col->find(['gender' => 'male'], ['skip' => 1, 'limit' => 1]);
$col->updateOne(['gender' => 'male'], ['$inc' => ['age' => 1]]);
$col->updateMany(['gender' => 'male'], ['$inc' => ['age' => 1]]);
$col->replaceOne(['gender' => 'female'], ['gender' => 'female', 'age' => 15]);
$col->aggregate([
['$match' => ['gender' => 'male']],
['$group' => ['_id' => '$gender', 'total' => ['$sum' => '$age']]],
]);
$col->deleteOne(['gender' => 'male']);
$col->deleteMany(['age' => 15]);
$col->drop();
// if there is a command not yet supported, use runCommand or runCommandCursor.
$client->my_database->runCommand(['ping' => 1]);
return $client->my_database->runCommandCursor(['listCollections' => 1]);
}
}
文档:https://pkg.go.dev/github.com/hyperf/gotask/v2/pkg/mongo_client?tab=doc