skaka的博客

every flight begins with a fall

微服务框架Finagle介绍 Part3: 在Finagle中开发基于Thrift协议的应用

| Comments

上篇文章中我们开发了一个基于Http协议的echo服务端和客户端. 这篇文章我们将开发一个基于Thrift协议的客户端和服务端. 这两篇文章对应的源代码地址在Github. 代码中有Java和Scala版本两套版本的实现, 但是这里我只会介绍Java版本.

Thrift最早由Facebook开源, 后被Apache收录成为顶级项目. Thrift严格来说不只是一种协议, 而是一个RPC框架. 使用Thrift, 我们只需要定义好使用的类型和接口声明, Thrift的代码生成工具能够自动为我们生成客户端和服务端代码. 我们现在来看如何在Finagle中使用Thrift.

首先定义一个Thrift的IDL文件, 文件位置在java-finagle-example/src/main/thrift/DemoService.thrift:

1
2
3
4
5
6
7
8
9
10
namespace java com.akkafun.service.thrift

service DemoService {
  string method1();

  i32 method2(1: i32 a, 2: i32 b);

  void method3();

}

定义了一个DemoService服务, 这个服务有三个示例方法. namespace的语法是为接口定义一个命名空间(对应Java里的包). method1没有参数, 方法的返回值类型是字符串. method2有两个参数, a和b, 参数和返回值类型都是int32类型. method3无参数, 无返回值.

现在我们来生成代码. Twitter提供了一个开源的工具Scrooge用来生成Finagle + Thrift的代码. 我们使用Scrooge提供的maven插件用来生成代码. 这个插件的配置可以查看pom.xml, 这里不做介绍. 运行maven命令: mvn clean compile, 生成的代码在java-finagle-example/target/classes/thrift目录下. 这里我们生成的是Scala代码. 虽然Scrooge提供Java代码的生成, 但是实际使用存在bug. 我们需要将这些代码手动拷贝到源代码目录下. 将这些代码拷贝到java-finagle-example/src/main/scala/thrift目录下.

现在我们来实现Thrift服务端代码. 打开java-finagle-example/src/main/java/com/akkafun/finagle/thrift/ThriftServer.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
public class ThriftServer implements DemoService<Future> {

    public static void main(String[] args) throws Exception {
        ListeningServer server = Thrift.serveIface("127.0.0.1:8081", new ThriftServer());

        Await.result(server);
    }

    @Override
    public Future<String> method1() {
        System.out.println("implement method1");
        return Future.value("abc");
    }

    @Override
    public Future<Integer> method2(int a, int b) {
        System.out.println("implement method2");
        return Future.value(a + b);
    }

    @Override
    public Future<BoxedUnit> method3() {
        System.out.println("implement method3");
        return Future.value(BoxedUnit.UNIT);
    }
}

我们使用maven命令生成的Scala代码, 里面有一个DemoService接口. 我们现在要做的事情就是实现这个接口. 我们需要实现DemoService接口声明的三个方法. 上面的实现都只是打印内容和返回简单的值. 实现了这个接口, 接下来我们就可以写启动服务的代码了. 上篇文章中我们启动Http服务器的时候使用的是Http.server相关的方法. 现在启动Thrift服务端可以使用Thrift.serveIface方法. 第一个参数传入监听的ip和端口, 第二个参数传入接口的实现类. 这样服务端的代码就完成了.

Thrift的客户端代码也相当简单. 打开java-finagle-example/src/main/java/com/akkafun/finagle/thrift/ThriftClient.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
35
36
37
38
39
40
@SuppressWarnings("unchecked")
public class ThriftClient {

    static Function1<Throwable, BoxedUnit> errorFunc = func(e -> {
        System.out.println("error: " + e.toString());
        return BoxedUnit.UNIT;
    });


    public static void main(String[] args) throws Exception {
        DemoService<Future> demoService = Thrift.newIface("127.0.0.1:8081", DemoService.class);

        Future<String> future1 = demoService.method1();
        Future<Integer> future2 = demoService.method2(1, 2);
        Future<BoxedUnit> future3 = demoService.method3();

        future1.onSuccess(func(r -> {
            System.out.println(r);
            return BoxedUnit.UNIT;
        }));
        future1.onFailure(errorFunc);

        future2.onSuccess(func(r -> {
            System.out.println(r);
            return BoxedUnit.UNIT;
        }));
        future2.onFailure(errorFunc);

        future3.onSuccess(func(r -> {
            System.out.println(r);
            return BoxedUnit.UNIT;
        }));
        future3.onFailure(errorFunc);

        Await.ready(future1);
        Await.ready(future2);
        Await.ready(future3);

    }
}

客户端代码中, 通过调用Thrift.newIface我们构造了一个DemoService的stub. 之后即可对DemoService的方法进行调用. 虽然调用方式看起来很像是本地调用, 实际上还是RPC. DemoService的服务端实现只是很简单的返回了几个值, 客户端实现只是把返回值打印了出来.现在我们来运行看看. 首先启动ThriftServer类, 然后启动ThriftClient. ThriftClient运行完毕自动结束, 你应该能在ThriftClient的控制台看到如下输出:

1
2
3
implement method2
implement method3
implement method1

ThriftServer控制台的输出:

1
2
3
()
3
abc

无论是客户端还是服务端, 方法被调用的顺序都是不固定的. 因为客户端的RPC调用是异步执行.

Finagle的开发实战就暂时介绍到这里. 通过前面的介绍你应该能了解到, 使用Finagle开发一个服务是非常的简单. 但是实际的多服务项目中, 几乎不会直接通过ip和端口来访问服务, 而是使用zookeeperetcd这种注册中心来完成. 下篇文章我会介绍如何将Finagle服务注册到zookeeper中以及如何使用zipkin来监控Finagle服务.

Comments