本文介绍: 点赞业务比较频繁,很多人业务可能都会有这个,比如:博客,视频,文章,动态,评论等,但是不应该核心业务,不应该大量地请求MySQL数据库,给数据库造成大量的资源消耗,MySQL的数据库是非常宝贵的.以某音为例,当我去搜索的时候,全抖音比较高的点赞数目应该是在1200w – 2000w,我们自己业务肯定答不到这么高的,但是假设有10个100w的点赞博客,user_id用户ID,publication_id博客id第一种方式是直接操作数据库.每次有点赞或者取消点赞操作时,就更新MySQL数据库的点赞
点赞

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

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

  1. 第一种方式是直接操作数据.每次有点赞或者取消点赞操作时,就更新MySQL数据库的点赞数.同时,插入或者更新一个user_idpublication_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 数据库点赞数+1
            boolean 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 数据库点赞数-1
            boolean 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的数据

​ 其实可以看到,大概是占用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唯一索引,涵盖了userIdtypelikedItemId字段。 1表示升序,如果需要降序索引可以使用-1。 unique: true选项确保索引唯一的。

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

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

    详细代码如下:

    @Service
    public class LikeServiceImpl implements LikeService {
        @Autowired
        private MongoTemplate mongoTemplate;
    
        @Autowired
        private PublicationService publicationService;
    
        /**
         * 为动态或者文章点赞
         *
         * @param publicationId 动态或者文章的ID
         * @param userId        用户的ID
         * @param type          类型,区分是文章还是动态
         * @return 点赞总数
         */
        @Override
        public 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("重复点赞");
            }
            //将点赞记录保存mongodb
            PublicationLike 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 {
                //返回点赞数+1
                return Math.toIntExact(RedisUtils.incrAtomicValue(redisLikeCountKey));
            }
    
    
        }
    
        @Override
        public 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;
                }
            }
        }
    
    }
    

原文地址:https://blog.csdn.net/xdpcxq/article/details/134641329

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任

如若转载,请注明出处:http://www.7code.cn/show_19703.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱suwngjj01@126.com进行投诉反馈,一经查实,立即删除!

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注