【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);
}
相关推荐
JosieBook16 分钟前
【数据库】时序数据库选型指南:在大数据与工业4.0时代,为何 Apache IoTDB 成为智慧之选?
大数据·数据库·时序数据库
程序员三明治17 分钟前
详解Redis锁误删、原子性难题及Redisson加锁底层原理、WatchDog续约机制
java·数据库·redis·分布式锁·redisson·watchdog·看门狗
chenzhou__26 分钟前
MYSQL学习笔记(个人)(第十五天)
linux·数据库·笔记·学习·mysql
一只自律的鸡1 小时前
【MySQL】第二章 基本的SELECT语句
数据库·mysql
liliangcsdn2 小时前
如何使用python创建和维护sqlite3数据库
数据库·sqlite
TDengine (老段)9 小时前
TDengine 数学函数 DEGRESS 用户手册
大数据·数据库·sql·物联网·时序数据库·iot·tdengine
TDengine (老段)9 小时前
TDengine 数学函数 GREATEST 用户手册
大数据·数据库·物联网·时序数据库·iot·tdengine·涛思数据
安当加密9 小时前
云原生时代的数据库字段加密:在微服务与 Kubernetes 中实现合规与敏捷的统一
数据库·微服务·云原生
爱喝白开水a9 小时前
LangChain 基础系列之 Prompt 工程详解:从设计原理到实战模板_langchain prompt
开发语言·数据库·人工智能·python·langchain·prompt·知识图谱
想ai抽9 小时前
深入starrocks-多列联合统计一致性探查与策略(YY一下)
java·数据库·数据仓库