https://blog.csdn.net/u011279240/article/details/80532555
几个大厂的面试题目目录:
java基础(40题)
多线程(51题)
设计模式(8点)
JVM(12题)
数据结构与算法(17题)
数据库(22题)
Spring (13题)
Netty(7大题)
缓存(9题)
技术框架(8题)
技术深度(12题)
分布式(33题)
系统架构(18题)
linux(9大题)
TCP/IP(19点)
软能力(12点)
自我问答(44点)html
目录
java 基础 3
多线程 12
设计模式 24
JVM 31
数据结构与算法 36
数据库 44
Spring 53
Netty 58
缓存 64
技术框架 68
技术深度 72
分布式 78
系统架构 91
LINUX 95
TCP/IP 97
软能力 108
自我问答总结 109前端
java 基础java
1.8node
1.9python
public class Singleton1 {
public static final Singleton1 instance = new Singleton1();
private Singleton1(){
}
public static Singleton1 getInstance(){
return instance;
}mysql
public class Singleton3 {
private static class SingletonHolder {
//静态初始化器,由JVM来保证线程安全
private static Singleton3 instance = new Singleton3();
}
private Singleton3() {
}
public static Singleton3 getInstance() {
return SingletonHolder.instance;
}
34. JNI的使用*
jni是一种协议,这个协议用来沟通java代码和外部的本地代码(c/c++),经过这个协议,java代码就能够调用外部的c++代码。
一、在java本地代码中声明一个native方法:例如:public native String helloJni();
二、在eclipse中建立一个文件夹,名称必须命名为jni;
三、在jni这个文件夹下建立一个.c文件,按照c代码的规范来写
四、ndk-build.cmd指令编译c代码(注意:若是不配置Android.mk文件的话就会报错);
五、配置Android.mk文件;
六、编译事后,自动生成一个.so的动态连接库;
七、在java代码中,把动态连接库加载到jvm虚拟机中加入一个静态代码块
八、像调用java代码同样,调用native方法;
35. AOP是什么*
面向切面编程,不影响功能的状况下添加内容扩展,好比添加log,权限等,经过Aspect切面,把业务共同调用的逻辑或者责任封装起来,减小重复代码,下降模块之间的耦合度。
一、横切关注点
对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点
二、切面(aspect)
类是对物体特征的抽象,切面就是对横切关注点的抽象
三、链接点(joinpoint)
被拦截到的点,由于Spring只支持方法类型的链接点,因此在Spring中链接点指的就是被拦截到的方法,实际上链接点还能够是字段或者构造器
四、切入点(pointcut)
对链接点进行拦截的定义
五、通知(advice)
所谓通知指的就是指拦截到链接点以后要执行的代码,通知分为前置、后置、异常、最终、环绕通知五类
六、目标对象
代理的目标对象
七、织入(weave)
将切面应用到目标对象并致使代理对象建立的过程
八、引入(introduction)
在不修改代码的前提下,引入能够在运行期为类动态地添加一些方法或字段
36. OOP是什么
面向对象编程,一种编程思想,万物皆对象
37. AOP与OOP的区别*
AOP: (Aspect Oriented Programming) 面向切面编程。是目前软件开发中的一个热点,也是Spring框架中容。利用AOP能够对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度下降,提升程序的可重用性,同时提升了开发的效率。主要的功能是:日志记录,性能统计,安全控制,事务处理,异常处理等等。
AOP、OOP在字面上虽然很是相似,但倒是面向不一样领域的两种设计思想。OOP(面向对象编程)针对业务处理过程的实体及其属性和行为进行抽象封装,以得到更加清晰高效的逻辑单元划分。 而AOP则是针对业务处理过程当中的切面进行提取,它所面对的是处理过程当中的某个步骤或阶段,以得到逻辑过程当中各部分之间低耦合性的隔离效果。这两种设计思想在目标上有着本质的差别。react
多线程linux
设计模式nginx
装饰器模式
一个接口component,一个具体实现类concreteComponent,一个装饰类Decorator,不一样的装饰实现类(super构造)ConcreteDecoratorA,ConcreteDecoratorB.
//Component 英雄接口
public interface Hero {
//学习技能
void learnSkills();
}
//ConcreteComponent 具体英雄盲僧
public class BlindMonk implements Hero {
private String name
public BlindMonk(String name) {
this.name = name;
}
@Override
public void learnSkills() {
System.out.println(name + “学习了以上技能!”);
}
}
//Decorator 技能栏 装饰类
public class Skills implements Hero{
//持有一个英雄对象接口
private Hero hero;
public Skills(Hero hero) {
this.hero = hero;
}
@Override
public void learnSkills() {
if(hero != null)
hero.learnSkills();
}
}
//ConreteDecorator 技能:Q 具体装饰类实现类
public class Skill_Q extends Skills{
private String skillName;
public Skill_Q(Hero hero,String skillName) {
super(hero);
this.skillName = skillName;
}
@Override
public void learnSkills() {
System.out.println(“学习了技能Q:” +skillName);
super.learnSkills();
}
}
//ConreteDecorator 技能:W
public class Skill_W extends Skills{
private String skillName;
public Skill_W(Hero hero,String skillName) {
super(hero);
this.skillName = skillName;
}
@Override
public void learnSkills() {
System.out.println(“学习了技能W:” + skillName);
super.learnSkills();
}
}
//ConreteDecorator 技能:E
public class Skill_E extends Skills{
private String skillName;
public Skill_E(Hero hero,String skillName) {
super(hero);
this.skillName = skillName;
}
@Override
public void learnSkills() {
System.out.println(“学习了技能E:”+skillName);
super.learnSkills();
}
}
//ConreteDecorator 技能:R
public class Skill_R extends Skills{
private String skillName;
public Skill_R(Hero hero,String skillName) {
super(hero);
this.skillName = skillName;
}
@Override
public void learnSkills() {
System.out.println(“学习了技能R:” +skillName );
super.learnSkills();
}
}
//客户端:召唤师
public class Player {
public static void main(String[] args) {
//选择英雄
Hero hero = new BlindMonk(“李青”);c++
Skills skills = new Skills(hero); Skills r = new Skill_R(skills,"猛龙摆尾"); Skills e = new Skill_E(r,"天雷破/摧筋断骨"); Skills w = new Skill_W(e,"金钟罩/铁布衫"); Skills q = new Skill_Q(w,"天音波/回音击"); //学习技能 q.learnSkills();
}
}
工厂模式
经过使用一个共同的接口来指向新建立的对象
一个共同接口,多个具体实现类,一个工厂类根据信息产生对象
抽象工厂模式
在简单的工厂模式上,2个工厂类封装
多个接口,每一个接口对应多个具体实现类,抽象接口工厂用来获取对应接口工厂,接口工厂继承抽象工厂,外加实现类信息获取具体实现类
单例模式*
通常状况下,不建议使用第 1 种和第 2 种懒汉方式,建议使用第 3 种饿汉方式。只有在要明确实现 lazy loading 效果时,才会使用第 5 种登记方式。若是涉及到反序列化建立对象时,能够尝试使用第 6 种枚举方式。若是有其余特殊的需求,能够考虑使用第 4 种双检锁方式。
三、饿汉式
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() { return instance; }
}
四、双检锁/双重校验锁(DCL,即 double-checked locking
public class Singleton {
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null)
{ synchronized (Singleton.class)
{
if (singleton == null)
{ singleton = new Singleton(); }
}
}
return singleton;
}
}
六、枚举
public enum Singleton {
INSTANCE;
public void whateverMethod() { }
}
观察者模式*
当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。好比,当一个对象被修改时,则会自动通知它的依赖对象。观察者模式属于行为型模式。
观察者模式使用三个类 Subject、Observer 和 Client。
public abstract class Observer {
public abstract void update(String msg);
}
第一个观察者:
public class F_Observer extends Observer {
public void update(String msg) {
System.out.println(F_Observer.class.getName() + " : " + msg);
}
}
第二个观察者:
public class S_Observer extends Observer {
public void update(String msg) {
System.out.println(S_Observer.class.getName() + " : " + msg);
}
}
第三个观察者:
public class T_Observer extends Observer {
public void update(String msg) {
System.out.println(T_Observer.class.getName() + " : " + msg);
}
}
被观察者:
public class Subject {
private List observers = new ArrayList<>(); //状态改变
public void setMsg(String msg) {
notifyAll(msg);
}
//订阅
public void addAttach(Observer observer) {
observers.add(observer);
}
//通知全部订阅的观察者
private void notifyAll(String msg) {
for (Observer observer : observers) {
observer.update(msg);
}
}
}
使用方法:
public class Main {
public static void main(String[] args) {
F_Observer fObserver = new F_Observer();
S_Observer sObserver = new S_Observer();
T_Observer tObserver = new T_Observer();
Subject subject = new Subject();
subject.addAttach(fObserver);
subject.addAttach(sObserver);
subject.addAttach(tObserver);
subject.setMsg(“msg change”);
}
}
动态代理模式*
委托类,中间类,代理类,中间类要实现InvocationHan,重写invoke方法。
静态代理类就想供应商和微商的赶脚,微商代理,能够作过滤处理。
而invoke动态代理,就能够在中间类中去过滤,中间类去掉用代理类中的方法。
适配器模式
将一个类的接口转换成客户但愿的另一个接口。适配器模式使得本来因为接口不兼容而不能一块儿工做的那些类能够一块儿工做。
类适配器,对象适配器,接口适配器
模板模式
public abstract class Game {
abstract void initialize();
abstract void startPlay();
abstract void endPlay(); //模板
public final void play(){ //初始化游戏 initialize(); //开始游戏 startPlay(); //结束游戏 endPlay(); } }
策略模式
一个策略接口,多个实现类,经过构造方法选择。
JVM
对象的访问:主流的虚拟机有两种——使用句柄和直接指针
句柄访问方式,java堆中将会划分出一块内存来做为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据和类型数据各自的具体地址信息。
直接指针访问方式,Java堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,reference中直接存储的就是对象地址。
1:对象的建立包括三步骤:①当遇到new命令的时候,会在常量池中检查该对象的符号引用是否存在,不存在则进行类的加载,不然执行下一步②分配内存,将将要分配的内存都清零。③虚拟机进行必要的设置,如设置hashcode,gc的分代年龄等,此时会执行命令在执行以前全部的字段都为0,执行指令之后,安装程序的意愿进行初始化字段。
2:对象的内存分配:包括对象头,实例数据,对齐填充
①对象头:包括对象的hascode,gc分代年龄,锁状态标等。
②实例数据:也就是初始化之后的对象的字段的内容,包括父类中的字段等
③对齐填充:对象的地址是8字节,虚拟机要求对象的大小是对象的整数倍(1倍或者两倍)。所以就会有空白区。
3:对象的访问:hotspan中 是采用对象直接指向对象地址的方式(这样的方式访问比较快)(还有一种方式就是句柄,也就是建一张表维护各个指向各个地址的指针,而后给指针设置一个句柄 (别名),而后引用直接指向这个别名,就能够得到该对象,这种的优点就是,实例对象地址改变了,只要修改句柄池中的指针就能够了,而不用引用自己不会发生改变)。
3. GC的两种断定方法:引用计数与引用链。*
1:引用计数:给一个对象设置一个计数器,当被引用一次就加1,当引用失效的时候就减1,若是该对象长时间保持为0值,则该对象将被标记为回收。优势:算法简单,效率高,缺点:很难解决对象之间的相互循环引用问题。
2:引用链(可达性分析):如今主流的gc都采用可达性分析算法来判断对象是否已经死亡。可达性分析:经过一系列成为GC Roots的对象做为起点,从这些起点向下搜索,搜索所走过的路径成为引用链,当一个对象到引用链没有相连时,则判断该对象已经死亡。
3:可做为gc roots的对象:虚拟机栈(本地方法表)中引用的对象(由于在栈内,被线程引用),方法区中类静态属性引用的对象,方法区中常量引用的(常量存放在常量池中,常量池是方法区的一部分)对象,native方法引用的对象
4:引用计数和引用链是只是用来标记,判断一个对象是否失效,而不是用来清除。
4. GC的三种收集方法:标记清除、标记整理、复制算法的原理与特色,分别用在什么地方,若是让你优化收集方法,有什么思路?*
标记-清除算法:首先标记处全部须要回收的对象,在标记完成后统一回收掉所欲被标记的对象。(效率不高,易产生大量不连续的内存碎片)
复制算法:两块内存,原有一块内存全部存活对象所有复制到另一块上,而后把上一块总体清除。(简单,高效,内存减半)
标记-整理算法:多有存活对象都向一段移动,而后直接清理掉段边界之外的内存。
分代收集算法:把堆分为新生代和老年代,新生代中,每次垃圾收集时都发现有大批对象死去,只有少许存放,那就选用复制算法,只须要付出少许存活对象的复制成本就能够完成手机。而老年代中由于对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记-清理”或“标记-整理算法”
5. GC收集器有哪些?CMS收集器与G1收集器的特色。
Serial收集器,ParNew收集器,Parallel Scavernge收集器,Serial Old收集器,Parallel Old收集器,CMS收集器,G1收集器。
CMS收集器以标记-清除,快,并发收集,低停顿。
G1收集器以标记-整理,并行+并发的垃圾收集器,老年代和新生代区域收集
6. Minor?GC与Full?GC分别在何时发生?*
Minor GC触发条件:当Eden区满时,触发Minor GC
Full GC触发条件:
(1)调用System.gc时,系统建议执行Full GC,可是没必要然执行
(2)老年代空间不足
(3)方法区空间不足
(4)经过Minor GC后进入老年代的平均大小大于老年代的可用内存
(5)由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小
7. 几种经常使用的内存调试工具:jmap、jstack、jconsole。
Jmap:Java内存映像工具
Jstack:Java堆栈跟踪工具
Jconsole:Java监视与管理控制台
8. 类加载的五个过程:加载、验证、准备、解析、初始化。
加载:
1):经过一个类的全限定名来获取定义此类的二进制字节流
2):将这个字节流锁表明的静态存储结构转化为方法区的运行时数据结构
3):在Java堆中生成一个表明这个类的java.lang.Class对象,做为方法区这些数据的访问入口。
验证:
验证是链接阶段的第一步,这一阶段的木得是未了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,而且不会畏寒虚拟机自身的安全。
文件格式验证、元数据验证、字节码验证和符号引用验证。
准备:
正式位类变量分派内存并设置类变量初始值的阶段,这些内存都将在方法区中进行分配。
Public static int value = 123;准备初始值为0
Public static final int value = 123;
编译时javac将值生成ConstantValue属性;准备初始值为123
解析:
数据结构与算法
冒泡排序原理:
相连元素两两比较,大的日后放,第一次完毕后,最大值就出如今了最大索引处。同理,,继续,便可获得一个排好序的数组。
选择排序原理:
每一趟从待排序的记录中选出最小的元素,顺序放在已排好序的序列最后,直到所有记录排序完毕。也就是:每一趟在n-i+1(i=1,2,…n-1)个记录中选取关键字最小的记录做为有序序列中第i个记录。
插入排序原理:
它的工做原理是经过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
归并排序的原理:
从小到大排序:首先让数组中的每个数单独成为长度为1的区间,而后两两一组有序合并,获得长度为2的有序区间,依次进行,直到合成整个区间。
快速排序的原理:
从小到大排序:在数组中随机选一个数(默认数组首个元素),数组中小于等于此数的放在左边,大于此数的放在右边,再对数组两边递归调用快速排序,重复这个过程。
堆排序的原理:
堆排序从小到大排序:首先将数组元素建成大小为n的大顶堆,堆顶(数组第一个元素)是全部元素中的最大值,将堆顶元素和数组最后一个元素进行交换,再将除了最后一个数的n-1个元素创建成大顶堆,再将最大元素和数组倒数第二个元素进行交换,重复直至堆大小减为1。
希尔排序的原理:
希尔排序是插入排序改良的算法,希尔排序步长从大到小调整,第一次循环后面元素逐个和前面元素按间隔步长进行比较并交换,直至步长为1,步长选择是关键。
桶排序的原理:
桶排序是计数排序的变种,把计数排序中相邻的m个”小桶”放到一个”大桶”中,在分完桶后,对每一个桶进行排序(通常用快排),而后合并成最后的结果。
7. 快排的partition函数与归并的Merge函数。
partition函数:双向扫描
Merge函数:最后再看每一组(一对)子表的归并,其原理是相同的,只是子表表长不一样,换句话说,是子表的首记录号与尾记录号不一样,把这个归并操做做为核心算法写成函数 merge
8. 对冒泡与快排的改进。*
8.1 对冒泡的改进
改进1:设置一个标志位,标志位表明在某一个冒泡遍历时候是否发生位置数据的交换,若是没有交换,则代表序列已经排序完成,不然继续排序。减小没必要要的遍历。
改进2:再设置一个标志位,标志位是序列的某个下标,下标以后的表明已经排序完成,下标以前未排序,则遍历大于标志位时,再也不遍历。减小一次遍历中已排完序的序列的遍历
改进3:在一次遍历时,同时找出最大值和最小值,从而提升效率。
参考:排序算法(一)——冒泡排序及改进
8.2对快排的改进
基准的选取影响快排的效率,通常基准的选取有三种:
1)固定位置。选序列第一位或者最后一位,算法的导论中提到的就是固定选择最后一位。
2)随机选取。对于序列中部分有序的状况,若是选择固定位置做为基准,会致使全序列都须要交换位置,这会使得效率低下。所以会采用随机选取数据做为基准。
3)三数取中。最佳划分是将序列划分红等长的两个子序列,所以提出三数取中的思想。取序列中,下标第一位,下标中间一位,下标最后一位的三个数进行排序,取排序结果中排中间的数据做为基准。(此外,也能够取5个数做为数据的基准。)
参考:三种快速排序以及快速排序的优化
针对以上三种状况中,三数取中效果最优,可是依然没法解决序列中出现重复状况,对此进行再次优化:
优化1:当待排序序列的长度分割到必定大小后,使用插入排序。对于很小和部分有序的数组,快排不如插排好。
优化2:与基准值相同的不加入分割。在每一次分割结束后,能够把与基准相等的元素聚在一块儿,继续下次分割时,不用再对与基准相等元素分割。减小重复序列的反复分割
优化3:优化递归操做,快排函数在函数尾部有两次递归操做,咱们能够对其使用尾递归优化。若是待排序的序列划分极端不平衡,递归的深度将趋近于n,而栈的大小是颇有限的,每次递归调用都会耗费必定的栈空间,函数的参数越多,每次递归耗费的空间也越多。优化后,能够缩减堆栈深度,由原来的O(n)缩减为O(logn),将会提升性能。
这里提一下尾递归,若是一个函数中全部递归形式的调用都出如今函数的末尾,咱们称这个递归函数是尾递归。须要说明的是递归调用必须整个函数体中最后执行的语句且它的返回值不属于表达式的一部分。
尾递归的优势:
1)尾递归经过迭代的方式,不存在子问题被屡次计算的状况
2)尾递归的调用发生在方法的末尾,在计算过程当中,彻底能够把上一次留在堆栈的状态擦掉,保证程序以O(1)的空间复杂度运行。
惋惜的是,在jvm中第二点并无被优化。
9. 二分查找,与变种二分查找。
二分查找的中间下标:mid=low+0.5∗(high−low)mid=low+0.5∗(high−low)
二分+插值:
若是序列长度为1000,查找的关键字在10位置上,则仍是须要从500中间开始二分查找,这样会产生屡次无效查询,所以优化的方式就是更改分割的比例,采用三分,四分,分割位置:mid′=low+(high−low)∗(key−a[low])/(a[high]−key)mid′=low+(high−low)∗(key−a[low])/(a[high]−key)
插值查找是根据要查找的关键字的key与查找表中最大最小记录的关键字比较以后的查找算法。
黄金分割比:用黄金分割比来做为mid值
10. 二叉树、B+树、AVL树、红黑树、哈夫曼树。
http://www.javashuo.com/article/p-kaacucir-dy.html
二叉树:
二叉树的数据结构就很少说了,这里列举一些常见题目
1)求解二叉树的节点
递归求解:
a) 树为空,节点数为0
b) 二叉树节点个数 = 左子树节点个数 + 右子树节点个数 + 1
2)求二叉树的深度
递归解法:
a)若是二叉树为空,二叉树的深度为0
b)若是二叉树不为空,二叉树的深度 = max(左子树深度, 右子树深度) + 1
3) 先根遍历,中序遍历,后序遍历
依然递归求解
4)广度优先
借助队列。
5)将二叉查找树变为有序的双向链表
要求不能建立新节点,只调整指针。
递归解法:
a)若是二叉树查找树为空,对应双向链表的第一个节点和最后一个节点是NULL
b)若是二叉查找树不为空:
设置参数flag,表明父节点与子节点的关系。若是修正的是左子树与父节点的关系,则递归返回的是序列最后的节点。
6)求二叉树第K层的节点个数
递归解法:
a)若是二叉树为空或者k<1返回0
b)若是二叉树不为空而且k==1,返回1
c)若是二叉树不为空且k>1,返回左子树中k-1层的节点个数与右子树k-1层节点个数之和
7)求二叉树中叶子节点的个数
递归解法:
a)若是二叉树为空,返回0
b)若是二叉树不为空且左右子树为空,返回1
c)若是二叉树不为空,且左右子树不一样时为空,返回左子树中叶子节点个数加上右子树中叶子节点个数
8)判断二叉树是否是平衡二叉树(AVL树)
递归解法:
a)若是二叉树为空,返回真
b)若是二叉树不为空,若是左子树和右子树都是AVL树而且左子树和右子树高度相差不大于1,返回真,其余返回假
9)由前序遍历序列和中序遍历序列重建二叉树
二叉树前序遍历序列中,第一个元素老是树的根节点的值。中序遍历序列中,左子树的节点的值位于根节点的值的左边,右子树的节点的值位于根节点的值的右边。
递归解法:
a)若是前序遍历为空或中序遍历为空或节点个数小于等于0,返回NULL;
b)建立根节点。前序遍历的第一个数据就是根节点的数据,在中序遍历中找到根节点的位置,可分别得知左子树和右子树的前序和中序遍历序列,重建左右子树
10)判断是否是彻底二叉树
11. 二叉树的前中后续遍历:递归与非递归写法,层序遍历算法。
12. 图的BFS与DFS算法,最小生成树prim算法与最短路径Dijkstra算法。
13. KMP算法。
14. 排列组合问题。
15. 动态规划、贪心算法、分治算法。(通常不会问到)
16. 大数据处理:相似10亿条数据找出最大的1000个数…等等
17. 算法的话实际上是个重点,由于最后都是要你写代码,因此算法仍是须要花很多时间准备,这里有太多算法题,写不全,个人建议是没事多在OJ上刷刷题(牛客网、leetcode等),剑指offer上的算法要能理解并本身写出来,编程之美也推荐看一看
数据库
https://my.oschina.net/yanpenglei/blog/1650277
默认已提交读。
3. innodb和myisam存储引擎的区别*
http://www.javashuo.com/article/p-kolhluwr-ba.html
1)InnoDB支持事务,MyISAM不支持,这一点是很是之重要。事务是一种高级的处理方式,如在一些列增删改中只要哪一个出错还能够回滚还原,而MyISAM就不能够了。
2)MyISAM适合查询以及插入为主的应用,InnoDB适合频繁修改以及涉及到安全性较高的应用
3)InnoDB支持外键,MyISAM不支持
4)从MySQL5.5.5之后,InnoDB是默认引擎
5)InnoDB不支持FULLTEXT类型的索引
6)InnoDB中不保存表的行数,如select count() from table时,InnoDB须要扫描一遍整个表来计算有多少行,可是MyISAM只要简单的读出保存好的行数便可。注意的是,当count()语句包含where条件时MyISAM也须要扫描整个表
7)对于自增加的字段,InnoDB中必须包含只有该字段的索引,可是在MyISAM表中能够和其余字段一块儿创建联合索引
8)清空整个表时,InnoDB是一行一行的删除,效率很是慢。MyISAM则会重建表
9)InnoDB支持行锁(某些状况下仍是锁整表,如 update table set a=1 where user like ‘%lee%’
4. MYSQL的两种存储引擎区别(事务、锁级别等等),各自的适用场景
如今通常都是选用innodb了,主要是myisam的全表锁,读写串行问题,并发效率锁表,效率低myisam对于读写密集型应用通常是不会去选用的。
MyISAM是表锁,InnoDB是行锁。
5. 查询语句不一样元素(where、jion、limit、group by、having等等)执行前后顺序
Join where groupby having limit
6. 数据库的优化(从sql语句优化和索引两个部分回答)*
https://www.jb51.net/article/107054.htm
对查询进行优化,要尽可能避免全表扫描,首先应考虑在 where 及 order by 涉及的列上创建索引。
应尽可能避免在 where 子句中对字段进行 null 值判断,不然将致使引擎放弃使用索引而进行全表扫描
应尽可能避免在 where 子句中使用 != 或 <> 操做符,不然将引擎放弃使用索引而进行全表扫描。
应尽可能避免在 where 子句中使用 or 来链接条件,若是一个字段有索引,一个字段没有索引,将致使引擎放弃使用索引而进行全表扫描
in 和 not in 也要慎用,不然会致使全表扫描
下面的查询也将致使全表扫描:
1 select id from t where name like ‘%abc%’
若是在 where 子句中使用参数,也会致使全表扫描。由于SQL只有在运行时才会解析局部变量,但优化程序不能将访问计划的选择推迟到运行时;它必须在编译时进行选择。然而,若是在编译时创建访问计划,变量的值仍是未知的,于是没法做为索引选择的输入项。以下面语句将进行全表扫描
7. 索引有B+索引和hash索引,各自的区别
B+树是一个平衡的多叉树,从根节点到每一个叶子节点的高度差值不超过1,并且同层级的节点间有指针相互连接。
在B+树上的常规检索,从根节点到叶子节点的搜索效率基本至关,不会出现大幅波动,并且基于索引的顺序扫描时,也能够利用双向指针快速左右移动,效率很是高。
哈希索引就是采用必定的哈希算法,把键值换算成新的哈希值,检索时不须要相似B+树那样从根节点到叶子节点逐级查找,只需一次哈希算法便可马上定位到相应的位置,速度很是快。
8. B+索引数据结构,和B树的区别
B 树能够看做是对2-3查找树的一种扩展,即他容许每一个节点有M-1个子节点。
• 根节点至少有两个子节点
• 每一个节点有M-1个key,而且以升序排列
• 位于M-1和M key的子节点的值位于M-1 和M key对应的Value之间
• 其它节点至少有M/2个子节点
B+树是对B树的一种变形树,它与B树的差别在于:
• 有k个子结点的结点必然有k个关键码;
• 非叶结点仅具备索引做用,跟记录有关的信息均存放在叶结点中。
• 树的全部叶结点构成一个有序链表,能够按照关键码排序的次序遍历所有记录。
9. 索引的分类(主键索引、惟一索引),最左前缀原则,哪些状况索引会失效*
MySQL索引分为普通索引、惟一索引、主键索引、组合索引、全文索引。索引不会包含有null值的列,索引项能够为null(惟一索引、组合索引等),可是只要列中有null值就不会被包含在索引中。
(1)普通索引:create index index_name on table(column);
或者建立表时指定,create table(…, index index_name column);
(2)惟一索引:相似普通索引,索引列的值必须惟一(能够为空,这点和主键索引不一样)
create unique index index_name on table(column);或者建立表时指定unique index_name column
(3)主键索引:特殊的惟一索引,不容许为空,只能有一个,通常是在建表时指定primary key(column)
(4)组合索引:在多个字段上建立索引,遵循最左前缀原则。alter table t add index index_name(a,b,c);
(5)全文索引:主要用来查找文本中的关键字,不是直接与索引中的值相比较,像是一个搜索引擎,配合match against使用,如今只有char,varchar,text上能够建立全文索引。在数据量较大时,先将数据放在一张没有全文索引的表里,而后再利用create index建立全文索引,比先生成全文索引再插入数据快不少。
(1)主键,unique字段;
(2)和其余表作链接的字段须要加索引;
(3)在where里使用>,≥,=,<,≤,is null和between等字段;
(4)使用不以通配符开始的like,where A like ‘China%’;
(5)汇集函数MIN(),MAX()中的字段;
(6)order by和group by字段;
三、什么时候不使用索引
(1)表记录太少;
(2)数据重复且分布平均的字段(只有不多数据值的列);
(3)常常插入、删除、修改的表要减小索引;
(4)text,image等类型不该该创建索引,这些列的数据量大(假如text前10个字符惟一,也能够对text前10个字符创建索引);
(5)MySQL能估计出全表扫描比使用索引更快时,不使用索引;
四、索引什么时候失效
(1)组合索引未使用最左前缀,例如组合索引(A,B),where B=b不会使用索引;
(2)like未使用最左前缀,where A like ‘%China’;
(3)搜索一个索引而在另外一个索引上作order by,where A=a order by B,只使用A上的索引,由于查询只使用一个索引 ;
(4)or会使索引失效。若是查询字段相同,也能够使用索引。例如where A=a1 or A=a2(生效),where A=a or B=b(失效)
(5)若是列类型是字符串,要使用引号。例如where A=‘China’,不然索引失效(会进行类型转换);
(6)在索引列上的操做,函数(upper()等)、or、!=(<>)、not in等;
10. 汇集索引和非汇集索引区别。
其中汇集索引表示表中存储的数据按照索引的顺序存储,检索效率比非汇集索引高,但对数据更新影响较大。非汇集索引表示数据存储在一个地方,索引存储在另外一个地方,索引带有指针指向数据的存储位置,非汇集索引检索效率比汇集索引低,但对数据更新影响较小。
11. 有哪些锁(乐观锁悲观锁),select时怎么加排它锁*
乐观锁不是数据库自带的,须要咱们本身去实现。乐观锁是指操做数据库时(更新操做),想法很乐观,认为此次的操做不会致使冲突,在操做数据时,并不进行任何其余的特殊处理(也就是不加锁),而在进行更新后,再去判断是否有冲突了。
与乐观锁相对应的就是悲观锁了。悲观锁就是在操做数据时,认为此操做会出现数据冲突,因此在进行每次操做时都要经过获取锁才能进行对相同数据的操做,这点跟java中的synchronized很类似,因此悲观锁须要耗费较多的时间。另外与乐观锁相对应的,悲观锁是由数据库本身实现了的,要用的时候,咱们直接调用数据库的相关语句就能够了。
用法: select … for update;
12. 关系型数据库和非关系型数据库区别
1.关系型数据库经过外键关联来创建表与表之间的关系,
2.非关系型数据库一般指数据以对象的形式存储在数据库中,而对象之间的关系经过每一个对象自身的属性来决定
外链接(OUTER JOIN):
分为三种:
左外链接(LEFT OUTER JOIN或LEFT JOIN)
右外链接(RIGHT OUTER JOIN或RIGHT JOIN)
全外链接(FULL OUTER JOIN或FULL JOIN)
交叉链接(CROSS JOIN):
没有WHERE 子句,它返回链接表中全部数据行的笛卡尔积
笛卡尔积是两个表每个字段相互匹配,去掉where 或者inner join的等值 得出的结果就是笛卡尔积。笛卡尔积也等同于交叉链接。
内链接: 只链接匹配的行。
左外链接: 包含左边表的所有行(无论右边的表中是否存在与它们匹配的行),以及右边表中所有匹配的行。
右外链接: 包含右边表的所有行(无论左边的表中是否存在与它们匹配的行),以及左边表中所有匹配的行。
全外链接: 包含左、右两个表的所有行,无论另一边的表中是否存在与它们匹配的行。
交叉链接 生成笛卡尔积-它不使用任何匹配或者选取条件,而是直接将一个数据源中的每一个行与另外一个数据源的每一个行都一一匹配
18. 死锁断定原理和具体场景,死锁怎么解决*
https://blog.csdn.net/XiaHeShun/article/details/81393796
数据库是一个多用户使用的共享资源,当多个用户并发地存取数据的时候,在数据库中就会发生多个事务同时存取同一个数据的状况,加锁是进行数据库并发控制的一种很是重要的技术。在实际应用中,若是两个事务须要一组有冲突的锁,而不能继续进行下去,这时便发生了死锁。
MySQL有三种锁的级别:页级、表级、行级。
表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的几率最高,并发度最低。
行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的几率最低,并发度也最高。
页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度通常
什么状况下会形成死锁
所谓死锁: 是指两个或两个以上的进程在执行过程当中。
因争夺资源而形成的一种互相等待的现象,若无外力做用,它们都将没法推动下去。
此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等的进程称为死锁进程。
表级锁不会产生死锁.因此解决死锁主要仍是针对于最经常使用的InnoDB。
死锁的关键在于:两个(或以上)的Session加锁的顺序不一致。
那么对应的解决死锁问题的关键就是:让不一样的session加锁有次序。
死锁的解决办法
查出的线程杀死 kill
设置锁的超时时间
19. varchar和char的使用场景。*
char的长度是不可变的,而varchar的长度是可变的。
varchar是以空间效率为首位。
char的存储方式是:对英文字符(ASCII)占用1个字节,对一个汉字占用两个字节。
varchar的存储方式是:对每一个英文字符占用2个字节,汉字也占用2个字节。
二者的存储数据都非unicode的字符数据。
20. mysql并发状况下怎么解决(经过事务、隔离级别、锁)
MySQL 高并发环境解决方案 分库 分表 分布式 增长二级缓存。。。。。
需求分析:互联网单位 天天大量数据读取,写入,并发性高。
现有解决方式:水平分库分表,由单点分布到多点数据库中,从而下降单点数据库压力。
集群方案:解决DB宕机带来的单点DB不能访问问题。
读写分离策略:极大限度提升了应用中Read数据的速度和并发量。没法解决高写入压力。
21. 数据库崩溃时事务的恢复机制(REDO日志和UNDO日志)
Undo Log
Undo Log是为了实现事务的原子性,在MySQL数据库InnoDB存储引擎中,还用了UndoLog来实现多版本并发控制(简称:MVCC)。
事务的原子性(Atomicity)事务中的全部操做,要么所有完成,要么不作任何操做,不能只作部分操做。若是在执行的过程当中发生了错误,要回滚(Rollback)到事务开始前的状态,就像这个事务历来没有执行过。
原理Undo Log的原理很简单,为了知足事务的原子性,在操做任何数据以前,首先将数据备份到一个地方(这个存储数据备份的地方称为UndoLog)。而后进行数据的修改。若是出现了错误或者用户执行了ROLLBACK语句,系统能够利用Undo Log中的备份将数据恢复到事务开始以前的状态。
之因此能同时保证原子性和持久化,是由于如下特色:
更新数据前记录Undo log。
为了保证持久性,必须将数据在事务提交前写到磁盘。只要事务成功提交,数据必然已经持久化。
Undo log必须先于数据持久化到磁盘。若是在G,H之间系统崩溃,undo log是完整的, 能够用来回滚事务。
若是在A-F之间系统崩溃,由于数据没有持久化到磁盘。因此磁盘上的数据仍是保持在事务开始前的状态。
缺陷:每一个事务提交前将数据和Undo Log写入磁盘,这样会致使大量的磁盘IO,所以性能很低。
若是可以将数据缓存一段时间,就能减小IO提升性能。可是这样就会丧失事务的持久性。所以引入了另一种机制来实现持久化,即Redo Log。
Redo Log
原理和Undo Log相反,Redo Log记录的是新数据的备份。在事务提交前,只要将Redo Log持久化便可,不须要将数据持久化。当系统崩溃时,虽然数据没有持久化,可是Redo Log已经持久化。系统能够根据Redo Log的内容,将全部数据恢复到最新的状态。
22. 查询语句不一样元素(where、jion、limit、group by、having等等)执行前后顺序
Join where limit group by having
Spring
IOC和DI是什么?
IOC—Inversion of Control(控制反转),IOC意味着将你设计好的对象交给容器控制,而不是传统的在你对象内部直接控制。
DI—Dependency Injection(依赖注入):是组件之间依赖关系由容器在运行期决定。
Spring IOC 的理解,其初始化过程?*
Spring IOC的核心是BeanFactory
定位并获取资源文件:经过配置文件获取资源对象
ClassPathResource res = new ClassPathResource(“my/applicationContext.xml”);
解析资源文件 Bean的载入和解析:对资源进行解析,获取bean对象
向IoC容器注册BeanDefinition:利用解析好的BeanDefinition对象完成最终的注册,将beanName和beanDefinition做为键值 放到了beanFactorty的map中
BeanFactory 和 FactoryBean的区别?*
BeanFactory是接口,提供了OC容器最基本的形式,给具体的IOC容器的实现提供了规范,
FactoryBean也是接口,为IOC容器中Bean的实现提供了更加灵活的方式,FactoryBean在IOC容器的基础上给Bean的实现加上了一个简单工厂模式和装饰模式(若是想了解装饰模式参考:修饰者模式(装饰者模式,Decoration)咱们能够在getObject()方法中灵活配置。其实在Spring源码中有不少FactoryBean的实现类.
BeanFactory
以Factory结尾,表示它是一个工厂类(接口),它负责生产和管理bean的一个工厂。在Spring中,BeanFactory是IOC容器的核心接口,它的职责包括:实例化、定位、配置应用程序中的对象及创建这些对象间的依赖。BeanFactory只是个接口,并非IOC容器的具体实现,可是Spring容器给出了不少种实现,如 DefaultListableBeanFactory、XmlBeanFactory、ApplicationContext等,其中XmlBeanFactory就是经常使用的一个,该实现将以XML方式描述组成应用的对象及对象间的依赖关系。
FactoryBean
通常状况下,Spring经过反射机制利用的class属性指定实现类实例化Bean,在某些状况下,实例化Bean过程比较复杂,若是按照传统的方式,则须要在中提供大量的配置信息。配置方式的灵活性是受限的,这时采用编码的方式可能会获得一个简单的方案。Spring为此提供了一个org.springframework.bean.factory.FactoryBean的工厂类接口,用户能够经过实现该接口定制实例化Bean的逻辑。FactoryBean接口对于Spring框架来讲占用重要的地位,Spring自身就提供了70多个FactoryBean的实现。
BeanFactory和ApplicationContext的区别?*
BeanFacotry是spring中比较原始的Factory。如XMLBeanFactory就是一种典型的BeanFactory。原始的BeanFactory没法支持spring的许多插件,如AOP功能、Web应用等。
ApplicationContext接口,它由BeanFactory接口派生而来,于是提供BeanFactory全部的功能。ApplicationContext以一种更向面向框架的方式工做以及对上下文进行分层和实现继承,ApplicationContext包还提供了如下的功能:
• MessageSource, 提供国际化的消息访问
• 资源访问,如URL和文件 ResourceLoader
• 事件传播ApplicationEvent和ApplicationListener
• 载入多个(有继承关系)上下文 ,使得每个上下文都专一于一个特定的层次,好比应用的web层
ApplicationContext 上下文的生命周期?*
https://blog.csdn.net/qq_32651225/article/details/78323527
Spring Bean 的生命周期?*
Spring AOP的实现原理?
AOP,面向切面。AOP技术利用一种称为“横切”的技术,解剖封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,这样就能减小系统的重复代码,下降模块间的耦合度,并有利于将来的可操做性和可维护性。AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特色是,他们常常发生在核心关注点的多处,而各处都基本类似。好比权限认证、日志、事务处理。
Spring 是如何管理事务的,事务管理机制?*
Spring的事务机制包括声明式事务和编程式事务。
编程式事务管理:Spring推荐使用TransactionTemplate,实际开发中使用声明式事务较多。
声明式事务管理:将咱们从复杂的事务处理中解脱出来,获取链接,关闭链接、事务提交、回滚、异常处理等这些操做都不用咱们处理了,Spring都会帮咱们处理。
声明式事务管理使用了AOP面向切面编程实现的,本质就是在目标方法执行先后进行拦截。在目标方法执行前加入或建立一个事务,在执行方法执行后,根据实际状况选择提交或是回滚事务。
如何管理的:
Spring事务管理主要包括3个接口,Spring的事务主要是由他们三个共同完成的。
1)PlatformTransactionManager:事务管理器–主要用于平台相关事务的管理
2)TransactionDefinition:事务定义信息–用来定义事务相关的属性,给事务管理器PlatformTransactionManager使用
3)TransactionStatus:事务具体运行状态–事务管理过程当中,每一个时间点事务的状态信息。
Spring 的不一样事务传播行为有哪些,干什么用的?*
一、PROPAGATION_REQUIRED:若是当前没有事务,就建立一个新事务,若是当前存在事务,就加入该事务,该设置是最经常使用的设置。
二、PROPAGATION_SUPPORTS:支持当前事务,若是当前存在事务,就加入该事务,若是当前不存在事务,就以非事务执行。‘
三、PROPAGATION_MANDATORY:支持当前事务,若是当前存在事务,就加入该事务,若是当前不存在事务,就抛出异常。
四、PROPAGATION_REQUIRES_NEW:建立新事务,不管当前存不存在事务,都建立新事务。
五、PROPAGATION_NOT_SUPPORTED:以非事务方式执行操做,若是当前存在事务,就把当前事务挂起。
六、PROPAGATION_NEVER:以非事务方式执行,若是当前存在事务,则抛出异常。
七、PROPAGATION_NESTED:若是当前存在事务,则在嵌套事务内执行。若是当前没有事务,则执行与PROPAGATION_REQUIRED相似的操做。
Spring 中用到了那些设计模式?*
工厂模式:在各类BeanFactory以及ApplicationContext建立中都用到了;
单例模式: 这个好比在建立bean的时候。
适配器(Adapter): 在Spring的Aop中,使用的Advice(通知)来加强被代理类的功能。
包装器: 动态地给一个对象添加一些额外的职责。就增长功能来讲,Decorator模式相比生成子类更为灵活。
代理(Proxy): Spring的Proxy模式在aop中有体现,好比JdkDynamicAopProxy和Cglib2AopProxy。
观察者(Observer): Spring中Observer模式经常使用的地方是listener的实现。如ApplicationListener。
策略(Strategy): 加载资源文件的方式,使用了不一样的方法
模板方法: Spring中的JdbcTemplate
Spring MVC 的工做原理?*
Spring如何解决循环依赖?
Spring的循环依赖的理论依据实际上是基于Java的引用传递,当咱们获取到对象的引用时,对象的field或则属性是能够延后设置的(可是构造器必须是在获取引用以前)。
Spring的单例对象的初始化主要分为三步:
(1)createBeanInstance:实例化,其实也就是调用对象的构造方法实例化对象
(2)populateBean:填充属性,这一步主要是多bean的依赖属性进行填充
(3)initializeBean:调用spring xml中的init 方法。
从上面讲述的单例bean初始化步骤咱们能够知道,循环依赖主要发生在第1、第二步。也就是构造器循环依赖和field循环依赖。
那么咱们要解决循环引用也应该从初始化过程着手,对于单例来讲,在Spring容器整个生命周期内,有且只有一个对象,因此很容易想到这个对象应该存在Cache中,Spring为了解决单例的循环依赖问题,使用了三级缓存。
Spring 如何保证 Controller 并发的安全?*
在Controller中使用ThreadLocal变量
在spring配置文件Controller中声明 scope=“prototype”,每次都建立新的controller
在控制器中不使用实例变量
Netty
BIO、NIO和AIO*
BIO: 同步并阻塞,服务器实现模式为一个链接一个线程,即客户端有链接请求时服务器端就须要启动一个线程进行处理(一客户端一线程)。该模型最大的问题就是缺少弹性伸缩能力,当客户端并发访问量增长后,服务端的线程数与客户端并发访问数呈1:1的关系,系统性能将急剧降低,随着并发访问量的继续增长,系统会发生线程堆栈溢出、建立新线程失败等问题,并最终致使宕机或僵死。
NIO:异步非阻塞,服务器实现模式为一个请求一个线程,客户端发送的链接请求都会注册到多路复用器上,多路复用器轮询到链接有I/O请求时才启动一个线程进行处理。
AIO:JDK1.7升级了NIO库,升级后的NIO库被称为NIO2.0,正式引入了异步通道的概念。NIO2.0的异步套接字通道是真正的异步非阻塞I/O,此即AIO。其服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理。
Netty 的各大组件*
Bootstrap or ServerBootstrap
Bootstrap,一个Netty应用一般由一个Bootstrap开始,它主要做用是配置整个Netty程序,串联起各个组件。
EventLoop
一个 EventLoop 在它的生命周期内只能与一个Thread绑定。
一个 EventLoop 可被分配至一个或多个 Channel
EventLoopGroup
一个EventLoopGroup 包含多个EventLoop 能够理解为一个线程池
ChannelPipeline
channelHandler的容器,每一个Channel会绑定一个ChannelPipeline,用于处理该Channel上的事件。
Channel
表明了一个socket链接,简化了socket进行操做的复杂性
Future or ChannelFuture
Netty 为异步非阻塞,即全部的 I/O 操做都为异步的,所以,咱们不能马上得知消息是否已经被处理了。Netty 提供了 ChannelFuture 接口,经过该接口的 addListener() 方法注册一个 ChannelFutureListener,当操做执行成功或者失败时,监听就会自动触发返回结果。ChannelInitializer
当一个连接创建时,咱们须要知道怎么来接收或者发送数据,固然,咱们有各类各样的Handler实现来处理它,那么ChannelInitializer即是用来配置这些Handler,它会提供一个ChannelPipeline,并把Handler加入到ChannelPipeline。
ChannelHandler
ChannelHandler 为 Netty 中最核心的组件,它充当了全部处理入站和出站数据的应用程序逻辑的容器。ChannelHandler 主要用来处理各类事件,这里的事件很普遍,好比能够是链接、数据接收、异常、数据转换等。
ChannelHandler 有两个核心子类 ChannelInboundHandler 和 ChannelOutboundHandler,其中 ChannelInboundHandler 用于接收、处理入站数据和事件,而 ChannelOutboundHandler 则相反。
Netty的线程模型*
http://www.javashuo.com/article/p-otghtmee-gm.html
基于Reactor的单线程模型、多线程模型、主从模型
netty支持Reactor的单线程模型、多线程、主从Reactor多线程模型。
从原理图中能够看到,服务端启动时建立了两个NioEventLoopGroup,这是两个相互独立的Reactor线程池,一个是boss线程池,一个是work线程池。两个分工明确,一个负责接收客户端链接请求,一个负责处理IO相关的读写操做,或者执行Task任务,或执行定时Task任务。其中NioEventLoopGroup中的NioEventLoop个数默认为处理器核数*2。
经过配置boss线程池和worker线程池的线程个数以及是否共享线程池,来配置单线程、多线程、主从Reactor多线程。
Boss线程池的任务:
a.接收客户端的链接请求,初始化channel参数
b.将链路状态变化时间通知给ChannelPipeline
Worker线程池的做用:
a.异步读取通讯对端的消息,发送读事件到ChannelPipeline
b.异步发送消息到通讯对端,调用ChannelPipeline的发送消息接口
c.执行系统Task任务
d.执行系统定时Task任务
单线程模型:
做为服务端,接收客户端的TCP链接;
做为客户端,向服务端发起TCP链接;
读取通讯对端的请求或者应答消息;
向通讯对端发送消息请求或者应答消息。
多线程模型:
专门由一个Reactor线程-Acceptor线程用于监听服务端,接收客户端链接请求;
网络I/O操做读、写等由Reactor线程池负责处理;
一个Reactor线程可同时处理多条链路,但一条链路只能对应一个Reactor线程,这样可避免并发操做问题。
主从线程模型:
服务端使用一个独立的主Reactor线程池来处理客户端链接,当服务端收到链接请求时,从主线程池中随机选择一个Reactor线程做为Acceptor线程处理链接;
链路创建成功后,将新建立的SocketChannel注册到sub reactor线程池的某个Reactor线程上,由它处理后续的I/O操做。
TCP 粘包/拆包的缘由及解决方法*
1.消息定长,例如每一个报文的大小为固定长度200字节,若是不够,空位补空格。
2.在包尾增长回车换行符进行分割,例如FTP协议。
3.将消息分为消息头和消息体,消息头中包含消息长度的字段,一般设计思路为消息头的第一个字段使用int32来表示消息的总长度
Uhost经过自定义消息协议来编码和解码,经过定义消息的消息msg,消息类型+消息数据
经过编码—8个字节,4个字节存放消息类型,4个字节存放消息长度,后面存放byte[]消息体。
了解哪几种序列化协议?包括使用场景和如何去选择*
序列化(serialization)就是将对象序列化为二进制形式(字节数组),通常也将序列化称为编码(Encode),主要用于网络传输、数据持久化等;
反序列化(deserialization)则是将从网络、磁盘等读取的字节数组还原成原始对象,以便后续业务的进行,通常也将反序列化称为解码(Decode),主要用于网络传输对象的解码,以便完成远程调用。
XML: 当作配置文件存储数据,实时数据转换
JSON: 轻量级的数据交换格式,简洁和清晰,跨防火墙访问;可调式性要求高的状况;基于Web browser的Ajax请求;传输数据量相对小,实时性要求相对低(例如秒级别)的服务
Fastjson: Fastjson是一个Java语言编写的高性能功能完善的JSON库协议交互
Web输出
Android客户端
Thrift: 并不只仅是序列化协议,而是一个RPC框架。它可让你选择客户端与服务端之间传输通讯协议的类别,即文本(text)和二进制(binary)传输协议, 为节约带宽,提供传输效率,通常状况下使用二进制类型的传输协议。
分布式系统的RPC解决方案
Avro: Avro属于Apache Hadoop的一个子项目。 Avro提供两种序列化格式:JSON格式或者Binary格式。Binary格式在空间开销和解析性能方面能够和Protobuf媲美,Avro的产生解决了JSON的冗长和没有IDL的问题
在Hadoop中作Hive、Pig和MapReduce的持久化数据格式
Protobuf:
protocol buffers 由谷歌开源而来,在谷歌内部久经考验。它将数据结构以.proto文件进行描述,经过代码生成工具能够生成对应数据结构的POJO对象和Protobuf相关的方法和属性。
对性能要求高的RPC调用
具备良好的跨防火墙的访问属性
适合应用层对象的持久化
Netty的零拷贝实现*
“零拷贝”是指计算机操做的过程当中,CPU不须要为数据在内存之间的拷贝消耗资源。而它一般是指计算机在网络上发送文件时,不须要将文件内容拷贝到用户空间(User Space)而直接在内核空间(Kernel Space)中传输到网络的方式。
传统意义的零拷贝:
这种方式须要四次数据拷贝和四次上下文切换:
数据从磁盘读取到内核的read buffer
数据从内核缓冲区拷贝到用户缓冲区
数据从用户缓冲区拷贝到内核的socket buffer
数据从内核的socket buffer拷贝到网卡接口的缓冲区
经过java的FileChannel.transferTo方法,能够避免上面两次多余的拷贝(固然这须要底层操做系统支持)
调用transferTo,数据从文件由DMA引擎拷贝到内核read buffer
接着DMA从内核read buffer将数据拷贝到网卡接口buffer
上面的两次操做都不须要CPU参与,因此就达到了零拷贝。
对于ByteBuf,Netty提供了多种实现:
Heap ByteBuf:直接在堆内存分配
Direct ByteBuf:直接在内存区域分配而不是堆内存
CompositeByteBuf:组合Buffer
Direct Buffers
直接在内存区域分配空间,而不是在堆内存中分配。若是使用传统的堆内存分配,当咱们须要将数据经过socket发送的时候,就须要从堆内存拷贝到直接内存,而后再由直接内存拷贝到网卡接口层。
Netty提供的直接Buffer,直接将数据分配到内存空间,从而避免了数据的拷贝,实现了零拷贝。
Composite Buffers
传统的ByteBuffer,若是须要将两个ByteBuffer中的数据组合到一块儿,咱们须要首先建立一个size=size1+size2大小的新的数组,而后将两个数组中的数据拷贝到新的数组中。可是使用Netty提供的组合ByteBuf,就能够避免这样的操做,由于CompositeByteBuf并无真正将多个Buffer组合起来,而是保存了它们的引用,从而避免了数据的拷贝,实现了零拷贝。
对于FileChannel.transferTo的使用
Netty中使用了FileChannel的transferTo方法,该方法依赖于操做系统实现零拷贝。
Netty的接收和发送ByteBuffer采用DIRECT BUFFERS,使用堆外直接内存进行Socket读写,不须要进行字节缓冲区的二次拷贝。若是使用传统的堆内存(HEAP BUFFERS)进行Socket读写,JVM会将堆内存Buffer拷贝一份到直接内存中,而后才写入Socket中。相比于堆外直接内存,消息在发送过程当中多了一次缓冲区的内存拷贝。
Netty提供了组合Buffer对象,能够聚合多个ByteBuffer对象,用户能够像操做一个Buffer那样方便的对组合Buffer进行操做,避免了传统经过内存拷贝的方式将几个小Buffer合并成一个大的Buffer。
Netty的文件传输采用了transferTo方法,它能够直接将文件缓冲区的数据发送到目标Channel,避免了传统经过循环write方式致使的内存拷贝问题。
Netty的高性能表如今哪些方面*
异步非阻塞通讯
Netty的IO线程NioEventLoop因为聚合了多路复用器Selector,能够同时并发处理成百上千个客户端Channel,因为读写操做都 是非阻塞的,这就能够充分提高IO线程的运行效率,避免因为频繁IO阻塞致使的线程挂起。另外,因为Netty采用了异步通讯模式,一个IO线程能够并发处理N个客户端链接和读写操做,这从根本上解决了传统同步阻塞IO一链接一线程模型,架构的性能、弹性伸缩能力和可靠性都获得了极大的提高。
零拷贝
Redis用过哪些数据数据,以及Redis底层怎么实现*
List,Map,String,ZSet,Set,
Redis的底层是C++实现的
Redis缓存穿透,缓存雪崩*
缓存穿透是指查询一个必定不存在的数据,因为缓存是不命中时被动写的,而且出于容错考虑,若是从存储层查不到数据则不写入缓存,这将致使这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。在流量大时,可能DB就挂掉了,要是有人利用不存在的key频繁攻击咱们的应用,这就是漏洞。
有不少种方法能够有效地解决缓存穿透问题,最多见的则是采用布隆过滤器,将全部可能存在的数据哈希到一个足够大的bitmap中,一个必定不存在的数据会被 这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。另外也有一个更为简单粗暴的方法(咱们采用的就是这种),若是一个查询返回的数据为空(无论是数据不存在,仍是系统故障),咱们仍然把这个空结果进行缓存,但它的过时时间会很短,最长不超过五分钟。
缓存雪崩是指在咱们设置缓存时采用了相同的过时时间,致使缓存在某一时刻同时失效,请求所有转发到DB,DB瞬时压力太重雪崩。
缓存失效时的雪崩效应对底层系统的冲击很是可怕。大多数系统设计者考虑用加锁或者队列的方式保证缓存的单线 程(进程)写,从而避免失效时大量的并发请求落到底层存储系统上。这里分享一个简单方案就时讲缓存失效时间分散开,好比咱们能够在原有的失效时间基础上增长一个随机值,好比1-5分钟随机,这样每个缓存的过时时间的重复率就会下降,就很难引起集体失效的事件。
如何使用Redis来实现分布式锁*
分布式锁通常有三种实现方式:
数据库乐观锁;
基于Redis的分布式锁;
基于ZooKeeper的分布式锁。
互斥性。在任意时刻,只有一个客户端能持有锁。
不会发生死锁。即便有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其余客户端能加锁。
具备容错性。只要大部分的Redis节点正常运行,客户端就能够加锁和解锁。
解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端本身不能把别人加的锁给解了。
加锁操做:jedis.set(key,value,“NX”,“EX”,timeOut)【保证加锁的原子操做】
key就是redis的key值做为锁的标识,value在这里做为客户端的标识,只有key-value都比配才有删除锁的权利【保证安全性】
经过timeOut设置过时时间保证不会出现死锁【避免死锁】
NX,EX什么意思?
NX:只有这个key不存才的时候才会进行操做,if not exists;
EX:设置key的过时时间为秒,具体时间由第5个参数决定
luaScript 这个字符串是个lua脚本,表明的意思是若是根据key拿到的value跟传入的value相同就执行del,不然就返回0【保证安全性】
jedis.eval(String,list,list);这个命令就是去执行lua脚本,KEYS的集合就是第二个参数,ARGV的集合就是第三参数【保证解锁的原子操做】
Redis的并发竞争问题如何解决
1.客户端角度,为保证每一个客户端间正常有序与Redis进行通讯,对链接进行池化,同时对客户端读写Redis操做采用内部锁synchronized。
2.服务器角度,利用setnx实现锁。
http://www.javashuo.com/article/p-svvbuzni-o.html
获取锁的时候,使用setnx加锁,并使用expire命令为锁添加一个超时时间,超过该时间则自动释放锁,锁的value值为一个随机生成的UUID,经过此在释放锁的时候进行判断。
获取锁的时候还设置一个获取的超时时间,若超过这个时间则放弃获取锁。
释放锁的时候,经过UUID判断是否是该锁,如果该锁,则执行delete进行锁释放。
Redis持久化的几种方式,优缺点是什么,怎么实现的*
一种是RDB持久化(原理是将Reids在内存中的数据库记录定时dump到磁盘上的RDB持久化),另一种是AOF(append only file)持久化(原理是将Reids的操做日志以追加的方式写入文件)。
RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操做过程是fork一个子进程,先将数据集写入临时文件,写入成功后,再替换以前的文件,用二进制压缩存储。
AOF持久化以日志的形式记录服务器所处理的每个写、删除操做,查询操做不会记录,以文本的方式记录,能够打开文件看到详细的操做记录。
RDB存在哪些优点呢?
1). 一旦采用该方式,那么你的整个Redis数据库将只包含一个文件,这对于文件备份而言是很是完美的。好比,你可能打算每一个小时归档一次最近24小时的数据,同时还要天天归档一次最近30天的数据。经过这样的备份策略,一旦系统出现灾难性故障,咱们能够很是容易的进行恢复。
2). 对于灾难恢复而言,RDB是很是不错的选择。由于咱们能够很是轻松的将一个单独的文件压缩后再转移到其它存储介质上。
3). 性能最大化。对于Redis的服务进程而言,在开始持久化时,它惟一须要作的只是fork出子进程,以后再由子进程完成这些持久化的工做,这样就能够极大的避免服务进程执行IO操做了。
4). 相比于AOF机制,若是数据集很大,RDB的启动效率会更高。
RDB又存在哪些劣势呢?
1). 若是你想保证数据的高可用性,即最大限度的避免数据丢失,那么RDB将不是一个很好的选择。由于系统一旦在定时持久化以前出现宕机现象,此前没有来得及写入磁盘的数据都将丢失。
2). 因为RDB是经过fork子进程来协助完成数据持久化工做的,所以,若是当数据集较大时,可能会致使整个服务器中止服务几百毫秒,甚至是1秒钟。
AOF的优点有哪些呢?
1). 该机制能够带来更高的数据安全性,即数据持久性。Redis中提供了3中同步策略,即每秒同步、每修改同步和不一样步。事实上,每秒同步也是异步完成的,其效率也是很是高的,所差的是一旦系统出现宕机现象,那么这一秒钟以内修改的数据将会丢失。而每修改同步,咱们能够将其视为同步持久化,即每次发生的数据变化都会被当即记录到磁盘中。能够预见,这种方式在效率上是最低的。至于无同步,无需多言,我想你们都能正确的理解它。
2). 因为该机制对日志文件的写入操做采用的是append模式,所以在写入过程当中即便出现宕机现象,也不会破坏日志文件中已经存在的内容。然而若是咱们本次操做只是写入了一半数据就出现了系统崩溃问题,不用担忧,在Redis下一次启动以前,咱们能够经过redis-check-aof工具来帮助咱们解决数据一致性的问题。
3). 若是日志过大,Redis能够自动启用rewrite机制。即Redis以append模式不断的将修改数据写入到老的磁盘文件中,同时Redis还会建立一个新的文件用于记录此期间有哪些修改命令被执行。所以在进行rewrite切换时能够更好的保证数据安全性。
4). AOF包含一个格式清晰、易于理解的日志文件用于记录全部的修改操做。事实上,咱们也能够经过该文件完成数据的重建。
AOF的劣势有哪些呢?
1). 对于相同数量的数据集而言,AOF文件一般要大于RDB文件。RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。
2). 根据同步策略的不一样,AOF在运行效率上每每会慢于RDB。总之,每秒同步策略的效率是比较高的,同步禁用策略的效率和RDB同样高效。
Redis的缓存失效策略*
当缓存须要被清理时(好比空间占用已经接近临界值了),须要使用某种淘汰算法来决定清理掉哪些数据。经常使用的淘汰算法有下面几种:
FIFO:First In First Out,先进先出。判断被存储的时间,离目前最远的数据优先被淘汰。
LRU:Least Recently Used,最近最少使用。判断最近被使用的时间,目前最远的数据优先被淘汰。
LFU:Least Frequently Used,最不常用。在一段时间内,数据被使用次数最少的,优先被淘汰。
Redis集群,高可用,原理
高可用性:在主机挂掉后,自动故障转移,使前端服务对用户无影响。
读写分离:将主机读压力分流到从机上。
https://www.cnblogs.com/leeSmall/p/8414687.html
RedisCluster是redis的分布式解决方案,在3.0版本后推出的方案,有效地解决了Redis分布式的需求,当一个服务挂了能够快速的切换到另一个服务,当遇到单机内存、并发等瓶颈时,可以使用此方案来解决这些问题
Redis集群采用了哈希分区的 虚拟槽分区 方式slot 0-16383,共16384槽位
进行分区
主从复制
持久化
故障切换
Redis缓存分片
若是只使用一个redis实例时,其中保存了服务器中所有的缓存数据,这样会有很大风险,若是单台redis服务宕机了将会影响到整个服务。解决的方法就是咱们能够采用分片/分区的技术,将原来一台服务器维护的整个缓存,如今换为由多台服务器共同维护内存空间。
Redis的数据淘汰策略
https://blog.csdn.net/suibo0912hf/article/details/51684625
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(驱逐):禁止驱逐数据
技术框架
@Resource的做用至关于@Autowired,只不过@Autowired按byType自动注入,而@Resource默认按 byName自动注入罢了。@Resource有两个属性是比较重要的,分是name和type,Spring将@Resource注解的name属性解析为bean的名字,而type属性则解析为bean的类型。因此若是使用name属性,则使用byName的自动注入策略,而使用type属性时则使用byType自动注入策略。若是既不指定name也不指定type属性,这时将经过反射机制使用byName自动注入策略。
@Resource装配顺序
1. 若是同时指定了name和type,则从Spring上下文中找到惟一匹配的bean进行装配,找不到则抛出异常
2. 若是指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常
3. 若是指定了type,则从上下文中找到类型匹配的惟一bean进行装配,找不到或者找到多个,都会抛出异常
4. 若是既没有指定name,又没有指定type,则自动按照byName方式进行装配;若是没有匹配,则回退为一个原始类型进行匹配,若是匹配则自动装配;
技术深度
断开链接须要四次挥手
提醒:中断链接端能够是Client端,也能够是Server端。只要将下面两角色互换便可。客户端进程发出链接释放报文,而且中止发送数据。释放数据报文首部,FIN=1,其序列号为seq=u(等于前面已经传送过来的数据的最后一个字节的序号加1),此时,客户端进入FIN-WAIT-1(终止等待1)状态。 TCP规定,FIN报文段即便不携带数据,也要消耗一个序号。
服务器收到链接释放报文,发出确认报文,ACK=1,ack=u+1,而且带上本身的序列号seq=v,此时,服务端就进入了CLOSE-WAIT(关闭等待)状态。TCP服务器通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,可是服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。
客户端收到服务器的确认请求后,此时,客户端就进入FIN-WAIT-2(终止等待2)状态,等待服务器发送链接释放报文(在这以前还须要接受服务器发送的最后的数据)。
服务器将最后的数据发送完毕后,就向客户端发送链接释放报文,FIN=1,ack=u+1,因为在半关闭状态,服务器极可能又发送了一些数据,假定此时的序列号为seq=w,此时,服务器就进入了LAST-ACK(最后确认)状态,等待客户端的确认。
客户端收到服务器的链接释放报文后,必须发出确认,ACK=1,ack=w+1,而本身的序列号是seq=u+1,此时,客户端就进入了TIME-WAIT(时间等待)状态。注意此时TCP链接尚未释放,必须通过2∗∗MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态。
服务器只要收到了客户端发出的确认,当即进入CLOSED状态。一样,撤销TCB后,就结束了此次的TCP链接。能够看到,服务器结束TCP链接的时间要比客户端早一些。
一致性Hash算法*
一致性hash做为一个负载均衡算法,能够用在分布式缓存、数据库的分库分表等场景中,还能够应用在负载均衡器中做为做为负载均衡算法。在有多台服务器时,对于某个请求资源经过hash算法,映射到某一个台服务器,当增长或减小一台服务器时,可能会改变这些资源对应的hash值,这样可能致使一部分缓存或数据失效了。一致性hash就是尽量在将同一个资源请求路由到同一台服务器中。
一致性哈希采用的作法以下:引入一个环的概念,如上面的第一个图。先将机器映射到这个环上,再将数据也经过相同的哈希函数映射到这个环上,数据存储在它顺时针走向的那台机器上。以环为中介,实现了数据与机器数目之间的解耦。这样,当机器的数目变化时,只会影响到增长或删除的那台机器所在的环的邻接机器的数据存储,而其余机器上的数据不受影响。
JVM如何加载字节码文件*
类从被加载到虚拟机内存到卸载出内存的生命周期包括:加载->链接(验证->准备->解析)->初始化->使用->卸载
加载:1经过一个类的权限定名来获取定义此类的二进制字节流
2 将这个字节流所表明的静态存储结构转化为方法区的运行时数据结构
3 在java堆中生成一个表明这个类的java.lang.Class对象,做为方法区这些数据的访问入口。
进行 文件格式、元数据、字节码。符号引用验证
准备 正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中进行分配。
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,类接口,字段,类方法,接口方法。
初始化加载程序字节码
类加载器如何卸载字节码
IO和NIO的区别,NIO优势
IO NIO
面向流 面向缓冲
阻塞IO 非阻塞IO
无 选择器
IO是面向流的,NIO是面向缓冲区的
Java IO面向流意味着每次从流中读一个或多个字节,直至读取全部字节,它们没有被缓存在任何地方;
NIO则能先后移动流中的数据,由于是面向缓冲区的
IO流是阻塞的,NIO流是不阻塞的
Java IO的各类流是阻塞的。这意味着,当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据彻底写入。该线程在此期间不能再干任何事情了
Java NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,可是它仅能获得目前可用的数据,若是目前没有数据可用时,就什么都不会获取。NIO可以让您只使用一个(或几个)单线程管理多个通道(网络链接或文件),但付出的代价是解析数据可能会比从一个阻塞流中读取数据更复杂。
非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不须要等待它彻底写入,这个线程同时能够去作别的事情。
选择器
Java NIO的选择器容许一个单独的线程来监视多个输入通道,你能够注册多个通道使用一个选择器,而后使用一个单独的线程来“选择”通道:这些通道里已经有能够处理的输入,或者选择已准备写入的通道。这种选择机制,使得一个单独的线程很容易来管理多个通道。
9. Java线程池的实现原理,keepAliveTime等参数的做用。
corePoolSize:线程池核心线程数量
maximumPoolSize:线程池最大线程数量
keepAliverTime:当活跃线程数大于核心线程数时,空闲的多余线程最大存活时间
unit:存活时间的单位
workQueue:存听任务的队列
handler:超出线程范围和队列容量的任务的处理程序
一、判断线程池里的核心线程是否都在执行任务,若是不是(核心线程空闲或者还有核心线程没有被建立)则建立一个新的工做线程来执行任务。若是核心线程都在执行任务,则进入下个流程。
二、线程池判断工做队列是否已满,若是工做队列没有满,则将新提交的任务存储在这个工做队列里。若是工做队列满了,则进入下个流程。
三、判断线程池里的线程是否都处于工做状态,若是没有,则建立一个新的工做线程来执行任务。若是已经满了,则交给饱和策略来处理这个任务。
https://www.jianshu.com/p/87bff5cc8d8c
keepAliveTime:线程空闲时的存活时间,即当线程没有任务执行时,继续存活的时间;默认状况下,该参数只在线程数大于corePoolSize时才有用;
10. HTTP链接池实现原理
一、下降延迟:若是不采用链接池,每次链接发起Http请求的时候都会从新创建TCP链接(经历3次握手),用完就会关闭链接(4次挥手),若是采用链接池则减小了这部分时间损耗,别小看这几回握手,本人通过测试发现,基本上3倍的时间延迟
二、支持更大的并发:若是不采用链接池,每次链接都会打开一个端口,在大并发的状况下系统的端口资源很快就会被用完,致使没法创建新的链接
PoolingHttpClientConnectionManager
配置请求超时设置—RequestConfig
CloseableHttpClient 获取httpClient对象,post,get封装
11. 数据库链接池实现原理
装载数据库驱动程序;
经过jdbc创建数据库链接;
访问数据库,执行sql语句;
断开数据库链接。
建立链接池,获取链接,用完后返回给链接池。
12. 数据库的实现原理
分布式
什么是CAP定理
一致性(C):在分布式系统中的全部数据备份,在同一时刻是否一样的值。(等同于全部节点访问同一份最新的数据副本),换句话就是说,任什么时候刻,所用的应用程序都能访问获得相同的数据。
可用性(A):在集群中一部分节点故障后,集群总体是否还能响应客户端的读写请求。(对数据更新具有高可用性),换句话就是说,任什么时候候,任何应用程序均可以读写数据。
分区容错性(P):以实际效果而言,分区至关于对通讯的时限要求。系统若是不能在时限内达成数据一致性,就意味着发生了分区的状况,必须就当前操做在C和A之间作出选择,换句话说,系统能够跨网络分区线性的伸缩和扩展。
CAP 理论和 BASE 理论
eBay的架构师Dan Pritchett源于对大规模分布式系统的实践总结,在ACM上发表文章提出BASE理论,BASE理论是对CAP理论的延伸,核心思想是即便没法作到强一致性(Strong Consistency,CAP的一致性就是强一致性),但应用能够采用适合的方式达到最终一致性(Eventual Consitency)。
基本可用(Basically Available)
基本可用是指分布式系统在出现故障的时候,容许损失部分可用性,即保证核心可用。
电商大促时,为了应对访问量激增,部分用户可能会被引导到降级页面,服务层也可能只提供降级服务。这就是损失部分可用性的体现。
软状态( Soft State)
软状态是指容许系统存在中间状态,而该中间状态不会影响系统总体可用性。分布式存储中通常一份数据至少会有三个副本,容许不一样节点间副本同步的延时就是软状态的体现。mysql replication的异步复制也是一种体现。
最终一致性( Eventual Consistency)
最终一致性是指系统中的全部数据副本通过必定时间后,最终可以达到一致的状态。弱一致性和强一致性相反,最终一致性是弱一致性的一种特殊状况。
CAP 理论
CAP理论
2000年7月,加州大学伯克利分校的Eric Brewer教授在ACM PODC会议上提出CAP猜测。2年后,麻省理工学院的Seth Gilbert和Nancy Lynch从理论上证实了CAP。以后,CAP理论正式成为分布式计算领域的公认定理。
CAP理论为:一个分布式系统最多只能同时知足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance)这三项中的两项。
1.1 一致性(Consistency)
一致性指“all nodes see the same data at the same time”,即更新操做成功并返回客户端完成后,全部节点在同一时间的数据彻底一致。
1.2 可用性(Availability)
可用性指“Reads and writes always succeed”,即服务一直可用,并且是正常响应时间。
1.3 分区容错性(Partition tolerance)
分区容错性指“the system continues to operate despite arbitrary message loss or failure of part of the system”,即分布式系统在遇到某节点或网络分区故障的时候,仍然可以对外提供知足一致性和可用性的服务。
CAP 理论和最终一致性
一言以蔽之:过程松,结果紧,最终结果必须保持一致性
最终一致性是弱一致性的一种特例。假如A首先write了一个值到存储系统,存储系统保证若是在A,B,C后续读取以前没有其它写操做更新一样的值的话,最终全部的读取操做都会读取到最A写入的最新值。此种状况下,若是没有失败发生的话,“不一致性窗口”的大小依赖于如下的几个因素:交互延迟,系统的负载,以及复制技术中replica的个数(这个能够理解为master/salve模式中,salve的个数),最终一致性方面最出名的系统能够说是DNS系统,当更新一个域名的IP之后,根据配置策略以及缓存控制策略的不一样,最终全部的客户都会看到最新的值
最终一致性实现方式
http://www.javashuo.com/article/p-ylrffhxg-cn.html
分布式事务,两阶段提交。
两阶段提交涉及到多个节点的网络通讯,通讯时间若是过长,事务的相对时间也就会过长,那么锁定资源的时间也就长了.在高并发的服务中,就会存在严重的性能瓶颈
如何实现分布式锁*
https://blog.csdn.net/xlgen157387/article/details/79036337
基于数据库实现分布式锁;
基于缓存(Redis等)实现分布式锁;
基于Zookeeper实现分布式锁;
如何实现分布式Session*
https://www.cnblogs.com/cxrz/p/8529587.html
基于数据库的Session共享
基于NFS共享文件系统
基于memcached 的session,如何保证 memcached 自己的高可用性?
基于resin/tomcat web容器自己的session复制机制
基于TT/Redis 或 jbosscache 进行 session 共享。
基于cookie 进行session共享
如何保证消息的一致性*
负载均衡
负载均衡是高可用网络基础架构的的一个关键组成部分,有了负载均衡,咱们一般能够将咱们的应用服务器部署多台,而后经过负载均衡将用户的请求分发到不一样的服务器用来提升网站、应用、数据库或其余服务的性能以及可靠性
https://baijiahao.baidu.com/s?id=1595722616086971162&wfr=spider&for=pc
正向代理(客户端代理)和反向代理(服务器端代理)*
正向代理是一个位于客户端和原始服务器(origin server)之间的服务器,为了从原始服务器取得内容,客户端向代理发送一个请求并指定目标(原始服务器),而后代理向原始服务器转交请求并将得到的内容返回给客户端。客户端必需要进行一些特别的设置才能使用正向代理。
(1)访问原来没法访问的资源,如google
(2)能够作缓存,加速访问资源
(3)对客户端访问受权,上网进行认证
(4)代理能够记录用户访问记录(上网行为管理),对外隐藏用户信息
反向代理(Reverse Proxy)实际运行方式是指以代理服务器来接受internet上的链接请求,而后将请求转发给内部网络上的服务器,并将从服务器上获得的结果返回给internet上请求链接的客户端,此时代理服务器对外就表现为一个服务器。
(1)保证内网的安全,能够使用反向代理提供WAF功能,阻止web攻击
(2)负载均衡,经过反向代理服务器来优化网站的负载
nginx支持配置反向代理,经过反向代理实现网站的负载均衡。这部分先写一个nginx的配置,后续须要深刻研究nginx的代理模块和负载均衡模块。
http://www.javashuo.com/article/p-nahkijxd-ce.html
CDN实现原理
(1)CDN节点解决了跨运营商和跨地域访问的问题,访问延时大大下降;
(2)大部分请求在CDN边缘节点完成,CDN起到了分流做用,减轻了源站的负载。
CDN即内容分发网络,加速的意思,那么网站CND服务是网站加速服务。
CDN加速将网站的内容缓存在网络边缘(离用户接入网络最近的地方),而后在用户访问网站内容的时候,经过调度系统将用户的请求路由或者引导到离用户接入网络最近或者访问效果的缓存服务器上,有该缓存服务器为用户提供内容服务;相对于直接访问源站,这种方式缩短了用户和内容之间的网络距离,从而达到加速的效果
怎么提高系统的QPS和吞吐量*
QPS(TPS):每秒钟request/事务 数量
并发数:系统同时处理的request/事务数
响应时间:通常取平均响应时间
简单而言经过增长集群来提高qps和吞吐量
实际上要比这个要复杂
首先咱们须要知道系统的瓶颈
咱们所知道的系统拓扑架构
对于rest接口而言
系统设施依次是:
dns
nginx
tomcat
db/soa
首先咱们能够经过增长集群来增长qps和吞吐量
其次考虑到负载均衡的问题,咱们能够经过其余设施来保证集群节点的负载均衡,进一步提升系统qps
因而就有nginx集群+负载均衡
tomcat集群+负载均衡
到db/soa这一层的时候,一样也能够经过增长集群+负载均衡的方式来解决
咱们还能够在每一层增长缓存来应对热点数据
然而另一个方面,能够系统拆分,服务拆分,分别针对瓶颈的系统单独增长集群和负载均衡来解决
一样db也能够分库分表,
由于单表超过1000万条数据时就很慢了,因此这个时候就须要库拆分,因而就有垂直拆分,水平拆分。
异步化,能够不一样调用的异步化,使用mq,好比发送短信,发送邮件等
综上所述:
集群+负载均衡
增长缓存
系统拆分
分库分表
垂直拆分+水平拆分
异步化+MQ
15. Dubbo的底层实现原理和机制*
采用分层的方式来架构,采用服务提供方和服务消费方简单模型。
Dubbo框架设计一共划分了10个层,而最上面的Service层是留给实际想要使用Dubbo开发分布式服务的开发者实现业务逻辑的接口层。图中左边淡蓝背景的为服务消费方使用的接口,右边淡绿色背景的为服务提供方使用的接口,位于中轴线上的为双方都用到的接口。
一、服务接口层(Service):该层是与实际业务逻辑相关的,根据服务提供方和服务消费方的业务设计对应的接口和实现。
二、配置层(Config):对外配置接口,以ServiceConfig和ReferenceConfig为中心,能够直接new配置类,也能够经过spring解析配置生成配置类。
三、服务代理层(Proxy):服务接口透明代理,生成服务的客户端Stub和服务器端Skeleton,以ServiceProxy为中心,扩展接口为ProxyFactory。
四、服务注册层(Registry):封装服务地址的注册与发现,以服务URL为中心,扩展接口为RegistryFactory、Registry和RegistryService。可能没有服务注册中心,此时服务提供方直接暴露服务。
五、集群层(Cluster):封装多个提供者的路由及负载均衡,并桥接注册中心,以Invoker为中心,扩展接口为Cluster、Directory、Router和LoadBalance。将多个服务提供方组合为一个服务提供方,实现对服务消费方来透明,只须要与一个服务提供方进行交互。
六、监控层(Monitor):RPC调用次数和调用时间监控,以Statistics为中心,扩展接口为MonitorFactory、Monitor和MonitorService。
七、远程调用层(Protocol):封将RPC调用,以Invocation和Result为中心,扩展接口为Protocol、Invoker和Exporter。Protocol是服务域,它是Invoker暴露和引用的主功能入口,它负责Invoker的生命周期管理。Invoker是实体域,它是Dubbo的核心模型,其它模型都向它靠扰,或转换成它,它表明一个可执行体,可向它发起invoke调用,它有多是一个本地的实现,也多是一个远程的实现,也可能一个集群实现。
八、信息交换层(Exchange):封装请求响应模式,同步转异步,以Request和Response为中心,扩展接口为Exchanger、ExchangeChannel、ExchangeClient和ExchangeServer。
九、网络传输层(Transport):抽象mina和netty为统一接口,以Message为中心,扩展接口为Channel、Transporter、Client、Server和Codec。
十、数据序列化层(Serialize):可复用的一些工具,扩展接口为Serialization、 ObjectInput、ObjectOutput和ThreadPool。
Dubbo提供了三个关键功能:基于接口的远程调用,容错与负载均衡,服务自动注册与发现。
Dubbo做为一个分布式服务框架,主要具备以下几个核心的要点:
服务定义
服务是围绕服务提供方和服务消费方的,服务提供方实现服务,而服务消费方调用服务。
服务注册
服务提供方,它须要发布服务,并且因为应用系统的复杂性,服务的数量、类型也不断膨胀;对于服务消费方,它最关心如何获取到它所须要的服务,而面对复杂的应用系统,须要管理大量的服务调用。并且,对于服务提供方和服务消费方来讲,他们还有可能兼具这两种角色,即既须要提供服务,有须要消费服务。
经过将服务统一管理起来,能够有效地优化内部应用对服务发布/使用的流程和管理。服务注册中心能够经过特定协议来完成服务对外的统一。Dubbo提供的注册中心有以下几种类型可供选择:
Multicast注册中心
Zookeeper注册中心
Redis注册中心
Simple注册中心
服务监控
服务提供方,仍是服务消费方,他们都须要对服务调用的实际状态进行有效的监控,从而改进服务质量。
远程通讯与信息交换
远程通讯须要指定通讯双方所约定的协议,在保证通讯双方理解协议语义的基础上,还要保证高效、稳定的消息传输。Dubbo继承了当前主流的网络通讯框架,主要包括以下几个:
Mina
Netty
Grizzly
服务调用
下面从Dubbo官网直接拿来,看一下基于RPC层,服务提供方和服务消费方之间的调用关系,
上图中,蓝色的表示与业务有交互,绿色的表示只对Dubbo内部交互。上述图所描述的调用流程以下:
服务提供方发布服务到服务注册中心;
服务消费方从服务注册中心订阅服务;
服务消费方调用已经注册的可用服务;
节点角色说明:
Provider: 暴露服务的服务提供方。
Consumer: 调用远程服务的服务消费方。
Registry: 服务注册与发现的注册中心。
Monitor: 统计服务的调用次数和调用时间的监控中心。
Container: 服务运行容器。
调用关系说明:
0. 服务容器负责启动,加载,运行服务提供者。
服务自动注册
客户端自动发现
变动下发
18. 接口的幂等性的概念
在数学里,幂等有两种主要的定义:
在某二元运算下,幂等元素是指被本身重复运算(或对于函数是为复合)的结果等于它本身的元素。例如,乘法下惟一两个幂等实数为0和1。 即 s s = s
某一元运算为幂等的时,其做用在任一元素两次后会和其做用一次的结果相同。例如,高斯符号即是幂等的,即f(f(x)) = f(x)。
HTTP的幂等性指的是一次和屡次请求某一个资源应该具备相同的反作用。如经过PUT接口将数据的Status置为1,不管是第一次执行仍是屡次执行,获取到的结果应该是相同的,即执行完成以后Status =1。
orderStatus由0->1 是须要幂等性的
19. 消息中间件如何解决消息丢失问题
消息持久化
ACK确认机制
设置集群镜像模式
1)单节点模式:最简单的状况,非集群模式,节点挂了,消息就不能用了。业务可能瘫痪,只能等待。
2)普通模式:默认的集群模式,某个节点挂了,该节点上的消息不能用,有影响的业务瘫痪,只能等待节点恢复重启可用(必须持久化消息状况下)。
3)镜像模式:把须要的队列作成镜像队列,存在于多个节点,属于RabbitMQ的HA方案
消息补偿机制:息补偿机制须要创建在消息要写入DB日志,发送日志,接受日志,二者的状态必须记录。而后根据DB日志记录check 消息发送消费是否成功,不成功,进行消息补偿措施,从新发送消息处理。
20. Dubbo的服务请求失败怎么处理
所以,将应用拆分,并抽取出核心服务来解决上述问题,还要考虑负载均衡、服务监控、高可用性、服务隔离与降级、路由策略、完善的容错机制、序列化方案的选择、通讯框架的选择、开发人员对底层细节无感知、服务升级兼容性等问题。Dubbo知足了以上全部需求。
21. 重连机制会不会形成错误
dubbo在调用服务不成功时,默认会重试2次。
Dubbo的路由机制,会把超时的请求路由到其余机器上,而不是本机尝试,因此 dubbo的重试机制也能必定程度的保证服务的质量。
可是若是不合理的配置重试次数,当失败时会进行重试屡次,这样在某个时间点出现性能问题,调用方再连续重复调用。
系统请求变为正常值的retries倍,系统压力会大增,容易引发服务雪崩,须要根据业务状况规划好如何进行异常处理,什么时候进行重试。
22. 对分布式事务的理解
分布式事务就是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不一样的分布式系统的不一样节点之上。以上是百度百科的解释,简单的说,就是一次大的操做由不一样的小操做组成,这些小的操做分布在不一样的服务器上,且属于不一样的应用,分布式事务须要保证这些小操做要么所有成功,要么所有失败。本质上来讲,分布式事务就是为了保证不一样数据库的数据一致性。
分布式事务,本质上是对多个数据库的事务进行统一控制,按照控制力度能够分为:不控制、部分控制和彻底控制。不控制就是不引入分布式事务,部分控制就是各类变种的两阶段提交,包括上面提到的消息事务+最终一致性、TCC模式,而彻底控制就是彻底实现两阶段提交。部分控制的好处是并发量和性能很好,缺点是数据一致性减弱了,彻底控制则是牺牲了性能,保障了一致性,具体用哪一种方式,最终仍是取决于业务场景。
下单—涉及扣库存和更新订单状态。
23. 如何实现负载均衡,有哪些算法能够实现?
既然要解决后端系统的承载能力:nginx的配置
均衡算法主要解决将请求如何发送给后端服务
随机(random)、轮训(round-robin)、一致哈希(consistent-hash)和主备(master-slave)。
24. Zookeeper的用途,选举的原理是什么?
分布式系统基本上都是主从结构,因此须要zookeeper进行协调服务,他作不少事情的,好比命名服务,配置管理,集群管理,分布式协调通知等等
当leader崩溃或者leader失去大多数的follower,这时候zk进入恢复模式,恢复模式须要从新选举出一个新的leader,让全部的Server都恢复到一个正确的状态。Zk的选举算法有两种:一种是基于basic paxos实现的,另一种是基于fast paxos算法实现的。系统默认的选举算法为fast paxos。
1.服务器初始化时Leader选举
zookeeper因为其自身的性质,通常建议选取奇数个节点进行搭建分布式服务器集群。以3个节点组成的服务器集群为例,说明服务器初始化时的选举过程。启动第一台安装zookeeper的节点时,没法单独进行选举,启动第二台时,两节点之间进行通讯,开始选举Leader。
1)每一个Server投出一票。他们两都选本身为Leader,投票的内容为(SID,ZXID)。SID即Server的id,安装zookeeper时配置文件中所配置的myid;ZXID,事务id,为节点的更新程度,ZXID越大,表明Server对Znode的操做越新。因为服务器初始化,每一个Sever上的Znode为0,因此Server1投的票为(1,0),Server2为(2,0)。两Server将各自投票发给集群中其余机器。 2)每一个Server接收来自其余Server的投票。集群中的每一个Server先判断投票有效性,如检查是否是本轮的投票,是否是来Looking状态的服务器投的票。 3)对投票结果进行处理。先了解下处理规则 - 首先对比ZXID。ZXID大的服务器优先做为Leader - 若ZXID相同,好比初始化的时候,每一个Server的ZXID都为0,就会比较myid,myid大的选出来作Leader。 对于Server而言,他接受到的投票为(2,0),由于自身的票为(1,0),因此此时它会选举Server2为Leader,将本身的更新为(2,0)。而Server2收到的投票为Server1的(1,0)因为比他本身小,Server2的投票不变。Server1和Server2再次将票投出,投出的票都为(2,0)。 4) 统计投票。每次投票以后,服务器都会统计投票信息,若是断定某个Server有过半的票数投它,那么该Server将会做为Leader。对于Server1和Server2而言,统计出已经有两台机器接收了(2,0)的投票信息,此时认为选出了Leader。 5)改变服务器状态。当肯定了Leader以后,每一个Server更新本身的状态,Leader将状态更新为Leading,Follower将状态更新为Following。 2.服务器运行期间的Leader选举 zookeeper运行期间,若是有新的Server加入,或者非Leader的Server宕机,那么Leader将会同步数据到新Server或者寻找其余备用Server替代宕机的Server。若Leader宕机,此时集群暂停对外服务,开始在内部选举新的Leader。假设当前集群中有Server一、Server二、Server3三台服务器,Server2为当前集群的Leader,因为意外状况,Server2宕机了,便开始进入选举状态。过程以下 1) 变动状态。其余的非Observer服务器将本身的状态改变为Looking,开始进入Leader选举。 2) 每一个Server发出一个投票(myid,ZXID),因为此集群已经运行过,因此每一个Server上的ZXID可能不一样。假设Server1的ZXID为145,Server3的为122,第一轮投票中,Server1和Server3都投本身,票分别为(1,145)、(3,122),将本身的票发送给集群中全部机器。 3) 每一个Server接收接收来自其余Server的投票,降下来的步骤与启动时步骤相同。
系统架构
https://www.cnblogs.com/vindia/category/1082005.html
系统应该有页面和接口
页面用于展现用户界面,接口用于获取数据
界面:秒杀页面,秒杀成功页面,秒杀失败页面,命中限流页面(查看订单页面不算秒杀系统的功能)
接口:秒杀下单接口,秒杀成功获取订单生成状态接口
LINUX
TCP/IP
OSI与TCP/IP各层的结构与功能,都有哪些协议。*
http://www.javashuo.com/article/p-btdxrjbn-hu.html
OSI模型—应用层,表示层,回话层,传输层,网络层,数据链路层,物理层
1.物理层:主要定义物理设备标准,如网线的接口类型、光纤的接口类型、各类传输介质的传输速率等。它的主要做用是传输比特流(就是由一、0转化为电流强弱来进行传输,到达目的地后在转化为一、0,也就是咱们常说的数模转换与模数转换)。这一层的数据叫作比特。
2.数据链路层:定义了如何让格式化数据以进行传输,以及如何让控制对物理介质的访问。这一层一般还提供错误检测和纠正,以确保数据的可靠传输。
3.网络层:在位于不一样地理位置的网络中的两个主机系统之间提供链接和路径选择。Internet的发展使得从世界各站点访问信息的用户数大大增长,而网络层正是管理这种链接的层。
4.传输层:定义了一些传输数据的协议和端口号(WWW端口80等),如:TCP(传输控制协议,传输效率低,可靠性强,用于传输可靠性要求高,数据量大的数据),UDP(用户数据报协议,与TCP特性偏偏相反,用于传输可靠性要求不高,数据量小的数据,如QQ聊天数据就是经过这种方式传输的)。 主要是将从下层接收的数据进行分段和传输,到达目的地址后再进行重组。经常把这一层数据叫作段。
5.会话层:经过传输层(端口号:传输端口与接收端口)创建数据传输的通路。主要在你的系统之间发起会话或者接受会话请求(设备之间须要互相认识能够是IP也能够是MAC或者是主机名)
6.表示层:可确保一个系统的应用层所发送的信息能够被另外一个系统的应用层读取。例如,PC程序与另外一台计算机进行通讯,其中一台计算机使用扩展二一十进制交换码(EBCDIC),而另外一台则使用美国信息交换标准码(ASCII)来表示相同的字符。若有必要,表示层会经过使用一种通格式来实现多种数据格式之间的转换。
7.应用层: 是最靠近用户的OSI层。这一层为用户的应用程序(例如电子邮件、文件传输和终端仿真)提供网络服务。
TCP与UDP的区别。*
TCP(Transmission Control Protocol):传输控制协议
UDP(User Datagram Protocol):用户数据报协议
TCP是面向链接的、可靠的、有序的、速度慢的协议;
UDP是无链接的、不可靠的、无序的、速度快的协议。
TCP开销比UDP大,TCP头部须要20字节,UDP头部只要8个字节。
TCP无界有拥塞控制,UDP有界无拥塞控制。
TCP报文结构。
http://www.javashuo.com/article/p-qjcsagnz-ke.html
一、端口号:用来标识同一台计算机的不一样的应用进程。
1)源端口:源端口和IP地址的做用是标识报文的返回地址。
2)目的端口:端口指明接收方计算机上的应用程序接口。
TCP报头中的源端口号和目的端口号同IP数据报中的源IP与目的IP惟一肯定一条TCP链接。
二、序号和确认号:是TCP可靠传输的关键部分。序号是本报文段发送的数据组的第一个字节的序号。在TCP传送的流中,每个字节一个序号。e.g.一个报文段的序号为300,此报文段数据部分共有100字节,则下一个报文段的序号为400。因此序号确保了TCP传输的有序性。确认号,即ACK,指明下一个期待收到的字节序号,代表该序号以前的全部数据已经正确无误的收到。确认号只有当ACK标志为1时才有效。好比创建链接时,SYN报文的ACK标志位为0。
三、数据偏移/首部长度:4bits。因为首部可能含有可选项内容,所以TCP报头的长度是不肯定的,报头不包含任何任选字段则长度为20字节,4位首部长度字段所能表示的最大值为1111,转化为10进制为15,15*32/8 = 60,故报头最大长度为60字节。首部长度也叫数据偏移,是由于首部长度实际上指示了数据区在报文段中的起始偏移值。
四、保留:为未来定义新的用途保留,如今通常置0。
五、控制位:URG ACK PSH RST SYN FIN,共6个,每个标志位表示一个控制功能。
1)URG:紧急指针标志,为1时表示紧急指针有效,为0则忽略紧急指针。
2)ACK:确认序号标志,为1时表示确认号有效,为0表示报文中不含确认信息,忽略确认号字段。
3)PSH:push标志,为1表示是带有push标志的数据,指示接收方在接收到该报文段之后,应尽快将这个报文段交给应用程序,而不是在缓冲区排队。
4)RST:重置链接标志,用于重置因为主机崩溃或其余缘由而出现错误的链接。或者用于拒绝非法的报文段和拒绝链接请求。
5)SYN:同步序号,用于创建链接过程,在链接请求中,SYN=1和ACK=0表示该数据段没有使用捎带的确认域,而链接应答捎带一个确认,即SYN=1和ACK=1。
6)FIN:finish标志,用于释放链接,为1时表示发送方已经没有数据发送了,即关闭本方数据流。
六、窗口:滑动窗口大小,用来告知发送端接受端的缓存大小,以此控制发送端发送数据的速率,从而达到流量控制。窗口大小时一个16bit字段,于是窗口大小最大为65535。
七、校验和:奇偶校验,此校验和是对整个的 TCP 报文段,包括 TCP 头部和 TCP 数据,以 16 位字进行计算所得。由发送端计算和存储,并由接收端进行验证。
八、紧急指针:只有当 URG 标志置 1 时紧急指针才有效。紧急指针是一个正的偏移量,和顺序号字段中的值相加表示紧急数据最后一个字节的序号。 TCP 的紧急方式是发送端向另外一端发送紧急数据的一种方式。
九、选项和填充:最多见的可选字段是最长报文大小,又称为MSS(Maximum Segment Size),每一个链接方一般都在通讯的第一个报文段(为创建链接而设置SYN标志为1的那个段)中指明这个选项,它表示本端所能接受的最大报文段的长度。选项长度不必定是32位的整数倍,因此要加填充位,即在这个字段中加入额外的零,以保证TCP头是32的整数倍。
十、数据部分: TCP 报文段中的数据部分是可选的。在一个链接创建和一个链接终止时,双方交换的报文段仅有 TCP 首部。若是一方没有数据要发送,也使用没有任何数据的首部来确认收到的数据。在处理超时的许多状况中,也会发送不带任何数据的报文段。
TCP的三次握手与四次挥手过程,各个状态名称与含义,TIMEWAIT的做用。
TCP拥塞控制。
https://blog.csdn.net/lishanmin11/article/details/77090316
拥塞控制就是防止过多的数据注入网络中,这样能够使网络中的路由器或者链路不会致使过载。拥塞控制是一个全局性的过程,和流量控制不一样,流量控制指点对点通讯量的控制
拥塞控制主要是四个算法:1)慢启动,2)拥塞避免,3)拥塞发生,4)快速恢复。
(1)在通讯子网出现过多数据包的状况,使得网络的性能降低,甚至不能正常工做,这种现象就称为拥塞。
(2)网络拥塞的成因主要有三:一、处理器的速度太慢。二、线路容量的限制。三、节点输出包的能力小于输入包的能力。
(3)拥塞控制与流量控制是相关的,流量控制在数据链路层对一条通讯路径上的流量进行控制,其的是保证发送者的发送速度不超过接收者的接收速度,它只涉及一全发送者和一个接收者,是局部控制。拥塞控制是对整个通讯子网的流量进行控制,其目的是保证通讯子网中的流量与其资源相匹配,使子网不会出现性能降低和恶化、甚至崩溃,是全局控制。
(4)拥塞控制的最终目标是:一、防止因为过载而使吞吐量降低,损失效率;二、合理分配网络资源;三、避免死锁;四、匹配传输速度。
(5)对拥塞控制,可用的方法有两类:开环控制和闭环控制。
一、开环控制的思想是经过良好的设计避免拥塞问题的出现,确保拥塞问题在开始时就不可能发生。开环控制方法包括什么时候接受新的通讯什么时候丢弃包、丢弃哪些包。其特色是在做出决定时不考虑网络当前的状态。
二、闭环控制的思想是反馈控制。即经过将网络工做的动态信息反馈给网络中节点的有关进程,节点根据网络当前的动态信息,调整转发数据包的策略。闭环控制过程包括三部分: ①监视系统 检测网络发生或将要发生拥塞的时间和地点。②报告 将监视中检测到的信息传送到能够进行拥塞控制的节点。③决策 调整系统的操做行为,以解决问题。
(6)对应于开环控制的方法有:(基于拥塞预防)
一、预约缓冲区 二、合理分配缓冲区 三、通讯量整形法(A、许可证算法,B、漏桶算法,C、令牌漏桶算法。)
对应于闭环控制的方法有:(基于拥塞抑制,即拥塞出现或即将出现时采起适当的措施进行控制,直到消除拥塞)
一、阻塞包法。 二、负载丢弃法
TCP滑动窗口与回退N针协议。
Http的报文结构。
(1)HTTP请求报文
一个HTTP请求报文由请求行(request line)、请求头部(header)、空行和请求数据4个部分组成,下图给出了请求报文的通常格式。
(2)HTTP响应也由三个部分组成,分别是:状态行、消息报头、响应正文。
8. Http的状态码含义。
1xx: 信息
消息: 描述:
100 Continue 服务器仅接收到部分请求,可是一旦服务器并无拒绝该请求,客户端应该继续发送其他的请求。
101 Switching Protocols 服务器转换协议:服务器将听从客户的请求转换到另一种协议。
103 Checkpoint 用于 PUT 或者 POST 请求恢复失败时的恢复请求建议。
2xx: 成功
消息: 描述:
200 OK 请求成功(这是对HTTP请求成功的标准应答。)
201 Created 请求被建立完成,同时新的资源被建立。
202 Accepted 供处理的请求已被接受,可是处理未完成。
203 Non-Authoritative Information 请求已经被成功处理,可是一些应答头可能不正确,由于使用的是其余文档的拷贝。
204 No Content 请求已经被成功处理,可是没有返回新文档。浏览器应该继续显示原来的文档。若是用户按期地刷新页面,而Servlet能够肯定用户文档足够新,这个状态代码是颇有用的。
205 Reset Content 请求已经被成功处理,可是没有返回新文档。但浏览器应该重置它所显示的内容。用来强制浏览器清除表单输入内容。
206 Partial Content 客户发送了一个带有Range头的GET请求,服务器完成了它。
3xx: 重定向
消息: 描述:
300 Multiple Choices 多重选择。连接列表。用户能够选择某连接到达目的地。最多容许五个地址。
301 Moved Permanently 所请求的页面已经转移至新的 URL 。
302 Found 所请求的页面已经临时转移至新的 URL 。
303 See Other 所请求的页面可在别的 URL 下被找到。
304 Not Modified 未按预期修改文档。客户端有缓冲的文档并发出了一个条件性的请求(通常是提供If-Modified-Since头表示客户只想比指定日期更新的文档)。服务器告诉客户,原来缓冲的文档还能够继续使用。
305 Use Proxy 客户请求的文档应该经过Location头所指明的代理服务器提取。
306 Switch Proxy 目前已再也不使用,可是代码依然被保留。
307 Temporary Redirect 被请求的页面已经临时移至新的 URL 。
308 Resume Incomplete 用于 PUT 或者 POST 请求恢复失败时的恢复请求建议。
4xx: 客户端错误
消息: 描述:
400 Bad Request 由于语法错误,服务器未能理解请求。
401 Unauthorized 合法请求,但对被请求页面的访问被禁止。由于被请求的页面须要身份验证,客户端没有提供或者身份验证失败。
402 Payment Required 此代码尚没法使用。
403 Forbidden 合法请求,但对被请求页面的访问被禁止。
404 Not Found 服务器没法找到被请求的页面。
405 Method Not Allowed 请求中指定的方法不被容许。
406 Not Acceptable 服务器生成的响应没法被客户端所接受。
407 Proxy Authentication Required 用户必须首先使用代理服务器进行验证,这样请求才会被处理。
408 Request Timeout 请求超出了服务器的等待时间。
409 Conflict 因为冲突,请求没法被完成。
410 Gone 被请求的页面不可用。
411 Length Required “Content-Length” 未被定义。若是无此内容,服务器不会接受请求。
412 Precondition Failed 请求中的前提条件被服务器评估为失败。
413 Request Entity Too Large 因为所请求的实体太大,服务器不会接受请求。
414 Request-URI Too Long 因为 URL 太长,服务器不会接受请求。当 POST 请求被转换为带有很长的查询信息的 GET 请求时,就会发生这种状况。
415 Unsupported Media Type 因为媒介类型不被支持,服务器不会接受请求。
416 Requested Range Not Satisfiable 客户端请求部分文档,可是服务器不能提供被请求的部分。
417 Expectation Failed 服务器不能知足客户在请求中指定的请求头。
5xx: 服务器错误
消息: 描述:
500 Internal Server Error 请求未完成。服务器遇到不可预知的状况。
501 Not Implemented 请求未完成。服务器不支持所请求的功能,或者服务器没法完成请求。
502 Bad Gateway 请求未完成。服务器充当网关或者代理的角色时,从上游服务器收到一个无效的响应。
503 Service Unavailable 服务器当前不可用(过载或者当机)。
504 Gateway Timeout 网关超时。服务器充当网关或者代理的角色时,未能从上游服务器收到一个及时的响应。
505 HTTP Version Not Supported 服务器不支持请求中指明的HTTP协议版本。
511 Network Authentication Required 用户须要提供身份验证来获取网络访问入口。
软能力
自我问答总结
https://blog.csdn.net/u010697681/article/details/79414112#面向对象特征
1.JDK,JRE,JVM(掌握)
(1)JVM
保证Java语言跨平台。针对不一样的操心系统提供不一样的JVM。
问题:java语言是跨平台的吗?JVM是跨平台的吗?
(2)JRE
java程序的运行环境。包括JVM和核心类库
(3)JDK
java开发环境。包括JRE和开发工具(javac,java)
(4)一个Java程序的开发流程
A:编写Java源程序
B:经过javac命令编译java程序,生成字节码文件
C:经过java命令运行字节码文件
2.主从数据库切换
https://blog.csdn.net/u012881904/article/details/77449710
通常使用多个dataSource,而后建立多个SessionFactory,入侵明显,修改多,session处理比较麻烦。
合适的方案使用AbstractRoutingDataSource实现类经过AOP或者手动处理实现动态使用咱们的数据源,入侵低。determineTargetDataSource –
determineCurrentLookupKey
Why:若是主库故障,能够切换从库
Why:针对与mysql,怎么保证主从同步,怎么通知代码切换
Bean获取它所在的Spring容器,可让该Bean实现ApplicationContextAware接口,Bean获取它所在的Spring容器,能够经过这个上下文环境对象获得Spring容器中的Bean。
Why:针对于非web项目的spring
StringBuffer是线程安全的,每次操做字符串,String会生成一个新的对象,而StringBuffer不会;StringBuilder是非线程安全的
String:字符串常量 每当用String操做字符串时,其实是在不断的建立新的对象,而原来的对象就会变为垃圾被GC回收掉,
StringBuffer:字符串变量 是可改变的对象,每当咱们用它们对字符串作操做时,其实是在一个对象上操做的
StringBuilder:字符串变量 是可改变的对象,每当咱们用它们对字符串作操做时,其实是在一个对象上操做的
String 字符串常量 线程安全 操做少许数据
StringBuffer 字符串变量 线程安全 操做大量数据 速度慢 多线程适合用
StringBuilder 字符串变量 线程不安全 操做大量数据 速度快 单线程适合用
http://www.javashuo.com/article/p-zcsymtvw-ds.html
String str = new String(“xyz”);建立了几个对象。
若是String常量池中,已经建立了"xyz",则不会继续建立,此时只建立了一个对象new String(“xyz”);
若是String常量池中没有建立"xyz",则会建立两个对象,一个对象的值是"xyz",一个对象是new String(“xyz”);
6.关系型数据库和非关系型数据库种类和关系
数据库
类型 特性 优势 缺点
关系型数据库
SQLite、Oracle、mysql 一、关系型数据库,是指采用了关系模型来组织
数据的数据库;
二、关系型数据库的最大特色就是事务的一致性;
三、简单来讲,关系模型指的就是二维表格模型,
而一个关系型数据库就是由二维表及其之间的联系所组成的一个数据组织。 一、容易理解:二维表结构是很是贴近逻辑世界一个概念,关系模型相对网状、层次等其余模型来讲更容易理解;
二、使用方便:通用的SQL语言使得操做关系型数据库很是方便;
三、易于维护:丰富的完整性(实体完整性、参照完整性和用户定义的完整性)大大减低了数据冗余和数据不一致的几率;
四、支持SQL,可用于复杂的查询。 一、为了维护一致性所付出的巨大代价就是其读写性能比较差;
二、固定的表结构;
三、高并发读写需求;
四、海量数据的高效率读写;
非关系型数据库
MongoDb、redis、HBase 一、使用键值对存储数据;
二、分布式;
三、通常不支持ACID特性;
四、非关系型数据库严格上不是一种数据库,应该是一种数据结构化存储方法的集合。 一、无需通过sql层的解析,读写性能很高;
二、基于键值对,数据没有耦合性,容易扩展;
三、存储数据的格式:nosql的存储格式是key,value形式、文档形式、图片形式等等,文档形式、图片形式等等,而关系型数据库则只支持基础类型。 一、不提供sql支持,学习和使用成本较高;
二、无事务处理,附加功能bi和报表等支持也很差;
Vector ,ArrayList 和LinkedList的区别?
• 一、Vector、ArrayList都是以相似数组的形式存储在内存中,LinkedList则以链表的形式进行存储。
• 二、Vector线程同步,ArrayList、LinkedList线程不一样步。
• 三、LinkedList适合指定位置插入、删除操做,不适合查找;ArrayList、Vector适合查找,不适合指定位置的插入、删除操做。
• 四、ArrayList在元素填满容器时会自动扩充容器大小的50%,而Vector则是100%,所以ArrayList更节省。
8.注解
登录、权限拦截、日志处理,以及各类Java框架,如Spring,Hibernate,JUnit 提到注解就不能不说反射,Java自定义注解是经过运行时靠反射获取注解。实际开发中,例如咱们要获取某个方法的调用日志,能够经过AOP(动态代理机制)给方法添加切面,经过反射来获取方法包含的注解,若是包含日志注解,就进行日志记录。
9.反射
反射就是把java类中的各类成分映射成一个个的Java对象
能够获取类的相关信息,能够进行设置,能够代理
spring 的 ioc/di 也是反射…
javaBean和jsp之间调用也是反射…
struts的 FormBean 和页面之间…也是经过反射调用…
JDBC 的 classForName()也是反射…
hibernate的 find(Class clazz) 也是反射…
10.加载器
11.ajax
运用XMLHttpRequest或新的Fetch API与网页服务器进行异步资料交换;
12.final, finally, finalize 的区别
17.使用Spring框架的好处是什么?
轻量:Spring 是轻量的,基本的版本大约2MB。
控制反转:Spring经过控制反转实现了松散耦合,对象们给出它们的依赖,而不是建立或查找依赖的对象们。
面向切面的编程(AOP):Spring支持面向切面的编程,而且把应用业务逻辑和系统服务分开。
容器:Spring 包含并管理应用中对象的生命周期和配置。
MVC框架:Spring的WEB框架是个精心设计的框架,是Web框架的一个很好的替代品。
事务管理:Spring 提供一个持续的事务管理接口,能够扩展到上至本地事务下至全局事务(JTA)。
异常处理:Spring 提供方便的API把具体技术相关的异常(好比由JDBC,Hibernate or JDO抛出的)转化为一致的unchecked 异常。
~8等于多少?8>>>2等于多少?
第一个答案是-9,第二个答案是2,无符号右移高位补0 。
子类可否重写父类的静态方法
不能,类对象,从属于对应的类。
什么是线程?
线程是操做系统可以进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运做单位。程序员能够经过它进行多处理器编程,你能够使用多线程对运算密集型任务提速。好比,若是一个线程完成一个任务要100毫秒,那么用十个线程完成改任务只需10毫秒。
线程和进程有什么区别?
线程是进程的子集,一个进程能够有不少线程,每条线程并行执行不一样的任务。不一样的进程使用不一样的内存空间,而全部的线程共享一片相同的内存空间。每一个线程都拥有单独的栈内存用来存储本地数据。
如何在Java中实现线程?
两种方式:java.lang.Thread 类的实例就是一个线程可是它须要调用java.lang.Runnable接口来执行,因为线程类自己就是调用的Runnable接口因此你能够继承java.lang.Thread 类或者直接调用Runnable接口来重写run()方法实现线程。
Java 关键字volatile 与 synchronized 做用与区别?
1,volatile
它所修饰的变量不保留拷贝,直接访问主内存中的。
2,synchronized
当它用来修饰一个方法或者一个代码块的时候,可以保证在同一时刻最多只有一个线程执行该段代码。
24.不一样的线程生命周期?
当咱们在Java程序中新建一个线程时,它的状态是New。当咱们调用线程的start()方法时,状态被改变为Runnable。线程调度器会为Runnable线程池中的线程分配CPU时间而且讲它们的状态改变为Running。其余的线程状态还有Waiting,Blocked 和Dead。
26.是死锁(Deadlock)?如何分析和避免死锁?
死锁是指两个以上的线程永远阻塞的状况,这种状况产生至少须要两个以上的线程和两个以上的资源。
分析死锁,咱们须要查看Java应用程序的线程转储。咱们须要找出那些状态为BLOCKED的线程和他们等待的资源。每一个资源都有一个惟一的id,用这个id咱们能够找出哪些线程已经拥有了它的对象锁。
避免嵌套锁,只在须要的地方使用锁和避免无限期等待是避免死锁的一般办法。
27.么是线程安全?Vector是一个线程安全类吗?
所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。若是每次运行结果和单线程运行的结果是同样的,并且其余的变量的值也和预期的是同样的,就是线程安全的。一个线程安全的计数器类的同一个实例对象在被多个线程使用的状况下也不会出现计算失误。很显然你能够将集合类分红两组,线程安全和非线程安全的。Vector 是用同步方法来实现线程安全的, 而和它类似的ArrayList不是线程安全的。
28.Java中如何中止一个线程?
Java提供了很丰富的API但没有为中止线程提供API。JDK 1.0原本有一些像stop(), suspend() 和 resume()的控制方法可是因为潜在的死锁威胁所以在后续的JDK版本中他们被弃用了,以后Java API的设计者就没有提供一个兼容且线程安全的方法来中止一个线程。当run() 或者 call() 方法执行完的时候线程会自动结束,若是要手动结束一个线程,你能够用volatile 布尔变量来退出run()方法的循环或者是取消任务来中断线程
29.什么是ThreadLocal?
ThreadLocal用于建立线程的本地变量,咱们知道一个对象的全部线程会共享它的全局变量,因此这些变量不是线程安全的,咱们能够使用同步技术。可是当咱们不想使用同步的时候,咱们能够选择ThreadLocal变量。
每一个线程都会拥有他们本身的Thread变量,它们能够使用get()\set()方法去获取他们的默认值或者在线程内部改变他们的值。ThreadLocal实例一般是但愿它们同线程状态关联起来是private static属性。
30.Sleep()、suspend()和wait()之间有什么区别?
Thread.sleep()使当前线程在指定的时间处于“非运行”(Not Runnable)状态。线程一直持有对象的监视器。好比一个线程当前在一个同步块或同步方法中,其它线程不能进入该块或方法中。若是另外一线程调用了interrupt()方法,它将唤醒那个“睡眠的”线程。
注意:sleep()是一个静态方法。这意味着只对当前线程有效,一个常见的错误是调用t.sleep(),(这里的t是一个不一样于当前线程的线程)。即使是执行t.sleep(),也是当前线程进入睡眠,而不是t线程。t.suspend()是过期的方法,使用suspend()致使线程进入停滞状态,该线程会一直持有对象的监视器,suspend()容易引发死锁问题。
object.wait()使当前线程出于“不可运行”状态,和sleep()不一样的是wait是object的方法而不是thread。调用object.wait()时,线程先要获取这个对象的对象锁,当前线程必须在锁对象保持同步,把当前线程添加到等待队列中,随后另外一线程能够同步同一个对象锁来调用object.notify(),这样将唤醒原来等待中的线程,而后释放该锁。基本上wait()/notify()与sleep()/interrupt()相似,只是前者须要获取对象锁。
31.什么是线程饿死,什么是活锁?
当全部线程阻塞,或者因为须要的资源无效而不能处理,不存在非阻塞线程使资源可用。JavaAPI中线程活锁可能发生在如下情形:
1,当全部线程在程序中执行Object.wait(0),参数为0的wait方法。程序将发生活锁直到在相应的对象上有线程调用Object.notify()或者Object.notifyAll()。
2,当全部线程卡在无限循环中。
32.什么是Java Timer类?如何建立一个有特定时间间隔的任务?
java.util.Timer是一个工具类,能够用于安排一个线程在将来的某个特定时间执行。Timer类能够用安排一次性任务或者周期任务。
java.util.TimerTask是一个实现了Runnable接口的抽象类,咱们须要去继承这个类来建立咱们本身的定时任务并使用Timer去安排它的执行。
33.Java中的同步集合与并发集合有什么区别?
同步集合与并发集合都为多线程和并发提供了合适的线程安全的集合,不过并发集合的可扩展性更高。
在Java1.5以前程序员们只有同步集合来用且在多线程并发的时候会致使争用,阻碍了系统的扩展性。
Java5介绍了并发集合像ConcurrentHashMap,不只提供线程安全还用锁分离和内部分区等现代技术提升了可扩展性。
34.同步方法和同步块,哪一个是更好的选择?
同步块是更好的选择,由于它不会锁住整个对象(固然你也可让它锁住整个对象)。同步方法会锁住整个对象,哪怕这个类中有多个不相关联的同步块,这一般会致使他们中止执行并须要等待得到这个对象上的锁。
35.么是线程池? 为何要使用它?
建立线程要花费昂贵的资源和时间,若是任务来了才建立线程那么响应时间会变长,并且一个进程能建立的线程数有限。
为了不这些问题,在程序启动的时候就建立若干线程来响应处理,它们被称为线程池,里面的线程叫工做线程。
从JDK1.5开始,Java API提供了Executor框架让你能够建立不一样的线程池。好比单线程池,每次处理一个任务;数目固定的线程池或者是缓存线程池(一个适合不少生存期短的任务的程序的可扩展线程池)。
36.java中invokeAndWait 和 invokeLater有什么区别?
这两个方法是Swing API 提供给Java开发者用来从当前线程而不是事件派发线程更新GUI组件用的。InvokeAndWait()同步更新GUI组件,好比一个进度条,一旦进度更新了,进度条也要作出相应改变。若是进度被多个线程跟踪,那么就调用invokeAndWait()方法请求事件派发线程对组件进行相应更新。而invokeLater()方法是异步调用更新组件的。
多线程中的忙循环是什么?
忙循环就是程序员用循环让一个线程等待,不像传统方法wait(), sleep() 或 yield() 它们都放弃了CPU控制,而忙循环不会放弃CPU,它就是在运行一个空循环。这么作的目的是为了保留CPU缓存。
在多核系统中,一个等待线程醒来的时候可能会在另外一个内核运行,这样会重建缓存。为了不重建缓存和减小等待重建的时间就能够使用它了。
Array不能够用泛型?
是的,list的能够,推荐用list,List能够提供编译期的类型安全保证,而Array却不能。
int num=Integer.valueOf(“12”);
int num2=Integer.parseInt(“12”);
double num3=Double.valueOf(“12.2”);
double num4=Double.parseDouble(“12.2”);
//其余的相似。经过基本数据类型的包装来的valueOf和parseXX来实现String转为XX
String a=String.valueOf(“1234”);//这里括号中几乎能够是任何类型
String b=String.valueOf(true);
String c=new Integer(12).toString();//经过包装类的toString()也能够
String d=new Double(2.3).toString();
AJAX有哪些有点和缺点?
优势:
一、最大的一点是页面无刷新,用户的体验很是好。
二、使用异步方式与服务器通讯,具备更加迅速的响应能力。
三、能够把之前一些服务器负担的工做转嫁到客户端,利用客户端闲置的能力来处理,减轻服务器和带宽的负担,节约空间和宽带租用成本。而且减轻服务器的负担,ajax的原则是“按需取数据”,能够最大程度的减小冗余请求,和响应对服务器形成的负担。
四、基于标准化的并被普遍支持的技术,不须要下载插件或者小程序。
缺点:
一、ajax不支持浏览器back按钮。
二、安全问题 AJAX暴露了与服务器交互的细节。
三、对搜索引擎的支持比较弱。
四、破坏了程序的异常机制。
五、不容易调试。
40.集合解析
List 和 Set 区别
List,Set都是继承自Collection接口
List特色:元素有放入顺序,元素可重复
Set特色:元素无放入顺序,元素不可重复,重复元素会覆盖掉
(注意:元素虽然无放入顺序,可是元素在set中的位置是有该元素的HashCode决定的,其位置实际上是固定的,加入Set 的Object必须定义equals()方法 ,另外list支持for循环,也就是经过下标来遍历,也能够用迭代器,可是set只能用迭代,由于他无序,没法用下标来取得想要的值。)
Set和List对比:
Set:检索元素效率低下,删除和插入效率高,插入和删除不会引发元素位置改变。
List:和数组相似,List能够动态增加,查找元素效率高,插入删除元素效率低,由于会引发其余元素位置改变。
List 和 Map 区别
List是对象集合,容许对象重复。
Map是键值对的集合,不容许key重复。
Arraylist 与 LinkedList 区别
Arraylist:
优势:ArrayList是实现了基于动态数组的数据结构,由于地址连续,一旦数据存储好了,查询操做效率会比较高(在内存里是连着放的)。
缺点:由于地址连续, ArrayList要移动数据,因此插入和删除操做效率比较低。
LinkedList:
优势:LinkedList基于链表的数据结构,地址是任意的,因此在开辟内存空间的时候不须要等一个连续的地址,对于新增和删除操做add和remove,LinedList比较占优点。LinkedList 适用于要头尾操做或插入指定位置的场景
缺点:由于LinkedList要移动指针,因此查询操做性能比较低。
适用场景分析:
当须要对数据进行对此访问的状况下选用ArrayList,当须要对数据进行屡次增长删除修改时采用LinkedList。
ArrayList 与 Vector 区别
public ArrayList(int initialCapacity)//构造一个具备指定初始容量的空列表。
public ArrayList()//构造一个初始容量为10的空列表。
public ArrayList(Collection<? extends E> c)//构造一个包含指定 collection 的元素的列表
Vector有四个构造方法:
public Vector()//使用指定的初始容量和等于零的容量增量构造一个空向量。
public Vector(int initialCapacity)//构造一个空向量,使其内部数据数组的大小,其标准容量增量为零。
public Vector(Collection<? extends E> c)//构造一个包含指定 collection 中的元素的向量
public Vector(int initialCapacity,int capacityIncrement)//使用指定的初始容量和容量增量构造一个空的向量
ArrayList和Vector都是用数组实现的,主要有这么三个区别:
Vector是多线程安全的,线程安全就是说多线程访问同一代码,不会产生不肯定的结果。而ArrayList不是,这个能够从源码中看出,Vector类中的方法不少有synchronized进行修饰,这样就致使了Vector在效率上没法与ArrayList相比;
两个都是采用的线性连续空间存储元素,可是当空间不足的时候,两个类的增长方式是不一样。
Vector能够设置增加因子,而ArrayList不能够。
Vector是一种老的动态数组,是线程同步的,效率很低,通常不同意使用。
适用场景分析:
Vector是线程同步的,因此它也是线程安全的,而ArrayList是线程异步的,是不安全的。若是不考虑到线程的安全因素,通常用ArrayList效率比较高。
若是集合中的元素的数目大于目前集合数组的长度时,在集合中使用数据量比较大的数据,用Vector有必定的优点。
HashMap 和 Hashtable 的区别
1.hashMap去掉了HashTable 的contains方法,可是加上了containsValue()和containsKey()方法。
2.hashTable同步的,而HashMap是非同步的,效率上逼hashTable要高。
3.hashMap容许空键值,而hashTable不容许。
注意:
TreeMap:非线程安全基于红黑树实现。TreeMap没有调优选项,由于该树总处于平衡状态。
Treemap:适用于按天然顺序或自定义顺序遍历键(key)。
参考:http://blog.csdn.net/qq_22118507/article/details/51576319
HashSet 和 HashMap 区别
set是线性结构,set中的值不能重复,hashset是set的hash实现,hashset中值不能重复是用hashmap的key来实现的。
map是键值对映射,能够空键空值。HashMap是Map接口的hash实现,key的惟一性是经过key值hash值的惟一来肯定,value值是则是链表结构。
他们的共同点都是hash算法实现的惟一性,他们都不能持有基本类型,只能持有对象
HashMap 和 ConcurrentHashMap 的区别
ConcurrentHashMap是线程安全的HashMap的实现。
(1)ConcurrentHashMap对整个桶数组进行了分割分段(Segment),而后在每个分段上都用lock锁进行保护,相对于HashTable的syn关键字锁的粒度更精细了一些,并发性能更好,而HashMap没有锁机制,不是线程安全的。
(2)HashMap的键值对容许有null,可是ConCurrentHashMap都不容许。
ConcurrentHashMap 的工做原理及代码实现
HashTable里使用的是synchronized关键字,这实际上是对对象加锁,锁住的都是对象总体,当Hashtable的大小增长到必定的时候,性能会急剧降低,由于迭代时须要被锁定很长的时间。
ConcurrentHashMap算是对上述问题的优化,其构造函数以下,默认传入的是16,0.75,16。
ConcurrentHashMap引入了分割(Segment),上面代码中的最后一行其实就能够理解为把一个大的Map拆分红N个小的HashTable,在put方法中,会根据hash(paramK.hashCode())来决定具体存放进哪一个Segment,若是查看Segment的put操做,咱们会发现内部使用的同步机制是基于lock操做的,这样就能够对Map的一部分(Segment)进行上锁,这样影响的只是将要放入同一个Segment的元素的put操做,保证同步的时候,锁住的不是整个Map(HashTable就是这么作的),相对于HashTable提升了多线程环境下的性能,所以HashTable已经被淘汰了。
41.线程解析
建立线程的方式及实现
Java中建立线程主要有三种方式
1.继承Thread类建立线程类
2.经过Runnable接口建立线程类
3.经过Callable和Future建立线程
(1)建立Callable接口的实现类,并实现call()方法,该call()方法将做为线程执行体,而且有返回值。
(2)建立Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
(3)使用FutureTask对象做为Thread对象的target建立并启动新线程。
(4)调用FutureTask对象的get()方法来得到子线程执行结束后的返回值
package com.thread;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class CallableThreadTest implements Callable
{
public static void main(String[] args)
{
CallableThreadTest ctt = new CallableThreadTest();
FutureTask ft = new FutureTask<>(ctt);
for(int i = 0;i < 100;i++)
{
System.out.println(Thread.currentThread().getName()+" 的循环变量i的值"+i);
if(i==20)
{
new Thread(ft,“有返回值的线程”).start();
}
}
try
{
System.out.println(“子线程的返回值:”+ft.get());
} catch (InterruptedException e)
{
e.printStackTrace();
} catch (ExecutionException e)
{
e.printStackTrace();
}
}
@Override
public Integer call() throws Exception
{
int i = 0;
for(;i<100;i++)
{
System.out.println(Thread.currentThread().getName()+" "+i);
}
return i;
}
}
建立线程的三种方式的对比
采用实现Runnable、Callable接口的方式创见多线程时,优点是:
线程类只是实现了Runnable接口或Callable接口,还能够继承其余类。
在这种方式下,多个线程能够共享同一个target对象,因此很是适合多个相同线程来处理同一份资源的状况,从而能够将CPU、代码和数据分开,造成清晰的模型,较好地体现了面向对象的思想。
劣势是:
编程稍微复杂,若是要访问当前线程,则必须使用Thread.currentThread()方法。
使用继承Thread类的方式建立多线程时优点是:
编写简单,若是须要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this便可得到当前线程。
劣势是:
线程类已经继承了Thread类,因此不能再继承其余父类。
下面对上面说的三个辅助类进行一个总结:
1)CountDownLatch和CyclicBarrier都可以实现线程之间的等待,只不过它们侧重点不一样:
CountDownLatch通常用于某个线程A等待若干个其余线程执行完任务以后,它才执行;
而CyclicBarrier通常用于一组线程互相等待至某个状态,而后这一组线程再同时执行;
另外,CountDownLatch是不可以重用的,而CyclicBarrier是能够重用的。
2)Semaphore其实和锁有点相似,它通常用于控制对某组资源的访问权限。
说说 CountDownLatch 与 CyclicBarrier 区别
CountDownLatch CyclicBarrier
减计数方式 加计数方式
计算为0时释放全部等待的线程 计数达到指定值时释放全部等待线程
计数为0时,没法重置 计数达到指定值时,计数置为0从新开始
调用countDown()方法计数减一,调用await()方法只进行阻塞,对计数没任何影响 调用await()方法计数加1,若加1后的值不等于构造方法的值,则线程阻塞
不可重复利用 可重复利用
java.util.concurrent.Exchanger应用范例与原理浅析
• 此类提供对外的操做是同步的;
• 用于成对出现的线程之间交换数据;
• 能够视做双向的同步队列;
• 可应用于基因算法、流水线设计等场景。
ThreadLocal 原理分析
ThreadLocal,不少地方叫作线程本地变量,也有些地方叫作线程本地存储,其实意思差很少。可能不少朋友都知道ThreadLocal为变量在每一个线程中都建立了一个副本,那么每一个线程能够访问本身内部的副本变量。
在每一个线程Thread内部有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,这个threadLocals就是用来存储实际的变量副本的,键值为当前ThreadLocal变量,value为变量副本(即T类型的变量)。
初始时,在Thread里面,threadLocals为空,当经过ThreadLocal变量调用get()方法或者set()方法,就会对Thread类中的threadLocals进行初始化,而且以当前ThreadLocal变量为键值,以ThreadLocal要保存的副本变量为value,存到threadLocals。
而后在当前线程里面,若是要使用副本变量,就能够经过get方法在threadLocals里面查找。
最多见的ThreadLocal使用场景为 用来解决 数据库链接、Session管理等。
讲讲线程池的实现原理
线程池的几种方式
newFixedThreadPool(int nThreads)
建立一个固定长度的线程池,每当提交一个任务就建立一个线程,直到达到线程池的最大数量,这时线程规模将再也不变化,当线程发生未预期的错误而结束时,线程池会补充一个新的线程
newCachedThreadPool()
建立一个可缓存的线程池,若是线程池的规模超过了处理需求,将自动回收空闲线程,而当需求增长时,则能够自动添加新线程,线程池的规模不存在任何限制
newSingleThreadExecutor()
这是一个单线程的Executor,它建立单个工做线程来执行任务,若是这个线程异常结束,会建立一个新的来替代它;它的特色是能确保依照任务在队列中的顺序来串行执行
newScheduledThreadPool(int corePoolSize)
建立了一个固定长度的线程池,并且以延迟或定时的方式来执行任务,相似于Timer。
线程的生命周期
新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)5种状态
(1)生命周期的五种状态
新建(new Thread)
当建立Thread类的一个实例(对象)时,此线程进入新建状态(未被启动)。
例如:Thread t1=new Thread();
就绪(runnable)
线程已经被启动,正在等待被分配给CPU时间片,也就是说此时线程正在就绪队列中排队等候获得CPU资源。例如:t1.start();
运行(running)
线程得到CPU资源正在执行任务(run()方法),此时除非此线程自动放弃CPU资源或者有优先级更高的线程进入,线程将一直运行到结束。
死亡(dead)
当线程执行完毕或被其它线程杀死,线程就进入死亡状态,这时线程不可能再进入就绪状态等待执行。
天然终止:正常运行run()方法后终止
异常终止:调用stop()方法让一个线程终止运行
堵塞(blocked)
因为某种缘由致使正在运行的线程让出CPU并暂停本身的执行,即进入堵塞状态。
正在睡眠:用sleep(long t) 方法可以使线程进入睡眠方式。一个睡眠着的线程在指定的时间过去可进入就绪状态。
正在等待:调用wait()方法。(调用motify()方法回到就绪状态)
被另外一个线程所阻塞:调用suspend()方法。(调用resume()方法恢复)
43. 锁机制
说说线程安全问题
线程安全是指要控制多个线程对某个资源的有序访问或修改,而在这些线程之间没有产生冲突。
在Java里,线程安全通常体如今两个方面:
一、多个thread对同一个java实例的访问(read和modify)不会相互干扰,它主要体如今关键字synchronized。如ArrayList和Vector,HashMap和Hashtable(后者每一个方法前都有synchronized关键字)。若是你在interator一个List对象时,其它线程remove一个element,问题就出现了。
二、每一个线程都有本身的字段,而不会在多个线程之间共享。它主要体如今java.lang.ThreadLocal类,而没有Java关键字支持,如像static、transient那样。
Volatile
在多线程并发编程中synchronized和Volatile都扮演着重要的角色,Volatile是轻量级的synchronized,它在多处理器开发中保证了共享变量的“可见性”。可见性的意思是当一个线程修改一个共享变量时,另一个线程能读到这个修改的值。
在多个线程之间可以被共享的变量被称为共享变量。共享变量包括全部的实例变量,静态变量和数组元素。他们都被存放在堆内存中,Volatile只做用于共享变量。
悲观锁 乐观锁
乐观锁 悲观锁
是一种思想。能够用在不少方面。
好比数据库方面。
悲观锁就是for update(锁定查询的行)
乐观锁就是 version字段(比较跟上一次的版本号,若是同样则更新,若是失败则要重复读-比较-写的操做。)
JDK方面:
悲观锁就是sync
乐观锁就是原子类(内部使用CAS实现)
本质来讲,就是悲观锁认为总会有人抢个人。
乐观锁就认为,基本没人抢。
乐观锁(Optimistic Lock):
每次获取数据的时候,都不会担忧数据被修改,因此每次获取数据的时候都不会进行加锁,可是在更新数据的时候须要判断该数据是否被别人修改过。若是数据被其余线程修改,则不进行数据更新,若是数据没有被其余线程修改,则进行数据更新。因为数据没有进行加锁,期间该数据能够被其余线程进行读写操做。
乐观锁:比较适合读取操做比较频繁的场景,若是出现大量的写入操做,数据发生冲突的可能性就会增大,为了保证数据的一致性,应用层须要不断的从新获取数据,这样会增长大量的查询操做,下降了系统的吞吐量。
数据存储分析
mysql索引使用技巧及注意事项
INSERT 与 UPDATE 语句在拥有索引的表中执行会花费更多的时间,而SELECT 语句却会执行得更快。这是由于,在进行插入或更新时,数据库也须要插入或更新索引值。
索引的类型:
UNIQUE(惟一索引):不能够出现相同的值,能够有NULL值
INDEX(普通索引):容许出现相同的索引内容
PROMARY KEY(主键索引):不容许出现相同的值
fulltext index(全文索引):能够针对值中的某个单词,但效率确实不敢恭维
组合索引:实质上是将多个字段建到一个索引里,列值的组合必须惟一
(1)使用ALTER TABLE语句建立索性
ALTER TABLE 表名 ADD 索引类型 (unique,primary key,fulltext,index)[索引名](字段名)
(2)使用CREATE INDEX语句对表增长索引
CREATE INDEX index_name ON table_name(username(length));
//create只能添加这两种索引;
CREATE INDEX index_name ON table_name (column_list)
CREATE UNIQUE INDEX index_name ON table_name (column_list)
(3)删除索引
删除索引能够使用ALTER TABLE或DROP INDEX语句来实现。DROP INDEX能够在ALTER TABLE内部做为一条语句处理,其格式以下:
drop index index_name on table_name ;
alter table table_name drop index index_name ;
alter table table_name drop primary key ;
(4) 组合索引与前缀索引
create table USER_DEMO
(
ID int not null auto_increment comment ‘主键’,
LOGIN_NAME varchar(100) not null comment ‘登陆名’,
PASSWORD varchar(100) not null comment ‘密码’,
CITY varchar(30) not null comment ‘城市’,
AGE int not null comment ‘年龄’,
SEX int not null comment ‘性别(0:女 1:男)’,
primary key (ID)
);
ALTER TABLE USER_DEMO ADD INDEX name_city_age (LOGIN_NAME(16),CITY,AGE);
索引的使用及注意事项
Explain select * from user where id=1;
1.索引不会包含有NULL的列
只要列中包含有NULL值,都将不会被包含在索引中,复合索引中只要有一列含有NULL值,那么这一列对于此符合索引就是无效的。
2.使用短索引
对串列进行索引,若是能够就应该指定一个前缀长度。例如,若是有一个char(255)的列,若是在前10个或20个字符内,多数值是惟一的,那么就不要对整个列进行索引。短索引不只能够提升查询速度并且能够节省磁盘空间和I/O操做。
3.索引列排序
mysql查询只使用一个索引,所以若是where子句中已经使用了索引的话,那么order by中的列是不会使用索引的。所以数据库默认排序能够符合要求的状况下不要使用排序操做,尽可能不要包含多个列的排序,若是须要最好给这些列建复合索引。
4.like语句操做
通常状况下不鼓励使用like操做,若是非使用不可,注意正确的使用方式。like ‘%aaa%’不会使用索引,而like ‘aaa%’能够使用索引。
5.不要在列上进行运算
6.不使用NOT IN 、<>、!=操做,但<,<=,=,>,>=,BETWEEN,IN是能够用到索引的
7.索引要创建在常常进行select操做的字段上。
这是由于,若是这些列不多用到,那么有无索引并不能明显改变查询速度。相反,因为增长了索引,反而下降了系统的维护速度和增大了空间需求。
8.索引要创建在值比较惟一的字段上。
9.对于那些定义为text、image和bit数据类型的列不该该增长索引。由于这些列的数据量要么至关大,要么取值不多。
10.在where和join中出现的列须要创建索引。
11.where的查询条件里有不等号(where column != …),mysql将没法使用索引。
12.若是where字句的查询条件里使用了函数(如:where DAY(column)=…),mysql将没法使用索引。
13.在join操做中(须要从多个数据表提取数据时),mysql只有在主键和外键的数据类型相同时才能使用索引,不然及时创建了索引也不会使用。
分表与分库使用场景以及设计方式
对于访问极为频繁且数据量巨大的单表来讲,咱们首先要作的就是减小单表的记录条数,以便减小数据查询所须要的时间,提升数据库的吞吐,这就是所谓的分表!
场景:分表可以解决单表数据量过大带来的查询效率降低的问题,可是,却没法给数据库的并发处理能力带来质的提高。面对高并发的读写访问,当数据库master
服务器没法承载写操做压力时,无论如何扩展slave服务器,此时都没有意义了。
所以,咱们必须换一种思路,对数据库进行拆分,从而提升数据库写入能力,这就是所谓的分库!
MySQL有三种锁的级别:页级、表级、行级。
表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的几率最高,并发度最低。
行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的几率最低,并发度也最高。
页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度通常
存储引擎的 InnoDB 与 MyISAM
1)InnoDB支持事务,MyISAM不支持,这一点是很是之重要。事务是一种高级的处理方式,如在一些列增删改中只要哪一个出错还能够回滚还原,而MyISAM就不能够了。
2)MyISAM适合查询以及插入为主的应用,InnoDB适合频繁修改以及涉及到安全性较高的应用
3)InnoDB支持外键,MyISAM不支持
4)从MySQL5.5.5之后,InnoDB是默认引擎
5)InnoDB不支持FULLTEXT类型的索引
6)InnoDB中不保存表的行数,如select count() from table时,InnoDB须要扫描一遍整个表来计算有多少行,可是MyISAM只要简单的读出保存好的行数便可。注意的是,当count()语句包含where条件时MyISAM也须要扫描整个表
7)对于自增加的字段,InnoDB中必须包含只有该字段的索引,可是在MyISAM表中能够和其余字段一块儿创建联合索引
8)清空整个表时,InnoDB是一行一行的删除,效率很是慢。MyISAM则会重建表
9)InnoDB支持行锁(某些状况下仍是锁整表,如 update table set a=1 where user like ‘%lee%’
索引数据结构设相关的计算机原理
上文说过,二叉树、红黑树等数据结构也能够用来实现索引,可是文件系统及数据库系统广泛采用B-/+Tree做为索引结构,这一节将结合计算机组成原理相关知识讨论B-/+Tree做为索引的理论基础。
当一个数据库表过于庞大,LIMIT offset, length中的offset值过大,则SQL查询语句会很是缓慢,你需增长order by,而且order by字段须要创建索引。
若是使用子查询去优化LIMIT的话,则子查询必须是连续的,某种意义来说,子查询不该该有where条件,where会过滤数据,使数据失去连续性。
若是你查询的记录比较大,而且数据传输量比较大,好比包含了text类型的field,则能够经过创建子查询。
分布式系统惟一ID生成方案汇总
• 关系型数据库 MySQL
MySQL 是一个最流行的关系型数据库,在互联网产品中应用比较普遍。通常状况下,MySQL 数据库是选择的第一方案,基本上有 80% ~ 90% 的场景都是基于 MySQL 数据库的。由于,须要关系型数据库进行管理,此外,业务存在许多事务性的操做,须要保证事务的强一致性。同时,可能还存在一些复杂的 SQL 的查询。值得注意的是,前期尽可能减小表的联合查询,便于后期数据量增大的状况下,作数据库的分库分表。
• 内存数据库 Redis
随着数据量的增加,MySQL 已经知足不了大型互联网类应用的需求。所以,Redis 基于内存存储数据,能够极大的提升查询性能,对产品在架构上很好的补充。例如,为了提升服务端接口的访问速度,尽量将读频率高的热点数据存放在 Redis 中。这个是很是典型的以空间换时间的策略,使用更多的内存换取 CPU 资源,经过增长系统的内存消耗,来加快程序的运行速度。
在某些场景下,能够充分的利用 Redis 的特性,大大提升效率。这些场景包括缓存,会话缓存,时效性,访问频率,计数器,社交列表,记录用户断定信息,交集、并集和差集,热门列表与排行榜,最新动态等。
使用 Redis 作缓存的时候,须要考虑数据不一致与脏读、缓存更新机制、缓存可用性、缓存服务降级、缓存穿透、缓存预热等缓存使用问题。
• 文档数据库 MongoDB
MongoDB 是对传统关系型数据库的补充,它很是适合高伸缩性的场景,它是可扩展性的表结构。基于这点,能够将预期范围内,表结构可能会不断扩展的 MySQL 表结构,经过 MongoDB 来存储,这就能够保证表结构的扩展性。
此外,日志系统数据量特别大,若是用 MongoDB 数据库存储这些数据,利用分片集群支持海量数据,同时使用汇集分析和 MapReduce 的能力,是个很好的选择。
MongoDB 还适合存储大尺寸的数据,GridFS 存储方案就是基于 MongoDB 的分布式文件存储系统。
• 列族数据库 HBase
HBase 适合海量数据的存储与高性能实时查询,它是运行于 HDFS 文件系统之上,而且做为 MapReduce 分布式处理的目标数据库,以支撑离线分析型应用。在数据仓库、数据集市、商业智能等领域发挥了愈来愈多的做用,在数以千计的企业中支撑着大量的大数据分析场景的应用。
• 全文搜索引擎 ElasticSearch
在通常状况下,关系型数据库的模糊查询,都是经过 like 的方式进行查询。其中,like “value%” 能够使用索引,可是对于 like “%value%” 这样的方式,执行全表查询,这在数据量小的表,不存在性能问题,可是对于海量数据,全表扫描是很是可怕的事情。ElasticSearch 做为一个创建在全文搜索引擎 Apache Lucene 基础上的实时的分布式搜索和分析引擎,适用于处理实时搜索应用场景。此外,使用 ElasticSearch 全文搜索引擎,还能够支持多词条查询、匹配度与权重、自动联想、拼写纠错等高级功能。所以,能够使用 ElasticSearch 做为关系型数据库全文搜索的功能补充,将要进行全文搜索的数据缓存一份到 ElasticSearch 上,达处处理复杂的业务与提升查询速度的目的。
ElasticSearch 不只仅适用于搜索场景,还很是适合日志处理与分析的场景。著名的 ELK 日志处理方案,由 ElasticSearch、Logstash 和 Kibana 三个组件组成,包括了日志收集、聚合、多维度查询、可视化显示等。
MongoDB 特性 优点
事务支持 MongoDB 目前只支持单文档事务,须要复琐事务支持的场景暂时不适合
灵活的文档模型 JSON 格式存储最接近真实对象模型,对开发者友好,方便快速开发迭代
高可用复制集 知足数据高可靠、服务高可用的需求,运维简单,故障自动切换
可扩展分片集群 海量数据存储,服务能力水平扩展
高性能 mmapv一、wiredtiger、mongorocks(rocksdb)、in-memory 等多引擎支持知足各类场景需求
强大的索引支持 地理位置索引可用于构建 各类 O2O 应用、文本索引解决搜索的需求、TTL索引解决历史数据自动过时的需求
Gridfs 解决文件存储的需求
aggregation & mapreduce 解决数据分析场景需求,用户能够本身写查询语句或脚本,将请求都分发到 MongoDB 上完成
从目前阿里云 MongoDB 云数据库上的用户看,MongoDB 的应用已经渗透到各个领域,好比游戏、物流、电商、内容管理、社交、物联网、视频直播等,如下是几个实际的应用案例。
• 游戏场景,使用 MongoDB 存储游戏用户信息,用户的装备、积分等直接之内嵌文档的形式存储,方便查询、更新
• 物流场景,使用 MongoDB 存储订单信息,订单状态在运送过程当中会不断更新,以 MongoDB 内嵌数组的形式来存储,一次查询就能将订单全部的变动读取出来。
• 社交场景,使用 MongoDB 存储存储用户信息,以及用户发表的朋友圈信息,经过地理位置索引实现附近的人、地点等功能
• 物联网场景,使用 MongoDB 存储全部接入的智能设备信息,以及设备汇报的日志信息,并对这些信息进行多维度的分析
• 视频直播,使用 MongoDB 存储用户信息、礼物信息等
redis内部数据结构深刻浅出
redis 是 key-value 存储系统,其中 key 类型通常为字符串,而 value 类型则为 redis 对象(redis object),能够绑定各类类型的数据,譬如 string、list 和set,redis.h 中定义了 struct redisObject,它是一个简单优秀的数据结构
Redis 基于内存存储数据,能够极大的提升查询性能,对产品在架构上很好的补充。例如,为了提升服务端接口的访问速度,尽量将读频率高的热点数据存放在 Redis 中。这个是很是典型的以空间换时间的策略,使用更多的内存换取 CPU 资源,经过增长系统的内存消耗,来加快程序的运行速度。
在某些场景下,能够充分的利用 Redis 的特性,大大提升效率。这些场景包括缓存,会话缓存,时效性,访问频率,计数器,社交列表,记录用户断定信息,交集、并集和差集,热门列表与排行榜,最新动态等。
使用 Redis 作缓存的时候,须要考虑数据不一致与脏读、缓存更新机制、缓存可用性、缓存服务降级、缓存穿透、缓存预热等缓存使用问题。
Redis 如何实现持久化
44.使用传统的 Socket 开发挺简单的,我为何要切换到 NIO 进行编程呢?
因为切换到 NIO 编程以后能够为系统带来巨大的可靠性、性能提高,因此,目前采用 NIO 进行通讯已经逐渐成为主流。