反應式編程(Reactive Programming)
反應式編程(Reactive Programming)這種新的編程範式越來越受到開發人員的歡迎。
在傳統的編程範式中,我們一般通過迭代器(Iterator)模式來遍歷一個序列。這種遍歷方式是由調用者來控制節奏的,採用的是拉的方式。每次由調用者通過 next()方法來獲取序列中的下一個值。
使用反應式流時採用的則是推的方式,即常見的發佈者-訂閱者模式。當發佈者有新的數據產生時,這些數據會被推送到訂閱者來進行處理。在反應式流上可以添加各種不同的操作來對數據進行處理,形成數據處理鏈。這個以聲明式的方式添加的處理鏈只在訂閱者進行訂閱操作時纔會真正執行.
Reactive由一系列事件以及發佈和訂閱這些事件的2個參與方組成的一個序列.
特點:
事件驅動;
異步非阻塞,無需引入線程概念。
推拉機制:數據提供方push,數據處理方拉去pull .
核心概念:
異步響應數據流 Reactive Stream:
功能類似:集合(Collections) ,表象:Java8 Stream.
單向流動;
時間+序列
數據流操作 Stream operators:
功能:操作數據 表象:過濾/合併/映射
產生新的流
觀察者模式:
定義了一種1對多的依賴關係,讓多個觀察者對象同時監聽某一主題對象(被觀察者)。
這個主題對象的狀態發生改變是會同時通知所有的觀察者,使他們能夠自動更新自己。
HttpClient
compile ‘io.projectreactor:reactor-core:3.3.4.RELEASE’
compile ‘io.projectreactor.netty:reactor-netty:0.9.6.RELEASE’
reactor.netty.http.client.HttpClient;
HttpClient.create()
.wiretap(true) //打印消息
.headers(h -> {
h.set(HttpHeaderNames.CONTENT_LENGTH, xml.length());
h.set(HttpHeaderNames.ACCEPT, "application/soap+xml, text/html") ;
h.set(HttpHeaderNames.CONTENT_TYPE, "application/soap+xml;charset=utf-8");
}) //設置消息頭屬性
.post() //post方法
.uri("http://127.0.0.1:8080/xxx")
.send(ByteBufFlux.fromString(Mono.just(body)))
.responseContent()
.aggregate()
.asString()
.map(str -> {
LOGGER.info("received {}", str); //收到的響應body
return str;
})
.timeout(Duration.ofMillis(10000), Mono.error(new Exception(sessionId+ " is timeout")))
.retryWhen(Retry.fixedDelay(10, Duration.ofSeconds(1))
.filter(throwable -> {
if (finished) {
return false;
}
return true;
})
.onRetryExhaustedThrow((retryBackoffSpec, ex) -> {
throw new RuntimeException("Failed retry Max times!");
})
)
.subscribe(body -> handleResponseBody);
;
HttpServer
DisposableServer server =
HttpServer.create()
.tcpConfiguration(tcpServer -> tcpServer.doOnConnection(connection ->
connection.addHandler("aggregator", new HttpObjectAggregator(10*1024*1024))
/*當我們用POST方式請求服務器的時候,對應的參數信息是保存在message body中的,如果只是單純的用HttpServerCodec是無法完全的解析Http POST請求的,因爲HttpServerCodec只能獲取uri中參數,所以需要加上HttpObjectAggregator*/
)
)
.writeap(true)
.host("127.0.0.1")
.port(8080)
.route(routes -> //設置路由。 類似Springboot中的 controller
routes.post("/anat",(request, response) -> {
final UnicastProcessor<Object> processor = UnicastProcessor.create();//異步消息響應流
final FluxSink<Object> sink = processor.sink();
final Flux<String> buf = request.receiveContent()
.flatMap(MyCodec::decode)//解碼請求消息
.flatMap(msg -> handle(msg, sink))//異步處理請求
.mergerWith(processor)
.flatMap(MyCodec::encode);//編碼響應消息
return response.status(HttpResponseStatus.OK)
.header(HttpHeaderNames,ACCEPT, "application/soap+xml, text/html")
.header(HttpHeaderNames.CONTENT_TYPE, "application/soap+xml;charset=utf-8")
.chunkedTransfer(false) //禁止使用分塊傳輸編碼
.sendString(Mono.from(buf)) //發送響應
.then();
})
)
.bindNow(Duration.ofDays(9999));
server.onDispose()
.block();
class Handler {
public Publisher<Object> handle(MyMessage msg, FluxSink<Object> sink)
{
....
//異步處理業務
sink.next(response); //發佈結果
}
}
TcpClient
TcpClient.create()
.host('127.0.0.1")
.port(9999)
.booststrap(booststrap -> bootstrap.option(ChannelOption.TCP_NODELAY, true)
.option(ChannelOption.SO_KEEPALIVE, true)
.option(ChannelOption.SO_RCVBUF, 81920)
.option(ChannelOption.SO_SNDBUF, 81920)
)
.observe((conn, state) -> {
if (state == ConnectionObserver.State.DISCONNECTING) {
...
} else if(state == ConnectionObserver.State.CONNECTED){
...
}
})
.doOnConnected(conn -> conn.addHandler(new LengthFieldBasedFrameDecoder(1024 * 1024, 0, 2, -2, 0)))
.handle((in, out) -> {
final UnicastProcessor<ByteBuf> processor = UnicastProcessor.create();
final FluxSink<ByteBuf> sink = processor.sink();
final Flux<ByteBuf> buf = in.receive()
.asInputStream()
.map(MyCodec::decode)
.flatMap(msg-> handle(msg, sink))
.mergerWith(processor)
.map(MyCodec::encode);
ChannelOperations<NettyInbound, NettyOutbound> conn = ( ChannelOperations<NettyInbound, NettyOutbound>)out;
return out.option(o -> o.flushOnEach())
.send(buf)
.then();
})
.writeap(true);
TcpServer
TcpServer.create()
.host("127.0.0.1")
.port(9999)
.bootstrap(b -> b.option(ChannelOption.SO_BACKLOG, 10)
.option(ChannelOption.SO_REUSEADDR, true)
.childOption(ChannelOption.SO_RCVBUF, 1024)
.childOption(ChannelOption.SO_SNDBUF, 1024)
)
.doOnConnection(c -> {
c.addHandler(new LengthFieldBasedFrameDecoder(1024, 0, 4, -4,0));
})
.observe((conn, state) -> {
if(state == ConnectionObserver.State.DISCONNECTING) {
LOGGER.info("connection{} lost session:{}", conn.address(), session);
}
})
.handle((in, out) -> {
final UnicastProcessor<ByteBuf> processor = UnicastProcessor.create();
final FluxSink<ByteBuf> sink = processor.sink();
final Flux<ByteBuf> buf = in.receive();
.asInputStream()
.map(MyCodec::decode)
.flatMap(msg-> handle(msg, sink))
.mergerWith(processor)
.onErrorResume(throwable -> {
LOGGER.info("Unexpected error:{} when process message ",throwable);
return Mono.empty();
})
.map(MyCodec::encode);
return out.option(o -> o.flushOnEach())
.send(buf)
.then();
} )
.bindNow(Duration.ofDays(300));