What is streaming output?

One noun comprehension

1 Streaming

Stream, also known as responsive, is a research and development framework based on asynchronous data flow. It is a concept and programming model, not a technical architecture. At present, there are responsive technical frameworks in each technology stack. The front-end React .js, RxJs, RxJava, Reactor on the server side, and RXJava on the Android side. The result is responsive programming.

2 Reactive/Responsive Programming

Reactive Programming/Reactive Programming is a programming paradigm based on the event model. As we all know, there are usually two ways to obtain the execution result of the previous task in the asynchronous programming mode. One is the active rotation training, which we call the Proactive method. . The other is to receive feedback passively, which we call Reactive. Simply put, in the Reactive mode, the feedback of the result of the previous task is an event, and the arrival of this event will trigger the execution of the next task.

This is the connotation of Reactive. We call the subject of processing and issuing events as Reactor, which can receive and process events, or after processing the event, send the next event to other Reactor.

The following is a schematic diagram of a Reactive model:

Of course, a new coding mode, its RunTime will reduce context switching to improve performance and reduce memory consumption. On the contrary, it will reduce the maintainability of the code. The pros and cons need to be measured according to the benefits brought by the scene.

3 Streaming output

Streaming output is more magical. It originated from the team's internal summary after a performance competition. It was born based on the theoretical basis of streaming in the specific application of page rendering and rendered HTML in network transmission. Some people also It is simply called streaming rendering. That is: split the page into several independent modules, each module has a separate data source and a separate page template, and each module is streamed on the server side for business logic processing and page template rendering, and then streamed The rendered HTML is output to the network, then the chunked HTML data is transmitted on the network, and then the streaming chunked HTML is rendered and displayed one by one in the browser. The specific process is as follows:

The streaming output for HTML can be performed as described above, and the streaming output for json data is derived. In fact, it is the same. It is nothing more than a layer of rendering logic. The data streaming output process is similar to the above figure and will not be repeated. Here, the client's request can be regarded as a responsive event, so the summary is that the client actively sends out the request, and the server returns the data in a streaming manner, that is, streaming output.

4 End-to-end responsive

Based on the streaming output, we can go a little deeper and find that not only the data between the client and the web server can be streamed on the network, but the data between the servers of the microservice can actually be streamed on the network. Type output, as shown in the figure below:

Data can be streamed between networks. Let's take a closer look at what the data stream will look like on the entire request-response link, as shown in the figure below:

In summary, we define: end-to-end responsive = streaming output + responsive programming.

Theoretical basis of two-stream output

What is the basic technical theory that supports us to stream output and receive data like the above process? Here are a few core technical points:

1 HTTP block transfer protocol

Chunked transfer encoding is a data transfer mechanism in the Hypertext Transfer Protocol (HTTP) that allows HTTP data sent from a web server to a client application (usually a web browser) to be divided into multiple parts. The block transfer encoding is only available in the 1.1 version of the HTTP protocol (HTTP/1.1).

If you need to use the chunked transfer encoding response format, we need to set the response header Transfer-Encoding: chunked in the HTTP response. Its specific transmission format is as follows (note that the newline character in the HTTP response is \r\n):

HTTP/1.1 200 OK\r\n
\r\n
Transfer-Encoding: chunked\r\n
...\r\n
\r\n
<chunked 1 length>\r\n
<chunked 1 content>\r\n
<chunked 2 length>\r\n
<chunked 2 content>\r\n
...\r\n
0\r\n
\r\n
\r\n

For the specific process, please refer to the Noun Understanding section of Streaming Output, the block transmission coding example:

func handleChunkedHttpResp(conn net.Conn) {
    buffer := make([]byte, 1024)
    n, err := conn.Read(buffer)
    if err != nil {
        log.Fatalln(err)
    }
    fmt.Println(n, string(buffer))

    conn.Write([]byte("HTTP/1.1 200 OK\r\n"))
    conn.Write([]byte("Transfer-Encoding: chunked\r\n"))
    conn.Write([]byte("\r\n"))

    conn.Write([]byte("6\r\n"))
    conn.Write([]byte("hello,\r\n"))

    conn.Write([]byte("8\r\n"))
    conn.Write([]byte("chunked!\r\n"))

    conn.Write([]byte("0\r\n"))
    conn.Write([]byte("\r\n"))
}

It should be noted here that HTTP block transmission is more suitable for synchronous HTML output (for browsers), because SEO is involved in many web pages, and SEO TDK elements must be output synchronously, so this method is more suitable for JSON data The streaming output is achieved through SSE, as follows.

2 HTTP SSE protocol

sse (Server Send Events) is a standard HTTP protocol, which is a streaming method for the server to send events to the client. Binding listener functions for some event types in the client to do business logic processing. It should be noted here that SEE is one-way, and only the server can send the event stream to the client. The specific process is as follows:

The following field types are restricted in the SSE protocol

1)event

Event type. If this field is specified, when the client receives the message, an event will be triggered on the current EventSource object. The event type is the field value of the field. You can use the addEventListener() method to listen on the current EventSource object. For any type of named event, if the message has no event field, the event handler on the onmessage property will be triggered.

2)data

The data field of the message. If the message contains multiple data fields, the client will use line breaks to concatenate them into a string as the field value.

3)id

The event ID will become the property value of the internal property "last event ID" of the current EventSource object.

4)retry

An integer value that specifies the reconnection time (in milliseconds). If the field value is not an integer, it will be ignored.

Client code example

// 客户端初始化事件源
const evtSource = new EventSource("//api.example.com/ssedemo.php", { withCredentials: true } );

// 对 message 事件添加一个处理函数开始监听从服务器发出的消息
evtSource.onmessage = function(event) {
  const newElement = document.createElement("li");
  const eventList = document.getElementById("list");

  newElement.innerHTML = "message: " + event.data;
  eventList.appendChild(newElement);
}

Server code example

date_default_timezone_set("America/New_York");
header("Cache-Control: no-cache");
header("Content-Type: text/event-stream");
$counter = rand(1, 10);
while (true) {
  // Every second, send a "ping" event.
  echo "event: ping\n";
  $curDate = date(DATE_ISO8601);
  echo 'data: {"time": "' . $curDate . '"}';
  echo "\n\n";
  // Send a simple message at random intervals.
  $counter--;
  if (!$counter) {
    echo 'data: This is a message at time ' . $curDate . "\n\n";
    $counter = rand(1, 10);
  }
  ob_end_flush();
  flush();
  sleep(1);
}

Example of effect

event: userconnect
data: {"username": "bobby", "time": "02:33:48"}
event: usermessage
data: {"username": "bobby", "time": "02:34:11", "text": "Hi everyone."}
event: userdisconnect
data: {"username": "bobby", "time": "02:34:23"}
event: usermessage
data: {"username": "sean", "time": "02:34:36", "text": "Bye, bobby."}

It should be noted here that when SSE is not used through http2, SSE will receive the maximum number of connections limit. At this time, the default maximum number of connections is only 6, that is, only 6 SSE connections can be established at the same time, but the limit here is for the same For domain names, another 6 SSE connections can be established for cross-domain domain names. The default maximum number of connections when using SSE via HTTP2 is 100.

At present, SSE has been integrated into spring5, and the webflux of Springboot2 is actually streaming data through SSE.

3 WebSocket

Websocket is more commonplace, here is the difference between it and SSE:

  • Websocket is another protocol different from HTTP. It is full-duplex communication. The protocol is relatively medium and has a relatively high degree of code intrusion.
  • SSE is a standard HTTP protocol, half-duplex communication, supports disconnection reconnection and custom events and data types, and is relatively light and flexible.

4 RSocket

In the microservice architecture, data transmission between different services is carried out through application protocols. Typical transmission methods include REST or SOAP API based on HTTP protocol and RPC based on TCP byte stream. But for HTTP, only the request response mode is supported. If the client needs to obtain the latest push messages, polling must be used, which undoubtedly causes a lot of waste of resources. Furthermore, if the response time of a certain request is too long, it will block the processing of other requests later; although Server-Sent Events (SSE) can be used to push messages, SSE is a simple text protocol that only provides Limited functions; while WebSocket can carry out two-way data transmission, but it does not provide application layer protocol support. Rsocket solves various problems faced by existing protocols.

Rsocket is a new type of application network protocol for reactive applications. It works on the 5/6 layer protocol in the seven-layer network model. It is an application layer protocol above TCP/IP. RSocket can use different underlying transport layers, including TCP, WebSocket and Aeron. TCP is suitable for the interaction between the various components of the distributed system, WebSocket is suitable for the interaction between the browser and the server, and Aeron is a transmission method based on the UDP protocol, which ensures that RSocket can be adapted to different scenarios, as shown in the figure above. Then RSocket uses a binary format to ensure efficient transmission and save bandwidth. Moreover, the reactive flow control ensures that the two parties in the message transmission will not collapse due to excessive request pressure. For more detailed information, please refer to RSocket[1]. Lei Juan also open sourced alibaba-rsocket-broker[2], if you are interested, you can learn more about it.

Rsocket provides four different interaction modes to meet all scenarios:

RSocket provides implementations in different languages, including Java, Kotlin, JavaScript, Go, .NET and C++, etc. The following is a simple Java implementation for learning only:

import io.rsocket.AbstractRSocket;
import io.rsocket.Payload;
import io.rsocket.RSocket;
import io.rsocket.RSocketFactory;
import io.rsocket.transport.netty.client.TcpClientTransport;
import io.rsocket.transport.netty.server.TcpServerTransport;
import io.rsocket.util.DefaultPayload;
import reactor.core.publisher.Mono;

public class RequestResponseExample {

  public static void main(String[] args) {
    RSocketFactory.receive()
        .acceptor(((setup, sendingSocket) -> Mono.just(
            new AbstractRSocket() {
              @Override
              public Mono<Payload> requestResponse(Payload payload) {
                return Mono.just(DefaultPayload.create("ECHO >> " + payload.getDataUtf8()));
              }
            }
        )))
        .transport(TcpServerTransport.create("localhost", 7000)) //指定传输层实现
        .start() //启动服务器
        .subscribe();

    RSocket socket = RSocketFactory.connect()
        .transport(TcpClientTransport.create("localhost", 7000)) //指定传输层实现
        .start() //启动客户端
        .block();

    socket.requestResponse(DefaultPayload.create("hello"))
        .map(Payload::getDataUtf8)
        .doOnNext(System.out::println)
        .block();

    socket.dispose();
  }
}

5 Responsive programming framework

If you want to achieve responsiveness across the entire link, the responsive programming framework is the core technology that supports this technology. This is a programming model change for developers. Programming with asynchronous data streams is a way of programming in the original process. The model has changed a lot.

A simple example is as follows:

@Override
public Single<Integer> remaining() {
    return Flowable.fromIterable(LotteryEnum.EFFECTIVE_LOTTERY_TYPE_LIST)
        .flatMap(lotteryType -> tairMCReactive.get(generateLotteryKey(lotteryType)))
        .filter(Result::isSuccess)
        .filter(result -> !ResultCode.DATANOTEXSITS.equals(result.getRc()))
        .map(result -> (Integer) result.getValue().getValue())
        .reduce((acc, lotteryRemaining) -> acc + lotteryRemaining)
        .toSingle(0);
}

In general, we can achieve streaming output through HTTP block transfer protocol, HTTP SSE protocol and RSocket, and end-to-end responsiveness through streaming output and responsive programming can be achieved.

Three-stream output application scenarios

Performance, experience and data are the three most important things in our daily work. For performance, it has always been the core point of our pursuit of ultimate and never-ending. Streaming output is also born to solve the problem of performance experience. Are all scenarios suitable for streaming output? Of course not. Which scenes are suitable for us to come to Kangkang?

The above are the main stages of the request life cycle provided by the Resource Timing API specification. From the above, let's look at the impact of several scenarios on the request life cycle.

1 Page streaming output scene

For dynamic pages (as opposed to static pages) are mainly composed of page styles, page interaction JS, and page dynamic data. In addition to the time-consuming phases of the request life cycle, there are also time-consuming phases of page rendering. When the browser gets HTML, it will first build the DOM tree, preload the scanner, and build the CSSOM tree. Javascript is compiled and executed. During the process, the loading of CSS files and loading of JS files block the page rendering process. If we split the page in the following way for streaming output, there will be great gains in performance.

Single interface dynamic page

For some scenarios such as SEO, the page needs to be rendered and output synchronously. At this time, the page is usually a single-interface dynamic page. You can split the page into a part above the body and a part below the body, for example:

<!-- 模块1 -->
<html>
  <head>
  <meta  />
    <link  />  
  <style></style>  
  <script src=""></script>
  </head>
  <body>

<!-- 模块2 -->
        <div>xxx</div>
        <div>yyy</div>  
        <div>zzz</div> 
    </body>
</html>

When module 1 arrives on the page and module 2 does not arrive, after module 1 is rendered, CSS and JS can be loaded while waiting for the arrival of module 2, which improves performance in several aspects:

  • Time to first byte of browser (TTFB)
  • The time when the packet arrives at the browser to download HTML
  • CSS and JS loading and execution time
  • After disassembling into modules, the network transmission time will be reduced to a certain extent

Single interface multi-floor page

<!-- 模块1 -->
<html>
  <head>
  <meta  />
    <link  />  
  <style></style>  
  <script src=""></script>
  </head>
  <body>

<!-- 模块2 -->
        <div>xxx1</div>
        <div>yyy1</div>  
        <div>zzz1</div> 

<!-- 模块3 -->
        <div>xxx2</div>
        <div>yyy2</div>  
        <div>zzz2</div>

<!-- 模块4 -->
        <div>xxx3</div>
        <div>yyy3</div>  
        <div>zzz3</div>
    </body>
</html>

In many scenes, one page displays multiple floors, such as the four King Kong on the homepage, various shopping guide floors, detailed information floors, etc., and even the back floors rely on the data of the front floors. In this case, the page floors can be split into multiple floors. There are additional performance improvements in addition to the performance improvements in the above-mentioned aspects: the data processing time of the interdependence of the data between the floors.

Multi-interface and multi-floor page

Under normal circumstances, most pages are rendered by synchronous SSR rendering and asynchronous CSR rendering. At this time, JS asynchronously loads asynchronous floors. If the synchronous rendering part is split according to single interface and multiple floors, it will be loaded and run asynchronously on the basis of the above. Rendering of floors.

In general, the streaming output based on the HTTP block transfer protocol covers almost all page scenes, for all pages to improve the performance experience.

2 Data streaming output scenario

Single interface big data

For APP or single-page systems, page rendering is performed by asynchronously loading data. A single interface will cause a long RT time for a single interface, and the large data packet body will cause a large loss of unpacking and sticking in the network. . If multiple asynchronous interfaces are used, the data request delay will be higher due to the limited network bandwidth and the CPU occupancy rate brought by the network IO will be higher. Therefore, the single interface can be split into multiple mutuals through analysis of business scenarios. Independent or certain coupled business modules stream the data of these modules to bring the following performance and experience improvements.

  • Time to first byte of browser (TTFB)
  • The time when the data packet arrives at the end side to download the data
  • The time the data is transmitted over the network

Multiple interdependent interfaces

But in most scenarios, the business scenarios we encounter are mutually coupled and related. For example, the data of the list module depends on the data of the new product module on it for business logic processing. In this case, the server side processes the data of the new product module. The data is output, and then the data of the list module is processed for output. This connection saves the time of interdependence and waiting.

Of course, the daily business scenarios will be relatively more complicated, but the page performance and experience will be greatly improved and assisted by streaming output.

Four summary

  • The past life of streaming output is streaming rendering, and this life is end-to-end responsiveness. Although these have brought improvements in performance experience, the acceptance of R&D model changes and the increase in operation and maintenance costs need to be weighed.
  • It briefly introduces several technical solutions for streaming output, which are suitable for different business scenarios.
  • Several scenarios suitable for streaming output are proposed, as well as methods to split pages and data.

Original link

This article is the original content of Alibaba Cloud and may not be reproduced without permission.

Guess you like

Origin blog.csdn.net/weixin_43970890/article/details/113845662