java面试看这一篇就够了
因为近期工作繁忙,项目进度紧凑,一直以来都未能抽出时间来系统地整理面试题,但这次腾出了一些时间来汇总并整理了答谢大家提供的面试题,也感谢众多小伙伴的支持与参与,正是这份支持是我们继续创作的动力来源,期待能在未来的日子里与更多人分享经验,如觉得有帮助,请多转发、收藏并关注我的动态,考虑到篇幅较长,请读者朋友们及时保存以便查阅
Jason
springboot相关面试话术
1、什么是springboot
Spring Boot 实际上是 Spring 项目中的一个子工程,在编程社区中它常被用来比喻搭建程序的基础架构。它的核心功能就是帮助开发者快速构建大型 Spring 项目的同时最大限度地降低所有的 XML 配置工作量,并确保系统能够实现零配置即可上手的状态。
2、为什么要用springboot
Spring Boot 以其卓越的表现著称,在部署效率方面尤为突出:首先无需外部容器支持即可独立运行,并内置多种Servlet容器如Tomcat、Jetty等;其次启动器启动后即可自动生成所需的组件配置;再者系统能够根据当前类路径下的类和 jar 包自动生成 bean 对象;此外 Spring Boot 完全依赖于条件注解实现功能配置;最后系统提供丰富的监控接口用于实时评估服务健康状况
3、springboot有哪些优点
降低开发时间、测试时间和总体开发努力。
通过JavaCong类的使用能够绕开XML文件的配置问题。
无需手动管理Maven项目的依赖关系以及版本兼容性问题。
设置默认值后将加快进入开发状态的速度。
无需额外配置Web服务器资源。
这表示你可以完全省去启动Tomcat、Glassfish或其他任何容器服务器的需求。
因为没有web.xml文件的存在而减少了配置步骤。
只需在代码中添加带@Configuration注解的对象类即可。
然后为每个Bean对象添加@Bean注解即可。
Spring框架会自动生成该对象实例,并按照常规方式进行管理。
您甚至可以将@Autowired 添加到 bean 方法中以简化操作流程。
基于环境的配置 使用这些属性:
-Dspring.proles.active = {enviornment}
在加载主应用程序属性文件后,
Spring框架会自动生成该对象实例,
并在(application{environment} .properties)中加载后续的应用程序属性文件.
application.yml
spring.active.profile=dev
application-dev.yml
application-test.yml
application-prod.yml
4、Spring Boot 的核心注解是哪个?它主要由哪几个注解组成的?
在启动类中使用的注解主要有以下三个关键点:首先整合了@Configuration注解来负责配置文件的管理;其次启用了自动生成配置功能,并允许对特定自动配置设置进行关闭;最后实现了组件扫描功能,在当前类所在的包及其子目录中进行扫描操作。
5、springboot项目有哪几种运行方式
使用打包命令或通过容器部署并使用Maven/Gradle插件启动主方法
我们可以将其视为启动器工具...它整合了多个可无缝集成到应用中的第三方库...通过这一机制您可以轻松地将Spring与其他技术集成到同一个应用中...无需到处寻找示例代码或下载额外的依赖项库...如果您的项目需要通过JPA访问数据库...只需在项目中添加一个特定的启动器即可实现此功能...这些启动器涵盖了项目中常用的各种第三方库以及必要的功能模块
7、springboot自动配置原理
关于@SpringbootApplication核心注解的相关内容,则是从springboot项目的框架展开论述的。其中包含的关键字段主要有三个:一个是@EnableAutoConfiguration注解,则是该注解的主要功能则是实现自动化配置机制;其主要作用在于当开发者完成基本应用架构搭建后,默认会触发一系列系统性配置操作;前提是项目中已导入了相关的jar包,并且这些jar包中已经包含了如Tomcat和SpringMVC等常用组件;这样一来,在开发一个基于Web的应用程序时,默认情况下就能获得较为完善的配置设置;而无需开发者手动进行繁琐的手动配置操作;此外,默认情况下还集成了一些常用的第三方组件如Redis和Elasticsearch等;这些组件的配置则全部由META-INF/spring.factories文件中的元数据信息自动管理;而文件中的每一个bean类路径都会被反射加载到IOC容器中;具体而言,在JavaConfig的作用下会依次读取并解析相应的配置文件进而构造相应的对象完成初始化工作
2
springcloud相关面试话术
1、什么是Springcloud
可被视为一系列框架的集合...它通过模仿 Spring Boot 的便捷特性来简化分布式系统开发...例如服务发现...功能...其主要目标是为了降低构建和开发 Spring 应用的复杂性...该框架遵循「约定优先于配置」的设计理念...从而使得开发者无需手动配置冗长的 XML 参数设置...而非重复造 unnecessarily复杂的轮子...而是整合了市场上较为成熟且功能完善的组件...将这些组件封装起来后不仅降低了各个模块的成本而且显著提升了整体系统的运行效率$said another way through the integration of a series of key components including Eureka Ribbon Feign etc. it provides a comprehensive solution for building distributed systems.
为什么选择这种方法呢?因为原先项目的架构采用的是集中化的模式。这种设计会导致任何一个小小的bug都可能导致整个系统崩溃。经过调整后,我们需要对系统进行全面升级,因此我们选择了基于传统架构的基础上,引入了微服务模式进行优化。采用微服务架构后,在任务分配效率以及系统的扩展性方面较之前有了显著提升,特别是在处理复杂需求时表现更为灵活可靠。
让我来向大家介绍一下Spring Cloud中的几个核心组件吧。我们的项目中就使用到了其中几个主要功能模块。其中最为关键的功能模块是注册中心Eureka。所有微服务都必须通过Eureka来进行管理流程。系统内部会实现心跳机制,并且还可以提供每个微服务的基本运行状态信息给调用方查询。
随后使用feign实现远程通信。作为一个基于HTTP的远程通信组件,在微服务架构中发挥着重要作用。该组件集成了一个名为Ribbon的负载均衡系统。能够准确识别当前注册中心中的服务实例数量,并根据内部算法平衡域访问这些实例。它的算法采用轮询与随机结合的方式分配请求以确保公平与高效
接着就是。
熔断器hystrix。
当我们在这个项目中使用这个组件时,
特别是在远程调用的情况下,
如果任何一个微服务响应超时或出现异常,
熔断器就会介入处理,
将当前请求终止。
随后会触发我们的回调方法,
并返回回调方法中的数据结果。
这将确保我们的微服务系统能够维持良好的请求链路运行,
并且这样的处理方式将避免用户界面长时间处于等待状态。
从而带来非常良好的用户体验感受。
随后是我们的网关.Zuul.它在我们的项目中扮演着入口角色. incoming requests enter the system through this gateway and first undergo path-based parsing before being routed to the appropriate microservices. Additionally, this gateway serves as an authentication mechanism. Upon logging in, a token is issued to users. When accessing resources, the token is transmitted as a header field within the request. The gateway intercepts these headers and verifies the token's authenticity before allowing access to the backend microservices. This demonstrates how our system integrates authentication seamlessly into its service architecture.
2、服务注册和服务发现是什么意思,springcloud是如何实现的
在微服务架构设计过程中, 我们经常需要处理不同组件之间的交互作用, 这通常会在各个组件的配置文件中记录所有需要请求的相关信息, 包括地址端口等关键参数。然而, 随着项目的规模不断扩大以及新旧功能不断迭代升级, 配置文件中的条目变得越来越多, 相关数据维护的工作量也随之提高。当某些核心业务逻辑发生变更时, 更新相关的配置信息虽然看似直接, 但手动修改配置文档仍然存在一定的风险。为此,Eureka系统提供了一套高效的解决方案:通过Eureka系统进行注册后, 在注册中心可以看到所有运行中的微服务及其相关信息;通过Eureka系统提供的自动发现功能(feign)实现远程调用;由于所有相关业务逻辑都在Eureka服务器上运行并通过该服务器进行交互查找, 因此完全无需担心服务器位置的变化带来的问题
3、负载均衡的意义是什么
我认为负载均衡的核心目的主要在于,在面对单一应用可能出现的高并发或异常情况时能够有效规避可能导致服务中断的风险;通过同时运行多个负载均衡实例可以在一定程度上缓解并提升系统的应对能力;从而确保在关键业务场景下系统的稳定运行。
在我们的项目中,其中服务接入层由nginx进行管理,经过 nginx 处理后,根据相应的域名进行路由,转发至对应的服务端口.由于 nginx 不承担任何业务处理逻辑,仅负责请求转发,因此使得整体处理效率显著提升.
Nginx采用了多种负载均衡策略,在其配置文件nginx.conf中将upstream模块配置为所需的方式即可。
首先是轮训,默认的就是这种方式
第二种权重策略就是基于服务器性能指标来设定较高的权重系数 从而使得Nginx能够处理更多客户端请求
第三种采用的是基于IP哈希的方式,在处理来自不同IP地址的请求时,默认会对该次哈希运算进行计算过程,并将请求结果按照该运算的结果值对应到相应的服务器上;对于随后来自同一IP地址的所有请求,则会被自动分配到这里。
第四种是最少连接数,把请求转发给连接数较少的后端服务器
除了这个之外,我们还可以基于响应速度和url hash进行处理,在项目中我们采用的是权重形式。
在我们的项目中采用微服务架构时, feign和zuul均采用了ribbon这一功能组件. ribbon主要负责在各微服务之间建立交互关系并实现负载均衡功能. 当我们在注册中心完成对微服务的注册且确保其名称一致时,系统会默认将这些微服务视为一个集群. 在发起远程调用或进行路由转发操作的过程中,ribbon会均匀地分配响应服务器的负载. ribbon采用轮询策略作为默认配置,但这种机制也可以根据实际需求进行自定义优化
4、hystrix介绍
在微服务架构中,hystrix扮演着熔断器的角色,并实现了包括资源隔离流量限制控制故障熔断机制触发降级处理以及提供详细的运维监控数据等功能
在微服务架构中存在多个相互调用的服务单元;当基础层的服务出现故障时,可能导致一系列的次生故障,从而可能导致整个系统的完全不可用.这种现象被称为Service Snowdrift Effect. Service Snowdrift Effect是由于'Service Provider'的不可靠而导致'Service Consumer'也不可靠并由此逐步加剧其不可靠性的过程
熔断器的工作原理极为简单,并与电流过载保护装置具有相似性。该机制能够迅速触发故障中断,并在检测到多个相似错误后迫使后续多次尝试均会立即中断。当其在一段时间内检测到多个相似错误时,在随后的所有调用中都会立即触发故障中断,从而防止应用程序不断地尝试执行可能会失败的操作。此外,在发生故障后,在每次故障中断期间的应用程序都会返回缓存存储的数据或直接执行回调操作,并通过这种方式避免因长时间超时而消耗大量 CPU资源。熔断器还允许应用判断错误是否已得到纠正:一旦发现已经纠正,则会在下次尝试时重新启用正常操作功能
当 Hystrix 命令尝试向后端发送请求次数超过设定比例(默认为 50%)时,断路器会切换至开放(Open)状态。此时所有请求将直接失败而不被转发至后端服务。断路器会在未恢复连接的情况下维持开放状态直至预设时间(通常为 5 秒)结束,在此期间它将自动过渡至部分开放(HALF-OPEN)状态。在此期间该机制将评估下一次请求的状态:如果成功,则断路器将恢复闭合(CLOSED)连接;如果未成功,则将继续维持开放(OPEN)状态以等待下次尝试。与家庭电路中的保险装置相仿,Hystrix 的断路器能够有效隔离失效的服务,避免不必要的流量浪费并确保系统的稳定运行
请阐述Eureka和ZooKeeper所提供的服务注册与发现功能有何不同?
ZooKeeper负责保障了一致性和容错能力,并非仅局限于一致性本身;而Eureka则负责保障了可用性与容错能力
ZooKeeper在选举期间注册成功但服务出现故障无法正常运行,在此期间用户无法访问该服务直至投票结束后所有参与方的数据已同步完毕之后方可继续使用该系统
ZooKeeper在选举期间注册成功但服务出现故障无法正常运行,在此期间用户无法访问该服务直至投票结束后所有参与方的数据已同步完毕之后方可继续使用该系统
在Eureka系统中各节点之间处于平等关系,在任意一台活跃的Eureka节点即可确保服务处于可用状态。然而由于该系统的自我保护机制会阻止其从注册列表中删除那些长时间未发送心跳却应已过期的服务项。即使存在故障恢复后仍能处理新增服务的注册与查询请求但不会同步至其他节点(高可用性集群)。当网络环境稳定后,当前实例的新注册信息将逐渐同步至其他节点(达到最终一致性水平)。
Eureka能够有效地处理在面对网络故障时部分节点断开连接的情况,在这种情况下它不会像ZooKeeper那样导致整个注册系统的崩溃。
2、ZooKeeper有Leader和Follower角色,Eureka各个节点平等
ZooKeeper遵循多数存活机制,在分布式系统中实现高可用性;Eureka采用了自主防御机制来应对数据分区带来的挑战。
4、Eureka的核心模块属于微服务架构的一部分,而ZooKeeper则是一个独立的进程,必须单独配置安装
3
ElasticSearch相关面试话术
1、介绍一下ElasticSearch,以及在项目中的应用
基于Lucene的分布式全文搜索引擎是由Java语言开发而成的。该系统采用RESTful web接口设计,在搜索领域具有较高的性能表现,在我们的项目中主要负责获取商品信息的相关数据支持工作。其中商品信息构成较为复杂,并且数据规模庞大,在现有配置下通常能够达到几十万条记录的数量级水平。若采用MySQL进行检索操作,则效率将显著降低,并会对MySQL服务器带来较大的负担
在使用过程中主要需要做几件事:
1、Es服务的安装,中文分词器使用IK分词器,这个主要是运维负责;
2、在项目中集成springDataElasticSearch框架,用来操作ES;
在开发一个实体类时,请确保该类中包含与商品相关的所有字段属性。为此类添加必要的注解信息以记录该索引库的基本数据特征:包括分片数量、副本数量等关键参数。对于某些字段无需附加说明(即默认情况下系统会根据存储的数据推断其类型),而其他字段则需明确标注数据类型及是否进行分词处理等特性。在完成所有字段设置后,请新增一个统一的搜索字段命名为'all'(大写字母),并将常用搜索项如商品标题、副标题及品牌标识等全部包含其中
4、初始全量数据导入
通过springboot测试类完成导入操作;按批次从mysql数据库提取数据;整合处理后的数据存储至es系统。
5、修改、新增、删除等增量数据导入
基于RabbitMQ实现,在商品上架及下架的过程中向MQ发送消息,并完成ES数据同步操作以及静态页面的数据同步操作。
6、使用es实现搜索
采用all机制进行关键词分词与检索,在matchQuery框架下完成该过程。该方法将输入的关键字先进行分词处理后,在matchQuery框架下完成关联性语义分析与结果候选生成,并根据设定规则对候选结果进行排序筛选以输出最终结果。具体而言,在项目中配置了一个all字段,默认情况下该字段包含所有可能参与分词的关键字或字段信息,并仅对该all字段进行分词处理,并在关键字搜索时对该字段的内容进行匹配。通过规格参数聚合来实现实体商品搜索功能中的相关展示与渲染;通过布尔逻辑运算实现对商品规格、品牌以及分类信息的有效筛选;通过布尔查询实现规格参数、品牌、分类信息的有效关联性语义分析并输出最终结果;同时支持基于此功能实现的商品库存管理相关业务逻辑开发及数据统计分析功能;此外还实现了基于此功能的商品推荐算法开发及个性化服务功能支持;最后实现了基于此功能的商品浏览界面优化及用户体验提升功能支持 2. 采用Elasticsearch技术的主要原因是什么?
由于商城中的数据未来规模将非常庞大,在这种情况下采用传统的模糊查询方式将会导致无法承受的性能损耗。具体而言,在执行模糊查询之前就需要对相关配置进行特殊设置,并由此产生的后果是不再建立索引这一关键操作。在百万级别规模的数据库中执行全表逐一扫描这样的操作无疑会导致检索效率低下。因此为了提高搜索效率我们转而采用基于Elasticsearch(ES)的全文检索技术在这种系统中我们只需要将频繁被查询的商品相关字段如商品名称描述价格以及商品id等信息存储到专门的倒排索引中ES内部就具备了高效的倒排索引机制这种机制的基本原理是将待检索的关键字进行分词处理后记录下每个词条对应的文档id在检索阶段则可以通过分词后的关键字定位到每个词条的相关文档id并通过id来定位到对应的数据记录从而实现了高效的全文检索操作这种方法也是全文检索领域中最常用也是最有效的实现方案。
3、 什么是桶(bucket)?什么是度量(metrics)?
这个系统会通过某种方法对数据进行分类管理,默认情况下会将相似的数据归为一类,在 Elasticsearch 中这类分类结构被称为 bucket(容器)。每个数据集在 Elasticsearch 中都会被赋予一个默认的 bucket 标识符(ID),具体来说就是这个 container 的名称标识符。例如根据国籍将人划分为不同的国家 bucket……或者我们按照年龄段对人进行划分:年龄范围从 0-1 岁、1-2 岁等。此外该系统还支持多种分类策略如基于日期的时间段分类策略、基于数值值的区间分类策略、基于词条内容的内容类型分类策略以及结合数值值与日期范围的复合型分类策略等等
在分组完成后的一般情况下,在组内执行聚合计算。这些在 Elasticsearch 中被称为度量指标。具体来说,包括取平均值、找出最大值与最小值、计算百分比以及累加等。
4、es内部存储的存储结构
es内部默认采用分布式存储架构,在创建索引库时需指定分片数量与副本数量参数。其中分片数量决定了数据如何分布在各个分片上;所有分片数据总量等于索引库总数据量;增加分片数量可提高数据分散程度;在搜索过程中es会同时检索各分片中的数据并返回结果;副本数量指的是在索引填充时同步复制的数量;提升副本数量会增加存储空间需求;建议遵循生产规范设置为5个分片及2个副本,默认配置即可满足常规需求。
4
页面静态化相关面试话术
首先说说为什么要进行页面静态化
从这一部分可以看出,该模块会预先设置并展示固定的30条数据结果。目前该模块的流量已经非常显著了,在整个平台上的访问量来看,这个数值已经是远高于预期水平。因此,在每次请求详情页时直接获取数据的方式显然不够高效
其次这个详情页面来说不会经常地发生变动因此每次从服务端获取的数据基本上都是稳定的这样一来我们就必须想办法解决如何快速响应如此庞大的流量需求
在我们的项目中,默认情况下服务端与前端都采用nginx进行反向代理。具体来说,则是基于两个关键点展开解决方案。首先是利用模版引擎技术,在用户体验首次访问某个特定数据的具体页面时(当用户首次访问某个数据的具体页面时),我们系统会通过thymeleaf技术自动生成静态HTML页面(通过thymeleaf技术自动生成静态HTML页面),响应并返回给前端(响应并返回给前端)。接着,在第二次访问该数据的具体页面时(接着,在第二次访问该数据的具体页面时),系统会通过 nginx 配置机制(通过 nginx 配置机制)先检查指定目录下是否存在已生成的静态HTML文件(先检查指定目录下是否存在已生成的静态HTML文件)。如果发现存在,则直接从该目录中调用相应文件进行展示(如果发现存在,则直接从该目录中调用相应文件进行展示);如果未找到,则会将请求转发至微服务端以获取最新的原始数据(如果不存在,则会将请求转发至微服务端以获取最新的原始数据)。
然后在网上的时候,在线内容会结合CDN技术,并指向由nginx代理提供静态页面的路径,在此过程中能够显著提升用户体验。
在同时创建静态页面的过程中会遇到一个问题:即当数据库发生修改时,需要将其同步到静态网页中。针对这一需求,在系统架构设计中我们选择了RabbitMQ来进行异步处理。具体而言,在数据库数据修改后,系统会通过MQ发送消息给静默化服务接收到消息后会重新生成一遍HTML页面从而实现了与数据库数据的实时同步
5
rabbitmq相关面试话术
1、介绍一下rabbitmq
RabbitMQ是一款基于AMQP协议、由Erlang语言实现的消息传递系统。其核心理念在于生产者不会将消息直接投递给队列,在消息投递给客户端之前会先进入交换机进行中转处理,并由交换机将消息转发至相应的队列。该系统对路由配置、负载均衡策略以及数据持久化功能均提供了良好的支持。
它里边有5种数据传递方式
该模型较为基础,在其架构中包含单个生产者、一个消息队列以及单个消费者。值得注意的是,在这种设计下,消息队列仅能被单个消费者监听。因此,在生产者向消息队列发送消息后方能实现的消息传递中只有一个消费者能够接收该消息。
第二种方式采用工作模式,在这种结构中存在单一的生产者节点和一个消息队列。该系统支持多个消费节点同时监控这个消息队列。然而,在实际运行过程中发现一个问题:当生产者向消息队列发送消息时,并不能保证只有一个消费节点能够接收该消息。
在采用交换机模式的三种架构中,在这三种模式中,在这种架构下,在这种配置下,在这种设置下,在这种配置下,在这种架构中,在这种模式里,在这种情境中,在这种环境下,在这种配置里
首先是基于广播机制的分发模式(Fanout),即扇出型传播方式。生产机构发消息至交换机,在此过程中后者会将信息传递至所有已绑定于当前交换机的队列中。这些接收到信息的监听队列对应的消费者均可获取数据;若无相应队列绑定至该交换机,则该消息会被MQ系统丢弃。
接下来介绍的是direct类型的一种特定实现方式——定向模式或路由模式。在该模式下,当将队列绑定到交换机上时,在生产者发送消息给交换机的时候(即当生产者发送消息给交换机时),系统会自动根据生产者所指定的.routing key(即其对应的.routing key)进行路由转发。具体而言,在这种情况下(即在这种情况下),交换机会根据接收到的消息中的.routing key值来决定向哪个队列进行转发(即相应的队列消费者能够接收到相应的消息)。需要注意的是,在这种情况下(即在这种情况下),如果换行没有找到对应的.routing key,则该消息会被丢弃(即如果系统未找到与之匹配的.routing key,则会丢弃该消息)。
该模式采用topic机制进行消息路由配置。当将队列绑定至交换机时,默认状态下也会指定自己的routing key值(即路由键)。生产者在发送消息至交换机时,则需要同时指定目标路由键值(即目标路由键),以便正确路由消息到对应的队列中。通常情况下,该routing key由多个单词通过.分隔符构成(如:my.rout.key)。在通配符设计中:
- #号表示任意长度的子键匹配(如:my.rout#)
- 号表示精确匹配单个词项(如:my.rout.)
具体来说:
- 如果生产者指定的通配符为my.rout# ,则会匹配到以下所有可能的routing键值:
- my.rout.a
- my.rout.a.b
- my.rout.a.b.c 等等
- 如果生产者指定通配符为my.rout.* ,则只能匹配到单一词项构成的目标路由键值(如:my.rout.a 或 my.rout.b 等)
值得注意的是,在实际配置中可以选择定义多个目标路由键值与队列进行绑定。交换机会根据指定的通配规则自动将消息分配至符合条件的目标队列中,并相应地将消费者资源分配至这些目标队列上以处理消息接收任务。如果某个队列无法找到符合其目标路由键值对应的可用目标队列,则系统会丢弃该消息
在生产环境中使用时,在 RabbitMQ 系统中实施消息持久化策略以防止 MQ 故障导致的消息丢失问题。具体而言,在 RabbitMQ 中我们需要对交换机、队列以及消息均进行持久化存储设置,并确保其会被保存到磁盘上以避免突然的电源中断等意外情况导致的数据丢失。
2、如何保证消息确定消息发送成功,并且被消费成功,有什么保障措施
无需记忆这些常见问题的具体表述方式,在考试中遇到类似题目时无需死记硬背即可应对。常见的提问方式有多种,在本题中主要涉及两个主要方面:一是确保消息发送的成功性,并且在实际考试中可能会以以下几种形式提出问题:比如在消息发送出现故障时如何解决;二是保证消息消费的成功性,并且在实际应用中可能会以以下几种方式进行询问:比如在消息消费过程中出现问题如何处理等
在消息发送过程中必须确保其成功到达 RabbitMQ 中的消息传输系统中包含了事务管理与确认机制。其中事物的概念类似于数据库事务的概念:即开启事务并执行其逻辑操作后提交到目标存储层;若在此过程中发生任何异常事件则会触发回滚机制;在回滚过程中可以通过引入相应的处理逻辑来实现重传或日志记录等功能;同时建议配置生产者确认机制:即在消息发送之后系统会为其分配唯一的标识码;当消息被交换机转发至队列后 RabbitMQ 会向生产者发送ack确认;若队列未接收到消息则会返回错误回执给生产者;生产者可以根据接收到的反馈结果尝试重新发送或记录日志信息;通过这种方式可以看出 RabbitMQ 在保证消息严格一致性的同时确实会对性能产生一定影响:如果应用场景要求严格的一致性则有必要采取上述配置措施;但若消息本身的重要性较低或者容许出现偶尔丢失的情况则可以选择不配置相关功能从而能够有效提升系统的性能表现
除了这一点之外,在完成信息包发送成功后还需要保证信息包在消费者端能够被成功消费。若在消费者端遇到信息包消费异常且未做相应的处理措施,则可能导致信息包丢失。此时可采用以下两种解决方案:一种是对消费者端的异常处理机制进行优化;另一种是增加对信息包状态的监控频率以及时发现并纠正问题
首先,在消费端部署手动ACK确认机制。当消息被消费完成后进行手动确认通知MQ系统已完成接收。如果MQ未收到ACK确认信息,则该条消息会被标记为unacked状态。通过查看项目日志或访问MQ管理界面(MQ Dashboard),我们可以观察到当消费者断开线缆后的消息会在队列中等待重新接收;一旦消费者重新连接在线缆状态下他们将能够再次接收到相关的信息。
第二种方法是通过数据库来实现的。当生产者发送消息成功后,在数据库中进行存储操作,并记录该消息的内容及其发生时间,并将其状态标记为未消耗状态;而当消费者处理完毕后,则会定期从数据库中检索超时未被处理的消息,并将其重新提交到队列中进行处理。这种方法能够有效解决消息消费失败的问题;但同时也带来了更高的依赖程度,并可能导致消息被多次处理的情况出现;因此我们可以在确保消息传输成功的前提下将这一逻辑转移至消费者端的处理流程中:即当生产者正常发送消息时,在接收端将其存储到myqsl队列并标记为未消耗状态;同时通过ack机制确认其已收到并准备进行处理;一旦消息被成功消费,则会将数据库中的状态更新为已消耗状态;而消费端则应配置定时任务执行类似的操作:定时从数据库中检索超时未被处理的消息并重新提交给生产者队列进行重传
这些解决办法都需要基于实际情况制定;如果消息必须保持强一致性,则不允许出现任何错误;如前所述,则应按照前述方法添加相应的配置。
我们项目中的mq主要用于数据同步使用。当mysql数据发生变化时,必须将修改同步至es索引库以及静态页面。在我们的配置中,采用了生产者确认模式,并为消费者设置了人工ack确认机制。
3、如何保证消息不被重复消费
在生产者端发送消息时,在数据变动部分执行MD5加密处理,并配置一个唯一ID。每次发送的数据均有一个递增的唯一ID(相当于版本号的功能)。在消费者端收到消息后首先查询数据库中对应的MD5值:如果发现数据库中的当前记录已被耗用,则不会对其进行处理;如果发现数据库中不存在该条记录或记录的状态未被耗用,则会重新对这条消息进行消费。
4、RabbitMQ 宕机了怎么处理
RabbitMQ 提供了消息的永续性功能(机制),它能够将内存中的信息永久存储于硬盘上,在兔兔服务重新启动的情况下也不会导致数据丢失。就队列管理而言,则分为永续性和非永续性两类:前者会在磁盘上建立长期存储并持续维护原有状态(即使服务重启),而后者则不具备这一特性,在服务重启时将会消失。从性能角度来看,则是非永续性的运行效率更高(因为它们无需存储于磁盘),即非永续化的性能优于永续性表现;而如果从数据可靠性考虑,则必须保证信息不会因服务中断或系统故障而丢失(因此永续性的优势在于数据始终存在)。因此,在实际应用中应当依据具体情况充分考虑其适用场景
6
认证授权相关面试话术
1、你给我说一下授权认证(登陆注册)的逻辑
对于这一部分而言相对简单我们可以支持的注册功能包括用户名密码注册、手机验证码登录以及微信绑定账户的登录流程
用户名密码注册登陆我们系统规定的是用户名不得重复,注册的时候,会去做一下重复校验,向后台提交注册信息的时候,密码都会经过md5加密传输,到后台会首先用加密工具生成32位的盐值,然后把用户名通过md5加密之后,用户名的md5和密码的md5和盐值结合之后,生成md5值,然后一起存入数据库,用户登录的时候,按照首先根据用户名去数据库查找用户,找出来用户之后,根据相同的逻辑计算加密之后的密码和数据库的密码对比,对比一致则登陆成功手机号注册登陆(推荐使用,方便快捷)这里用到了阿里的短信服务功能,注册的时候,手机号码校验通过之后,向用户手机发送验证码,后台将验证码和手机号对应关系存入redis,用户提交注册之后,验证码跟redis中对比即可微信登陆绑定系统用户注册这里结合微信登陆使用的,微信登陆之后,如果发现对应openid没有绑定系统用户,需要提示用户去绑定,然后才能注册常识:微信开发,有两个平台,微信开放平台,主要用于app端以及web端扫码登录等开发,app中,微信登陆,微信支付,微信分享等微信公众平台,主要用于微信网页开发,公众号开发,公众号中网页登陆,微信分享,微信支付等微信开放平台注册开发者帐号,并拥有一个已审核通过的网站应用,并获得相应的AppID和AppSecret。申请微信登录且通过审核后,可开始接入流程。就是你的网站要想实现微信扫码登陆功能首先要在微信备案通过,它才会给你个AppID和AppSecret。1、用户点击网站微信登陆图标。2、网站带上AppID和回调域名参数请求微信OAuth2.0授权登陆。3、第二步通过后,微信返回二维码供用户扫码。4、用户扫码确定授权。5、微信带上code参数回调java后端回调地址。6、java后端获取到code后,在带上带上AppID和AppSecret和code再去调微信接口获取access_token。7、获取access_token后就可以解析用户的一些基本信息,比如:微信用户头像、用户名、性别、城市等一些基本参数。因为是app登陆,需要用到微信开放平台的接入功能,微信登陆采用的auth2.0的验证机制,在开放平台里注册了账号并通过企业认证,获取了AppID 和 AppSecret之后,第一步我们的服务器先获取code,传给移动端,移动端跟微信交互,用户确认授权之后,然后移动端请求我们后台获取用户信息,我们后台会先去用code获取access_token,然后在通过access_token获取用户信息,同时会返回用户的openId,我们会根据这个openId去我们的数据库查,是否已经获取过用户信息,如果获取过用户信息,看一下最后获取时间,因为每3天更新一次,所以这里会看一下是否需要重新获取如果没有获取过用户信息,会通过access_token获取用户信息,提示绑定系统用户,access_token失效时间为2个小时,因为微信有接口请求次数限制,2小时之内不会再去请求微信的access_token微信app登陆web端微信扫码登录因为我们系统是微服务架构,所以这里使用jwt实现了单点登录,因为平台有很多,有web端、管理端,还有一个清结算平台,实现单点登录会更方便用户使用,登录之后,给用户颁发了一个token令牌,每次请求的时候,都会在请求头里携带这个token,经过我们的网关的时候,会对这个token做一个权限认证,认证通过,然后才能请求到我们的微服务具体的接口
2、说一下jwt
在微服务集群架构中,每个服务对外提供的接口均为Rest风格规范.而Rest风格体系的一个核心标准便是:服务实现无状态特性.即:
服务端无需存储任何关于客户端请求者的相关信息,在每次请求时都需要提供自我描述的信息以识别客户端的身份。通常这种情况下使用的是token作为登录凭证。这样处理的好处在于能够有效防止未授权访问并提升系统的安全性。例如,在这种机制下服务端能够快速验证客户端的身份并进行授权访问控制。
客户端的所有请求操作均不涉及服务端存储的信息。任何一次或任意多次请求都不需要访问到同一台集群的状态以实现一致性和可靠性要求;而透明的服务架构则允许支持任意次数弹性伸缩以应对负载波动需求;在本项目中采用的是无状态登录技术方案,在这种架构下使用token作为身份认证机制;该token由jwt算法生成并分为三部分:头部、载体(内容)、签名;其中头部用于存储识别信息载体内容则包含用户的基本字段(如id和username);而签名是基于头信息与体数据结合使用RAS算法进行非对称加密处理后得到。
3、说一下auth2.0机制
其实在一种权限管理机制下运行着这一过程。由数据的所有者授予该系统一份访问权限凭证后,系统将随后生成一个临时访问令牌(Token)。这个Token取代传统密码方式,被第三方应用程序所使用即可完成授权流程。
令牌(token)与密码(password)的功能是一样的,并且都可以用来登录系统,在某种程度上它们之间也存在一定的差异。
(1)该系统中的令牌具有短暂的有效期,在达到指定时间后会自动失效,并且不能自行更改。密码则通常保持很长时间不变,并且无需更改即可维持原样。
(2)可由数据所有者撤销该令牌,并在使用后立即失效。就如例子所示,在线用户可随时取消快递员的令牌。密码通常不被他人所撤销。
(3)令牌具有权限范围(scope),例如仅限于小区二号门这一特定区域。在网络服务中,仅读类型的令牌相比具备读写权限的令牌更为安全可靠。密码通常代表完整的权限配置。
基于这一系列设计的巧妙构思下,在任何情况下都能够提供一个安全可靠的解决方案。
请记住:只要有知道了 tokens 就可以直接进入系统。通常情况下 系统不会进行额外的身份确认 因此 为了确保安全起见 tokens 必须严格保密 任何泄露都会带来相同的危害 即使 tokens 和密码的安全性相同 那么 tokens 的有效期也应该设置得非常短暂。
具体来说,auth2.0一共分成四种授权类型
第一类是基于授权码的模式。这种模式是最常用且安全度最高的方法,在实际应用中被广泛应用。它特别适合那些具有服务端支持的Web应用环境。该模式的核心在于将授权管理与身份验证分开处理:一方面由前端发送由用户提供的信息(如注册信息),另一方面则由服务端接收并验证这些信息以确认用户身份。在这个过程中,“授权码”作为关键的安全凭证被生成并传递给客户端设备(如手机)。这种分离化的管理架构不仅能够有效提升系统的安全性(因为客户端无法直接访问敏感资源),还可以简化系统架构设计并提高运行效率。具体来说,“授权码”的生成通常依赖于特定算法(如哈希算法),而验证过程则通过调用预先配置好的API来实现。“回调地址”这一概念则用于建立客户端与服务端之间的交互通道:当客户端发起登录请求时会自动触发服务端上的特定回调函数,并附带包含必要的验证信息(如上文所述)。这种方式不仅可以实现跨平台的身份验证功能(如常见的第三方登录方式如微信登录等),还能在不同版本之间无缝衔接并保持一致的安全性标准
也就是隐藏式、密码式以及客户端凭证这三种模式吧?我在大概了解了一下这些概念,并未真正操作过。
7
nginx相关面试话术
1、介绍一下nginx
Nginx 是一个高效率的 HTTP 逆向代理服务器,并提供负载均衡、静态与动态分离等功能
我先来说说反向代理功能吧
反向代理是指由代理服务器响应用户的请求,并将该请求转发给内部服务器群,在这些服务器中获取所需数据后返回给客户端系统。这种机制使得远程客户端无需直接连接至被控服器即可实现访问与交互。反向代理的核心在于:前端负责执行服务。
反向代理的情况下,则主要涉及对一个服务器模块的配置。具体而言,在该模块内部需设置 server_name 以及指定相应的端口号,并根据需求设置 forward rules for the location。此外还可以设置代理后的静态资源。
再来探讨一下负载均衡的相关知识吧。在实际应用中,负载均衡的核心机制是通过代理服务器将接收到的请求均匀地分配到各个服务器上,以实现资源的最佳利用和性能的最大化。在这一过程中,我们主要关注的是如何有效缓解网络拥塞问题、提升单个节点的服务能力以及确保数据传输的安全性与稳定性等多重目标。其中,服务就近原则通常会使得响应更加迅速且效率更高,从而以更高的整体服务质量为用户提供更好的访问体验。此外,通过合理的负载均衡策略配置,后台 server 的繁忙请求量能够得到有效的分流与管理,避免出现大规模并发导致的服务中断或性能瓶颈现象发生。值得注意的是,默认情况下系统会优先采用轮询策略作为其分配机制的基础,这也是为什么在 many 现实场景下这种策略能够展现出较高的适用性和稳定性原因
正确设置upstream模块中的负载均衡策略,在此基础之上为每个服务器指定相应的IP地址与端口组合,并在服务器模块中根据预设的转发策略将流量发送至该upstream模块即可完成负载均衡的配置工作
2、nginx如何处理http请求
Nginx这一块的处理流程融合了多进程和异步机制的应用,在具体实现中采用了一种高效的同步方式
首先呢是多进程机制
每当服务器接收一个客户端请求时,在处理过程中就会有主进程(Master process)负责创建一个工件(Worker process),该工件会与之建立通信并进行交互;当该连接断开时,则这个工件也随之终止。
该方法的优势在于各子进程之间互不影响且无需加锁,在避免因使用锁而导致的性能损失的同时降低了程序设计的复杂性以及开发成本。此外该方法可实现各子进程间的完全分离与独立运行,在主进程中一旦出现异常退出情况其他从进程中即可正常处理而主进程中则迅速切换至新的子进程中从而最大限度地保障了服务的连续性与稳定性。其主要缺点在于操作系统生成子进程中需进行内存复制等操作这会带来一定的资源消耗与时间开销。因此在处理大量请求时可能会导致系统整体性能有所下降
还有就是异步非阻塞机制
每个工作进程 使用 异步非阻塞方式 ,可以处理多个客户端请求 。
当某个工作进程接收到客户端的请求后,在调用IO进行处理时发现无法立即获得结果,则会转而去处理其他请求(这就是非阻塞的方式);而客户端在同一时间段内同样不需要等待响应就可以进行其他操作(这就是异步操作)。
一旦 IO 完成返回操作后, 系统会立即向该工作进程发出通知信号; 该进程接收到相关通知信息后会暂且搁置当前处理的事务以应对客户端的需求.
3、nginx常用命令
启动:nginx
当我们修改配置了之后,重启:nginx -s reload
停止nginx:nginx -s stop
4、什么是动静分离,为什么要动静分离
从我的理解来看,这主要是将前端的静态资源与后端的请求进行分离。这种做法的主要目的是为了提高静态资源的访问速度。通常前后端分离的技术在项目中较为常见。通过分离后,我们可以将静态资源托管到CDN服务器中,这样不仅能够实现用户就近访问资源,还能显著提升带宽容量.
5、如何保证nginx高可用
由于Nginx是我们项目的核心入口节点,在保障其正常运行方面具有至关重要的作用。因此,在我们的架构设计中,默认会部署Nginx集群,并结合KeepAlive技术实现两台服务器之间的负载均衡与故障切换。为了确保每个服务都能快速响应请求并提供稳定访问,在实际部署过程中我们采用了一种基于虚拟IP地址的负载均衡策略:通过生成一个虚拟IP地址(Virtual IP)来映射到实际服务器上的公网IP地址。这种设计不仅能够有效提升系统的扩展性与容错能力,在某台服务器出现故障时系统会自动将绑定到该机器上的虚拟IP地址转移到另一台健康的服务器上。
8
前端相关面试话术
1、介绍下VUE(如果分开问你的话,就分开说)
在用户的认知中
vue常用的指令包括常见的如 v-for 、 v-if 和 v-show 等。
用于绑定事件的指令是 v-on 。
通常可简写为 @ 符号 。
用于绑定属性的指令是 v-bind 。
通常可简写为冒号:
另外关于Vue生命周期的知识中提到过一个重要的钩子函数——created事件,在组件创建后会自动触发该钩子函数。我们在服务端初始化数据以支持后续的数据准备过程时通常会使用该 hook 函数来处理相关操作。根据具体需求有时候我们会采用不同的策略:比如当页面整体框架渲染完成后才进行数据加载,则可以在 mounted 事件处执行相应的操作以完成数据同步;这种机制类似于内置JavaScript API中的onload方法用于实现类似功能
除此之外,在实际开发中也存在一些特殊需求场景需要处理。常见的情况是开发者会采用分层架构来进行模块化设计,在这种架构下通常会遇到一个问题:如何实现跨层级的数据交互与功能关联?这个问题可以通过动态绑定属性的方式进行处理,在子组件中通过props接收数据与方法。但需要注意的是,在某些情况下(如基于DOM的传统UI框架),由于其对DOM树的直接控制能力不足(尤其是针对现有结构难以进行增删改查操作),导致其无法直接修改父节点对应的业务对象值。这就要求我们必须设计一种间接的方式来实现这一功能:即先让父节点具备某种特定的方法接口(通常以@符号前缀的方式进行声明),然后由子节点通过@特定属性名的方式获取并引用这些接口;最后当需要执行相关操作时可以通过$emit指令触发相关操作以完成跨层级的数据更新与业务逻辑变更
另外一般vue项目都是通过npm管理的,npm呢就相当于与前端的maven,主要是帮助我们管理js依赖的,我们在想要添加js依赖的时候,比如axios,可以通过npm install -g exios命令来下载就行了,也不需要我们从网上手动下载了,另外结合webpack打包的工具,在开发以及部署的时候方便很多了,不过这些都是我们专门的前端工程师来做的,用vue的脚手架搭建框架,以及配置路由等等,我们实际去写的时候,都是往里边填代码,我这边平时就是好琢磨,就研究了一下,现在来说,简单的框架搭建,以及npm管理来说,都不是问题。
2、说说你了解过的UI框架
遵循Vue风格的MVVM理念,在实际应用中最为常见的便是ElementUI;除此之外,还有Vuetify和iview等工具可供选择。
遵循jQuery风格的手动DOM元素操作,并包含Bootstrap、La UI以及早期版本的Easy UI等
如今移动端开发框架越来越受欢迎的应用程序开发平台之一是Flutter应用程序开发平台。其中一个是Flutter应用程序开发平台是由谷歌推出的一个完全免费使用且功能开放的移动界面技术平台。
3、jquery元素选择器都有哪些
("#id")(".clazz")$("标签名")
9
工作流相关面试话术
1、工作流话术
工作流这块儿。在实际操作中采用activiti较为常见。此外还有其他的一些工作流引擎,在网上的资料上大概了解了一下。其中像JBPM以及workflow等工具的实际应用情况较为有限。而activity目前来讲在数据库系统中涉及23个相关表
当使用该功能时,首先要配置包含23张表的数据库结构。接着加载activity的配置文件。在选择加载方式时存在两种可能性。嗯,在编写这个演示脚本的过程中,我采用了Spring框架来解析XML配置文件作为基础设置。该XML文档中包含核心引擎管理器ProcessEngineConfiguration以及数据库连接信息等关键参数设置。将这些设置整合到Spring的核心配置中完成初始化。
接下来需要定义的是一个流程。为了实现这一目标,我们借助Eclipse插件采用绘制流程图的方式实现了简单的理由请假功能的设计,并最终输出了包含该请假管理功能的一个压缩文件包。
下一步需要将该流程部署至Activity中。具体操作如下:首先利用processEngine获取待部署的类;接着为该流程命名并配置必要的参数;最后将该压缩包配置文件部署至activity中运行;执行上述步骤后系统将返回对应流程的唯一标识符
接下来必须启动一个流程实例,在系统已经部署完成的情况下,当有人提交请假请求时,则应当启动一个新的流程实例。每个流程实例都会被分配一个独一无二的ID号码,并且每个实例都有其独特的运行状态信息。为了使系统更加灵活高效,在设计变量时可以区分全局变量与局部变量这两种类型:全局变量会对所有正在运行中的流程产生影响;而局部变量仅限于特定 workflow 的操作范围之内。这些变量会在整个 workflow 的执行过程中持续发挥作用,并根据其属性进行传递下去。随后这个 workflow 示例会继续向下一个节点传递任务,在接收任务后同样通过 taskservice 进行处理工作;最终直到任务被完整执行完毕。
总体而言呢,这个activity通过利用代码实现了对一些流程性问题的简化处理。然而,在其运行过程中还会遇到较为复杂的环节。例如,在如何实现多任务协同处理方面以及如何处理复杂的节点问题上都需要进一步探索和完善。这些正是我对activity工作流的基本认识和理解。
10
spring相关面试话术
1、介绍一下spring
关于Spring的知识,在实际项目开发中我们一直都在应用它。不论是基于SSH的架构还是基于SSM的框架,“Spring Boot”都实现了良好的集成。其核心理念主要包括三点:IOC控制反转、“AOP切面编程”。
让我先介绍一下IOC是什么。它指的是Spring框架中的逆向控制模式。通过将类的管理权移交给Spring框架来进行协调,在我们配置应用时,在Spring的核心配置文件中设置相关参数。并预先设置好了Bean标签名以及完整的类路径信息;如果需要传递特定的属性或数据,则应在配置文件中相应地进行参数设置。这样Spring框架就会利用反射机制自动生成相应的对象实例并将它们注入到容器环境中进行管理。
在应用中使用时,请结合依赖注射技术进行操作:将希望使用的类导入到相应的位置是可以的。具体而言主要有以下几种方式:构造器注入、getset 注入以及注解式注入。目前我们主要采用 @autowired 和 @resource 标签来进行依赖管理。
然后就是AOP切面编程技术,在不影响源代码的前提下增加对代码功能的提升。通过预先在配置文件中标明切点位置,并设计相应的逻辑流程即可完成代码功能的提升。该提升机制支持在切点运行前、运行中以及运行后阶段分别实施额外的功能扩展,在不影响源码的同时灵活应对多种业务需求变化,在.NET平台项目中通常将该技术应用于权限控制、日志管理以及事务处理等领域
2、AOP的实现原理
这部分内容了解了spring的核心机制,并了解到其底层采用的是基于动态代理的技术实现。关于动态代理的概念是:一种技术框架下不会直接修改类文件中的字节码代码结构;相反,在每次程序运行时会在内存中临时为每个需要被切点增强的目标方法生成一个独立的AOP(面向切面编程)对象。这种AOP对象会包含目标对象的所有相关方法,并且在其特定切点处增加额外的功能模块;随后会自动调用原始目标对象对应的方法实现流程。
在Spring AOP框架中实现动态代理的主要方式主要包括两种途径:基于JDK的动态代理机制以及基于CGLIB的动态代理机制:
JDK 动态代理仅能实现接口层面的代理功能,并不具备对类级别的代理能力。核心InvocationHandler 接口及 Proxy 类负责将横切逻辑与业务流程有机整合;而 Proxy 则通过动态创建符合接口规范的对象来生成目标类的代理实例。若所涉代理类未遵循InvocationHandler 接口,则 Spring AOP系统将默认采用 CGLIB 进行目标类的动态代理操作。CGLIB(Code Generation Library)作为一套代码生成型库,在运行时能够自动生成并配置指定类型对象的子类实例,并对其中关键方法进行重写以增强功能特性;这种基于继承机制的动力态代理方式存在一定的局限性:若目标对象被明确标记为 final属性,则无法借助CGLIB完成其动力态代理过程。然而,在当前业务场景中并未涉及过final类型的目标对象;我们主要针对Controller层面接口权限以及日志记录等模块进行了代理操作;同时也在Service层面实现了事务管理系统的统一协调功能
Spring 提供了两种 IoC 容器,分别为 BeanFactory 和 ApplicationContext
该 IoC 容器专门用于管理 Bean 类型的基础实例,并全面支持 IoC 服务流程。换句话说,该容器是一个创建并初始化 Bean 实例的工厂,在其生命周期中会为每个 Bean 实例创建实例并启动其生命周期事件(如构造函数、销毁函数等)。该容器不仅负责为每个 Bean 实例创建实例并启动其生命周期事件(如构造函数、销毁函数等),还负责启动相应的生命周期事件(如构造函数、销毁函数等)。
ApplicationContext 被定义为 BeanFactory 的一个延伸类,并被称为开发环境。它不仅继承了 BeanFactory 的全部功能,并且还增添了国际化支持、资源管理、事件处理等功能。
他俩的主要差别在于:当 Bean 的某一个属性未被实例化时,在通过 BeanFactory 加载后进行首次 getBean() 调用时会触发异常;然而,在ApplicationContext 初始化阶段会自动执行相关验证工作以确保所有依赖的属性均已成功注入。
因此,在实际开发中,通常都选择使用 ApplicationContext
4、@Autowired 和 @Resource的区别
@Autowired通常会按照类型进行默认注入。若未发现该类型的定义,则系统会尝试根据类名进行自动注入。当需要指定具体类名时,默认情况下只需添加注解即可实现功能。
如果在使用过程中发现无法自动识别某个类型的属性或字段,请可以通过手动添加 "@Qualifier("特定属性或字段的类名")" 来完成相应的配置。
@Resource注解同样可以从容器中注入Bean,默认情况下会按照名称进行注入。
如果没有找到对应名称,则会根据类型进行查找。
另外,在注解中可以直接指定name属性为 '@/resource(name="类名")'
5、springbean的生命周期
生命周期这块无非就是从创建到销毁的过程
Spring 容器负责管理 singleton bean scope 中的 bean 对象 lifecycle,在此 singleton bean scope 下, Spring 可以准确掌握 singleton bean scope 中的 bean 对象生成时间点、初始化过程完成时间点以及销毁时间点。
而针对那些配置为 prototype 作用域的 Bean,在 Spring 中只负责初始化工作。一旦容器生成相应的实例后,则将该实例交由客户端代码进行管理。值得注意的是,在这种情况下,Spring 容器将不再关注自身的生命周期。每当客户端发起对配置为 prototype 作用域的 Bean 的请求时,Spring 容器都会动态生成新的实例,并且无需处理这些被配置为 prototype 作用域的对象的生命 cycle。
整体来说就4个步骤:实例化bean,属性赋值,初始化bean,销毁bean
第一步是创建一个Bean实例。容器会根据BeanDefinition获取必要的信息来创建Bean实例,并利用依赖注入技术完成Bean中所有属性值的配置注入。随后进行属性赋值的过程中,默认会调用Reflection来实现赋值功能。接着是初始化Bean之前的工作流程:如果在配置文件中定义了init-method属性,则会调用指定的初始化方法;如果配置文件中定义了init-method属性,则会调用指定的初始化方法;最后是释放Bean资源的过程:最后是释放Bean资源,并与初始化方法类似地为destroy-method指定函数,在Bean销毁前执行必要的操作。
Spring 容器中的 bean 可以分为 5 个范围:
singleton遵循单例模式运作的Bean在Spring容器中只有一个实例,并且默认作用域。主要分布在Controller、Service和DAO层。
prototype:prototyped model(原型模式),每当Spring容器读取基于prototyped model定义的Bean对象时,默认会生成新的实例。
(3)request:对于一个 HTTP 请求而言,在此期间容器会返回与该 Bean 相关联的一个实例。而对于每个新的 HTTP 请求,则将分配给不同的实例,并且其作用域仅限于当前的 HTTP Request。
在每次 HTTP Session 中运行时(即当启动一个新请求时),容器都会为该 Bean 分配同一实例(即同一虚拟机)。而当处理不同的 HTTP 请求时,则会依次分配新的实例(即新的虚拟机),这些虚拟机的作用域仅限于当前正在处理的那个 HTTP Session 期间。
global-session:Global scope refers to the same instance of a Bean that a container returns within a global HTTP session.
7、事务的传播特性
分析显示,在不同操作类型之间进行交互时(即在相互调用的过程中),系统的传递机制得以实现。在处理事务时,在系统中定义了特定的行为模式来决定是否生成新的事务操作,并指导如何执行这些操作。

8、事务的隔离级别
Spring事务的核心其实是数据库对事务的支持。如果没有数据库对事务的支持,则Spring不具备实现事务功能的能力。实际应用中,默认情况下应用必须通过基于日志的机制进行提交与回滚操作。隔离级别分为四种类型
Read Uncommitted(未提交模式):允许另一个交易实例查看该交易实例尚未提交的数据,默认最低级别的隔离级别,在任何情况下都无法保证一致性。
Read Committed(已提交模式):确保只有当一个交易实例完成数据提交操作才会被另一个交易实例访问,并且能够看到该交易实例对其现有记录的更新操作。
Repetutable Read(可重复性读取):保证在一个交易实例完成数据提交后才会被另一个交易实例访问,并且无法查看该交易实例对其现有记录的更改操作。
Serializable(串行化):规定在一个具体操作期间内不会发生不可恢复性冲突或幻影现象的设计模式名称。
9、Spring框架中使用的主要设计模式有哪些?
(1)工厂模式:BeanFactory就是简单工厂模式的体现,用来创建对象的实例;
(2)单例模式:Bean默认为单例模式。
(3)代理模式:Spring采用了其AOP功能基于JDK提供的动态代理机制以及CGLIB实现的字节码生成技术。
(4)模板方法:旨在解决代码重复出现的问题。例如RestTemplate、JmsTemplate以及JpaTemplate等
观察者模式:定义了一种基于对象键的一对多依赖关系,在这种模式下,在某个事件触发后会自动通知相关的多个目标组件进行相应的处理或更新操作;例如,在Spring框架中,默认情况下实现了该模式的技术方案即为Listener类的实现--ApplicationListener
10、spring中如何处理bean在线程并发时线程安全问题
通常情况下,在Spring框架中实现大部分Bean对象成为singleton只需要满足特定条件即可;然而,在某些特殊场景下,默认配置可能无法满足需求。通过使用ThreadLocal机制来处理这些Bean中的非线程安全状态,在Spring中实现了对这些问题的有效解决。
Thread-Local 和线程同步技术都是为了解决多线程中共享变量引起的访问冲突问题。其中同步技术采用了一种'时间换空间'的技术手段,在这种机制下仅提供了一份共享变量给所有参与竞争的线程使用,在这种情况下不同线程在进行操作前需获取互斥锁以保证数据一致性;而没有互斥锁保护的其他无阻塞策略则导致未获取到互斥锁的其他线程则需依次排队等待处理以避免数据混乱状态的发生。而 Thread-Local 则采用了另一种'空间换时间'的技术手段
ThreadLocal通过创建为每个线程专用的局部变量实例来实现对共享数据的安全隔离。由于每个线程都有独立的本地变量实例可用,在这种情况下就不必单独进行同步操作。通过将不可靠的数据封装到ThreadLocal内部对象中,开发者能够确保在多线程环境中数据访问的安全性。
在我们的项目中,该拦截器实现了类似的逻辑,在我们的微服务架构中,网关不仅负责用户登录操作,还承担鉴权任务。当具体处理各个微服务时,我们需要通过token来解析用户的相关信息。在预处理阶段(preHandler),我们使用threadlocal来管理相关变量。随后,在所有的controller和service执行过程中,直接从threadlocal中获取用户的相关信息,并在完成后及时清理这些变量以避免内存占用问题。
11
springMVC相关面试话术
话术
1、介绍一下springMVC
作为一个视图层框架 springmvc 采用的是 mvc 模型,在实际应用中非常便捷地实现了接收与处理 request 和 response 的功能 这种架构设计使得开发团队能够更加高效地完成日常开发任务 在其内部结构中 springmvc 提供了多个关键组件共同构成了完整的功能模块
它的核心组件是DispatcherServlet,在接收用户的网络请求时会返回响应信息。它的功能相当于一个中间件或控制中枢,在协调整个处理流程的同时统一管理各组件间的交互关系,并通过减少各组件之间的依赖性来提升系统的可扩展性。
处理器映射器(HandlerMapping):该组件的作用是基于请求的URL路径,并通过注解或XML配置设置完成匹配。
还有就是处理器适配器(HandlerAdapter):其主要功能是通过映射器获取相关信息,并依据预先设定好的处理流程运行相关任务,并最终返回一个ModelAndView对象。
最后是视图解析器(ViewResolver):它负责执行解析操作,并且利用ModelAndView对象的View信息将逻辑视图名转换为真实的显示视图Name并返回给用户
接下来我给你说下springmvc的执行流程吧
2、springMVC的执行流程
(1)用户发送请求至前端控制器 DispatcherServlet;
(2)DispatcherServlet 处理请求时会利用 HandlerMapping 处理器映射器来完成请求处理以获取 Handle;
(3)处理器映射器依据请求 URL 定位具体处理器 并创建相应的处理器对象及拦截器(若有则同时创建)最终将它们一并返回给 DispatcherServlet
(4)DispatcherServlet 调用 HandlerAdapter 处理器适配器;
(5)HandlerAdapter 经过适配调用 具体处理器(Handler,也叫后端控制器);
(6)Handler 执行完成返回 ModelAndView;
HandlerAdapter负责将Handler处理后的结果传递给DispatcherServlet
DispatcherServlet 负责将 ViewModel 传递给 ViewResolver 完成解析;
(9)ViewResolver 解析后返回具体 View;
(10)DispatcherServlet 对 View 进行渲染视图(即将模型数据填充至视图中)
(11)DispatcherServlet 响应用户。
3、springMVC接收前台参数的几种方式
当进行数据传递时:
- 如果采用ur1拼接方法,则可以直接通过目标接收;
- 同样支持string和int两种类型;
- 当向后端发送数据时:
- 如果是js对象形式,则需附加@requestBody标记;
- 必须确保目标中至少包含一个字段;
- 并且所有指定的数据项都存在于目标结构中;
- 对于get请求方式而言,在处理数据时可以选择普通对象或字符串/整数类型;
- 当采用form表单提交数据时,在处理接受的数据类型上同样可以选择普通对象或字符串/整数类型;
- springMVC中的常用注解包括:@PathVariable, @RequestBody, @RequestEntity, @ResponseMessage等
@RequestMapping:表示暴露于网络中的资源实体及其操作途径;通过@method字段参数来标识 HTTP 方法类型
@GetMapping、@PostMapping:规定了请求方式的方法的请求路径
@RequestParam:接收单一参数的
@PathVariable:用于从路径中接收参数的
@CookieValue:用于从cookie中接收参数的
@RequestBody:用于接收js对象的,将js对象转换为Java对象
@ResponseBody:返回json格式数据
该注解用于部署在类上,在前后端分离架构中常与@ResourceBody结合使用,在前端框架较少直接引用该注解时通常作为接口部署并频繁使用,在此类场景下通常表明该类将返回所有数据以JSON格式呈现。
等等
5、spring如何整合springMVC
简而言之,在SSM框架中SpringMVC实现了技术整合的方式就是在web.xml文件中设置核心控制器DispatcherServlet;这种设计能够对指定类型请求进行拦截;随后,在springMVC.xml文件中设置扫描器功能以便识别并包含带@controller注解的对象类;如今,在SSM框架中,默认采用注解式开发方式;其中不仅包括基本的控制类如@Service、@Repository、@Repository等;还包括更复杂的控制结构如AOP面向切面编程中的@Aspect;另外还会根据需求设置响应体映射信息以实现特定业务逻辑;除此之外还需要配置视图解析器以协调处理后的跳转功能;其中包括设定页面路径前缀和文件扩展名等内容;同时为了满足上传需求还需要进行multipart数据格式的参数配置如单个上传文件的最大体积限制以及每日最大上传量限制等。
12
mybatis相关面试话术
1、介绍一下mybatis,说一下它的优点和缺点是什么?
Mybatis is a semi-ORM (Object-Relational Mapping) framework for managing persistence, integrating JDBC seamlessly into the development process so that developers can focus solely on SQL statements without worrying about drivers, connections, or statement creation. It allows for the direct execution of native SQL operations without requiring traditional SQL code writing.
优点:
采用基于SQL语句的编程方法具有很高的灵活性,并未对应用程序或数据库原有设计架构产生任何改变。将所有SQL指令存储于XML格式中实现了对SQL指令与程序代码之间耦合关系的有效解除了,并提供了一套基于XML的标准标签体系以实现功能目标。该系统不仅支持编写动态生成的SQL语句,并允许对生成的 SQL 语句进行重用以提高开发效率
2:很好的与各种数据库兼容;
支持对象与数据库ORM字段的关系映射功能实现,并提供对象关系映射标签的支持;同时能够对对象关系组件进行维护。
相较于JDBC来说,本系统去除了冗余代码;无需手动开启连接;能够很好地与Spring框架集成。
缺点:
编写SQL语句是一项具有较高复杂性的任务;尤其是在涉及大量字段和关系表的情况下(即关联表较多时),开发人员需具备相应的技能。
2:SQL语句依赖于数据库,导致数据库移植性差,不能随意更换数据库;
2、MyBatis与Hibernate有哪些不同?
首先Hibernate属于完全面向对象的持久层框架,MyBatis属于部分自动化处理的持久层框架.
在开发方面,Hibernate提供了封装好的SQL语句,在使用时可以直接调用,并显著提高了系统的开发效率。而Mybatis则属于半自动化工具,在操作SQL时仍需手动编写代码。面对大型复杂的项目时,在处理大规模数据查询需求上可能不如PostgreSQL等数据库高效,并且复杂的SQL语句较多。因此,在处理大型复杂项目时选择Hibernate可能不是一个理想的选择。
在SQL优化方面:Hibernate自动生成SQL,在某些情况下会产生较为繁琐的语句,这可能会导致性能消耗增加;而Mybatis则需要手动编写SQL,在一定程度上能够避免不必要的查询操作,并从而提升系统运行效率。
对象管理方面:Hibernate 是一个全面的对象-关系映射框架,在开发过程中无需过分关注底层细节;只需专注于对象管理和业务逻辑设计即可;而 Mybatis 则需要独立配置其特定的映射关系以实现数据层对接。
在Hibernate的应用场景下,二级缓存参数设置首先通过SessionFactory生成相应的配置文件完成详细设定,并且接着,在具体的表-对象映射关系中指定对应的缓存类型。对于MyBatis而言,二级缓存参数设置则是在每个单独的表与对象映射关系中分别进行细致的参数调整。这样做的好处是可以根据不同表的特点和需求灵活设置各自的缓存策略。
总体来说:Mybatis 精巧、便捷(易于使用)、高效(快速运行)、简单明了(无需复杂的配置)、直接高效(提供良好的性能),部分自动化;Hibernate 功能强大(支持复杂的业务需求),高度可靠(稳定性高)、非自动化的部分实现
3、#{}和${}的区别是什么?
#{}是预编译处理,${}是字符串替换。
Mybatis在处理参数引用时, 会将sql中的参数引用替换成?号, 使用PreparedStatement的set方法来赋值;
Mybatis在处理{}时,就是把{}替换成变量的值。
使用#{}可以有效的防止SQL注入,提高系统安全性。
4、当实体类中的属性名和表中的字段名不一样 ,怎么办 ?
第一种方法:在查询的sql语句中对字段名进行别名为指定,并使其与实体类属性名称保持一致。
第二种方法:该方法基于
第三种方法:在实体类通过@Column注解也可以实现;
一般来说,在开发中会有一个XML映射文件,并为它创建对应的DAO接口。那么这个DAO接口的工作原理是什么呢?如果在这些方法的参数不同时是否能够实现重载?
在数据库开发中,Dao接口与Mapper接口之间存在对应关系。具体而言,在映射开发过程中:映射文件中的namespace字段名称对应着DAO或mapper类中的全限定名;而DAO或mapper类的方法名称则对应于映射文件中相应Mapper类中的Statement对象标识符;最后,在处理事务操作时,在DAO或mapper类方法内部所接收的所有参数将被转换为传递给数据库执行sql语句所需的参数集合,并将其作为输入传递给数据库执行sql语句所需的参数集合。
该Mappper接口并未提供对应的具体类。当调用该Mappper接口的方法时,在此过程中会将访问权限名称与方法名称连接生成唯一的键值。在Mybatis框架中,默认情况下每个
由于采用全限定名与操作码相结合的方式进行存储与查找设计,在Mapper接口中无法实现对现有已有映射关系的支持以及新增映射关系的能力设计。Mapper 接口基于JDK提供的动态代理机制实现其核心功能,在MyBatis运行时阶段会通过JDK提供的动态代理机制自动生成相应的映射对象Proxy。生成后的Proxy会对所有被映射的方法进行拦截,并代入相关参数后自动执行对应的SQL语句;随后将运算结果传递给调用者完成数据交互流程。
6、mybatis 如何执行批量插入?
存在两种处理方式:第一种是常规XML中insert语句实现单条记录插入,在调用方重复执行N次;第二种是将XML中的insert语句用于一次性处理包含N条记录的一组列表。
Insert operation with batch ID: insertBatch. This action inserts new records into the 'person' table by including columns from the 'Base.Column.List'. The data for each record is as follows: null for the primary key, the name of the individual, their sex, and their address as specified in the loop through each item in the list using an index.
7、mybatis 如何获取自动生成的(主)键值?
该方法每次调用总会返回一个整数值, 该数值表示新插入的数据行号而非自增主键字段; 当启用自增机制时, 在完成该方法操作后系统会自动生成新的主键字段, 并将其赋值给传递给该方法的对象参数中相应的字段; 为了实现这一功能需确保对象中包含两个新增属性来存储必要的信息.
8、在mapper中如何传递多个参数?
第一种://DAO层的函数public void UserselectUser(String name, String area); //对应的XML文件中, #{0}表示接收DAO层的第一个参数, #{1}表示第二个参数, 依此类推即可。
第二种实现:通过 @param 注释实现 public interface usermapper { public selectUserByUsername(string username, string hashedpassword); }
9、Mybatis有哪些动态sql?
Mybatis支持在XML映射文件中以标签的形式编写动态生成的SQL语句。该功能基于表达式的值执行逻辑判断并动态拼接生成相应的SQL语句。它提供了以下九种动态SQL标签:TRIM | WHERE | SET | FOREACH | IF | CHOOSE | WHEN | ELSEWISE | BIND
在XML映射文件中,除了常用的SELECT、INSERT、UPDA、DELETE标签之外,还有哪些标签?
该系统包含了五个核心组件:结构结果映射表(
11、Mybatis的Xml映射文件中,不同的Xml映射文件,id是否可以重复?
多个XML映射文件若带有namespace设置,则允许字段号重复使用;反之,则不允许字段号重复使用。
原因在于通过将namespace+id组合起来作为Map<String, MapperStatement>的键使用。如果缺少了namespace,则仅凭id进行操作可能会导致数据覆盖问题。因此,在这种情况下(即没有namespace),如果出现相同的id值,则会导致数据覆盖问题。然而引入了namespace后,则允许各个不同的namespace拥有相同的id值。
然而,在过去Mybatis版本中namespaces具有可选性;而在最新版本中namespaces已经变得必要。
12、Mybatis的一级、二级缓存?
本地一级缓存机制:基于PerpetualCache实现的HashMap类型的本地缓存机制,在其作用域限定为Session对象时,在Session发生flush或close操作后,默认情况下会开启一级缓存。
二者在运作机制上具有相似之处。通常会采用PerpetualCache和HashMap作为存储方案。它们的主要区别在于作用域设定为Mapper或Namespace,并支持自定义存储来源如Ehcache。默认情况下不会启用二级缓存。若要启用二阶缓存,则需通过二阶缓存属性类来实现Serializable接口(用于持久化对象状态)。可以在相应的映射文件中设置相关的配置参数...。
对于缓存数据更新机制而言,在某个作用域(如一级缓存Session或二级缓存Namespaces)执行了C/U/D操作后,默认情况下会清除该作用域中所有相关选中的缓存并进行更新。若启用了二级缓存功能,则会根据相应的配置选项来决定是否进行刷新。
13、使用MyBatis的mapper接口调用时有哪些要求?
1:Mapper接口的方法名与其所对应的mapper.xml文件中的每个SQL节点具有相同的ID;2:Mapper接口的方法接受的数据类型的格式与对应于其SQL节点定义的一致;3:Mapper接口的方法返回的数据类型的格式与对应于其SQL节点的结果数据类型的匹配;4:位于mapper.xml文件中的命名空间等同于该Mapper接口所属类路径
14、mybatis plus 了解过么?和mybatis有啥区别?
Mybatis-Plus作为Mybatis的一个增强版工具,在原有功能上仅对原有功能进行扩展,并不进行任何修改。它支持同时调用其特有的功能与原生功能。该工具旨在提高开发效率,并内置单表操作 mapper。
15、MyBatis框架及原理?
MyBatis 是一种支持自定义 SQL、存储程序以及高级映射功能的优秀持久层框架;它主要用于实现两个核心任务:
通过封装JDBC操作实现反射机制,实现了Java类与SQL语句之间的高效转换。该框架的主要设计目标在于使得在执行SQL语句时对输入输出的数据管理更为便捷;因此,在编写有效的SQL查询语句以及快速获取其执行结果方面成为了该框架的核心优势。
他的执行流程包括
获取或加载配置文件内容,并确保其包含数据库连接信息以及Mapper映射文件或Mapper包路径的信息
基于这些信息能够建立SqlSessionFactory, 程序运行期间会初始化并管理该对象, 当程序退出后会自动销毁
通过SqlSessionFactory实例创建一个SqlSession对象,并在单个线程或方法体内执行特定的SQL语句操作。该对象属于过程级别的资源管理类型,在每次调用后应确保资源被正确释放以避免潜在的问题。
当用户配置mapper.xml文件中的方法时
5.将返回的结果通过映射,包装成java对象。
13
基础部分相关面试话术
1、String类中常用的方法
split(): 将目标字段分割成一个字符数组;indexOf(): 从目标字段提取出指定字符的位置索引值;trim(): 去除目标字段开头与结尾处的所有空白字符;replace(): 替换目标字段中的特定子串或字符序列;hashCode(): 计算目标字段对应的哈希代码值;subString(): 获取目标字段中某一段连续的子串;equals(): 比较两个对象是否完全相等;length(): 获取目标字段中的字符总数;valueOf(): 将当前的对象转换为对应的数值类型;concat(): 将多个字段连接在一起形成一个新的字段;compareTo(): 比较两个可比较类型的元素之间的顺序关系;compareToIgnoreCase(): 不区分大小写的情况下比较两个可比较类型的元素之间的顺序关系
2、重载重写的区别
它是发生在同一个类中的操作属于同一类别中的操作称为重载其名称必须一致参数的数据类型有差异数量上存在差异排列顺序有变化
重写:基于父子类关系的方法中要求方法名.参数列表应保证相同的一致性,并且其返回值域不应超出父类的限制;同时抛出的所有异常也应受限于父类。
3、int、Integer自动拆箱、装箱介绍
装箱就是 自动将基本数据类型转换为包装类型;
拆箱就是 自动将包装类型转换为基本数据类型;
当定义变量时,例如将Integer类型赋值给num变量后会包装为对象进行操作;而int类型赋值给num2则会解包以完成相应的操作。
在比较的时候,也会会发生拆箱和装箱操作
无论怎样,在Java语言中
从这两个定义可以知道,就是这两种行为发生在传递的过程中
在值传递过程中,在函数调用时会将实际数值进行传输,在处理这些数值后,其原始数值保持不变。
在引用传递的过程中,在涉及的是引用的情况下,操作引用来自变量后会导致原有变量的值发生变化。
在传输过程中,若传输的内容是基本数据类型或String类型的,则采用的是属于值传输的方式,原有的变量不会受到影响;若传输的对象发生变化,且若修改了其属性数值,则会导致原有对象被直接更改;否则,原有对象将不受任何影响;此外,需了解等价操作符与equals方法之间的区别
运算符的功能是用来判定两个对象是否为同一实例的工具。它不仅用于判断两个引用变量指向的对象是否为同一实例(即是否为同一个对象),还特别适用于处理引用与非引用类型的对比关系:当基本数据类型参与比较时(无论是相同类型的基元数据之间还是基元数据与对应的封装类之间进行比较),运算符始终会进行值层面的比对;而对于引用类型的变量而言,则直接通过内存地址进行比对。
该函数返回的结果也用于判断两个对象是否相同。然而,在大多数情况下,默认情况下该类未特别重写equals()方法,则通过调用equals()函数比较该类的两个对象时等价于不显式调用‘==’运算符来进行比较。通常有以下两种使用场景:其一是在无需自定义相等情况时直接调用默认实现;另一种则是根据具体需求自定义相等情况以满足特定应用需求。
另一种方式是实现了equals()方法,并根据修改后的逻辑进行判断。通常情况下,我们都会实现equals()方法用于比较两个对象的内容是否相等;如果两个对象的内容完全相同,则认为它们是相等的。
6、String 和 StringBuffer,StringBuilder 的区别是什么
就可变性而言,在底层结构上来看的话,其基础是一个字符数组。通过final关键字进行了修饰之后,则具备了不可改变的特性。而StringBuilder和StringBuffer则提供了可变字符串的操作。
就安全性而言,在String类中定义的对象被视为不可变或静态对象,并因此具有线程安全特性。StringBuffer通过在对其操作前加入锁保护其内部状态,在每次调用相关方法时都确保内部数据不会被其他线程干扰地修改。相比之下,在StringBuilder类中并未为操作前后加入锁机制保护其内部数据结构的变化行为,则导致其不具备线程安全特性。
从性能角度来看,在处理字符串类型时会存在一定的开销
通常而言,在处理较少的字符串对象时我们推荐使用String类而在单线程场景下处理大量数据时则更适合采用StringBuilder类这种情形下我们可以选择StringBuffer来管理规模较大的字符串内容
7、final、finally、finalize的区别
在Java中,final标识符主要用于确保类的安全性。具体而言,在经过final修饰后创建的类对象就不能再被其他代码继承或继承者扩展(即无法被继承),同时通过使用final关键字对类进行标记后,则该类将无法被继承(即无法被继承)。此外,在经过final修饰的关键字用于方法时,则该方法也无法进行重新定义(即无法重写)。当对一个字段应用final关键字时,则该字段将被视为不可变(即相当于常量),其值无法发生任何变化(即不可修改)。
在处理异常时,默认情况下final都会被调用。它也被称为默认情况下final都会被调用的一种机制,在编程中尤其常见于异常处理流程中。具体来说,在一个普通的try-catch块中完成任务后运行完毕的时候,则会触发final这一机制。通常我们会在一个简单的final块中编写一些用于关闭资源的具体方法。然而需要注意的是,在某些特殊情况下(如遇到如System.exit这样的特殊情形导致主线程中断),即使当一个普通的try-catch块中有返回值的情况发生时也会自动触发这个机制并运行相应的操作步骤。
该方法属于Object类,在垃圾收集器执行时会被调用,并且会被系统处理
8、接口和抽象类的区别
接口的方法默认是 public,所有方法在接口中不能有实现,不过在Java 8 开始接口方法可以有默认实现,抽象类可以有非抽象的方法接口中的实例变量默认是 final 类型的,而抽象类中则不一定一个类可以实现多个接口,但最多只能实现一个抽象类一个类实现接口的话要实现接口的所有方法,而抽象类不一定接口不能用 new 实例化,但可以声明,但是必须引用一个实现该接口的对象,从设计层面来 说,抽象是对类的抽象,是一种模板设计,接口是行为的抽象,是一种行为的规范。9、Java的基本数据类型
共有8种基本数据类型具体包括 byte, short, int, long float, double, char 和 boolean 每个对应一个封装类
10、jdk1.8的新特性
这块的话,我先说一些我们项目中经常会用到的吧
可以说,在lambda表达式这块儿中既可以实现遍历集合的功能,也可以轻松地定义匿名函数,并且无疑是非常便捷的.
另外,在switch语句中引入了变量作为string类型的处理能力。此前该语句仅限于处理基本数据类型的变量。
此外,在流式编程中使用strem的方式可以让我们在处理集合中的数据时非常简便地完成各种操作。
除此之外,在处理日期时更加便捷。这些新引入的时间类不仅支持自定义日期设置,还允许对年月日时分秒进行灵活的时间段加减操作,并且提供快速格式化和类型转换功能。
我也发现了一些类似的资料也提到了这一点其中一种情况类似于Java 8提供了支持给接口添加一个非抽象的方法的能力这让我印象深刻特别是在设计过程中非常有用的一点是它简化了开发流程无需为每个实现类重复编写这些基础功能这也让我意识到在某些情况下使用默认关键字能够显著提升代码效率
11、JVM类加载的过程
该方法的操作流程如下:首先会对项目中指定的类文件进行读取,并将其中的数据加载至内存环境中。随后会对获取到的数据进行有效性验证、格式解析以及初始化处理。经过上述步骤后,系统将自动生成符合虚拟机需求的Java类型对象,并将其准备好供后续运行使用。
java 类加载过程涉及一系列步骤:装载→验证→准备→解析以及初始化。通常情况下是按照这一顺序依次完成整个过程。然而,在某些特定情况下,在初始化完成后也可以执行解析步骤。为了支持Java运行时绑定,并且在一个操作阶段启动下一个操作阶段的过程而不必等待前一阶段结束
再说具体一点就是
首先将Student.class文件加载到内存中并在栈内存中申请s的空间同时在堆内存中创建学生对象。随后对学生的成员变量先执行静态初始化程序后启动动态 initialize 程序并通过构造函数赋予初始属性。当学生实例化完成后立即将其引用地址赋值给 s 变量以便后续操作能够正常进行。接下来向大家介绍一下反射功能
Java 中的反射机制是指它能够在运行时状态中实现对任何类的信息访问能力,并能通过动态的方式获取这些信息并执行相关操作;这种基于实例的行为使得程序能够灵活地响应不同的需求。
对于Java语言而言,在确定一个类名称后,我们能够利用反射机制获取该类的全部信息。其主要作用在于:动态判断任何一个对象所属的类;此外,在运行时还能够创建任何所需的类实例。像Spring这样的常用框架正是依赖于Java语言中的反射机制。同样地,在运行时我们还可以查询任何类所拥有的成员变量和方法;此外,在运行时调用任何一个对象的方法也是可能的。
通常建议采用如下代码:$clazz = Class.forName("类的全路径");通过此操作后即可轻松访问类中的所有method和属性。请注意,默认情况下只能对public或protected的方法进行操作。对于私有method,则需先通过getDeclaredMethod()函数获取其声明对象并调用setAccessible(true)才能进行操作。
13、说一下常见的异常
最常见的情况莫过于null pointer异常NullPointException了,当它们调用其他对象的方法时就容易发生。
也即是说FileNotFound异常发生了,在执行文件操作的过程中,默认情况下如果出现路径书写错误的情况时可能会触发这个异常。或者是在从Windows切换至Linux的过程中由于系统环境配置差异导致的路径格式不一致问题也会引起该错误反复出现。
此外,在将JSON中的数据转换为具体类型的过程中也会频繁遇到ClassCastException这一类型转换异常。
接下来遇到的常见问题是SQLException。非常常见的错误信息通常是Unknown column xxx这种情况通常发生于列名输入有误的情况或者SQL语句解析出现偏差的情况。具体来说You have an error in your SQL syntax检查xxxx附近xxx这通常表示SQL语句存在语法问题导致解析失败异常信息会详细指示出错的具体位置例如在selectOne方法中如果尝试获取结果集却返回了多条记录这说明该方法的设计存在不足无法仅返回单一的结果集
评论(0)
14
集合相关面试话术
1、Java里常见的数据结构都有哪些以及特征
数组是最常用的数据结构,在其设计中容量是固定的,并且能够通过索引定位元素;所有元素都具有相同的属性或类型。
在结构上与数组相仿的是序列类容器,在设计上具有动态容量的特点。此类容器通常基于固定尺寸的...实现,并且能够根据需求自动扩展或收缩规模以适应存储需求的变化。允许存储重复项的情况下,在操作时会考虑如何处理这些情况
集合集合和列表很相似,不过它不能放重复的元素
仅允许对最新插入的元紧行操作;这些操作遵循先进先出的原则。当你移除栈顶元索时,在这种情况下你就可以访问倒数第二个位置。这些方法中常用的包括peek(它返回但不删掉当前顶部值)、push(将新值添加到顶端)以及pop(取出并删掉当前顶部值)等函数的操作机制能够有效管理数据结构的行为模式
与堆栈具有类似的特性,在结构上也存在一定的关联性。其区别在于,在队列中最先插入的元素也是最先被删除的,并且始终遵循先进先出的原则。这些操作通常包括peek()用于获取头部数据、offer()函数用于在队尾增加新元素以及poll()用于从队头取出数据
数据结构中的一种线性存储方式称为链表(List),它由一系列称为节点(Node)的数据元素构成。每个节点不仅存储着实际的数据信息,并且还包含指向其后继节点的一个指针(Link)。对于双链式链表而言,则会包含指向其前驱节点的另一个指针。举个例子,在某些情况下我们可以使用简单链式结构与双链式结构来实现栈(Stack)与队列(Queue),因为它们都具备两端都可以方便地插入与删除操作的特点。不过,在某些情况下需要频繁地在中间插入或删除节点。
2、HashMap底层原理
我先给您说一下我理解的HashMap吧
在JDK 1.8之前的版本中,默认情况下使用的是数组与链表的结合结构。当初始化一个HashMap时,默认会创建一个空数组作为其内部数据结构。当调用put方法向HashMap中插入数据时,请先根据键对象的hashCode属性确定存储位置,并将相应的value值放置到该位置上;如果该位置为空,则直接将新元素加入此处;如果已有元素存在,则将其追加到元素链表头部的位置上;而get方法则需要先根据键对象的hashCode属性确定目标位置,并通过equals方法遍历该位置处存在的元素链表以获取对应的value值;此外,在当前 HashMap 的容量达到现有容量75%时(即容量超过现有容量75%),系统会自动将当前 HashMap 扩容为原来的2倍大小以满足负载需求;其中这个0.75比例即为所谓的负载因子设置依据
自 JDK 1.8 版本起,HashMap进行了重要更新。最显著的变化在于采用了红黑树结构。该数据结构由数组、链表和红黑树三种组件构成。在JDK 1.7版本中,这个链表的长度并非固定,在这种情况下,当多个键(Key)具有相同的哈希码值时(即发生冲突),对应的链表长度难以被精确控制。导致GET操作的时间复杂度与其长度直接相关。为了提升这一功能模块的性能水平,在该链表元素数量超过8个时(即当该链表元素数量超过8个时),系统会自动引入了红黑树结构来优化数据查找效率
HashMap存在线段不安全问题, 即允许多个线段同时对HashMap进行增删改查操作, 可能会导致数据的一致性问题. 若需确保HashMap的线段安全性, 可采用Collections中的synchronizedMap方法来实现其线段安全性, 或者使用ConcurrentHashMap以确保数据原子性修改.
3、介绍下ConcurrentHashMap
也可以表述为:哪些 HashMap 类实现了线程安全?通过什么机制确保了 ConcurrentHashMap 的线程安全性?
该类实现了线程安全性的要求,并采用分段锁机制作为核心管理策略。其基本架构基于一个Segment数组作为核心数据存储区域,在这种设计下每一个Segment相当于一个小规模的哈希表单元。每个Segment继承自ReentrantLock来实现加锁功能,在这种机制下每当进行加锁操作时会锁定特定的一个segment区域。这样只要确保每个segment内部实现线程安全即可达到全局线程安全的效果保障需求。每一个segment存储了HashEntry对象集合,默认划分了16个这样的段落结构单位以保证系统的高并发处理能力。理论上最多可允多达16个独立操作在不同segment上进行读写互斥处理而不发生数据竞争问题的发生风险;只有当各个操作分布在各自独立的segment中就不会导致冲突问题出现
15
线程相关面试话术
1、创建线程的方式
首先,在Java编程中提到 Thread 类时,默认情况下它是实现了 Runnable 接口的一个具体运行实例。因此我们可以创建一个新类并使其继承自 Thread 类或直接实现 Runnable 接口;随后需要重写或覆盖其 run 方法以定义具体的执行逻辑。在初始化多个线程时可以通过调用该类内部定义的 start 方法来启动它们;该 start() 方法作为一个 native 方法的主要功能是启动相应的子线程并执行其中定义的操作。
另外一种方式是实现Callable接口,并且这个接口可看作是Runnable接口的一种增强版。它的执行方法不再是run方法而是采用call方法来进行操作,并且调用该call方法时可能会返回值。我们可以创建一个 FutureTask 类的实例对象,并通过调用该FutureTask对象的get()方法来获取执行结果。需要注意的是该执行结果必须与指定FutureType保持一致,并且在调用call方法时还可能抛出异常。这些机制使得我们能够清晰地了解线程内部的具体运行状态
另外一种方法是利用线段库来实现资源管理功能。这种技术的核心思想是通过预先配置好的多进程容器来管理资源分配问题。具体来说,在这种设计模式下系统会预先生成多个进程对象并将它们组织在一个统一的执行环境中以便后续调用。这样一来当需要处理某个特定任务时就可以直接从该执行环境里获取相应的进程而不必单独创建新进程这不仅能够显著减少不必要的资源开销还能提高程序的整体运行效率。通常情况下,在Java开发中推荐使用JDK提供的Concurrent包中的相关类来构建这些类型的并行执行器。例如它可以创建四种不同的并行模式分别是单实例模式、共享 pool模式、固定 pool模式以及无锁模式等不同配置满足各种场景下的需求
FixedThreadPool 配置为固定容量的线程池系统,在这种架构下,默认或可自定义设置最大承载能力参数为N,并支持并行处理最多N个请求或任务。
CACHED_THREADPOOL是一种可复用的并行队列。它不受其规模限制,并且每个队列中的元素均可以被复用。然而,在处理大量请求时(如高并发情况下),这种结构可能会导致运行时内存溢出问题。
创建了一个定时调度系统(ScheduledThreadPool),其中包含了两个主要功能模块:固定速率作业(FixRate)与固定时延作业(fixDelay)。FixRate功能通过设定固定的周期持续运行作业服务请求;而fixDelay功能则会在当前作业服务完成后等待指定时长后才开始运行下一个作业服务请求。
配置一个单线程的 ThreadPool
2、线程都有哪些方法
线程里的方法有很多,我给您说说我们常用的方法吧
该方法用于线程等待;使用该方法后;该线程将进入 waiting 状态;仅当发生通知 notify 或 notifyAll 时才可继续执行;此时会释放对象的锁;因此,在同步方法或同步代码块中通常会使用 wait 方法。
调用sleep函数让当前线程暂时休眠时,这时当前线程被阻塞处于time-waiting阶段。
在Python中使用yield关键字实现的功能是在单个函数中实现多个入口点机制,在这种机制下会执行函数中的代码到某个点后暂停当前操作并返回给调用者等待命令后再重新执行 suspended state 这种状态就是所谓的让步行为,在源代码层面理解为通过yield机制实现资源的释放或让渡,在实际应用中表现为当一个函数被调用时会将控制权暂时转移给另一个正在运行的任务或进程使其能够继续执行当前任务直到被唤醒并继续执行被暂停的任务
通过join方法实现队列插队,在这种机制下,允许当前的主线程优先执行任务,并使其他从属线程暂且搁置任务直至主线程处理完毕。
interrupt线程中断,这个就是中断当前线程的意思
notify唤醒线程,notifyAll唤醒所有线程
3、sleep和wait的区别
过去我也经常使用这两个方法来编写代码,在总结时我发现它们的主要区别大致分为三个方面。
首先就是sleep不会释放锁,而wait会释放锁
接着呢就是sleep不会解除cpu占用,wait会释放cpu资源
此外,在使用wait时需要注意的是,在指定的时间到达后,并非所有等待操作都会自动唤醒;只有在指定的时间到达后,并非所有等待操作都会自动唤醒;只有在指定的时间到达后,并非所有等待操作都会自动唤醒
4、介绍下你理解的线程中的锁
我认为在多线程环境中对数据的安全性进行保护通常需要对涉及数据操作的代码进行加锁处理,并且这种机制必须是一致性的。为了实现这一点,在Java中我们通常使用静态变量来作为同步机制,并且会将相关的代码块包裹在synchronized关键字内完成之后释放该同步锁。只有当其他线程获取到该同步锁后才能执行相应的操作。这种机制有效地保障了多线程环境下的数据一致性。
但是,在操作锁时如果出现不当操作就可能导致死锁问题出现。具体表现为:当A线程在等待B线程释放所获取的锁时,而B线程此时也在同时等待A线程释放所获取的锁,则会出现两个线程相互阻塞无法继续的情况而导致死锁现象发生。因此,在实际应用中应当尽量避免多线程之间存在相互依赖获取和释放资源的情况发生
还有对锁的分类的话,分为乐观锁和悲观锁
乐观锁的本质是一种较为积极的机制,在获取数据的过程中,默认假设其他操作者不会同时修改该数据。因此,在进行更新操作时会判断在此时间段内是否有其他操作者对同一数据进行了修改尝试,并通过使用版本号机制或者基于一致性校验(CAS)算法来实现这一判断过程。对于那些 predominantly read 的场景而言这一优化措施能够显著提升系统的吞吐量类似于数据库中提供的 write_condition 优化措施它们本质上都是乐观锁的一种实现形式
悲枧lock的概念在于每当程序试图访问数据库中的数据时都会假设其他人可能会修改这些数据因此会在访问前加入一把锁这种机制会导致等待该线程完成修改操作后才能释放lock以便其他线程可以继续访问数据。而在数据库系统中采用行级lock和表级lock这两种机制都是基于相同的悲观式独占锁定原则即它们都要求在执行任何操作前对目标数据进行锁定以防止同时操作导致的数据不一致或丢失问题。 Java语言中通过synchronized关键字以及ReentrantLock等独占锁实现了这一经典的悲观式独占锁定策略
5、线程池的原理
我之前看过线程池相关的源码,线程池主要由4个部分组成,
line pool manager: responsible for creating and managing thread pools
work threads:
the task interfaces within the thread pool: each task must implement
schedules tasks for work threads and maintains a queue for pending tasks
upon processing, tasks are added to the queue
upon the creation of these threads, they are started immediately
if the number of threads exceeds the maximum limit, any additional threads will wait in line; once other threads complete their tasks, they will dequeue tasks from the queue to process.
his key features include thread reuse; controlling maximum concurrency; and thread management.
6、线程池的核心参数都有哪些
corePoolSize设置为固定值意味着该组别中的核心进程将持续运行而不受当前任务的影响;当现有进程数量少于设定值时,在资源充足的情况下会不断生成新的进程直至达到预定的数量。
queueCapacity定义为允许进入阻塞队列的任务上限;一旦所有核心进程中都被占用,则新请求将被加入到队列中等待处理。
maxPoolSize表示可用的最大进程数量;设置此值有助于限制资源使用情况并避免过度扩展系统资源需求。
keepAliveTime配置参数决定了系统在无新请求情况下将进程从睡眠状态唤醒并重新投入处理所需的最短时间;一旦达到该阈值并将无新请求输入,则系统将停止唤醒机制以节省资源。
16
锁相关面试话术
1、介绍一下乐观锁和悲观锁
optimistic locking mechanism essentially represents a relatively positive approach. During the data retrieval process, it assumes that no other modifications will occur to the data, thus avoiding locking. However, when performing updates, it will check whether any other users have simultaneously updated the data during that specific time frame. This can be implemented using version number management or CAPS (Consistency Algorithm Protocol System) algorithms. For scenarios with frequent read operations, optimistic locking mechanisms are particularly effective, as they can enhance throughput similar to how write_condition mechanisms operate in databases. Essentially, both approaches share comparable optimistic locking strategies.
当试图从数据库中获取数据时(也就是进行一次读取操作),也会考虑到可能出现的情况:即其他人可能会对同一份数据进行修改。因此,在这种情况下就需要加一把锁。这种机制会导致等待想要读取该数据的其他线程必须暂时搁置请求直至当前线程完成对该数据的修改操作后才释放该锁以便其他人可以继续读取该份数据)。这些类型的锁定机制(行级锁定和表级锁定)都是在操作前先对目标数据进行锁定并执行相关操作 ,它们都属于典型的悲观锁定策略 。Java语言提供了诸如synchronized关键字和ReentrantLock等独占锁定机制作为实现悲观锁定策略的具体工具或方法
在Java编程语言中,不同种类的锁都采用了悲观锁的实现方式;当对数据进行操作时,这些数据会被当前线程所封锁。
2、介绍一下公平锁和非公平锁
公平锁被称为在线程等待获取同一把锁时严格按照申请时间顺序进行分配的一种机制。它确保在程序正常运行时所有线程都能顺利获得所需的锁而不会出现执行不到的情况然而维护这种顺序需要额外的机制这导致其效率相较于非公平锁稍低
非公平锁:其概念与"公平锁"完全相反,在这种机制下会由随机的线程来竞争获取锁,并且整体效率较高。然而这种机制可能导致个别线程长时间得不到CPU资源而导致执行受限。
生成一个ReentrantLock,默认情况下它是非公平的;不过如果需要的话,默认情况下它是非公平的
new ReentrantLock(); //默认非公平锁 new ReentrantLock(true); //公平锁
3、重入锁(递归锁)和不可重入锁(自旋锁)
在多线程编程中,重入锁机制指的是当一个线程已经获得了某个资源(如lockA)后,在另一个函数(如methodB())执行时再次需要使用该资源的情况下仍然能够直接进入执行状态的机制。这种机制特别适用于同一时刻有多个函数或子线程需要频繁地访问同一个资源的情况,在这种情况下就显得非常高效可靠。例如,在Java编程语言中,默认的synchronized关键字以及专门提供的ReentrantLock都实现了这种高效的可重入锁特性
互斥锁机制:在methodA进入methodB的过程中无法直接获取lock,在尝试获取时需要先解鎖以确保互斥性从而避免冲突并保证系统的稳定运行
4、共享锁和独占锁
java 并发包提供的加锁模式分为独占锁和共享锁。
独占锁
在单个 exclusive lock 模式中运行时,默认情况下只有唯一一个线程能同时获取到锁定资源;而 ReentrantLock 则通过采用独占机制实现了互斥功能。作为一种悲观式的加锁机制,在这种情况下确保了所有读操作的安全性不受其他操作的影响;当一个专用地获取互斥资源时,则所有其他专用地必须等待
共享锁
共享锁允许多个线程同时获取互斥 lock,并发访问共享资源。例如 ReadWriteLock 就是其中一种实现方式。这种共享互斥 lock 属于一种乐观式的加锁机制。其特点是放宽加锁策略以支持多个读操作并发。
5、synchronized和threadlocal的区别
synchronized关键字主要解决多线程共享数据的同步问题。
ThreadLocal使用场合主要解决多线程中数据因并发产生不一致问题。
ThreadLocal和synchronized都用于应对多线程并发访问问题。然而,在实现细节上存在显著差异:synchronized是基于锁机制来保证同一时间只能有一个线程访问共享资源;而ThreadLocal则通过为每个线程提供独立的数据副本来实现数据隔离功能,在这一层面上完全避免了不同线程之间的数据共享风险。需要注意的是,在完成操作后应当清理ThreadLocal中的相关数据以释放资源。
然而Synchronized恰恰相反,在多线程通信中其显著的特点便是能够实现数据共享特性与之相提并论
6、ConcurrentHashMap如何实现线程安全
该哈希表采用分段锁机制。该哈希表的核心部分由...中的...元素构成。具体而言, 每个...继承自...类, 用于实现加锁操作。每次加锁操作都会锁定一个特定的..., 因此, 只要每个...(即单个段)本身能够保证线程安全, 则整个哈希表就能实现全局线程安全。每个单个段内维护着一个哈希表数组, 而默认情况下会分割成16个独立的段。因此, 理论上最多可同时支持16个不同段上的并发操作, 但需要注意的是这些操作必须分布于不同的段中才能确保安全性
17
MySQL相关面试话术
1、解释一下单列索引和联合索引
单列索引用定义为在表格中某一个特定字段建立一种数据引用机制,在数据库设计中这一概念有助于提高数据检索效率与存储空间利用率。而当需要同时管理多个字段的数据关联关系时,则采用联合索引来实现这一目标。这种设计模式允许将单独存在的字段通过特定的方式连接起来,在执行复杂查询时能够提供更高的性能保障。特别地,在处理多条件查询任务时,默认情况下使用复合主键能显著提升性能,并规定最多支持两个主键组合以确保系统的稳定运行。
在为我们的数据库建立索引时需要考虑该表的数据更新频率。当表中的索引数量较多时会对数据更新速度造成较大影响。因为建立索引的过程本质上就是构建一棵二叉搜索树,在每次数据更新后都需要重新计算相应的二叉搜索树结构以维持其高效性
索引并不是时时都会生效的,比如以下几种情况就能导致索引失效:
如果在查询条件中存在OR运算且其中某些字段带有索引依然无法利用这一情况也是尽量少用OR的原因之一这是因为如果我们希望OR运算能够生效就必须确保所有参与运算的字段都具有有效的索引否则系统会在默认情况下切换到全表扫描的方式这可能会降低查询效率为了避免这种情况的发生因此建议尽量减少对OR运算的依赖特别是在处理复杂查询时更应优先考虑使用其他优化策略例如分而治之的方法即将复杂的查询分解为多个独立的小范围查询以分别处理这样不仅能够提升执行效率还能有效避免潜在的性能瓶颈此外还需要注意的是对于字符串类型的字段虽然它们通常支持通配符匹配但这些通配符必须以%开头否则可能导致无法正确匹配的问题因此在构建查询时必须确保所有涉及字符串比较的操作都明确地标注了正确的引用方式以保证数据库能够正确理解和执行这些操作最后关于是否需要为MySQL数据库创建索引这个问题其实并没有简单的答案不同的场景下可能有不同的最佳实践建议因此我将从以下几个方面为您详细讲解如何根据具体情况进行合理的索引设计(接着讨论第四题)
2、使用索引查询的优缺点
使用索引优点第一条:确保数据库表中的每一行数据具有唯一性;第二条:显著提升数据索引的速度;当采用分组与排序语句时
进行数据检索时,同样可以显著减少查询中分组和排序的时间;
缺点:创建索引与维护索引会占用资源,并且耗时随着数据规模的增长而增长
3、mysql存储引擎都有哪些,有什么区别
目前我知道的主要数据库查询语言包括MyISAM、InnoDB、BDB以及MEMORY等几种技术方案。从 MySQL 5.5 开始采用 InnoDB 作为默认存储引擎,在 MySQL 5.4 及更低版本中,则采用 MyISAM 作为默认存储引擎。接下来我会为您详细讲解它们之间的区别。
InnoDB存储引擎具有良好的事务处理能力、数据恢复效果以及高效的并发控制机制。然而,在读取性能方面存在一定的劣势,并占用较大的磁盘空间以存储索引信息。该引擎支持自增列(auto_increment),其值不允许为空;若为空则会从现有最大值自动递增1个单位;若超出现有范围则直接采用当前值进行存储。此外还支持外键(foreign key)关系管理,并具备乐观锁机制以实现事务的安全性保障及回滚功能;同时支持多版本并发控制以提升事务的安全性水平,并采用行级锁机制实现这一目标。基于B+Tree数据结构的设计使得索引查找效率得到显著提升;而MyISAM存储引擎不具备完整的事务管理功能仅提供表级锁保护并独立缓存索引文件而不维护数据文件这一特点导致其在全文搜索方面的表现较为有限
4、创建索引的原则
常被搜索字段建立索引可提高搜索速度。主键字段必须唯一并按顺序组织数据。外键字段用于提高表连接速度。范围查询字段由于其有序性呈现连续范围。排序字段因具有有序属性而能加速查询过程。常用 WHERE 语句涉及的字段应建立索引起以加快条件判断速度
通过EXPLAIN语句查看执行计划,在SQL前后插入关键字'EXPLAIN'并分析查询结果中各字段的数据类型是否存在相应的索引
举例:EXPLAIN select * from table where id=2;当我们对sql语句进行优化时,在type级别的优化必须达到ref级别的标准下(即每次查询必须使用索引),以确保数据访问效率得到显著提升。
6、有没有做过数据库建模,自己是设计表和模块
一位拥有三年工作经历的专业人士必须参与过系统设计工作,并且具有真实工作经历的程序员则会在系统架构规划、功能需求分析以及数据模型构建等方面均有所参与。
数据库建模的过程主要依赖于PowerDesigner这一专业软件的应用,在进行数据库建模之前需要对项目的整体需求进行全面分析,在设计阶段会先生成相应的原型图示来辅助思考,在初期阶段可能会有一些微调和优化的需求随后会进一步细化字段设置以满足具体的应用场景 在后续的实际应用中若需对客户的需求进行调整 则相应地会对现有的数据库架构进行优化 对于规模较小的项目来说 通常可以直接利用Navicat这样的专业工具来进行操作
7、左连接、右连接、内连接的区别
使用内联结的情况下(即inner join),两个表格之间的关联数据能够被查询到;而如果无法实现关联,则无法进行查询操作。在左联结中(left join),会完整地查询到该主表的所有记录;如果右联表中没有对应的数据,则会返回null值;与之相反的是右联结(right join)。
举例来说,请考虑一个员工与部门表的关系模型,在这种情况下:如果我们要获取每位职员及其所属部门的信息,则采用内联结是最合适的方式;而若想获取每个部门下的对应职员信息,则需采用左联结的方法(即以部门表为左表进行联结)。这样处理后即可涵盖所有无职员的部门情况。
8、 count(1)和count(*) 有什么区别
从执行结果来看count(*)和count(1)没有区别,因为他们都不过滤空值
从执行效率来看MySQL会对count(*)做优化
(1)如果列为主键,count(列名)效率优于count(1)
(2)如果列不为主键,count(1)效率优于count(列名)
(3)如果表中存在主键,count(主键列名)效率最优
(4)如果表中只有一列,则count(*)效率最优
9、mysql查询语句的优化?
对查询进行优化时应尽量避免全表扫描首先应在WHERE子句涉及的关键字前建立索引以提升查询效率。在WHERE子句中不应使用!=或<>操作符因为这会导致引擎放弃索引而进行全表扫描例如:select id from t where num is null这种情况下建议在num字段上设置默认值以避免null值问题如将num字段设置为非null类型则可简化查询表达式并减少执行开销另外不应在WHERE子句中过度使用OR操作符因为这同样会放弃索引而造成性能下降例如:select id from t where num=10 or num=20这种情况下建议采用分页查询技术通过联合查询来替代OR条件如:select id from t where num=10 union all select id from t where num=20此外以%开头的模糊查询也会引发全表扫描例如:select id from t where name like '%abc%'此时若想提高效率可考虑全面检索方法以提高搜索精度最后关于IN和NOT IN操作需谨慎使用因为它们同样可能导致全表扫描例如:select id from t where num in(1,2,3)对于连续数值范围的处理应尽量采用between运算符而非IN运算符例如:select id from t where num between 1 and 3此外在WHERE子句中应尽量避免对字段进行复杂的表达式运算这会使得引擎无法有效利用索引例如:select id from t where num/2=100这种情况下建议将其转换为更简单的表达式形式如:select id from t where num=100*2类似地对于字段函数操作也应避免因为这会迫使引擎放弃索引机制例如:select id from t where substring(name,1,3)='abc'这种情况下建议采用更高效的正则表达式替代如:select id from t where name like 'abc%'此外在WHERE子句中的等号左边不应进行函数算术运算或其他复杂表达式运算否则可能会影响系统的索引利用能力因此在构造复杂查询时必须注意索引字段的应用优先级尤其是在复合索引的情况下必须确保正确引用第一个字段才能发挥预期效果最后关于exists替代in的选择通常是一个明智的做法例如:select num from a where num in(select num from b)可被以下语句取代:select num from a where exists(select 1 from b where num=a.num然而并非所有索引都能有效提升查询性能SQL系统会在数据分布规律的基础上自动优化查询策略当大量数据具有重复性分布时某些特定的索引可能不会带来实质性的性能提升因此在设计索引策略时需要权衡其带来的好处与可能带来的副作用最终需要根据具体业务需求进行合理规划
该方法:整合sql插入语句,在整合后日志总量减少;数据量及频率下降;经优化后的整合可提升效率水平。
同时也能减少SQL语句解析的次数,减少网络传输的IO
比如:INSERT INTO table(uid,content, type) VALUES ('userid_0', 'content_0', 0);
改为:
INSERT INTO table (uid,content, type) VALUES ('userid_0', 'content_0', 0), ('userid_1','content_1', 1);
第二种方法:在同一个事务中进行插入处理
因为每次执行INSERT操作时 MySQL会在该操作开始时启动一个新事务 所有插入操作都在这个事务的所有步骤完成后才会执行提交 这样一来就可以减少创建新事务所消耗的资源时间 这也确保了数据的一致性和安全性
11、mysql查询重复数据?
比如A表有字段id,pid,sname,
查询重复数据:select * from A
where pid in (select pid from A group by pid having count(pid) > 1);
12、了解过MySQL存储过程和视图吗,介绍一下
具体改写内容
通过关联存储过程名称调用Call方法,并将传入的参数作为输入进行处理;最后将处理后的结果以@符号进行声明。
将一个" 视 "视为一张虚拟表结构,在这种情况下不存储任何实际数据。当通过SQL语句调用该虚' 视 '时,默认返回的是MySQL系统自动生成的数据结果;这些结果来源于MySQL中的其他表,并且由于两者都属于同一名称空间(即' 表 '与' 视 '共享相同的数据库命名空间),因此数据库不允许存在具有相同名称的' 表 '与' 视 '对象。这种设置使得对于用户而言,在执行查询操作时能够获得的安全性较高:允许仅显示受授权的数据,并且使复杂查询既易于理解又便于操作。
在我们公司曾经有过一个项目,在那个时候运用了五张表的数据联查。后来编写SQL语句时发现操作较为缓慢和繁琐。于是我们创建了一个视图,并可以直接在该视图上进行查询,并且速度显著提升。这个视图的功能仅限于数据查询,并不具备增删改功能。
13、where和having的区别
这两个功能主要用于添加查询条件。其中where子句用于连接并比较表中的记录;而having子句则用于基于聚合后的数据进行进一步筛选。
例如,在公司中筛选出平均薪资超过10,000美元的部门编号
常用的聚合函数有:count、sum、avg、min、max
14、数据库三范式介绍一下
第一范式,原子性,列或者字段不能再分
规范化程度较高的数据库模式必须遵循第一范式的要求,并且规定每张表只能存储单一类型的数据。这种设计原则能够防止将多种类型的数据存放在同一张表中而导致的数据混乱和插入异常问题。
第三范式(3NF),具有单一关系性(单一属性),避免传递依赖关系,在满足第二范式的基础上的所有字段必须与主键直接关联而无需通过中间字段间接关联。
15、select语句的执行顺序
from--->where--->group by--->having--->计算所有的表达式--->order by-- ->select 输出
总体流程通常是这样的。当数据库中的SQL语句包含子查询时,同样遵循这一模式进行处理。
16、mysql分库分表介绍下
采用分库分表的方式,则是在MySQL数据库面临数据量增加导致单表单库存储空间利用率提升有限、查询性能受到影响等技术瓶颈时的一种解决方案。其主要通过两种不同的数据分布策略来实现系统优化:一种是水平方向上的数据分布模式;另一种是垂直方向上的数据分布模式。
垂直拆分的方式是将一个数据库表(例如包含30个字段的数据源)进行分割处理。具体而言,则是将其拆分为两个或多个子表(例如将一个包含30个字段的数据源分割为20个字段和10个字段),或者按照不同的策略(例如将数据源分割为三个子表)进行分解存储。这种拆分原则的核心在于识别并分离出长字段、静态字段以及高查询频率的动态字段单独存储为独立的子表,并与主数据源之间建立一对一的关系存储模式。这样一来,在实现水平上的扩展不仅能够提高系统的可维护性,并且有助于功能模块化的划分与管理;然而需要注意的是,在这种垂直方向上的优化能够一定程度上提升I/O性能水平(即优化I/O性能),但仍然面临单表规模过大的挑战
水平划分的方式通常是根据数据规模进行划分,在实际操作中我们设定每个数据库表的最大容量为200万条记录左右,并采用user_0001、user_0002等命名方式进行命名管理。在执行查询操作时通过逻辑代码实现对数据的控制。这样设计能够有效避免单表或单库因大数据量和高并发带来的性能问题,并且显著提升了系统的稳定性和负载能力。需要注意的是,在这种划分方式下可能会导致跨片事务的一致性难以保障,并且跨库关联查询的性能会有所下降。因此在实施时应当结合具体业务需求来选择最适合的数据分区策略
我们在项目中采用读写分离与MySQL集群相结合的技术实现。其中read.write分离技术能够确保数据的安全性,在这种架构下,“集群”的概念实质上实现了数据水平上的分布式存储。在我们的项目中采用了MySQL Cluster(简称MyCat)技术,在其框架下配置好主数据库与从数据库。具体而言,在执行数据库增删改操作时均针对主数据库执行;而查询操作则针对从数据库执行。“ MySQL自5.6版本起就已经内置了主从复制功能这一机制基于MySQL的日志文件实现数据同步
18
JVM相关面试话术
1、介绍下JVM

JVM主要包含:getClassLoader、execution engine、native interface、runtime data storage。
ClassLoader用于将类文件管理并导入内存环境。ClassLoader的主要职责是负责将符合条件的类文件导入内存。对于程序能否正常运行而言,则由执行引擎来完成相关的验证与处理工作。
执行引擎:负责解释命令,交由操作系统执行
本地接口:本地接口的作用是融合不同的语言为java所用。
JVM运行时划分为五大组成部分:堆用于存储对象实例;虚拟机栈负责程序调用与异常处理;本地方法栈专门管理跨 JVM 方法调用;方法区分放各类静态资源;程序计数器跟踪当前线程执行位置。在这些组成部分中;VMS(虚拟机栈)、LMS(本地方法栈)及PC(程序计数器)属于线程私有部分;而MD(方法区)与堆H是属于线程共享的部分;各区域所占内存容量因结构特点而异。
程序计数器用于管理线程执行过程中涉及的字节码指令行号,在条件判断、循环体执行以及异常处理等操作时都会使用这个计数器。当运行的是Java方法时(即虚拟机层面),该计数器存储当前正在进行处理的字节码指令地址;而当运行的是Native方法时(如JNI),该计数器无记录。
**Java虚拟机栈:**每个方法在执行时都会生成一个栈帧(frame),用于存储局部变量表、操作栈、动态链接信息以及方法返回地址。整个方法自被调用直至完成执行的过程,则对应着该方法所引发的一系列栈操作——依次被压入并弹出的过程。
**本地方法栈:**与虚拟机栈非常相似,在结构上也存在许多共同点。两者在功能上也存在显著的区别——一个是用于执行Java应用程序的方法;另一个则是用于执行本地应用程序的方法。有些虚拟机会将这两个栈合并为一个。
堆: Java堆是Java虚拟机所管理的最大内存块,在运行时就被创建出来,并且在虚拟机启动时就被分配给所有线程共享使用。该内存区域的主要功能就是存储对象实例,在这里几乎每个对象实例都会分配到相应的内存空间中去。
作为垃圾收集器(GC)的核心管理区
**工作区:**描述了已由Java虚拟机加载的类信息、已确定的静态常量、指定的静态变量以及经过编译的代码片段等数据。
2、介绍下内存泄漏和内存溢出
- 内存泄漏被称为 memory leak: 它指的是程序在申请并占用一定数量的内存量后,在后续操作中无法有效地回收这些被占用的内存量。虽然单次的内存泄漏通常不会造成严重问题, 但长期积累后会导致系统运行时出现 memory overflow 错误。
- 内存溢出被称为 out of memory: 当程序在尝试为 int 类型数据分配存储空间时却存储了 long 类型的数据, 这将导致系统资源不足, 并最终报错 Out Of Memory (OOM), 这就是所谓的内存溢出。
Java内存泄漏的主要原因在于持久引用了短暂存活的对象,使得内存难以被释放。让我举几个具体的案例来说明。
创建并初始化一个静态列表变量。
public void method() {
从i=1开始一直到i小于100。
在循环内部:
生成新对象。
追加到列表中。
将对象设为null。
}
循环依次申请Object对象,并将其放入一个预先初始化好的ArrayList中;当仅释放单个引用(o=null)时,则该列表容器仍然持有对该对象的引用;因此,在单个Object被加入到此列表容器后就必须立即从列表容器中移除它;最简单的方法就是将此列表容器实例设置为null以释放其占用的空间
在多数拦截器设计中,内存泄漏通常会导致内存泄漏问题,尤其是在某些特定的实现方式下
各种类型的内存泄漏都源于不同类型的网络或数据传输链接(即数据库、socket或I/O相关的问题),只有当明确调用了其close()方法关闭该资源时才会停止这种内存泄漏;否则该资源不会被自动回收。
下面我再给您说说内存溢出的情况吧(接着背第三题)
3、列举一些会导致内存溢出的类型都有哪些,分别怎么造成的
该错误类型表明程序在运行过程中加载了大量JAR文件或类文件,导致Java虚拟机内存不足以装载所需的类,并与永久代内存(PermGen)空间相关。主要有以下两种解决方法:第一种方法:增大可用内存空间容量;第二种方法:优化应用以减少不必要的类加载数量。
1、增加java虚拟机中的PermSize和MaxPermSize参数的大小,其中PermSize是初始永久保存区域大小,MaxPermSize是最大永久保存区域大小。比如说针对tomcat,在catalina.sh 文件中增加这两个参数的配置就行了(一系列环境变量名说明结束处(大约在70行左右) 增加一行:JAVA_OPTS=" -XX:PermSize=64M -XX:MaxPermSize=128m")。如果是windows服务器还可以在系统环境变量中设置。用tomcat发布ssh架构的程序时很容易发生这种内存溢出错误。使用上述方法,我成功解决了部署ssh项目的tomcat服务器经常宕机的问题。2、清理应用程序中web-inf/lib下的jar,如果tomcat部署了多个应用,很多应用都使用了相同的jar,可以将共同的jar移到 tomcat共同的lib下,减少类的重复加载。这种方法是网上有人推荐过,我没试过,但感觉减少不了太大的空间,最靠谱的还是第一种方法。
第二种 OutOfMemoryError :Java heap space 发生这种错误的主要原因是 java 虚拟机创建的对象过多,在执行垃圾回收循环之前(即前一步骤之后),虚拟机已将可用的堆内存全部占用。与其容量直接相关的是这个问题的根本原因。解决这类问题通常有两条可行思路:
审查程序是否存在死循环问题以及不必要的对象复制行为,并找出导致该问题的原因后修正程序及算法设置。适当调高Java虚拟机中Xms和Xmx参数的值。
第三种 OutOf Memory Error(OOOM):无法创建新的本机线程,在Java应用程序中有时会发生这种情况。由于JVM分配过多内存而导致的问题包括但不限于例如当JVM内存配置超出可用内存量的一半时 会导致此类异常发生。当线程数量较多时 如果你为 JVM 分配的 内存量增加到某个阈值以上 就会显著增加发生此类问题的风险
例如系统可用内存总量设定为2G其中1.5G将用于JVM占用剩余部分则有约500MB可供使用这部分内存中的部分将被用来加载必要的系统文件因此真正可用于运行逻辑代码的空间可能仅剩下400MB然而关键的一点在于当使用Java语言创建一个线程时JVM内部会动态地生成对应的Thread对象与此同时操作系统也会在此基础上生成一个真实的物理线程并将其放置在剩余的400MB内存空间中以实现资源的有效管理与优化自 JDK 1.4 标准下默认值设置为 256KB随着软件版本升级到 JDK 1.5 后默认值提升至每个线程占用 1MB 的空间因此在当前可用的400MB内存空间内最多只能支持约400个独立运行的Java线程
在这种情况下, 如果要创建更多的线程, 则必须缩减JVM的最大内存分配量; 同时也可以考虑增加系统总内存的容量.
4、JVM中垃圾回收的算法
在对象中添加一个称为"引用来说说的对象"(引用来追踪)的机制,在这种情况下当某个对象的引用来说说的对象(引用来追踪)值为零时,则表明没有任何其他地方指向它。每当存在一个被此对象指向的情况时,则将该引用来说说的对象(引用来追踪)值加一;当此引用来说说的对象(引用来追踪)被解除时,则将其值减一。
这个看似有趣而简单的机制实际上并未广泛应用于大多数Java虚拟机系统中。因为会导致一种严重的缺陷——对象间的循环依赖关系。
可达性分析算法 该算法通过有效手段规避了对象间循环引用的问题。每个对象实例均以树状结构呈现,其中根节点特称为"GC Roots"。从该节点开始递归遍历并进行标记,完成对该树的遍历之后,未被标记的对象将被视为已死亡状态,即可回收处理。
该算法采用分段处理的方式将垃圾回收划分为标记阶段和清除阶段。
在标记过程中,在以根对象为基础的情况下,在找出所有可以从根节点到达的对象之后,则那些未被标记的对象即为未被引用的垃圾对象
在清除阶段,清除掉所以的未被标记的对象。
该方法存在明显的缺陷,在垃圾回收完成后可能会留下很多内存碎片。
在标记清除算法的基础上进行了优化,并将其划分为三个主要阶段:第一阶段为标记阶段,在此过程中对相关数据进行初步筛选;第二阶段为压缩阶段,在完成标记过程后将未被筛选的数据集中放置在一起并确定其起始位置;第三阶段为清除阶段,在这一过程中对所有无误数据进行彻底清理以消除潜在的磁盘碎片。需要注意的是压缩阶段会消耗一定系统资源,在实际应用中应当注意避免处理过量的数据以保证系统的高效运行。
复制算法(Java中新生代采用)主要概念在于将内存划分为两个区域,在同一时间段内仅占用其中一个区域。当回收器运行时,会将当前活跃对象复制到暂时未被使用的内存区域,并清除该区域内所有已知活跃对象。随后将未被使用的区域转换为当前使用区域,反之亦然。显然地,在存活对象数量较多时该算法效率会有所降低,并且这种做法会导致系统内存容量被平分使用;然而它能够有效避免产生内存碎片。
此GC算法有效地克服了基于标记-清除算法产生的" 内存碎片化" 问题。首先对需要回收和不需要回收的 内存进行标记。随后将不需要回收 的 内存数据复制至新分配 的 连续性专用区域。从而使得旧 内存块得以完整 回收而这些新 区域被规划成连续 且无间隙 的 形式。然而该算 法的主要缺陷在于它会导致 系统总 memory 的一部分被占用用于 复制操作必须预先预留一 部分 memory 空间用于存储 临时数据副本
分代搜集算法(基于Java堆的设计)的核心理念在于将对象按照生命周期长度划分为若干区间,并根据不同内存区的具体特征采取相应的回收策略以实现垃圾回收效率的提升
JVM负责管理的一块最大内存空间被划分为若干区域用于存储各类对象实例。其中,在Java语言中这一块内存分为新生代和老年代两大类部分。新生代又被划分为Eden、从存活区到存活区以及到存活区三个子区域。这种划分的目的在于通过更有效的组织方式来实现对这些对象实例的有效管理和及时回收
JVM每次只调用Eden区域和一个Survivor区域来为对象分配内存,并且由于每次只能动用其中之一来进行对象服务操作的原因,在任何时候总有一个Survivor区域处于空闲状态。由此可知新生代的实际可用内存占比达到90%左右。此外该系统采用的是复制算法来进行垃圾回收操作其清除频率相对较高因而能够有效提高运行效率;若新生代在多次循环清除过程中仍然保持着存活状态就会被系统自动转移至老年代而某些内存占用较高的场景则会直接跳转至老年代执行相关操作以释放更多的资源;而该系统的标记清除算法由于其清除频率较低的特点能够在一定程度上减少资源浪费从而提升了整体系统的运行效率
该算法通过将空间分割为若干不同且连续的空间区间,并使每个区间各自独立地进行处理和回收操作;通过分别回收这些空间区间的方法来实现资源的有效管理;同时该方法的一个显著优点是可以灵活调节一次性回收的空间数量。
5、类加载的过程
加载过程需要通过查找路径定位相应的.class文件并导入至工作空间。
在完成加载后需进行验证以确保导入文件没有问题。
为类中的静态字段分配内存空间属于系统资源分配的第一步。
在虚拟机运行期间会将常量池中的符号引用替换为其直接内存地址引用。
最后,在对象实例化前必须完成所有必要的初始化操作以确保系统的正常运行。
首先是对父类的静态变量及相关的静态代码块进行处理(注意两者间的处理次序)。其次是对子类自身的静态变量及相关静态代码块进行处理。再次对属于父类范围内的成员变量赋值操作进行处理。随后对属于父类范围内的普通代码块进行处理。第五则是对属于父类范围内的构造方法()进行初始化操作。第六则是对属于子类范围内的普通代码块进行处理。最后则是对属于子类范围内的构造方法()进行初始化操作;值得注意的是,在客户端调用时,默认会使用new关键字来创建实例,并调用其构造方法。
6、怎么判断对象是否可以被回收
一般有两种方法来判断:
给每个对象分配一个引用计数器,在每次存在对象引用时,则该计数值加一;每当对象资源被释放后,则将其引用计数值减一。只有在引用计数值归零后才能进行回收处理。然而这种机制存在一个局限性即无法解决循环引用的问题;从当前垃圾回收根节点开始向下探索路径即构成了所谓的"引用来连接"关系链。如果某个目标类在其构造过程中并未引用来连接到任何其他节点则表明该目标类属于可回收范围之外
JDK 集成提供了大量监控工具,它们被存放在 JDK 的 bin 目录中;其中较为常用的就是 jconsole 和 jvisualvm 这两种视图监控工具。
JConsole 用于对 JVM 内存区域内的线程与类进行实时监控。
JVisualVM 是 JDK 提供的一款综合性能分析工具,在性能优化方面表现尤为突出。它支持生成完整的运行时状态报告,并提供详细的性能基准数据。
JStat 命令能够提供关于堆内存使用情况的数据概览。
JMap 是一款用于深入分析 Java 运行时中进程间通信机制及资源占用情况的专业工具。
我先给您介绍下jdk1.7中的堆的情况吧
年轻代的区域划分为Eden区以及两个大小相等的Survivor区,在Survivor区间中,则仅在某一时段内只有一个区域被使用;而另一个则用于复制正在执行的对象;当Eden区间满的时候垃圾回收机制就会将存活的对象移动至空闲的Survivor区域内;经过15次垃圾回收后存活于老年代的对象会被转移至该区域
年老代中的对象主要保留具有较长生命周期的个体。这些对象通常属于较为陈旧的一批。当某些对象从年轻一代被复制并转移一定次数后,则会逐渐被转移到年迈一代中。通常系统若采用应用级缓存策略,则其中存储的对象往往会在这一区间内停留较长时间。
Perm 永久代主要保存类、方法以及字段对象等数据信息,在内存管理中如果不加以妥善配置可能会导致内存溢出的情况发生。具体而言,在一个Tomcat实例下同时部署多个应用程序时可能会出现此类问题;然而,在涉及热部署的应用服务器环境中偶尔会因过度频繁地进行服务重启而导致OutOfMemoryError:PermGenSpace错误的发生。这种情况下通常的原因在于每次重启服务后未及时清除不再使用的类对象(即Class),从而使得大量Class对象长时间停留在永久代(Permanent Generation Space)中占用内存资源。对于这类问题一般只需通过重启服务即可解决。
Virtual虚拟区最大内存和初始内存的差值,就是虚拟区
在 JDK 1.8 中的主要变化是使用元数据区域取代了永久代。这块内存区域不在虚拟机内部,在本地内存区域中使用。我查看了官方解释后得知:这是因为后续需要融合两个 JVM 的版本。由于一个版本中没有设计永久代这一概念;另一方面,在实际应用中发现由于永久代内存通常不够用或容易发生内存泄漏问题;因此决定弃用永久代并改用元数据区域;这样就改为了使用本地内存区域
19
Linux相关面试话术(常用命令)
1、linux常用命令

20
Redis相关面试话术
1、介绍一下redis
Redis是一种非关系型数据库,在我们的项目中主要用来存储高频率的数据以缓解数据库的压力,并且采用的是非阻塞IO多路复用机制。该系统通过单线端口监听并实现了对Redis的单线程纯内存操作。为了实现这一功能我们调用了springdata-redis组件来进行Redis数据的操作
在我们的项目中涉及到了多个领域或地方的数据存储需求较高。例如首页的热门数据、数据字典中的信息等都采用了高效存储技术以提升访问速度
redis包含五种数据类型:字符串(string)、列表(list)、哈希(hash)、集合(set)以及有序集合(zset)。其中最常用的是字符串(string)、列表(list)以及哈希(hash)。对于一些只需键值对存储的应用场景——如一些系统开关设置——它们都会以字符串(string)类型进行存储。此外,“开放注册”等一些简单的键值对也采用字符串存储方式。而对于那些需要快速查找的数据——如我们的首页推荐数据与热门数据——则采用哈希表进行存储:即使用一个固定的字符串作为键(key),每条数据的ID作为字段(field),对应的数据则作为值(value)进行存储
Redis还提供了一种称为RDB(Redo-based)的持久化机制,默认情况下Redis会使用这种机制进行数据存储。该方法通过生成快照的方式保存数据,在固定的时间段内发生的变化情况会被记录下来,并会将这些快照保存到磁盘上以便后续恢复使用。为了保证高效的数据访问效率,在完成整个数据持久化流程之后Redis才会更新主数据文件,并单独创建一个子进程专门负责这一过程以避免主进程执行I/O操作从而降低了系统资源消耗的风险。这种设计不仅提升了系统的稳定性还使得备份操作变得更加便捷因为每当新快照生成时都会创建一个新的快照文件而无需担心旧版本丢失的问题。然而这种技术也存在一定的局限性即若在完成整个持续性流程之前发生故障则可能导致无法恢复的数据丢失风险
还有一种称为AOF模式,在Redis中常采用的即时持久化方案。这种模式通过记录并保存Redis执行的所有write操作(即write指令),在下次Redis重新启动时,则只需依次从头到尾重复执行这些操作即可实现数据恢复功能。然而需要注意的是这种方法会导致系统性能出现明显下降
两个不同的情况下可以同时开启这两种方式,在两个不同的情况下(即当两种方式同时被启动时),Redis在数据恢复过程中会优先选择AOF(应用程序对象文件)作为恢复手段。
我们在项目的开发中采用了默认的RDB持久化技术。由于我们存储的数据在某种程度上并不是特别关键,并且即使数据丢失了也能从数据库中恢复过来。其核心优势在于性能表现。
2、redis缓存雪崩和缓存穿透、缓存预热、缓存降级
我们可以从概念上理解为:在现有缓存失效的情况下,在新缓存尚未被插入到Redis期间
例如,在配置缓存时我们采用了相同的过期时间。在相同的时间段内发生了大面积的缓存失效事件。这些原本应当通过缓存快速响应的请求不得不转而查询数据库导致了对数据库CPU和内存的巨大压力严重情况下可能导致数据库发生宕机。从而引发了一系列连锁反应最终导致整个系统的崩溃。
解决办法:
最有效的方案通常是使用锁机制或者队列机制来防止大量线程同时对数据库进行读写操作。这种设计能够确保即使在发生故障时也能有效隔离并处理高负载情况下的数据访问问题。此外,在缓存管理方面采用一种分散化的设计思路更为合理:即通过随机化的失效策略来实现缓存节点的时间分布不均匀化,并确保在任何时刻都不会出现固定的失效时间点。这一方案能够在一定程度上平衡效率与可靠性之间的关系,并且能够通过动态调整各节点的存活周期来优化资源利用率
缓 cachedata穿透是指当用户发起数据查询请求时,在数据库中不存在对应的数据项,则相应的缓存在该位置也不会存储任何相关内容。
这会导致用户的查询请求无法从缓存层中获取所需的数据,
每次都需要再次向数据库提交请求以获取数据,
最终返回空值或null。
从而使得这些请求能够直接访问数据库而不经过缓存层,
这也是提高系统性能时常关注的缓存命中率问题所在。
解决办法
通常会选择布隆过滤器作为解决方案
所谓缓存预热:是指当系统上线后会将相关的缓存数据直接导入至缓存系统中。这样可以在用户发起请求时无需先从数据库中查询相关信息而后将数据加载至内存中以供访问;这种机制能够有效避免因频繁的数据读取导致的性能瓶颈问题!因此当有用户访问服务器时可以直接获取到已经被预先导入至本地存储中的相关数据而不必经过远程服务器的响应过程!
操作方式:
1、直接写个缓存刷新页面,上线时手工操作下;
2、数据量不大,可以在项目启动的时候自动进行加载;
然后就是缓存更新:
1、定时去清理过期的缓存;
每当有用户向系统发送请求时,
系统会首先检查该请求涉及的缓存。
如果该缓存已过期,
则从底层系统获取最新数据并更新缓存状态。
缓存级别下放:在流量激增导致服务出现性能瓶颈的情况下(如响应延迟或无法响应),即使存在故障或性能瓶颈的服务仍需维持可用性以保障业务连续性。Redis可作为辅助机制提供数据冗余存储方案以应对部分负载压力转移至非关键业务单元的可能性,并通过关键指标监控自动触发降级流程以确保主业务不受影响。该策略的核心目标是通过下放部分负载压力至非关键业务单元确保主业务不受影响
3、redis分布式锁
在分布式系统架构中,默认情况下将应用部署至单机服务器上通常采用Java内置的synchronized同步机制来管理对象级别的互斥访问。然而针对分布式的架构设计需求每台服务器上的对象实例存在差异因此必须采用专门的分布式锁机制来实现对对象级别的保护这一机制在Redis数据库中通过键值对(key-value)的方式得以实现具体实现步骤如下首先利用setnx方法尝试获取锁状态若返回1则表示成功获得了锁随后立即设置一个有效时长以防止服务中断期间锁保持一致状态完成业务逻辑处理后释放锁以便其他线程可在此处进行相关的操作
4、redis主从复制
依赖持久化机制,在内存与磁盘之间建立可靠的数据备份关系后就能够实现高可用性设计目标。具体而言,在服务重启操作时可能会导致一部分或极少部分的数据丢失风险;然而需要注意的是,在单一服务器上的数据存储模式存在潜在风险;因此,在这种架构下,默认配置下每台主从节点都会进行实时通信;为此,在这种架构下,默认配置下每台主从节点都会进行实时通信;为此,在这种架构下,默认配置下每台主从节点都会进行实时通信;为此,在这种架构下,默认配置下每台主从节点都会进行实时通信;为此,在这种架构下,默认配置下每台主从节点都会进行实时通信;
Redis的主从架构支持一主多从或级联架构设计。根据是否实现全量复制分为全量同步和增量同步两种模式。配置极为简便只需将从节点设置为主节点IP地址即可若需设置密码则应在相应位置附加相关参数读取功能受限仅限于数据读取而无法执行更新操作
全量同步主要发生在初次同步的时候,大概的步骤是
从客户端与主节点建立连接关系之后会立即执行SYNCHRONIZE命令;当主节点接收到SYNCHRONIZE命名之后就开始执行BGSAVE命令以生成RDB文件的同时还会利用缓冲区持续记录此后所有的Write操作;完成BGSAVE动作之后主节点会将快照文件发送给所有客户端节点并且在此过程中也会继续收集后续发出的所有Write指令;客户端接收到快照文件之后会删除掉之前所有的过期数据然后将最新的快照内容加载进来;最后当Secondary节点启动并正常运行起来的时候Master节点所执行的所有新增操作都会被自动同步到Client节点。
5、redis集群
Redis本就内置了集群操作功能redis_cluster。每个集群至少配备3个Master节点和3个Slave节点。各个实例均采用独立的配置文件管理。Master和Slave节点无需额外配置。通过选举机制,默认确定一个Master节点作为主库以及若干Slave节点作为备选库。确保最终能产生一个明确的Leader节点就必须避免出现两台机器得票相等导致僵局的情况。建议集群服务器总数设置为奇数数量即总共有2n+1个Node参与选举。当集群发生故障时剩余存活Node的数量必须超过n+1个否则Leader将无法获得足够多数Node的支持而陷入瘫痪系统将自动退出运行状态通常情况下我们会设置至少5个Node(n=2)作为Cluster的基础架构
Redis 2.8版本中具备了哨兵工具来实现自动化系统监控及故障恢复功能。其主要作用是实时监控Redis主节点和副节点的运行状态,并在主数据库发生故障时自动将负载转移至当前运行正常的副节点。
我们的公司使用Ruby脚本来构建Redis集群。其中包含了6台服务器,在数据传输过程中采用双路通道机制完成。如果你有兴趣的话,请继续听我讲解他们的存储机制。每当向系统中插入新数据时(即所谓的"put"操作),Redis都会根据输入的关键字计算出对应的哈希值,并将该数据存入相应的位置(即所谓的"get"操作)。这也是我所掌握的相关知识。
6、除了redis,还了解哪些别的非关系型数据库
有memacache,MongoDB这些,以及redis这几个都是非关系型数据库
memacache是一种基于内存的数据缓存框架,在设计上仅限于存储基础类型的字符串信息,并规定其最大承载容量限定为1兆字节(1MB)。该缓存机制在服务运行中断或进行重启操作时将无法维持数据一致性,并缺乏文件形式的数据持久化能力
MongoDB的方式是将数据存储在磁盘上
Redis相比Memcached和MongoDB而言,在性能及安全性上有显著优势——它不仅具备丰富且灵活的数据类型选择,并且运行速度极快——达到了512MB的最大容量,并且可以在内存中存储数据,在重启系统后也能快速将磁盘上的数据加载回内存中;相较于Memcached和MongoDB,在性能及安全性方面均表现更为出色
7、redis数据同步
这块主要功能是与MySQL数据库实现数据同步。由于MySQL的数据可能会随时发生变化,在实际应用中使用时需要考虑这一点。为了确保数据一致性,在每次数据库操作完成后都需要检查是否有变化,并及时更新到Redis存储系统中。具体来说,在新增一个线程的情况下(例如,在增删改操作时),系统会将这些变化的数据更新到Redis中。此外,在数据变化时(比如删除某些记录),我们可以选择性地删除Redis中的相关数据,并等待下次用户查询时再重新加载最新的数据库结果来保证一致性。
8、介绍一下redis的pipeline
pipeline的话,就是可以批量执行请求的命令
Redis作为单线程服务器,在执行命令时会引发其他客户端进入阻塞状态。当处理相关操作时发现系统处于高并发场景下,则会严重影响系统的响应效率。因此Redis为此提供了一种称为PIPELINE的技术,在这种机制下我们可以将多个操作打包成一个提交请求从而减少了I/O瓶颈并提升了整体性能。
因为管道是批量处理命令的一种方式,在实际应用中我们通常会将其与Redis的事务功能相结合使用。管道系统本身也具备遵循ACID原则的特点:MULTI指令启动一个事务过程;EXEC指令执行该事务操作;DISCARD指令则用于清除事务中的状态标记并恢复到非事务状态;在操作前还可以搭配Watch机制进行监控;当在处理一组命令时希望防止某一个变量被其他客户端修改,则可采用Watch机制进行监控;一旦发现被修改,则会触发自动回滚机制;操作完成后则需撤回Watch监控以释放资源
9、介绍下redis中key的过期策略
定时删除:同时设置key的时间戳并建立一个定时任务,在该定时间到达时自动清除该key。
惰性删除机制:当key过期时不再进行删除操作;在每次从数据库获取key的过程中进行时间验证,在发现已过期的key时执行删除操作,并返回null值
定期删除:每隔一段时间执行一次删除过期key操作
redis 过期策略是:定期删除+惰性删除。
即意味着Redis系统每隔100ms间隔会随机选取带有过期时间设置的key进行检查判断。若该键值对已超时则移除之。
