本文已参与好文召集令活动,点击查看:后端、大前端双赛道投稿,2万元奖池等你挑战!前端
- 考查对Java设计的掌握程度
- Java的private修饰符并非为了绝对安全性设计的,更多的是对用户常规使用Java的一种约束
- 从外部对对象进行常规调用时,能够清晰了解类结构
- Java类的初始化顺序:
- 基类静态代码块,基类静态成员变量(并列优先级,按照代码中出现的前后顺序执行,而且只有第一次加载时执行)
- 派生类静态代码块,派生类静态成员变量(并列优先级,按照代码中出现的前后顺序,而且只有第一次加载时执行)
- 基类普通代码块,基类普通成员变量(并列优先级,按照代码中出现的前后顺序执行)
- 基类构造函数
- 派生类普通代码块,派生类普通成员变量(并列优先级,按照代码中出现的前后顺序执行)
- 派生类构造函数
- 成员变量:
- 能够不经初始化,在类的加载过程当中的准备阶段能够赋予默认值
- 赋值和取值访问的前后顺序具备不肯定性
- 成员变量能够在一个方法调用前赋值,也能够在方法调用后进行赋值. 这是在运行时发生的,编译器肯定不了,全部交给JVM来赋值
- 局部变量:
- 在使用以前须要显式赋予初始值
- 局部变量的赋值和访问顺序是肯定的
- 这样设计是一种约束,尽最大可能减小使用者犯错:
- 假使局部变量可使用默认值,可能总会无心间忘记赋值,进而致使不可预期的状况发生
- 从结构实现上来说:
- HashMap实现是数组+链表+红黑树(红黑树部分是JDK 1.8以后增长的)
- HashMap最多容许一条记录的键为null,容许多条记录的值为null
- HashMap是非线程安全的
- 可变与不可变:
- String不可变,每一次执行 "+" 都会新生成一个新对象,因此频繁改变字符串的状况下不用String,以节省内存
- 是否多线程安全:
- StringBuilder并无对方法进行加同步锁,因此是非线程安全的.StringBuffer和String都是线程安全的
- ArrayList在内存不够时默认扩展是50%+1个,Vector默认是扩展1倍
- Vector是属于线程安全级别的,可是大多数状况下不使用Vector,由于线程安全须要更大的系统开销
- HashTable继承Dictionary类,HashMap继承AbstrctMap类
- HashTable不容许空键值对,而HashMap容许空键值对,但最多只有一个空对象
- HashTable同步,而HashMap不一样步,效率上比HashTable要高
- ConcurrentHashMap融合了HashTable和HashMap两者的优点:
- HashTable是作了同步的,是线程安全的,而HashMap未考虑同步,因此HashMap在单线程状况下效率比较高
- HashTable在多线程的状况下,同步操做能保证程序执行的正确性
- 可是HashTable是阻塞的,每次同步执行的时候都要锁住整个结构
- ConcurrentHashMap正好解决了效率和阻塞问题:
- ConcurrentHashMap容许多个修改操做并发进行,技术的关键是使用了锁分离,即一个Array保存多个Object,使用这些对象的锁做为分离锁,get或者put的时候随机使用任意一个
- ConcurrentHashMap使用了多个锁来控制对Hash表的不一样部分进行的修改
- 从JDK 1.6开始,在HashEntry结构中,每次插入将新添加节点做为链的头节点,这与HashMap实现相同.
- 每次删除一个节点时,会将删除节点以前的全部节点拷贝一份组成一个新的链,而将当前节点的上一个节点的next指向当前节点的下一个节点.从而在删除之后会有两条链存在
- 所以能够保证即便在同一条链中,有一个线程在删除,而另外一个线程在遍历,都能工做良好.由于遍历的线程能继续使用原有的链
- 在Java 8中,使用volatile HashEntry保存数据,table元素做为锁.从Table数组+单向链表又加上了红黑树
- 红黑树是一种特别的二叉查找树,红黑树的特性:
- 节点为红或黑
- 根节点为黑
- 叶节点为黑
- 一节点为红,则一节点为黑
- 一节点到其子孙节点全部路径上的黑节点数目相同
- ArrayList底层的数据结构是数组,支持随机访问.LinkedList的底层数据结构是链表,不支持随机访问
- 使用下表访问一个元素:
- ArrayList的时间复杂度是O(1)
- LinkedList的时间复杂度是O(n). LinkedList是双向链表
- Comparable接口用于定义对象的天然顺序,是排序接口
- Comparator一般用于定义用户定制的顺序,是比较接口
- 若是须要控制某个类的次序,而该类自己不支持排序,即没有实现Comparable接口,就能够创建一个"该类的比较器"来进行排序
- Comparable老是只有一个,可是能够有多个Comparator来定义对象的顺序
- 抽象类是不容许被实例化的类,一个类只能使用一次继承关系,可是一个类能够实现多个接口
- 抽象类和接口所反映出的设计理念不一样:
- 抽象类表示的是 "is - a"
- 接口表示的是 "like - a"
- 实现抽象类和接口的类必须实现其中的全部方法.抽象类能够有非抽象方法,接口中则不能有实现方法,可是在Java 8中容许接口中有静态默认方法
- 接口中定义的变量默认是public static final型,且必须给出初值,因此实现类中不能从新定义,也不能改变这个值
- 抽象类中定义的变量默认是friendly型,这个变量的值能够在子类中从新定义,也能够从新赋值
- 子类中实现父类中的抽象方法时.可见性能够大于等于父类中的
- 接口实现类类中的接口方法的可见性只能与接口中的相同,即为public
- 使用抽象类是为了重用,减小编码量,下降耦合性
- 重载和重写都是使用相同的名称实现不一样的功能,可是重载是编译时活动,重写是运行时活动
- 能够在同一个类中重载方法,但只能在子类中重写方法,重写必需要有继承
- 重载:
- 重载的时候,方法名要同样,可是参数类型和参数个数不同,返回值类型能够相同也能够不一样
- 没法以返回型别做为重载函数的区分标准
- 重写:
- 在子类中能够根据须要对从基类中继承的方法进行重写
- 重写的方法和被重写的方法必须具备相同的方法名称,参数列表和返回类型
- 重写方法不能使用比被重写方法更严格的访问权限
- Collection< E >是Java集合框架中的基本接口
- Collections是Java集合框架提供的一个工具类,其中包含了大量用于操做和返回集合的静态方法
- 多态指的是父类引用指向子类的对象,调用方法时会调用子类的实现而不是父类的实现
- 多态的实现关键在于动态绑定
- clone()
- equals()
- hashCode()
- toString()
- notify()
- notifyAll()
- wait()
- finalize()
- getClass()
- 泛型即参数化类型,在建立集合时,指定集合元素的类型,此集合只能传入该类型的参数
- 类型擦除:Java编译器生成的字节码不包括泛型信息,因此在编译时擦除
- 泛型用最顶级的父类替换
- 移除
- Lambda表达式
- 容许像对象同样传递匿名函数Stream API,充分利用现代多核CPU,能够写出很简洁的代码
- Date与Time API,有一个稳定简单的日期和时间库可供使用
- 接口中能够有静态,默认方法
- 重复注解,能够将相同的注解在同一类型上使用屡次
- protected可在包内及包外子类访问
- default只能在同一包内访问
- private只能在同一个类中访问
- 集合
- 线性结构
- 数组
- 队列
- 链表
- 栈
- 树形结构
- 图状结构
Java中的TreeMap是使用红黑树实现的java
- 匿名内部类就是没有名字的内部类,匿名内部类只能使用一次,一般用来简化代码编写
- 匿名内部类只能访问外部类的final变量
- 在Java 8中,若是局部变量被匿名内部类访问,那么该局部变量至关于自动使用了final修饰
- 经过枚举
- 经过静态内部类
- 也能够经过双重检查建立单例模式,可是这种单例模式是线程不安全的
- poll()和remove都是从队列中取出一个元素
- poll()在获取元素失败时会返回空
- remove()在获取元素失败时会抛出异常
- 使用迭代器
Iterator it = list.iterator(); while (it.hasNext()) { if (...) { it.remove(); } } 复制代码
- GPL: GNU General Public License,GNU通用公共许可协议
- LGPL: GNU Lesser General Public License, GNU宽通用公共许可协议
- BSD: Berkeley Software Distribution, 伯克利软件分发许可协议
- MIT: Massachusetts Institute of Technology
- Apache: Apache Licence, Apache许可协议
- MPL: Mozilla Public Licence, Mozilla公共许可协议
- 线程同步与否和阻塞非阻塞没有关系
- 同步是一个过程,阻塞是线程的一种状态
- 多个线程操做共享变量时会出现竞争
- 须要使用同步来防止两个以上的线程同时进入临界区内,在这个过程当中,后进入临界区的线程将阻塞,等待先进入的线程走出临界区
- 同步和异步最大的区别是: 一个须要等待,一个不须要等待
- 同步能够避免出现死锁,读脏数据的发生,通常共享某一资源的时候使用
- 若是每一个人都有修改权限,同时修改一个文件,有可能使一我的读取另外一我的已经删除的内容,就会出错
- 同步就会按照顺序来修改
- 线程池的做用是根据系统自身的状况,有效的限制执行线程的数量,使得运行效果达到最佳
- 线程池主要执行的是:
- 控制执行线程的数量
- 超出数量的线程排队等候
- 等待有任务执行完毕
- 再从队列中最前面取出任务执行
- wait()方法应该在循环中调用:
- 由于当线程获取到CPU开始执行的时候,其余条件可能尚未知足
- 因此在处理前,循环检测条件是否知足更好
- wait(),notify()和notifyAll()方法是java.lang.Object类为线程提供的用于实现线程间通讯的同步控制的等待和唤醒方法
- 实现线程的方法:
- 继承Thread类,重写run函数
- 实现Runnable接口,重写run函数
- 实现Callable接口,重写call函数
- 伪共享是多线程系统(这个系统的每隔处理器都有本身的局部缓存)中一个广泛存在的性能问题
- 缓存系统中是以缓存行(cache line)为单位存储的
- 缓存行是2的整数幂个连续字节,通常为32 - 256字节
- 最多见的缓存行是64个字节
- 当多线程修改相互独立的变量时,若是这些变量共享同一个缓存行,就会影响彼此的性能,这就是伪共享
- java.util.concurrent
- java.util.concurrent.atomic
- java.util.concurrent.lock
- ReadWriteRock读写锁的使用场景:
- 读 - 读
- 读 - 写
- 写 - 写
- 除了读 - 读之间是共享的,其他都是互斥的
- 考查对AQS, CAS的掌握程度
- Semaphore
- CountDownLatch
- CyclicBarrier
- Exchanger
- ReadWriteLock读写锁的使用场景:
- 读,读
- 读,写
- 写,写
- 除了读和读之间是共享的,其余都是互斥的
这样以后会讨论怎样实现互斥锁和同步锁的,了解对AQS,CAS的掌握程度,技术学习深度node
- Semaphore拿到执行权的线程之间是互斥的
- Semaphore, CountDownLatch, CyclicBarrier, Exchanger是Java并发编程中的4个辅助类,了解CountDownLatch和CyclicBarrier之间的区别
- Semaphore可能有多把锁,能够容许多个线程同时拥有执行权,这些有执行权的线程若是并发访问同一对象,会产生线程安全问题
- Semaphore:
- 能够有多把锁,容许多个线程同时拥有执行权
- 这些有执行权的线程若是并发访问同一对象,会产生线程安全问题
- 单例模式是最常遇到的设计模式之一,考查对常常碰到的问题的理解的深度
- 单例一共有5种实现方式:
- 饿汉
- 懒汉
- 静态内部类
- 双检锁
- 枚举
- 要是写了简单的懒汉式可能会问: 要是多线程状况下怎样保证线程安全呢?
- 使用双检锁能够保证线程安全.
- 为何要两次校验?光是双检锁还会有什么问题?
- 对象在定义的时候加上volatile关键字
- 引伸讨论原子性和可见性,Java内存模型,类的加载过程
- 枚举方式,静态内部类,双检锁均可以实现单例模式. 双检锁的单例模式:
public Class Singleton { private Singleton() { } private volatile static Singleton instance; public static Singleton getInstance() { if (null == instance) { synchronized (Singleton.class) { if (null == instance) { instance = new Singleton(); } } } return instance; } } 复制代码
- 死锁的四个条件:
- 示例: 定义两个ArrayList,都加上锁A,B.线程1,2. 线程1获取到锁A,请求锁B. 线程2获取到锁B,请求锁A. 在等待对方释放锁的过程当中都不会让出已得到的锁
public class DeadLock { public static void main(String[] args) { final List<Integer> list1 = Arrays.asList(1, 2, 3); final List<Integer> list2 = Arrays.asList(4, 5 ,6); new Thread(new Runnable() { @Override public void run() { synchronized (list1) { for (Integer i : list1) { System.out.println(i); } try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (list2) { for (Integer i : list2) { System.out.println(i); } } } } }).start(); new Thread(new Runnable() { @Override public void run() { synchronized (list2) { for (Integer i : list2) { System.out.println(i); } try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (list1) { for (Integer i : list1) { System.out.println(i); } } } } }).start(); } } 复制代码
- 相等
- new一个对象赋给变量
- 这行表达式建立了几个对象
- 是
- 不可使用for循环直接删除ArrayList中的特定元素:
- 不一样的for循环会发生不一样的异常
- 泛型for会抛出ConcurrentModificationException
- 普通的for想要删除集合中重复且连续的元素,只能删除第一个
- 缘由:
- JDK中的ArrayList源码
- ArrayList中的remove有两个同名方法,只是入参不一样:
- 入参为Object的实现:
- 通常状况下程序的执行路径走到else路径下最终调用faseRemove() 方法,会执行System.arraycopy() 方法,致使删除元素时涉及到数组元素的移动
- 普通for循环,在 遍历第一个符合删除条件的字符串时将该元素从数组中删除,而且将后一个元素即第二个元素移动到当前位置,致使下一次遍历时后一个字符串并无遍历成功,因此没法删除. 这种可使用倒序删除的方式来避免
- 解决方法: 使用迭代器Iterator
List<String> list = new ArrayList(Arrays.asList("a", "b", "b", "c", "d")); Iterator<String> iterator = list.iterator(); while (iterator.hasNext()) { String element = iterator.next(); if ("b".equals(element)) { iterator.remove(); } } 复制代码
- 第一步: 线程池判断核心线程池里的线程是否都在执行任务. 若是不是,则建立一个新的工做线程来执行任务. 若是核心线程池里的线程都在执行任务,则执行第二步
- 第二步: 线程池判断工做队列是否已经满了. 若是工做队列没有满,则将新提交的任务存储在这个工做队列中等待. 若是工做队列满了,则执行第三步
- 第三步: 线程池判断线程池的线程是否都处于工做状态. 若是没有,则建立一个新的工做线程来执行任务. 若是已经满了,则交给饱和策略来处理这个任务
- 抽象队列同步器AQS: AbstractQueuedSychronizer
- 若是说java.util.concurrent的基础是CAS的话,那么AQS就是整个Java并发包的核心
- ReentrantLock, CountDownLatch, Semaphore都用到了AQS
- AQS实际上以双向队列的形式链接全部的Entry:
- ReentrantLock: 全部等待的线程都被放在一个Entry中并连成双向队列,前面一个线程使用ReentrantLock好了,则双向队列实际上的第一个Entry开始运行
- AQS定义了对双向队列全部的操做,而且只开放了tryLock和tryRelease方法给开发者使用.开发者能够根据本身的实现重写tryLock和tryRelease方法来实现本身的并发功能
- 比较并替换CAS: Compare and Swap
- 假设有三个操做数:
- 内存之V
- 旧的预期值A
- 要修改的值B
- 当且仅当预期值A和内存值V相同时,才会将内存值修改成B并返回true. 不然什么都不作并返回false.
- 整个比较并替换的操做是一个原子操做
- CAS必需要volatile变量配合,这样才能保证每次拿到的变量是主内存中最新的响应值. 不然旧的预期值A对某条线程来讲,永远是一个不会变的值A. 只要某次CAS操做失败,则CAS操做永远不会成功
- CAS高效地解决了原子操做的问题,但仍然存在三大问题:
- 循环时间长开销很大
- 只能保证一个共享变量的原子操做
- ABA问题
- synchronized(this)原理:
- 两条指令: monitorenter和monitorexit
- 同步方法: 从同步方法的反编译的结果中能够看出 - 方法的同步并无经过指令monitorenter和monitorexit来实现,相对于普通方法,在常量池中多了ACC_SYNCHRONIZED标识符
- JVM就是根据ACC_SYNCHRONIZED标识符来实现方法同步的:
- 当方法被调用时,调用指令将会检查方法的ACC_SYNCHRONIZED访问标志是否被设置
- 若是设置了,执行线程将先获取monitor,获取成功以后才能执行方法体,方法执行完以后再释放monitor
- 在方法执行期间,其他任何线程都没法再得到同一个monitor对象
- 理解volatile关键字的做用的前提是要理解Java的内存模型
- volatile关键字的做用主要有两点:
- 多线程主要围绕可见性和原子性两个特性展开.使用volatile关键字修饰的变量,保证了在多线程之间的可见性.即每次读取到volatile变量,必定是最新的数据
- 底层代码的执行: Java代码 -> 字节码 -> 根据字节码执行对应的C/C++代码 -> C/C++代码被编译成汇编语言 -> 和硬件电路交互.现实中,为了获取更好的性能,JVM可能会对指令进行重排序,多线程下可能会出现意想不到的问题.使用volatile则会禁止对语义重排序,不过也会必定程度上下降代码的执行效率
- 从实践角度而言,volatile的一个重要做用就是和CAS结合,保证了原子性. 好比AtomicInteger
- B树和B+树,既考查MySQL索引的实现原理,也考查数据结构基础
- 首先从二叉树提及:
- 由于会产生退化现象,提出平衡二叉树
- 再提出怎么样让每一层放的节点多一些来减小遍历高度,引伸出m叉树
- m叉搜索树一样会有退化现象,引出m叉平衡树,即B树
- 这个时候每一个节点既放了key又放了value.怎样使每一个节点放尽量多的key值,以减小遍历高度也就是访问磁盘的次数
- 能够将每一个节点只放key值,将value值放在叶子节点,在叶子节点的value值增长指向相邻节点的指针,这就是优化后的B+树
- 而后谈谈数据库索引失效的状况:
- 为何给离散度低的字段,好比性别创建索引是不可取的?查询数据反而更慢
- 若是将离散度高的字段和离散度低的字段,好比性别创建联合索引会怎样,有什么须要注意的?
- AOP和IOC是Spring的精华部分
- AOP:
- AOP能够看做是对OOP的补充,对代码进行横向扩展
- 经过代理模式实现.代理模式有静态代理和动态代理.
- Spring利用的是动态代理,在程序运行过程当中将加强代码织入源代码中
- IOC: 控制反转
- 将对象的控制权交给Spring框架,用户使用对象无需建立,直接使用便可
- AOP和IOC重点要了解设计思想
- Spring的循环依赖问题:
- 什么是循环依赖?
- 怎样检测出循环依赖?
- Spring循环依赖有几种方式,使用基于setter属性的循环依赖为何不会出现问题?
- 1. 用户发送请求 -> DispatcherServlet: 前端控制器收到请求后本身不进行处理,而是委托给其他解析器进行处理,做为统一的访问点,进行全局的流程控制
- 2. DispatcherServlet -> HandlerMapping: HandlerMapping将会把请求映射为HandlerExecutionChain对象.HandlerExecutionChain包含一个Hander处理器,多个HandlerInterceptor拦截器
- 3. DispatcherServlet -> HandlerAdapter: HandlerAdapter将会将处理器包装为适配器,从而支持多种类型的处理器
- 4. HandlerAdapter -> 处理器功能方法的调用: HandlerAdapter将会根据适配的结果调用真正的处理器的功能处理方法,完成功能处理,并返回一个ModelAndView对象. ModelAndView对象包含模型数据.逻辑视图名
- 5. ModelAndView的逻辑视图名 -> ViewResolver: ViewResolver将逻辑的视图名解析为具体的View
- 6. View -> 渲染: View会根据传进来的Model模型数据进行渲染,这里的Model是一个Map数据结构
- 7. 返回控制权给DispatcherServlet,由DispatcherServlet返回响应给用户
- 第一范式: 数据库中的表的全部字段值都是不可分割的原子数据项
- 第二范式: 数据库表中的每一列都和主键相关,而不能只和主键的某一部分相关
- 第三范式: 数据库表中每一列数据都和主键直接相关,不能间接相关
- 范式是为了减小数据冗余
- 重复性强的字段,不适合添加索引
- MySQL给离散度低的字段,好比性别设置索引,再以性别做为条件查询反而会更慢
- 一个表可能会涉及两个数据结构:
- 数据表: 存放表中的数据
- 索引
- 索引:
- 将一个或几个字段(组合索引)按规律排列起来,再附加上该字段所在行数据的物理地址(位于表中)
- 好比有个字段是年龄,若是须要选取某个年龄段的全部行,那么通常状况下可能须要进行一次全表扫描
- 可是若是以这个年龄段创建一个索引,那么索引会按照年龄值根据特定的数据结构建一个排列,这样在索引中就能迅速定位,不须要进行全表扫描
- 为何性别不适合创建索引呢?
- 由于访问索引须要有额外的IO开销,从索引中拿到的只是地址,要想真正访问到数据仍是要对表进行一次IO
- 若是要从表中的100万行数据中取几个数据,那么利用索引迅速定位,访问索引的IO开销就能够忽略不计
- 若是要从标中的100万行数据取50万行数据,再访问50万次表,加起来的开销并不会比对表进行一次完整的扫描小
- 若是将性别字段设为聚焦索引,那么确定能加快大约一半该字段的查询速度
- 聚焦索引:
-所以聚焦索引要用到搜索最频繁的字段上
- 指的是表自己数据按照哪一个字段的值来进行排序
- 聚焦索引不会付出额外IO开销
- 聚焦索引只能有一个
- 能够根据业务场景须要,将性别和其他的字段创建联合索引. 好比时间戳,要将时间戳字段放在性别前面
- undo log:
- redo log:
- binlog:
- 数据库中的索引的结构是一种排序的数据结构,数据库的索引是经过B树和变形的B+树实现的
- 什么状况下不适合创建索引:
- 对于在查询过程当中不多使用或者参考的列
- 对于只有不多数据值的列
- 对于定义为image,text和bit数据类型的列
- 当修改性能远远大于检索性能时
- 根据系统自身的环境状况,有效限制线程数量,使得运行效果达到最佳
- 线程主要是经过控制执行线程的数量,超出数量的线程排队等候,等待有任务执行完毕,再从队列最前面取出任务执行
- SQL优化
- 表结构优化
- 索引优化
- 缓存参数优化
- 生产者消费者模式:
- synchronized锁住一个LinkedList:
- 生产者: 只要队列不满,生产后往里存
- 消费者: 只要队列不空,消费后往外取
- 二者经过wait() 和notify() 进行协调
- 要考虑怎么样提升效率
- 熟悉消息队列设计精要思想及使用
- 异步处理: 相对于传统的串行,并行方式,提升了系统的吞吐量
- 应用解耦: 系统间经过消息通讯,不用关心其余系统的处理
- 流量削峰: 能够经过消息队列长度控制请求量,能够缓解短期内高并发请求
- 日志处理: 解决大量日志传输
- 消息通信: 消息队列通常都内置了高效的通讯机制,所以能够用在纯的消息通信. 好比实现点对点消息队列,聊天室等
- 将全部Broker和待分配的Partition排序
- 将第i个Partion分配到第 (i mod n) 个Broker上
- 将第i个Partion的第j个Replica分配到第 ((i+j) mod n) 个Broker上
- 消息队列的顺序问题
- 消息有序指的是能够按照消息的发送顺序来消费
- 假定生产者产生了2条消息:M1,M2.假定M1发送到S1,M2发送到S2.若是要保证M1优先于M2被消费,如何保证:
- 解决方案:
- 保证生产者 - MQSever - 消费者是一对一对一的关系
- 缺陷:
- 并行度会成为系统的瓶颈,吞吐量不够
- 会出现更多的异常处理问题: 只要消费者出现问题,就会致使整个流程堵塞,不得不解决阻塞的问题
- 能够经过合理的设计或者将问题分解来规避:
- 不关注乱序的应用实际大量存在
- 队列无序并不意味着消息无序
- 消息的重复问题:
- 形成消息重复的根本缘由: 网络不可达
- 因此解决这个问题的方法就是绕过这个问题.也就是: 若是消费端收到两条同样的消息,应该怎样处理?
- 解决方案:
- 消费端处理消息的业务逻辑保持幂等性
- 只要保持幂等性,无论来多少条重复消息,最后处理的结果都同样
- 保证每条消息都有惟一编号且保证消息处理成功与去重表的日志同时出现
- 利用一张日志表来记录已经处理成功的消息的ID,若是新到的消息ID已经在日志表中,那么就再也不处理这条消息
- 服务容器负责启动,加载,运行服务提供者
- 服务提供者在启动时,向注册中心注册本身提供的服务
- 服务消费者在启动时,向注册中心订阅本身所需的服务
- 注册中心返回服务提供者地址列表给消费者,若是有变动,注册中心将基于长链接推送变动数据给消费者
- 服务消费者,从提供者地址列表中,基于软负载均衡算法,选择一台提供者进行调用.若是调用失败,再选择另外一台进行调用
- 服务消费者和服务提供者,在内存中累计调用次数和调用时间,定时每分钟发送统计数据到监控中心
- Random:
- 随机负载均衡策略,按权重设置随机几率
- 在一个截面上的碰撞几率高,但调用量越大分布越均匀,并且按几率使用权重后也比较均匀,有利于动态调整提供者权重
- RoundRobin:
- 轮循负载均衡策略,按公约后的权重设置轮循比率
- 存在慢的提供者累积请求的问题
- 好比: 第二台机器很慢,但没有宕机,当请求到第二台机器就会卡住,长此以往,全部的请求都会卡在 调到第二台机器的时候
- LeastActive:
- 最少活跃调用数负载均衡策略,相同活跃数的随机调用.活跃数指的是调用先后计数差
- 使慢的提供者收到更少的请求,由于越慢的提供者的调用先后计数差会越大
- ConsistentHash:
- 一致性Hash负载均衡策略,相同的参数请求老是发到同一提供者
- 当某台提供者宕机时,本来发往该提供者的请求,基于虚拟节点,平摊到其余提供者,不会引发剧烈变更
- 缺省只对第一个参数Hash,若是要修改,须要修改 < dubbo:parameter key="hash.arguments" value="0,1" />
- 缺省使用160份虚拟节点,若是要修改,须要修改< dubbo:parameter key="hash.nodes" value="320" >
- Failover: 失败自动切换,当出现失败,重试其余服务器. 一般用于读操做,但重试会带来更长延迟. 能够经过设置retries="2" 来设置重试次数,不包含第一次
- Failfast: 快速失败,只发起一次调用,失败当即报错. 一般用于非幂等性的写操做,好比新增记录
- Failsafe: 失败安全,出现异常时,直接忽略. 一般用于写入审计日志等操做
- Failback: 失败自动恢复,后台记录失败请求,定时重发. 一般用于消息通知操做
- Forking: 并行调用多个服务器,只要一个成功即返回. 一般用于实时性要求比较高的读操做,但须要浪费更多服务资源,能够经过设置 forks="2"来设置最大并行数
- Broadcast: 广播调用全部提供者,逐个调用,任意一台报错即报错. 一般用于通知全部提供者更新缓存或日志等本地资源信息
- Dubbo做为RPC框架,首先要完成的就是跨系统,跨网络的服务调用
- 消费方和提供方遵循统一的接口定义
- 消费方调用接口时,Dubbo将其转换为统一格式的数据结构
- 经过网络传输,提供方根据规则找到接口实现,经过反射完成调用
- 消费方获取的是对远程服务的一个代理 Proxy, 提供方由于要支持不一样的接口实现,须要一个包装层Wrapper
- 调用过程:
![]()
- 消费方的Proxy和提供方的Wrapper得以让Dubbo构建出复杂,统一的体系
- 这种动态代理与包装是经过SPI的插件方式实现的,接口就是ProxyFactory:
@SPI("javassist") public interface ProxyFactory { @Adaptive({Constants.PROXY_KEY}) <T> T getProxy(Invoker<T> invoker) throws RpcException; @Adaptive({Constants.PROXY_KEY}) <T> invoker<T> getInvoker(T proxy, Class<T> type, URL url) throws RpcException; } 复制代码
- ProxyFactor有两种实现方式:
- 基于JDK的代理实现
- 基于javassist的实现
- ProxyFactory接口上定义了 @SPI("javassist"), 默认为javassist的实现
- Dubbo序列化: 阿里基于Java的序列化实现
- Hessian2序列化: Hessian是一种跨语言的高效二进制的序列化方式. 这里实际不是原生的Hessian2序列化,而是阿里修改过的Hessian Lite,是Dubbo默认启用的序列化方式
- Json序列化: 目前有两种实现:
- 采用阿里的fastjson库
- 采用Dubbo自身实现的简单Json库
- 通常状况下,json这种文本序列化性能不如二进制序列化
- Kryo和FST: Kryo和FST的性能广泛优于Hessian和Dubbo序列化
- Hessian是一个轻量级的remoting on http工具,采用Binary RPC协议,很适合发送二进制数据,同时又具备防火墙穿透能力
- Hessian支持跨语言串行
- Hessian序列化比Java默认的序列化具备更好的性能和易用性
- Hessian序列化支持的语言比较多
- Protoco Buffer是谷歌出品的一种轻量而且高效的结构化数据存储格式,性能比Json,XML强大得多
- Protoco的序列化和反序列化简单而且速度快. 缘由在于:
- 编码和解码方式简单,只须要简单的数学运算=位移等等
- 采用Protoco Buffer自身的框架代码和编译器共同完成
- Protoco Buffer的数据压缩效果好,即序列化后数据量的体积小. 缘由在于:
- 采用独特的编码方式,好比Varint,Zigzag编码方式等等
- 采用 T - L - V 数据存储方式,减小了分隔符的使用而且数据存储得紧凑
- 能够
- Dubbo消费者在应用启动时会从注册中心拉取已注册的生产者的地址接口,并缓存在本地. 每次调用时,按照本地存储的地址进行调用
- ZooKeeper是一个分布式应用协调系统,已经应用到了许多分布式项目中,用来完成
- 统一命名服务
- 状态同步服务
- 集群管理
- 分布式应用配置项的管理
- 每一个Server在内存中存储了一份数据
- ZooKeeper启动时,将从实例中选举一个leader(Paxo协议)
- Leader负责处理数据更新等操做(Zab协议)
- 当且仅当大多数Server在内存中成功修改数据时,一个更新操做成功
- Netty是一个网络通讯框架
- Netty进行事件处理的流程:
- Channel是链接的通道,是ChannelEvent的产生者
- ChannelPipeline能够理解为ChannelHandler的集合
- IO的方式一般分为:
- 同步阻塞的BIO
- 同步非阻塞的NIO
- 异步非阻塞的AIO
- 在使用同步阻塞的BIO的网络应用:
- 若是要同时处理多个客户端请求,或者是在客户端要同时和多个服务器进行通信,就必须使用多线程来处理
- 同步非阻塞的NIO基于Reactor:
- 当socket有流可读或者可写入socket时,操做系统会相应的通知引用程序进行处理,应用再将流读取到缓冲区或者写入操做系统
- 这个时候,不是一个链接就要对应一个处理线程了.而是有效的请求,对应一个线程,当链接没有数据时,是没有工做线程来处理的
- 异步非阻塞的AIO与NIO不一样:
- 当进行读写操做时,只须要直接调用API的read或者write方法便可
- 这两种方法均为异步的:
- 对于读操做而言, 当有流可读取时,操做系统会将可读的流传入read方法的缓冲区,并通知应用程序
- 对于写操做而言,当操做系统将write方法传递的流写入完毕时,操做系统主动通知应用程序
- read或者write方法都是异步的,完成后会主动调用回调函数
- 系统拆分的分类:
- 从资源角度:
- 应用拆分
- 数据库拆分
- 从采用的前后顺序:
- 水平扩展
- 垂直拆分
- 业务拆分
- 水平拆分
- 是否使用Dubbo依据实际业务场景来决定:
- 当垂直应用愈来愈多,应用之间的交互不可避免,将核心业务抽取出来,做为独立的服务,逐渐造成稳定的服务中心,使前端应用能更快速的响应多变的市场需求. 此时,用于提升业务复用以及整合的分布式框架RPC是关键
- 当服务愈来愈多,容量的评估,小服务资源的浪费等问题逐渐显现,此时须要增长一个调度中心基于访问压力实时管理集群容量,提升集群的利用率. 此时,用于提升机器利用率的资源调度和治理中心SOA是关键
- Dubbo支持服务治理,而Thrift不支持
- Thrift是跨语言RPC框架
setNX key value value保证惟一性,避免线程A释放线程B拿到的锁面试
设置过时时间算法
set命令提供了相应的原子操做命令来保证set key value和设置过时时间的原子操做数据库
Redis集群使用的是多主多从,当一半以上的主节点set成功,才算成功编程
先Delete缓存,再更新DB,延时一段时间再Delete缓存 或者先更新DB,延时一段时间再Delete缓存json
由于若是线程A先Delete缓存,此时线程B发现缓存中没有数据,则从DB中读出老数据并reload到缓存,线程A更新数据库以后,则缓存与数据库数据库中的数据不一致,所以须要延时一段时间执行删除后端
重试机制设计模式
- 并发编程中的问题:
- 原子性问题
- 可见性问题
- 有序性问题
- volatile:
- volatile关键字能保证可见性,只能禁止指令重排序,不能保证原子性
- 可见性只能保证每次读取的是最新的值,可是volatile没法保证对变量的操做的原子性
- 在生成的会变语句中加入Lock关键字和内存屏障
- Lock:
- Lock实现提供了比使用synchronized方法和语句可得到的更普遍的锁定操做,可以使用更优雅的方式解决线程同步问题
- 用synchronized修饰的方法或者语句块在代码执行完以后锁自动释放,然而使用Lock修饰的方法或者语句须要手动释放锁
热部署
- 管道: Pipe
- 命名管道: Named Pipe
- 信号: Signal
- 消息队列: Message
- 共享内存
- 内存映射: Mapped Memory
- 信号量: Semaphore
- 套接口: Socket
Synchronized修饰静态方法,锁定自己不是实例.非静态方法锁定实例
- 死锁: 指多个进程在运行过程当中因争夺资源而形成的一种僵局
- 产生缘由: 竞争资源
- 当系统中多个进程使用共享资源,而且资源不足以知足须要,会引发进程对资源的竞争而产生死锁
- 进程间推动的顺序不当
- 请求和释放资源的顺序不当,一样也会产生进程死锁
- 互斥条件: 进程独占资源
- 请求与保持: 进程因请求资源而阻塞时,对已得到的资源保持不放
- 不剥夺条件: 进程已经得到资源,在未使用完以前,不能强行剥夺
- 循环等待: 若干进程之间造成头尾相接的循环等待资源关系
线上服务器是分布式多台部署的,常常会面临解决分布式场景下数据一致性问题,这是就要利用分布式锁来解决这些问题
- Java类的初始化顺序:
- 基类静态代码块,基类静态成员变量. 并列优先级,按照代码中出现的前后顺序执行,而且只有第一次加载时执行
- 派生类静态代码块,派生类静态成员变量. 并列优先级,按照代码中出现的前后顺序执行,而且只有第一次加载时执行
- 基类普通代码块,基类普通成员变量. 并列优先级,按照代码块中出现的前后顺序执行
- 基类构造函数.
- 派生类普通代码块,派生类普通成员变量. 并列优先级,按照代码块中出现的前后顺序执行
- 派生类构造函数.
- 方法区是JVM规范中要求的 ,永久区是Hotspot虚拟机对方法区的具体实现
- 方法区是规范,永久区是实现方式(JDK 1.8之后作了改变)
- 文件中有几个类,编译后就有几个class文件
- 成员变量是能够不经初始化的,在类加载过程的准备阶段便可以给成员变量赋予默认值.
- 局部变量在使用以前须要显式赋予初始值
- javac不是推断不出不能够这样作,对于成员变量而言,其赋值和取值访问的前后顺序具备不肯定性,对于一个成员变量能够在一个方法调用前赋值,也能够在方法调用后进行赋值,这是运行时发生的,编译器肯定不了,交给JVM作比较合适
- 对于局部变量而言,局部变量的赋值和访问顺序是肯定的,这样设计是一种约束,尽最大程度减小使用者犯错的可能性:
- 假使局部变量可使用默认值,可能总会无心间忘记赋值,进而致使不可预期的状况出现
- 类加载过程:
- 加载
- 验证: 验证阶段做用是保证Class文件的字节流包含的信息符合JVM规范,不会给JVM形成伤害
- 准备: 准备阶段为变量分配内存并设置类变量的初始化
- 解析: 解析过程是将常量池内的符号引用替换成直接引用
- 初始化
- 双亲委派模型中的方法: 双亲委派是指若是一个类收到类加载请求,不会本身先尝试加载,先找父类加载器完成.当顶层启动类加载器表示没法加载这个类的时候,子类才会本身去加载.当回到最开始的发起者加载器还没法加载时,并不会向下找,而是抛出ClassNotFound异常
- 启动(Bootstrap)类加载器
- 标准扩展(Extension)类加载器
- 应用程序(Application)类加载器
- 上下文(Custom)类加载器
- 意义是防止内存中出现多份一样的字节码
- JVM如何判断一个对象已经变成可回收的垃圾:
- 引用计数器法: 引用计数器没法解决循环引用的问题
- 根搜索算法: 从一系列的GC Roots对象开始向下搜索,搜索的路径称为引用链.当一个对象到GC Roots之间没有引用链时称为引用不可达.引用不可达的对象被认为是可回收对象
- 几种垃圾回收器:
- Serial New或者Serial Old: 串行
- Parrallel New: 并行
- Parrallel Scavenge
- Parrallel Old
- G1: 一款并行与并发收集器,而且可创建可预测的停顿时间模型,总体上是基于标记清理,局部采用复制
- CMS
- CMS收集器是一个以得到最短回收停顿时间为目标的收集器,是一种并发收集器,采用的是Mark - Sweep算法
- 方法区(Method): 被全部线程共享,方法区包含全部的类信息和静态变量
- 堆(Heap): 被全部的线程共享,存放对象实例以及数组,Java堆是GC的主要区域
- 栈(Stack): 每个线程包含一栈区,栈中保存一些局部变量
- 程序计数器: 当前线程执行的字节码行指示器
- 新生代存放全部新生成的对象
- 老年代存放的都是一些生命周期较长的对象
- 持久代主要存放的是Java类的类信息,与垃圾收集要收集的Java对象关系不大
- 内存溢出: out of memory,程序申请内存时,没有足够的内存
- 内存泄露: 垃圾对象没法回收,但是使用memory analyzer工具查看泄露
- 进程: 运行中的程序,具备独立性,动态性,并发性
- 线程: 指进程中的顺序执行流
- 进程与线程的区别:
- 进程间不共享内存
- 建立进程进行资源分配的代价要大得多,因此多线程在高并发的环境中效率高
- 序列化: 将Java对象转化为字节序列
- 反序列化: 将字节序列转化为Java对象
- 序列化和反序列化主要是为了Java线程间的通信,实现对象传递.只有实现了Serializable或者Externalizable接口类对象才可被序列化
在JVM中,int类型的变量的长度是一个固定值,与平台无关,4个字节,长度为32位
- Java中一共有四种类型的引用:
- StrongReference
- SoftReference
- WeakReference
- PhantomReference
- StrongReference是Java的默认引用实现,会尽量长时间的存活于JVM内,当没有任何对象指向时将会被GC回收
- SoftReference会尽量长的保留引用直到JVM内存不足时才会被回收,经过虚拟机保证.这一特性使得SofeReference很是适合缓存应用
- WeakReference是一个弱引用,当所引用的对象在JVM内再也不有强引用时,将被GC回收
- WeakReference和SoftReference的区别:
- WeakReference与SoftReference都有利于提升GC和内存的效率
- WeakReference一旦失去最后一个强引用,就会被GC回收
- SoftReference会尽量长的保留引用直到JVM内存不足时才会被回收,经过虚拟机保证
- Java中的堆和栈属于不一样的内存区域,使用目的也不一样
- 栈一般用于保存方法帧和局部变量.而对象老是在堆上分配
- 栈一般比堆小,也不会在多个线程之间共享,而堆是被整个JVM全部线程共享
- Java堆空间:
- 当经过Java命令启动Java进程的时候,会分配内存,内存的一部分用于建立堆空间
- 当程序中建立对象的时候,就从堆空间中分配内存
- GC:
- GC是JVM内部的一个进程,回收无效对象的内存用于未来的分配
- 在TCP链接中,数据流必须以正确的顺序送达对方
-TCP可靠性:
- 经过顺序编码和确认(ACK) 来实现的
- TCP链接是经过三次握手进行初始化的,三次握手的目的是同步链接双方序列号和确认号并交换TCP窗口大小信息:
- 第一次: 客户端发起链接
- 第二次: 表示服务器收到了客户端请求
- 第三次: 表示客户端收到了服务器反馈
- 在cookie中存储的session id
- cd: 用来改变所在目录. cd / - 转到根目录, cd ~ - 转到用户目录
- ls: 用来查看目录的内容
- cp: 用来拷贝文件. cp sourceFileName targetFileName
- mv: 移动文件. mv t.txt Document
- 加法Hash: 所谓的加法Hash就是把输入元素一个一个加起来构成最后的结果
- 位运算Hash: 这种类型的Hash函数经过利用各类位运算,好比移位或者异或来充分的混合输入元素
- 乘法Hash: 33*hash + key.charAt(i)
- 一致性Hash的设计目标是为了解决因特网中的热点(Hot spot)问题,一致性Hash算法提出了在动态变化的Cache环境中,断定Hash算法好坏的四个定义:
- 平衡性 :Balance
- 单调性 :Monotonicity
- 分散性 :Spread
- 负载 :Load
- get是从服务器获取信息, post是向服务器传信息
- get传送的数据量比较小, post传递的数据量能够比较大
- get的安全性比post低
- TCP: Tranfer Control Protocol, 是一种面向链接的保证传输协议,在传输数据流以前,双方会创建一条虚拟的通讯道,能够极少差错传输数据
- UDP: User DataGram Protocol,是一种无链接的协议,使用UDP时,每个数据段都是一个独立的信息,包括完整的源地址和目的地,在网络上以任何可能的路径到达目的地.所以,可否到达目的地,以及到达目的地的时间和内容的完整性都不能保证
- TCP比UDP多了创建链接的时间.相对UDP而言,TCP具备更高的安全性和可靠性
- TCP协议传输的大小不限制,一旦链接被创建,双方能够按照吧必定的格式传输大量的数据,而UDP是一个不可靠协议,大小有限制,每次不能超过64K