V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
hansonwang99
V2EX  ›  程序员

RPC 框架实践之: Apache Thrift

  •  
  •   hansonwang99 ·
    hansonwang99 · 2018-05-15 07:19:33 +08:00 · 3401 次点击
    这是一个创建于 2172 天前的主题,其中的信息可能已经有所发展或是发生改变。

    我的桌面

    这是我的简单桌面之一。


    一、概述

    RPC ( Remote Procedure Call )即 远程过程调用,说的这么抽象,其实简化理解就是一个节点如何请求另一节点所提供的服务。在文章 微服务调用链追踪中心搭建 一文中模拟出来的调用链:ServiceA ---> ServiceB ---> ServiceC 就是一个远程调用的例子,只不过这篇文章里是通过RestTemplate这种 同步调用方式,利用的是 HTTP 协议在应用层完成的,这种方法虽然奏效,但有时效率并不高。而 RPC 可以不依赖于应用层协议,可以直接基于 TCP 进行远程调用,在传输层中即可完成通信,因此更适于某些对效率要求更高的场景。由于 RPC 调用方式依赖于客户端与服务端之间建立 Socket 连接来实现二进制数据通信,底层会比较复杂,所以一些 RPC 框架应运而生来封装这种复杂性,让开发者将精力聚焦于业务之上。常见的 RPC 框架包括:Thrift、gRPC、Finagle、Dubbo 等等,从本文开始作者将选一些实践一下,本文主要记录作者对于 Thrift 框架的实践过程。

    Thrift 是 Apache 的项目,它结合了功能强大的软件堆栈和代码生成引擎,可以在诸多语言之间提供无缝支持。

    心动不如行动吧!


    二、实验环境

    • Mac OS X 10.13.2
    • SpringBoot 2.0.1
    • Thrift 0.11.0
    • IDE:IntelliJ IDEA 2018.01

    为了便于读者理解,我先将下文内容总结一下,包含 7 点:

    • Thrift 环境搭建
    • IDEA 中 Thrift 插件配置
    • 创建 Thrift 项目并编译(目的:定义 RPC 接口)
    • 开发 Thrift API 接口
    • 开发 RPC 服务端
    • 开发 RPC 客户端
    • RPC 通信实际实验

    三、Thrift 环境搭建

    • **方法一:**原生安装方式,通过官方提供的步骤一步一步来安装

    参考这里:Mac 上 Thrift 官方安装教程

    • **方法二:**使用 brew 工具(推荐

    brew install thrift

    brew install thrift


    四、IDEA 中 Thrift 插件配置

    方法一:直接在 IDEA 界面中配置

    打开 IDEA 的插件中心,搜索 Thrift 即可安装

    利用插件中心安装 Thrift

    方法二:手动下载 Thrift 插件安装

    就像文章 SpringBoot 优雅编码之:Lombok 加持 一文中在 IDEA 中安装 Lombok 插件一样,有时由于网络原因,方法一不奏效时插件装不上,此时可以手动下载插件并安装。

    可以去如下地址下载 Thrift 插件: http://plugins.jetbrains.com/plugin/7331-thrift-support

    手动下载 Thrift 插件

    然后去 IDEA 中 Install plugin from disk... 选择下载的 zip 包安装,然后重启 IDE 即可

    Install plugin from disk

    安装完成的成功标志是 Compiler 中出现了 Thrift 编译器!如下图所示:

    成功安装 Thrift 插件


    五、创建 Thrift 项目并编译(定义 RPC 接口)

    • 第一步:创建 Thrift 项目并配置

    IDE 很智能地在 New Project 时提供 Thrift 项目创建选项:

    Thrift 项目创建选项

    项目创建完成以后,在 Project Settings 中设置好 Facets 的 Thrift 配置,如下图所示,这里我们添加一个 Java 的 Generator

    添加一个 Java 的 Generator

    在弹出的对话框中配置好 Output folder 路径,该路径用于存放由 thrift 文件 转化而成的 java 源文件

    配置好 Output folder

    OK,Thrift 项目就绪了!

    • 第二步:创建 thrift 接口文件

    这里创建一个 thrift 接口文件:RPCDateService.thrift

    thrift 文件的写法我不赘述,跟 gRPC 一样有其自己的语法,namespace 是最后生成的接口文件的包名

    namespace java com.hansonwang99.thrift.interface
    service RPCDateService{
        string getDate(1:string userName)
    }
    

    在该接口文件中,我们定义了一个 提供日期的 Service,让客户端能通过该接口查询到服务器当前的时间

    • 第三步:编译 Thrift 源文件生成 Java 接口类

    右击.thrift 源文件,点击 Recompile 'xxx.thrift' 即可完成 thrift 接口文件 ---> java 接口文件 的转换

    Recompile 'xxx.thrift'

    输出的 Java 接口文件生成于上文中配置的 output 中,其 包结构=上文.thrift 文件中的 namespace ,其包结构如下图所示,该 Java 接口十分重要,后续会用于实现 Client 和 Server 之间的 RPC 调用。

    输出的.java 文件


    六、开发 Thrift API 接口

    我们创建一个 Maven 项目:ThriftAPI,其包含的的就是上文由自定义 Thrift 接口生成的 Java 接口:RPCDateService.java 文件,该文件将用于后面的 RPC 服务端和 RPC 客户端的代码实现!

    • pom.xml 中添加 thrift 依赖
        <dependencies>
            <dependency>
                <groupId>org.apache.thrift</groupId>
                <artifactId>libthrift</artifactId>
                <version>0.11.0</version>
            </dependency>
        </dependencies>
    
    • 添加 RPCDateService.java

    将上文 第五步RPCDateService.thrift 生成的 RPCDateService.java 原样拷贝到该 Maven 项目中即可,代码结构如下:

    ThriftAPI 代码结构

    再次强调,该 ThriftAPI 项目 会服务于下文即将要创建的 RPC 服务端和 RPC 客户端


    七、开发 RPC 服务端

    我们是利用 SpringBoot 来实现 RPC 服务端

    • pom.xml 中添加依赖

    这里除了自动添加好的 SpringBoot 依赖外,需要额外添加的就是上文的 ThriftAPI 依赖

    <dependency>
            <groupId>com.hansonwang99</groupId>
            <artifactId>ThriftAPI</artifactId>
             <version>1.0-SNAPSHOT</version>
    </dependency>
    
    • 创建 Controller 并实现 RPC 接口
    @Controller
    public class RPCDateServiceImpl implements RPCDateService.Iface {
        @Override
        public String getDate(String userName) throws TException {
            Date now=new Date();
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("今天是"+"yyyy 年 MM 月 dd 日 E kk 点 mm 分");
            String nowTime = simpleDateFormat.format( now );
            return "Hello " + userName + "\n" + nowTime;
        }
    }
    

    这里将服务器当前时间以字符串形式返回给调用端!

    • 编写 RPCThriftServer:用于启动 RPC 服务器
    @Component
    public class RPCThriftServer {
        protected final Logger logger = LoggerFactory.getLogger(this.getClass());
        @Value("${thrift.port}")
        private int port;
        @Value("${thrift.minWorkerThreads}")
        private int minThreads;
        @Value("${thrift.maxWorkerThreads}")
        private int maxThreads;
    
        private TBinaryProtocol.Factory protocolFactory;
        private TTransportFactory transportFactory;
    
        @Autowired
        private RPCDateServiceImpl rpcDateService;
    
        public void init() {
            protocolFactory = new TBinaryProtocol.Factory();
            transportFactory = new TTransportFactory();
        }
    
        public void start() {
            RPCDateService.Processor processor = new RPCDateService.Processor<RPCDateService.Iface>( rpcDateService );
            init();
            try {
                TServerTransport transport = new TServerSocket(port);
                TThreadPoolServer.Args tArgs = new TThreadPoolServer.Args(transport);
                tArgs.processor(processor);
                tArgs.protocolFactory(protocolFactory);
                tArgs.transportFactory(transportFactory);
                tArgs.minWorkerThreads(minThreads);
                tArgs.maxWorkerThreads(maxThreads);
                TServer server = new TThreadPoolServer(tArgs);
                logger.info("thrift 服务启动成功, 端口={}", port);
                server.serve();
            } catch (Exception e) {
                logger.error("thrift 服务启动失败", e);
            }
        }
    }
    
    • 创建 SpringBootApplication
    @SpringBootApplication
    public class RPCThriftServerApplication {
        private static RPCThriftServer rpcThriftServer;
        public static void main(String[] args) {
            ApplicationContext context = SpringApplication.run(RPCThriftServerApplication.class, args);
            try {
                rpcThriftServer = context.getBean(RPCThriftServer.class);
                rpcThriftServer.start();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    • 添加配置文件 application.properties
    thrift.port=6666
    thrift.minWorkerThreads=10
    thrift.maxWorkerThreads=100
    

    我们让 thrift 服务起在 6666 端口!

    • 启动 RPC 服务端服务

    RPC 服务端启动成功


    八、开发 RPC 客户端

    这里同样用 SpringBoot 来实现 RPC 客户端!

    • pom.xml 中添加依赖

      此处同 RPC 服务端依赖,不赘述

    • 编写 RPCThriftClient:用于发出 RPC 调用

    这里包含两个文件:RPCThriftClient.javaRPCThriftClientConfig.java

    RPCThriftClient.java如下:

    public class RPCThriftClient {
        private RPCDateService.Client client;
        private TBinaryProtocol protocol;
        private TSocket transport;
        private String host;
        private int port;
    
        public String getHost() {
            return host;
        }
        public void setHost(String host) {
            this.host = host;
        }
        public int getPort() {
            return port;
        }
        public void setPort(int port) {
            this.port = port;
        }
    
        public void init() {
            transport = new TSocket(host, port);
            protocol = new TBinaryProtocol(transport);
            client = new RPCDateService.Client(protocol);
        }
    
        public RPCDateService.Client getRPCThriftService() {
            return client;
        }
    
        public void open() throws TTransportException {
            transport.open();
        }
    
        public void close() {
            transport.close();
        }
    }
    

    RPCThriftClientConfig.java是利用 config 生成 bean

    @Configuration
    public class RPCThriftClientConfig {
        @Value("${thrift.host}")
        private String host;
        @Value("${thrift.port}")
        private int port;
    
        @Bean(initMethod = "init")
        public RPCThriftClient rpcThriftClient() {
            RPCThriftClient rpcThriftClient = new RPCThriftClient();
            rpcThriftClient.setHost(host);
            rpcThriftClient.setPort(port);
            return rpcThriftClient;
        }
    }
    
    • 编写 Restful 的 Controller 作为调用入口
    @RestController
    @RequestMapping("/hansonwang99")
    public class RPCThriftContoller {
        protected final Logger logger = LoggerFactory.getLogger(this.getClass());
        @Autowired
        private RPCThriftClient rpcThriftClient;
    
        @RequestMapping(value = "/thrift", method = RequestMethod.GET)
        public String thriftTest(HttpServletRequest request, HttpServletResponse response) {
            try {
                rpcThriftClient.open();
                return rpcThriftClient.getRPCThriftService().getDate("hansonwang99");
            } catch (Exception e) {
                logger.error("RPC 调用失败", e);
                return "error";
            } finally {
                rpcThriftClient.close();
            }
        }
    }
    
    • 创建 SpringBootApplication
    @SpringBootApplication
    public class RPCThriftClientApplication {
        public static void main(String[] args) {
            SpringApplication.run(RPCThriftClientApplication.class, args);
        }
    }
    
    • 添加配置文件 application.properties
    thrift.host=localhost
    thrift.port=6666
    server.port=9999
    
    • 启动 RPC 客户端服务

    RPC 客户端启动成功


    九、RPC 通信实验

    我们浏览器输入:localhost:9999/hansonwang99/thrift 即可查看客户端从服务端取回的服务器当前时间,说明 RPC 通信过程打通!

    客户端成功从服务端取回结果


    十、后记

    作者更多的原创文章:在 V2EX

    本文实验代码已开源,点击获取

    扩展阅读:


    17 条回复    2018-05-16 23:10:59 +08:00
    carlclone
        1
    carlclone  
       2018-05-15 07:37:17 +08:00
    马克
    sdrzlyz
        2
    sdrzlyz  
       2018-05-15 07:44:52 +08:00 via Android
    大清已经亡了。除了老项目及自己改进过的 thrift,实在想不出为啥这个时候还选 thrift。。。坐等被喷
    DearMark
        3
    DearMark  
       2018-05-15 08:19:27 +08:00
    @sdrzlyz 跨语言用这个,有更好的选择吗?
    askfilm
        4
    askfilm  
       2018-05-15 08:47:45 +08:00
    @sdrzlyz 新的东西不一定是最好的,thrift 是个成熟优秀的产品, 你说的有些过激了
    p2pCoder
        5
    p2pCoder  
       2018-05-15 08:56:16 +08:00
    thrift 还是序列化重于 rpc 吧
    hand515
        6
    hand515  
       2018-05-15 09:28:02 +08:00
    @p2pCoder #5 Protobuf 才是序列化终于 RPC 吧
    webluoye
        7
    webluoye  
       2018-05-15 09:35:43 +08:00
    目前跨语言也是用这个
    guoziyan
        8
    guoziyan  
       2018-05-15 09:52:59 +08:00
    拓展太麻烦了 新项目已经不用 thrift 了
    Raymon111111
        9
    Raymon111111  
       2018-05-15 10:39:23 +08:00
    thrift 在我司一天承载几百亿的调用, 好用的很.
    hansonwang99
        10
    hansonwang99  
    OP
       2018-05-15 11:16:53 +08:00 via iPhone
    @Raymon111111 真棒
    hansonwang99
        11
    hansonwang99  
    OP
       2018-05-15 11:17:22 +08:00 via iPhone
    m
    odirus
        12
    odirus  
       2018-05-15 11:19:21 +08:00
    @Raymon111111 #9

    求推荐贵司目前使用的 thrift 服务治理方案,能满足这么多的调用需求,应该有成熟的服务治理方案了。
    A555
        13
    A555  
       2018-05-15 17:06:40 +08:00
    居然是大法笔记本 我的大法笔记本上次被人说是 vivo 的 尴尬
    hansonwang99
        14
    hansonwang99  
    OP
       2018-05-15 17:19:07 +08:00 via iPhone
    @A555 哈哈
    Narcissu5
        15
    Narcissu5  
       2018-05-15 19:04:03 +08:00
    通过 brew 安装的 thrift 是 0.9 的,新的已经到 0.11 了。除了 windows 其它平台使用 thrift 的第一课应该是如何编出来
    SouthCityCowBoy
        16
    SouthCityCowBoy  
       2018-05-15 22:42:57 +08:00
    膜拜一波
    hansonwang99
        17
    hansonwang99  
    OP
       2018-05-16 23:10:59 +08:00
    @Narcissu5 我的用 brew 安装的 thrift 是 0.11.0
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   3513 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 29ms · UTC 11:10 · PVG 19:10 · LAX 04:10 · JFK 07:10
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.