1.谈谈你对Spring的理解
Spring框架为Java应用程序提供了全面的解决方案,帮助开发者简化开发流程、提高代码质量和可维护性。IOC和AOP是Spring框架中的两个核心概念。 IOC: IOC也叫控制反转,是Spring用来解耦的一种设计思想,它把对象的控制权从程序员手中反转到Spring手中。在没有IOC之前,对象都是程序员在类中主动创建,需要哪个创建哪个;有了IOC之后,对象交给Spring容器创建和管理,哪个对象中需要其它对象属性,Spring会自动完成依赖注入。 总之,IOC可以将对象的创建和对象之间依赖关系的维护交给Spring自动完成。 AOP: AOP又叫面向切面编程,核心思想是将与业务无关,却被业务模块所共同调用的逻辑(如事务处理、日志管理)封装起来,再动态插入到业务模块中。 使用AOP可以减少系统重复代码,降低模块间的耦合度,有利于扩展和维护,Spring AOP是基于动态代理的,底层同时支持JDK和CGLIB的代理方式,并且会根据被代理类是否有接口自动选择最合适的代理方式。 我们在开发中用到AOP的场景有:事务管理、日志、性能监视、安全检查。
2.Spring的常用注解有哪些
常用的Spring注解主要分为下面几大类:
用于创建对象的:@Component、@Controller、@Service、@Repository 标注在自己开发的类上,Spring会用注解标注的类创建出对象,然后放入容器 用于依赖注入的:@Autowired 标注在属性或属性对应的set方法上,Spring会根据被标注属性的类型自动对属性进行赋值 和@Autowired一起使用,用于依赖注入的:@Qualifier 在同一类型的bean有多个的情况下Spring会根据name进行选择注入 用于配置类的:@Configuration、@Bean 标注在配置类中,用于声明配置类和向Spring容器中放入一些配置有关的对象 用于声明注解扫描的@ComponentScan,声明Bean的作用域的@Scope,用于切面编程的@Around,@Pointcut等等
3.Spring中的bean线程安全吗
Spring中的Bean主要分为单例和多例。
● 每次获取多例对象都会创建新实例,线程之间不存在共享Bean问题,也就不存在线程安全问题。 ● 单例对象是所有线程共享一个实例,就可能会存在线程安全问题。单例对象又分为无状态和有状态。
无状态Bean是只对对象的成员变量进行查询操作,不修改成员变量的值,所以不存在线程安全问题。 有状态Bean需要对Bean中的成员变量进行数据更新操作,所以就可能存在线程安全问题。 所以在Spring中,只有有状态的单例Bean才会存在线程安全问题。 处理有状态单例Bean的线程安全问题,有以下两种方法: 1、将Bean的作用域由单例改为多例 2、将需要的可变成员变量保存在ThreadLocal中, ThreadLocal本身具备线程隔离特性,这相当于为每个线程提供了一个独立的变量副本,每个线程只需操作自己的线程副本变量,从而解决线程安全问题。
4.Spring中的设计模式有哪些
Spring中常用的设计模式有:
工厂模式:Spring 使用工厂模式通过 BeanFactory 和 ApplicationContext 创建 Bean 对象 单例模式: Spring 中的 Bean 默认都是单例的 代理模式: Spring 的 AOP 功能用到了 JDK 的动态代理和 CGLIB 字节码生成技术 模板方法:用来解决代码重复的问题。比如 RestTemplate、JDBCTemplate、JpaTemplate 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式 观察者模式: Spring 事件驱动模型是观察者模式很经典的一个应用。定义对象间一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知被制动更新,如 Spring 中 listener 的实现 ApplicationListener。
5.Spring事务传播行为有几种
事务传播行为是为了解决业务层方法之间互相调用的事务问题。 当事务方法被另一事务方法调用时,必须指定事务应该如何传播。 方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。
Spring支持7种事务传播行为:
必须事务:若当前存在事务,就加入该事务;若当前没有事务,就创建一个新的事务 必须新事务:创建一个新事务,若当前存在事务,就把当前事务挂起 强制事务:若当前存在事务,就加入该事务;若当前没有事务,就抛出异常 支持事务:若当前存在事务,就加入该事务;若当前没有事务,就以非事务的方式继续运行 不支持事务:以非事务方式运行,若当前存在事务,则把当前事务挂起 强制无事务:以非事务方式运行,若当前存在事务,则抛出异常 嵌套事务:若当前存在事务,就创建一个当前事务的嵌套事务来运行;若当前没有事务,就创建一个事务
嵌套事务是已存在事务的一个子事务,嵌套事务开始执行时,将取得一个保存点 若这个嵌套事务失败,将回滚到此保存点 嵌套事务是外部事务的一部分,只有外部事务结束后它才会被提交
6.Spring是怎么解决循环依赖的
IOC容器解决循环依赖,需要用到几个重要成员:
singletonObjects:一级缓存,存放初始化好的 Bean 的集合,从此集合中取出来的 Bean 可以立马返回。
earlySingletonObjects:二级缓存,存放创建好但没有初始化属性的 Bean 的集合,它用来解决循环依赖。
singletonFactories:三级缓存,存放单例 Bean 工厂的集合。
singletonsCurrentlyInCreation:存放正在被创建的 Bean 的集合。
IOC 容器解决循环依赖的思路:
初始化 Bean 之前,将这个 BeanName 放入三级缓存 创建 Bean ,将准备创建的 Bean 放入 singletonsCurrentlyInCreation (正在创建的 Bean) createNewInstance 方法执行完后执行 addSingletonFactory,将这个实例化但没有属性赋值的 Bean 放入二级缓存,并从三级缓存中移除 进行属性赋值和自动注入时,引发关联创建 关联创建时,检查“正在被创建的 Bean”中是否有即将注入的 Bean。如果有,检查二级缓存中是否有当前创建好但没有赋值初始化的 Bean。如果没有,检查三级缓存中是否有正在创建中的 Bean。至此一般会有,将这个 Bean 放入二级缓存,并从三级缓存中移除 之后 Bean 被成功注入,最后执行 addSingleton,将这个完全创建好的 Bean 放入一级缓存,从二级缓存和三级缓存移除,并记录已经创建了的单例 Bean
7.SpringBoot自动配置原理
Springboot自动装配是基于注解编程和约定优于配置的思想来设计的。
自动装配就是自动把其他组件中的Bean装载到IOC容器中,不需要开发人员再去配置文件中添加大量配置。
只需在SpringBoot的启动类上添加@SpringBootApplication的注解,就能开启自动装配。
@SpringBootApplication开启自动装配,底层最重要的一部分是@EnableAutoConfiguration这个注解来实现的,它的作用是:
读取所有包中两个指定配置文件中的所有自动配置类(xxxxAutoConfiguration) 这些值必须声明为Spring的配置类,也就是在类中需要向Spring容器放入对象 为了防止非当前所需的组件进入到容器,配置类中需要使用@Conditional注解来声明配置成立的必要条件
8.SpringBoot配置文件类型以及加载顺序
SpringBoot支持的配置文件类型有properties、yaml 、yml等。
bootstrap.yml和application.yml是SpringBoot支持的两个核心配置文件,区别在于
boostrap比applicaton优先加载,在应用程序上下文的引导阶段生效,且里面的属性不能被覆盖。一般在SpringCloud Config或者Nacos中会用到它。
application.yml用于SpringBoot项目的自动化配置,我们会把自己项目的业务配置项写在这里面。
9.SpringCloud的常用组件有哪些
SpringCloud 是一系列框架的有序集合。它利用 SpringBoot 的开发便利性巧妙地简化了分布式系统基础设施的开发。
如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用 SpringBoot 的开发风格做到一键启动和部署。
早期我们一般认为的Spring Cloud五大组件是
● Eureka : 注册中心 ● Ribbon : 负载均衡 ● Feign : 远程调用 ● Hystrix : 服务熔断 ● Zuul/Gateway : 网关
随着SpringCloudAlibba在国内兴起 , 我们项目中使用了一些阿里巴巴的组件
● 注册中心/配置中心 Nacos ● 负载均衡 Ribbon ● 服务调用 Feign ● 服务保护 sentinel ● 服务网关 Gateway
10.说一说Feign的工作原理
Feign是SpringCloud技术栈中用于远程调用的一个HTTP客户端,主要作用是将远程服务调用和本地方法调用格式统一。
Feign的工作步骤如下:
首先在SpringBoot的启动类上添加@EnableFeignClients 注解,开启对Feign的支持 当程序启动时,会扫描所有标有@FeignClient的注解的类,并且将这些信息注入Spring IOC 容器中 当定义的 Feign 接口中的方法被调用时,通过JDK的代理方式,来生成具体的 RequestTemplate RequestTemplate对象封装了 HTTP 请求需要的全部信息,如请求参数名,请求方法等信息 然后RequestTemplate生成 Request,并将Request交给Client去处理,这里的 Client 可以是 JDK 原生的 URLConnection、Apache 的 HttpClient等 最后Client被封装到LoadBalanceClient类,这个类结合Ribbon负载均衡发起服务之间的调用
11.说一说Nacos的工作原理
Nacos是SpringCloudAlibaba技术栈的一项技术,在项目中主要用作服务注册中心和服务配置中心。
● Nacos作为服务注册中心具备下面这些能力
1.服务注册:服务提供者会将自己的地址信息注册到Nacos中,在nacos中形成一张服务清单
2.服务发现:服务消费者会从Nacos中查询服务提供者的信息,并且缓存到本地,并且每隔30s更新一次
3.当服务提供者的地址发生变化之后,Nacos也会主动推送最新的地址信息给消费者
4.服务续约:服务提供者会间隔一定时间就给Nacos发送心跳,表明自己在线
5.服务剔除:当nacos一段时间内接收不到服务微服务的续约请求时或者收到微服务的下线请求时,就会将服务地址从服务清单中删除
● Nacos作为服务配置中心的原理
Nacos允许微服务将一些经常改动的配置项保存到Nacos中,然后在本地的bootstrap.yml中指定远程配置的位置信息 Nacos的配置发生变化之后,会主动推送给微服务,微服务进行热更新 Nacos还支持使用多环境、命名空间的方式实现多套配置文件的共存
12.说一说Ribbon的工作原理和常用负载均衡策略
我们项目中使用的是Feign来进行远程微服务的调用,Feign底层集成了Ribbon,大体使用流程如下:
当请求发出的时候,会被Ribbon的负载均衡拦截器所拦截 Ribbon会提取请求路径中微服务的名称,然后去服务治理中心中查找微服务的对应的服务地址 Ribbon会使用配置的负载均衡策略从众多地址中选择一个,进行调用 Ribbon官方提供了7种负载均衡策略 轮询策略:按照一定的顺序依次调用服务实例 权重策略:根据每个服务提供者的响应时间分配一个权重,响应时间越长,权重越小,被选中的可能性也就越低。它的实现原理是,刚开始使用轮询策略并开启一个计时器,每一段时间收集一次所有服务提供者的平均响应时间,然后再给每个服务提供者附上一个权重,权重越高被选中的概率也越大。 随机策略:从服务提供者的列表中随机选择一个服务实例 最小连接数策略:遍历服务提供者列表,选取连接数最小的一个服务实例。如果有相同的最小连接数,那么会调用轮询策略进行选取。 重试策略:按照轮询策略来获取服务,如果获取的服务实例为 null 或已经失效,则在指定的时间之内不断地进行重试来获取服务,如果超过指定时间依然没获取到服务实例则返回 null。 可用性敏感策略:先过滤掉非健康的服务实例,然后再选择连接数较小的服务实例 区域敏感策略:根据服务所在区域(zone)的性能和服务的可用性来选择服务实例,在没有区域的环境下,该策略和轮询策略类似。
13.Hystrix的作用是什么?断路器的工作原理
在微服务架构中,我们会拆分出很多的服务,服务之间存在复杂的调用关系,那么这些服务一旦会出现失败的情况,就会导致服务雪崩 Hystrix就是来防止服务雪崩的工具,它具有服务降级,服务熔断,服务隔离,监控等一些防止雪崩的技术。 它有四种防雪崩手段:
服务隔离:隔离服务之间相互影响 服务监控:在服务发生调用时,会将每秒请求数、成功请求数等运行指标记录下来 服务熔断:接口调用失败就会进入调用接口提前定义好的一个熔断的方法,返回错误信息 服务降级:接口调用失败就调用本地的方法返回一个空 断路器状态机包括三个状态: 1、 closed:关闭状态,断路器放行所有请求,并开始统计异常比例、慢请求比例。超过阈值则切换到open状态 2、open:打开状态,服务调用被熔断,访问被熔断服务的请求会被拒绝,快速失败,直接走降级逻辑。Open状态5秒后(默认值)会进入 3、half-open状态half-open:半开状态,放行一次请求,根据执行结果来判断接下来的操作 ○ 请求成功:则切换到closed状态 ○ 请求失败:则切换到open状态
14.解释一下服务雪崩、服务熔断、服务降级
服务熔断:当下游服务因访问压力过大而响应变慢或失败,为保证整个服务的可用性,上游服务会暂时切断对下游服务的调用。这种牺牲局部,保全整体的措施就叫做熔断。
服务降级:就是为服务提供一个托底方案,一旦服务无法正常调用,就使用托底方案。
服务雪崩:服务雪崩是指由于某个服务故障或不可用,导致其他依赖于该服务的服务也不可用的情况。
15.拦截器和过滤器的区别是什么
拦截器和过滤器都可以实现请求的拦截处理,不同点有下面几个:
● 技术栈所属不同:过滤器属于JavaWeb技术,依赖Servlet容器;而拦截器是属于Spring的技术 ● 实现原理不同:拦截器是基于Java的反射机制,而过滤器是基于函数回调 ● 拦截范围不同:过滤器可以拦截所有请求,而拦截器主要是针对发往controller请求 ● 拦截位置不同:过滤器在前端控制器前拦截行,而拦截器在前端控制器后拦截
16.Redis的数据类型和使用场景有哪些
Redis最常见的数据类型有5种,分别是String、List、Hash、Set、ZSet,下面给您详细介绍一下:
String:简单的 key-value 类型,最大能存储512MB数据。场景:计数、缓存文章标题、微博内容等
List:底层是链表,特点是:增删容易,随机访问困难。场景:发布与订阅或者说消息队列
Hash:类似于Java中的HashMap,适合存储对象。场景:系统中对象数据的存储
Set:是一种无序集合,可以方便的求交、并、差集。 场景:共同关注、共同粉丝、共同喜好等功能
ZSet:相比于set来讲,多了1个权重参数 score,元素会按照score进行排序。场景:各种排行榜,弹幕消息
17.Redis为什么这么快
Redis之所以运行速度快主要是因为:
纯内存操作:Redis的绝大部分请求是纯内存操作,非常快 单线程:Redis的核心部分是单线程运行的,避免了不必要的上下文切换,也不存在线程切换导致的 CPU消耗 使用 I/O 多路复用模型和非阻塞 IO
18.Redis的集群有哪些
Redis中提供的集群主要有三种,分别是主从、哨兵和分片集群
主从集群主要用来解决Redis的并发问题,一般是一个主节点负责数据写入,多个从节点负责数据读取,主节点的数据实时同步给从节点 哨兵集群主要用来解决Redis的高可用问题,哨兵会监控集群中节点的状态,并在主节点出现问题时重新选主 分片集群主要用来解决Redis的海量数据存储问题,它要求有多个主节点,写入的数据会经过计算落到其中一个上
在计算过程中Redis引入了哈希槽的概念,Redis集群有16384个哈希槽,每个 key通过CRC16校验后对16384取模来决定放置哪个槽
分片集群的每个节点负责一部分 hash 槽,就可以计算出一个key会出现在哪个节点上了,查询的时候也是同时的方式来定位
19.Redis的持久化方式有哪些
Redis是基于内存的数据存储,为了保证数据安全,需要将内存中的数据备份到磁盘上,官方提供了两种数据持久化的方式,分别是RDB和AOF。
RDB采用定期更新的方式,它会定期把Redis中的数据生成快照同步到磁盘上,磁盘上保存的是Redis的内存快照。 优点:数据文件相比于AOF较小,数据恢复速度较快 缺点:耗时并且存在丢失数据的风险
AOF是将Redis所执行过的所有写指令都记录到磁盘上,在下次Redis重启时,只需要将指令重写一遍就可以了 优点:数据丢失的风险大大降低了 缺点数据文件的大小相比于rdb较大,而且数据恢复的时候速度较慢
我们公司是同时开启RDB和AOF 持久化机制的,这样的好处是:
在Redis重启时先使用AOF日志进行恢复,然后再使用RDB快照进行备份 而且将AOF的appendfsync 参数为 everysec,保证每秒将AOF缓冲区中的写操作同步到 AOF 文件中,提高数据的持久化能力 定期进行RDB快照的备份,以便在需要时进行全量数据恢复
这样配置可以充分利用RDB和AOF两种持久化机制的优势,提高数据的可靠性和恢复能力。
20.了解过Redis的事务吗
Redis中本身没有事务的概念,但它有几个命令组合起来能实现类似于事务的效果。也就是说,Redis事务的本质是一组命令的集合。 这里用到的命令主要有5个,分别是:
MULTI:组装一个事务 EXEC:执行一个事物 DISCARD:取消一个事务 WATCH:用来监视一些key,一旦这些key在事务执行之前被改变,就取消事务的执行 UNWATCH:取消 WATCH 命令对所有key的监视 总结说:Redis事务就是一次性、顺序性、排他性地执行一个队列中的一系列命令。Reids中,单条命令式原子性执行的,但事务不保证原子性,且没有回滚。
21.Redis实现分布式锁的原理是什么
Redis实现分布式锁主要利用Redis的setnx
命令。setnx
是SET if not exists(如果不存在,就SET)的简写。 上面这几个命令就是最基本的用来完成分布式锁的命令。
加锁:使用setnx key value
命令,若key不存在,设置value(加锁成功)。若已经存在lock(也就是有客户端持有锁了),设置失败(加锁失败)。
解锁:使用del
命令,通过删除键值释放锁。释放锁之后,其他客户端可以通过setnx
命令进行加锁。
22.如何保存Redis数据与DB数据的一致性
保证Redis和MySQL数据一致性的常见方案有三种:
同步双写:程序更新完MySQL后立即同步更新redis 异步监听:通过Canal(通道)监听MySQL的binlog日志,然后再通过程序将变化的数据更新到 Redis MQ异步:程序更新完MySQL后,投递一条消息到MQ中,然后再通过一个程序监听MQ,获取到消息,然后更新Redis
23.Redis过期删除策略和内存淘汰策略有哪些
过期删除策略: Redis的过期删除策略 是 当Redis中的key过期后何时进行删除的处理方案,常用的删除策略有两个:
● 惰性删除:只在取出 key 的时候才对数据进行过期检查,过期了就删除 ● 定期删除:每隔一段时间抽取一批 key,执行删除过期 key 操作
两者相比,定期删除对内存更加友好,惰性删除对 CPU 更加友好。
Redis 采用的是定期删除+惰性/懒汉式删除。
内存淘汰策略: Redis的内存淘汰策略 是 当Redis的内存已存满,又有新的数据需要保存时的处理方案,官方提供的淘汰策略有八种:
no-eviction:禁止驱逐数据,内存不足以容纳新写入数据时,新写入操作会报错。 volatile-lru:从已设置过期时间的数据集中选择最近最少使用的数据淘汰 volatile-ttl:从已设置过期时间的数据集中选择将要过期的数据淘汰 volatile-random:从已设置过期时间的数据集中任意选择数据淘汰 volatile-lfu:从已设置过期时间的数据集中挑选最不经常使用的数据淘汰 allkeys-lru:在所有的数据集中选择最近最少使用的数据淘汰 allkeys-random:在所有的数据集中任意选择数据淘汰 allkeys-lfu:在所有的数据集中选择最不经常使用的数据淘汰
24.Redis常见的缓存问题和对应的解决方案
缓存穿透解决方案: 在我们的项目中会将缓存放到数据库前面,查询的时候先查缓存,缓存有了就不用再去查数据库了,这样可以大大减轻数据库的访问压力 而缓存穿透指的是请求一直在查询一个数据库中不存在的数据,这样缓存中没有,请求就会到达数据库,而数据库也没有,也就没法缓存 所以每一次请求都会直接到数据库中查询,这就极有可能导致数据库被压垮 常用的解决方案有两个:
查询返回的数据为空,仍把这个空结果进行缓存,但过期时间尽量设置稍短一些 使用布隆过滤器:将所有可能存在的数据哈希到一个足够大的 bitmap 中,一个一定存在的数据会被这个 bitmap 拦截掉,从而避免了对DB的查询 缓存击穿解决方案: 在我们的项目中会将缓存放到数据库前面,查询的时候先查缓存,缓存有了就不用再去查数据库了,这样可以大大减轻数据库的访问压力 缓存击穿指的是对于一个设置了过期时间的key,在其缓存失效的瞬间,有大量的请求访问这个它,这些请求在缓存找不到就会直接到数据,导致数据库被压垮 常用的解决方案有两个: 使用互斥锁:当缓存失效时,不立即去数据库查询,而是先去获取一把全局锁,那个线程获取到了,就去数据库查询,获取不到的就等待重试查询缓存 修改设置key有效期的逻辑,大体如下: 在设置key的时候,不给它设置过期时间,而是单独设置一个过期时间字段一块存入缓存中 当查询的时候,从redis取出数据后判断时间是否过期,如果过期则开通另外一个线程进行数据同步,当前线程正常返回数据
两种方案对比: 缓存雪崩解决方案: 在我们的项目中会将缓存放到数据库前面,查询的时候先查缓存,缓存有了就不用再去查数据库了,这样可以大大减轻数据库的访问压力 缓存雪崩指的是大量的key在某一时刻同时失效,这样大量的请求全部转发到DB,DB 瞬时压力过重雪崩 解决方案也很简单,就是在设置key的过期时间的时候,尽量加一些随机值,这样缓存过期时间的重复率就会降低。
25.MySQL中char和varchar的区别
char和varchar是MySQL中的字符串类型,区别在于下面几方面:
最大长度:char最大长度是255字符,varchar最大长度是65535个字节 占用长度:char是定长的,不足的部分用隐藏空格填充,varchar是不定长的 空间使用:char会浪费空间,varchar会更加节省空间查找效率:char 查找效率会很高,varchar查找效率会更低 因此我们如果存储固定长度的列,例如身份证号、手机号建议使用char 其它不定长度的建议使用varchar,使用varchar的时候也要尽量让声明长度贴近实际长度
26.事务的四大特性是什么
事务的四大特性指的是原子性、一致性、隔离性、持久性 ● 原子性:事务是最小的执行单位,不允许分割,同一个事务中的所有命令要么全部执行,要么全部不执行 ● 一致性:事务执行前后,数据的状态要保持一致,例如转账业务中,无论事务是否成功,转账者和收款人的总额应该是不变的 ● 隔离性:并发访问数据库时,一个事务不被其他事务所干扰,各并发事务是独立执行的 ● 持久性:一个事务一旦提交,对数据库的改变应该是永久的,即使系统发生故障也不能丢失
27.并发事务会带哪些问题
并发事务下,可能会产生如下的问题: ● 脏读:一个事务读取到了另外一个事务没有提交的数据 ● 不可重复读:一个事务读取到了另外一个事务修改的数据 ● 幻读(虚读):一个事务读取到了另外一个事务新增的数据
28.事务隔离级别有哪些
事务隔离级别是用来解决并发事务问题的方案,不同的隔离级别可以解决的事务问题不一样 ● 读未提交: 允许读取尚未提交的数据,可能会导致脏读、幻读或不可重复读 ● 读已提交: 允许读取并发事务已提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生 ● 可重复读: 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生 ● 可串行化: 所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,该级别可以防止脏读、不可重复读以及幻读。 上面的这些事务隔离级别效率依次降低,安全性依次升高,如果不单独设置,MySQL默认的隔离级别是可重复读
29.索引的创建原则
索引可以大幅度提高查询的效率,但不是所有的字段都要加,也不是加的越多越好,因为索引会占据磁盘空间,也会影响增删改的效率 我们在建立索引的时候应该遵循下面这些原则:
主键字段、外键字段应该添加索引 经常作为查询条件、排序条件或分组条件的字段需要建立索引 经常使用聚合函数进行统计的列可以建立索引 经常使用多个条件查询时建议使用组合索引代替多个单列索引 除此之外,下面这些情况,不应该建立索引 数据量小的表不建议添加索引 数据类型的字段是TEXT、BLOB、BIT等数据类型的字段不建议建索引 不要在区分度低的字段建立索引,比如性别字段、年龄字段等
30.索引失效的情况
索引失效指的是虽然在查询的列上添加了索引,但是某些情况下,查询的时候依旧没有用到索引,常见的情况有
使用like关键字时,模糊匹配使用%开头将导致索引失效 使用连接条件时,如果条件中存在没有索引的列会导致索引失效 在索引列上进行计算、函数运算、类型转换将导致索引失效 使用 !=、not in、is null、is not null时会导致索引失效 使用联合索引时,没有遵循最左匹配原则会导致索引失效
31.如何知道索引是否失效
MySQL中自带了一个关键字叫explain,它可以加在一个sql的前面来查看这条sql的执行计划 在执行计划中,我们主要观察两列的结果,一列是type,一列是extra 第一个type是重要的列,显示本次查询使用了何种类型,常见的值从坏到好依次为:all、index、range、ref、eq_ref 、const、system ● all表示全表扫描数据文件返回符合要求的记录 ● index表示全表扫描索引文件返回符合要求的记录 ● range表示检索指定范围的行,常见于使用>,<,between,in,like等运算符的查询中 ● ref表示两表查询时,驱动表可能返回多行数据,也就是查询条件在主表中是加了一个普通索引 ● eq_ref表示两表查询时,驱动表只返回一行数据,也就是查询条件在主表中是唯一的 ● const表示索引一次就能得到结果,一般是使用唯一索引或者主键作为查询条件 ● system表示表中仅有一行数据,很少见到 我们在优化的时候尽量优化到range级别以上 除了type之外我们需要关注一下extra列,它表示执行状态说明 ● 要保证此列不要出现using filesort、using temporary等使用临时表或外部文件的情况 ● 如果出现using index最好了,它表示列数据仅仅使用了索引中的信息而没有回表查询
32.常见的数据库存储引擎有哪些
MyISAM和InnoDB是目前MySQL中最为流行的两种存储引擎,它们的区别有这几方面:
MyISAM不支持事务,每次操作都是原子的;InnoDB支持事务,支持事务的四种隔离级别 MyISAM不支持外键,InnoDB支持外键 MyISAM仅仅支持表级锁,即每次操作是对整个表加锁;InnoDB支持行级锁,因此可以支持写并发 MyISAM属于非聚集性索引,它的数据和索引不在同一个文件中;InnoDB属于聚集性索引,它的数据和索引在同一个文件中 MyISAM中主键和非主键索引的数据部分都是存储的文件的指针;InnoDB主键索引的数据部分存储的是表记录,非主键索引的数据部分存储的是主键值
33.索引的数据结构是什么
在MySQL中索引使用的数据结构是B+Tree,B+树是基于B树的变种,它具有B树的平衡性,而且树的高度更低 ● B+树非叶子节点不存数据只存索引,因此其内部节点相对B树更小,树的高度更小,查询产生的I/O更少 ● B+树查询效率更高,B+树使用双向链表串连所有叶子节点,区间查询效率更高 ● B+树查询效率更稳定,B+树每次都必须查询到叶子节点才能找到数据,而B树查询的数据可能不在叶子节点,也可能在,这样就会造成查询的效率的不稳定
34.数据库中的锁有哪些
MySQL中的锁从不同维度可以分为不同的种类
从锁的粒度上可以分为表锁和行锁 表锁指的是会锁定修改数据所在的整个表,开销小,加锁快,锁定粒度大,发生锁冲突概率高 行锁指的是会锁定修改数据所在的行记录,开销大,加锁慢,锁定粒度小,发生锁冲突概率低 从锁的排他性上分为共享锁和排他锁 共享锁指的是当一个事务针对同一份数据加上共享锁之后,另一个事务也可以再往上加一把共享锁,也可以读数据,但是不能改 对索引列加共享锁,锁定的是一行数据;对非索引列加共享锁,锁定的是整表数据 排他锁指的的是当一个事务针对同一份数据加上排他锁之后,另一个事务只能读数据,不能改,也不能再上其它任务锁 还有两种概念上的锁是悲观锁和乐观锁 悲观锁是指一个事务在修改数据的时候,总是认为别人也会修改此数据,所以强制要使用锁来保证数据安全 乐观锁是指一个事务在修改数据的时候,总是认为别人不会修改此数据,因为不加任何锁 这种情况下万一在当前事务修改的时候,数据被其它事务也修改了,机会出现问题,此时常用的方案是: 给数据表中添加一个version列,每次更新后都将这个列的值加1,读取数据时,将版本号读取出来 在执行更新的时候,会先比较版本号,如果相同则执行更新,如果不相同,说明此条数据已经发生了变化,就放弃更新或重试
35.数据库的日志类型有哪些
MySQL的很多功能都是依靠日志来实现的,比如事务回滚,数据备份,主从复制等等,常见的日志有下面几个
binlog归档日志 负责记录对数据库的写操作,一般用在主从复制过程中记录日志,从库拷贝此日志做重放实现数据同步 redolog重做日志 用于确保事务的持久性。防止在发生故障的时间点,尚有脏页未写入磁盘 在重启 mysql 服务的时候,根据 redo log 进行重做,从而达到事务的持久性这一特性 undo log 回滚日志 保存了事务发生之前的数据的一个版本,可以用于回滚
36.谈一谈你对SQL优化的经验
我在企业中优化Sql大体分为三步:
查找问题sql,主要手段是开启mysql的慢查询日志,它会将执行时间较长的sql记录记录下来 找到sql之后,我会分析出现问题的原因,原因很多,主要字段类型选择错误、sql语句效率低、索引失效等等 根据问题不同,我会再去定具体的解决方案 简单给您说几个常见的吧 确定选择的引擎是否合适 myisam适合于查询为主,增删较少,无事务要求的数据表 Innodb适用于有事务处理,或者包括很多的更新和删除的数据表 表设计是否合理 单表不要有太多字段,建议在20以内 合理的加入冗余字段可以提高查询速度 确定字段的数据类型是否合适 数值型字段的比较比字符串的比较效率高得多,字段类型尽量使用最小、最简单的数据类型 设置合适的字符串类型(char和varchar)char定长效率高,varchar可变长度,效率稍低,varchar的长度只分配真正需要的空间 尽量使用TIMESTAMP而非DATETIME,尽量设计所有字段都得有默认值,尽量避免null 确定sql的书写是否有的题 SELECT语句务必指明字段名称,避免直接使用select * SQL语句中IN包含的值不应过多 可以用内连接,就尽量不要使用外连接 使用连接连接查询来代替子查询 使用联合(UNION)来代替手动创建的临时表 表数据比较多的时候是否添加了合适的索引 表的主键、外键必须有索引 经常出现在where子句中的字段,特别是大表的字段,应该建立索引 经常用于排序、分组的字段,应当建立索引 加上索引之后,还应该使用Explain来确认索引是否生效 如果上面的几项都没有问题,那可能就是因为服务器性能或者数据量过大导致的查询慢,此时可以考虑读写分离 也就是我们搭建一个MySQL的主从集群,让1个主节点负责写入数据,多个从节点负责查询数据,以分摊查询压力
37.MyBatis中#{}和${}的区别是什么
在Mybatis中#{}和${}都可以用于在sql语句中拼接参数,但是在使用方面有很多的区别
处理方式不同:
表示的是字符串拼接,
M
y
b
a
t
i
s
在处理它时,会直接将
{}表示的是字符串拼接,Mybatis在处理它时,会直接将
表示的是字符串拼接, M y ba t i s 在处理它时,会直接将 {}替换成变量的值 而#{}是预编译处理,Mybatis在处理它时,会将sql中的#{}替换为?号,然后底层使用JDBC的预编译对象来赋值 安全性不同:${}存在SQL注入问题,#{}可以有效的防止SQL注入 效率不同:KaTeX parse error: Expected 'EOF', got '#' at position 23: …l到数据库每次都要重新编译,而#?{}处理的sql只需要编译一次… {},当然这也不是说
就没有使用场景比如:如果
s
q
l
中需要动态传递表名或者字段名,那就只能使用
{}就没有使用场景 比如:如果sql中需要动态传递表名或者字段名,那就只能使用
就没有使用场景比如:如果 s ql 中需要动态传递表名或者字段名,那就只能使用 {}了
38.使用RabbitMQ如何保证消息不丢失
消息从生产者发送到消费者接收,会经历多个过程 , 其中的每一步都可能导致消息丢失 大体可以分为这样几种情况:
消息发送到交换机丢失 消息从交换机路由到队列丢失 消息保存到队列中丢失 消费者消费消息丢失 针对每一步,RabbitMQ分别给出了解决方案: 消息发送到交换机丢失:发布者确认机制 消息发送到交换机失败会向生产者返回失败原因,生产者通过回调接收发送结果,如果发送失败,重新发送,或者记录日志人工介入 消息从交换机路由到队列丢失:发布者回执机制 消息从交换机路由到队列失败会向生产者返回失败原因 ,生产者通过回调接收回调结果,如果发送失败,重新发送,或者记录日志人工介入 消息保存到队列中丢失:MQ持久化 RabbitMQ运行开启交换机持久化、队列持久化、消息持久化,以保证消息在传输过程中不会丢失 消费者消费消息丢失:消费者确认机制 消费者确认机制指的是只有消费者一方确认消息消费成功了,mq才删除消息,否则就会重新发送消息给消费者 通过RabbitMQ本身所提供的机制基本上已经可以保证消息不丢失, 但是因为一些特殊的原因还是会发送消息丢失问题 , 例如 : 回调丢失 , 系统宕机, 磁盘损坏等 , 这种概率很小 , 但是如果想规避这些问题 , 进一步提高消息发送的成功率, 也可以通过程序自己进行控制。
设计一个消息状态表 , 主要包含 : 消息id , 消息内容 , 交换机 , 消息路由key , 发送时间, 签收状态等字段 , 发送方业务执行完毕之后 , 向消息状态表保存一条消息记录, 消息状态为未签收 , 之后再向MQ发送消息 , 消费方接收消息消费完毕之后 , 向发送方发送一条签收消息 , 发送方接收到签收消息之后 , 修改消息状态表中的消息状态为已签收 ! 之后通过定时任务扫描消息状态表中这些未签收的消息 , 重新发送消息, 直到成功为止 , 对于已经完成消费的消息定时清理即可 !
39.如何解决消息的重复消费问题和消息堆积问题
消息的重复消费问题: 在使用RabbitMQ进行消息收发的时候,如果发送失败或者消费失败会自动进行重试,那么就有可能会导致消息的重复消费 解决方案: ● 每条消息设置一个唯一的标识id ● 幂等方案 ○ token+redis ○ 分布式锁 ○ 数据库锁(悲观锁、乐观锁) 解决消息堆积有几种种思路:
提高消费者的消费能力,例如使用多线程消费 增加消费者数量,提高消费速度,可以使用ork队列模式,设置多个消费者消费消费同一个队列中的消息 扩大队列容积,提高堆积上限 使用RabbitMQ惰性队列,接收到消息后直接存入磁盘而非内存,消费者要消费消息时才会从磁盘中读取并加载到内存
40.什么是倒排索引
倒排索引是搜索引擎的核心,它是一种像数据结构一样的散列图,可将用户从单词导向文档或网页。主要目标是快速从数百万文件中查找数据 倒排索引主要体现在文档的保存和查询流程中 ● 保存文档时,会先根据文档进行分词,然后使用分好的词条作为key进行排序,然后将文档的标识作为value进行存储 ● 查询文档时,也会先对查询关键字进行分词,然后根据分好的词条直接定位相关文档,再做结果的合并
41.ES中的查询关键字有哪些
在ES中用于声明查询条件的关键字主要有: ● match_all:查询所有 ● match、multi_match:全文检索 ● term:精准词条查询 ● range:范围查询 ● bool、must、must_not、should、filter:复合查询 还有一些跟地理位置、相关性算分相关的
42.HashMap的底层结构和扩容机制
HashMap底层数据结构是哈希表,哈希表在JDK1.8之前是数组+链表实现,在JDK1.8之后是数组+链表+红黑树实现的 下面我以map中存储对象的流程给您说一下它的实现原理吧
当我们创建一个HashMap的时候,JDK就会在内存中创建一个长度为16的数组 当我们调用put方法像HashMap中保存一个元素的时候,它会先调用key的hashCode方法计算出key的hash值 然后使用得到hash值对数组长度取余,找出当前对象的元素在数组中的位置 接下来,它会判断算出的位置上是否有元素,如果没有,就会将此元素直接存储到当前位置上 如果算出的位置上有元素或者是有链表,它会再调用key的equals方法跟存在元素的key做比较 如果有一个比较得到的结果为true,则会进行值的覆盖,如果都为false,则会将元素追加在链表的末尾 当然,为了降低Hash冲突和链表长度,HashMap还做了一些优化 当元素的数量超过数组大小与加载因子的乘积的时候,就会执行扩容,扩容为原来的2倍,并将原来数组中的键重新进行hash运算,然后分配到新数组中 当链表的长度>8,并且数组长度>=64的时候,链表就会转换为红黑树,当红黑树结点数小于6时将再次转回为链表。 HashMap的扩容机制是怎样的: HashMap的扩容机制是指当HashMap中的元素个数超过数组长度乘以负载因子时,就会重新分配一个更大的数组,并将原来的元素重新计算哈希值并插入到新的数组中。 在JDK1.8中,底层是调用resize方法实现扩容的,它的默认做法是:当元素个数超过数组长度的0.75倍时触发扩容,每次扩容的时候,都是扩容为原来的2倍, 扩展后Node对象的位置要么在原位置,要么移动到原偏移量两倍的位置
43.ConcurrentHashMap的底层结构
ConcurrentHashMap的底层结构是由一个Segment数组和HashEntry数组组成的。 Segment是一种可重入锁,用来控制对HashEntry数组的访问。每个Segment维护着一个HashEntry数组,这样可以将整个Map分成多个Segment,每个Segment都可以独立地进行扩容和操作,从而提高并发性能。 在Java 8及以上版本,ConcurrentHashMap底层结构发生了改变,使用了Node数组和红黑树来替代Segment数组和HashEntry数组,以提高并发性能和减少内存占用。
44.线程池重要参数有哪些,任务队列有哪些,拒绝策略有哪些
线程池在创建的时候最大支持传入7个参数,分别是:
核心线程数 最大线程数 临时线程的空闲时间:临时线程会在空闲这段时间后 临时线程的空闲时间单位 工作队列:用来保存等待执行的任务的 threadFactory:设置创建线程的工厂 handler:线程池的拒绝策略 任务队列有哪些: 阻塞队列指的是当线程中核心线程数已满,新任务到达时,存储线程的队列,常见的有下面几种: ● ArrayBlockingQueue:基于数组结构的有界阻塞队列 ● LinkedBlockingQueue:基于链表结构的有界阻塞队列 ● PriorityBlockingQueue:具有优先级别的阻塞队列 ● SynchronousQueue:不存储元素的阻塞队列,每个插入操作都必须等待一个移出操作 拒绝策略有哪些: 拒绝策略是指将任务添加到线程池中时,线程池拒绝该任务所采取的相应策略,官方提供的有4种: ● AbortPolicy:直接抛出异常,默认策略 ● CallerRunsPolicy:用调用者所在的线程来执行任务 ● DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务 ● DiscardPolicy:直接丢弃任务
45.说一说线程池工作流程
当我们提交一个任务到线程池中,线程池的处理流程如下:
首先判断线程池里的核心线程是否都在执行任务,如果不是,则创建一个新的工作线程来执行任务。 如果核心线程都在执行任务,则判断工作队列是否已满,如果没满,则将新提交的任务存储在这个工作队列里。 如果工作队列满了,则判断线程数是否小于最大线程数,如果是,则创建临时线程直接执行任务 如果线程数已经到达了最大线程数,则会执行对应的拒绝策略逻辑
46.synchronized和lock的区别
synchronized是Java中的关键字;Lock是一个接口 synchronized关键字可以直接修饰方法,也可以修饰代码块;而lock只能修饰代码块 synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生; Lock在发生异常时,如果没有主动通过unLock()方法去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁 Lock可以让等待锁的线程响应中断;而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断 在性能上来说,如果竞争资源不激烈,两者的性能是差不多的;而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized
47.JVM的垃圾回收算法有哪些
目前JVM中的垃圾回收算法主要有四个,分别是:标记清除算法、标记-整理算法、复制算法和分代收集算法
标记清除算法是将垃圾回收分为2个阶段,分别是标记和清除 它会先使用根据可达性分析算法找到垃圾资源进行标记,然后对这些标记为可回收的内容进行垃圾回收 这种算法的主要不足有两个: ○ 效率问题,标记和清除阶段都要遍历多有对象,并且在GC时,需要停止应用程序,对于交互性要求比较高的应用而言这个体验是非常差的 ○ 空间问题,对象被回收之后会产生大量不连续的内存碎片,当需要分配较大对象时,由于找不到合适的空闲内存而不得不再次触发垃圾回收动作 标记整理算法也是将垃圾回收分为2个阶段,分别是标记和整理清除 它的第一阶段也是会先将存活的对象先标记出来 不一样的地方在于第二阶段,它会将所有存活的对象向前移动放在一起,然后将无用空间回收,这样就会出现连续的可用空间了 所以它解决了空间碎片问题,但是效率低的问题依旧存在 复制算法,将原有的内存空间一分为二,每次只用其中的一半 在垃圾回收时,将正在使用的对象复制到另一个内存空间中,然后将当前内存空间清空,交换两个内存的角色,完成垃圾的回收。 这种算法的缺点在于分配2块内存空间,在同一个时刻,只能使用一半,内存使用率较低 分代收集算法,它会将整个堆内存分成几部分空间,每个空间中放入不同类型的对象,然后各自适合的算法回收 在JDK8时,堆被分为了两份:新生代和老年代,默认空间比例为1:2 对于新生代,内部又被分为了三个区域:Eden区,S0区,S1区,,默认空间比例为8:1:1 它的基本工作机制是: 当创建一个对象的时候,这个对象会被分配在新生代的Eden区,当Eden区要满了时候,触发MinorGC 当进行MinorGC后,此时在Eden区存活的对象被移动到S0区,并且当前对象的年龄会加1,清空Eden区 当再一次触发MinorGC的时候,会把Eden区中存活下来的对象和S0中的对象,移动到S1区中,这些对象的年龄会加1,清空Eden区和S0区 当再一次触发YoungGC的时候,会把Eden区中存活下来的对象和S1中的对象,移动到S0区中,这些对象的年龄会加1,清空Eden区和S1区 对象的年龄达到了某一个限定的值(默认15岁),那么这个对象就会进入到老年代中,除此之外,大对象也会直接放入老年代空间 当老年代满了之后,触发FullGC**。**FullGC同时回收新生代和老年代 在上述过程中,新生代中的对象存活率比较低,所以选用复制算法;老年代中对象存活率高,所以使用标记-整理算法 小细节: 当对新生代产生GC:MinorGC,老年代代产生GC:Major GC ,新生代和老年代产生FullGC Minor GC非常频繁,一般回收速度也很快,Major GC一般会伴随一次Minor GC,Major GC的速度要慢很多,一般要比Minor GC慢10倍 占用内存较大的对象,对于虚拟机内存分配是一个坏消息,虚拟机提供了一个-XX:PretenureSizeThreshold让大于这个设置的对象直接存入老年代 虚拟机给每个对象定义了一个Age年龄计数器,对象在Eden中出生并经过第一次Minor GC后仍然存活,年龄+1,此后每熬过一次Minor GC则年龄+1, 当年龄增加到一定程度(默认15岁),就会晋升到老年代。可通过参数设置晋升年龄 -XX:MaxTenuringThreshold
48.你们工作中是如何是git的
我在工作中对于git的使用,可分为以下几个步骤: 首先,每天上班之后,我会从公司远程仓库中进行拉取(pull),以保证本地项目和远程仓库项目进度一致 然后,在本地的开发分支上新建一个当天的分支,进行代码开发 开发过程中一般在完成某功能或某一模块时,进行本地提交(commit) 当我们完成一个完整模块的开发后,会将本地新分支的代码合并到开发分支 最后,提交(push)到远程仓库前,先进行拉取(pull),如果有冲突,就先进行冲突解决,解决完毕之后,再push
49.说几个常用的运维命令
查看目录 ls 查看简单信息 ll -a 查看所有详情 切换目录 cd /绝对路径 ./ …/相对路径 创建目录:mkdir -pv 目录名 删除目录(文件):rm -rf 目录(文件) 复制目录(文件):cp -r 原位置 新位置 剪切目录(文件):mv 原位置 新位置 编辑文件 vim 文件名 命令模式 i 编辑模式 esc 命令模式 :wq 查看文件:more 文件名 文件压缩:tar -zcvf xxx.tar.gz 指定文件 文件解压:tar -zxvf xxx.tar.gz -C /指定解压路径 文件授权:chmod 755 -R 目录(文件) 防火墙 服务: systemctl start|stop|status|restart firewalld 策略: 开启: firewall-cmd --zone=public -add-port=80/tcp --permanent 关闭: firewall-cmd --zone=public -remove-port=80/tcp --permanent 重载: firewall-cmd --reload 查看: firewall-cmd --zone=public --list-ports 查看端口(8080):netstat -an | grep 8080 查看进程(tomcat):ps -ef | grep tomcat 强杀进程:kill -9 pid
50.说几个常见的vue指令
双向绑定(v-model):用于实现双向数据绑定。它将表单元素的值与 Vue 实例的数据进行关联,使得数据的改变会自动反映在表单元素上,同时用户的输入也会更新数据。 属性绑定(v-bind):用于绑定 HTML 元素的属性或组件的属性。可以动态地将数据绑定到元素的属性上,使其可以根据数据的变化而变化。 列表遍历(v-for):用于循环渲染列表数据。通过遍历数组或对象,可以生成重复的 HTML 元素,并绑定相应的数据。 条件判断(v-if/v-show): v-if:根据表达式的真假条件来决定是否渲染 DOM 元素。如果条件为真,则渲染元素;如果条件为假,则从DOM中移除元素。 v-show:根据表达式的真假条件来控制元素的显示和隐藏。如果条件为真,则显示元素;如果条件为假,则隐藏元素,但不会从DOM中移除。 事件绑定(v-on):用于监听 DOM 事件,并触发相应的方法。可以在 HTML 元素上绑定事件监听器,比如点击、输入等事件。 异步交互(axios)
51.项目处理过哪些问题让你记忆深刻,如何解决的?
52.公司有多少人,有多少开发人员,有多少前端人员,有多少产品经理
项目经理(1人):对整个项目负责,任务分配、把控进度 产品经理(1人):进行需求调研,输出需求调研文档、产品原型等 UI设计师(1人):根据产品原型输出界面效果图 架构师(0人):项目整体架构设计、技术选型等 开发工程师(3+2):功能代码实现 测试工程师(2人):编写测试用例,输出测试报告 运维工程师(1人):软件环境搭建、项目上线
53.公司在哪个城市,那个大厦,那条街,你住在哪里?怎么去公司
54.项目中遇到的异常有哪些,怎么解决这些异常
55.为什么离职?
56.上家薪资构成,社保基数?
57.现在有offer了吗?为什么没有去
58.加班怎么看?