java基础面试题
Java的四种引用,强弱软虚
- 强引用(Strong Reference):java中的默认引用就是强引用,new一个Object对象,并将其赋值给obj,这个obj就是new Object()的强引用。强引用的特性是只要有强引用存在,被引用的对象就不会被垃圾回收
- 软引用(Soft Reference):软引用在java中专门有一个SoftReference类型,软引用的意思是只有在内存不足的情况下,被引用的对象才会被回收。
- 弱引用(weak Reference):弱引用和软引用类似,不同的是weakReference引用的对象只要垃圾回收执行,就会被回收,而不管内存是否不足
- 虚引用(PhantomReference):PhantomReference的作用是跟踪垃圾回收器收集对象的活动,在GC的过程中,如果发现有PhantomReference,GC则会将引用放到ReferenceQueue中,由程序员自己处理,当程序员调用ReferenceQueue.pull()方法,将引用出ReferenceQueue移除之后,Reference对象会变成Inactive状态,意味着被引用的对象可以被回收了
深拷贝和浅拷贝的区别?
- 浅拷贝:被复制对象的所有变量都含有与原来的对象相同的值,而所有的其他对象引用的仍然指的是原来的对象。换言之,浅拷贝仅仅复制所考虑的对象,而不是复制它所引用的对象
- 深拷贝:被复制对象的所有变量都含有与原来的对象相同的值。而那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。换言之,深拷贝要把复制的对象所引用的对象都复制了一遍
Spring面试题
Autowired和Resource关键字的区别?
- 共同点
- 两者都是用做bean注入使用的注解,Resource不是Spring的注解
- 两者都可以在字段和setter方法上面
- 不同点
- Autowired是Spring提供的注解,是按照类型来装配依赖对象的,默认情况下要求依赖的对象必须存在,如果允许null值可以设置required属性为false,如果我们想要按照名称来进行装配,可以结合@Qualifier注解一起使用
- Resource是由J2EE提供的注解,有两个重要的属性name和type,默认是byName
- Resource的装配顺序
- 同时指定了name和type,从Spring的上下文中找到唯一匹配的bean进行装配,找不到则抛出异常
- 指定name,从上下文指定名称匹配的bean进行装配,找不到抛出异常
- 指定type,从上下文指定类型匹配bean进行装配,找不到或是找到多个,都会抛出异常
- 既没有指定name也没有type,则自动按照byName方式,没有匹配,则回退一个原始类型进行装配
谈一谈你对Spring的理解
- Spring是一个轻量级的IOC和AOP容器框架,目的是简化企业应用程序的开发。常见的配置方法是基于XML的配置,基于注解的配置,基于java的配置
- Spring主要是由
- Spring Core:核心类库,提供IOC服务
- Spring Context:提供框架式的Bean访问方式,以及企业级功能
- Spring AOP:提供AOP服务
- Spring DAO:对JDBC的抽象,简化了数据访问异常的处理
- Spring ORM:对现有的ORM框架的支持
- Spring WEB :提供基本的面向Web的综合特性,例如多方文件上传
- Spring MVC:提供面向Web应用的Model-View-Controller实现
谈谈你对SpringIOC的理解
- IOC就是控制反转,指将创建对象的控制权进行转移,将创建对象的权利转到Spring容器当中,并根据容器的配置文件去创建实例和管理各个实例之间的依赖关系,将对象和对象之间松散耦合,也利于功能的复用
- IOC是通过依赖注入的方式来实现的(DI)
谈谈你对SpringAOP的理解
- AOP(面向切面编程)能将那些与业务无关的,却为业务模块所共同调用的逻辑或责任(例如:事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块之间的耦合性,并有利于未来的可扩展性和可维护性。
- SpringAOP是基于动态代理的,如果要代理的对象实现了某个接口,那么AOP就会使用JDK动态代理去创建代理对象;而对于没有实现接口的对象,就无法使用JDK动态代理,转而使用CGlib动态代理生成一个被代理对象的子类来作为代理
- Spring AOP中集成了AspectJ
Spring AOP 和 AspectJ有什么区别
- Spring AOP是运行是增强,AspectJ是编译时增强
- Spring AOP是基于代理,AspectJ是基于字节码进行操作的
- Spring AOP中已经集成了AspectJ,AspectJ相比于Spring AOP功能更加强大,但是Spring AOP的使用相对来说更加简单
- 如果切面较少,两者性能差异不大,若切面太多,最好选择AspectJ,它比Spring AOP快很多
Spring AOP中,关注点和横切关注点的区别是什么
- 关注点是应用中一个模块的行为,一个关注点可能被定义为一个我们想要实现的功能
- 横切关注点是一个关注点,这个关注点是整个应用都会使用的功能,并影响整个应用,如:日志、安全和数据传输,几乎每个模块都需要的功能
- 连接点代表一个应用程序的某个位置,在这个位置我们可以插入AOP切面,实际上是应用程序执行Spring AOP的位置
- 切入点是一个或一组连接点,通知将在这些位置执行。可以通过表达式或匹配的方式指明切入点
什么是通知,有几种类型
- 通知是在方法执行前或在方法执行后需要做的动作,实际上是程序执行是要通过SpringAOP框架触发的代码段
- before:前置通知,在一个方法执行前被调用
- after:在方法执行之后调用的通知,无论方法执行是否成功
- after-returning:仅当方法成功完成后执行的通知
- after-throwing:在方法抛出异常退出时执行的通知
- around:在方法执行之前和之后调用的通知
谈一谈Spring bean的生命周期
- 实例化bean:对于BeanFactory容器,当用户向容器请求一个尚未初始化的bean时,或初始化bean的时候需要注入另一个尚未初始化的依赖时,容器就会调用createBean进行实例化,对于ApplicationContext容器,当容器启动结束后,通过获取BeanDefinition对象中的信息,实例化所有的bean
- 设置对象属性(依赖注入):实例化后的对象被封装在BeanWrapper对象中,紧接着,Spring根据BeanDefinition中的信息,以及通过BeanWrapper提供的设置属性的接口完成依赖注入
- 处理Aware接口:接着,Spring会检测对象是否实现了xxxAware接口,并将相关的xxxAware实例注入给Bean
- 如果这个Bean已经实现了BeanNameAware接口,会调用它实现的setBeanName(String beanid)方法,此处传递的就是Spring配置文件中Bean的id值
- 如果这个Bean已经实现了BeanFactoryAware接口,会调用它实现的setBeanFactory()方法,此处传递的就是Spring工厂自身
- 如果这个Bean已经实现了ApplicationContectAware接口,会调用setApplicationContext(ApplicationContext)方法,传入Spring上下文
- BeanPostProcessor:如果想对Bean进行一些自定义的处理,那么可以让Bean实现BeanPostProcesser接口,那会调用postProcessBeforeInitialization(Object obj,Sting s)方法
- InitializingBean和init-method:如果Bean在Spring配置文件中配置了init-method属性,则会自动调用其配置的初始化方法
- 如果这个Bean实现了BeanPostProcessor,将会调用postProcessAfterInitializetion(Object obj,String s)方法;由于这个方法是在Bean初始化结束时调用的,所以可以被应用于内存或缓存技术(在此Bean就被正确的创建了,之后就可直接使用)
- DisposableBean:当Bean不在需要的时候,会经过清理阶段,如果Bean实现了DisposableBean这个接口,会调用destory()方法
- destory-method:最后,如果这个Bean的Spring配置中配置了destroy-method属性,会自动调用其配置的销毁方法。
说一说Spring支持的bean作用域
- singleton:默认的,每个容器都创建一个bean的实例,单例的模式有BeanFactory自身来维护
- prototype:为每个bean请求提供一个实例
- request:为每一个网络的请求创建一个实例,在请求完成后,bean会失效并被垃圾回收器回收
- session:与request范围类似,确保每个session中有一个bean的实例,在session过期后,bean会随之失效
- global-session:全局作用域,global-session和Portlet应用相关,当你的应用部署在Portlet容器中工作时,它包含很多portlet。如果你想要声明让所有的portlet共同全局的存储变量的话,那么这全局变量需要存储在global-session中。全局作用域与servlet中的session作用域效果相同
说说Spring中ApplicationContext和BeanFactory的区别
- 包目录不同org.springframework.beans.factory.BeanFactory,org.springframework.context.ApplicationContext
- 国际化:BeanFactory不支持国际化,因为BeanFactory没有扩展Spring中的MessageResource接口
- 事件机制:基本上牵涉到事件(Event)方面的设计,就离不开观察者模式,ApplicationContext 的事件机制主要通过 ApplicationEvent 和 ApplicationListener 这两个接口来提供的,和 Java swing 中的事件机制一样。即当 ApplicationContext 中发布一个事件时,所有扩展了 ApplicationListener 的 Bean都将接受到这个事件,并进行相应的处理。
- 底层资源的访问:ApplicationContext扩展了ResourceLoader(资源加载器)接口,从而可以用来加载多个Resource,而BeanFactory是没有扩展ResourceLoader
- 对Web应用的支持:BeanFactory通常以编程的方式被创建,ApplicationContext能以声明的方式创建,如使用ContextLoader,当然你也可以使用ApplicationContext的实现方式之一,以编程的方式创建ApplicationContext实例
- 延迟加载
- BeanFactory采用的是延迟加载的形式来注入Bean的,即只有在使用到某个Bean时,才对该对象进行加载实例化,这样,我们就不能发现一些存在spring的配置问题,而ApplicationContext相反,它是在容器启动的时候,一次性创建所有的bean,这样在容器启动时,我们就可以发现Spring中存在的配置错误
- BeanFactory和ApplicationContext都支持BeanPostProcessor、BeanFactoryPostProcessor的使用。两者的区别是,BeanFactory需要手动注册,而ApplicationContext是自动注册
- 常用容器
- BeanFactory类型有XmlBeanFactory,它可以根据XML文件中定义的内容,创建相应的Bean
- ClassPathXmlApplicationContext:从ClassPath的XML配置文件中读取上下文,并生成上下文定义。应用程序上下文从程序环境变量中取得
- FileSystemXmlApplicationContext:由文件系统中的XML配置文件读取上下文
- XmlWebApplicationContext:由Web应用的XML文件读取上下文。例如我们在Spring MVC使用的情况
Spring是怎么解决循环依赖的?
- A完成初始化第一步并将自己提前曝光出来(通过ObjectFactory将自己提前曝光),在初始化的过程中发现自己依赖对象B,此时会尝试去get(B),这时候发现B还没有创建出来
- 然后B就走流程创建,在B初始化的过程中,发现依赖C,C还没有创建出来
- 这时候由C开始初始化进程,但是在初始化的过程中,发现自己依赖A,于是去get(A),这时候由于A已经添加到了缓存中(一般都是添加至三级缓存),通过ObjectFactory提前曝光,所以可以通过ObjectFactory#getObject()方法来拿到A对象,C拿到A对象后顺利完成初始化,然后将自己添加到一级缓存中
- 回到B,B拿到C,完成初始化,A可以顺利拿到B完成初始化。到这里整个链路已经完成了初始化的过程
Spring的事务传播级别
- PROPAGATION_REQUIRED:默认的Spring事务传播级别,若当前存在事务,则加入改事务,没有则创建事务
- PROPAGATION_REQUIRE_NEW:若当前没有事务,则新建一个事务。若当前存在事务,也新建一个事务,新老事务相互独立。外部事务抛出异常回滚不会影响内部事务的正常提交
- PROPAGATION_NESTED:如果当前存在事务,则嵌套在当前事务中执行,如果没有当前事务,则创建一个新事务,类似于REQUIRE_ NEW
- PROPAGATION_SUPPORTS:支持当前事务,若当前不存在事务,以非事务的方式执行
- PROPAGATION_NOT_SUPPORTED:以非事务的方式运行,若当前存在事务,则把当前的事务挂起
- PROPAGATION_MANDATORY:强制事务执行,若当前不存在事务,则抛出异常
- PROPAGATION_NEVER:以非事务的方式运行,如果当前存在事务,则抛出异常
Spring MVC
说一说SpringMVC的常用注解
- RequestMapping:用于处理请求url映射的注解,可用于类或者方法上。用于类上,则表示类中所有响应请求的方法都是以改地址作为父路径
- RequestBody:注解实现接收http请求的json数据,将json转换为java对象
- ResponseBody:注解实现将Controller方法返回的对象转换为json对象响应给客户
说一说你对spring MVC的理解
什么是MVC
- MVC是一种设计模式
- M-model模型:完成业务逻辑,由javabean构成,service+dao+entity
- V-view视图:做界面展示的jsp、html
- C-controller控制器:接收请求–>调用模型–>根据结果派发页面
SpringMVC的工作原理
- 用户发送请求到前端控制器DispatcherServlet
- DispatcherServlet收到请求调用HandlerMapping处理器映射器
- 处理器映射器找到具体的处理器(可以根据xml配置,注解进行查找),生成处理器对象以及处理器凝结器(如果有则生成)一并返回给DispatcherServlet
- DispatcherServlet调用HandlerAdapter处理器适配器
- HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器)
- Controller执行完成返回ModelAndView
- HandlerAdapter将controller执行结果ModelAndView传给DispatcherServlet
- DispatcherServlet昂ModelAndView传给ViewReslover视图解析器
- ViewReslove解析后,返回具体的View
- DispatcherServlet根据view进行视图渲染(即将模型数据填充至视图中)
- DispatcherServlet响应用户
组件说明
- DispatcherServlet:前端控制器,整个流程控制的中心,控制其他组件的执行,统一调度,降低组件之间的耦合性,提高每个组件的扩展性
- HandlerMapping:通过扩展处理器映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等;
- HandlerAdapter:通过扩展处理器适配器,支持更多类型的处理器
- ViewReslover:通过扩展视图解析器,支持更多类型的视图解,例如:jsp、freemarker、pdf、excel等
组件的作用
- 前端控制器(DIspatcherServlet),由框架提供:接收请求,响应结果,相当于换发器,中央处理器。有了DispatcherServlet减少了其他组件之间的耦合度。用户请求到达前端控制器,它就相当于mvc模式中的c,DispatcherServlet是整个流程的控制中心,由它调用其他组件处理用户的请求,DispatcherServlet的存在降低了组件之间的耦合性
- 处理器映射器(HandlerMapping),由框架提供:根据请求的url查找Handler,HandlerMapping负责根据用户请求到Handler即处理器springmvc提供了不同的映射器实现不同的映射方式,例如:配置文件方式,接口实现方式,注解方式等
- 处理器适配器(HandlerAdapter):按照特定规则(HandlerAdapter要求的规则)去执行Handler通过HandlerAdapter对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型处理器进行执行
- 处理器(Handler),需要开发:编写Handler时按照HandlerAdapter额要求去做,这样适配器才可以去正确的执行Handler,Handler是继DispatcherServlet前端控制器后端控制器,在DispatcherServlet 控制下Handler对具体的用户请求进行处理。由于Handler涉及到具体的用户业务请求,所以一般情况下需要开发者根据业务需求开发Handler
- 视图解析器(ViewReslover),框架提供:进行视图解析,根据逻辑视图名解析成真正的视图,ViewReslover负责将处理结果生成View视图,ViewReslover首先根据逻辑视图名解析成物理视图名即具体的页面地址,再生成View视图对象,最后对View进行渲染,将处理结果通过页面展示给用户。springmvc框架提供了很多的View视图类型,包括:jstlView、freemarkerView、pdfView等。 一般情况下需要通过页面标签或页面模版技术将模型数据通过页面展示给用户,需要由工程师根据业务需求开发具体的页面。
Mybatis面试题
什么是Mybatis
- Mybatis是一个半ORM(对象关系映射)框架,内部封装了JDBC,开发的时候只需要关注SQL本身,不需要花费精力去处理加载驱动、创建连接、创建statement等繁杂的过程。程序员直接编写原生态SQL,可以严格控制SQL执行性能,灵活度高
- Mybatis可以使用xml或者注解的形式来配置和映射原生信息,将pojo映射成数据库中的记录,避免了几乎所有的JDBC代码和手动设置参数以及获取结果集
- 通过xml文件或者注解的方式将要执行的各种 statement 配置起来,并通过java对象和statement中sql的动态参数进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射为java对象并返回。(从执行sql到返回result的过程)。
#{}和${}的区别
- #{}是预编译,是一个占位符,${}是字符串替换,是拼接符
- Mybatis在处理#{}的时候会将sql中的#{}替换成?号,调用PreparedStatement来赋值
- Mybatis在处理 的 时 候 就 是 把 {}的时候就是把 的时候就是把{}替换成变量的值,调用Statement来赋值
- #{}的变量替换是在DBMS(数据库管理系统)中、变量替换后,#{}对应的变量自动加上单引号
- ${}的变量替换是在DBMS(数据库管理系统)外、变量替换后, ${}对应的变量不会加上单引号
- 使用#{}可以有效的防止sql注入,提高系统的安全性
当实体类中的属性名和表中的字段名不一样 ,怎么办 ?
- 通过在查询的sql语句中定义字段的别名(实体类为custName,数据库为name)
<select id=”selectUser” parametertype=”int” resultetype=”com.project.jiale.User”>
select id , name as custName from table where id = #{id}
</select>
- 添加关联映射
<select id="getOrder" parameterType="int" resultMap="orderresultmap">
select * from orders where order_id=#{id}
</select>
<resultMap type=”me.gacl.domain.order” id=”orderresultmap”>
<!–用id属性来映射主键字段–>
<id property=”id” column=”order_id”>
<!–用result属性来映射非主键字段,property为实体类属性名,column为数据表中的属性–>
<result property = “orderno” column =”order_no”/>
<result property=”price” column=”order_price” />
</reslutMap>
Mybatis是如何进行分页的?分页插件的原理是什么?
- Mybatis使用RowBounds对象进行分页,它是针对ResultSet结合集执行的内存分页,而非物理分页
- 可以在SQL内直接拼写带有物理分页的参数来完成物理分页的功能,也可以用分页插件来完成分页功能(如:SQL中的limit)
- 分页插件的原理是使用Mybatis提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的SQL,然后重写SQL,根据dialect方言,添加对应的物理分页语句和物理分页参数
Mybatis是否支持延迟加载?如果支持,它的实现原理是什么?
- Mybatis仅支持association多对一关联对象和collection一对多关联集合对象的延迟加载
- 在Mybatis配置文件中,可以配置是否启用延迟加载lazyLoadingEnabled=true|false。
- lazyLoadingEnabled:延迟加载的全局开关。当开启时,所有关联对象都会延迟加载
- aggressiveLazyLoading:当开启时,任何方法的调用都会加载该对象的所有属性。 否则,每个属性会按需加载,可通过association和collection中的fetchType属性设置当前的分步查询是否使用延迟加载,fetchType=“lazy(延迟加载)|eager(立即加载)”
- 原理:使用CGLIB创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,比如调用a.getB().getName(),拦截器invoke()方法发现a.getB()是null值,那么就会单独发送事先保存好的查询关联对象的sql,把B查询到,然后调用a.getB(),于是a的对象b属性就有了,接着完成a.getB().getName()方法的调用,这就是延迟加载的基本原理
Mybatis的缓存机制
一级缓存(默认开启)
- sqlsession级别的缓存,缓存的数据只在sqlsession内有效
- 一级缓存是基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为 Session,当 Session flush 或 close 之后,该 Session 中的所有 Cache 就将清空
- 一级缓存是sqlsession级别的缓存在操作数据库的时候需要先创建sqlsession会话对象,在对象中有一个HashMap用于存储缓存数据,次HashMap是当前会话对象私有的,别的sqlsession会话对象无法访问
- 一级缓存是基于sqlsession的缓存,一级缓存的内容不能跨越sqlsession。由mybatis自动维护。不同的sqlsession之间的缓存区域是相互不影响的
- 流程:每个sqlsession中持有一个executor,每个executor中有一个LocalCache。当用户发起查询的时候,Mybatis根据当前执行的语句生成MappedStatement,在LocalCache进行查询,如果命中缓存,直接返回结果,若没有,则查询数据库,将结果写入到LocalCache,最后返回给用户
- 如果sqlsession执行了DML(insert、update、delete)操作,并commit,那么mybatis会清空当前sqlsession的所有缓存数据,这样可以保证缓存中存的数据永远和数据库中一致,避免出现脏读
- 一个sqlsession结束后,一级缓存也就不存在了
- mybatis的缓存是基于[namespace:sql语句:参数]来进行缓存的。意思就是,SqlSession的HashMap存储缓存数据时,是使用[namespace:sql:参数]作为key,查询返回的语句作为value保存的。
二级缓存
- 二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap 存储,不同在于其存储作用域为 Mapper(Namespace),并且可自定义存储源,如 Ehcache。作用域为 namespance 是指对该 namespance 对应的配置文件中所有的 select 操作结果都缓存,这样不同线程之间就可以共用二级缓存。可实现多个sqlsession共享缓存
- 开启二级缓存后,会使用 CachingExecutor 装饰Executor,进入一级缓存的查询流程前,先在 CachingExecutor 进行二级缓存的查询
- 二级缓存可以设置返回的缓存对象策略
- 当readOnly=“true”时,表示二级缓存返回给所有的调用者同一个缓存对象实例
- 当 readOnly=“false”(默认)时,返回给调用者的是二级缓存总缓存对象的拷贝
- MyBatis 的二级缓存相对于一级缓存来说,实现了 SqlSession 之间缓存数据的共享,同时粒度更加细,能够到 namespace 级别,通过 Cache 接口实现类不同的组合,对 Cache 的可控性也更强。
- MyBatis 在多表查询时,极大可能会出现脏数据,有设计上的缺陷,安全使用二级缓存的条件比较苛刻
- 在分布式环境下,由于默认的 MyBatis Cache 实现都是基于本地的,分布式环境下必然会出现读取到脏数据,需要使用集中式缓存将 MyBatis 的 Cache 接口实现,有一定的开发成本,直接使用 Redis、Memcached 等分布式缓存可能成本更低,安全性也更高。
SpringBoot面试题
为什么要用SpringBoot
- 独立运行:SpringBoot内嵌了各种servlet容器,Tomcat,Jetty等,现在不需打成war包部署到容器中,只需要打成一个可执行的jar包就能独立运行,所有的依赖包都在一个jar包内
- 简化配置:spring-boot-starter-web启动容器自动依赖了其他组件,简化了maven的配置。
- 自动配置:SpringBoot能根据当前类路径下的类,jar包来自动配置bean,如添加一个spring-boot-starter-web启动器就能拥有web的功能,无需其他配置
- 无代码生成和xml配置:SpringBoot配置的过程中没有代码的生成,也无需xml配置文件就能完成所有的配置,这一切都是借助于条件注解完成的,这也是Spring4.X的核心功能之一
- 应用监控:SpringBoot提供一系列端点可以监控服务及应用,做健康检测
Spring Boot 的核心注解是哪个?它主要由哪几个注解组成的?
- 核心注解是启动类上面的注解@SpringBootApplication,主要包含了下面三个注解
- @SpringBootConfiguration:组合了@Configuration注解,实现配置文件的功能
- @EnableAutoConfiguration:打开自动配置的功能,也可以关闭某个自动配置的选项,如关闭数据源自动配置功能:@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
- @ComponentScan:Spring组件扫描
如何在Spring Boot启动的时候运行一些特定的代码?
- 如果你想在Spring Boot启动的时候运行一些特定的代码,你可以实现接口ApplicationRunner或者CommandLineRunner,这两个接口实现方式一样,它们都只提供了一个run方法。
- CommandLineRunner:启动获取命令行参数
Spring Boot自动装配原理
Mysql面试题
数据库的三范式是什么?
- 第一范式:列不可再分,每一列属性都是不可再分的属性值,确保列的原子性
- 第二范式:行可以唯一区分,非主键列要完全依赖于主键,而不能是依赖于主键的一部分
- 第三范式:非主键列只依赖于主键,不依赖于其他非主键
MySQL数据库引擎有哪些
- MYISAM:全表锁,拥有较高的执行速度,不支持事务,不支持外键,并发能力差,占用空间较小,对事务完整性没有要求,以select、insert为主的应用基本上可以使用这引擎
- innodb:行级锁,提供了具有提交、回滚和崩溃恢复能力的事务安全,支持自动增长列,支持外键约束,并发能力强,占用空间是MYISM的2.5倍,处理效率相对会差一些
- Memory:全表锁,存储在内容中,速度快,但会占用和数据量成正比的内存空间且数据在mysql重启的时候会丢失,默认使用hash索引,检索效率非常高,但不适用于精确查找,主要用于那些内容变化不频繁的代码表
- MERGE:是一组MYISAM表的组合
说说InnoDB与MyISAM的区别
- 在 MySQL 5.1 及之前的版本中,MyISAM 是默认的存储引擎,而在 MySQL 5.5 版本以后,默认使用 InnoDB 存储引擎。
- MyISAM 不支持行级锁,换句话说,MyISAM 会对整张表加锁,而不是针对行。同时,MyISAM 不支持事务和外键。MyISAM 可被压缩,存储空间较小,而且 MyISAM 在筛选大量数据时非常快。
- Innodb支持事务,MyISAM不支持,对于Innodb每一条sql语言都默认封装成事务,自动提交,这样会影响速度,所以我们最好把多条sql语言放在begin和commit之间,组成一个事务
- InnoDB支持外键,而MyISAM不支持。对一个包含外键的InnoDB表转为MYISAM会失败
- InnoDB是聚集索引,数据文件是和索引绑在一起的,必须要有主键,通过主键索引效率很高。但是辅助索引需要两次查询,先查询到主键,然后再通过主键查询到数据。因此,主键不应该过大,因为主键太大,其他索引也都会很大。而MyISAM是非聚集索引,数据文件是分离的,索引保存的是数据文件的指针。主键索引和辅助索引是独立的。
- InnoDB不保存表的具体行数,执行select count(*) from table时需要全表扫描。而MyISAM用一个变量保存了整个表的行数,执行上述语句时只需要读出该变量即可,速度很快;
- Innodb不支持全文索引,而MyISAM支持全文索引,查询效率上MyISAM要高;
数据库的事务
- 原子性:整个事务所有的操作,要么全部完成,要么全部不完成,不可能停滞在中间的某一个环节。 事务在执行过程中发生错误,会被回滚(Rollback)到事务开始之前的状态(undolog)
- 一致性:一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。(一致性是我们追求的根本,一致性的实现是由其他三个特点来保证的)
- 隔离性:隔离状态执行事务,使他们好像是在系统在给定时间内执行的唯一操作(MVCC锁)
- 持久性:在事务完成以后,该事务对数据库所做的更改便持久的保存在数据库中,并不会回滚(redolog)
SQL的优化手段
- 查询的时候尽量不要用select *
- 尽量减少子查询,用left join 、right join、inner join 代替
- 减少使用in或者not in,使用exists,not exists或者关联查询语句代替
- or的查询尽量使用union或者union all代替(在确认没有重复数据或者不用剔除重复数据的时候,union all会更好)
- 避免在where后面使用!=或者<>操作符,否则搜索将会放弃使用索引,从而全表扫描
- 避免在where后面进行表达式、函数和算数运算符操作
- 应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描
- 复合索引遵循最左匹配原则
- like查询%不能在前,因为无法使用索引,如果需要模糊匹配可以使用全文索引
简单说一说drop、delete与truncate的区别
- delete:属于数据库操作语言,表示删除表中的数据,执行删除的过程是每次从表中删除一行,并且将删除的操作记录作为事务记录在日志上,便于进行回滚操作。
- drop:属于数据库定义语言,表示删除表,也可用来删除数据库,当删除和重新创建表时,所有与之相关联的索引、完整性约束和触发器也被删除。同样,所有针对被删除表的授权也会被删除。
- truncate:属于数据库定义语言,表示删除表中所有数据,只能针对于表,执行速度快,删除表中所有的数据并且不可恢复,不影响与被删除的表相关联的任何结构、约束、触发器或者授权。
并发事务带来哪些问题
- 脏读(Dirty read): 当一个事务正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问了这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是“脏数据”,依据“脏数据”所做的操作可能是不正确的。
- 丢失修改(Lost to modify): 指在一个事务读取一个数据时,另外一个事务也访问了该数据,那么在第一个事务中修改了这个数据后,第二个事务也修改了这个数据。这样第一个事务内的修改结果就被丢失,因此称为丢失修改。 例如:事务1读取某表中的数据A=20,事务2也读取A=20,事务1修改A=A-1,事务2也修改A=A-1,最终结果A=19,事务1的修改被丢失。
- 不可重复读(Unrepeatableread): 指在一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。
- 幻读(Phantom read): 幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。
- 不可重复读的重点是修改比如多次读取一条记录发现其中某些列的值被修改,幻读的重点在于新增或者删除比如多次读取一条记录发现记录增多或减少了。
事务隔离级别有哪些?MySQL的默认隔离级别是?
- READ-UNCOMMITTED(读取未提交): 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。
- READ-COMMITTED(读取已提交): 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。
- REPEATABLE-READ(可重复读): 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。(mysql默认隔离级别)
- SERIALIZABLE(可串行化): 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。
MySQL 中一条查询 SQL 是如何执行的?
- 取得链接:使用到MySql的连接器
- 查询缓存:key为SQL语言,value为查询结果,查到就返回
- 分析器:分为词法分析和语法分析,此阶段做一些sql语法的解析和校验
- 优化器:表中存在多个索引的时候,决定使用哪个索引,或者一个语句存在多个表关联的时候,决定各个表的连接数据
- 执行器:通过分析器让 SQL 知道你要干啥,通过优化器知道该怎么做,于是开始执行语句。执行语句的时候还要判断是否具备此权限,没有权限就直接返回提示没有权限的错误;有权限则打开表,根据表的引擎定义,去使用这个引擎提供的接口,获取这个表的第一行,判断 id 是都等于 1。如果是,直接返回;如果不是继续调用引擎接口去下一行,重复相同的判断,直到取到这个表的最后一行,最后返回。
MySql的索引类型
- 主键索引:索引列中的值必须是唯一的,不允许有空值
- 普通索引:MySql中基本索引类型,没有什么限制,允许在定义索引的列中插入重复值和空值
- 唯一索引:索引列中的值必须是唯一的,但是允许为空值
- 全文索引:只能在文本类型char、varchar、text类型字段上面创建全文索引。字段长度比较大的时候,如果创建普通索引,在进行like模糊查询的时候效率比较低,这时候可以创建全文索引
- 空间索引:MySQL在5.7之后的版本支持了空间索引,而且支持OpenGIS几何数据模型。MySQL在空间索引这方面遵循OpenGIS几何数据模型规则。
- 前缀索引:在文本类型如CHAR,VARCHAR,TEXT类列上创建索引时,可以指定索引列的长度,但是数值类型不能指定。
- 组合索引:遵循最左匹配原则,一般情况下在条件允许的情况下使用组合索引替代多个单列索引使用。
什么是MVCC,可以解决什么问题?
- 多版本并发控制(MVCC):是一种来解决读写冲突的无锁并发控制。为事务分配单向增长的时间戳,为每个修改保存一个版本,版本和事务时间戳关联,读操作只读事务开始前的快照。这样在读操作不用阻塞写操作,写操作不用堵塞读操作的同时,避免了脏读和不可重复读
- MVCC 的目的就是多版本并发控制,在数据库中的实现,就是为了解决读写冲突,它的实现原理主要是依赖记录中的 3 个隐式字段、undo 日志、Read View 来实现的。
- 在RC隔离级别里,每次进行快照读操作的时候都会重新生成readview,所以每次可以查询到最新的结果记录,在RR隔离级别里,只有当前事务在第一次进行快照读的时候才会生成readview,之后进行的快照读操作都会沿用之前的readview
说一说MySql数据库中的锁
- 共享锁(读锁):一个事务对数据加了读锁后,其他事务只能对该数据加读锁,不能加写锁
- 排他锁(写锁):一个事务对数据加了写锁,其他事务不能对该数据加任何锁
- 表锁:系统开销最小,会锁定整张表,MyISAM 使用表锁。
- 行锁:容易出现死锁,发生冲突概率低,并发高,InnoDB 支持行锁(必须有索引才能实现,否则会自动锁全表,那么就不是行锁了)。
乐观锁和悲观锁
- 悲观锁:数据库被外界(包括本系统当前的其他事务以及来自外部系统的事务处理)修改保持着保守态度,因此在整个数据修改的过程中,将数据处于锁状态。悲观的实现往往是依靠数据库提供的锁机制,也只有数据库层面提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统汇总实现了加锁机制,也是没有办法保证系统不会被修改数据
- 在悲观锁的情况下,为了保证事务的隔离性,就需要一致性锁定读。读取数据时给加锁,其他事务无法修改这些数据。修改数据的时候也要加上锁,其他事务无法读取这些数据
- 乐观锁:相对于悲观锁,乐观锁机制采用了更加宽松的加锁机制,悲观锁大多数情况下依赖数据库的加锁机制实现,以保证操作最大程度的独占性,但随之而来的就是数据库性能的大量开销,特别是对长事务而言,乐观锁机制一定程度上解决了这个问题
- 乐观锁大多是基于数据版本(version)记录机制实现。
- 数据版本:为数据添加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表添加一个version字段来实现。读取出数据的时,将此版本号一同读出,之后更新时,对此版本号+1,此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行对比,如果提交的数据版本大于数据库表当前版本,则予以更新,否则认为是过期数据。
怎样尽量避免死锁的出现?
- 设置获取锁的超时时间,至少能保证在最差的情况下,可以退出程序,不至于一直等待导致死锁
- 设置按照同一顺序访问资源,类似于串行执行
- 避免事务中的用户交叉
- 保持事务简短并在一个批处理中
- 使用低隔离级别
- 使用绑定链接
redis面试题
reids有哪些好处
- 读取速度快,因为数据存储在内存中,所以读取的速度快
- 支持多种数据结构,包括字符串、列表、集合、有序集合、哈希等
- 支持事务,且遵守原子性
- 还拥有其他丰富的功能,如:队列、主从复制、集群、数据持久化等
redis和memcache
- redis和memcache都是将数据放在内存中,都是内存数据库。不过memcache还可以用于缓存其他东西,如:图片和视频等
- memcache仅支持key-value结构的数据类型,redis不仅仅支持简单的key-value类型,还提供list、set、hash等数据结构的存储
- 虚拟内存redis当物理内存用完时,可以将一些很久没用的value交换到磁盘
- 分布式–设定 Memcache 集群,利用 magent 做一主多从; Redis 可以做一主多从。都可以一主一从
- 存储数据安全memcache挂掉之后,数据没了,而redis可以定期保存到磁盘(持久化)
- memcache的单个value最大1m,redis的单个value最大512m
- memcache挂掉之后数据不可恢复,redis挂掉,数据可以通过aof恢复
- Redis 原生就支持集群模式, Redis3.0 版本中,官方便能支持Cluster模式了, Memcached 没有原生的集群模式,需要依赖客户端来实现,然后往集群中分片写入数据。
- Memcached 网络IO模型是多线程,非阻塞IO复用的网络模型,原型上接近于 nignx 。而 Redis使用单线程的IO复用模型,自己封装了一个简单的 AeEvent 事件处理框架,主要实现类epoll,kqueue 和 select ,更接近于Apache早期的模式。
为什么redis单线程模型相率也那么高
- 单线程操作会避免频繁的上下文切换,从而速度变快
- redis是存内存操作
- 基于非阻塞的IO复用模型机制
- Redis采用单线程模型,每条命令执行如果占用大量时间,会造成其他线程阻塞,对于Redis这种高性能服务是致命的,所以Redis是面向高速执行的数据库
说一说redis的线程模型
- redis的内部使用的是文件事件处理器(file event handler),这个文件事件处理器是单线程的,所以说redis是单线程模型。它采用IO多路复用机制同时监听多个socket,根据socket上的事件来选择对应的事件处理器进行处理
- 文件事件处理器的结构:多个socket、IO多路复用程序、文件事件分派器、事件处理器(连接应答处理器、命令请求处理器、命令回复处理器)
- 多个socket可能会产生不同的操作,每个操作对应不同的文件事件,但是IO多路复用程序会监听多个socket,会将socket产生的事件放入队列中排队,事件分派器每次从队列中取出一个事件,把改事件交给对应的事件处理器进行处理
redis一次通信的过程
- 客户端socket1想redis的server socket请求建立连接,此时server socket会产生一个AE_READABLE事件,IO多路复用程序监听到server socket产生的事件后,将该事件压入队列中,文件事件分派器从队列中获取该事件,交给连接应答处理器。连接应答处理器会创建一个能与客户端通信的socket1,并将该socket1的AE_READABLE事件与命令请求处理器关联
- 假设此时客户端发送了一个set key value请求,此时redis中的socket1会产生AE_READABLE事件,IO 多路复用程序将事件压入队列,此时事件分派器从队列中获取到该事件,由于前面 socket1 的 AE_READABLE 事件已经与命令请求处理器关联,因此事件分派器将事件交给命令请求处理器来处理。命令请求处理器读取 socket1 的 set key value 并在自己内存中完成 set key value 的设置。操作完成后,它会将 socket1 的 AE_WRITABLE 事件与命令回复处理器关联。
- 如果此时客户端准备好接收返回结果了,那么 redis 中的 socket1 会产生一个AE_WRITABLE 事件,同样压入队列中,事件分派器找到相关联的命令回复处理器,由命令回复处理器对 socket1 输入本次操作的一个结果,比如 ok ,之后解除 socket1 的AE_WRITABLE 事件与命令回复处理器的关联。
说一说redis的优点和缺点
- 优点
- 速度快,因为数据都是存在内存里面的,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1)
- 支持丰富的数据结构:支持String、List、Set、Sorted Set、Hash五种基础的数据结构
- 持久化存储:Redis提供RDB和AOF两种数据的持久化方案,解决内存数据库最担心的就是万一redis挂掉,数据会消失掉
- 高可用:内置redis sentinel,提供高可用方案,实现主从故障自动转移。内置RedisCluster,提供集群方案,实现基于槽的分片方案,从而支持更大的redis规模
- 丰富的特性:Key过期、计数、分布式锁、消息队列等
- 缺点
- 由于redis是内存数据库,所以单台机器,存储的数据量跟机器本身的内存大小相关,虽然redis本身有key过期策略,但是还是提前预估和节约内存。如果内存增长过快,需要定期删除策略
- 如果进行完整重同步,由于需要生成RDB文件,并进行文件传输,会占用主机的CPU,并会消耗现网的宽带。不过redis2.8版本,已经有了部分重同步的功能,但是还是有可能有完整重同步的。比如,新上线的备机
- 修改配置文件,进行重启, 将硬盘中的数据加载进内存,时间比较久,在这个过程中,reids不能提服务
redis缓存刷新策略有哪些?
- LRU/LFU/FIFO算法剔除:当redis memory达到最大值的时候,首先关注的是过期的数据,通过删除策略来达到保护内存的效果,这种方式只要关注缓存的策略配置,不需要关心具体的每一个key到底是怎么过期的,每一个key是到底怎样被处理的
- 超时剔除:设置过期时间
- 主动更新:开发控制生命周期
redis的持久化机制
- RDB持久化方式
- 用数据集快照的方式半持久化模式记录redis的数据库的所有键值对,在某个时间点将数据写入到一个临时文件,持久化结束后,用这个临时文件替换上次持久化的文件,达到数据恢复
- 还有一个文件dump.rdb,方便持久化
- 容灾性好,一个文件可以保存到安全的磁盘
- 性能最大化,fork子进程来完成写操作,让主进程继续处理命令,所以是IO最大化。使用单独子进程来进行持久化,主进程不会进行任何IO操作,保证了redis的高性能
- 相对于数据集大时,比AOF的启动效率更高
- 数据安全性低,RDB是隔一段时间进行持久化,如果持久化之间redis发生故障,会发生数据丢失。所以这种方式更适合数据要求不严谨额时候
- AOF(Append-only)持久化方式
- 是指所有的命令记录以redis命令请求协议的格式完全持久化存储,保存为AOF文件
- 数据安全,AOF持久化可以配置appendfsync属性,有always,没进行一次命令操作就记录到AOF文件中
- 通过append模式写文件,即使中途服务器宕机,可以通过redis-check-aof工具解决数据一致性问题
- AOF机制的rewrite模式。AOF文件没被rewrite之前(文件过大时会对命令进行合并重写),可以删除其中的某些命令(比如误操作的flushall)
- AOF文件比RDB文件大,且恢复速度慢
- 数据集大的时候,比RDB启动效率低
持久化有两种,我们改如何进行选择呢?
- 不要仅仅只适用RDB,这样会丢失很多数据
- 也不能仅仅适用AOF,会产生两个问题,通过AOF做冷备没有RDB做冷备额恢复速度更快,RDB每次简单粗暴生成数据快照,更加健壮,可以避免AOF这种复杂的备份和恢复机制的BUG
- redis支持同时开启两种持久化方式,我们可以综合使用AOF和RDB两种持久化机制,用AOF来保证文件的不丢失,作为数据恢复的第一选择,用RDB来做不同程度的冷备,在AOF文件都丢失或损坏不可用的时候,还可以使用RDB来进行快速的数据恢复
- 如果同时使用RDB和AOF两种持久化机制,那么在redis重启的时候,会使用AOF来重新构建数据,因为AOF中的数据更加完整
说说你对redis事务的理解
- redis中的事务是一组命令的集合,是redis的最 小执行单元。他可以保证一次执行多条命令。每个事务是一个单独的隔离操作,事务中的所有命令都会序列化、按顺序的执行。服务端在执行事务的过程中,不会被其他客户端发送来的命令请求打断
- 他的原理是先将属于一个事务的命令发送给redis,然后依次执行这些命令
- redis的事务是不支持回滚的,但是执行的命令有语法错误,redis会执行失败,这些问题可以从程序层面捕获并解决。但是如果出现其他问题,则依然会执行余下的命令。这样做的原因是因为回滚需要增加很多工作,而不支持回滚则可以保持简单、快速的特性
- redis服务端在执行事务的过程中,不会其他客户端发送来的命令请求打断。直到事务命令全部执行完毕才会执行其他客户端命令
熟悉哪些 Redis 集群模式?
- Redis Sentinel:体量较小时,选择 Redis Sentinel ,单主 Redis 足以支撑业务。
- Redis Cluster:Redis 官方提供的集群化方案,体量较大时,选择 Redis Cluster ,通过分片,使用更多内存
- Twemprox:Twemprox 是 Twtter 开源的一个 Redis 和 Memcached 代理服务器,主要用于管理 Redis 和Memcached 集群,减少与Cache 服务器直接连接的数量
- Codis:Codis 是一个代理中间件,当客户端向 Codis 发送指令时, Codis 负责将指令转发到后面的Redis 来执行,并将结果返回给客户端。一个 Codis 实例可以连接多个 Redis 实例,也可以启动多个 Codis 实例来支撑,每个 Codis 节点都是对等的,这样可以增加整体的 QPS 需求,还能起到容灾功能。
- 客户端分片:在 Redis Cluster 还没出现之前使用较多,现在基本很少热你使用了,在业务代码层实现,起几个毫无关联的 Redis 实例,在代码层,对 Key 进行 hash 计算,然后去对应的 Redis 实例操作数据。这种方式对 hash 层代码要求比较高,考虑部分包括,节点失效后的替代算法方案,数据震荡后的自动脚本恢复,实例的监控,等等。
假如 Redis 里面有 1 亿个 key,其中有 10w 个 key 是以某个固定的已知的前缀开头的,如果将它们全部找出来?
- keys命令
- 直接使用keys命令进行查询,这个命令有个缺点,keys命令是遍历查询的,查询的时间复杂度是O(n),数据量越大查询时间越长。而且redis单线程,keys指令会导致线程阻塞一段时间,会导致线上的redis停顿一段时间,直到keys执行完毕才能恢复。这在生产环境是不允许的。除此之外,需要注意的是,这个命令是没有分页功能的会一次性查出所有符合条件的key值,会发现查询结果非常大,输出的信息非常多。所以不推荐使用这个命令
- scan命令
- scan命令可以实现和keys一样的匹配功能,但是scan命令在执行的过程中不会阻塞线程,并且查找的数据可能存在重复,需要客户端操作去重。因为scan是通过游标方式查询的,所以不会导致redis出现假死的问题。redis查询的过程中会把游标返回给客户端,单次返回空值且游标不为0,则说明遍历没有结束,客户端继续遍历。scan在检索的过程中,被删除的元素是不会查询出来的,但是在迭代的过程中有元素被修改,scan不能保证查询出对应元素。相对来说,scan指令查找花费的时间会比keys指令长
缓存和数据库谁先更新
- 写请求过来,将写请求缓存到缓存队列中,并且开始执行写请求的具体操作(删除缓存中的数据,更新数据库,更新缓存)
- 如果在更新数据库的过程中,又来了个读请求,将读请求再次存入到缓存队列中(可以搞个n队列,采用key的hash值进行队列个数取模hash%n,落到对应的队列中,队列需要保证顺序性)顺序性保证等待队列前的写请求执行完成,才会执行读请求之前的写请求删除缓存失败,直接返回,此时数据库中的数据是旧值,并且与缓存中的数据是一致的,不会出现数据一致性问题
- 写请求删除缓存成功,则更新数据库,如果更新失败,则直接返回,写请求结束,此时数据库中的值依旧是旧值,读请求过来,发现缓存中没有数据,则会直接想数据库请求,同时将数据写到缓存中,此时也不会出现数据一致性的问题
- 更新数据成功之后,再更新缓存,如果此时更新缓存失败,则缓存中没有数据,数据库中是新值,写请求结束,此时读请求还是一样的,发现缓存中没有数据,同样会从数据库中读取,并存到缓存中,其实这里不管更新缓存失败还是成功,都不会出现数据一致性的问题
缓存雪崩、缓存穿透、缓存预热、缓存更新、缓存降级等问题
- 缓存雪崩:我们可以简单的理解为:由于原有的缓存失效,新缓存未到期间(例如:我们设置缓存时采用了相同的过期时间,在同一时刻出现大面积的缓存时期)所有原本应该访问缓存的请求都去访问数据库了,对数据库和CPU的内存造成巨大压力,严重会造成数据库宕机,从而形成一系列反应,造成整个系统崩溃。解决办法:大多数系统设计者考虑用加锁(最多的解决办法)或者队列的方式来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上。还有一个简单的方法就是将缓存失效时间分散开。
- 缓存穿透:缓存穿透是指用户查询数据,数据库中没有,缓存中也没有,这就导致用户查询的时候,先在缓存中查找返回null,再去数据库中查找返回null(相当于进行了两次无用的查询)。解决办法:最常见的是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被bitmap拦截掉,从而避免了对底层存储系统的查询压力。另外也有一个更为简单粗暴的方法,如果一个查询返回的数据是空(不管是数据不存在,还是故障),我们仍然把这个空结果进行缓存,但是他的过期时间很短,最长不超过五分钟。通过这个直接设置的默认值存放到缓存,这样第二次到缓存中获取就有值了,而不会继续访问数据库,这种方法最简单直接
- 缓存预热:缓存预热就是系统上线时,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题
- 缓存更新:除了缓存服务器自带的缓存失效策略外(redis默认有6个策略可供选择),我们还可以根据具体的业务需求进行自定义的缓存淘汰,常见的策略有两种①定时清理过期的缓存②当有用户请求过来是时,在判断这个请求用到的缓存是否过期,过期的话就去底层系统得到新的数据并更新缓存。两种各有优劣,第一个的缺点是维护大量缓存的keyhi比较麻烦的,第二个缺点是每次用户请求过来都要判断缓存失效,逻辑比较复杂。
- 缓存降级:当访问量剧增,服务器出现问题(如响应时间慢或者不响应)或非核心服务影响到核心流程的性能时,仍然要保证服务是可用的,即使是有损服务。系统可以根据一些关键数据进行自动降级,也可以配置开关进行人工降级。降级的最终目的是保证核心业务是可用的,即使是有损的。而有些服务是无法降级的(如:购物车、结算)以参考日志级别设置预案: (1)一般:比如有些服务偶尔因为网络抖动或者服务正在上线而超时,可以自动降级; (2)警告:有些服务在一段时间内成功率有波动(如在95~100%之间),可以自动降级或人工降级,并发送告警; (3)错误:比如可用率低于90%,或者数据库连接池被打爆了,或者访问量突然猛增到系统能承受的最大阀值,此时可以根据情况自动降级或者人工降级; (4)严重错误:比如因为特殊原因数据错误了,此时需要紧急人工降级。服务降级的目的:为了防止redis服务故障,导致数据库跟着一起发生雪崩问题,因此,对于不重要的缓存数据,可以采用服务降级策略,例如一个常见的做法,redis出现问题,不去数据库查询,而是直接返回默认值给用户
redis的数据类型,以及使用场景
- String:常规的get/set操作,value可以是String也可以是数字,一般做一些较为复杂的计数功能缓存
- hash:value存放的是结构化对象,比较方便的就是操作中的某个字段
- list:使用list的数据结构,可以做简单的消息队列功能,可以使用range命令,做基于redis的分页功能,性能极佳,用户体验好
- set:因为set存放的是一堆不重复值的集合,所以可以用来全局去重
- sorted set:比set多了一个权重参数,集合中的元素能够按sorted进行排列。可以做排行榜应用,取TOP N操作。
redis的过期策略以及内存淘汰机制
- redis采用的是定期删除+惰性删除策略
- 定期删除,redis默认每隔100ms检查,是否有过期的key,有过期的key则删除(redis不是每过100ms将所有的key检查一遍,而是随机抽取进行检查),如果我们只采用定期删除策略,会导致很多key到时间没有被删除。
- 惰性删除:在获取某个key的时候,redis会检查一遍,这个key如果设置了过期时间,那么判断是否过期了,过期了就删除
- 如果定期删除没有删除key,也没有请求key,说明惰性删除没有生效,那么redis的内存会越来越高,那么就应该采用内存淘汰机制(redis.conf有一行配置:maxmemory-policy volatile-lru)
- 内存淘汰机制
- volatile-lru:从已设置过期时间的数据集中挑选最近最少使用的数据淘汰
- volatile-ttl:从已设置过期时间的数据集中挑选将要过期的数据淘汰
- volatile-random:从已设置过期时间的数据集中挑选任意数据淘汰
- allkeys-lru:从数据集中挑选最近最少使用的数据淘汰
- allkeys-random:从数据集中挑选随机的数据进行淘汰
- no-enviction(驱逐):禁止驱逐数据,新写入操作会报错
Nginx面试题
简述一下什么是Nginx,它有什么优势和功能?
- Nginx是一个web服务器和反向代理服务器
- nginx运行速度较快:一方面, 正常情况下,单次请求会得到更快的响应;另一方面,在高峰期(如有数以万计的并发请求),Nginx可以比其他web服务器更快的响应请求
- 高扩展性,跨平台:Nginx的设计极具扩展性,它完全是由多个不同功能、不同层次、不同类型且耦合度极低的模块组成。因此,当对某一个模块修复Bug或进行升级时,可以专注于模块自身,无须在意其他。而且在HTTP模块中,还设计了HTTP过滤器模块:一个正常的HTTP模块在处理完请求后,会有一串HTTP过滤器模块对请求的结果进行再处理。这样,当我们开发一个新的HTTP模块时,不但可以使用诸如HTTP核心模块、events模块、log模块等不同层次或者不同类型的模块,还可以原封不动地复用大量已有的HTTP过滤器模块。这种低耦合度的优秀设计,造就了Nginx庞大的第三方模块,当然,公开的第三方模块也如官方发布的模块一样容易使用。 Nginx的模块都是嵌入到二进制文件中执行的,无论官方发布的模块还是第三方模块都是如此。这使得第三方模块一样具备极其优秀的性能,充分利用Nginx的高并发特性,因此,许多高流量的网站都倾向于开发符合自己业务特性的定制模块。
- 高可靠性:用于反向代理,宕机的可能性微乎其微,高可靠性是我们选择Nginx的最基本条件,因为Nginx的可靠性是大家有目共睹的,很多家高流量网站都在核心服务器上大规模使用Nginx。Nginx的高可靠性来自于其核心框架代码的优秀设计、模块设计的简单性;另外,官方提供的常用模块都非常稳定,每个worker进程相对独立,master进程在1个worker进程出错时可以快速“拉起”新的worker子进程提供服务
- 低内存消耗:一般情况下,10 000个非活跃的HTTP Keep-Alive连接在Nginx中仅消耗2.5MB的内存,这是Nginx支持高并发连接的基础。
- 单机支持10万以上的并发连接 :这是一个非常重要的特性!随着互联网的迅猛发展和互联网用户数量的成倍增长,各大公司、网站都需要应付海量并发请求,一个能够在峰值期顶住10万以上并发请求的Server,无疑会得到大家的青睐。理论上,Nginx支持的并发连接上限取决于内存,10万远未封顶。当然,能够及时地处理更多的并发请求,是与业务特点紧密相关的。
- 热部署:master管理进程与worker工作进程的分离设计,使得Nginx能够提供热部署功能,即可以在7×24小时不间断服务的前提下,升级Nginx的可执行文件。当然,它也支持不停止服务就更新配置项、更换日志文件等功能。
- 最自由的BSD许可协议:这是Nginx可以快速发展的强大动力。BSD许可协议不只是允许用户免费使用Nginx,它还允许用户在自己的项目中直接使用或修改Nginx源码,然后发布。这吸引了无数开发者继续为Nginx贡献自己的智慧。 以上7个特点当然不是Nginx的全部,拥有无数个官方功能模块、第三方功能模块使得Nginx能够满足绝大部分应用场景,这些功能模块间可以叠加以实现更加强大、复杂的功能,有些模块还支持Nginx与Perl、Lua等脚本语言集成工作,大大提高了开发效率。这些特点促使用户在寻找一个Web服务器时更多考虑Nginx。 选择Nginx的核心理由还是它能在支持高并发请求的同时保持高效的服务。
Nginx如何处理一个HTTP请求的?
- 结合多进程机制和异步机制,异步机制使用的是异步非阻塞方式。
- 多进程机制:服务器每收到客户端时,就有服务器主进程(master process)生成一个子进程(worker process)出来和客户端建立连接进行交互,直到连接断开,该子进程就结束了。使用进程的好处是各个进程之间相互独立,不需要加锁,减少了使用锁对性能造成影响,同时降低了编程的复杂度,降低开发成本。其次,采用独立的线程,可以让进程相互之间不会影响,如果一个进程发生异常退出时,其他进程正常工作,master进程则会很快的启动新的进程,确保服务会中断,从而将风险降到最低。
- 异步非阻塞机制:每个工作进程使用异步非阻塞方式,可以处理多个客户端请求。当某个工作进程接收到客户端的请求后,调用IO进行处理,如果不能及时的得到结果,就去处理其他请求(即为非阻塞);而客户端在此期间也无需等待响应,可以去处理其他事情(异步)。当IO返回时,就会通知次工作进程,该进程得到通知,暂时挂起当前处理的事务去响应客户端的请求。
在Nginx中,如何使用未定义的服务器名称来阻止处理请求?
Server{
listen 80;
server_name "";
return 444;
}
- 这里服务器名被保存为一个空字符串,它将在没有“主机”头字段的情况下匹配请求,而一个特殊的Nginx的非标准代码444被返回,从而终止连接
- 一般推荐worker进程数与CPU内核保持一致,这样一来不会存在大量的子进程生成和管理任务,避免了进程之间竞争CPU资源和进程切换开销。而且Nginx为了更好的利用多核特性,提供了CPU亲缘性的绑定选项,我们可以将某一个进程绑在某一个核上,这样就不会因为进程的切换带来Cache的失效
- 对于每个请求,有且只有一个工作进程对其处理。首先,每个worker进程都是从master中fork过来的,在master进程中,先建立好listen的socket(listenfd)之后,然后再fork出多个worker进程
- 所有worker进程的listenfd会在新连接到来时变的可读,为保证只有一个进程处理该连接,所有worker进程在注册listenfd读事件之前抢占accept_mutex,抢到互斥锁的那个进程注册listenfd读事件,在读事件里调用accept接受该连接
- 当一个worker进程在accept这个连接后,就开始读取请求、解析请求、处理请求,产生数据后,再返回给客户端,最后才断开连接。这样一个完成的请求就是这样的了。我们可以看到。一个请求。完全由worker进程来处理,而且只在一个worker进程中处理
- 在nginx服务器运行的过程中,主进程和工作进程需要进行交互。交互依赖于socket实现的管道来实现
Nginx服务器上的Master和Worker进程分别是什么?
- 主程序master process启动后,通过一个for循环来接收和处理外部信号
- 主进程通过fork()函数产生worker子进程,每个子进程执行一个for循环来实现nginx服务器对事件的接收和处理
正向代理和反向代理
- 正向代理: 如果把局域网外的 Internet 想象成一个巨大的资源库,则局域网中的客户端要访问 Internet,则需要通过代理服务器来访问,这种代理服务就称为正向代理
- 反向代理:,其实客户端对代理是无感知的,因为客户端不需要任何配置就可以访问,我们只需要将请求发送到反向代理服务器,由反向代理服务器去选择目标服务器获取数据后,在返回给客户端,此时反向代理服务器和目标服务器对外就是一个服务器,暴露的是代理服务器地址,隐藏了真实服务器 IP 地址。
Dubbo面试题
说一说Dubbo服务请求流程
注册中心挂了,consumer还能不能获取到provider?
- 可以,因为刚开始初始化的时候,consumer会将需要的所有提供者的地址等信息拉取到本地缓存,所以注册中心挂了可以继续通信。但是provider挂了就没法调用了
- consumer本地缓存服务列表
Dubbo负载均衡的策略?
- 随机、轮询、活跃度、一致性hash
Dubbo容错率?
- failover cluster 模式:provider 宕机重试以后,请求会分到其他的 provider 上,默认两次,可以手动设置重试次数,建议把写操作重试次数设置成 0。
- failback 模式:失败自动恢复会在调用失败后,返回一个空结果给服务消费者。并通过定时任务对失败的调用进行重试,适合执行消息通知等操作。
- failfast cluster 模式:快速失败只会进行一次调用,失败后立即抛出异常。适用于幂等操作、写操作,类似于 failovercluster 模式中重试次数设置为 0 的情况。
- failsafe cluster 模式:失败安全是指,当调用过程中出现异常时,仅会打印异常,而不会抛出异常。适用于写入审计日志等操作。
- forking cluster 模式:并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过 forks=“2” 来设置最大并行数
- broadcacst cluster 模式:广播调用所有提供者,逐个调用,任意一台报错则报错。通常用于通知所有提供者更新缓存或日志等本地资源信息。