天天看點

基于hprose-golang建立RPC微服務

Hprose

(High Performance Remote Object Service Engine)

是一款先進的輕量級、跨語言、跨平台、無侵入式、高性能動态遠端對象調用引擎庫。它不僅簡單易用,而且功能強大。

官網:https://hprose.com/

本文将講解如何使用

Hprose

go 服務端編寫一個微服務,并實作用戶端調用。

本文的涉及的項目代碼托管在github:https://github.com/52fhy/hprose-sample 。

使用Go實作服務端

初始化

git初始化:

git init
echo "main" >> .gitignore 
echo "# hprose-sample" >> README.md           

項目使用

go mod

管理依賴,請確定安裝的Go版本支援該指令。先初始化

go.mod

檔案:

go mod init sample           

最終項目目錄結構一覽:

├── config
│   └── rd.ini
├── dao
├── main.go
├── model
└── util
    ├── config.go
    └── state.go
├── service
│   └── sample.go
├── go.mod
├── go.sum
├── client_test.go
├── README.md
├── php
├── logs           

golang寫微服務的好處就是我們可以按照自己理想的目錄結構寫代碼,而無需關注代碼

autoload

問題。

配置項

我們使用

go-ini/ini

來管理配置檔案。

項目位址:https://github.com/go-ini/ini

文檔位址:https://ini.unknwon.io/

這個庫使用起來很簡單,文檔完善。有2種用法,一種是直接加載配置檔案,一種是将配置映射到結構體,使用面向對象的方法擷取配置。這裡我們采用第二種方案。

首先在

conf/

裡建個配置檔案

rd.ini

:

ListenAddr = 0.0.0.0:8080

[Mysql]
Host = localhost
Port = 3306
User = root
Password =
Database = sample

[Redis]
Host = localhost
Port = 6379
Auth =           

編寫

util/config.go

加載配置:

package util

import "github.com/go-ini/ini"

type MysqlCfg struct{
    Host string
    Port int32
    User string
    Password string
    Database string
}

type RedisCfg struct{
    Host string
    Port int32
    Auth string
}

type Config struct {
    ListenAddr string
    Mysql MysqlCfg
    Redis RedisCfg
}

//全局變量
var Cfg Config

//加載配置
func InitConfig(ConfigFile string) error {
    return ini.MapTo(Cfg, ConfigFile)
}           

main.go

這裡我們需要實作項目初始化、服務注冊到RPC并啟動一個TCP server。

package main

import (
    "flag"
    "fmt"
    "github.com/hprose/hprose-golang/rpc"
    "sample/service"
    "sample/util"
)

func hello(name string) string {
    return "Hello " + name + "!"
}

func main() {
    //解析指令行參數
    configFile := flag.String("c", "config/rd.ini", "config file")
    flag.Parse()

    err := util.InitConfig(*configFile)
    if err != nil {
        fmt.Printf("load config file fail, err:%v\n", err)
        return
    }

    fmt.Printf("server is running at %s\n", util.Cfg.ListenAddr)

    //tcp,推薦
    server := rpc.NewTCPServer("tcp4://" + util.Cfg.ListenAddr + "/")

    //注冊func
    server.AddFunction("hello", hello)

    //注冊struct,命名空間是Sample
    server.AddInstanceMethods(&service.SampleService{}, rpc.Options{NameSpace: "Sample"})
    err = server.Start()
    if err != nil {
        fmt.Printf("start server fail, err:%v\n", err)
        return
    }
}           

我們看到,RPC裡注冊了一個函數

hello

,還注冊了

service.SampleService

裡的所有方法。

注:這裡注冊服務的時候使用了

NameSpace

選項進而支援命名空間,這個在官方的WIKI裡沒有示例說明,很容易忽略。

其中

SampleService

是一個結構體,定義在

service/sample.go

檔案裡:

sample.go

package service

import (
    "sample/model"
    "sample/util"
)

//定義服務
type SampleService struct {
}

//服務裡的方法
func (this *SampleService) GetUserInfo(uid int64) util.State {
    var state util.State

    if uid <= 0 {
        return state.SetErrCode(1001).SetErrMsg("uid不正确").End()
    }

    var user model.User
    user.Id = uid
    user.Name = "test"
    return state.SetData(user).End()
}
           

日志

作為一個線上項目,我們需要在業務代碼裡列印一些日志輔助我們排查問題。日志這裡直接使用

beego

的日志庫

logs

package util

import (
    "errors"
    "fmt"
    "github.com/astaxie/beego/logs"
)

var Logger *logs.BeeLogger

func InitLog() error {
    Logger = logs.NewLogger(10)

    err := Logger.SetLogger(logs.AdapterMultiFile, fmt.Sprintf(`{"filename":"/work/git/hprose-sample/logs/main.log", "daily":true,"maxdays":7,"rotate":true}`))
    if err != nil {
        return errors.New("init beego log error:" + err.Error())
    }
    Logger.Async(1000)
    return nil
}
           

這裡定義裡全局變量

Logger

,之後可以在項目任意地方使用。

日志選項裡

filename

最好是動态配置,這裡為了示範,直接寫的固定值。

使用示例:

if uid <= 0 {
    util.Logger.Debug("uid error. uid:%d", uid)
}           

Go測試用例

每個項目都應該寫測試用例。下面的用例裡,我們将測試上面注冊的服務是否正常。

package main

import (
    "github.com/hprose/hprose-golang/rpc"
    "sample/util"
    "testing"
)

//stub:申明服務裡擁有的方法
type clientStub struct {
    Hello       func(string) string
    GetUserInfo func(uid int64) util.State
}

//擷取一個用戶端
func GetClient() *rpc.TCPClient {
    return rpc.NewTCPClient("tcp4://127.0.0.1:8050")
}

//測試服務裡的方法
func TestSampleService_GetUserInfo(t *testing.T) {
    client := GetClient()

    defer client.Close()
    var stub clientStub
    client.UseService(&stub, "Sample") //使用命名空間

    rep := stub.GetUserInfo(10001)
    if rep.ErrCode > 0 {
        t.Error(rep.ErrMsg)
    } else {
        t.Log(rep.Data)
    }
}

//測試普通方法
func TestHello(t *testing.T) {
    client := GetClient()

    defer client.Close()
    var stub clientStub
    client.UseService(&stub)

    rep := stub.Hello("func")
    if rep == "" {
        t.Error(rep)
    } else {
        t.Log(rep)
    }
}           

運作:

$ go test -v

=== RUN   TestSampleService_GetUserInfo
--- PASS: TestSampleService_GetUserInfo (0.00s)
    client_test.go:31: map[name:test id:10001]
=== RUN   TestHello
--- PASS: TestHello (0.00s)
    client_test.go:47: Hello func!
PASS
ok      sample  0.016s
           

PHP調用

php-client

需要先下載下傳

hprose/hprose

composer config repo.packagist composer https://packagist.phpcomposer.com
composer require "hprose/hprose:^2.0"           

client.php

<?php

include "vendor/autoload.php";

try{
    $TcpServerAddr = "tcp://127.0.0.1:8050";
    $client = \Hprose\Socket\Client::create($TcpServerAddr, false);
    $service = $client->useService('', 'Sample');
    $rep = $service->GetUserInfo(10);
    print_r($rep);
} catch (Exception $e){
    echo $e->getMessage();
}
           

運作:

$ php php/client.php 

stdClass Object
(
    [errCode] => 0
    [errMsg] => 
    [data] => stdClass Object
        (
            [id] => 10
            [name] => test
        )

)
           

實際使用時最好對該處調用的代碼做進一步的封裝,例如實作異常捕獲、傳回碼轉換、日志列印等等。

編寫codetips

本節不是必須的,但是在多人合作的項目上,可以提高溝通效率。

hprose 不支援一鍵生成各語言的用戶端代碼(沒有IDL支援),在寫代碼的時候PHP編譯器沒法提示。我們可以寫一個類或者多個類,主要是Model類和Service類:

  • Model類定義字段屬性,當傳參或者讀取傳回對象裡内容的是,可以使用

    Get/Set

    方法;
  • Service類類似于抽象類,僅僅是把go服務端裡的方法用PHP定義一個空方法,包括參數類型、傳回值類型,這個類并不會真正引入,隻是給IDE作為代碼提示用的。

示例:

class SampleService
{
    /**
     * 擷取使用者資訊
     * @param int $uid
     * @return State
     */
    public function GetUserInfo(int $uid): State
    {
    }

}           

調用的地方(請使用phpStorm檢視提示效果):

/**
 * @return SampleService
 * @throws Exception
 */
function getClient()
{
    $TcpServerAddr = "tcp://127.0.0.1:8050";
    $client = \Hprose\Socket\Client::create($TcpServerAddr, false);
    $service = $client->useService('', 'Sample');
    return $service;
}

try {
    $client = getClient();
    $rep = $client->GetUserInfo(10);
    echo $rep->errCode . PHP_EOL;
    print_r($rep);
} catch (Exception $e) {
    echo $e->getMessage();
}           

方法

getClient

傳回的注釋裡加了

@return SampleService

,下面調用的

$rep->errCode

就會有代碼提示了。詳見:https://github.com/52fhy/hprose-sample/tree/master/php 。

部署

線上微服務需要背景長期穩定運作,可以使用

supervisord

工具。

如果還沒有安裝,請餐參考:Supervisor使用教程。

新增一個常駐任務,需要建立配置。

以上述sample為例,建立配置:

go_hprose_sample.ini

:

[program:go_hprose_sample]
command=/usr/local/bin/go  /work/git/hprose-sample/main
priority=999                ; the relative start priority (default 999)
autostart=true              ; start at supervisord start (default: true)
autorestart=true            ; retstart at unexpected quit (default: true)
startsecs=10                ; number of secs prog must stay running (def. 10)
startretries=3              ; max # of serial start failures (default 3)
exitcodes=0,2               ; 'expected' exit codes for process (default 0,2)
stopsignal=QUIT             ; signal used to kill process (default TERM)
stopwaitsecs=10             ; max num secs to wait before SIGKILL (default 10)
user=root                 ; setuid to this UNIX account to run the program
log_stdout=true
log_stderr=true             ; if true, log program stderr (def false)
logfile=/work/git/hprose-sample/logs/supervisor/go_hprose_sample.log
logfile_maxbytes=1MB        ; max # logfile bytes b4 rotation (default 50MB)
logfile_backups=10          ; # of logfile backups (default 10)
stdout_logfile_maxbytes=20MB  ; stdout 日志檔案大小,預設 50MB
stdout_logfile_backups=20     ; stdout 日志檔案備份數
stdout_logfile=/work/git/hprose-sample/logs/supervisor/go_hprose_sample.stdout.log           
注:上述配置僅供參考,請務必了解配置的含義。

然後啟動任務:

supervisorctl reread
supervisorctl update
supervisorctl start go_hprose_sample           

線上部署最少要2台機器,組成負載均衡。這樣當更新的時候,可以一台一台的上線,避免服務暫停。

Hprose 總結

優點:

  • 輕量級、跨語言、跨平台
  • 更少的網絡傳輸量,使用二進制傳輸協定
  • 簡單,跟着官方提供的例子很快就能掌握基本的使用
  • 文檔完善

缺點:

  • 不支援IDL(接口描述語言),是以無法一鍵生成用戶端調用代碼,需要手動維護

參考

1、Supervisor使用教程 - 飛鴻影 - 部落格園

https://www.cnblogs.com/52fhy/p/10161253.html

2、Home · hprose/hprose-golang Wiki

https://github.com/hprose/hprose-golang/wiki

3、go-ini/ini: 超贊的 Go 語言 INI 檔案操作

https://ini.unknwon.io/

4、golang中os/exec包用法

https://www.cnblogs.com/vijayfly/p/6102470.html