阿里妹导读:流式输出在阿里内部已经遍地开花,大家耳熟能详却又好奇不已。清楚的是知道它是性能利器从而提升业务转化,不清楚的是到底什么样的技术才算是流式输出?支撑流式输出的技术理论又有哪些?流式输出适合什么样的应用场景?今天我们就来揭开这层面纱,让大家雾里看花但又能清清楚楚地看到“花”。
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
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"))
}
// 客户端初始化事件源
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);
}
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);
}
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."}
Websocket是区别于HTTP的另外一种协议,是全双工通信,协议相对来说比较中,对代码侵入度比较高。
SSE是标准的HTTP协议,是半双工通信,支持断线重连和自定义事件和数据类型,相对轻便灵活。
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();
}
}
@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);
}
<!-- 模块1 -->
<html>
<head>
<meta />
<link />
<style></style>
<script src=""></script>
</head>
<body>
<!-- 模块2 -->
<div>xxx</div>
<div>yyy</div>
<div>zzz</div>
</body>
</html>
到达浏览器的首字节时间(TTFB)
数据包到达浏览器下载HTML的时间
CSS和JS的加载及执行时间
拆成模块之后网络传输的时间会有一定的降低
<!-- 模块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>
到达浏览器的首字节时间(TTFB)
数据包到达端侧下载数据的时间
数据在网络传输的时间
流式输出的前世为流式渲染,今生为端到端的响应式,这些虽然带来了性能体验上的提升,但对研发模式变革的接受程度和运维成本的增加需要加以权衡。
简单介绍了几种流式输出的技术方案,适合不同的业务场景。
提出了流式输出适合的几种场景,以及对页面和数据进行拆分的方法。
相关链接 [1]https://rsocket.io/
[2]https://github.com/alibaba/alibaba-rsocket-broker