├── 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 | --------------------------------------------------------------------------------