Thrift多种语言Rpc调用实战

  每个公司,随着业务持续不断地增长,作为单体项目本身,都会变得越发臃肿,不论是部署,开发,排查问题都变得越来越蛋疼。这个时候,我们会想到的方法就是讲一些业务服务逐步先服务化,在之后是微服务架构,甚至到最终是服务网格的模式。
  当然本期的主题就是在拆分为多个服务时,我们应该怎么处理我们的相互调用,我们最先考虑的就是http调用,但是对于这样的调用存在的问题就是我们在拆分服务的时候,希望不用过多修改代码,不用关心调用里需要处理的细节,这就引出了RPC,全称远程过程调用,虽然是两个服务之间的调用,但是在代码层面上就是一个普通的函数调用,里面的调用细节已经封装好了,不用你关心,听着就好爽,至于RPC相关的知识,我推荐的是极客时间的<<趣谈网络协议>>相关章节,讲的很好也很透彻,这是链接:RPC综述
  既然选择了RPC,我们就会考虑一些比较成熟的框架(当然不是自己搞了。。。。因为实践就是最好的方案)。网上其实有许多不错的方案,包括dubbo,dubbox, grpc, thrift等。不过本次的文章主要讲的是多种语言之间的调用,所以我们的重点在于grpc和Thrift,grpc主要使用的是pb,而Thrift则较为灵活,支持多种压缩,在传输协议上grpc是http2.0,而Thrift则是同时支持http和tcp。当然两者更详细的对比大家可以看下这篇文章grpc 对比 thrift,在我编写实践中,发现thrift编写简单直观,虽然在代码生成上略多一些,但我还是选择了thrift,当然grpc也是一个非常不错的框架,另外实名反对上面那篇文章讲的thrift的文档偏少,其实主要是中文的文档偏少,但是其实因为却是简单方便,不需要过多文档。

实战准备

1.安装最基本的thrift工具,因为我本身使用的是mac,所以安装起来还是挺方便的

brew install thrift

其他平台的安装方式可以查看各平台安装thrift

通过在命令行调用 thrift -version确定安装成功

17:43:46 › thrift -version
Thrift version 0.11.0

2.本次实战讲解用的是php和go,php作为我们的client端,go就作为我们的server端

thrift 的构成

Thrift 采用IDL(Interface Definition Language)来定义通用的服务接口,然后通过Thrift提供的编译器,可以将服务接口编译成不同语言编写的代码,通过这个方式来实现跨语言的功能。

  thrift大致采用的结构就如下图所示:

thrift.jpg

Transport 就是网络传输部分,通过抽象化传输部分,应用可以根据自己的需要来选择自己传输方式,甚至可以使用自己写的传输方式。
Protocol 简单来说就是我们的编解码方式,选择一种特定的方式来转化成我们的传输数据,也可以自定义方式。
Processor 顾名思义,从我们的输入protocol 获取数据,然后传递到我们实例化的handler上进行处理,然后在把处理的结果返回到输出protocol上。
Server 则是我们服务器运行的方式,因为Thrift采用的是C/S架构,这样我们的server可以选择方式来处理远程客户端,比如单线程,事件驱动。

想要对thrift有更深入的了解,或者想对thrift文件编写和使用有更深了解的同学可以看这遍大牛写的文章,讲地很透彻。thrift使用指南

代码实现

我们的IDL文件作为实例,比较简单,client端和server端都只有唯一文件,但是namespace 应该以client语言为准 (我实践的结果。。。)

//example.thrift
namespace php example

struct Data {
    1:string text
}


service format_data {
    Data do_format(1:Data data)
}

go server的实现

1.在根目录里创建example.thrift 文件
2.执行命令

 thrift -r --gen go example.thrift
  1. 根目录会多出一个gen-go 的目录,里面是thrift的实例化代码,都是自动生成的。


    go server
  2. 引入thrift-go 的库
go get git.apache.org/thrift.git/lib/go/thrift

如果因为不可抗拒原因无法下载,可以使用源码安装(当然我还是建议go get获取,比较方便,可以在网上查下如何go get 能成功的办法,在这里就不做讲解了)

通过源码安装:

*   在 $GOPATH 的 src 目录下创建多层级目录:git.apache.org/thrift.git/lib/go
*   从 github 上下载 [thrift 0.10.0](https://github.com/apache/thrift/archive/0.10.0.zip) 的源码 ,解压后进入 thrift-0.10.0/lib/go 目录下,将 thrift 目录 copy 到刚创建的 $GOPATH/src/git.apache.org/thrift.git/lib/go 目录下
*   在任意目录下执行 $ go install git.apache.org/thrift.git/lib/go/thrift 就完成了 golang 的 thrift 库的安装

5.接下来,我们编写函数的实例化代码,(为了方便,我把所有代码写在main.go 里)

type FormatDataImpl struct {}

func (fdi *FormatDataImpl) DoFormat(ctx context.Context,data *example.Data) (r *example.Data, err error) {
    var rData example.Data
    rData.Text = strings.ToUpper(data.Text)

    fmt.Println(data, rData)

    return &rData, nil
}

上面的代码其实比较简单,我们定义了一个类型,并且有一个方法DoFormat,该方法实例化了我们定义的方法,由于我使用的go版本是1.10,所以方法的参数会增加ctx context.Context,如果低版本应该和你定义的是保持一致的。
6.接下来定义我们的server

    handler := &FormatDataImpl{}
    processor := example.NewFormatDataProcessor(handler)
    serverTransport, err := thrift.NewTServerSocket(HOST + ":" + PORT)
    if err != nil {
        log.Fatalln("Error:", err)
    }

    transportFactory := thrift.NewTFramedTransportFactory(thrift.NewTTransportFactory())
    protocolFactory := thrift.NewTBinaryProtocolFactoryDefault()

    server := thrift.NewTSimpleServer4(processor, serverTransport, transportFactory, protocolFactory)
    fmt.Println("Running at:", HOST + ":" + PORT)
    server.Serve()

之前我讲了thrift的大致结构,所以我们的server也是这样生成的,我相信你肯定看懂了,嘿嘿。
这是一个采用阻塞式socker,以frame为单位进行传输,采用二进制格式的简单的单线程服务模型。
想对各层次具有哪些格式和类型,可以查阅这篇文章Thrift 架构总结
7.所以我们完整的main.go 就如下

package main

import (
    "context"
    "fmt"
    "git.apache.org/thrift.git/lib/go/thrift"
    "log"
    "strings"
    "thrift/gen-go/example"
)

type FormatDataImpl struct {}

func (fdi *FormatDataImpl) DoFormat(ctx context.Context,data *example.Data) (r *example.Data, err error) {
    var rData example.Data
    rData.Text = strings.ToUpper(data.Text)

    fmt.Println(data, rData)

    return &rData, nil
}

const (
    HOST = "localhost"
    PORT = "8080"
)


func main()  {
    handler := &FormatDataImpl{}
    processor := example.NewFormatDataProcessor(handler)
    serverTransport, err := thrift.NewTServerSocket(HOST + ":" + PORT)
    if err != nil {
        log.Fatalln("Error:", err)
    }

    transportFactory := thrift.NewTFramedTransportFactory(thrift.NewTTransportFactory())
    protocolFactory := thrift.NewTBinaryProtocolFactoryDefault()

    server := thrift.NewTSimpleServer4(processor, serverTransport, transportFactory, protocolFactory)
    fmt.Println("Running at:", HOST + ":" + PORT)
    server.Serve()

}
  1. 编译运行,在go1.10可能会出现如下报错


    error

    这是因为生成的代码有问题,没给Flush函数传入context造成的,比较蛋疼,我们可以先手动给它传入了

oprot.Flush(ctx)

9.接下来我们正常的编译运行,如下图


go ok

10.ok 现在我们的server端OK了

php client的实现 (基于php7)

1.准备工作,由于官方的包没有做成composer包,所以我们测试的话可以按照官方的方法直接copy代码放入我们的项目,但我的建议,如果在正式环境中,最好把代码打成一个composer包,方便管理使用。

  thrift的php官方代码仓库在github,我们在根目录创建lib,然后在lib下创建Thrift这个目录,然后把链接点开,把lib下面的所有代码都拷贝到Thrift这个目录下面,结果大概如下图。

tree

2.接下来就是生成我们的php代码,依然用的还是那个thrift文件
比较简单的生成方式是

thrift -r --gen php thrift/example.thrift 

这样生成的方式会将所有类都糅合进一个文件,所以我们也可以这样

thrift -r --gen php:psr4,validate,json thrift/example.thrift
  • psr4 代码代码生成会遵循psr4标准,每个类一个文件
  • validate 会生成校验代码,对参数进行校验
  • json 生成的代码会实例化 JsonSerializable接口

当然这样生成的代码是client端的,代码量其实不是那么多,如果要生成server端代码,还需要加上server 参数

thrift --gen php:server,psr4,validate,json

3.接下来是我们如何引入thrift-php 的类,我们因为是直接拷贝的代码,所以需要手动引入,

require_once __DIR__.'/lib/Thrift/ClassLoader/ThriftClassLoader.php';

use Thrift\ClassLoader\ThriftClassLoader;

$GEN_DIR = realpath(dirname(__FILE__)).'/gen-php';
$loader = new ThriftClassLoader();
$loader->registerNamespace('Thrift',__DIR__.'/lib');
$loader->registerNamespace('example',$GEN_DIR);
$loader->register();

需要注意的一点,如果没有加上psr4,我们生成的代码都在一个类,需要引入我们的 example的命名空间需要换成

$loader->registerDefinition('example',$GEN_DIR);

4.接下来是我们的调用

 $socket = new TSocket('localhost',8080);
    $transport = new TFramedTransport($socket);
    $protocol = new TBinaryProtocol($transport);

    $client = new format_dataClient($protocol);

    $transport->open();

    $data = new Data();
    $data->text = 'World!';

    $res = $client->do_format($data);
    var_dump($res);

    $transport->close();

为什么这样写我相信大家应该都懂了,😄
5.我们完整的client.php 代码如下

<?php


error_reporting(E_ALL);
require_once __DIR__.'/lib/Thrift/ClassLoader/ThriftClassLoader.php';

use Thrift\ClassLoader\ThriftClassLoader;

$GEN_DIR = realpath(dirname(__FILE__)).'/gen-php';
$loader = new ThriftClassLoader();
$loader->registerNamespace('Thrift',__DIR__.'/lib');
$loader->registerNamespace('example',$GEN_DIR);
$loader->register();

use Thrift\Protocol\TBinaryProtocol;
use Thrift\Transport\TFramedTransport;
use Thrift\Transport\TSocket;
use example\Data;
use example\format_dataClient;

try {
    $socket = new TSocket('localhost',8080);
    $transport = new TFramedTransport($socket);
    $protocol = new TBinaryProtocol($transport);

    $client = new format_dataClient($protocol);

    $transport->open();

    $data = new Data();
    $data->text = 'World!';

    $res = $client->do_format($data);
    var_dump($res);

    $transport->close();
} catch (\Exception $e) {
    print 'TException:'.$e->getMessage().PHP_EOL;
}

  1. 接下来执行我们的client,结果就如下
13:02:54 › php Client.php
object(example\Data)#9 (1) {
  ["text"]=>
  string(6) "WORLD!"
}

7.然后看下我们server端的输出

API server listening at: 127.0.0.1:60656
Running at: localhost:8080
Data({Text:World!}) {WORLD!}
  1. 这就是我们一次成功的rpc调用(当然是基于我们简单又灵活的thrift了),希望通过这样一个简单的过程,大家对rpc有了更深的了解,能够应用到日常生活中去。

参考文章

Thrift IDL基本语法
thrift go 的完整调用简单实现
走进 thrift server无废话 Thrift 之 Hello World( PHP 版).
Thrift使用指南
Thrift 的原理和使用

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,012评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,589评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,819评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,652评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,954评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,381评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,687评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,404评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,082评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,355评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,880评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,249评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,864评论 3 232
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,007评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,760评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,394评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,281评论 2 259

推荐阅读更多精彩内容