RPC服务框架探索之Thrift

2023-03-02,,,,

前言
架构服务化后,需要实现一套方便调用各服务的框架,现在开源如日中天,优先会寻找开源实现,如果没有合适自家公司业务的,才会考虑从零开发,尤其是一切以KPI为准绳的公司,谁会跟钱过不去?N个月之前,公司大神就开始调研了,最后选中了Thrift这个RPC服务框架。使用不熟悉的技术,我会感到很恐惧,它就相当于一个黑盒,我对它一无所知,它是如何运转的?出了问题该如何解决?带着一丝不安,查阅了相关技术文档。

RPC
很早之前听说过soap,restful api,rpc之类的服务协议,一直都没有机会深入实践,对它们理解的不够深。它们的目的都是提供本地调用远程服务的能力,只是实现方式不同而已。RPC(remote procedure call)意思是远程过程调用,编码时可以把它当作本地方法一样调用,无需关心内部的实现细节,对于调用方很友好很简单。我查阅资料,发现RPC之类的东西很早很早以前就出现了,存在即是合理的,肯定有它的理由。跟本地调用相比有什么优点缺点呢?根据查阅的资料以及自己的理解总结如下:
优势
1 提高系统吞吐能力
2 业务服务解耦
3 更易构建服务分布式集群
4 基础服务重用更方便
劣势
1 因网络开销方法执行时间更长
2 系统更复杂对运维挑战很大
3 排错成本增加
4 数据序列化消耗CPU资源
现在是移动互联时代,数据无时无刻不在产生着,随着时间的推移,随着用户数的增加,随着更多业务的开展,随着功能迭代的频繁,只有业务服务化只有更易构建分布式集群的架构才能应对这些挑战,毕竟单台服务器的能力是有限的,即使是IOE这种高端设备。尤其是现在地下黑产如此猖獗的今天,时不时的遭受到类DDOS攻击,可以方便扩展节点的架构是多么重要,否则会死的很惨。
因此通过RPC协议实现微服务架构是利大于弊的。

Thrift
Thrift是RPC服务协议的一种实现,它是由Facebook开发,2007年开源并且2008年成为Apache开源项目。它的实现目标是支持多语言跨平台简单易使用高性能对业务开发透明屏蔽实现细节专注业务开发。大部分语言提供了类库,可以让开发人员只专注在业务接口实现部分。

实现原理
它由传输层,协议层,处理器,服务层所组成,每个部分不互相依赖,职责单一。
传输层:流协议传输数据,支持socket,http,文件等媒介
协议层:数据封包解包,支持binary,compact,json等
处理器:接口代理,请求转发给相应接口处理
服务层:组装传输层,协议层,处理器,提供RPC服务
客户端调用接口-》客户端数据封包-》传输层-》服端解包-》服端处理器-》服端接口处理。处理完成遵循同样的链路响应。

服端网络模型
简单说明,不做深入了解。
1 单进程
2 单进程多线程
3 单进程事件驱动

IDL
IDL(interface description language)接口描述语言,它包含简单数据类型定义,复杂数据类型结构体定义,复杂数据类型列表定义集合定义,异常类型定义,命名空间声明,接口定义规范等,通过这些规范的组合可以定义好任意复杂的接口,定义好之后,使用IDL编译器可以生成指定语言源码,传输层协议层处理器这些代码会自动生成,只需要专注业务接口具体实现即可。具体类型定义参考官方文档吧。

安装
安装很简单,不同的系统官方文档都有说明,不需要完全死板按照步骤执行,相关依赖如果存在可以跳过相应步骤。安装成功之后就可以使用thrift命令编译IDL文件了。

命令
定义好的IDL文件名以.thrift结尾,通过thrift编译。命令参数说明如下:

 Usage: thrift [options] file
Options:
-version Print the compiler version
-o dir Set the output directory for gen-* packages
(default: current directory)
-out dir Set the ouput location for generated files.
(no gen-* folder will be created)
-I dir Add a directory to the list of directories
searched for include directives
-nowarn Suppress all compiler warnings (BAD!)
-strict Strict compiler warnings on
-v[erbose] Verbose mode
-r[ecurse] Also generate included files
-debug Parse debug trace to stdout
--allow-neg-keys Allow negative field keys (Used to preserve protocol
compatibility with older .thrift files)
--allow-64bit-consts Do not print warnings about using -bit constants
--gen STR Generate code with a dynamically-registered generator.
STR has the form language[:key1=val1[,key2[,key3=val3]]].
Keys and values are options passed to the generator.
Many options will not require values. Options related to audit operation
--audit OldFile Old Thrift file to be audited with 'file'
-Iold dir Add a directory to the list of directories
searched for include directives for old thrift file
-Inew dir Add a directory to the list of directories
searched for include directives for new thrift file Available generators (and options):
as3 (AS3):
bindable: Add [bindable] metadata to all the struct classes.
c_glib (C, using GLib):
cocoa (Cocoa):
log_unexpected: Log every time an unexpected field ID or type is encountered.
debug_descriptions:
Allow use of debugDescription so the app can add description via a cateogory/extension
validate_required:
Throws exception if any required field is not set.
async_clients: Generate clients which invoke asynchronously via block syntax.
pods: Generate imports in Cocopods framework format.
promise_kit: Generate clients which invoke asynchronously via promises.
cpp (C++):
cob_style: Generate "Continuation OBject"-style classes.
no_client_completion:
Omit calls to completion__() in CobClient class.
no_default_operators:
Omits generation of default operators ==, != and <
templates: Generate templatized reader/writer methods.
pure_enums: Generate pure enums instead of wrapper classes.
include_prefix: Use full include paths in generated files.
moveable_types: Generate move constructors and assignment operators.
csharp (C#):
async: Adds Async support using Task.Run.
wcf: Adds bindings for WCF to generated classes.
serial: Add serialization support to generated classes.
nullable: Use nullable types for properties.
hashcode: Generate a hashcode and equals implementation for classes.
union: Use new union typing, which includes a static read function for union types.
d (D):
dart (Dart):
library_name: Optional override for library name.
library_prefix: Generate code that can be used within an existing library.
Use a dot-separated string, e.g. "my_parent_lib.src.gen"
pubspec_lib: Optional override for thrift lib dependency in pubspec.yaml,
e.g. "thrift: 0.x.x". Use a pipe delimiter to separate lines,
e.g. "thrift:| git:| url: git@foo.com"
delphi (delphi):
ansistr_binary: Use AnsiString for binary datatype (default is TBytes).
register_types: Enable TypeRegistry, allows for creation of struct, union
and container instances by interface or TypeInfo()
constprefix: Name TConstants classes after IDL to reduce ambiguities
events: Enable and use processing events in the generated code.
xmldoc: Enable XMLDoc comments for Help Insight etc.
erl (Erlang):
legacynames: Output files retain naming conventions of Thrift 0.9. and earlier.
maps: Generate maps instead of dicts.
otp16: Generate non-namespaced dict and set instead of dict:dict and sets:set.
go (Go):
package_prefix= Package prefix for generated files.
thrift_import= Override thrift package import path (default:git.apache.org/thrift.git/lib/go/thrift)
package= Package name (default: inferred from thrift file name)
ignore_initialisms
Disable automatic spelling correction of initialisms (e.g. "URL")
read_write_private
Make read/write methods private, default is public Read/Write
gv (Graphviz):
exceptions: Whether to draw arrows from functions to exception.
haxe (Haxe):
callbacks Use onError()/onSuccess() callbacks for service methods (like AS3)
rtti Enable @:rtti for generated classes and interfaces
buildmacro=my.macros.Class.method(args)
Add @:build macro calls to generated classes and interfaces
hs (Haskell):
html (HTML):
standalone: Self-contained mode, includes all CSS in the HTML files.
Generates no style.css file, but HTML files will be larger.
noescape: Do not escape html in doc text.
java (Java):
beans: Members will be private, and setter methods will return void.
private-members: Members will be private, but setter methods will return 'this' like usual.
nocamel: Do not use CamelCase field accessors with beans.
fullcamel: Convert underscored_accessor_or_service_names to camelCase.
android: Generated structures are Parcelable.
android_legacy: Do not use java.io.IOException(throwable) (available for Android 2.3 and above).
option_type: Wrap optional fields in an Option type.
java5: Generate Java 1.5 compliant code (includes android_legacy flag).
reuse-objects: Data objects will not be allocated, but existing instances will be used (read and write).
sorted_containers:
Use TreeSet/TreeMap instead of HashSet/HashMap as a implementation of set/map.
generated_annotations=[undated|suppress]:
undated: suppress the date at @Generated annotations
suppress: suppress @Generated annotations entirely
javame (Java ME):
js (Javascript):
jquery: Generate jQuery compatible code.
node: Generate node.js compatible code.
ts: Generate TypeScript definition files.
json (JSON):
merge: Generate output with included files merged
lua (Lua):
omit_requires: Suppress generation of require 'somefile'.
ocaml (OCaml):
perl (Perl):
php (PHP):
inlined: Generate PHP inlined files
server: Generate PHP server stubs
oop: Generate PHP with object oriented subclasses
rest: Generate PHP REST processors
nsglobal=NAME: Set global namespace
validate: Generate PHP validator methods
json: Generate JsonSerializable classes (requires PHP >= 5.4)
py (Python):
twisted: Generate Twisted-friendly RPC services.
tornado: Generate code for use with Tornado.
no_utf8strings: Do not Encode/decode strings using utf8 in the generated code. Basically no effect for Python .
coding=CODING: Add file encoding declare in generated file.
slots: Generate code using slots for instance members.
dynamic: Generate dynamic code, less code generated but slower.
dynbase=CLS Derive generated classes from class CLS instead of TBase.
dynfrozen=CLS Derive generated immutable classes from class CLS instead of TFrozenBase.
dynexc=CLS Derive generated exceptions from CLS instead of TExceptionBase.
dynimport='from foo.bar import CLS'
Add an import line to generated code to find the dynbase class.
package_prefix='top.package.'
Package prefix for generated files.
old_style: Deprecated. Generate old-style classes.
rb (Ruby):
rubygems: Add a "require 'rubygems'" line to the top of each generated file.
namespaced: Generate files in idiomatic namespaced directories.
st (Smalltalk):
swift (Swift):
log_unexpected: Log every time an unexpected field ID or type is encountered.
debug_descriptions:
Allow use of debugDescription so the app can add description via a cateogory/extension
async_clients: Generate clients which invoke asynchronously via block syntax.
promise_kit: Generate clients which invoke asynchronously via promises.
xml (XML):
merge: Generate output with included files merged
no_default_ns: Omit default xmlns and add idl: prefix to all elements
no_namespaces: Do not add namespace definitions to the XML model
xsd (XSD):

实践
平常使用PHP开发,所以使用PHP实践下,对它有更深入的了解,一般服端开发使用编译型的语言,执行效率更高,以实现简单的NoSQL功能为例子(源码里有各种语言的实现例子)。
1 接口定义(nosql.thrift)

/*
* thrift简单示例 模仿thrift源码教程
* nosql数据库简单实现
*/ #由wadeyu创建 wadeyu.cnblogs.com /**
* 命名空间
*/
namespace cpp NoSql
namespace java NoSql
namespace php NoSql /**
* 异常:无效参数
*/
exception InvalidParametorException{
} /**
* 定义服务
*/
service NoSqlService{
/**
* 获取key的值
*/
string get(1:string key) throws (1:InvalidParametorException ex), /**
* 设置值
*/
bool set_(1:string key, 2:string value) throws (1:InvalidParametorException ex), /**
* 自增
*/
i32 incr(1:string key) throws (1:InvalidParametorException ex),
}

2 编译(生成服端代码)

[wadeyu@localhost thriftdemo]$ thrift --gen php:server -out ./server ./meta/nosql.thrift

3 编写服端启动代码

 <?php
/*
* php简单服端脚本
*/ #author by wadeyu: wadeyu.cnblogs.com define('BASE_DIR',dirname(__FILE__) . '/');
define('VENDOR_DIR',BASE_DIR.'vendor/'); use Server\NoSqlHandler;
use NoSql\NoSqlServiceProcessor;
use Thrift\Factory\TBinaryProtocolFactory;
use Thrift\Factory\TTransportFactory;
use Thrift\Server\TServerSocket;
use Thrift\Server\TSimpleServer; $loader = include_once VENDOR_DIR.'autoload.php';
$loader->addPsr4('Server\\',BASE_DIR.'server/'); include_once BASE_DIR.'server/NoSql/NoSqlService.php';
include_once BASE_DIR.'server/NoSql/Types.php'; $serverTransport = new TServerSocket('localhost',9090);
$clientTransport = new TTransportFactory;
$binaryProtocol = new TBinaryProtocolFactory;
$nosqlProcessor = new NoSqlServiceProcessor( new NoSqlHandler );
$simpleServer = new TSimpleServer(
$nosqlProcessor,
$serverTransport,
$clientTransport,
$clientTransport,
$binaryProtocol,
$binaryProtocol
);
echo "start listening:localhost:9090 \n";
$simpleServer->serve();

4 启动服务

[wadeyu@localhost thriftdemo]$ php phpserver.php
start listening:localhost:

5 编译(生成客户端代码)

[wadeyu@localhost thriftdemo]$ thrift --gen php -out ./client ./meta/nosql.thrift

6 客户端接口调用

 <?php
/*
* php简单客户端脚本
*/ #author by wadeyu: wadeyu.cnblogs.com define('BASE_DIR',dirname(__FILE__) . '/');
define('VENDOR_DIR',BASE_DIR.'vendor/'); use Thrift\Protocol\TBinaryProtocol;
use Thrift\Transport\TSocket;
use NoSql\NoSqlServiceClient; $loader = include_once VENDOR_DIR.'autoload.php'; include_once BASE_DIR.'client/NoSql/NoSqlService.php';
include_once BASE_DIR.'client/NoSql/Types.php'; $transport = new TSocket('localhost',9090);
$protocol = new TBinaryProtocol($transport);
$service = new NoSqlServiceClient($protocol);
$startTime = microtime(true);
try{
$transport->open();
$key1 = 'test1';
$ret = $service->get($key1);
var_dump($ret);
$ret = $service->set_($key1,'test1');
var_dump($ret);
$key2 = 'test2';
$ret = $service->incr($key2);
var_dump($ret);
$transport->close();
}catch(Exception $ex){
throw $ex;
}
var_dump('cost:'.(microtime(true)-$startTime));
 [wadeyu@localhost thriftdemo]$ php phpclient.php
string() "NULL"
bool(true)
int()
string() "cost:0.22478580474854"

示例源码下载
https://github.com/wadeyu/thriftdemo/archive/master.zip

后记
纸上得来终觉浅,得知此事要躬行。其实很早就开始写这篇文章了,自己很懒,拖了几个星期才完成,以后要改掉这个毛病了。

参考资料
【1】thrift官方文档

http://thrift.apache.org/docs/
【2】thrift安装

http://thrift.apache.org/docs/install/
【3】thrift IDL规范

http://thrift.apache.org/docs/idl

http://thrift.apache.org/docs/types
【4】thrift实现论文

http://thrift.apache.org/static/files/thrift-20070401.pdf

RPC服务框架探索之Thrift的相关教程结束。