1.SpringCloud常用组件有哪些?
最常用的5个组件是Nacos、Gateway网关、Feign远程调用、Sentinel熔断降级和Ribbon负载均衡其中Nacos是eureka的升级版,是阿里巴巴开发的,不仅仅可以作为注册中心,还可以作为配置中心
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容器将调用指定的方法
【注意】:
单例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响应用户
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种⽅案
//获取锁
使⽤它的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⽅法进⾏解锁。
19. Spring事务隔离级别?事务失效场景?如何改变传播⾏为?
19.1. 事务隔离级别
读未提交、读已提交、可重复读和串⾏化
19.2. 事务失效
19.3. 传播⾏为
19.4. 修改传播⾏为
20. RabbitMQ如何保证消息可靠性?
20.1. ⽣产者
20.1.1. 消息是否到达交换机
@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
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. 死信
21. 延迟队列最后⼀秒钟跳转⽀付界⾯然后库存进⾏回滚⽤户已经⽀付的问题?
22. Spring循环依赖怎么解决?
23. 继承?封装?多态?接⼝和抽象类的区别?
23.1. ⾯向对象编程的3⼤特性
23.2. 接⼝和抽象类区别
24. 单例模式?(其它设计模式)
24.1. 单例模式
24.2. 四⼤原则
构造⽅法私有、以静态⽅法返回或枚举返回实例、确保只有⼀个实例、确保反序列时不会重新构建对象
24.3. 为什么使⽤单例模式
25. stream的常⽤API
26. 死锁的解决⽅法
27. Spring中有哪些设计模式?你的项⽬中有了什么设计模式?
27.1. Spring中的设计模式
设计模式
|
在哪⾥⽤到
|
描述
|
⼯⼚模式
|
BeanFactory和 ApplicationContext
| |
单例模式
|
Spring容器中的Bean
| |
代理模式
|
Spring AOP
|
基于反射实现动态代码
|
模板⽅法模式
|
Spring中以Template结尾的类
|
基于反射实现动态代码
|
观察者模式
|
Spring事件驱动模型(发布订阅事件)
| |
适配器模式
|
AOP中的AdvisorAdapter
MVC中的HandlerAdapter
| |
装饰器模式
|
Spring中的含有Wrapper和Decorator的类
| |
策略模式
|
资源访问Resource接⼝
| |
链式模式
|
springMVC中的拦截器
|