- 当前位置:
- 首页
- VIP_社区项目实战
- 正文
社区项目面试提炼+答案总结(重要重要)
这里我针对各个模块提炼的一些问题,有点难,大家回答不出来也没有关系,参考下别人的回答即可,这些回答也是来自于其他学员的答案整理。
框架技术细节
你在项目中是如何使用Spring Boot来搭建整个系统的?
帅地注意:这一部分,主要是考察你是否真的做过,对这些框架的使用是否熟悉,因为很多人,都是抄的,可能连框架最基本的一些认识也不大懂。
这里我印象中有两种创建方式,一种是spring官网创建springboot,一种是直接idea里面创建。需要注意的是我们使用的插件特别多,jdk8和jdk12又比较老,如果springboot的版本号和jdk或者插件不兼容,就会出现各种各样的离谱问题。这里可以去一些技术交流平台获取对应适配版本之类的。
使用 SpringBoot 搭建,你觉得有哪些优缺点?
(这里可以扯一扯SSM,然后再扯SpringBoot)
Spring依赖注入DI来管理各层的组件,使用面向切面编程AOP管理事物、日志、权限等。
SpringMVC代表了Model(模型)View(视图)Controller(控制)接收外部请求,进行分发和处理。
Mybatis是基于jdbc的框架,主要用来操作数据库,并且将业务实体和数据表联系起来。
而项目之所以选择使用SpringBoot,首先是看中了它在配置方面做了特别多的简化。在SSM框架下,使用需要大量的配置,包括XML配置文件的编写、依赖的引入等。而Spring Boot采用约定大于配置的原则,大部分常用的配置都有合理的默认值,无需手动配置,直观的感受就是只需要创建就可以快速启动项目。
其次是集成度高,在SSM框架中,Spring、SpringMVC、MyBatis是独立的框架,需要开发者手动整合,而Spring Boot将它们集成在一起,通过自动配置和Starter依赖,可以更方便地使用这些框架,减少了整合的工作量。
然后是依赖管理做的好,在SSM框架中,需要手动管理各种依赖包的版本和冲突问题,而Spring Boot提供了丰富的Starter依赖和插件化机制,可以方便地引入和管理各种依赖,减少了依赖管理的工作量。
最后就是社区活跃,我们在消息队列技术选型的过程中也有考虑到四种不同消息队列的社区活跃度。Spring Boot拥有庞大的社区支持和活跃的开发团队,提供了丰富的文档、教程和示例,同时拥有丰富的插件和生态系统,可以满足各种不同需求。
可以说一下你这个项目,用到了哪些 SpringBoot 的注解吗?
(我把一些有印象的放进来,也可能有一些项目里用过但我忘了)
- @Autowired: 自动装配Bean。
- @Component: 标识一个普通的组件类,使其成为Spring管理的Bean。
- @Service: 标识服务层组件。
- @Controller: 标识控制器组件,用于处理HTTP请求。
- @Configuration: 标识一个配置类,通常用于定义Bean。
- @Bean: 用于方法上,将方法返回值注册为Spring应用上下文中的Bean。
- @Value: 用于注入配置文件中的值。
- @RequestMapping: 用于映射请求路径到控制器方法或类。
- @GetMapping: 处理HTTP GET请求的简化注解。
- @PostMapping: 处理HTTP POST请求的简化注解。
- @RequestParam: 绑定请求参数。
- @RequestBody: 绑定请求体到方法参数。
- @Transactional: 标识一个方法或类需要事务支持。
你是如何配置和使用Spring Security来实现不同用户角色的权限控制的?
配置和使用Spring Security来实现不同用户角色的权限控制,是一个挺有趣的过程。首先,得先在项目里添加Spring Security的依赖,这样才能用上它的功能。然后就是创建一个配置类,这个配置类需要继承一个叫WebSecurityConfigurerAdapter的东西。通过重写里面的一些方法,我们可以对安全性进行详细配置。
在配置类里,主要有两个方法需要重写,一个是用来配置HttpSecurity的,这个方法可以用来定义哪些URL路径需要进行权限控制,设置登录页面、处理登录逻辑、处理注销逻辑以及配置CSRF保护等等。比如说,可以设置某些路径只能被特定角色访问,其他人访问就会被拒绝。
另一个方法是用来配置AuthenticationManagerBuilder的,这个方法主要是用来定义用户认证信息的来源。可以选择内存方式来管理用户信息,这种方式比较简单,适合测试或者小项目。也可以选择数据库方式来管理用户信息,这种方式更适合正式项目。还可以自定义用户服务,适合那些需要特殊认证逻辑的场景。
用户和角色管理方面,可以通过内存、数据库或者自定义用户服务来定义用户和角色信息。内存方式就是直接在配置文件里定义用户、密码和角色,适合简单测试。数据库方式则是通过数据表来存储用户、密码和角色信息,适合正式环境。自定义用户服务则需要实现一个叫UserDetailsService的接口,能更灵活地进行用户认证。
在方法或者类上使用注解也能实现权限控制。比如,可以在方法上用@PreAuthorize注解来控制访问权限,这样可以根据方法参数来检查权限。还可以用@Secured注解在方法上定义需要的角色,这样只有特定角色才能访问这个方法。另外,还可以用@RolesAllowed注解来限制特定角色访问某些资源。
配置登录逻辑也是很重要的一部分。通过formLogin()方法,可以配置登录页面的路径、处理登录的路径、登录成功和失败后的跳转路径等等。这样用户登录后能正确跳转到对应的页面。如果登录失败,也能跳转到指定的页面,提示用户重新登录。
最后是配置注销逻辑。通过logout()方法,可以定义注销的路径以及注销成功后的跳转路径。这样用户注销后能正确跳转到对应的页面,确保用户体验良好。
总的来说,使用Spring Security来实现不同用户角色的权限控制,既能确保系统的安全性,也能根据用户角色对系统功能进行精细化控制,提升系统的可靠性和用户体验。虽然一开始配置可能有点复杂,但一旦掌握了,确实是个很强大的工具。
Mybatis 操作某一个表,讲一下大致流程
首先,我们要通过 Maven 在 pom.xml 文件中添加 Mybatis 的依赖。这个步骤是必不可少的,因为只有这样,我们才能在项目中使用 Mybatis 的功能。添加依赖之后,就是配置 MyBatis,包括设置数据源、配置 SQL 映射文件等。这一步主要是为了让 Mybatis 知道从哪里获取数据,以及如何处理这些数据。
接下来,我们需要创建一个 Mapper 接口,用于定义操作数据库的方法。这个接口相当于我们和数据库打交道的桥梁。在这个接口中,我们可以定义各种操作数据库的方法,比如查询、插入、更新、删除等。为了让这些方法能够实际操作数据库,我们需要编写对应的 XML 映射文件,或者使用注解来定义 SQL 语句。这样,当我们调用这些方法时,Mybatis 就会根据我们定义的 SQL 语句去操作数据库。
这里还可以提到一个特别常用的插件叫 Mybatis Generator。这个插件可以帮助我们自动生成 XML 文件和 Mapper 接口,大大减少了我们的工作量。通过这个插件,我们只需要配置一些基本的信息,就能自动生成大量的代码,非常方便。
总的来说,使用 Mybatis 操作数据库的流程就是:首先添加依赖,然后配置 Mybatis,接着创建 Mapper 接口,最后编写 XML 映射文件或使用注解定义 SQL 语句。如果想要进一步简化工作,还可以使用 Mybatis Generator 插件来自动生成代码。通过这些步骤,我们就可以轻松地使用 Mybatis 来操作数据库了。
项目通用模版(常见)
这部分是通用模版,就是很多面试官会从这些问题中,来切入你的项目,大家在回答的时候,尽可能做好引导
介绍一下你的项目
(我包装成了一个校园树洞)这个项目是我大三参加校园创新创业大赛的时候打算做的一个项目,主要的契机来源于当时学校里很火的一个app,叫做校园树洞。一个类似匿名论坛,但不匿名学校的app。并且也拥有热帖热榜这类功能。但是很遗憾由于它的敏感词过滤做的不是很好,总有一些非正能量的内容,后来被官方封掉了。
于是以此为灵感,再加上要参加比赛我就做了一个类似的项目。这个项目是我和另一个小伙伴负责的,我负责后端部分,主要使用了Springboot、Mybatis、MySQL、Redis、Kafka等工具。主要实现了用户的注册、登录、发帖、点赞、系统通知等功能。另外引入了redis数据库来提升网站的整体性能,实现了用户凭证的存取、点赞关注,热榜和共同好友,观看数统计和签到等功能。基于 Kafka 实现了系统通知:当用户获得点赞、评论后得到通知。包括用前缀树来做敏感词过滤,ThreadLocal保存用户信息等等。
(介绍为什么没上线)后来写前端的小伙伴因为某些原因没有继续做,所以这个项目的前端资源还是有问题,我能力有限就暂时没有上线,但是源码和一些功能测试给学院的老师评审展示之后还是成功拿到了优秀奖。
你觉得项目的难点或者亮点是什么?
PS:注意,所谓难点或者亮点,并不一定真的需要多难,而是要讲出一个 why,以及你要熟悉,因为面试官也是给你机会让你挑内容来谈
其实从技术层面的角度讲,难者不会会者不难。但对于我来说比较难的是探索和思考一个功能的实现方式,以及反向思考一个中间件可以开拓出哪些新的用户会喜欢的功能,并找到最好的实现方式来解决需求。
比如这个部分对我来说印象最深刻的就是redis这一中间件的利用和思考。最一开始的电商支付项目,我是纯粹的把它当作购物车缓存来用,采用了hash的数据结构进行一个用户多个商品的数量存储。但是第二个项目我想基于redis支持的多种数据结构和方法来更深入的学习redis的实际功能应用方向都有哪些,并对之前的功能做进一步优化(比如访问人数可以用hyperloglog来做而不用set)于是利用Redis的数据结构实现了比如点赞,相互关注,排行榜,访问人数和活跃度计算这些功能,他们分别应用了不同的数据结构,也通过这种热爱驱使的对于技术应用场景和实现方式的思考,锻炼了我的学习和动手能力。
介绍一下项目中的一些表结构
在我的项目中,设计了很多数据库表。先说说校园树洞这个项目,其中有一个评论表(comment),这个表结构比较典型。首先是 id,这是每条评论的唯一标识符。然后是 userid,表示发评论的用户 ID。接下来是 entitytype 和 entityid,分别代表被评论的对象类型和对象的 ID。对象类型可以是帖子或者评论,具体用数字来区分。然后是评论内容 context,以及评论的状态 status,这个状态可以表示评论是否已删除、是否已审核等。最后还有一个 createtime,记录评论的创建时间。
再说说电商支付项目,这个项目里有一个用户表(user),结构也比较有代表性。这个表包含 username(用户名),password(密码),email(邮箱),role(角色权限),以及 is_deleted(是否被删除)。这里有个特别的设计,就是即使用户被标记为删除了,我们也不会真的从数据库中删除他的记录,而是通过设置 is_deleted 标记来实现伪删除。这样,如果一个用户已经被删除,那么即使数据库中有这个邮箱,也可以用这个邮箱重新创建新用户,因为我们只是标记了删除状态,并没有实际删除数据。
这种设计不仅确保了数据的完整性和可追溯性,还能避免因实际删除数据而带来的数据丢失风险。通过合理的表结构设计,我们能够更高效地管理和使用数据库,提高系统的整体性能和可靠性。
平时怎么做单元测试的?
在平时的开发过程中,单元测试是必不可少的一部分。我主要使用JUnit4来编写测试类。JUnit4是一个非常流行的Java单元测试框架,它能帮助我们方便地进行测试和验证代码的正确性。通过编写测试类和测试方法,我们可以对项目中的各种功能进行全面的测试,确保代码逻辑的正确性和稳定性。
除了JUnit4,对于API接口的测试,我会使用Postman。Postman是一款强大的API测试工具,可以模拟各种HTTP请求,验证接口的正确性和响应结果。通过Postman,我们可以方便地进行接口测试,包括GET、POST、PUT、DELETE等请求方法,并验证接口的响应数据和状态码。
如果涉及到Linux系统的性能测试,通常会用到一些系统监控工具。比如,top和uptime用于了解系统负载情况。top可以实时显示系统中各个进程的资源使用情况,而uptime则提供了系统运行时间和平均负载信息。如果top工具无法使用,ps也是一个不错的替代工具,它可以显示当前系统中的进程列表及其详细信息。
在进行压力测试时,会使用到stress和sysbench。stress是一款简单的压力测试工具,可以对CPU、内存、IO等资源进行压力测试,帮助我们评估系统在高负载下的表现。而sysbench则是一个多功能的多线程测试工具,可以对CPU、内存、磁盘IO、数据库等进行性能测试。
此外,还有一些Linux性能分析工具,比如sysstat。sysstat包含多个实用程序,可以对系统的各种资源进行监控和分析。比如,mpstat可以分析CPU的使用情况,pidstat可以分析进程的资源使用情况,vmstat可以分析系统的内存和虚拟内存使用情况。这些工具对于定位性能瓶颈、优化系统性能非常有帮助。
通过这些工具和方法,我们可以全面地进行单元测试和性能测试,确保系统的稳定性和高效性。单元测试不仅能够验证代码的正确性,还能在项目的后期维护中,提供极大的帮助,减少Bug的引入,提高开发效率。
登录注册
关于登陆注册模块,其实可以问的非常非常多,这取决于你是怎么实现的以及在简历上是如何写的,如果你自己加了很多内容,那你简历上可以好好写一下。
用户注册,是否有进行邮箱验证或者手机验证?如果有的话,怎么保证 60 秒内只能发送一次验证码?以及如何防止验证码接口被刷?
如果是验证码登录,登录的时候随机生成一个4位UUID,后端用google的kaptcha依赖,生成图片与验证码字符串,uuid为key,验证码字符串为value,并设置key的过期时间,存入redis。传递base64图片和uuid给前端,进行校验。redis性能高,可以设置有效时间,很方便。
我们通过邮箱进行验证,输入邮箱点击发送,请求传递给后端,到达redis,设置redis的key值为邮箱,redis会先判断有效时间是否小于4min(持续时间5min,60s内只发送一次)之后查询是否邮箱已被注册,用Javamail基于smtp协议发邮件,邮件里和验证码差不多,是一个随机的code,判断code是否一致就行,删除缓存
描述下你的登录流程设计
在设计登录流程时,首先会生成一个随机验证码,然后用户输入验证码后,我们会进行比对,确保用户输入的验证码和生成的验证码一致,这一步主要是为了防止机器人和恶意攻击。
接下来,根据用户是否勾选了“记住此用户”来确定登录的过期时间。如果用户选择了记住登录,我们会设置一个较长的过期时间,以便用户下次访问时无需重新登录。如果没有选择,则会设置一个较短的过期时间,确保安全性。
然后,我们会验证用户传输的参数,包括用户名和密码,确保这些参数不为空。接着,会检查用户是否已经注册,并且账户是否激活。如果用户不存在或未激活,就会提示相关的错误信息。如果用户存在且已激活,我们还需要验证用户输入的密码是否正确。
在验证通过后,我们会生成一个用户登录凭证对象,将用户的相关信息和登录状态记录下来。这个登录凭证对象会插入到数据库中,同时也会存储在Redis中,以方便后续的登录状态验证。通过将登录凭证存储在Redis中,可以提高系统的性能和安全性。
最后,我们会将登录凭证存入用户的浏览器Cookie中,并返回给用户。这样,用户在后续访问时,可以通过Cookie中的登录凭证进行快速验证,而无需每次都重新输入用户名和密码。
将登录凭证存入Redis有几个主要的好处:
- 提高安全性:将Token存储在Redis中,比保存在本地Cookie或浏览器存储中更安全,因为攻击者无法访问服务器上的Token。Redis还支持设置过期时间,可以自动删除旧Token,进一步提高安全性。
- 分布式部署:如果应用程序采用分布式部署架构,在每个节点上保存Token会增加管理和同步的复杂性。使用Redis作为统一的Token存储介质可以简化这个过程,确保所有节点都可以访问相同的Token。
- 支持多端登录:如果应用程序允许用户从多个设备或浏览器登录,需要跨设备/浏览器共享Token。将Token存储在Redis中可以轻松实现这一点,因为所有设备和浏览器都可以访问相同的Redis服务器。
- 高效查询:Redis是内存中的数据存储系统,具有快速的读取和写入速度。与传统数据库相比,Redis的响应时间更短,可以提高整个应用程序的性能。这样,即使在高并发的情况下,系统也能快速响应用户的登录请求。
通过这些步骤和策略,我们不仅能提高系统的安全性和性能,还能提供更好的用户体验。
如果同一个用户同时在多个终端登录,你是如何处理?
这里我有查到保持单个终端登录的方式,可以通过JWT进行解决。用户登录成功后,后端生成两个令牌:一个是短期有效的JWT,另一个是长期有效的刷新令牌。客户端在每次请求中携带JWT,后端验证JWT的签名和有效期。如果客户端的JWT即将过期,客户端就使用刷新令牌向后端请求更新JWT。每个设备都可以使用相同的刷新令牌来请求新的JWT,从而实现多个设备同时登录。当用户在新设备上登录时,旧设备上的JWT和刷新令牌会失效,确保安全性和唯一性。
PS:如果实际项目没有处理过,那你可以说一说你会怎么处理
考虑过单点登录问题吗?如果以后多个应用想同用同一套登陆系统,登陆这块应该如何设计?
是的,我们确实考虑过单点登录(SSO)的问题。单点登录可以极大地简化用户的登录流程,尤其是在多个应用需要共享同一个登录系统的情况下。学校的官网就是一个很好的例子,它在url里有一个SSO系统,无论是访问教务系统还是学工系统,都需要先在验证中心登录。
具体来说,这个流程是这样的:用户首先访问SSO登录页面并进行身份验证。如果登录成功,SSO验证中心会生成一个令牌(Token),并将这个Token发给客户端。然后,当用户访问其他需要认证的应用时,比如教务系统或学工系统,客户端会将这个Token附带在请求中发送给这些应用。
这些应用接收到Token后,会去SSO验证中心验证这个Token的有效性。如果Token是有效的,说明用户已经通过了认证,应用就会允许用户访问。如果Token无效,应用就会拒绝用户的访问,并可能引导用户重新登录。
这种设计有几个关键点。首先,SSO验证中心是整个系统的核心,它负责所有的用户认证操作,生成和验证Token。其次,所有需要认证的应用都需要与SSO验证中心进行通信,验证Token的有效性。最后,Token的安全性和有效期是需要重点考虑的,确保Token不会被滥用或长时间有效。
通过这种方式,用户只需要登录一次,就可以访问多个不同的应用,极大地提升了用户体验。同时,这种设计也提高了系统的安全性和可管理性,因为所有的认证操作都集中在一个地方进行,便于统一管理和监控。
如果以后有更多的应用需要共享同一套登录系统,只需要确保这些应用能够与SSO验证中心进行通信,并且正确处理Token的验证过程即可。这种扩展性使得单点登录系统非常适合大规模、多应用的环境,能够有效简化用户管理和提高系统的安全性。
如何处理用户密码的存储
这里项目中我们使用md5+盐来解决。md5是一种常见的哈希函数,用于生成数据的摘要。它通常用于存储密码的哈希值,而不是直接存储原始密码。并且它是单向的,很安全。平时登录学校网站的时候,点击登录按钮的一瞬间,你会发现密码对应的小黑点从原来的突然变成了许多位,这里应该就是前端或者数据库对密码进行了md5+盐的处理。加盐是为了防止攻击者直接暴力破解密码,也就是将常用密码表全部md5处理之后一一和数据库中对应的明文存储的md5处理过的密码进行比对,如果对应上了,那么也就被破译了。而加入一小段随机的字符串,能够极大的增加破译难度,让这种暴力破解方式基本不可能成功。
你觉得你这种密码加密方式,是否存在安全问题?你觉得可以怎么提高安全性?
这里我看过一个博客,有讲到一种社会工程学的破解方式。如果黑客通过电话、电子邮件或者社交工程技术,向用户索要重设密码或者确认安全问题的信息,而盐是固定的字符串的话,那么就会出现盐本身被破译的情况。为了防止这种情况,必须要为盐的生成也涉及一个随机算法,并且每隔一段时间需要更换盐。而为了防止之前的密码用新盐解不开,可以将旧盐存放起来?
Cookie session,你在项目中是怎么使用的?
在项目早期,我们使用的是Cookie来存储用户信息。这种方式是把用户的信息直接存储在客户端浏览器的Cookie里。虽然实现起来比较简单,但安全性相对较低,因为Cookie是存储在客户端的,容易被攻击者获取或篡改。
后来,我们转向使用Session。Session的方式是将用户的信息存储在服务器端的数据库里,而客户端只保存一个Session ID。每次用户请求时,客户端会把这个Session ID发送给服务器,服务器根据Session ID找到对应的用户信息。这种方式安全性比Cookie高很多,因为敏感数据不再存储在客户端,而是保存在服务器端。
最终,我们采用了一种更为安全和高效的方案,即使用JWT(JSON Web Token)结合Redis来实现用户认证。在这种方式下,我们在用户登录成功后生成一个JWT,这个JWT包含了用户的身份信息,并用服务器的密钥进行签名,防止被篡改。然后,我们把这个JWT存储在客户端的Cookie里,同时在服务器端的Redis中保存与这个JWT相关的信息。
这种方式有几个显著的优点。首先,安全性更高,因为JWT是签名的,客户端无法篡改其中的信息。其次,使用Redis来存储Token及其相关信息,能够支持分布式部署和多端登录。所有的节点都可以访问相同的Redis服务器,确保用户的Token在所有节点间的一致性。最后,Redis是一个内存数据库,读写速度非常快,能够提高整个系统的性能。
通过这些方式的逐步演进,我们在项目中既保证了用户认证的安全性,又提高了系统的性能和灵活性。每次用户登录后,Token都会存储在客户端的Cookie里,服务器通过Redis验证Token的有效性,确保用户会话的安全和高效。
评论系统
说一下你的评论系统是怎么设计的?
都是基于mysql的用户表和评论表来实现,用户提交评论,包含了文章的id,评论的内容以及可选的父评论id。数据库验证用户的身份,随后存储评论内容放入数据库,返回一个评论id和时间戳。 服务器根据文章ID查询所有评论,并按时间顺序排序。
评论表包括评论id,评论者id,被评论的评论id,内容,时间戳,点赞数等内容。
对于多级评论,你会如何设计?
多级评论可以通过parentid字段来解决。顶级评论的该字段为null,然后递归向下找就可以得到评论的层级结构。
这里可能涉及到评论树相关的内容, 是用于表示评论及其回复之间层级关系的一种数据结构。它类似于树状结构,每个评论可以有多个子评论,从而形成一个多级嵌套的层次关系 。 通过树状结构,评论及其回复的关系层次分明,易于理解和导航。 也能够方便前端同学的代码实现。
评论的展示顺序是如何确定的?有哪些排序规则?
在我们的系统中,评论的展示顺序可以通过多种方式来确定,主要取决于具体的需求和用户体验。常用的排序规则包括以下几种:
首先,可以根据评论在数据库中的时间戳来排序。这样我们可以按时间顺序展示评论。比如,可以选择升序排列,这样最早的评论会排在最前面;也可以选择降序排列,这样最新的评论会排在最前面。这种排序方式非常直观,用户可以很容易地看到最新的互动或者从头开始阅读所有评论。
其次,我们可以根据评论的点赞数来排序。这种方式下,点赞数越多的评论会排在越前面,通常用于展示那些被认为更有价值或更受欢迎的评论。为了实现这种排序,可以使用Redis的有序集合(zset)。在有序集合中,每个元素都会有一个分数,我们可以将评论的ID作为元素,点赞数作为分数,这样就可以轻松实现基于点赞数的排序。
此外,还有一种常见的排序方式是综合排序,即结合时间和点赞数等多个因素,使用一定的加权算法来确定评论的最终排序。这种方式可以同时考虑评论的时效性和受欢迎程度,提供更平衡的展示效果。比如,我们可以给每条评论的时间戳和点赞数赋予不同的权重,然后计算一个综合得分,按得分排序。
总的来说,评论的展示顺序可以灵活调整,具体选择哪种排序规则可以根据具体场景和用户需求来决定。通过合理的排序,可以提升用户的阅读体验,使重要或热门的评论更容易被发现和互动。
对于热门评论和最新评论,你是如何进行排序和展示的?
在系统中,对于热门评论和最新评论的排序和展示,我们会采用不同的策略来确保用户能够方便地看到最重要和最新的内容。
首先,热门评论通常是指那些得到最多点赞或回复的评论。为了实现热门评论的排序,我们会综合考虑评论的点赞数、回复数和其他可能的互动指标。常见的方法是使用Redis的有序集合(zset)来存储评论的ID和其对应的得分。得分可以是点赞数、回复数等指标的加权和。比如,我们可以给每条评论的点赞数和回复数赋予不同的权重,计算一个综合得分,然后根据得分对评论进行排序。这样,得分最高的评论会排在最前面,确保用户能看到最受欢迎的评论。
其次,最新评论是指按照发布时间排序的评论。这种排序方式非常直观,可以让用户看到最新的互动情况。具体实现上,我们会根据评论在数据库中的时间戳进行排序。可以选择降序排列,这样最新的评论会排在最前面。这个过程可以在数据库查询时通过SQL语句直接实现,例如使用ORDER BY子句按时间戳降序排列。
为了确保用户能同时看到热门评论和最新评论,我们通常会在页面上分开展示这两类评论。比如,页面的顶部或显眼位置展示几条热门评论,下面再按时间顺序展示最新评论。这样既能满足用户查看受欢迎评论的需求,也能让他们及时了解最新的讨论。
面对大量评论数据,如何优化评论系统的加载速度
第一个是分页加载,对评论进行分类处理,避免一次性加载过多的评论,比如20条一页。
前端使用分页按钮和输入框跳转页码,每次只显示固定数量的评论
后端根据请求参数返回对应页码的评论数据
第二个就是使用redis来存储热门文章,例如站内hot100,并设置合理的缓存过期与更新机制来保证数据一致性。有点类似微博热搜的处理。
第三个是合理加mysql索引,比如对postid之类的常用字段添加索引,优化数据库查询
还有其他的方法,例如前端懒加载,只有滚动到评论区才加载。或者预加载之类的
前缀树是怎么实现的?说一下插入和查找的复杂度?
基础知识:
前缀树,也就是字典树,用来高效地存储和查找字符串。每个节点在树中代表一个字符,从根节点到任何一个节点的路径都代表一个字符串的前缀。每个节点主要有两个部分:一个是指向子节点的集合,另一个是一个布尔值,用来标记这个节点是否是某个单词的结尾。
当你要往前缀树里插入一个新字符串时,你会从根节点开始,一次处理字符串中的每一个字符。如果当前字符对应的节点还不存在,你就创建一个新的节点。这样操作的时间复杂度是 O(n),其中 n 是你要插入的字符串的长度。空间复杂度方面,如果插入的字符串和树中已有的字符串有很多重叠部分,那么你只需要额外的空间来存储那些新的、不重复的节点,空间复杂度可能会是 O(1)。但如果你插入的字符串完全没有重叠,那最坏情况下空间复杂度可能会达到 O(n),因为你可能需要为每个新字符都创建新的节点。
至于查找一个字符串的过程也是类似的。你从根节点开始,依次查找字符串中的每个字符。如果每个字符的节点都存在,并且最后一个字符的节点标记为结束,那么这个字符串就是树中已有的。查找的时间复杂度同样是 O(n),因为你要检查每个字符。空间复杂度在查找时是 O(1),因为查找过程只涉及访问已有的节点,不需要额外的存储空间。
具体实现:
我们计划使用两种不同的方法。第一种方法是直接利用Java提供的字符串操作API,具体来说,就是使用contains()方法。这种方法的思路很直接:我们遍历每一个需要检测的字符串,然后依次检查这个字符串是否包含我们的敏感词列表中的任何一个词。如果包含,我们就认定它是敏感词。这种方式很方便,因为Java已经为我们提供了内置的字符串匹配功能,我们只需要调用相关方法即可。
为了进行测试,我们需要生成一些数据。我们会生成一个包含10万个随机字符串的文件,这些字符串会被用作测试数据。生成数据后,我们还需要准备一个敏感词列表,这个列表会被存储在名为sensitive_words.txt的文件中。文件的格式非常简单,每一行代表一个敏感词,比如“badword1”、“badword2”这样的词语。
第二种方法是使用前缀树,也叫Trie。前缀树是一种特殊的树结构,非常适合处理前缀匹配问题。在实现过程中,我们首先要构建这个前缀树。我们会写一个方法来读取sensitive_words.txt文件,把每一个敏感词逐一插入到前缀树中。插入的过程是这样:我们从根节点开始,根据敏感词的每一个字符,逐级创建或查找对应的子节点,直到整个词都被插入到前缀树里。为了表示某个节点是一个词的结束,我们还会在最后一个字符对应的节点上打一个标记。通过这种方式,前缀树能够快速地判断一个字符串是否以某个敏感词为前缀或者是否完全匹配某个敏感词。
接着,我们要对这两种方法的性能进行测试。我们会分别使用这两种方法来检测10万条测试数据中的敏感词,并且记录下它们所花的时间。对于Java原生API的方式,我们就是简单地遍历每一个测试字符串,然后用contains()方法去检查是否包含任何一个敏感词。而对于前缀树的方法,我们会遍历每一个测试字符串,然后用前缀树提供的搜索功能来检查这个字符串中是否包含敏感词。
测试完成后,我们会对比两种方法的执行时间,看看哪一种在处理大规模数据时表现得更好。Java原生API的contains()方法虽然直接,但可能在处理大量数据或复杂匹配时效率不如前缀树。而前缀树的优势在于,它特别适合频繁的前缀匹配或敏感词查找任务,因为它能够在较短时间内定位到匹配的词语。
前缀树什么时候开始构建和加载数据?
最初,我们可能会选择在项目启动时就构建前缀树,这样可以确保在系统运行的每个时刻,前缀树都能提供最新的数据。这样做的好处是,系统启动后就能立即提供敏感词过滤的功能,不会有延迟。
不过,这种方法也有缺点。比如,如果敏感词表发生变化,我们需要重新构建前缀树以确保新的敏感词被及时加载,这可能会对系统性能产生影响。此外,对于那些只浏览内容而不发帖或评论的用户来说,前缀树的构建可能会显得多余,因为他们不会触发敏感词的处理。
一种思路是设置定期任务来更新前缀树。例如,可以定时检查敏感词表的变化,然后更新前缀树。这种方式能有效地保持前缀树的最新状态,同时减少了项目启动时的负担。对于那些只浏览不互动的用户,可以选择在他们实际进行发帖或评论操作时才加载前缀树,或根据实际需要进行预加载。这样做能减少内存占用,提升系统的响应速度。
前缀树还有其他应用场景吗?
前缀树确实有很多有用的应用场景,除了我们常见的敏感词过滤,它还可以用于很多其他功能。比如,自动补全就是一个典型的应用场景。在输入框中,当用户输入一部分词汇时,前缀树可以帮助系统快速找到所有以该前缀开头的词汇,从而给出相关的自动补全建议。这种功能在搜索引擎和文本输入系统中非常常见。
另外,拼写检查也是前缀树的一项重要应用。在拼写检查的过程中,系统可以通过前缀树快速检查用户输入的单词是否在字典中存在,如果没有找到匹配的词汇,就可以给出拼写错误的提示,并可能提供修正建议。
前缀树还可以用于统计和分析,比如每年统计十大热词的工作。通过前缀树记录和更新热门词汇,系统可以有效地跟踪词汇的使用频率,并根据统计数据识别出最常使用的词汇。这对于语言处理、文本分析等任务非常有用。
点赞 关注
Redis 里面存的是什么?
Redis 是一个非常灵活的内存数据库,它支持多种数据结构,比如字符串、哈希、列表、集合、排序集合等。在你提到的场景中,Redis 用来存储点赞和关注信息。
对于点赞功能,我们可以使用 Redis 的集合(set)。例如,我们会用用户 ID 作为键,创建一个用户点赞的集合。如果用户点赞了某个对象,我们就把这个对象的 ID 加入到集合中。例如,sadd user:likes:10000 object001 这条命令表示用户 ID 为 10000 的用户点赞了 ID 为 object001 的对象。集合的好处是它不允许重复,这样就能确保每个用户对某个对象只能点赞一次。
为了管理点赞数和进行排序,我们会用 Redis 的有序集合(zset)。在这个集合中,成员是对象的 ID,分数则是点赞的总数。每次用户点赞一个对象,我们都会更新这个对象在 zset 中的分数。这样一来,我们就可以很容易地得到每个对象的点赞数,并且可以通过排序操作快速获得点赞数最多的对象。
关注功能也是类似的实现方式。我们会用集合来存储用户关注的对象。每个用户的关注列表可以存储在一个集合中,当用户关注一个新对象时,就把这个对象的 ID 加入到集合里。这样,同样地,集合能够帮助我们有效管理和查询用户的关注信息。
总的来说,Redis 的集合和有序集合在这些功能的实现中扮演了关键角色,它们能够帮助我们高效地处理点赞和关注数据,并进行快速的查询和排序操作。
当大量用户同时点赞时,如何处理高并发问题?
当大量用户同时进行点赞操作时,处理高并发问题是至关重要的。首先,我们使用 Redis 作为缓存,因为 Redis 是一个内存数据库,能够非常高效地处理大量并发请求。它的性能非常高,即使在大规模并发操作的情况下,也能够迅速响应。
其次,为了避免频繁的写操作,我们可以采用批量处理的方式。具体来说,我们可以在一定的时间窗口内将点赞操作统一进行一次写入。这样可以显著减少对 Redis 的写操作频率,从而减轻系统负担,避免因频繁写入导致的性能问题。
如果系统的压力非常大,我们还可以利用消息队列来进行异步处理。点赞请求可以先写入消息队列中,然后由后台服务异步处理这些请求。这种方法可以显著提高系统的响应速度和处理能力,因为前端用户的请求不需要等待点赞操作完成即可获得响应,从而提高用户体验。同时,异步处理也能够帮助我们分散压力,避免瞬时的高并发导致系统崩溃或性能下降
高并发状态下,你是怎么保证点赞数量的线程安全的?
在处理高并发状态下点赞数量的线程安全问题时,我们可以采取几种有效的措施。首先,Redis 本身提供了一些原子操作,比如 INCRBY,它可以安全地增加一个键的值,这些操作在 Redis 内部是线程安全的,确保了即使在高并发的情况下,点赞数量的更新也不会出现冲突。
其次,我们可以使用分布式锁来进一步确保安全性。分布式锁能够确保在同一时刻只有一个线程能够修改点赞数量。这种方式通过锁机制避免了多个线程同时对同一数据进行操作,从而防止了数据的不一致性。
此外,消息队列也可以帮助解决线程安全问题。通过消息队列,我们可以将点赞请求异步处理,这样可以有效地避免多个线程同时操作同一个数据,减少了并发冲突的可能性。
最后,Redis 支持 Lua 脚本,这也是保证线程安全的一个好方法。我们可以将多个操作封装在一个 Lua 脚本中执行,这样可以确保这些操作的原子性。在脚本执行的过程中,Redis 会将所有的操作作为一个事务来处理,从而避免了线程间的干扰。通过这些方法,我们可以有效地管理高并发状态下的点赞数量,确保系统的稳定性和数据的准确性。
使用了哪些策略来避免重复点赞或误操作?
首先,点赞去重可以通过使用 Redis 的 set 集合来实现。因为 set 集合中的元素是唯一的,我们可以利用这一特性来确保每个用户对每个对象只能点赞一次。每次用户点赞时,我们将用户的 ID 和对象的 ID 作为 set 的元素进行存储。由于 set 集合自动处理重复元素,这样可以有效地避免重复点赞的问题。
其次,在面对多台设备可能同时进行点赞的情况时,分布式锁是一种很好的解决方案。通过分布式锁,我们可以确保在同一时刻只有一个操作能够对特定对象进行点赞。这种锁机制避免了多台设备同时对同一对象进行点赞的冲突,从而保证了数据的一致性和准确性。
另外,防重 token 也是一种常见的防止重复操作的方法。在每次用户点赞时,我们生成一个唯一的 token,并将其发送到服务器进行验证。服务器会检查该 token 是否已经被使用过,如果 token 已经存在,则拒绝本次操作。这种方法能有效地防止因重复请求或误操作导致的重复点赞问题。
这些策略可以结合使用,以确保系统在处理点赞操作时的准确性和稳定性,同时提高用户体验。
关注 这个功能,你是怎么实现的?
在实现关注功能时,我主要使用了 Redis 的 Set 数据结构,这个方法高效且简单。每个用户的关注关系和粉丝关系都可以通过两个不同的 Set 来管理。
具体来说,每个用户的关注列表和粉丝列表分别存储在两个 Set 中。比如,对于用户 A,他关注的用户的 ID 会被存储在用户 A 的关注集合中。而用户 A 的 ID 则会被存储在每一个被关注用户的粉丝集合中。当用户 A 关注某个用户 B 时,我们将用户 B 的 ID 添加到用户 A 的关注集合里,同时将用户 A 的 ID 添加到用户 B 的粉丝集合里。这样,当用户 A 取消关注某个用户 B 时,我们就从这两个集合中分别移除相应的 ID。
Redis 的 Set 操作非常快速,支持高效的添加、删除和查询操作。这样我们能够迅速获取某个用户的关注列表和粉丝列表,同时也能很快计算关注数和粉丝数。由于 Redis 是一个内存数据存储系统,它的操作速度远快于传统的数据库系统,因此能有效应对大规模用户的关注操作,确保系统的高效性和响应速度。
关注的时候,如何确保数据一致性?
确保数据一致性是关键,尤其是因为涉及到两个操作——将用户添加到关注列表和将用户添加到粉丝列表,这两个操作本身不是原子的。为了解决这个问题,我们可以采用几种方法来确保数据的一致性。
首先,使用 Redis 的 Lua 脚本是一种很有效的方法。通过 Lua 脚本,我们可以将这两个操作合并成一个原子性的操作指令。也就是说,在 Redis 中执行 Lua 脚本时,脚本里的所有命令会在一个事务中完成,确保操作的原子性和一致性。这样,无论是添加关注还是更新粉丝列表,这两个操作都可以在同一个 Lua 脚本中完成,避免了中间状态可能带来的数据不一致问题。
其次,虽然 Redis 支持事务功能(MULTI、EXEC 命令),但在实践中,使用 Lua 脚本通常更为灵活和高效。Redis 的事务虽然能保证命令的顺序执行,但它们不能保证命令的原子性。如果在事务中出现问题,可能会导致部分操作成功,部分操作失败。而 Lua 脚本的原子性保证能够更好地解决这个问题。
最后,如果系统特别复杂或者需要高并发支持,还可以考虑引入分布式事务管理工具来协调多个操作。但在 Redis 中,通常情况下,Lua 脚本就能很好地满足关注功能的数据一致性需求。这样可以确保关注和粉丝列表的更新始终保持一致,避免出现数据不一致的情况。
Spring事务注解怎么实现的?
在 Spring 中,事务管理通过 @Transactional 注解实现,这个注解提供了一种声明式的事务管理方式,使得我们可以用非常简洁的方式来处理事务问题。
当你在某个方法上加上 @Transactional 注解时,Spring 会自动为这个方法创建一个事务。在这个事务的上下文中,所有的数据库操作都被包含在内。具体来说,这样做有几个关键的步骤:
- 开始事务:当方法被调用时,Spring 会创建一个新的事务或加入现有的事务(如果当前已经有一个事务的话)。事务的开启是由事务管理器(通常是
DataSourceTransactionManager或JpaTransactionManager)来完成的。 - 执行方法:在事务开启的情况下,方法内部的所有数据库操作都会被包括在这个事务里。Spring 会确保这些操作要么全部成功,要么全部失败。
- 提交事务:如果方法执行没有异常,事务会被提交。提交操作会将所有的数据库操作实际保存到数据库中。
- 回滚事务:如果方法在执行过程中抛出了一个运行时异常(或者符合
@Transactional配置的回滚规则),Spring 会自动回滚事务,撤销所有的数据库操作,确保数据库状态不会被不完整的数据更改。
此外,@Transactional 注解还允许你通过属性配置事务的行为,比如设置隔离级别、传播行为、回滚规则等。隔离级别定义了一个事务可以看到另一个事务的未提交更改的程度,而传播行为则定义了事务之间的关系,比如是否共享事务。
总之,@Transactional 注解在 Spring 中简化了事务管理,让你无需手动管理事务的开始、提交和回滚,通过声明式的方式确保数据的一致性和完整性。
消息队列
为啥选择 kafka,你是基于什么缘由?了解过其他 MQ 吗?
kafka首先是可以将一个topic分成多个partition,每一个partition都是并行且有序的,生产者和消费者都可以并行的写入和读取数据。所以它的分布式架构让他无论在可用性上还是可扩展性上都比其他的mq要更优秀。
我的第一个项目使用了rabbitmq,因为它的管理界面对新手特别友好,中小规模的消息传递需求也可以特别好的完成。但是它使用erlang语言,不好阅读源码学习。并且高并发情况下不太好
activemq是比较老的mq,社区内的内容和经验沉淀比较充实,但是扩展性和性能都不好
rocketmq是阿里的mq,其实它也是高性能低延迟的代表,当初搞出来是为了能够支持双十一的活动,所以抗高并发的能力很强,也是国人开发基于Java的开源mq。主要是社区建设上和使用的经验上没有kafka那么经典,学习角度来讲希望能有更活跃的社区和资料来学习。
kafka是单机还是集群?自己部署的kafka还是直接调用其他人的服务
我是单机模式部署,因为配置比较简单,也方便进行本地测试。自己部署的,Java环境已经有了,直接官方下载,内部带有zookeeper直接配置和启动即可,创建并验证主题,发送并消费消息都没有问题。
ElasticSearch全局搜索
参考资料
还有一篇比较硬核的:2万字详解,吃透 ES
搜索功能是如何实现的?
首先是在springboot中添加spring data es的依赖,并且在配置文件中对es的相关信息进行配置,例如集群名称,节点地址等。然后在帖子类和评论类里添加es的注解@Document(索引名,类型等参数)。之后创建对应的 Repository 接口,用于与 Elasticsearch 进行交互 。在帖子评论发布的时候,将数据通过kafka异步发送到kafka的topic上,并创建kafka的消息监听器@kafkaListener 然后将收到的消息存入es当中。 通过 Elasticsearch 的查询 DSL 实现关键词搜索功能 。
ElasticSearch做全局搜索的优势是什么?为啥不选择用MySQL?
全局搜索的优势:首先es是分布式搜索引擎,能够轻松的在多节点之间分片复制数据,吞吐量和可用性都很高。其次它支持实时搜索,可以快速对数据进行索引查询。并且它基于lucene实现了强大的全文搜索能力,还有丰富的查询DSL,可以满足复杂的搜索情况。最后他们的社区建设很好,插件和扩展也十分丰富。
为什么不用Mysql:mysql虽然支持全文搜索,但是无论是功能上还是性能上都很差。在处理复杂大规模数据查询的情况下,性能很低。并且它更适合结构化内容,但是搜索引擎的查询内容往往都不符合结构化的要求。此外,在分布式的扩展性,实时性上,es都完爆mysql。MySQL 作为关系型数据库,更适合事务处理和结构化数据管理,无法在全文检索和复杂查询场景中提供同样的性能和功能
如何保证数据同步的一致性?
保证一致性:首先es支持版本控制,可以在索引文档的时候指定是哪个版本的,从而保证更新数据不会破坏以前的版本。其次es使用乐观并发控制,可以确保多个客户端并发更新同一个文档的时候,不会出现数据覆盖。es同样也支持定期的数据校验和修复,并且也支持数据备份和快照功能。
请描述一下ElasticSearch的基本架构和工作原理
先来聊聊lucene;lucene对es影响最大的一点就是倒排索引。
倒排索引是为每个单词建立一个记录,列出包含该单词的所有文档。这样在搜索时,只需查找单词对应的记录即可快速定位包含该单词的所有文档。从而实现高性能全文搜索。此外,lucene底层还实现了复杂的评分与排序算法,丰富的分词器和分析器。缺点也很明显,太难用了。直接使用它需要大量的配置工作。而es则实现了更高层次的抽象功能,使得用户可以很方便的实现复杂的搜索分析需求,易用性更强。比如通过简单直观的restful api,通过http请求进行操作,支持复杂的DSL查询语句。
再来看它的基本架构,
首先是集群,集群是由一个或多个节点组成的集合,集群中的节点协同工作。
其次是节点,节点就是一个运行的es实例,可以是主节点,数据节点,协调节点之类。
然后是索引,可以看作是一个数据库,有多个类型相似的文档。
类型相当于是关系型数据库里的表,在我用的版本已经被弃用,但是postman里面还是有它的影子。
最后是字段,字段相当于是列,也就是每个字段对应的值。
最后来看工作原理,当我们把数据发送给es时,这些数据会被封存在一个名为索引的结构中,索引中包括多个文档,每个文档都是json格式的数据对象。文档存储在索引的分片里,每一个分片是一个独立的llucene索引。es自动将文档分配到不同的分片里,并配置分片的副本以提高可用性。除此以外,我们前面有提过的倒排索引将文档中的词项映射到文本的id中,也就是这些词出现在哪些文档中。这样就可以实现快速的查询。
数据写入流程如下:客户端发送http请求,将文档写入es中,然后es集群根据索引的路由算法,将请求路由到负责处理该文档的主分片中,随后复制到所有副本分片中。
数据查询流程如下:客户端发送一个http请求,包含查询的dsl,es的协调节点解析查询dsl,将查询请求发给对应的分片,然后分片搜索,将匹配的结果返回给协调分片。协调分片进行结果的合并,并返回结果给客户端。
集群管理:es集群由多个节点构成,每个节点都是独立的es实例。主节点负责管理集群的状态,包括索引的创建删除更新等操作。索引被切割成多个分片,每个分片在不同的节点上,用于提高可用性。主节点检测到节点故障时,要分配故障节点的分片给其他的可用节点。
你是如何设计和创建索引的?索引结构是怎样的?
索引主要是为了搜索帖子和评论的关键词,所以对应的字段就是标题,内容,作者id,时间戳等等。
然后要定义映射,标题和内容全文搜索需要文本类型和标准分析器。时间戳则是使用日期类型并定义日期格式。
分片和副本的数量不用太多,分片数量设置为3,副本设置为1
最后写出来的代码里,title和context的analyzer是standard。作者可以用关键词,时间戳我们定义了format。
数据是如何从数据库同步到ElasticSearch的?使用了哪些工具或技术?
这里可以先使用CDC工具例如Debezium来捕获数据变更,将变更记录转化为消息,发送给kafka的topic,kafka消费者读取这个数据变更事件,转化为es可以接受的格式,发送给es。
ElasticSearch如何进行全文检索?你在项目中是如何配置的?
主要大概有三个方法,
首先是match query,单个字段中进行全文的检索。
然后是Multi match query,在多个字段中进行全文检索。
最后是bool query,进行复杂布尔查询,可以组合多种查询条件。
配置的大体过程就是先安装es,然后springboot中导入依赖,在配置文件里配置es,实体类里添加es的注释document,最后创建一个接口继承es repository,利用编写的服务类中dsl进行查询即可。
搜索结果的排序是如何实现的?有哪些排序规则?
在ElasticSearch中,搜索结果的排序主要依赖于相关性评分。这一评分系统默认使用BM25算法,这是一种基于信息检索的评分算法,它综合了几个重要因素来决定结果的相关性。
BM25算法考虑了词频(文档中查询词的出现次数)、文档长度(文档的总词数)和逆文档频率(词在所有文档中的稀有程度)等因素。这些因素帮助算法计算每个文档的相关性得分,从而对搜索结果进行排序。简单来说,它帮助确定哪些文档最相关,以便将最匹配的结果排在最前面。
除了基于BM25的相关性评分,ElasticSearch还允许按其他字段进行排序。例如,你可以按文档的发布时间排序,确保最新的内容显示在最前面。还可以指定多个排序条件,比如先按相关性评分排序,再按发布时间排序,这种多重排序条件能更精确地满足用户的搜索需求。
此外,ElasticSearch也支持自定义排序规则,你可以通过编写自己的评分脚本来实现特定的排序需求。这样可以根据业务逻辑或特定的需求调整排序方式,实现更加灵活的结果排序。
如何实现高效的分页查询?
小数据量的情况下,使用基于from和size参数的查询即可。指定从第几条数据开始,返回多少条记录。但是大量数据时由于跳过的数据太多,性能会下降。
search-after适用于深度分页,避免大数据量分页时的性能问题。它要求排序字段唯一,查询结果稳定。第一次查询设置排序字段,第二次采用第一次查询最后一个文档的排序值进行下次查询,避免大数据量的性能瓶颈。
scroll适合对大量数据滚动处理的情况。初始化滚动时间和查询参数,获取一个scroll id,之后的查询会利用scroll id进行逐步的读取数据。就相当于是一个书签,能够定位上下文,从上一次的地方继续读,避免一次性加载太多,内存的溢出或性能问题。
随着数据量的增加,ElasticSearch的性能是否会受到影响?你是如何应对的?
如果有钱,首选肯定是硬件上选择更好的硬件配置,增加内存,使用更快的存储设备。
没钱就从数据建模角度先优化。首先设置合理的索引分片数量,过多会导致资源消耗与管理困难,过少会导致分布不均,查询并行度低。其次是查询优化,例如过滤查询代替评分查询,不涉及计算过程以提高性能。采用前缀查询,只返回必要的字段等。最后是索引优化,定期进行段合并,删除旧数据。
没尝试过但可去做的就是缓存优化,例如查询先做缓存,热点查询数据预热等。
测压
有测过项目的 QPS 吗?怎么测的?
qps没有测试过,但是有对比测试过Java自带的正则表达式匹配与前缀树对文本的敏感词性能测试,以及mysql和redis存储数据的性能差异测试。并且最终都得到了准确的测试结果。
了解过哪些测压工具?
我平时的性能测试主要依赖于 JUnit4 和 Postman。JUnit4 用于单元测试,确保代码的正确性和稳定性。Postman 主要用来测试 API 的功能和性能,帮助验证请求和响应的时间。
在美团实习期间,我接触过一些更为复杂的性能测试方法。比如,参与了 Webkit 和 Chrome 浏览器的性能测试。在这个过程中,我们在源码中的关键执行方法处设置了纳秒级别的日志,用来精确计时。通过这些精细的日志记录,我们能够详细地分析每个方法的执行时间,并排除缓存等因素的影响,以获得准确的性能测试结果。
这些经历让我了解到性能测试不仅仅是运行一些简单的工具,更涉及到如何深入系统底层、如何细致地分析性能瓶颈,从而为系统优化提供科学依据。
一个请求从发出到收到响应,时间主要花在哪里了?
从一个请求发出到收到响应的时间,主要花费在几个关键环节上。首先是DNS解析域名的过程,这一步将你输入的域名转换为实际的IP地址。接下来是TCP三次握手,这个过程确保客户端和服务器之间的连接是可靠的。在连接建立后,请求被发送到服务器,服务器处理请求并返回响应。最后,客户端接收到响应,连接关闭时会进行四次挥手以结束连接。
在这些步骤中,实际时间消耗的主要部分通常是在网络传输上。这是因为数据在网络中传输的速度受到物理距离和网络状况的限制。例如,光纤的物理传输距离就会影响数据的往返时间。因此,尽管其他步骤如DNS解析和TCP连接也会占用时间,但网络传输延迟通常是最显著的时间消耗因素。
通用模版
有没有遇到不容易落地或者让你阻塞很久的地方?
其实对于我来说,技术是一个难者不会会者不难的事情。我对自己的学习能力也比较自信,所以接手一个新的知识我都会很快的学会并实操。所以可能之前觉得很难的技术,做完又觉得没什么可讲的。
但是,对我来说最不容易的就是对一个技术的思考和应用的角度。什么样的业务场景下要使用什么样的技术最好?什么样的技术可以解决哪些问题?这些思考的过程对于我来说反而是最难以落地的。
比如,我在redis的学习过程中,也有阅读过一些类似redis开发与运维这样的书。基础的数据结构也掌握,但是它能够用来解决什么样的问题呢?我在项目中充分的发掘了redis各项数据结构可以实现的功能,例如hash结构作为购物车存储,set用于实现点赞关注好友关系之类的功能,zset用于实现点赞热榜,bitmap用于实现每日签到,hyperloglog实现对传统set统计网站访问次数的优化实现。如何发掘一项技术的应用角度,并反之在各种应用场景下选择最合适的技术,是最难的。
如果让你重新再做一个,你觉得哪些地方你可以做的更好?
可以学习更多有关微服务的相关知识,在第一个项目中我做了电商支付双系统项目,一位面试官希望从微服务的角度来启发我对于这个项目更深层次的学习。
可以多学习更多的技术中间件,例如nginx,netty等。也像redis一样去思考探索更多的应用方向,并添加在我的项目中。
可以学习更多前端相关的知识,做到前端内容的独立开发,这样既能够补足项目缺失的前端部分,也能够更好的理解前端同学的需要,为未来的工作提高效率。
面试中遇到的问题补充
ThreadLocal存储用户信息,在分布式的场景下怎么办?
在分布式系统中,使用 ThreadLocal 存储用户信息会遇到一些问题,因为 ThreadLocal 是针对单个线程的局部变量存储,通常只能在同一个JVM(Java Virtual Machine)内访问。而在分布式环境中,不同的服务可能运行在不同的JVM实例上,因此 ThreadLocal 中存储的用户信息无法跨越这些实例进行共享。这会导致用户信息无法在不同的服务实例之间传递,出现一致性问题。
为了解决这个问题,通常需要采用一些全局共享的机制来替代ThreadLocal在分布式系统中的局部存储角色。一个常见的方案是将用户信息存储在分布式缓存或数据库中,这样每个服务实例都可以通过统一的接口访问和更新这些信息,确保在分布式环境下用户数据的一致性和可用性。
此外,使用JWT(JSON Web Token)也是一种有效的方式,通过在请求中携带用户信息,服务实例可以独立验证并提取用户数据,而不依赖于ThreadLocal。这些解决方案都能够确保在分布式系统中,不同服务实例之间的用户信息可以被正确传递和共享,从而避免一致性问题。
评论(1)
这个项目视频在哪里呢