当前位置:   article > 正文

牛客论坛项目中使用到Redis的地方总结

牛客论坛项目中使用到Redis的地方总结

在这里插入图片描述

实体分为很多类,实体的确定要通过实体类型和实体id两个属性同时确定。牛客论坛中使用到了3类实体:
在这里插入图片描述

在这里插入图片描述

1 登录

使用到的Redis命令

set key value // 设置指定key的值为value
get key // 获取指定key的值
  • 1
  • 2

1.1 存储/获取验证码

验证码文本,登录前,用户根据验证码图片输入验证码文本,用户提交登录,服务器会将用户输入的验证码文本于缓存在Redis中的验证码文本做字符串比较。

1、将验证码存入Redis

String redisKey = RedisKeyUtil.getKaptchaKey(kaptchaOwner);
redisTemplate.opsForValue().set(redisKey, text, 60, TimeUnit.SECONDS);
  • 1
  • 2

2、获取验证码

String redisKey = RedisKeyUtil.getKaptchaKey(kaptchaOwner);
kaptcha = (String) redisTemplate.opsForValue().get(redisKey);
  • 1
  • 2

1.2 存储/获取登录凭证

登录成功后,用户获得登录凭证,里面存储了用户id、凭证过期时间、状态,用于登录和登出功能。

1、存储登录凭证:

String redisKey = RedisKeyUtil.getTicketKey(loginTicket.getTicket());
redisTemplate.opsForValue().set(redisKey, loginTicket);
  • 1
  • 2

2、获取登录凭证:

String redisKey = RedisKeyUtil.getTicketKey(ticket);
LoginTicket loginTicket = (LoginTicket) redisTemplate.opsForValue().get(redisKey);
  • 1
  • 2

2 点赞

使用到的Redis命令:

// 以下是集合的命令
sadd key element [element …] // 向集合key添加一个或多个元素
srem key element [element …] // 移除集合key中的一个或多个元素
sismember key memeber // 用于判断元素 member是否集合的成员。
scard key  // 获取集合的成员数

// 以下是字符串的命令
incr key	//将key中储存的字符串数值增一
decr key	//将key中储存的字符串数值减一
get key 	// 获取字符串数值
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

2.1 用户给实体点赞 / 取消点赞

public void like(int userId, int entityType, int entityId, int entityUserId) {
    redisTemplate.execute(new SessionCallback() {
        @Override
        public Object execute(RedisOperations operations) throws DataAccessException {
            String entityLikeKey = RedisKeyUtil.getEntityLikeKey(entityType, entityId);
            String userLikeKey = RedisKeyUtil.getUserLikeKey(entityUserId);

            boolean isMember = operations.opsForSet().isMember(entityLikeKey, userId);

            // 开始一个Redis事务
            operations.multi();

            if (isMember) {
                operations.opsForSet().remove(entityLikeKey, userId);
                operations.opsForValue().decrement(userLikeKey);
            } else {
                operations.opsForSet().add(entityLikeKey, userId);
                operations.opsForValue().increment(userLikeKey);
            }

            // 执行Redis事务,并返回结果
            return operations.exec();
        }
    });
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

2.2 查询某实体点赞的数量

public long findEntityLikeCount(int entityType, int entityId) {
        String entityLikeKey = RedisKeyUtil.getEntityLikeKey(entityType, entityId);
        return redisTemplate.opsForSet().size(entityLikeKey);
    }
  • 1
  • 2
  • 3
  • 4

2.3 查询某人对某实体的点赞状态

public int findEntityLikeStatus(int userId, int entityType, int entityId) {
        String entityLikeKey = RedisKeyUtil.getEntityLikeKey(entityType, entityId);
        return redisTemplate.opsForSet().isMember(entityLikeKey, userId) ? 1 : 0;
    }
  • 1
  • 2
  • 3
  • 4

2.4 查询某个用户获得的赞

public int findUserLikeCount(int userId) {
        String userLikeKey = RedisKeyUtil.getUserLikeKey(userId);
        Integer count = (Integer) redisTemplate.opsForValue().get(userLikeKey);
        return count == null ? 0 : count.intValue();
    }
  • 1
  • 2
  • 3
  • 4
  • 5

3 关注

使用到的Redis命令

zadd key score member [score member …]	// 向有序集合key添加一个或多个成员,或者更新已存在成员的分数
zrem key member [member …]	// 移除有序集合key中的一个或多个成员
zcard key	// 获取有序集合key的成员数
zscore key member	//返回有序集合key中,成员member的分数
zrange key start end [withscores]	//返回有序集合key中,指定区间内的成员
zrevrange key start end [withscores]	//返回有序集合key中,指定区间内的成员,通过索引,分数从高到低
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

3.1 用户关注某个实体

某个用户关注的实体,有序集合里面存放的是entity_id,分数是时间,实体类型在key里。
可以关注某个用户,entity_type=3
可以关注某个帖子,entity_type=1
可以关注某条评论,entity_type=2

public void follow(int userId, int entityType, int entityId) {
    redisTemplate.execute(new SessionCallback() {
        @Override
        public Object execute(RedisOperations operations) throws DataAccessException {
            String followeeKey = RedisKeyUtil.getFolloweeKey(userId, entityType);
            String followerKey = RedisKeyUtil.getFollowerKey(entityType, entityId);

            operations.multi();

            operations.opsForZSet().add(followeeKey, entityId, System.currentTimeMillis());
            operations.opsForZSet().add(followerKey, userId, System.currentTimeMillis());

            return operations.exec();
        }
    });
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

3.2 用户取消关注

public void unfollow(int userId, int entityType, int entityId) {
    redisTemplate.execute(new SessionCallback() {
        @Override
        public Object execute(RedisOperations operations) throws DataAccessException {
            String followeeKey = RedisKeyUtil.getFolloweeKey(userId, entityType);
            String followerKey = RedisKeyUtil.getFollowerKey(entityType, entityId);

            operations.multi();

            operations.opsForZSet().remove(followeeKey, entityId);
            operations.opsForZSet().remove(followerKey, userId);

            return operations.exec();
        }
    });
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

3.3 查询关注的实体的数量

public long findFolloweeCount(int userId, int entityType) {
    String followeeKey = RedisKeyUtil.getFolloweeKey(userId, entityType);
    return redisTemplate.opsForZSet().zCard(followeeKey);
}
  • 1
  • 2
  • 3
  • 4

3.4 查询实体的粉丝的数量

public long findFollowerCount(int entityType, int entityId) {
    String followerKey = RedisKeyUtil.getFollowerKey(entityType, entityId);
    return redisTemplate.opsForZSet().zCard(followerKey);
}
  • 1
  • 2
  • 3
  • 4

3.5 查询当前用户是否已关注该实体

由于有序集合没有类似集合那样sismember key memeber // 用于判断元素 member是否集合的成员。
所以只能用获取分数,判断分数是不是为空

public boolean hasFollowed(int userId, int entityType, int entityId) {
    String followeeKey = RedisKeyUtil.getFolloweeKey(userId, entityType);
    return redisTemplate.opsForZSet().score(followeeKey, entityId) != null;
}
  • 1
  • 2
  • 3
  • 4

3.6 查询某用户关注的人(关注列表) ,分页展示

public List<Map<String, Object>> findFollowees(int userId, int offset, int limit) {
    String followeeKey = RedisKeyUtil.getFolloweeKey(userId, ENTITY_TYPE_USER);
    Set<Integer> targetIds = redisTemplate.opsForZSet().reverseRange(followeeKey, offset, offset + limit - 1);

  • 1
  • 2
  • 3
  • 4

3.7 查询某用户的粉丝 (粉丝列表),分页展示

public List<Map<String, Object>> findFollowers(int userId, int offset, int limit) {
    String followerKey = RedisKeyUtil.getFollowerKey(ENTITY_TYPE_USER, userId);
    Set<Integer> targetIds = redisTemplate.opsForZSet().reverseRange(followerKey, offset, offset + limit - 1);
  • 1
  • 2
  • 3

4 网站数据统计

使用到的Redis命令:

// 以下是HyperLogLog的命令
pfadd key element [element ...] // 向HyperLogLog添加元素
pfcount key [key ..]  // 计算独立用户数
pfmerge destkey sourcekey [sourcekey ...] // 求出多个HyperLogLog的并集并复制给destkey

// 以下是bitmaps的命令
setbit key offset value // 设置第offset个位置的值
getbit key offset   // 获取第offset个位置的值
bitop or destkey key [key ...] //多个bitmaps做Or运算
bitcount key [start end] // 获取bitmaps指定范围值为1的个数
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

4.1 统计独立访客

独立访客,union
1、将指定的IP计入UV

public void recordUV(String ip) {
    String redisKey = RedisKeyUtil.getUVKey(df.format(new Date()));
    redisTemplate.opsForHyperLogLog().add(redisKey, ip);
}
  • 1
  • 2
  • 3
  • 4

2、统计指定日期范围内的UV

// 合并这些数据
String redisKey = RedisKeyUtil.getUVKey(df.format(start), df.format(end));
redisTemplate.opsForHyperLogLog().union(redisKey, keyList.toArray());

// 返回统计的结果
return redisTemplate.opsForHyperLogLog().size(redisKey);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

4.2 统计日活跃用户

日活跃用户,or位运算
1、 将指定用户计入DAU

public void recordDAU(int userId) {
    String redisKey = RedisKeyUtil.getDAUKey(df.format(new Date()));
    redisTemplate.opsForValue().setBit(redisKey, userId, true);
}
  • 1
  • 2
  • 3
  • 4

2、 统计指定日期范围内的DAU

// 进行OR运算
return (long) redisTemplate.execute(new RedisCallback() {
    @Override
    public Object doInRedis(RedisConnection connection) throws DataAccessException {
        String redisKey = RedisKeyUtil.getDAUKey(df.format(start), df.format(end));
        connection.bitOp(RedisStringCommands.BitOperation.OR,
                redisKey.getBytes(), keyList.toArray(new byte[0][0]));
        return connection.bitCount(redisKey.getBytes());
    }
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

5 缓存设计

使用到的Redis命令:

set key value // 设置指定key的值为value
get key // 获取指定key的值
del key [key …]	 // 删除一个或多个key
  • 1
  • 2
  • 3

5.1 优先从缓存中取值

private User getCache(int userId) {
    String redisKey = RedisKeyUtil.getUserKey(userId);
    return (User) redisTemplate.opsForValue().get(redisKey);
}
  • 1
  • 2
  • 3
  • 4

5.2 取不到时初始化缓存数据

private User initCache(int userId) {
    User user = userMapper.selectById(userId);
    String redisKey = RedisKeyUtil.getUserKey(userId);
    redisTemplate.opsForValue().set(redisKey, user, 3600, TimeUnit.SECONDS);
    return user;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

5.3 数据变更时清除缓存数据

private void clearCache(int userId) {
    String redisKey = RedisKeyUtil.getUserKey(userId);
    redisTemplate.delete(redisKey);
}
  • 1
  • 2
  • 3
  • 4

6 热帖排行

使用到的Redis命令

sadd key element [element …]	// 向集合key添加一个或多个元素
	spop key	// 移除并返回集合中的一个随机元素

  • 1
  • 2
  • 3

里面存放的是帖子id,用于启动定时任务计算帖子分数,有哪些帖子要计算分数。

6.1 记录要计算分数的帖子

// 
String redisKey = RedisKeyUtil.getPostScoreKey();
redisTemplate.opsForSet().add(redisKey, postId);
  • 1
  • 2
  • 3

## 6.1 获取所有要计算分数的帖子

while (operations.size() > 0) {
    this.refresh((Integer) operations.pop());
}
  • 1
  • 2
  • 3
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/很楠不爱3/article/detail/404061
推荐阅读
相关标签
  

闽ICP备14008679号