JetLinks物聯網基礎平臺-響應式

響應式

JetLinks使用reactor作爲響應式編程框架,從網絡層(webflux,vert.x)到持久層(r2dbc,elastic)全部
封裝爲非阻塞,響應式調用.

響應式可以理解爲觀察者模式,通過訂閱發佈數據流中的數據對數據進行處理.
reactor提供了強大的API,簡化了對數據各種處理方式的複雜度,如果你已經大量使用了java8 stream api,使用reactor將很容易上手.

TIP注意:響應式傳統編程最大的區別是:
響應式中的方法調用是在構造一個流以及處理流中數據的邏輯,當中產生了數據(發佈,訂閱),纔會執行構造好的邏輯.
傳統編程則是直接執行邏輯獲取結果.

優點

非阻塞,集成netty等框架可實現更高的網絡併發處理能力.
API豐富,實現很多複雜的功能只需要幾行代碼,例如:

  1. 前端展示實時數據處理進度.
  2. 請求撤銷,可獲取到連接斷開事件.
  3. 定時(interval),延遲(delay),超時(timout),以及細粒度的流量控制(limitRate).

缺點

調試不易,異常棧難跟蹤,對開發人員有更高的要求.

warning 注意:響應式只是一個編程模型,並不能直接提高系統的併發處理能力.
通常與netty(reactor-netty)等框架配合,從上(網絡)到下(持久化)全套實現非阻塞,響應式纔有意義.

說明

系統中大量使用到了reactor,其核心類只有2個Flux(0-n個數據的流),Mono(0-1個數據的流).
摒棄傳統編程的思想,熟悉Flux,MonoAPI,就可以很好的使用響應式編程了.

常用API:

  1. map: 轉換流中的元素: flux.map(UserEntity::getId)
  2. flatMap: 轉換流中的元素爲新的流: flux.flatMap(this::findById)
  3. flatMapMany: 轉換Mono中的元素爲Flux(1轉多): mono.flatMapMany(this::findChildren)
  4. concat: 將多個流連接在一起組成一個流(按順序訂閱) : Flux.concat(header,body)
  5. merge: 將多個流合併在一起,同時訂閱流: Flux.merge(save(info),saveDetail(detail))
  6. zip: 壓縮多個流中的元素: Mono.zip(getData(id),getDetail(id),UserInfo::of)
  7. then: 流完成後執行.
  8. doOnNext: 流中產生數據時執行.
  9. doOnError: 發送錯誤時執行.
  10. doOnCancel: 流被取消時執行.
  11. onErrorContinue: 流發生錯誤時,繼續處理數據而不是終止整個流.
  12. defaultIfEmpty: 當流爲空時,使用默認值.
  13. switchIfEmpty: 當流爲空時,切換爲另外一個流.
  14. 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()
 }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章