巧用redis实现点赞功能,它不比mysql香吗?

2023-10-27

提到点赞,大家一想到的是不是就是朋友圈的点赞呀?其实点赞对我们来说并不陌生,我们经常会在手机软件或者网页中看到它,今天就让我们来了解一下它的实现吧。我们常见的设计思路大概分为两种:一种自然是用MySQL等数据库直接落地存储, 另外一种就是将点赞的数据保存到Redis等缓存里,在一定时间后刷回MySQL等数据库。

首先我们来说一下两种方法各自的优缺点:我们以MySQL和Redis为例。

1、直接写入数据库:

优点: 这种方法实现简单,只需完成数据库的增删改查就行;

缺点: 数据库读写压力大,如果遇到热门文章在短时间内被大量点赞的情况,直接操作数据库会给数据库带来巨大压力,影响效率。

2、使用Redis缓存:

优点: 性能高,读写速度快,缓解数据库读写的压力;

缺点: 开发复杂,不能保证数据安全性即redis挂掉的时候会丢失数据, 同时不及时同步redis中的数据, 可能会在redis内存置换的时候被淘汰掉。不过对于点赞数据我们不需要那么精确,丢失一点数据问题不大。

接下来就从以下三个方面对点赞功能做详细的介绍

•Redis 缓存设计
•数据库设计
•开启定时任务持久化存储到数据库

1、Redis缓存设计及实现

Redis的整合我们在上一篇文章中已经介绍过了,此处就不再赘述了。我们了解到,我们在做点赞的时候需要记录以下几类数据:一类是某用户被其他用户点赞的详细记录,一类是。考虑到查询与存取方便快捷,我这边采用Hash结构进行存储,存储结构如下:

(1)某用户被其他用户点赞的详细记录:MAP_USER_LIKED为键值,被点赞用户id::点赞用户id为filed,1或者0为value

(2)某用户被点赞的数量统计:MAP_USER_LIKED_COUNT为键值,被点赞用户id为filed,count为value

部分代码如下:

/**
* 将用户被其他用户点赞的数据存到redis
*/
@Override
public void saveLiked2Redis(String likedUserId, String likedPostId) {
    String key = RedisKeyUtils.getLikedKey(likedUserId, likedPostId);
    redisTemplate.opsForHash().put(RedisKeyUtils.MAP_KEY_USER_LIKED,key, LikedStatusEnum.LIKE.getCode());
}

//取消点赞
@Override
public void unlikeFromRedis(String likedUserId, String likedPostId) {
    String key = RedisKeyUtils.getLikedKey(likedUserId, likedPostId);
    redisTemplate.opsForHash().put(RedisKeyUtils.MAP_KEY_USER_LIKED,key,LikedStatusEnum.UNLIKE.getCode());
}

/**
* 将被点赞用户的数量+1
*/
@Override
public void incrementLikedCount(String likedUserId) {
    redisTemplate.opsForHash().increment(RedisKeyUtils.MAP_KEY_USER_LIKED_COUNT,likedUserId,1);
}

//-1
@Override
public void decrementLikedCount(String likedUserId) {
    redisTemplate.opsForHash().increment(RedisKeyUtils.MAP_KEY_USER_LIKED_COUNT, likedUserId, -1);
}

/**
* 获取Redis中的用户点赞详情记录
*/
@Override
public List<UserLikeDetail> getLikedDataFromRedis() {
    Cursor<Map.Entry<Object,Object>> scan = redisTemplate.opsForHash().scan(RedisKeyUtils.MAP_KEY_USER_LIKED, ScanOptions.NONE);
    List<UserLikeDetail> list = new ArrayList<>();
    while (scan.hasNext()){
        Map.Entry<Object, Object> entry = scan.next();
        String key = (String) entry.getKey();
        String[] split = key.split("::");
        String likedUserId = split[0];
        String likedPostId = split[1];
        Integer value = (Integer) entry.getValue();
        //组装成 UserLike 对象
        UserLikeDetail userLikeDetail = new UserLikeDetail(likedUserId, likedPostId, value);
        list.add(userLikeDetail);
        //存到 list 后从 Redis 中删除
        redisTemplate.opsForHash().delete(RedisKeyUtils.MAP_KEY_USER_LIKED, key);
    }
    return list;
}

/**
* 获取Redis中的用户被点赞数量
*/
@Override
public List<UserLikCountDTO> getLikedCountFromRedis() {
    Cursor<Map.Entry<Object,Object>> cursor = redisTemplate.opsForHash().scan(RedisKeyUtils.MAP_KEY_USER_LIKED_COUNT, ScanOptions.NONE);
    List<UserLikCountDTO> list = new ArrayList<>();
    while(cursor.hasNext()){
        Map.Entry<Object, Object> map = cursor.next();
        String key = (String) map.getKey();
        Integer value = (Integer) map.getValue();
        UserLikCountDTO userLikCountDTO = new UserLikCountDTO(key,value);
        list.add(userLikCountDTO);
        //存到 list 后从 Redis 中删除
        redisTemplate.opsForHash().delete(RedisKeyUtils.MAP_KEY_USER_LIKED_COUNT,key);
    }
    return list;
}

Redis存储结构如图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eLmhKsDL-1640588099427)(C:\Users\20102273\Desktop\1.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4e4pNcpn-1640588099431)(C:\Users\20102273\Desktop\2.png)]

2、数据库设计

这里我们可以和直接将点赞数据存到数据库一样,设计两张表:

(1)用户被其他用户点赞的详细记录:user_like_detail

DROP TABLE IF EXISTS `user_like_detail`;
CREATE TABLE `user_like_detail`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `liked_user_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '被点赞的用户id',
  `liked_post_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '点赞的用户id',
  `status` tinyint(1) NULL DEFAULT 1 COMMENT '点赞状态,0取消,1点赞',
  `create_time` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '创建时间',
  `update_time` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '修改时间',
  PRIMARY KEY (`id`) USING BTREE,
  INDEX `liked_user_id`(`liked_user_id`) USING BTREE,
  INDEX `liked_post_id`(`liked_post_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '用户点赞表' ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;

(2)用户被点赞的数量统计:user_like_count

DROP TABLE IF EXISTS `user_like_count`;
CREATE TABLE `user_like_count`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `like_num` int(11) NULL DEFAULT 0,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;

3、开启定时任务持久化存储到数据库

我们使用Quartz来实现定时任务,将Redis中的数据存储到数据库中,为了演示效果,我们可以设置一分钟或者两分钟存储一遍数据,这个视具体业务而定。在同步数据的过程中,我们首先要将Redis中的数据在数据库中进行查重,舍弃重复数据,这样我们的数据才会更加准确。

部分代码如下

//同步redis的用户点赞数据到数据库
@Override
@Transactional
public void transLikedFromRedis2DB() {
    List<UserLikeDetail> list = redisService.getLikedDataFromRedis();
    list.stream().forEach(item->{
        //查重
        UserLikeDetail userLikeDetail = userLikeDetailMapper.selectOne(new LambdaQueryWrapper<UserLikeDetail>()
           .eq(UserLikeDetail::getLikedUserId, item.getLikedUserId())
           .eq(UserLikeDetail::getLikedPostId, item.getLikedPostId()));
        if (userLikeDetail == null){
            userLikeDetail = new UserLikeDetail();
            BeanUtils.copyProperties(item, userLikeDetail);
            //没有记录,直接存入
            userLikeDetail.setCreateTime(LocalDateTime.now());
            userLikeDetailMapper.insert(userLikeDetail);
        }else{
            //有记录,需要更新
            userLikeDetail.setStatus(item.getStatus());
            userLikeDetail.setUpdateTime(LocalDateTime.now());
            userLikeDetailMapper.updateById(item);
        }
    });
}

@Override
@Transactional
public void transLikedCountFromRedis2DB() {
    List<UserLikCountDTO> list = redisService.getLikedCountFromRedis();
    list.stream().forEach(item->{
        UserLikeCount user = userLikeCountMapper.selectById(item.getKey());
        //点赞数量属于无关紧要的操作,出错无需抛异常
        if (user != null){
            Integer likeNum = user.getLikeNum() + item.getValue();
            user.setLikeNum(likeNum);
            //更新点赞数量
            userLikeCountMapper.updateById(user);
        }
    });
}

至此我们就实现了基于Redis的点赞功能,我们还需要注意一点:查询用户点赞情况时,需要同时查询数据库+缓存中的数据。

以上就是今天的全部知识了,想了解更多,请关注“阿Q说代码”,获取源码请回复关键字“redis点赞”。你也可以后台留言说出你的疑惑,阿Q将会在后期的文章中为你解答。每天学习一点点,每天进步一点点。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

巧用redis实现点赞功能,它不比mysql香吗? 的相关文章

  • MySQL:计算日期/时间之间的差异 - 仅在周一至周五“工作周”期间

    我需要计算开始日期 时间和结束日期 时间之间的差异 但是 我只想在 5 天的工作周内执行此操作 不包括周六 周日 做这个的最好方式是什么 我的想法是 从日期开始 我必须获取星期几 如果是工作日 那么我将添加到累加器中 如果不是 那么我不会添
  • MySQL 和 Hibernate 之间的主键自增由谁负责?

    MySQL CREATE TABLE role id role INT 11 unsigned NOT NULL AUTO INCREMENT PRIMARY KEY id role AUTO INCREMENT 1 休眠 Entity p
  • covertJSONtoSQL 在 NiFi 中返回空值

    我正在设计一项工作 使用以下命令将数据从 MySQL 中的数据库转移到另一个数据库 MySQL 执行SQL处理器随后将Avro转换为Json then 将Json转换为SQL then PutSQL如下流程图所示 将JSON转换为SQL返回
  • MySQL 通过 current_timestamp 选择上个月的数据

    直到今天 当我使用 MySQL 并需要对日期 时间执行操作时 我使用带有 unix 时间戳的 int 列 没有出现任何问题 但今天在阅读了一些指南后 我决定默认使用 current timestamp 测试时间戳列 所以我感兴趣如何按列选择
  • 使用 Gorilla 会话自定义后端有什么优势?

    我想使用 Redis 进行会话管理 但我不明白使用 Redis 作为 Gorilla 会话包的自定义后端比直接使用它有什么优势 Gorilla 会话包的链接 http www gorillatoolkit org pkg sessions
  • libmysqlclient.a 和 libmysqlclient_r.a 有什么区别?

    我应该使用哪个来链接 mysqlclient 库 它们之间有什么区别 我似乎找不到答案 谢谢 较新版本的 MySQL 客户端发行版不包含 r 版本 有些可能有从 libmyqslclient r a 到 libmyqslclient a 的
  • 如何暂停或恢复 celery 任务?

    我的项目中有一项要求 客户可以暂停或恢复正在挂起的流程 而不是流程流程 我在用网络套接字显示芹菜任务结果 但在暂停 恢复时我不明白如何设计代码 我想到的唯一方法就是revoke暂停请求中的任务 同时保留数据撤销的过程在缓存中 并稍后在res
  • PHP 和 MySQL - 高效处理多个一对多关系

    我正在寻求一些有关使用 MySQL 和 PHP 检索和显示数据的最佳方法的建议 我有 3 个表 所有一对多关系如下 Each SCHEDULE有很多覆盖每个覆盖都有很多地点 我想检索这些数据 以便它可以全部显示在单个 PHP 页面上 例如列
  • 如何在 MySQL 中测试 Select for Update

    我正在表演SELECT FOR UPDATE或 InnoDB 表的行级锁定 我的目的是只有一个请求可以读取同一行 因此 如果两个用户同时请求相同的数据 其中只有一个人获取数据 即第一个触发查询的人 但是我如何测试锁定是否已放置 因为我正在通
  • 非常大的字段会对 MySQL 数据库产生负面影响吗?

    我目前正在使用 Django 构建一个网站 并希望托管用户生物样式页面 该页面可能长达几 KB 这些字段不一定需要搜索 但在查找用户名时确实需要提供 将这些数据存储在数据库中会产生负面影响吗 如果我使用带有数据库链接的静态文本文件 我的服务
  • MySQL中如何声明变量?

    如何在mysql中声明一个变量 以便我的第二个查询可以使用它 我想写一些类似的东西 SET start 1 SET finish 10 SELECT FROM places WHERE place BETWEEN start AND fin
  • PHP MySQL 使用选项/选择 HTML 表单标签进行多重搜索查询

    我正在尝试使用两个搜索字段设置基本的 MySQL LIKE 搜索 我不想拥有它 所以它有多个可选搜索字段 例如if isset POST city isset POST name 我不知道如何用 HTML 来做到这一点
  • 如何检测Mysql/innodb中的死锁?

    我知道在 Innodb 中使用事务时不可避免地会发生死锁 并且如果应用程序代码正确处理死锁 它们是无害的 正如手册所说 只需再试一次 所以我想知道 如何检测死锁 死锁是否会发出一些特殊的 mysql 错误号 如果重要的话 我正在使用 PHP
  • 如果 Redis 已经是堆栈的一部分,为什么 Memcached 仍然与 Redis 一起使用?

    Redis 可以执行 Memcached 提供的所有操作 LRU 缓存 项目过期以及现在版本 3 x 中的集群 目前处于测试阶段 或通过 twemproxy 等工具执行 性能也类似 此外 Redis 增加了持久性 因此您无需在服务器重新启动
  • mysql自动存储记录创建时间戳

    mysql 有什么方法可以在创建记录时自动将时间戳存储在记录行中 我试图使用时间戳 数据类型 和 current timestamp 作为默认值 但后来意识到每次更新记录时都会更新 我只需要一些可以存储创建时间戳的东西 Thanks Set
  • 学说迁移后备

    我们正在使用原则迁移 当迁移包含多个操作并且其中一个操作失败时 通常会出现问题 例如 如果迁移添加了 5 个外键 其中第 5 个失败 而字段长度不同 则修复字段错误并重新生成迁移不会not修复整个问题 而现在出现一个与 4 个密钥已存在有关
  • 从Django中具有外键关系的两个表中检索数据? [复制]

    这个问题在这里已经有答案了 This is my models py file from django db import models class Author models Model first name models CharFie
  • 在 android 中建立与 MySQL 的池连接

    我需要从我的 Android 应用程序访问 MySQL 数据库 现在所有的工作都通过 DriverManager getConnection url 等等 但我必须从多个线程访问数据库 所以我必须使用连接池 问题1 是 com mysql
  • 通过触发器应用表的列权限

    现在 我有一个名为 Members 的表 其中包含内容 分为联系人数据 银行数据 现在 管理员应该能够创建 更新 删除用户 这些用户保存在另一个表中 该表只能访问管理员 用户应该获得自己的 mysql 用户帐户 管理员还应该能够设置权限 例
  • SQL 最近日期

    我需要在 php 中获取诸如 2010 04 27 之类的日期作为字符串 并在表中找到最近的 5 个日期 表中的日期保存为日期类型 您可以使用DATEDIFF http dev mysql com doc refman 5 1 en dat

随机推荐

  • android手机销售app(IDEA,SpringBoot,SSM,MySQL)+支付宝支付+全套视频教程

    本项目亮点 支付宝支付 eCharts柱状图图表数据统计 项目功能介绍 本系统包含后台管理和前端app双端系统 后台管理的功能包含 登录 退出 修改管理员信息 基本信息与头像 资源管理 角色管理 资源权限分配 字典管理 用户管理 图书管理
  • Matlab学习4-图像处理之图像加法、图像减法、加噪

    图像处理 图像加法 例图像的叠加 调亮色等 图像减法 例捕捉运动图像的轨迹 环境matlab2020 使用imadd 加 imsubtract 减 imresize 改 imnoise 图像加噪 matlab函数 imadd X Y 将两个
  • 农业温室大棚养殖系统智能监控方案

    温室大棚农作物的种植给人们的生活带来极大的便利 并得到了迅速的推广和应用 在不适宜植物生长的季节 为保证作物温室生育期和作物产量 实时地收集温度 湿度 光照 气体浓度以及土壤水分等信息并汇总物通博联智能网关上传到物通博联云 为了给农作物创造
  • WebSocket 协议使用

    WebSocket 协议实现在受控环境中运行不受信任代码的一个客户端到一个从该代码已经选择加入通信的远程主机之间的全双工通信 用于这个的安全模型是通 常由 web 浏览器使用的基于来源的安全模型 该协议包括一个打开阶段握手 接着是基本消息帧
  • 数据分析之数据预处理、分析建模、可视化

    大纲 思维导图 1 数据分析概述 1 1 简介 1 2 发展历程 1 3 应用领域 1 4 开发流程 2 数据类型 2 1 结构化与非结构化数据 2 2 定性与定量数据 2 3 截面数据与时间序列数据 3 数据来源 4 数据预处理方法 4
  • 初始vue(二)

    vue详细学习 二 class的操作 div class play judge data judge true div data msg div 1212323 div data msg div 1212323 div 不能解析 的内容 d
  • 【深度学习】Pytorch 系列教程(一):PyTorch数据结构:1、Tensor(张量):维度(Dimensions)、数据类型(Data Types)

    目录 一 前言 二 实验环境 三 PyTorch数据结构 0 分类 1 Tensor 张量 1 维度 Dimensions 0维 标量 1维 向量 2维 矩阵 3维张量 2 数据类型 Data Types 一 前言 ChatGPT PyTo
  • linux中gvim配置

    文章目录 前言 一 在哪配置 二 设置语句 三 运行结果 前言 对于在linux上工作的硬件工程师来说 换到一个新的服务器或者工作环境 首先要做的几件事中肯定有一项是设置gvim配置 这里纪录下我的常用gvim配置和注释 仅供参考 如有错误
  • mysql TRUNCATE delete

    mysql truncate 和delete 都用与删除数据表里的数据 truncate命令则是直接将全表的数据清空掉 delete命令可以不带where 可以达到同样的目的 delete通过where带上条件删除部分数据 从这可以看出de
  • Nginx_http_upstream_check_module应用

    ngx http upstream check module 该模块可以为Nginx提供主动式后端服务器健康检查的功能 该模块在Nginx 1 4 0版本以前没有默认开启 它可以在配置编译选项的时候开启 configure with htt
  • C++的特性(封装、继承、多态、抽象)的详解

    封装 封装目的 模块化 信息隐藏 封装 隐藏对象的属性和实现细节 仅对外公开接口和对象进行交互 将数据和操作数据的方法进行有机结合 是通过特性和行为的组合来创建新数据类型让接口与具体实现相隔离 C 中是通过类来实现的 为了尽量避免某个模块的
  • MIPI I3C简介

    前面的文章介绍过MIPI联盟发布的MIPI CSI DSI D PHY等接口 这一篇文章来简单聊一聊I3C 同样由MIPI联盟制定 主要用于替代传统的USRT I2C和SPI 并向下兼容I2C 由于已经有网友写过相关的文章 并且写的很不错
  • signature=462fd3702561f02c1dc8858a887d01f8,baly-20201118

    0001747079 20 000139 txt 20201119 0001747079 20 000139 hdr sgml 20201119 20201119073031 ACCESSION NUMBER 0001747079 20 0
  • EF(Entity Framework)通用DBHelper通用类,增删改查以及列表

    其中 通用类名 DBhelper 实体类 UserInfo 1 新增 2 DBHelper
  • wedo巡线机器人编程教程_这是一个机器人和编程的时代

    图中在草地上自在奔跑的机器人是波士顿动力公司 BostonDynamics 开发的类人双足机器人Atlas 由麻省理工 MIT 电子工程与计算机科学系的教授马克 雷波特在1992年创立 一直致力于将机器人变成自然界的一个新物种 经过20多年
  • Springboot集成knife4j实现风格化API文档

    Springboot集成knife4j实现风格化API文档 POM引入插件
  • GoLang之使用uber-go/dig进行依赖注入

    文章目录 GoLang之使用uber go dig斤进行依赖注入 1 依赖输注入介绍 2 main函数反面例子 3 下载DI依赖 4 main函数使用DI优化 5 注意点 GoLang之使用uber go dig斤进行依赖注入 注 本文是基
  • AtomicInteger、Unsafe类、ABA问题

    AtomicInteger Java中的AtomicInteger大家应该很熟悉 它是为了解决多线程访问Integer变量导致结果不正确所设计的一个基于多线程并且支持原子操作的Integer类 AtomicInteger内部有一个变量UnS
  • Linux 中的 chroot 命令及示例

    Linux Unix系统中的chroot命令用于更改根目录 Linux Unix 类系统中的每个进程 命令都有一个称为root 目录的当前工作目录 它更改当前正在运行的进程及其子进程的根目录 在此类修改的环境中运行的进程 命令无法访问根目录
  • 巧用redis实现点赞功能,它不比mysql香吗?

    提到点赞 大家一想到的是不是就是朋友圈的点赞呀 其实点赞对我们来说并不陌生 我们经常会在手机软件或者网页中看到它 今天就让我们来了解一下它的实现吧 我们常见的设计思路大概分为两种 一种自然是用MySQL等数据库直接落地存储 另外一种就是将点