koala 学习笔记

koala 学习笔记

相关技术栈:linux、golang

编写IDL

在业务项目目录下创建helloworld目录,进入该目录,然后创建编辑hello.thrift,该文件用来生成基本框架。输入以下内容:

1
2
3
4
5
6
7
struct Result {      //定义了一个Result结构体,为接口返回的结构体数据
1: string response,
}

service HelloService { //定义了一个服务,只有一个接口↓
Result HelloWorld(1:string name); //HelloWorld,该接口接收一个string参数,返回一个Result结构体,见上。
}

生成框架代码

键入如下命令生成框架代码,请下载最新版本的thrift工具

1
/path/to/soa_tools/thrift --gen go --packagePath micode.be.xiaomi.com/systech/helloworld hello.thrift

编译执行

执行 ./build.sh 进行编译,编译成功后,生成的程序在bin目录下。

自动构建

启动自动构建程序(auto_build.sh),自动构建程序会实时检测项目目录下源码的是否变更,如果有变更将自动进行编译、重启程序。

了解框架代码结构

生成的框架代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
.
├── auto_build.sh //自动构建脚本
├── bin
│ └── HelloService
├── build.sh //编译脚本
├── config
│ ├── scm_config.ini
│ ├── scm_config.ini.online.env //线上配置
│ └── scm_config.ini.test.env //测试环境配置
├── hello.thrift //接口描述文件
├── package.sh //编译打包脚本,使用请参考:http://mis.n.mi.com/koala/publish/package.html
├── README
├── run //启动脚本
├── run.sh //带supervise的启动脚本
├── framework //框架基础代码(无需更改)
│ └── app
│ ├── app.go
│ ├── config.go
│ ├── generate.go
│ ├── global.go
│ ├── initDb.go
│ ├── initRabbitmq.go
│ ├── initRedis.go
│ ├── initRedisMock.go
│ ├── initSqlMock.go
│ ├── manager.go
│ ├── plugin.go
│ ├── register.go
│ ├── rpc.go
│ └── user_config.go
├── hello //网络相关基础代码,无需更改
│ ├── constants.go
│ ├── helloservice.go
│ ├── helloservice_host_wrapper.go
│ ├── hello_service-remote
│ │ └── hello_service-remote.go
│ ├── helloservice_wrapper.go
│ └── ttypes.go
├── main
│ ├── HelloService.go
│ ├── HelloWorld_handler.go //HelloWorld的入口文件,需要业务实现
│ ├── hook.go //框架各个阶段的回调接口,可以实现自定义功能
│ └── main.go
└── model //业务model层
└── supervise
└── supervise
└── glide.yaml // 依赖说明文件
└── glide.lock // 依赖版本锁文件

对于 thrift IDL 中定义的每一个接口,会在 main 目录单独一个代码文件,并在接口名后面加上_handler后缀。 例如,对于 hello.thrift 定义的 HelloWorld 接口,生成的实现文件为 main/HelloWorld_handler.go。

实现 HelloWorld 接口

打开 main/HelloWorld_handler.go文件,框架生成的源代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
package main

import (
"micode.be.xiaomi.com/systech/helloworld/hello"
"micode.be.xiaomi.com/systech/soa/context"
)
//ctx 用来记录请求在框架运行中当中的上下文,比如traceid以及appid等公共信息
//name 也就是我们IDL中定义的参数
func (p *HelloServiceHandler) HelloWorld(ctx *context.XContext, name string) (r *hello.Result_, errRet error) {

return
}

该接口返回一个hello.Result结构,以及若有错误产生,返回一个errRet。
我们记得,hello.Result_结构体的定义如下:

1
2
3
type Result_ struct {
Response string `thrift:"response,1" json:"response"`
}

当处理正常时,返回一个Response字符串。因此,我们的HelloWorld接口非常简单,实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import (
"micode.be.xiaomi.com/systech/helloworld/hello"
"micode.be.xiaomi.com/systech/soa/context"
"fmt"
)

func (p *HelloServiceHandler) HelloWorld(context *context.XContext, name string) (r *hello.Result_, err error) {

r = &hello.Result_{}
r.Response = fmt.Sprintf("hello world,%s", name)

return
}

配置组名和服务名

启动服务之前必须先在 config/scm_config.ini 中配置组名和服务名。
打开 config/scm_config.ini,在group和service两个字段,填入相应的信息。如下所示:

1
2
3
4
5
6
7
8
9
;基础配置
[xbase]
config_type=local #local or etcd
config_addr=http://etcd.test.mi.com #配置中心地址
;项目所在的小组名,一定要配置,不配置将会panic,详情请参见mis.n.mi.com
group=systech
;项目的服务名,一定要配置,不配置将会panic,详情请参见mis.n.mi.com
service=hello
....

(注)服务名:服务开发完了之后,通过SOA平台进行注册,注册服务的时候会分配一个唯一的服务名。

启动服务HelloService

使用如下命令进行启动:

1
./bin/HelloService

调用和测试

生成SDK

通过以下命令,生成Golang的SDK:

1
/path/to/soa_tools/thrift --gen go --packagePath micode.be.xiaomi.com/your/project ./hello.thrift

生成的SDK在当前目录sdk/v3/目录下。

如果基于 koala v3 开发的项目需要生成 sdk 给 koala v1 或 v2 的项目使用,请使用命令:

1
2
/path/to/soa_tools/thrift --gen go --v1 ./hello.thrift
/path/to/soa_tools/thrift --gen go --v2 ./hello.thrift

生成的 SDK 在 sdk/v1/ sdk/v2 目录下。

编写测试代码

golang版rpc包括两种调用方式:

  • 通过IP调用:一般在开发过程,为了简化调用流程,我们可以直接指定后端服务ip和port进行调用。 在项目目录下(位置任意,只要import了…/helloworld/sdk/v3/hello包即可)新建main.go文件,编辑以下代码:
1
2
3
4
5
6
7
8
9
10
11
package main

import (
"fmt"
"micode.be.xiaomi.com/systech/helloworld/sdk/v3/hello"
)

func main() {
client:=hello.NewHelloServiceClientHostWrapper("127.0.0.1",12508,"xxx")
fmt.Println(client.HelloWorld("xiaoming"))
}

由于我们代码依赖koala基础库,为了简化编译步骤,我们采用脚本进行编译运行,在当前目录下 打开run.sh,输入以下内容:

1
2
3
4
5
#!/bin/bash

curdir=`pwd`
glide init && glide up
go run $curdir/main.go

运行run.sh,即可看到程序运行结果。

  • 通过服务名调用:在测试或线上环境中,我们需要在SOA平台上进行订阅xm_ip_service,然后通过服务名进行调用。 调用代码如下,保存为main.go:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package main

import(
"fmt"
"micode.be.xiaomi.com/your/project/sdk/v3/ip"
"micode.be.xiaomi.com/systech/soa/xrpc"
"micode.be.xiaomi.com/systech/soa/thrift"
)

func init() {

//初始化rpc组件,必须要到平台上订阅xm_ip_service服务
//在本例中,xm_ip_service_client已经订阅了xm_ip_service服务,所以能够调用成功
//参数1:group 订阅者所在的组名
//参数2:service 订阅者所在的服务名(项目名)
//参数3:etcd_host etcd地址,
1. 测试环境为http://etcd.test.mi.com
1. 线上环境为http://soa01.etcd.b2c.srv:4001
_, err := xrpc.NewXRpcDefault("misite", "xm_ip_service_client", "http://etcd.test.mi.com")
if (err != nil) {
fmt.Println(err)
}
}

func main() {

//参数1:serviceName 调用方的服务名
//参数2:logid 用来标识一次请求,一般从上游获取,没有可以自己随机生成
//参数3:rpcId 填0即可
context := thrift.NewXContext("xm_ip_service_client", 1000, 0)

//1. 参数1:serviceName 服务提供方的服务名
//2. 参数2:ctx 调用上下文
client := ip.NewIPServiceClientWrapper2("xm_ip_service", context)

ipList := make([]string, 2)
ipList = append(ipList, "100.200.39.4")
fmt.Println(client.QueryGeoInfoByIP(ipList))
}

运行测试程序

正常情况下,将会返回类似如下结果:

1
Result_({Response:hello world,liuxing1}) <nil>