首頁 > 軟體

Redis 快取淘汰策略和事務實現樂觀鎖詳情

2022-07-21 14:01:41

快取淘汰策略

標題LRU原理

LRU(Least recently used,最近最少使用)演演算法根據資料的歷史存取記錄來進行淘汰資料,其核心思想是“如果資料最近被存取過,那麼將來被存取的機率也更高”。

最常見的實現是使用一個連結串列儲存快取資料,詳細演演算法實現如下:

  • 新資料插入到連結串列頭部;
  • 每當快取命中(即快取資料被存取),則將資料移到連結串列頭部;
  • 當連結串列滿的時候,將連結串列尾部的資料丟棄。

在Java中可以使用LinkHashMap去實現LRU利用雜湊連結串列實現:

標題Redis快取淘汰策略

設定最大快取

在 redis 中,允許使用者設定最大使用記憶體大小maxmemory,預設為0,沒有指定最大快取,如果有新的資料新增,超過最大記憶體,則會使redis崩潰,所以一定要設定。

redis 記憶體資料集大小上升到一定大小的時候,就會實行資料淘汰策略。

淘汰策略

redis淘汰策略設定:maxmemory-policy voltile-lru,支援熱設定

redis 提供 6種資料淘汰策略:

  • volatile-lru:從已設定過期時間的資料集(server.db[i].expires)中挑選最近最少使用的資料淘汰
  • volatile-ttl:從已設定過期時間的資料集(server.db[i].expires)中挑選將要過期的資料淘汰
  • volatile-random:從已設定過期時間的資料集(server.db[i].expires)中任意選擇資料淘汰
  • allkeys-lru:從資料集(server.db[i].dict)中挑選最近最少使用的資料淘汰
  • allkeys-random:從資料集(server.db[i].dict)中任意選擇資料淘汰
  • no-enviction(驅逐):禁止驅逐資料

Redis事務

Redis事務介紹

  • Redis 的事務是通過 MULTI 、 EXEC 、 DISCARD 和 WATCH 、UNWATCH這五個命令來完成的。
  • Redis 的單個命令都是原子性的,所以這裡需要確保事務性的物件是命令集合。
  • Redis 將命令集合序列化並確保處於同一事務的命令集合連續且不被打斷的執行
  • Redis 不支援回滾操作。 事務命令

MULTI

用於標記事務塊的開始。 Redis會將後續的命令逐個放入佇列中,然後使用EXEC命令原子化地執行這個命令序列。

語法:

multi

EXEC

在一個事務中執行所有先前放入佇列的命令,然後恢復正常的連線狀態

語法:

exec

DISCARD

清除所有先前在一個事務中放入佇列的命令,然後恢復正常的連線狀態。

語法:

discard

WATCH

當某個[事務需要按條件執行]時,就要使用這個命令將給定的[鍵設定為受監控]的狀態。

語法:

watch key [key…]

注意事項:使用該命令可以實現 Redis 的樂觀鎖。

UNWATCH

清除所有先前為一個事務監控的鍵

語法:

unwatch

命令圖解:

事務演示:

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set s1 111
QUEUED
127.0.0.1:6379> hset set1 name zhangsan
QUEUED
127.0.0.1:6379> exec
1) OK
2) (integer) 1
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set s2 222
QUEUED
127.0.0.1:6379> hset set2 age 20
QUEUED
127.0.0.1:6379> discard
OK
127.0.0.1:6379> exec (error) ERR EXEC without MULTI 
127.0.0.1:6379> watch s1
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set s1 555
QUEUED 127.0.0.1:6379> exec # 此時在沒有exec之前,通過另一個命令視窗對監控的s1欄位進行修改 
(nil)
127.0.0.1:6379> get s1
111

Redis 不支援事務回滾(為什麼呢)

大多數事務失敗是因為語法錯誤或者型別錯誤,這兩種錯誤,在開發階段都是可以預見的Redis 為了效能方面就忽略了事務回滾。

Redis樂觀鎖

樂觀鎖基於CAS(Compare And Swap)思想(比較並替換),是不具有互斥性,不會產生鎖等待而消耗資源,但是需要反覆的重試,但也是因為重試的機制,能比較快的響應。因此我們可以利用redis來

實現樂觀鎖。具體思路如下:

  • 利用redis的watch功能,監控這個redisKey的狀態值
  • 獲取redisKey的值
  • 建立redis事務
  • 給這個key的值+1
  • 然後去執行這個事務,如果key的值被修改過則回滾,key不加1
public void watch() {
	try {
		String watchKeys = "watchKeys";
		//初始值 value=1
		jedis.set(watchKeys, 1);
		//監聽key為watchKeys的值
		jedis.watch(watchkeys);
		//開啟事務
		Transaction tx = jedis.multi();
		//watchKeys自增加一
		tx.incr(watchKeys);
		//執行事務,如果其他執行緒對watchKeys中的value進行修改,則該事務將不會執行
		//通過redis事務以及watch命令實現樂觀鎖
		List<Object> exec = tx.exec();
		if (exec == null) {
			System.out.println("事務未執行");
		} else {
			System.out.println("事務成功執行,watchKeys的value成功修改");
		}
	} catch (Exception e) {
		e.printStackTrace();
	} finally {
		jedis.close();
	}
}

Redis樂觀鎖實現秒殺

public class RedisLock {
    public static void main(String[] arg) {
        //庫存key 
        String redisKey = "stock";
        ExecutorService executorService = Executors.newFixedThreadPool(20);
        try {
            Jedis jedis = new RedisProperties.Jedis("127.0.0.1", 6378);
            // 可以被秒殺的庫存的初始值,庫存總共20個
            jedis.set(redisKey, "0");
            jedis.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        for (int i = 0; i < 1000; i++) {
            executorService.execute(() -> {
                Jedis jedis1 = new Jedis("127.0.0.1", 6378);
                try {
                    jedis1.watch(redisKey);
                    String redisValue = jedis1.get(redisKey);
                    int valInteger = Integer.valueOf(redisValue);
                    String userInfo = UUID.randomUUID().toString();
                    // 沒有秒完
                    if (valInteger < 20) {
                        Transaction tx = jedis1.multi();
                        tx.incr(redisKey);
                        List list = tx.exec();
                        // 秒成功 失敗返回空list而不是空
                        if (list != null && list.size() > 0) {
                            System.out.println("使用者:" + userInfo + ",秒殺成 功!當前成功人數:" + (valInteger + 1));
                        }
                        // 版本變化,被別人搶了。
                        else {
                            System.out.println("使用者:" + userInfo + ",秒殺失 敗");
                        }
                    }
                    // 秒完了
                    else {
                        System.out.println("已經有20人秒殺成功,秒殺結束");
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    jedis1.close();
                }
            });
        }
        executorService.shutdown();
    }
}

到此這篇關於Redis 快取淘汰策略和事務實現樂觀鎖詳情的文章就介紹到這了,更多相關Redis 快取淘汰策略內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com