封装:将客观事物封装成抽象的类,并对属性和方法实现访问控制;
继承:继承父类的非私有属性和方法,分为实现继承和接口继承,实现继承不需要实现方法,接口继承继承抽象方法,需要实现方法;
多态:一个实例的相同方法在不同的情形下有不同的表现形式,父类引用不同的子类实现;
抽象:数据抽象->类的属性,过程抽象->类的方法。
final:常量关键字,标识属性时,属性在赋值后不可被改变,标识方法后,方法不可被重写,但可被重载,标识类时,类不可被继承;
finally:用try catch后面,表示,不管有无异常发生,finally里的代码最后都会被执行,一般用来进行一些连接的关闭;
finalize:Objct中的一个方法,任何类都可以重写它,该方法在垃圾回收器清除对象之前被调用。
当然,equals可以被重写,但必须遵循以下五个原则:
1. 三目运算符会对统一冒号两边的数据类型,可能引起空指针,如下
Long aLong = null;
Long bLong = null;
Long s = aLong == null ? bLong : 0;//会报空指针,bLong自动拆箱
Long s1 = aLong == null ? bLong : aLong;//不会发生自动拆箱,不会报空指针
Long s2 = aLong == null ? 0 : aLong;//不会发生自动拆箱,不会报空指针
Long s3 = aLong == null ? bLong : aLong + bLong;//会报空指针,相加封装类型会自动拆箱
GET:从服务器请求数据,不应该进行修改,参数显示在url后面,可缓存;
POST:向服务器提交数据,进行数据的更新,参数保存在HTTP请求包的包体中,不可缓存。
session:由于http是无状态协议,所以用session来保存用户的会话状态,session保存在服务器,用户登录后服务器会返回给客户端一个名为JsessionId的cookie,用户再次访问时就会带上这个cookie,服务器通过它找到相应的session,session cookie针对某一次会话而言,会话结束session cookie也就消失了,session是需要借助cookie才能正常工作的,如果客户端完全禁止cookie,session将失效;
cookies:cookies保存在客户端浏览器中,通过session和cookie可以实现自动登录。
实现session分布式处理,主要实现各模块通过JSESSIONID去拿到session,session可以缓存到redis中。
session分布式处理的主要问题:
session过期。 通过Timing Wheel(时间轮),管理大量定时任务,定时销毁过期session。
分布式集群session的几种处理方式:
粘性Session是指将用户锁定到某一个服务器上,同一个用户访问的都是同一台服务器。
优点:简单,不用对session做任何处理。
缺点:缺乏容错性,如果当前访问的服务器发生故障,用户被转移到第二个服务器上时,他的session信息都将失效。
服务器session赋值
任何服务器上的session发生改变,该节点会把这个session的所有内容序列化,然后给其他节点保存,以此保证session同步。
优点:可容错,各服务器间session能够实时响应。
缺点:网络负荷压力大,节点多或访问量大时性能低下。
session共享机制(主要使用)
使用分布式缓存方案,如redis集群保存session。
session持久化到数据库
优点:服务器出现问题,session不会丢失。
缺点:如果网站的访问量很大,把session存储到数据库中,会对数据库造成很大压力,读取session较慢。
terracotta实现session复制
Terracotta的基本原理是对于集群间共享的数据,当在一个节点发生变化的时候,Terracotta只把变化的部分发送给Terracotta服务器,然后由服务器把它转发给真正需要这个数据的节点。可以看成是对第二种方案的优化。
优点:这样对网络的压力就非常小,各个节点也不必浪费CPU时间和内存进行大量的序列化操作。把这种集群间数据共享的机制应用在session同步上,既避免了对数据库的依赖,又能达到负载均衡和灾难恢复的效果。
详细讲述分布式session传送门
用户进程空间<——>内核空间
内核空间<——>设备空间(磁盘网络等)
网络io通常包括下面两个阶段:
等待网络数据到达网卡—>读取到内核缓冲区,数据准备好;
从内核缓冲区复制数据到进程空间;
所谓同步,就是在发出一个“调用”时,在没有得到结果之前,该调用就不会返回,但是一旦调用返回,就得到返回值了;
异步则是相反,“调用”在发出之后,调用者不会立即得到结果,而是在“调用”发出后,被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用。
阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回
非阻塞调用是指在不能立即得到结果之前,该调用不会阻塞当前线程。
同步io:当用户发出io请求操作之后,如果数据没有准备就绪,需要通过用户线程或内核不断去轮询数据是否就绪,当数据就绪时,再将数据从内核拷贝到用户线程;
异步io:只有io请求操作的发出是由用户线程来进行的,内核自动完成数据是否准备就绪和数据拷贝到用户空间的过程,然后通知用户线程io操作已经完成;
阻塞io:内核在检查数据未就绪时,会一直等待,直到数据就绪;
非阻塞io:如果数据没有就绪,则会返回一个标志消息告知用户线程当前要读的数据没有准备就绪;
ArrayList:底层由数组实现,增删慢,查找快,线程不安全
LinkedList:底层由链表实现,增删快,查找慢,线程不安全,LinedList比ArrayList更占内存
Vector:底层由数组实现,增删慢,查找快,线程安全
HashSet:底层数据结构是哈希表(即实现了一个HashMap),依赖于hashCode()和equals两个方法
TreeSet: 底层数据结构是红黑树(一个自平衡的二叉树,TreeMap),TreeSet保证保证元素的排序方式,自然排序(元素实现Comparable接口)和比较器排序(元素实现Comparator接口)
LinkedHashSet:底层数据结构为链表和哈希表,由链表保证元素有序,由哈希表保证元素唯一
HashMap:基于散列表实现,插入和查询键值对的开销固定,线程不同步,hashMap的键和值可以接受null
Hashtable:与HashMap类似,但是是线程同步的
LinkedHashMap:通过链表保存了记录的插入顺序,用Iterator遍历LinkedHashMap时,先得到的记录肯定是先插入的,线程不同步
ConcurrentHashMap:实现分段加锁,兼顾了线程安全性和运行的效率
当要插入一个键值对时,首先计算key的hash值,利用key的哈希值与map的容量进行二进制与运算,获得键值对的在数组中的插入位置,若发生冲突,即两个键值对插在同一个位置,新的键值对会放在链表的头部,hashMap认为后插入的Entry被查找的可能性更大,这里,Entry是一个双向循环链表的时,查找可能会发生死循环;
HashMap有两个重要的参数容量和负载因子,随着插入的值越来越多,hashmap发生冲突的数量就越多,这时候,hashmap就需要扩容,hashmap在当size大于初始容量时,会进行扩容,并且每次自动扩展或手动初始化时,长度必须是2的幂次方,这是为了让键值对在hashmap中更均匀的分布,因为2的幂次方-1后的二进制值都为1,在做与运算时,键的hash值能更好的散列,扩容时会对原来的key重新进行散列,使其均匀地分布在新的hash表中
ConcurrentHashMap通过使用Segment的方式来减少悲观锁的产生,ConcurrentHashMap可以看成一个二级哈希表,第一个哈希表中存储Segment对象,而Segment对象里又维护了一个哈希表,所以ConcurrentHashMap在进行get、put操作是要对key进行俩次哈希计算,第一次哈希计算找到Segment,第二次哈希计算找到真正键值对存放的位置;
ConcurrentHashMap只在put操作时对Segment加锁,因此,不同线程在操作不同Segment时不会阻塞,减少了悲观锁的产生,提高了效率;
ConcurrentHashMap是使用了类似于CAS乐观锁的思想来保证size方法统计时不会出现问题,用modCount变量就是用来统计当前集合修改次数;
线程有以下五种状态:
线程的状态转换图:
父类 > 子类,静态 > 非静态 > 构造方法
父类的静态代码块
本类的静态代码块
父类的普通代码块
本类的普通代码块
父类的构造方法
本类的构造方法
java栈:是线程隔离的,存放对象的地址,方法执行时生成栈帧,每个栈帧都包含一些局部变量、基本类型变量区、操作栈和方法返回值等信息;
本地方法栈:是线程隔离的,和java栈类似,执行系统方法时使用;
程序计数器:是线程隔离的,保存当前正在执行的程序的内存地址,为了线程切换后能回到准确的执行位置,每个线程都需要有一个独立的程序计数器,各个本地方法栈:是线程隔离的,和java栈类似,执行系统方法时使用;
堆:线程共享,在jvm启动时创建,存储对象,包括对象的属性,对象方法的地址等,java堆是垃圾回收器(GC)管理的主要区域,是线程不安全的;
方法区:线程共享,方法区存放了要加载的类的消息(名称、修饰符等)、类的静态变量、final类型常量、类的属性信息、类的方法信息等,代表一个类的Class对象,并且包括常量池(java8常量池从方法堆中移除,方法区使用元空间实现)。方法区也是堆的一部分,即是java堆中的永久代Permanet Generation,存储相对稳定。
垃圾回收机制:不同的对象生命周期不同,采用分代垃圾回收,把不同生命周期的对象放在不同的代上,不同的代上采用适合的垃圾回收方式进行回收。
jvm共划分为三个分代空间:年轻代(Young Generation)、年老代(Old Generation)和持久代(Permanent)
垃圾回收方式有两种:次收集(minor gc)和全收集(full gc)
垃圾回收器种类:
分布式垃圾回收(DGC):
服务端的一个本地对象持有它的本地引用
客户端获得远程对象的存根对象,也就是说,客户端持有它的远程引用
服务端的远程对象已经注册到rmiregistry注册表中,也就是说,rmiregistry注册表持有了它的远程引用
当客户端获得一个远程对象存根时,就会向服务器方式一条租约(lease)通知,以告诉服务器自己持有了这个远程对象的引用
租约是有期限的,如果租约到期了,服务器则认为客户端不再持有远程对象的引用
客户端定期向服务端发送租约通知,以保证服务器知道客户端一直持有者远程对象的引用
jvm把class文件中的类描述数据从文件加载到内存,并对数据进行校验、解析、初始化,使这些数据最终成为可以被jvm直接使用的java类型,这个过程叫做java的类加载机制。
class文件中的“类”从加载到jvm内存中,到卸载出内存过程有以下七个声明周期阶段:
加载:主要完成下面三件事:
通过一个类的全限定名来获取这个类的二进制字节流(class文件)。获取的方式可以通过jar包、war包、网络中获取、jsp文件生成等方式;
将这个字节流所代表的静态存储结构转化为方法区的的运行时数据;
在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口,存放在方法区。
验证:验证被加载后的类是否有正确的结构,类数据是否符合虚拟机的要求,确保不会危害虚拟机安全
准备:为静态变量在方法区分配内存,并赋默认初值(0或null),静态常量直接初始化,如statit int a=100,在准备阶段a
解析:将类的二进制数据中的符号引用(因为在class文件中不知道真正的内存地址,所以用符号表示)换成直接引用(指向内存中的地址)
初始化:初始化的主要工作是为静态变量赋程序设定的默认初值,如statis int a=100,在准备阶段a=0,初始化后a=100。有且只有以下五种情况必须对类进行初始化:
new 创建类的实例;
对类进行反射调用的时候,如果类没有进行过初始化,则要先对类进行初始化;
当初始化化一个类时,如果它的父类没有初始化,则必须先初始化符类;
当虚拟机启动时,用户指定一个主类(包含main方法的类),虚拟机会首先初始化这个类;
当使用jdk1.7的动态语言支持时,java.lang.invoke.MethodHandler实例最后解析的方法句柄,并且这个方法句柄对应的类没有初始化。
使用
卸载
其中,验证、准备、解析统称为链接阶段,加载、验证、准备、初始化、卸载的开始顺序是确定的,但进行与结束的顺序不一定。
判断两个类相等需要满足下面三个条件:
同一个class文件
同一个类加载器加载
同一个虚拟机加载
类加载器分类:
类加载器之间的层次关系被称为类加载器的双亲委派模型, 该模型要求除了顶层的启动类加载器外,其他的类加载器都应该有自己的父类加载器,而这种父子关系一般通过组合关系来实现,而不是通过继承。
双亲委派加载机制:当一个类收到了类加载请求,它首先不会尝试自己去加载这个类,而是把这个加载任务委派给父类去完成,每一个层次类加载器都是如此,因此所有请求都应该传送到启动类加载器中,只有当父类加载器反馈自己无法完成这个请求的时候(在它的加载路径下没有找到所需加载的Class),子类加载器才会尝试自己去加载。
双亲委派模型的作用:防止内存中出现多份同样的字节码,即确保同一个类不会被加载两次。
通常不可以,因为jvm通过全限定名加载类,即使我们写了这样一个类,由于类加载器的双亲委派模型,我们自己写的类也不会被加载到(系统的System已经先被加载了),这时我们可以自定义一个类加载器,并把该类放到一个其他加载器加载不到的地方,这样就可以由自定义的类加载器来加载该类。
Class.forName():除将类的.class文件加载到jvm中外,还会对类进行解释,执行类的static代码块(也有可以控制是否执行的重载方法);
Class.loadClass():只是将类的.class文件加载到jvm中,不会执行static中的内容,只有在newInstance(反射)才会去执行static块;
概念:
静态分派发生在编译期,分派根据静态类型消息发生,方法重载就是静态分派,编译时多态,还有静态方法也是;
动态分派发生在运行期,分派根据实际类型定位方法执行版本,方法重写就是动态分派,运行时多态(普通方法);
单分派根据一个宗量的类型(实际类型)进行方法的选择
多分派根据多于一个宗量的类型进行选择
动态分派编译器就确定了目标方法的参数类型(如重写,方法参数确定),唯一可以影响到虚拟机选择的因素只有此方法的接收者的实际类型。
静态分派选择依据为方法接收者的静态类型和方法参数的类型。
在运行状态中,对于任意一个类,都能知道这个类的属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取消息及动态调用对象方法的功能叫做java的反射机制。
获得Class对象的三种方式:
优点:
缺点:
构造器引用:Object::new
普通方法:Object::a
静态方法引用:Class::a
优点:
缺点:
聚集索引和非聚集索引根本的区别是表记录的排列顺序和索引的排列顺序是否一致。
水平拆分:即把表的数据按某种规则(比如按ID散列)切分到一个或多个数据库(server)上,水平拆分适用于数据库表并不多,而单张表的数据量很大;
垂直拆分:即把关系紧密(比如同一模块)的表切分出来放在不同的server上,垂直拆分使用于因为表多而数据多的情况;
一般情况都是水平拆分和垂直拆分结合起来,先进行垂直拆分,再对高负载的server进行水平拆分;
分表分库中间件mycat
分库策略:
根据数值范围,比如用户Id为1-9999的记录分到第一个库,10000-20000的分到第二个库,以此类推。
根据数值取模,比如用户Id mod n,余数为0的记录放到第一个库,余数为1的放到第二个库,以此类推。
分库分表带来的问题:
事务问题:
使用分布式事务:交给数据库管理,简单有效,但性能代价高,特别是shard越来越多时;
由应用程序和数据库共同控制:将一个跨多个数据库的分布式事务分拆成多个仅处 于单个数据库上面的小事务,并通过应用程序来总控 各个小事务。
跨节点join问题:分两次查询实现。在第一次查询的结果集中找出关联数据的id,根据这些id发起第二次请求得到关联数据。
ID问题:不能再依赖数据库自身的主键生成机制,使用UUID作主键是最简单的方案,但是缺点也是非常明显的。由于UUID非常的长,除占用大量存储空间外,最主要的问题是在索引上,在建立索引和基于索引进行查询时都存在性能问题
跨分片的排序分页:需要在不同的分片节点中将数据进行排序并返回,并将不同分片返回的结果集进行汇总和再次排序
死锁一般是两个操作都要等待对方释放锁后才会释放里一个对象需要的锁,或者形成环路,导致锁无法释放。
mysql的造成死锁有以下几种情况:
如何尽量避免死锁:
主要是减少读写磁盘的次数,磁盘与主存的速度差距巨大,b树能很大减少读写磁盘的次数;
B树是很严格的平衡二叉搜索树(叶子节点深度都相同),B树树深较小,读写磁盘次数大大减少。
B树插入和删除的时候,是采用沿途分裂和沿途和并的方式进行的,和查找时,读写磁盘的次数差不多
并且InnoDB引擎在B+树叶子节点层添加了顺序访问的指针,从而加快顺序访问的速度
limit当数目过大时,数据读取会很慢,limit10000,20的意思扫描满足条件的10020行,扔掉前面的10000行,返回最后的20行,问题就在这里,优化:
子查询优化法:先找出第一条数据,然后大于等于这条数据的id就是要获取的数据,如:select * from Member where id>= (select id from Member limit 10000,1) limit 20;但是数据必须是连续的,可以说不能有where条件,where条件会筛选数据;
倒排表优化法:倒排表法类似建立索引,用一张表来维护页数,然后通过高效的连接得到数据,但只适合数据数固定的情况,数据不能删除,维护页表困难;
反向查找优化法:偏移超过一半记录数的时候,先用排序,这样偏移就反转了,但order by优化比较麻烦,要增加索引,索引影响数据的修改效率,并且要知道总记录数 ,偏移大于数据的一半;
limit限制优化法:把limit偏移量限制低于某个数。。超过这个数等于没数据
事务的ACID特性:
原子性(Atomicity):改变数据状态要么是一起完成,要么一起失败
持久性(Durable):事务提交后,数据必须以一种持久性方式存储起来
隔离性(Isolation):事务以相互隔离的方式执行,即使有并发事务,彼此之间也影响
一致性(Consistency):数据的状态是完整一致的
事务的4种隔离级别:
ReadUncommitted:读未提交,一个事务可以读取另一个未提交事务的数据,即脏读;
ReadCommitted:读提交,一个事务要等另一个事务提交后才可以读取数据,可以解决脏读,但是还存在不可重复读问题,即一个事务内两个相同的查询却返回了不同的数据,因为在这两次查询中可能有另一个事务update修改了数据;
Repeatable read:重复读,就是在读取数据时,开启事务,不允许其他事务update,重复读可以解决不可重复读问题,但还是存在幻读,即一个事务两次查询之间另一个事务insert插入了数据,导致第二次查询出的数据多了一条记录;
Serializable:序列化,Serializable 是最高的事务隔离级别,在该级别下,事务串行化顺序执行,可以避免脏读、不可重复读和幻读问题,但是这种事务隔离级别效率低下,比较耗数据库性能,一般不使用
事务的传播行为:
PROPAGATION_REQUIRED:支持当前事务,如果当前没有事务,就新建一个事务
比如:方法B的事务级别是PROPAGATION_REQUIRED,方法A中调用方法B,这是如方法A已经起了一个事务,方法B就不会新起事务,而是处于A方法的事务中,如果A方法没有事务,则B方法会新起一个事务
PROPAGATION_SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行
和上面不同的是,如果方法A没有事务,则方法B以非事务方式执行
PROPAGATION_MANDATORY:支持当前事务,如果当前没有事务,就抛出异常
如果方法A没有事务,则方法B抛出异常,即它只能被父事务调用
PROPAGATION_REQUIRES_NEW:新建事务,如果当前存在事务,把当前事务挂起
方法B会新建一个事务,方法A的事务会被挂起,等待方法B的事务执行完后,方法A的事务才继续执行。相当于两个事务
PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
方法B以非事务方法执行,方法A的事务被挂起,等待方法B执行完,方法A的事务才执行
PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常
方法B不支持在事务当中执行,即如果方法A有事务,则会抛出异常
PROPAGATION_NESTED: 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作
String:string可以包含任何数据,比如jpg图片(生成二进制)或者序列化的对象、json字符串等、序列化的二进制对象等;
Hash:hash是一个string类型的field和value的映射表,相当于hashmap,采用key-field-value的方式,一个key可对应多个field,一个field对应一个value,默认当hash成员个数不超过64个时采用线性紧凑格式存储,超过该值自动转成真正的HashMap。hash也可以用来存储对象的值;
List:list是一个链表结构,每个子元素都是string类型的双向链表,主要功能是push和pop,从链表的头部或者尾部添加删除元素,这样list既可以作为栈,又可以作为队列;
Set:set是一个string类型的无序集合,通过hash table 实现,不允许插入重复元素;
SortedSet:有序集合,一列存 value,一列存顺序,插入元素时可以指定元素插入的位置;
缓存:缓存热数据,经常会被查询,但是不经常被修改或者删除的数据;
计数器:计点击数等应用。由于单线程,可以避免并发问题,保证不会出错,而且100%毫秒级性能,不过要注意持久化;
队列:队列不仅可以把并发请求变成串行,并且还可以做队列或者栈使用;
分布式锁:验证前端的重复请求,秒杀系统,全局增量ID生成
位操作:大数据处理;
最新列表:例如新闻列表页面最新的新闻列表,如果总数量很大的情况下,尽量不要使用select a from A limit 10这种low货,尝试redis的 LPUSH命令构建List;
排行榜:用sortedset有序集合;
RDB持久化:RDB快照模式,在指定的时间间隔内生成数据集的时间点快照,Redis 将数据库快照保存在名字为 dump.rdb 的二进制文件中;
优点:
1、利于灾难恢复,快照模式只有一个rdb文件,它保存了 Redis 在某个时间点上的数据集,方便传送到别的数据中心;
2、性能最大化,父进程在保存 RDB 文件时唯一要做的就是 fork 出一个子进程,然后这个子进程就会处理接下来的所有保存工作,父进程无须执行任何磁盘 I/O 操作;
3、RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。
缺点:
1、rdb不能最大限度地避免数据丢失,因为系统一旦在定时持久化之前出现宕机现象,此前没有来得及写入磁盘的数据都将丢失;
2、数据集比较庞大时, fork() 可能会非常耗时,造成服务器在某某毫秒内停止处理客户端。
AOF持久化:AOF 持久化记录服务器执行的所有写操作命令,并在服务器启动时,通过重新执行这些命令来还原数据集,AOF 文件是一个只进行追加操作的日志文件;
优点:
1、AOF可以带来更高的数据安全性,aof同步策略有每修改同步、每秒同步和不同步,就算发生故障停机,也最多只会丢失一秒钟的数据;
2、AOF 文件是一个只进行追加操作的日志文件,因此在写入过程中即使出现宕机现象,也不会破坏日志文件中已经存在的内容;
3、Redis 可以在 AOF 文件体积变得过大时,自动地在后台对 AOF 进行重写: 重写后的新 AOF 文件包含了恢复当前数据集所需的最小命令集合;
4、AOF包含一个格式清晰、易于理解的日志文件用于记录所有的修改操,可以通过该文件完成数据的重建。
缺点:
1、对于相同数量的数据集而言,AOF文件通常要大于RDB文件;
2、根据同步策略的不同,AOF在运行效率上往往会慢于RDB。
同时使用RDB和AOF,这种情况下,当 Redis 重启时, 它会优先使用 AOF 文件来还原数据集, 因为 AOF 文件保存的数据集通常比 RDB 文件所保存的数据集更完整,当然也可以关闭持久化功能,让数据只在服务器运行时存在。
因为redis所有数据都是存储在内存中,而cpu操作内存的速度是非常快速的,可能远小于多线程时线程上下文切换的时间,所以单线程的效率会更高,省去了上下文切换的代价;
而且在单线程基础上任何原子操作都可以几乎无代价地实现,而多线程下一些操作需要进行加锁,而降低效率
缓存雪崩:短时间内,大面积缓存到过期,导致大量原本应该访问缓存的请求都去查询数据库了,而对数据库CPU和内存造成巨大压力;
措施:用加锁或者队列的方式来保证不会有大量的线程对数据库一次性进行读写,或在原有的失效时间加上随机值,避免短时间内,大量缓存过期;
缓存穿透:该key被高并发访问,缓存访问没有命中,从而导致了大量请求达到数据库;
措施:采用布隆过滤器,所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力;
缓存降级:当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然需要保证服务还是可用的,即使是有损服务。
客户端分片:通过Redis客户端预先定义好的路由规则,把对Key的访问转发到不同的Redis实例中,最后把返回结果汇集;
优点:所有的逻辑都是可控的,不依赖于第三方分布式中间件;
缺点:增加或者减少Redis实例的数量,需要手工调整分片的程序,可维护性差;
twemproxy:twitter开源的redis代理,Twemproxy通过引入一个代理层,将多个Redis实例进行统一管理,Twemproxy根据路由规则发送到正确的Redis实例,最后Twemproxy把结果汇集返回给客户端。
优点:客户端像连接Redis实例一样连接Twemproxy,不需要改任何的代码逻辑;支持无效Redis实例的自动删除;Twemproxy与Redis实例保持连接,减少了客户端与Redis实例的连接数;
缺点:由于Redis客户端的每个请求都经过Twemproxy代理才能到达Redis服务器,这个过程中会产生性能损失;Twemproxy无法平滑地增加Redis实例,维护性差;
Codis:Codis是一个支持平滑增加Redis实例的Redis代理软件,包含以下四个部分:
codis proxy:Redis客户端连接到Redis实例的代理,Codis Proxy是无状态的,可以用Keepalived等负载均衡软件部署多个Codis Proxy实现高可用
zookeeper:分布式的、开源的应用程序协调服务,Codis依赖于ZooKeeper存储数据路由表的信息和Codis Proxy节点的元信息
codis config:Codis管理工具。可以执行添加删除CodisRedis节点、添加删除Codis Proxy、数据迁移等操作
codis redis:Codis项目维护的Redis分支,添加了slot和原子的数据迁移命令
redis 3.0集群:Redis 3.0集群采用了P2P的模式,完全去中心化。Redis把所有的Key分成了16384个slot,每个Redis实例负责其中一部分slot。
优点:部署简单,不依赖其他组件;
缺点:业务升级困难,对协议进行了较大的修改,对应的Redis客户端也需要升级
异步处理:比如用户注册后,需要发注册邮件和注册短信,发送邮件和短信就可以通过消息队列进行异步处理,提高了系统的吞吐量;
应用解耦:不同应用间通过消息队列来完成通信与数据处理,比如订单系统和库存系统就可以通过消息队列来很好的解耦;
流量削峰:用消息队列缓解短时间内的高流量爆发,比如秒杀活动,短时间内流量暴增,很可能会压垮服务器,这是可以先把用户请求写入消息队列,再从消息队列中读取请求处理
日志处理:日志处理是指将消息队列应用在日志处理中,比如kafka的应用;
消息通讯:比如实现点对点消息队列,或者聊天室等;
为了保证消息必达,MQ使用了消息超时、重传、确认机制。不管是client没有收到server的ack还是server没有收到client的ack,都会使得消息可能被重复发送,这时就要保证系统的幂等性,包括mq server内部和client消费者。
mq内部的幂等性:MQ内部生成一个全局唯一、与业务无关的消息ID:inner-msg-id,可以根据inner-msg-id判断消息是否重复接收;
消息消费者的幂等性:生成一个唯一ID标记每一条消息,将消息处理成功和去重日志通过事物的形式写入去重表;
对消息进行持久化处理,如存储在内存里,存储在分布式KV里,存储在磁盘文件里,存储在数据库里等,持久化的形式能更大程度地保证消息的可靠性(如断电等不可抗外力),并且理论上能承载更大限度的消息堆积(外存的空间远大于内存)
利用MessageSource进行国际化 ,BeanFactory不支持国际化,ApplicationContext扩展了MessageResource接口,具有消息的处理能力(i18N);
applicationContext加入了强大的事件机制;
applicationContext扩展了ResourceLoader(资源加载器)接口,从而可以用来加载多个Resource;
对web应用的支持不一样;
.BeanFactroy采用的是延迟加载形式来注入Bean的,即只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化;ApplicationContext则相反,它是在容器启动时,一次性创建了所有的Bean;
实例化BeanFactoryPostProcessor实现类
执行BeanFactoryPostProcessor的postProcessBeanFactory方法
实例化BeanPostProcessor实现类
实例化InstantiationAwareBeanPostProcessor实现类
执行InstantiationAwareBeanPostProcessor的postProcessBeforeInstantiation方法
执行Bean的构造方法
执行InstantiationAwareBeanPostProcessor的postProcessPropertyValues方法
为bean注入属性
调用BeanNameAware的setBeanName方法
调用BeanFactoryAware的setBeanFactory方法
执行BeanPostProcessor的postProcessBeforeInitialization方法
执行InitializingBean的afterPropertiesSet方法
调用
执行BeanPostProcessor的postProcessAfterInitialization方法
执行InstantiationAwareBeanPostProcessor的postProcessAfterInstantiation方法
容器初始化成功,执行业务代码后,下面销毁容器
调用DiposibleBean的Destory方法
调用
通过xml配置的方式实现IOC:
xml配置文件中配置要注册bean的信息,包括beanName,class,init-method,detory-method,property,scope(作用域)等;
spring工厂类读取配置文件,根据配置信息通过反射机制创建类的实例,并通过setter方法给属性赋值;
beanfactory是最基本的Ioc容器接口,定义了Ioc容器的基本功能规范,最终的默认实现类是DefaultListableBeanFactory,beanfactory采用懒加载模式;
通过自动装配来实现依赖注入,spring提供了一系列的注解来实现bean的自动装配:
@Autowired:根据类型注入,如果需要按名称进行装配,则需要配合@Qualifier;
@Autowired注解是由spring提供,@Resource是由J2EE提供,所以,如果要减少对spring的依赖,尽量用@Resource注解;
@Resource:默认是按照名称来装配注入的,只有当找不到与名称匹配的bean才会按照类型来装配注入;
spring aop通过代理方式实现在方法调用前后插入执行相应逻辑,spring aop的代理方式主要有JDK的动态代理和CGLIB的动态代理:
Aop概念:
连接点(joinPoint):程序执行中的某一行为
通知(advice):切面对于某个连接点所产生的动作
前置通知:在连接点执行之前的通知
环绕通知:包围一个连接点的通知,可以在方法的调用前后完成自定义的行为,也可以选择不执行
返回后通知:在某个连接点正常完成执行后的通知,不包括排除异常的情况
后置通知:在连接点执行之后的通知
目标对象(target object):被一个后多个切面所通知的对象
AOP代理(aop Proxy):spring aop有两种代理,jdk代理和cglib代理
Aop可以有效降低模块之间的耦合度,使系统容易扩展,更好的代码复用
Spring事务处理,是将事务处理的工作统一起来,并为事务处理提供通用的支持。
工作原理:
spring事务源码解析:
spring mvc将所有请求都提交给DispatcherServlet,它会将请求交给其他模块负责处理
DispatcherServlet查询一个或多个HandlerMapping,找到处理请求的Controller
Controller进行业务处理后,会返回一个ModelAndView
DispatcherServlet查询一个或多个试图解析器,找到ModelAndView对象指定的视图对象
视图对象负责渲染和返回给客户端
Spring为实现单例类可继承,使用的是单例注册表的方式:
工厂模式:将对象的创建和使用分离,即应用程序将对象的创建和初始化工作交给工厂对象,这样spring管理的就不是普通bean,而是工厂bean;
单例模式:保证一个类只有一个实例,并提供一个访问它的全局访问点,spring默认情况下的bean都是单例;
适配器模式:spring mvc中就有用到适配器,由于Controller的类型不同,有多重实现方式,那么调用方式就不是确定的,因此利用适配器代替controller执行相应的方法;
代理模式:为其他对象提供一种代理以控制对这个对象的访问,spring aop中用到了jdk代理和cglib代理;
观察者模式:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新,常用的地方是listener的实现;
${}是Properties配置文件的变量占位符,它可以用于标签属性值和sql内部,属于静态文本替换
#{}是sql的参数占位符,Mybatis会将sql中的#{}替换为?号
Dao接口,就是人们常说的Mapper接口,接口的全限名,就是映射文件中的namespace的值,接口的方法名,就是映射文件中MappedStatement的id值
Dao接口的工作原理就是jdk动态代理,Mybatis运行时会使用jdk代理为dao接口生成proxy对象,代理对象proxy会拦截接口方法,转而执行MappedStatement所代表的sql,然后将sql执行结果返回
Dao接口里的方法,是不能重载的,因为是全限名+方法名的保存和寻找策略
Mybatis使用RowBounds对象进行分页,它是针对ResultSet结果集执行的内存分页,而非物理分页
分页插件的基本原理是使用Mybatis提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的sql,然后重写sql,根据dialect方言,添加对应的物理分页语句和物理分页参数
Mybatis动态sql可以让我们在Xml映射文件内,以标签的形式编写动态sql,完成逻辑判断和动态拼接sql的功能,Mybatis提供了9种动态sql标签trim|where|set|foreach|if|choose|when|otherwise|bind
其执行原理为,使用OGNL从sql参数对象中计算表达式的值,根据表达式的值动态拼接sql,以此来完成动态sql的功能
延迟加载即先从单表查询,需要时再从关联表去关联查询
resultMap可以实现高级映射(使用association、collection实现一对一及一对多映射),association、collection具备延迟加载功能。
延迟加载可以大大提高数据库性能,可以有效解决n+1问题
mybatis时半自动的,仅有基本的字段映射,需要自己手写sql,hibernate是全自动的,可以根据javabean和数据库的映射结构自动生成sql
hibernate的数据库移植性远大于mybatis,hibernate通过其强大的映射结构和hql,大大降低了对象与数据库的耦合
sql的直接优化上面,mybatis要比hibernate方便很多
hibernate有完善的二级缓存机制
Netty 作为异步高性能的通信框架,往往作为基础通信组件被一些 RPC 框架使用,如dubbo;
Netty也经常用作推送服务;
Netty还可以用来开发各种服务器
java 原生NIO的空轮询bug,若Selector的轮询结果为空,也没有wakeup或新消息处理,则发生空轮询,最终导致CPU 100%。Netty解决办法:
Netty使用reactor反应堆线程模型,reactor模式是事件驱动的,有一个或多个并发输入源,有一个事件分离器,有多个事件处理器;这个事件分离器会同步的将输入的请求(Event)多路复用的分发给相应的事件处理器。
NioEventLoop是Netty的Reactor线程,它在Netty Reactor线程模型中的职责如下:
所谓零拷贝,就是在操作数据时,不需要将数据buffer从一个内存区域拷贝到另一个内存区域,因为少了一次内存的拷贝,cpu的效率得到了提高。
服务端步骤:
客户端步骤:
心跳检测:
断线重连:
粘包:tcp传输数据包时,会发生粘包现象,前后两个不同的数据包粘在了一起,服务端很难处理
拆包:tcp传输数据包时,一个包被拆成两份,一份粘在另一个数据包上,导致一个包数据不全,一个包数据错乱,这种情况下既发生了拆包,也发生了粘包
粘包、拆包发生原因:
粘包、拆包的解决办法:
好处:前后端分离可以让前端和后端解耦,两者可以同时开工,不互相依赖,开发效率更高,而且分工比较均匀,代码也更容易管理
后端专注于:后端控制层(Restful API) 、服务层和数据访问层
前端专注于:前端控制层(Nodejs)和视图层
前后端分离模式:
rpc框架实现步骤:
rpc调用过程:
restful提供一种软件架构风格、设计风格,提供了一组设计原则和约束条件,每一个URI代表一种资源,客户端和服务器之间,传递这种资源的某种表现层,客户的通过四个HTTP动词(GET、POST、PUT、DELETE),对服务端资源进行操作,实现“表现层状态转化”。
restful优点:
接口的幂等性是指接口可重复调用,在多次调用的情况下,接口最终得到的结果是一致的。
cap理论,是指在分布式系统中,不可能同时满足一致性(Consistency)、高可用性(Availability)和分区容错性(artition tolerance)这三个基本需求,最多只能满足其中两项
对于分布式系统而言,分区容错性是一个最基本的要求,因为分布式系统中的组件必然要部署到不同的节点,必然会出现子网络,网络问题是必然会出现的异常,因此只能在一致性和高可用性之间进行权衡。
base理论是指,基本可用(Basically Available)、软状态/柔性事务(Soft-state)和最终一致性(Eventual Consistency),是基于CAP理论演化而来,是对CAP中一致性和高可用性的权衡的结果。
核心思想:即使无法做到强一致性,但每个业务根据自身的特点,采用适当的方式使系统达到最终一致性
BASE理论通过牺牲强一致性来获得可用性,是ACID的一种替代方案
分布式系统中的最终一致性解决方案:
原理:将分布式事务转化为多个本地事务,然后通过重试等方式达到最终一致性。
4. 分布式事务服务DTS方案:DTS是一个分布式事务框架,用来保障在大规模分布式环境下事务的最终一致性。
一、一个完整的业务活动由一个主业务服务和若干从业务服务组成
二、主业务服务负责发起并完成整个业务活动
三、从业务服务提供TTC性业务操作
四、业务活动管理器控制业务活动的一致性,它登记业务活动中的操作,并在活动提交时确认所有的两阶段事务的 confirm 操作,在业务活动取消时调用所有两阶段事务的 cancel 操作
分布式锁的特点:
互斥性,保证在分布式集群中,同一方法在同一时间只能被一台机器上的一个线程执行
必须是可重入锁,也叫递归锁,是指允许同一个线程多次获取锁,它有一个与锁相关的获取计数器,如果拥有锁的某个线程再次获得锁,那么计数器就加一,然后锁被释放两次才被真正释放。
不会发生死锁,客户端持有锁崩溃后,其他客户端可以获得锁
有高可用的获得锁和释放锁功能
分布式锁的三种实现方式:
开闭原则:对扩展开放、对修改关闭。即程序扩展时不能去修改原有代码,为实现这种效果尽量实现接口和抽象类
里氏替换原则(lsp):里氏替换原则是对实现抽象化的具体步骤的规范
依赖倒转原则:针对接口变成,依赖于抽象,而不依赖与具体
接口隔离原则:使用多个隔离接口,比使用单个接口要好,降低类之间的耦合度
最少知道法则(迪米特法则):一个实体应当尽量少地与其他实体发生相互作用,使系统功能模块相对独立
合成复用原则:尽量使用合成/聚合的方式(持有对方对象),而不是继承
创建者模式:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式
结构型模式:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式
行为模式:策略模式、状态模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、访问模式、中介者模式、解释器模式
静态代理:代理类和委托类实现相同的接口,代理类中接收并保存代理类对象,实现方法中调用委托类方法。
JDK代理(动态代理):代理对象不需要实现接口,代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象,但目标对象必须实现接口。
CGLIB代理:也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展。目标对象可以不用实现接口。
单例模式:单例类只能有一个实例,必须自己创建自己的唯一实例,并给其他对象提供这一实例。单例模式分为懒汉模式、饿汉模式和注册表模式。
适配器模式:适配器将某个接口转换成客户端期望的另一个接口表示,主要目的是兼容性,即接口不匹配问题,source-》adapter-》target。适配器分以下三种类型:
观察者模式:在对象之间定义了一对多的依赖,这样一来,当一个对象改变状态,依赖它的对象会收到通知并自动更新。这个模式是松耦合的,改变主题或观察者中的一方,另一方不会受到。
状态机模式:允许对象在内部转态改变时,改变它的行为,对象看起来就好像修改了它的类(每个状态可以做出不同的动作)。
状态机组成:
策略模式:策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们可以相互替换,使算法独立于使用它们的客户而独立变化
桥接模式:将抽象部分与它的实现部分分离开来,使它们可以独立变化。提高了系统的可扩充性,实现细节对客户透明,可以对客户隐藏实现细节,jdbc就是使用的桥接模式。包含如下几个角色: