Java 高频面试题

1.SpringCloud常用组件有哪些?

最常用的5个组件是Nacos、Gateway网关、Feign远程调用、Sentinel熔断降级和Ribbon负载均衡其中Nacos是eureka的升级版,是阿里巴巴开发的,不仅仅可以作为注册中心,还可以作为配置中心

format,png

1.1.注册中心

服务提供者在启动时,会将自己的元数据信息注册到Nacos服务器中,同时Nacos也会对注册的服务进行心跳检测,以确保服务的可用性,服务消费者在需要调用服务时,可以通过Nacos的服务发现功能获取服务提供者的地址列表,从而实现负载均衡和服务调用

1.2.配置中心

配置中心则是负责管理应用程序的配置信息,nacos提供了一个可视化的配置管理界面,支持多种的配置文件,支持动态配置管理,当配置信息发生变化时,nacos会自动推送最新的配置信息给应用程序,从而实现配置的实时更新

1.3.网关Gateway

用于实现服务的路由、负载均衡、过滤和熔断等功能。采用了异步非阻塞的模型,采用Netty作为底层的HTTP库,提高了网关的吞吐量和性能

网关中的过滤器:

1.默认过滤器(只能使用SpringCloudGateway自带的过滤器)

2.路由过滤器(只能使用SpringCloudGateway自带的过滤器)

3.全局过滤器(创建一个类实现GlobalFilter接口,重写filter方法,可用于鉴权)

4.三种过滤器默认执行顺序(默认->路由->全局)全局过滤器实现Ordered接口,重写getOrder方法,返回-1即可实现在其他两个过滤器前执行(默认和路由过滤器的Order默认是1开始的)

1.4.Feign远程调用

1.定义接口:在Feign客户端的接口中定义需要调用的远程服务的方法,使用注解指定服务名称、请求方式、请求路径、请求参数,响应结果等信息

2.解析接口:在应用请求时,Feign会扫描所有带有@FeignClient注解的接口,并将其解析成可执行的HTTP请求,生成动态代理对象并保存在Spring的容器中

3.发起请求:当应用调用Feign客户端的接口方法时,Feign中依赖的Ribbon组件会去获取@FeignClient注解中name属性的值,即服务名,通过这个服务名去找注册中心拉取服务提供者列表,缓存到本地。基于负载均衡的方式选择一个服务提供者,根据接口声明的方法上的请求路径,请求参数,请求方法,发送http请求

4.相应结果:远程服务接收到请求后,会根据请求路径和请求参数执行相应的逻辑,并将结果封装成HTTP响应返回。Feign客户端接收到响应后,根据响应结果类型进行反序列化,并返回给应用程序。以接口和注解的方式,实现对HTTP请求的映射和调用

底层原理:

Feign的底层实现基于动态代理和HTTP客户端,它将接口和HTTP请求/响应绑定在一起,通过注解配置的方式简化了服务调用的过程,提高了代码的可读性和可维护性

1.5.Sentinel熔断降级

Sentinel是一款开源的服务治理组件,主要提供流量控制、熔断降级和系统负载保护等功能。熔断降级是指在分布式系统中,即当系统出现异常或不可用时,通过断路器的方式将请求快速失败,避免请求不断堆积,引起系统的崩溃(雪崩)。Sentinel在防止微服务雪崩上有以下四种方案:

1.超时处理:设定超时时间,请求超过一定时间没有响应就返回错误信息,不会无休止等待

2.舱壁模式:限定每个业务能使用的线程数,避免耗尽整个tomcat的资源,因此也叫线程隔离

3.熔断降级:由断路器统计业务执行的异常比例,如果超出阈值则会熔断该业务,拦截访问该业务的一切请求

4.流量控制:限制业务访问的QPS,避免服务因流量的突增而故障

1.6.Ribbon负载均衡

主要作用是在服务消费者与服务提供者之间进行负载均衡,将请求分发到不同的服务实例上,从而提高系统的可用性和性能。Ribbon通过向注册中心获取服务实例信息,并通过算法选择合适的实例进行请求转发,从而实现负载均衡。可以通过配置不同的负载均衡策略,实现不同的负载均衡方式,如轮询、随机、加权随机、最少活跃数

2.Spring Bean的生命周期

bean的生命周期是指:Spring容器在管理Bean时,会根据特定的顺序进行初始化、依赖注入、执行业务逻辑等一系列操作,最终销毁Bean;

1.实例化:Spring容器通过反射和工厂方法创建bean的实例

2.属性注入:Spring容器将bean的属性值和依赖项注入到实例中

3.初始化:如果bean实现了initializingBean接口,Spring容器将调用afterPropertiesSet()方法;如果在bean配置中声明了init-method属性,则Spring容器将调用指定的方法。(此时就可以使用bean了)

4.销毁:如果bean实现了DisposableBean接口,Spring容器将在关闭应用程序上下文时调用destroy()方法;如果在bean配置中声明了destroy-method属性,则Spring容器将调用指定的方法

7d3660d2b15670bb5d1332cba676c3bc.png

【注意】:

单例bean,只有一个实例会在应用程序上下文中存在,并且在容器关闭时销毁;对于原型bean,每次调用getBean()方法时都会创建一个新实例,因此不会进行销毁。

3.SpringMVC执行流程

mvc的执行流程大致分为11步:

1.用户发送出请求到前端控制器DispatcherServlet

2.DispatcherServlet收到请求调用HandlerMapping(处理器映射器)

3.HandlerMapping找到具体的处理器(可查找xml配置和注解配置),生成处理器对象及处理器拦截器(如果有),再一起返回给DispatcherServlet

4.DispatcherServlet调用HandlerAdapter(处理器适配器)

5.HandlerAdapter经过适配调用具体的处理器(Handler/Controller)

6.Controller执行完成返回ModelAndView对象

7.HandlerAdapter将Controller执行结果ModelAndView返回给DispatcherServlet

8.DispatcherServlet将ModelAndView传给ViewReslover(视图解析器)

9.ViewReslover解析后返回具体View(视图)

10.DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)

11.DispatcherServlet响应用户

112d910d1a6eeaac5d978099947665de.png

 4.Spring Boot自动装配原理

1.启动类上@SpringBootApplication注解

2.底层用了3个核心注解,@SpringBootConfiguration声明当前类是一个配置类,@ComponentScan默认扫描启动类所在的包及其子包,@EnableAutoConfiguration开启自动配置

3.@EnableAutoConfiguration底层封装了@Import注解,指定了一个ImportSelector接口的实现类,低版本调用selectImports(),高版本调用getAutoConfigurationEntry(),读取当前项目下所有依赖jar包中META-INF/spring.factories、META-INF/spring/org.springframework.boot.AutoConfiguration.imports两个文件里面定义的配置类

4.在配置类中定义一个@Bean标识的方法,还定义了@Conditional开头的注解,条件如果满足,spring会自动调用配置类中@Bean标识的方法,并把方法的返回值注册到IOC容器中

5.SQL优化(索引类型、失效场景、执行计划)b+tree底层

5.1.SQL优化方法

1.执行计划:

是SQL在数据库中执行时的表现情况,通常用于SQL性能分析,优化等场景。在MySQL使用explain关键字来查看SQL的执行计划

2.如何查看SQL执行计划:

在查询语句前加explain关键字

explain的作用:

查看表的读取顺序

查看数据库读取操作的操作类型

查看哪些索引有可能被用到

查看哪些索引真正被用到

查看表之间的引用

查看表中有多少行记录被优化器查询

3.字段分析:

1.id:select查询的序列号,包括一组数字,表示查询中执行Select子句或操作表的顺序(id值相同:执行顺序由上而下;id值不同:id值越大优先级越高)

2.select_type:表示SELECT语句的类型

3.table:显示这查询的数据是关于哪张表的

4.type:区分索引,这是重要的列,显示连接使用了何种类型。从最好的最差的连接类型为:system>const>eq_ref>ref>ref_or_null>index_merge>unique_subquery>index_subquery>range>index>ALL

一般来说,得保证查询至少达到range级别,最好能达到ref,到index就可以

5.possible_keys:指出MySQL能使用哪个索引在该表中找到行。如果是空的,就是没有相关的索引

6.key:实际使用的索引。如果为NULL,则没有使用索引

7.key_len:最长二点索引宽度。如果键是NULL,长度就是NULL。在不损失精确性的情况下,长度越短越好

8.ref:显示使用哪个列或常数与key一起从表中选择行

9.rows:显示MySQL认为它执行查询时必须检查的行数

10.Extra:执行状态说明,该列包含MySQL解决查询的详细信息

4.优化:

-SELECT语句务必指明字段名称(避免直接使用*)

-SQL语句要避免造成索引失效的写法

-SQL语句中IN包含的值不应过多

-当只需要一条数据的时候,使用limit 1

-如果排序字段没有使用索引,就尽量少排序

-如果限制条件中其他字段没有索引,尽量少用or

-尽量用union all代替union

-避免在where子句中对字段进行null值判断

-不建议使用%前缀模糊查询

-避免在where子句中对字段进行表达式操作

-Join优化 能用innerjoin就不用left join right join,如必须使用 一定要以小表为驱动 A left join B

5.其中常见索引的类型:

普遍索引、唯一索引、主键索引、复合索引

6.失效场景:

使用函数或表达式查询、使用NOT或!=操作符、使用or操作符、对索引列进行运算、like模糊查询前置%、多个索引查询,只有第一个有效

7.b+树的底层:

B+树是一种多路平衡查找树,它采用平衡树的思想,能够高效地支持数据的CRID操作

B+Tree中所有数据都存储在叶子节点中,叶子节点之间通过指针连接形成链表,便于范围查询;非叶子节点只存储索引,不存储实际数据,使得非叶子节点能够存储更多的索引,一个非叶子节点可以存储的数据为16kb,一个key一般为8字节,指针一般是6个字节,所以一个非叶子节点大概就可以存储1000条数据,第二层就可以达到百万条数据,第三层就达到了千万条数据,所以b+树一般是2-4层

6.数据库有哪些约束?

主键约束、外键约束、唯一约束、非空约束、默认约束

7.线程池:有几种、哪些参数、状态及执行过程?

7.1.线程池

通常通过java.util.concurrent包提供的ThreadPollExecutor类来实现。ThreadpollExecutor类有七个参数:

1.corePollSize:核心线程池大小

2.maximumPollSize:线程池最大线程数

3.keepAliveTime:线程空闲时间

4.unit:keepAliveTime的时间单位

5.workQueue:任务队列

6.threadFactory:线程工厂

7.RejectHandler:拒绝策略(有四种1.CallerRunsPolicy:在任务被拒绝添加后,会在调用者线程中直接执行被拒绝的任务 2.DiscardPolicy:直接丢弃被拒绝的任务,不做任何处理 3.AbortPolicy:直接抛出RejectExecutionException异常 4.DiscardOldestPolicy:将最早被放入等待队列的任务丢弃,然后将新任务加入等待队列)

7.2.四种线程池

1.FixedThreadPool:固定大小线程池

2.CachedThreadPoll:缓存线程池

3.SingleThreadPool:单线程池

4.ScheduledThreadPool:定时线程池

7.3.线程池状态

1.RUNNING:线程池处于正常运行状态,可以接受新的任务并处理已有的任务

2.SHUTDOWN:线程池不再接受新的任务,但是会处理完队列中已有的任务

3.STOP:线程池不再接受新的任务,并且会尝试中断正在执行的任务

4.TIDYING:线程池中的所有任务都已经执行完毕,正在进行资源回收和清理工作

5.TERMINATED:线程池已经被完全终止,不再接收新的任务并且已经释放所有的资源

通过ThreadPoolExecutor类的getPoolSize()方法获取当前线程池中的线程数量

通过getActiveCount()方法获取正在执行任务的线程数量

通过getTaskCount()方法获取已经提交但还未执行的任务数量

通过shutdown()、shutdownNow()等方法来改变线程池的状态,以达到暂停或终止线程池的目的

7.4.执行过程

1.创建线程池:创建一个线程池对象,指定线程池中包含的线程数

2.提交任务:通过execute()方法向线程池提交任务,任务会被封装成一个Runnable对象并添加到任务队列中等待执行

3.判断任务队列:线程池中的线程会不断从任务队列中取出任务执行,如果任务队列中没有任务,则线程会等待直到任务队列中有新的任务

4.执行任务:线程池中的线程会按照FIFO(先进先出)的顺序从任务队列中取出任务执行,当一个线程执行完任务后,它会继续取出下一个任务执行,直到线程池中的所有线程都处于空闲状态或线程池被关闭

5.关闭线程池:调用shutdown()或shutdownNow()方法关闭线程池,线程池会拒绝接受新的任务并尝试停止已有的任务执行,同时会等待所有线程执行完毕并释放资源  shutdown()是会等待所有已经已经提交的任务执行完成,再关闭。shutdownNow()则是让线程池会立即停止所有正在执行的任务,并尝试中断所有处于等待状态的线程,同时返回一个未执行的任务列表

8.多线程中三大特性?如何解决?线程有哪些状态?

8.1.三大特性是指

1.原子性:原子性是指一个操作是不可被中断的,要么执行成功,要么全部失败。在多线程中,如果多个线程同时访问同一份数据,可能会发生数据竞争的问题,导致数据不一致。使用synchronized关键字、Lock对象等方式可以保证原子性

2.可见性:可见性是指一个线程修改的状态对其他线程是可见的。在多线程中,由于线程之间的数据共享,一个线程修改的变量可能对其他线程不可见,导致数据不一致的问题。使用volatile关键字可以保证可见性

3.有序性:有序性:有序性是指程序执行的顺序按照代码的先后顺序执行。在多线程中,由于线程的交替执行,程序执行的顺序可能发生变化,导致程序出现错误。使用synchronized关键字、Lock对象等方式可以保证有序性

8.2.线程的状态

1.新建(New):线程被创建但未启动;

2.运行(Runnable):线程正在执行;

3.阻塞(Blocked):线程被阻塞,等待某个条件的唤醒;

4.等待(Waiting):线程处于无限期等待状态,需要其他线程显式地唤醒;

5.计时等待(Timed Waiting):线程处于有限期等待状态,等待一定时间后自动唤醒;

6.终止(Terminated):线程执行完毕或因异常退出;

9.如何优雅的退出线程?

1.设置退出标志:在程序需要退出时,可以设置一个标志位,标识线程需要退出。【推荐】:可记录到业务具体执行到哪个步骤,还有哪些没执行等,以便后续启动后可继续执行下去

2.中断阻塞:对于正在阻塞的线程,可以通过调用interrupt()方法中断线程的阻塞状态,使其抛出interruptedException异常,从而退出阻塞

3.等待线程结束:对于正在运行的线程,可以调用join()方法等待其结束。这个方法会阻塞当前线程,直到目标线程结束

4.清理资源:线程结束后,需要清理线程所占用的资源,如关闭文件、网络连接等

10.HashMap

1.Hashmap是Java中的一个数据结构,可以实现键值对的存储和查询。它基于哈希表实现存储结构;

2.JDK1.7及以前的版本使用的是数组+链表的结构  1.7使用的是拉链法:将链表和数组相结合。也就是说创建一个链表数组,数组中每一格就是一个链表。若遇到哈希冲突,则将冲突的值加到链表中即可

3.JDK1.8之后使用的是数组+链表+红黑树的结构 jdk1.8在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间。扩容resize()时,红黑树拆分成的树的结点数小于等于临界值6个,则退化成链表。

4.死循环问题在数组进行扩容的时候,因为1.7链表是头插法,在进行数据迁移的过程中,有可能导致死循环在JDK1.8中将扩容算法做了调整,不再将元素加入链表头,而是使用尾插法,解决死循环问题

11.八大基础数据类型

1.整数型:byte、short、int、long

2.浮点型:float、double

3.字符型:char、boolean

12.拦截器和过滤器区别?执行流程?

12.1.区别

1.拦截器可以获取IOC容器中的各个bean,而过滤器就不行

2.过滤器几乎对所有的请求都可以起作用,而拦截器只能对SpringMVC请求起作用

3.请求先被过滤器拦截,再进入拦截器,最后进入控制器

4.拦截器是Spring中的,而过滤器是tomcat中的

12.2.执行流程

1.过滤器:当一个客户端发送请求时,先被过滤器拦截处理,执行过滤器中的方法,如果通过了过滤器中的校验,则会继续处理该请求;否则,直接返回错误信息给客户端,中断后续操作

2.拦截器:在请求到达控制器前,拦截器会对请求进行处理,在拦截器中的preHandle()方法中,可以对请求进行拦截、处理等操作,如果拦截器放行,则继续进入控制器执行业务逻辑;在控制器执行完成后,会执行拦截器中的postHandle()方法和afterCompletion()方法进行处理

3.在执行顺序上,过滤器在拦截器之前执行

13.Java中有哪些集合?有什么区别?

有List、Map、Set三种

1.List集合:List是一种有序的集合,可以存储重复的元素,常见的List集合有ArrayList、LinkedList和Vector。其中,ArrayList是基于数组实现的,支持随机访问和快速添加和删除元素;LinkedList是基于链表实现的,支持快速添加和删除元素,但是不支持随机访问;Vector是线程安全的List集合,但是性能相对较差。List集合通常用于存储需要保持顺序的数据。  使用场景:存储需要保持顺序的数据,例如用户操作日志、消息队列等。   实现栈、队列等数据结构,例如使用LinkedList实现队列、栈等。   在集合中进行随机访问,例如需要通过索引获取某个元素

2.Set集合:Set是一个不允许重复元素的集合,常见的Set集合有HashSet、LinkedHashSet和TreeSet。其中Hash Set是基于哈希表实现的,元素的存储顺序不固定;LinkedHashSet是基于哈希表和链表实现的,元素的存储顺序与添加顺序一致;TreeSet是基于红黑树实现的,元素会按照自然顺序或者指定的比较器进行排序。Set集合通常用于去重和判断某个元素是否存在。    使用场景:去重操作,例如过滤重复的数据、统计数据中不同元素的个数等。判断某个元素是否存在,例如判断用户是否已经登录、统计数据中某个元素出现的次数等。在使用迭代器遍历集合时,避免重复遍历相同的元素

3.Map集合:Map是一种键值对的集合,常见的Map集合有HashMap、LinkedHashMap和TreeMap。其中,HashMap是基于哈希表实现的,键值对的存储顺序不固定;LinkedHashMap是基于哈希表和链表实现的,键值对的存储顺序与添加顺序一致;TreeMap是基于红黑树实现的,键值对会按照键的自然顺序或者指定的比较器进行排序。Map集合通常用于快速查找和存储键值对的数据。    使用场景:存储键值对的数据,例如存储用户信息、缓存数据、记录数据统计信息等。 快速查找,例如根据键快速查找对应的值、查找最近更新的数据等。存储需要排序的键值对,例如使用TreeMap实现有序映射

14.你遇到过什么难题,是怎么解决的?

方式一:遇到的比较难解决的bug,讲bug的解决思路

方式二:没有用到的技术,规定两天完成,是怎么做的?1.技术入门  2.理解功能的需求  3.将这个技术整合的项目中

15.redis数据类型有哪些?雪崩、击穿、穿透

15.1数据类型

1.String:用于缓存、计数器、分布式锁等场景

2.Hash:用于存储对象,例如用户信息、商品信息等

3.List:可以进行队列和栈的操作常用于消息队列、任务队列等场景

4.Set:可以进行集合运算,例如并集、交集、差集等操作常用于标签、好友列表等场景

5.ZSet:可以按照分数排序,也可以进行范围查询。常用于排行榜、积分系统等场景

15.2.缓存问题

1.缓存穿透:当一个查询的数据在缓存中不存在,但在数据库中也不存在时,这个查询会穿透到数据库,导致数据库负载过高 解决方法使用布隆过滤器,将不存在于数据库的数据进行过滤;对于查询结果为空的key,设置空值缓存,有效时间设置短一些

2.缓存击穿:当一个热点数据过期时,大量的请求会同时访问数据库,导致数据库负载过高。解决方法有:设置热点数据永不过期,定期更新缓存

3.缓存雪崩:当大量的缓存数据同时过期时,大量的请求会同时访问数据库,导致数据库负载过高 解决方法:给缓存的key设置随即过期时间,防止大量的缓存数据同时过期

16.Redis key过期策略?内存淘汰机制?

16.1.过期策略

基于惰性删除的策略:当访问一个已经过期的key时,Redis会立即将它删除。 基于定期删除的策略:Redis会定期地检查所有key是否过期,将过期的key删除。基于内存淘汰的策略:当Redis的内存达到一定限制时,Redis会通过一些算法,将一些冷门的、不常使用的key删除,腾出空间。

16.2.淘汰策略:

1.noeviction:当内存使用超过配置的时候会返回错误,不会驱逐任何键

2.allkeys-lru:加入键的时候,如果过限,首先通过LRU算法驱逐最久没有使用的键

3. volatile-lru:加入键的时候如果过限,首先从设置了过期时间的键集合中驱逐最久没有使用的

4. allkeys-random:加入键的时候如果过限,从所有key随机删除

5.volatile-random:加入键的时候如果过限,从过期键的集合中随机驱逐

6.volatile-ttl:从配置了过期时间的键中驱逐马上就要过期的键

7.volatile-lfu:从所有配置了过期时间的键中驱逐使用频率最少的键

8.allkeys-lfu:从所有键中驱逐使用频率最少的键  

16.3.简单记忆

1.最近最少使用(LRU)算法:淘汰最近最少使用的key。

2.最不经常使用(LFU)算法:淘汰最近使用次数最少的key。

3.随机淘汰算法:随机选择一个key进行淘汰。

4.根据ttl:生存时间    默认是不进行淘汰,直接返回异常

17.Redis持久化策略?RDB与AOF区别?底层原理?

1.RDB: 是一种快照式持久化方式,当触发某些特定的事件时,Redis会将内存中的数据保存到
磁盘上一个指定的文件中,文件后缀一般为rdb。RDB持久化的优点是备份文件小、加载速度快,适合用于大规模数据恢复。缺点是数据可能会丢失,因为数据并不是实时保存的

2.AOF: 则是将所有写操作追加到一个日志文件中,即追加式持久化方式。在AOF模式下,每个写操作都会以文本的形式记录在一个追加文件中,从而记录了所有数据的历史操作 OF持久化的优点是数据不会丢失,因为每个写操作都会记录在追加文件中,可以避免因为某个事件没有触发而导致数据丢失。缺点是备份文件大、加载速度相对RDB慢,适合用于小规模数据恢复。

18. 如何实现分布式锁?5种⽅案 

有5种⽅案
1. 基于数据库主键或唯⼀索引实现分布式锁:在数据库表中创建⼀个带有唯⼀性约束条件的字段作为分布式锁,当需要获得锁时,向数据库表中插⼊⼀条记录,如果插⼊成功则表示获得了
锁,否则表示锁已被其他线程占⽤。释放锁时,删除该记录即可。
2. 基于 Redis 实现分布式锁:Redis 的 SETNX 命令可以⽤来实现分布式锁。当需要获得锁时,调⽤ SETNX 命令尝试将⼀个字符串作为 key 插⼊到 Redis 中,如果插⼊成功则表示获得了锁,否则表示锁已被其他线程占⽤。释放锁时,删除该 key 即可。为避免死锁,需要为锁设置⼀个过期时间。
3. 基于 ZooKeeper 实现分布式锁:ZooKeeper 的顺序节点可以⽤来实现分布式锁。当需要获得锁时,创建⼀个带有顺序号的节点,并检查当前所有节点中是否⾃⼰的节点编号最⼩,如果是则表示获得了锁,否则监听⾃⼰前⾯⼀个节点的删除事件,等待锁释放。释放锁时,删除⾃⼰创建的节点即可。
4. 基于 Consul 实现分布式锁:DistributedLock是封装了分布式锁的⼯具类,通过传⼊consul主机ip和端⼝构建。
//获取锁
使⽤它的getLock获取
DistributedLock.LockContext 对象(锁上下⽂对象),
参数是锁key和过期时间(秒)。
通过调⽤锁上下⽂对象的isGetLock 返回是否可以获取锁,如果返回false 则表示已被锁住。
为了能够释放锁,我们需要将 sessionid 加⼊redis 。redisTemplate.boundValueOps(
"key" ) .set(lockContext.getSession(), Duration.ofSeconds(60));//存⼊redis
后是为了释放锁通过 lockContext.getSession() 可以获取 sessionid
//解锁
String sessionId = redisTemplate.boundValueOps("key").get();
DistributedLock lock = new DistributedLock(consulConfig.getConsulRegisterH
ost(),
 consulConfig.getConsulRegisterP
ort());
lock.releaseLock(sessionId);从redis中提取出sessionId ,
调⽤lock的releaseLock⽅法进⾏解锁。
5. 使⽤ Redisson 实现分布式锁:Redisson 是⼀个基于 Redis 的分布式 Java 对象框架,提供了分布式锁的实现。通过 Redisson 的 getLock ⽅法可以获得⼀个锁对象,调⽤锁对象的lock 和 unlock ⽅法即可获得和释放锁。Redisson ⽀持多种锁模式,包括可重⼊锁、公平锁、联锁等。

19. Spring事务隔离级别?事务失效场景?如何改变传播⾏为?

19.1. 事务隔离级别

读未提交、读已提交、可重复读和串⾏化

使⽤@Transactional注解的isolation属性来控制事务的隔离级别

19.2. 事务失效

1. 事务使⽤在静态⽅法上会失效
2. 事务使⽤在⾮public上(访问权限问题)
3. 事务中对异常进⾏捕获,出现异常后spring框架⽆法感知到异常,事务就会失效
4. ⽅法⽤ final 修饰
5. ⽅法内部调⽤
6. 未被 spring 管理
7. 多线程调⽤
8. 表不⽀持事务 

 19.3. 传播⾏为

1. REQUIRED:如果当前存在⼀个事务,则加⼊该事务;否则创建⼀个新的事务。这是默认传播⾏为。(默认)
2. SUPPORTS:如果当前存在⼀个事务,则加⼊该事务;否则以⾮事务的⽅式执⾏。
3. MANDATORY:如果当前存在⼀个事务,则加⼊该事务;否则抛出异常。
4. REQUIRES_NEW:创建⼀个新的事务,如果当前存在事务,则将当前事务挂起。
5. NOT_SUPPORTED:以⾮事务⽅式执⾏操作,如果当前存在事务,则将当前事务挂起。
6. NEVER:以⾮事务⽅式执⾏,如果当前存在事务,则抛出异常。
7. NESTED:如果当前存在事务,则在嵌套事务内执⾏。如果当前没有事务,则执⾏与REQUIRED类似的操作

19.4. 修改传播⾏为

使⽤@Transactional注解的propagation属性来控制事务的传播⾏为

20. RabbitMQ如何保证消息可靠性?

20.1. ⽣产者

开启确认机制。

20.1.1. 消息是否到达交换机

配置⽂件:publisher-confirm-type: correlated
@Test
public void testPublishConfirm() throws InterruptedException {
 String queue = "simple.queue";
 String message = "hello, spring amqp!";
 CorrelationData correlationData = new CorrelationData(UUID.randomUUID(
).toString());
 // 异步回调⽅法:确认消息是否到交换机
 correlationData.getFuture().addCallback(
 result -> {
 if (result.isAck()) {
 // 交换机收到了
 log.debug("消息发送成功, ID:{}", correlationData.getId());
 }else{
 // 交换机没收到
 log.error("消息发送失败, ID:{}, 原因{}",correlationData.getI
d(), result.getReason());
 }
 }, error -> {
 //出异常
 log.error("消息发送异常, ID:{}, 原因{}",correlationData.getId(),erro
r.getMessage());
 });
 rabbitTemplate.convertAndSend("itcast.direct", "green", message,correl
ationData);
 Thread.sleep(10000);
}

 20.1.2. 消息是否到达队列

配置⽂件: spring.rabbitmq.publisher-returns: true, spring.rabbitmq.template.mandatory: true

@Configuration
@Slf4j
public class CommonConfig implements ApplicationContextAware {
 @Resource
 private RabbitTemplate rabbitTemplate;
 @Override
 public void setApplicationContext(ApplicationContext applicationContex
t) throws BeansException {
 // 如果消息没到队列则会回调这个⽅法
 // 消息到达队列则不会调⽤这个⽅法
 rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback
() {
 @Override
 public void returnedMessage(Message message, int replyCode, St
ring replyText,
 String exchange, String routingKey
) {
 log.info("消息发送失败,应答码:{},原因:{},交换机:{},路由键:{},
消息:{}",
 replyCode, replyText, exchange, routingKey, message.to
String());
 }
 });
 }
}

20.2. MQ

20.2.1. 持久化

声明(创建)交换机(durable为true)、 队列(durable为true)、发送消息时指定持久化(MessageDeliveryMode.PERSISTENT)

 20.2.2. 不能⾃动删除

 交换机与队列都不能⾃动删除

 20.3. 消费者

 20.3.1. 确认模式

共有3种确认模式:默认使⽤auto  

spring:
 rabbitmq:
  listener:
   simple:
     acknowledge-mode: none|auto|manual
1. none:代表消费就删除队列中的消息,不管消费者成功与否
2. auto:如果消费者代码出异常,则⾃动返回nack并重⼊队列。如果消费者没出异常则返回ack。导致不断的重试
3. manual: ⼿⼯ack,消费者⽅法参数中,需要添加Channel与Message参数,通过channel.basicAck 或channel.basicNack

20.3.2. Spring重试机制

spring:
 rabbitmq:
  listener:
   simple:
    retry:
     enabled: true # 开启消费者失败重试
     initial-interval: 1000 # 初识的失败等待时⻓为1秒
     multiplier: 3 # 失败的等待时⻓倍数,下次等待时⻓ = multiplier * last-interval
     max-attempts: 4 # 最⼤重试次数
     stateless: true # true⽆状态;false有状态。如果业务中包含事务,这⾥改为false

20.3.3. 失败策略

 重试次数耗尽,如果消息依然失败,则需要有MessageRecover。有3种不同的实现

1. RejectAndDoNotRequeueRecover:重试耗尽后,直接reject,丢弃消息。默认就是这种⽅式

2. ImmediateRequeueMessageRecoverer:重试耗尽后,返回nack,消息重新⼊队

3. RepublishMessageRecoverer:重试耗尽后,将失败消息投递到指定的交换机

@Bean
public MessageRecoverer republishMessageRecoverer(RabbitTemplate rabbitTemp
late){
 return new RepublishMessageRecoverer(rabbitTemplate, "error.direct", "error");
}

20.3.4. 死信

重试次数耗尽时,使⽤第⼀种RejectAndDoNotRequeueRecover,则消息会进⼊死信,由运维人员来处理。

21. 延迟队列最后⼀秒钟跳转⽀付界⾯然后库存进⾏回滚⽤户已经⽀付的问题?

1. 统⼀下单 传⼊订单失效时间
2. 延迟队列,多10秒再处理(取消订单前,查询订单获取订单最新⽀付的状态,再决定是否更改订单状态)调⽤⽀付统⼀下单接⼝,传⼊的主要参数(订单号,商品名程,回调地址,价格,订单失效时间),我们⾃⼰的失效时间可以延迟10秒,如果没有接收的⽀付平台的消息,则调⽤查询订单API,主要参数为商户id、⼦商户id、微信⽀付订单号

22. Spring循环依赖怎么解决?

spring循环依赖是指两个或更多个Bean之间存在相互依赖的情况,例如A依赖于B,⽽B依赖于A
三种解决⽅案:
1. 通过构造函数注⼊解决循环依赖:当Bean之间有循环依赖时,使⽤构造函数注⼊可以解决问题。因为Spring容器可以通过构造函数来创建对象,并将它们注⼊到依赖项中。这种⽅式需要
确保依赖关系是通过构造函数来注⼊的,⽽不是通过Setter⽅法。
2. 通过Setter⽅法注⼊解决循环依赖:另⼀种解决⽅案是使⽤Setter⽅法注⼊。在这种情况下,Spring容器会先创建所有Bean的实例,然后通过Setter⽅法注⼊依赖项。如果循环依赖,容
器将返回⼀个代理对象,稍后再将实际对象注⼊到代理中。
3. 使⽤@Lazy注解解决循环依赖:@Lazy注解可以⽤于延迟注⼊依赖项。当⼀个Bean被标记为
延迟注⼊时,Spring容器会在需要时才创建它。这种⽅式可以解决循环依赖的问题,因为每个Bean都是在需要时才被创建的,⽽不是在启动时⼀次性创建所有Bean。

23. 继承?封装?多态?接⼝和抽象类的区别?  

 23.1. ⾯向对象编程的3⼤特性

1. 封装是指将对象的状态和⾏为包装在⼀起,对外部隐藏对象的实现细节,只暴露出⼀些公共的接⼝,通过这些接⼝访问对象的属性和⽅法。封装的好处是增强了对象的安全性和可靠性,同时也⽅便了对象的使⽤和维护。
2. 继承是指⼀个类(称为⼦类或派⽣类)可以继承另⼀个类(称为⽗类或基类)的属性和⽅法。⼦类可以通过继承获得⽗类的属性和⽅法,并且还可以在此基础上增加⾃⼰的属性和⽅法。
3. 多态是指同⼀种⾏为具有多种不同的表现形式或状态。具体来说,多态性可以通过⽅法的重载和覆盖、接⼝和抽象类等机制实现。多态性的好处是增强了程序的灵活性和可扩展性,同时也⽅便了代码的复⽤和维护。

 23.2. 接⼝和抽象类区别

1. 接⼝和抽象类都是⽤来实现多态性的机制。
2. 接⼝是⼀种纯抽象的类型,只包含⽅法声明⽽没有实现。
3. 通过实现接⼝,⼀个类可以具备接⼝中声明的⽅法,从⽽实现多态性。
4. 抽象类是⼀种包含抽象⽅法的类,抽象⽅法只有声明⽽没有实现。
5. 通过继承抽象类,⼦类必须实现⽗类的抽象⽅法,从⽽实现多态性。
6. 接⼝和抽象类的区别在于:接⼝只包含⽅法的声明⽽没有实现,⽽抽象类包含抽象⽅法和具体⽅法;⼀个类可以实现多个接⼝,但只能继承⼀个抽象类。

24. 单例模式?(其它设计模式)

24.1. 单例模式

懒汉式,饿汉式、双键锁、内部类、枚举

24.2. 四⼤原则  

构造⽅法私有、以静态⽅法返回或枚举返回实例、确保只有⼀个实例、确保反序列时不会重新构建对象 

24.3. 为什么使⽤单例模式 

1. 节省资源:在某些情况下,创建对象的开销⽐较⼤,如果每次都创建新的对象,会消耗⼤量的
系统资源。使⽤单例模式可以在应⽤程序中只创建⼀个实例,避免了重复创建对象的开销,从
⽽节省了系统资源。
2. 简化代码:如果某个类需要在应⽤程序中频繁使⽤,那么在每个地⽅都创建该类的实例会导致
代码变得复杂。使⽤单例模式可以将类的实例化过程封装在⼀个单独的类中,并提供⼀个全局
访问点,从⽽简化了代码结构。
3. 维护全局状态:在某些情况下,需要在应⽤程序中维护⼀个全局状态,例如⽇志对象或配置⽂
件对象。使⽤单例模式可以保证该类只有⼀个实例,从⽽保证了全局状态的⼀致性。
4. 保证线程安全:在多线程环境下,如果多个线程同时访问⼀个对象,并且该对象没有进⾏任何
线程安全的处理,那么可能会出现数据竞争和其他线程安全问题。使⽤单例模式可以保证该类
只有⼀个实例,并且提供了⼀个全局访问点,从⽽避免了线程安全问题。
5. 使⽤单例主要保证:
懒加载
线程安全
可以使⽤反射破坏

25. stream的常⽤API

1. filter:过滤集合中不符合条件的元素。
2. map:将集合中的每个元素映射为另⼀个元素。
3. flatMap:将集合中的每个元素映射为多个元素,并将结果合并成⼀个流。
4. sorted:对集合进⾏排序。
5. distinct:去除集合中的重复元素。
6. limit:限制集合中元素的数量。
7. skip:跳过集合中的前⼏个元素。
8. reduce:将集合中的元素逐个聚合成⼀个结果。
9. collect:将集合中的元素收集到⼀个集合中。
10. forEach:对集合中的每个元素执⾏⼀个操作。
11. count:计算集合中元素的数量。
12. anyMatch:判断集合中是否存在满⾜条件的元素。
13. allMatch:判断集合中所有元素是否都满⾜条件。
14. noneMatch:判断集合中是否没有元素满⾜条件。
15. findFirst:查找集合中第⼀个符合条件的元素。
16. findAny:查找集合中任意⼀个符合条件的元素。

26. 死锁的解决⽅法

死锁是指两个或多个进程或线程⽆限期地等待彼此释放所占⽤的资源
避免循环依赖:死锁通常是由于多个线程对资源的互相依赖导致的。因此,可以尝试在设计时避免循环依赖,从⽽避免死锁的发⽣。
1. 统⼀获取锁的顺序:当多个线程需要获取多个锁时,可以规定所有线程必须按照同样的顺序获取锁,从⽽避免不同线程之间的锁竞争导致的死锁。
2. 设置超时时间:如果⼀个线程在⼀定时间内⽆法获取所需的锁,就会⾃动放弃该锁并退出,从⽽避免死锁的发⽣。这个⽅法可以通过设置锁的超时时间来实现。
3. 使⽤资源分配图:资源分配图可以帮助我们理解资源之间的依赖关系,并且可以通过检查图中的环路来检测是否有死锁的情况发⽣。
4. 引⼊抢占机制:抢占机制可以中断正在运⾏的线程并强制释放它所占⽤的资源,从⽽避免死锁的发⽣。但是需要注意,这种⽅法可能会导致程序执⾏的不确定性。

27. Spring中有哪些设计模式?你的项⽬中有了什么设计模式?

27.1. Spring中的设计模式

设计模式
在哪⾥⽤到
描述
⼯⼚模式
BeanFactory和 ApplicationContext
单例模式
Spring容器中的Bean
代理模式
Spring AOP
基于反射实现动态代码
模板⽅法模式
Spring中以Template结尾的类
基于反射实现动态代码
观察者模式
Spring事件驱动模型(发布订阅事件)
适配器模式
AOP中的AdvisorAdapter
MVC中的HandlerAdapter
装饰器模式
Spring中的含有Wrapper和Decorator的类
策略模式
资源访问Resource接⼝
链式模式
springMVC中的拦截器

28. 如何防⽌重复提交

使⽤拦截器|过滤器+Redis来解决
1. 获取请求对象Request,获取请求路径与请求参数,获取登录⽤户id
2. 以请求路径+参数+登录⽤户id为key,使⽤字符串的incr命令,⾃增⻓加1,根据操作后的返回
值判断
3. 如果返回1,则设置这个key的有效时间为15s(不允许15s内重复提交)
4. 如果返回值>1,则代表之前⽤户已经提交过了,则拦截⽤户请求
使⽤字符串的incr,由于单次incr的操作具有原⼦性,每次操作后的值⼀定会不⼀样,则是⾃增长的,就可以通过是否>1来判断它增⻓了⼏次,也就是提交了⼏次。
expire超时失效:如果超过15S,key会⾃动消失,那时⽤户⼜可以提交请求了。

29. 如何解决消息重复消费问题

消息重复消费⽆可避免,它是⼀种消息可靠性的保证。因此当消息重复消费时,只要做幂等处理即可
1. 发送时,给每个消息⼀个唯⼀标识,当要开始处理消息时,先执⾏保存到redis或数据库中。redis中setnx如果数据已存在会返回0,不存在返回1。数据库则使⽤唯⼀索引,如果数据存在表中,则插⼊数据时报错
2. 给要更新的数据,设置更新的条件。如:update set num=num-1 where num>0
3. 给1个队列的1个消费者消费,消费前先查询判断是否处理过,如是处理过则不处理(status!=0)。处理后⻢上更新状态为其它值。即只处理某种状态的数据,其它不处理

30. 如何优化接⼝

则检查接⼝经过的每个环节所花费的时⻓,看哪个环节花的时间最⻓。
● 如果是业务代码:则看是否能使⽤异步处理(异步线程,或异步mq)
● 如果是数据库响应过⻓:就把sql语句拿来做 执⾏计划分析。采⽤sql优化⽅案进⾏优化。
           ○ sql优化:索引优化:是否使⽤了索引列,索引是否正确使⽤。如果没索引,则创建相应索引,优先使复合索引(索引覆盖,索引下推,减少回表可能)
           ○ 分表法:
                       ■ ⽔平拆:如果表中数据近千万级别,则拆⽤分表
                       ■ 垂直拆:如果表中列过多,数据量不⼤,但查询慢,则根据字段使⽤频率拆分

  • 30
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值