Using RMI + ZooKeeper to implement a remote calling framework

Source: Internet
Author: User
Tags zookeeper

In the Java world, there is a technique for "cross-virtual machine" calls, which is RMI (remote method Invocation, a long-distance methods call). For example, service a runs in JVM1, service B runs in JVM2, and service A and service B can call each other remotely, just as you would call a local method, which is RMI. In distributed systems, we use RMI technology to easily 服务提供者 separate (service Provider) from 服务消费者 (service Consumer), fully reflect the weak coupling between components, and the system architecture is easier to scale.

In this paper, from the simplest RMI service and invocation example, let the reader quickly grasp the use of RMI, and then point out the limitations of RMI, finally, I provide a simple solution to this problem, that is, using ZooKeeper easy to solve the RMI call process involved in the problem.

Let's start with one of the simplest RMI examples!

1 Publishing RMI Services

To publish an RMI service, we only need to do three things:

    1. Define an RMI interface
    2. To write an implementation class for the RMI interface
    3. Publishing RMI services through JNDI
1.1 Defining an RMI interface

The RMI interface is actually a normal Java interface, but the RMI interface must inherit java.rmi.Remote , and the method of each RMI interface must declare an java.rmi.RemoteException exception, as follows:

Package Demo.zookeeper.remoting.common;import Java.rmi.remote;import Java.rmi.remoteexception;public interface HelloService extends Remote {    string SayHello (string name) throws RemoteException;}

The interface is inherited Remote , in effect, to let the JVM know that the interface needs to be used for remote invocation, and it RemoteException is thrown to let the program invoking the RMI service catch the exception. After all, anything strange happens during a remote call (for example, a broken network). It is important to note that RemoteException is a "inspected exception" and must be handled by itself when invoked try...catch... .

1.2 Implementing classes to write RMI interfaces

HelloServiceThis is a very simple thing to do, but it is important to note that we have to let the implementation class inherit the java.rmi.server.UnicastRemoteObject class, in addition, a constructor must be provided, and the constructor must throw an java.rmi.RemoteException exception. Now that we are using the RMI framework provided by the JVM, we have to follow this requirement, otherwise we will not be able to successfully publish the RMI service, in a word: we have to do the cards according to the rules!

Package Demo.zookeeper.remoting.server;import Demo.zookeeper.remoting.common.helloservice;import Java.rmi.remoteexception;import Java.rmi.server.unicastremoteobject;public class Helloserviceimpl extends UnicastRemoteObject implements HelloService {    protected Helloserviceimpl () throws remoteexception {    }    @ Override public    string SayHello (string name) throws RemoteException {        return String.Format ("Hello%s", name); c5/>}}

In order to meet the requirements of the RMI framework, we did do a lot of extra work (inheriting UnicastRemoteObject classes, throwing RemoteException exceptions), but these efforts prevented us from releasing the RMI service! We can easily publish RMI services through the JNDI provided by the JVM (Java naming and directory Interface,java naming and directory interface).

1.3 Publishing RMI services through JNDI

To publish the RMI service, we need to tell JNDI three basic information: 1. Domain name or IP address (host), 2. Port number (port), 3. Service names, which form the URL (or "RMI address") of the RMI protocol:

Rmi://

If we were to publish the RMI service locally, it host would be "localhost". In addition, RMI defaults to port "1099", we can also set the value of port itself (as long as it does not conflict with other ports). serviceis actually a service name based on the same host and port, we might as well use the Java full class name to represent it, which makes it easier to guarantee the uniqueness of the RMI address.

For our example, the RMI address is:

Rmi://localhost:1099/demo.zookeeper.remoting.server.helloserviceimpl

We can publish the RMI service simply by providing a main () method, as follows:

Package Demo.zookeeper.remoting.server;import Java.rmi.naming;import Java.rmi.registry.locateregistry;public class Rmiserver {public    static void Main (string[] args) throws Exception {        int port = 1099;        String url = "Rmi://localhost:1099/demo.zookeeper.remoting.server.helloserviceimpl";        Locateregistry.createregistry (port);        Naming.rebind (URL, new Helloserviceimpl ());}    }

It is important to note that we LocateRegistry.createRegistry() create a registry in JNDI by means of a single RMI port number. In addition, by Naming.rebind() way of binding RMI address and RMI service implementation class, here is the rebind() method, it is equivalent to call naming unbind() and bind() method, just use the rebind () method came more happy, so we chose it.

Running the main () method, the RMI service will be automatically released, and all that is left to do is write an RMI client to invoke the published RMI service.

2 Invoking the RMI service

We also use a main () method to invoke the RMI service, which is simpler than a release, and we only need to know two things: 1. RMI request path, 2. RMI Interface (the RMI implementation class must not be required, otherwise it is called locally). A few lines of code can call the RMI service you just published, like this:

Package Demo.zookeeper.remoting.client;import Demo.zookeeper.remoting.common.helloservice;import java.rmi.Naming; public class Rmiclient {public    static void Main (string[] args) throws Exception {        String url = "Rmi://localhost:1 099/demo.zookeeper.remoting.server.helloserviceimpl ";        HelloService HelloService = (helloservice) naming.lookup (URL);        String result = Helloservice.sayhello ("Jack");        SYSTEM.OUT.PRINTLN (result);}    }

When we run the main () method above, we see the "Hello Jack" output in the console, indicating that the RMI call was successful.

Limitations of the 3 RMI service

We can see that with JNDI, the so-called Naming and directory service, we successfully publish and invoke the RMI service. In fact, JNDI is a registry where the server puts the service object into the registry and the client obtains the service object from the registry. On the server we have the RMI service, and we register it in JNDI, we create a skeleton on the server, and Skeleton when the client first successfully connects to JNDI and obtains the remote service object, it immediately creates a local Stub (stub), and the remote communication is actually Skeleton and Stub to complete, the data is based on the TCP/IP protocol, sent on the "Transport layer". Undoubtedly, in theory, RMI must be faster than WebService, after all, WebService is based on HTTP, and HTTP carries data through the "Application layer" to transmit, the transport layer is more lower than the application layer, the lower the lower the faster.

Since RMI is faster and easier to use than WebService, why do we sometimes use WebService?

In fact, the reason is simple, WebService can implement the cross-language system calls, and RMI can only implement the call between the Java system. In other words, RMI's cross-platform is not as good as WebService, if our system is developed in Java, then of course the first choice is RMI service.

It seems that RMI is indeed very good, in addition to not cross-platform, there are those problems?

I think there are two limitations:

    1. RMI uses the default Java serialization method, and for systems with high performance requirements, it may be necessary to use other serialization schemes (for example: Proto Buffer).
    2. The RMI service will inevitably fail at runtime, for example, if the RMI service is unable to connect, causing the client to become unresponsive.

In general, the Java default serialization method is indeed sufficient to meet our requirements, if the performance is not a problem, we need to solve the actual 2nd, that is, let the system has HA (high availability, highly available).

4 using ZooKeeper to provide highly available RMI services

ZooKeeper is a sub-project of Hadoop that addresses data consistency issues between distributed systems. If the reader is not yet aware of how ZooKeeper works and how to use it, you can learn by following these links:

    • ZooKeeper official website
    • Distributed Service Framework zookeeper– managing data in a distributed environment

This article assumes that the reader has a good understanding of ZooKeeper and provides a simple solution to the high availability of RMI.

To address the high availability of RMI services, we need to use ZooKeeper as a 服务注册表 (service Registry) to allow multiple 服务提供者 (service Provider) to form a cluster that allows 服务消费者 (service Consumer) access to specific service providers through the service registry for specific service access addresses (i.e. RMI service addresses). As shown in the following:

It is important to note that the service registry is not a load Balancer, it provides not a "reverse proxy" service, but a "service registration" and "Heartbeat detection" functionality.

Use the service registry to register the RMI address, which is a good understanding, then "heartbeat detection" and how to understand it? To put it bluntly is to send a request to each service provider via the service center (actually a long Socket connection), and if the service provider is not responding for a long time, it thinks that the service providers have been "hung up" and will only select one of the service providers who are still alive as the current service provider.

Perhaps the reader will consider that the service center may have a single point of failure, if the service registry is broken, the entire system is paralyzed. It seems that to achieve this architecture, it is important to ensure that service centers are also highly available.

ZooKeeper to meet all of the requirements we mentioned above.

    1. The ZooKeeper temporary znode is used to store the RMI address of the service provider, and the corresponding Znode is automatically cleared once the Session with the service provider is interrupted.
    2. Let service consumers listen to these znode, and once the Znode data (RMI address) is found to be changed, a copy of the valid data will be retrieved.
    3. ZooKeeper's innate clustering capabilities (e.g., data synchronization and leading election features) ensure high availability of the service registry.
4.1 Service Providers

You need to write a ServiceProvider class to publish the RMI service and register the RMI address in ZooKeeper (actually stored on znode).

Package Demo.zookeeper.remoting.server;import Demo.zookeeper.remoting.common.constant;import java.io.IOException; Import Java.net.malformedurlexception;import Java.rmi.naming;import Java.rmi.remote;import Java.rmi.remoteexception;import Java.rmi.registry.locateregistry;import Java.util.concurrent.CountDownLatch; Import Org.apache.zookeeper.createmode;import Org.apache.zookeeper.keeperexception;import Org.apache.zookeeper.watchedevent;import Org.apache.zookeeper.watcher;import Org.apache.zookeeper.ZooDefs;import Org.apache.zookeeper.zookeeper;import Org.slf4j.logger;import Org.slf4j.loggerfactory;public class ServiceProvider    {private static final Logger Logger = Loggerfactory.getlogger (Serviceprovider.class);    Used to wait for the syncconnected event to fire after continuing to execute the current thread private countdownlatch latch = new Countdownlatch (1); Publish RMI Service and register RMI address to ZooKeeper public void publish (remote remote, string host, int port) {String url = Pub Lishservice (remote, host, Port);    Publish RMI service and return RMI address    if (URL! = null) {ZooKeeper ZK = ConnectServer ();//Connect ZooKeeper server and get ZooKeeper object if (ZK! =    NULL) {CreateNode (ZK, URL);//Create Znode and put RMI address on Znode}}}//Publish RMI Service        Private String publishservice (remote remote, string host, int port) {string url = null;            try {url = String.Format ("rmi://%s:%d/%s", host, Port, Remote.getclass (). GetName ());            Locateregistry.createregistry (port);            Naming.rebind (URL, remote);        Logger.debug ("Publish RMI Service (URL: {})", URL); } catch (RemoteException |        Malformedurlexception e) {logger.error ("", e);    } return URL;        }//Connect ZooKeeper server Private ZooKeeper ConnectServer () {ZooKeeper ZK = null;                try {ZK = new ZooKeeper (constant.zk_connection_string, Constant.zk_session_timeout, New Watcher () { @Override public void Process (WatchedeveNT Event) {if (event.getstate () = = Event.KeeperState.SyncConnected) {Latch.cou Ntdown ();            Wakes the currently executing thread}}); Latch.await (); Causes the current thread to be in a wait state} catch (IOException |        Interruptedexception e) {logger.error ("", e);    } return ZK; }//Create Znode private void CreateNode (ZooKeeper zk, String url) {try {byte[] data = url.getbytes            (); String path = zk.create (Constant.zk_provider_path, data, ZooDefs.Ids.OPEN_ACL_UNSAFE, Createmode.ephemeral_ sequential);        Create a temporary and ordered Znode logger.debug ("Create Zookeeper node ({} = {})", Path, URL); } catch (Keeperexception |        Interruptedexception e) {logger.error ("", e); }    }}

The Constant constants involved, see the following code:

Package Demo.zookeeper.remoting.common;public interface Constant {    String zk_connection_string = "localhost:2181" ;    int zk_session_timeout =;    String Zk_registry_path = "/registry";    String Zk_provider_path = Zk_registry_path + "/provider";}

Note: We first need to use ZooKeeper's client tool to create a persistent znode named "/registry", which does not hold any data, you can use the following command:

Create/registry NULL
4.2 Service Consumers

The service consumer needs to connect ZooKeeper at the time of creation, while listening to /registry the node's NodeChildrenChanged events, that is, once the node's child nodes change, it needs to regain the newest child nodes. The child node mentioned here is the RMI address that the service provider publishes. It should be emphasized that these child nodes are temporary and will be automatically deleted when the Session of the service provider and the ZooKeeper service registry is interrupted.

Package Demo.zookeeper.remoting.client;import Demo.zookeeper.remoting.common.constant;import java.io.IOException; Import Java.net.malformedurlexception;import Java.rmi.connectexception;import Java.rmi.naming;import Java.rmi.notboundexception;import Java.rmi.remote;import Java.rmi.remoteexception;import java.util.ArrayList; Import Java.util.list;import Java.util.concurrent.countdownlatch;import java.util.concurrent.ThreadLocalRandom; Import Org.apache.zookeeper.keeperexception;import Org.apache.zookeeper.watchedevent;import Org.apache.zookeeper.watcher;import Org.apache.zookeeper.zookeeper;import Org.slf4j.logger;import Org.slf4j.loggerfactory;public class Serviceconsumer {private static final Logger Logger = Loggerfactory.getlogger (Ser    Viceconsumer.class);    Used to wait for the syncconnected event to fire after continuing to execute the current thread private countdownlatch latch = new Countdownlatch (1);  Define a volatile member variable to hold the latest RMI address (given that the variable might be modified by another thread, and once modified, the value of the variable affects all threads) private volatile list<string> urllist = new ArrayList<> (); Constructor public Serviceconsumer () {ZooKeeper ZK = ConnectServer ();//Connect ZooKeeper server and get ZooKeeper object if (ZK! = null)    {Watchnode (ZK);//Observe all child nodes of the/registry node and update the Urllist member variable}}        Find RMI Service public <t extends remote> T lookup () {T service = null;        int size = Urllist.size ();            if (Size > 0) {String URL; if (size = = 1) {url = urllist.get (0);//If there is only one element in Urllist, the element is fetched directly logger.debug ("using onl            Y URL: {} ", url);                } else {url = urllist.get (Threadlocalrandom.current (). Nextint (size));//If multiple elements exist in the urllist, an element is randomly obtained            Logger.debug ("Using random URL: {}", url); Service = Lookupservice (URL);    Find RMI service from JNDI} return service;        }//Connect ZooKeeper server Private ZooKeeper ConnectServer () {ZooKeeper ZK = null; try {ZK = new ZooKeeper (Constant. Zk_connection_string, Constant.zk_session_timeout, New Watcher () {@Override public void Pro                        Cess (Watchedevent event) {if (event.getstate () = = Event.KeeperState.SyncConnected) { Latch.countdown ();            Wakes the currently executing thread}}); Latch.await (); Causes the current thread to be in a wait state} catch (IOException |        Interruptedexception e) {logger.error ("", e);    } return ZK; }//Observe if all child nodes under the/registry node have changed private void Watchnode (final ZooKeeper zk) {try {list<string& Gt NodeList = Zk.getchildren (Constant.zk_registry_path, New Watcher () {@Override public void P                        Rocess (Watchedevent event) {if (event.gettype () = = Event.EventType.NodeChildrenChanged) { Watchnode (ZK);            If the child node changes, call the method again (in order to get the data from the newest child node)}}); list<string> dataList = new arraylist<> (); Used to hold data for/registry all child nodes for (String node:nodelist) {byte[] data = Zk.getdata (constant.zk_ Registry_path + "/" + node, false, NULL);            Gets the data datalist.add (new String) in the child node of the/registry;            } logger.debug ("node data: {}", dataList); Urllist = dataList; Update the latest RMI address} catch (Keeperexception |        Interruptedexception e) {logger.error ("", e);        }}//Find RMI Remote service object in JNDI @SuppressWarnings ("unchecked") Private <T> T lookupservice (String URL) {        T remote = NULL;        try {remote = (T) naming.lookup (URL); } catch (Notboundexception | malformedurlexception | RemoteException e) {if (e instanceof connectexception) {//If the connection is interrupted, use the first RMI address in urllist to find (this is                A simple retry method to ensure that no exception is thrown) Logger.error ("connectexception, url: {}", url); if (Urllist.size ()!= 0) {url = urllist.get (0);                return Lookupservice (URL);        }} logger.error ("", e);    } return remote; }}
4.3 Publishing services

We need to call ServiceProvider's publish () method to publish the RMI service, which will automatically register the RMI address in ZooKeeper when the publication is successful.

Package Demo.zookeeper.remoting.server;import Demo.zookeeper.remoting.common.helloservice;public Class Server {  Public    static void Main (string[] args) throws Exception {        if (args.length! = 2) {            System.err.println ("please Using Command:java Server <rmi_host> <rmi_port> ");            System.exit ( -1);        }        String host = args[0];        int port = integer.parseint (Args[1]);        ServiceProvider Provider = new ServiceProvider ();        HelloService HelloService = new Helloserviceimpl ();        Provider.publish (HelloService, host, port);        Thread.Sleep (Long.max_value);    }}

Note: When running the main () method of the Server class, be sure to use command-line arguments to specify host and port, for example:

Java server localhost 1099java Server localhost 2099

The above two Java commands can run two server programs locally, and of course you can run more server programs at the same time, as long as the port is different.

4.4 Invoking the service

Locate the RMI remote service object by calling the Serviceconsumer lookup () method. We use a "dead loop" to simulate a remote method that is called every 3 seconds.

Package Demo.zookeeper.remoting.client;import Demo.zookeeper.remoting.common.helloservice;public class Client { Public    static void Main (string[] args) throws Exception {        Serviceconsumer consumer = new Serviceconsumer ();        while (true) {            HelloService HelloService = Consumer.lookup ();            String result = Helloservice.sayhello ("Jack");            SYSTEM.OUT.PRINTLN (result);            Thread.Sleep (+);}}}    
4.5 How to use

Verify the high availability of the RMI service according to the following steps:

    1. To run two Server programs, make sure that the port is different.
    2. Run a Client program.
    3. Stop one of the server programs and observe the changes in the client console (stopping a server does not cause client-side calls to fail).
    4. Restart the server program that you just closed, and continue to observe the Client console changes (the newly started server will join the candidate).
    5. Stop all the Server programs, or observe the client console changes (client retries the connection, multiple connections fail, and automatically shuts down).
5 Summary

Through this article, we tried to use ZooKeeper to implement a simple RMI service high availability solution that registers all service provider-issued RMI services through ZooKeeper, allowing service consumers to monitor ZooKeeper Znode to obtain the currently available RMI services 。 This program is limited to RMI services and provides a reference for any form of service (e.g. WebService).

It is a relatively perfect solution if we cooperate with ZooKeeper's own cluster, and for the ZooKeeper cluster, the reader should practice it by himself.

As the author of Water is limited, for the description of the wrong place, also ask readers to make suggestions, and look forward to better solutions.

Using RMI + ZooKeeper to implement a remote calling framework

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.