├── README.txt
├── api
├── pom.xml
└── src
│ └── main
│ └── thrift
│ └── foogenerator.thrift
├── client
├── pom.xml
└── src
│ └── main
│ └── java
│ └── MyAppThriftClient.java
├── commons-thrift
├── pom.xml
└── src
│ └── main
│ └── java
│ └── com
│ └── example
│ └── myapp
│ └── commons
│ └── ClusterFactory.java
├── pom.xml
└── server
├── pom.xml
└── src
└── main
└── java
└── com
└── example
└── myapp
├── FooServiceHandler.java
└── MyAppThriftServer.java
/README.txt:
--------------------------------------------------------------------------------
1 | # Not maintained
2 |
3 | This project is a couple of years old now. Finagle has changed its public API, so the code posted here probably doesn't work. It's kept around for historic reasons.
4 |
5 | === Thrift-Zookeeper example ===
6 | This example used Finagle, a library developed at Twitter.
7 | It contains:
8 | - api: A Thrift API that defines a FooService
9 | - commons-thrift: Common logic used for service discovery
10 | - server: A Thrift server providing the FooService
11 | - client: A consumer of the service
12 |
13 | To run:
14 | - mvn clean install
15 | - Run a local ZooKeeper instance
16 | - Start as many instances of MyAppThriftServer as you want
17 | - Run the client (MyAppThriftClient)
18 |
19 | Example output:
20 | Online servers: [/0:0:0:0:0:0:0:0:3705, /0:0:0:0:0:0:0:0:5071, /0:0:0:0:0:0:0:0:2381]
21 |
22 | Got hey, this is a response from port=3705
23 | Got hey, this is a response from port=3705
24 | Got hey, this is a response from port=5071
25 | Got hey, this is a response from port=5071
26 | Got hey, this is a response from port=2381
27 | Got hey, this is a response from port=3705
28 | ...
29 |
30 | Monitoring
31 | - Start a server instance
32 | - Check out http://localhost:XXXX/stats.txt (XXXX is the port number for Ostrich admin)
33 |
34 | What are the good parts?
35 | - Service discovery
36 | - Finagle handles load balancing, so it connects to one of the servers ZooKeeper has registered.
37 | - It also handles connection pooling, retries, timeouts, statistics, backpressure
38 | - It can be used to create Scala-native Thrift clients/servers
39 | - It's written to be asynchronous
40 | - No thrift binary required for building
41 | - Throw in 10 lines of code and there's monitoring, with Ostrich:
42 | https://github.com/twitter/ostrich
43 | - Throw in 10 lines of code and there's distributed tracing, with Zipkin:
44 | - https://github.com/twitter/zipkin
45 |
--------------------------------------------------------------------------------
/api/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 |
5 |
6 | com.example
7 | myapp
8 | 1.0-SNAPSHOT
9 |
10 |
11 | myapp-api
12 |
13 |
14 |
15 | com.twitter
16 | scrooge-runtime_2.10
17 | 3.1.1
18 |
19 |
20 |
21 |
22 |
23 |
24 | com.twitter
25 | scrooge-maven-plugin
26 | 3.1.1
27 |
28 | java
29 |
30 |
31 | --finagle
32 | --ostrich
33 |
34 |
35 |
36 |
37 |
38 | thrift-sources
39 | generate-sources
40 |
41 | compile
42 |
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/api/src/main/thrift/foogenerator.thrift:
--------------------------------------------------------------------------------
1 | namespace java com.example.myapp.thrift
2 |
3 | struct Foo {
4 | 1: string bar;
5 | 2: string bazz;
6 | 3: i32 squirrel;
7 | }
8 |
9 | service FooService {
10 | Foo giveMeSomeFoo(i32 id);
11 | }
--------------------------------------------------------------------------------
/client/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 |
5 |
6 | com.example
7 | myapp
8 | 1.0-SNAPSHOT
9 |
10 |
11 | myapp-client
12 |
13 |
14 |
15 | com.example
16 | myapp-api
17 | 1.0-SNAPSHOT
18 |
19 |
20 | com.twitter
21 | finagle-thrift_2.10
22 | 6.3.0
23 |
24 |
25 | com.example
26 | commons-thrift
27 | 1.0-SNAPSHOT
28 |
29 |
30 |
--------------------------------------------------------------------------------
/client/src/main/java/MyAppThriftClient.java:
--------------------------------------------------------------------------------
1 | import com.example.myapp.commons.ClusterFactory;
2 | import com.example.myapp.thrift.Foo;
3 | import com.example.myapp.thrift.FooService;
4 | import com.twitter.finagle.Service;
5 | import com.twitter.finagle.builder.ClientBuilder;
6 | import com.twitter.finagle.builder.Cluster;
7 | import com.twitter.finagle.stats.InMemoryStatsReceiver;
8 | import com.twitter.finagle.thrift.ThriftClientFramedCodec;
9 | import com.twitter.finagle.thrift.ThriftClientRequest;
10 | import com.twitter.util.Duration;
11 | import org.apache.thrift.protocol.TBinaryProtocol;
12 |
13 | import java.net.SocketAddress;
14 | import java.util.List;
15 | import java.util.Set;
16 | import java.util.concurrent.TimeUnit;
17 | import java.util.logging.Logger;
18 |
19 | public class MyAppThriftClient {
20 | public static void main(String[] args) {
21 | Cluster cluster = ClusterFactory.getForService("FooService");
22 |
23 | // Querying for a list of online servers is not necessary for Finagle,
24 | // but can be used for vanilla thrift servers.
25 | List onlineServers = ClusterFactory.getOnlineServers("FooService");
26 | System.out.println("Online servers: "+onlineServers.toString());
27 |
28 | Service service =
29 | ClientBuilder.safeBuild(ClientBuilder.get()
30 | .cluster(cluster) // this is where service discovery happens
31 | .name("FooService client")
32 | .codec(ThriftClientFramedCodec.get())
33 | .timeout(Duration.apply(2, TimeUnit.SECONDS))
34 | .retries(4)
35 | .hostConnectionLimit(1)
36 | // .logger(Logger.getLogger("ROOT"))
37 | );
38 |
39 | FooService.FutureIface client = new FooService.FinagledClient(
40 | service,
41 | new TBinaryProtocol.Factory(),
42 | "FooService",
43 | new InMemoryStatsReceiver()
44 | );
45 |
46 | // Do some stuff
47 | for (int i = 0; i < 20; i++) {
48 | // Call .get() on the future to wait for it to return a value
49 | Foo foo = client.giveMeSomeFoo(i).get();
50 | System.out.println("Got "+foo.getBazz());
51 | // (or use functional programming so you don't block the thread)
52 | }
53 | }
54 | }
--------------------------------------------------------------------------------
/commons-thrift/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 |
5 |
6 | com.example
7 | myapp
8 | 1.0-SNAPSHOT
9 |
10 |
11 | commons-thrift
12 |
13 |
14 |
15 | com.twitter
16 | finagle-serversets_2.10
17 | 6.3.0
18 |
19 |
20 |
--------------------------------------------------------------------------------
/commons-thrift/src/main/java/com/example/myapp/commons/ClusterFactory.java:
--------------------------------------------------------------------------------
1 | package com.example.myapp.commons;
2 |
3 | import com.google.common.collect.ImmutableSet;
4 | import com.twitter.common.net.pool.DynamicHostSet;
5 | import com.twitter.common.quantity.Amount;
6 | import com.twitter.common.quantity.Time;
7 | import com.twitter.common.zookeeper.ServerSet;
8 | import com.twitter.common.zookeeper.ServerSetImpl;
9 | import com.twitter.common.zookeeper.ZooKeeperClient;
10 | import com.twitter.finagle.builder.Cluster;
11 | import com.twitter.finagle.builder.Server;
12 | import com.twitter.finagle.zookeeper.ZookeeperServerSetCluster;
13 | import com.twitter.thrift.ServiceInstance;
14 | import scala.collection.JavaConversions;
15 | import scala.collection.Seq;
16 |
17 | import java.net.InetSocketAddress;
18 | import java.net.SocketAddress;
19 | import java.util.*;
20 |
21 | public class ClusterFactory {
22 | static Amount sessionTimeout = Amount.of(15, Time.SECONDS);
23 | static List nodes = Arrays.asList(
24 | // Use a cluster of nodes...
25 | // new InetSocketAddress("zk1.myapp.com", 2181),
26 | // new InetSocketAddress("zk2.myapp.com", 2181),
27 | // new InetSocketAddress("zk3.myapp.com", 2181),
28 | // new InetSocketAddress("zk4.myapp.com", 2181),
29 | // new InetSocketAddress("zk5.myapp.com", 2181),
30 |
31 | // ...or use local ZooKeeper node
32 | new InetSocketAddress("localhost", 2181)
33 | );
34 |
35 | public static ZookeeperServerSetCluster getForService(String clusterName) {
36 | ServerSet serverSet = new ServerSetImpl(getZooKeeperClient(), getPath(clusterName));
37 | return new ZookeeperServerSetCluster(serverSet);
38 | }
39 |
40 | public static void reportServerUpAndRunning(Server server, String clusterName) {
41 | getForService(clusterName).join(server.localAddress(), new scala.collection.immutable.HashMap());
42 | }
43 |
44 | public static List getOnlineServers(String clusterName) {
45 | try {
46 | ZookeeperServerSetCluster cluster = getForService(clusterName);
47 | // Run the monitor() method, which will block the thread until the initial list of servers arrives.
48 | new ServerSetImpl(zooKeeperClient, getPath(clusterName)).monitor(new DynamicHostSet.HostChangeMonitor(){
49 | public void onChange(ImmutableSet serviceInstances) {
50 | // do nothing
51 | }
52 | });
53 | return JavaConversions.asJavaList(cluster.snap()._1());
54 | } catch (DynamicHostSet.MonitorException e) {
55 | throw new RuntimeException("Couldn't get list of online servers", e);
56 | }
57 | }
58 |
59 | private static String getPath(String clusterName) {
60 | return "/myapp/services/"+clusterName;
61 | }
62 |
63 | private static ZooKeeperClient zooKeeperClient;
64 | private static ZooKeeperClient getZooKeeperClient() {
65 | if (zooKeeperClient == null) {
66 | zooKeeperClient = new ZooKeeperClient(sessionTimeout, nodes);
67 | }
68 | return zooKeeperClient;
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 |
5 | com.example
6 | myapp
7 | 1.0-SNAPSHOT
8 | pom
9 |
10 | myapp
11 |
12 |
13 | UTF-8
14 | 1.7
15 | 1.7
16 |
17 |
18 |
19 | commons-thrift
20 | api
21 | client
22 | server
23 |
24 |
25 |
26 |
27 | Twitter
28 | http://maven.twttr.com/
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/server/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 |
5 |
6 | com.example
7 | myapp
8 | 1.0-SNAPSHOT
9 |
10 |
11 | myapp-server
12 |
13 |
14 |
15 | com.example
16 | myapp-api
17 | 1.0-SNAPSHOT
18 |
19 |
20 | com.twitter
21 | finagle-thrift_2.10
22 | 6.3.0
23 |
24 |
25 | com.twitter
26 | finagle-core_2.10
27 | 6.3.0
28 |
29 |
30 | com.twitter
31 | ostrich_2.10
32 | 9.1.0
33 |
34 |
35 | com.example
36 | commons-thrift
37 | 1.0-SNAPSHOT
38 |
39 |
40 |
--------------------------------------------------------------------------------
/server/src/main/java/com/example/myapp/FooServiceHandler.java:
--------------------------------------------------------------------------------
1 | package com.example.myapp;
2 |
3 | import com.example.myapp.thrift.Foo;
4 | import com.example.myapp.thrift.FooService;
5 | import com.twitter.ostrich.stats.Stats;
6 | import com.twitter.util.Future;
7 |
8 | public class FooServiceHandler implements FooService.FutureIface {
9 | private String bazz;
10 | public FooServiceHandler(String bazz) {
11 | this.bazz = bazz;
12 | }
13 |
14 | public Future giveMeSomeFoo(int id) {
15 | Stats.incr("number_of_foo_calls"); // Report stats to Ostrich
16 |
17 | // Domain objects are immutable. They come with constructors and builders.
18 | Foo foo = new Foo.Builder()
19 | .bar("test")
20 | .bazz("hey, this is a response from " + bazz)
21 | .squirrel(42)
22 | .build();
23 |
24 | // Finagle is async, so wrap the return value in a Future
25 | // if the implementation is synchronous
26 | return Future.value(foo);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/server/src/main/java/com/example/myapp/MyAppThriftServer.java:
--------------------------------------------------------------------------------
1 | package com.example.myapp;
2 |
3 | import com.example.myapp.commons.ClusterFactory;
4 | import com.example.myapp.thrift.FooService;
5 | import com.twitter.finagle.builder.Server;
6 | import com.twitter.finagle.builder.ServerBuilder;
7 | import com.twitter.finagle.thrift.ThriftServerFramedCodec;
8 | import com.twitter.ostrich.admin.AdminHttpService;
9 | import com.twitter.ostrich.admin.RuntimeEnvironment;
10 | import org.apache.thrift.protocol.TBinaryProtocol;
11 |
12 | import java.net.InetSocketAddress;
13 | import java.util.Random;
14 | import java.util.logging.Logger;
15 |
16 | public class MyAppThriftServer {
17 | public static void main(String[] args) {
18 | // In this example; use some random port between 2000-9999.
19 | // In a real world; probably deploy with the same port on different servers
20 | int port = new Random().nextInt(8000)+2000;
21 |
22 | // Pass port number into our handler for this example (for debugging)
23 | FooServiceHandler handler = new FooServiceHandler("port="+port);
24 |
25 | Server server = ServerBuilder.safeBuild(
26 | new FooService.FinagledService(handler, new TBinaryProtocol.Factory()),
27 | ServerBuilder.get()
28 | .name("FooService")
29 | .codec(ThriftServerFramedCodec.get())
30 | .maxConcurrentRequests(50)
31 | // .logger(Logger.getLogger("ROOT"))
32 | .bindTo(new InetSocketAddress(port))
33 | );
34 |
35 | ClusterFactory.reportServerUpAndRunning(server, "FooService");
36 |
37 | System.out.println("The server, running from port "+port+" joined the FooService cluster.");
38 |
39 | int ostrichPort = port + 1;
40 | RuntimeEnvironment runtime = new RuntimeEnvironment("");
41 | AdminHttpService admin = new AdminHttpService(ostrichPort, 0, runtime);
42 | admin.start();
43 | System.out.println("Ostrich reporting started on port "+ostrichPort);
44 | }
45 | }
46 |
--------------------------------------------------------------------------------