日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > 数据库 >内容正文

数据库

Redis实现求交集操作结果缓存的设计方案

發布時間:2025/3/17 数据库 18 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Redis实现求交集操作结果缓存的设计方案 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

Redis的集合操作

實話說,Redis提供的集合操作是我選擇它成為內存數據庫的一個主要理由,它彌補了傳統關系型數據庫在這方面帶來的復雜度,使得只需要簡單的一個命令就可以完成一個復雜SQL任務,并且交、并、差操作在實際的業務場景中應用非常廣泛,比如快速檢索出具備一系列標簽屬性的一個集合,本篇文章將主要介紹對于求交集操作結果緩存的設計方案。

Redis命令

對于set類型,提供了sinter、sinterstore進行交集操作,對于sortedset,提供了zinter、zinterstore進行交集操作。命令十分便捷,對于不保存結果的方法sinter和zinter只需要輸入待求交集的集合的key的數組就可以得到求交集后的結果集合,對于保存結果的方法,你可以將求交集后的結果集合保存到你指定key的一個集合中。

設計方案

目標

計算給定集合群的交集集合,并對計算過程中所產生的中間結果集合進行緩存,從而達到下次計算給定集合群的一些子群集時,先查詢是否存在交集key,如果存在直接讀取,避免重復計算。

原始方法(Only)

以下我們以Redis Java客戶端庫Jedis進行舉例,Jedis提供了與Redis命令的一致接口方法,實現了交集操作,如下代碼:

public Set<String> inter(String[] keys, String keycached){int size = keys.length;Jedis jedis = new Jedis("127.0.0.1");if(size < 2){try{return jedis.smembers(keys[0]);} finally {jedis.close();}}try{jedis.sinterstore(keycached, keys);return jedis.smembers(keycached);} finally {jedis.close();}}

原始方法的問題在于只對最終的交集key進行了緩存,簡潔方便,但每次變更給定集合群時,都需要重新在此計算。

原始方法上的改造方案(All)

在原始方法上進行改造,我們可以在計算過程中依次增加計算集合群的集合數量,比如給定的集合群key{A,B,C,D},我們先計算A、B,保存一個{A,B}的交集結果,再依次計算A、B、C和A、B、C、D并對結果進行保存。?
顯然,這是個糟糕的方案,但確實完成了我們設定的目標,參考代碼如下:

private Set<String> interByAll(String... keys){Jedis jedis = new Jedis("127.0.0.1");Set<String> value = null;int interNum = 2;for(int i = 0; i < (keys.length - 1); i++){String keystored = "";String[] keyintered = new String[interNum];for(int j = 0; j < interNum; j++){keystored += (keys[j] + "&");keyintered[j] = keys[j];}if(jedis.sinterstore(keystored, keyintered) == 0){jedis.sadd(keystored, "nocache");}if(interNum == keys.length){value = jedis.smembers(keystored);}interNum++;}jedis.close();return value;}

?

遞歸方案(Recursive)

根據上面糟糕的設計方案,你應該改進實現一種遞歸方案,遞歸方案的好處是你每次只求一對集合的交集,逐步完成對整個給定集合群的交集計算,計算過程如下圖所示:

private boolean isEnd = false;private Set<String> value = null;private Set<String> getKeysWithInner(String[] keys, String srckey, Jedis jedis, int i){String key = null;if(srckey == null){//表示為第一次求交集srckey = keys[i++];key = keys[i++];} else {key = keys[i++];}String keystored = srckey + "&" + key;//生成緩存keyif(jedis.sinterstore(keystored, srckey, key) == 0){jedis.sadd(keystored, "nocache");}if(i == keys.length){ //當與最后一個key求交集后,返回結果,并跳出遞歸調用value = jedis.smembers(keystored);isEnd = true;}while(!isEnd){//遞歸調用,一對key集合求交集 getKeysWithInner(keys, keystored, jedis, i);}return value;}public Set<String> interByRecursive(String... keys){int size = keys.length;Jedis jedis = new Jedis("127.0.0.1");if(size < 2){try{return jedis.smembers(keys[0]);} finally {jedis.close();}}try{return getKeysWithInner(keys, null, jedis, 0);} finally {isEnd = false;jedis.close();}}

?

該方案的優勢不僅僅是對計算過程進行了緩存,而且,每次都只是完成一對集合的計算,計算量顯著降低。

Fork-Join方案(Fork-Join)

一寫遞歸方案,你就可以直接想到使用Fork-Join框架進行改造,并使其并行化,計算過程如下圖所示:

?

InterTask類:

public class InterTask extends RecursiveTask<String>{private String[] keys = null;private static final int THRESHOLD = 3;public InterTask(String[] keys){this.keys = keys;}private static String genKeyinnered(String... keys){StringBuilder sb = new StringBuilder();for(String key : keys){sb.append(key);sb.append("&");}return sb.toString().substring(0, sb.length() - 1);}@Overrideprotected String compute() {int size = keys.length;if(size < THRESHOLD){//當keys數組中元素數為2個或1個時,計算交集,并退出遞歸if(size < 2){return keys[0];}Jedis jedis = new Jedis("127.0.0.1");try{jedis.sinterstore(genKeyinnered(keys), keys[0], keys[1]);} finally {jedis.close();}return genKeyinnered(keys);} else {//取keys數組的中值進行分治算法String[] leftkeys = new String[size / 2];String[] rightkeys = new String[size - (size / 2)];//按中值拆分keys數組for(int i = 0; i < size; i++){if(i < leftkeys.length){leftkeys[i] = keys[i];} else {rightkeys[i - leftkeys.length] = keys[i];}}InterTask lefttask = new InterTask(leftkeys);InterTask righttask = new InterTask(rightkeys);lefttask.fork();righttask.fork();//取得從遞歸中返回的一對存儲交集結果的keyString left = lefttask.join();String right = righttask.join();Jedis jedis = new Jedis("127.0.0.1");try{jedis.sinterstore(left + "&" + right, left, right);} finally {jedis.close();}return left + "&" + right;}}}

?

這里運用了最基礎的分治算法思想,逐步將一個大的給定集合拆解為若干個成對的集合進行交集計算。?
調用方法:

public Set<String> interByForkJoin(String... keys){Set<String> value = null;Jedis jedis = new Jedis("127.0.0.1");InterTask task = new InterTask(keys);ForkJoinPool forkJoinPool = new ForkJoinPool();Future<String> result = forkJoinPool.submit(task);try {String key = result.get();value = jedis.smembers(key);} catch (InterruptedException e) {e.printStackTrace();} catch (ExecutionException e) {e.printStackTrace();} finally {jedis.close();}return value;}

?

測試

測試數據準備

我們這里準備100個集合,每個給定集合包含1000000個元素,參考如下代碼:

Jedis jedis = new Jedis("127.0.0.1");jedis.flushAll();String token = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";Random rand = new Random();String[] keys = new String[100];for(int i = 0; i < 100; i++){keys[i] = "ct" + i;String[] values = new String[1000000];for(int j = 0; j < 1000000; j++){StringBuilder sb = new StringBuilder();for(int k = 0;k < 2; k++){sb.append(token.charAt(rand.nextInt(62)));}values[j] = sb.toString();}jedis.sadd(keys[i], values);}jedis.close();

測試結果

我們分別對25、50、75、100的給定集合進行交集計算,測試結果如下:

我們可以清楚的看到,All方案是多么的糟糕,剔除All方案的結果:

總體來說Only方案和Recursive方案不分伯仲,但在相對較小的給定合集計算場景下,Recursive存在優勢,而且其進行了計算過程結果的緩存。?
對于Fork-Join方案表示比較遺憾,當然這里可以采用另外一種更優的分解算法完成并行過程,但是就Redis本身作為通過單線程epoll模型實現的異步IO來說,可能客戶端的并行計算在服務端仍然被串行化處理,另外,分治算法拆分數組的時間損耗也不能忽略。

轉載:https://blog.csdn.net/xreztento/article/details/53289193

總結

以上是生活随笔為你收集整理的Redis实现求交集操作结果缓存的设计方案的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。