基于hprose-golang创建RPC微服务
Hprose(High Performance Remote Object Service Engine)
是一款先进的轻量级、跨语言、跨平台、无侵入式、高性能动态远程对象调用引擎库。它不仅简单易用,而且功能强大。
本文将讲解如何使用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
基于hprose-golang创建RPC微服务的更多相关文章
- QCon技术干货:个推基于Docker和Kubernetes的微服务实践
2016年伊始,Docker无比兴盛,如今Kubernetes万人瞩目.在这个无比需要创新与速度的时代,由容器.微服务.DevOps构成的云原生席卷整个IT界.在近期举办的QCon全球软件开发大会上, ...
- SpringCloud系列二:Restful 基础架构(搭建项目环境、创建 Dept 微服务、客户端调用微服务)
1.概念:Restful 基础架构 2.具体内容 对于 Rest 基础架构实现处理是 SpringCloud 核心所在,其基本操作形式在 SpringBoot 之中已经有了明确的讲解,那么本次为 了清 ...
- 【GoLang】go 微服务框架 && Web框架学习资料
参考资料: 通过beego快速创建一个Restful风格API项目及API文档自动化: http://www.cnblogs.com/huligong1234/p/4707282.html Go 语 ...
- 在阿里云容器服务上开发基于Docker的Spring Cloud微服务应用
本文为阿里云容器服务Spring Cloud应用开发系列文章的第一篇. 一.在阿里云容器服务上开发Spring Cloud微服务应用(本文) 二.部署Spring Cloud应用示例 三.服务发现 四 ...
- 基于Apollo实现.NET Core微服务统一配置(测试环境-单机)
一.前言 注:此篇只是为测试环境下的快速入门.后续会给大家带来生产环境下得实战开发. 具体的大家可以去看官方推荐.非常的简单明了.以下介绍引用官方内容: Apollo(阿波罗)是携程框架部门研发的分布 ...
- 基于 Apache APISIX 的下一代微服务架构
2019 年 12 月 14 日,又拍云联合 Apache APISIX 社区举办 API 网关与高性能服务最佳实践丨Open Talk 广州站活动,Apache APISIX PPMC 温铭做了题为 ...
- 场景实践:基于 IntelliJ IDEA 插件部署微服务应用
体验简介 阿里云云起实验室提供相关实验资源,点击前往 本场景指导您把微服务应用部署到 SAE 平台: 登陆 SAE 控制台,基于 jar 包创建应用 基于 IntelliJ IDEA 插件更新 SAE ...
- NodeJS 基于 Dapr 构建云原生微服务应用,从 0 到 1 快速上手指南
Dapr 是一个可移植的.事件驱动的运行时,它使任何开发人员能够轻松构建出弹性的.无状态和有状态的应用程序,并可运行在云平台或边缘计算中,它同时也支持多种编程语言和开发框架.Dapr 确保开发人员专注 ...
- 基于 Spring Cloud 完整的微服务架构实战
本项目是一个基于 Spring Boot.Spring Cloud.Spring Oauth2 和 Spring Cloud Netflix 等框架构建的微服务项目. @作者:Sheldon地址:ht ...
随机推荐
- 001_html基本结构
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...
- Java学习笔记之---内部类
Java学习笔记之---内部类 (一)成员内部类 内部类在外部使用时,无法直接实例化,需要借助外部类信息才能实例化 内部类的访问修饰符可以任意,但是访问范围会受到影响 内部类可以直接访问外部类的成员, ...
- Spark 中 RDD的运行机制
1. RDD 的设计与运行原理 Spark 的核心是建立在统一的抽象 RDD 之上,基于 RDD 的转换和行动操作使得 Spark 的各个组件可以无缝进行集成,从而在同一个应用程序中完成大数据计算任务 ...
- linux服务器无telnet等测试工具,测试http+json服务连通性
1. 问题描述: 1.公司内部服务器需要通过http接口方式访问另一公司内部接口服务器. 2.申请信息安全开通访问权限,但是只能开通到服务器+端口号,例如:192.168.1:8080,无ping权限 ...
- 不调用free会内存泄露吗?
内存泄露的概念大家可以自行百度下,本文不做阐述.本文要讲的是在程序中分配了内存,但是最后没有使用free()函数来释放这块内存,会导致内存泄露吗?比如有如下代码: #include <stdio ...
- Git介绍以及安装
Git的作者是大名鼎鼎的Linux之父Linus Torvalds. Git是一个分布式的版本控制系统,Git三个字母的含义可以理解为"Global Information Tracker& ...
- 基于 HTML5 WebGL 的民航客机飞行监控系统
前言 前些日子出差,在飞机上看到头顶的监控面板,除了播放电视剧和广告之外,还会时不时的切换到一个飞机航行的监控系统,不过整个监控系统让人感到有一点点的简陋,所以我就突发奇想制作了一个采用 HT for ...
- [03] HEVD 内核漏洞之UAF
作者:huity出处:https://www.cnblogs.com/huity35/p/11240997.html版权:本文版权归作者所有.文章在博客园.个人博客同时发布.转载:欢迎转载,但未经作者 ...
- python下载报错:Could not install packages due to an EnvironmentError: [WinError 5] 拒绝访问
更新pip模块的版本:python -m pip install --upgrade pip 但是遇到报错提示: Could not install packages due to an Enviro ...
- 基于drone构建CI-CD系统
kubernetes集群三步安装 CI 概述 用一个可描述的配置定义整个工作流 程序员是很懒的动物,所以想各种办法解决重复劳动的问题,如果你的工作流中还在重复一些事,那么可能就得想想如何优化了 持续集 ...