参考文章RPC 基本原理与 Apach Thrift 初体验

RPC基本原理

RPC(Remote Procedure Call),远程过程调用,大部分的RPC框架都遵循如下三个开发步骤:

1. 定义一个接口说明文件:描述了对象(结构体)、对象成员、接口方法等一系列信息;
2. 通过RPC框架所提供的编译器,将接口说明文件编译成具体的语言文件;
3. 在客户端和服务器端分别引入RPC编译器所生成的文件,即可像调用本地方法一样调用服务端代码;

RPC通信过程如下图所示



通信过程包括以下几个步骤:

1、客户过程以正常方式调用客户桩(client stub,一段代码);
2、客户桩生成一个消息,然后调用本地操作系统;
3、客户端操作系统将消息发送给远程操作系统;
4、远程操作系统将消息交给服务器桩(server stub,一段代码);
5、服务器桩将参数提取出来,然后调用服务器过程;
6、服务器执行要求的操作,操作完成后将结果返回给服务器桩;
7、服务器桩将结果打包成一个消息,然后调用本地操作系统;
8、服务器操作系统将含有结果的消息发送回客户端操作系统;
9、客户端操作系统将消息交给客户桩;
10、客户桩将结果从从消息中提取出来,返回给调用它的客户过程;

所有这些步骤的效果是,将客户过程对客户桩发出的本地调用转换成对服务器过程的本地调用,而客户端和服务器都不会意识到有中间步骤的存在。

这个时候,你可能会想,既然是调用另一台机器的服务,使用 RESTful API 也可以实现啊,为什么要选择 RPC 呢?我们可以从两个方面对比:

  • 资源粒度。RPC 就像本地方法调用,RESTful API 每一次添加接口都可能需要额外地组织开放接口的数据,这相当于在应用视图中再写了一次方法调用,而且它还需要维护开发接口的资源粒度、权限等;
  • 流量消耗。RESTful API 在应用层使用 HTTP 协议,哪怕使用轻型、高效、传输效率高的 JSON 也会消耗较大的流量,而 RPC 传输既可以使用 TCP 也可以使用 UDP,而且协议一般使用二制度编码,大大降低了数据的大小,减少流量消耗。

对接异构第三方服务时,通常使用 HTPP/RESTful 等公有协议,对于内部的服务调用,应用选择性能更高的二进制私有协议。

Thrift架构

thrift主要用于各个服务之间的RPC通信,支持跨语言。thrift是一个典型的CS结构,客户端和服务端可以使用不同的语言开发,thrift通过IDL(Interface Description Language)来关联客户端和服务端。thrift的整体架构图如下图所示



图中Your Code是用户实现的业务逻辑,接下来的FooService.ClientFoo.write()/read()是thrift根据IDL生成的客户端和服务端的代码,对应于RPC中Client stub和Server stub。TProtocol 用来对数据进行序列化与反序列化,具体方法包括二进制,JSON 或者 Apache Thrift 定义的格式。TTransport 提供数据传输功能,使用 Apache Thrift 可以方便地定义一个服务并选择不同的传输协议。

如下图所示为thrift的网络栈结构

thirft使用socket进行数据传输,数据以特定的格式发送,接收方进行解析。我们定义好thrift的IDL文件后,就可以使用thrift的编译器来生成双方语言的接口、model,在生成的model以及接口代码中会有解码编码的代码。

TTransport层

代表thrift的数据传输方式,thrift定义了如下几种常用数据传输方式

  • TSocket: 阻塞式socket;
  • TFramedTransport: 以frame为单位进行传输,非阻塞式服务中使用;
  • TFileTransport: 以文件形式进行传输;

TProtocol层

代表thrift客户端和服务端之间传输数据的协议,通俗来讲就是客户端和服务端之间传输数据的格式(例如json等),thrift定义了如下几种常见的格式

  • TBinaryProtocol: 二进制格式;
  • TCompactProtocol: 压缩格式;
  • TJSONProtocol: JSON格式;
  • TSimpleJSONProtocol: 提供只写的JSON协议;

thrift支持的Server模型

thrift主要支持以下几种服务模型

  • TSimpleServer: 简单的单线程服务模型,常用于测试;
  • TThreadPoolServer: 多线程服务模型,使用标准的阻塞式IO;
  • TNonBlockingServer: 多线程服务模型,使用非阻塞式IO(需要使用TFramedTransport数据传输方式);
  • THsHaServer: THsHa引入了线程池去处理,其模型读写任务放到线程池去处理,Half-sync/Half-async处理模式,Half-async是在处理IO事件上(accept/read/write io),Half-sync用于handler对rpc的同步处理;

thrift IDL文件

thrift IDL不支持无符号的数据类型,因为很多编程语言中不存在无符号类型,thrift支持一下几种基本的数据类型

  • byte: 有符号字节
  • i16: 16位有符号整数
  • i32: 32位有符号整数
  • i64: 63位有符号整数
  • double: 64位浮点数
  • string: 字符串

此外thrift还支持以下容器类型:

  • list: 一系列由T类型的数据组成的有序列表,元素可以重复;
  • set: 一系列由T类型的数据组成的无序集合,元素不可重复;
  • map: 一个字典结构,Key为K类型,Value为V类型,相当于java中的HashMap;

thrift容器中元素的类型可以是除了service之外的任何类型,包括exception

thirft支持struct类型,目的就是讲一些数据聚合在一起,方便传输管理,struct定义形式如下:

struct People {
1:string name;
2:i32 age;
3:string gender;
}

thrift支持枚举类型,定义形式如下:

enum Gender {
MALE,
FEMALE
}

thrift支持自定义异常类型exception,异常定义形式如下:

exception RequestException {
1:i32 code;
2:string reason;
}

thrift定义服务相当于Java中创建接口一样,创建的service经过代码生thrift代码生成工具编译后就会生成客户端和服务端的框架代码,service的定义形式如下:

service HelloWorldService {
// service中可以定义若干个服务,相当于Java Interface中定义的方法
string doAction(1:string name, 2:i32 age);
}

thrift支持给类型定义别名,如下所示:

typedef i32 int
typedef i64 long

thrift也支持常量的定义,使用const关键字:

const i32 MAX_RETRIES_TIME = 10;
const string MY_WEBSITE = "http://facebook.com";

thrift支持命名空间,命名空间相当于Java中的package,主要用于组织代码,thrift使用关键字namespace定义命名空间,格式是namespace 语言名 路径,如下示例所示:

namespace java com.test.thrift.demo

thrift也支持文件包含,相当于CPP中的include,Java中的import,使用关键字include:

include "global.thrift"

#///**/都可以作为thrift文件中的注释。

thrift提供两个关键字requiredoptional,分别用于表示对应的字段是必填的还是可选的(推荐尽量使用optional),如下所示:

struct People {
1:required string name;
2:optional i32 age;
}

thrift应用示例

本示例中我们使用java编写thrift的服务端程序,使用python编写thrift的客户端程序。

首先定义thrift IDL文件

// data.thrift
namespace java thrift.generated
namespace py py.thrift.generated typedef i16 short
typedef i32 int
typedef i64 long
typedef bool boolean
typedef string String // struct关键字用于定义结构体,相当于面向对象编程语言中的类
struct Person {
// 相当于定义类中的成员,并生成相应的get和set方法,optional表示username这个成员可以没有
1: optional String username,
2: optional int age,
3: optional boolean married
} // 定义一个异常类型,用于接口中可能抛出的异常
exception DataException {
1: optional String message,
2: optional String callStack,
3: optional String date
} // 定义服务接口
service PersonService {
Person getPersonByUsername(1: required String username) throws (1: DataException data),
void savePerson(1: required Person person)
}

执行thrift --gen java src/thrift/data.thrift生成对应的java代码,并引入到Java工程当中,代码结构如下图所示

编写Java服务端代码,data.thrift的service中定义了两个服务,我们需要定义相应服务的实现类(相当于handler),如下所示:

import thrift.generated.DataException;
import thrift.generated.Person;
import thrift.generated.PersonService; public class PersonServiceImpl implements PersonService.Iface {
@Override
public Person getPersonByUsername(String username) throws DataException {
System.out.println("Got Client Param: " + username); return new Person().setUsername(username).setAge(20).setMarried(false);
} @Override
public void savePerson(Person person) throws DataException {
System.out.println("Got Client Param:"); System.out.println(person.username);
System.out.println(person.age);
System.out.println(person.married);
}
}

同时我们需要借助thrift为我们提供的类库实现一个服务器来监听rpc请求,代码如下所示:

import org.apache.thrift.TProcessorFactory;
import org.apache.thrift.protocol.TCompactProtocol;
import org.apache.thrift.server.THsHaServer;
import org.apache.thrift.server.TServer;
import org.apache.thrift.transport.TFramedTransport;
import org.apache.thrift.transport.TNonblockingServerSocket;
import thrift.generated.PersonService; public class ThriftServer {
public static void main(String[] args) throws Exception { // 定义服务器使用的socket类型
TNonblockingServerSocket tNonblockingServerSocket = new TNonblockingServerSocket(8899); // 创建服务器参数
THsHaServer.Args arg = new THsHaServer.Args(tNonblockingServerSocket).minWorkerThreads(2).maxWorkerThreads(4); // 请求处理器
PersonService.Processor<PersonServiceImpl> processor = new PersonService.Processor<>(new PersonServiceImpl()); // 配置传输数据的格式
arg.protocolFactory(new TCompactProtocol.Factory());
// 配置数据传输的方式
arg.transportFactory(new TFramedTransport.Factory());
// 配置处理器用来处理rpc请求
arg.processorFactory(new TProcessorFactory(processor)); // 本示例中使用半同步半异步方式的服务器模型
TServer server = new THsHaServer(arg);
System.out.println("Thrift Server Started!");
// 启动服务
server.serve();
}
}

编写python客户端,执行thrift --gen py src/thrift/data.thrift生成对应的python代码,并引入到python工程当中,代码结构如下图所示



python客户端代码如下所示:

# -*- coding:utf-8 -*-
__author__ = 'kpzhang' from py.thrift.generated import PersonService
from py.thrift.generated import ttypes from thrift import Thrift
from thrift.transport import TSocket
from thrift.transport import TTransport
from thrift.protocol import TCompactProtocol import sys reload(sys)
sys.setdefaultencoding('utf8') try:
tSocket = TSocket.TSocket("localhost", 8899)
tSocket.setTimeout(900) transport = TTransport.TFramedTransport(tSocket)
protocol = TCompactProtocol.TCompactProtocol(transport)
client = PersonService.Client(protocol) transport.open() person = client.getPersonByUsername("张三") print person.username
print person.age
print person.married print '---------------------' newPerson = ttypes.Person();
newPerson.username = "李四"
newPerson.age = 30
newPerson.married = True client.savePerson(newPerson) transport.close() except Thrift.TException, tx:
print '%s' % tx.message

客户端可以向调用本地的方法一样调用服务端的方法。

[thrift] thrift基本原理及使用的更多相关文章

  1. Thrift 安装及使用

    前言:由于最近在看storm Topology提交过程的源代码,写好的topology jar文件是通过Thrift RPC的形式提交给nimbus的.故了解下Thrift的基本原理. 参考:http ...

  2. Thrift-java学习小结

    ➠更多技术干货请戳:听云博客 Thrift是什么?什么情况下使用thrift Thrift源于大名鼎鼎的facebook之手,在2007年facebook提交Apache基金会将Thrift作为一个开 ...

  3. RPC学习----Thrift快速入门和Java简单示例

    一.什么是RPC? RPC(Remote Procedure Call Protocol)——远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议. RPC协议 ...

  4. rpc框架之 thrift 学习 1 - 安装 及 hello world

    thrift是一个facebook开源的高效RPC框架,其主要特点是跨语言及二进制高效传输(当然,除了二进制,也支持json等常用序列化机制),官网地址:http://thrift.apache.or ...

  5. 解决thrift: ···No such file or directory问题

    感谢Anker分享:error while loading shared libraries: xxx.so.x" 错误的原因和解决办法 今天在装thrift的时候遇到一个这样的问题: ro ...

  6. thrift编译安装

    关于thrift的介绍:http://www.ibm.com/developerworks/cn/java/j-lo-apachethrift/ Apache Thrift 是 Facebook 实现 ...

  7. thrift demo

    基于上一篇博客,安装thrift complier之后,就需要进行跑跑程序,来看看是否如同预期的那种效果. 前面的thrift compiler的主要作用,其实就是为了IDL的,就是防止客户端和服务端 ...

  8. 安装thrift全过程

    为了研究基于thrift的RPC框架,其实,是想自己基于thrift写一个微服务的platform.首先就是安装Thrift,便于IDL架构生成java的接口文件.多的不说了,开始install的过程 ...

  9. Apache Thrift学习之一(入门及Java实例演示)

    目录: 概述 下载配置 基本概念 数据类型 服务端编码基本步骤 客户端编码基本步骤 数据传输协议 实例演示(java) thrift生成代码 实现接口Iface TSimpleServer服务模型 T ...

随机推荐

  1. C#模版学习研究

    原文链接1   原文链接2 using System; using System.Collections.Generic; using System.Text; using T = System.By ...

  2. Android开发——蓝牙

    ---恢复内容开始--- 前言 孤芳自赏,一揽芳华: 人情冷暖,自在人心: 登高远眺,望步止前: 喜笑言开,欺人骗己. 上篇文章介绍了基本的蓝牙使用,书写的demo也不是很完善,希望各位大神能够改正. ...

  3. iOS-控件响应用户控制事件之事件处理

    事件处理 响应者对象 在iOS中不是任何对象都能处理事件,只有继承了UIResponder的对象才能接收并处理事件.我们称之为“响应者对象” UIApplication.UIViewControlle ...

  4. ArrayList源码分析(基于JDK1.8)

    public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess ...

  5. 【java_设计模式】建造者模式

    应用场景对生成对象进行链式配置好处:按需配置对象,灵活修改默认配置,比构造方法创建对象更直观. public static void init() {if (okHttpClient == null) ...

  6. delphi 7 生成 调用 bat文件的exe文件

    unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms ...

  7. 输入一个字符串输出ASCII的十六进制值

    #include <stdio.h> #include <string.h> #define LEN 1024 void main() { char s[LEN] = &quo ...

  8. 获取minist数据并转换成lmdb

    caffe本身是没有数据集的,但在data目录下有获取数据的一些脚本.MNIST,一个经典的手写数字库,包含60000个训练样本和10000个测试样本,每个样本为28*28大小的黑白图片,手写数字为0 ...

  9. gulp(1) 的使用

    1.安装node.js 2.全局安装gulp.js npm install gulp -g 3.在项目本地根目录再安装(通过黑窗口安装)npm install --save-dev gulp/ 或者 ...

  10. [Luogu] P1865 A % B Problem

    题目描述 区间质数个数 输入输出格式 输入格式: 一行两个整数 询问次数n,范围m 接下来n行,每行两个整数 l,r 表示区间 输出格式: 对于每次询问输出个数 t,如l或r∉[1,m]输出 Cros ...