Redis之zset常用命令及好友關係設置簡易實現札記

1、Redis有序集的使用

    不管是排行榜、關注他人、取關他人或好友推薦等功能的實現,背後還是利用Redis的有序數列集合(zset)及相關命令來實現。

先回顧排行榜實現原理:
    有序集合由三部分組成,KEY(鍵)、score(成員的得分)、member(成員)。有序集合的每一項,都是以鍵值對的形式存儲,每一項都有一個分數。有序集合會根據score自動排序。利用這個特性,就可實現排行榜。
score 是數字類型,可以是整形也可以是浮點型。相同分值的情況下,redis是按照 member 的 ASCII碼進行排序。

拋出一個小疑問:
    假如排行榜的評分標準不僅僅單比較一個屬性,而是多屬性的比較時(如:相同分值下再按照先來後到的順序排序),應該怎麼辦?
    策略:對評分過程做細節處理,將 score進行改造,同時記錄得分與時間信息。實現方式有以下兩種:

  • 重新拼接一個得分 + 時間差的整數score。
  • 重新拼接一個得分 + 時間差的浮點數score。整數部分使用得分,小數部分使用時間差。注:1、浮點數時,小數點前後位數和不要超過16位,最好15位。超過16位後,score值存入redis,會發生精度丟失。2、時間差由unix時間戳生成

redis有序集zset常用命令:

//獲取整個排行榜,分值遞減排列,跟ZRANGE作用相反
ZREVRANGE yourkey 0 -1 withscores

//獲取排行榜前n名,分值遞減排列
ZREVRANGE yourkey 0 n-1 withscores

//添加一個或多個成員到有序集合,或者如果它已經存在更新其分數
ZADD key score1 member1 [score2 member2]
 
//由索引返回一個成員範圍的有序集合(分值遞增)。
ZRANGE key start stop [WITHSCORES]
 
//獲取給定有序集合中的成員及相關聯的分值
ZSCORE key member
 
//獲取成員的排名
ZRANK key member

//移除某個成員
ZREM key member

//統計集合內的成員數
ZCARD key

//返回指定範圍元素的個數,key後面加(min max)or (min max  or min max) or min max 可設置開閉區間
ZCOUNT key min max

//獲取兩個或多個集合的交集,時間複雜度: O(N * M), N 爲給定集合當中基數最小的集合, M 爲給定集合的個數。
ZINTER key1 key2 [key3···]

//簡單使用(將key1和key2的交集存儲於key3):
ZINTERSTORE key3 key1 key2

//取兩個或多個集合的交集,複雜使用:
//numkeys指定key的數量,必須
//WEIGHTS選項,與前面設定的key對應,對應key中每一個score都要乘以這個權重
//AGGREGATE選項,指定交集的聚合方式
//SUM:將所有集合中某一個元素的score值之和作爲結果集中該成員的score值
//MIN:將所有集合中某一個元素的score值中最小值作爲結果集中該成員的score值
//MAX:將所有集合中某一個元素score值中最大值作爲結果集中該成員的score值
ZINTERSTORE destination numkeys key [key …][WEIGHTS weight [weight …]] [AGGREGATE SUM|MIN|MAX]

2、簡單實現好友關係設置

  • 分析
         現在很多社交都有關注或取關等功能,若僅僅是獲取用戶的一些粉絲或所關注的人,可採用傳統數據庫實現類似功能,實現較容易, 但若是想要查詢出兩個、甚至多個用戶共同關注了哪些人,或者想要查詢兩個或多個用戶的共同粉絲的話就會很麻煩,效率也不會很高。但是如果用redis去做的話,得益於redis封裝了多個集合的交集、並集、差集等操作。實現起來就會相當簡單高效。只需爲每個用戶設置兩個集合即可,fans_userID集合表示用戶擁有的粉絲集合,following_userID集合表示用戶所關注的人結合。後續操作如下:

  • 核心邏輯代碼如下:

     首先添加相關依賴,再定義一個簡易redis工具類

/**
 * @author [email protected]
 * @since 0.1.0
 **/
public final class RedisUtil {
    
    private static String ADDR = "localhost";
    
    private static int PORT = 6379;
    
    private static String AUTH = "admin";
    
    //控制一個pool最多有多少個狀態爲idle(空閒的)的jedis實例,默認值8。
    private static int MAX_IDLE = 200;
    
    //超時時間
    private static int TIMEOUT = 10000;
    
    //是否提前進行validate操作
    private static boolean TEST_ON_BORROW = true;
    
    private static JedisPool jedisPool = null;
    
    static {
        try {
            JedisPoolConfig config = new JedisPoolConfig();
            config.setMaxIdle(MAX_IDLE);
            config.setTestOnBorrow(TEST_ON_BORROW);
            jedisPool = new JedisPool(config, ADDR, PORT, TIMEOUT, AUTH);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public synchronized static Jedis getJedis() {
        try {
            if (jedisPool != null) {
                return jedisPool.getResource();
            } else {
                return null;
            }
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    @SuppressWarnings("deprecation")
    public static void returnResource(final Jedis jedis) {
        if (jedis != null) {
            jedisPool.returnResource(jedis);
        }
    }
}

     封裝簡單的關注工具類FollowUtil,
     根據redis相關命令實現關注、獲取粉絲、獲取共同好友等功能,詳見註釋:

/**
 * @author [email protected]
 * @since 0.1.0
 **/
public class FollowUtil {
    
    private static final String FOLLOWING = "FOLLOWING_";
    
    private static final String FANS = "FANS_";
    
    private static final String COMMON_KEY = "COMMON_FOLLOWING_";

    /**
     * 關注或者取消關注
     */    
    public static int addOrRelease(String userId, String followingId) {
        if (userId == null || followingId == null) {
            return -1;
        }
        // 0 = 取消關注 1 = 關注
        int isFollow = 0; 
        Jedis jedis = RedisUtil.getJedis();
        String followingKey = FOLLOWING + userId;
        String fansKey = FANS + followingId;        
        if (jedis.zrank(followingKey, followingId) == null) {
            // 說明userId沒有關注過followingId
            jedis.zadd(followingKey, System.currentTimeMillis(), followingId);
            jedis.zadd(fansKey, System.currentTimeMillis(), userId);
            isFollow = 1;
        } else { 
            // 取消關注
            jedis.zrem(followingKey, followingId);
            jedis.zrem(fansKey, userId);
        }
        return isFollow;
    }

    /**
     * 驗證兩個用戶之間的關係
     * 0: 沒關係 
     * 1: 自己 
     * 2: userId關注了otherUserId 
     * 3: otherUserId是userId的粉絲 
     * 4: 互相關注
     * @param userId userId
     * @param otherUserId otherUserId
     * @return int
     */
    public int checkRelations (String userId, String otherUserId) {
        if (userId == null || otherUserId == null) {
            return 0;
        }
        if (userId.equals(otherUserId)) {
            return 1;
        }
        Jedis jedis = RedisUtil.getJedis();
        String followingKey = FOLLOWING + userId;
        int relation = 0;
        // userId是否關注otherUserId
        if (jedis.zrank(followingKey, otherUserId) != null) { 
            relation = 2;
        }
        String fansKey = FANS + userId;
        // userId粉絲列表中是否有otherUserId
        if (jedis.zrank(fansKey, userId) != null) {
            relation = 3;
        }
        if ((jedis.zrank(followingKey, otherUserId) != null)
                && jedis.zrank(fansKey, userId) != null) {
            relation = 4;
        }
        return relation;
    }

    /**
     * 獲取用戶所有關注的人
     * @param userId userId
     * @return set
     */
    public static Set<String> findFollowings(String userId) {
        return findSet(FOLLOWING + userId);
    }
    
    /**
     * 獲取用戶所有粉絲
     * @param userId userId
     * @return set
     */
    public static Set<String> findFans(String userId) {
        return findSet(FANS + userId);
    }
    
    /**
     * 獲取兩個共同關注的人
     * @param userId userId
     * @param otherUserId otherUserId
     * @return set
     */
    public static Set<String> findCommonFollowing(String userId, String otherUserId) {
        if (userId == null || otherUserId == null) {
            return new HashSet<>();
        }
        Jedis jedis = RedisUtil.getJedis();
        String commonKey = COMMON_KEY + userId + "_" + otherUserId;
        // 取交集並存於鍵爲commonKey的set中
        jedis.zinterstore(commonKey, FOLLOWING + userId, FOLLOWING + otherUserId);
        Set<String> result = jedis.zrange(commonKey, 0, -1);
        jedis.del(commonKey);
        return result;
    }

    /**
     * 根據key獲取有序集set
     * @param key key
     * @return set
     */
    private static Set<String> findSet(String key) {
        if (key == null) {
            return new HashSet<>();
        }
        Jedis jedis = RedisUtil.getJedis();
        // 按照score從大到小排序
        return jedis.zrevrange(key, 0, -1); 
    }
}
  • 好友推薦的實現
    採用關聯規則挖掘算法實現好友推薦,典型的推薦算法如:Apriori、FP-Growth及相關改進算法。

3 參考

https://blog.csdn.net/chengqiuming/article/details/79189955
https://blog.csdn.net/u013239111/article/details/81201404

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章