Detailed explanation of Java RMI based on distributed architecture

Posted by ljCharlie on Sun, 12 Dec 2021 10:05:43 +0100

Distributed architecture infrastructure: detailed explanation of Java RMI

Introduction to RMI

Java RMI, remote method call( Remote Method Invocation ), a method for implementing remote procedure calls (RPCs) (Remote procedure call) Java API, which can directly transfer serialized Java objects and Distributed garbage collection . Its implementation depends on Java virtual machine (JVM), so it only supports calls from one JVM to another.

rmi architecture diagram

Implementation of rmi

(1) Implement rmi directly using Registry

Server:
public class RegistryService {
    public static void main(String[] args) {
        try {
            // An instance of the remote object Registry on the local host, default port 1099
            Registry registry = LocateRegistry.createRegistry(1099);
            // Create a remote object
            HelloRegistryFacade hello = new HelloRegistryFacadeImpl();
            // Register the remote object on the RMI registration server and name it HelloRegistry
            registry.rebind("HelloRegistry", hello);
            System.out.println("======= start-up RMI Service success! =======");
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
}
Interface:

Inherit Remote interface

public interface HelloRegistryFacade extends Remote {

    String helloWorld(String name) throws RemoteException;

}
Interface implementation:

Inherit UnicastRemoteObject

public class HelloRegistryFacadeImpl extends UnicastRemoteObject implements HelloRegistryFacade{

    public HelloRegistryFacadeImpl() throws RemoteException {
        super();
    }

    @Override
    public String helloWorld(String name) {
        return "[Registry] Hello! " + name;
    }

}
client:
public class RegistryClient {
    public static void main(String[] args) {
        try {
            Registry registry = LocateRegistry.getRegistry(1099);
            HelloRegistryFacade hello = (HelloRegistryFacade) registry.lookup("HelloRegistry");
            String response = hello.helloWorld("ZhenJin");
            System.out.println("=======> " + response + " <=======");
        } catch (NotBoundException | RemoteException e) {
            e.printStackTrace();
        }
    }
}
Illustration:

source: https://www.tutorialspoint.com/java_rmi/java_rmi_introduction.htm

rmi call procedure

Registry(registry)Is the namespace where all server objects are placed.
Each time the server creates an object, it uses bind()or rebind()Method to register the object.
These are registered with a unique name called the binding name.

To invoke a remote object, the client needs a reference to that object,as(HelloRegistryFacade). 
That is, the name bound through the server(HelloRegistry)Get object from registry(lookup()method). 

(2) rmi implementation using Naming method

Server:
public class NamingService {
    public static void main(String[] args) {
        try {
            // An instance of the remote object Registry on the local host
            LocateRegistry.createRegistry(1100);
            // Create a remote object
            HelloNamingFacade hello = new HelloNamingFacadeImpl();
            // Register the remote object on the RMI registration server and name it Hello 
            //The standard format of the bound URL is: rmi://host:port/name
            Naming.bind("rmi://localhost:1100/HelloNaming", hello);
            System.out.println("======= start-up RMI Service success! =======");
        } catch (RemoteException | MalformedURLException | AlreadyBoundException e) {
            e.printStackTrace();
        }
    }
}

The interface is implemented in the same way as Registry

client:
public class NamingClient {
    public static void main(String[] args) {
        try {
            String remoteAddr="rmi://localhost:1100/HelloNaming";
            HelloNamingFacade hello = (HelloNamingFacade) Naming.lookup(remoteAddr);
            String response = hello.helloWorld("ZhenJin");
            System.out.println("=======> " + response + " <=======");
        } catch (NotBoundException | RemoteException | MalformedURLException e) {
            e.printStackTrace();
        }
    }
}
Naming part source code:
public static Remote lookup(String name)
    throws NotBoundException,java.net.MalformedURLException,RemoteException{
    ParsedNamingURL parsed = parseURL(name);
    Registry registry = getRegistry(parsed);

    if (parsed.name == null)
        return registry;
    return registry.lookup(parsed.name);
}

Naming is actually an encapsulation of Registry

Scala implements rmi

As mentioned above, rmi is a remote call through the JVM virtual machine, which is confirmed by Scala,kotlin and other JVM languages

Server:
object ScalaRmiService extends App {
  try {
    val user:UserScalaFacade = new UserScalaFacadeImpl
    LocateRegistry.createRegistry(1103)
    Naming.rebind("rmi://localhost:1103/UserScala", user)
    println("======= start-up RMI Service success! =======")
  } catch {
    case e: IOException => println(e)
  }
}
Interface
trait UserScalaFacade extends Remote {

  /**
    * Get user information by user name
    */
  @throws(classOf[RemoteException])
  def getByName(userName: String): User

  /**
    * Obtain user information by user gender
    */
  @throws(classOf[RemoteException])
  def getBySex(userSex: String): List[User]

}
Interface implementation:
class UserScalaFacadeImpl extends UnicastRemoteObject with UserScalaFacade {

  /**
    * Simulate a database table
    */
  private lazy val userList = List(
    new User("Jane", "female", 16),
    new User("jack", "male", 17),
    new User("ZhenJin", "male", 18)
  )

  override def getByName(userName: String): User = userList.filter(u => userName.equals(u.userName)).head

  override def getBySex(userSex: String): List[User] = userList.filter(u => userSex.equals(u.userSex))

}
Entity class:

Entity classes must be serializable for a remote transfer

class User(name: String, sex: String, age: Int) extends Serializable {

  var userName: String = name
  var userSex: String = sex
  var userAge: Int = age
  override def toString = s"User(userName=$userName, userSex=$userSex, userAge=$userAge)"

}
Scala client:
object ScalaRmiClient extends App {

  try {

    val remoteAddr="rmi://localhost:1103/UserScala"
    val userFacade = Naming.lookup(remoteAddr).asInstanceOf[UserScalaFacade]

    println(userFacade.getByName("ZhenJin"))
    System.out.println("--------------------------------------")
    for (user <- userFacade.getBySex("male")) println(user)

  } catch {
    case e: NotBoundException => println(e)
    case e: RemoteException => println(e)
    case e: MalformedURLException => println(e)
  }

} 
Java client:
public class JavaRmiClient {

    public static void main(String[] args) {

        try {
            String remoteAddr="rmi://localhost:1103/UserScala";
            UserScalaFacade userFacade = (UserScalaFacade) Naming.lookup();

            User zhenJin = userFacade.getByName("ZhenJin");
            System.out.println(zhenJin);
            System.out.println("--------------------------------------");
            List<User> userList = userFacade.getBySex("male");
            System.out.println(userList);

        } catch (NotBoundException | RemoteException | MalformedURLException e) {
            e.printStackTrace();
        }
    }
}

The above experiments can prove that Scala and Java can communicate with each other, and scala itself can directly reference Java classes

Introduction to serialization

Serialization( Serialization )Is the conversion of a data structure or object state into something that can be stored (for example, in a file or memory) In buffer )Or the process of transmitting (for example, through a network connection), and deserialization is the opposite operation of extracting data structures from a series of bytes

Serializable and Deserialize

Kotlin implements rmi

Server:
fun main(args: Array<String>) {
    try {
        val hello: HelloKotlinFacade = HelloKotlinFacadeImpl()
        LocateRegistry.createRegistry(1102)
        Naming.rebind("rmi://localhost:1101/HelloKotlin", hello)
        println("======= start-up RMI Service success! =======")
    } catch (e: IOException) {
        e.printStackTrace()
    }
}
client:
fun main(args: Array<String>) {
    try {
        val hello = Naming.lookup("rmi://localhost:1102/HelloKotlin") as HelloKotlinFacade
        val response = hello.helloWorld("ZhenJin")
        println("=======> $response <=======")
    } catch (e: NotBoundException) {
        e.printStackTrace()
    } catch (e: RemoteException) {
        e.printStackTrace()
    } catch (e: MalformedURLException) {
        e.printStackTrace()
    }
}

Implementation and interface omission

Spring boot implements rmi

StringBoot can simply implement rmi through configuration

Server:
@Configuration
public class RmiServiceConfig {
    @Bean
    public RmiServiceExporter registerService(UserFacade userFacade) {
        RmiServiceExporter rmiServiceExporter = new RmiServiceExporter();
        rmiServiceExporter.setServiceName("UserInfo");
        rmiServiceExporter.setService(userFacade);
        rmiServiceExporter.setServiceInterface(UserFacade.class);
        rmiServiceExporter.setRegistryPort(1101);
        return rmiServiceExporter;
    }
}
client:
@Configuration
public class RmiClientConfig {

    @Bean
    public UserFacade userInfo() {
        RmiProxyFactoryBean rmiProxyFactoryBean = new RmiProxyFactoryBean();
        rmiProxyFactoryBean.setServiceUrl("rmi://localhost:1101/UserInfo");
        rmiProxyFactoryBean.setServiceInterface(UserFacade.class);
        rmiProxyFactoryBean.afterPropertiesSet();
        return (UserFacade) rmiProxyFactoryBean.getObject();
    }

}
Client test class:
@Autowired
private UserFacade userFacade;
    
@Test
public void userBySexTest() {
    try {
        List<User> userList = userFacade.getBySex("male");
        userList.forEach(System.out::println);
    } catch (RemoteException e) {
        e.printStackTrace();
    }
}

It can be seen from the test class that this is no different from our usual program calling internal methods!

rmi call procedure

You can deepen your understanding through the following articles:

https://stuff.mit.edu/afs/athena/software/java/java_v1.2.2/distrib/sun4x_56/docs/guide/rmi/Factory.html

rmi factory called procedure

  • There are two remote service interfaces for client s to call, Factory and Product interfaces

  • FactoryImpl class implements the Factory interface, and ProductImpl class implements the Product interface

    1. FactoryImpl Registered to rmi-registry in
    2. client End request one Factory Reference to 
    3. rmi-registry return client End one FactoryImpl Reference to 
    4. client End call FactoryImpl A remote method request for ProductImpl Remote reference for
    5. FactoryImpl Return to client End one ProductImpl quote 
    6. client adopt ProductImpl Call remote method by reference 
    

socket factory documentation:
https://docs.oracle.com/javase/8/docs/technotes/guides/rmi/socketfactory/index.html

Zookeeper implements rmi

source: http://www.importnew.com/20344.html

Installing Zookeeper

Unzip ZooKeeper

tar -zxvf zookeeper-3.4.12.tar.gz

Create a new zoo in the conf directory cfg

cd zookeeper-3.4.12/conf
vim zoo.cfg

zoo. The CFG code is as follows (specify the log file directory yourself):

tickTime=2000
dataDir=/usr/local/zookeeper-3.4.12/data 
dataLogDir=/usr/local/zookeeper-3.4.12/log
clientPort=2181

In the bin directory, start Zookeeper:

cd zookeeper-3.4.12/bin
./zkServer.sh start

consumer:

public class RmiConsumer {

    // Used to wait for the SyncConnected event to trigger and continue executing the current thread
    private CountDownLatch latch = new CountDownLatch(1);

    // Define a volatile member variable to store the latest RMI address (considering that the variable may be modified by other threads, once modified, the value of the variable will affect all threads)
    private volatile List<String> urlList = new ArrayList<>();

    // constructor 
    public RmiConsumer() {
        ZooKeeper zk = connectServer(); // Connect to the ZooKeeper server and get the 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, get the element directly
                log.debug("using only url: {}", url);
            } else {
                url = urlList.get(ThreadLocalRandom.current().nextInt(size)); // If there are multiple elements in urlList, one element is obtained randomly
                log.debug("using random url: {}", url);
            }
            service = lookupService(url); // Find RMI services from JNDI
        }
        return service;
    }

    // Connect to 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.countDown(); // Wake up the currently executing thread
                    }
                }
            });
            latch.await(); // Puts the current thread in a waiting state
        } catch (IOException | InterruptedException e) {
            log.error("", e);
        }
        return zk;
    }

    // Observe whether all child nodes under the / registry node have changed
    private void watchNode(final ZooKeeper zk) {
        try {
            List<String> nodeList = zk.getChildren(Constant.ZK_REGISTRY_PATH, event -> {
                if (event.getType() == Watcher.Event.EventType.NodeChildrenChanged) {
                    watchNode(zk); // If the child node changes, the method is called again (to obtain the data in the latest child node)
                }
            });
            List<String> dataList = new ArrayList<>(); // Used to store data in all child nodes of / registry
            for (String node : nodeList) {
                byte[] data = zk.getData(Constant.ZK_REGISTRY_PATH + "/" + node, false, null); // Gets the data in the child node of / registry
                dataList.add(new String(data));
            }
            log.debug("node data: {}", dataList);
            urlList = dataList; // Update the latest RMI address
        } catch (KeeperException | InterruptedException e) {
            log.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) {
            log.error("Remote lookup error!", e);
        }
        return remote;
    }
}

producer:

public class RmiProvider {


    /**
     * Used to wait for the SyncConnected event to trigger and continue executing the current thread
     */
    private CountDownLatch latch = new CountDownLatch(1);

    // Publish RMI service and register RMI address in ZooKeeper
    public void publish(Remote remote, String host, int port) {
        String url = publishService(remote, host, port); // Publish RMI service and return RMI address
        if (url != null) {
            ZooKeeper zk = connectServer(); // Connect to the ZooKeeper server and get the ZooKeeper object
            if (zk != null) {
                createNode(zk, url); // Create a ZNode and put the RMI address on the 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);
            log.debug("publish rmi service (url: {})", url);
        } catch (RemoteException | MalformedURLException e) {
            log.error("", e);
        }
        return url;
    }

    // Connect to 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.countDown(); // Wake up the currently executing thread
                    }
                }
            });
            latch.await(); // Puts the current thread in a waiting state
        } catch (IOException | InterruptedException e) {
            log.error("", e);
        }
        return zk;
    }

    /**
     * Create node
     */
    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 orderly ZNode
            log.debug("create zookeeper node ({} => {})", path, url);
        } catch (KeeperException | InterruptedException e) {
            log.error("", e);
        }
    }
}

Illustration:

Topics: Java Distribution architecture