Go micro + PHP + consumer

First we build a service with go micro. (for the use of go micro, please refer to the official instance or document)

//Create a new microservice
micro new --type "srv" user-srv

 

Define our service. Here we define two rpc services, Register and User
 1 // modify proto
 2 syntax = "proto3";
 3  4 package go.micro.srv.user;
 5  6 service User {
 7     rpc Register(RegisterRequest) returns (UserInfo) {}
 8     rpc User(UserInfoRequest) returns (UserInfo) {}
 9     rpc Stream(StreamingRequest) returns (stream StreamingResponse) {}
10     rpc PingPong(stream Ping) returns (stream Pong) {}
11 }
12 13 14 message UserInfoRequest {
15     int64 userId  = 1;
16 }
17 18 message RegisterRequest {
19     string username  = 1;
20     string email     = 2;
21     string password  = 3;
22 }
23 24 message UserInfo {
25     int64  id       =  1;
26     string username =  2;
27     string email    =  3;
28 }
29 30 31 32 message StreamingRequest {
33     int64 count = 1;
34 }
35 36 message StreamingResponse {
37     int64 count = 1;
38 }
39 40 message Ping {
41     int64 stroke = 1;
42 }
43 44 message Pong {
45     int64 stroke = 1;
46 }

 

Then generate and execute the following command, we can find two more files in the proto file. This proto is generated for us, which will be used later.

protoc --proto_path=${GOPATH}/src:. --micro_out=. --go_out=. proto/user/user.proto

 

Write our business logic and modify the handle/user.go file

 1 type User struct{}
 2  3 // Call is a single request handler called via client.Call or the generated client code
 4 func (e *User) Register(ctx context.Context, req *user.RegisterRequest, rsp *user.UserInfo) error {
 5     log.Log("Received User.Register request")
 6     rsp.Id    = 1
 7     rsp.Email = req.Email
 8     rsp.Username = req.Username
 9     return nil
10 }
11 12 13 func (e *User) User(ctx context.Context, req *user.UserInfoRequest, rsp *user.UserInfo) error {
14     log.Log("Received User.Register request")
15     rsp.Id    = 1
16     rsp.Email = "741001560@qq.com"
17     rsp.Username = "chensi"
18     return nil
19 }
20 21 // Stream is a server side stream handler called via client.Stream or the generated client code
22 func (e *User) Stream(ctx context.Context, req *user.StreamingRequest, stream user.User_StreamStream) error {
23     log.Logf("Received User.Stream request with count: %d", req.Count)
24 25     for i := 0; i < int(req.Count); i++ {
26         log.Logf("Responding: %d", i)
27         if err := stream.Send(&user.StreamingResponse{
28             Count: int64(i),
29         }); err != nil {
30             return err
31         }
32     }
33 34     return nil
35 }
36 37 // PingPong is a bidirectional stream handler called via client.Stream or the generated client code
38 func (e *User) PingPong(ctx context.Context, stream user.User_PingPongStream) error {
39     for {
40         req, err := stream.Recv()
41         if err != nil {
42             return err
43         }
44         log.Logf("Got ping %v", req.Stroke)
45         if err := stream.Send(&user.Pong{Stroke: req.Stroke}); err != nil {
46             return err
47         }
48     }
49 }
50  

 

Finally, modify our main.go file to find the consumption when the service is used.

 1 func main() {
 2     //initCfg()
 3     // New Service
 4  5     micReg := consul.NewRegistry()
 6  7     service := micro.NewService(
 8         micro.Server(s.NewServer()),
 9         micro.Name("go.micro.srv.user"),
10         micro.Version("latest"),
11         micro.Registry(micReg),
12     )
13 14     // Initialise service
15     service.Init()
16 17     // Run service
18     if err := service.Run(); err != nil {
19         log.Fatal(err)
20     }
21 }
22  

 

We use consumer for microservice discovery. Of course, you need to install consumer first

wget https://releases.hashicorp.com/consul/1.2.0/consul_1.6.1_linux_amd64.zip
unzip consul_1.6.1_linux_amd64.zip
mv consul /usr/local/bin/

 

When we start consumer, because it is on the local virtual machine, we can simply handle

consul agent -dev  -client 0.0.0.0 -ui

 

At this time, you can start the ui of consumer. My local vagrant virtual machine is 192.168.10.100, so what we open is http://192.168.10.100:8500/ui/dc1/services

Start the service of user SRV and find the service registration information of go.micro.srv.user in the consumer.

Here's the code for hyperf. According to the official document installation framework, when installing rpc, you need to select grpc. Note that you need to install php7.2 or above on your system, and 4.3 or above on your swoole. I use the latest homestead, so it is relatively simple to install these dependencies, so it is highly recommended here.

The first time you start it, the official will ask you to modify some parameters of php.ini. You just need to install it.

This part of the process itself refers to the official documents, as for the installation of some extensions, you can Google or Baidu.

After installing the framework, create a new directory of grpc and proto under the root directory, and copy the user.proto file in go micro to the directory of proto in the hyperf project. Then execute the command in the directory

protoc --php_out=plugins=grpc:../grpc user.proto

 

After successful execution, two more folders will be found in the grpc directory.

Next, we start to write client code, create a directory of Grpc and a file of UserClient.php in the app directory of the hyperf project

 1 namespace App\Grpc;
 2  3  4 use Go\Micro\Srv\User\RegisterRequest;
 5 use Go\Micro\Srv\User\UserInfo;
 6 use Hyperf\GrpcClient\BaseClient;
 7  8 class UserClient extends BaseClient
 9 {
10     public function Register(RegisterRequest $argument)
11     {
12         return $this->simpleRequest(
13             '/user.User/Register',
14             $argument,
15             [UserInfo::class, 'decode']
16         );
17     }
18

 

As for the code of this block, the official documents are written in detail. For details, please refer to the official documents.

Create a new route

Router::addRoute(['GET', 'POST', 'HEAD'], '/grpc', 'App\Controller\IndexController@grpc');

Write controller

 1 public function grpc ()
 2 {
 3  4         $client = new \App\Grpc\UserClient('127.0.0.1:9527', [
 5             'credentials' => null,
 6         ]);
 7  8         $request = new RegisterRequest();
 9         $request->setEmail("741001560@qq.com");
10         $request->setUsername("chensi");
11         $request->setPassword("123456");
12 13         /**
14          * @var \Grpc\HiReply $reply
15          */
16         list($reply, $status) = $client->Register($request);
17 18         $message = $reply->getId();
19         return [
20             'id' => $message
21         ];
22     }

 

At this time, you need to load the grpc directory in the root directory. Modify the composer.json file

```

// psr-4 Add two new lines below
"autoload": {
        "psr-4": {
            "App\\": "app/",
            "GPBMetadata\\": "grpc/GPBMetadata",
            "Go\\": "grpc/Go"
        },
        "files": []
    }

 

Then execute the composer dump autoload command. Then start the hyperf project and open the browser to input http://192.168.10.100:9501/grpc Go back. Then we can see the result.

At this time, we will find a problem that consumer is not used at all on the client side. In the code, we still need to indicate our port number. Then take a look at the official documents that actually support consumer, and then transform the code.

Create a new directory of Register under app to create a file consul services.php, and then start to write the code for service discovery. After installing the consumer package, you need to look at the source code yourself because the official consumer package does not have a document. The official has made a simple encapsulation on the api provided by consumer, such as KV, Health, etc., which needs to wear a client when instantiating. A simple example is provided below.

 1 <?php
 2 declare(strict_types=1);
 3  4 namespace App\Register;
 5  6 use Hyperf\Consul\Health;
 7 use Psr\Container\ContainerInterface;
 8 use Hyperf\Guzzle\ClientFactory;
 9 10 class ConsulServices
11 {
12 13     public $servers;
14     private $container;
15 16 17     public function __construct(ContainerInterface $container)
18     {
19         $this->container = $container;
20     }
21 22     public function getServers()
23     {
24         $health = new Health(function ()  {
25             return $this->container->get(ClientFactory::class)->create([
26                 'base_uri' => 'http://127.0.0.1:8500',
27             ]);
28         });
29         $resp = $health->service("go.micro.srv.user");
30         $servers = $resp->json();
31         if (empty($servers)){
32             $this->servers = [];
33         }
34         foreach ($servers as $server) {
35             $this->servers[] = sprintf("%s:%d",$server['Service']['Address'],$server['Service']['Port']);
36         }
37     }
38 }

 

At this time, a problem is found. If you request every time you come, it will cause a lot of load to the consumer. Now that the swoole framework is used, you can request once every time swoole is started, and then save the information found by the service. Modify the configuration file server.

 1 'callbacks' => [
 2 //        SwooleEvent::ON_BEFORE_START => [Hyperf\Framework\Bootstrap\ServerStartCallback::class, 'beforeStart'],
 3         SwooleEvent::ON_BEFORE_START => [\App\Bootstrap\ServerStartCallback::class, 'beforeStart'],
 4         SwooleEvent::ON_WORKER_START => [Hyperf\Framework\Bootstrap\WorkerStartCallback::class, 'onWorkerStart'],
 5         SwooleEvent::ON_PIPE_MESSAGE => [Hyperf\Framework\Bootstrap\PipeMessageCallback::class, 'onPipeMessage'],
 6     ],
 7 Can be in ServerStartCallback Request in class consul Just get the parameters after the service discovery.
 8 
 9 namespace App\Bootstrap;
10 11 use App\Register\ConsulServices;
12 13 class ServerStartCallback
14 {
15     public function beforeStart()
16     {
17         $container = \Hyperf\Utils\ApplicationContext::getContainer();
18         $container->get(ConsulServices::class)->getServers();
19     }
20 }

 

 

Change the original controller

public function grpc ()
{
​
        $container = \Hyperf\Utils\ApplicationContext::getContainer();
        $servers = $container->get(ConsulServices::class)->servers;
        if (empty($servers)) {
            return [
                'errCode' => 1000,
                'msg'     => 'Service does not exist',
            ];
        }
        $key = array_rand($servers,1); // Hahaha a simple load balancing
        $hostname = $servers[$key];
        $client = new \App\Grpc\UserClient($hostname, [
            'credentials' => null,
        ]);
        $request = new RegisterRequest();
        $request->setEmail("741001560@qq.com");
        $request->setUsername("chensi");
        $request->setPassword("123456");
​
        /**
         * @var \Grpc\HiReply $reply
         */
        list($reply, $status) = $client->Register($request);
​
        $message = $reply->getId();
        return [
            'id' => $message
        ];
    }

 

Restart the service, and then refresh the browser. At this time, a simple micro service based on go rpc server and php client is built. Of course, there is no heartbeat mechanism at this time. The hyperf official website provides a timer function. We just need to check the service regularly.

Keywords: PHP JSON Google

Added by 1337hovie on Thu, 07 Nov 2019 08:41:22 +0200