- Notifications
You must be signed in to change notification settings - Fork22
Tatala Tutorial
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.
https://repo1.maven.org/maven2/com/github/zijan/tatala/
<dependency><groupId>com.github.zijan</groupId><artifactId>tatala</artifactId><version>0.3.0</version></dependency>
- 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……
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
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
publicinterfaceExampleManager {publicStringsayHello(intId,Stringname);}
ExampleManagerImpl.java
publicclassExampleManagerImplimplementsExampleManager{publicStringsayHello(intId,Stringname) {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
publicclassExampleServer {publicstaticvoidmain(Stringargs[]) {intlistenPort =10001;intpoolSize =10;AioSocketServerserver =newAioSocketServer(listenPort,poolSize);server.start();}}
Then client side code is something like:EasyClient.java
publicclassEasyClient {privatestaticTransferObjectFactorytransferObjectFactory;privatestaticExampleManagermanager;publicstaticvoidmain(String[]args) {transferObjectFactory =newTransferObjectFactory("127.0.0.1",10001,5000);transferObjectFactory.setImplClass(ExampleManagerImpl.class);manager = (ExampleManager)ClientProxyFactory.create(ExampleManager.class,transferObjectFactory);Stringresult =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.
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.
publicinterfaceExampleManager {publicStringsayHello(intId,Stringname);}
publicclassExampleManagerImplimplementsExampleManager{publicStringsayHello(intId,Stringname) {return"["+Id+"]"+"Hello "+name+" !";}}
Create a socket server class, in order to deploy our server logic on server side.
publicclassExampleServer {publicstaticvoidmain(Stringargs[]) {intlistenPort =10001;intpoolSize =10;AioSocketServerserver =newAioSocketServer(listenPort,poolSize);server.start();}}
Build Tatala client proxy.
publicclassExampleClientProxy {publicStringsayHello(intId,Stringname) {TransferObjectFactorytransferObjectFactory =newTransferObjectFactory("127.0.0.1",10001,5000);TransferObjectto =transferObjectFactory.createTransferObject();//to.setCalleeClass(ExampleServerProxy.class);//set class full name, for some case client can't load server classto.setCalleeClass("com.qileyuan.tatala.example.proxy.ExampleServerProxy");to.setCalleeMethod("sayHello");to.registerReturnType(TransferObject.DATATYPE_STRING);to.putInt(Id);to.putString(name);ObjectresultObj =ServerExecutor.execute(to);Stringresult = (String)resultObj;returnresult;}}
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.
publicclassExampleServerProxy {privateExampleManagermanager =newExampleManagerImpl();publicStringsayHello(TransferObjectto) {intId =to.getInt();Stringname =to.getString();Stringresult =manager.sayHello(Id,name);returnresult;}}
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.
publicclassExampleClient {publicstaticvoidmain(String[]args)throwsException {ExampleClientProxyproxy=newExampleClientProxy();Stringresult =proxy.sayHello(18,"JimT");}}
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);
For performance concern, we can use wrapper class instead of serializable object. That is user-defined object, convert data into byte array manually.
AccountWrapperaccountWrapper =newAccountWrapper(account);to.putWrapper("account",accountWrapper);
AccountWrapper class contain customization object - Account.
publicclassAccountWrapperimplementsTransferObjectWrapper {privateAccountaccount;publicAccountWrapper(Accountaccount) {this.account =account;}publicintgetLength(){returnTransferUtil.getLengthOfInt() +TransferUtil.getLengthOfString(account.getName()) +TransferUtil.getLengthOfString(account.getAddress());}publicvoidgetByteArray(TransferOutputStreamtouts) {touts.writeInt(account.getId());touts.writeString(account.getName());touts.writeString(account.getAddress());}publicTestAccountWrappergetObjectWrapper(TransferInputStreamtins){account =newTestAccount();account.setId(tins.readInt());account.setName(tins.readString());account.setAddress(tins.readString());returnthis;}}
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
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();
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);
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.
DefaultProxydefaultProxy =newExampleDefaultProxy();server.registerProxy(defaultProxy);server.start();
ExampleDefaultProxy.java
publicclassExampleDefaultProxyextendsDefaultProxy{privateExampleManagermanager =newExampleManagerImpl();publicObjectexecute(TransferObjectto){StringcalleeMethod =to.getCalleeMethod();if(calleeMethod.equals("sayHello")){intId =to.getInt();Stringname =to.getString();Stringresult =manager.sayHello(Id,name);returnresult;}returnnull;}}
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.
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:
TransferObjectto =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.
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.
Tatala support server intercepts each client request, and injects business logic on it, such as security check.
Create session filter.
publicclassMyFilterimplementsSessionFilter {publicvoidonClose(ServerSessionsession) {//on connection close, do something}publicbooleanonReceive(ServerSessionsession,byte[]receiveData) {//after each receive, do somethingreturntrue;}}
Register my filter on socket server class.
AioSocketServerserver =newAioSocketServer(listenPort,poolSize);MyFilterfilter =newMyFilter();server.registerSessionFilter(filter);
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
publicclassExampleReturnExceptionextendsTatalaRollbackException{privatestaticfinallongserialVersionUID =1L;publicExampleReturnException() {super();}publicExampleReturnException(Stringmsg) {super(msg);}}
Create a method on ExampleManagerImpl
publicvoidexceptionCall(intId) {if(Id <0){thrownewExampleReturnException("Server error: id < 0");}}
ExampleServerProxy
publicvoidexceptionCall(TransferObjectto) {intId =to.getInt();manager.exceptionCall(Id);}
Client
publicvoidreturnExceptionTest() {TransferObjectto =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.
Since tatala support byte array, so we can use Protobuf as object serializing solution, instead of Serializable and WrapperClass.
Client Proxy.
publicAccountgetAccountProto (Accountaccount)throwsInvalidProtocolBufferException{TransferObjectto =transferObjectFactory.createTransferObject();to.setCalleeClass(ExampleServerProxy.class);to.setCalleeMethod("getAccountProto");to.registerReturnType(TransferObject.DATATYPE_BYTEARRAY);AccountProto.Account.BuilderaccountProtoBuilder =AccountProto.Account.newBuilder();accountProtoBuilder.setId(account.getId());accountProtoBuilder.setName(account.getName());to.putByteArray(accountProtoBuilder.build().toByteArray());byte[]returnByteArray = (byte[])ServerExecutor.execute(to);AccountProto.AccountaccountProto =AccountProto.Account.parseFrom(returnByteArray);if(accountProto !=null){AccountreturnAccount =newAccount();returnAccount.setId(accountProto.getId());returnAccount.setName(accountProto.getName());returnAccount.setAddress(accountProto.getAddress());returnreturnAccount;}else{returnnull;}}
Server Proxy.
publicbyte[]getAccountProto(TransferObjectto)throwsInvalidProtocolBufferException {byte[]byteArray =to.getByteArray();AccountProto.AccountaccountProto =AccountProto.Account.parseFrom(byteArray);Accountaccount =newAccount();account.setId(accountProto.getId());account.setName(accountProto.getName());account.setAddress(accountProto.getAddress());AccountreturnAccount =manager.getAccount(account);AccountProto.Account.BuilderaccountProtoBuilder =AccountProto.Account.newBuilder();accountProtoBuilder.setId(returnAccount.getId());accountProtoBuilder.setName(returnAccount.getName());accountProtoBuilder.setAddress(returnAccount.getAddress());returnaccountProtoBuilder.build().toByteArray();}
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.
AioSocketServerserver =newAioSocketServer("127.0.0.1","10001",16);//set zookeeper address, as load balancerserver.setZKRegistryAddress("127.0.0.1:2181");server.start();
In client code, when create TransferObjectFactory object put zookeeper server ip:port and timeout value.
StringzookeeperRegistryAddress ="127.0.0.1:2181";TransferObjectFactorytransferObjectFactory =newTransferObjectFactory(zookeeperRegistryAddress,5000);transferObjectFactory.setImplClass(ExampleManagerImpl.class);ExampleManagermanager = (ExampleManager)ClientProxyFactory.create(ExampleManager.class,transferObjectFactory);manager.sayHello(1,"Jim");
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.
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 |
Require JDK1.7, because using Java AIO.
Third part libs include Protobuf and Log4j.
This library is distributed under Apache License Version 2.0