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

实体分为很多类,实体的确定要通过实体类型和实体id两个属性同时确定。牛客论坛中使用到了3类实体:

1 登录

使用到的Redis命令:

bash 复制代码
set key value // 设置指定key的值为value
get key // 获取指定key的值

1.1 存储/获取验证码

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

1、将验证码存入Redis

java 复制代码
String redisKey = RedisKeyUtil.getKaptchaKey(kaptchaOwner);
redisTemplate.opsForValue().set(redisKey, text, 60, TimeUnit.SECONDS);

2、获取验证码

java 复制代码
String redisKey = RedisKeyUtil.getKaptchaKey(kaptchaOwner);
kaptcha = (String) redisTemplate.opsForValue().get(redisKey);

1.2 存储/获取登录凭证

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

1、存储登录凭证:

java 复制代码
String redisKey = RedisKeyUtil.getTicketKey(loginTicket.getTicket());
redisTemplate.opsForValue().set(redisKey, loginTicket);

2、获取登录凭证:

java 复制代码
String redisKey = RedisKeyUtil.getTicketKey(ticket);
LoginTicket loginTicket = (LoginTicket) redisTemplate.opsForValue().get(redisKey);

2 点赞

使用到的Redis命令:

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

// 以下是字符串的命令
incr key	//将key中储存的字符串数值增一
decr key	//将key中储存的字符串数值减一
get key 	// 获取字符串数值

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

java 复制代码
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();
        }
    });
}

2.2 查询某实体点赞的数量

java 复制代码
public long findEntityLikeCount(int entityType, int entityId) {
        String entityLikeKey = RedisKeyUtil.getEntityLikeKey(entityType, entityId);
        return redisTemplate.opsForSet().size(entityLikeKey);
    }

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

java 复制代码
public int findEntityLikeStatus(int userId, int entityType, int entityId) {
        String entityLikeKey = RedisKeyUtil.getEntityLikeKey(entityType, entityId);
        return redisTemplate.opsForSet().isMember(entityLikeKey, userId) ? 1 : 0;
    }

2.4 查询某个用户获得的赞

java 复制代码
public int findUserLikeCount(int userId) {
        String userLikeKey = RedisKeyUtil.getUserLikeKey(userId);
        Integer count = (Integer) redisTemplate.opsForValue().get(userLikeKey);
        return count == null ? 0 : count.intValue();
    }

3 关注

使用到的Redis命令

bash 复制代码
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中,指定区间内的成员,通过索引,分数从高到低

3.1 用户关注某个实体

某个用户关注的实体,有序集合里面存放的是entity_id,分数是时间,实体类型在key里。

可以关注某个用户,entity_type=3

可以关注某个帖子,entity_type=1

可以关注某条评论,entity_type=2

java 复制代码
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();
        }
    });
}

3.2 用户取消关注

java 复制代码
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();
        }
    });
}

3.3 查询关注的实体的数量

java 复制代码
public long findFolloweeCount(int userId, int entityType) {
    String followeeKey = RedisKeyUtil.getFolloweeKey(userId, entityType);
    return redisTemplate.opsForZSet().zCard(followeeKey);
}

3.4 查询实体的粉丝的数量

java 复制代码
public long findFollowerCount(int entityType, int entityId) {
    String followerKey = RedisKeyUtil.getFollowerKey(entityType, entityId);
    return redisTemplate.opsForZSet().zCard(followerKey);
}

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

由于有序集合没有类似集合那样sismember key memeber // 用于判断元素 member是否集合的成员。

所以只能用获取分数,判断分数是不是为空

java 复制代码
public boolean hasFollowed(int userId, int entityType, int entityId) {
    String followeeKey = RedisKeyUtil.getFolloweeKey(userId, entityType);
    return redisTemplate.opsForZSet().score(followeeKey, entityId) != null;
}

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

java 复制代码
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);

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

java 复制代码
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);

4 网站数据统计

使用到的Redis命令:

bash 复制代码
// 以下是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的个数

4.1 统计独立访客

独立访客,union

1、将指定的IP计入UV

java 复制代码
public void recordUV(String ip) {
    String redisKey = RedisKeyUtil.getUVKey(df.format(new Date()));
    redisTemplate.opsForHyperLogLog().add(redisKey, ip);
}

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

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

// 返回统计的结果
return redisTemplate.opsForHyperLogLog().size(redisKey);

4.2 统计日活跃用户

日活跃用户,or位运算

1、 将指定用户计入DAU

java 复制代码
public void recordDAU(int userId) {
    String redisKey = RedisKeyUtil.getDAUKey(df.format(new Date()));
    redisTemplate.opsForValue().setBit(redisKey, userId, true);
}

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

java 复制代码
// 进行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());
    }
});

5 缓存设计

使用到的Redis命令:

bash 复制代码
set key value // 设置指定key的值为value
get key // 获取指定key的值
del key [key ...]	 // 删除一个或多个key

5.1 优先从缓存中取值

java 复制代码
private User getCache(int userId) {
    String redisKey = RedisKeyUtil.getUserKey(userId);
    return (User) redisTemplate.opsForValue().get(redisKey);
}

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

java 复制代码
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;
}

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

java 复制代码
private void clearCache(int userId) {
    String redisKey = RedisKeyUtil.getUserKey(userId);
    redisTemplate.delete(redisKey);
}

6 热帖排行

使用到的Redis命令

bash 复制代码
sadd key element [element ...]	// 向集合key添加一个或多个元素
	spop key	// 移除并返回集合中的一个随机元素

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

6.1 记录要计算分数的帖子

java 复制代码
// 
String redisKey = RedisKeyUtil.getPostScoreKey();
redisTemplate.opsForSet().add(redisKey, postId);

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

java 复制代码
while (operations.size() > 0) {
    this.refresh((Integer) operations.pop());
}
相关推荐
阿华的代码王国16 分钟前
MySQL ------- 索引(B树B+树)
数据库·mysql
码爸37 分钟前
flink 批量压缩redis集群 sink
大数据·redis·flink
Hello.Reader44 分钟前
StarRocks实时分析数据库的基础与应用
大数据·数据库
执键行天涯1 小时前
【经验帖】JAVA中同方法,两次调用Mybatis,一次更新,一次查询,同一事务,第一次修改对第二次的可见性如何
java·数据库·mybatis
yanglamei19621 小时前
基于GIKT深度知识追踪模型的习题推荐系统源代码+数据库+使用说明,后端采用flask,前端采用vue
前端·数据库·flask
工作中的程序员1 小时前
ES 索引或索引模板
大数据·数据库·elasticsearch
严格格1 小时前
三范式,面试重点
数据库·面试·职场和发展
微刻时光2 小时前
Redis集群知识及实战
数据库·redis·笔记·学习·程序人生·缓存
单字叶2 小时前
MySQL数据库
数据库·mysql
mqiqe2 小时前
PostgreSQL 基础操作
数据库·postgresql·oracle