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:
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); } } }