仿牛客网论坛项目

项目本身

项目的背景,解决了一个什么样的问题

这个项目的整体结构来源于牛客网,主要使用了Springboot、Mybatis、MySQL、Redis、Kafka、等工具。主要实现了用户的注册、登录、发帖、点赞、系统通知、按热度排序、搜索等功能。另外引入了redis数据库来提升网站的整体性能,实现了用户凭证的存取、点赞关注的功能。基于 Kafka 实现了系统通知:当用户获得点赞、评论后得到通知。利用定时任务定期计算帖子的分数,并在页面上展现热帖排行榜。

项目中的职责是什么

1、完成软件系统代码的实现,编写代码注释和开发文档; 2、辅助进行系统的功能定义,程序设计; 3、根据设计文档或需求说明完成代码编写,调试,测试和维护;

项目中使用的技术栈是什么?项目架构是怎么样的?使用微服务了嘛?

服务器分为表现层/业务层/数据层,其中Spring MVC是工作在表现层,作用是接收/解析用户发送的请求,调用对应的业务类,根据业务类返回的结果(ModelAndView),调用view进行视图渲染,并将渲染后的View返回给请求者。具体分为以下8步:

  1. 客户端(浏览器)发送请求给前端处理器(DispatcherServlet)(发送请求,响应结果);

  2. DispatcherServlet根据请求信息调用HandlerMapping,查找到对应的Handler;

  3. 查找到对应的Handler(也就是Controller)后,由HandlerAdapter适配器处理;

  4. HandlerAdapter根据Handler来调用真正的Controller;

  5. Controller进行业务处理,返回ModelAndView对象,Model是数据对象,View是逻辑上的View;

  6. ViewResolver根据逻辑view找到实际view;

  7. DispatcherServlet把Model传给view进行视图渲染,然后返回给请求者。

mvc三层架构:

  1. C - Controller:控制器。接受用户请求,调用 Model 处理,然后选择合适的View给客户。

  2. M - Model:模型。业务处理模型,接受Controller的调遣,处理业务,处理数据。

  3. 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 commentList = commentService.findCommentsByEntity(ENTITY_TYPE_POST, post.getId(), page.getOffset(), page.getLimit());
        //评论Vo列表
        List> commentVoList = new ArrayList<>();
        if (commentList != null) {
            for (Comment comment : commentList) {
                //评论Vo
                Map commentVo = new HashMap<>();
                //评论
                commentVo.put("comment", comment);
                //作者
                commentVo.put("user", userService.findUserById(comment.getUserId()));
                //回复列表
                List replyList = commentService.findCommentsByEntity(ENTITY_TYPE_COMMENT, comment.getId(), 0, Integer.MAX_VALUE);
                //回复的Vo
                List> replyVoList = new ArrayList<>();
                if (replyList != null) {
                    for (Comment reply : replyList) {
                        Map 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散列表。

  1. 使用redis存储验证码:

  2. 因为验证码需要频繁的进行访问与刷新,因此对性能的要求较高; 验证码不需要永久保存,通常在很短的时间后就会失效; 分布式部署的时候,存在session共享的问题。

  3. 使用redis存储登录凭证:因为后台在每次处理请求的时候都要查询用户的登录凭证,访问的频率非常高,因此需要使用redis存储。

  4. 使用redis缓存用户信息: 因为后台在每次处理请求的时候都要根据用户的凭证查询用户信息,访问的频率非常高。

  5. Redis可以使用zset对需要排序的数据进行自定义的排序。

怎样存储的点赞/关注/缓存用户数据:

  • 点赞:点赞key是实体类型实体id,vlaue存的是登录者的用户id,使用的数据类型是set无序集合存储

  • 关注:使用zSet类型存储,key为被关注者,value保存关注者以及关注时间为score

  • 缓存用户数据:使用Value类型,key为用userID得到的key,value为user对象(设置过期时间,且数据修改时需要清除缓存)

一般我们来使用redis做缓存,那么redis如何与数据库配合,使得我们的项目质量更高呢。我们一般将用户访问频繁,且修改频度低的数据放在缓存中,以提高响应速度。在前端发来访问请求时,我们一般进行以下逻辑操作:

  1. 查询操作:

    前端发来请求时,先进行缓存的查询,如果缓存存在要查询的数据,则返回。否则去数据库中查询,并添加到缓存中,再返回数据,这样在下次查询时,便可直接从缓存中取。

  2. 添加操作:

    添加操作我们直接添加到数据库即可,也可以在添加到缓存的同时添加到数据库。但在数据量较大时,推荐的做法是先将数据添加到缓存,在另一个线程中将数据同步到数据库。

  3. 修改操作:

    修改操作先修改数据库,再将缓存的数据删除即可,不要直接更新缓存,效率太低。

    注意:本文仅仅适用于普通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,定时任务失败怎么办?

你可能感兴趣的:(数据库,java,sql)