WhyApacheThrift

因为最近在项目中需要集成进来一个Python编写的机器学习算法,但是我的后端主要使用的是SpringCloud技术栈. 于是面临着异构语言之间的通信实现方式的抉择. 因为业务逻辑是这样的

主要就是实现2-3这部分请求响应, 实现的方式挺多的, 只要有能力甚至将py封装成一个WebServer对外提供服务, 或者是选择使用消息中间件, 但是大部分消息中间的通信模型都是单向的,即发布订阅, 不过也能实现上面的业务需求

项目中一开始的实现其实是像下面这样的, 选择简单粗暴直接使用socket编程实现, py用socket写一个服务端, java用socket实现客户端, 双方之间实现异构通信, 就像下面代码的两段,在本地运行的话双方通信的速度还可以,但是当我将他制作成docket镜像打包发布到线上时, 双方的通信竟然需要9s

一个请求需要九秒钟, 这肯定是不能接受的

        InetAddress localhost = InetAddress.getByName("192.168.88.1");
Socket socket = new Socket(localhost.getHostName(), 9999);
OutputStream outputStream = socket.getOutputStream();
InputStream inputStream = socket.getInputStream();
// 向py发送消息
PrintStream out = new PrintStream(outputStream);
// 发送内容
out.print("hi python");
// 获取消息
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String targetName = bufferedReader.readLine();
System.err.println("获取返回的消息 " + targetName);
import socket
import time
# 建立一个服务端
server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server.bind(('localhost',9999)) #绑定要监听的端口
server.listen(5) #开始监听 表示可以使用五个链接排队
while True:# conn就是客户端链接过来而在服务端为期生成的一个链接实例
conn,addr = server.accept() #等待链接,多个链接的时候就会出现问题,其实返回了两个值
print(conn,addr)
while True:
try:
data = conn.recv(1024) #接收数据
print('recive:',data.decode()) #打印接收到的数据
conn.send(data.upper()) #然后再发送数据
time.sleep(3)
except ConnectionResetError as e:
print('关闭了正在占线的链接!')
break
conn.close()

其实我们现在遇到的大部分问题,前辈们都遇到过,就比如针对这个需求,其实业内的实现可能有很多种, Apache的顶级 项目Thrift就能完美解决这个问题,而且通信的速度,效果, 稳定性都超级理想

Thrift简介

Apache Thrift 是facebook捐献给Apache, 现在它也是 Apace的顶级项目 , Thrift的意为节俭的, 本质上thrift是一种接口描述语言和二进制通信协议 , 设计的初衷就是为了实现跨越语言的服务调用服务, 是一个绝对优秀的RPC框架

目前,Thrift 支持的语言有很多, 诸如: C#、C++(基于POSIX兼容系统)、Cappuccino、Cocoa、Delphi、Erlang、Go、Haskell、Java、Node.js、OCaml、Perl、PHP、Python、Ruby和Smalltalk。

安装

官网下载地址: http://thrift.apache.org/download

下载编译器

为啥使用这个编译器呢? 因为我们真正在开始时, 使用的代码都是使用Thrift自动为我们生成出来的, 说白了, 就是我们只需要根据业务逻辑定义好 .thrift 文件, 通过这个它的编译器编译这个配置文件,并且使用告诉编译器为我们生成什么语言的代码就ok了, Thrift 框架会自动的将双方通信的编解码的逻辑生成在给我们的代码中, 还包含对socket的封装, 整体是一条龙的服务

因为是使用的windows本, 所以下载 .exe 结尾的编译器

添加环境变量

过一会生成代码代码时是在idea中完成的, 故这一步免不了, 不然它会说找不到这个命令

第一个坑: 重命名一下这个编译器, 去掉前面的小版本号再去添加环境变量

第二个坑: 如果在控制台输入thrift显示版本号, 但是idea中不识别, 就重启电脑

Thrift 的架构体系

为啥要看Thrift的架构体系呢? 虽然Thrift会帮我们自动的生成一些模板代码, 但是还是需要自己手动编码客户端和服务端的代码的, 了解了它的架构体系, 再看如果编写服务端和客户端的代码也能看的懂, 在代码中构造的对象在下面的架构体系中都是有迹可循的

如上图,Thrift是典型的CS架构 我们可以将thrift称为 IDL(Interface DescriptionLanguage)语言 , 并且上图中的服务端和客户端可以使用不同的语言进行开发, 那两种不同语言开发的服务端和客户端之间使用什么进行通信由Thrift来实现

  • YourCode: 就是我们的自己的业务代码
  • FooServiceClient: 用来和服务端通信的客户端对象
  • Foo write()/read() : 这是Thrift为我们自动生成的代码, 底层封装了通过socket对数据的传输逻辑
  • TProtocol: 协议层, 在这一层中规定了数据传输使用的哪种协议
  • TTransport: 传输层: 在这一层中规定了数传输的格式,比如需不需要进行压缩
  • Underlying IO : 数据在网络中的IO交互

Thrift的传输协议

这种传输协议实际上就是规定了数据在网络上采用什么样的格式进行传输

  • TBinaryProtocol : 以二进制格式进行传输
  • TCompactProtocol: 对二进制数据进一步压缩的格式
  • TJsonProtocol: json格式
  • TSimpleJsonProtocol: 针对Json的只写协议
  • TDebugProtocol: 简单易懂的文本格式, 常用于去 调试代码使用

Thrift的数据传输方式

  • TSocket : 阻塞式的Socket 效率最低
  • TFrameTransport: 在非阻塞应用服务中常用. 以Frame为单位进行传输
  • TFileTransport: 以文本格式进行传输
  • TMemoryTransport: 使用内存进行IO, 在java中的实现是 ByteArrayOutPutStream

Thrift支持的服务模型

这种服务类型说的就是服务端示例的类型, 有如下几种

  • TSimpleServer: 简单的单线程服务模型
  • TThreadPoolServer: 虽然是表中的阻塞式IO, 但是采用多线程模型处理
  • TNonblockingServer: 多线程服务模型, 使用的是非阻塞IO常和TFramedTransport数据传输方式搭配使用
  • THsHaServer: 引用线程池去处理, 采用的是半同步,半异步的模式, 针对不同类型的消息, 进行不同的处理, 比如对IO类型的消息异步处理, 对Handler的RPC远程过程调用进行同步处理

Thrift的数据类型

thrift不支持无符号数据类型

简单的数据类型:

名称 简介
byte 有符号字节
i16 16位有符号整数
i32 32位有符号整数
i64 64位有符号整数
double 64位浮点数
string 字符串类型

thrift的容器类型: (支持泛型)

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

结构体: 这个struct类似C语言中的结构体, 初衷也是将不同的数据聚合在一起,方便传输,经过编译器编译完成后其实就是java中的类

struct Student{
1:string name;
2:i32 age;
}

枚举类型

enum Gender{
MALE,
FEMALE
}

异常类型: thrift 支持异常类型表示服务端和客户端之间的通信所可能抛出来的异常, 并且我们可以在service中的方法上throws 异常, 用描述异常出现的时间,异常的类型

exception RequestException{
1:i32 coed
2: string reason
}

服务类型: 服务端和客户端通信使用到的接口 , 就好比java中的接口, 它是一系列方法的集合, thrift 会将service转换成客户端和服务端的框架的代码 , 定义形式如下

service MyService{
string ask(1:string name,2:i32 age)
}

类型定义: 可以像下面这样,使用类似C语言的语法为变量取别名, 转换成我们习惯的命名格式

typedef i32 int
typedef i64 long

常量const类型: thrift 同样支持常量的定义, 使用const关键字:

const string NAME="XXX"

命名空间类型: 关键字是 namespace , thrift的命名空间相当于java中的package, 实际使用上thrift也会将生成的代码放在这下面指定的包中

格式: namespace 语言  路径
实例: namespace java com.changwu.thrift.Demo

文件包含: 同样向C/C++那样,支持文件之间相互包含的操作. 在java中这个动作就是Import

include "global.thrift"

注释: thrift 中的注释有一下几种

// XXXX
# XXX
/*XXX*/

可选和必填的选项, 关键字分别是 required 和 optional, 分别表示对应的字段是可选的还是必填的

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

实战

编写Thrift文件

我们使用上面定义好的thrift文件,去生成我们预期的目标语言的代码

namespace java com.changwu.thrift
namespace py py.thrift // 去别名字
typedef string String
typedef bool boolean
// 我们通过 .thrift文件 描述对象(struct), 方法(service), 类型, 异常等信息
struct Message {
1: optional String msg,
} exception MyExcetion{
1:optional String message,
2:optional String callStack,
3:optional String date
} service PersonService{
Message getResultFromPy(1:required String message) throws(1:MyExcetion e),
}

代码生成

代码生成命令 执行后会在根路径下多出 gen-java的目录,生成的代码也在这里面

命令	thrift --gen 语言 .thrift文件路径
java实例: thrift --gen java src/thrift/data.thrift
py 实例: thrift --gen py src/thrift/data.thrift

执行完这两条命令后我们会得到这样的结果

导入运行时依赖

导入运行时依赖jar包

<!-- https://mvnrepository.com/artifact/org.apache.thrift/libthrift -->
<dependency>
<groupId>org.apache.thrift</groupId>
<artifactId>libthrift</artifactId>
<version>0.13.0</version>
</dependency>

编码

使用java实现客户端, Thrift将客户端的代码生成在 服务类型类中, 也就是我们上面说的那个MessageService中,

通过上面的对Thrift架构的了解我们也能知道, 构建客户端时需要选择 TProtocol 和 TTransport ,并且得和服务端保持一致, 故下面的示例代码也就清晰明了了

        // 帮点端口号和超时时间
TTransport tTransport = new TFramedTransport(new TSocket("localhost",9999),600);
TProtocol protocol = new TCompactProtocol(tTransport);
MessageService.Client client = new MessageService.Client(protocol);
try{
// 打开socket
tTransport.open();
// 发送
Message p = client.getResultFromPy("张三123");
System.out.println("结果: "+p.getMsg());
}catch (Exception e){
e.printStackTrace();
}finally {
tTransport.close();
}

py端Server实现

首先在py端, 实现前面定义的接口的信息, 当收到消息时回调的的具体的业务逻辑就在这里定义

class MessageServiceImpl:

    def getResultFromPy(self, msg):
print("获取到msg = " + msg)
message = ttypes.Message()
message.msg = 'hello java'
return messag

服务端的编写

服务端和客户端代码的编写其实都是遵循前面所说的Thrift的架构规范的, 其次, 需要使用Idea安装一下thrift与py相关的类库

下载 http://www.apache.org/dyn/closer.cgi?path=/thrift/0.13.0/thrift-0.13.0.tar.gz

在本地解压开, 然后找到里面的py目录, 进入lib 找到py 安装对py的支持

执行命令:

python setup.py install
from thrift.transport import TTransport
from thrift.protocol import TCompactProtocol
from thrift.server import TServer
from py.thrift import MessageService
from MessageServiceImpl import MessageServiceImpl try:
# handler就是我们对 ThriftService的实现
personServiceHandler = MessageServiceImpl()
# Foo read()/write() 被Thrift生成在 MesageServive中
processor = MessageService.Processor(personServiceHandler)
# TSocket TProtocol 和 Transport 从上面下载的类库导入进来
# 这三者的作用在上面的架构图上也能提现出来
serverSocket = TSocket.TServerSocket(host="127.0.0.1",port=9999)
transportFactory = TTransport.TFramedTransportFactory()
protocolFactory = TCompactProtocol.TCompactProtocolFactory() # py提供了四种Server , TThreadPoolServer TServer THeaderProtocolFactory
server = TServer.TThreadPoolServer(processor,serverSocket,transportFactory,protocolFactory)
print(">>>>>>>>>>>>>>>>>>服务端启动>>>>>>>>>>>>>>>>>>>>")
server.serve() except Thrift.TException as ex:
print("%s" % ex.message)

ok, 至此代码编写完了, 可是试着运行一下, 体验异构通信魅力

Apache Thrift 的魅力的更多相关文章

  1. Apache thrift RPC 双向通信

    在上一篇介绍Apache thrift 安装和使用,写了一个简单的demo,讲解thrift服务的发布和客户端调用,但只是单向的客户端发送消息,服务端接收消息.而客户端却得不到服务器的响应. 在不涉及 ...

  2. Apache Thrift 跨语言服务开发框架

    Apache Thrift 是一种支持多种编程语言的远程服务调用框架,由 Facebook 于 2007 年开发,并于 2008 年进入 Apache 开源项目管理.Apache Thrift 通过 ...

  3. Apache Thrift 环境配置

    在 Ubuntu 14.04 下Apache Thrift 的安装方法: 1安装依赖包 sudo apt-get install libboost-dev libboost-test-dev libb ...

  4. Apache Thrift 服务开发框架学习记录

    Apache Thrift 是 Facebook 实现的一种高效的.支持多种编程语言的远程服务调用的框架. 前言: 目前流行的服务调用方式有很多种,例如基于 SOAP 消息格式的 Web Servic ...

  5. Apache Thrift

    Baidu Thrift  Google Thrift Apache Thrift - 可伸缩的跨语言服务开发框架

  6. Apache Thrift - 可伸缩的跨语言服务开发框架

    To put it simply, Apache Thrift is a binary communication protocol 原文地址:http://www.ibm.com/developer ...

  7. Apache Thrift学习之二(基础及原理)

    Apache Thrift 是 Facebook 实现的一种高效的.支持多种编程语言的远程服务调用的框架.本文将从 Java 开发人员角度详细介绍 Apache Thrift 的架构.开发和部署,并且 ...

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

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

  9. Apache Thrift入门(安装、测试与java程序编写)

    安装Apache Thrift ubuntu linux运行: #!/bin/bash #下载 wget http://mirrors.cnnic.cn/apache/thrift/0.9.1/thr ...

随机推荐

  1. SpringBoot整合SSM(代码实现Demo)

    SpringBoot整合SSM 如图所示: 一.数据准备: 数据库文件:数据库名:saas-export,表名:ss_company 创建表语句: DROP TABLE IF EXISTS ss_co ...

  2. 微信授权就是这个原理,Spring Cloud OAuth2 授权码模式

    上一篇文章Spring Cloud OAuth2 实现单点登录介绍了使用 password 模式进行身份认证和单点登录.本篇介绍 Spring Cloud OAuth2 的另外一种授权模式-授权码模式 ...

  3. uWSGI+django+nginx 的工作原理流程与部署历程

    一.前言 献给和我一样懵懂中不断汲取知识,进步的人们. 霓虹闪烁,但人们真正需要的,只是一个可以照亮前路的烛光 二.必要的前提 2.1 准备知识 django 一个基于python的开源web框架,请 ...

  4. [考试反思]0816NOIP模拟测试23

    210 210 210 170 还可以.暴力打满就rk4了? 但不管怎么说,总算是在改完题之后理直气壮的写考试反思了. T1是个dp,说水也不太水.(当然某脸只要A掉了一道题就要说那是水题) 我的思路 ...

  5. 轰炸行动(bomb):tarjan,拓扑排序

    考场上看错题,没什么好说的. 然而它就是一个大板子. 发的题解勉强还能看.但是我还想再讲讲. 题目的表述是,如果从A能直接或间接到B,那么就不能同时轰炸A和B. 那么我们从图里随便拽出一条有向路径,从 ...

  6. 复制/etc/profile至/tmp/目录,用查找替换命令删除/tmp/profile文件中的 行首的空白字符及在vim中设置tab缩进为4个字符

    1.复制/etc/profile至/tmp/目录,用查找替换命令删除/tmp/profile文件中的 行首的空白字符 在命令模式下,使用正则表达式匹配 行首有空白字符行的模式:^[[:space:]] ...

  7. Zookeeper作为配置中心使用说明

    为了保证数据高可用,那么我们采用Zookeeper作为配置中心来保存数据.SpringCloud对Zookeeper的集成官方也有说明:spring_cloud_zookeeper 这里通过实践的方式 ...

  8. Kubernetes4-web管理界面

    一.简介 1.环境 环境还是前面的环境 2.Kubernetes Dashboard web界面 kubernetes dashboard (仪表盘)是一个将通用的基于web的监控和操作界面加入kub ...

  9. elastalter邮件告警

    一:简介 ElastAlert是一个简单的框架,用于通过Elasticsearch中的数据异常警告,峰值或其他感兴趣的模式. 监控类型 "匹配Y时间内有X个事件的地方"(frequ ...

  10. django_4数据库3——admin

    生成admin界面 1.setting.py中,保证'django.contrib.admin',应用打开,django1.11默认打开的 2.url.py中的admin默认时打开的 3.对model ...