根据笔者面试经验总结出来的一些知识点。mark一下,希望能给大家帮助。到高级了,需要所有的知识点都成体系,零散的知识点很容易让面试官抓到薄弱处,给与降维打击。所以,我也尽量按照知识体系模块来划分。
面试java,肯定少不了jvm原理。此外关系型数据库,非关系型数据库,分布式缓存,分布式锁;高并发,
内存模块
1、程序计数器:字符码行号指示器。
2、虚拟机栈:java方法执行的内存模型,存储局部变量,操作栈,动态栈,方法出口灯信息。使用连续的内存空间
3、java堆:保存所有对象实例,包括数组。数组也需要在堆上分配。可使用不连续的内存空间。
4、方法区:jdk1.8中,该区域已彻底移除,取而代之的是元空间。元空间不受jvm内存约束,直接在本地内存中申请内存分配。可使用不连续的内存空间。
5、运行时常量池:方法区的一部分,1.8后该区域转移到Heap堆中。
Java内存模型,除了定义了一套规范,还提供了一系列原语,封装了底层实现后,供开发者直接使用。
比如volatile,synchronize,final等。
volatile三大特性:原子性,可见性和有序性
synchronize关键字:保证同一时间只有一个线程在调度,同样保证了原子性,可见性和有序性。
可见性是基于CPU和线程之间的缓存一致性原理来说的。
对于线程独享的内存区域,生命周期与线程相同,且该区域内对象在生成时需要明确其生命周期。
对于线程共享的区域(方法区,Heap堆),生命周期与虚拟机相同。该区域内对象没有明确的死亡时间,所以GC的主要场所就在于Heap堆。
Concurrent Mode Fail:CMS GC的过程中同时业务线程将对象放入老年代,而此时老年代空间不足,或者在做Minor GC的时候,新生代Survivor空间放不下,需要放入老年代,而老年代也放不下而产生的。产生Concurrent Mode Failure的时候,收集器会降级成Serial Old收集器,停顿业务线程,等待GC收集完成。这将导致停顿时间增加,降低性能。通过调低CMSInitiatingOccupancyFraction参数,降低触发CMS的老年代负载因子,让CMS提前进入GC收集,确保分配资源时有足够的空间。
产生不连续空间:可以通过这是以下两个参数控制
-XX:UseCMSCompactAtFullCollection:在full GC钱,使用标记-整理算法
-XX:CMSFullGCBeforeCompaction=5:执行多少次非整理的Full GC后,进行一次压缩full GC。默认值为0,即每次Full GC都执行压缩full GC,整理内存碎片。
线程池中的几个重要的参数
corePoolSize :核心线程数量
maximumPoolSize :线程最大线程数
workQueue :阻塞队列,存储等待执行的任务 很重要 会对线程池运行产生重大影响
keepAliveTime :线程没有任务时最多保持多久时间终止
unit :keepAliveTime的时间单位
threadFactory :线程工厂,用来创建线程
rejectHandler :当拒绝处理任务时的策略
线程池处理流程
阻塞队列:
饱和策略(Rejected Execution Handler)
Executor框架成员-ThreadPoolExecutor
关闭线程
原理:均为便利线程池中的工作线程,并逐个调用线程的interrupt方法来终端。
只要调用其中一个方法,isShutdown方法就会返回true,所有任务都完成关闭,才表示线程池关闭成功,isTerminad方法才会返回true。
在Java中,类装载器把一个类装入Java虚拟机中,要经过三个步骤来完成:装载、链接和初始化,其中链接又可以分成校验、准备和解析三步,除了解析外,其它步骤是严格按照顺序完成的,各个步骤的主要工作如下:
Class.forName 和 ClassLoader.loadClass 的区别
Class.forName(className)方法,其实调用的方法是Class.forName(className,true,classloader);注意看第2个boolean参数,它表示的意思,在loadClass后必须初始化。比较下我们前面准备jvm加载类的知识,我们可以清晰的看到在执行过此方法后,目标对象的 static块代码已经被执行,static参数也已经被初始化。
再看ClassLoader.loadClass(className)方法,其实他调用的方法是ClassLoader.loadClass(className,false);还是注意看第2个 boolean参数,该参数表示目标对象被装载后不进行链接,这就意味这不会去执行该类静态块中间的内容。
双亲委派模式
synchronized:互斥,可重入,不可中断,非公平的隐式锁实现
synchroniz关键字在普通方法和静态方法上的区别:
synchronized修饰不加static的方法,锁是加在单个对象上,不同的对象没有竞争关系;
synchronized修饰加了static的方法,锁是加载类上,这个类所有的对象竞争一把锁。
区别
ReentrantLock
lock锁中比较常见的就是ReentrantLock,可重入锁。
锁优化
针对synchronized关键字,jvm做了一系列锁优化,使得synchronized的加锁效率和lock相当。
锁优化的策略包括:
另外,对加锁的过程也做了优化。
对资源加锁时,默认加偏向锁,即一个乐观锁。认为当前资源不存在争抢,线程获取锁后,一直持有该锁,直到有其他线程申请获取该锁。
当有线程争抢的时候,终止偏向锁,膨胀为轻量级锁。采用CAS方式置换锁资源持有者的标志位(标志位记录线程ID)。采用CAS获取锁资源,而不是挂起线程,这里其实就是自旋锁。自旋一段时间仍然获取不到锁资源,也会膨胀为重量级锁。这个时间是系统设置的,也可以让锁来根据每次从开始自旋到获取到锁资源的时间,来自适应设置这个等待时间。这时候,就是一个自适应自旋锁。
当两个以上的线程同时争抢锁资源的时候,锁会膨胀为重量级锁。争抢的线程进入阻塞队列,并挂起。
1、原理
jdk静态代理实现比较简单,一般是直接代理对象直接包装了被代理对象。
jdk动态代理是接口代理,被代理类A需要实现业务接口,业务代理类B需要实现InvocationHandler接口。jdk动态代理会根据被代理对象生成一个继承了Proxy类,并实现了该业务接口的jdk代理类,该类的字节码会被传进去的ClassLoader加载,创建了jdk代理对象实例,
jdk代理对象实例在创建时,业务代理对象实例会被赋值给Proxy类,jdk代理对象实例也就有了业务代理对象实例,同时jdk代理对象实例通过反射根据被代理类的业务方法创建了相应的Method对象m(可能有多个)。当jdk代理对象实例调用业务方法,如proxy.addUser();这个会先把对应的m对象作为参数传给invoke()方法(就是invoke方法的第二个参数),调用了jdk代理对象实例的invoke()回调方法,在invoke方法里面再通过反射来调用被代理对象的因为方法,即result = method.invoke(target, args);。
cglib动态代理是继承代理,通过ASM字节码框架修改字节码生成新的子类,重写并增强方法的功能。
2、优缺点
jdk静态代理类只能为一个被代理类服务,如果需要代理的类比较多,那么会产生过多的代理类。jdk静态代理在编译时产生class文件,运行时无需产生,可直接使用,效率好。
jdk动态代理必须实现接口,通过反射来动态代理方法,消耗系统性能。但是无需产生过多的代理类,避免了重复代码的产生,系统更加灵活。
cglib动态代理无需实现接口,通过生成子类字节码来实现,比反射快一点,没有性能问题。但是由于cglib会继承被代理类,需要重写被代理方法,所以被代理类不能是final类,被代理方法不能是final。
因此,cglib的应用更加广泛一点。
ThreadLocal数据结构
jdk1.8之后,hashMap的改进之处
1,节点table的定位,废弃了原有的indexfor方法,使用key的hash值和长度-1的二进制数做按位与运算,提高了定位的效率。采用这种方式,同样的hash值,在resize之后,位置可能不变,或者位置变为当前位置加长度的一半。hashmap扩容时,默认扩容为当前的两倍。
2,在解决hash冲突的时候,1.8之前,采用的是链表形式。链表形式的查询效率是O(n)。在n变大时,查询效率降低。因此在1.8之后,当链表元素超过TREEIFY_THRESHOLD时,会裂变成红黑树。红黑树的查询复杂度为O(logn),在n增大时,效率提升显著。
当n>TREEIFY_THRESHOLD(默认8)时,链表=》红黑树
当n
static class Node
3,concurrentHashMap除了上述改进外,废弃了原来分区加锁的概念,采用了CAS非阻塞的形式,保证了数据一致性。
IO数据结构划分
根据流的流向以及操作的数据单元不同,将流分为了四种类型,每种类型对应一种抽象基类。这四种抽象基类分别为:InputStream,Reader,OutputStream以及Writer。
内存泄漏如何排查
1,系统出现运行缓慢,或者爆出OOM异常,先分析是否是因为内存泄漏引起。jvm堆大小设置不合理,过小的时候,也会因为无法给大对象分配资源而爆出OOM;或者因为年轻代分配不了对象,导致短暂存在的大对象分配到老年代,带来频繁full GC也是运行缓慢的原因。所以首先确认,是不是因为出现了OOM。
2,如果确定是出现了内存泄漏,导致了内存溢出,参照一下步骤排查。
a,登录Linux系统,使用jps或者ps命令,获取正在执行的jvm的线程id,即PID
使用jps:jps -l
使用ps:ps aux | grep java
b,使用jstate命令查看jvm线程的状态。主要是GC状态,以及各个内存模块的占用比例。
jstat -gcutil 20954 1000 # 每1000毫秒查看一次线程20954的gc信息
c,如果确实是内存模块在短时间内达到饱和,并且居高不下,那基本上就可以确定是内存泄漏了。
d,使用jmap命令,转储堆栈信息,分析内存
jmap -histo:live 3514 | head -7
e,使用分析工具查看堆栈信息。可使用jdk自带的visualVM分析dump文件。该工具在jkd的bin目录下。
CPU过载,如何排查
1,找到最耗CPU的进程
2,找到最耗CPU的线程(假设1中查到的进程pid为10765 )
3,将线程PID转化为16进制
假设2中查到的线程id为10804,其十六进制为2a34。
之所以要转化为16进制,是因为堆栈里,线程id是用16进制表示的。
4,查看堆栈信息
jstack 10765 | grep ‘0x2a34’ -C5 --color
通过打印并过滤线程的堆栈信息,就可以看到线程在执行的任务,然后具体分析代码。
项目 | InnoDB | MyISAM |
---|---|---|
存储形式 | table.frm 表结构 table.ibd 数据和索引 |
table.frm 表结构 table.myd 数据 table.myi 索引 |
锁的粒度 | 行级锁 间隙锁策略,防止幻读 MVCC控制事务,支持并发 实现四个事务隔离级别 |
表级锁,并发写入效率低 读操作相对快速 |
事务 | 典型的事务型存储引擎 | 不支持事务 |
索引 | B+树实现聚簇索引 主键索引的叶子节点存放数据 辅助索引的叶子节点存放主键值 |
B+树实现的非聚簇索引 主键索引和辅助索引的叶子节点存放的都是数据的物理存储地址 |
存储特点 | 基于聚簇索引:对主键的查询具有很高的性能 | 非聚簇索引 支持全文索引,压缩空间函数,延迟更新索引健等;表空间小,基于压缩-》性能高 |
由于索引结构的关系,InnoDB的索引查询,最终都是要走到主键索引。而MyISAM则不需要。查询到key对应的叶子节点后,就可以直接通过叶子节点存放的物理地址,拿到数据。
B+数索引建立,B+树原理,
聚簇索引
索引的顺序即为数据的物理存储顺序。即搜索的叶子节点存放的是数据本身。
非聚簇索引
索引的顺序与数据的存储物理地址无关。索引的叶子节点存放的是数据指针。
1,经常用作过滤器,或者查询频率较高的字段上建立索引
2,在sql语句中,经常进行group by或者order by的字段上建立索引
3,在取值范围较小的枚举类字段上避免建立索引,如性别
4,对于经常存取的字段避免建立索引
5,用于链接的列(主键/外键)建立索引
6,在经常查询的多个列上,建立复合索引,先后顺序按使用频率来定。
对表数据的修改,增加,或者删除的时候,会对根据事务id,对数据生成多个版本,并通过redo,undo日志来记录操作。在事务提交之前,对于数据表的读取,都还是取原来的版本。等到数据提交后,其他事务才能读取到提交后的数据。
MySQL数据库为我们提供的四种隔离级别:
① Serializable (串行化):可避免脏读、不可重复读、幻读的发生。(锁表)
② Repeatable read (可重复读):可避免脏读、不可重复读的发生。
(mvcc每次select查询之后使用第一个查询的read view 避免读取到别人提交的数据,)
③ Read committed (读已提交):可避免脏读的发生,会读取到别人提交的事务数据。
(mvcc每次select同一条数据都会有一个新的read view,也就是会有多个)
④ Read uncommitted (读未提交):最低级别,任何情况都无法保证。
数据量达到一定级别之后,需要进行分表分库,以达到数据库快速响应的目标。
分表:数据纵向扩展,即按照某个既定的规则,根据主键索引来把数据映射到不同的表中。分表后,对于查询分页有一定影响。比较好的解决方案是每次查询都带上分表用的id来查询。否则,对于数据的查询需要排序和分页的时候,很难处理。有一种解决方案是,维护一个主键和搜索条件的中间表,只附带少数常用字段。在排序分页后,拿着主键id去查询详细数据。或者采用分表工具来实现。
分库:分库属于一个横向扩展,一般按照业务来划分。比如系统中商品数据量达到一定程度,可考虑分出一个商品库,然后商品分成20个子表。通过这种方式,达到商品信息查询的快速响应。
常见问题:
常见组件:
简单易用的组件:
强悍重量级的中间件:
通过数据库对sql语句的预编译,将传入的参数作为一个整体属性,写入到筛选的参数中,而不会被数据库解释为sql语句,从而防止sql注入。
explain查询计划中,几个核心的字段含义及枚举:
1,select_type: 查询的类型
2,type: 搜索数据的方法
3,possible_keys: 可能使用的索引
4,key: 最终决定要使用的key
5,key_len: 查询索引使用的字节数。通常越少越好
6,ref: 查询的列或常量
7,rows: 需要扫描的行数,估计值。通常越少越好
8,extra: 额外的信息
redis支持的数据类型包括:字符串、列表、集合、散列表、有序集合。
Redis为什么这么快
1、完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。数据存在内存中,类似于 HashMap,HashMap 的优势就是查找和操作的时间复杂度都是O(1);
2、数据结构简单,对数据操作也简单,Redis 中的数据结构是专门进行设计的;
3、采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;
4、使用多路 I/O 复用模型,非阻塞 IO;
5、使用底层模型不同,它们之间底层实现方式以及与客户端之间通信的应用协议不一样,Redis 直接自己构建了 VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求;
redis持久化
AOF:
RDB:
redis集群部署
一致性hash算法
判定哈希算法好坏的四个定义
1、平衡性(Balance):平衡性是指哈希的结果能够尽可能分布在所有的缓冲(Cache)中去,这样可以使得所有的缓冲空间得到利用。很多哈希算法都能够满足这一条件。
2、单调性(Monotonicity):单调性是指如果已经有一些内容通过哈希分派到了相应的缓冲中,又有新的缓冲加入到系统中。哈希的结果应该能够保证原有已分配的内容可以被映射到原有的或者新的缓冲中去,而不会映射到旧的缓冲集合中的其他缓冲区。
3、分散性(Spread):在分布式环境中,终端有可能看不到所有的缓冲,而只能看到其中的一部分。当终端希望通过哈希过程将内容映射到缓冲上去,由于不同终端所见的缓冲范围有可能不同,从而导致哈希的结果不一致,最终的结果是相同的内容被不同的终端映射到不同的缓冲区中。这种情况显然是应该避免的,因为它导致相同内容被存储到不同缓冲中去,降低了系统存储的效率。分散性的定义就是上述情况发生的严重程度。好的哈希算法应该能够尽量避免不一致的情况发生,也就是尽量降低分散性。
4、负载(Load):负载问题实际上是从另一个角度看待分散性问题。既然不同的终端可能将相同的内容映射到不同的缓冲区中,那么对于一个特定的缓冲区而言,也可能被不同的用户映射到不同的内容。与分散性一样,这种情况也是应当避免的,因此好的哈希算法应能够尽量降低缓冲的负荷。
一致性hash使用 hashcode & (len - ) 按位与的计算来快速定位节点位置。通过这个方式取代hashCode对len取模的方式,使得hash算法满足单调性,同时降低负载。
redis的分布式锁实现
何为分布式锁?
分布式锁如何实现?基于redis
详情参照:
分布式锁之Redis实现
redis实现分布式锁,当获取锁失败时如何处理。
1,首先判断处理任务的时耗,如果是短暂任务,比如50ms内能完成,那可以考虑自旋等待。让当前线程等待50ms后再次请求锁。
2,可以设置一个阈值,自旋次数达到阈值后仍然没有获取到锁,可以根据任务类型来采用不同策略处理。例如,如果是查询任务,则可以直接丢弃任务,然后抛出异常。系统查询失败后,重新查询即可。如果是数据处理任务,则可以建立阻塞队列来存储任务。
3,阻塞队列的建立有很多中形式,如果采用redis作为分布式锁,则可以直接在redis中创建阻塞队列,讲任务序列化后存储在redis队列中。数据类型可选list来实现。
参照文章:
深入讲讲spring的IoC的实现方式
软件系统,可看作由一组关注点组成。其中,直接的业务关注点,是核心关注点。而为核心关注点提供服务的,就是横切关注点。一般情况下,切面中封装的方法都是横切关注点。
封装各种横切关注点的类,就是切面。
所谓连接点是指那些被拦截到的点。就是spring配置文件中的切入点表达式中需要拦截的所有的方法。
所谓切入点是指我们要对那些joinpoint进行拦截的定义。
在类里面可以有很多方法被增强,比如实际操作中,需要增强类里面的add方法和update方法,那么当前被增强的方法称为切入点。
所谓通知是指拦截到joinpoint之后所要做的事情(增强的代码)就是通知。通知分为前置通知、后置通知、异常通知、最终通知、环绕通知。
@Before前置通知:在方法执行前执行
@After后置通知:在方法执行后执行
@After-throwing异常通知:在执行核心关注点过程中,如果抛出异常则会执行
@After-returning返回通知:在后置之前执行(如果出现异常,不执行)–在方法正常执行通过之后执行的通知
@Around环绕通知:在方法之前和之后执行
6、目标对象(Target)
代理的目标对象,即增强方法所在的类。
7、织入(weave)
将切面应用到目标对象并导致代理对象创建的过程或者说把增强用到类的过程
8、引入(introduction)
一般不适用
在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段
切点表达式
切点的功能是指出切面的通知应该从哪里织入应用的执行流。切面只能织入公共方法。
在Spring AOP中,使用AspectJ的切点表达式语言定义切点其中excecution()是最重要的描述符,其它描述符用于辅助excecution()。
excecution()的语法如下:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)throws-pattern?)
这个语法看似复杂,但是我们逐个分解一下,其实就是描述了一个方法的特征:
问号表示可选项,即可以不指定。
excecution(* com.tianmaying.service.BlogService.updateBlog(…))
modifier-pattern:表示方法的修饰符
ret-type-pattern:表示方法的返回值
declaring-type-pattern:表示方法所在的类的路径
name-pattern:表示方法名
param-pattern:表示方法的参数
throws-pattern:表示方法抛出的异常
在切点中引用Bean
Spring还提供了一个bean()描述符,用于在切点表达式中引用Spring Beans。例如:
excecution(* com.tianmaying.service.BlogService.updateBlog(…)) and bean(‘tianmayingBlog’)
这表示将切面应用于BlogService的updateBlog方法上,但是仅限于ID为tianmayingBlog的Bean。
也可以排除特定的Bean:
excecution(* com.tianmaying.service.BlogService.updateBlog(…)) and !bean(‘tianmayingBlog’)
自定义一个注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Super {
}
参数说明
@Target是用来标记作用目标
@Target(ElementType.TYPE)——接口、类、枚举、注解
@Target(ElementType.FIELD)——字段、枚举的常量
@Target(ElementType.METHOD)——方法
@Target(ElementType.PARAMETER)——方法参数
@Target(ElementType.CONSTRUCTOR) ——构造函数
@Target(ElementType.LOCAL_VARIABLE)——局部变量
@Target(ElementType.ANNOTATION_TYPE)——注解
@Target(ElementType.PACKAGE)——包
@Retention表示注解的保留位置
RetentionPolicy.SOURCE:这种类型的Annotations只在源代码级别保留,编译时就会被忽略,在class字节码文件中不包含。
RetentionPolicy.CLASS:这种类型的Annotations编译时被保留,默认的保留策略,在class文件中存在,但JVM将会忽略,运行时无法获得。
RetentionPolicy.RUNTIME:这种类型的Annotations将被JVM保留,所以他们能在运行时被JVM或其他使用反射机制的代码所读取和使用。
@Document:说明该注解将被包含在javadoc中
@Inherited:说明子类可以继承父类中的该注解
构造器注入
setter方法注入
接口注入
实现方式
@Resouce
@Inject
@Autowired
xml
spring通过默认使用单例模式和setter方法注入来避免循环引用出错。使用单例模式,在下次引用时,可以返回之前创建的实例。使用setter方法注入,可以先创建出实例,然后再进行属性的注入。
spring在TransactionDefinition接口中定义了七个事务传播行为:
spring事务失效场景:
首先使用如下代码 确认你的bean 是代理对象吗?
必须是Spring定义(通过XML或注解定义都可以)的Bean才接受事务。直接new出来的对象添加事务是不起作用的。
如使用mysql且引擎是MyISAM,则事务会不起作用,原因是MyISAM不支持事务,可以改成InnoDB;
@Transactional 注解只能应用到 public 可见度的方法上。 如果你在 protected、private 或者 package-visible 的方法上使用 @Transactional 注解,它也不会报错,事务也会失效。这一点由Spring的AOP特性决定的;
如果调用的方法没加@Transactional,那么被调用的方法家了@Transactional,也不会回滚。自调用问题,自身调用内部方法时,默认编译成this.method(),而不会重新生成代理类,自然不会走事务代理。
解决方案:
一局发生场景,选择对应的解决方案
生命周期如下:
RocketMQ在面试中那些常见问题及答案+汇总
Dubbo常见面试题
Dubbo源码环境搭建
负载均衡策略:
1,随机策略
2,轮询策略
3,最少使用策略
4,一致性hash策略
Dubbo默认选择hession作为序列化工具,Netty作为传输工具
常用的序列化方式有:
Dubbo序列化:高效二进制序列化,不成熟,不建议生产使用
hession2:高效二进制序列化,且序列化后的数据较小,适合传输
json:文本序列化,序列化之后,还要转换成二进制流来传输,性能不好
Java序列化:java自带的序列化工具,性能不理想。
关于hession序列化
Dubbo 服务注册与暴露
Dubbo(二十二)–dubbo-原理-服务暴露流程
通过ServiceBean来实现服务的注册与暴露。ServiceBean实现了两个重要的机制:
InitializingBean 和监听ContextRefreshedEvent事件。
InitializingBean:当组件创建完对象以后会调用InitializingBean的唯一的方法afterPropertiesSet,也就是在属性设置完以后来回调这个方法。afterPropertiesSet就是把spring配置文件中dubbo的标签内容保存起来。保存在ServiceBean里面。
监听ContextRefreshedEvent事件:当我们ioc容器整个刷新完成,也就是ioc容器里面所有对象都创建完成以后来回调方法onApplicationEvent(ContextRefreshedEvent event)。在这个方法里面会把上一个机制中获取到的dubbo配置包括地址,端口,服务名称等信息组成url,暴露给注册中心。同时会在底层拉起一个netty服务器,监听本机的相应端口。在暴露服务的时候,要获取到invoker(下图getInvoker()),再用exporter来暴露执行器。Exporter会用两个,dubboExporter和registryExporter。DubboExporter来开启netty服务器,registryExporter用来注册,服务(执行器)和对应的url地址,注册到注册表里。
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
一级缓存
Mybatis对缓存提供支持,但是在没有配置的默认情况下,它只开启一级缓存,一级缓存只是相对于同一个SqlSession而言。所以在参数和SQL完全一样的情况下,我们使用同一个SqlSession对象调用一个Mapper方法,往往只执行一次SQL,因为使用SelSession第一次查询后,MyBatis会将其放在缓存中,以后再查询的时候,如果没有声明需要刷新,并且缓存没有超时的情况下,SqlSession都会取出当前缓存的数据,而不会再次发送SQL到数据库。
一级缓存生命周期和sqlsession一致。随SqlSession的创建而创建,随SqlSession的销毁而销毁。SqlSession的任何insert,update,delete操作都会清空SqlSession中的一级缓存。只是清空数据,对象仍然存在,没有被销毁。
二级缓存
MyBatis的二级缓存是Application级别的缓存,它可以提高对数据库查询的效率,以提高应用的性能。SqlSessionFactory层面上的二级缓存默认是不开启的,二级缓存的开启需要进行配置,实现二级缓存的时候,MyBatis要求返回的POJO必须是可序列化的。 也就是要求实现Serializable接口,配置方法很简单,只需要在映射XML文件配置就可以开启缓存了。
1,在mapper文件中开启本mapper范围内的二级缓存
<mapper namespace="com.yihaomen.mybatis.dao.StudentMapper">
<cache eviction="LRU" flushInterval="100000" readOnly="true" size="1024"/>
mapper>
2,在配置文件中,开启全局的二级缓存
<configuration>
<settings>
<setting name="cacheEnabled" value="true" />
.....
settings>
....
configuration>
关于二级缓存:
二级缓存之所以要求所有的返回pojo都是可序列化的,是因为二级缓存的存储媒介不限于系统内存,也可以是memcached,OsCached等三方缓存库。如果是基于内存的二级缓存,可以不用实现Serializable。
三次握手,四次挥手
设计模式中心思想:高内聚、低耦合
一,单一职责原则
单一职责原则(Single Responsibility Principle, SRP):一个类只负责一个功能领域中的相应职责,或者可以定义为:就一个类而言,应该只有一个引起它变化的原因。
二,开闭原则
开闭原则(Open-Closed Principle, OCP):一个软件实体应当对扩展开放,对修改关闭。即软件实体应尽量在不修改原有代码的情况下进行扩展。
三,里氏替换原则
里氏代换原则(Liskov Substitution Principle, LSP):所有引用基类(父类)的地方必须能透明地使用其子类的对象。
四,依赖倒置原则
依赖倒转原则(Dependency Inversion Principle, DIP):抽象不应该依赖于细节,细节应当依赖于抽象。换言之,要针对接口编程,而不是针对实现编程。
五,接口隔离原则
接口隔离原则(Interface Segregation Principle, ISP):使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些它不需要的接口。
六,迪米特法则
迪米特法则(Law of Demeter, LoD):一个软件实体应当尽可能少地与其他实体发生相互作用。迪米特法则又称为最少知识原则(LeastKnowledge Principle, LKP)。
Domain Drived Design:领域驱动设计
CAP理论
CAP理论作为分布式系统的基础理论,它描述的是一个分布式系统在以下三个特性中:
实际应用中,系统最多能满足其中两个,无法做到兼顾。
BASE理论
分布式系统规范之BASE理论
IO模型可分为五种:
IO模型参照
五种IO模型(详解+形象例子说明)
SnowFlake 算法,是 Twitter 开源的分布式 id 生成算法。其核心思想就是:使用一个 64 bit 的 long 型的数字作为全局唯一 id,生成该id时,给每一位上赋予一定的含义,从而生成各个机器都不重复的全局唯一id。在分布式系统中的应用十分广泛,且ID 引入了时间戳。
雪花算法的原理和实现Java
Paxos算法
应用程序连接到任意一台服务器后提起状态修改请求(也可以是获得某个状态所的请求),会将这个请求发送给集群中其他服务器进行表决。如果某个服务器同时收到了另一个应用程序同样的修改请求,它可能会拒绝服务器1的表决,并且自己也发起一个同样的表决请求,那么其他服务器就会根据时间戳和服务器排序进行表决。
ZooKeeper Atomic Broadcast
又称ZAB协议,简化版的Paxos算法,zookeeper自己实现的保持数据一致性的协议。这个协议保证的是最终一致性。也就是说,zookeeper满足CAP理论中的A(可用)和P(分区容错)
消息广播具体步骤
1)客户端发起一个写操作请求。
2)Leader 服务器将客户端的请求转化为事务 Proposal 提案,同时为每个 Proposal 分配一个全局的ID,即zxid。
3)Leader 服务器为每个 Follower 服务器分配一个单独的队列,然后将需要广播的 Proposal 依次放到队列中取,并且根据 FIFO 策略进行消息发送。
4)Follower 接收到 Proposal 后,会首先将其以事务日志的方式写入本地磁盘中,写入成功后向 Leader 反馈一个 Ack 响应消息。
5)Leader 接收到超过半数以上 Follower 的 Ack 响应消息后,即认为消息发送成功,可以发送 commit 消息。
6)Leader 向所有 Follower 广播 commit 消息,同时自身也会完成事务提交。Follower 接收到 commit 消息后,会将上一条事务提交。