从零开始基于gothrift创建一个RPC服务

Thrift 是一种被广泛使用的 rpc 框架,可以比较灵活的定义数据结构和函数输入输出参数,并且可以跨语言调用。为了保证服务接口的统一性和可维护性,我们需要在最开始就制定一系列规范并严格遵守,降低后续维护成本。Thrift开发流程是:先定义IDL,使用thrift工具生成目标语言接口(interface)代码,然后进行开发。 官网: http://thrift.apache.org/ github:https://github.com/apache/thr... 安装Thrift将Thrift IDL文件编译成目标代码需要安装Thrift二进制工具。 Mac建议直接使用brew安装,节省时间: brew install thrift安装后查看版本: $ thrift -versionThrift version 0.12.0也可以下载源码安装,参考:http://thrift.apache.org/docs...。 源码地址:http://www.apache.org/dyn/clo... CentOS需下载源码安装,参考:http://thrift.apache.org/docs...。 Debian/Ubuntu需下载源码安装,先安装依赖:http://thrift.apache.org/docs...,然后安装thrift:http://thrift.apache.org/docs...。 Windows可以直接下载二进制包。地址:http://www.apache.org/dyn/clo...。 实战该小节我们通过一个例子,讲述如何使用Thrift快速开发出一个RPC微服务,涉及到Golang服务端、Golang客户端、PHP客户端、PHP服务端。项目名就叫做thrift-sample,代码托管在 https://github.com/52fhy/thri...。 推荐使用Golang服务端实现微服务,PHP客户端实现调用。 编写thrift IDLthrift├── Service.thrift└── User.thriftUser.thrift namespace go Samplenamespace php Samplestruct User { 1:required i32 id; 2:required string name; 3:required string avatar; 4:required string address; 5:required string mobile;}struct UserList { 1:required list<User> userList; 2:required i32 page; 3:required i32 limit;}Service.thrift include "User.thrift"namespace go Samplenamespace php Sampletypedef map<string, string> Datastruct Response { 1:required i32 errCode; //错误码 2:required string errMsg; //错误信息 3:required Data data;}//定义服务service Greeter { Response SayHello( 1:required User.User user ) Response GetUser( 1:required i32 uid )}说明: 1、namespace用于标记各语言的命名空间或包名。每个语言都需要单独声明。2、struct在PHP里相当于class,golang里还是struct。 3、service在PHP里相当于interface,golang里是interface。service里定义的方法必须由服务端实现。 4、typedef和c语言里的用法一致,用于重新定义类型的名称。 5、struct里每个都是由1:required i32 errCode;结构组成,分表代表标识符、是否可选、类型、名称。单个struct里标识符不能重复,required表示该属性不能为空,i32表示int32。 ...

July 13, 2019 · 6 min · jiezi

RPC一thrift-框架-go语言开发

1、写 thrift 文件定义好 service :方法、入参出参2、生成 代码并发包3、编写 server 端实现4、本地启动server端,进行测试5、打包启动 rpc 服务 具体实现:1、写 thrift 文件定义好 service 以及 入参出参的 struct namespace 定义生成的文件目录和名称。 namespace py test_thrift.action_cardnamespace go test_thrift.action_cardnamespace java test_thrift.action_cardenum ResouceCode { ALPHA = 1, BETA = 2}序号:类型 名称struct BannerParams { 1: i64 member_id; 2: i32 num;}service MemberService { Banner get_banner(1: BannerParams params)}生成 py 或者 go 代码,并发布镜像gen: thrift -r --gen go:package_prefix=github.com/serenity/gen-go/ ./thrift_files/test/test.thrift 2、编写 server 端实现 package serviceimport ( "github.com/serenity/gen-go/test_thrift/member" "github.com/serenity/golang/pkg/action_card/controller" "github.com/serenity/golang/pkg/common/model")func GetMemberServiceProcessor(msgChan chan model.Message) *member.MemberServiceProcessor { // service - api memberService := controller.NewMemberService() // processor return member.NewMemberServiceProcessor(memberService)}3、客户端调用测试 ...

June 25, 2019 · 1 min · jiezi

thrift-增加跨语言的调用链TODO

日常在工作中会遇到这样的问题,PHP 通过 thrift 接口调用 java 服务,java 通过dubbo 接口调用java 的另外一个服务。 但是到了出错的时候,会遇到以下问题。 或者 没有形成完整的调用过程路径,或者无法追踪来源。0. 一个小例子在上个星期中,突然我负责管理的一个java 服务(内部服务),短时间内大流量预警。 通过 grafana 查看请求量,确实异常。 问题来到了进一步确认流量异常的接口,或者来源。 通过 ops 日志系统,固定时间段。可以看到流量异常阶段,更多的访问了 一个dubbo 接口 确认调用来源想通过 graylog 的方式,查看某个 dubbo 接口,一段时间访问的排名。 类似这种 问题 :dubbo 经过和运维沟通不好做流量和日志监控如果,有一个调用链,把这种跨语言的调用打通一下,那么做日志错误跟踪岂不是很方便 1. thrift在此之前,先推荐一篇文章 Uber分布式追踪系统Jaeger使用介绍和案例【PHP Hprose Go】 100. 致敬关联: 1. https://fredal.xin/hunter-wit...

June 19, 2019 · 1 min · jiezi

Thrift RPC 系列教程(5)—— 接口设计篇:struct & enum设计

好的接口,如同漂亮的美女,是人都会多看一眼。一个示例比如,要我们设计一个 User。那很简单,典型的 class 嘛,按照 OOP 的套路走就行了,于是:struct User{ 1: string id; 2: string name; 3: i64 age; 4: string address; …}这样的设计,不能说特别糟糕,也不能说特别完美 。实际上,我们可以让这个 class 更加清晰直观一点。实际开发中的思考实际中的 class,需求其实还是挺多的,比如属性也许会很多(比如十几项属性?)需要某个属性,有默认值需要某个属性,可设置可不设置需要某个属性,强制设置,并提供很直观的提示等等等等好的语言设计者,应该会考虑到这些。幸好,作为典型实用派的Thrift,考虑到了这些。充分利用好 Thrift 提供的特性在 Thrift 的官方文档中,说明这些特性。见:https://thrift.apache.org/doc… 。无非是从语法层面上,提供一些关键词,让读代码的人,维护代码的人,能够有个清晰的认知。比如,上面我们的 User,大概可以变成这样:struct User{ 1: required string id; // 需要明确指定ID 2: required string name; // 需要明确指定name 3: optional i64 age; // 年龄, 可填可不填 4: optional string address; 5: optional bool isAdmin=false; // 默认属性, 需要设置时,就去设置 …}这样,哪些应该有,哪些可以没有,都是一目了然的。enum 的设计由于Thrift 并未为 enum 提供很多功能,所以 enum 写起来就很简单了,注意命名风格即可:enum GenderEnum { MALE = 1, FEMALE = 2,} ...

November 11, 2018 · 1 min · jiezi

Thrift RPC 系列教程(2)——全局变量&全局常量

全局变量,就像不可控的孩子,你放心你的孩子总是消失你的视野范围内吗?为啥有全局变量通常来说,『全局变量』被视为一种不好的编程风格,因为它实在是不可控,怕它的状态不知道在什么时候就被改变了,根源就是『人们害怕未知』。但是如果是全局常量,则问题不大,反正变来变去,还是同一个东西。Thrift支持全局常量Thrift中的常量,其实就是模块级别的常量,语法上,自然是简单得不得了:// 普通基础类型const i32 INT32CONSTANT = 9853// 复杂容器类型const map<string,string> MAPCONSTANT = {‘hello’:‘world’, ‘goodnight’:‘moon’};const set<string> NAMES = {“name1”, “name2”};const list<string> NAMES_LIST = [“name1”, “name2”];全局常量有啥用?如同你使用其他编程语言一样,全局常量在它们里面有啥用,那么在Thrift中就有啥用。比如我有一个需求,需要为系统的每个端口起一个名字,以统一规范,统一给每个微服务系统使用。那么我们就可以:const map<string, i64> PORTNAME_PORT = { “MYSQL_PORT”: 8806, “REDIS_PORT”: 7379,};不过,总体而言,『全局XX』带来了方便,也带来了问题,没有什么事情是非得使用『全局』去解决的,所以总体上不是 非常推荐的风格。

November 11, 2018 · 1 min · jiezi

Thrift RPC 系列教程(3)——模块化

模块化是好事,以及,它让我联想到了 C 语言,以及它那如同平原一样的命名空间。为什么需要模块化所谓『模块化』,是一种很自然的事情,体现了『分而治之』的思想。坦白来说,这是一个无需过多讨论的话题。如果你写过C 语言,就会对它那『近乎平坦』的命名空间,感到熟悉。所有的函数,变量都在同一个全局命名空间(static 类型的除外,事实上,static 函数,是C语言中少数的模块化手段之一)。如果你 include一个东西,你都不知道你在干嘛,you know nothing。比如:#include <stdio.h>#include <stdlib.h>有时候我也许能记得 printf 在 stdio里面,但大部分时候我的记性不怎么好,也不爱记忆这种东西。而且,为了防止命名冲突,大部分时候,我们都要给我们的函数,加各种命名前缀,比如,写一个链表实现的时候,会出现诸如List_Add(List* l, void* item) List_Remove(List* l, void* item) 这样的东西。这和上古时代的『匈牙利命名法则』(感兴趣的朋友,可以百科搜索一下),何其相似,各种无聊的 btn 前缀,不明觉厉的变量命名。关键是,在比较现代化的编程语言中,或者比较现代化的编程IDE中,偶尔能看到这样的命名风格,这时让人有一种莫名的,穿越上的喜感。Thrift中的模块化幸好,Thrift 虽然借用了 include 这个关键字,但是没有搞成 C 语言那样(也许用 import 是更好的选择?)。用法如下:// 导入一个模块,模块的访问空间,就是文件名// 这里及其明确清醒,甚至需要写清楚文件名后缀include “shared.thrift”// 使用// 必须要加命名空间前缀,否则访问不到对应的要素service Calculator extends shared.SharedService {}和这个使用感觉,最相同的,就是 Go 语言了,既简单又明确。

November 11, 2018 · 1 min · jiezi

Thrift RPC 系列教程(4)——源码目录结构组织

Thrift 代码就是编程代码。是代码,就应该有良好的工程组织,并且,单独git仓库、版本管理,都是必不可少的。前面我们简单总结了一些 Thrift 的一些基础知识点,但无非是一些细节层面的东西,所谓『细枝末节』也。而一些东西,想要用得舒服,工程组织架构,一样都不能少。然而,代码组织架构,如同『一千个读者,就有一千本《红楼梦》』一样,每个人都有自己的理解,无非是选择问题,无非是口味问题。就我而言,一般喜欢小的、分而治之的东西,所以,我一般有如下的诉求:独立的git仓库管理清晰的目录命名自动化src/ services/ 对外暴露接口 XXService.thrift structs/ 对象,class person.thrift enums/ 公共枚举 constants/ 公共常量 exceptions/ 公共异常,有些团队,RPC不喜欢使用异常,所以这个目录可以没有compile_thrift_to_cpp.sh 编译脚本compile_thrift_to_py.sh看得出,上面的目录组织,喜欢直接使用 Thrift 中的名词概念,可以谓之『就近原则』。适合一些“不愿记忆,或者记忆力有点不足”的人(like me)。这样,利用自动化集成工具,要么将编译后的编程语言源码放到内部公共依赖源中,要么直接集成到项目目录下。

November 11, 2018 · 1 min · jiezi

Thrift RPC 系列教程(1)——Thrift语言

Thrift不是严格意义上的编程语言,但是却胜过很多编程语言,充满了美感。基础数据类型Thrift 这门编程语言提供了如下几种基础的数据类型:bool: A boolean value (true or false)byte: An 8-bit signed integeri16: A 16-bit signed integeri32: A 32-bit signed integeri64: A 64-bit signed integerdouble: A 64-bit floating point numberstring: A text string encoded using UTF-8 encoding一般来说,我们也就常用那几种,就像在其他日常编程语言中一样。比如我,基本就是这『三板斧』:booli32 ( 现在逐步常用 i64 了,因为性能啥的,我基本不是第一时间关注的)doublestring复杂数据类型(容器)再让我们来看看,Thrift提供了哪些容器类型:list: An ordered list of elements. Translates to an STL vector, Java ArrayList, native arrays in scripting languages, etc.set: An unordered set of unique elements. Translates to an STL set, Java HashSet, set in Python, etc. Note: PHP does not support sets, so it is treated similar to a Listmap: A map of strictly unique keys to values. Translates to an STL map, Java HashMap, PHP associative array, Python/Ruby dictionary, etc. While defaults are provided, the type mappings are not explicitly fixed. Custom code generator directives have been added to allow substitution of custom types in various destination languages简直了C++ STL 一毛一样,命名都差不多。唯独 list 这种数据结构,其实是『动态数组』,单从名字上看,很容易让人联系到链表,这在其他的编程语言中,也有这个现象,比如Python 中的也叫做 list 。class,即struct稍微正常一点的语言,对 OOP 的支持,自然是必不可少的,我觉得,最好直接提供 class 这个关键字,尽量有清晰的语义。但是 Thrift 只有一个 struct,基本上和 C 的struct,一样,也是功能少得可怜,不过考虑到它仅仅是一个中间语言,自然是情有可原的。我们来看一下,一个写得好的 struct,应该如何定义,做到既清晰又完备的:struct Person { 1: required string name; // 必须字段,很明确 2: required i64 age; 3: optional string addr; // 可选字段 4: optional string defaultValue = “DEFAULT”; // 默认字段 5: string otherValue; // 不是很明确!}interface,即service在『面向接口编程』的原则下,『接口』是一个很重要的因素。有的人称之为函数,有的人称为方法,本文我们统称为『方法』。在Thrift中,定义接口是一件很简单的事情( 摘自官网的一个示例 ):// 接口, 还可以继承, 也许我们有时候可以搞个 『BaseService』 之类的,不过我很少用到。service Calculator extends shared.SharedService { // 正常方法,和C++这类传统语言,基本一模一样。 void ping(), i32 add(1:i32 num1, 2:i32 num2), i32 calculate(1:i32 logid, 2:Work w) throws (1:InvalidOperation ouch), // 特殊方法,基本很少用到了,在我有限的经历中,只使用过一次,读者没必要关注它 oneway void zip()}异常,即exception关于异常,在Thrift中就像定义 struct 一样,因为exception从概念上讲,也是一种class,所谓『万事万物皆对象』嘛。不过现在我们用『exception』这个关键字,也正好符合我前文所讲的,清晰的语义。让我们看看Thrift中的异常是如何定义的:exception InvalidOperation { 1: i32 whatOp, 2: string why}枚举枚举这个东西,真的是太重要了,和前面的exception类似,它也不过是一种class而已。不过Thrift中只支持枚举 int 值,比较遗憾,其实很多时候,对枚举的要求,我们是很丰富的,比如支持 枚举 string。Thrift中枚举如下:enum Operation { // 功能着实比较孱弱 ADD = 1, SUBTRACT = 2, MULTIPLY = 3, DIVIDE = 4}如果喜欢我的文章,请关注我的公众号:『浮生若梦的编程』。 也可以关注我的简书专栏:『浮生若梦的编程』。 或者加入我的知识星球,『浮生若梦的编程』,获取更多干货。 ...

November 6, 2018 · 2 min · jiezi