Starting with this blog post, we will move on to a new article series. This article series summarizes the main principles, methods and implementations of inter-system communication. We’ll look at typical information formats, look at traditional RMI calls, and extend to focus on RPC calls and use cases; Finally, we will cover the implementation of SOA architecture, including ESB implementation and service registry/governance implementation, as well as the rationale, implementation and use cases.

Inter-system communication is another key technical area that architects need to master. If understanding and mastering load balancing layer technology requires some knowledge of Linux and operating systems, then understanding and mastering inter-system communication layer technology requires some programming experience (preferably JAVA programming experience, We will mainly use JAVA technology as an example).

1. A scene

First of all, let’s take A look at A display scene: in real life, there are two technicians A and B communicating in the form of question and answer. As shown below:

Let’s take a look at some of the main points of this picture:

  • They both use Chinese to communicate. If one of them speaks Yugoslav and the other speaks Somali, and neither of them understands the other’s language family, it’s obvious that A is trying to convey something that B cannot understand.

  • Their voices are carried through the air. In addition to supporting their breathing, the air also supports the transmission of their voice. Without air they can’t understand what each other says in Chinese.

  • They communicate in A coordinated way, that is, A asks A question and waits for B to respond. After receiving B’s answer, A can ask the next question.

  • As humans, they process information in the same way: with their mouths, with their ears, and with their brains.

  • In the current communication scenario, there are only two people, A and B. But N people could come in at any time. The NTH person may not be communicating in Chinese.

2. Information format

It was clear that by talking in Chinese, the two understood each other’s intentions. To ensure efficient delivery of information, we must put it in a format that all participants can understand. For example, Chinese has its own specific grammatical structures, such as subject-verb-object and definite complement.

In the computer world, in order to make sure that information can be processed, it has to be formatted in a certain way, and it has to be understood by the target. Common information formats include:

  • XML: Extensible Markup Language, published and maintained by the W3C (World Wide Web Consortium). XML language widely used, rich extension. Suitable for network communication information description format (generally “application layer” protocol). For example, the XMPP communication protocol defined by Google is described using XML. However, the broader use of XML is to describe the system environment (because it causes more unnecessary content transfer), such as server configuration descriptions, Spring configuration descriptions, Maven repository descriptions, and so on.

  • JSON: The JavaScript Object Notation (JSON) is a lightweight data interchange format. It is designed along the same lines as XML: language-independent (popular languages support JSON descriptions: Go, Python, C, C++, C#, JAVA, Erlang, JavaScript, and so on); But unlike XML, JSON is designed to communicate. The JSON format has less capacity to describe the same data.

  • Protocol Buffer (PB) : The Protocol Buffer (PB) is a Google format for data exchange that is language – and platform-independent. Google provides implementations for three languages: Java, c++, and python, each of which contains a compiler and library files for the corresponding language.

  • TLV: T (tag/type field) L (length/size field) V (value/content field). This information format is usually used in the financial and military fields. It carries out information serialization/deserialization through bit operation of bytes (it is said that wechat information format also adopts TLV, but I don’t know the actual situation) :

Here’s an article about TLV: serialized TLV for Communication Protocols. The TLV format is the most efficient, and it doesn’t even have the “{}” symbol used in JSON to separate layers.

  • Custom format Of course, if your two internal systems already agree on an information format, you can of course use your own custom format for the description. You can use to describe a c + + structure, and then serialize/sequence it, or use a plain text, with “|” divided these strings, then serialization/sequence of it.

In this series of posts, we won’t focus on information formats, but we will spend some time comparing the speed and performance of various information formats over the network, and introduce some typical information format selection scenarios.

3. Network protocols

As depicted in the first picture, there is an invisible but important element: air. Sound travels through air, not through a vacuum. The same information is spread in the network, there is no network can not spread information. Network protocol is the “air” in the computer field. In the following figure, we use the OSI model as a reference:

  • Physical layer: The physical layer is our network equipment layer, such as our network card, switch and other equipment, between them we generally transmit electrical or optical signals.

  • Data link layer: Data links are divided into physical links and logical links. A physical link is responsible for assembling a set of electrical signals, called “frames”; The logical link layer ensures the correctness of frame transmission through some rules and protocols, and enables frames from multiple sources and targets to be transmitted on the same physical link to achieve link multiplexing.

  • Network layer: The most widely used protocols at the network layer are IP (IPV4 and IPV6) and IPX. These protocols solve the problem of locating the source and target, and how to get to the target from the source.

  • Transport layer: TCP and UDP are the most commonly used protocols of the transport layer. The most important job of the transport layer is to carry content information, and provide some kind of communication mechanism through their protocol specifications. For example, the TCP protocol uses a three-way handshake before formal data transmission. The verification mechanism ensures the correctness of each data packet. If an incorrect data packet is sent again.

  • Application layer: HTTP, FTP, and TELNET are all application layer protocols. Application layer protocols are the most flexible protocols and can even be defined by programmers themselves. Here’s how the HTTP protocol works:

In this series of posts, we won’t be focusing on network protocols. This is because the knowledge of network network protocol is a relatively independent field of knowledge, more than a dozen articles are not necessarily clear. If you are interested in network protocols, two books are recommended: TCP/IP: Volume 1- Protocols and TCP/IP: Volume 2- Implementation.

4. Communication mode/framework

At the beginning of the article, one of them prescribes a way to communicate: “You have to listen to what I have to say and give me feedback. I will ask the second question.” Although this way of communication is not efficient, it is very effective: problem by problem.

But if the people who take part in the communication process information ability is stronger, so they can use another way to communicate: “I give my question made up a number, after asking the first X question, I will not wait for your return, will ask the first X + 1 question, also you after hearing my first question, after X side deal with my problem, listen to my first X + 1 problem.”

In fact, the two types of communication can be found in the computer world, which is BIO (blocking mode) communication and NIO (non-blocking mode) communication that we will focus on in this series of posts.

4-1. BIO communication mode

Previously, most network communication was in blocking mode, i.e. :

  • After a client sends a request to the server, the client waits (and does nothing else) until the server returns a result or there is a network problem.

  • Similarly, while processing A request from client A, another request from client B waits until the processing thread on the server completes its previous processing.

As shown below:

There are several problems with traditional BIO communication:

  • At the same time, the server can only accept requests from client A. Although the requests from clients A and B are processed at the same time, the request information sent by client B can be accepted only after the server receives the request data from client A.

  • Because the server can only handle one client request at a time, the second request can only be processed when the processing is complete and returns (or when an exception occurs). Obviously, such a process is not acceptable in the case of high concurrency.

If the server has only one thread, you can use multithreading to solve the problem.

  • When the server receives a request from client X, it (after reading all the request data) sends the request to a separate thread for processing, and the main thread continues to accept the request from client Y.

  • The client side can also use a child thread to communicate with the server side. In this way, the main thread of the client is not affected, and the child thread notifies the main thread through listening mode/observing mode (and other design modes) when the server receives a response.

As shown below:

But using threads to solve this problem is actually limited:

  • While the processing of the request is handed off to a separate thread on the server side, the operating system notifies Accept () singly. In other words, the “business process” after the server receives the data packet can be multi-threaded, but the data packet still needs to be received one by one (we can see this clearly in the example code and debug process below).

  • In Linux, there is a limited number of threads that can be created. You can run the cat /proc/sys/kernel/threads-max command to check the maximum number of threads that can be created. Of course this value can be changed, but the more threads there are, the longer it takes for the CPU to switch, and the less time there is to process the real business.

  • Creating a thread is resource-intensive. When the JVM creates a thread, it allocates a stack space, even if the thread is not doing any work. The default size of this space is 128K, which you can adjust with the -xSS parameter.

  • Of course, you could use a ThreadPoolExecutor thread pool to alleviate the thread creation problem, but that would cause the BlockingQueue backlog to continue to grow, again consuming a lot of resources.

    • In addition, threads will not be closed if your application makes heavy use of long connections. In this way, system resource consumption is more likely to be out of control.
  • So, if you really want to solve the blocking problem with threads alone, you can figure out for yourself how much concurrency you can accept on a server node at a time. It seems that using threads alone is not the best way to solve this problem.

4-2. BIO communication mode in-depth analysis

In this series of posts, communication methods/frameworks will be a focus. Including the principles of NIO, and by explaining the use of Netty, JAVA native NIO framework, to familiarize yourself with these core principles.

In fact, as you can see from the above, the problem with the BIO is not whether multiple threads (including thread pools) are used to process the request, but that the accept() and read() points are blocked. It’s easy to test this problem. We simulated 20 clients (simulated with 20 threads), using JAVA’s synchronization counter CountDownLatch to ensure that all 20 clients were initialized and then sent requests to the Server at the same time, and then we watched the Server receive information.

4-2-1. Simulate 20 concurrent client requests and use single thread on the server side:

  • Client code (SocketClientDaemon)

    package testBSocket;

    import java.util.concurrent.CountDownLatch;

    public class SocketClientDaemon { public static void main(String[] args) throws Exception { Integer clientNumber = 20; CountDownLatch countDownLatch = new CountDownLatch(clientNumber);

    For (int index = 0; index < clientNumber ; index++ , countDownLatch.countDown()) { SocketClientRequestThread client = new SocketClientRequestThread(countDownLatch, index); new Thread(client).start(); } // This wait does not involve specific experimental logic, just to ensure that the daemon thread, after starting all threads, Has reached the awaited state synchronized (SocketClientDaemon. Class) {SocketClientDaemon. Class. Wait (); }}Copy the code

    } 123456789101112131415161718192021

  • The client code (SocketClientRequestThread simulation request)

    package testBSocket;

    import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; import java.util.concurrent.CountDownLatch;

    import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.log4j.BasicConfigurator;

    / * *

    • A SocketClientRequestThread threads to simulate a client request.
    • @author yinwenjie

    */ public class SocketClientRequestThread implements Runnable {

    static { BasicConfigurator.configure(); } / Log * * * * / private static final Log LOGGER. = LogFactory getLog (SocketClientRequestThread. Class); private CountDownLatch countDownLatch; /** * @param countDownLatch */ private Integer clientIndex; /** * countDownLatch is a synchronization counter provided by Java. * When the counter value is reduced to 0, all threads affected by it will be activated. This guarantee the authenticity of simulation concurrent requests * @ param countDownLatch * / public SocketClientRequestThread (countDownLatch countDownLatch, Integer clientIndex) { this.countDownLatch = countDownLatch; this.clientIndex = clientIndex; } @Override public void run() { Socket socket = null; OutputStream clientRequest = null; InputStream clientResponse = null; try { socket = new Socket("localhost",83); clientRequest = socket.getOutputStream(); clientResponse = socket.getInputStream(); / / wait, until the start of all threads SocketClientDaemon finish all threads and then send the request with this. CountDownLatch. Await (); ClientRequest. Write ((" this is the first "+ this. ClientIndex +" clientRequest. ).getBytes()); clientRequest.flush(); / / here waiting, until the server returns information SocketClientRequestThread. LOGGER. The info (" first "+ enclosing clientIndex +" a client request is completed, wait for the server to return information "); int maxLen = 1024; byte[] contextBytes = new byte[maxLen]; int realLen; String message = ""; // At this point, the program will wait for the server to return information (note that neither in nor out can be close, While ((realLen = clientResponse.read(contextBytes, 0, maxLen))! = -1) { message += new String(contextBytes , 0 , realLen); } SocketClientRequestThread. LOGGER. The info (" the information received from the server: "+ message); } catch (Exception e) { SocketClientRequestThread.LOGGER.error(e.getMessage(), e); } finally { try { if(clientRequest ! = null) { clientRequest.close(); } if(clientResponse ! = null) { clientResponse.close(); } } catch (IOException e) { SocketClientRequestThread.LOGGER.error(e.getMessage(), e); }}}Copy the code

    } 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364 6566676869707172737475767778798081828384858687888990

  • Server (SocketServer1) single thread

    package testBSocket;

    import java.io.InputStream; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket;

    import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.log4j.BasicConfigurator;

    public class SocketServer1 {

    static { BasicConfigurator.configure(); } /** * private static final Log LOGGER = LogFactory.getLog(socketServer1.class); public static void main(String[] args) throws Exception{ ServerSocket serverSocket = new ServerSocket(83); try { while(true) { Socket socket = serverSocket.accept(); InputStream in = socket.getinputStream (); OutputStream out = socket.getOutputStream(); Integer sourcePort = socket.getPort(); int maxLen = 2048; byte[] contextBytes = new byte[maxLen]; Int realLen = in.read(contextBytes, 0, maxLen); String message = new String(contextBytes, 0, realLen); / / print the information below SocketServer1. LOGGER. The info (" server receives from the port: "+ sourcePort +" : "+ message); Out. write(" Reply message! ") .getBytes()); / / close out the close (); in.close(); socket.close(); } } catch(Exception e) { SocketServer1.LOGGER.error(e.getMessage(), e); } finally { if(serverSocket ! = null) { serverSocket.close(); }}}Copy the code

    } 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960

4-2-2. As mentioned above, we can use multi-threading to optimize server-side processing:

The client-side code is the same as above, with the most important changes to the server-side code:

package testBSocket; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.log4j.BasicConfigurator; public class SocketServer2 { static { BasicConfigurator.configure(); } private static final Log LOGGER = LogFactory.getLog(SocketServer2.class); public static void main(String[] args) throws Exception{ ServerSocket serverSocket = new ServerSocket(83); try { while(true) { Socket socket = serverSocket.accept(); // Of course business processing can be handed off to a thread (thread pools can be used here), and thread creation is expensive. SocketServerThread SocketServerThread = new SocketServerThread(socket); SocketServerThread = new SocketServerThread(socket); new Thread(socketServerThread).start(); } } catch(Exception e) { SocketServer2.LOGGER.error(e.getMessage(), e); } finally { if(serverSocket ! = null) { serverSocket.close(); }}}} /** * Of course, after receiving the socket from the client, the processing of the business can be handed over to a thread. * This does not change the socket being accepted () one by one. * @author yinwenjie */ SocketServerThread implements Runnable {/** ** private static final Log LOGGER = LogFactory.getLog(SocketServerThread.class); private Socket socket; public SocketServerThread (Socket socket) { this.socket = socket; } @Override public void run() { InputStream in = null; OutputStream out = null; In = socket.getinputStream (); out = socket.getOutputStream(); Integer sourcePort = socket.getPort(); int maxLen = 1024; byte[] contextBytes = new byte[maxLen]; Int realLen = in.read(contextBytes, 0, maxLen); int realLen = in.read(contextBytes, 0, maxLen); String message = new String(contextBytes, 0, realLen); / / print the information below SocketServerThread. LOGGER. The info (" server receives from the port: "+ sourcePort +" : "+ message); Out. write(" Reply message! ") .getBytes()); } catch(Exception e) { SocketServerThread.LOGGER.error(e.getMessage(), e); } finally {// try to close try {if(in! = null) { in.close(); } if(out ! = null) { out.close(); } if(this.socket ! = null) { this.socket.close(); } } catch (IOException e) { SocketServerThread.LOGGER.error(e.getMessage(), e); }}} } 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364 6566676869707172737475767778798081828384858687888990919293949596979899100101Copy the code

4-2-3, look at the execution effect on the server side:

I believe that the server using a single thread effect is not to see, let’s take a look at the server using multiple threads when the situation:

4-2-4, root of the problem

The important question then is not “whether multithreading is used or not”, but why the Accept () and read() methods are blocked. The asynchronous I/O pattern is designed to deal with such concurrency. But in order to explain asynchronous IO mode, in the introduction of IO mode, we must first understand what is blocking synchronization, non-blocking synchronization, multiplexing synchronization mode.

Here I would like to mention that in a web article titled “Java NIO vs. IO in Detail (Popular),” the author mainly talks about his understanding of hard disk operation in non-blocking mode. In my opinion, as long as there is IO, there will be blocking or non-blocking issues, whether the IO is network or hard disk. This is why the basic JAVA NIO framework has FileChannel (and FileChannel does not support non-blocking mode at the operating system level), DatagramChannel, and SocketChannel. NIO exists not only to solve disk read and write performance, but also to solve a wider range of problems. But on the other hand, the author of the article is just expressing his own thoughts, there is no need to argue “rigidly”.

The API documentation describes the use of the serversocket.accept () method:

Listens for a connection to be made to this socket and accepts it. The method blocks until a connection is made.

So let’s first look at why serversocket.accept () is blocked. Here’s how blocking synchronous IO works:

  • The server thread issues an Accept action asking the operating system if there is a new socket message sent from port X.

  • Note that you are asking for the operating system. Socket I/O mode support is based on the operating system, so the natural synchronous I/O/ asynchronous I/O support is required at the operating system level. The diagram below:

  • If the operating system does not find a socket coming from the specified port X, the operating system waits. The serversocket.accept () method then waits. This is why the Accept () method blocks: its internal implementation uses OS level synchronous IO.

    The concepts of blocking AND non-blocking IO are program-level. It mainly describes how to deal with the problem after the program requests the IO operation of the operating system if the IO resource is not ready: the former waits; The latter continues execution (and polls using threads until an IO resource is ready)

    Synchronous IO and asynchronous IO are concepts at the operating system level. The main description is the operating system after receiving the program request IO operation, if the IO resource is not ready, how to respond to the program problem: the former does not respond until the IO resource is ready; The latter returns a token (so that the program and itself know where to notify future data), which is then returned to the program using an event mechanism when the IO resource is ready.

= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = (too much content, consists of two articles published) next include: invocation style profile (RMI, RPC, MQ), integration means profile (service governance, ESB implementation, achieve on its own

Welcome to join my knowledge planet to discuss architecture and exchange source code. Join way, search public number: Java source code