巧用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香吗? 的相关文章

  • 跨水平服务器计算 socket.io 用户数

    我有多个使用 redisstore 水平扩展的 socket io 服务器 我已经有效地设置了房间 并且能够成功地跨服务器广播到房间等 现在我正在尝试构建一个状态页面 但我无法弄清楚如何简单地计算跨服务器连接的用户数量所有服务器 io so
  • NodeJS MySQL - 如何知道连接是否释放

    我正在开发 NodeJS MySQL Web API 我在用mysql https www npmjs com package mysqlnpm 模块 我想知道连接是否已释放 是否有任何函数或变量 喜欢 if connection isRe
  • 将波斯语字符串发送到mysql

    我正在尝试发送Persian字符串到mysql数据库 但它将数据保存为这样的 D8 AC D8 AF DB 8C D8 AF 英文字符串没有问题 var new this val ajax url url new type GET succ
  • 如何从准备好的语句中获取标量结果?

    是否可以将准备好的语句的结果设置为变量 我正在尝试创建以下存储过程 但失败了 第 31 行出现错误 1064 42000 您的 SQL 语法有错误 检查与您的 MySQL 服务器版本相对应的手册 了解在 stmt USING m c a 附
  • MYSQL:如何从姓氏中找到player_id?

    我现在尝试使用非标准化 摘要 表中的数据填充 testMatch 表 如下 测试匹配表 Field Type Null Key Default Extra match id int 11 NO PRI NULL match date dat
  • MySQL 多索引与多列索引进行搜索

    在我正在编写的软件中 它能够搜索给定的表以获取信息 搜索表单有 5 个字段 当然所有字段都对应于表中的不同列 但所有字段都是可选的 我的问题是关于多列索引是否有效以及为其构建查询的正确方法 如果我有一个跨 5 列的索引 并且我构建了一个查询
  • MySQL 无法使用 PHP 连接到本地主机上的服务器

    我正在使用 XAMPP 1 7 2 可以通过 cmd 和 SQLYog 连接到 MySQL 但不能以编程方式连接 这段 PHP 代码 conn mysql connect localhost root if conn die Could n
  • GROUP_CONCAT 逗号分隔符 - MySQL

    我有一个疑问 我在哪里使用GROUP CONCAT和自定义分隔符 因为我的结果可能包含逗号 这一切都运行良好 但它仍然以逗号分隔 所以我的输出是 Result A Result B Result C 我怎样才能做到这一点 输出是 Resul
  • Redis 与 SQL Server 性能对比

    应用程序性能是使用缓存而不是关系数据库的主要原因之一 因为它以键值对的形式将数据存储在内存中 所以我们可以将经常访问的不经常更改的数据存储在缓存中 从缓存中读取比从数据库中读取要快得多 Redis 是分布式缓存市场上最好的解决方案之一 我正
  • 使用存储过程并发访问MySQL数据库

    我有一个存储过程 它将读取然后增加数据库中的值 许多程序同时使用这个特定的过程 我担心并发问题 特别是读写器问题 有人可以建议我任何可能的解决方案吗 thanks 首先 正如另一篇文章中所述 使用 InnoDB 从 MySQL 5 5 开始
  • 浏览器关闭后从数据库中删除

    我正在开发一个电子商务应用程序 但问题是 当用户将产品添加到购物车并在订购前关闭浏览器时 购物车会带走所有产品 所有购物车项目都保存在表中 如果用户关闭浏览器而不订购 我只想刷新购物车 您可以使用 Javascript 事件捕获浏览器关闭并
  • MySql 完全联接(联合)和多个日期列的排序

    一个相当复杂的 sql 查询 我可能使它变得更加困难 我有两张桌子 消息 newsid 日期时间 新闻文本 图片 图片ID 日期时间 imgPath 两者没有关系 我只是在新闻 图片创建的日期之前加入 到目前为止的 SQL SELECT F
  • MySQL 多个 IN 条件对同一个表进行子查询

    我有多个带有子查询的 IN 条件 SELECT S name S email FROM something S WHERE 1 NOT IN SELECT id FROM tags WHERE somethingId S id AND 2
  • 工厂模式数据库连接

    我正在尝试使用 MySQL 实现数据库连接上的工厂模式 SQL Server 面临奇怪的错误 你调用的对象是空的 在 SQL 命令对象上 internal class SqlServerDB IDatabase private SqlCon
  • 使用 JOIN 和 UNION 合并不同表中的记录

    我需要创建一个查询来组合两个表中的数据 我认为可能是 JOIN 和 UNION 的组合 在此示例中 我需要列出状态处于活动状态的所有姓名 仅一次 并将他们的葡萄酒 苏打水 晚餐 甜点和水果偏好组合起来 按姓名排序 我不确定单独的 JOIN
  • StackExchange.Redis 事务方法冻结

    我有这段代码用于在 Stackexchange Redis 中添加对象和索引字段 事务冻结线程中的所有方法 Why var transaction Database CreateTransaction this line freeze th
  • ON DUPLICATE KEY UPDATE 的自动增量过多

    我有一个包含列的基本表 id 主要是AI 名称 唯一 etc 如果唯一列不存在 则插入该行 否则更新该行 INSERT INTO pages name etc VALUES bob randomness ON DUPLICATE KEY U
  • 如何将另一列的整数值添加到日期列?

    我试图将整数添加到日期 但出现以下错误 1064 你的 SQL 语法有错误 检查与您的 MySQL 服务器版本相对应的手册 了解在第 6 行的 wp OrderDate INTERVAL WPProduct Duration DAY AS
  • $_SESSION 中保存大量信息可以吗?

    我需要存储许多数组 SESSION以防止从 MySQL 检索信息 可以吗 其中 太多 的信息有多少 SESSION还是没有 太多 谢谢 附 或者更好地使用http php net manual en book memcache php ht
  • 为什么运行 docker 容器后 mysql 数据所有权更改为 systemd-journal-remote

    我的mysql数据库存储在 home mysql代替 var lib mysql 该目录曾经属于mysql 但是 当我运行命令时docker compose up使用这个 yml 文件 version 3 services mariadb

随机推荐

  • 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等数据库直接落地存储 另外一种就是将点