【Redis】签到点赞和UV统计

Redis签到点赞和UV统计

点赞

点赞功能分析

需求:

  1. 同一个用户只能点赞一次,再次点击则取消点赞
  2. 如果当前用户已经点赞,则点赞按钮高亮显示(前端判断字段isLike属性)

实现步骤:

  1. 利用Redis的set集合判断是否点赞过,将用户id保存到set中
  2. 判断当前登录用户是否点赞过,赋值给isLike字段
  3. 通过Redis的set集合中Scard命令获取成员个数,即点赞次数

业务实现

LikedDTO

java 复制代码
@Data
@AllArgsConstructor
@NoArgsConstructor
public class LikedDTO {
    /**
     * 点赞数量
     */
    long likedSum;
    /**
     * 用户是否点过赞
     */
    Boolean isLiked;
}

点赞操作

java 复制代码
// 点赞操作
@Override
public String doLike() {
    String key = "RedisSessionDemo:liked";
    String phone = UserHolder.getUser().getPhone();
    // 查询是否点赞过
    Boolean isLiked = redisTemplate.opsForSet().isMember(key, phone);
    if (BooleanUtil.isTrue(isLiked)) {
        // 点赞过 -> 取消点赞
        redisTemplate.opsForSet().remove(key, phone);
        return "取消点赞成功";
    }
    // 没点赞过 -> 点赞
    redisTemplate.opsForSet().add(key, phone);
    return "点赞成功";
}

获取点赞数据

java 复制代码
// 获取点赞数据
@Override
public LikedDTO getLiked() {
    String key = "RedisSessionDemo:liked";
    Long likedNum = redisTemplate.opsForSet().size(key);
    if (likedNum == null) {
        likedNum = 0L;
    }
    UserDTO user = UserHolder.getUser();
    Boolean isLiked = false;
    if (user != null) {
        isLiked = redisTemplate.opsForSet().isMember(key, user.getPhone());
    }
    return new LikedDTO(likedNum, isLiked);
}

点赞排行

功能分析

点赞排行:类似朋友圈的点赞列表,按照点赞的先后顺序展示头像等信息。

使用 sorted set 结构,将点赞的时间戳作为分数值记录。

功能实现

修改点赞函数

java 复制代码
// 获取点赞数据
@Override
public LikedDTO getLiked2() {
    String key = "RedisSessionDemo:liked";
    Long likedNum = redisTemplate.opsForZSet().size(key);
    if (likedNum == null) {
        likedNum = 0L;
    }
    UserDTO user = UserHolder.getUser();
    boolean isLiked = false;
    if (user != null) {
        Double score = redisTemplate.opsForZSet().score(key, user.getPhone());
        isLiked = (score != null && score > 0);
    }
    return new LikedDTO(likedNum, isLiked);
}

// 点赞操作
@Override
public String doLike2() {
    String key = "RedisSessionDemo:liked";
    String phone = UserHolder.getUser().getPhone();
    // 查询是否点赞过
    Double isLiked = redisTemplate.opsForZSet().score(key, phone);
    if (isLiked != null && isLiked > 0) {
        // 点赞过 -> 取消点赞
        redisTemplate.opsForZSet().remove(key, phone);
        return "取消点赞成功";
    }
    // 没点赞过 -> 点赞
    redisTemplate.opsForZSet().add(key, phone, System.currentTimeMillis());
    return "点赞成功";
}

获取点赞列表

java 复制代码
// 获取点赞列表
@Override
public List<String> getLikedList() {
    String key = "RedisSessionDemo:liked";
    // 获取所有元素
    Set<String> set = redisTemplate.opsForZSet().range(key, 0, -1);
    if (set != null) {
        return new ArrayList<>(set);
    }
    return Collections.emptyList();
}

用户签到

BitMap用法

我们按月来统计用户签到信息,签到记录为1,未签到则记录为0。

把每一个bit位对应当月的每一天,形成了映射关系。用0和1标示业务状态,这种思路就称为位图(BitMap)。

Redis中是利用string类型数据结构实现BitMap,因此最大上限是512M,转换为bit则是 2 32 2^{32} 232 个bit位。

BitMap的操作命令有:

  1. SETBIT:向指定位置(offset)存入一个0或1
  2. GETBIT :获取指定位置(offset)的bit值
  3. BITCOUNT :统计BitMap中值为1的bit位的数量
  4. BITFIELD :操作(查询、修改、自增)BitMap中bit数组中的指定位置(offset)的值
  5. BITFIELD_RO :获取BitMap中bit数组,并以十进制形式返回
  6. BITOP :将多个BitMap的结果做位运算(与 、或、异或)
  7. BITPOS :查找bit数组中指定范围内第一个0或1出现的位置

实现签到功能

因为BitMap底层是基于String数据结构,因此其操作也都封装在字符串相关操作中了。

java 复制代码
public Boolean sign() {
    String phone = UserHolder.getUser().getPhone();
    Date date = new Date();
    String yearAndMonth = new SimpleDateFormat("yyyy:MM").format(date);
    String key = "RedisSessionDemo:user:sign:" + phone + ":" + yearAndMonth;
    int day = Integer.parseInt(new SimpleDateFormat("DD").format(date));
    // 实现签到
    redisTemplate.opsForValue().setBit(key, day, true);
    return true;
}

签到统计

连续签到:从最后一次签到开始向前统计,直到遇到第一次未签到为止的签到次数

封装SignData类

java 复制代码
@Data
@AllArgsConstructor
@NoArgsConstructor
public class SignData {
    // 月签到次数
    Integer MonthTimes;
    // 月连续签到次数
    Integer ContinuousTimes;
}

业务实现

java 复制代码
@Override
public SignData signdata() {
    // 获取 bitmap
    String phone = UserHolder.getUser().getPhone();
    Date date = new Date();
    String yearAndMonth = new SimpleDateFormat("yyyy:MM").format(date);
    String key = "RedisSessionDemo:user:sign:" + phone + ":" + yearAndMonth;
    int day = Integer.parseInt(new SimpleDateFormat("DD").format(date));
    List<Long> list = redisTemplate.opsForValue().bitField(key, BitFieldSubCommands.create().get(BitFieldSubCommands.BitFieldType.unsigned(day + 1)).valueAt(0));
    if (list == null || list.isEmpty()) {
        return new SignData(0, 0);
    }
    Long sign = list.get(0);
    if (sign == null) {
        return new SignData(0, 0);
    }

    // 统计计算
    int MonthTimes = 0;
    int ContinuousTimes = 0;
    boolean isContinuous = true;
    while (sign != 0) {
        // 连续签到
        if (isContinuous) {
            if ((sign & 1) == 1) {
                ContinuousTimes++;
            } else {
                isContinuous = false;
            }
        }
        // 月签到次数
        if ((sign & 1) == 1) {
            MonthTimes++;
        }
        sign = sign >> 1;
    }
    return new SignData(MonthTimes, ContinuousTimes);
}

UV统计

HyperLogLog

  • UV:全称Unique Visitor,也叫独立访客量,是指通过互联网访问、浏览这个网页的自然人。1天内同一个用户多次访问该网站,只记录1次。
  • PV:全称Page View,也叫页面访问量或点击量,用户每访问网站的一个页面,记录1次PV,用户多次打开页面,则记录多次PV。往往用来衡量网站的流量。

UV统计在服务端做会比较麻烦,因为要判断该用户是否已经统计过了,需要将统计过的用户信息保存。但是如果每个访问的用户都保存到Redis中,数据量会非常恐怖。

Hyperloglog(HLL)是从Loglog算法派生的概率算法,用于确定非常大的集合的基数,而不需要存储其所有值。相关算法

Redis中的HLL是基于string结构实现的,单个HLL的内存永远小于16kb,内存占用低的令人发指!

作为代价,其测量结果是概率性的,有小于0.81%的误差。不过对于UV统计来说,这完全可以忽略。

  1. 作用:做海量数据的统计工作
  2. 优点:内存占用极低、性能非常好
  3. 缺点:有一定的误差

业务实现

java 复制代码
@Test
void hyperlogTest() {
    for (int i = 0; i < 100; i++) {
        stringRedisTemplate.opsForHyperLogLog().add("hyperlogTest", "user-" + i);
    }
    Long size = stringRedisTemplate.opsForHyperLogLog().size("hyperlogTest");
    System.out.println(size);
}
相关推荐
heartbeat..2 小时前
Spring AOP 全面详解(通俗易懂 + 核心知识点 + 完整案例)
java·数据库·spring·aop
麦聪聊数据4 小时前
MySQL并发与锁:从“防止超卖”到排查“死锁”
数据库·sql·mysql
AC赳赳老秦5 小时前
DeepSeek 私有化部署避坑指南:敏感数据本地化处理与合规性检测详解
大数据·开发语言·数据库·人工智能·自动化·php·deepseek
YMatrix 官方技术社区6 小时前
YMatrix 存储引擎解密:MARS3 存储引擎如何超越传统行存、列存实现“时序+分析“场景性能大幅提升?
开发语言·数据库·时序数据库·数据库架构·智慧工厂·存储引擎·ymatrix
辞砚技术录7 小时前
MySQL面试题——索引2nd
数据库·mysql·面试
linweidong7 小时前
C++thread pool(线程池)设计应关注哪些扩展性问题?
java·数据库·c++
欧亚学术8 小时前
突发!刚刚新增17本期刊被剔除!
数据库·论文·sci·期刊·博士·scopus·发表
黑白极客8 小时前
怎么给字符串字段加索引?日志系统 一条更新语句是怎么执行的
java·数据库·sql·mysql·引擎
大厂技术总监下海9 小时前
数据湖加速、实时数仓、统一查询层:Apache Doris 如何成为现代数据架构的“高性能中枢”?
大数据·数据库·算法·apache
LeenixP9 小时前
RK3576-Debian12删除userdata分区
linux·运维·服务器·数据库·debian·开发板