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 zhanghuigen@cetiti.com
 * @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 zhanghuigen@cetiti.com
 * @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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值