Spring Boot+MyBatis+Redis+ActiveMQ+MySQL+Thymeleaf实现简单的高并发点赞功能
1.项目概述
在社交网站或App中,点赞场景非常多,比如微信、微博、QQ空间、博客、抖音等软件都有点赞功能。别小看一个简单的点赞功能,里面要考虑的细节还是很多的。比如一些名人发的微博,由于其粉丝众多,一条微博可能在短时间内有上百万的点赞数。面对这种高并发点赞场景,如果项目没有设计好,必会导致后端服务器和数据库由于压力过大而出现异常。大型的互联网公司后端架构必回采取很多措施来解决这种高并发场景引发的问题。本项目虽然没有美观的前端页面和复杂的业务逻辑,但是能够把高并发访问场景使用到的小部分技术进行剖析与整合,形成一个小demo,并能应用到今后的工作中。由于我还是一名大三学生,因为疫情在家未参加实习,接触不到真实的高并发场景,但今后去找实习肯定也是需要到,所有特地做了这个小项目来联手。
2.数据库表和持久化类的设计
2.1 数据库设计
1.user表,用于存放用户的基本信息
为了应对在百万级数据的情况下,提高查询效率,建立索引如下:
CREATE INDEX idx_user_name ON user(name); CREATE INDEX idx_user_account ON user(account);
2.mood表,用于存在微博消息
为了应对在百万级数据的情况下,提高查询效率,建立索引如下:
CREATE INDEX idx_mood_userId ON mood(userId);
3.user_mood表,用于建立用户与微博的关联,为了简化业务逻辑,此表省去了点赞者的用户名,即每当用户a点赞用户b的微博c时,会把b的用户名和c的id写入到user_mood表中。
同样的,为了应对在百万级数据的情况下,提高查询效率,建立索引如下:
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
package com.zhongger.highconcurrentpaise.Mapper; import com.zhongger.highconcurrentpaise.domain.Mood; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; import org.apache.ibatis.annotations.Update; import java.util.List; /** * @Author Zhongger * @Description 微博管理类 * @Date */ @Mapper public interface MoodMapper { @Select("select * from mood") List<Mood> findAll(); @Select("select * from mood where id=#{id}") Mood findMoodById(@Param("id") String id); @Update("update mood set praiseNum=#{mood.praiseNum} where id=#{mood.id}") boolean updateMoodPraiseNum(@Param("mood")Mood mood); }
- 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()); } }


