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