Day30:热帖排行、生成长图、将文件上传到云服务器、优化热门帖子列表、压力测试

热帖排行

不同的算分方式:

image

只存变化的帖子到redis中,每五分钟算一次分,定时任务

存redis

构建redis键

//统计帖子分数
//key:post:score -> value:postId
public static String getPostScoreKey() {return PREFIX_POST + SPLIT + "score";
}

添加帖子时

 // 计算帖子分数String redisKey = RedisKeyUtil.getPostScoreKey();redisTemplate.opsForSet().add(redisKey, post.getId());

加精时

@RequestMapping(path = "/wonderful", method = RequestMethod.POST)@ResponseBodypublic String setWonderful(int id) {//加精是改statusdiscussPostService.updateStatus(id, 1);//触发发帖事件,将帖子存入es服务器Event event = new Event().setTopic(TOPIC_PUBLISH).setUserId(hostHolder.getUser().getId()).setEntityType(ENTITY_TYPE_POST).setEntityId(id);eventProducer.fireEvent(event);// 计算帖子分数String redisKey = RedisKeyUtil.getPostScoreKey();redisTemplate.opsForSet().add(redisKey, id);return CommunityUtil.getJsonString(0);}

评论时

//触发发帖时间,存到es服务器if(comment.getEntityType() == ENTITY_TYPE_POST) {event = new Event().setTopic(TOPIC_PUBLISH).setUserId(comment.getUserId()).setEntityType(ENTITY_TYPE_POST).setEntityId(discussPostId);eventProducer.fireEvent(event);// 计算帖子分数String redisKey = RedisKeyUtil.getPostScoreKey();redisTemplate.opsForSet().add(redisKey, discussPostId);}

点赞时

  if(entityType == ENTITY_TYPE_POST){//计算帖子分数String redisKey = RedisKeyUtil.getPostScoreKey();redisTemplate.opsForSet().add(redisKey, postId);}

设置定时任务

定时任务类:

public class PostScoreRefreshJob implements Job, CommunityConstant {private static final Logger logger = LoggerFactory.getLogger(PostScoreRefreshJob.class);@Autowiredprivate RedisTemplate redisTemplate;@Autowiredprivate DiscussPostService discussPostService;@Autowiredprivate LikeService likeService;@Autowiredprivate ElasticsearchService elasticsearchService;// 牛客纪元private static final Date epoch;static {try {epoch = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2021-08-01 00:00:00");} catch (ParseException e) {throw new RuntimeException("初始化日期失败!", e);}}@Overridepublic void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException{String redisKey = RedisKeyUtil.getPostScoreKey();BoundSetOperations operations = redisTemplate.boundSetOps(redisKey);if (operations.size() == 0) {logger.info("[任务取消] 没有需要刷新的帖子!");return;}logger.info("[任务开始] 正在刷新帖子分数: " + operations.size());while (operations.size() > 0) {this.refresh((Integer) operations.pop());}logger.info("[任务结束] 帖子分数刷新完毕!");}private void refresh(int postId) {// 查询帖子DiscussPost post = discussPostService.findDiscussPostById(postId);if (post == null) {logger.error("该帖子不存在: id = " + postId);return;}// 是否精华boolean wonderful = post.getStatus() == 1;// 评论数量int commentCount = post.getCommentCount();// 点赞数量long likeCount = likeService.findEntityLikeCount(ENTITY_TYPE_POST, postId);// 计算权重double w = (wonderful ? 75 : 0) + commentCount * 10 + likeCount * 2;// 分数 = 帖子权重 + 距离天数(天数越大,分数越低)//Math.max(w, 1) 防止分数为负数//秒->天double score = Math.log10(Math.max(w, 1))+ (post.getCreateTime().getTime() - epoch.getTime()) / (1000 * 3600 * 24);// 更新帖子分数discussPostService.updateScore(postId, score);// 同步es的搜索数据post.setScore(score);elasticsearchService.saveDiscussPost(post);}
}

配置Quartz任务

//刷新帖子分数的任务@Beanpublic JobDetailFactoryBean postScoreRefreshJobDetail() {JobDetailFactoryBean factoryBean = new JobDetailFactoryBean();factoryBean.setJobClass(PostScoreRefreshJob.class);factoryBean.setName("postScoreRefreshJob");factoryBean.setGroup("communityJobGroup");// 是否持久保存factoryBean.setDurability(true);factoryBean.setRequestsRecovery(true);return factoryBean;}//刷新帖子分数的触发器@Beanpublic SimpleTriggerFactoryBean postScoreRefreshTrigger(JobDetail postScoreRefreshJobDetail) {SimpleTriggerFactoryBean factoryBean = new SimpleTriggerFactoryBean();factoryBean.setJobDetail(postScoreRefreshJobDetail);factoryBean.setName("postScoreRefreshTrigger");factoryBean.setGroup("communityTriggerGroup");// 5分钟刷新一次factoryBean.setRepeatInterval(1000 * 60 * 5);factoryBean.setJobDataMap(new JobDataMap());return factoryBean;}

image

在首页按时间和分数展现

之前的mapper默认按时间排,现在修改成两种模式,0安时间排,1按得分排

@Mapper
public interface DiscussPostMapper {//userId为0时,表示查询所有用户的帖子,如果不为0,表示查询指定用户的帖子//offset表示起始行号,limit表示每页最多显示的行数//orderMode表示排序模式,0-默认排序,1-按热度排序List<DiscussPost> selectDiscussPosts(int userId, int offset, int limit, int orderMode);//查询帖子的行数//userId为0时,表示查询所有用户的帖子int selectDiscussPostRows(@Param("userId") int userId);//@param注解用于给参数取别名,拼到sql语句中,如果只有一个参数,并且在<if>标签里,则必须加别名int insertDiscussPost(DiscussPost discussPost);DiscussPost selectDiscussPostById(int id);//根据id查询帖子int updateCommentCount(int id, int commentCount);//修改帖子类型int updateType(int id, int type);//修改帖子状态int updateStatus(int id, int status);//修改帖子分数int updateScore(int id, double score);}

修改xml:

    <select id="selectDiscussPosts" resultType="DiscussPost">select<include refid="selectFields"></include>from discuss_postwhere status != 2<if test="userId != 0">and user_id = #{userId}</if><if test="orderMode == 0">order by type desc, create_time desc</if><if test="orderMode == 1">order by type desc, score desc, create_time desc</if>limit #{offset}, #{limit}</select>

重构service:

    public List<DiscussPost> findDiscussPosts(int userId, int offset, int limit, int orderMode) {return discussPostMapper.selectDiscussPosts(userId, offset, limit, orderMode);}

重构homeController(传入orderMode)

package com.newcoder.community.controller;import com.newcoder.community.entity.DiscussPost;
import com.newcoder.community.entity.Page;
import com.newcoder.community.service.DiscussPostService;
import com.newcoder.community.service.LikeService;
import com.newcoder.community.service.UserService;
import com.newcoder.community.util.CommunityConstant;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;import java.util.ArrayList;
import java.util.List;
import java.util.Map;@Controller
public class HomeController implements CommunityConstant {@Autowiredprivate UserService userService;@Autowiredprivate DiscussPostService discussPostService;@Autowiredprivate LikeService likeService;@RequestMapping(path = "/index", method = RequestMethod.GET)public String getIndexPage(Model model, Page page, @RequestParam(name="orderMode", defaultValue = "0") int orderMode) {//方法调用前,Spring会自动把page注入给model,所以html中可以直接访问page的数据。//先查前10个帖子page.setRows(discussPostService.findDiscussPostRows( 0));page.setPath("/index?orderMode=" + orderMode);List<DiscussPost> list = discussPostService.findDiscussPosts(0,page.getOffset(), page.getLimit(), orderMode);List<Map<String, Object>> discussPosts = new ArrayList<>();if(list != null) {for (DiscussPost post : list) {Map<String, Object> map = new java.util.HashMap<>();map.put("post", post);map.put("user", userService.findUserById(post.getUserId()));//查询帖子的点赞数量long likeCount = likeService.findEntityLikeCount(ENTITY_TYPE_POST, post.getId());map.put("likeCount", likeCount);discussPosts.add(map);}}model.addAttribute("discussPosts", discussPosts);model.addAttribute("orderMode", orderMode);return "/index";}@RequestMapping(path = "/error", method = RequestMethod.GET)public String getErrorPage() {return "/error/500";}// 没有权限时的页面@RequestMapping(path = "/denied", method = RequestMethod.GET)public String getDeniedPage() {return "/error/404";}}
  • @RequestParam注解,参数通过request请求传过来,有默认值。

修改index.html

<!-- 筛选条件 --><ul class="nav nav-tabs mb-3"><li class="nav-item"><a th:class="|nav-link ${orderMode==0?'active':''}|" th:href="@{/index(orderMode=0)}">最新</a></li><li class="nav-item"><a th:class="|nav-link ${orderMode==1?'active':''}|" th:href="@{/index(orderMode=1)}">热门</a></li></ul>

生成长图(Deprecated)

image

wkhtmltopdf

将文件上传到云服务器(Deprecated)

上面上一步的长图,工具没调试好,因此也不用。

优化热门帖子列表

缓存:适用于不经常更新的数据(更新缓存不频繁)

image

(避免直接访问数据库,Redis可跨服务器)

多级缓存-redis

image

缓存详细的调用过程:

image

(本地缓存→ redis→ 数据库)

导入依赖

使用Caffeine进行本地缓存

<!-- https://mvnrepository.com/artifact/com.github.ben-manes.caffeine/caffeine -->
<dependency><groupId>com.github.ben-manes.caffeine</groupId><artifactId>caffeine</artifactId>
</dependency>

设置参数

# CaffeineProperties
caffeine.posts.max-size=15
caffeine.posts.expire-seconds=180

在业务层注入:

@Value("${caffeine.posts.max-size}")
private int maxSize;@Value("${caffeine.posts.expire-seconds}")
private int expireSeconds;

修改DiscussPostService

配置缓存

//帖子列表缓存
private LoadingCache<String, List<DiscussPost>> postListCache;//帖子总数缓存
private LoadingCache<Integer, Integer> postRowsCache;

缓存写

在构造函数执行之前:

@PostConstructpublic void init() {//初始化帖子列表缓存postListCache = Caffeine.newBuilder().maximumSize(maxSize).expireAfterWrite(expireSeconds, TimeUnit.SECONDS).build(new CacheLoader<String, List<DiscussPost>>() {@Overridepublic @Nullable List<DiscussPost> load(String key) throws Exception {if (key == null || key.length() == 0)throw new IllegalArgumentException("参数错误!");String[] params = key.split(":");if (params == null || params.length != 2)throw new IllegalArgumentException("参数错误!");int offset = Integer.valueOf(params[0]);int limit = Integer.valueOf(params[1]);//TODO: 二级缓存:Redis -> mysqllogger.debug("load post list from DB.");return discussPostMapper.selectDiscussPosts(0, offset, limit, 1);}});//初始化帖子总数缓存postRowsCache = Caffeine.newBuilder().maximumSize(maxSize).expireAfterWrite(expireSeconds, TimeUnit.SECONDS).build(new CacheLoader<Integer, Integer>() {@Overridepublic @Nullable Integer load(Integer integer) throws Exception {logger.debug("load post rows from DB.");return discussPostMapper.selectDiscussPostRows(0);}});}

缓存读

public List<DiscussPost> findDiscussPosts(int userId, int offset, int limit, int orderMode) {//只有首页按照热门排序才会缓存//key是offset:limit//get方法是从缓存中取数据if (userId == 0 && orderMode == 1) {return postListCache.get(offset + ":" + limit);}//不缓存logger.debug("load post list from DB.");return discussPostMapper.selectDiscussPosts(userId, offset, limit, orderMode);}public int findDiscussPostRows(int userId) {if (userId == 0) {return postRowsCache.get(userId);}//不缓存logger.debug("load post rows from DB.");return discussPostMapper.selectDiscussPostRows(userId);}

首先在测试类中创建函数插入100000w条帖子:

//创建10w条数据进行压力测试@Testpublic void initDataForTest() {for(int i = 0; i < 100000; i++) {DiscussPost post = new DiscussPost();post.setUserId(111);post.setTitle("互联网寒冬");post.setContent("今年的互联网寒冬真是太冷了。");post.setCreateTime(new Date());post.setScore(Math.random() * 2000);discussPostService.addDiscussPost(post);}}

image

测试代码

 @Testpublic void testCache() {//第一次查询,应该从数据库中查System.out.println(discussPostService.findDiscussPosts(0, 0, 10, 1));//第二次查询,应该从缓存中查System.out.println(discussPostService.findDiscussPosts(0, 0, 10, 1));//第三次查询,应该从缓存中查System.out.println(discussPostService.findDiscussPosts(0, 0, 10, 1));//第三次查询,应该从数据库中查System.out.println(discussPostService.findDiscussPosts(0, 0, 10, 0));}

image

(前三次查询,日志只打印了一次,说明只差了一次数据库)

压力测试

使用JMeter进行测试,下载地址:

https://jmeter.apache.org/download_jmeter.cgi

下载后到bin目录下,运行

sh jmeter.sh

出来GUI界面,依次配置测试计划、线程组、HTTP请求,在聚合报告里看:

image

image

不加缓存

注释掉ServiceLogAspect的注解,可以干净日志

image

100个线程,吞吐量约12.5

加缓存

image

直接191.7,增加了十几倍

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

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

相关文章

练习题(2024/5/5)

1左叶子之和 给定二叉树的根节点 root &#xff0c;返回所有左叶子之和。 示例 1&#xff1a; 输入: root [3,9,20,null,null,15,7] 输出: 24 解释: 在这个二叉树中&#xff0c;有两个左叶子&#xff0c;分别是 9 和 15&#xff0c;所以返回 24示例 2: 输入: root [1] 输…

LeetCode 131 —— 分割回文串

阅读目录 1. 题目2. 解题思路3. 代码实现 1. 题目 2. 解题思路 首先&#xff0c;按照 LeetCode 5——最长回文子串 中的思路&#xff0c;我们先求出 d p dp dp&#xff0c;这样我们就知道了所有的子串是否是回文子串。 然后&#xff0c;我们进行一个 dfs 搜索&#xff0c;起…

5月4(信息差)

&#x1f384; HDMI ARC国产双精度浮点dsp杜比数码7.1声道解码AC3/dts/AAC环绕声光纤、同轴、USB输入解码板KC33C &#x1f30d; 国铁集团回应高铁票价将上涨 https://finance.eastmoney.com/a/202405043066422773.html ✨ 源代码管理平台GitLab发布人工智能编程助手DuoCha…

AI大模型探索之路-训练篇11:大语言模型Transformer库-Model组件实践

系列篇章&#x1f4a5; AI大模型探索之路-训练篇1&#xff1a;大语言模型微调基础认知 AI大模型探索之路-训练篇2&#xff1a;大语言模型预训练基础认知 AI大模型探索之路-训练篇3&#xff1a;大语言模型全景解读 AI大模型探索之路-训练篇4&#xff1a;大语言模型训练数据集概…

QT5带UI的常用控件

目录 新建工程&#xff0c;Qmainwindow带UI UI设计器 常用控件区 Buttons 按钮 containers 容器 控件属性区域 对象监视区 布局工具区 信号与槽区 简单例子1 放置一个按钮控件&#xff0c;改文本为发送&#xff0c;该按键为Button1&#xff1b; 按钮关联信号和…

Redisson 分布式锁和同步器

系列文章目录 文章目录 系列文章目录前言前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这篇文章男女通用,看懂了就去分享给你的码吧。 redisson 是基于redis的扩展库,使得redis除了应用于缓存以外,还能做队列…

golang学习笔记(内存模型和分配机制)

操作系统的存储管理 虚拟内存管理 虚拟内存是一种内存管理技术&#xff0c;它允许操作系统为每个进程提供一个比实际物理内存更大的地址空间。这个地址空间被称为虚拟地址空间&#xff0c;而实际的物理内存则被称为物理地址空间。使用虚拟内存有以下几点好处&#xff1a; 内…

【机器学习-21】集成学习---Bagging之随机森林(RF)

【机器学习】集成学习---Bagging之随机森林&#xff08;RF&#xff09; 一、引言1. 简要介绍集成学习的概念及其在机器学习领域的重要性。2. 引出随机森林作为Bagging算法的一个典型应用。 二、随机森林原理1. Bagging算法的基本思想2. 随机森林的构造3. 随机森林的工作机制 三…

JVM的垃圾回收机制(GC机制)

在Java代码运行的过程中&#xff0c;JVM发现 某些资源不需要再使用的时候&#xff0c;就会自动把资源所占的内存给回收掉&#xff0c;就不需要程序员自行操作了。“自动回收资源”就是JVM的“垃圾回收机制”&#xff0c;“垃圾回收机制”也称"GC机制"。 对于Java代码…

【论文笔记】Training language models to follow instructions with human feedback A部分

Training language models to follow instructions with human feedback A 部分 回顾一下第一代 GPT-1 &#xff1a; 设计思路是 “海量无标记文本进行无监督预训练少量有标签文本有监督微调” 范式&#xff1b;模型架构是基于 Transformer 的叠加解码器&#xff08;掩码自注意…

OceanBase开发者大会实录-杨传辉:携手开发者打造一体化数据库

本文来自2024 OceanBase开发者大会&#xff0c;OceanBase CTO 杨传辉的演讲实录—《携手开发者打造一体化数据库》。完整视频回看&#xff0c;请点击这里&#xff1e;> 各位 OceanBase 的开发者&#xff0c;大家上午好&#xff01;今天非常高兴能够在上海与大家再次相聚&…

半监督节点分类:标签传播和消息传递

基础概念回顾 传统图机器学习的特征工程——节点层面&#xff0c;连接层面&#xff0c;全图层面 节点层面&#xff1a;信用卡欺诈 连接层面&#xff1a;推荐可能认识的人 全图层面&#xff1a;预测分子结构 半监督节点分类 半监督节点分类&#xff1a;用已知标签节点预测未…

路由器的构成

一、路由器简介 路由器是互联网中的关键设备&#xff1a; 连接不同的网络路由器是多个输入端口和多个输出端口的专用计算机&#xff0c;其任务是转发分组&#xff08;转发给下一跳路由器&#xff09;下一跳路由器也按照这种方法处理分组&#xff0c;直到该分组到达终点为止 …

Gone框架介绍3 - 使用gone命令,自动生成Priest函数

文章目录 1. 安装辅助工具: gone2. 创建一个名为gen-code的新项目3. 创建Goner4. 使用辅助工具5. 添加main函数 我在两年前实现了一个Golang的依赖注入框架&#xff0c;并且集成了gin、xorm、redis、cron、消息中间件等功能&#xff0c;自己觉得还挺好用的&#xff1b;之前一直…

js api part3

环境对象 环境对象&#xff1a; 指的是函数内部特殊的 变量 this &#xff0c; 它代表着当前函数运行时所处的环境 作用&#xff1a; 弄清楚this的指向&#xff0c;可以让我们代码更简洁 函数的调用方式不同&#xff0c;this 指代的对象也不同 【谁调用&#xff0c; this 就是…

中科院突破:TalkingGaussian技术实现3D人脸动态无失真,高效同步嘴唇运动!

DeepVisionary 每日深度学习前沿科技推送&顶会论文分享&#xff0c;与你一起了解前沿深度学习信息&#xff01; 引言&#xff1a;探索高质量3D对话头像的新方法 在数字媒体和虚拟互动领域&#xff0c;高质量的3D对话头像技术正变得日益重要。这种技术能够在虚拟现实、电影…

银河麒麟桌面版开机后网络无法自动链接 麒麟系统开机没有连接ens33

1.每次虚拟机开机启动麒麟操作系统&#xff0c;都要输入账号&#xff0c;密码。 进入点击这个ens33 内网才连接 2. 如何开机就脸上呢&#xff1f; 2.1. 进入 cd /etc/sysconfig/network-scripts 2.2 修改参数 onbootyes 改为yes 2.3 重启即可 a. 直接重启机器查看是否正常&…

docker原理

Docker原理 在前面我们学习了Docker&#xff0c;接下来我们探究一下Docker的底层技术原理 Linux 命名空间&#xff08;namespace&#xff09;、控制组&#xff08;cgroups&#xff09;和 联合文件系统&#xff08;UnionFS&#xff09; 三大技术支撑了目前 Docker 的实现&…

Unity SteamVR入门

概述 VR项目现在在当前已经是非常热门的技术&#xff0c;可以给玩家身临其境的感觉&#xff0c;接下来让我们学习这部分的内容吧&#xff01; SteamVR Input SteamVR绑定流程&#xff0c;在Windows窗口的点击SteamVR-input&#xff0c;图1&#xff0c;在这里可以选择你需要绑定…

2024五一杯数学建模C题思路分享 - 煤矿深部开采冲击地压危险预测

文章目录 1 赛题选题分析 2 解题思路2.1 问题重述2.2 第一问完整思路2.2 二、三问思路更新 3 最新思路更新 1 赛题 C题 煤矿深部开采冲击地压危险预测 煤炭是中国的主要能源和重要的工业原料。然而&#xff0c;随着开采深度的增加&#xff0c;地应力增大&#xff0c;井下煤岩动…