首先我們用go-micro構建一個服務。(關於go-micro的使用可以參照官方範例或者文件)
//新建一個微服務 micro new --type "srv" user-srv
定義我們的服務,這裡定義兩個rpc服務,Register和User
// 修改proto syntax = "proto3"; package go.micro.srv.user; service User { rpc Register(RegisterRequest) returns (UserInfo) {} rpc User(UserInfoRequest) returns (UserInfo) {} rpc Stream(StreamingRequest) returns (stream StreamingResponse) {} rpc PingPong(stream Ping) returns (stream Pong) {} } message UserInfoRequest { int64 userId = 1; } message RegisterRequest { string username = 1; string email = 2; string password = 3; } message UserInfo { int64 id = 1; string username = 2; string email = 3; } message StreamingRequest { int64 count = 1; } message StreamingResponse { int64 count = 1; } message Ping { int64 stroke = 1; } message Pong { int64 stroke = 1; }
然後生成執行下面命令我們就可以發現在proto檔案中多出兩個檔案。這個proto為我們生成的,後面會用到。
protoc --proto_path=${GOPATH}/src:. --micro_out=. --go_out=. proto/user/user.proto
寫我們的業務邏輯,修改handle/user.go檔案
type User struct{} // Call is a single request handler called via client.Call or the generated client code func (e *User) Register(ctx context.Context, req *user.RegisterRequest, rsp *user.UserInfo) error { log.Log("Received User.Register request") rsp.Id = 1 rsp.Email = req.Email rsp.Username = req.Username return nil } func (e *User) User(ctx context.Context, req *user.UserInfoRequest, rsp *user.UserInfo) error { log.Log("Received User.Register request") rsp.Id = 1 rsp.Email = "[email protected]" rsp.Username = "chensi" return nil } // Stream is a server side stream handler called via client.Stream or the generated client code func (e *User) Stream(ctx context.Context, req *user.StreamingRequest, stream user.User_StreamStream) error { log.Logf("Received User.Stream request with count: %d", req.Count) for i := 0; i < int(req.Count); i++ { log.Logf("Responding: %d", i) if err := stream.Send(&user.StreamingResponse{ Count: int64(i), }); err != nil { return err } } return nil } // PingPong is a bidirectional stream handler called via client.Stream or the generated client code func (e *User) PingPong(ctx context.Context, stream user.User_PingPongStream) error { for { req, err := stream.Recv() if err != nil { return err } log.Logf("Got ping %v", req.Stroke) if err := stream.Send(&user.Pong{Stroke: req.Stroke}); err != nil { return err } } }
最後修改我們的main.go檔案,服務發現使用時consul。
func main() { //initCfg() // New Service micReg := consul.NewRegistry() service := micro.NewService( micro.Server(s.NewServer()), micro.Name("go.micro.srv.user"), micro.Version("latest"), micro.Registry(micReg), ) // Initialise service service.Init() // Run service if err := service.Run(); err != nil { log.Fatal(err) } }
我們使用consul做微服務發現,當然首先你需要安裝consul
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/
啟動consul的時候由於在是本地虛擬機器上面,所以我們可以簡單處理
consul agent -dev -client 0.0.0.0 -ui
這時候可以啟動consul的ui了,我本地vagrant的虛擬機器192.168.10.100,那麼我們開啟的是http://192.168.10.100:8500/ui/dc1/services
啟動user-srv的服務發現consul裡面出現 go.micro.srv.user 的服務註冊資訊了
下面來寫hyperf的程式碼了。按照官方文件安裝框架,安裝的時候rpc需要選擇grpc,需要注意的是你的系統上面需要安裝php7.2以上的版本,swoole版本也需要4.3的版本以上,我用的是最新homestead,所以相對而言安裝這些依賴比較簡單,所以在此強烈推薦。
第一次啟動時候官方會要求修改一些php.ini的引數,大家安裝要求走就是了。
這部分的流程自己參照官方文件,至於一些擴充套件的安裝可以谷歌或者百度。
安裝好框架之後再根目錄下面新建一個grpc和proto的目錄,把go-micro裡面user.proto檔案複製到hyperf專案的proto的目錄之下。然後在目錄下執行命令
protoc --php_out=plugins=grpc:../grpc user.proto
執行成功之後會發現在grpc目錄下多出兩個資料夾。
接下來我們開始編寫client的程式碼,在hyperf專案的app目錄下新建一個Grpc的目錄並且新建一個UserClient.php的檔案
namespace AppGrpc; use GoMicroSrvUserRegisterRequest; use GoMicroSrvUserUserInfo; use HyperfGrpcClientBaseClient; class UserClient extends BaseClient { public function Register(RegisterRequest $argument) { return $this->simpleRequest( '/user.User/Register', $argument, [UserInfo::class, 'decode'] ); }
關於這一塊的程式碼,其實官方文件寫得特別詳細,具體可以參照官方文件。
新建一個路由
Router::addRoute(['GET', 'POST', 'HEAD'], '/grpc', '[email protected]');
編寫控制器
public function grpc () { $client = new AppGrpcUserClient('127.0.0.1:9527', [ 'credentials' => null, ]); $request = new RegisterRequest(); $request->setEmail("[email protected]"); $request->setUsername("chensi"); $request->setPassword("123456"); /** * @var GrpcHiReply $reply */ list($reply, $status) = $client->Register($request); $message = $reply->getId(); return [ 'id' => $message ]; }
這時候還需要吧根目錄下的grpc目錄載入進來。修改composer.json檔案
``` // psr-4 下面新增兩個行 "autoload": { "psr-4": { "App": "app/", "GPBMetadata": "grpc/GPBMetadata", "Go": "grpc/Go" }, "files": [] }
然後執行composer dump-autoload命令。然後啟動hyperf專案,開啟瀏覽器輸入http://192.168.10.100:9501/grpc回車,這時候我們就能看到結果了。
這時候我們會發現一個問題,那就是consul在client端壓根沒用到,在程式碼中我們還是需要指明我們的埠號。然後再看看官方文件其實是支援consul的,那麼將程式碼改造下。
在app下新建一個Register的目錄建立一個檔案ConsulServices.php,然後開始編寫服務發現的程式碼,安裝consul包以後,由於官方提供的consul包沒有文件所以需要自己去看原始碼。官方在consul提供的api上面做了簡單的封裝,如KV、Health等,在範例化話的時候需要穿一個用戶端過去。下面提供一個簡單的範例。
<?php declare(strict_types=1); namespace AppRegister; use HyperfConsulHealth; use PsrContainerContainerInterface; use HyperfGuzzleClientFactory; class ConsulServices { public $servers; private $container; public function __construct(ContainerInterface $container) { $this->container = $container; } public function getServers() { $health = new Health(function () { return $this->container->get(ClientFactory::class)->create([ 'base_uri' => 'http://127.0.0.1:8500', ]); }); $resp = $health->service("go.micro.srv.user"); $servers = $resp->json(); if (empty($servers)){ $this->servers = []; } foreach ($servers as $server) { $this->servers[] = sprintf("%s:%d",$server['Service']['Address'],$server['Service']['Port']); } } }
這時候發現一個問題如果每次請求過來都去請求一次必然給consul造成很大的負荷。既然用到了swoole框架可以在每次swoole啟動的時候去請求一次,然後把服務發現的資訊存起來。修改組態檔server。
'callbacks' => [ // SwooleEvent::ON_BEFORE_START => [HyperfFrameworkBootstrapServerStartCallback::class, 'beforeStart'], SwooleEvent::ON_BEFORE_START => [AppBootstrapServerStartCallback::class, 'beforeStart'], SwooleEvent::ON_WORKER_START => [HyperfFrameworkBootstrapWorkerStartCallback::class, 'onWorkerStart'], SwooleEvent::ON_PIPE_MESSAGE => [HyperfFrameworkBootstrapPipeMessageCallback::class, 'onPipeMessage'], ], 可以在ServerStartCallback類裡面請求consul進行服務發現 後面拿到引數就好了。 namespace AppBootstrap; use AppRegisterConsulServices; class ServerStartCallback { public function beforeStart() { $container = HyperfUtilsApplicationContext::getContainer(); $container->get(ConsulServices::class)->getServers(); } }
改造一下原來的控制器
public function grpc () { $container = HyperfUtilsApplicationContext::getContainer(); $servers = $container->get(ConsulServices::class)->servers; if (empty($servers)) { return [ 'errCode' => 1000, 'msg' => '服務不存在', ]; } $key = array_rand($servers,1); // 哈哈哈一個簡單的負載均衡 $hostname = $servers[$key]; $client = new AppGrpcUserClient($hostname, [ 'credentials' => null, ]); $request = new RegisterRequest(); $request->setEmail("[email protected]"); $request->setUsername("chensi"); $request->setPassword("123456"); /** * @var GrpcHiReply $reply */ list($reply, $status) = $client->Register($request); $message = $reply->getId(); return [ 'id' => $message ]; }
重新啟動服務,這時候然後重新整理瀏覽器試試。這時候一個簡單基於go rpc server和php client的微服務就搭建完成了。當然了這時候還沒有心跳機制,hyperf官網提供了一個定時器的功能,我們定時去刷服務發現就好了。
以上就是go-micro+php+consul實現簡單的微服務的詳細內容,更多請關注TW511.COM其它相關文章!