資料結構之Redis應用~常用命令~應用場景(重點)

Guo_1_9發表於2019-03-04

資料結構之Redis應用之常用命令之應用場景

說明

  • 1、本文參考了Redis開發實戰指南GitBook,還有《Redis實戰》自己之前的筆記。主體框架來自這裡

  • 2、感謝大佬們的付出,在這裡自己只是記錄,加深自己的印象。

  • 3、本文會同步放到我自己的Guo_GitHub,方便自己複習,喜歡的可以點個star。

  • 4、原諒我現在還沒能力自己總結,所以只能臨摹別人,加自己的理解,結合自己之前的筆記

  • 5、如有拼寫錯誤,還請諒解。有不同觀點,可以評論出來,一起努力。

gogogo 我們正式進入主題吧,

Redis中5種資料結構的使用場景介紹

Redis is an open source (BSD licensed), in-memory data structure store, used as a database, cache and message broker. It supports data structures such as strings, hashes, lists, sets, sorted sets with range queries, bitmaps, hyperloglogs and geospatial indexes with radius queries. Redis has built-in replication, Lua scripting, LRU eviction, transactions and different levels of on-disk persistence, and provides high availability via Redis Sentinel and automatic partitioning with Redis Cluster。

推介看《Redis In Action》非常非常非常不錯,尤其前三章。使用Redis的關鍵點不是設計資料庫,而是如何選擇合適場景的資料結構。就像我們之前操作容器時,選擇不同容易裝不同物件。每個都有各自的各點,具體的內容過兩天看原始碼的時候分享給大家。今天的主題是以下五種資料結構。

主要5種資料結構:

  • String ——>字串
  • Hash ——>字典(雜湊)
  • List ——>列表
  • Set ——>集合
  • Sorted Set ——>有序集合

1. String——字串

String資料結構是簡單的Key-Value型別,Value不僅可以是String,也可以是數字。使用String型別,可以完全實現目前Memcached(只有一種String)的功能,並且效率更高,還可以享受Redis的定時持久化(RDB模式或AOF模式),提供日誌及Replication等功能。除了提供與Memcached的get、set、incr、decr等操作外,Redis還提供了下面一些操作:

  1. LEN niushuai:O(1)獲取字串長度
  2. APPEND niushuai redis:往字串 append 內容,而且採用智慧分配記憶體(每次2倍)
  3. 設定和獲取字串的某一段內容
  4. 設定及獲取字串的某一位(bit)
  5. 批量設定一系列字串的內容
  6. 原子計數器
  7. GETSET 命令的妙用,請於清空舊值的同時設定一個新值,配合原子計數器使用
//1、計數器的使用
    String articleId = String.valueOf(conn.incr("article:"));     //String.valueOf(int i) : 將 int 變數 i 轉換成字串
複製程式碼

放一些常用命令,方便自己複習

set key value [ex 秒數] / [px 毫秒數]  [nx] /[xx]     //返回1表示成功,0失敗
incr key                                             //對key做加加操作,
decr key                                             //對key做減減操作
setnx key value                                      //僅當key不存在時才set,nx表示not exist。(重點)
mset key1 value1   key2 value2 .。。                 //一次設定多個key,
--------------------------------------------------------------------------------
get key                                              //如果key不存在返回null
mget key1 key2 ... keyN                              //一次獲取多個key的值,如果對於的key不存在,則返回null
getset key value                                     //設定新值返回舊值。
複製程式碼

分散式鎖的思路:

  1. C3傳送SETNX lock.foo 想要獲得鎖,由於C0還持有鎖,所以Redis返回給C3一個0
  2. C3傳送GET lock.foo 以檢查鎖是否超時了,如果沒超時,則等待或重試。
  3. 反之,如果已超時,C3通過下面的操作來嘗試獲得鎖:
  4. GETSET lock.foo
  5. 通過GETSET,C3拿到的時間戳如果仍然是超時的,那就說明,C3如願以償拿到鎖了。
  6. 如果在C3之前,有個叫C4的客戶端比C3快一步執行了上面的操作,那麼C3拿到的時間戳是個未超時的值,這時,C3沒有如期獲得鎖,需要再次等待或重試。留意一下,儘管C3沒拿到鎖,但它改寫了C4設定的鎖的超時值,不過這一點非常微小的誤差帶來的影響可以忽略不計。

虛擬碼:

# get lock
lock = 0
while lock != 1:
    timestamp = current Unix time + lock timeout + 1       //時間戳
    lock = SETNX lock.foo timestamp                        //嘗試獲取鎖,返回0,則下面檢查是否超時,GET。
    if lock == 1 or (now() > (GET lock.foo) and now() > (GETSET lock.foo timestamp)):
        break;                                            //關鍵點上面這條程式碼
    else:
        sleep(10ms)

# do your job
do_job()

# release
if now() < GET lock.foo:
    DEL lock.foo
複製程式碼

gogogo

incr key                       //對key做加加操作,
decr key                       //對key做減減操作

incrby key interge             //對key加指定的數

incrbyfloat key floatnumber     //針對浮點數

append key value               //返回新字元的長度

substr key start end           //並不會修改字串,返回擷取的字串

getrange key start end         //返回子串

strlen  key                     //取指定key的Value
複製程式碼

2. Hash——字典

在Memcached中,經常就愛那個一些結構化的資訊打包成hashMap,在客戶端序列化後儲存為一個字串的值,(通常為JSON格式),比如使用者的暱稱、年齡、性別、積分。這時候在需要修改其中某一項時,通用需要將字串(JSON)取出來,然後進行反序列化,修改某一項的值,在序列化成字串(JSON)儲存回去。而Redis和Hash結構可以使你像在資料庫中Update一個屬性一樣只修改某一項屬性值。

底層實現是hash table,一般操作複雜度是O(1),要同時操作多個field時就是O(N),N是field的數量。應用場景:土法建索引。比如User物件,除了id有時還要按name來查詢。

常用命令:

hset key field value                        //設定hash field為指定值,如果key不存在,則先建立
hsetnx                                      //同時,如果存在返回0,nx是not exist的意思
hmset key filed1 value1 ... filedN valueN   //設定多個值

hget key field                              //獲取指定的hash field
hmget key field1 field2                     //獲取全部指定的field
-------------------------------------------------------------------------------
hincrby key field integer                  //將指定的hash field加上給定值
hexists key field                          //測試指定field是否存在
hdel  key field                            //刪除指定的field
hlen key                                   //返回會指定hash的field數量

hgetall                                    //返回hash所有field和value
複製程式碼

場景:


/**
 * 使用Redis重新實現登入cookie,取代目前由關係型資料庫實現的登入cookie功能
 * 1、將使用一個雜湊來儲存登入cookie令牌與與登入使用者之間的對映。
 * 2、需要根據給定的令牌來查詢與之對應的使用者,並在已經登入的情況下,返回該使用者id。
 */

//1、嘗試獲取並返回令牌對應的使用者    注意“login:” 一般使用冒號做分割符,這是不成文的規矩
 conn.hget("login:", token);
//2、維持令牌與已登入使用者之間的對映。
conn.hset("login:", token, user);
//6、移除被刪除令牌對應的使用者資訊
conn.hdel("login:", tokens);
---------------------------注意中間還有其他步驟----------------------------------
/**
 * 使用cookie實現購物車——就是將整個購物車都儲存到cookie裡面,
 * 優點:無需對資料庫進行寫入就可以實現購物車功能,
 * 缺點:怎是程式需要重新解析和驗證cookie,確保cookie的格式正確。並且包含商品可以正常購買
 * 還有一缺點:因為瀏覽器每次傳送請求都會連cookie一起傳送,所以如果購物車的體積較大,
 * 那麼請求傳送和處理的速度可能降低。
 * -----------------------------------------------------------------
 * 1、每個使用者的購物車都是一個雜湊,儲存了商品ID與商品訂單數量之間的對映。
 * 2、如果使用者訂購某件商品的數量大於0,那麼程式會將這件商品的ID以及使用者訂購該商品的數量新增到雜湊裡。
 * 3、如果使用者購買的商品已經存在於雜湊裡面,那麼新的訂單數量會覆蓋已有的。
 * 4、相反,如果某使用者訂購某件商品數量不大於0,那麼程式將從雜湊裡移除該條目
 * 5、需要對之前的會話清理函式進行更新,讓它在清理會話的同時,將舊會話對應的使用者購物車也一併刪除。
 */

//1、從購物車裡面移除指定的商品        注意"cart:" 可以在資料遷移、轉換 、和刪除時輕鬆識別
conn.hdel("cart:" + session, item);
//2、將指定的商品新增到購物車
conn.hset("cart:" + session, item, String.valueOf(count));
//6、移除被刪除令牌對應的使用者資訊
conn.hdel("login:", tokens);

--------------------------------------------------------------------------------
//5、將文章資訊儲存到一個雜湊裡面。
//HMSET key field value [field value ...]
//同時將多個 field-value (域-值)對設定到雜湊表 key 中。
//此命令會覆蓋雜湊表中已存在的域。
conn.hmset(article, articleData);

//為雜湊表 key 中的域 field 的值加上增量 increment 。
//增量也可以為負數,相當於對給定域進行減法操作。
//HINCRBY counter page_view 200
conn.hincrBy(article, "votes", 1L);

//如果返回1表示還沒有這個資料,注意-1後面的L
conn.hincrBy(article, "votes", -1L);
//3、根據文章ID獲取文章的詳細資訊
Map<String,String> articleData = conn.hgetAll(id);
--------------------------測試-------------------------------------------------
//2、測試文章的投票過程
articleVote(conn, "other_user", "article:" + articleId);
String votes = conn.hget("article:" + articleId, "votes");
System.out.println("我們為該文章投票,目前該文章的票數 " + votes);
assert Integer.parseInt(votes) > 1;

複製程式碼

其實應用場景還有很多。這裡只是摘出程式片段中的一部分,具體可以點這裡。需要注意的是組合使用,取其精華、去其糟粕。

3. List——列表

List說白了就是連結串列(Redis使用雙端連結串列實現的List),使用List結構,我們可以輕鬆的實現最新訊息的排行等功能(比如Twitter的TimeLine),List的另一個應用就是訊息佇列,可以利用List的PUSH操作,將任務存在List中,然後工作線POP操作取出任務執行。Redis還提供了操作List中某一段元素的API,可以直接查詢,刪除List中某一段元素。

資料結構之Redis應用~常用命令~應用場景(重點)

命令:

lpush key string                        //在key對應的list的頭部新增元素
rpush key string                        //在list的尾部新增元素

lpushx key value                        //如果key不存在,什麼都不做
rpushx key value                        //同上

linsert key BEFORE|AFTER pivot value     //在list對應的位置之前或之後
---------------------------------------------------------------------------
llen key                                 //檢視列表對應的長度
lindex key index                         //指定索引的位置,0第一個

lrange key start end                     //檢視一段列表
lrange key 0 -1                          // -1表示返回所有資料

ltrim key start end                      //保留指定區間的元素

lset key index value                     //idnex表示指定索引的位置

ldel                                     //刪除元素

blpop key [key ...] timeout               //阻塞佇列
brpop key [key ...] timeout
複製程式碼

基於redis構建訊息佇列點這裡,寫的非常不錯。為了自己複習,就拿來主義了。

一般來說,訊息佇列有兩種場景:一種是釋出者訂閱者模式;一種是生產者消費者模式。利用redis這兩種場景的訊息佇列都能夠實現。

  • 生產者消費者模式:生產者生產訊息放到佇列裡,多個消費者同時監聽佇列,誰先搶到訊息誰就會從佇列中取走訊息;即對於每個訊息只能被最多一個消費者擁有。(常用於處理高併發寫操作)
  • 釋出者訂閱者模式:釋出者生產訊息放到佇列裡,多個監聽佇列的消費者都會收到同一份訊息;即正常情況下每個消費者收到的訊息應該都是一樣的。(常用來作為日誌收集中一份原始資料對多個應用場景)

1、redis作為訊息中介軟體:

  • 1)Producer/ConsumerMode: 該方式是藉助redis的list結構實現的。Producer呼叫redis的lpush往特定key裡塞入訊息,Consumer呼叫brpop(阻塞方法)去不斷監聽該key。
// producer code
String key = "demo:mq:test";
String msg = "hello world";
redisDao.lpush(key, msg);
// consumer code
String key = "demo:mq:test";
while (true) {
    // block invoke
   List<String> msgs = redisDao.brpop(BLOCK_TIMEOUT, listKey);    //注意brpop
       if (msgs == null) continue;
           String jobMsg = msgs.get(1);
               processMsg(jobMsg);
}
複製程式碼

1、使用redis怎麼做訊息佇列

  • 1、首先redis它的設計是用來做快取的,但是由於它自身的某種特性使得他可以用來做訊息佇列。它有幾個阻塞式的API(brpop、Sub,他們都是阻塞版的)可以使用,正是這些阻塞式的API讓他有做訊息佇列的能力。
  • 2、其次,訊息佇列的其他特性例如FIFO也很容易實現,只需要一個List物件從頭取資料,從尾部塞資料即可實現。

2、訂閱-釋出系統

Pub/Sub 從字面上理解就是釋出(Publish)與訂閱(Subscribe),在 Redis 中,你可以設定對某一個 key 值進行訊息釋出及訊息訂閱,當一個 key 值上進行了訊息釋出後,所有訂閱它的客戶端都會收到相應的訊息。這一功能最明顯的用法就是用作實時訊息系統,比如普通的即時聊天,群聊等功能。

程式碼實現點這裡和上面同一個人,已徵求

要使用Jedis的Publish/Subscribe功能,必須編寫對JedisPubSub的自己的實現。


public class MyListener extends JedisPubSub {

	// 取得訂閱的訊息後的處理
	@Override
	public void onMessage(String channel, String message) {
		// TODO Auto-generated method stub
		System.out.println(channel + "=" + message);
	}

	// 取得按表示式的方式訂閱的訊息後的處理
	@Override
	public void onPMessage(String pattern, String channel, String message) {
		// TODO Auto-generated method stub
		System.out.println(pattern + ":" + channel + "=" + message);
	}
	// 初始化訂閱時候的處理
	@Override
	public void onSubscribe(String channel, int subscribedChannels) {
		// TODO Auto-generated method stub
		System.out.println("初始化 【頻道訂閱】 時候的處理  ");
	}
	// 取消訂閱時候的處理
	@Override
	public void onUnsubscribe(String channel, int subscribedChannels) {
		// TODO Auto-generated method stub
		System.out.println("// 取消 【頻道訂閱】 時候的處理  ");
	}

	// 初始化按表示式的方式訂閱時候的處理
	@Override
	public void onPSubscribe(String pattern, int subscribedChannels) {
		// TODO Auto-generated method stub
		System.out.println("初始化 【模式訂閱】 時候的處理  ");
	}
	// 取消按表示式的方式訂閱時候的處理
	@Override
	public void onPUnsubscribe(String pattern, int subscribedChannels) {
		// TODO Auto-generated method stub
		System.out.println("取消 【模式訂閱】 時候的處理  ");
	}
}
複製程式碼

2、Sub

public class Sub {
	public static void main(String[] args) {
		try {
			Jedis jedis = getJedis();
			MyListener ml = new MyListener();

			//可以訂閱多個頻道
			//jedis.subscribe(ml, "liuxiao","hello","hello_liuxiao","hello_redis");
			//jedis.subscribe(ml, new String[]{"hello_foo","hello_test"});
			//這裡啟動了訂閱監聽,執行緒將在這裡被阻塞
			//訂閱得到資訊在lister的onPMessage(...)方法中進行處理

			//使用模式匹配的方式設定頻道
			jedis.psubscribe(ml, new String[]{"hello_*"});
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
複製程式碼

需要注意的是:

呼叫subscribe()或psubscribe() 時,當前執行緒都會阻塞。

3、Pub

public class Pub {
	public static void main(String[] args) {
		try {
			Jedis jedis = getJedis();
			jedis.publish("hello_redis","hello_redis");

		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
複製程式碼

4. Set——集合(重點)

Set 就是一個集合,集合的概念就是一堆不重複值的組合。利用 Redis 提供的 Set 資料結構,可以儲存一些集合性的資料。比如在Twitter應用中,可以將一個使用者所有的關注人存在一個集合中,將其所有粉絲存在一個集合。因為 Redis 非常人性化的為集合提供了求交集、並集、差集等操作,那麼就可以非常方便的實現如共同關注、共同喜好、二度好友等功能,對上面的所有集合操作,你還可以使用不同的命令選擇將結果返回給客戶端還是存集到一個新的集合中。

  1. 共同好友、二度好友
  2. 利用唯一性,可以統計訪問網站的所有獨立 IP
  3. 好友推薦的時候,根據 tag 求交集,大於某個 threshold 就可以推薦

命令:

sadd key member               //新增元素,成功返回1,

srem key member               //移除元素,成功返回1

spop key                     //刪除並返回,如果set是空或者不存在則返回null

srandmember key              //同spop,隨機取set中一個元素,但是不刪除

smove srckey dstkey member   //集合間移動元素

scard key                    //檢視集合的大小,如果set是空或者key不存在則返回0

sismember key member         //判斷member是否在Set中,存在返回1,0表示不存在或key不存在

smembers  key                 //獲取所有元素,返回key對應的所有元素,結果是無序的哦

--------------------------------------------------------------------------------
//集合交集
sinter key1 key2                  //返回所有給定key的交集
sinterstore dstkey key1 key2      //同sinter,並同時儲存並集到dstkey下

//集合並集
sunion key1 key2                 //返回所有給定key的並集
sunionstore dstkey key1 key2      //同sunion,並同時儲存並集到dstkey下

//集合差集
sdiff key1 key2                   //返回給定key的差集
sdiffstore dstkey key1 key2       //同sdiff,並同時儲存並集到dstkey下
複製程式碼

為了防止使用者對同一篇文章進行多次投票,需要為每篇文章記錄一個已投票使用者名稱單。使用集合來儲存已投票的使用者ID。由於集合是不能儲存多個相同的元素的,所以不會出現同個使用者對同一篇文章多次投票的情況。 程式碼實現:

2、程式需要使用SADD將文章釋出者的ID新增到記錄文章已投票使用者名稱單的集合中
並使用EXPIRE命令為這個集合設定一個過期時間,讓Redis在文章釋出期滿一週之後自動刪除這個集合。
//2、新增到記錄文章已投使用者名稱單中,
conn.sadd(voted, user);
//3、設定一週為過期時間
conn.expire(voted, ONE_WEEK_IN_SECONDS);
--------------------------------------------------------------------------------
4、檢查使用者是否第一次為這篇文章投票,如果是第一次,則在增加這篇文章的投票數量和評分。
將一個或多個 member 元素加入到集合 key 當中,已經存在於集合的 member 元素將被忽略。
if (conn.sadd("voted:" + articleId, user) == 1) {
    //ZINCRBY salary 2000 tom   # tom 加薪啦!
    conn.zincrby("score:", VOTE_SCORE, article);
    //HINCRBY counter page_view 200
    conn.hincrBy(article, "votes", 1L);
}
---------------------------------------------------------------------------------
/**
 * 記錄文章屬於哪個群組
 * 將所屬一個群組的文章ID記錄到那個集合中
 * Redis不僅可以對多個集合執行操作,甚至在一些情況下,還可以在集合和有序集合之間執行操作
 */

 //1、構建儲存文章資訊的鍵名
String article = "article:" + articleId;
for (String group : toAdd) {
    //2、將文章新增到它所屬的群組裡面
    conn.sadd("group:" + group, article);
}
複製程式碼

5. Sorted Set——有序集合(重點之重點)

Sorted Set的實現是hash table(element->score, 用於實現ZScore及判斷element是否在集合內),和skip list(score->element,按score排序)的混合體。 skip list有點像平衡二叉樹那樣,不同範圍的score被分成一層一層,每層是一個按score排序的連結串列。 ZAdd/ZRem是O(log(N)),ZRangeByScore/ZRemRangeByScore是O(log(N)+M),N是Set大小,M是結果/操作元素的個數。可見,原本可能很大的N被很關鍵的Log了一下,1000萬大小的Set,複雜度也只是幾十不到。當然,如果一次命中很多元素M很大那誰也沒辦法了。

常用命令 :

zadd key score member             //新增元素到集合,元素在集合中存在則更新對應的score
zrem key member                   //1表示成功,如果元素不存在則返回0

zremrangebyrank min max           //刪除集合中排名在給定的區間

zincrvy key member                //增加對於member的score的值。

zcard key                         //返回集合中元素的個數

//獲取排名
zrank key member                 //返回指定元素在集合中的排名,
zrebrank key member              //同時,但是集合中的元素是按score從大到小排序的

//獲取排行榜
zrange key start end            //類似lrange操作從集合中去指定區間的元素,返回時有序的。

//給給定分數區間的元素
zrangebyscore key min max

//評分的聚合
ZUNIONSTORE destination numkeys key [key ...] [WEIGHTS weight] [AGGREGATE SUM|MIN|MAX]
複製程式碼

程式碼實現:

/**
 * 1、每次使用者瀏覽頁面的時候,程式需都會對使用者儲存在登入雜湊裡面的資訊進行更新,
 * 2、並將使用者的令牌和當前時間戳新增到記錄最近登入使用者的集合裡。
 * 3、如果使用者正在瀏覽的是一個商品,程式還會將商品新增到記錄這個使用者最近瀏覽過的商品有序集合裡面,
 * 4、如果記錄商品的數量超過25個時,對這個有序集合進行修剪。
 */

 //1、獲取當前時間戳
 long timestamp = System.currentTimeMillis() / 1000;
 //2、維持令牌與已登入使用者之間的對映。
 conn.hset("login:", token, user);
 //3、記錄令牌最後一次出現的時間
 conn.zadd("recent:", timestamp, token);
 if (item != null) {
     //4、記錄使用者瀏覽過的商品
     conn.zadd("viewed:" + token, timestamp, item);
     //5、移除舊記錄,只保留使用者最近瀏覽過的25個商品
     conn.zremrangeByRank("viewed:" + token, 0, -26);
     //6、為有序集key的成員member的score值加上增量increment。通過傳遞一個負數值increment 讓 score 減去相應的值,
     conn.zincrby("viewed:", -1, item);
 }
---------------------------------------------------------------------------------
/**
 *儲存會話資料所需的記憶體會隨著時間的推移而不斷增加,所有我們需要定期清理舊的會話資料。
 * 1、清理會話的程式由一個迴圈構成,這個迴圈每次執行的時候,都會檢查儲存在最近登入令牌的有序集合的大小。
 * 2、如果有序集合的大小超過了限制,那麼程式會從有序集合中移除最多100個最舊的令牌,
 * 3、並從記錄使用者登入資訊的雜湊裡移除被刪除令牌對應的使用者資訊,
 * 4、並對儲存了這些使用者最近瀏覽商品記錄的有序集合中進行清理。
 * 5、於此相反,如果令牌的數量沒有超過限制,那麼程式會先休眠一秒,之後在重新進行檢查。
 */

 public void run() {
         while (!quit) {
             //1、找出目前已有令牌的數量。
             long size = conn.zcard("recent:");
             //2、令牌數量未超過限制,休眠1秒,並在之後重新檢查
             if (size <= limit) {
                 try {
                     sleep(1000);
                 } catch (InterruptedException ie) {
                     Thread.currentThread().interrupt();
                 }
                 continue;
             }

             long endIndex = Math.min(size - limit, 100);
             //3、獲取需要移除的令牌ID
             Set<String> tokenSet = conn.zrange("recent:", 0, endIndex - 1);
             String[] tokens = tokenSet.toArray(new String[tokenSet.size()]);

             ArrayList<String> sessionKeys = new ArrayList<String>();
             for (String token : tokens) {
                 //4、為那些將要被刪除的令牌構建鍵名
                 sessionKeys.add("viewed:" + token);
             }
             //5、移除最舊的令牌
             conn.del(sessionKeys.toArray(new String[sessionKeys.size()]));
             //6、移除被刪除令牌對應的使用者資訊
             conn.hdel("login:", tokens);
             //7、移除使用者最近瀏覽商品記錄。
             conn.zrem("recent:", tokens);
         }
     }
}

//7、移除使用者最近瀏覽商品記錄。
conn.zrem("recent:", tokens);
複製程式碼

另外一個案例:

/**
 * 為了應對促銷活動帶來的大量負載,需要對資料行進行快取,具體做法是:
 * 1、編寫一個持續執行的守護程式,讓這個函式指定的資料行快取到redis裡面,並不定期的更新。
 * 2、快取函式會將資料行編碼為JSON字典並儲存在Redis字典裡。其中資料列的名字會被對映為JSON的字典,
 * 而資料行的值則被對映為JSON字典的值。
 * -----------------------------------------------------------------------------------------
 * 程式使用兩個有序集合來記錄應該在何時對快取進行更新:
 * 1、第一個為呼叫有序集合,他的成員為資料行的ID,而分支則是一個時間戳,
 * 這個時間戳記錄了應該在何時將指定的資料行快取到Redis裡面
 * 2、第二個有序集合為延時有序集合,他的成員也是資料行的ID,
 * 而分值則記錄了指定資料行的快取需要每隔多少秒更新一次。
 * ----------------------------------------------------------------------------------------
 * 為了讓快取函式定期的快取資料行,程式首先需要將hangID和給定的延遲值新增到延遲有序集合裡面,
 * 然後再將行ID和當前指定的時間戳新增到排程有序集合裡面。
 */
public void scheduleRowCache(Jedis conn, String rowId, int delay) {
    //1、先設定資料行的延遲值
    conn.zadd("delay:", delay, rowId);
    //2、立即對需要行村的資料進行排程
    conn.zadd("schedule:", System.currentTimeMillis() / 1000, rowId);
}

--------------------------------------------------------------------------------------

/**
 * 1、通過組合使用排程函式和持續執行快取函式,實現類一種重讀進行排程的自動快取機制,
 * 並且可以隨心所欲的控制資料行快取的更新頻率:
 * 2、如果資料行記錄的是特價促銷商品的剩餘數量,並且參與促銷活動的使用者特別多的話,那麼最好每隔幾秒更新一次資料行快取:
 * 另一方面,如果資料並不經常改變,或者商品缺貨是可以接受的,那麼可以每隔幾分鐘更新一次快取。
 */
public class CacheRowsThread extends Thread {
    private Jedis conn;
    private boolean quit;

    public CacheRowsThread() {
        this.conn = new Jedis("localhost");
        this.conn.select(14);
    }

    public void quit() {
        quit = true;
    }

    public void run() {
        Gson gson = new Gson();
        while (!quit) {
            //1、嘗試獲取下一個需要被快取的資料行以及該行的排程時間戳,返回一個包含0個或一個元組列表
            Set<Tuple> range = conn.zrangeWithScores("schedule:", 0, 0);
            Tuple next = range.size() > 0 ? range.iterator().next() : null;
            long now = System.currentTimeMillis() / 1000;
            //2、暫時沒有行需要被快取,休眠50毫秒。
            if (next == null || next.getScore() > now) {
                try {
                    sleep(50);
                } catch (InterruptedException ie) {
                    Thread.currentThread().interrupt();
                }
                continue;
            }
            //3、提前獲取下一次排程的延遲時間,
            String rowId = next.getElement();
            double delay = conn.zscore("delay:", rowId);
            if (delay <= 0) {
                //4、不必在快取這個行,將它從快取中移除
                conn.zrem("delay:", rowId);
                conn.zrem("schedule:", rowId);
                conn.del("inv:" + rowId);
                continue;
            }
            //5、繼續讀取資料行
            Inventory row = Inventory.get(rowId);
            //6、更新排程時間,並設定快取值。
            conn.zadd("schedule:", now + delay, rowId);
            conn.set("inv:" + rowId, gson.toJson(row));
        }
    }
}
複製程式碼

具體的內容看我之前寫的筆記購物網站的redis相關實現(Java)文章投票網站的redis相關實現(Java).

這個Redis主題就算記錄完了,gogogo。

相關文章