一、什么是缓存穿透?缓存击穿?缓存雪崩?怎么解决?
1、缓存穿透:缓存中查不到,数据库也查不到。
解决方式:
方式1、对参数进行合法性校验。
方式2、将数据库中没有查到结果的数据也写入缓存。这时要注意为防止Redis被无用Key占满,这一类缓存的有效期要设置得短一点。
方式3、引入布隆过滤器,在访问Redis之前判断数据是否存在。要注意布隆过滤器存在一定的误判,并且布隆过滤器只能加数据不能减数据。
2、缓存击穿?缓存中没有,数据库中有。一般是出现在存数据初始化以及Key过期了的情况。他的问题在于,重新写入缓存需要一定的时间,如果是在高并发场景下,过多的请求就会直接瞬间到达DB上,给DB造成很大的压力。
解决方式:设置这个热点数据永不过期,在这个数据修改的时候,删除Key,下次从数据库查询加分布式锁,重新key设置永不过期
3、缓存雪崩:缓存大面积过期,导致请求都被转发到DB。
解决方式:把缓存的过期时间错开,例如,原有的统一失效时间基础上,增加一个随机值。
二、如何保证Redis与数据库的数据一致?
当我们对数据进行修改的时候,到底是先删除缓存,还是先写数据库?
1、如果先删缓存,再写数据库:在高并发场景下,当第一个线程删除了缓存,还没有来得及写数据库,第二个线程来读取数据,会发现缓存中的数据库空,那就会去读数据库中的旧数据(旧值,脏数据),读完之后,把读到的结果写入缓存,这样缓存中的值就会与数据修改后的值对应不上。
解决方案:
延时双删:先删除缓存,然后再写数据库,休眠一小会,再次删除缓存
2、先写数据库,再删缓存:如果数据库写完了之后,缓存删除失败,数据就会不一致。
解决方案:
给缓存设置一个过期时间
三、Redis如何配置key过期时间?他的实现原理是什么?
redis设置key的过期时间:1、EXPIRE 2 SETEX
实现原理:
1、定期删除:每隔一段时间,执行一次删除过期KEY的操作
2、懒汉式删除:当使用get,getset等指令去获取数据时,判断key是否过期,过期后,就先把key删除,再执行后面的操作
redis是将两种方式结合来使用。
懒汉式删除:
定期删除:平衡执行频率和执行时长
定期删除时会遍历每个database(默认16),检查当前库中指定个数的Key(默认20个)。随机抽查这些Key,如果有过期的就删除。
四、谈谈你对微服务的理解?
微服务是一种架构风格,通过将大型的单体应用划分为比较小的服务单元,从而降低整个系统的复杂度。
优点:
1、服务部署更灵活:每个应用都可以是一个独立的项目,可以独立部署,不依赖其他服务,耦合性降低。
2、技术更新灵活:在大型单体应用中,技术要进行更新,往往是非常困难的,而微服务可以根据业务特点,灵活选择技术栈。
3、应用的性能得到提高:大型单体应用中,往往启动就会成为一个很耗时的一步
4、代码复用:很多底层服务可以以REST API的方式对外提供统一的服务,所有基础服务可以在整个微服务系统中通用
缺点:
1、服务调用的复杂度提高了
2、分布式事务
3、测试的难度提高了
4、运维的难度提高了
五、分布式事务如何处理?怎么保证事务一致性?
分布式事务 !=seate
分布式事务:就是要将不同节点上的事务操作,提供操作原子性保证,同时成功或者同时失败。
分布式事务第一个要点就是要在原本没有直接关联的事务之间建立联系
1、Http连接:最大努力通知。--事后补偿
2、MQ:事务消息机制。
3、Seata:也定制出分布式事务机制。
两阶段:AT XA 就在于要锁资源
三阶段 :TCC 在两阶段的基础上增加一个准备阶段。在准备阶段是不锁资源的
SAGA模式:类似于熔断。业务自己实现正向操作和补偿操作的逻辑。
六、怎么拆分微服务?怎么设计出高内聚、低耦合的微服务?
拆分微服务的时候,为了尽量保证微服务的稳定,会有一些基本的准则:
1、微服务之间尽量不要有业务交叉
2、微服务之间只能通过接口进行服务调用,而不能绕过接口直接访问对方的数据
3、高内聚,低耦合
高内聚,低耦合是一种从上而下指导微服务设计的方法,实现高内聚低耦合的工具主要有:同步的接口调用和异步的事件驱动
七、Spring框架中Bean的创建过程是怎样的?
首先:简单来说,Spring框架的Bean经过四个阶段:实例化-》属性赋值-》初始化-》销毁
具体来说:Spring中Bean经过了以下几个步骤:
1、实例化:new xxx();两个时机:1、当客户端向容器申请一个Bean时,2、当容器在初始化一个Bean时发现还需要依赖别一个Bean。(BeanDefinition对象保存)
2、设置对象属性(依赖注入):Spring通过BeanDefinition找到对象依赖的其他对象,并将这些对象赋予当前对象
3、处理Aware接口:Spring会检测对象是否实现xxxAware接口,如果实现了,就会调用对应的方法,常用的Aware接口:如BeanNameAware,BeanClassLoaderAware,BeanFactoryAware
4、BeanPostProcessor前置处理:调用BeanPostProcessor的postProcessBeforeInitialization方法
5、InitalizingBean:Spring检测对象如果实现了这个接口,就会执行他的afterPropertiesSet()方法,定制初始化逻辑。
6、init-method(@PostConstruct):如果Spring发现Bean配置了这个属性,就会调用他的配置方法,执行初始化逻辑。
7、BeanPostProcessor后置处理:调用BeanPostProcessor的postProcessAfterInitization方法
8、DisposableBean:当Bean实现了这个接口,在对象销毁前就会调用destory()方法
9、destroy-method(@PreDestroy)
八、Spring框架中的Bean是线程安全的吗?如果线程不安全,要如何处理?
Spring容器本身没有提供Bean的线程安全策略,因此,也可以说Spring容器中的Bean不是线程安全的。
要如何处理线程安全问题,就要分情况来分析。
Spring中的作用域:1、sington(单例),2、prototype(多例),3、request:为每个request请求创建一个实例,请求完成后失效、4、session:为每个会话创建一个实例,5、global-session:全局作用域
1、对于prototype作用域,每次都生成一个新的对象,所以不存在线程安全问题。
2、sington作用域:默认就是线路不安全。但是对于开发中大部分的bean,其实是无状态的,不需要保证线程安全。所以在平常的MVC开发中,是不会有线程安全的问题的。
无状态表示这个实例没有属性对象,不能保存数据,是不娈的类。比如:controll、service、dao
有状态表示实例是有属性对象,可以保存数据,是线程不安全的,比如pojo
但是如果要保证线程安全,可以将Bean作用域改为prototype 比如:model
另外还可以采用ThreadLocal来解决线程安全问题。ThreadLocal为每个线程保存一个副本变量,每个线程只操作自己的副本变量
九、Spring如何处理循环依赖问题?
三级缓存
循环依赖:多个对象之间存在循环的引用关系,在初始化过程当中,就会出现“先有蛋还是先有鸡”的问题。
方式一:@Lazy注解:解决构造方法造成的循环依赖问题
方式二:三级缓存
一级缓存:缓存最终的单例池对象:
private final Map<String,Object> singletonObjects=new ConcurrentHashMap<>(256);
二级缓存:缓存初始化的对象:
private final Map<String,Object> earlySingletonObject=new ConcurrentHashMap<>(16);
三级缓存:缓存对象AOP的配置:
private final Map<String,ObjectFactory<?>> singletonFactoreis =new HashMap<>(16);
对于对象之间的普通引用:
二级缓存会保存new出来的不完整对象,这样当单例池中找到依赖的属性时,就可以先从二级缓存中获取到不完整对象,完成对象创建,在后续的依赖注入过程中,将单例池中对象的引用关系调整完成。
三级缓存:如果引用的对象配置了AOP,那在单例池中最终就会需要注入动态代理对象,而不是原对象。而生成动态代理是要在对象初始化完成之后才开始的。于是Spring增加三级缓存,保存所有对象的动态代理配置信息。在发现有循环依赖时,将这个对象动态代理信息获取出来,提前进行AOP,生成动态代理。
核心代码就在DefaultSingletonBeanRegistry的getSingleton方法当中。
十、Spring如何处理事务?
spring当中支持编程式事务和声明式事务管理两种方式:
1、编程式事务可以使用TransactionTemplate
2、声明式事务:是Spring在Aop基础上提供的事务实现机制。他的最大优点就是不需要在业务代码中添加事务管理的代码,只需要在配置文件中做相关的事务规则声明就可以了。但是声明式事务只能针对方法级别,无法控制代码级别的事务管理
Spring中对事务定义了不同的传播级别:Propagation
1、PROPAGATION_REQUIRED:默认传播行为。如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入到事务中。
2、PROPAGATION_SUPPORTS:如果当前存在事务,就加入该事务。如果当前不存在事务,就抛出异常
3、PROPAGETION_REQUIRES_NEW:无论当前存不存在事务,都创建新事务进行执行。
还有几个不写了
Spring中事务的隔离级别:
1、ISOLATION_DEFAULT(默认):使用数据库默认的事务隔离级别
2、ISOLATION_READ_UNCOMMEITTED:读未提交、允许事务在执行过程 ,读取其他事务未提交的数据
3、ISOLATION_READ_COMITTERD:读已提交。允许事务在执行过程中,读取其他事务已经提交的数据
4、ISOLATION_PEPEATEABLE_READ:可重复读。在同一个事务内,任意时刻的查询结果是一致的
5、ISOLATION_SERIALAZABLE:序列化