哈哈哈哈哈操欧洲电影,久草网在线,亚洲久久熟女熟妇视频,麻豆精品色,久久福利在线视频,日韩中文字幕的,淫乱毛视频一区,亚洲成人一二三,中文人妻日韩精品电影

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

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

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

本地緩存 Caffeine 中的時(shí)間輪(TimeWheel)是什么?

京東云 ? 來(lái)源:jf_75140285 ? 作者:jf_75140285 ? 2025-08-05 14:48 ? 次閱讀
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

我們?cè)敿?xì)介紹了 Caffeine 緩存添加元素和讀取元素的流程,并詳細(xì)解析了配置固定元素?cái)?shù)量驅(qū)逐策略的實(shí)現(xiàn)原理。在本文中我們將主要介紹 配置元素過(guò)期時(shí)間策略的實(shí)現(xiàn)原理,補(bǔ)全 Caffeine 對(duì)元素管理的機(jī)制。在創(chuàng)建有過(guò)期時(shí)間策略的 Caffeine 緩存時(shí),它提供了三種不同的方法,分別為 expireAfterAccess, expireAfterWrite 和 expireAfter,前兩者的元素過(guò)期機(jī)制非常簡(jiǎn)單:通過(guò)遍歷隊(duì)列中的元素(expireAfterAccess 遍歷的是窗口區(qū)、試用區(qū)和保護(hù)區(qū)隊(duì)列,expireAfterWrite 有專(zhuān)用的寫(xiě)順序隊(duì)列 WriteOrderDeque),并用當(dāng)前時(shí)間減去元素的最后訪(fǎng)問(wèn)時(shí)間(或?qū)懭霑r(shí)間)的結(jié)果值和配置的時(shí)間作對(duì)比,如果超過(guò)配置的時(shí)間,則認(rèn)為元素過(guò)期。而 expireAfter 為自定義過(guò)期策略,使用到了時(shí)間輪 TimeWheel。它的實(shí)現(xiàn)相對(duì)復(fù)雜,在源碼中相關(guān)的方法都會(huì)包含 Variable 命名(變量;可變的),如 expiresVariable。本文以如下源碼創(chuàng)建自定義過(guò)期策略的緩存來(lái)了解 Caffeine 中的 TimeWheel 機(jī)制,它創(chuàng)建的緩存類(lèi)型為 SSA,表示 Key 和 Value 均為強(qiáng)引用且配置了自定義過(guò)期策略:

public class TestReadSourceCode {
    @Test
    public void doReadTimeWheel() {
        Cache cache2 = Caffeine.newBuilder()
//                .expireAfterAccess(5, TimeUnit.SECONDS)
//                .expireAfterWrite(5, TimeUnit.SECONDS)
                .expireAfter(new Expiry() {
                    @Override
                    public long expireAfterCreate(Object key, Object value, long currentTime) {
                        // 指定過(guò)期時(shí)間為 Long.MAX_VALUE 則不會(huì)過(guò)期
                        if ("key0".equals(key)) {
                            return Long.MAX_VALUE;
                        }
                        // 設(shè)置條目在創(chuàng)建后 5 秒過(guò)期
                        return TimeUnit.SECONDS.toNanos(5);
                    }

                    // 以下兩個(gè)過(guò)期時(shí)間指定為默認(rèn) duration 不過(guò)期
                    @Override
                    public long expireAfterUpdate(Object key, Object value, long currentTime, @NonNegative long currentDuration) {
                        return currentDuration;
                    }
                    @Override
                    public long expireAfterRead(Object key, Object value, long currentTime, @NonNegative long currentDuration) {
                        return currentDuration;
                    }
                })
                .build();

        cache2.put("key2", "value2");
        System.out.println(cache2.getIfPresent("key2"));
        try {
            Thread.sleep(6000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(cache2.getIfPresent("key2"));
    }
}

在前文中我們提到過(guò),Caffeine 中 maintenance 方法負(fù)責(zé)緩存的維護(hù),包括執(zhí)行緩存的驅(qū)逐,所以我們便以這個(gè)方法作為起點(diǎn)去探究時(shí)間過(guò)期策略的執(zhí)行:

abstract class BoundedLocalCache extends BLCHeader.DrainStatusRef
        implements LocalCache {

    @GuardedBy("evictionLock")
    void maintenance(@Nullable Runnable task) {
        setDrainStatusRelease(PROCESSING_TO_IDLE);

        try {
            drainReadBuffer();

            drainWriteBuffer();
            if (task != null) {
                task.run();
            }

            drainKeyReferences();
            drainValueReferences();

            // 元素過(guò)期策略執(zhí)行
            expireEntries();
            evictEntries();

            climb();
        } finally {
            if ((drainStatusOpaque() != PROCESSING_TO_IDLE)
                    || !casDrainStatus(PROCESSING_TO_IDLE, IDLE)) {
                setDrainStatusOpaque(REQUIRED);
            }
        }
    }
}

在其中我們能發(fā)現(xiàn) expireEntries 元素過(guò)期策略的執(zhí)行方法,該方法的源碼如下所示:

abstract class BoundedLocalCache extends BLCHeader.DrainStatusRef
        implements LocalCache {

    @GuardedBy("evictionLock")
    void expireEntries() {
        // 獲取當(dāng)前時(shí)間
        long now = expirationTicker().read();
        // 基于訪(fǎng)問(wèn)和寫(xiě)后的過(guò)期策略
        expireAfterAccessEntries(now);
        expireAfterWriteEntries(now);
        // *主要關(guān)注*:執(zhí)行自定義過(guò)期策略
        expireVariableEntries(now);

        // Pacer 用于調(diào)度和執(zhí)行定時(shí)任務(wù),創(chuàng)建 Caffeine 緩存時(shí)可通過(guò) scheduler 方法來(lái)配置
        // 默認(rèn)為 null
        Pacer pacer = pacer();
        if (pacer != null) {
            long delay = getExpirationDelay(now);
            if (delay == Long.MAX_VALUE) {
                pacer.cancel();
            } else {
                pacer.schedule(executor, drainBuffersTask, now, delay);
            }
        }
    }
    
}

在自定義的過(guò)期策略中,我們便能夠發(fā)現(xiàn) TimeWheel 的身影,并調(diào)用了它的 TimeWheel#advance 方法:

abstract class BoundedLocalCache extends BLCHeader.DrainStatusRef
        implements LocalCache {

    @GuardedBy("evictionLock")
    void expireVariableEntries(long now) {
        if (expiresVariable()) {
            timerWheel().advance(this, now);
        }
    }
}

在深入 TimeWheel 的源碼前,我們先通過(guò)源碼注釋了解下它的作用。TimeWheel 是 Caffeine 緩存中定義的類(lèi),它的類(lèi)注釋如下寫(xiě)道:

一個(gè) 分層的 時(shí)間輪,能夠以O(shè)(1)的時(shí)間復(fù)雜度添加、刪除和觸發(fā)過(guò)期事件。過(guò)期事件的執(zhí)行被推遲到 maintenance 維護(hù)方法中的 TimeWheel#advance 邏輯中。

A hierarchical timer wheel to add, remove, and fire expiration events in amortized O(1) time. The expiration events are deferred until the timer is advanced, which is performed as part of the cache's maintenance cycle.

可見(jiàn)上文中我們看見(jiàn)的 TimeWheel#advance 方法是用來(lái)執(zhí)行元素過(guò)期事件的。在這段注釋中提到了 分層(hierarchical) 的概念,注釋中還有一段內(nèi)容對(duì)這個(gè)特性進(jìn)行了描述:

時(shí)間輪將計(jì)時(shí)器事件存儲(chǔ)在循環(huán)緩沖區(qū)的桶中。Bucket 表示粗略的時(shí)間跨度,例如一分鐘,并使用一個(gè)雙向鏈表來(lái)記錄事件。時(shí)間輪按層次結(jié)構(gòu)(秒、分鐘、小時(shí)、天)構(gòu)建,這樣當(dāng)時(shí)間輪旋轉(zhuǎn)時(shí),在遙遠(yuǎn)的未來(lái)安排的事件會(huì)級(jí)聯(lián)到較低的桶中。它允許在O(1)的時(shí)間復(fù)雜度下添加、刪除和過(guò)期事件,在整個(gè) Bucket 中的元素都會(huì)發(fā)生過(guò)期,同時(shí)有大部分元素過(guò)期的特殊情況由時(shí)間輪的輪換分?jǐn)偂?/p>

A timer wheel stores timer events in buckets on a circular buffer. A bucket represents a coarse time span, e.g. one minute, and holds a doubly-linked list of events. The wheels are structured in a hierarchy (seconds, minutes, hours, days) so that events scheduled in the distant future are cascaded to lower buckets when the wheels rotate. This allows for events to be added, removed, and expired in O(1) time, where expiration occurs for the entire bucket, and the penalty of cascading is amortized by the rotations.

大概能夠推測(cè),它的“分層”概念指的是根據(jù)事件的過(guò)期時(shí)間將這些事件放在不同的“層”去管理,秒級(jí)別的在一層,分鐘級(jí)別的在一層等等。那么接下來(lái)我們根據(jù)它的注釋內(nèi)容,詳細(xì)探究一下 TimeWheel 是如何實(shí)現(xiàn)這種機(jī)制的。

constructor

我們先來(lái)看它的構(gòu)造方法:

final class TimerWheel implements Iterable> {
    // 定義了 5 個(gè)桶,每個(gè)桶的容量分別為 64、64、32、4、1
    static final int[] BUCKETS = {64, 64, 32, 4, 1};
    final Node[][] wheel;

    TimerWheel() {
        wheel = new Node[BUCKETS.length][];
        for (int i = 0; i < wheel.length; i++) {
            wheel[i] = new Node[BUCKETS[i]];
            for (int j = 0; j < wheel[i].length; j++) {
                wheel[i][j] = new Sentinel();
            }
        }
    }

    // 定義 Sentinel 內(nèi)部類(lèi),創(chuàng)建對(duì)象表示雙向鏈表的哨兵節(jié)點(diǎn)
    static final class Sentinel extends Node {
        Node prev;
        Node next;
        
        Sentinel() {
            prev = next = this;
        }
    }
}

它會(huì)創(chuàng)建如下所示的二維數(shù)組,每行都作為一個(gè)桶(Bucket),根據(jù) BUCKETS 數(shù)組中定義的容量,每行桶的容量分別為 64、64、32、4、1,每個(gè)桶中的初始元素是創(chuàng)建 Sentinel 為節(jié)點(diǎn)的雙向鏈表,如下所示(其中...表示圖中省略了31個(gè)桶):

wKgZO2iRqSuAK2NzAA2_QYM1fLY310.png

它為什么會(huì)創(chuàng)建一個(gè)內(nèi)部類(lèi) Sentinel 并將其作為桶中的初始元素呢?在《算法導(dǎo)論》中講解鏈表的章節(jié)提到過(guò)這個(gè)方法:雙向鏈表中沒(méi)有元素時(shí)也不為 null,而是創(chuàng)建一個(gè)哨兵節(jié)點(diǎn)(Sentinel),它不存儲(chǔ)任何數(shù)據(jù),只是為了方便鏈表的操作,減少代碼中的判空邏輯,而在此處將其命名為 Sentinel,表示采用這個(gè)方法,又同時(shí)提高了代碼的可讀性。

schedule

現(xiàn)在它的數(shù)據(jù)結(jié)構(gòu)我們已經(jīng)有了基本的了解,那么究竟什么時(shí)候會(huì)向其中添加元素呢?在前文中我們提到過(guò),向 Caffeine 緩存中 put 元素時(shí)會(huì)注冊(cè) AddTask 任務(wù),任務(wù)中有一段邏輯會(huì)調(diào)用 TimeWheel#schedule 方法向其中添加元素:

final class AddTask implements Runnable {
    @Override
    @GuardedBy("evictionLock")
    @SuppressWarnings("FutureReturnValueIgnored")
    public void run() {
        // ...
        if (isAlive) {
            // ...
            // 如果自定義時(shí)間策略,則執(zhí)行 schedule 方法
            if (expiresVariable()) {
                // node 為添加的節(jié)點(diǎn)
                timerWheel().schedule(node);
            }
        }
        // ...
    }
    
}

我們來(lái)看看 schedule 方法,根據(jù)的它的入?yún)⒖梢园l(fā)現(xiàn)上文注釋中提到的“過(guò)期事件”便是 Node 節(jié)點(diǎn)本身:

final class TimerWheel implements Iterable> {

    static final long[] SPANS = {
            // 1073741824L 2^30 1.07s
            ceilingPowerOfTwo(TimeUnit.SECONDS.toNanos(1)),
            // 68719476736L 2^36 1.14m
            ceilingPowerOfTwo(TimeUnit.MINUTES.toNanos(1)),
            // 36028797018963968L 2^45 1.22h
            ceilingPowerOfTwo(TimeUnit.HOURS.toNanos(1)),
            // 864691128455135232L 2^50 1.63d
            ceilingPowerOfTwo(TimeUnit.DAYS.toNanos(1)),
            // 5629499534213120000L 2^52 6.5d
            BUCKETS[3] * ceilingPowerOfTwo(TimeUnit.DAYS.toNanos(1)),
            BUCKETS[3] * ceilingPowerOfTwo(TimeUnit.DAYS.toNanos(1)),
    };
    // Long.numberOfTrailingZeros 表示尾隨 0 的數(shù)量
    static final long[] SHIFT = {
            // 30
            Long.numberOfTrailingZeros(SPANS[0]),
            // 36
            Long.numberOfTrailingZeros(SPANS[1]),
            // 45
            Long.numberOfTrailingZeros(SPANS[2]),
            // 50
            Long.numberOfTrailingZeros(SPANS[3]),
            // 52
            Long.numberOfTrailingZeros(SPANS[4]),
    };

    final Node[][] wheel;

    long nanos;
    
    public void schedule(Node node) {
        // 在 wheel 中找到對(duì)應(yīng)的桶,node.getVariableTime() 獲取的是元素的“過(guò)期時(shí)間”
        Node sentinel = findBucket(node.getVariableTime());
        // 將該節(jié)點(diǎn)添加到桶中
        link(sentinel, node);
    }

    // 初次添加時(shí),time 為元素的“過(guò)期時(shí)間”
    Node findBucket(long time) {
        // 計(jì)算 duration 持續(xù)時(shí)間(有效期)
        long duration = time - nanos;
        // length 為 4
        int length = wheel.length - 1;
        for (int i = 0; i < length; i++) {
            // 注意這里它是將持續(xù)時(shí)間和 SPANS[i + 1] 作比較,如果小于則認(rèn)為該節(jié)點(diǎn)在這個(gè)“層級(jí)”中
            if (duration < SPANS[i + 1]) {
                // tick 指鐘表的滴答聲,用于表示它所在層級(jí)的偏移量,舉個(gè)例子:
                // 如果 duration < SPANS[1] 表示秒級(jí)別的時(shí)間跨度,則右移 SHIFT[0] 30 位,SPANS[0] 為 2^30 對(duì)應(yīng) 1.07s,右移 30 位足以將 time 表示為秒級(jí)別的時(shí)間跨度
                long ticks = (time >>> SHIFT[i]);
                // 2進(jìn)制數(shù)-1 的位與運(yùn)算計(jì)算出節(jié)點(diǎn)在該層級(jí)的實(shí)際索引位置
                int index = (int) (ticks & (wheel[i].length - 1));
                return wheel[i][index];
            }
        }
        // 如果遍歷完所有層級(jí)都沒(méi)有合適的,那么將其放在最后一層
        return wheel[length][0];
    }

    // 雙向鏈表的尾插法添加元素,有了 Sentinel 哨兵節(jié)點(diǎn)避免了代碼中的判空邏輯
    void link(Node sentinel, Node node) {
        node.setPreviousInVariableOrder(sentinel.getPreviousInVariableOrder());
        node.setNextInVariableOrder(sentinel);

        sentinel.getPreviousInVariableOrder().setNextInVariableOrder(node);
        sentinel.setPreviousInVariableOrder(node);
    }
}

其中 node.getVariableTime() 為元素的過(guò)期時(shí)間,那么這個(gè)過(guò)期時(shí)間是何時(shí)計(jì)算的呢?在 put 方法中有如下邏輯:

abstract class BoundedLocalCache extends BLCHeader.DrainStatusRef
        implements LocalCache {
    @Nullable
    V put(K key, V value, Expiry expiry, boolean onlyIfAbsent) {
        requireNonNull(key);
        requireNonNull(value);

        Node node = null;
        long now = expirationTicker().read();
        int newWeight = weigher.weigh(key, value);
        Object lookupKey = nodeFactory.newLookupKey(key);
        for (int attempts = 1; ; attempts++) {
            Node prior = data.get(lookupKey);
            if (prior == null) {
                if (node == null) {
                    node = nodeFactory.newNode(key, keyReferenceQueue(),
                            value, valueReferenceQueue(), newWeight, now);
                    // 計(jì)算過(guò)期時(shí)間并為 variableTime 賦值
                    setVariableTime(node, expireAfterCreate(key, value, expiry, now));
                }
            }
            // ...
        }
        // ...
    }

    long expireAfterCreate(@Nullable K key, @Nullable V value,
                           Expiry expiry, long now) {
        if (expiresVariable() && (key != null) && (value != null)) {
            // 此處的 expireAfterCreate 便為自定義的有效期
            long duration = expiry.expireAfterCreate(key, value, now);
            // 將定義的有效期在當(dāng)前操作時(shí)間上進(jìn)行累加得出過(guò)期時(shí)間
            return isAsync ? (now + duration) : (now + Math.min(duration, MAXIMUM_EXPIRY));
        }
        return 0L;
    }
}

可以發(fā)現(xiàn)在元素被添加時(shí)它的過(guò)期時(shí)間已經(jīng)被計(jì)算好了。接著回到 findBucket 方法,其中有邏輯 duration < SPANS[i + 1] 確定節(jié)點(diǎn)所在時(shí)間輪的層級(jí)。SPANS 數(shù)組中存儲(chǔ)的是層級(jí)的“時(shí)間跨度邊界”,如注釋中標(biāo)記的,SPANS[0] 表示第一層級(jí)的時(shí)間為 1.07s,SPANS[1] 表示第二層級(jí)的時(shí)間為 1.14m,for 循環(huán)開(kāi)始時(shí)便以 SPANS[1] 作為比較,如果 duration 小于 SPANS[1],那么將該節(jié)點(diǎn)放在第一層級(jí),那么第一層級(jí)的時(shí)間范圍為 t < 1.14 min,為秒級(jí)的時(shí)間跨度;第二層級(jí)的時(shí)間范圍為 1.14 min <= t < 1.22 h,為分鐘級(jí)的時(shí)間跨度,接下來(lái)的時(shí)間跨度以此類(lèi)推。在這里也明白了為什么 final Node[][] wheel 數(shù)據(jù)結(jié)構(gòu)會(huì)將第一行、第二行元素大小設(shè)定為 64,因?yàn)榈谝恍袨槊爰?jí)別的時(shí)間跨度,60s 即為 1min,那么在這個(gè)秒級(jí)別的跨度下 64 的容量足以,同理,第二行為分鐘級(jí)別的時(shí)間跨度,60min 即為 1h,那么在這個(gè)分鐘級(jí)別的跨度下 64 的容量也足夠了。

現(xiàn)在我們已經(jīng)了解了向 TimeWheel 中添加元素的邏輯,那么現(xiàn)在我們可以回到文章開(kāi)頭提到的 maintenance 方法中調(diào)用的 TimeWheel#advance 方法了。

advance

advance 有推進(jìn)、前進(jìn)的意思,我認(rèn)為在 TimeWheel 中,advance 用來(lái)表示“某個(gè)層級(jí)的時(shí)間有沒(méi)有流動(dòng)”會(huì)更合適,以下是它的源碼:

final class TimerWheel implements Iterable> {

    static final long[] SHIFT = {
            Long.numberOfTrailingZeros(SPANS[0]),
            Long.numberOfTrailingZeros(SPANS[1]),
            Long.numberOfTrailingZeros(SPANS[2]),
            Long.numberOfTrailingZeros(SPANS[3]),
            Long.numberOfTrailingZeros(SPANS[4]),
    };
    
    long nanos;
    
    public void advance(BoundedLocalCache cache, long currentTimeNanos) {
        // nanos 總是記錄 advance 方法被調(diào)用時(shí)的時(shí)間
        long previousTimeNanos = nanos;
        nanos = currentTimeNanos;

        // 校正時(shí)間戳的溢出
        if ((previousTimeNanos < 0) && (currentTimeNanos > 0)) {
            previousTimeNanos += Long.MAX_VALUE;
            currentTimeNanos += Long.MAX_VALUE;
        }

        try {
            // 遍歷所有層級(jí),如果當(dāng)前層級(jí)的時(shí)間沒(méi)有流動(dòng),則結(jié)束循環(huán)
            // 因?yàn)樗械膶蛹?jí)是按照時(shí)間遞增的順序排列的,如果低層級(jí)的時(shí)間都沒(méi)有流動(dòng),那么證明更高的層級(jí)時(shí)間更沒(méi)有流動(dòng)了,比如秒級(jí)別時(shí)間沒(méi)變,那么分鐘級(jí)別的時(shí)間更不可能變
            for (int i = 0; i < SHIFT.length; i++) {
                long previousTicks = (previousTimeNanos >>> SHIFT[i]);
                long currentTicks = (currentTimeNanos >>> SHIFT[i]);
                // 計(jì)算出某層級(jí)下時(shí)間的流動(dòng)范圍
                long delta = (currentTicks - previousTicks);
                if (delta <= 0L) {
                    break;
                }
                // 如果當(dāng)前層級(jí)的時(shí)間有流動(dòng),則調(diào)用 expire 方法
                expire(cache, i, previousTicks, delta);
            }
        } catch (Throwable t) {
            nanos = previousTimeNanos;
            throw t;
        }
    }
}

我們接著看 expire 方法:

final class TimerWheel implements Iterable> {

    final Node[][] wheel;

    long nanos;
    
    void expire(BoundedLocalCache cache, int index, long previousTicks, long delta) {
        Node[] timerWheel = wheel[index];
        int mask = timerWheel.length - 1;
        
        // 計(jì)算要遍歷處理的槽位數(shù)量,假設(shè) delta 不會(huì)出現(xiàn)負(fù)值(只有時(shí)間范圍超過(guò) 2^61 nanoseconds (73 years) 才會(huì)溢出)
        int steps = Math.min(1 + (int) delta, timerWheel.length);
        // 上一次 advance 的操作時(shí)間即為起始索引
        int start = (int) (previousTicks & mask);
        // 計(jì)算結(jié)果索引
        int end = start + steps;

        for (int i = start; i < end; i++) {
            // 拿到桶中的哨兵節(jié)點(diǎn),獲取到尾節(jié)點(diǎn)和頭節(jié)點(diǎn)
            Node sentinel = timerWheel[i & mask];
            Node prev = sentinel.getPreviousInVariableOrder();
            Node node = sentinel.getNextInVariableOrder();
            // 重置哨兵節(jié)點(diǎn),意味著這個(gè)桶中的元素都需要被處理,該層級(jí)的時(shí)間有流逝,處理但不意味著有元素過(guò)期
            sentinel.setPreviousInVariableOrder(sentinel);
            sentinel.setNextInVariableOrder(sentinel);

            // node != sentinel 表示 node 并不是哨兵節(jié)點(diǎn),證明其中有元素需要被處理
            while (node != sentinel) {
                // 標(biāo)記 next 節(jié)點(diǎn)的引用
                Node next = node.getNextInVariableOrder();
                // 將當(dāng)前節(jié)點(diǎn)從雙向鏈表中斷開(kāi)
                node.setPreviousInVariableOrder(null);
                node.setNextInVariableOrder(null);

                try {
                    // 先拿元素的過(guò)期時(shí)間與當(dāng)前操作時(shí)間比較,判斷有沒(méi)有過(guò)期,如果過(guò)期則會(huì)執(zhí)行 evictEntry 方法,驅(qū)逐元素
                    if (((node.getVariableTime() - nanos) > 0)
                            || !cache.evictEntry(node, RemovalCause.EXPIRED, nanos)) {
                        // 如果沒(méi)有過(guò)期則重新 schedule 節(jié)點(diǎn),因?yàn)殡S著時(shí)間的流逝,該節(jié)點(diǎn)可能會(huì)被重新分配到更低的時(shí)間層級(jí)中,以便被更好的管理過(guò)期時(shí)間
                        schedule(node);
                    }
                    node = next;
                } catch (Throwable t) {
                    // 處理時(shí)發(fā)生異常,將節(jié)點(diǎn)重新加入到鏈表中
                    node.setPreviousInVariableOrder(sentinel.getPreviousInVariableOrder());
                    node.setNextInVariableOrder(next);
                    sentinel.getPreviousInVariableOrder().setNextInVariableOrder(node);
                    sentinel.setPreviousInVariableOrder(prev);
                    throw t;
                }
            }
        }
    }
}

expire 方法并不復(fù)雜,本質(zhì)上是將未過(guò)期的節(jié)點(diǎn)重新執(zhí)行 TimeWheel#schedule 方法,將其劃分到更精準(zhǔn)的時(shí)間分層;將過(guò)期的節(jié)點(diǎn)驅(qū)逐,evictEntry 方法在 緩存之美:萬(wàn)文詳解 Caffeine 實(shí)現(xiàn)原理 中已經(jīng)介紹過(guò)了,這里就不再贅述了。

總結(jié)一下 TimeWheel 的流程:只有指定了 expireAfter 時(shí)間過(guò)期策略的緩存才會(huì)使用到時(shí)間輪。當(dāng)元素被添加時(shí),它的過(guò)期時(shí)間已經(jīng)被計(jì)算好并賦值到 variableTime 字段中,根據(jù)當(dāng)前元素的剩余有效期(variableTime - nanos)劃分它在具體的時(shí)間輪層級(jí)(wheel),隨著時(shí)間的流逝(advance),它所在的時(shí)間輪層級(jí)可能會(huì)不斷變化,可能由小時(shí)級(jí)別被轉(zhuǎn)移(schedule)到分鐘級(jí),當(dāng)然它也可能過(guò)期直接被驅(qū)逐(evictEntry)。這樣操作按照元素剩余有效期將其劃分到更精準(zhǔn)的時(shí)間層級(jí)中,可以更精準(zhǔn)的控制元素的過(guò)期時(shí)間,比如秒級(jí)時(shí)間沒(méi)有流逝的話(huà),那么便無(wú)需檢查分鐘級(jí)或更高時(shí)間跨度級(jí)別的元素是否過(guò)期。Caffeine 緩存完整的原理圖如下:

wKgZPGiRqSyAINsgABH3gnIczwA741.png

詳細(xì)了解需要結(jié)合 緩存之美:萬(wàn)文詳解 Caffeine 實(shí)現(xiàn)原理。

審核編輯 黃宇

聲明:本文內(nèi)容及配圖由入駐作者撰寫(xiě)或者入駐合作網(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)投訴
  • 源碼
    +關(guān)注

    關(guān)注

    8

    文章

    689

    瀏覽量

    31450
  • PUT
    PUT
    +關(guān)注

    關(guān)注

    0

    文章

    6

    瀏覽量

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

掃碼添加小助手

加入工程師交流群

    評(píng)論

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

    京東緩存中間件架構(gòu)與緩存內(nèi)核優(yōu)化

    一、京東緩存中間件架構(gòu) 1、背景 在當(dāng)今高并發(fā)、分布式的系統(tǒng)架構(gòu),緩存已成為提升應(yīng)用性能、降低數(shù)據(jù)庫(kù)負(fù)載的核心組件。隨著業(yè)務(wù)規(guī)模的擴(kuò)大與系統(tǒng)復(fù)雜度的增加,緩存的使用和管理面臨諸多挑戰(zhàn)
    的頭像 發(fā)表于 04-03 16:18 ?1791次閱讀
    京東<b class='flag-5'>緩存</b>中間件架構(gòu)與<b class='flag-5'>緩存</b>內(nèi)核優(yōu)化

    KeepAlive:組件緩存實(shí)現(xiàn)深度解析

    我們學(xué)習(xí)了 Suspense 如何處理異步組件加載。今天,我們將探索Vue3另一個(gè)強(qiáng)大的特性:KeepAlive。它允許我們?cè)诮M件切換時(shí)緩存組件實(shí)例,避免重復(fù)渲染,極大地提升了用戶(hù)體驗(yàn)和性能
    發(fā)表于 03-05 19:17

    本地搭建 Clawdbot + ZeroNews 訪(fǎng)問(wèn)

    ClawdBot 是一個(gè)本地部署的開(kāi)源AI助手,支持跨平臺(tái),可接入多種通訊工具并執(zhí)行本地操作,強(qiáng)調(diào)隱私與可控性。
    的頭像 發(fā)表于 02-03 17:51 ?603次閱讀
    <b class='flag-5'>本地</b>搭建 Clawdbot + ZeroNews 訪(fǎng)問(wèn)

    C語(yǔ)言的緩沖區(qū)(緩存)詳解

    填滿(mǎn)標(biāo)準(zhǔn)I/O緩存后才進(jìn)行實(shí)際I/O操作。全緩沖的典型代表是對(duì)磁盤(pán)文件的讀寫(xiě)。   2) 行緩沖   在這種情況下,當(dāng)在輸入和輸出遇到換行符時(shí),執(zhí)行真正的I/O操作。這時(shí),我們輸入的字符先存
    發(fā)表于 01-14 07:30

    ESP32 編譯過(guò)程 bootloader 配置階段的 CMake 緩存沖突錯(cuò)誤,記錄

    你遇到的是 ESP32 編譯過(guò)程 bootloader 配置階段的 CMake 緩存沖突錯(cuò)誤,核心原因是系統(tǒng)混合了 ESP-IDF v5.5.1 和 v5.4.3 兩個(gè)版本的路徑,導(dǎo)致
    發(fā)表于 12-23 07:07

    DES密鑰產(chǎn)生模塊結(jié)構(gòu)設(shè)計(jì)

    DES密鑰產(chǎn)生模塊的置換選擇PC-1、循環(huán)左移、置換選擇PC-2均采用assign語(yǔ)句實(shí)現(xiàn),配合一個(gè)二選一選通器和一個(gè)十六選一選通器實(shí)現(xiàn)。其中二選一選通器以mode模式判斷信號(hào)為選通信
    發(fā)表于 10-30 07:13

    Redis緩存的經(jīng)典問(wèn)題和解決方案

    用戶(hù)瘋狂查詢(xún)數(shù)據(jù)庫(kù)不存在的數(shù)據(jù),每次查詢(xún)都繞過(guò)緩存直接打到數(shù)據(jù)庫(kù),導(dǎo)致數(shù)據(jù)庫(kù)壓力驟增。
    的頭像 發(fā)表于 08-20 16:24 ?881次閱讀

    緩存之美:萬(wàn)文詳解 Caffeine 實(shí)現(xiàn)原理(上)

    文章將采用“總-分-總”的結(jié)構(gòu)對(duì)配置固定大小元素驅(qū)逐策略的 Caffeine 緩存進(jìn)行介紹,首先會(huì)講解它的實(shí)現(xiàn)原理,在大家對(duì)它有一個(gè)概念之后再深入具體源碼的細(xì)節(jié)之中,理解它的設(shè)計(jì)理念,從中能學(xué)習(xí)到
    的頭像 發(fā)表于 08-05 14:49 ?791次閱讀
    <b class='flag-5'>緩存</b>之美:萬(wàn)文詳解 <b class='flag-5'>Caffeine</b> 實(shí)現(xiàn)原理(上)

    高性能緩存設(shè)計(jì):如何解決緩存偽共享問(wèn)題

    在多核高并發(fā)場(chǎng)景下, 緩存偽共享(False Sharing) 是導(dǎo)致性能驟降的“隱形殺手”。當(dāng)不同線(xiàn)程頻繁修改同一緩存行(Cache Line)的獨(dú)立變量時(shí),CPU緩存一致性協(xié)議會(huì)
    的頭像 發(fā)表于 07-01 15:01 ?869次閱讀
    高性能<b class='flag-5'>緩存</b>設(shè)計(jì):如何解決<b class='flag-5'>緩存</b>偽共享問(wèn)題

    Chrony高精度時(shí)間同步配置

    時(shí)間同步,就是將本地時(shí)間與互聯(lián)網(wǎng)時(shí)間進(jìn)行校對(duì),為系統(tǒng)提供一個(gè)統(tǒng)一時(shí)間
    的頭像 發(fā)表于 06-28 16:06 ?1339次閱讀
    Chrony高精度<b class='flag-5'>時(shí)間</b>同步配置

    邊驅(qū)動(dòng)電機(jī)專(zhuān)利技術(shù)發(fā)展

    摘要:利用邊電機(jī)直接驅(qū)動(dòng)電動(dòng)汽車(chē)采用邊電機(jī),避免了機(jī)械傳動(dòng)系統(tǒng)的能量損失,使電能得到了最大的利用。電動(dòng)汽車(chē)采用邊直驅(qū)式可立即產(chǎn)生旋轉(zhuǎn)動(dòng)力,減少了加速
    發(fā)表于 06-10 13:15

    邊電機(jī)驅(qū)動(dòng)汽車(chē)性能仿真與控制方法的研究

    [摘要] 為多域車(chē)輛的陸地行駛,設(shè)計(jì)了邊電機(jī)驅(qū)動(dòng)系統(tǒng),構(gòu)建了基于邊驅(qū)動(dòng)系統(tǒng)的車(chē)輛模型,并對(duì)驅(qū)動(dòng)控制方法進(jìn)行了研究。在轉(zhuǎn)向動(dòng)力學(xué)理論分析基礎(chǔ)上,在ADAMS 建立了多體動(dòng)力學(xué)模型:提出了車(chē)輛驅(qū)動(dòng)
    發(fā)表于 06-10 13:10

    請(qǐng)問(wèn)如何增大usb3.0從設(shè)備fifo接口固件的寫(xiě)dma緩存大小?

    現(xiàn)有的固件是默認(rèn)的,分別配置了2個(gè)1KB的緩存給讀和寫(xiě)的dma。我想要多分配一點(diǎn)緩存給寫(xiě)dma,比如分配4kB給寫(xiě)dma。請(qǐng)教一下該如何修改ez usb suite的參數(shù)。
    發(fā)表于 05-14 08:13

    MCU緩存設(shè)計(jì)

    MCU 設(shè)計(jì)通過(guò)優(yōu)化指令與數(shù)據(jù)的訪(fǎng)問(wèn)效率,顯著提升系統(tǒng)性能并降低功耗,其核心架構(gòu)與實(shí)現(xiàn)策略如下: 一、緩存類(lèi)型與結(jié)構(gòu) 指令緩存(I-Cache)與數(shù)據(jù)緩存(D-Cache)? I-Cache?:
    的頭像 發(fā)表于 05-07 15:29 ?1260次閱讀

    Nginx緩存配置詳解

    Nginx 是一個(gè)功能強(qiáng)大的 Web 服務(wù)器和反向代理服務(wù)器,它可以用于實(shí)現(xiàn)靜態(tài)內(nèi)容的緩存,緩存可以分為客戶(hù)端緩存和服務(wù)端緩存。
    的頭像 發(fā)表于 05-07 14:03 ?1370次閱讀
    Nginx<b class='flag-5'>緩存</b>配置詳解
    清水河县| 开原市| 深水埗区| 江山市| 隆回县| 云安县| 营山县| 扶绥县| 洪雅县| 西平县| 龙海市| 隆尧县| 酉阳| 大邑县| 慈利县| 舒城县| 临城县| 万源市| 宁明县| 罗田县| 亳州市| 涞源县| 亚东县| 宜城市| 正定县| 封开县| 汤原县| 库车县| 凤台县| 太保市| 耒阳市| 库伦旗| 邵阳县| 台南县| 清流县| 横山县| 明水县| 禄丰县| 泽州县| 东安县| 府谷县|