一、Java基础
1、java 的三大特性?封装、继承、多态
封装:即隐藏对象的属性和实现细节,将公共代码抽取出来,可以进行复用。 一般体现在公共方法的封装。工具类。 继承:子类继承父类的特征和行为,使得子类对象(实例)具有父类的属性和方法,或子类从父类继承方法,使得子类具有父类相同的行为。
1.提升代码的可重用性
2.能够扩展功能
3.方便维护
多个子类都继承了父类的某个功能,当功能需要修改时,只要修改父类这个功能即可
多态:一个行为具有多种不同表现形式或形态的能力,不同类的的对象可以对同一消息做出不同反应,父类可以根据子类的不同,而使得同一个方法产生不同的结果(向上转型,向下转型)
2、抽象和接口的区别?实现:抽象类的子类使用 extends 来继承;接口必须使用 implements 来实现接口。
构造函数:抽象类可以有构造函数;接口不能有。 实现数量:类可以实现很多个接口;但是只能继承一个抽象类。 访问修饰符:接口中的方法默认使用 public 修饰;抽象类中的方法可以是任意访问修饰符。
3、集合有哪些?List、Set、Map 的区别主要体现在两个方面:元素是否有序、是否允许元素重复。 三者之间的区别,如下表:
4、HashMap 和 HashTable 的区别?存储:HashMap 允许 key 和 value 为 null,而 Hashtable 不允许。
线程安全:Hashtable 是线程安全的,而 HashMap 是非线程安全的。 推荐使用:在 Hashtable 的类注释可以看到,Hashtable 是保留类不建议使用,推荐在单线程环境下使用 HashMap 替代,如果需要多线程使用则用 ConcurrentHashMap 替代。
5、HashMap 的原理?扩容怎么实现的?原理:HashMap 基于 Hash 算法实现的,我们通过 put(key,value)存储,get(key)来获取。当传入 key 时,HashMap 会根据 key.hashCode() 计算出 hash 值,根据 hash 值将 value 保存在 bucket 里。当计算出的 hash 值相同时,我们称之为 hash 冲突,HashMap 的做法是用链表和红黑树存储相同 hash 值的 value。当 hash 冲突的个数比较少时,使用链表否则使用红黑树。当两个不同的键对象的hashcode相同时会发生什么? 它们会储存在同一个bucket位置的链表中。键对象的equals()方法用来找到键值对。
扩容:若new一个HashMap时没有指定容量(数组大小),则HashMap初始为null,即没有大小,在第一次执行put()时会将容量初始化为16,默认加载因子为0.75,扩容阈值 Threadhold 则为容量 capacityload factor(16 0.75=12),当Map中元素个数超过阈值后,进行扩容机制,容量变为原来的两倍(容量必须为2的幂次,且每次扩容都为2倍)。若new一个HashMap时候若指定了容量,比如 HashMap map = new HashMap<>(33); 指定了数组大小为33,那数组大小就是33了吗? 显然不是,这样new了以后,会根据指定的正整数“33”找到不小于指定容量的2的幂数)(源码内部是以位运算实现的,速度快),将这个数设置赋值给阈值(threshold)。第一次调用put方法时,会将阈值赋值给容量,然后让阈值=0.75*容量。 第三问为什么不一开始就使用红黑树,不是效率很高吗? 因为红⿊树需要进⾏左旋,右旋,变⾊这些操作来保持平衡,⽽单链表不需要。 当元素⼩于8个当时候,此时做查询操作,链表结构已经能保证查询性能。 当元素⼤于8个的时候,此时需要红⿊树来加快查 询速度,但是新增节点的效率变慢了。 因此,如果⼀开始就⽤红⿊树结构,元素太少,新增效率⼜⽐较慢,⽆疑这是浪费性能的。 第四问什么时候退化为链表 为6的时候退转为链表。中间有个差值7可以防⽌链表和树之间频繁的转换。 假设⼀下,如果设计成链表个数超过8则链表转 换成树结构,链表个数⼩于8则树结构转换成链表, 如果⼀个HashMap不停的插⼊、删除元素,链表个数在8左右徘徊,就会 频繁的发⽣树转链表、链表转树,效率会很低。
6、静态变量和实例变量的区别?所属对象不同:静态变量是为所有对象共享的,即它是属于某个类的,而实例变量是属于某一个对象的,不具有共享性。
内存空间分配的不同:静态变量在类加载内存的时候会对其进行分配空间,且只会分配一次内存空间,以后对该静态变量的操作都是在这一块内存上完成的。与静态变量不同,每创建一个对象,都会为该对象的实例变量进行分配内存空间。 访问方式不同:访问静态变量可以有两种方式:类名.静态变量名 或者 对象名.静态变量名。而访问实例变量只有一种方法,即对象名.实例变量名。
7、static、super、this关键字各自作用?this:代表对当前所在类的引用。
super:代表对父类对象的引用,用于子类虽然重写了父类的某个方法,但是依旧想调用父类的这个方法。 static:方便在没有创建对象的情况下,对属性和方法进行调用。
8、equals 和 hashcode?阿里巴巴开发规范有一句话:只要重写 equals,就必须重写 hashCode。
hashCode()方法的作用是为了获取哈希码,返回的是一个int整数,而哈希码的作用是确定对象在哈希表的索引下标。
equals()方法的作用是判断两个对象是否相等,equals()方法是定义在Object类中,而所有的类的父类都是Object,所以如果不重写equals方法则会调用Object类的equals方法。Object类的equals方法是用“==”号进行比较,在很多时候,因为==号比较的是两个对象的内存地址而不是实际的值,所以不是很符合业务要求。所以很多时候我们需要重写equals方法,去比较对象中每一个成员变量的值是否相等。
总而言之:
哈希码不相等,则两个对象一定不相同。 哈希码相等,两个对象不一定相同。 两个对象相同,则哈希码和值都一定相等。
9、泛型?反射?反射:在Java程序运行过程中,ClassLoader会根据需要加载这些class文件。每一个class文件加载完成后,虚拟机都会为其生成一个Class对象,可以通过类名.class 或者 实例应用.getClass() 获取该对象。通过 class 对象,我们就可以获取 class 文件中绝大部分信息,这就是 Java 反射。
泛型:不同的数据结构可以使用同样的操作。
10、jre 和 jdk 的区别?JDK:Java Development Kit 的简称,java 开发工具包,提供了 java 的开发环境和运行环境。
JRE:Java Runtime Environment 的简称,java 运行环境,为 java 的运行提供了所需环境。
具体来说 JDK 其实包含了 JRE,同时还包含了编译 java 源码的编译器 javac,还包含了很多 java 程序调试和分析的工具。简单来说:如果你需要运行 java 程序,只需安装 JRE 就可以了,如果你需要编写 java 程序,需要安装 JDK。
二、多线程和线程池的一些问题
1、多线程创建方式?项目中怎么使用的?创建线程有三种方式:
继承 Thread 重写 run 方法; 实现 Runnable 接口; 实现 Callable 接口。
使用:https://www.cnblogs.com/three-fighter/p/14491256.html
2、线程池的参数有哪些?怎么进行参数配置?
corePoolSize:表示当前线程池的核心线程数大小,即最小线程数(初始化线程数),线程池会维护当前数据的线程在线程池中,即使这些线程一直处于闲置状态,也不会被销毁; maximumPoolSize:表示线程池中允许的最大线程数;后文中会详细讲解 keepAliveTime :表示空闲线程的存活时间,当线程池中的线程数量大于核心线程数且线程处于空闲状态,那么在指定时间后,这个空闲线程将会被销毁,从而逐渐恢复到稳定的核心线程数数量; unit:当前unit表示的是keepAliveTime存活时间的计量单位,通常使用TimeUnit.SECONDS秒级; workQueue:任务工作队列;后文会结合maximumPoolSize一块来讲 threadFactory:线程工厂,用于创建新线程以及为线程起名字等 handler:拒绝策略,即当任务过多无法及时处理时所需采取的策略;
https://zhuanlan.zhihu.com/p/394094807 配置参数时需要考虑 CPU密集型任务 、 IO密集型任务 、内存使用率 、下游系统抗并发的能力 配置参数: CPU密集型 CPU的核数+1 IO密集型 一般配置 2*CPU的核数 参考公式(某大厂配置): CPU核数/(1-阻塞系数) 阻塞系数在0.8~0.9之间 比如8核CPU 8/(1-0.9) = 80个线程数https://riemann.blog.csdn.net/article/details/104704197?spm=1001.2101.3001.6650.2&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-2-104704197-blog-110132365.pc_relevant_paycolumn_v3&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-2-104704197-blog-110132365.pc_relevant_paycolumn_v3&utm_relevant_index=4
3、synchronized的底层原理?有几种实现方式?锁的优化有哪些?
底层原理:synchronized是由一对monitorenter/monitorexit指令实现的,monitor对象是同步的基本实现单元。在Java6之前,monitor的实现完全是依靠操作系统内部的互斥锁,因为需要进行用户态到内核态的切换,所以同步操作是一个无差别的重量级操作,性能也很低。但在java6的时候,java虚拟机对此进行了大刀阔斧地改进,提供了三种不同的monitor实现,也就是常说的三种不同的锁:偏向锁(Biased Locking)、轻量级锁和重量级锁,大大改进了其性能。 实现方式:
https://zhuanlan.zhihu.com/p/343305760
4、volatile关键字的作用?与synchronized区别?
volatile 是变量修饰符;synchronized 是修饰类、方法、代码段。 volatile 仅能实现变量的修改可见性,不能保证原子性;而 synchronized 则可以保证变量的修改可见性和原子性。 volatile 不会造成线程的阻塞;synchronized 可能会造成线程的阻塞。
5、synchronized与Lock的区别是什么?synchronized 可以给类、方法、代码块加锁;而 lock 只能给代码块加锁。
synchronized 不需要手动获取锁和释放锁,使用简单,发生异常会自动释放锁,不会造成死锁;而 lock 需要自己加锁和释放锁,如果使用不当没有 unLock()去释放锁就会造成死锁。 通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办到。
6、synchronized 和 ReentrantLock 区别是什么?synchronized 早期的实现比较低效,对比 ReentrantLock,大多数场景性能都相差较大,但是在 java 6 中对 synchronized 进行了非常多的改进。 主要区别如下:
ReentrantLock 使用起来比较灵活,但是必须有释放锁的配合动作;
ReentrantLock 必须手动获取与释放锁,而 synchronized 不需要手动释放和开启锁; ReentrantLock 只适用于代码块锁,而 synchronized 可用于修饰方法、代码块等。 volatile 标记的变量不会被编译器优化;synchronized 标记的变量可以被编译器优化。
7、ReetrantLock的原理是什么?ReentrantLock表示重入锁,它是唯一一个实现了Lock接口的类。重入锁指的是 线程在获得锁之后,再次获取该锁不需要阻塞,而是直接关联一次计数器增加重入次,同时它也是个典型的独占模式AQS,同步状态为0时表示空闲。当有线程获取到空闲的同步状态时,它会将同步状态加1,将同步状态改为非空闲,于是其他线程挂起等待。在修改同步状态的同时,并记录下自己的线程,作为后续重入的依据,即一个线程持有某个对象的锁时,再次去获取这个对象的锁是可以成功的。如果是不可重入的锁的话,就会造成死锁。https://zhuanlan.zhihu.com/p/232458435#:~:text=%20ReentrantLock%E6%98%AF%E4%B8%AA%E5%85%B8%E5%9E%8B%E7%9A%84%E7%8B%AC%E5%8D%A0%E6%A8%A1%E5%BC%8FAQS%EF%BC%8C%E5%90%8C%E6%AD%A5%E7%8A%B6%E6%80%81%E4%B8%BA0%E6%97%B6%E8%A1%A8%E7%A4%BA%E7%A9%BA%E9%97%B2%E3%80%82,%E5%BD%93%E6%9C%89%E7%BA%BF%E7%A8%8B%E8%8E%B7%E5%8F%96%E5%88%B0%E7%A9%BA%E9%97%B2%E7%9A%84%E5%90%8C%E6%AD%A5%E7%8A%B6%E6%80%81%E6%97%B6%EF%BC%8C%E5%AE%83%E4%BC%9A%E5%B0%86%E5%90%8C%E6%AD%A5%E7%8A%B6%E6%80%81%E5%8A%A01%EF%BC%8C%E5%B0%86%E5%90%8C%E6%AD%A5%E7%8A%B6%E6%80%81%E6%94%B9%E4%B8%BA%E9%9D%9E%E7%A9%BA%E9%97%B2%EF%BC%8C%E4%BA%8E%E6%98%AF%E5%85%B6%E4%BB%96%E7%BA%BF%E7%A8%8B%E6%8C%82%E8%B5%B7%E7%AD%89%E5%BE%85%E3%80%82%20%E5%9C%A8%E4%BF%AE%E6%94%B9%E5%90%8C%E6%AD%A5%E7%8A%B6%E6%80%81%E7%9A%84%E5%90%8C%E6%97%B6%EF%BC%8C%E5%B9%B6%E8%AE%B0%E5%BD%95%E4%B8%8B%E8%87%AA%E5%B7%B1%E7%9A%84%E7%BA%BF%E7%A8%8B%EF%BC%8C%E4%BD%9C%E4%B8%BA%E5%90%8E%E7%BB%AD%E9%87%8D%E5%85%A5%E7%9A%84%E4%BE%9D%E6%8D%AE%EF%BC%8C%E5%8D%B3%E4%B8%80%E4%B8%AA%E7%BA%BF%E7%A8%8B%E6%8C%81%E6%9C%89%E6%9F%90%E4%B8%AA%E5%AF%B9%E8%B1%A1%E7%9A%84%E9%94%81%E6%97%B6%EF%BC%8C%E5%86%8D%E6%AC%A1%E5%8E%BB%E8%8E%B7%E5%8F%96%E8%BF%99%E4%B8%AA%E5%AF%B9%E8%B1%A1%E7%9A%84%E9%94%81%E6%98%AF%E5%8F%AF%E4%BB%A5%E6%88%90%E5%8A%9F%E7%9A%84%E3%80%82
8、cas是什么?会有什么问题?CAS(Compare And Swap 比较并且替换)是乐观锁的一种实现方式,是一种轻量级锁,JUC中很多工具类的实现就是基于CAS的。 线程在读取数据时不进行加锁,在准备写回数据时,先去查询原值,操作的时候比较原值是否修改,若未被其他线程修改则写回,若已被修改,则重新执行读取流程。 CAS的缺点:
循环时间长资源占用问题,如果替换失败,线程会自旋,直到替换为止,会占用CPU资源 ABA问题
因为CAS需要在操作值的时候检查下主内存的值是否发生变化,如果没有发生变化就交换,但是假如一个值原先是A,变成了B,又变成了A,那么使用CAS进行检查的时候是未发现此值的变化的,但实际上它是变化的了。ABA问题的解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A 就会变成1A-2B-3A。
只能保证一个共享变量的原子操作
9、死锁?怎么进行防止? 当线程A持有独占锁a,并尝试去获取独占锁b的同时,线程B持有独占锁b,并尝试获取独占锁a的情况下,就会发生AB两个线程由于互相持有对方需要的锁,而发生的阻塞现象,我们称为死锁。 防止方法:
拒绝策略:首先我们知道线程池的拒绝策略参数的类型是 RejectedExecutionHandler 类型的,那么我们可以先来了解一下关于这个接口的关系
在上述类图中我们可以看到 RejectedExecutionHandler 接口有四个实现类,同时都提供了无参构造函数,这四个实现类对应了不同的拒绝策略,都有各自的适用场景 AbortPolicy 拒绝策略: 终止策略,这是ThreadPoolExecutor线程池默认的拒绝策略,程序将会抛出RejectedExecutionException异常。这种策略在拒绝任务时,会直接抛出一个类型为RejectedExecutionException 的 RuntimeException,让你感知到任务被拒绝了,于是你便可以根据业务逻辑选择重试或者放弃提交等策略。DiscardPolicy 拒绝策略: 丢弃策略,什么都不做,即丢弃新提交的任务。这种策略是当任务提交时直接将刚提交的任务丢弃,而且不会给与任何提示通知,所以这种策略使用要慎重,因为有一定的风险,对我们来说根本不知道提交的任务有没有被丢弃DiscardOldestPolicy 拒绝策略: 丢弃最早未处理请求策略,丢弃最先进入阻塞队列的任务以腾出空间让新的任务入队列。这种策略和上面相似。不过它丢弃的是队列中的头节点,也就是存活时间最久的CallerRunsPolicy 拒绝策略: 调用者运行策略,线程池中没办法运行,那么就由提交任务的这个线程运行(哪儿来的回哪儿儿去~)。这种策略算是最完善的相对于其他三个,当线程池无能力处理当前任务时,会将这个任务的执行权交予提交任务的线程来执行,也就是谁提交谁负责,这样的话提交的任务就不会被丢弃而造成业务损失,同时这种谁提交谁负责的策略必须让提交线程来负责执行,如果任务比较耗时,那么这段时间内提交任务的线程也会处于忙碌状态而无法继续提交任务,这样也就减缓了任务的提交速度,这相当于一个负反馈。也有利于线程池中的线程来消化任务
阻塞队列有哪些:https://blog.csdn.net/xiewenfeng520/article/details/106954169
11、CountDownLatch和CyclicBarrier的区别?项目中怎么使用?为啥会选择CountDownLatch不选CyclicBarrier?共同点:都是阻塞线程
CountDownLatch的计数器只能使用一次。而CyclicBarrier的计数器可以使用reset()方法重置。所以CyclicBarrier能处理更为复杂的业务场景,比如如果计算发生错误,可以重置计数器,并让线程们重新执行一次。 CyclicBarrier还提供其他有用的方法,比如getNumberWaiting方法可以获得CyclicBarrier阻塞的线程数量。isBroken方法用来知道阻塞的线程是否被中断。比如以下代码执行完之后会返回true。 CountDownLatch会阻塞主线程,CyclicBarrier不会阻塞主线程,只会阻塞子线程。 某线程中断CyclicBarrier会抛出异常,避免了所有线程无限等待。
不同点:CountDownLatch是主线程被多线程阻塞,直到多线程执行完成才被唤醒继续执行,所以更关注主线程等待多线程执行完成再继续执行的场景;CyclicBarrier是多线程各自被阻塞在栅栏前,是多线程之间的相互等待,直到全部的多线程全部执行完成,然后并发的去共同做某件事https://zhuanlan.zhihu.com/p/409112151
三、MyBatis 的常见问题
1、一级缓存和二级缓存?
开启二级缓存数据查询流程:二级缓存 -> 一级缓存 -> 数据库。 缓存更新机制:当某一个作用域(一级缓存 Session/二级缓存 Mapper)进行了C/U/D 操作后,默认该作用域下所有 select 中的缓存将被 clear。
2、枚举怎么通过mybatis实现?
org.apache.ibatis.type.EnumTypeHandler 和 org.apache.ibatis.type.EnumOrdinalTypeHandler EnumTypeHandler Mybatis 中默认的枚举转换器,获取枚举中的 name 属性 EnumOrdinalTypeHandler 获取枚举中 ordinal 属性,相当于索引,从1开始
插入之前,主键回填(适用于没有自增的主键,主键需要自己指定(Oracle)):
6、mybatis没有接口实现类,mapper的工作原理是什么?MyBatis 使用了 动态代理 来生成了实现类。 1、加载mybatis全局配置文件(数据源、mapper映射文件等),解析配置文件,MyBatis基于XML配置文件生成Configuration,和一个个MappedStatement(包括了参数映射配置、动态SQL语句、结果映射配置),其对应着标签项。2、SqlSessionFactoryBuilder通过Configuration对象生成SqlSessionFactory,用来开启SqlSession。3、SqlSession对象完成和数据库的交互:a、用户程序调用mybatis接口层api(即Mapper接口中的方法)b、SqlSession通过调用api的Statement ID找到对应的MappedStatement对象c、通过Executor(负责动态SQL的生成和查询缓存的维护)将MappedStatement对象进行解析,sql参数转化、动态sql拼接,生成jdbc Statement对象d、JDBC执行sql。e、借助MappedStatement中的结果映射关系,将返回结果转化成HashMap、JavaBean等存储结构并返回。
7、mybatis支持懒加载吗,有哪些方式,原理是什么
Mybatis仅支持association关联对象和collection关联集合对象的延迟加载,association指的就是一对一,collection指的就是一对多查询。在Mybatis配置文件中,可以配置是否启用延迟加载lazyLoadingEnabled=true|false。它的原理是,使用CGLIB创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,比如调用a.getB().getName(),拦截器invoke()方法发现a.getB()是null值,那么就会单独发送事先保存好的查询关联B对象的sql,把B查询上来,然后调用a.setB(b),于是a的对象b属性就有值了,接着完成a.getB().getName()方法的调用。这就是延迟加载的基本原理。当然了,不光是Mybatis,几乎所有的包括Hibernate,支持延迟加载的原理都是一样的。
8、mybatis是什么?优缺点?为啥是半orm?
MyBatis 是一个半 ORM (对象关系映射)框架,它封装了 JDBC,开发时只需要关注 SQL 语句本身,不需要花费精力去处理加载驱动、创建连接、创建 statement 等繁杂的过程。程序员直接编写原生态 sql,可以严格控制 sql 执行性能、灵活度高。MyBatis 可以使用 xml 或注解来配置和映射原生信息,将POJO映射成数据库中的记录,避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。ORM 是对象关系映射,是一种为了解决关系型数据库数据域简单 Java 对象(POJO)的映射关系的计数。而MyBatis 在查询关联对象或关联集合对象时,需要手动编写 sql 来完成,所以,称之为半自动 ORM 映射工具。
9、mybatis 和 hibernate 的区别有哪些?灵活性:Mybatis 更加灵活,自己可以写 sql 语句,使用起来比较方便。
可移植性:Mybatis 有很多自己写的sql,应为每个数据库的 sql 可以不相同,所以可移植性比较差。学习和使用门槛:Mybatis 入门比较简单,使用门槛也更低。二级缓存:hibernate 拥有更好的二级缓存,它的二级缓存可以自行更换为第三方的二级缓存。
10、书写mybatis中得mapper接口需要注意什么?(1)Mapper接口方法名和mapper.xml中定义的每个sql的id相同;(2)Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql 的parameterType的类型相同;(3)Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同;(4)Mapper.xml文件中的namespace即是mapper接口的类路径。
11、常用的标签有哪些?
四、Spring 常规问题
1、spring的aop和ioc是什么或者spring的核心是什么?依赖注入?aop:aop 是面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。简单的来说就是统一处理某一切面的问题 的编程思想,如统一处理日志、异常等。
ioc:控制反转是 Spring 的核心,对于 Spring 框架来说就是由 Spring 来负责控制对象的生命周期和对象间的关系。简单来说,控制指的是当前对象对内部成员的控制权;控制反转指的是,这种控制权不由当前对象管理了,由其他(类、第三方容器)来管理。依赖注入(Dependency Injection),即组件之间的依赖关系由容器在应用系统运行期来决定,也就是由容器动态地将某种依赖关系的目标对象实例注入到应用系统中的各个关联的组件之中。组件不做定位查询,只提供普通的Java方法让容器去决定依赖关系。
2、spring的事务(ACID)和传播机制?
事务的特性:
原子性(Atomicity):事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成,要么全部不起作用。一致性(Consistency):一旦失误完成,不管成功还是失败,系统必须确保它所创建的业务处于一致状态,而不是部分完成部分失败。在现实中的数据不应该被破坏。隔离性(Isolation):可能会有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。持久性(Durability):一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响,这样的就能从任何系统崩溃中恢复过来。通常情况下,事务的结果被写到持久化容器中。
传播机制:
3、spring的事务方式有几种?区别是什么?
编程式事务:在代码中硬编码声明式事务:在配置文件中配置
基于XML的声明式事务基于注解的声明式事务
4、spring的有几个模块,分别干啥的?
Spring Core: 基础,可以说 Spring 其他所有的功能都需要依赖于该类库。主要提供 IoC 依赖注入功能。
Spring Aspects : 该模块为与AspectJ的集成提供支持。Spring AOP :提供了面向切面的编程实现。Spring JDBC : Java数据库连接。Spring JMS :Java消息服务。Spring ORM : 用于支持Hibernate等ORM工具。Spring Web : 为创建Web应用程序提供支持。Spring Test : 提供了对 JUnit 和 TestNG 测试的支持。
5、spring的容器有哪些?区别是什么?Spring 提供了两种容器类型:BeanFactory 和 ApplicationContext;
相同点:两者都是 Spring 的两个接口,用来获取 Spring 容器中的 bean;不同点:
对 bean 的加载方式不同:前者 BeanFactory 是使用的懒加载的方式,只有在调用 getBean() 时才会进行实例化,后者 ApplicationContext 是使用的预加载的方式,即在应用启动后就实例化所有的 bean。特性不同:BeanFactory 接口只提供了 IOC/DI 的支持,常用的 API 是XMLBeanFactory,ApplicationContext 是整个spring的中央接口,它继承了BeanFactory 接口,具备 BeanFactory 的所有特性,还有一些其他特性,比如:AOP的功能,事件发布/响应(ApplicationEvent)等。应用场景不同:BeanFactory 适合系统资源(内存)较少的环境中使用延迟实例化,比如运行在移动应用中;ApplicationContext 适合企业级的大型 web 应用,将耗时的内容在系统启动的时候就完成。
如何实现 ApplicationContext 的延迟加载?
两种方式:
bean元素而配置 lazy-init=truebeans元素中配置default-lazy-init=true 让这个beans中的所有bean延迟实例化
6、spring使用了哪些设计模式?spring aop 和aspectj采用了什么代理?
设计模式:
1.工厂设计模式: spring ioc核心的设计模式的思想体现就是工厂模式,他自己这个IOC容器就是一个大的工厂,把所有的bean实例都给放在了spring容器里,如果你要使用bean,就找spring容器就可以了,自己不用创建对象了。2.单例设计模式:Spring中的bean默认作用域就是singleton都是单例的。3.代理设计模式:Spring AOP功能的实现就用到了代理模式,Spring AOP生成一些代理对象,做一定的增强,然后我们对目标对象的访问呢就是基于这个代理对象去访问。4.模板方法模式:Spring中的jdbcTemplate、hibernateTemplate等以Template结尾的对数据库操作的类,它们就使用到了模板模式。5.包装器设计模式:我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。6.观察者模式:Spring事件驱动模型就是观察者模式很经典的一个应用,定义对象键一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知被制动工薪,如Spring中listener的实现—ApplicationListener。7.适配器模式:Spring AOP的增强或通知(Advice)使用到了适配器模式、Spring MVC中也是用到了适配器模式适配Controller。
spring aop 和 aspectj的区别:
Spring AOP 属于运行时增强,而 AspectJ 是编译时增强。 Spring AOP 基于代理(Proxying),而 AspectJ 基于字节码操作(Bytecode Manipulation)。Spring AOP 已经集成了 AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。AspectJ 相比于 Spring AOP 功能更加强大,但是 Spring AOP 相对来说更简单,如果我们的切面比较少,那么两者性能差异不大。但是,当切面太多的话,最好选择 AspectJ ,它比Spring AOP 快很多。
7、spring的自动装配是什么?有哪些方式?
Spring 的自动装配是什么:spring中提供了向 Bean 中自动注入依赖的功能,这个过程就是自动装配。
有两种方式:
基于xml文件的自动装配:byType(类型),byName(名称),constructor(根据构造函数)基于注解的自动装配:@Autowired,@Resource,@Value
8、spring bean的生命周期?bean的方式?
Bean 容器找到配置文件中 Spring Bean 的定义。
Bean 容器利用 Java Reflection API 创建一个Bean的实例。如果涉及到一些属性值 利用 set()方法设置一些属性值。如果 Bean 实现了 BeanNameAware 接口,调用 setBeanName()方法,传入Bean的名字。如果 Bean 实现了 BeanClassLoaderAware 接口,调用 setBeanClassLoader()方法,传入 ClassLoader对象的实例。与上面的类似,如果实现了其他 *.Aware接口,就调用相应的方法。如果有和加载这个 Bean 的 Spring 容器相关的 BeanPostProcessor 对象,执行postProcessBeforeInitialization() 方法如果Bean实现了InitializingBean接口,执行afterPropertiesSet()方法。如果 Bean 在配置文件中的定义包含 init-method 属性,执行指定的方法。如果有和加载这个 Bean的 Spring 容器相关的 BeanPostProcessor 对象,执行postProcessAfterInitialization() 方法当要销毁 Bean 的时候,如果 Bean 实现了 DisposableBean 接口,执行 destroy() 方法。当要销毁 Bean 的时候,如果 Bean 在配置文件中的定义包含 destroy-method 属性,执行指定的方法。
bean的注入方式:https://www.cnblogs.com/tuyang1129/p/12873492.html
set 方法注入构造器注入静态工厂注入实例工厂注入注解注入
五、SpringMVC常规问题
1、springMvc是什么?工作原理或者流程简述下?SpringMVC是一个基于Java的实现了MVC设计模式的轻量级WEB框架,通过将Model、View、Controller分离,简化开发。
运行流程:
spring mvc 先将请求发送给DispatchServlet。DispatchServlet 查询一个或多个HandlerMapping,找到处理请求的Controller。DispatchServlet 再把请求提交到对应的 Controller 。Controller 进行业务逻辑处理后,会返回一个 ModelAndView 。DispatchServlet 查询一个或多个ViewResolver 视图解析器,找到 ModelAndView 对象指定的视图对象。视图对象负责渲染返回给客户端。
2、springMvc的注解有哪些,作用是什么?
注解原理:
注解本质是一个继承了Annotation的特殊接口,其具体实现类是Java运行时生成的动态代理类。我们通过反射获取注解时,返回的是Java运行时生成的动态代理对象。通过代理对象调用自定义注解的方法,会最终调用AnnotationInvocationHandler的invoke方法。该方法会从memberValues这个Map中索引出对应的值。而memberValues的来源是Java常量池。
常用注解:
@RequestMapping:用于处理请求 url 映射的注解,可用于类或方法上。用于类上,则表示类中的所有响应请求的方法都是以该地址作为父路径。@RequestBody:注解实现接收http请求的json数据,将json转换为java对象。@ResponseBody:注解实现将conreoller方法返回对象转化为json对象响应给客户。https://zhuanlan.zhihu.com/p/369330399
3、springMvc与struts2的区别?
相同点:
都是基于mvc的表现层框架,都用于web项目的开发。
不同点:
1.前端控制器不一样。Spring MVC的前端控制器是servlet:DispatcherServlet。struts2的前端控制器是filter:StrutsPreparedAndExcutorFilter。2.请求参数的接收方式不一样。Spring MVC是使用方法的形参接收请求的参数,基于方法的开发,线程安全,可以设计为单例或者多例的开发,推荐使用单例模式的开发(执行效率更高),默认就是单例开发模式。struts2是通过类的成员变量接收请求的参数,是基于类的开发,线程不安全,只能设计为多例的开发。3.Struts采用值栈存储请求和响应的数据,通过OGNL存取数据,Spring MVC通过参数解析器是将request请求内容解析,并给方法形参赋值,将数据和视图封装成ModelAndView对象,最后又将ModelAndView中的模型数据通过reques域传输到页面。Jsp视图解析器默认使用jstl。4.与spring整合不一样。Spring MVC是spring框架的一部分,不需要整合。在企业项目中,Spring MVC使用更多一些。
4、springMvc和servlet的区别?原生Servlet需要通过客户端根据后台请求类型创建多个Servlet来进行接收前端传过来的HTTP请求。SpringMVC只需要通过一个Servlet(DispatcherServlet)前端控制器来接收所有的前端传来的HTTP请求,然后处理接收HTTP请求,下发给对应的处理类。Servlet:性能最好,处理Http请求的标准。SpringMVC:开发效率高(好多共性的东西都封装好了,是对Servlet的封装,核心的DispatcherServlet最终继承自HttpServlet)这两者的关系,就如同MyBatis和JDBC,一个性能好,一个开发效率高,是对另一个的封装。
五、jvm1、垃圾回收算法有哪些,区别,优缺点
标记-清除算法:标记无用对象,然后进行清除回收。缺点:效率不高,无法清除垃圾碎片。
缺点:标记和清除的效率都不高。 会产生大量的碎片,而导致频繁的回收。优点:最基础的收集算法,比较简单
标记-整理算法:标记无用对象,让所有存活的对象都向一端移动,然后直接清除掉端边界以外的内存。
缺点:算法复杂度大,执行步骤较多
复制算法:按照容量划分二个大小相等的内存区域,当一块用完的时候将活着的对象复制到另一块上,然后再把已使用的内存空间一次清理掉。缺点:内存使用率不高,只有原来的一半。
缺点:需要浪费额外的内存作为复制区。 当存活率较高时,复制算法效率会下降。优点:简单高效。
分代算法:根据对象存活周期的不同将内存划分为几块,一般是新生代和老年代,新生代基本采用复制算法,老年代采用标记整理算法。
缺点:算法复杂度大,执行步骤较多。
2、为啥分年轻代和老年代
为什么分年老代和新生代(根据对象存活的时间来看,有的对象寿命长,有的对象寿命短。应该将寿命长的对象放在一个区,寿命短的对象放在一个区。不同的区采用不同的垃圾收集算法。寿命短的区清理频次高一点,寿命长的区清理频次低一点。提高效率。所以就有了新生代和老年代。)
1)新生代(Young Gen):年轻代主要存放新创建的对象,内存大小相对会比较小,垃圾回收会比较频繁。年轻代分成1个Eden Space和2个Suvivor Space(from 和to)。2)老年代(Tenured Gen):年老代主要存放JVM认为生命周期比较长的对象(经过几次的Young Gen的垃圾回收后仍然存在),内存大小相对会比较大,垃圾回收也相对没有那么频繁。
为什么要分为Eden和Survivor?为什么要设置两个Survivor区?
1.如果没有Survivor,Eden区每进行一次Minor GC,存活的对象就会被送到老年代。老年代很快被填满,触发Major GC.老年代的内存空间远大于新生代,进行一次Full GC消耗的时间比Minor GC长得多,所以需要分为Eden和Survivor。2.Survivor的存在意义,就是减少被送到老年代的对象,进而减少Full GC的发生,Survivor的预筛选保证,只有经历16次Minor GC还能在新生代中存活的对象,才会被送到老年代。3.设置两个Survivor区最大的好处就是解决了碎片化,刚刚新建的对象在Eden中,经历一次MinorGC,Eden中的存活对象就会被移动到第一块survivor space S0,Eden被清空;等Eden区再满了,就再触发一次Minor GC,Eden和S0中的存活对象又会被复制送入第二块survivor spaceS1(这个过程非常重要,因为这种复制算法保证了S1中来自S0和Eden两部分的存活对象占用连续的内存空间,避免了碎片化的发生)。
3、垃圾回收器有哪些,简述cms,g1垃圾回收器
Serial:最早的单线程串行垃圾回收器。
Serial Old:Serial 垃圾回收器的老年版本,同样也是单线程的,可以作为 CMS 垃圾回收器的备选方案。ParNew:是 Serial 的多线程版本。Parallel 和 ParNew 收集器类似是多线程的,但 Parallel 是吞吐量优先的收集器,可以牺牲等待时间换取系统的吞吐量。Parallel Old 是 Parallel 老生代版本,Parallel 使用的是复制的内存回收算法,Parallel Old 使用的是标记-整理的内存回收算法。CMS:一种以获得最短停顿时间为目标的收集器,非常使用 B/S 系统。G1:一种兼顾吞吐量和停顿时间的 GC 实现,是 JDK9 以后默认的 GC 选项。
CMS收集器是一种老年代区域的垃圾收集器,往往配合ParNew 收集器来使用。它适合在注重用户体验的应用上使用,实现了GC线程和用户线程并发进行(部分步骤)。采用了标记-清除算法。回收过程大致分为5个步骤:
初始标记:暂停其他线程(STW),标记GC roots 直接引用的对象。过程很快并发标记:从GC roots 直接引用的对象出发向下查找所有引用的对象。这个过程最耗时,和用户线程并发执行。【这个过程可能出现的问题:用户线程执行中可能产生新的垃圾(浮动垃圾),无法被标记。】重新标记:为修正 并发标记 中产生变动的对象标识,主要用三色标记中的增量更新算法来进行标记。这个过程会暂停其他进程(STW)。并发清除:将标记为可回收的对象进行回收,和用户线程并发执行。【*这个过程也会产生新垃圾对象(浮动垃圾),这些对象将在下次GC时回收】并发重置:将标记为不可回收的对象的标志清除。
G1两大特点:1、控制回收垃圾的时间:这个是G1的优势,可以控制回收垃圾的时间,还可以建立停顿的时间模型,选择一组合适的Regions作为回收目标,达到实时收集的目的2、空间整理:和CMS一样采用标记-清理的算法,但是G1不会产生空间碎片,这样就有效的使用了连续空间,不会导致连续空间不足提前造成GC的触发G1把Java内存拆分成多等份,多个域(Region),逻辑上存在新生代和老年代的概念,但是没有严格区分https://www.jianshu.com/p/ab54489f5d71
4、内存溢出遇到过吗?怎么解决?内存溢出 out of memory,是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;比如申请了一个integer,但给它存了long才能存下的数,那就是内存溢出。内存溢出就是你要求分配的内存超出了系统能给你的,系统不能满足需求,于是产生溢出。内存泄露 memory leak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。memory leak会最终会导致out of memory!内存溢出的原因以及解决方法引起内存溢出的原因有很多种,小编列举一下常见的有以下几种:1.内存中加载的数据量过于庞大,如一次从数据库取出过多数据;2.集合类中有对对象的引用,使用完后未清空,使得JVM不能回收;3.代码中存在死循环或循环产生过多重复的对象实体;4.使用的第三方软件中的BUG;5.启动参数内存值设定的过小内存溢出的解决方案:第一步,修改JVM启动参数,直接增加内存。(-Xms,-Xmx参数一定不要忘记加。)第二步,检查错误日志,查看“OutOfMemory”错误前是否有其它异常或错误。第三步,对代码进行走查和分析,找出可能发生内存溢出的位置。重点排查以下几点:1.检查对数据库查询中,是否有一次获得全部数据的查询。一般来说,如果一次取十万条记录到内存,就可能引起内存溢出。这个问题比较隐蔽,在上线前,数据库中数据较少,不容易出问题,上线后,数据库中数据多了,一次查询就有可能引起内存溢出。因此对于数据库查询尽量采用分页的方式查询。2.检查代码中是否有死循环或递归调用。3.检查是否有大循环重复产生新对象实体。4.检查对数据库查询中,是否有一次获得全部数据的查询。一般来说,如果一次取十万条记录到内存,就可能引起内存溢出。这个问题比较隐蔽,在上线前,数据库中数据较少,不容易出问题,上线后,数据库中数据多了,一次查询就有可能引起内存溢出。因此对于数据库查询尽量采用分页的方式查询。5.检查List、MAP等集合对象是否有使用完后,未清除的问题。List、MAP等集合对象会始终存有对对象的引用,使得这些对象不能被GC回收。第四步,使用内存查看工具动态查看内存使用情况
5、有哪些命令进行问题排查?排查工具?
1. 先通过top 命令找到消耗cpu 很高的进程id 假设是13745(通过top找到占用cpu很高的进程)top 命令是我们在Linux 下最常用的命令之一,它可以实时显示正在执行进程的CPU 使用率、内存使用率以及系统负载等信息。其中上半部分显示的是系统的统计信息,下半部分显示的是进程的使用率统计信息。jinfo -flags 13745 查看进程的配置信息 jmap -heap 13745 查看进程的堆信息2. 执行top -Hp 13745 单独监控该进程(单独监控占用cpu很高的进程),获取当前进程下的所有线程信息3. 找到消耗cpu 特别高的线程编号,假设是13751(要等待一阵)4. 执行jstack 13745 对当前的进程做dump,输出所有的线程信息5. 将第3 步得到的线程编号13751 转成16 进制是35B76. 根据第5 步得到的35B7 在第4 步的线程信息里面去找对应线程内容7. 解读线程信息,定位具体代码位置(排查问题)
排查工具:
jconsole:用于对 jvm 中的内存、线程和类等进行监控;jvisualvm:jdk 自带的全能分析工具,可以分析:内存快照、线程快照、程序死锁、监控内存的变化、gc变化等。
6、创建java对象在内存中如何划分?在类加载检查通过后,接下来虚拟机将为新生对象分配内存。对象所需的内存大小在类加载完成后便可确定,为对象分配空间的任务等同于把一块确定大小的内存从 Java 堆中划分出来。分配方式有 “指针碰撞” 和 “空闲列表” 两种,选择哪种分配方式由 Java 堆是否规整决定,而 Java 堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。https://blog.csdn.net/weixin_36898373/article/details/118540906
Java把内存划分为两种,一种是栈内存,另一种是堆内存。1、栈内存在函数中定义的一些基本类型的变量和对象的引用变量都在函数的栈内存中分配。当在一段代码块定义一个变量时,Java就在栈中为这个变量分配内存空间,当超过变量的作用后,Java会自动释放掉为该变量所分配的内存空间,该内存空间可以立即被另作他用。2、堆内存堆内存用来存放由new创建的对象和数组。在堆中分配的内存,由Java中的自动垃圾回收器来管理。3、栈和堆的之间的关系在堆中产生一个数组或对象后,还可以在栈中定义一个特殊的变量,让栈中这个变量的值等于数组或对象在堆内存中的首地址,栈中的这个变量就变成了数组或对象的引用变量。引用变量就相当于是为数组或对象定义一个名词,以后就可以在程序中使用栈中的引用变量来访问堆中的数组或对象。
7、jvm调优?1、 jps,JVM Process Status Tool,显示指定系统内所有的HotSpot虚拟机进程。2、 jstat,JVM statistics Monitoring是用于监视虚拟机运行时状态信息的命令,它可以显示出虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据。3、 jmap,JVM Memory Map命令用于生成heap dump文件4、 jhat,JVM Heap Analysis Tool命令是与jmap搭配使用,用来分析jmap生成的dump,jhat内置了一个微型的HTTP/HTML服务器,生成dump的分析结果后,可以在浏览器中查看5、 jstack,用于生成java虚拟机当前时刻的线程快照。6、 jinfo,JVM Configuration info 这个命令作用是实时查看和调整虚拟机运行参数
8、类加载过程类装载分为以下 5 个步骤:
加载:根据查找路径找到对应的 class 文件然后导入;检查:检查加载的 class 文件的正确性;准备:给类中的静态变量分配内存空间;解析:虚拟机将常量池中的符号引用替换成直接引用的过程。符号引用就理解为一个标识,而直接应用直接指向内存中的地址;初始化:对静态变量和静态代码块执行初始化工作。
https://zhuanlan.zhihu.com/p/92534103
9、双委派机制是什么?怎么打破?在介绍双亲委派模型之前先说下类加载器,对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立在 jvm 中的唯一性,每一个类加载器,都有一个独立的类名称空间。类加载器就是根据指定全限定名称将 class 文件加载到 jvm 内存,然后再转化为 class 对象。类加载器分类:
启动类加载器(Bootstrap ClassLoader),是虚拟机自身的一部分,用来加载 JAVA_HOME/lib/ 目录中的,或者被 -Xbootclasspath 参数所指定的路径中并且呗虚拟机识别的类库;其他类加载器:
拓展类加载器(Extension ClassLoader):负责加载\lib\ext 目录或 java.ext.dirs 系统变量指定的路径中的所有类库;应用程序类加载器(Application CalssLoader):负责加载用户类路径(classpath)上的指定类库,我们可以直接使用这个类加载器,一般情况,如果我们没有自定义类加载器,默认就是用这个加载器。
双亲委派模型:如果一个类加载器收到了类加载的请求,它首先不会自己去加载这个类,而是把这个请求委派给父类加载器去完成,每一层的类加载器都是如此,这样所有的加载请求都会被传送到顶层的启动类加载器中,只有当父加载无法完成加载请求(它的搜索范围中没找到所需的类)时,子加载器才会尝试去加载类。两个典型的方法:
自定义类加载器,重写loadClass方法使用线程上下文类加载器
六、RabbitMq的问题https://juejin.cn/post/6844904125935665160
1、消息怎么避免丢失?把消息持久化磁盘,保证服务器重启消息不丢失。
每个集群中至少有一个物理磁盘,保证消息落入磁盘。
2、重复性消费怎么解决?保证消息不被重复消费的关键是保证消息队列的幂等性,这个问题针对业务场景来答分以下几点:1.比如,你拿到这个消息做数据库的insert操作。那就容易了,给这个消息做一个唯一主键,那么就算出现重复消费的情况,就会导致主键冲突,避免数据库出现脏数据。2.再比如,你拿到这个消息做redis的set的操作,那就容易了,不用解决,因为你无论set几次结果都是一样的,set操作本来就算幂等操作。3.如果上面两种情况还不行,上大招。准备一个第三方介质,来做消费记录。以redis为例,给消息分配一个全局id,只要消费过该消息,将以K-V形式写入redis。那消费者开始消费前,先去redis中查询有没消费记录即可。
3、死信队列和延时队列?https://zhuanlan.zhihu.com/p/375096641
4、rabbitmq的作用有哪些?项目怎么用得?
异步处理 - 相比于传统的串行、并行方式,提高了系统吞吐量。应用解耦 - 系统间通过消息通信,不用关心其他系统的处理。流量削锋 - 可以通过消息队列长度控制请求量;可以缓解短时间内的高并发请求。日志处理 - 解决大量日志传输。消息通讯 - 消息队列一般都内置了高效的通信机制,因此也可以用在纯的消息通讯。比如实现点对点消息队列,或者聊天室等。
详答
解耦、异步、削峰是什么?。
解耦:A 系统发送数据到 BCD 三个系统,通过接口调用发送。如果 E 系统也要这个数据呢?那如果 C 系统现在不需要了呢?A 系统负责人几乎崩溃…A 系统跟其它各种乱七八糟的系统严重耦合,A 系统产生一条比较关键的数据,很多系统都需要 A 系统将这个数据发送过来。如果使用 MQ,A 系统产生一条数据,发送到 MQ 里面去,哪个系统需要数据自己去 MQ 里面消费。如果新系统需要数据,直接从 MQ 里消费即可;如果某个系统不需要这条数据了,就取消对 MQ 消息的消费即可。这样下来,A 系统压根儿不需要去考虑要给谁发送数据,不需要维护这个代码,也不需要考虑人家是否调用成功、失败超时等情况。就是一个系统或者一个模块,调用了多个系统或者模块,互相之间的调用很复杂,维护起来很麻烦。但是其实这个调用是不需要直接同步调用接口的,如果用 MQ 给它异步化解耦。异步:A 系统接收一个请求,需要在自己本地写库,还需要在 BCD 三个系统写库,自己本地写库要 3ms,BCD 三个系统分别写库要 300ms、450ms、200ms。最终请求总延时是 3 + 300 + 450 + 200 = 953ms,接近 1s,用户感觉搞个什么东西,慢死了慢死了。用户通过浏览器发起请求。如果使用 MQ,那么 A 系统连续发送 3 条消息到 MQ 队列中,假如耗时 5ms,A 系统从接受一个请求到返回响应给用户,总时长是 3 + 5 = 8ms。削峰:减少高峰时期对服务器压力。
缺点有以下几个:
系统可用性降低本来系统运行好好的,现在你非要加入个消息队列进去,那消息队列挂了,你的系统不是呵呵了。因此,系统可用性会降低;系统复杂度提高加入了消息队列,要多考虑很多方面的问题,比如:一致性问题、如何保证消息不被重复消费、如何保证消息可靠性传输等。因此,需要考虑的东西更多,复杂性增大。一致性问题A 系统处理完了直接返回成功了,人都以为你这个请求就成功了;但是问题是,要是 BCD 三个系统那里,BD 两个系统写库成功了,结果 C 系统写库失败了,咋整?你这数据就不一致了。
https://juejin.cn/post/6844904125935665160
七、Redis
1、redis的分布式锁怎么用和是实现方式?redis 分布式锁其实就是在系统里面占一个“坑”,其他程序也要占“坑”的时候,占用成功了就可以继续执行,失败了就只能放弃或稍后重试。占坑一般使用 setnx(set if not exists)指令,只允许被一个程序占有,使用完调用 del 释放锁。
2、redis 持久化有几种方式?redis 的持久化有两种方式,或者说有两种策略:
RDB(Redis Database):指定的时间间隔能对你的数据进行快照存储。AOF(Append ONLY File):每一个收到的写命令都通过 write 函数追加到文件中。
3、redis哨兵模式?集群模式?https://juejin.cn/post/7109506994075074567
4、redis 淘汰策略有哪些?volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰。
volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰。volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰。allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰。allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰。no-enviction(驱逐):禁止驱逐数据。
5、redis缓存穿透?缓存雪崩?缓存击穿?解决方式有哪些?一、缓存雪崩
我们可以简单的理解为:由于原有缓存失效,新缓存未到期间(例如:我们设置缓存时采用了相同的过期时间,在同一时刻出现大面积的缓存过期),所有原本应该访问缓存的请求都去查询数据库了,而对数据 库CPU和内存造成巨大压力,严重的会造成数据库宕机。从而形成一系列连锁反应,造成整个系统崩溃。 解决办法: 大多数系统设计者考虑用加锁( 最多的解决方案)或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上。还有一个简单方案就将缓存失效时间分散开。 二、缓存穿透 缓存穿透是指用户查询数据,在数据库没有,自然在缓存中也不会有。这样就导致用户查询的时候,在缓存中找不到,每次都要去数据库再查询一遍,然后返回空(相当于进行了两次无用的查询)。这样请求就绕过缓存直接查数据库,这也是经常提的缓存命中率问题。 解决办法; 最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。另外也有一个更为简单粗 暴的方法,如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。通过这个直接设置的默认值存放到缓存,这 样第二次到缓冲中获取就有值了,而不会继续访问数据库,这种办法最简单粗暴。 5TB的硬盘上放满了数据,请写一个算法将这些数据进行排重。如果这些数据是一些32bit大小的数据 该如何解决?如果是64bit的呢? 对于空间的利用到达了一种极致,那就是Bitmap和布隆过滤器(Bloom Filter)。 Bitmap: 典型的就是哈希表 缺点是,Bitmap对于每个元素只能记录1bit信息,如果还想完成额外的功能,恐怕只能靠牺牲更多的空间、时间来完成了布隆过滤器(推荐)就是引入了k(k>1)k(k>1)个相互独立的哈希函数,保证在给定的空间、误判率下,完成元素判重的过程。 它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。 Bloom-Filter算法的核心思想就是利用多个不同的Hash函数来解决“冲突”。 Hash存在一个冲突(碰撞)的问题,用同一个Hash得到的两个URL的值有可能相同。为了减少冲突,我们可以多引入几个Hash,如果通过其中的一个Hash值我们得出某元素不在集合中,那么该元素肯定不在集合中。只有在所有的Hash函数告诉我们该元素在集合中时,才能确定该元素存在于集合中。这便是Bloom-Filter的基本思想。 Bloom-Filter一般用于在大数据量的集合中判定某元素是否存在。 三、缓存预热缓存预热这个应该是一个比较常见的概念,相信很多小伙伴都应该可以很容易的理解,缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据 库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据! 解决思路: 1、直接写个缓存刷新页面,上线时手工操作下; 2、数据量不大,可以在项目启动的时候自动进行加载; 3、定时刷新缓存四、缓存更新除了缓存服务器自带的缓存失效策略之外(Redis默认的有6中策略可供选择),我们还可以根据具体的业务需求进行自定义的缓存淘汰,常见的策略有两种:(1)定时去清理过期的缓存; (2)当有用户请求过来时,再判断这个请求所用到的缓存是否过期,过期的话就去底层系统得到新数据并更新缓存。 两者各有优劣,第一种的缺点是维护大量缓存的key是比较麻烦的,第二种的缺点就是每次用户请求过来都要判断缓存失效,逻辑相对比较复杂!具体用哪种方案,大家可以根据自己的应用场景来权衡五、缓存降级当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然需要保证服务还是可用的,即使是有损服务。系统可以根据一些关键数据进行自动降级,也可以配置开 关实现人工降级。 降级的最终目的是保证核心服务可用,即使是有损的。而且有些服务是无法降级的(如加入购物车、结算)。以参考日志级别设置预案: (1)一般:比如有些服务偶尔因为网络抖动或者服务正在上线而超时,可以自动降级; (2)警告:有些服务在一段时间内成功率有波动(如在95~100%之间),可以自动降级或人工降级,并发送告警; (3)错误:比如可用率低于90%,或者数据库连接池被打爆了,或者访问量突然猛增到系统能承受的最大阀值,此时可以根据情况自动降级或者人工降级; (4)严重错误:比如因为特殊原因数据错误了,此时需要紧急人工降级。服务降级的目的,是为了防止Redis服务故障,导致数据库跟着一起发生雪崩问题。因此,对于不重要的缓存数据,可以采取服务降级策略,例如一个比较常见的做法就是,Redis出现问题,不去数据库查询,而是直接返回默认值给用户
6、redis为啥那么快?Redis 是基于内存的数据库,跟磁盘数据库相比,完全吊打磁盘的速度。不论读写操作都是在内存上完成的,内存直接由 CPU 控制,也就是 CPU 内部集成的内存控制器,所以说内存是直接与 CPU 对接,享受与 CPU 通信的最优带宽。首先,采用了多路复用io阻塞机制多路I/O复用模型是利用 select、poll、epoll 可以同时监察多个流的 I/O 事件的能力,在空闲的时候,会把当前线程阻塞掉,当有一个或多个流有 I/O 事件时,就从阻塞态中唤醒,于是程序就会轮询一遍所有的流(epoll 是只轮询那些真正发出了事件的流),并且只依次顺序的处理就绪的流,这种做法就避免了大量的无用操作。这里“多路”指的是多个网络连接,“复用”指的是复用同一个线程。采用多路 I/O 复用技术可以让单个线程高效的处理多个连接请求(尽量减少网络 IO 的时间消耗),且 Redis 在内存中操作数据的速度非常快,也就是说内存内的操作不会成为影响Redis性能的瓶颈,主要由以上几点造就了 Redis 具有很高的吞吐量。然后,数据结构简单,操作节省时间最后,运行在内存中,自然速度快