Skip to content
Jim Tang edited this page Mar 6, 2017 · 31 revisions

Overview

Tatala is an easy-to-use RPC middleware, cross language and cross platform, that convert method signature (include callee class name, target method name, the number of its arguments and server return) into byte array, communicate with client and server base on socket.

Right now, there are Tatala-Java (client & server) and Tatala-client-csharp available.

Download

https://repo1.maven.org/maven2/com/github/zijan/tatala/

Maven

<dependency>
	<groupId>com.github.zijan</groupId>
	<artifactId>tatala</artifactId>
	<version>0.3.0</version>
</dependency>

Features

  • Easy-to-use quickly develop and setup a network communication component
  • Cross language and platform
  • High performance and distributed
  • Binary communication protocol
  • Long lived persistent connection
  • Multi thread on both client and server side
  • Support synchronous or asynchronous method call
  • Compression for big content
  • Support clinet one-way call and server push call
  • Server return runtime exception to clien side, so client be able to rollback transaction
  • Google Protocol Buffers as object serializing solution
  • Filter control, we can add filters on server side, preprocess input byte array before call server code
  • Use zookeeper as a service registry, load balance and failover
  • Can use for cross-language RPC, high performance cache server, distributed message service, MMO game server……

Quick link

Common Steps
Serializable
Wrapper Class
Asynchronous
Compress
Default Server Proxy
Without Proxy
Client One-way Call
Server Push
Session Filter
Return Runtime Exception
Google Protocol Buffer
Zookeeper as load balancer

Get Started

Download tatala.jar from repository. If you're using ant, change your build.xml to include tatala.jar. If you're using eclipse, add the jar to your project build path.

As you known, easy-to-use is the first consideration among Tatala features. It can make developer create RPC just like local method call. They don’t have to care about socket or thread all kind stuff.

For example, we have server logic ExampleManager.class and ExampleManagerImpl.class.

ExampleManager.java

public interface ExampleManager {
    public String sayHello(int Id, String name);
}

ExampleManagerImpl.java

public class ExampleManagerImpl implements ExampleManager{
	public String sayHello(int Id, String name) {
		return "["+Id+"]"+"Hello "+name+" !";
	}
}

We need to create a socket server class, in order to deploy our server logic on server side. In this sample, socket server listener port is 10001. ExampleServer.java

public class ExampleServer {
	public static void main(String args[]) {
		int listenPort = 10001;
		int poolSize = 10;
		AioSocketServer server = new AioSocketServer(listenPort, poolSize);
		server.start();
	}
}

Then client side code is something like: EasyClient.java

public class EasyClient {
	private static TransferObjectFactory transferObjectFactory;
	private static ExampleManager manager;
	
	public static void main(String[] args) {
		transferObjectFactory = new TransferObjectFactory("127.0.0.1", 10001, 5000);
		transferObjectFactory.setImplClass(ExampleManagerImpl.class);
		manager = (ExampleManager)ClientProxyFactory.create(ExampleManager.class, transferObjectFactory);
		
		String result = manager.sayHello(18, "JimT");
		System.out.println("result: "+result);
	}
}

Create TransferObjectFactory object with server ip, port ant timeout, and set implement class. Create a proxy, make method call. Of cause, client side need have that interface class (ExampleManager.class) in classpath.

That is everything from server to client. Don't have any configuration files. It is so simple, right?

There are more examples on tutorial section.

Tutorial

Common Steps

Top

For build a Tatala RPC, need to three steps:

  • Real Server Logic and socket server class

Real business logic codes, which run in server side. Socket server deploys server logic, and listens for client.

  • Tatala Proxy

It is the agency between client and server Logic. The codes run in both of client and server side.

  • Client

Who is service consumer sent request to socket server proxy and receive response from proxy. The codes run in client side.

Let’s go through all steps.

Just build simple server logic, interface and implement class.

public interface ExampleManager {
	public String sayHello(int Id, String name);
}
public class ExampleManagerImpl implements ExampleManager{
	public String sayHello(int Id, String name) {
		return "["+Id+"]"+"Hello "+name+" !";
	}
}

Create a socket server class, in order to deploy our server logic on server side.

public class ExampleServer {
	public static void main(String args[]) {
		int listenPort = 10001;
		int poolSize = 10;
		AioSocketServer server = new AioSocketServer(listenPort, poolSize);
		server.start();
	}
}

Build Tatala client proxy.

public class ExampleClientProxy {
	public String sayHello(int Id, String name) {
		TransferObjectFactory transferObjectFactory = new TransferObjectFactory("127.0.0.1", 10001, 5000);
		TransferObject to = transferObjectFactory.createTransferObject();
		//to.setCalleeClass(ExampleServerProxy.class);
		//set class full name, for some case client can't load server class
		to.setCalleeClass("com.qileyuan.tatala.example.proxy.ExampleServerProxy");
		to.setCalleeMethod("sayHello");
		to.registerReturnType(TransferObject.DATATYPE_STRING);
		to.putInt(Id);
		to.putString(name);
		Object resultObj = ServerExecutor.execute(to);
		String result = (String) resultObj;
		return result;
	}
}

Create TransferObjectFactory by passing three parameters, serverip, port and timeout. Create transfer object by TransferObjectFactory. Set callee class (it’s server proxy. Set class full name, for some case client can't load server class), callee method and return type. Put parameter value into transfer object orderly. Call ServerExecutor.

Build Tatala server proxy.

public class ExampleServerProxy {
	private ExampleManager manager = new ExampleManagerImpl();
	public String sayHello(TransferObject to) {
		int Id = to.getInt();
		String name = to.getString();
		String result = manager.sayHello(Id, name);
		return result;
	}
}

Create callee class and callee method running in server side. Get parameters from transfer object with order must same as client. Call real business logic object.

Create client, which send request to client proxy and receive response from proxy.

public class ExampleClient {
	public static void main(String[] args) throws Exception {
		ExampleClientProxy proxy= new ExampleClientProxy();
		String result = proxy.sayHello(18, "JimT");
	}
}

Serializable

Top

We can simply use serialization object to transfer java object cross different JVM. In client proxy, if we want to pass a value object as a parameter. Make object implements serializable, and put into transfer object.

to.putSerializable("account", account);

Wrapper Class

Top

For performance concern, we can use wrapper class instead of serializable object. That is user-defined object, convert data into byte array manually.

AccountWrapper accountWrapper = new AccountWrapper(account);
to.putWrapper("account", accountWrapper);

AccountWrapper class contain customization object - Account.

public class AccountWrapper implements TransferObjectWrapper {
	private Account account;
	public AccountWrapper(Account account) {
		this.account = account;
	}
	public int getLength(){
		return TransferUtil.getLengthOfInt() + 
			   TransferUtil.getLengthOfString(account.getName()) +
			   TransferUtil.getLengthOfString(account.getAddress());
	}
	public void getByteArray(TransferOutputStream touts) {
		touts.writeInt(account.getId());
		touts.writeString(account.getName());
		touts.writeString(account.getAddress());
	}
	public TestAccountWrapper getObjectWrapper(TransferInputStream tins){
		account = new TestAccount();
		account.setId(tins.readInt());
		account.setName(tins.readString());
		account.setAddress(tins.readString());
		return this;
	}
}

There are three implemented methods:
getLength - get customization object byte array length
getByteArray - convert customization object into byte array
getObjectWrapper - convert byte array back to customization object

Asynchronous

Top

Tatala supports asynchronous call in client side proxy. If we need return object, we can get it from future object.

to.setAsynchronous(true);
Future<AccountWrapper> future = (Future<AccountWrapper>) ServerExecutor.execute(to);
accountWrapper = future.get();

Compress

Top

Tatala supports transfer data compression. If we want to send a big content object to server or want to retrieve some big content from server, we can set true on compress flag.

to.setCompress(true);

Default Server Proxy

Top

In client side, we put callee class name and callee method name into transfer object. The callee is server side prox, it is executed by reflection. Server side proxy calls real service code. If we don’t want to execute server proxy in reflection way, we can create a server proxy extends DefaultProxy and register our proxy into server before start in server main class.

Add some code on ExampleServer.

DefaultProxy defaultProxy = new ExampleDefaultProxy();
server.registerProxy(defaultProxy);
server.start();

ExampleDefaultProxy.java

public class ExampleDefaultProxy extends DefaultProxy{
	private ExampleManager manager = new ExampleManagerImpl();
	public Object execute(TransferObject to){
		String calleeMethod = to.getCalleeMethod();
		if(calleeMethod.equals("sayHello")){
			int Id = to.getInt();
			String name = to.getString();
			String result = manager.sayHello(Id, name);
			return result;
		}
		return null;
	}
}

Without Proxy

Top

If you feel it’s boring about writing proxy class. Tatala support create RPC without any client and server proxy. It base on Java dynamic proxy. So performance is slight bad, and don’t support wrapper class, protobuf and Tatala-client-csharp.

Just look up the first example EasyClient on Get Started section.

Client One-way Call

Top

Sometime client call don't need server return, so client don't have to wait server response. Register ReturnType with TransferObject.DATATYPE_NORETURN, server will not send any data back to client. Increase performance.
Client code:

  TransferObject to = transferObjectFactory.createTransferObject();
  to.setCalleeClass(ExampleServerProxy.class);
  to.setCalleeMethod("sayHello");
  to.putInt(0);
  to.putString("Jim");
  to.registerReturnType(TransferObject.DATATYPE_NORETURN);
  ServerExecutor.execute(to);

For client multiple thread, use TransferObject.DATATYPE_NORETURN, server execution may not follow sequence as client call. To avoid this, use TransferObject.DATATYPE_VOID instead. So client always wait server response, even though void value. Server execution sequence is same as client call.

Server Push

Top

After client setup long connection with server, sometime server needs to push message to client, such as server broadcast a message to all players in chat room. You can execute a server call by Tatala session, as long as you keep the client session and handle it on server logic.

Check Chat Room example for more detail.

Session Filter

Top

Tatala support server intercepts each client request, and injects business logic on it, such as security check.

Create session filter.

public class MyFilter implements SessionFilter {
	public void onClose(ServerSession session) {
		//on connection close, do something
	}
	public boolean onReceive(ServerSession session, byte[] receiveData) {
		//after each receive, do something
		return true;
	}
}

Register my filter on socket server class.

AioSocketServer server = new AioSocketServer(listenPort, poolSize);
MyFilter filter = new MyFilter();
server.registerSessionFilter(filter);

Return Runtime Exception

Top

In some case, server need to throw exception and return exception to client. Client can catch that exception and rollback transaction.

Create ExampleReturnException extends TatalaRollbackException

public class ExampleReturnException extends TatalaRollbackException{
	private static final long serialVersionUID = 1L;
	public ExampleReturnException() {
		super();
	}
	public ExampleReturnException(String msg) {
		super(msg);
	}
}

Create a method on ExampleManagerImpl

	public void exceptionCall(int Id) {
		if(Id < 0){
			throw new ExampleReturnException("Server error: id < 0");
		}
	}

ExampleServerProxy

	public void exceptionCall(TransferObject to) {
		int Id = to.getInt();
		manager.exceptionCall(Id);
	}

Client

	public void returnExceptionTest() {
		TransferObject to = transferObjectFactory.createTransferObject();
		to.setCalleeClass(ExampleServerProxy.class);
		to.setCalleeMethod("exceptionCall");
		to.putInt(-1);
		to.registerReturnType(TransferObject.DATATYPE_VOID);
		ServerExecutor.execute(to);
	}

TatalaRollbackException is RuntimeException, so it's optional to try-catch return exception on client side. Make life more easy.

Google Protocol Buffer

Top

Since tatala support byte array, so we can use Protobuf as object serializing solution, instead of Serializable and WrapperClass.

Client Proxy.

	public Account getAccountProto (Account account) throws InvalidProtocolBufferException{
		TransferObject to = transferObjectFactory.createTransferObject();
		to.setCalleeClass(ExampleServerProxy.class);
		to.setCalleeMethod("getAccountProto");
		to.registerReturnType(TransferObject.DATATYPE_BYTEARRAY);

		AccountProto.Account.Builder accountProtoBuilder = AccountProto.Account.newBuilder();
		accountProtoBuilder.setId(account.getId());
		accountProtoBuilder.setName(account.getName());
		to.putByteArray(accountProtoBuilder.build().toByteArray());

		byte[] returnByteArray = (byte[]) ServerExecutor.execute(to);

		AccountProto.Account accountProto = AccountProto.Account.parseFrom(returnByteArray);
		if(accountProto != null){
			Account returnAccount = new Account();
			returnAccount.setId(accountProto.getId());
			returnAccount.setName(accountProto.getName());
			returnAccount.setAddress(accountProto.getAddress());
			return returnAccount;
		}else{
			return null;
		}
	}

Server Proxy.

	public byte[] getAccountProto(TransferObject to) throws InvalidProtocolBufferException {
		byte[] byteArray = to.getByteArray();
		AccountProto.Account accountProto = AccountProto.Account.parseFrom(byteArray);
		Account account = new Account();
		account.setId(accountProto.getId());
		account.setName(accountProto.getName());
		account.setAddress(accountProto.getAddress());
		
		Account returnAccount = manager.getAccount(account);

		AccountProto.Account.Builder accountProtoBuilder = AccountProto.Account.newBuilder();
		accountProtoBuilder.setId(returnAccount.getId());
		accountProtoBuilder.setName(returnAccount.getName());
		accountProtoBuilder.setAddress(returnAccount.getAddress());

		return accountProtoBuilder.build().toByteArray();
	}

Zookeeper as load balancer

Top

We can setup more then one server side. All servers register their-self in zookeeper, when server start. Before tatala send each client request, will randomly select one available server from zookeeper as target. In case one server is dead, client request will send to rest servers. That can make our service high scalability and availability.

Start a zookeeper as registry server. In server code, create AioSocketServer object and set zookeeper address (ip:port). When server start, tatala will register server address into zookeeper.

  AioSocketServer server = new AioSocketServer("127.0.0.1", "10001", 16);
  //set zookeeper address, as load balancer
  server.setZKRegistryAddress("127.0.0.1:2181");
  server.start();

In client code, when create TransferObjectFactory object put zookeeper server ip:port and timeout value.

  String zookeeperRegistryAddress = "127.0.0.1:2181";
  TransferObjectFactory transferObjectFactory = new TransferObjectFactory(zookeeperRegistryAddress, 5000);
  transferObjectFactory.setImplClass(ExampleManagerImpl.class);
  ExampleManager manager = (ExampleManager)ClientProxyFactory.create(ExampleManager.class, transferObjectFactory);
  manager.sayHello(1, "Jim");

Protocol

In client side, put method signature into transfer object, tatala convert transfer object into byte array and send to server. In server side, convert byte array into transfer object that content method signature, like callee class, method name, arguments and return type. Tatala executor loads that information and invokes that target method.

Support Type

Supported parameter and return type table

Type Java C#
bool Y Y
byte Y Y
short Y Y
chat Y Y
int Y Y
long Y Y
float Y Y
double Y Y
Date Y Y
String Y Y
byte[] Y Y
int[] Y Y
long[] Y Y
float[] Y Y
double[] Y Y
String[] Y Y
Serializable Y N
Protobuf Y Y
WrapperClass Y Y

Other Notes

Require JDK1.7, because using Java AIO.

Third part libs include Protobuf and Log4j.

License

This library is distributed under Apache License Version 2.0

Contact