百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

Thrift之服务模型和序列化机制(thrift服务治理)

ccwgpt 2024-10-15 08:58 26 浏览 0 评论

一、Thrift介绍

Thrift是一个软件框架,用来进行可扩展且跨语言的服务的开发。它结合了功能强大的软件堆栈和代码生成引擎。其允许你定义一个简单的定义文件中的数据类型和服务接口。以作为输入文件,编译器生成代码用来方便地生成RPC客户端和服务器通信的无缝跨编程语言。

二、Thrift基础架构

Thrift是一个客户端和服务端的架构体系,数据通过socket传输;

具有自己内部定义的传输协议规范(TProtocol)和传输数据标准(TTransports);

通过IDL脚本对传输数据的数据结构(struct) 和传输数据的业务逻辑(service)根据不同的运行环境快速的构建相应的代码;

通过自己内部的序列化机制对传输的数据进行简化和压缩提高高并发、 大型系统中数据交互的性能。

  • Thrift 支持的数据类型

基本类型:
  bool: 布尔值
  byte: 8位有符号整数
  i16: 16位有符号整数
  i32: 32位有符号整数
  i64: 64位有符号整数
  double: 64位浮点数
  string: UTF-8编码的字符串
  binary: 二进制串
结构体类型:
  struct: 定义的结构体对象
容器类型:
  list: 有序元素列表
  set: 无序无重复元素集合
  map: 有序的key/value集合
异常类型:
  exception: 异常类型
服务类型:
  service: 具体对应服务的类

  • 协议

Thrift可以让你选择客户端与服务端之间传输通信协议的类别,在传输协议上总体上划分为文本(text)和二进制(binary)传输协议, 为节约带宽,提供传输效率,一般情况下使用二进制类型的传输协议为多数,但有时会还是会使用基于文本类型的协议,这需要根据项目/产品中的实际需求:
1、TBinaryProtocol – 二进制编码格式进行数据传输。
2、TCompactProtocol – 这种协议非常有效的,使用Variable-Length Quantity (VLQ) 编码对数据进行压缩。
3、TJSONProtocol – 使用JSON的数据编码协议进行数据传输。
4、TSimpleJSONProtocol – 这种节约只提供JSON只写的协议,适用于通过脚本语言解析
5、TDebugProtocol – 在开发的过程中帮助开发人员调试用的,以文本的形式展现方便阅读。

  • 传输层

1、TSocket- 使用堵塞式I/O进行传输,也是最常见的模式。
2、TFramedTransport- 使用非阻塞方式,按块的大小,进行传输,类似于Java中的NIO。
3、TFileTransport- 顾名思义按照文件的方式进程传输,虽然这种方式不提供Java的实现,但是实现起来非常简单。
4、TMemoryTransport- 使用内存I/O,就好比Java中的ByteArrayOutputStream实现。

5、TZlibTransport- 使用执行zlib压缩,不提供Java的实现。


三、Thrift网络服务模型

Thrif 提供网络模型:单线程、多线程、事件驱动。从另一个角度划分为:阻塞服务模型、非阻塞服务模型。

  • 阻塞服务

TSimpleServer

TThreadPoolServer

  • 非阻塞服务模型

TNonblockingServer

THsHaServer

TThreadedSelectorServer

1、TSimpleServer

TSimpleServer实现是非常的简单,循环监听新请求的到来并完成对请求的处理,是个单线程阻塞模型。由于是一次只能接收和处理一个socket连接,效率比较低,在实际开发过程中很少用到它。

2、TThreadPoolServer

ThreadPoolServer为解决了TSimpleServer不支持并发和多连接的问题, 引入了线程池。但仍然是多线程阻塞模式即实现的模型是One Thread Per Connection。

线程池采用能线程数可伸缩的模式,线程池中的队列采用同步队列(SynchronousQueue)。

ThreadPoolServer拆分了监听线程(accept)和处理客户端连接的工作线程(worker), 监听线程每接到一个客户端, 就投给线程池去处理。

import org.apache.thrift.TException;
import org.apache.thrift.TProcessor;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.server.TServer;
import org.apache.thrift.server.TThreadPoolServer;
import org.apache.thrift.transport.TServerSocket;

/**
 * 注册服务端
 *     线程池服务模型,使用标准的阻塞式IO,预先创建一组线程处理请求
 */
public class ThriftTThreadPoolServer {
    // 注册端口
    public static final int SERVER_PORT = 8080;

    public static void main(String[] args) throws TException {
        TProcessor tprocessor = new HelloWorld.Processor<HelloWorld.Iface>(new HelloWorldImpl());
        // 阻塞IO
        TServerSocket serverTransport = new TServerSocket(SERVER_PORT);
        // 多线程服务模型
        TThreadPoolServer.Args tArgs = new TThreadPoolServer.Args(serverTransport);
        tArgs.processor(tprocessor);
        // 客户端协议要一致
        tArgs.protocolFactory(new TBinaryProtocol.Factory());
         // 线程池服务模型,使用标准的阻塞式IO,预先创建一组线程处理请求。
        TServer server = new TThreadPoolServer(tArgs);
        System.out.println("Hello TThreadPoolServer....");
        server.serve(); // 启动服务
    }
}
import org.apache.thrift.TException;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport;

/**
 * 客户端调用
 * 阻塞
 */
public class BlockClient {
    public static final String SERVER_IP = "127.0.0.1";
    public static final int SERVER_PORT = 8080;
    public static final int TIMEOUT = 30000;

    public static void main(String[] args) throws TException {
        // 设置传输通道
        TTransport transport = new TSocket(SERVER_IP, SERVER_PORT, TIMEOUT);
        // 协议要和服务端一致
        // 使用二进制协议 
        TProtocol protocol = new TBinaryProtocol(transport);
        // 创建Client
        HelloWorld.Client client = new HelloWorld.Client(protocol);
        transport.open();
        String result = client.sayHello("thrift");
        System.out.println("result : " + result);
        // 关闭资源
        transport.close();
    }
}


TThreadPoolServer模式优点:

线程池模式中,数据读取和业务处理都交由线程池完成,主线程只负责监听新连接,因此在并发量较大时新连接也能够被及时接受。线程池模式比较适合服务器端能预知最多有多少个客户端并发的情况,这时每个请求都能被业务线程池及时处理,性能也非常高。

TThreadPoolServer模式缺点:

线程池模式的处理能力受限于线程池的工作能力,当并发请求数大于线程池中的线程数时,新请求也只能排队等待。

3、TNonblockingServer

TNonblockingServer采用单线程非阻塞(NIO)的模式, 借助Channel/Selector机制, 采用IO事件模型来处理。所有的socket都被注册到selector中,在一个线程中通过seletor循环监控所有的socket,每次selector结束时,处理所有的处于就绪状态的socket,对于有数据到来的socket进行数据读取操作,对于有数据发送的socket则进行数据发送,对于监听socket则产生一个新业务socket并将其注册到selector中。

private void select() {
  try {
    selector.select();  // wait for io events.
    // process the io events we received
    Iterator<SelectionKey> selectedKeys = selector.selectedKeys().iterator();
    while (!stopped_ && selectedKeys.hasNext()) {
      SelectionKey key = selectedKeys.next();
      selectedKeys.remove();
      if (key.isAcceptable()) {
        handleAccept(); // deal with accept
      } else if (key.isReadable()) {
        handleRead(key);    // deal with reads
      } else if (key.isWritable()) {
        handleWrite(key); // deal with writes
      }
    }
  } catch (IOException e) {
  }
}

select代码里对accept/read/write等IO事件进行监控和处理, 唯一可惜的这个单线程处理. 当遇到handler里有阻塞的操作时, 会导致整个服务被阻塞住。

import org.apache.thrift.TException;
import org.apache.thrift.TProcessor;
import org.apache.thrift.protocol.TCompactProtocol;
import org.apache.thrift.server.TNonblockingServer;
import org.apache.thrift.server.TServer;
import org.apache.thrift.transport.TFramedTransport;
import org.apache.thrift.transport.TNonblockingServerSocket;

/**
 * 注册服务端
 *     使用非阻塞式IO,服务端和客户端需要指定 TFramedTransport 数据传输的方式  
 */
public class ThriftTNonblockingServer {
    // 注册端口
    public static final int SERVER_PORT = 8080;

    public static void main(String[] args) throws TException {
        // 处理器
        TProcessor tprocessor = new HelloWorld.Processor<HelloWorld.Iface>(new HelloWorldImpl());
        // 传输通道 - 非阻塞方式  
        TNonblockingServerSocket serverTransport = new TNonblockingServerSocket(SERVER_PORT);
        // 异步IO,需要使用TFramedTransport,它将分块缓存读取。  
        TNonblockingServer.Args tArgs = new TNonblockingServer.Args(serverTransport);
        tArgs.processor(tprocessor);
        tArgs.transportFactory(new TFramedTransport.Factory());
        // 使用高密度二进制协议 
        tArgs.protocolFactory(new TCompactProtocol.Factory());
        // 使用非阻塞式IO,服务端和客户端需要指定TFramedTransport数据传输的方式
        TServer server = new TNonblockingServer(tArgs);
        System.out.println("Hello TNonblockingServer....");
        server.serve(); // 启动服务
    }
}
import org.apache.thrift.TException;
import org.apache.thrift.protocol.TCompactProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.transport.TFramedTransport;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport;

/**
 * 客户端调用
 * 非阻塞
 */
public class NonblockingClient {
    public static final String SERVER_IP = "127.0.0.1";
    public static final int SERVER_PORT = 8080;
    public static final int TIMEOUT = 30000;

    public static void main(String[] args) throws TException {
        // 设置传输通道,对于非阻塞服务,需要使用TFramedTransport,它将数据分块发送  
        TTransport transport = new TFramedTransport(new TSocket(SERVER_IP, SERVER_PORT, TIMEOUT));
        // 协议要和服务端一致
        //HelloTNonblockingServer
        // 使用高密度二进制协议 
        TProtocol protocol = new TCompactProtocol(transport);
        
        // 使用二进制协议 
        //TProtocol protocol = new TBinaryProtocol(transport);
        HelloWorld.Client client = new HelloWorld.Client(protocol);
        transport.open();
        String result = client.sayHello("jack");
        System.out.println("result : " + result);
        // 关闭资源
        transport.close();
    }
}

TNonblockingServer模式优点:

相比于TSimpleServer效率提升主要体现在IO多路复用上,TNonblockingServer采用非阻塞IO,同时监控多个socket的状态变化;

TNonblockingServer模式缺点:

TNonblockingServer模式在业务处理上还是采用单线程顺序来完成,在业务处理比较复杂、耗时的时候,例如某些接口函数需要读取数据库执行时间较长,此时该模式效率也不高,因为多个调用请求任务依然是顺序一个接一个执行。

4、THsHaServer

THsHaServer类是TNonblockingServer类的子类,为解决TNonblockingServer的缺点, THsHa引入了线程池去处理, 其模型把读写任务放到线程池去处理即多线程非阻塞模式。HsHa是: Half-sync/Half-async的处理模式, Half-aysnc是在处理IO事件上(accept/read/write io), Half-sync用于handler对rpc的同步处理上。因此可以认为THsHaServer半同步半异步。

THsHaServer的优点:

与TNonblockingServer模式相比,THsHaServer在完成数据读取之后,将业务处理过程交由一个线程池来完成,主线程直接返回进行下一次循环操作,效率大大提升;

THsHaServer的缺点:

主线程需要完成对所有socket的监听以及数据读写的工作,当并发请求数较大时,且发送数据量较多时,监听socket上新连接请求不能被及时接受。

5、TThreadedSelectorServer

TThreadedSelectorServer是大家广泛采用的服务模型,其多线程服务器端使用非堵塞式I/O模型,是对TNonblockingServer的扩充, 其分离了Accept和Read/Write的Selector线程, 同时引入Worker工作线程池。

(1)一个AcceptThread线程对象,专门用于处理监听socket上的新连接;

(2)若干个SelectorThread对象专门用于处理业务socket的网络I/O操作,所有网络数据的读写均是有这些线程来完成;

(3)一个负载均衡器SelectorThreadLoadBalancer对象,主要用于AcceptThread线程接收到一个新socket连接请求时,决定将这个新连接请求分配给哪个SelectorThread线程。

(4)一个ExecutorService类型的工作线程池,在SelectorThread线程中,监听到有业务socket中有调用请求过来,则将请求读取之后,交个ExecutorService线程池中的线程完成此次调用的具体执行



MainReactor就是Accept线程, 用于监听客户端连接, SubReactor采用IO事件线程(多个), 主要负责对所有客户端的IO读写事件进行处理. 而Worker工作线程主要用于处理每个rpc请求的handler回调处理(这部分是同步的)。因此其也是Half-Sync/Half-Async(半异步-半同步)的 。

TThreadedSelectorServer模式对于大部分应用场景性能都不会差,因为其有一个专门的线程AcceptThread用于处理新连接请求,因此能够及时响应大量并发连接请求;另外它将网络I/O操作分散到多个SelectorThread线程中来完成,因此能够快速对网络I/O进行读写操作,能够很好地应对网络I/O较多的情况。

import org.apache.thrift.TException;
import org.apache.thrift.TProcessor;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.server.TServer;
import org.apache.thrift.server.TThreadedSelectorServer;
import org.apache.thrift.transport.TFramedTransport;
import org.apache.thrift.transport.TNonblockingServerSocket;

/**
 * TFramedTransport 数据传输的方式
 */
public class ThriftTThreadedSelectorServer {
    // 注册端口
    public static final int SERVER_PORT = 8080;

    public static void main(String[] args) throws TException {        
        TProcessor tprocessor = new HelloWorld.Processor<HelloWorld.Iface>(new HelloWorldImpl());
        
        TThreadedSelectorServer.Args tArgs = new TThreadedSelectorServer.Args(serverTransport);
        tArgs.processor(tprocessor);
        tArgs.transportFactory(new TFramedTransport.Factory());
        // 二进制协议
        tArgs.protocolFactory(new TBinaryProtocol.Factory());
        
        TServer server = new TThreadedSelectorServer(tArgs);
        System.out.println("Hello TThreadedSelectorServer....");
        server.serve(); // 启动服务
        
    }

}


四、Thrift序列化机制

Thrift提供了可扩展序列化机制, 不但兼容性好而且压缩率高。

thrift 数据格式描述

thrift的向后兼容性(Version)借助属性标识(数字编号id + 属性类型type)来实现, 可以理解为在序列化后(属性数据存储由 field_name:field_value => id+type:field_value)

我们定义IDL文件形如

namespace java stu.thrift;
 
struct User {
  1: required string name
  2: required string address
}

是不是和我们使用序列化的数据xml/json有了很大的差别,那么我们来比较是常见的数据传输格式

数据传输格式类型优点缺点Xml文本

1、良好的可读性

2、序列化的数据包含完整的结构

3、调整不同属性的顺序对序列化/反序列化不影响

1、数据传输量大

2、不支持二进制数据类型

Json文本

1、良好的可读性

2、调整不同属性的顺序对序列化/反序列化不影响

1、丢弃了类型信息, 比如"price":100, 对price类型是int/double解析有二义性

2、不支持二进制数据类型

Thrift二进制高效

1、不宜读

2、向后兼容有一定的约定限制,采用id递增的方式标识并以optional修饰来添加

Google Protobuf二进制高效

1、不宜读

2、向后兼容有一定的约定限制

相关推荐

RACI矩阵:项目管理中的角色与责任分配利器

作者:赵小燕RACI矩阵RACI矩阵是项目管理中的一种重要工具,旨在明确团队在各个任务中的角色和职责。通过将每个角色划分为负责人、最终责任人、咨询人和知情人四种类型,RACI矩阵确保每个人都清楚自己...

在弱矩阵组织中,如何做好项目管理工作?「慕哲制图」

慕哲出品必属精品系列在弱矩阵组织中,如何做好项目管理工作?【慕哲制图】-------------------------------慕哲制图系列0:一图掌握项目、项目集、项目组合、P2、商业分析和NP...

Scrum模式:每日站会(Daily Scrum)

定义每日站会(DailyScrum)是一个Scrum团队在进行Sprint期间的日常会议。这个会议的主要目的是为了应对Sprint计划中的不断变化,确保团队能够有效应对挑战并达成Sprint目标。为...

大家都在谈论的敏捷开发&amp;Scrum,到底是什么?

敏捷开发作为一种开发模式,近年来深受研发团队欢迎,与瀑布式开发相比,敏捷开发更轻量,灵活性更高,在当下多变环境下,越来越多团队选择敏捷开发。什么是敏捷?敏捷是一种在不确定和变化的环境中,通过创造和响应...

敏捷与Scrum是什么?(scrum敏捷开发是什么)

敏捷是一种思维模式和哲学,它描述了敏捷宣言中的一系列原则。另一方面,Scrum是一个框架,规定了实现这种思维方式的角色,事件,工件和规则/指南。换句话说,敏捷是思维方式,Scrum是规定实施敏捷哲学的...

敏捷项目管理与敏捷:Scrum流程图一览

敏捷开发中的Scrum流程通常可以用一个简单的流程图来表示,以便更清晰地展示Scrum框架的各个阶段和活动。以下是一个常见的Scrum流程图示例:这个流程图涵盖了Scrum框架的主要阶段和活动,其中包...

一张图掌握项目生命周期模型及Scrum框架

Mockito 的最佳实践(mock方法)

记得以前面试的时候,面试官问我,平常开发过程中自己会不会测试?我回答当然会呀,自己写的代码怎么不测呢。现在想想我好像误会他的意思了,他应该是想问我关于单元测试,集成测试以及背后相关的知识,然而当时说到...

EffectiveJava-5-枚举和注解(java枚举的作用与好处)

用enum代替int常量1.int枚举:引入枚举前,一般是声明一组具名的int常量,每个常量代表一个类型成员,这种方法叫做int枚举模式。int枚举模式是类型不安全的,例如下面两组常量:性别和动物种...

Maven 干货 全篇共:28232 字。预计阅读时间:110 分钟。建议收藏!

Maven简介Maven这个词可以翻译为“知识的积累”,也可以翻译为“专家”或“内行”。Maven是一个跨平台的项目管理工具。主要服务于基于Java平台的项目构建、依赖管理和项目信息管理。仔...

Java单元测试框架PowerMock学习(java单元测试是什么意思)

前言高德的技术大佬在谈论方法论时说到:“复杂的问题要简单化,简单的问题要深入化。”这句话让我感触颇深,这何尝不是一套编写代码的方法——把一个复杂逻辑拆分为许多简单逻辑,然后把每一个简单逻辑进行深入实现...

Spring框架基础知识-第六节内容(Spring高级话题)

Spring高级话题SpringAware基本概念Spring的依赖注入的最大亮点是你所有的Bean对Spring容器的存在是没有意识的。但是在实际的项目中,你的Bean必须要意识到Spring容器...

Java单元测试浅析(JUnit+Mockito)

作者:京东物流秦彪1.什么是单元测试(1)单元测试环节:测试过程按照阶段划分分为:单元测试、集成测试、系统测试、验收测试等。相关含义如下:1)单元测试:针对计算机程序模块进行输出正确性检验工作...

揭秘Java代码背后的质检双侠:JUnit与Mockito!

你有没有发现,现在我们用的手机App、逛的网站,甚至各种智能设备,功能越来越复杂,但用起来却越来越顺畅,很少遇到那种崩溃、卡顿的闹心事儿?这背后可不是程序员一拍脑袋写完代码就完事儿了!他们需要一套严谨...

单元测试框架哪家强?Junit来帮忙!

大家好,在前面的文章中,给大家介绍了以注解和XML的方式分别实现IOC和依赖注入。并且我们定义了一个测试类,通过测试类来获取到了容器中的Bean,具体的测试类定义如下:@Testpublicvoid...

取消回复欢迎 发表评论: