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

文章介绍了在牛客论坛中使用Redis进行实体类型和ID的确定,涉及登录验证、验证码管理、用户行为(如点赞、关注)的存储与检索,以及数据统计(UV、DAU)和缓存设计。还讨论了如何通过Redis实现热帖排行榜的动态更新。
摘要由CSDN通过智能技术生成

在这里插入图片描述

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

在这里插入图片描述

1 登录

使用到的Redis命令:

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

1.1 存储/获取验证码

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

1、将验证码存入Redis

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

2、获取验证码

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

1.2 存储/获取登录凭证

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

1、存储登录凭证:

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

2、获取登录凭证:

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

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 	// 获取字符串数值

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();
        }
    });
}

2.2 查询某实体点赞的数量

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

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;
    }

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();
    }

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

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();
        }
    });
}

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();
        }
    });
}

3.3 查询关注的实体的数量

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

3.4 查询实体的粉丝的数量

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

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;
}

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);

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);

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的个数

4.1 统计独立访客

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

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

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

// 合并这些数据
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

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

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());
    }
});

5 缓存设计

使用到的Redis命令:

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

5.1 优先从缓存中取值

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

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;
}

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

private void clearCache(int userId) {
    String redisKey = RedisKeyUtil.getUserKey(userId);
    redisTemplate.delete(redisKey);
}

6 热帖排行

使用到的Redis命令

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

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

6.1 记录要计算分数的帖子

// 
String redisKey = RedisKeyUtil.getPostScoreKey();
redisTemplate.opsForSet().add(redisKey, postId);

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

while (operations.size() > 0) {
    this.refresh((Integer) operations.pop());
}
仿牛客论坛Redis主要用于两个方面的功能:缓存用户信息和生成验证码。 首先,服务器会生成验证码,将这个随机字符串作为Redis的key,将生成的验证码字符串作为value储在Redis。这个验证码会在客户端的cookie里,并且Redis只会保这个验证码的信息60秒钟。 其次,Redis还用于缓存用户信息。在登录时,服务器会生成一个登录凭证,即LoginTicket,然后将这个LoginTicket储在Redis。每次请求时,拦截器会拦截这个LoginTicket,从Redis获取相应的登录凭证信息。 当用户退出登录时,服务器会将这个登录凭证的状态设置为1,表示已经注销。然后将这个更新后的登录凭证储在Redis总结来说,仿牛客论坛Redis主要用于缓存用户信息和生成验证码。通过使用Redis,可以提高系统的性能和效率,减轻数据库的负载压力。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [4.7仿牛客社区项目——Redis优化登录模块(验证码、登录凭证、缓存用户信息)](https://blog.csdn.net/Doreen_FF/article/details/118274468)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值