響應式
JetLinks使用reactor作爲響應式編程框架,從網絡層(webflux
,vert.x
)到持久層(r2dbc
,elastic
)全部
封裝爲非阻塞
,響應式
調用.
響應式可以理解爲觀察者模式
,通過訂閱
和發佈
數據流中的數據對數據進行處理.
reactor
提供了強大的API,簡化了對數據各種處理方式的複雜度,如果你已經大量使用了java8 stream api
,使用reactor
將很容易上手.
TIP注意:
響應式
與傳統編程
最大的區別是:
響應式
中的方法調用是在構造
一個流以及處理流中數據
的邏輯,當流
中產生了數據(發佈,訂閱
),纔會執行構造好的邏輯.
傳統編程
則是直接執行邏輯獲取結果.
優點
非阻塞,集成netty
等框架可實現更高的網絡併發處理能力.
API豐富,實現很多複雜的功能只需要幾行代碼,例如:
- 前端展示實時數據處理進度.
- 請求撤銷,可獲取到連接斷開事件.
- 定時(
interval
),延遲(delay
),超時(timout
),以及細粒度的流量控制(limitRate
).
缺點
調試不易,異常棧難跟蹤,對開發人員有更高的要求.
warning 注意:響應式只是一個編程模型,並不能直接提高系統的併發處理能力.
通常與netty(reactor-netty
)等框架配合,從上(網絡
)到下(持久化
)全套實現非阻塞
,響應式
纔有意義.
說明
系統中大量使用到了reactor
,其核心類只有2個Flux
(0-n個數據的流),Mono
(0-1個數據的流).
摒棄傳統編程
的思想,熟悉Flux
,Mono
API,就可以很好的使用響應式編程了.
常用API:
map
: 轉換流中的元素:flux.map(UserEntity::getId)
flatMap
: 轉換流中的元素爲新的流:flux.flatMap(this::findById)
flatMapMany
: 轉換Mono中的元素爲Flux(1轉多):mono.flatMapMany(this::findChildren)
concat
: 將多個流連接在一起組成一個流(按順序訂閱) :Flux.concat(header,body)
merge
: 將多個流合併在一起,同時訂閱流:Flux.merge(save(info),saveDetail(detail))
zip
: 壓縮多個流中的元素:Mono.zip(getData(id),getDetail(id),UserInfo::of)
then
: 流完成後執行.doOnNext
: 流中產生數據時執行.doOnError
: 發送錯誤時執行.doOnCancel
: 流被取消時執行.onErrorContinue
: 流發生錯誤時,繼續處理數據而不是終止整個流.defaultIfEmpty
: 當流爲空時,使用默認值.switchIfEmpty
: 當流爲空時,切換爲另外一個流.as
: 將流作爲參數,轉爲另外一個結果:flux.as(this::save)
完整文檔請查看官方文檔
注意
代碼格式化
使用reactor
時,應該注意代碼儘量以.
換行並做好相應到縮進.例如:
//錯誤
return paramMono.map(param->param.getString("id")).flatMap(this::findById);
//建議
return paramMono
.map(param->param.getString("id"))
.flatMap(this::findById);
lamdba
避免在一個lambda
中編寫大量的邏輯代碼,推薦參考領域模型
,將具體當邏輯放到對應到實體
或者領域對象
中.例如:
//錯誤
return devicePropertyMono
.map(prop->{
Map<String,Object> map = new HashMap<>();
map.put("property",prop.getProperty());
....
return map;
})
.flatMap(this::doSomeThing)
//建議
//在DeviceProperty中編寫toMap方法實現上面lambda中到邏輯.
return devicePropertyMono
.map(DeviceProperty::toMap)
.flatMap(this::doSomeThing)
null處理
數據流中到元素不允許爲null
,因此在進行數據轉換到時候要注意null
處理.例如:
//存在缺陷
return this.findById(id)
.map(UserEntity::getDescription); //getDescription可能返回null,爲null時會拋出空指針,
非阻塞與阻塞
默認情況下,reactor
的調度器由數據的生產者(Publisher
)決定,在WebFlux
中則是netty
的工作線程,
爲了防止工作線程被阻塞導致服務崩潰,在一個請求的流中,禁止執行存在阻塞(如執行JDBC
)可能的操作的,如果無法避免阻塞操作,應該指定調度器如:
paramMono
.publishOn(Schedulers.elastic()) //指定調度器去執行下面的操作
.map(param-> jdbcService.select(param))
上下文
在響應式中,大部分情況是禁止使用ThreadLocal
的(可能造成內存泄漏).因此基於ThreadLocal
的功能都無法使用,reactor中引入了上下文,在一個流中,可共享此上下文
,通過上下文進行變量共享以例如:事務
,權限
等功能.例如:
//從上下文中獲取
@GetMapping
public Mono<UserInfo> getCurrentUser(){
return Mono.subscriberContext()
.map(ctx->userService.findById(ctx.getOrEmpty("userId").orElseThrow(IllegalArgumentException::new));
}
//定義過濾器設置數據到上下文中
class MyFilter implements WebFilter{
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain){
return chain.filter(exchange)
.subscriberContext(Context.of("userId",...))
}
}
warning 注意:在開發中應該將多個流組合爲一個流,而不是分別訂閱流.例如:
//錯誤
flux.doOnNext(data->this.save(data).subscribe());
//正確
flux.flatMap(this::save);
前端請求
EventSource
接口是Web內容與服務器發送的事件的接口。一個EventSource
實例打開一個持久連接HTTP服務器,它發送事件的text/event-stream
格式。連接保持打開狀態,直到通過調用關閉EventSource.close()
。
打開連接後,來自服務器的傳入消息將以事件的形式傳遞到您的代碼中。如果傳入消息中有一個事件字段,則觸發的事件與事件字段的值相同。如果不存在任何事件字段,則將message
觸發通用事件。
var source = new EventSource(request.createTokenUrl("network/websocket/server/" + id + "/_subscribe/" + data.type));
printLog("開始訂閱");
source.onmessage = function (ev) {
printLog(ev.data);
};
source.onerror = function (ev) {
receiveMessage.setValue("");
printLog("error");
source.close()
}