仿牛客网论坛项目
Posted honor boom
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了仿牛客网论坛项目相关的知识,希望对你有一定的参考价值。
项目本身
项目的背景,解决了一个什么样的问题
这个项目的整体结构来源于牛客网,主要使用了Springboot、Mybatis、mysql、Redis、Kafka、等工具。主要实现了用户的注册、登录、发帖、点赞、系统通知、按热度排序、搜索等功能。另外引入了redis数据库来提升网站的整体性能,实现了用户凭证的存取、点赞关注的功能。基于 Kafka 实现了系统通知:当用户获得点赞、评论后得到通知。利用定时任务定期计算帖子的分数,并在页面上展现热帖排行榜。
项目中的职责是什么
1、完成软件系统代码的实现,编写代码注释和开发文档; 2、辅助进行系统的功能定义,程序设计; 3、根据设计文档或需求说明完成代码编写,调试,测试和维护;
项目中使用的技术栈是什么?项目架构是怎么样的?使用微服务了嘛?
服务器分为表现层/业务层/数据层,其中Spring MVC是工作在表现层,作用是接收/解析用户发送的请求,调用对应的业务类,根据业务类返回的结果(ModelAndView),调用view进行视图渲染,并将渲染后的View返回给请求者。具体分为以下8步:
-
客户端(浏览器)发送请求给前端处理器(DispatcherServlet)(发送请求,响应结果);
-
DispatcherServlet根据请求信息调用HandlerMapping,查找到对应的Handler;
-
查找到对应的Handler(也就是Controller)后,由HandlerAdapter适配器处理;
-
HandlerAdapter根据Handler来调用真正的Controller;
-
Controller进行业务处理,返回ModelAndView对象,Model是数据对象,View是逻辑上的View;
-
ViewResolver根据逻辑view找到实际view;
-
DispatcherServlet把Model传给view进行视图渲染,然后返回给请求者。
mvc三层架构:
-
C - Controller:控制器。接受用户请求,调用 Model 处理,然后选择合适的View给客户。
-
M - Model:模型。业务处理模型,接受Controller的调遣,处理业务,处理数据。
-
V - View:视图。返回给客户看的结果。
对Spring IoC的理解:
IoC的意思是控制反转,是一种设计思想,把需要在程序中手动创建对象的控制权交给了Spring框架。IoC的载体是IoC容器,本质是一个工厂,数据结构上来看是一个Map,用来存放着各种对象。当我们创建一个对象时,只需要配置好配置文件/注解,而不用担心对象是怎么被创建出来的。
IoC的优点:降低耦合,对象被容器管理需要两份数据:你的对象定义 + 配置文件,对象间的关系体现在配置文件,不会直接产生耦合。
项目是怎么搭建的,机器配置是怎么样的,有做分布式嘛?
项目的具体功能细节,如论坛项目中的评论如何存储?怎么展示所有的评论?
user_id对应的是发评论的用户,entity_type是指评论的类型,论坛部分,有两种类型,对帖子的评论和对评论的评论,为了方便区分,对评论的评论我们成为回复,entity_id对应回复的实体的id,target_id也就是回复的对象,这个主要是在回复的时候需要显示回复的谁。
private int id;
private int userId;
private int entityType;
private int entityId;
private int targetId;
private String content;
private int status;
private Date createTime;
一个是查询评论,一个是查询评论数量,这里也需要用到分页查询,用了offset和limit
-
之前这个方法返回了帖子和作者的数据,因为评论需要分页,所以传入page对象。
-
对page对象进行配置,一页显示5条,page的路径和总的评论数
-
首先用上面写的方法查询评论,放到list里,然后还需要进行一些处理。查询到的评论里只有user_id,没有用户名,同时评论下还有回复。
-
VO代表显示对象,用来显示在页面上的对象。
-
整个逻辑就是查出当前帖子下的所有评论,遍历所有评论,处理用户名等信息,查询评论下的回复,遍历每个回复,处理用户名等信息。
@RequestMapping(path = "/detail/discussPostId", method = RequestMethod.GET)
public String getDiscussPost(@PathVariable("discussPostId") int discussPostId, Model model, Page page)
//帖子
DiscussPost post = discussPostService.findDiscussPostById(discussPostId);
model.addAttribute("post", post);
//作者
User user = userService.findUserById(post.getUserId());
model.addAttribute("user", user);
//评论的分页信息
page.setLimit(5);
page.setPath("/discuss/detail/" + discussPostId);
page.setRows(post.getCommentCount());
//评论:给帖子的评论
//回复:给评论的评论
//评论列表
List<Comment> commentList = commentService.findCommentsByEntity(ENTITY_TYPE_POST, post.getId(), page.getOffset(), page.getLimit());
//评论Vo列表
List<Map<String, Object>> commentVoList = new ArrayList<>();
if (commentList != null)
for (Comment comment : commentList)
//评论Vo
Map<String, Object> commentVo = new HashMap<>();
//评论
commentVo.put("comment", comment);
//作者
commentVo.put("user", userService.findUserById(comment.getUserId()));
//回复列表
List<Comment> replyList = commentService.findCommentsByEntity(ENTITY_TYPE_COMMENT, comment.getId(), 0, Integer.MAX_VALUE);
//回复的Vo
List<Map<String, Object>> replyVoList = new ArrayList<>();
if (replyList != null)
for (Comment reply : replyList)
Map<String, Object> replyVo = new HashMap<>();
//回复
replyVo.put("reply", reply);
//作者
replyVo.put("user", userService.findUserById(reply.getUserId()));
//回复目标
User target = reply.getTargetId() == 0 ? null : userService.findUserById(reply.getTargetId());
replyVo.put("target", target);
replyVoList.add(replyVo);
commentVo.put("replys", replyVoList);
//回复数量
int replyCount = commentService.findCommentCount(ENTITY_TYPE_COMMENT, comment.getId());
commentVo.put("replyCount", replyCount);
commentVoList.add(commentVo);
model.addAttribute("comments", commentVoList);
return "/site/discuss-detail";
怎么实现注册功能的?
根据请求来拆解功能 1,打开注册网页 2,把注册的信息发送给服务器(点注册) 3,把激活邮件发送给邮箱 4,利用激活链接打开网页
每一次请求都是先开发数据访问层,在开发业务层,最后开发视图层(三层架构),但是每一次请求不一定要用到这三层
项目中的框架或者中间件的使用细节。(项目中如何使用ES,ES怎么支持搜索?redis和DB是如何配合使用的)
redis的使用
概念:redis是一个非关系型数据库,数据存储在内存中,读写速度快。可以存储键和五种不同类型值的映射。只能以字符串为键,值支持:字符串,列表,无序集合,有序集合,hash散列表。
-
使用redis存储验证码:
-
因为验证码需要频繁的进行访问与刷新,因此对性能的要求较高; 验证码不需要永久保存,通常在很短的时间后就会失效; 分布式部署的时候,存在session共享的问题。
-
使用redis存储登录凭证:因为后台在每次处理请求的时候都要查询用户的登录凭证,访问的频率非常高,因此需要使用redis存储。
-
使用redis缓存用户信息: 因为后台在每次处理请求的时候都要根据用户的凭证查询用户信息,访问的频率非常高。
-
Redis可以使用zset对需要排序的数据进行自定义的排序。
怎样存储的点赞/关注/缓存用户数据:
-
点赞:点赞key是实体类型实体id,vlaue存的是登录者的用户id,使用的数据类型是
set无序集合
存储 -
关注:使用zSet类型存储,key为被关注者,value保存关注者以及关注时间为score
-
缓存用户数据:使用Value类型,key为用userID得到的key,value为user对象(设置过期时间,且数据修改时需要清除缓存)
一般我们来使用redis做缓存,那么redis如何与数据库配合,使得我们的项目质量更高呢。我们一般将用户访问频繁,且修改频度低的数据放在缓存中,以提高响应速度。在前端发来访问请求时,我们一般进行以下逻辑操作:
-
查询操作:
前端发来请求时,先进行缓存的查询,如果缓存存在要查询的数据,则返回。否则去数据库中查询,并添加到缓存中,再返回数据,这样在下次查询时,便可直接从缓存中取。
-
添加操作:
添加操作我们直接添加到数据库即可,也可以在添加到缓存的同时添加到数据库。但在数据量较大时,推荐的做法是先将数据添加到缓存,在另一个线程中将数据同步到数据库。
-
修改操作:
修改操作先修改数据库,再将缓存的数据删除即可,不要直接更新缓存,效率太低。
注意:本文仅仅适用于普通web项目,对于高并发项目,需考虑数据同步问题。
kafka的使用
Kafka简介:Kafka是一种消息队列,主要用来处理大量数据状态下的消息队列,一般用来做日志的处理,是一个分布式、支持分区的(partition)、多副本的(replica),基于zookeeper协调的分布式消息系统,它的最大的特性就是可以实时的处理大量数据以满足各种需求场景。
当有点赞,评论,关注请求时,会发送系统通知点赞,评论,关注的对象。在处理系统信息时,使用到了Kafka。
具体来说,先定义了生产者类和消费者类,其中生产者被点赞/评论/关注功能对应的Controller使用,产生消息。而消费者负责消息(message)到来时,把消息存到数据库内。
ES的使用
项目扩展在进行帖子搜索时,使用到了ES。可用Repository和Template两种方式,由于Repository搜索到的结果(直接返回的post类,方便)没有高亮标签(why),所以使用了template方式重写了mapResults函数,获得了带有高亮标签的post。 搜索:定义SearchQuery,确定搜素内容,排序方式,高亮等。接着使用elasticTemplate.queryForPage方法,需要重写mapResults函数,得到高亮数据。
项目中存在的问题及解决方案
是怎样实现统一捕获异常的? 在SpringBoot的项目某一路径下,加上对应的错误页面,发生错误时自动会跳转。服务器的三层结构中,错误会层层向上传递,所以只需要在表现层(controller)统一处理错误即可。 方法:在controller中加上advice包,并通过注解@ControllerAdvice和@ExceptionHandler,统一捕获异常。
是怎样实现统一记录日志的? 使用了AOP技术(面向切面编程),这里使用到的是SpringAOP。 AOP技术能够将哪些与业务,但是为业务模块共同调用的逻辑或责任(比如事务处理,日志记录,权限控制等),封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的扩展性和维护性。
SpringAOP本质上基于动态代理,当要代理的对象实现了某接口,会使用JDK动态代理,在运行时通过创建接口的代理实例,织入代码。当要代理的对象没有实现接口,则使用Cglib技术(编译时增强),通过子类代理织入代码。
项目的具体功能点如何优化(查询评论是在DB中扫表查询吗?想要查询的更快需要做哪些优化?)
1、使用索引 尽量避免全表扫描,首先应考虑在where及order by,group by涉及的列上建立索引。
2、优化SQL语句 通过explain来查看SQL语句的执行效果,可以帮助选择更好的索引和优化查询语句。例如:
explain select * from news;
不要返回用不到的字段 ,不在索引列做运算或者使用函数 ,查询尽可能使用limit减少返回的行数,减少数据传输时间和带宽浪费。
3、优化数据库对象 优化表的数据类型
select * from 表名 procedure analyse();
对表进行拆分: 可以提高表的访问效率。有两种拆分方法: 垂直拆分:把主键和一些列放在一个表中,然后把主键和另外的列放在另一个表中。 使用场景:如果一个表中某些列常用,另外一些不常用,就可以垂直拆分。 水平拆分:根据一列或者多列数据的值把数据行放到两个独立的表中。 使用中间表来提高查询速度: 创建中间表,表结构和源表结构完全相同,转移要统计的数据到中间表,然后在中间表上进行统计,得出想要的结果。
项目中最有挑战的模块,怎么解决的
点赞、关注、缓存用户的信息,通过redis这一个非关系型数据库,将数据存储在内存中,读写速度快,满足高性能的要求。
项目中使用spring框架的原因
spring有很多模块组成,利用这些模块可以方便开发工作。
这些模块是:核心容器(spring core)/数据访问和集成(Spring JDBC)/Web(Spring Web/MVC)/AOP(Spring Aop)/消息模块/测试模块(Spring Test)等。
项目要增大10倍的qps,你会怎么设计?
-
QPS(TPS):每秒钟request/事务 数量
-
并发数: 系统同时处理的request/事务数
-
响应时间: 一般取平均响应时间
由公式:QPS(TPS)= 并发数/平均响应时间 可以看出,要提高qps,我们必须做2个方面努力
增加并发数:
1.比如增加tomcat并发的线程数,开喝服务器性能匹配的线程数,可以更多满足服务请求。
2.增加数据库的连接数,预建立合适数量的TCP连接数。
3.后端服务尽量无状态话,可以更好支持横向扩容,满足更大流量要求。
4.调用链路上的各个系统和服务尽量不要单点,要从头到尾都是能力对等的,不能让其中某一点成为瓶颈。
5.RPC调用的尽量使用线程池,预先建立合适的连接数。
减少平均响应时间:
1.请求尽量越前结束,越好,这样压力就不要穿透到后面的系统上,可以在各个层上加上缓存
2.流量消峰。放行适当的流量,处理不了的请求直接返回错误或者其他提示。和水坝道理很类似
3.减少调用链
4.优化程序
5.减少网络开销,适当使用长连接
6.优化数据库,建立索引
项目上线后出现线上问题怎么解决?比如频繁fullGC,定时任务失败怎么办?
以上是关于仿牛客网论坛项目的主要内容,如果未能解决你的问题,请参考以下文章