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.mzph.cn/diannao/6738.shtml

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

相关文章

公众号/小程序 开发模式切换

开发公众号/小程序 模式切换 https://ke.qq.com/course/6033257/14616022822424425#term_id106263577

Redis(Jedis和SpringBoot整合Redis)

文章目录 1.Jedis1.介绍2.环境配置1.创建maven项目2.pom.xml引入依赖3.新建一个包并创建一个文件 3.Jedis远程连接到Redis1.Redis放到服务器可以连接的前提条件2.为Redis设置密码1.编辑配置文件2.找到 requirepass3.设置密码为root4.重启Redis&#xff0c;在shutdown的时候报错…

算法入门<一>:C++各种排序算法详解及示例源码

1、排序算法 排序算法&#xff08;sorting algorithm&#xff09;用于对一组数据按照特定顺序进行排列。排序算法有着广泛的应用&#xff0c;因为有序数据通常能够被更高效地查找、分析和处理。 1.1 评价维度 运行效率&#xff1a;我们期望排序算法的时间复杂度尽量低&#xf…

机械臂标准DH建模及正运动学分析(以IRB4600型工业机械臂为例)

1. 前言 对于工业机械臂而言&#xff0c;运动学是不考虑力学特性的情况下对机械臂的几何参数与其位置、速度、加速度等运动特性的关系研究。DH建模是运动学的基础&#xff0c;全称为Denavit-Hartenberg建模方法&#xff0c;是一种广泛应用于机器人运动学中的建模技术。该方法通…

05_G1垃圾收集器

G1垃圾收集器简介 垃圾优先 Garbage-First&#xff08;G1&#xff09;垃圾收集器面向多处理器机器&#xff0c;适用于大内存场景。它尝试在无需太多配置的情况下实现垃圾收集暂停时间目标&#xff0c;并同时实现高吞吐量。G1旨在通过适用于当前目标应用和环境的功能&#xff0…

5月4(信息差)

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

【前端开发---Vue2】史上最详细的Vue2入门教程,从基础到进阶带你彻底掌握Vue(三)

本篇重点分享常见指令修饰符、v-bind指令用于 class 类名 和 style 行内样式 动态控制、v-model在其他表单元素的使用...... 并结合具体案例来让小伙伴们掌握的更透彻&#xff01;喜欢就先关注一下吧~ 声明&#xff1a;图片资源来自于黑马程序员公开学习资料 本人在学习当中&am…

golang学习笔记(协程的基础知识)

golang的协程 协程是一种轻量级的线程&#xff0c;它可以实现并发执行的并行操作。协程是Go语言中的一个核心特性&#xff0c;它使得程序能够以并发的方式运行&#xff0c;并且非常高效。与传统的线程相比&#xff0c;协程的创建和销毁成本非常低&#xff0c;可以方便地启动大…

浏览器中不能使用ES6的扩展语法...报错

浏览器大多数已经支持ES6&#xff08;ECMAScript 2015&#xff09;的扩展语法&#xff08;...&#xff09;&#xff0c;包括Chrome、Firefox、Safari和Edge等。然而&#xff0c;如果你在某些浏览器中遇到无法使用扩展语法的问题&#xff0c;可能是由以下原因导致的&#xff1a;…

神经网络之防止过拟合

今天我们来看一下神经网络中防止模型过拟合的方法 在机器学习和深度学习中&#xff0c;过拟合是指模型在训练数据上表现得非常好&#xff0c;但在新的、未见过的数据上表现不佳的现象。这是因为模型过于复杂&#xff0c;以至于它学习了训练数据中的噪声和细节&#xff0c;而不…

一款开源高性能AI应用框架

前言 LobeChat 是一个基于 Next.js 框架构建的 AI 会话应用&#xff0c;旨在提供一个 AI 生产力平台&#xff0c;使用户能够与 AI 进行自然语言交互。 LobeChat应用架构 LobeChat 的整体架构由前端、EdgeRuntime API、Agents 市场、插件市场和独立插件组成。这些组件相互协作&a…

P8799 [蓝桥杯 2022 国 B] 齿轮

P8799 [蓝桥杯 2022 国 B] 齿轮 分析 最右边的齿轮的转速是最左边齿轮的q倍 最右边的齿轮的半径是最左边齿轮的q倍 题意即为&#xff1a;查询数组中是否存在两个数&#xff0c;其中一个是另一个的q倍 题目范围&#xff1a;查询次数q:2*10^5&#xff0c;数组范围2*10^5&…

XXL-JOB定时任务

1. xxl-job初识 1.1 xxl-job介绍 xxl-job 是大众点评大佬徐雪里开源的一款分布式任务调度框架&#xff0c;具有简单易用、轻量级、可扩展的特点。相比于Spring Task, Quartz&#xff0c;xxl-job有记录执行日志和运行大盘&#xff0c;方便开发人员和运维人员更好的管理任务。 …

如何进行面向对象分析、面向对象设计和面向对象编程

目录 1.引言 2.案例介绍和难点剖析 3.如何进行面向对象分析 4.如何进行面向对象设计 5.如何进行面向对象编程 6.总结 1.引言 面向对象分析(OOA)、面向对象设计(00D)和面向对象编程(OOP)是面向对象开发的3个主要环节。 在以往的工作中&#xff0c;作者发现&#xff0c;很多…

【JavaEE 初阶(一)】初识线程

❣博主主页: 33的博客❣ ▶️文章专栏分类:JavaEE◀️ &#x1f69a;我的代码仓库: 33的代码仓库&#x1f69a; &#x1faf5;&#x1faf5;&#x1faf5;关注我带你了解更多线程知识 目录 1.前言2.进程3.线程4.线程和进程的区别5.Thread创建线程5.1继承Thread创建线程5.2实现R…

专项技能训练五《云计算网络技术与应用》实训7-2:使用OpenDaylight界面下发流表

文章目录 使用OpenDaylight的web界面下发流表1. 根据前面实训教程&#xff0c;启动并登录到opendaylight的web控制页面&#xff0c;并使用mininet2. 单击左侧的“Nodes”查看节点信息。其中尤其需要关注Node Id&#xff0c;下发流表的时候会用到Node Id&#xff0c;如下图所示。…

无穷级数错题本

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 <

word中取消分页符或分段符前后的空格

在Word中&#xff0c;有时候&#xff0c;我们添加分页符后&#xff0c;从分页符后面的文字就全部掉到了下一页&#xff0c;那么如何避免呢&#xff1f; 选择word选项--高级&#xff0c;然后下滑到下面&#xff0c;将“取消分页符或分段符前后的空格”选中&#xff0c;如下图所…

解读简单的一段深度学习代码(已跑通)

最近一直想要学习深度学习的内容&#xff0c;想要复现大佬的代码&#xff0c;试了好多有的是不给数据&#xff0c;有的总是跑不通&#xff0c;这一个是已经跑通的一个代码&#xff0c;以上为证&#xff0c;学习最快的方式就是直接实战&#xff0c;大部分的内容都是比较偏向于理…

机器学习笔记导航(吴恩达版)

01.机器学习笔记01&#xff1a;机器学习前置概念导入、线性回归、梯度下降算法 02.机器学习笔记02&#xff1a;多元线性回归、多元梯度下降算法、特征缩放、均值归一化、正规方程 03.机器学习笔记03&#xff1a;octave安装、创建矩阵 04.机器学习笔记04&#xff1a;octave中移动…