ellios's blog

ellios's trivial story.

Thrift入门

| Comments

前几天介绍了下Protocol Buffer,这次研究下和PB比较类似的Thrift。这两者都定义了代码独立的数据结构描述语法,并且提供了工具将定义的数据结构转化为相应的代码(Java,C++,Python之类的)。但是从使用上来说,Thrift提供的功能要更丰富一些,简单列下对二者区别的认识

  • Thrfit从语法上来说,更接近C的语法,对程序员的友好性要更好些
  • Thrift对rpc的支持比较好,接口的描述语法更类似与通常的接口声明。PB的接口只支持单一的输入参数,如果有多个参数,需要自己定义参数的数据结构,使用起来很别扭。不仅仅是语法上,Thrift对rpc是提供了完整的支持的,用Thrift可以很方便的写出rpc的Server和Client端,PB则要自己写网络层。
  • PB对于容器类的数据结构只提供了对list的直接支持,而Thrift还提供了set和map类型,符合通常程序员的使用习惯
  • Thrift直接支持的语言比较多,PB只支持Java,C++,Python,不过PB有很多第三方实现的对其他语言的支持
  • PB的文档更好些,从发布的节奏上来看PB要更活跃一些。学习Thrift一定要看看源码
  • PB的性能要更好些,不过Thrift也不会太差,一般的生产环境下,这点性能提升可以忽略了

使用

Thrift在使用前同样需要安装,这里给个安装的传送门。Thrift的源码里有些tutorial的代码,里面有各种语言的使用示例,初学thrift的话,把tutorial里的代码看明白,基本就能入门了。

Types

先来打点基础,看下thrift支持哪些数据类型:

  • bool Boolean, one byte
  • byte Signed byte
  • i16 Signed 16-bit integer
  • i32 Signed 32-bit integer
  • i64 Signed 64-bit integer
  • double 64-bit floating point value
  • string String
  • binary Blob (byte array)
  • map<t1,t2> Map from one type to another
  • list Ordered list of one type
  • set Set of unique elements of one type

相对与pb,thrift多了map,list,set这些容器类型,但是基本的数值类型少了一些,不过我们需要用到的也都有了。

示例

基础的内容就介绍这么多了,下面直接上例子,估计看了例子大家对thrift的语法也了解差不多了。

demo.thrift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/*
* demo.thrift
* thrift usage demo
*/
namespace cpp ellios.me
namespace java ellios.me
namespace py ellios.me

enum Operation {
  ADD = 1,
  SUBTRACT = 2,
  MULTIPLY = 3,
  DIVIDE = 4
}

struct Work {
  1: i32 num1 = 0,
  2: i32 num2,
  3: Operation op,
  4: optional string comment,
}

exception InvalidOperation {
  1: i32 what,
  2: string why
}

service Calculator {

   i32 calculate(1:i32 logid, 2:Work w) throws (1:InvalidOperation ouch),

}

java的例子

执行下面的命令,会生成相应的java代码。

1
 thrift --gen java --out . demo.thrift
Java Server

首先实现接口

CaloculatorServiceImpl.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class CaloculatorServiceImpl  implements Calculator.Iface {
    @Override
    public int calculate(int logid, Work work) throws InvalidOperation, TException {
        System.out.println("calculate(" + logid + ", {" + work.op + "," + work.num1 + "," + work.num2 + "})");
        int val = 0;
        switch (work.op) {
            case ADD:
                val = work.num1 + work.num2;
                break;
            case SUBTRACT:
                val = work.num1 - work.num2;
                break;
            case MULTIPLY:
                val = work.num1 * work.num2;
                break;
            case DIVIDE:
                if (work.num2 == 0) {
                    InvalidOperation io = new InvalidOperation();
                    io.what = work.op.getValue();
                    io.why = "Cannot divide by 0";
                    throw io;
                }
                val = work.num1 / work.num2;
                break;
            default:
                InvalidOperation io = new InvalidOperation();
                io.what = work.op.getValue();
                io.why = "Unknown operation";
                throw io;
        }
        return val;
    }
}

下面实现一个Server,让人们可以调用服务

DemoServer.java
1
2
3
4
5
6
7
8
9
10
11
12
13
public class DemoServer {

    public static void main(String [] args) throws TTransportException {
        Calculator.Iface calculatorService = new CaloculatorServiceImpl();
        Calculator.Processor processor = new Calculator.Processor(calculatorService);
        TServerTransport serverTransport = new TServerSocket(8000);
        TServer server = new TThreadPoolServer(new TThreadPoolServer.Args(serverTransport).processor(processor));

        System.out.println("Starting the simple server...");
        server.serve();
    }

}
Java Client

接下来实现一个客户端,调用服务。

DemoClient.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class DemoClient {

    public static void main(String [] args) throws Exception {

        TTransport transport = new TSocket("localhost", 8000);
        transport.open();
        TProtocol protocol = new TBinaryProtocol(transport);
        Calculator.Iface client = new Calculator.Client(protocol);

        Work work = new Work();

        work.op = Operation.DIVIDE;
        work.num1 = 1;
        work.num2 = 0;
        try {
            int quotient = client.calculate(1, work);
            System.out.println("Whoa we can divide by 0");
        } catch (InvalidOperation io) {
            System.out.println("Invalid operation: " + io.why);
        }

        work.op = Operation.SUBTRACT;
        work.num1 = 15;
        work.num2 = 10;
        try {
            int diff = client.calculate(2, work);
            System.out.println("15-10=" + diff);
        } catch (InvalidOperation io) {
            System.out.println("Invalid operation: " + io.why);
        }

        transport.close();
    }
}

python示例

由于前面已经实现了一个Java的Server,这里就只实现一个client了。

demo_client.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import sys

from me.ellios.demo import Calculator
from me.ellios.demo.ttypes import *

from thrift import Thrift
from thrift.transport import TSocket
from thrift.transport import TTransport
from thrift.protocol import TBinaryProtocol

try:
  # Make socket
  transport = TSocket.TSocket('localhost', 8000)
  # Buffering is critical. Raw sockets are very slow
  transport = TTransport.TBufferedTransport(transport)
  # Wrap in a protocol
  protocol = TBinaryProtocol.TBinaryProtocol(transport)
  # Create a client to use the protocol encoder
  client = Calculator.Client(protocol)
  # Connect!
  transport.open()
  work = Work()
  work.op = Operation.DIVIDE
  work.num1 = 1
  work.num2 = 0
  try:
    quotient = client.calculate(1, work)
    print 'Whoa? You know how to divide by zero?'
  except InvalidOperation, io:
    print 'InvalidOperation: %r' % io
  work.op = Operation.SUBTRACT
  work.num1 = 15
  work.num2 = 10
  diff = client.calculate(1, work)
  print '15-10=%d' % (diff)
  # Close!
  transport.close()
except Thrift.TException, tx:
  print '%s' % (tx.message)

参考资料

Comments