Hprose(High Performance Remote Object Service Engine)

是一款先进的轻量级、跨语言、跨平台、无侵入式、高性能动态远程对象调用引擎库。它不仅简单易用,而且功能强大。

官网:https://hprose.com/

本文将讲解如何使用Hprose go 服务端编写一个微服务,并实现客户端调用。

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

使用Go实现服务端

初始化

git初始化:

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

项目使用go mod管理依赖,请确保安装的Go版本支持该命令。先初始化go.mod文件:

  1. go mod init sample

最终项目目录结构一览:

  1. ├── config
  2.    └── rd.ini
  3. ├── dao
  4. ├── main.go
  5. ├── model
  6. └── util
  7. ├── config.go
  8. └── state.go
  9. ├── service
  10.    └── sample.go
  11. ├── go.mod
  12. ├── go.sum
  13. ├── client_test.go
  14. ├── README.md
  15. ├── php
  16. ├── logs

golang写微服务的好处就是我们可以按照自己理想的目录结构写代码,而无需关注代码 autoload 问题。

配置项

我们使用go-ini/ini来管理配置文件。

项目地址:https://github.com/go-ini/ini

文档地址:https://ini.unknwon.io/

这个库使用起来很简单,文档完善。有2种用法,一种是直接加载配置文件,一种是将配置映射到结构体,使用面向对象的方法获取配置。这里我们采用第二种方案。

首先在conf/里建个配置文件rd.ini:

  1. ListenAddr = 0.0.0.0:8080
  2. [Mysql]
  3. Host = localhost
  4. Port = 3306
  5. User = root
  6. Password =
  7. Database = sample
  8. [Redis]
  9. Host = localhost
  10. Port = 6379
  11. Auth =

编写util/config.go加载配置:

  1. package util
  2. import "github.com/go-ini/ini"
  3. type MysqlCfg struct{
  4. Host string
  5. Port int32
  6. User string
  7. Password string
  8. Database string
  9. }
  10. type RedisCfg struct{
  11. Host string
  12. Port int32
  13. Auth string
  14. }
  15. type Config struct {
  16. ListenAddr string
  17. Mysql MysqlCfg
  18. Redis RedisCfg
  19. }
  20. //全局变量
  21. var Cfg Config
  22. //加载配置
  23. func InitConfig(ConfigFile string) error {
  24. return ini.MapTo(Cfg, ConfigFile)
  25. }

main.go

这里我们需要实现项目初始化、服务注册到RPC并启动一个TCP server。

  1. package main
  2. import (
  3. "flag"
  4. "fmt"
  5. "github.com/hprose/hprose-golang/rpc"
  6. "sample/service"
  7. "sample/util"
  8. )
  9. func hello(name string) string {
  10. return "Hello " + name + "!"
  11. }
  12. func main() {
  13. //解析命令行参数
  14. configFile := flag.String("c", "config/rd.ini", "config file")
  15. flag.Parse()
  16. err := util.InitConfig(*configFile)
  17. if err != nil {
  18. fmt.Printf("load config file fail, err:%v\n", err)
  19. return
  20. }
  21. fmt.Printf("server is running at %s\n", util.Cfg.ListenAddr)
  22. //tcp,推荐
  23. server := rpc.NewTCPServer("tcp4://" + util.Cfg.ListenAddr + "/")
  24. //注册func
  25. server.AddFunction("hello", hello)
  26. //注册struct,命名空间是Sample
  27. server.AddInstanceMethods(&service.SampleService{}, rpc.Options{NameSpace: "Sample"})
  28. err = server.Start()
  29. if err != nil {
  30. fmt.Printf("start server fail, err:%v\n", err)
  31. return
  32. }
  33. }

我们看到,RPC里注册了一个函数hello,还注册了service.SampleService里的所有方法。

注:这里注册服务的时候使用了NameSpace选项从而支持命名空间,这个在官方的WIKI里没有示例说明,很容易忽略。

其中SampleService是一个结构体,定义在service/sample.go文件里:

sample.go

  1. package service
  2. import (
  3. "sample/model"
  4. "sample/util"
  5. )
  6. //定义服务
  7. type SampleService struct {
  8. }
  9. //服务里的方法
  10. func (this *SampleService) GetUserInfo(uid int64) util.State {
  11. var state util.State
  12. if uid <= 0 {
  13. return state.SetErrCode(1001).SetErrMsg("uid不正确").End()
  14. }
  15. var user model.User
  16. user.Id = uid
  17. user.Name = "test"
  18. return state.SetData(user).End()
  19. }

日志

作为一个线上项目,我们需要在业务代码里打印一些日志辅助我们排查问题。日志这里直接使用 beego的日志库logs

  1. package util
  2. import (
  3. "errors"
  4. "fmt"
  5. "github.com/astaxie/beego/logs"
  6. )
  7. var Logger *logs.BeeLogger
  8. func InitLog() error {
  9. Logger = logs.NewLogger(10)
  10. err := Logger.SetLogger(logs.AdapterMultiFile, fmt.Sprintf(`{"filename":"/work/git/hprose-sample/logs/main.log", "daily":true,"maxdays":7,"rotate":true}`))
  11. if err != nil {
  12. return errors.New("init beego log error:" + err.Error())
  13. }
  14. Logger.Async(1000)
  15. return nil
  16. }

这里定义里全局变量Logger,之后可以在项目任意地方使用。

日志选项里filename最好是动态配置,这里为了演示,直接写的固定值。

使用示例:

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

Go测试用例

每个项目都应该写测试用例。下面的用例里,我们将测试上面注册的服务是否正常。

  1. package main
  2. import (
  3. "github.com/hprose/hprose-golang/rpc"
  4. "sample/util"
  5. "testing"
  6. )
  7. //stub:申明服务里拥有的方法
  8. type clientStub struct {
  9. Hello func(string) string
  10. GetUserInfo func(uid int64) util.State
  11. }
  12. //获取一个客户端
  13. func GetClient() *rpc.TCPClient {
  14. return rpc.NewTCPClient("tcp4://127.0.0.1:8050")
  15. }
  16. //测试服务里的方法
  17. func TestSampleService_GetUserInfo(t *testing.T) {
  18. client := GetClient()
  19. defer client.Close()
  20. var stub clientStub
  21. client.UseService(&stub, "Sample") //使用命名空间
  22. rep := stub.GetUserInfo(10001)
  23. if rep.ErrCode > 0 {
  24. t.Error(rep.ErrMsg)
  25. } else {
  26. t.Log(rep.Data)
  27. }
  28. }
  29. //测试普通方法
  30. func TestHello(t *testing.T) {
  31. client := GetClient()
  32. defer client.Close()
  33. var stub clientStub
  34. client.UseService(&stub)
  35. rep := stub.Hello("func")
  36. if rep == "" {
  37. t.Error(rep)
  38. } else {
  39. t.Log(rep)
  40. }
  41. }

运行:

  1. $ go test -v
  2. === RUN TestSampleService_GetUserInfo
  3. --- PASS: TestSampleService_GetUserInfo (0.00s)
  4. client_test.go:31: map[name:test id:10001]
  5. === RUN TestHello
  6. --- PASS: TestHello (0.00s)
  7. client_test.go:47: Hello func!
  8. PASS
  9. ok sample 0.016s

PHP调用

php-client

需要先下载hprose/hprose

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

client.php

  1. <?php
  2. include "vendor/autoload.php";
  3. try{
  4. $TcpServerAddr = "tcp://127.0.0.1:8050";
  5. $client = \Hprose\Socket\Client::create($TcpServerAddr, false);
  6. $service = $client->useService('', 'Sample');
  7. $rep = $service->GetUserInfo(10);
  8. print_r($rep);
  9. } catch (Exception $e){
  10. echo $e->getMessage();
  11. }

运行:

  1. $ php php/client.php
  2. stdClass Object
  3. (
  4. [errCode] => 0
  5. [errMsg] =>
  6. [data] => stdClass Object
  7. (
  8. [id] => 10
  9. [name] => test
  10. )
  11. )

实际使用时最好对该处调用的代码做进一步的封装,例如实现异常捕获、返回码转换、日志打印等等。

编写codetips

本节不是必须的,但是在多人合作的项目上,可以提高沟通效率。

hprose 不支持一键生成各语言的客户端代码(没有IDL支持),在写代码的时候PHP编译器没法提示。我们可以写一个类或者多个类,主要是Model类和Service类:

  • Model类定义字段属性,当传参或者读取返回对象里内容的是,可以使用Get/Set方法;
  • Service类类似于抽象类,仅仅是把go服务端里的方法用PHP定义一个空方法,包括参数类型、返回值类型,这个类并不会真正引入,只是给IDE作为代码提示用的。

示例:

  1. class SampleService
  2. {
  3. /**
  4. * 获取用户信息
  5. * @param int $uid
  6. * @return State
  7. */
  8. public function GetUserInfo(int $uid): State
  9. {
  10. }
  11. }

调用的地方(请使用phpStorm查看提示效果):

  1. /**
  2. * @return SampleService
  3. * @throws Exception
  4. */
  5. function getClient()
  6. {
  7. $TcpServerAddr = "tcp://127.0.0.1:8050";
  8. $client = \Hprose\Socket\Client::create($TcpServerAddr, false);
  9. $service = $client->useService('', 'Sample');
  10. return $service;
  11. }
  12. try {
  13. $client = getClient();
  14. $rep = $client->GetUserInfo(10);
  15. echo $rep->errCode . PHP_EOL;
  16. print_r($rep);
  17. } catch (Exception $e) {
  18. echo $e->getMessage();
  19. }

方法getClient返回的注释里加了@return SampleService,下面调用的$rep->errCode就会有代码提示了。详见:https://github.com/52fhy/hprose-sample/tree/master/php

部署

线上微服务需要后台长期稳定运行,可以使用supervisord工具。

如果还没有安装,请餐参考:Supervisor使用教程

新增一个常驻任务,需要新建配置。

以上述sample为例,新建配置:go_hprose_sample.ini:

  1. [program:go_hprose_sample]
  2. command=/usr/local/bin/go /work/git/hprose-sample/main
  3. priority=999 ; the relative start priority (default 999)
  4. autostart=true ; start at supervisord start (default: true)
  5. autorestart=true ; retstart at unexpected quit (default: true)
  6. startsecs=10 ; number of secs prog must stay running (def. 10)
  7. startretries=3 ; max # of serial start failures (default 3)
  8. exitcodes=0,2 ; 'expected' exit codes for process (default 0,2)
  9. stopsignal=QUIT ; signal used to kill process (default TERM)
  10. stopwaitsecs=10 ; max num secs to wait before SIGKILL (default 10)
  11. user=root ; setuid to this UNIX account to run the program
  12. log_stdout=true
  13. log_stderr=true ; if true, log program stderr (def false)
  14. logfile=/work/git/hprose-sample/logs/supervisor/go_hprose_sample.log
  15. logfile_maxbytes=1MB ; max # logfile bytes b4 rotation (default 50MB)
  16. logfile_backups=10 ; # of logfile backups (default 10)
  17. stdout_logfile_maxbytes=20MB ; stdout 日志文件大小,默认 50MB
  18. stdout_logfile_backups=20 ; stdout 日志文件备份数
  19. stdout_logfile=/work/git/hprose-sample/logs/supervisor/go_hprose_sample.stdout.log

注:上述配置仅供参考,请务必理解配置的含义。

然后启动任务:

  1. supervisorctl reread
  2. supervisorctl update
  3. 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微服务的更多相关文章

  1. QCon技术干货:个推基于Docker和Kubernetes的微服务实践

    2016年伊始,Docker无比兴盛,如今Kubernetes万人瞩目.在这个无比需要创新与速度的时代,由容器.微服务.DevOps构成的云原生席卷整个IT界.在近期举办的QCon全球软件开发大会上, ...

  2. SpringCloud系列二:Restful 基础架构(搭建项目环境、创建 Dept 微服务、客户端调用微服务)

    1.概念:Restful 基础架构 2.具体内容 对于 Rest 基础架构实现处理是 SpringCloud 核心所在,其基本操作形式在 SpringBoot 之中已经有了明确的讲解,那么本次为 了清 ...

  3. 【GoLang】go 微服务框架 && Web框架学习资料

    参考资料: 通过beego快速创建一个Restful风格API项目及API文档自动化:  http://www.cnblogs.com/huligong1234/p/4707282.html Go 语 ...

  4. 在阿里云容器服务上开发基于Docker的Spring Cloud微服务应用

    本文为阿里云容器服务Spring Cloud应用开发系列文章的第一篇. 一.在阿里云容器服务上开发Spring Cloud微服务应用(本文) 二.部署Spring Cloud应用示例 三.服务发现 四 ...

  5. 基于Apollo实现.NET Core微服务统一配置(测试环境-单机)

    一.前言 注:此篇只是为测试环境下的快速入门.后续会给大家带来生产环境下得实战开发. 具体的大家可以去看官方推荐.非常的简单明了.以下介绍引用官方内容: Apollo(阿波罗)是携程框架部门研发的分布 ...

  6. 基于 Apache APISIX 的下一代微服务架构

    2019 年 12 月 14 日,又拍云联合 Apache APISIX 社区举办 API 网关与高性能服务最佳实践丨Open Talk 广州站活动,Apache APISIX PPMC 温铭做了题为 ...

  7. 场景实践:基于 IntelliJ IDEA 插件部署微服务应用

    体验简介 阿里云云起实验室提供相关实验资源,点击前往 本场景指导您把微服务应用部署到 SAE 平台: 登陆 SAE 控制台,基于 jar 包创建应用 基于 IntelliJ IDEA 插件更新 SAE ...

  8. NodeJS 基于 Dapr 构建云原生微服务应用,从 0 到 1 快速上手指南

    Dapr 是一个可移植的.事件驱动的运行时,它使任何开发人员能够轻松构建出弹性的.无状态和有状态的应用程序,并可运行在云平台或边缘计算中,它同时也支持多种编程语言和开发框架.Dapr 确保开发人员专注 ...

  9. 基于 Spring Cloud 完整的微服务架构实战

    本项目是一个基于 Spring Boot.Spring Cloud.Spring Oauth2 和 Spring Cloud Netflix 等框架构建的微服务项目. @作者:Sheldon地址:ht ...

随机推荐

  1. python数据库-MySQL数据库高级查询操作(51)

    一.什么是关系? 1.分析:有这么一组数据关于学生的数据 学号.姓名.年龄.住址.成绩.学科.学科(语文.数学.英语) 我们应该怎么去设计储存这些数据呢? 2.先考虑第一范式:列不可在拆分原则 这里面 ...

  2. ReentrantLock源码的一点总结

    ReentrantLock 是可重入锁,可重入锁的意思就是同一个线程可以重复获得该锁. 如何做到可重复获得该锁?计数器实现. 第一次加锁,cas比较是不是0,是0设置为1,并设置当前拥有锁的线程: 第 ...

  3. 大话Spark(9)-源码之TaskScheduler

    上篇文章讲到DAGScheduler会把job划分为多个Stage,每个Stage中都会创建一批Task,然后把Task封装为TaskSet提交到TaskScheduler. 这里我们来一起看下Tas ...

  4. Linux 安装 lanmp

    Lanmp介绍 lanmp一键安装包是wdlinux官网2010年底开始推出的web应用环境的快速简易安装包. 执行一个脚本,整个环境就安装完成就可使用,快速,方便易用,安全稳定 lanmp一键安装包 ...

  5. Spring MVC中使用FastJson自定义注解

    最近在做.net转译成Java.其中遇到一个很蛋疼的问题.以前.net属性名都是首字母大写.造成返回给客户端的JSON字符串属性名称都是首字母大写.为了和前端对接我们以前都是如下图所示做法 publi ...

  6. python基础知识三 字典-dict + 菜中菜

    3.7字典:dict+菜中菜 1.简介 ​ 无序,可修改,用于存储数据,大量,比列表快,将数据和数据之间关联 ​ 定义:dict1 = {'cx':10,'liwenhu':80,'zhangyu': ...

  7. 入职两个月,WPF开发感想

    1 .新工作,新开始 2.WPF初次接触以及学习MVVM开发模式 3.后台数据操作,ORACLE 存储过程(边做边学) 4.总结 4.1工作开发中的小问题 ,遇到的坑:  4.2 解决的问题,学校到的 ...

  8. c语言进阶7-结构体

    一.  结构体: 在程序设计基础当中我们学习了变量,变量可以节省使用空间相对于常量而言,大家来看下表: 学号 姓名 职位 性别 数学 英语 语文 总成绩 1 刘琳 班委 女 50 61 56 167 ...

  9. [leetcode] 19. Remove Nth Node From End of List (Medium)

    原题链接 删除单向链表的倒数第n个结点. 思路: 用两个索引一前一后,同时遍历,当后一个索引值为null时,此时前一个索引表示的节点即为要删除的节点. Runtime: 13 ms, faster t ...

  10. python课堂整理12---递归

    一.递归特性 1.必须有一个明确的结束条件 2.每次进入更深一层递归时,问题规模相比上次递归都应有所减少 3.递归效率不高,递归层次过多会导致栈溢出(在计算机中,函数调用是通过栈(stack)这种数据 ...