1、JDK、JRE、JVM区别和联系
jvm:java虚拟机。它只认识 xxx.class 这种类型的文件,它能够将 class 文件中的字节码指令进行识别并调用操作系统向上的 API 完成动作。所以说,jvm 是 Java 能够跨平台的核心
jre:运行环境。它主要包含两个部分,jvm 的标准实现和 Java 的一些基本类库。它相对于 jvm 来说,多出来的是一部分的 Java 类库。
jdk:开发工具包。包含jre和jvm。
2、==和equals的区别
==:用于比较引用和比较基本数据类型时具有不同的功能。
equal:用来检测两个对象是否相等,即两个对象的内容是否相等。
3、final
被final修饰的变量不能被改变,被final修饰的类不能被继承,被final修饰的方法不能被重写。
4、String、StringBuffer、StringBuild
String:是一个不可变字符串,底层使用final修饰
- 在修改字符串操作比较多的时候用StringBuilder或StringBuffer.
- 在要求线程安全的情况下用StringBuffer
- 在不要求线程安全的情况下用StringBuilder
5、重载和重写的区别
- 重载:方法名一致,参数列表不同,参数类型不同。与返回值无关
- 重写:方法名一致,参数列表一致,返回类型一致,存在子类中。
6、接口和抽象类
- 抽象类:只能单继承。可以存在普通成员函数。成员变量可以是多种类型的。
- 接口:多实现。只能只存在public abstract方法。只能是 publi static final类型的。
7、list和set的区别
- list:有序可重复。按对象进入顺序保存对象,允许多个Null元素对象,可以用iteratiterator遍历取出所有元素。在逐一遍历,还可以使用get(int index)获取指定下标元素。
- set:无序不可重复最多允许一个Null元素对象,取元素时只能通过iteratoriterator取得所有元素,在逐一遍历各个元素。
8、hashCode和equals
- 两者都是比较对象
- equals比较,比较的比较全面、比较复杂,性能低,所以加上hashCode取得hash码更加快速,准确。
- 直接使用hashCode来比较的话,不同的对象可能会拥有相同的hash码,所以比较的结果不是那么的准确。
9、ArrayList和linkedList区别
- ArrayList基于数组实现,连续存储在内存中。从中间插入元素比linkedList慢,因为他会将其之后的元素复制,再写入。从末尾添加就不会比linkedList慢~
- linkedList基于链表实现,分散在内存中。
10、HashMap和HashTable的区别
- HashMap:非线程安全的,允许key和value为null。
- 底层基于链表和数组实现。java8开始链表高度达到8,数组长度超过64,链表转化为红黑树,元素以内部类Node节点存在。
- key为nul,存在下标0的位置。
- HashTable:线程安全的,每一个方法synchronized修饰。不允许key和value为null
11、ConcurrentHashMap原理,jdk7和jdk8版本的区别
jdk1.7
- 数据结构:ReentrantLock+Segment+HashEntity,一个Segment中包含一个HashEntity数组,每个HashEntity又是一个链表结构
jdk1.8
- synchronized+CAS(乐观锁)+Node+红黑树,Node的val和next都用volatile修饰,保证可见性,查找、替换、赋值操作都是使用CAS
12、如何实现一个IOC容器
- 配置文件配置包扫描路径
- 递归包扫描获取.class文件
- 反射、确定需要交给ioc管理的类
- 对需要注入的类进行依赖注入
13、java类加载器有哪些
- 根加载器,bootstraClassLoader
- 扩展加载器:ExtenuationClassLoader
- 应用程序加载器:AppClassLoader
- 自定义加载:比如阿里巴巴觉得JDK8不好用,自己编写一个alibbaJDK.(高端定制)
14、什么叫双亲委派机制?
- 类加载器去加载类的时候,从上往下加载,加载到了类,后边的就不加载了。
双亲委派机制的作用就是保证程序的安全。
15、什么是字节码?
.class文件中的二进制数据就是字节码。
代{过}{滤}理类要实现的接口是MethodInterceptor
而且需要一个增强剂的类(Enhancer)
16、JAVA中异常体系
java中的所有异常都来自顶级父类Throwable。Throwble下有两个子类Exception和Error
- Exeception:不会导致程序终止。分为runTimeExeception和CheckedExeception检查异常。
- Error:表示程序无法处理的错误,一旦发生错误,将停止程序运行。如内存溢出
17、Gc如何判断对象可以被回收
- 强引用:就算内存不足也不会被回收
- 弱引用:内存不足时,发现就会被回收
- 软引用:发现就会被回收
- 虚引用:形同虚设,随时都会被回收
18、线程的生命周期,线程有哪些状态
- 线程通常有五种状态:
- 阻塞的情况又分为三种
- 等待阻塞:运行线程执行wait()方法会使线程处于等待状态,不能被自动唤醒,必须依靠其它线程调用notifyAll()方法
- 同步阻塞:运行线程在获取对象的同步锁时,若该同步被其他线程占用,则JVM会把线程放入“锁池”中
- 其他阻塞:运行的线程执行sleep或join方法,或者发出I/O请求时,JVM会把该线程置为阻塞状态。当sleep状态超时,join等待线程终止或者超时,或者I/O处理完毕,线程会重新进入就绪状态。
19、sleep()、wait()、join()、yield()的区别
- sleep:sleep 方法是属于 Thread类中的,sleep 过程中线程不会释放锁,只会阻塞线程,让出cpu给其他线程,但是他的监控状态依然保持着,当指定的时间到了又会自动恢复运行状态,可中断,sleep 给其他线程运行机会时不考虑线程的优先级,因此会给低优先级的线程以运行的机会
- wait:属于 Object 类中的,wait 过程中线程会释放对象锁,只有当其他线程调用 notify 才能唤醒此线程。wait 使用时必须先获取对象锁,即必须在 synchronized 修饰的代码块中使用,那么相应的 notify 方法同样必须在 synchronized 修饰的代码块中使用,如果没有在synchronized 修饰的代码块中使用时运行时会抛出IllegalMonitorStateException的异常
- join:等待调用join方法的线程结束之后,程序再继续执行,一般用于等待异步线程执行完结果之后才能继续运行的场景。例如:主线程创建并启动了子线程,如果子线程中要进行大量耗时运算计算某个数据值,而主线程要取得这个数据值才能运行,这时就要用到 join 方法了
- yield和 sleep 一样都是 Thread 类的方法,都是暂停当前正在执行的线程对象,不会释放资源锁,和 sleep 不同的是 yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。还有一点和 sleep 不同的是 yield 方法只能使同优先级或更高优先级的线程有执行的机会
20、线程安全的理解
多个线程的结果与单线程的结果一致,或者说是多线程运行后的结果是预期的结果
21、Thread、Runable的区别
Thread和Runable实际是继承关系,操作复杂线程推荐使用thread,如果只是执行一个简单线程建议使用runable
22、ThreadLocal的原理和使用场景
通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。而使用ThreadLocal创建的变量只能被当前线程访问,其它线程则无法访问及修改。
Thread.currentThread():获取当前线程的引用,既代码段正在被哪一个线程调用。
- ThreadLocal的实现原理就是ThreadLocal里面有一个静态的ThreadLocalMap
- ThreadLocalMap是ThreadLocal里面的一个静态类
- ThreadLocal的值是放入了当前线程的一个ThreadLocalMap实例中,所以只能在本线程中访问,其他线程无法访问。
ThreadLocal类的应用场景:承载一些线程相关的数据
因为javaee三层架构里面,如果使用事务,要在service层里面进行开启事务(因为处理逻辑业务都是service层),然后又因为如果我们要使用事务,那么就必须保证执行sql语句的connection连接和开启事务的connection连接都要保持是同一个对象,所以我们要确保在service层和dao层的两个connection连接都是同一个,但是怎么保证connection连接对象都是同一个呢
据,service层处理业务),connection连接对象我们应该是在service层出现的,但是你却放到了dao层,这样数据的处理和逻辑业务的处理没有分离开来,javaee的三层开发就没有他的效果了,所以这一种方式的解决方法不好,所以我们就通过ThreadLocal的方式来存储这个connection对象,这样就能够保证在service层和dao层的数据保证一致了
23、并发、并行、串行的区别
- 串行:在时间上不可能发生重叠,前一个任务没弄完,下一个任务就只能等待
- 并行:在时间上是重叠的,两个任务在同一时刻互不干扰的同时执行
- 并发:允许两个任务彼此干扰。同一时间点,只有一个任务运行,交替执行
24、并发的三大特性
- 原子性
- 不可中断,要么全部执行,要么全部不执行
- 关键字:synchronized
- 可见性
- 对其他线程的可见性,a修改了线程的值,其它线程也能发现
- 关键字:synchronized、volatile、final
- 有序性
- 虚拟机在编译时,对于那些改变顺序之后不会对最终结果造成影响的代码,虚拟机不会按照我们期望的循序执行,有可能对他们重新排序,实际上,对于那些代码重新排序之后,虽然对变量的值没有造成影响,但是,可能会出现线程安全问题
- 关键字:synchronized、volatile
25、为什么用线程池?解释下线程池的参数?
26、线程池中线程复用原理
首先线程池会有一个管理任务的队列,这个任务队列里就是存放着各种任务,线程池会一直不停的循环的去查看消息队列里有没有接到任务,如果没有,则继续循环,如果有了则开始创建线程。
27、Spring是什么
- Spring是一个开源框架
- Spring为简化企业应用开发而生
28、谈谈你对AOP的理解
切面编程,解耦合,不破坏代码结构
29、谈谈你对IOC的理解
控制反转,传统是new一个对象,而用了spring交给spring去管理,创建对象。
第一步:编写applicationContext.xml文件,固定的,不需要自己创建。找个项复制来就行。
第二步:在此文件中配置想交给spring容器管理的对象实例。
第三步:加载applicationContext.xml文件,得到applicationContext对象实例,
第四步:调用getBean方法拿到配置的实例对象。
30、BeanFactory和ApplicationContext有什么区别
BeanFactory:在启动中不会实例化Bean,在拿取的时候才会。
ApplicationContext:启东时就会实例化。
31、springBean生命周期
- Spring容器根据配置中的bean定义实例化bean。
- Spring使用依赖注入填充所有属性,如bean中所定义的配置。
- 如果bean实现BeanNameAware接口,则工厂通过传递自身的实例来调用setBeanName()
- 如果bean实现了BeanFactoryAware接口,工厂传递自身的实例来调用setBeanFactory()
- 如果存在与bean关联的任何BeanPostProcessors,则调用preProcessBeforeiniyialization()方法
- 如果bean指定了init方法(<bean>的init-method属性),那么将调用他。
- 如果存在与bean关联的任何BeanPostProcessors,则将调用postProcessAfterinitialization()方法
- 如果bean实现DisposableBean接口,当Spring容器关闭时,会调用destory().
- 如果为bean指定了destroy()方法(<bean>的destroy-method 属性),那么将调用它。
32、spring benan的作用域
- Singleton:每个spring ioc容器仅有一个单实例
- prototype:每次请求都会产生一个新的实例
- Request:每次HTTP请求都会产生一个新的实例,且该bean仅在当前Http请求有效
- session:每次HTTP请求都会产生新的bean,且只在当前Http Session有效
- global-session:在一个全局的Http Session中一个bean对应一个实例。(该作用域仅在基于web的Spring ApplicationContext情形下有效)
33、Spring框架中的单例Bean是线程安全的吗?
不是,Spring框架并没有对单例bean进行任何多线程的封装处理。关于单例bean的线程安全和并发问题需要开发者自行去搞定。但实际上,大部分的Spring bean并没有可变的状态(比如Serview类和DAO类),所以在某种程度上说Spring的单例bean是线程安全的。如果你的bean有多种状态的话(比如 View Model 对象),就需要自行保证线程安全。最浅显的解决办法就是将多态bean的作用域由“singleton”变更为“prototype”。
34、Spring框架中都用到了那些设计模式
- 代{过}{滤}理模式—在AOP和remoting中被用的比较多。
- 单例模式—在spring配置文件中定义的bean默认为单例模式。
- 模板方法—用来解决代码重复的问题。
- 工厂模式—BeanFactory用来创建对象的实例。
- 适配器--spring aop
- 装饰器--spring data hashmapper
- 观察者-- spring 时间驱动模型
- 回调--Spring ResourceLoaderAware回调接口
35、事务的并发问题
- 脏读:一个事务读取到了另一个事物还未提交的数据
- 虚度:一个事物多次读取同一行数据,读到了不同的结果
- 幻读:一个事务多次读取同一张表,读到了不同的行数
36、Spring事务的实现方式以及隔离级别
实现方式:加上一个注解@Transactional
隔离级别:
- 对未提交:啥也不能解决,任由脏读发生
- 读已提交:解决了脏读问题
- 可重复读:解决了脏读、幻读(spring默认隔离级别)
- 串行化度:解决了脏读、虚度、幻读,但是性能差
37、spring事务的传播机制
- 【掌握】PROPAGATION_REQUIRED: 表示当前方法必须运行在事务中。如果当前事务存在,方法将会在该事务中运行。否则,会启动一个新的事务。绝大多数情况都是用这个
- 【掌握】 PROPAGATION_SUPPORTS: 表示当前方法不需要事务上下文,但是如果存在当前事务的话,那么该方法会在这个事务中运行,如果没有事务,我也不会创建。有很小一部分会用到这个传播行为
剩下五个了解,几乎用不到。
- PROPAGATION_MANDATORY 表示该方法必须在事务中运行,如果当前事务不存在,则会抛出一个异常
- PROPAGATION_REQUIRED_NEW 表示当前方法必须运行在它自己的事务中。一个新的事务将被启动。如果存在当前事务,在该方法执行期间,当 前事务会被挂起。如果使用JTATransactionManager的话,则需要访问TransactionManager
- PROPAGATION_NOT_SUPPORTED 表示该方法不应该运行在事务中。如果存在当前事务,在该方法运行期间,当前事务将被挂起。如果使用JTATransactionManager的话,则需要访问TransactionManager
- PROPAGATION_NEVER 表示当前方法不应该运行在事务上下文中。如果当前正有一个事务在运行,则会抛出异常
- PROPAGATION_NESTED 表示如果当前已经存在一个事务,那么该方法将会在嵌套事务中运行。嵌套的事务可以独立于当前事务进行单独地提交或回滚。如果当前事务不存在,那么其行为与PROPAGATION_REQUIRED一样。注意各厂商对这种传播行为的支持是有所差异的。可以参考资源管理器的文档来确认它们是否支持嵌套事务
38、Spring事务什么时候失效
- 数据库引擎不支持事务
- @transactional加在private方法上(@Transactional只能加在public方法上,如果需要在private方法中加入事务,可以使用Aspect配transactionManager使用.)
- 异常被catch
- 异常类型错误
- 没有被Spring管理
- 没有配置TransactionManager
39、什么是bean的自动装配,有哪些方式
开启自动装配,只需要在xml配置文件<bean>中定义"autowire"属性。
<bean id="cutomer" class="com.llt.xxx.Customer" autowire=""/>
autowire属性有五种装配方式:
- no-这是默认设置,表示没有自动装配
- byName-根据bean属性名称进行自动装配。
- byType-根据bean的类型进行自动装配
- contructor 类似byType,不过应用于构造器的参数,如果一个bean与构造器参数的类型相同,则自动装配,否则导致异常
- autodetect:如果有默认构造器就是用contructor进行装配,否则使用byType进行自动装配
40、Spring boot、SpringMVC和Spring有什么区别
- SpringFrame SpringFramework 最重要的特征是依赖注入。所有 SpringModules 不是依赖注入就是 IOC 控制反转。 当我们恰当的使用 DI 或者是 IOC 的时候,我们可以开发松耦合应用。松耦合应用的单元测试可以很容易的进行。
- SpringMVC Spring MVC 提供了一种分离式的方法来开发 Web 应用。通过运用像 DispatcherServelet,MoudlAndView 和 ViewResolver 等一些简单的概念,开发 Web 应用将会变的非常简单。
- SpringBoot Spring 和 SpringMVC 的问题在于需要配置大量的参数。
41、SpringMVC工作流程
- 用户发送请求到前端控制器 DispatcherServlet
- dispatcherServlet 收到请求调用HandlerMapping处理映射器
- HandlerMapping找到具体的处理器,生成处理器及拦截器一并返回给DispatcherServlet
- DispatcherServlet 调用HandlerAdapter处理适配器
- HandlerAdpter经过适配器调用具体的controller
- Controller执行完成返回ModelAndView
- HandlerAdapter将controller执行结果返回给dispatcherServlet
- DispatcherServlet将ModelAndView传给视图解析器ViewReslover
- ViewReslover解析后返回具体的视图view
- 前端控制器根据视图进行视图渲染
- 最后DispatcherServlet响应给客户
42、spring中的九大主键
- HandlerMapping:处理映射器
- HandelrAdapter:适配器
- HandlerExeptionReslover:捕获异常,交给render方法进行渲染
- ViewResplver:视图解析器
- RequestToViewNameTranslator:根据ViewNma查钊View
- LocaleResolver:国际化
- ThemeRsolver:解析主题
- MultiparReslover:文件上传
- FlashMapManager:用来管理Flash,主要用在重定向传递参数
43、springboot自动配置原理
@import + @Configuration + Spring spi
自动配置由各个starter提供,使用@Configuration+@Bean定义配置类,放到META-INF/spring。factories下
使用spring spi烧苗平META-INF/搜spring.factories下的配置类
使用@import导入自动配置类
@Configuration是一个符合注解,复合注解里边有一个@EnableAutoConfiguration,这个注解的作用就是开启自动装配。
44、如何理解springboot中的Starter
使用spring+SpringMVC,如果需要映入mybatis等框架,需要到xml中定义mybatis需要的bean
starter就是定义一个starter的jar包,写一个@Configuration配置类,将这些bean定义在里面,然后在starter包的META-IN/spring。factories中写入该配置类,springboot会按照约定来加载该配置类
开发人员只需要将相应的starter包依赖拖进应用,进行相应的属性配置,就可以直接进行代码开发,使用对应的功能。
45、什么事嵌入式服务器?为什么要使用嵌入式服务器
节省了下载安装tomcat,应用也不需要再打war包,然后放到webapp目录下运行
只需要安装了java虚拟机,就可以直接在上面部署应用程序了
springboot已经内置了tomcat.jar,运行main方法时会去启动tomcat,并利用tomcat的spi机制加载SpringMVC
46、mybatis的优缺点
- 基于SQL语句编程,解除sql与程序代码的耦合,并重用
- 与JDBC相比,消除了大量冗余代码
- 很好的与各种数据库兼容
- 能够与spring很好地集成
缺点:
- sql语句的编写工作量大,对开发人员编写sql语句的功底有一定要求
- sql语句因爱数据库,移植性差,不能随意更换数据库
47、#{}和${}的区别
{}:预编译,占位符是以拼接字符连接,提高安全性
${}:字符串替换,是拼接符,容易造成sql注入
48、简述Mybatis的插件运行原理,如何编写一个插件
Mybatis 仅可以编写针对 ParameterHandler、ResultSetHandler、StatementHandler、Executor 这 4 种接口的插件,Mybatis 使用 JDK 的动态代{过}{滤}理,为需要拦截的接口生成代{过}{滤}理对象以实现接口方法拦截功能,每当执行这4种接口对象的方法时,就会进入拦截方法,具体就是 InvocationHandler 的 invoke()方法,当然,只会拦截那些你指定需要拦截的方法。
编写插件:实现 Mybatis 的 Interceptor 接口并复写 intercept()方法,然后在给插件编写注解,指定要拦截哪一个接口的哪些方法即可,记住,别忘了在配置文件中配置你编写的插件。
49、索引的基本原理
- 索引用来快速的寻找那些具有特定值的记录,如果没有索引,一般来说执行查询时遍历整张表
- 索引的原理就是把无序的数据编程有序的查询
50、java创建对象的几种方式
有4种显式地创建对象的方式:
1.用new语句创建对象,这是最常用的创建对象的方式。
2.运用反射手段,调用Java.lang.Class或者java.lang.reflect.Constructor类的newInstance()实例方法。
3.调用对象的clone()方法。
4.运用反序列化手段,调用java.io.ObjectInputStream对象的readObject()方法.
52、加载ApplicationContext.xml文件的方式有哪些?
- ClassPathXmlApplicationContext 对象加载
- FileSystemXmlApplicationContext
52、Spring创建对象
- 有参构造:就是需要参数才能够创建对象
- 无参构造:参构造就是利用反射类的newinstance方法创建对象。
- 静态工厂:创建一个工厂类,直接类名.方法调用
- 非静态工厂:先把工厂实例创建,因为非静态方法只能被实例调用
53、依赖注入
- set注入
- 构造函数注入
- 注解注入:@Resource和@Autowired注解注入
54、AOP底层原理
就是动态代{过}{滤}理
JDK动态代{过}{滤}理和CGLIB动态代{过}{滤}理:
55、全局异常捕获
- @ControllerAdvice:如果有页面返回,那么就是用@ControllerAdvice
- @RestControllerAdvice:如果只是返回JSON数据,那么就用@RestControllerAdvice
56、实现拦截器的步骤
实现HandlerInterceptor接口,覆写其三个方法,并且交给Spring容器管理,也就是加上@Component注解。其中三个方法的名称:
-
boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)//在控制器方法请求之前执行
-
void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView)//在控制器方法执行完成之后执行。
-
void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex)//视图渲染之后执行
57、SpringMVC和Spring容器的关系
在Spring整体框架的核心概念中,容器是核心思想,就是用来管理Bean的整个生命周期的。Spring和SpringMVC的容器存在父子关系,即Spring是父容器,SpringMVC是其子容器,子容器可以访问父容器的对象,父容器不能访问子容器的类。**例如:Controller中能调用Service,但是Service不能调用Controller。Spring父容器负责所有其他非@Controller注解的Bean的注册,而SpringMVC只负责@Controller注解,处理器映射、视图解析器的Bean的注册,使得他们各负其责、明确边界。配置到子容器的只能是子容器自己访问,配置到父容器的,父子容器都能访问。
58、mysql锁的类型有哪些
基于所得属性分类:共享锁,排它锁
基于锁的粒度分类:行级锁,表级锁,页级锁,间歇锁,临建锁
59、事务的特性和隔离级别
事务基本特性ACID分别是:
- 原子性:要么全部成功,要么要不失败
- 一致性:事务必须始终保持系统处于一致的状态,不管在任何给定的时间并发事务有多少
- 隔离性:一个事务的修改在最终提交前对其他事物是不可见的
- 持久性:一个事务一旦提交,事物的操作便永久性的保存在DB中。即使此时再执行回滚操作也不能撤消所做的更改
Redis
60、Rides中的RDB和AOF机制
- RDB:(快照持久化,默认开启),Redis创建快照之后,可以对快照进行备份,可以将快照复制到其他服务器从而创建具有相同数据的服务器副本(Redis主从结构,主要用来提高Redis性能),还可以将快照留在原地以便重启服务器的时候使用。
- AOF:AOF持久化 的实时性更好,默认没有开启,开启AOF持久化后每执行一条会更改Redis中的数据的命令,Redis就会将该命令写入硬盘中的AOF文件。AOF文件的保存位置和RDB文件的位置相同,都是通过dir参数设置的,默认的文件名是appendonly.aof。
61、redis 内存淘汰机制
redis 提供 6种数据淘汰策略:
- volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
- volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
- volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key(这个是最常用的)
- allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
- no-eviction:禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,新写入操作会报错。这个应该没人使用吧!
62、redis为什么是单进程单线程
因为单线程代码清晰,线程安全,不用去考虑各种锁
63、为什么redis需要把所有数据放到内存中?
redis为了达到最快的读写速度,将数据都读到内存中,并通过异步的方式将 数据写入磁盘;所以redis具有快速和数据持久化的特征;
64、redis支持的数据类型
- string值是字符串类型
- list 值是一个集合
- set 值是一个无重复数据的集合
- sorted set 值是一个无重复数据并且排序的集合
- hash 值可以理解是一个对象;
65、是否使用过Redis集群,集群的原理是什么?
Redis Sentinal着眼于高可用,在master宕机时会自动将slave提升为master继续提供服务。//哨兵
Redis Cluster着眼于扩展性,在单个redis内存不足时,使用Cluster进行分片存储。//集群
66、使用redis有哪些好处?
(1) 速度快,因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1)
(2) 支持丰富数据类型,支持``string``,list,``set``,sorted ``set``,hash
(3) 支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行
(4) 丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除
67、redis的回收策略
最近最少使用的数据淘汰,将要过期的数据淘汰,已经过期的数据淘汰
68、redis 设置过期时间
我们 set key 的时候,都可以给一个 expire time,就是过期时间,通过过期时间我们可以指定这个 key 可以存活的时间。
定期删除:redis默认是每隔 100ms 就随机抽取一些设置了过期时间的key,检查其是否过期,如果过期就删除。注意这里是随机抽取的。为什么要随机呢?你想一想假如 redis 存了几十万个 key ,每隔100ms就遍历所有的设置过期时间的 key 的话,就会给 CPU 带来很大的负载!
惰性删除 :定期删除可能会导致很多过期 key 到了时间并没有被删除掉。所以就有了惰性删除。假如你的过期 key,靠定期删除没有被删除掉,还停留在内存里,除非你的系统去查一下那个 key,才会被redis给删除掉。这就是所谓的惰性删除,也是够懒的哈!
69、缓存雪崩、缓存穿透、缓存击穿
缓存雪崩:缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉。
- 解决办法
- 事前:尽量保证整个 redis 集群的高可用性,发现机器宕机尽快补上。选择合适的内存淘汰策略。
- 事中:本地ehcache缓存 + hystrix限流&降级,避免MySQL崩掉
- 事后:利用 redis 持久化机制保存的数据尽快恢复缓存
缓存穿透:一般是黑客故意去请求缓存中不存在的数据,导致所有的请求都落到数据库上,造成数据库短时间内承受大量请求而崩掉。
- 解决办法:
- 有很多种方法可以有效地解决缓存穿透问题,最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被 这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。另外也有一个更为简单粗暴的方法(我们采用的就是这种),如果一个查询返回的数据为空(不管是数 据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。
缓存击穿:缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力
-
解决办法
- 设置热点数据永远不过期。
- 加互斥锁,互斥锁参考代码如下:
public static String getData(String key) throws InterruptedException{
//从缓存读取数据
String result = getDataFromRedis(key);
//缓存中不存在数据
if (result == nul1){
//去获取锁,获取成功,去数据库取数据
if (reenLock.tryLock()){
//从数据库获取数据
result = getDataFromMysql(key);
//更新缓存数据
if(result != null){
setDataToCache(key,result);
}
//释放锁
rennLock.unlock();
}
//获取锁失败
else{
//暂停100ms再重新去获取数据
Thread.sleeo(100);
result = getData(key);
}
}
return result;
}
说明:
1)缓存中有数据,直接走上述代码13行后就返回结果了
2)缓存中没有数据,第1个进入的线程,获取锁并从数据库去取数据,没释放锁之前,其他并行进入的线程会等待100ms,再重新去缓存取数据。这样就防止都去数据库重复取数据,重复往缓存中更新数据情况出现。
3)当然这是简化处理,理论上如果能根据key值加锁就更好了,就是线程A从数据库取key1的数据并不妨碍线程B取key2的数据,上面代码明显做不到这点
70、Redis事务
Redis 通过 MULTI、EXEC、WATCH 等命令来实现事务(transaction)功能。在传统的关系式数据库中,常常用 ACID 性质来检验事务功能的可靠性和安全性。在 Redis 中,事务总是具有原子性(Atomicity)、一致性(Consistency)和隔离性(Isolation),并且当 Redis 运行在某种特定的持久化模式下时,事务也具有持久性(Durability)。
71、Shiro
登录流程: 首先前端用户传入用户名个密码,后台创建UsernamePasswordToken,将用户名和密码封装到这个对象中,然后用SecuirityUtil.getSubject()获得Subject对象,Subject对象就是shiro用来登录的对象,调用login(token)方法来执行登录操作,就会进入UserRealm中的dogetAuthcation认证方法,对login方法进行异常捕获,如果抛出异常,则登录失败,如果没有抛出异常,doGetAuthcation返回一个简单的登录信息,表示用户登录成功。
登录成功之后,用注解@RequireRoles和RequirePermissions验证该用户是否有角色和权限。