点赞业务对MySQL和Redis和MongoDB理解

点赞

点赞业务比较频繁,很多人业务可能都会有这个,比如:博客,视频,文章,动态,评论等,但是不应该是核心业务,不应该大量地请求MySQL数据库,给数据库造成大量的资源消耗,MySQL的数据库是非常宝贵的.

以某音为例,当我去搜索的时候,全抖音比较高的点赞数目应该是在1200w - 2000w,我们自己的业务肯定答不到这么高的,但是假设有10个100w的点赞的博客,user_id为用户ID,publication_id为博客的id

  1. 第一种方式是直接操作数据库.每次有点赞或者取消点赞操作时,就更新MySQL数据库的点赞数.同时,插入或者更新一个user_id和publication_id的数据行,如果点赞操作非常频繁,会对数据库产生很大的压力.如果有大量的点赞记录,会对数据库产生很大的数据量,一篇文章,100w+的点赞的记录,对于MySQL来说,是非常恐怖的.

  2. 第二种方式是通过MySQL + Redis的Set来实现,具体代码如下,以下的代码为B站Redis黑马点评项目:

    @Override
    public Result likeBlog(Long id){// 1. 获取登录用户Long userId = UserHolder.getUser().getId();// 2. 判断当前登录用户是否已经点赞String key = BLOG_LIKED_KEY + id;Boolean isMember = stringRedisTemplate.opsForSet().isMember(key, userId.toString());if(BooleanUtil.isFalse(isMember)){// 3. 如果未点赞,可以点赞// 3.1 数据库点赞数+1boolean isSuccess = update().setSql("liked = liked + 1").eq("id", id).update();// 3.2 保存用户到Redis的set集合if(isSuccess){stringRedisTemplate.opsForSet().add(key, userId.toString());}} else {// 4. 如果已点赞,取消点赞// 4.1 数据库点赞数-1boolean isSuccess = update().setSql("liked = liked - 1").eq("id", id).update();// 4.2 把用户从Redis的set集合移除if(isSuccess){stringRedisTemplate.opsForSet().remove(key, userId.toString());}}
    }

    这样造成的问题是,Redis是内存数据库,点赞信息存储在内存中。当点赞数量非常大时,会占用大量内存。

    下面测试一下,一个key为"userId:114514:publication_id:738836",value为100000-1100000的数据

    • 数据量

       scard userId:114514:publication_id:738836
      

      image-20231105180611714

    • 判断一个value是否存在这个set中-----(对应的业务为"判断当前登录用户是否已经点赞")

          @Testpublic void selectBigKey() {String key = "userId:114514:publication_id:738836";String value1 = "100000";String value2 = "5000000";// 记录开始时间long startTime = System.nanoTime();boolean cacheSet1 = RedisUtils.containsInCacheSet(key, value1);if (cacheSet1) {System.out.println("代码2:" + "存在这个value");} else {System.out.println("代码2:" + "不存在这个value");}// 记录结束时间long endTime = System.nanoTime();// 计算执行时间(以毫秒为单位)long executionTime = (endTime - startTime) / 1_000_000; // 将纳秒转换为毫秒System.out.println("代码执行时间1: " + executionTime + " 毫秒");// 记录开始时间long startTime2 = System.nanoTime();boolean cacheSet2 = RedisUtils.containsInCacheSet(key, value2);if (cacheSet2) {System.out.println("代码2:" + "存在这个value");} else {System.out.println("代码2:" + "不存在这个value");}// 记录结束时间long endTime2 = System.nanoTime();// 计算执行时间(以毫秒为单位)long executionTime2 = (endTime2 - startTime2) / 1_000_000; // 将纳秒转换为毫秒System.out.println("代码执行时间2: " + executionTime2 + " 毫秒");}
      

      image-20231105180819204

      可以看到,其实对于时间来说,61毫秒和66毫秒可以说时间非常短了,不愧是redis,即使数据量很大,但是查询一个value是否在比较大的set的效率是非常短的.

    • 往一个key中添加一个value,或者删除一个value--->(对应一个点赞,和取消点赞)

          @Testpublic void addAndRemoveElementFromBigKey() {String key = "userId:114514:publication_id:738836";String value1 = "10000000";String value2 = "200000";// 记录开始时间long startTime = System.nanoTime();boolean cacheSet1 = RedisUtils.addToCacheSet(key, value1);// 记录结束时间long endTime = System.nanoTime();// 计算执行时间(以毫秒为单位)long executionTime = (endTime - startTime) / 1_000_000; // 将纳秒转换为毫秒System.out.println("添加一个元素的执行时间: " + executionTime + " 毫秒");// 记录开始时间long startTime2 = System.nanoTime();boolean cacheSet2 = RedisUtils.removeFromCacheSet(key, value2);// 记录结束时间long endTime2 = System.nanoTime();// 计算执行时间(以毫秒为单位)long executionTime2 = (endTime2 - startTime2) / 1_000_000; // 将纳秒转换为毫秒System.out.println("删除一个元素的代码执行时间: " + executionTime2 + " 毫秒");}
      

      image-20231105182159692

      但从时间来讲,只有一个字:快

    • 查询占用的内存的空间

      MEMORY USAGE  userId:114514:publication_id:738836
      

      image-20231105182354488

​ 其实可以看到,大概是占用66mb,如果用户的id为雪花算法的id,那可能占用的内存100mb

以上来说,主要还是一个bigkey的问题,如果点赞的数量过大,占用的内存过大,宝贵的内存不应该给这种业务.

  1. 自然而然,我们想到用非关系型数据库,但是不要是基于内存的,我想到的是用MongoDB的方案

    我们可以往MongoDB中插入一条这样的数据:

    db.collectionName.insertOne({"id": "yourIdValue","userId": yourUserIdValue,"type": yourTypeValue,"likedItemId": yourLikedItemIdValue,"createTime": new Date("yourCreateTimeValue")
    });

    id 主键id,userId为用户的ID,type为文章或者动态或者其他的类型,likedItemId为文章或者动态或者其他的类型的主键ID,createTime为点赞时间

    在MongoDB中,可以使用createIndex方法来创建唯一索引。为userId,typelikedItemId字段创建一个唯一索引。

    db.collectionName.createIndex({ "userId": 1, "type": 1, "likedItemId": 1 },{ unique: true, name: "unique_index_name" }
    );
    

    详细解释:

    • collectionName:集合名称。
    • unique_index_name:你想要给索引起的名字,可以根据你的需求替换为其他名称。

    这个命令将在collectionName集合上创建一个名为unique_index_name的唯一索引,涵盖了userIdtypelikedItemId字段。 1表示升序,如果需要降序索引,可以使用-1。 unique: true选项确保索引是唯一的。

    执行这个命令后,如果有重复的组合出现在这三个字段上,MongoDB将会阻止插入并抛出错误。

    即如果里面有记录为已经点过赞,点赞就是往里面加记录,取消点赞就是删除记录

    详细代码如下:

    @Service
    public class LikeServiceImpl implements LikeService {@Autowiredprivate MongoTemplate mongoTemplate;@Autowiredprivate PublicationService publicationService;/*** 为动态或者文章点赞** @param publicationId 动态或者文章的ID* @param userId        用户的ID* @param type          类型,区分是文章还是动态* @return 点赞总数*/@Overridepublic Integer likePublication(Long publicationId, Long userId, Integer type) {// 构建查询条件Criteria criteria = Criteria.where("userId").is(userId).and("type").is(type).and("likedItemId").is(publicationId);// 创建查询对象并应用查询条件Query query = new Query(criteria);boolean isExists = mongoTemplate.exists(query, PublicationLike.class);if (isExists) {Asserts.fail("重复点赞");}//将点赞记录保存到mongodbPublicationLike publicationLike = new PublicationLike();publicationLike.setType(type);publicationLike.setCreateTime(DateUtil.date());publicationLike.setLikedItemId(publicationId);publicationLike.setUserId(userId);PublicationLike savedLike = mongoTemplate.save(publicationLike);//点赞数统计String redisLikeCountKey = String.format(RedisConstant.PUBLICATION_LIKE_COUNT, publicationId, userId, type);Long likeCount = RedisUtils.getAtomicValueWithDefault(redisLikeCountKey);//如果没有缓存过点赞数,则查询数据库if (likeCount.equals(-1L)) {Publication publication = publicationService.getById(publicationId);RedisUtils.setAtomicValue(redisLikeCountKey, publication.getLikeCount());return publication.getLikeCount();} else {//返回点赞数+1return Math.toIntExact(RedisUtils.incrAtomicValue(redisLikeCountKey));}}@Overridepublic Integer unlikePublication(Long publicationId, Long userId, Integer type) {// 构建查询条件Criteria criteria = Criteria.where("userId").is(userId).and("type").is(type).and("likedItemId").is(publicationId);// 创建查询对象并应用查询条件Query query = new Query(criteria);boolean isExists = mongoTemplate.exists(query, PublicationLike.class);if (!isExists) {Asserts.fail("未点赞过该内容,无法取消点赞");}// 从MongoDB中删除点赞记录mongoTemplate.remove(query, PublicationLike.class);// 更新点赞数统计String redisLikeCountKey = String.format(RedisConstant.PUBLICATION_LIKE_COUNT, publicationId, userId, type);Long likeCount = RedisUtils.getAtomicValueWithDefault(redisLikeCountKey);// 如果点赞数存在于缓存中,减少点赞数并返回if (!likeCount.equals(-1L)) {long newLikeCount = RedisUtils.decrAtomicValue(redisLikeCountKey);return Math.toIntExact(newLikeCount);} else {// 如果点赞数没有缓存,查询数据库并更新缓存Publication publication = publicationService.getById(publicationId);if (publication != null) {RedisUtils.setAtomicValue(redisLikeCountKey, publication.getLikeCount());return publication.getLikeCount();} else {Asserts.fail("无法获取点赞数");return 0;}}}}
    

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/173521.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【视觉SLAM十四讲学习笔记】第三讲——旋转向量和欧拉角

专栏系列文章如下: 【视觉SLAM十四讲学习笔记】第一讲——SLAM介绍 【视觉SLAM十四讲学习笔记】第二讲——初识SLAM 【视觉SLAM十四讲学习笔记】第三讲——旋转矩阵 【视觉SLAM十四讲学习笔记】第三讲——Eigen库 本章将介绍视觉SLAM的基本问题之一:如何…

sql21(Leetcode1174即时食物配送2)

代码: # Write your MySQL query statement belowselect round (sum(order_date customer_pref_delivery_date) * 100 /count(*),2 ) as immediate_percentage from Delivery where (customer_id, order_date) in (select customer_id, min(order_date)from deliv…

Unity 自带的一些可以操控时间的属性或方法。

今天来总结下Unity自带的一些可以操控时间的方法。 1、Time.time。比较常用计算运行时间而触发特定事件。 public class Controller : MonoBehaviour {public float eventTime 5f; // 触发事件的时间private float startTime; // 游戏开始的时间private void Start(){startT…

1300.二人的花纹纸游戏【算法必会题目】(前缀和题-JavaPythonC++实现)

文章目录 一.二人的花纹纸游戏【算法必会题目】(模拟题-Java&Python&C++实现)1.1题目背景1.2题目描述1.3形式化题面1.4提示二.题解2.1 解题思路2.1.1 题解2.2 解题代码2.2.1 C++2.2.2 python2.2.3 Java2.3 代码解释2.3.1 C++ 代码解释:2.3.2 Java 代码解释:2.3.3 P…

大数据学习(24)-spark on hive和hive on spark的区别

&&大数据学习&& 🔥系列专栏: 👑哲学语录: 承认自己的无知,乃是开启智慧的大门 💖如果觉得博主的文章还不错的话,请点赞👍收藏⭐️留言📝支持一下博主哦&#x1f91…

计算机毕业设计|基于SpringBoot+MyBatis框架的电脑商城的设计与实现(用户资料修改)

计算机毕业设计|基于SpringBootMyBatis框架的电脑商城的设计与实现(用户资料修改) 该项目分析着重于设计和实现基于SpringBootMyBatis框架的电脑商城。首先,通过深入分析项目所需数据,包括用户、商品、商品类别、收藏、订单、购物…

【活动回顾】sCrypt在2023伦敦区块链大会上的精彩表现

2023伦敦区块链大会,是本年度最盛大的比特币及区块链行业活动。大会于2023年5月31日至6月2日,在伦敦女王伊丽莎白二世中心举行,旨在展示BSV区块链的真正潜力。 sCrypt Inc 的创始人兼 CEO 刘晓晖, 作为演讲嘉宾出席了会议。他向大…

掌握高效性能测试技能:JMeter基础入门!

一、JMeter基础 A、JMeter介绍 Apache JMeter是Apache组织开发的基于Java的压力测试工具。 Apache JMeter may be used to test performance both on static and dynamic resources (files, Servlets, Perl scripts, Java Objects, Data Bases and Queries, FTP Servers and …

【活动回顾】sCrypt在柏林B2029开发者周

B2029 是柏林的一个区块链爱好者、艺术家和建设者聚会,学习、讨论和共同构建比特币区块链地方。 在2023年6月9日至11日,举行了第7次Hello Metanet研讨会。本次研讨会旨在为参与者提供一个学习、讨论和共同构建比特币区块链的平台。 在这个充满激情和创意…

旋转框检测项目相关python库知识总结(mmrotate、ppyolo_r、yolov5_obb)

旋转框常用于检测带有角度信息的矩形框,即矩形框的宽和高不再与图像坐标轴平行。相较于水平矩形框,旋转矩形框一般包括更少的背景信息。旋转框检测常用于遥感等场景中,本博文简单的介绍了可应用于旋转框数据训练的开源库,数据结构…

SpringCloud 微服务全栈体系(十八)

第十一章 分布式搜索引擎 elasticsearch 八、RestClient 查询文档 文档的查询同样适用 RestHighLevelClient 对象,基本步骤包括: 准备 Request 对象准备请求参数发起请求解析响应 1. 快速入门 以 match_all 查询为例 1.1 发起查询请求 代码解读&…

数字技术-IPC专利分类号对应表

数字技术-IPC专利分类号对应表,基于2023年的关键数字技术专利分类体系,通过国际专利分类(IPC)号进行筛选。这些数据涵盖了各种数字技术领域的创新,包括但不限于人工智能、大数据、云计算、物联网、5G通信等。利用关键词…

538. 把二叉搜索树转换为累加树

538. 把二叉搜索树转换为累加树 题目: 给出二叉 搜索 树的根节点,该树的节点值各不相同,请你将其转换为累加树(Greater Sum Tree),使每个节点 node 的新值等于原树中大于或等于 node.val 的值之和。 提醒…

浅谈联网汽车安全漏洞

“智能网联汽车存在内生共性问题,即软硬件的漏洞后门,基于此进行的网络攻击可以直接带来勒索、盗窃、大规模车辆恶意操控风险,还有数据泄露等网络安全事件。如果内生的漏洞后门问题不解决,系统自身难保,很难谈系统安全…

QTextEdit多行富文本框控件

​锋哥原创的PyQt6视频教程: 2024版 PyQt6 Python桌面开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili2024版 PyQt6 Python桌面开发 视频教程(无废话版) 玩命更新中~共计21条视频,包括:2024版 PyQt6 Python桌面开发 视频教程(无废话…

推荐你一个基于Koin, Ktor Paging等组件的KMM Compose Multiplatform项目

推荐你一个基于Koin, Ktor & Paging等组件的KMM Compose Multiplatform项目 Kotlin Multiplatform Mobile(KMM)已经从一个雄心勃勃的想法发展成为一个稳定而强大的框架,为开发人员提供了在多个平台上无缝共享代码的能力。通过最近的稳定…

平衡二叉树(AVL)

(附代码,简洁好理解) 目录 什么是平衡二叉树? 如何保证二叉树平衡? 左旋/右旋 左右双旋/右左双旋 代码 树的结构: 树的高度与平衡因子: 左/右旋: 平衡维护: …

70-76-堆、贪心算法

LeetCode 热题 100 文章目录 LeetCode 热题 100堆70. 中等-数组中的第K个最大元素71. 中等-前K个高频元素72. 困难-数据流中的中位数 贪心算法73. 简单-买卖股票的最佳时机74. 中等-跳跃游戏75. 中等-跳跃游戏II76. 中等-划分字母区间 本文存储我刷题的笔记。 堆 70. 中等-数组…

数字图像处理Python实现-图像特效与卷积滤波

图像特效与卷积滤波 文章目录 图像特效与卷积滤波1、准备2、快速了解卷积滤波3、应用卷积滤波在这篇文章中,我们将探索如何使用卷积内核来实现图像的模糊、锐化、轮廓和浮雕等特效。 1、准备 像往常一样,我们导入 numpy 和 matplotlib 等库。 此外,我们从 skimage 和 scipy…

命令行编译java

十分惭愧,java学了有七八年了,现在才来写这个博客,但是相信正确的事情即使迟到也好过不到,希望借助博客可以帮助记忆。 单个java文件 这个相信大家都知道,只是为了结构明确。这里java会找到内部的main方法。必须是在…