chinese直男口爆体育生外卖, 99久久er热在这里只有精品99, 又色又爽又黄18禁美女裸身无遮挡, gogogo高清免费观看日本电视,私密按摩师高清版在线,人妻视频毛茸茸,91论坛 兴趣闲谈,欧美 亚洲 精品 8区,国产精品久久久久精品免费

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

大報(bào)文問(wèn)題實(shí)戰(zhàn)

OSC開(kāi)源社區(qū) ? 來(lái)源:京東云開(kāi)發(fā)者 ? 2023-07-13 09:56 ? 次閱讀
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

導(dǎo)讀

大報(bào)文問(wèn)題,在京東物流內(nèi)較少出現(xiàn),但每次出現(xiàn)往往是大事故,甚至導(dǎo)致上下游多個(gè)系統(tǒng)故障。大報(bào)文的背后,是不同商家業(yè)務(wù)體量不同,特別是B端業(yè)務(wù)的采購(gòu)及銷售出庫(kù)單,一些頭部商家對(duì)京東系統(tǒng)支持業(yè)務(wù)復(fù)雜度及容量能力的要求越來(lái)越高。因此我們有必要把這個(gè)問(wèn)題重視起來(lái),從組織上根本上解決。

一、認(rèn)識(shí)大報(bào)文問(wèn)題

大報(bào)文問(wèn)題,是指不同的系統(tǒng)通過(guò)網(wǎng)絡(luò)進(jìn)行數(shù)據(jù)交互時(shí)payload size過(guò)大導(dǎo)致的系統(tǒng)可用性下降問(wèn)題。

dd672328-20a0-11ee-962d-dac502259ad0.png

??對(duì)于大報(bào)文的產(chǎn)生方,過(guò)大的報(bào)文在序列化時(shí)消耗更多內(nèi)存和CPU,在傳輸時(shí)(JSF/MQ)可能超過(guò)中間件的大小限制導(dǎo)致傳輸失??;對(duì)于大報(bào)文的消費(fèi)方,過(guò)大的報(bào)文在反序列化時(shí)會(huì)產(chǎn)生大對(duì)象,消耗更多的內(nèi)存和CPU,容易觸發(fā)FullGC甚至OOM,而在處理過(guò)程中要遍歷的內(nèi)容更多,造成響應(yīng)變慢,如果涉及數(shù)據(jù)庫(kù)操作容易產(chǎn)生大事務(wù)、慢SQL,這些容易觸發(fā)超時(shí),如果客戶端有重試機(jī)制,會(huì)進(jìn)一步加重大報(bào)文消費(fèi)方負(fù)載,嚴(yán)重時(shí)導(dǎo)致服務(wù)集群整體不可用。

此外,由于大報(bào)文與小報(bào)文是在一個(gè)接口上完成的,使用相同的UMP key,它會(huì)導(dǎo)致監(jiān)控失真,報(bào)警閾值無(wú)效。如果日志記錄了原始報(bào)文,也可能磁盤打滿和響應(yīng)變慢。

在京東物流技術(shù)體系內(nèi),具體表現(xiàn)為:

大報(bào)文場(chǎng)景 后果
MQ的producer發(fā)送了大的Message 由于JMQ對(duì)消息大小的限制,導(dǎo)致producer發(fā)送失敗:消息未送達(dá)
MQ consumer反序列化Message并處理計(jì)算時(shí)產(chǎn)生大對(duì)象,頻繁FullGC,CPU使用率飆升
JSF Consumer調(diào)用API時(shí)傳入大入?yún)⒅?/td> 由于JSF Server對(duì)payload大小限制,導(dǎo)致服務(wù)端將報(bào)文拋棄:無(wú)法送達(dá)
JSF Provider響應(yīng)變慢,產(chǎn)生大對(duì)象,頻繁FullGC,CPU使用率飆升,甚至OOM;請(qǐng)求處理超時(shí)
JSF Provider返回值包含大對(duì)象 由于JSF Consumer對(duì)payload大小限制,導(dǎo)致consumer無(wú)法獲取響應(yīng)
JSF Consumer產(chǎn)生大對(duì)象,頻繁FullGC,CPU使用率飆升,甚至OOM

JMQ/JSF對(duì)payload大小的限制都屬于防御性保護(hù)措施,目前的值是科學(xué)的,它們都已經(jīng)足夠大了。在緊急止血情況下可以調(diào)整配置參數(shù)來(lái)暫時(shí)提高payload大小限制,但長(zhǎng)期看它會(huì)加重系統(tǒng)的風(fēng)險(xiǎn),應(yīng)該從設(shè)計(jì)入手避免超過(guò)payload大小限制。

1.1 背景知識(shí)

1.1.1 JMQ限制

根據(jù)JMQ的官方文檔,單條消息大?。篔MQ4不要超過(guò)4M,JMQ2不要超過(guò)2M。

具體原理是發(fā)送消息時(shí)在生產(chǎn)端做主動(dòng)校驗(yàn),如果消息大小超過(guò)閾值則拋出異常(代碼實(shí)現(xiàn)與官方文檔不一致):

class ClusterManager {
    protected volatile int maxSize = 4194304; // 4MB
}


class MessageProducer implement Producer { // Producer接口的具體實(shí)現(xiàn)類
    ClusterManager clusterManager;


    // producer.send時(shí)做校驗(yàn)
    int checkMessages(List messages) {
        int size = 0;
        for (Message message : messages) {
            size += message.getSize() // 壓縮后的大小
        }
        if (size > this.clusterManager.getMaxSize()) {
            throw new IllegalArgumentException("the total bytes of message body must be less than " + this.clusterManager.getMaxSize());
        }
    }
}

經(jīng)與JMQ團(tuán)隊(duì)確認(rèn),JMQ消息大小的限制,以代碼實(shí)現(xiàn)為準(zhǔn)(官方文檔不準(zhǔn)確):

dd8121b0-20a0-11ee-962d-dac502259ad0.png

??1.1.2 JSF限制

根據(jù)JSF官方文檔,JSF可以在server和consumer端分別設(shè)置payload size,默認(rèn)都是8MB。

dd982c3e-20a0-11ee-962d-dac502259ad0.png

?? 需要注意,觸發(fā)provider報(bào)文長(zhǎng)度限制時(shí),JSF consumer(老版本)并不會(huì)立即失敗,而是依靠客戶端超時(shí)后才返回(感覺(jué)是JSF的缺陷)。具體原因:JSF依靠底層netty來(lái)實(shí)現(xiàn)報(bào)文長(zhǎng)度限制,當(dāng)provider從請(qǐng)求報(bào)文頭里取得本次請(qǐng)求payload size發(fā)現(xiàn)超過(guò)限定值時(shí),不會(huì)繼續(xù)讀取報(bào)文體,而是拋出netty定義的TooLongFrameException,而該異常的處理依賴netty的ChannelHandler.exceptionCaught方法,JSF里沒(méi)有對(duì)TooLongFrameException做處理(吃掉異常),provider端不給consumer任何響應(yīng)(請(qǐng)求被扔進(jìn)黑洞),因此造成consumer一直等待響應(yīng)直到超時(shí),而這可能把consumer端的業(yè)務(wù)線程池拖死。


class LengthFieldBasedFrameDecoder { // 基于netty io.netty.handler.codec.LengthFieldBasedFrameDecoder的改動(dòng)
    protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
        // 從JSF協(xié)議的報(bào)文頭里獲取本次請(qǐng)求的payload size,此時(shí)還沒(méi)有讀取8MB的body
        long frameLength = getUnadjustedFrameLength(in, actualLengthFieldOffset, lengthFieldLength, byteOrder);
        if (frameLength > maxFrameLength) { // maxFrameLength即8MB限制
            throw new TooLongFrameException();
        }
    }
}


class ServerChannelHandler implements ChannelHandler {
    public void exceptionCaught(ChannelHandlerContext ctx, final Throwable cause) {
        if (cause instanceof IOException) {
            // ...
        } else if (cause instanceof RpcException) {
            // 這里可以看到遇到這種異常,JSF是如何給consumer端響應(yīng)的
            ResponseMessage responseMessage = new ResponseMessage(); // 給consumer的響應(yīng)
            responseMessage.getMsgHeader().setMsgType(Constants.RESPONSE_MSG);
            String causeMsg = cause.getMessage();
            String channelInfo = BaseServerHandler.getKey(ctx.channel());
            String causeMsg2 = "Remote Error Channel:" + channelInfo + " cause: " + causeMsg;
            ((RpcException) cause).setErrorMsg(causeMsg2);
            responseMessage.setException(cause); // 異常傳遞給consumer
            // socket.write回consumer
            ChannelFuture channelFuture = ctx.writeAndFlush(responseMessage);
        } else {
            // TooLongFrameException會(huì)走到這里,它的繼承關(guān)系如下:
            // TooLongFrameException -> DecoderException -> CodecException -> RuntimeException
            // 異常被吃掉了,不給consumer響應(yīng)
            logger.warn("catch " + cause.getClass().getName() + " at {} : {}",
                    NetUtils.channelToString(channel.remoteAddress(), channel.localAddress()),
                    cause.getMessage());
        }
    }
}

經(jīng)與JSF團(tuán)隊(duì)確認(rèn),consumer端或provider端發(fā)出的消息過(guò)大(超過(guò)playload)時(shí)consumer端得不到正確的異常響應(yīng)只提示請(qǐng)求超時(shí)的問(wèn)題,已經(jīng)在1.7.5版本修復(fù):需要provider端升級(jí)。升級(jí)后,如果consumer端發(fā)送的消息過(guò)大,provider會(huì)立即響應(yīng)RpcException。

ddd8599e-20a0-11ee-962d-dac502259ad0.png

??此外,在JSF舊版本下,consumer使用了默認(rèn)的5秒超時(shí),但consumer拋出超時(shí)異??傆脮r(shí)是48秒,這是為什么?

ddfdc404-20a0-11ee-962d-dac502259ad0.png

??這是因?yàn)閏onsumer配置的timeout不包括序列化時(shí)間,這48秒是把8MB的報(bào)文序列化的耗時(shí):


class JSFClientTransport {
    // consumer同步調(diào)用provider
    ResponseMessage send(BaseMessage msg, int timeout) {
        MsgFuture future = doSendAsyn(msg, timeout);
        return future.get(timeout, TimeUnit.MILLISECONDS);
    }


    MsgFuture doSendAsyn(final BaseMessage msg, int timeout) {
        final MsgFuture resultFuture = new MsgFuture(getChannel(), msg.getMsgHeader(), timeout);
        Protocol protocol = ProtocolFactory.getProtocol(msg.getProtocolType(), msg.getMsgHeader().getCodecType());
        byteBuf = protocol.encode(request, byteBuf); // 發(fā)送報(bào)文前的序列化
        RequestMessage request = (RequestMessage) msg;
        request.setMsg(byteBuf);
        channel.writeAndFlush(request, channel.voidPromise()); // socket.write,異步IO
        resultFuture.setSentTime(JSFContext.systemClock.now());
    }
}


class MsgFuture implements java.util.concurrent.Future {
    final long genTime = JSFContext.systemClock.now(); // new的時(shí)候就賦值了
    volatile long sentTime;


    // 拋出超時(shí)異常邏輯
    ClientTimeoutException clientTimeoutException() {
        Date now = new Date();
        String errorMsg = "[JSF-22110]Waiting provider return response timeout . Start time: " + DateUtils.dateToMillisStr(new Date(genTime))
                + ", End time: " + DateUtils.dateToMillisStr(now)
                + ", Client elapsed: " + (sentTime - genTime) // 它包括:序列化時(shí)間,由于異步IO因此不包括socket.write時(shí)間
                + "ms, Server elapsed: " + (now.getTime() - sentTime);
        return new ClientTimeoutException(errorMsg);
    }
}

1.1.3 物流網(wǎng)關(guān)限制

物流網(wǎng)關(guān)在nginx層通過(guò)client_max_body_size做了5MB限制。這意味著,JSF限制了8MB,但通過(guò)物流網(wǎng)關(guān)對(duì)外開(kāi)放成HTTP JSON API時(shí),調(diào)用者實(shí)際的限制是5MB。

1.1.4 MySQL限制

max_allowed_packet,net_buffer_length等參數(shù)在底層控制TCP層的報(bào)文長(zhǎng)度,京東物流體系內(nèi)該值足夠大,研發(fā)不必關(guān)注。

研發(fā)需要關(guān)注的是字段長(zhǎng)度的定義,主要是varchar的長(zhǎng)度。MySQL通過(guò)sql_mode參數(shù)控制字段超過(guò)長(zhǎng)度后的行為是字段截?cái)噙€是中斷事務(wù)。對(duì)于京東物流業(yè)務(wù)執(zhí)行鏈路比較長(zhǎng)的場(chǎng)景來(lái)講,同一個(gè)字段可能多處保存,例如訂單行里的skuName,就會(huì)在OFC/WMS等系統(tǒng)保存,sku_name varchar長(zhǎng)度的不一致,特殊場(chǎng)景下可能造成上下游交互出現(xiàn)問(wèn)題。

1.1.5 其他限制

DUCC value 的長(zhǎng)度默認(rèn)限制為 4W 字符。

UMP Key的限制128。

JMQ的businessId長(zhǎng)度限制100,Producer在發(fā)送是默認(rèn)超時(shí)2秒,Producer發(fā)送失敗默認(rèn)重試2次。

JMQ消費(fèi)者拋出異常會(huì)導(dǎo)致重試(進(jìn)入retry-db),首次重試10分鐘,如果重試還不成功會(huì)越來(lái)越慢推送直至過(guò)期。過(guò)期時(shí)間:JMQ2為3天,JMQ4為30天。

JSF如果不配置consumer timeout,則使用默認(rèn)值:5秒。

Zookeeper ZNode限制長(zhǎng)度 1MB。雖然可以通過(guò)jute.maxbuffer這個(gè)Java系統(tǒng)屬性修改,但強(qiáng)烈不建議。

原則上,所有依賴的中間件都要確認(rèn)其限制約束,提升健壯性,避免邊界條件被觸發(fā)而產(chǎn)生出乎意料的錯(cuò)誤。

1.2 產(chǎn)生原因

1.2.1 集合類字段無(wú)約束

導(dǎo)致京東物流線上事故的大報(bào)文問(wèn)題中,絕大部分都屬于該類問(wèn)題。而這又可以細(xì)分為兩種場(chǎng)景:


interface JsfAPI {
    // 場(chǎng)景1:批量接口,對(duì)批量的大小無(wú)限制
    void foo(List requests);    
}


class Request {
    // 場(chǎng)景2:對(duì)一個(gè)類內(nèi)部的集合類字段大小無(wú)限制
    // JMQ產(chǎn)生大報(bào)文,絕大部分屬于該場(chǎng)景
    List items;
}

當(dāng)數(shù)據(jù)量增大時(shí),報(bào)文也會(huì)增大,造成幾MB到幾十MB的報(bào)文傳輸,系統(tǒng)為了處理這樣大數(shù)據(jù)量的報(bào)文,必然會(huì)產(chǎn)生大對(duì)象,并且這種對(duì)象會(huì)一直處于內(nèi)存中,在數(shù)據(jù)保存處理時(shí),會(huì)造成內(nèi)存不能釋放,可能觸發(fā)頻繁FullGC,CPU使用率飆升。同時(shí),處理集合數(shù)據(jù),往往會(huì)有數(shù)據(jù)遍歷過(guò)程,如果無(wú)并發(fā)則時(shí)間復(fù)雜度是O(N),大的數(shù)據(jù)集必然帶來(lái)更慢的響應(yīng)速度,而consumer端不會(huì)根據(jù)payload大小動(dòng)態(tài)設(shè)置超時(shí)時(shí)間,它可能導(dǎo)致consumer端超時(shí),超時(shí)可能帶來(lái)多次重試,進(jìn)而加重服務(wù)端壓力。

例如:無(wú)印良品訂單sku品類過(guò)多,比如一個(gè)出庫(kù)單包含2萬(wàn)個(gè)sku的極端情況。

例如:WMS出庫(kù)發(fā)貨后向ECLP回傳信息,之前都是通過(guò)一個(gè)JMQ Topic: eclp_delivery進(jìn)行回傳,一份消息包含了(訂單主檔,箱明細(xì),包裹明細(xì))3部分信息。后來(lái)中石化場(chǎng)景下,一個(gè)訂單的包裹明細(xì)數(shù)量非常多,導(dǎo)致ECLP處理報(bào)文時(shí)CPU飆升,同時(shí)MQ Listener與對(duì)外服務(wù)共享CPU,導(dǎo)致接單功能可用率降低。后來(lái),從源頭入手把一個(gè)訂單按照明細(xì)進(jìn)行分頁(yè)式拆分(之前是整單回傳,之后是按明細(xì)分頁(yè)回傳),同時(shí)把eclp_delivery這一個(gè)topic拆分成3個(gè)topic:(訂單,箱明細(xì),包裹明細(xì)),解決了大報(bào)文問(wèn)題。

1.2.2 大字段無(wú)約束

它指的是某一個(gè)字段(不是集合大小),由于沒(méi)加長(zhǎng)度限制,在特定場(chǎng)景下傳入了遠(yuǎn)超預(yù)期大小的數(shù)據(jù)而造成的故障。

ECLP的商品主數(shù)據(jù)有個(gè)下發(fā)商品的接口,有個(gè)字段skuName,接口沒(méi)有對(duì)該字段長(zhǎng)度進(jìn)行約束。系統(tǒng)一直平穩(wěn)運(yùn)行,直到有個(gè)商家下發(fā)了某一個(gè)商品,它的skuName達(dá)到了10KB(事后發(fā)現(xiàn),商家是把該商品詳情頁(yè)的整個(gè)HTML通過(guò)skuName傳過(guò)來(lái)了),插入數(shù)據(jù)庫(kù)時(shí)超過(guò)了字段長(zhǎng)度限制varchar(200),導(dǎo)致插入失敗,但由于沒(méi)有考慮到這種場(chǎng)景,返回了誤導(dǎo)的錯(cuò)誤提示。展開(kāi)來(lái)看,如果ECLP為skuName定義了MySQL Text類型字段,還會(huì)有更嚴(yán)重問(wèn)題:ECLP接收下商品,下發(fā)給WMS,但WMS里的skuName是varchar(200),這個(gè)問(wèn)題就只能人工處理了,甚至與商家溝通。

WMS6.0為了考慮多場(chǎng)景全滿足,在出庫(kù)單預(yù)留了擴(kuò)展字段,在接單時(shí)技術(shù)BP自行決定寫入哪個(gè)擴(kuò)展字段。京喜BP下發(fā)出庫(kù)單時(shí)在訂單明細(xì)維度傳入了handOverSlip(交接單,其實(shí)是團(tuán)單信息,里面有多層明細(xì)嵌套),該字段其實(shí)是一個(gè)大JSON,單個(gè)長(zhǎng)度10KB上下,接單環(huán)節(jié)沒(méi)問(wèn)題。但組建集合單會(huì)把多個(gè)出庫(kù)單組建成一個(gè)集合單,共產(chǎn)生3000多個(gè)明細(xì),僅handOverSlip就占30MB,造成組建集合單后下發(fā)(JSF調(diào)用)揀貨時(shí)遇到了JSF 8MB限制問(wèn)題,下發(fā)失敗,單據(jù)卡在那里,現(xiàn)場(chǎng)生產(chǎn)無(wú)法繼續(xù)。

WMS6.0的用戶中心系統(tǒng),為其他系統(tǒng)提供了發(fā)送咚咚通知的服務(wù),具體實(shí)現(xiàn)是調(diào)用集團(tuán)的咚咚發(fā)送接口:xxx生產(chǎn)系統(tǒng) -> 用戶中心 -> 咚咚系統(tǒng)。鏈路上每一個(gè)環(huán)節(jié)都未對(duì)通知內(nèi)容content字段長(zhǎng)度做限制。一次xxx生產(chǎn)系統(tǒng)調(diào)用用戶中心傳入了超8MB的content字段,觸發(fā)了咚咚系統(tǒng)的JSF底層的報(bào)文限制,最終在用戶中心產(chǎn)生了ClientTimeoutException,它導(dǎo)致用戶中心的JSF業(yè)務(wù)線程池打滿;而由于用戶中心為所有業(yè)務(wù)生產(chǎn)系統(tǒng)服務(wù),現(xiàn)場(chǎng)操作會(huì)依賴它,進(jìn)而導(dǎo)致生產(chǎn)卡頓,現(xiàn)場(chǎng)多環(huán)節(jié)無(wú)法正常生產(chǎn)。

Amazon FBA的SP-API(Sell Partner API),對(duì)可能出現(xiàn)風(fēng)險(xiǎn)的字段都做了長(zhǎng)度限制,例如:


String displayableOrderComment; // maxLength: 1000
String sellerSku; // maxLength: 50
String giftMessage; // maxLength: 512
String displayableComment; // maxLength: 250

de128830-20a0-11ee-962d-dac502259ad0.png

1.2.3 查詢接口返回大量數(shù)據(jù)

ECLP主數(shù)據(jù)有個(gè)接口:導(dǎo)出所有warehouse list,調(diào)用方很多,訪問(wèn)頻率不高,每次響應(yīng)長(zhǎng)度3MB。該接口在線上出現(xiàn)過(guò)多次事故(2019年)。這個(gè)接口顯然是不該存在的,但把它下線需要推動(dòng)所有的調(diào)用方改動(dòng),這個(gè)周期很長(zhǎng)阻力也很大。

最開(kāi)始,直接查數(shù)據(jù)庫(kù),出現(xiàn)事故后加入JimDB,再次出現(xiàn)事故后配置了JimDB的local cache,后又加入JSF限流等措施。

出現(xiàn)故障時(shí),ECLP CPU飆升,導(dǎo)致服務(wù)超時(shí),京東零售調(diào)用方配置的超時(shí)設(shè)置很短,這導(dǎo)致越來(lái)越多的請(qǐng)求打過(guò)來(lái),加重了ECLP負(fù)擔(dān)。

1.2.4 導(dǎo)出問(wèn)題

這個(gè)問(wèn)題與【1.2.3 查詢接口返回大量數(shù)據(jù)】看上去類似,但有很大不同:一個(gè)同步調(diào)用,返回的數(shù)據(jù)量相對(duì)少,另一個(gè)異步執(zhí)行,返回?cái)?shù)據(jù)量巨大。

WMS6.0的報(bào)表都有導(dǎo)出的需求,例如導(dǎo)出最近3個(gè)月的明細(xì)數(shù)據(jù)。貼近商家的OFC(如ECLP),也有類似需求,商家要求導(dǎo)出明細(xì)數(shù)據(jù)。系統(tǒng)執(zhí)行過(guò)程大致是:根據(jù)用戶指定的條件異步執(zhí)行SQL,把數(shù)據(jù)庫(kù)返回的數(shù)據(jù)集寫入Excel,并存放到blob storage(指定TTL),用戶在規(guī)定時(shí)間(TTL)內(nèi)根據(jù)storage key去blob storage下載,完成整個(gè)導(dǎo)出過(guò)程。

這里的關(guān)鍵問(wèn)題是如何查詢數(shù)據(jù)庫(kù),而數(shù)據(jù)庫(kù)作為共享資源往往是整個(gè)系統(tǒng)的瓶頸(增加復(fù)本數(shù)量意味著成本上升),它變慢會(huì)拖垮整個(gè)系統(tǒng)。如何查詢數(shù)據(jù)庫(kù),有8個(gè)可選項(xiàng):

de367678-20a0-11ee-962d-dac502259ad0.png

??導(dǎo)出問(wèn)題的本質(zhì),是大范圍table scan,很難設(shè)計(jì)精細(xì)的復(fù)合索引。WMS6.0最初使用的是方案1,它會(huì)產(chǎn)生深分頁(yè)limit offset問(wèn)題:越往后的頁(yè)面越慢,對(duì)數(shù)據(jù)庫(kù)的壓力越大。舉例:要導(dǎo)出100萬(wàn)行記錄,每頁(yè)1萬(wàn),那么到50萬(wàn)記錄時(shí),每次分頁(yè)查詢相當(dāng)于數(shù)據(jù)庫(kù)要掃描50萬(wàn)+行記錄后拋棄絕大部分并返回1萬(wàn)行,這還要繼續(xù)執(zhí)行50次,此外分頁(yè)組件還要額外執(zhí)行count語(yǔ)句以計(jì)算總行數(shù)。如果每頁(yè)是1千呢?因此,數(shù)據(jù)庫(kù)的壓力被放大了,可以簡(jiǎn)單理解為“全表掃描”了【50 + 100(count計(jì)算)=150】次,遠(yuǎn)不如不分頁(yè)(不分頁(yè)還要解決OOM問(wèn)題)。目前,WMS6.0改用了方案8,根本上解決了數(shù)據(jù)庫(kù)慢查詢問(wèn)題。思路是不再盲目靜態(tài)分頁(yè),而是根據(jù)時(shí)間條件切分成多個(gè)SQL,分別查詢,保證每個(gè)SQL返回?cái)?shù)據(jù)量不大從而避免慢SQL。例如,某個(gè)倉(cāng)要導(dǎo)出最近3個(gè)月的出庫(kù)單數(shù)據(jù),那么把這1個(gè)date range拆分(explode)成N個(gè)date range,分別執(zhí)行:


condition = DateRange(from = "2022-01-01 0000", to = "2022-04-01 0000") // 用戶指定的時(shí)間范圍:3個(gè)月
// sql = select * from ob_shipment_order where xxx and update_time between condition.from and condition.to
List chunks = explode(condition)
for (DateRange chunk : chunks) {
    // 該chunk的時(shí)間范圍已經(jīng)變成了1天,甚至是1小時(shí),具體值是根據(jù)SQL執(zhí)行計(jì)劃估算得來(lái)的:數(shù)據(jù)量越大則拆分越細(xì)
    sql = select * from ob_shipment_order where xxx and update_time between chunk.from and chunk.to
    mysql.query(sql)
}

1.2.5 payload約束不一致產(chǎn)生的問(wèn)題

鏈路上經(jīng)過(guò)不同的系統(tǒng),不同系統(tǒng)對(duì)payload size的約束不同,也可能產(chǎn)生問(wèn)題,因?yàn)闆Q定是否可以正常處理的是最小的那個(gè),但鏈路長(zhǎng)時(shí)相關(guān)方可能不知道,在異步場(chǎng)景下這個(gè)問(wèn)題尤為明顯。

例如,aws的API Gateway與Lambda對(duì)payload size有不同的約束,最終用戶必須知道限制最嚴(yán)格的那一個(gè)環(huán)節(jié)。

de68d672-20a0-11ee-962d-dac502259ad0.png

??對(duì)于京東物流,JSF與JMQ的限制不同,理論上可能產(chǎn)生這樣的問(wèn)題:JSF調(diào)用者發(fā)送8MB的請(qǐng)求,JSF提供者處理時(shí)采用同步轉(zhuǎn)異步機(jī)制,異步把該請(qǐng)求8MB發(fā)送MQ,它會(huì)導(dǎo)致MQ發(fā)送永遠(yuǎn)無(wú)法成功,而JSF的調(diào)用方卻渾然不覺(jué)。

如果通過(guò)物流網(wǎng)關(guān)對(duì)外開(kāi)放,網(wǎng)關(guān)nginx限制是5MB,而JSF是8MB,設(shè)計(jì)上沒(méi)問(wèn)題(fail fast),但可能造成服務(wù)方承諾與調(diào)用者感知端到端的不一致。

JSF對(duì)provider(jsf:server)和consumer可以分別設(shè)置不同的報(bào)文大小限制,理論上也可能出現(xiàn)問(wèn)題,但在京東物流尚未出現(xiàn),可不必關(guān)注。

1.2.6 其他非入口場(chǎng)景

它發(fā)生在系統(tǒng)執(zhí)行過(guò)程內(nèi)部。典型場(chǎng)景是DAO層查詢數(shù)據(jù)庫(kù)返回大結(jié)果集,Redis大key問(wèn)題等。這要根據(jù)具體中間件機(jī)制來(lái)識(shí)別,例如,MyBatis支持插件來(lái)識(shí)別DAO查詢出大結(jié)果集:


public class ListResultInterceptor implements org.apache.ibatis.plugin.Interceptor {
    private static final int RESULTSET_SIZE_THRESHOLD = 10000;


    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object result = invocation.proceed();
        if (result != null && result instanceof List) {
            int resultSetSize = ((List) result).size();
            if (resultSetSize > RESULTSET_SIZE_THRESHOLD) {
                // 報(bào)警
            }
        }


        return result;
    }
}
二、設(shè)計(jì)原則

de75c0ee-20a0-11ee-962d-dac502259ad0.png

2.1 主動(dòng)顯式強(qiáng)約束

即,主動(dòng)防御式自我保護(hù),而不是依靠使用者的“自覺(jué)”:外部用戶不可信賴。

對(duì)于JSF,可以通過(guò)JSR303向API Consumer顯式傳遞約束,并且該約束可以通過(guò)框架對(duì)業(yè)務(wù)代碼無(wú)侵入地自動(dòng)執(zhí)行。對(duì)于MQ,由于生產(chǎn)者與消費(fèi)者解耦,無(wú)法直接傳遞約束,只能靠主動(dòng)監(jiān)控、人工協(xié)調(diào)。

它的前提條件,是研發(fā)有能力去主動(dòng)識(shí)別出大報(bào)文風(fēng)險(xiǎn)。

2.2 Fail Fast

如果有前端,那么前端加約束,避免大報(bào)文傳遞給后端。

對(duì)于后端,鏈?zhǔn)降纳舷掠侮P(guān)系中,上游要把好關(guān)。

這個(gè)原則并不是說(shuō)下游不用關(guān)心大報(bào)文問(wèn)題,恰恰相反,鏈路的每個(gè)環(huán)節(jié)都要關(guān)心,但Fail Fast可以降低整體的不必要的損耗成本,也可以緩解某個(gè)環(huán)節(jié)保護(hù)機(jī)制缺失帶來(lái)的人工介入和修數(shù)成本。

2.3 上下游對(duì)齊隱式約束

同一個(gè)業(yè)務(wù)字段在上下游傳遞時(shí),字段長(zhǎng)度約束要一致,否則可能會(huì)出現(xiàn)上游成功落庫(kù)下游無(wú)法落庫(kù)的情況。

2.4 大報(bào)文產(chǎn)生方負(fù)責(zé)拆分

解決大報(bào)文的根本思路是拆分報(bào)文:大 -> 小。

對(duì)應(yīng)MQ來(lái)講,應(yīng)該是Producer負(fù)責(zé)拆分大報(bào)文為小報(bào)文。

對(duì)于JSF來(lái)講,有兩種情況:

consumer產(chǎn)生的大報(bào)文:應(yīng)該provider加約束,強(qiáng)迫consumer端分頁(yè)拆分請(qǐng)求。參考AJAX機(jī)制

典型場(chǎng)景:揀貨下架調(diào)用庫(kù)存預(yù)占接口,一次性傳入1萬(wàn)個(gè)sku

provider產(chǎn)生的大報(bào)文:應(yīng)該變成分頁(yè)返回結(jié)果

典型場(chǎng)景:一次性返回所有warehouse列表

de968bee-20a0-11ee-962d-dac502259ad0.png

??dec3ed64-20a0-11ee-962d-dac502259ad0.png

需要注意的是,拆分報(bào)文,會(huì)增加生產(chǎn)方和消費(fèi)方的復(fù)雜度,尤其是消費(fèi)方:冪等,集齊,(并發(fā)和異步調(diào)用時(shí)產(chǎn)生的)亂序,業(yè)務(wù)的原子性保證等。例如,一個(gè)出庫(kù)單明細(xì)行過(guò)多時(shí),整單預(yù)占庫(kù)存(大報(bào)文) -> 按訂單明細(xì)分頁(yè)預(yù)占(小報(bào)文)。揀貨下架按明細(xì)維度分頁(yè)調(diào)用庫(kù)存預(yù)占接口場(chǎng)景下,如果訂單不允許缺量:整單預(yù)占時(shí),該訂單預(yù)占庫(kù)存的原子性(要么全成功預(yù)占,要么一個(gè)sku都不預(yù)占)是由庫(kù)存系統(tǒng)(provider)保證的;而在按訂單明細(xì)維度分頁(yè)預(yù)占時(shí),原子性需要在揀貨系統(tǒng)(consumer)保證,即如果后面頁(yè)碼的預(yù)占失敗則需要把前面頁(yè)碼的預(yù)占釋放。這增加consumer端復(fù)雜度,但為了系統(tǒng)的性能和可用性,這是值得的。當(dāng)然,也有另外一個(gè)可選方案,仍舊讓庫(kù)存保證原子性,但庫(kù)存接口需要增加類似(currentPage, totalPages)的參數(shù),那樣就是庫(kù)存更復(fù)雜了。無(wú)論如何,都增加了整體復(fù)雜度。

三、具體辦法

3.1 報(bào)文分頁(yè)

適用場(chǎng)景:MQ,以及JSF返回大報(bào)文響應(yīng)。

為了保持報(bào)文的完整性,也便于消費(fèi)方實(shí)現(xiàn)冪等、集齊等邏輯,需要在報(bào)文里額外增加分頁(yè)信息:currentPage/totalPages。


class Payload {
    List items;
    int currentPage, totalPages;
}


void sendPayload(Payload payload) {
    int currentPage = 1;
    int totalPages = payload.getItems().size() / batchSize;
    Lists.partition(payload.getItems, batchSize).forEach(subItems -> {
        Payload subPayload = new Payload(subItems)
        subPayload.setPageInfo(currentPage, totalPages)
        producer.send(subPayload)
        currentPage++;
    });
}

在極端復(fù)雜場(chǎng)景下,也可以考慮分拆topic,但不推薦,因?yàn)樗赡茴~外引入亂序問(wèn)題。

MQ報(bào)文編解碼除了目前的JSON外,也可以考慮Protobuf等更高效格式。例如京東零售訂單快照orderver就由xml升級(jí)到了PB。

3.2 報(bào)文轉(zhuǎn)存

適用場(chǎng)景:MQ/JSF。

這種方案,也被稱為Claim Check Pattern。

把大的明細(xì)List,按照固定batch size轉(zhuǎn)存到JFS/OSS/JimKV/S3等外部blob storage,在報(bào)文里存放指針(blob地址)列表。


class BigPayload {
    List items;
}


class SmallPayload {
    List itemBlobKeys;
}


void sendPayload(BigPayload bigPayload) {
    SmallPayload smallPayload = new SmallPayload();
    Lists.partition(bigPayload.getItems(), batchSize).forEach(subItems -> {
        List itemBlobKeys = blogStore.putObjects(subItems)
        smallPayload.addItemBlobKeys(itemBlobKeys);
    });


    producer.send(JSON.encode(smallPayload);
}

目前上游系統(tǒng)(eclp、序列號(hào)、OMC等)、DTC、下游系統(tǒng)(各版本W(wǎng)MS)的信息傳遞使用了該辦法,共用一個(gè)JFS集群。

Side effects:1)引入額外依賴,而且消費(fèi)方被迫引入依賴 2)需要Blob存儲(chǔ)的TTL機(jī)制或定期清理,否則加大存儲(chǔ)成本 3)為消費(fèi)方帶來(lái)了不確定性,從blob拿回的數(shù)據(jù)可能超大,在反序列化和處理過(guò)程中有OOM/FullGC等風(fēng)險(xiǎn)(雖然一些json庫(kù)提供了底層的基于詞法token的Streaming Parsing API,但如果要讀取全部?jī)?nèi)容仍然耗費(fèi)大量?jī)?nèi)存)

3.3 報(bào)文截?cái)?/strong>

適用場(chǎng)景:大字段。

在確定用戶體驗(yàn)可以接受的情況下,上層進(jìn)行字段內(nèi)容截?cái)?truncate)。及早截?cái)啵灰蕾囅聦訑?shù)據(jù)庫(kù)的截?cái)鄼C(jī)制。

3.4 分頁(yè)調(diào)用

適用場(chǎng)景:JSF。

兩種場(chǎng)景:一種是批量接口,即入?yún)⑹羌?,另一種是入?yún)?duì)象里有集合字段。


class FooRequest {
    @javax.validation.constraints.Size(min = 1, max = 200)
    private List barItems;
}


interface JsfAPI {
    // 場(chǎng)景1:批量接口
    void foo(@javax.validation.constraints.Size(min = 1, max = 200) List requests)


    // 場(chǎng)景2:請(qǐng)求對(duì)象里有集合字段
    void bar(FooRequest request);
}

對(duì)于JSF Consumer,可以通過(guò)JSF異步調(diào)用,它相當(dāng)于redis pipeline模式,也可以通過(guò)客戶端線程池并發(fā)調(diào)用方式實(shí)現(xiàn)分頁(yè)調(diào)用,二者耗時(shí)相同,推薦使用前者:1)代碼實(shí)現(xiàn)簡(jiǎn)單 2)節(jié)省了額外線程池成本。

dedc7960-20a0-11ee-962d-dac502259ad0.png

df031566-20a0-11ee-962d-dac502259ad0.png


int maxJsfRetries = 3; // JSF async下的自動(dòng)重試只能應(yīng)用層自己做了
int retried = 0;
do {
    List>> futures = new LinkedList();
    Lists.partition(voList, batchSize).forEach(subVoList -> {
        ObLocatingOrderDto dto = mapper.INSTANCE.toDTO(subVoList);
        locatingAppService.outboundOrderLocate(dto); // async JSF call
        ResponseFuture> future = RpcContext.getContext().getFuture();
        futures.add(future);
    });


    for (ResponseFuture> future : futures) {
        try {
            Result result = future.get();
        } catch (RpcException jsfException) {
            retried++;
        } catch (Throwable e) {
            // 額外的業(yè)務(wù)邏輯:與JSF并發(fā)同步調(diào)用相同的處理邏輯
        }
    }
} while (retried <= maxJsfRetries);

JSF異步調(diào)用時(shí),jsf:consumer配置的retries無(wú)效,這是因?yàn)楫惒桨l(fā)送后如果出現(xiàn)網(wǎng)絡(luò)超時(shí),只能由業(yè)務(wù)代碼通過(guò)future.get()才能拿到結(jié)果,JSF底層沒(méi)有機(jī)會(huì)進(jìn)行自動(dòng)重試。而同步調(diào)用時(shí),JSF底層可以判斷出超時(shí),它有機(jī)會(huì)根據(jù)配置進(jìn)行自動(dòng)重試。更多細(xì)節(jié)可以查看JSF的FailoverClient.doSendMsg方法。

3.5 MQ替代JSF

適用場(chǎng)景:?jiǎn)蜗蛲ㄖ愓?qǐng)求,相當(dāng)于AsyncAPI。

大的報(bào)文往往意味著更長(zhǎng)的處理時(shí)長(zhǎng),JSF同步調(diào)用下consumer必須同步等待provider端的返回,這會(huì)同時(shí)占用consumer和provider雙方的線程池資源,極端情況下可能導(dǎo)致雙方線程池用盡。JSF下可能耗盡線程池,進(jìn)而拖死被強(qiáng)依賴的上游,產(chǎn)生雪崩效應(yīng);而MQ下,只會(huì)消費(fèi)積壓。

異步交互,使得上游對(duì)下游響應(yīng)時(shí)間的依賴轉(zhuǎn)換為吞吐率的依賴。JMQ實(shí)現(xiàn)了消費(fèi)者和生產(chǎn)者在時(shí)間和空間上的解耦,消息的消費(fèi)者可以承受更大范圍的處理速度范圍。

3.6 總結(jié)

df2b067a-20a0-11ee-962d-dac502259ad0.png

四、最佳實(shí)踐

4.1 單個(gè)接口與批量接口分離

根據(jù)sku編號(hào)查詢商品資料,往往伴隨著多個(gè)sku一起查詢的需求,如何設(shè)計(jì)接口?

有的這樣:


interface JsfAPI {
    Result getSkuInfo(String sku);
    Result> listSkuInfo(List skus);
}

由于批量接口在技術(shù)上已經(jīng)滿足了單個(gè)查詢的功能,有的團(tuán)隊(duì)干脆去掉了單個(gè)查詢接口,造成使用者查詢單個(gè)sku時(shí):


Result result = jsfAPI.listSkuInfo(Lists.newArrayList("EMG1800752592"));

應(yīng)該這樣:


interface JsfAPI {
    Result getSkuInfo(String sku);
}


interface JsfBulkAPI {
    Result> listSkuInfo(List skus);
}

4.2 線程池隔離

JsfAPI與JsfBulkAPI把批量與單一接口進(jìn)行分離后,可以分配到不同的線程池,盡可能互不干擾,這同理于Bulkhead Pattern。

單一接口 批量接口
處理關(guān)鍵業(yè)務(wù),SLA要求更高 風(fēng)險(xiǎn)高,性能差

JSF可以通過(guò)jsf:server定義線程池,并為jsf:provider分配不同的server。

4.3 大報(bào)文與小報(bào)文分離

如果大報(bào)文實(shí)在無(wú)法拆分(例如,上游團(tuán)隊(duì)不配合),為了降低極端請(qǐng)求對(duì)絕大部分正常請(qǐng)求的影響,可以采用大小報(bào)文分離的辦法。

對(duì)于JMQ,為了防止某一個(gè)大報(bào)文的消費(fèi)長(zhǎng)耗時(shí)或異常導(dǎo)致小報(bào)文的消費(fèi)積壓,可以把大報(bào)文轉(zhuǎn)發(fā)到“慢隊(duì)列”進(jìn)行消費(fèi)。

此外,也要考慮如何緩解UMP監(jiān)控失真問(wèn)題。

4.4 JMQ設(shè)置合理的批量大小

df3a6bf6-20a0-11ee-962d-dac502259ad0.png

??該值決定了MessageListener.onMessage入?yún)essages的size。


interface MessageListener {
    void onMessage(List messages) throws Exception;
}

JMQ Consumer的ACK是以批為單位的,例如設(shè)置為10,則10條消息里任意一條產(chǎn)生異常都會(huì)導(dǎo)致10條全部重新消費(fèi)。大報(bào)文場(chǎng)景下,如果發(fā)現(xiàn)問(wèn)題,可以把該值調(diào)整為1,避免大小報(bào)文相互影響。

大批量消費(fèi)主要有兩個(gè)好處:1)壓縮效果好(JMQ在發(fā)現(xiàn)報(bào)文超過(guò)100B時(shí)就進(jìn)行壓縮),TCP I/O性能高 2)降低獲取消息的等待耗時(shí),因?yàn)樗喈?dāng)于prefetch(具體原理是LinkedBlockingDeque的capacity,如果拉取的消息數(shù)超過(guò)它,則IO阻塞以防止拉取新消息)。同時(shí)它也有兩大負(fù)面效應(yīng):1)ACK以批為單位,一個(gè)錯(cuò)誤導(dǎo)致整批錯(cuò)誤,整批重試 2)消息大小限制取決于整批所有消息大小,可能觸發(fā)大報(bào)文問(wèn)題。

df790bae-20a0-11ee-962d-dac502259ad0.png

??對(duì)于京東物流絕大部分業(yè)務(wù)系統(tǒng)來(lái)講,這點(diǎn)提升與繁重的業(yè)務(wù)處理來(lái)比不值一提,例如:I/O節(jié)省了5ms,但單個(gè)消息處理需要200ms(因?yàn)橐ㄟ^(guò)接口查詢,處理,然后寫庫(kù)),反倒是side effect成為主要矛盾。因此,絕大部分場(chǎng)景下該值應(yīng)該設(shè)置為1。如果業(yè)務(wù)邏輯類似于集齊:把N個(gè)消息拿下來(lái),本地緩沖暫不處理,等滿足條件了再merge并一次性處理,那么可以調(diào)整批量大小為非1。

JMQ Producer提供了批量發(fā)送方法:


interface Producer {
    void send(List messages) throws JMQException;
}

我們的業(yè)務(wù)代碼也在使用,例如:


/**
 * 發(fā)送分播結(jié)果消息
 */
public void send(List checkResultDtos) {
    List messageList = Lists.newArrayList();
    for (CheckResultDto checkResultDto : checkResultDtos) {
        String messageText = JmqMessage.createReportBody(checkResultDto.getUuid(), Lists.newArrayList(checkResultDto));
        messageList.add(JmqMessage.create(topic, messageText, checkResultDto.getUuid(), checkResultDto.getWarehouseNo()));
    }
    producer.send(messageList);
}

這里要注意,分批發(fā)送時(shí),1)發(fā)送的超時(shí)(默認(rèn)2s)作用于整批消息,而不是單個(gè)消息 2)消息大小限制(4MB)作用于整批消息之和,因此批包含的消息越多越可能失敗。

4.5 避免大日志

尤其是AOP/Interceptor/Filter等統(tǒng)一處理的代碼,因?yàn)閷?duì)報(bào)文的打印往往需要先json序列化。


if (logger.isInfoEnabled()) {
    log.info(JsonUtil.toJson(request); // CPU intensive and disk I/O intensive(雖然日志是順序?qū)?
}

如果確實(shí)要記錄,也可以考慮采樣率方式記錄大報(bào)文日志。

4.6 顯式約束由嚴(yán)開(kāi)始

開(kāi)放API由于消費(fèi)方多而且不確定性高,客觀上造成了“只有一次做對(duì)的機(jī)會(huì)”。

List size limit, property max length limit等,要在開(kāi)放API的第一時(shí)間公布出去。如果開(kāi)始不約束,后期加約束可能遭遇大的阻力和溝通成本。此外,遵循從嚴(yán)開(kāi)始的規(guī)律,為自己爭(zhēng)取主動(dòng):你把限制放開(kāi),沒(méi)人找你岔,反之則阻力大。例如:order.items max size limit由100變成200,你可以放心地做;但由200變成100,你要征得現(xiàn)有使用者的全部確認(rèn)。

例如,Amazon FBA的SP-API對(duì)集合的條數(shù)限制絕大部分是50。

五、治理機(jī)制

5.1 識(shí)別大報(bào)文場(chǎng)景

無(wú)論采用哪種大報(bào)文問(wèn)題解決辦法,識(shí)別出大報(bào)文場(chǎng)景是前提。

技術(shù)上,可以通過(guò)JSF Filter分析報(bào)文長(zhǎng)度,把尚未觸發(fā)8MB但有潛在風(fēng)險(xiǎn)的自動(dòng)識(shí)別出來(lái)。但JMQ無(wú)相關(guān)機(jī)制,業(yè)務(wù)系統(tǒng)要自行實(shí)現(xiàn)相關(guān)攔截機(jī)制。

5.1.1 JSF自動(dòng)識(shí)別

provider端自動(dòng)識(shí)別即可。


@Slf4j
public final class PayloadSizeFilter extends AbstractFilter {
    private static final int PAYLOAD_SIZE_THRESHOLD = 4 << 20; // 4MB = 8MB(JSF限制) * 50%
    private static final int BATCH_SIZE_THRESHOLD = 1000;


    @Override
    public ResponseMessage invoke(RequestMessage requestMessage) {
        if (!RpcContext.getContext().isProviderSide()) {
            // 只在provider端檢查大報(bào)文:它才是我們要保護(hù)的對(duì)象
            return getNext().invoke(requestMessage);
        }


        // 自動(dòng)識(shí)別潛在的大報(bào)文場(chǎng)景:針對(duì)報(bào)文大小
        Integer payloadSize = requestMessage.getMsgHeader().getLength();
        if (payloadSize != null && payloadSize > PAYLOAD_SIZE_THRESHOLD) {
            // 這里使用最簡(jiǎn)單的日志把潛在大報(bào)文暴露出來(lái),各團(tuán)隊(duì)可以做更細(xì)化的機(jī)制
            // 由于logbook限制只有error level日志才能配置"關(guān)鍵字報(bào)警",這里使用log.error
            // 如果不想自動(dòng)報(bào)警,只是人工巡檢,可以log.warn
            String methodName = requestMessage.getMethodName();
            String className = requestMessage.getClassName();
            log.error("Suspected BIG payload: {}.{}, {}>{}", className, methodName, payloadSize, PAYLOAD_SIZE_THRESHOLD);
        }


        // 自動(dòng)識(shí)別潛在的大報(bào)文場(chǎng)景:報(bào)文字節(jié)小,但仍會(huì)導(dǎo)致處理慢,例如 List orderNos,如果發(fā)來(lái)1萬(wàn)個(gè)單號(hào)?
        // 這里只能識(shí)別出入?yún)⑹荓ist的場(chǎng)景,對(duì)于字段類型是List的場(chǎng)景無(wú)效
        Invocation invocation = requestMessage.getInvocationBody();
        Class[] argClasses = invocation.getArgClasses();
        Object[] args = invocation.getArgs();
        for (int i = 0; i < argClasses.length; i++) {
            Class argClass = argClasses[i];
            if (Collection.class.isAssignableFrom(argClass)) {
                // 入?yún)㈩愋褪荂ollection
                Collection collection = (Collection) args[i];
                if (collection.size() > BATCH_SIZE_THRESHOLD) {
                    log.error("Too BIG Collection argument: {}>{}", collection.size(), BATCH_SIZE_THRESHOLD);
                }
            }
        }


        return getNext().invoke(requestMessage);
    }
}

5.1.2 JMQ自動(dòng)識(shí)別

在consumer端加自動(dòng)識(shí)別,如果發(fā)現(xiàn),協(xié)同producer方確認(rèn)風(fēng)險(xiǎn)判斷是否需要改造。


public interface BigPayloadTrait extends MessageListener {
    int THRESHOLD_BIG_PAYLOAD = 2 << 20; // 2MB = 4MB(JMQ限制) * 50%


    default boolean suspectedBigPayload(List messages) {
        for (Message message : messages) {
            if (message.getSize() > THRESHOLD_BIG_PAYLOAD) {
                return true;
            }
        }


        return false;
    }
}

5.2 有效的監(jiān)控

人工識(shí)別會(huì)有遺漏場(chǎng)景,關(guān)注監(jiān)控全局指標(biāo),尤其是分析一些跳點(diǎn),可能補(bǔ)充發(fā)現(xiàn)大報(bào)文場(chǎng)景。

5.3 設(shè)計(jì)應(yīng)急預(yù)案

有些大報(bào)文問(wèn)題,可能暫時(shí)無(wú)法通過(guò)技術(shù)手段解決,例如,已經(jīng)有商家接入的對(duì)外接口,開(kāi)放時(shí)沒(méi)有對(duì)List size限制,加限制后需要商家配合修改做客戶端分頁(yè),而商家不配合。這時(shí)候,可以采用大促期降級(jí),限流,加開(kāi)關(guān),加強(qiáng)監(jiān)控,設(shè)計(jì)應(yīng)急預(yù)案,為此接口提供獨(dú)立的線程池來(lái)隔離正常請(qǐng)求等手段解決。

5.4 常態(tài)化的大報(bào)文搗亂演練

以第三方視角幫助識(shí)別出尚未識(shí)別的大報(bào)文場(chǎng)景,不要自己給自己搗亂。

5.5 團(tuán)隊(duì)執(zhí)行

推進(jìn)大報(bào)文治理工作時(shí),為了便于項(xiàng)目追蹤管理,可以采用如下流程。

dfb0eefc-20a0-11ee-962d-dac502259ad0.png

??5.5.1 新的API和MQ

這里也包括現(xiàn)有API/MQ上加字段場(chǎng)景。

設(shè)計(jì)和評(píng)審時(shí),檢查:

字段長(zhǎng)度,在上下游上長(zhǎng)度對(duì)齊

JSF接口對(duì)List等集合類型加@Size顯式約束和校驗(yàn),對(duì)List性批量接口入?yún)⒁布覢Size

MQ Producer確保不發(fā)出大報(bào)文

5.5.2 現(xiàn)有系統(tǒng)治理

為所有JSF和MQ加入大報(bào)文預(yù)先監(jiān)控機(jī)制(具體可參考【5.1 識(shí)別大報(bào)文場(chǎng)景】,根據(jù)是否改得動(dòng)做相應(yīng)的治理動(dòng)作。

審核編輯:湯梓紅

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • cpu
    cpu
    +關(guān)注

    關(guān)注

    68

    文章

    11080

    瀏覽量

    217113
  • 內(nèi)存
    +關(guān)注

    關(guān)注

    8

    文章

    3125

    瀏覽量

    75271
  • SQL
    SQL
    +關(guān)注

    關(guān)注

    1

    文章

    783

    瀏覽量

    45158
  • 京東
    +關(guān)注

    關(guān)注

    2

    文章

    1024

    瀏覽量

    49278
  • 報(bào)文
    +關(guān)注

    關(guān)注

    0

    文章

    39

    瀏覽量

    4188

原文標(biāo)題:大報(bào)文問(wèn)題實(shí)戰(zhàn)

文章出處:【微信號(hào):OSC開(kāi)源社區(qū),微信公眾號(hào):OSC開(kāi)源社區(qū)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

    評(píng)論

    相關(guān)推薦
    熱點(diǎn)推薦

    CAN報(bào)文定義

    1. CAN報(bào)文定義CAN報(bào)文是指發(fā)送單元向接受單元傳送數(shù)據(jù)的幀。我們通常所說(shuō)的CAN報(bào)文是指在CAN線(內(nèi)部CAN、整車CAN、充電CAN)上利用ECU和CAN卡接收到的十六進(jìn)制報(bào)文
    發(fā)表于 09-14 09:23

    PLC MES  真正實(shí)戰(zhàn) 源程序 視頻 報(bào)文 結(jié)構(gòu) ,多個(gè)例子,,

    真正實(shí)戰(zhàn)源程序視頻報(bào)文結(jié)構(gòu),多個(gè)例子,,
    發(fā)表于 09-10 11:09

    精密電阻大報(bào)警電路圖

    精密電阻大報(bào)警電路圖
    的頭像 發(fā)表于 06-10 09:58 ?2420次閱讀
    精密電阻<b class='flag-5'>大報(bào)</b>警電路圖

    直流電流過(guò)大報(bào)警電路圖

    直流電流過(guò)大報(bào)警電路圖
    的頭像 發(fā)表于 06-10 10:03 ?3790次閱讀
    直流電流過(guò)<b class='flag-5'>大報(bào)</b>警電路圖

    報(bào)文交換,報(bào)文交換是什么意思

    報(bào)文交換,報(bào)文交換是什么意思 報(bào)文交換(Message Switching )又稱為存儲(chǔ)轉(zhuǎn)發(fā)交換,與電路交換的原理不同,不需要提供通信雙方的物理連接,而是將
    發(fā)表于 03-18 15:31 ?6770次閱讀

    icmp報(bào)文和ip報(bào)文分析

    . ICMP允許主機(jī)或路由報(bào)告差錯(cuò)情況和提供有關(guān)異常情況。ICMP是因特網(wǎng)的標(biāo)準(zhǔn)協(xié)議,但I(xiàn)CMP不是高層協(xié)議,而是IP層的協(xié)議。通常ICMP報(bào)文被IP層或更高層協(xié)議(TCP或UDP)使用。一些ICMP報(bào)文把差錯(cuò)報(bào)文返回給用戶進(jìn)程
    發(fā)表于 11-03 09:09 ?1w次閱讀
    icmp<b class='flag-5'>報(bào)文</b>和ip<b class='flag-5'>報(bào)文</b>分析

    tcp報(bào)文格式詳解

    TCP(Transmission ControProtocol)傳輸控制協(xié)議是一種面向連接的、可靠的、基于字節(jié)流的傳輸層協(xié)議。TCP報(bào)文是TCP層傳輸?shù)臄?shù)據(jù)單元,也稱為報(bào)文段。
    發(fā)表于 12-08 11:11 ?3.3w次閱讀
    tcp<b class='flag-5'>報(bào)文</b>格式詳解

    報(bào)文的傳輸原理你了解嗎

    CAN總線通訊是我們每天都會(huì)使用的工業(yè)通訊總線,工程師更多的是關(guān)注報(bào)文是否能夠正常接收,解析結(jié)果是否正確。卻忽略了CAN總線的報(bào)文是怎么產(chǎn)生以及收發(fā)的,所以遇到通訊異常的問(wèn)題時(shí)就會(huì)無(wú)從下手。那么這篇文章將會(huì)帶您快速了解報(bào)文的傳輸
    的頭像 發(fā)表于 04-25 14:50 ?2.1w次閱讀
    <b class='flag-5'>報(bào)文</b>的傳輸原理你了解嗎

    盤點(diǎn)2019上半年中國(guó)移動(dòng)互聯(lián)網(wǎng)發(fā)展大報(bào)

    2019上半年中國(guó)移動(dòng)互聯(lián)網(wǎng)大報(bào)
    的頭像 發(fā)表于 07-31 17:11 ?4025次閱讀
    盤點(diǎn)2019上半年中國(guó)移動(dòng)互聯(lián)網(wǎng)發(fā)展<b class='flag-5'>大報(bào)</b>告

    CAN基礎(chǔ):電平、邏輯、報(bào)文是怎么來(lái)的

    CAN總線的報(bào)文是怎么產(chǎn)生以及收發(fā)的,遇到通訊異常的問(wèn)題時(shí)從什么角度分析?這篇文章將會(huì)帶您快速了解報(bào)文的傳輸原理。
    的頭像 發(fā)表于 12-26 02:46 ?2729次閱讀

    ModbusTCP報(bào)文詳解

    ModbusTCP報(bào)文詳解是工業(yè)控制常用的一種協(xié)議,通過(guò)對(duì)Modbus報(bào)文的理解,能很快提升自己的實(shí)際應(yīng)用能力。
    發(fā)表于 06-07 15:15 ?4次下載

    區(qū)域短報(bào)文和全球短報(bào)文服務(wù)的區(qū)別在哪里

    北斗系統(tǒng)是全球首個(gè)提供區(qū)域短報(bào)文通信服務(wù)和全球短報(bào)文服務(wù)的衛(wèi)星導(dǎo)航系統(tǒng),目前在邊防、水利、林業(yè)、電力、海上通信等各個(gè)行業(yè)應(yīng)用。支持北斗短報(bào)文通信服務(wù)的產(chǎn)品例如北斗短報(bào)文手持終端,北斗短
    的頭像 發(fā)表于 11-24 16:38 ?4727次閱讀

    接收UDP報(bào)文的過(guò)程

    最近工作中遇到某個(gè)服務(wù)器應(yīng)用程序 UDP 丟包,在排查過(guò)程中查閱了很多資料,總結(jié)出來(lái)這篇文章,供更多人參考。 在開(kāi)始之前,我們先用一張圖解釋 linux 系統(tǒng)接收網(wǎng)絡(luò)報(bào)文的過(guò)程。 首先網(wǎng)絡(luò)報(bào)文通過(guò)
    的頭像 發(fā)表于 11-11 11:22 ?1329次閱讀
    接收UDP<b class='flag-5'>報(bào)文</b>的過(guò)程

    CAN報(bào)文為什么會(huì)發(fā)送失?。?/a>

    CAN總線調(diào)試過(guò)程中出現(xiàn)報(bào)文發(fā)送失敗。很多工程師都對(duì)此只知其一不知其二,今天我們就以CAN報(bào)文發(fā)送失敗的問(wèn)題來(lái)做一次探討。在了解CAN報(bào)文為什么會(huì)發(fā)送失敗之前我們先看看一幀標(biāo)準(zhǔn)的CAN報(bào)文
    的頭像 發(fā)表于 04-12 08:25 ?3034次閱讀
    CAN<b class='flag-5'>報(bào)文</b>為什么會(huì)發(fā)送失???

    什么是北斗短報(bào)文功能?如何實(shí)現(xiàn)北斗短報(bào)文通信?

    北斗短報(bào)文功能是指通過(guò)北斗衛(wèi)星進(jìn)行短報(bào)文通信的功能。這種功能允許用戶在沒(méi)有移動(dòng)通信信號(hào)覆蓋的偏遠(yuǎn)山區(qū)、海洋、沙漠等地帶,通過(guò)北斗短報(bào)文終端發(fā)送和接收文本信息,進(jìn)行基本的數(shù)據(jù)通信。北斗短報(bào)文
    的頭像 發(fā)表于 05-25 10:16 ?6593次閱讀
    什么是北斗短<b class='flag-5'>報(bào)文</b>功能?如何實(shí)現(xiàn)北斗短<b class='flag-5'>報(bào)文</b>通信?