最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

RocketMQ?producer容錯(cuò)機(jī)制源碼解析

 更新時(shí)間:2023年03月17日 11:34:02   作者:hsfxuebao  
這篇文章主要為大家介紹了RocketMQ?producer容錯(cuò)機(jī)制源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

1. 前言

本文主要是介紹一下RocketMQ消息生產(chǎn)者在發(fā)送消息的時(shí)候發(fā)送失敗的問(wèn)題處理?這里有兩個(gè)點(diǎn),一個(gè)是關(guān)于消息的處理,一個(gè)是關(guān)于broker的處理,比如說(shuō)發(fā)送消息到broker-a的broker失敗了,我們可能下次就不想發(fā)送到這個(gè)broker-a,這就涉及到一個(gè)選擇broker的問(wèn)題,也就是選擇MessageQueue的問(wèn)題。

2. 失敗重試

其實(shí)失敗重試我們?cè)诮榻BRocketMQ消息生產(chǎn)者發(fā)送消息的時(shí)候介紹過(guò)了,其實(shí)同步發(fā)送與異步發(fā)送都會(huì)失敗重試的,比如說(shuō)我發(fā)送一個(gè)消息,然后超時(shí)了,這時(shí)候在MQProducer層就會(huì)進(jìn)行控制重試,默認(rèn)是重試2次的,加上你發(fā)送那次,一共是發(fā)送3次,如果重試完還是有問(wèn)題的話(huà),這個(gè)時(shí)候就會(huì)拋出異常了。

我們來(lái)看下這一塊的代碼實(shí)現(xiàn)( DefaultMQProducerImpl 類(lèi)sendDefaultImpl方法):

這塊其實(shí)就是用for循環(huán)實(shí)現(xiàn)的,其實(shí)不光RocketMQ,分布式遠(yuǎn)程調(diào)用框架Dubbo的失敗重試也是用for循環(huán)實(shí)現(xiàn)的。

3. 延遲故障

我們都知道,在RocketMQ中一個(gè)topic其實(shí)是有多個(gè)MessageQueue這么一個(gè)概念的,然后這些MessageQueue可能對(duì)應(yīng)著不同的broker name,比如說(shuō)id是0和1的MessageQueue 對(duì)應(yīng)的broker name是 broker-a ,然后id是2和3的MessageQueue對(duì)應(yīng)的broker name 是broker-b

我們發(fā)送消息的時(shí)候,其實(shí)涉及到發(fā)送給哪個(gè)MessageQueue這么一個(gè)問(wèn)題,當(dāng)然我們可以在發(fā)送消息的時(shí)候指定這個(gè)MessageQueue,如果你不指定的話(huà),RocketMQ就會(huì)根據(jù)MQFaultStrategy 這么一個(gè)策略類(lèi)給選擇出來(lái)一個(gè)MessageQueue。

我們先來(lái)看下是在哪里選擇的,其實(shí)就是在我們重試的循環(huán)中: org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl#sendDefaultImpl

...
// 重試發(fā)送
for (; times < timesTotal; times++) {
    String lastBrokerName = null == mq ? null : mq.getBrokerName();
    // todo 選擇message queue
    MessageQueue mqSelected = this.selectOneMessageQueue(topicPublishInfo, lastBrokerName);
    ...

我們可以看到,它會(huì)把topicPublishInfo 與 lastBrokerName 作為參數(shù)傳進(jìn)去,topicPublishInfo 里面其實(shí)就是那一堆MessageQueue, 然后這個(gè)lastBrokerName 是上次我們選擇的那個(gè)broker name , 這個(gè)接著我們來(lái)看下這個(gè)selectOneMessageQueue實(shí)現(xiàn):

public MessageQueue selectOneMessageQueue(final TopicPublishInfo tpInfo, final String lastBrokerName) {
    // todo
    return this.mqFaultStrategy.selectOneMessageQueue(tpInfo, lastBrokerName);
}

可以看到它調(diào)用了MQFaultStrategy 這個(gè)類(lèi)的selectOneMessageQueue 方法,我們接著進(jìn)去:

public MessageQueue selectOneMessageQueue(final TopicPublishInfo tpInfo, final String lastBrokerName) {
    // 發(fā)送延遲故障啟用,默認(rèn)為false
    if (this.sendLatencyFaultEnable) {
        try {
            // 獲取一個(gè)index
            int index = tpInfo.getSendWhichQueue().getAndIncrement();
            for (int i = 0; i < tpInfo.getMessageQueueList().size(); i++) {
                int pos = Math.abs(index++) % tpInfo.getMessageQueueList().size();
                if (pos < 0)
                    pos = 0;
                MessageQueue mq = tpInfo.getMessageQueueList().get(pos);
                // 選取的這個(gè)broker是可用的 直接返回
                if (latencyFaultTolerance.isAvailable(mq.getBrokerName()))
                    return mq;
            }
            // 到這里 找了一圈 還是沒(méi)有找到可用的broker
            // todo 選擇 距離可用時(shí)間最近的
            final String notBestBroker = latencyFaultTolerance.pickOneAtLeast();
            int writeQueueNums = tpInfo.getQueueIdByBroker(notBestBroker);
            if (writeQueueNums > 0) {
                final MessageQueue mq = tpInfo.selectOneMessageQueue();
                if (notBestBroker != null) {
                    mq.setBrokerName(notBestBroker);
                    mq.setQueueId(tpInfo.getSendWhichQueue().getAndIncrement() % writeQueueNums);
                }
                return mq;
            } else {
                latencyFaultTolerance.remove(notBestBroker);
            }
        } catch (Exception e) {
            log.error("Error occurred when selecting message queue", e);
        }
        return tpInfo.selectOneMessageQueue();
    }
    // todo
    return tpInfo.selectOneMessageQueue(lastBrokerName);
}

這種延遲故障策略其實(shí)是由sendLatencyFaultEnable來(lái)控制的,它默認(rèn)是關(guān)閉的。

3.1 最普通的選擇策略

我們先來(lái)看下最普通的選擇策略,可以看到調(diào)用了TopicPublishInfo 的selectOneMessageQueue方法:

public MessageQueue selectOneMessageQueue(final String lastBrokerName) {
    // 消息第一個(gè)發(fā)送的時(shí)候 還沒(méi)有重試 也沒(méi)有上一個(gè)brokerName
    if (lastBrokerName == null) {
        return selectOneMessageQueue();
    } else {
        // 這個(gè) 出現(xiàn)在重試的時(shí)候
        for (int i = 0; i < this.messageQueueList.size(); i++) {
            int index = this.sendWhichQueue.getAndIncrement();
            int pos = Math.abs(index) % this.messageQueueList.size();
            if (pos < 0)
                pos = 0;
            MessageQueue mq = this.messageQueueList.get(pos);
            // 避開(kāi) 上次發(fā)送的brokerName
            if (!mq.getBrokerName().equals(lastBrokerName)) {
                return mq;
            }
        }
        // todo 到最后 沒(méi)有避開(kāi)  只能隨機(jī)選一個(gè)
        return selectOneMessageQueue();
    }
}

它這里里面分成了2部分,一個(gè)是沒(méi)有 這個(gè)lastBroker的,也就是這個(gè)這個(gè)消息還沒(méi)有被重試過(guò),這是第一次發(fā)送這個(gè)消息,這個(gè)時(shí)候它的lastBrokerName就是null,然后他就會(huì)直接走selectOneMessageQueue 這個(gè)無(wú)參方法。

public MessageQueue selectOneMessageQueue() {
    // 相當(dāng)于 某個(gè)線(xiàn)程輪詢(xún)
    int index = this.sendWhichQueue.getAndIncrement();
    int pos = Math.abs(index) % this.messageQueueList.size();
    if (pos < 0)
        pos = 0;
    return this.messageQueueList.get(pos);
}

先是獲取這個(gè)index ,然后使用index % MessageQueue集合的大小獲得一個(gè)MessageQueue集合值的一個(gè)下標(biāo)(索引),這個(gè)index 其實(shí)某個(gè)線(xiàn)程內(nèi)自增1的,這樣就形成了某個(gè)線(xiàn)程內(nèi)輪詢(xún)的效果。這個(gè)樣子的話(huà),同步發(fā)送其實(shí)就是單線(xiàn)程的輪詢(xún),異步發(fā)送就是多個(gè)線(xiàn)程并發(fā)發(fā)送,然后某個(gè)線(xiàn)程內(nèi)輪詢(xún),我們看下他這個(gè)單個(gè)線(xiàn)程自增1效果是怎樣實(shí)現(xiàn)的。

public class ThreadLocalIndex {
    private final ThreadLocal<Integer> threadLocalIndex = new ThreadLocal<Integer>();
    private final Random random = new Random();
    public int getAndIncrement() {
        Integer index = this.threadLocalIndex.get();
        // 如果不存在就創(chuàng)建  然后設(shè)置到threadLocalIndex中
        if (null == index) {
            index = Math.abs(random.nextInt());
            this.threadLocalIndex.set(index);
        }
        index = Math.abs(index + 1);
        this.threadLocalIndex.set(index);
        return index;
    }
}

可以看到這個(gè)sendWhichQueue 是用ThreadLocal實(shí)現(xiàn)的,然后這個(gè)樣子就可以一個(gè)線(xiàn)程一個(gè)index,而且不會(huì)出現(xiàn)線(xiàn)程安全問(wèn)題。

好了這里我們就把這個(gè)消息第一次發(fā)送時(shí)候MessageQueue看完了,然后我們?cè)賮?lái)看下它其他重試的時(shí)候是怎樣選擇的,也就是lastBrokerName不是null的時(shí)候:

public MessageQueue selectOneMessageQueue(final String lastBrokerName) {
    // 消息第一個(gè)發(fā)送的時(shí)候 還沒(méi)有重試 也沒(méi)有上一個(gè)brokerName
    if (lastBrokerName == null) {
        return selectOneMessageQueue();
    } else {
        // 這個(gè) 出現(xiàn)在重試的時(shí)候
        for (int i = 0; i < this.messageQueueList.size(); i++) {
            int index = this.sendWhichQueue.getAndIncrement();
            int pos = Math.abs(index) % this.messageQueueList.size();
            if (pos < 0)
                pos = 0;
            MessageQueue mq = this.messageQueueList.get(pos);
            // 避開(kāi) 上次發(fā)送的brokerName
            if (!mq.getBrokerName().equals(lastBrokerName)) {
                return mq;
            }
        }
        // todo 到最后 沒(méi)有避開(kāi)  只能隨機(jī)選一個(gè)
        return selectOneMessageQueue();
    }
}

這里其實(shí)就是選擇一個(gè)不是lastBrokerName 的MessageQueue,可以看到它是循環(huán) MessageQueue 集合大小數(shù)個(gè),這樣可能把所有的MessageQueue都看一遍,注意 這個(gè)循環(huán)只是起到選多少次的作用,具體的選擇還是要走某線(xiàn)程輪詢(xún)的那一套,到最后是在是選不出來(lái)了,也就是沒(méi)有這一堆MessageQueue都是在lastBrokerName上的,只能調(diào)用selectOneMessageQueue輪詢(xún)選一個(gè)了。

到這我們就把最普通的選擇一個(gè)MessageQueue介紹完了。

3.2 延遲故障的實(shí)現(xiàn)

下面我們?cè)賮?lái)介紹下那個(gè)延遲故障的實(shí)現(xiàn),這個(gè)其實(shí)就是根據(jù)你這個(gè)broker 的響應(yīng)延遲時(shí)間的大小,來(lái)影響下次選擇這個(gè)broker的權(quán)重,他不是絕對(duì)的,因?yàn)楦鶕?jù)它這個(gè)規(guī)則是在找不出來(lái)的話(huà),他就會(huì)使用那套普通選擇算法來(lái)找個(gè)MessageQueue。

它是這樣一個(gè)原理:

  • 在每次發(fā)送之后都收集一下它這次的一個(gè)響應(yīng)延遲,比如我10點(diǎn)1分1秒200毫秒給broker-a了一個(gè)消息,然后到了10點(diǎn)1分1秒900毫秒的時(shí)候才收到broker-a 的一個(gè)sendResult也就是響應(yīng),這個(gè)時(shí)候他就是700ms的延遲,它會(huì)跟你就這個(gè)300ms的延遲找到一個(gè)時(shí)間范圍,他就認(rèn)為你這個(gè)broker-a 這個(gè)broker 在某個(gè)時(shí)間段內(nèi),比如說(shuō)30s內(nèi)是不可用的。然后下次選擇的時(shí)候,他在第一輪會(huì)找那些可用的broker,找不到的話(huà),就找那些上次不是這個(gè)broker的,還是找不到的話(huà),他就絕望了,用最普通的方式,也就是上面說(shuō)的那種輪詢(xún)算法找一個(gè)MessageQueue出來(lái)。

接下來(lái)我們先來(lái)看下它的收集延遲的部分,是這個(gè)樣子的,還是在這個(gè)失敗重試?yán)锩?,然后它?huì)在響應(yīng)后或者異常后面都加一行代碼來(lái)收集這些延遲:

...
// todo 進(jìn)行發(fā)送
sendResult = this.sendKernelImpl(msg, mq, communicationMode, sendCallback, topicPublishInfo, timeout - costTime);
endTimestamp = System.currentTimeMillis();
// todo isolation 參數(shù)為false(看一下異常情況)
this.updateFaultItem(mq.getBrokerName(), endTimestamp - beginTimestampPrev, false);
...

這是正常響應(yīng)后的,注意它的isolation 參數(shù),也就是隔離 是false,在看下異常的

...
catch (RemotingException e) {
    endTimestamp = System.currentTimeMillis();
    this.updateFaultItem(mq.getBrokerName(), endTimestamp - beginTimestampPrev, true);
    log.warn(String.format("sendKernelImpl exception, resend at once, InvokeID: %s, RT: %sms, Broker: %s", invokeID, endTimestamp - beginTimestampPrev, mq), e);
    log.warn(msg.toString());
    exception = e;
    continue;
}
...

他這個(gè)isolation 參數(shù)就是true ,也就是需要隔離的意思。

public void updateFaultItem(final String brokerName, final long currentLatency, boolean isolation) {
    // todo
    this.mqFaultStrategy.updateFaultItem(brokerName, currentLatency, isolation);
}

可以看到是調(diào)用了mqFaultStrategy 的updateFaultItem 方法:

public void updateFaultItem(final String brokerName, final long currentLatency, boolean isolation) {
    // 是否開(kāi)啟延遲故障容錯(cuò)
    if (this.sendLatencyFaultEnable) {
        // todo 計(jì)算不可用持續(xù)時(shí)間
        long duration = computeNotAvailableDuration(isolation ? 30000 : currentLatency);
        // todo 存儲(chǔ)
        this.latencyFaultTolerance.updateFaultItem(brokerName, currentLatency, duration);
    }
}

先是判斷是否開(kāi)啟了這個(gè)延遲故障的這么一個(gè)配置,默認(rèn)是不啟動(dòng)的,但是你可以自己?jiǎn)?dòng)set下就可以了setSendLatencyFaultEnable(true)

DefaultMQProducer producer = new DefaultMQProducer("please_rename_unique_group_name");
producer.setNamesrvAddr("127.0.0.1:9876");
producer.setSendLatencyFaultEnable(true);

首先是計(jì)算這個(gè)它認(rèn)為broker不可用的這么一個(gè)時(shí)間,參數(shù)就是你那個(gè)響應(yīng)延遲,熔斷的話(huà)就配置30000毫秒, 否則的話(huà)就是正常的那個(gè)響應(yīng)時(shí)間

/**
 * 計(jì)算不可用持續(xù)時(shí)間
 * @param currentLatency 當(dāng)前延遲
 */
private long computeNotAvailableDuration(final long currentLatency) {
    // latencyMax = {50L, 100L, 550L, 1000L, 2000L, 3000L, 15000L};
    // notAvailableDuration = {0L, 0L, 30000L, 60000L, 120000L, 180000L, 600000L};
    // 倒著遍歷
    for (int i = latencyMax.length - 1; i >= 0; i--) {
        // 如果延遲大于某個(gè)時(shí)間,就返回對(duì)應(yīng)服務(wù)不可用時(shí)間,可以看出來(lái),響應(yīng)延遲100ms以下是沒(méi)有問(wèn)題的
        if (currentLatency >= latencyMax[i])
            return this.notAvailableDuration[i];
    }
    return 0;
}

他這個(gè)計(jì)算規(guī)則是這個(gè)樣子的,他有兩個(gè)數(shù)組,一個(gè)是響應(yīng)延遲的,一個(gè)是不可使用的時(shí)間,兩個(gè)排列都是從小到大的順序,倒著先找響應(yīng)延遲,如果你這個(gè)延遲大于某個(gè)時(shí)間,就找對(duì)應(yīng)下標(biāo)的不可使用的時(shí)間,比如說(shuō)響應(yīng)延遲700ms,這時(shí)候他就會(huì)找到30000ms不可使用時(shí)間。

計(jì)算完這個(gè)不可使用時(shí)間后接著調(diào)用了latencyFaultTolerance的updateFaultItem方法,這個(gè)方法其實(shí)就是用來(lái)存儲(chǔ)的:

public void updateFaultItem(final String name, final long currentLatency, final long notAvailableDuration) {
    // 從緩存中獲取
    FaultItem old = this.faultItemTable.get(name);
    // 緩存沒(méi)有的情況
    if (null == old) {
        final FaultItem faultItem = new FaultItem(name);
        // 設(shè)置延遲
        faultItem.setCurrentLatency(currentLatency);
        // 設(shè)置啟用時(shí)間
        faultItem.setStartTimestamp(System.currentTimeMillis() + notAvailableDuration);
        // 設(shè)置faultItemTable 中
        old = this.faultItemTable.putIfAbsent(name, faultItem);
        // 如果已經(jīng)有了,拿到 老的進(jìn)行更新
        if (old != null) {
            old.setCurrentLatency(currentLatency);
            old.setStartTimestamp(System.currentTimeMillis() + notAvailableDuration);
        }
    } else {
        // 緩存中已經(jīng)有了,直接拿老的進(jìn)行更新
        old.setCurrentLatency(currentLatency);
        old.setStartTimestamp(System.currentTimeMillis() + notAvailableDuration);
    }
}

他有個(gè)faultItemTable 這個(gè)緩存,記錄著 每個(gè)broker的FaultItem的項(xiàng),這個(gè)FaultItem就是保存它能夠使用的一個(gè)時(shí)間(當(dāng)前時(shí)間戳+不可使用時(shí)間),其實(shí)這個(gè)方法就是做更新或者插入操作。

好了到這我們就把它這個(gè)收集響應(yīng)延遲指標(biāo)與計(jì)算可用時(shí)間這快就解析完了,再回頭看下那個(gè)選擇MessageQueue的方法:

可以看到它先是找那種可用的,然后不是上一個(gè)broker的那個(gè),如果好幾輪下來(lái)沒(méi)有找到的話(huà)就選擇一個(gè)

public String pickOneAtLeast() {
    // 將map中里面的放到tmpList 中
    final Enumeration<FaultItem> elements = this.faultItemTable.elements();
    List<FaultItem> tmpList = new LinkedList<FaultItem>();
    while (elements.hasMoreElements()) {
        final FaultItem faultItem = elements.nextElement();
        tmpList.add(faultItem);
    }
    // 如果不是null
    if (!tmpList.isEmpty()) {
        // 洗牌算法
        Collections.shuffle(tmpList);
        // 排序
        Collections.sort(tmpList);
        final int half = tmpList.size() / 2;
        // 沒(méi)有 2臺(tái)機(jī)器
        if (half <= 0) {
            // 選擇第一個(gè)
            return tmpList.get(0).getName();
        } else {
            // 有2臺(tái)機(jī)器及以上,某個(gè)線(xiàn)程內(nèi)隨機(jī)選排在前半段的broker
            final int i = this.whichItemWorst.getAndIncrement() % half;
            return tmpList.get(i).getName();
        }
    }
    return null;
}

先是排序,然后將所有的broker/2 ,如果是小于等于0的話(huà),說(shuō)明就2個(gè)broker以下,選第一個(gè),如果是2臺(tái)以上,就輪詢(xún)選一個(gè)

先來(lái)看下排序規(guī)則:

/**
 * 失敗條目(規(guī)避規(guī)則條目)
 */
class FaultItem implements Comparable<FaultItem> {
    // 條目唯一鍵,這里是brokerName
    private final String name;
    // todo currentLatency 和startTimestamp  被volatile修飾
    // 本次消息發(fā)送的延遲時(shí)間
    private volatile long currentLatency;
    // 故障規(guī)避的開(kāi)始時(shí)間
    private volatile long startTimestamp;
    public FaultItem(final String name) {
        this.name = name;
    }
    @Override
    public int compareTo(final FaultItem other) {
        // 將能提供服務(wù)的放前面
        if (this.isAvailable() != other.isAvailable()) {
            if (this.isAvailable())
                return -1;
            if (other.isAvailable())
                return 1;
        }
        // 找延遲低的 放前面
        if (this.currentLatency < other.currentLatency)
            return -1;
        else if (this.currentLatency > other.currentLatency) {
            return 1;
        }
        // 找最近能提供服務(wù)的  放前面
        if (this.startTimestamp < other.startTimestamp)
            return -1;
        else if (this.startTimestamp > other.startTimestamp) {
            return 1;
        }
        return 0;
    }

它是把能提供服務(wù)的放前面,然后沒(méi)有,就找那種延遲低的放前面,也沒(méi)有的話(huà)就找最近能提供服務(wù)的放前頭。 找到這個(gè)broker 之后然后根據(jù)這個(gè)broker name 獲取寫(xiě)隊(duì)列的個(gè)數(shù),其實(shí)你這個(gè)寫(xiě)隊(duì)列個(gè)數(shù)有幾個(gè),然后你這個(gè)broker對(duì)應(yīng)的MessageQueue就有幾個(gè),如果write size >0的話(huà),然后這個(gè)broker 不是null,就找一個(gè)mq,然后設(shè)置上它的broker name 與queue id

如果write<=0,直接移除這個(gè)broker對(duì)應(yīng)FaultItem,最后實(shí)在是找不到就按照上面那種普通方法來(lái)找了。

好了,到這我們延遲故障也介紹完成了。

參考文章

RocketMQ4.8注釋github地址

RocketMQ源碼分析專(zhuān)欄

以上就是RocketMQ producer容錯(cuò)機(jī)制源碼解析的詳細(xì)內(nèi)容,更多關(guān)于RocketMQ producer容錯(cuò)機(jī)制的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • mybatis-plus分頁(yè)查詢(xún)?nèi)N方法小結(jié)

    mybatis-plus分頁(yè)查詢(xún)?nèi)N方法小結(jié)

    本文主要介紹了mybatis-plus分頁(yè)查詢(xún)?nèi)N方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-05-05
  • SpringBoot ThreadLocal實(shí)現(xiàn)公共字段自動(dòng)填充案例講解

    SpringBoot ThreadLocal實(shí)現(xiàn)公共字段自動(dòng)填充案例講解

    每一次在Controller層中封裝改動(dòng)數(shù)據(jù)的方法時(shí)都要重新設(shè)置一些共性字段,顯得十分冗余。為了解決此問(wèn)題也是在項(xiàng)目中第一次利用到線(xiàn)程,總的來(lái)說(shuō)還是讓我眼前一亮,也開(kāi)闊了視野,對(duì)以后的開(kāi)發(fā)具有深遠(yuǎn)的意義
    2022-10-10
  • springCloud集成nacos config的過(guò)程

    springCloud集成nacos config的過(guò)程

    本文介紹spring cloud集成nacos config的過(guò)程,通過(guò)實(shí)例代碼圖文相結(jié)合給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧
    2024-08-08
  • SpringCloud使用Feign文件上傳、下載

    SpringCloud使用Feign文件上傳、下載

    這篇文章主要為大家詳細(xì)介紹了SpringCloud使用Feign文件上傳、下載功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-04-04
  • 簡(jiǎn)單了解JAVA內(nèi)存泄漏和溢出區(qū)別及聯(lián)系

    簡(jiǎn)單了解JAVA內(nèi)存泄漏和溢出區(qū)別及聯(lián)系

    這篇文章主要介紹了簡(jiǎn)單了解JAVA內(nèi)存泄漏和溢出區(qū)別及聯(lián)系,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-03-03
  • 詳解Struts2中配置默認(rèn)Action的方法

    詳解Struts2中配置默認(rèn)Action的方法

    本篇文章主要介紹了詳解Struts2中配置默認(rèn)Action的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-01-01
  • SpringCloud之loadbalancer負(fù)載均衡組件實(shí)戰(zhàn)詳解

    SpringCloud之loadbalancer負(fù)載均衡組件實(shí)戰(zhàn)詳解

    LoadBalancer是Spring Cloud官方提供的負(fù)載均衡組件,可用于替代Ribbon,這篇文章主要介紹了SpringCloud之loadbalancer負(fù)載均衡組件,需要的朋友可以參考下
    2023-06-06
  • Springboot整合camunda+mysql的集成流程分析

    Springboot整合camunda+mysql的集成流程分析

    本文介紹基于mysql數(shù)據(jù)庫(kù),如何實(shí)現(xiàn)camunda與springboot的集成,如何實(shí)現(xiàn)基于springboot運(yùn)行camunda開(kāi)源流程引擎,本文分步驟圖文相結(jié)合給大家介紹的非常詳細(xì),需要的朋友參考下吧
    2021-06-06
  • Java多線(xiàn)程事務(wù)管理的實(shí)現(xiàn)

    Java多線(xiàn)程事務(wù)管理的實(shí)現(xiàn)

    本文主要介紹了Java多線(xiàn)程事務(wù)管理的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-07-07
  • java中使用map排序的實(shí)例講解

    java中使用map排序的實(shí)例講解

    在本篇文章里小編給大家整理了一篇關(guān)于java中使用map排序的實(shí)例講解內(nèi)容,有興趣的朋友們可以學(xué)習(xí)下。
    2020-12-12

最新評(píng)論

大名县| 余干县| 宜城市| 永福县| 朝阳县| 襄樊市| 南丰县| 萨嘎县| 靖州| 浦东新区| 黄骅市| 石渠县| 南投市| 稷山县| 神池县| 阿坝县| 五指山市| 新源县| 临沧市| 柯坪县| 拉萨市| 南宁市| 鹤壁市| 新泰市| 右玉县| 海晏县| 驻马店市| 鸡东县| 洛川县| 台前县| 普安县| 阳泉市| 开平市| 玉龙| 潍坊市| 曲阳县| 奉节县| 两当县| 广饶县| 邵武市| 贡觉县|