<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                Redis 和 MySQL 是面試繞不過的兩座大山,他們一個是關系型數據庫的代表(MySQL),一個是鍵值數據庫以及緩存中間件的一哥。尤其 Redis 幾乎是所有互聯網公司都在用的技術,比如國內的 BATJ、新浪、360、小米等公司;國外的微軟、Twitter、Stack Overflow、GitHub、暴雪等公司。我從業了十幾年,就職過 4、5 家公司,有的公司用 MySQL、有的用 SQL Server、甚至還有的用 Oracle 和 DB2,但緩存無一例外使用的都是 Redis,從某種程度上來講 Redis 是普及率最高的技術,沒有之一。 我們本課時的面試題是,Redis 是如何處理過期數據的?當內存不夠用時 Redis 又是如何處理的? #### 典型回答 我們在新增 Redis 緩存時可以設置緩存的過期時間,該時間保證了數據在規定的時間內失效,可以借助這個特性來實現很多功能。比如,存儲一定天數的用戶(登錄)會話信息,這樣在一定范圍內用戶不用重復登錄了,但為了安全性,需要在一定時間之后重新驗證用戶的信息。因此,我們可以使用 Redis 設置過期時間來存儲用戶的會話信息。 對于已經過期的數據,Redis 將使用兩種策略來刪除這些過期鍵,它們分別是惰性刪除和定期刪除。 惰性刪除是指 Redis 服務器不主動刪除過期的鍵值,而是當訪問鍵值時,再檢查當前的鍵值是否過期,如果過期則執行刪除并返回 null 給客戶端;如果沒過期則正常返回值信息給客戶端。 它的優點是不會浪費太多的系統資源,只是在每次訪問時才檢查鍵值是否過期。缺點是刪除過期鍵不及時,造成了一定的空間浪費。 惰性刪除的源碼位于 src/db.c 文件的 expireIfNeeded 方法中,如下所示: ``` int expireIfNeeded(redisDb *db, robj *key) { // 判斷鍵是否過期 if (!keyIsExpired(db,key)) return 0; if (server.masterhost != NULL) return 1; /* 刪除過期鍵 */ // 增加過期鍵個數 server.stat_expiredkeys++; // 傳播鍵過期的消息 propagateExpire(db,key,server.lazyfree_lazy_expire); notifyKeyspaceEvent(NOTIFY_EXPIRED, "expired",key,db->id); // server.lazyfree_lazy_expire 為 1 表示異步刪除,否則則為同步刪除 return server.lazyfree_lazy_expire ? dbAsyncDelete(db,key) : dbSyncDelete(db,key); } // 判斷鍵是否過期 int keyIsExpired(redisDb *db, robj *key) { mstime_t when = getExpire(db,key); if (when < 0) return 0; if (server.loading) return 0; mstime_t now = server.lua_caller ? server.lua_time_start : mstime(); return now > when; } // 獲取鍵的過期時間 long long getExpire(redisDb *db, robj *key) { dictEntry *de; if (dictSize(db->expires) == 0 || (de = dictFind(db->expires,key->ptr)) == NULL) return -1; serverAssertWithInfo(NULL,key,dictFind(db->dict,key->ptr) != NULL); return dictGetSignedIntegerVal(de); } ``` 惰性刪除的執行流程如下圖所示: ![](https://img.kancloud.cn/b0/ec/b0ece4d09ee792b5df8e0eb0c940f571_989x702.png) 除了惰性刪除之外,Redis 還提供了定期刪除功能以彌補惰性刪除的不足。 定期刪除是指 Redis 服務器每隔一段時間會檢查一下數據庫,看看是否有過期鍵可以被清除。 默認情況下 Redis 定期檢查的頻率是每秒掃描 10 次,用于定期清除過期鍵。當然此值還可以通過配置文件進行設置,在 redis.conf 中修改配置“hz”即可,默認的值為“hz 10”。 > 小貼士:定期刪除的掃描并不是遍歷所有的鍵值對,這樣的話比較費時且太消耗系統資源。Redis 服務器采用的是隨機抽取形式,每次從過期字典中,取出 20 個鍵進行過期檢測,過期字典中存儲的是所有設置了過期時間的鍵值對。如果這批隨機檢查的數據中有 25% 的比例過期,那么會再抽取 20 個隨機鍵值進行檢測和刪除,并且會循環執行這個流程,直到抽取的這批數據中過期鍵值小于 25%,此次檢測才算完成。 定期刪除的源碼在 expire.c 文件的 activeExpireCycle 方法中,如下所示: ``` void activeExpireCycle(int type) { static unsigned int current_db = 0; /* 上次定期刪除遍歷到的數據庫ID */ static int timelimit_exit = 0; static long long last_fast_cycle = 0; /* 上次執行定期刪除的時間點 */ int j, iteration = 0; int dbs_per_call = CRON_DBS_PER_CALL; // 需要遍歷數據庫的數量 long long start = ustime(), timelimit, elapsed; if (clientsArePaused()) return; if (type == ACTIVE_EXPIRE_CYCLE_FAST) { if (!timelimit_exit) return; // ACTIVE_EXPIRE_CYCLE_FAST_DURATION 快速定期刪除的執行時長 if (start < last_fast_cycle + ACTIVE_EXPIRE_CYCLE_FAST_DURATION*2) return; last_fast_cycle = start; } if (dbs_per_call > server.dbnum || timelimit_exit) dbs_per_call = server.dbnum; // 慢速定期刪除的執行時長 timelimit = 1000000*ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC/server.hz/100; timelimit_exit = 0; if (timelimit <= 0) timelimit = 1; if (type == ACTIVE_EXPIRE_CYCLE_FAST) timelimit = ACTIVE_EXPIRE_CYCLE_FAST_DURATION; /* 刪除操作花費的時間 */ long total_sampled = 0; long total_expired = 0; for (j = 0; j < dbs_per_call && timelimit_exit == 0; j++) { int expired; redisDb *db = server.db+(current_db % server.dbnum); current_db++; do { // ....... expired = 0; ttl_sum = 0; ttl_samples = 0; // 每個數據庫中檢查的鍵的數量 if (num > ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP) num = ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP; // 從數據庫中隨機選取 num 個鍵進行檢查 while (num--) { dictEntry *de; long long ttl; if ((de = dictGetRandomKey(db->expires)) == NULL) break; ttl = dictGetSignedInteger // 過期檢查,并對過期鍵進行刪除 if (activeExpireCycleTryExpire(db,de,now)) expired++; if (ttl > 0) { ttl_sum += ttl; ttl_samples++; } total_sampled++; } total_expired += expired; if (ttl_samples) { long long avg_ttl = ttl_sum/ttl_samples; if (db->avg_ttl == 0) db->avg_ttl = avg_ttl; db->avg_ttl = (db->avg_ttl/50)*49 + (avg_ttl/50); } if ((iteration & 0xf) == 0) { /* check once every 16 iterations. */ elapsed = ustime()-start; if (elapsed > timelimit) { timelimit_exit = 1; server.stat_expired_time_cap_reached_count++; break; } } /* 判斷過期鍵刪除數量是否超過 25% */ } while (expired > ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP/4); } // ....... } ``` 定期刪除的執行流程,如下圖所示: ![](https://img.kancloud.cn/60/45/6045bdb30a9561ce9a1917fbe50f890f_1032x804.png) > 小貼士:Redis 服務器為了保證過期刪除策略不會導致線程卡死,會給過期掃描增加了最大執行時間為 25ms。 以上是 Redis 服務器對待過期鍵的處理方案,當 Redis 的內存超過最大允許的內存之后,Redis 會觸發內存淘汰策略,這和過期策略是完全不同的兩個概念,經常有人把二者搞混,這兩者一個是在正常情況下清除過期鍵,一個是在非正常情況下為了保證 Redis 順利運行的保護策略。 當 Redis 內存不夠用時,Redis 服務器會根據服務器設置的淘汰策略,刪除一些不常用的數據,以保證 Redis 服務器的順利運行。 #### 考點分析 本課時的面試題并非 Redis 的入門級面試題,需要面試者對 Redis 有一定的了解才能對答如流,并且 Redis 的過期淘汰策略和內存淘汰策略的概念比較類似,都是用于淘汰數據的。因此很多人會把二者當成一回事,但其實并不是,這個面試者特別注意一下,和此知識點相關的面試題還有以下這些: * Redis 內存淘汰策略有哪些? * Redis 有哪些內存淘汰算法? #### 知識擴展 * [ ] Redis 內存淘汰策略 我們可以使用 config get maxmemory-policy 命令,來查看當前 Redis 的內存淘汰策略,示例代碼如下: ``` 127.0.0.1:6379> config get maxmemory-policy 1) "maxmemory-policy" 2) "noeviction" ``` 從上面的結果可以看出,當前 Redis 服務器設置的是“noeviction”類型的內存淘汰策略,那么這表示什么含義呢?Redis 又有幾種內存淘汰策略呢? 在 4.0 版本之前 Redis 的內存淘汰策略有以下 6 種。 * noeviction:不淘汰任何數據,當內存不足時,執行緩存新增操作會報錯,它是 Redis 默認內存淘汰策略。 * allkeys-lru:淘汰整個鍵值中最久未使用的鍵值。 * allkeys-random:隨機淘汰任意鍵值。 * volatile-lru:淘汰所有設置了過期時間的鍵值中最久未使用的鍵值。 * volatile-random:隨機淘汰設置了過期時間的任意鍵值。 * volatile-ttl:優先淘汰更早過期的鍵值。 可以看出我們上面示例使用的是 Redis 默認的內存淘汰策略“noeviction”。 而在 Redis 4.0 版本中又新增了 2 種淘汰策略: * volatile-lfu,淘汰所有設置了過期時間的鍵值中最少使用的鍵值; * allkeys-lfu,淘汰整個鍵值中最少使用的鍵值。 > 小貼士:從以上內存淘汰策略中可以看出,allkeys-xxx 表示從所有的鍵值中淘汰數據,而 volatile-xxx 表示從設置了過期鍵的鍵值中淘汰數據。 這個內存淘汰策略我們可以通過配置文件來修改,redis.conf 對應的配置項是“maxmemory-policy noeviction”,只需要把它修改成我們需要設置的類型即可。 需要注意的是,如果使用修改 redis.conf 的方式,當設置完成之后需要重啟 Redis 服務器才能生效。 還有另一種簡單的修改內存淘汰策略的方式,我們可以使用命令行工具輸入“config set maxmemory-policy noeviction”來修改內存淘汰的策略,這種修改方式的好處是執行成功之后就會生效,無需重啟 Redis 服務器。但它的壞處是不能持久化內存淘汰策略,每次重啟 Redis 服務器之后設置的內存淘汰策略就會丟失。 * [ ] Redis 內存淘汰算法 內存淘汰算法主要包含兩種:LRU 淘汰算法和 LFU 淘汰算法。 LRU( Least Recently Used,最近最少使用)淘汰算法:是一種常用的頁面置換算法,也就是說最久沒有使用的緩存將會被淘汰。 LRU 是基于鏈表結構實現的,鏈表中的元素按照操作順序從前往后排列,最新操作的鍵會被移動到表頭,當需要進行內存淘汰時,只需要刪除鏈表尾部的元素即可。 Redis 使用的是一種近似 LRU 算法,目的是為了更好的節約內存,它的實現方式是給現有的數據結構添加一個額外的字段,用于記錄此鍵值的最后一次訪問時間。Redis 內存淘汰時,會使用隨機采樣的方式來淘汰數據,它是隨機取 5 個值 (此值可配置) ,然后淘汰最久沒有使用的數據。 LFU(Least Frequently Used,最不常用的)淘汰算法:最不常用的算法是根據總訪問次數來淘汰數據的,它的核心思想是“如果數據過去被訪問多次,那么將來被訪問的頻率也更高”。 LFU 相對來說比 LRU 更“智能”,因為它解決了使用頻率很低的緩存,只是最近被訪問了一次就不會被刪除的問題。如果是使用 LRU 類似這種情況數據是不會被刪除的,而使用 LFU 的話,這個數據就會被刪除。 Redis 內存淘汰策略使用了 LFU 和近 LRU 的淘汰算法,具體使用哪種淘汰算法,要看服務器是如何設置內存淘汰策略的,也就是要看“maxmemory-policy”的值是如何設置的。 #### 小結 本課時我們講了 Redis 的過期刪除策略:惰性刪除 + 定期刪除;還講了 Redis 的內存淘汰策略,它和過期策略是完全不同的兩個概念,內存淘汰策略是當內存不夠用時才會觸發的一種機制,它在 Redis 4.0 之后提供了 8 種內存淘汰策略,這些淘汰策略主要使用了近 LRU 淘汰算法和 LFU 淘汰算法。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看