Spring Boot+MyBatis+Redis+ActiveMQ+MySQL+Thymeleaf实现简单的高并发点赞功能(上)

本文涉及的产品
RDS MySQL DuckDB 分析主实例,集群系列 4核8GB
简介: Spring Boot+MyBatis+Redis+ActiveMQ+MySQL+Thymeleaf实现简单的高并发点赞功能

Spring Boot+MyBatis+Redis+ActiveMQ+MySQL+Thymeleaf实现简单的高并发点赞功能


1.项目概述


在社交网站或App中,点赞场景非常多,比如微信、微博、QQ空间、博客、抖音等软件都有点赞功能。别小看一个简单的点赞功能,里面要考虑的细节还是很多的。比如一些名人发的微博,由于其粉丝众多,一条微博可能在短时间内有上百万的点赞数。面对这种高并发点赞场景,如果项目没有设计好,必会导致后端服务器和数据库由于压力过大而出现异常。大型的互联网公司后端架构必回采取很多措施来解决这种高并发场景引发的问题。本项目虽然没有美观的前端页面和复杂的业务逻辑,但是能够把高并发访问场景使用到的小部分技术进行剖析与整合,形成一个小demo,并能应用到今后的工作中。由于我还是一名大三学生,因为疫情在家未参加实习,接触不到真实的高并发场景,但今后去找实习肯定也是需要到,所有特地做了这个小项目来联手。


2.数据库表和持久化类的设计


2.1 数据库设计


1.user表,用于存放用户的基本信息


20200228170727470.png


为了应对在百万级数据的情况下,提高查询效率,建立索引如下:


CREATE INDEX idx_user_name ON user(name);
CREATE INDEX idx_user_account ON user(account);


2.mood表,用于存在微博消息


20200228171218230.png



为了应对在百万级数据的情况下,提高查询效率,建立索引如下:


CREATE INDEX idx_mood_userId ON mood(userId);


3.user_mood表,用于建立用户与微博的关联,为了简化业务逻辑,此表省去了点赞者的用户名,即每当用户a点赞用户b的微博c时,会把b的用户名和c的id写入到user_mood表中。


20200228172028609.png


同样的,为了应对在百万级数据的情况下,提高查询效率,建立索引如下:


CREATE INDEX idx_UM_userId ON user_mood(userID);
CREATE INDEX idx_UM_moodId ON user_mood(moodID);
2.2持久化类

1.User

package com.zhongger.highconcurrentpaise.domain;
import java.io.Serializable;
/**
 * @Author Zhongger
 * @Description User对象
 * @Date 2020.2.28
 */
public class User implements Serializable {
    private String id;
    private String name;
    private String account;
    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getAccount() {
        return account;
    }
    public void setAccount(String account) {
        this.account = account;
    }
    @Override
    public String toString() {
        return "User{" +
                "id='" + id + '\'' +
                ", name='" + name + '\'' +
                ", account='" + account + '\'' +
                '}';
    }
}

2.Mood

package com.zhongger.highconcurrentpaise.domain;
import java.io.Serializable;
import java.util.Date;
/**
 * @Author Zhongger
 * @Description 微博内容对象
 * @Date 2020.2.28
 */
public class Mood implements Serializable {
    private String id;
    private String content;
    private String userId;
    private Date publishTime;
    private Integer praiseNum;
    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
    public String getContent() {
        return content;
    }
    public void setContent(String content) {
        this.content = content;
    }
    public String getUserId() {
        return userId;
    }
    public void setUserId(String userId) {
        this.userId = userId;
    }
    public Date getPublishTime() {
        return publishTime;
    }
    public void setPublishTime(Date publishTime) {
        this.publishTime = publishTime;
    }
    public Integer getPraiseNum() {
        return praiseNum;
    }
    public void setPraiseNum(Integer praiseNum) {
        this.praiseNum = praiseNum;
    }
    @Override
    public String toString() {
        return "Mood{" +
                "id='" + id + '\'' +
                ", content='" + content + '\'' +
                ", userId='" + userId + '\'' +
                ", publishTime=" + publishTime +
                ", praiseNum=" + praiseNum +
                '}';
    }
}

3.UserMood

package com.zhongger.highconcurrentpaise.domain;
import java.io.Serializable;
/**
 * @Author Zhongger
 * @Description 用户与朋友圈的关联类
 * @Date 2020.2.28
 */
public class UserMood implements Serializable {
    private String id;
    private String userId;
    private String moodId;
    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
    public String getUserId() {
        return userId;
    }
    public void setUserId(String userId) {
        this.userId = userId;
    }
    public String getMoodId() {
        return moodId;
    }
    public void setMoodId(String moodId) {
        this.moodId = moodId;
    }
    @Override
    public String toString() {
        return "UserMood{" +
                "id='" + id + '\'' +
                ", userId='" + userId + '\'' +
                ", moodId='" + moodId + '\'' +
                '}';
    }
}


3.Dao层对象


由于本项目较小,采用注解版本的MyBatis来开发。

1.UserMapper:

package com.zhongger.highconcurrentpaise.Mapper;
import com.zhongger.highconcurrentpaise.domain.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
/**
 * @Author Zhongger
 * @Description 用户管理接口
 * @Date 2020.2.28
 */
@Mapper
public interface UserMapper {
    //查询用户
    @Select("select id,name,account from user where id=#{id}")
    User findUserById(@Param("id") String id);
}

2.MoodMapper

  1. UserMoodMapper
package com.zhongger.highconcurrentpaise.Mapper;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
/**
 * @Author Zhongger
 * @Description 用户朋友圈点赞关联类
 * @Date 2020.2.28
 */
@Mapper
public interface UserMoodMapper {
    @Insert("insert into user_mood(userId,moodId) values(#{userId},#{moodId})")
    boolean save(@Param("userId") String userId, @Param("moodId") String moodId);
}

4.Service层与DTO对象


注意,DTO是返回给前端的对象

1.MoodDTO


package com.zhongger.highconcurrentpaise.domain;
/**
 * @Author Zhongger
 * @Description 返回给前端的MoodDTO
 * @Date
 */
public class MoodDTO extends Mood {
    private String userName;
    private String userAccount;
    public String getUserName() {
        return userName;
    }
    public void setUserName(String userName) {
        this.userName = userName;
    }
    public String getUserAccount() {
        return userAccount;
    }
    public void setUserAccount(String userAccount) {
        this.userAccount = userAccount;
    }
}

2.UserDTO

package com.zhongger.highconcurrentpaise.domain;
/**
 * @Author Zhongger
 * @Description 返回给前端的UserDTO
 * @Date
 */
public class UserDTO extends User {
}

3.传统点赞方式的Service接口及其实现类


package com.zhongger.highconcurrentpaise.service;
import com.zhongger.highconcurrentpaise.domain.Mood;
import com.zhongger.highconcurrentpaise.domain.MoodDTO;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
 * @Author Zhongger
 * @Description
 * @Date
 */
public interface MoodService {
    List<MoodDTO> findAllMood();
    //===================传统点赞方式===============//
    boolean praiseMood(String userId,String moodId);
    boolean update(@Param("mood")Mood mood);
    Mood findMoodById(String id);
    //============================================//
}


package com.zhongger.highconcurrentpaise.service;
import com.zhongger.highconcurrentpaise.domain.UserMood;
/**
 * @Author Zhongger
 * @Description
 * @Date
 */
public interface UserMoodPraiseService {
    boolean saveUserMood(UserMood userMood);
}



package com.zhongger.highconcurrentpaise.service;
import com.zhongger.highconcurrentpaise.domain.UserDTO;
/**
 * @Author Zhongger
 * @Description
 * @Date
 */
public interface UserService {
    UserDTO findUserById(String id);
}
package com.zhongger.highconcurrentpaise.service.impl;
import com.zhongger.highconcurrentpaise.Mapper.MoodMapper;
import com.zhongger.highconcurrentpaise.Mapper.UserMapper;
import com.zhongger.highconcurrentpaise.Mapper.UserMoodMapper;
import com.zhongger.highconcurrentpaise.domain.Mood;
import com.zhongger.highconcurrentpaise.domain.MoodDTO;
import com.zhongger.highconcurrentpaise.domain.User;
import com.zhongger.highconcurrentpaise.domain.UserMood;
import com.zhongger.highconcurrentpaise.service.MoodService;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
 * @Author Zhongger
 * @Description
 * @Date
 */
@Service
public class MoodServiceImpl implements MoodService {
    @Resource
    private MoodMapper moodMapper;
    @Resource
    private UserMapper userMapper;
    @Resource
    private UserMoodMapper userMoodMapper;
    @Override
    public List<MoodDTO> findAllMood() {
        List<Mood> moodList = moodMapper.findAll();
        return converModelToDTO(moodList);
    }
    @Override
    public boolean praiseMood(String userId, String moodId) {
        //建立点赞者和朋友圈的联系
        UserMood userMood = new UserMood();
        userMood.setUserId(userId);
        userMood.setMoodId(moodId);
        userMoodMapper.save(userMood.getUserId(),userMood.getMoodId());
        //更新朋友圈的点赞数量
        Mood mood = this.findMoodById(moodId);
        mood.setPraiseNum(mood.getPraiseNum()+1);
        this.update(mood);
        return Boolean.TRUE;
    }
    @Override
    public boolean update(Mood mood) {
        return moodMapper.updateMoodPraiseNum(mood);
    }
    @Override
    public Mood findMoodById(String id) {
        return moodMapper.findMoodById(id);
    }
    private List<MoodDTO> converModelToDTO(List<Mood> moodList){
        if (CollectionUtils.isEmpty(moodList)) return Collections.EMPTY_LIST;
        List<MoodDTO> moodDTOList = new ArrayList<>();
        for (Mood mood:moodList) {
            MoodDTO moodDTO = new MoodDTO();
            User user = userMapper.findUserById(mood.getUserId());
            moodDTO.setId(mood.getId());
            moodDTO.setContent(mood.getContent());
            moodDTO.setPraiseNum(mood.getPraiseNum());
            moodDTO.setPublishTime(mood.getPublishTime());
            moodDTO.setUserId(mood.getUserId());
            moodDTO.setUserName(user.getName());
            moodDTO.setUserAccount(user.getAccount());
            moodDTOList.add(moodDTO);
        }
        return moodDTOList;
    }
}
package com.zhongger.highconcurrentpaise.service.impl;
import com.zhongger.highconcurrentpaise.Mapper.UserMapper;
import com.zhongger.highconcurrentpaise.domain.User;
import com.zhongger.highconcurrentpaise.domain.UserDTO;
import com.zhongger.highconcurrentpaise.service.UserService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
/**
 * @Author Zhongger
 * @Description
 * @Date
 */
@Service
public class UserServiceImpl implements UserService {
    @Resource
    private UserMapper userMapper;
    @Override
    public UserDTO findUserById(String id) {
        User user = userMapper.findUserById(id);
        return converModelToDTO(user);
    }
    private UserDTO converModelToDTO(User user){
        UserDTO userDTO = new UserDTO();
        userDTO.setId(user.getId());
        userDTO.setName(user.getName());
        userDTO.setAccount(user.getAccount());
        return userDTO;
    }
}
package com.zhongger.highconcurrentpaise.service.impl;
import com.zhongger.highconcurrentpaise.Mapper.UserMoodMapper;
import com.zhongger.highconcurrentpaise.domain.UserMood;
import com.zhongger.highconcurrentpaise.service.UserMoodPraiseService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
/**
 * @Author Zhongger
 * @Description
 * @Date
 */
@Service
public class UserMoodPraiseServiceImpl implements UserMoodPraiseService {
    @Resource
    private UserMoodMapper userMoodMapper;
    @Override
    public boolean saveUserMood(UserMood userMood) {
        return userMoodMapper.save(userMood.getUserId(),userMood.getMoodId());
    }
}


相关文章
|
2月前
|
NoSQL Java 网络安全
SpringBoot启动时连接Redis报错:ERR This instance has cluster support disabled - 如何解决?
通过以上步骤一般可以解决由于配置不匹配造成的连接错误。在调试问题时,一定要确保服务端和客户端的Redis配置保持同步一致。这能够确保SpringBoot应用顺利连接到正确配置的Redis服务,无论是单机模式还是集群模式。
315 5
|
2月前
|
XML Java 应用服务中间件
【SpringBoot(一)】Spring的认知、容器功能讲解与自动装配原理的入门,带你熟悉Springboot中基本的注解使用
SpringBoot专栏开篇第一章,讲述认识SpringBoot、Bean容器功能的讲解、自动装配原理的入门,还有其他常用的Springboot注解!如果想要了解SpringBoot,那么就进来看看吧!
461 2
|
3月前
|
NoSQL Java 调度
分布式锁与分布式锁使用 Redis 和 Spring Boot 进行调度锁(不带 ShedLock)
分布式锁是分布式系统中用于同步多节点访问共享资源的机制,防止并发操作带来的冲突。本文介绍了基于Spring Boot和Redis实现分布式锁的技术方案,涵盖锁的获取与释放、Redis配置、服务调度及多实例运行等内容,通过Docker Compose搭建环境,验证了锁的有效性与互斥特性。
279 0
分布式锁与分布式锁使用 Redis 和 Spring Boot 进行调度锁(不带 ShedLock)
|
5月前
|
缓存 前端开发 Java
SpringBoot 实现动态菜单功能完整指南
本文介绍了一个动态菜单系统的实现方案,涵盖数据库设计、SpringBoot后端实现、Vue前端展示及权限控制等内容,适用于中后台系统的权限管理。
530 2
|
5月前
|
NoSQL Java Redis
Redis基本数据类型及Spring Data Redis应用
Redis 是开源高性能键值对数据库,支持 String、Hash、List、Set、Sorted Set 等数据结构,适用于缓存、消息队列、排行榜等场景。具备高性能、原子操作及丰富功能,是分布式系统核心组件。
604 2
|
4月前
|
存储 NoSQL Redis
采用Redis的Bitmaps实现类似Github连续提交状态的功能。
在现实世界的应用开发中,实现类似于Github提交跟踪系统时,还可能需要考虑用户时区、闰年等日期相关的边界条件,以及辅助数据的存储和查询优化,例如对活跃用户的即时查询和统计等。不过这些都可以在Bitmaps的基础功能之上通过额外的代码逻辑来实现。
121 0
|
6月前
|
缓存 NoSQL 算法
高并发秒杀系统实战(Redis+Lua分布式锁防超卖与库存扣减优化)
秒杀系统面临瞬时高并发、资源竞争和数据一致性挑战。传统方案如数据库锁或应用层锁存在性能瓶颈或分布式问题,而基于Redis的分布式锁与Lua脚本原子操作成为高效解决方案。通过Redis的`SETNX`实现分布式锁,结合Lua脚本完成库存扣减,确保操作原子性并大幅提升性能(QPS从120提升至8,200)。此外,分段库存策略、多级限流及服务降级机制进一步优化系统稳定性。最佳实践包括分层防控、黄金扣减法则与容灾设计,强调根据业务特性灵活组合技术手段以应对高并发场景。
1889 7
|
7月前
|
存储 监控 NoSQL
使用Redis实现延迟消息发送功能
使用 Redis 的密码认证功能,为实例设置密码以防止未授权访问。为消息提供适当加密,确保消息内容在网络传输过程中不被窃取或篡改。
305 16
|
7月前
|
NoSQL 算法 安全
redis分布式锁在高并发场景下的方案设计与性能提升
本文探讨了Redis分布式锁在主从架构下失效的问题及其解决方案。首先通过CAP理论分析,Redis遵循AP原则,导致锁可能失效。针对此问题,提出两种解决方案:Zookeeper分布式锁(追求CP一致性)和Redlock算法(基于多个Redis实例提升可靠性)。文章还讨论了可能遇到的“坑”,如加从节点引发超卖问题、建议Redis节点数为奇数以及持久化策略对锁的影响。最后,从性能优化角度出发,介绍了减少锁粒度和分段锁的策略,并结合实际场景(如下单重复提交、支付与取消订单冲突)展示了分布式锁的应用方法。
601 3