admin 管理员组文章数量: 887018
1.Java类加载的过程
类加载的过程包括:加载、验证、准备、解析、初始化,其中验证、准备、解析统称为连接。
加载:通过-个类的全限定名来获取定义此类的二进制字节流,在内存中生成-个代表这个类的java lang. Class对象。
验证:确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
准备:为静态变量分配内存并设置静态变量初始值,这里所说的初始值“通常情况”下是数据类型的零值。
解析:将常量池内的符号引用替换为直接引用。
初始化:到了初始化阶段,填正开始执行类中定义的Java初始化程序代码。主要是静态变量赋值动作和静态语句块(static{}) 中的语句。
双亲委派机制:1、如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行。
2、如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归请求最终将到达顶层的启动类加载器。
3、如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式。
作用:1、保护程序安全,防止核心API被随意篡改。在java.lang包下,开发者自定义的类中的main方法不允许执行,防止恶意代码对程序产生破坏。2、避免类的重复加载。一个类只会被加载一次。
沙箱安全机制:自定义string类,但是在加载自定义String类的时候会率先使用引导类加载器加载,而引导类加载器在加载的过程中会先加载jdk自带的文件(rt. jar包中java\lang\String.class),报错信息说没有main方法,就是因为加载的是rt. jar包中的String类。这样可以保证对java核心源代码的保护,这就是沙箱安全机制。
类的加载是由类加载器完成的,类加载器包括:根加载器(BootStrap)、扩展加载器(Extension)、系统加载器(System)和用户自定 义类加载器(java.lang.ClassLoader 的子类)。
从 Java 2(JDK 1.2)开始,类加载过程采取了双亲委托机制(PDM)。PDM 更好的保证了 Java 平台的安全性,在该机制中,JVM 自带的 Bootstrap 是根加载器,其他的加载器都有且仅有一个父类加载器。类的加载首先请求父类加载器加载,父类加载器无能为力时才由其子类 加载器自行加载。JVM 不会向 Java 程序提供对 Bootstrap 的引用。下面是关于几个类 加载器的说明:
- Bootstrap:一般用本地代码实现,负责加载 JVM 基础核心类库(rt.jar);
- Extension:从 java.ext.dirs 系统属性所指定的目录中加载类库,它的父加载器是 Bootstrap;
- System:又叫应用类加载器,其父类是 Extension。它是应用最广泛的类加载器。它从环境变量 classpath 或者系统属性 java.class.path 所指定的目录中记载类,是用户自定义加载器的默认父加载器。
2、Java中Object类常用方法
3、switch 是否能作用在 byte 上,是否能作用在 long 上,是否能作用在 String 上
在 Java 5 以前,switch(expr)中,expr 只能是 byte、short、char、int。从 Java5 开始,Java 中引入了枚举类型,expr 也可以是 enum 类型,从 Java 7 开始,expr 还可以是字符串(String),但是长整型(long)在目前所有的版本中都是不可以的。
4、重载和重写有什么区别?
重载:发生在同一个类中,方法名相同但参数列表不同,参数列表包括参数类型,参数个数,参数的顺序。但与方法的返回类型和访问修饰符无关,即重载的方法不能通过返回类型来区分。
重写:发生在父类和子类中,方法名、参数列表必须完全相同。而且返回值的级别应该比父类要小,抛出的异常级别也要比父类小,访问修饰符的级别要高于父类。例如父类返回LIst,子类就可以返回ArrayList;父类抛出Exception,子类就可以抛出IOException;父类的方法是private,子类可以是public。记住两小一大即可:子类的返回值类型小于等于父类,子类的异常级别小于等于父类,子类的权限修饰符大于等于父类。
5、构造器能被重写吗?
构造器不能被继承,因此不能被重写。但可以被重载。
6、Java IO 分类
Java BIO: 同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。
Java NIO: 同步非阻塞,服务器实现模式为一个请求一个线程,即当一个连接创建后,不需要对应一个线程,这个连接会被注册到多路复用器上面,所以所有的连接只需要一个线程就可以搞定,当这个线程中的多路复用器进行轮询的时候,发现连接上有请求的话,才开启一个线程进行处理,也就是一个请求一个线程模式。BIO与NIO一个比较重要的不同,是我们使用BIO的时候往往会引入多线程,每个连接一个单独的线程;而NIO则是使用单线程或者只使用少量的多线程,每个连接共用一个线程。
Java AIO(NIO.2): 异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理。
7、浅拷贝
创建一个新对象,然后将当前对象的非静态字段复制到该新对象,如果字段是值类型的, 那么对该字段执行复制;如果该字段是引用类型的话,则复制引用但不复制引用的对象。 因此,原始对象及其副本引用同一个对象。
深拷贝
深拷贝不仅复制对象本身,而且复制对象包含的引用指向的所有对象
8、什么是反射机制?
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
静态编译和动态编译
静态编译:在编译时确定类型,绑定对象 动态编译:运行时确定类型,绑定对象 反射机制优缺点
优点: 运行期类型的判断,动态加载类,提高代码灵活度。 缺点: 性能瓶颈:反射相当于一系列解释操作,通知 JVM 要做的事情,性能比直接的java代码要慢很多。 反射机制的应用场景有哪些?
反射是框架设计的灵魂。
在我们平时的项目开发过程中,基本上很少会直接使用到反射机制,但这不能说明反射机制没有用,实际上有很多设计、开发都与反射机制有关,例如模块化的开发,通过反射去调用对应的字节码;动态代理设计模式也采用了反射机制,还有我们日常使用的 Spring/Hibernate 等框架也大量使用到了反射机制。
举例:①我们在使用JDBC连接数据库时使用Class.forName()通过反射加载数据库的驱动程序;②Spring框架也用到很多反射机制,最经典的就是xml的配置模式。Spring 通过 XML 配置模式装载 Bean 的过程:1) 将程序内所有 XML 或 Properties 配置文件加载入内存中; 2)Java类里面解析xml或properties里面的内容,得到对应实体类的字节码字符串以及相关的属性信息; 3)使用反射机制,根据这个字符串获得某个类的Class实例; 4)动态配置实例的属性
Java获取反射的三种方法
1.通过new对象实现反射机制 2.通过路径实现反射机制 3.通过类名实现反射机制
Map
HashMap: JDK1.8之前HashMap由数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突).JDK1.8以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间 LinkedHashMap:LinkedHashMap 继承自 HashMap,所以它的底层仍然是基于拉链式散列结构即由数组和链表或红黑树组成。另外,LinkedHashMap 在上面结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。同时通过对链表进行相应的操作,实现了访问顺序相关逻辑。 HashTable: 数组+链表组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的 TreeMap: 红黑树(自平衡的排序二叉树)
HashMap为什么初始大小为16以及2倍扩容:两个方面
1、hash运算的过程其实就是对目标元素的Key进行hashcode,再对Map的容量进行取模,而JDK 的工程师为了提升取模的效率,使用位运算代替了取模运算,这就要求Map的容量一定得是2的幂。2、降低hash碰撞的几率。而作为默认容量,太大和太小都不合适,所以16就作为一个比较合适的经验值被采用了。为了保证任何情况下Map的容量都是2的幂,HashMap在两个地方都做了限制。首先是,如果用户制定了初始容量,那么HashMap会计算出比该数大的第一个2的幂作为初始容量。另外,在扩容的时候,也是进行成倍的扩容,即4变成8,8变成16。
Java集合的快速失败机制 “fail-fast”?
是java集合的一种错误检测机制,当多个线程对集合进行结构上的改变的操作时,有可能会产生 fail-fast 机制。
例如:假设存在两个线程(线程1、线程2),线程1通过Iterator在遍历集合A中的元素,在某个时候线程2修改了集合A的结构(是结构上面的修改,而不是简单的修改集合元素的内容),那么这个时候程序就会抛出 ConcurrentModificationException 异常,从而产生fail-fast机制。
原因:迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。集合在被遍历期间如果内容发生变化,就会改变modCount的值。每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍历;否则抛出异常,终止遍历。
HashMap 和 ConcurrentHashMap 的区别
ConcurrentHashMap对整个桶数组进行了分割分段(Segment),然后在每一个分段上都用lock锁进行保护,相对于HashTable的synchronized锁的粒度更精细了一些,并发性能更好,而HashMap没有锁机制,不是线程安全的。(JDK1.8之后ConcurrentHashMap启用了一种全新的方式实现,利用CAS算法。) HashMap的键值对允许有null,但是ConCurrentHashMap都不允许。 ConcurrentHashMap 和 Hashtable 的区别?
ConcurrentHashMap 和 Hashtable 的区别主要体现在实现线程安全的方式上不同。
底层数据结构: JDK1.7的 ConcurrentHashMap 底层采用 分段的数组+链表 实现,JDK1.8 采用的数据结构跟HashMap1.8的结构一样,数组+链表/红黑二叉树。Hashtable 和 JDK1.8 之前的 HashMap 的底层数据结构类似都是采用 数组+链表 的形式,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的; 实现线程安全的方式(重要):
① 在JDK1.7的时候,ConcurrentHashMap(分段锁) 对整个桶数组进行了分割分段(Segment),每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。(默认分配16个Segment,比Hashtable效率提高16倍。) 到了 JDK1.8 的时候已经摒弃了Segment的概念,而是直接用 Node 数组+链表+红黑树的数据结构来实现,并发控制使用 synchronized 和 CAS 来操作。(JDK1.6以后 对 synchronized锁做了很多优化) 整个看起来就像是优化过且线程安全的 HashMap,虽然在JDK1.8中还能看到 Segment 的数据结构,但是已经简化了属性,只是为了兼容旧版本;
② Hashtable(同一把锁) :使用 synchronized 来保证线程安全,效率非常低下。当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态,如使用 put 添加元素,另一个线程不能使用 put 添加元素,也不能使用 get,竞争会越来越激烈效率越低。
ConcurrentHashMap 底层具体实现知道吗?实现原理是什么?
JDK1.7
首先将数据分为一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据时,其他段的数据也能被其他线程访问。
在JDK1.7中,ConcurrentHashMap采用Segment + HashEntry的方式进行实现。
一个 ConcurrentHashMap 里包含一个 Segment 数组。Segment 的结构和HashMap类似,是一种数组和链表结构,一个 Segment 包含一个 HashEntry 数组,每个 HashEntry 是一个链表结构的元素,每个 Segment 守护着一个HashEntry数组里的元素,当对 HashEntry 数组的数据进行修改时,必须首先获得对应的 Segment的锁。
该类包含两个静态内部类 HashEntry 和 Segment ;前者用来封装映射表的键值对,后者用来充当锁的角色; Segment 是一种可重入的锁 ReentrantLock,每个 Segment 守护一个HashEntry 数组里得元素,当对 HashEntry 数组的数据进行修改时,必须首先获得对应的 Segment 锁。
JDK1.8
在JDK1.8中,放弃了Segment臃肿的设计,取而代之的是采用Node + CAS + Synchronized来保证并发安全进行实现,synchronized只锁定当前链表或红黑二叉树的首节点,这样只要hash不冲突,就不会产生并发,效率又提升N倍。
并发编程三要素是什么?在 Java 程序中怎么保证多线程的运行安全?
并发编程三要素(线程的安全性问题体现在):
原子性:原子,即一个不可再被分割的颗粒。原子性指的是一个或多个操作要么全部执行成功要么全部执行失败。
可见性:一个线程对共享变量的修改,另一个线程能够立刻看到。(synchronized,volatile)
有序性:程序执行的顺序按照代码的先后顺序执行。(处理器可能会对指令进行重排序)
CopyOnWriteArrayList实现原理
使用volatile修饰内部数组
private transient volatile Object[] array;
看这行代码,使用volatile修饰了内部数组
volatile关键字保证了每次拿到的内部数组都是最新值。因为volatile关键字表示直接去主存中获取值,因此哪怕别的线程刚修改完内部数组,也能保证获取内部数组时是最新的。
2) 加锁
提到并发编程,当然少不了加锁。
final transient ReentrantLock lock = new ReentrantLock();
CopyOnWriteArrayList每创建一个实例,都会同时创建一个ReentrantLock锁。
CopyOnWriteArrayList会在增,删,改操作时添加锁,而不会在读操作时加锁。
3) 使用COW思想操作数组。
COW即是CopyOnWrite的缩写。即每次在写入之前,先获取源数据的拷贝,修改完拷贝后,再保存到源数据
线程和进程区别
简述线程、程序、进程、协程的基本概念。以及他们之间关系是什么?
线程
是操作系统能够调度运算的最小单位。一个进程在其执行的过程中可以产生多个线程。与
进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在
各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。
程序
是含有指令和数据的文件,被存储在磁盘或其他的数据存储设备中,也就是说程序是静态的代码。
进程
是程序的一次执行过程,是操作系统分配资源的最小单位,因此进程是动态的。系统运行一个程序即是一个 进程从创建,运行到消亡的过程。简单来说,一个进程就是一个执行中的程序,它在计算机中一个指令 接着一个指令地执行着,同时,每个进程还占有某些系统资源如 CPU 时间,内存空间,文件,输入输出 设备的使用权等等。换句话说,当程序在执行时,将会被操作系统载入内存中。 线程是进程划分成的更 小的运行单位。线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程 中的线程极有可能会相互影响。从另一角度来说,进程属于操作系统的范畴,主要是同一段时间内,可 以同时执行一个以上的程序,而线程则是在同一程序内几乎同时执行一个以上的程序段。
协程
它是一种用户态的轻量级线程,协程的调度完全由用户控制。它有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁地访问全局变量,所以上下文的切换非常快。协程虽然是微线程,但并不会和某一个线程绑定,它可以在A线程中执行,经过某一个时刻的挂起,等下次调度到恢复执行的时候,很可能会在B线程中执行。
说一下线程之间是如何通信的?
线程之间的通信有两种方式:共享内存和消息传递。 共享内存
在共享内存的并发模型里,线程之间共享程序的公共状态,线程之间通过写-读内存中的公共状态来 隐式进行通信。典型的共享内存通信方式,就是通过共享对象进行通信。
例如上图线程 A 与 线程 B 之间如果要通信的话,那么就必须经历下面两个步骤:
- 线程 A 把本地内存 A 更新过得共享变量刷新到主内存中去。
- 线程 B 到主内存中去读取线程 A 之前更新过的共享变量。
进程间的通信方式?
竞态条件:即两个或多个线程同时对一共享数据进行修改,从而影响程序运行的正确性时,这种就被称为竞态条件(race condition)。
临界区:不仅共享资源会造成竞态条件,事实上共享文件、共享内存也会造成竞态条件、那么该如何避免呢?或许一句话可以概括说明:禁止一个或多个进程在同一时刻对共享资源(包括共享内存、共享文件等)进行读写。换句话说,我们需要一种 互斥(mutual exclusion) 条件,这也就是说,如果一个进程在某种方式下使用共享变量和文件的话,除该进程之外的其他进程就禁止做这种事(访问统一资源)。
一个好的解决方案,应该包含下面四种条件
任何时候两个进程不能同时处于临界区
不应对 CPU 的速度和数量做任何假设
位于临界区外的进程不得阻塞其他进程
不能使任何进程无限等待进入临界区
忙等互斥:当一个进程在对资源进行修改时,其他进程必须进行等待,进程之间要具有互斥性,我们讨论的解决方案其实都是基于忙等互斥提出的。
进程间的通信用专业一点的术语来表示就是 Inter Process Communication,IPC,它主要有下面 7种通信方式
1、消息传递:消息传递是进程间实现通信和同步等待的机制,使用消息传递,进程间的交流不需要共享变量,直接就可以进行通信;消息传递分为发送方和接收方
2、先进先出队列:先进先出队列指的是两个不相关联进程间的通信,两个进程之间可以彼此相互进程通信,这是一种全双工通信方式
3、管道:管道用于两个相关进程之间的通信,这是一种半双工的通信方式,如果需要全双工,需要另外一个管道。
4、直接通信:在这种进程通信的方式中,进程与进程之间只存在一条链接,进程间要明确通信双方的命名。
5、间接通信:间接通信是通信双方不会直接建立连接,而是找到一个中介者,这个中介者可能是个对象等等,进程可以在其中放置消息,并且可以从中删除消息,以此达到进程间通信的目的。
6、消息队列:消息队列是内核中存储消息的链表,它由消息队列标识符进行标识,这种方式能够在不同的进程之间提供全双工的通信连接。
7、共享内存:共享内存是使用所有进程之间的内存来建立连接,这种类型需要同步进程访问来相互保护。
消息传递
在消息传递的并发模型里,线程之间没有公共状态,线程之间必须通过明确的发送消息来显式进行 通信。在 Java 中典型的消息传递方式,就是 wait() 和 notify() ,或者 BlockingQueue 。
Java中活锁和死锁有什么区别?
活锁:一个线程通常会有会响应其他线程的活动。如果其他线程也会响应另一个线程的活动,那么就有 可能发生活锁。同死锁一样,发生活锁的线程无法继续执行。然而线程并没有阻塞——他们在忙于响应 对方无法恢复工作。这就相当于两个在走廊相遇的人:甲向他自己的左边靠想让乙过去,而乙向他的右 边靠想让甲过去。可见他们阻塞了对方。甲向他的右边靠,而乙向他的左边靠,他们还是阻塞了对方。
死锁:两个或更多线程阻塞着等待其它处于死锁状态的线程所持有的锁。死锁通常发生在多个线程同时 但以不同的顺序请求同一组锁的时候,死锁会让你的程序挂起无法完成任务。
什么是可重入锁(ReentrantLock)?
Java.util.concurrent.lock 中的 Lock 框架是锁定的一个抽象,它允许把锁定的实现作为Java 类,而不是 作为语言的特性来实现。这就为Lock 的多种实现留下了空间,各种实现可能有不同的调度算法、性能特 性或者锁定语义。
ReentrantLock 类实现了Lock ,它拥有与synchronized 相同的并发性和内存语义,但是添加了类似锁 投票、定时锁等候和可中断锁等候的一些特性。此外,它还提供了在激烈争用情况下更佳的性能。(换 句话说,当许多线程都想访问共享资源时,JVM可以花更少的时候来调度线程,把更多时间用在执行线 程上。)
形成死锁的四个必要条件是什么
互斥条件:线程(进程)对于所分配到的资源具有排它性,即一个资源只能被一个线程(进程)占用,直到被该线程(进程)释放 请求与保持条件:一个线程(进程)因请求被占用资源而发生阻塞时,对已获得的资源保持不放。 不剥夺条件:线程(进程)已获得的资源在末使用完之前不能被其他线程强行剥夺,只有自己使用完毕后才释放资源。 循环等待条件:当发生死锁时,所等待的线程(进程)必定会形成一个环路(类似于死循环),造成永久阻塞
如何避免线程死锁
我们只要破坏产生死锁的四个条件中的其中一个就可以了。
- 破坏互斥条件
这个条件我们没有办法破坏,因为我们用锁本来就是想让他们互斥的(临界资源需要互斥访问)。
- 破坏请求与保持条件
一次性申请所有的资源。
- 破坏不剥夺条件
占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。
- 破坏循环等待条件
靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件。
说一下 runnable 和 callable 有什么区别?
- 相同点
都是接口都可以编写多线程程序都采用Thread.start()启动线程
- 主要区别
Runnable 接口 run 方法无返回值;Callable 接口 call 方法有返回值,是个泛型,和Future、FutureTask配合可以用来获取异步执行的结果 Runnable 接口 run 方法只能抛出运行时异常,且无法捕获处理;Callable 接口 call 方法允许抛出异常,可以获取异常信息 注:Callalbe接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执行,如果不调用不会阻塞。
.常见运行时异常
NullPointerException:空指针异常,当应用程序试图在需要对象的地方出现null时抛出该异常
ArithmeticException: 数学运算异常,当出现异常的运算条件时抛出此异常
ArrayIndexOutOfBoundsException: 数组下标越界异常,用非法索引访问数组时抛出的异常
ClassCastException :类型转换异常,当试图将对象强制转换为不是实例的子类时抛出该异常
NumberFormatException:数字格式不正确异常,当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时抛出该异常
4.常见编译异常
SQLException:操作数据库时查询表可能发生的异常
IOException:操作文件时发生的异常
FileNotFoundException:操作不存在文件时发生的异常
ClassNotFoundException:加载类而类不存在时发生的异常
EOFException:操作文件到文件末尾发生异常
IllegalArguementException:参数异常
线程的状态和基本操作
说说线程的生命周期及五种基本状态?
新建(new):新创建了一个线程对象。
可运行(runnable):线程对象创建后,当调用线程对象的 start()方法,该线程处于就绪状态,等待被线程调度选中,获取cpu的使用权。
运行(running):可运行状态(runnable)的线程获得了cpu时间片(timeslice),执行程序代码。注:就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;
阻塞(block):处于运行状态中的线程由于某种原因,暂时放弃对 CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被 CPU 调用以进入到运行状态。
请说出与线程同步以及线程调度相关的方法。
(1) wait():使一个线程处于等待(阻塞)状态,并且释放所持有的对象的锁;
(2)sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要处理 InterruptedException 异常;
(3)notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由 JVM 确定唤醒哪个线程,而且与优先级无关;
(4)notityAll():唤醒所有处于等待状态的线程,该方法并不是将对象的锁给所有线程,而是让它们竞争,只有获得锁的线程才能进入就绪状态;
sleep() 和 wait() 有什么区别?
两者都可以暂停线程的执行
类的不同:sleep() 是 Thread线程类的静态方法,wait() 是 Object类的方法。 是否释放锁:sleep() 不释放锁;wait() 释放锁。 用途不同:Wait 通常被用于线程间交互/通信,sleep 通常被用于暂停执行。 用法不同:wait() 方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的 notify() 或者 notifyAll() 方法。sleep() 方法执行完成后,线程会自动苏醒。或者可以使用wait(long timeout)超时后线程会自动苏醒。
为什么线程通信的方法 wait(), notify()和 notifyAll()被定义在 Object 类里?
Java中,任何对象都可以作为锁,并且 wait(),notify()等方法用于等待对象的锁或者唤醒线程,在 Java 的线程中并没有可供任何对象使用的锁,所以任意对象调用方法一定定义在Object类中。
wait(), notify()和 notifyAll()这些方法在同步代码块中调用
有的人会说,既然是线程放弃对象锁,那也可以把wait()定义在Thread类里面啊,新定义的线程继承于Thread类,也不需要重新定义wait()方法的实现。然而,这样做有一个非常大的问题,一个线程完全可以持有很多锁,你一个线程放弃锁的时候,到底要放弃哪个锁?当然了,这种设计并不是不能实现,只是管理起来更加复杂。
综上所述,wait()、notify()和notifyAll()方法要定义在Object类中。
Thread 类中的 yield 方法有什么作用?
使当前线程从执行状态(运行状态)变为可执行态(就绪状态)。
当前线程到了就绪状态,那么接下来哪个线程会从就绪状态变成执行状态呢?可能是当前线程,也可能是其他线程,看系统的分配了
synchronized可重入的原理
重入锁是指一个线程获取到该锁之后,该线程可以继续获得该锁。底层原理维护一个计数器,当线程获取该锁时,计数器加一,再次获得该锁时继续加一,释放锁时,计数器减一,当计数器值为0时,表明该锁未被任何线程所持有,其它线程可以竞争获取锁。
什么是自旋
很多 synchronized 里面的代码只是一些很简单的代码,执行时间非常快,此时等待的线程都加锁可能是一种不太值得的操作,因为线程阻塞涉及到用户态和内核态切换的问题。既然 synchronized 里面的代码执行得非常快,不妨让等待锁的线程不要被阻塞,而是在 synchronized 的边界做忙循环,这就是自旋。如果做了多次循环发现还没有获得锁,再阻塞,这样可能是一种更好的策略。
多线程中 synchronized 锁升级的原理是什么?
synchronized 锁升级原理:在锁对象的对象头里面有一个 threadid 字段,在第一次访问的时候 threadid 为空,jvm 让其持有偏向锁,并将 threadid 设置为其线程 id,再次进入的时候会先判断 threadid 是否与其线程 id 一致,如果一致则可以直接使用此对象,如果不一致,则升级偏向锁为轻量级锁,通过自旋循环一定次数来获取锁,执行一定次数之后,如果还没有正常获取到要使用的对象,此时就会把锁从轻量级升级为重量级锁,此过程就构成了 synchronized 锁的升级。
锁的升级的目的:锁升级是为了减低了锁带来的性能消耗。在 Java 6 之后优化 synchronized 的实现方式,使用了偏向锁升级为轻量级锁再升级到重量级锁的方式,从而减低了锁带来的性能消耗。
synchronized 和 Lock 有什么区别?
首先synchronized是Java内置关键字,在JVM层面,Lock是个Java类; synchronized 可以给类、方法、代码块加锁;而 lock 只能给代码块加锁。 synchronized 不需要手动获取锁和释放锁,使用简单,发生异常会自动释放锁,不会造成死锁;而 lock 需要自己加锁和释放锁,如果使用不当没有 unLock()去释放锁就会造成死锁。 通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办到。
什么是 CAS
CAS 是 compare and swap 的缩写,即我们所说的比较交换。
cas 是一种基于锁的操作,而且是乐观锁。在 java 中锁分为乐观锁和悲观锁。悲观锁是将资源锁住,等一个之前获得锁的线程释放锁之后,下一个线程才可以访问。而乐观锁采取了一种宽泛的态度,通过某种方式不加锁来处理资源,比如通过给记录加 version 来获取数据,性能较悲观锁有很大的提高。
CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。如果内存地址里面的值和 A 的值是一样的,那么就将内存里面的值更新成 B。CAS是通过无限循环来获取数据的,若果在第一轮循环中,a 线程获取地址里面的值被b 线程修改了,那么 a 线程需要自旋,到下次循环才有可能机会执行。
FutureTask是什么
FutureTask表示一个异步运算的任务。FutureTask里面可以传入一个Callable的具体实现类,可以对这 个异步运算的任务的结果进行等待获取、判断是否已经完成、取消任务等操作。当然,由于FutureTask 也是Runnable接口的实现类,所以FutureTask也可以放入线程池中。
多线程锁的升级原理是什么?
什么是自旋
很多synchronized里面的代码只是一些很简单的代码,执行时间非常快,此时等待的线程都加锁可能是一种不太值得的操作,因为线程阻塞涉及到用户态和内核态切换的问题。
自旋锁的优缺点?
自旋锁不会引起调用者休眠,如果自旋锁已经被别的线程保持,调用者就一直循环在那里看是否该自旋锁的保持者释放了锁。由于自旋锁不会引起调用者休眠,所以自旋锁的效率远高于互斥锁。
虽然自旋锁效率比互斥锁高,但它会存在下面两个问题: 1、自旋锁一直占用CPU,在未获得锁的情况 下,一直运行,如果不能在很短的时间内获得锁,会导致CPU效率降低。 2、试图递归地获得自旋锁会 引起死锁。递归程序决不能在持有自旋锁时调用它自己,也决不能在递归调用时试图获得相同的自旋 锁。
由此可见,我们要慎重的使用自旋锁,自旋锁适合于锁使用者保持锁时间比较短并且锁竞争不激烈的情 况。正是由于自旋锁使用者一般保持锁时间非常短,因此选择自旋而不是睡眠是非常必要的,自旋锁的 效率远高于互斥锁。
你知道怎么创建线程池吗?
在 Java 语言中,并发编程都是通过创建线程池来实现的,而线程池的创建方式也有很多种,每种线程池的创建方式都对应了不同的使用场景,总体来说线程池的创建可以分为以下两类:
1、通过 Executors 执行器自动创建线程池:
(1)newSingleThreadExecutor:创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
(2)newFixedThreadPool:创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。如果希望在服务器上使用线程池,建议使用 newFixedThreadPool方法来创建线程池,这样能获得更好的性能。
(3) newCachedThreadPool:创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60 秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说 JVM)能够创建的最大线程大小。
(4)newScheduledThreadPool:创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。
2、通过 ThreadPoolExecutor 手动创建线程池。
ThreadPoolExecutor() 是最原始的线程池创建,也是阿里巴巴 Java 开发手册中明确规范的创建线程池的方式。
ThreadPoolExecutor构造函数重要参数分析
ThreadPoolExecutor 3 个最重要的参数:
corePoolSize :核心线程数,线程数定义了最小可以同时运行的线程数量。 maximumPoolSize :线程池中允许存在的工作线程的最大数量 workQueue:当新任务来的时候会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,任务就会被存放在队列中。 ThreadPoolExecutor其他常见参数:
keepAliveTime:线程池中的线程数量大于 corePoolSize 的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了 keepAliveTime才会被回收销毁; unit :keepAliveTime 参数的时间单位。 threadFactory:为线程池提供创建新线程的线程工厂 handler :线程池任务队列超过 maxinumPoolSize 之后的拒绝策略 ThreadPoolExecutor饱和策略
ThreadPoolExecutor 饱和策略定义:
如果当前同时运行的线程数量达到最大线程数量并且队列也已经被放满了任时,ThreadPoolTaskExecutor 定义一些策略:
ThreadPoolExecutor.AbortPolicy:抛出 RejectedExecutionException来拒绝新任务的处理。
ThreadPoolExecutor.CallerRunsPolicy:调用执行自己的线程运行任务。您不会任务请求。但是这种策略会降低对于新任务提交速度,影响程序的整体性能。另外,这个策略喜欢增加队列容量。如果您的应用程序可以承受此延迟并且你不能任务丢弃任何一个任务请求的话,你可以选择这个策略。
ThreadPoolExecutor.DiscardPolicy:不处理新任务,直接丢弃掉。
ThreadPoolExecutor.DiscardOldestPolicy: 此策略将丢弃最早的未处理的任务请求。 举个例子: Spring 通过 ThreadPoolTaskExecutor 或者我们直接通过 ThreadPoolExecutor 的构造函数创建线程池的时候,当我们不指定 RejectedExecutionHandler 饱和策略的话来配置线程池的时候默认使用的是
ThreadPoolExecutor.AbortPolicy。在默认情况下,ThreadPoolExecutor 将抛出 RejectedExecutionException 来拒绝新来的任务 ,这代表你将丢失对这个任务的处理。 对于可伸缩的应用程序,建议使用
ThreadPoolExecutor.CallerRunsPolicy。当最大池被填满时,此策略为我们提供可伸缩队列
Java 中都有哪些引用类型?
强引用:发生 gc 的时候不会被回收。 软引用:有用但不是必须的对象,在发生内存溢出之前会被回收。 弱引用:有用但不是必须的对象,在下一次GC时会被回收。 虚引用(幽灵引用/幻影引用):无法通过虚引用获得对象,用 PhantomReference 实现虚引用,虚引用的用途是在 gc 时返回一个通知。
Java 虚拟机规范规定的区域分为以下 5 个部分:
程序计数器(Program Counter Register):当前线程所执行的字节码的行号指示器,字节码解析器的工作是通过改变这个计数器的值,来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能,都需要依赖这个计数器来完成;Java 虚拟机栈(Java Virtual Machine Stacks):用于存储局部变量表、操作数栈、动态链接、方法出口等信息; 本地方法栈(Native Method Stack):与虚拟机栈的作用是一样的,只不过虚拟机栈是服务 Java 方法的,而本地方法栈是为虚拟机调用 Native 方法服务的; Java 堆(Java Heap):Java 虚拟机中内存最大的一块,是被所有线程共享的,几乎所有的对象实例都在这里分配内存; 方法区(Methed Area):用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据。
怎么判断对象是否可以被回收?
垃圾收集器在做垃圾回收的时候,首先需要判定的就是哪些内存是需要被回收的,哪些对象是「存活」的,是不可以被回收的;哪些对象已经「死掉」了,需要被回收。
一般有两种方法来判断:
引用计数器法:为每个对象创建一个引用计数,有对象引用时计数器 +1,引用被释放时计数 -1,当计数器为 0 时就可以被回收。它有一个缺点不能解决循环引用的问题; 可达性分析算法:从 GC Roots 开始向下搜索,搜索所走过的路径称为引用链。当一个对象到 GC Roots 没有任何引用链相连时,则证明此对象是可以被回收的。
在Java中,对象什么时候可以被垃圾回收
当对象对当前使用这个对象的应用程序变得不可触及的时候,这个对象就可以被回收了。 垃圾回收不会发生在永久代,如果永久代满了或者是超过了临界值,会触发完全垃圾回收(Full GC)。如果你仔细查看垃圾收集器的输出信息,就会发现永久代也是被回收的。这就是为什么正确的永久代大小对避免Full GC是非常重要的原因。
说一下 JVM 有哪些垃圾回收算法?
标记-清除算法:标记无用对象,然后进行清除回收。缺点:效率不高,无法清除垃圾碎片。 复制算法:按照容量划分二个大小相等的内存区域,当一块用完的时候将活着的对象复制到另一块上,然后再把已使用的内存空间一次清理掉。缺点:内存使用率不高,只有原来的一半。 标记-整理算法:标记无用对象,让所有存活的对象都向一端移动,然后直接清除掉端边界以外的内存。 分代算法:根据对象存活周期的不同将内存划分为几块,一般是新生代和老年代,新生代基本采用复制算法,老年代采用标记整理算法
新生代垃圾回收器和老年代垃圾回收器都有哪些?有什么区别?
新生代回收器:Serial、ParNew、Parallel Scavenge 老年代回收器:Serial Old、Parallel Old、CMS 整堆回收器:G1 新生代垃圾回收器一般采用的是复制算法,复制算法的优点是效率高,缺点是内存利用率低;老年代回收器一般采用的是标记-整理的算法进行垃圾回收。
G1 收集器
Garbage first 垃圾收集器是目前垃圾收集器理论发展的最前沿成果,相比与 CMS 收集器, G1 收集器两个最突出的改进是:
- 基于标记-整理算法,不产生内存碎片。
- 可以非常精确控制停顿时间,在不牺牲吞吐量前提下,实现低停顿垃圾回收。G1 收集器避免全区域垃圾收集,它把堆内存划分为大小
固定的几个独立区域,并且跟踪这些区域的垃圾收集进度,同时在后台维护一个优先级列表,每次根据所允许的收集时间, 优先回收 垃圾最多的区域。区域划分和优先级区域回收机制,确保 G1 收集器可以在有限时间获得最高的垃圾收集效率
JVM 的永久代中会发生垃圾回收么
垃圾回收不会发生在永久代,如果永久代满了或者是超过了临界值,会触发完全垃圾回收(Full GC)。如果你仔细查看垃圾收集器的输出信 息,就会发现永久代也是被回收的。这就是为什么正确的永久代大小对避免Full GC是非常重要的原因。请参考下Java8:从永久代到元数据 区 (注:Java8中已经移除了永久代,新加了一个叫做元数据区的native内存区)
网络模型分层
OSI分层 (7层):物理层、数据链路层、网络层、传输层、会话层、表示层、应用层。 TCP/IP分层(4层):网络接口层、 网际层、运输层、 应用层。 五层协议 (5层):物理层、数据链路层、网络层、运输层、 应用层
- 应用层
应用层的任务是通过应用进程之间的交互来完成特定的网络作用,常见的应用层协议有域名系统DNS,HTTP协议等。
- 表示层
表示层的主要作用是数据的表示、安全、压缩。可确保一个系统的应用层所发送的信息可以被另一个系统的应用层读取。
- 会话层
会话层的主要作用是建立通信链接,保持会话过程通信链接的畅通,同步两个节点之间的对话,决定通信是否被中断以及通信中断时决定从何处重新发送。。
- 传输层
传输层的主要作用是负责向两台主机进程之间的通信提供数据传输服务。传输层的协议主要有传输控制协议TCP和用户数据协议UDP。
- 网络层
网络层的主要作用是选择合适的网间路由和交换结点,确保数据及时送达。常见的协议有IP协议。
- 数据链路层
数据链路层的作用是在物理层提供比特流服务的基础上,建立相邻结点之间的数据链路,通过差错控制提供数据帧(Frame)在信道上无差错的传输,并进行各电路上的动作系列。 常见的协议有SDLC、HDLC、PPP等。
- 物理层
物理层的主要作用是实现相邻计算机结点之间比特流的透明传输,并尽量屏蔽掉具体传输介质和物理设备的差异
交换机
交换机工作于OSI参考模型的第二层,即数据链路层。交换机内部的CPU会在每个端口成功连接时,通过ARP协议学习它的MAC地址,保存成一张 ARP表。在今后的通讯中,发往该MAC地址的数据包将仅送往其对应的端口,而不是所有的端口。因此,交换机可用于划分数据链路层广播,即冲突域;但它不 能划分网络层广播,即广播域。
2)路由器
路由器(Router)是一种计算机网络设备,提供了路由与转送两种重要机制,可以决定数据包从来源端到目的端所经过 的路由路径(host到host之间的传输路径),这个过程称为路由;将路由器输入端的数据包移送至适当的路由器输出端(在路由器内部进行),这称为转 送。路由工作在OSI模型的第三层——即网络层,例如网际协议。
3)网关
网关(Gateway),网关顾名思义就是连接两个网络的设备,区别于路由器(由于历史的原因,许多有关TCP/IP 的文献曾经把网络层使用的路由器(Router)称为网关,在今天很多局域网采用都是路由来接入网络,因此现在通常指的网关就是路由器的IP),经常在家 庭中或者小型企业网络中使用,用于连接局域网和Internet
谈谈你对域名缓存的了解?
为了提高 DNS 查询效率,并减轻服务器的负荷和减少因特网上的 DNS 查询报文数量,在域名服务器中广泛使用了高速缓存,用来存放最近查询过的域名以及从何处获得域名映射信息的记录。
DNS的工作流程
DNS的定义:DNS的全称是domain name system,即域名系统。DNS是因特网上作为域名和IP地址相互映射的一个分布式数据库,能够使用户更方便的去访问互联网而不用去记住能够被机器直接读取的IP地址。比如大家访问百度,更多地肯定是访问http://www.baidu,而不是访问112.80.248.74,因为这几乎无规则的IP地址实在太难记了。DNS要做的就是将http://www.baidu解析成112.80.248.74。
- 在浏览器中输入http://www.baidu域名,操作系统会先检查自己本地的hosts文件是否有这个域名的映射关系,如果有,就先调用这个IP地址映射,完成域名解析。
- 如果hosts文件中没有,则查询本地DNS解析器缓存,如果有,则完成地址解析。
- 如果本地DNS解析器缓存中没有,则去查找本地DNS服务器,如果查到,完成解析。
- 如果没有,则本地服务器会向根域名服务器发起查询请求。根域名服务器会告诉本地域名服务器去查询哪个顶级域名服务器。
- 本地域名服务器向顶级域名服务器发起查询请求,顶级域名服务器会告诉本地域名服务器去查找哪个权限域名服务器。
- 本地域名服务器向权限域名服务器发起查询请求,权限域名服务器告诉本地域名服务器http://www.baidu所对应的IP地址。
- 本地域名服务器告诉主机http://www.baidu所对应的IP地址。
了解ARP协议吗?
ARP协议属于网络层的协议,主要作用是实现从IP地址转换为MAC地址。在每个主机或者路由器中都建有一个ARP缓存表,表中有IP地址及IP地址对应的MAC地址。先来看一下什么时IP地址和MAC地址。
- IP地址:IP地址是指互联网协议地址,IP地址是IP协议提供的一种统一的地址格式,它为互联网上的每一个网络和每一台主机分配一个逻辑地址,以此来屏蔽物理地址的差异。
- MAC地址:MAC地址又称物理地址,由网络设备制造商生产时写在硬件内部,不可更改,并且每个以太网设备的MAC地址都是唯一的。
各种协议的介绍
ICMP协议: 因特网控制报文协议。它是TCP/IP协议族的一个子协议,用于在IP主机、路由器之间传递控制消息。
TFTP协议: 是TCP/IP协议族中的一个用来在客户机与服务器之间进行简单文件传输的协议,提供不复杂、开销不大的文件传输服务。
HTTP协议: 超文本传输协议,是一个属于应用层的面向对象的协议,由于其简捷、快速的方式,适用于分布式超媒体信息系统。
DHCP协议: 动态主机配置协议,是一种让系统得以连接到网络上,并获取所需要的配置参数手段。
NAT协议:网络地址转换属接入广域网(WAN)技术,是一种将私有(保留)地址转化为合法IP地址的转换技术,
DHCP协议:一个局域网的网络协议,使用UDP协议工作,用途:给内部网络或网络服务供应商自动分配IP地址,给用户或者内部网络管理员作为对所有计算机作中央管理的手段
有了IP地址,为什么还要用MAC地址?
是因为IP地址是和地域相关的,对于同一个子网上的设备,IP地址的前缀都是一样的,这样路由器通过IP地址的前缀就知道设备在在哪个子网上了,而只用MAC地址的话,路由器则需要记住每个MAC地址在哪个子网,这需要路由器有极大的存储空间,是无法实现的。
TCP与UDP有什么区别
是否面向连接 | 可靠性 | 传输形式 | 传输效率 | 消耗资源 | 应用场景 | 首部字节 | |
---|---|---|---|---|---|---|---|
TCP | 面向连接 | 可靠 | 字节流 | 慢 | 多 | 文件/邮件传输 | 20~60 |
UDP | 无连接 | 不可靠 | 数据报文段 | 快 | 少 | 视频/语音传输 | 8 |
有时候面试还会问到TCP的首部都包含什么
- TCP首部(图片来源于网络):
前20个字节是固定的,后面有4n个字节是根据需而增加的选项,所以TCP首部最小长度为20字节。
在这里插入图片描述
- UDP首部(图片来源于网络):
UDP的首部只有8个字节,源端口号、目的端口号、长度和校验和各两个字节。
在这里插入图片描述
TCP协议如何保证可靠传输
主要有校验和、序列号、超时重传、流量控制及拥塞避免等几种方法。
- 校验和:在发送算和接收端分别计算数据的校验和,如果两者不一致,则说明数据在传输过程中出现了差错,TCP将丢弃和不确认此报文段。
- 序列号:TCP会对每一个发送的字节进行编号,接收方接到数据后,会对发送方发送确认应答(ACK报文),并且这个ACK报文中带有相应的确认编号,告诉发送方,下一次发送的数据从编号多少开始发。如果发送方发送相同的数据,接收端也可以通过序列号判断出,直接将数据丢弃。如果
在这里插入图片描述
- 超时重传:在上面说了序列号的作用,但如果发送方在发送数据后一段时间内(可以设置重传计时器规定这段时间)没有收到确认序号ACK,那么发送方就会重新发送数据。
这里发送方没有收到ACK可以分两种情况,如果是发送方发送的数据包丢失了,接收方收到发送方重新发送的数据包后会马上给发送方发送ACK;如果是接收方之前接收到了发送方发送的数据包,而返回给发送方的ACK丢失了,这种情况,发送方重传后,接收方会直接丢弃发送方冲重传的数据包,然后再次发送ACK响应报文。
如果数据被重发之后还是没有收到接收方的确认应答,则进行再次发送。此时,等待确认应答的时间将会以2倍、4倍的指数函数延长,直到最后关闭连接。
- 流量控制:如果发送端发送的数据太快,接收端来不及接收就会出现丢包问题。为了解决这个问题,TCP协议利用了滑动窗口进行了流量控制。在TCP首部有一个16位字段大小的窗口,窗口的大小就是接收端接收数据缓冲区的剩余大小。接收端会在收到数据包后发送ACK报文时,将自己的窗口大小填入ACK中,发送方会根据ACK报文中的窗口大小进而控制发送速度。如果窗口大小为零,发送方会停止发送数据。
- 拥塞控制:如果网络出现拥塞,则会产生丢包等问题,这时发送方会将丢失的数据包继续重传,网络拥塞会更加严重,所以在网络出现拥塞时应注意控制发送方的发送数据,降低整个网络的拥塞程度。拥塞控制主要有四部分组成:慢开始、拥塞避免、快重传、快恢复,如下图(图片来源于网络)。
在这里插入图片描述
这里的发送方会维护一个拥塞窗口的状态变量,它和流量控制的滑动窗口是不一样的,滑动窗口是根据接收方数据缓冲区大小确定的,而拥塞窗口是根据网络的拥塞情况动态确定的,一般来说发送方真实的发送窗口为滑动窗口和拥塞窗口中的最小值。
- 慢开始:为了避免一开始发送大量的数据而产生网络阻塞,会先初始化cwnd为1,当收到ACK后到下一个传输轮次,cwnd为2,以此类推成指数形式增长。
- 拥塞避免:因为cwnd的数量在慢开始是指数增长的,为了防止cwnd数量过大而导致网络阻塞,会设置一个慢开始的门限值ssthresh,当cwnd>=ssthresh时,进入到拥塞避免阶段,cwnd每个传输轮次加1。但网络出现超时,会将门限值ssthresh变为出现超时cwnd数值的一半,cwnd重新设置为1,如上图,在第12轮出现超时后,cwnd变为1,ssthresh变为12。
- 快重传:在网络中如果出现超时或者阻塞,则按慢开始和拥塞避免算法进行调整。但如果只是丢失某一个报文段,如下图(图片来源于网络),则使用快重传算法。
从上图可知,接收方正确地接收到M1和M2,而M3丢失,由于没有接收到M3,在接收方收到M5、M6和M7时,并不会进行确认,也就是不会发送ACK。这时根据前面说的保证TCP可靠性传输中的序列号的作用,接收方这时不会接收M5,M6,M7,接收方可以什么都不会,因为发送方长时间未收到M3的确认报文,会对M3进行重传。除了这样,接收方也可以重复发送M2的确认报文,这样发送端长时间未收到M3的确认报文也会继续发送M3报文。
**但是根据快重传算法,要求在这种情况下,需要快速向发送端发送M2的确认报文,在发送方收到三个M2的确认报文后,无需等待重传计时器所设置的时间,可直接进行M3的重传,这就是快重传。**(面试时说这一句就够了,前面是帮助理解)
- 快恢复:从上上图圈4可以看到,当发送收到三个重复的ACK,会进行快重传和快恢复。快恢复是指将ssthresh设置为发生快重传时的cwnd数量的一半,而cwnd不是设置为1而是设置为为门限值ssthresh,并开始拥塞避免阶段。
TCP的三次握手及四次挥手
必考题
在介绍三次握手和四次挥手之前,先介绍一下TCP头部的一些常用字段。
- 序号:seq,占32位,用来标识从发送端到接收端发送的字节流。
- 确认号:ack,占32位,只有ACK标志位为1时,确认序号字段才有效,ack=seq+1。
- 标志位:
- SYN:发起一个新连接。
- FIN:释放一个连接。
- ACK:确认序号有效。
三次握手
三次握手的本质就是确定发送端和接收端具备收发信息的能力,在能流畅描述三次握手的流程及其中的字段含义作用的同时还需要记住每次握手时 接收端和发送端的状态。这个比较容易忽略。
先看一张很经典的图(图片来源于网络),发送端有CLOSED、SYN-SENT、ESTABLISHED三种状态,接收端有CLOSED、LISTEN、SYN-RCVD、ESTABLISHED四种状态。
在这里插入图片描述
假设发送端为客户端,接收端为服务端。开始时客户端和服务端的状态都是CLOSE。
- 第一次握手:客户端向服务端发起建立连接请求,客户端会随机生成一个起始序列号x,客户端向服务端发送的字段中包含标志位SYN=1,序列号seq=100。第一次握手前客户端的状态为CLOSE,第一次握手后客户端的状态为SYN-SENT。此时服务端的状态为LISTEN
- 第二次握手:服务端在收到客户端发来的报文后,会随机生成一个服务端的起始序列号y,然后给客户端回复一段报文,其中包括标志位SYN=1,ACK=1,序列号seq=y,确认号ack=x+1。第二次握手前服务端的状态为LISTEN,第二次握手后服务端的状态为SYN-RCVD,此时客户端的状态为SYN-SENT。(其中SYN=1表示要和客户端建立一个连接,ACK=1表示确认序号有效)
- 第三次握手:客户端收到服务端发来的报文后,会再向服务端发送报文,其中包含标志位ACK=1,序列号seq=x+1,确认号ack=y+1。第三次握手前客户端的状态为SYN-SENT,第三次握手后客户端和服务端的状态都为ESTABLISHED。
需要注意的一点是,第一次握手,客户端向服务端发起建立连接报文,会占一个序列号。但是第三次握手,同样是客户端向服务端发送报文,这次却不占序列号,所以建立连接后,客户端向服务端发送的第一个数据的序列号为x+1。
四次挥手
和三次握手一样,先看一张非常经典的图(图片来源于网络),客户端在四次挥手过程中有ESTABLISHED、FIN-WAIT-1、FIN-WAIT-2、TIME-WAIT、CLOSED等五个状态,服务端有ESTABLISHED、CLOSE-WAIT、LAST-ACK、CLOSED等四种状态。最好记住每次挥手时服务端和客户端的状态。 假设客户端首先发起的断开连接请求
在这里插入图片描述
- 第一次挥手:客户端向服务端发送的数据完成后,向服务端发起释放连接报文,报文包含标志位FIN=1,序列号seq=u。此时客户端只能接收数据,不能向服务端发送数据。
- 第二次挥手:服务端收到客户端的释放连接报文后,向客户端发送确认报文,包含标志位ACK=1,序列号seq=v,确认号ack=u+1。此时客户端到服务端的连接已经释放掉,客户端不能像服务端发送数据,服务端也不能向客户端发送数据。但服务端到客户端的单向连接还能正常传输数据。
- 第三次挥手:服务端发送完数据后向客户端发出连接释放报文,报文包含标志位FIN=1,标志位ACK=1,序列号seq=w,确认号ack=u+1。
- 第四次挥手:客户端收到服务端发送的释放连接请求,向服务端发送确认报文,包含标志位ACK=1,序列号seq=u+1,确认号ack=w+1。
为什么TCP连接的时候是3次?两次是否可以?
不可以,主要从以下两方面考虑(假设客户端是首先发起连接请求):
- 假设建立TCP连接仅需要两次握手,那么如果第二次握手时,服务端返回给客户端的确认报文丢失了,客户端这边认为服务端没有和他建立连接,而服务端却以为已经和客户端建立了连接,并且可能向服务端已经开始向客户端发送数据,但客户端并不会接收这些数据,浪费了资源。如果是三次握手,不会出现双方连接还未完全建立成功就开始发送数据的情况。
- 如果服务端接收到了一个早已失效的来自客户端的连接请求报文,会向客户端发送确认报文同意建立TCP连接。但因为客户端并不需要向服务端发送数据,所以此次TCP连接没有意义并且浪费了资源。
为什么TCP连接的时候是3次,关闭的时候却是4次?
因为需要确保通信双方都能通知对方释放连接,假设客服端发送完数据向服务端发送释放连接请求,当客服端并不知道,服务端是否已经发送完数据,所以此次断开的是客服端到服务端的单向连接,服务端返回给客户端确认报文后,服务端还能继续单向给客户端发送数据。当服务端发送完数据后还需要向客户端发送释放连接请求,客户端返回确认报文,TCP连接彻底关闭。所以断开TCP连接需要客户端和服务端分别通知对方并分别收到确认报文,一共需要四次。
TCP 是如何保证可靠性的
- 首先,TCP 的连接是基于三次握手,而断开则是四次挥手。确保连接和断开的可靠性。
- 其次,TCP 的可靠性,还体现在有状态;TCP 会记录哪些数据发送了,哪些数据被接受了,哪些没有被接受,并且保证数据包按序到达,保证数据传输不出差错。
- 再次,TCP 的可靠性,还体现在可控制。它有数据包校验、ACK 应答、超时重传(发送方)、失序数据重传(接收方)、丢弃重复数据、流量控制(滑动窗口)和拥塞控制等机制。
什么是对称加密与非对称加密
- 对称加密
对称加密指加密和解密使用同一密钥,优点是运算速度快,缺点是如何安全将密钥传输给另一方。常见的对称加密算法有DES、AES等等。
- 非对称加密
非对称加密指的是加密和解密使用不同的密钥,一把公开的公钥,一把私有的私钥。公钥加密的信息只有私钥才能解密,私钥加密的信息只有公钥才能解密。优点解决了对称加密中存在的问题。缺点是运算速度较慢。常见的非对称加密算法有RSA、DSA、ECC等等。
非对称加密的工作流程:A生成一对非堆成密钥,将公钥向所有人公开,B拿到A的公钥后使用A的公钥对信息加密后发送给A,经过加密的信息只有A手中的私钥能解密。这样B可以通过这种方式将自己的公钥加密后发送给A,两方建立起通信,可以通过对方的公钥加密要发送的信息,接收方用自己的私钥解密信息。
HTTPS的加密过程
上面已经介绍了对称加密和非对称加密的优缺点,HTTPS是将两者结合起来,使用的对称加密和非对称加密的混合加密算法。具体做法就是使用非对称加密来传输对称密钥来保证安全性,使用对称加密来保证通信的效率。
简化的工作流程:服务端生成一对非对称密钥,将公钥发给客户端。客户端生成对称密钥,用服务端发来的公钥进行加密,加密后发给服务端。服务端收到后用私钥进行解密,得到客户端发送的对称密钥。通信双方就可以通过对称密钥进行高效地通信了。
但是仔细想想这其中存在一个很大地问题,就是客户端最开始如何判断收到的这个公钥就是来自服务端而不是其他人冒充的?
这就需要证书上场了,服务端会向一个权威机构申请一个证书来证明自己的身份,到时候将证书(证书中包含了公钥)发给客户端就可以了,客户端收到证书后既证明了服务端的身份又拿到了公钥就可以进行下一步操作了。
HTTPS的加密过程:
- 客户端向服务端发起第一次握手请求,告诉服务端客户端所支持的SSL的指定版本、加密算法及密钥长度等信息。
- 服务端将自己的公钥发给数字证书认证机构,数字证书认证机构利用自己的私钥对服务器的公钥进行数字签名,并给服务器颁发公钥证书。
- 服务端将证书发给客服端。
- 客服端利用数字认证机构的公钥,向数字证书认证机构验证公钥证书上的数字签名,确认服务器公开密钥的真实性。
- 客服端使用服务端的公开密钥加密自己生成的对称密钥,发给服务端。
- 服务端收到后利用私钥解密信息,获得客户端发来的对称密钥。
- 通信双方可用对称密钥来加密解密信息。
上述流程存在的一个问题是客户端哪里来的数字认证机构的公钥,其实,在很多浏览器开发时,会内置常用数字证书认证机构的公钥。
流程图如下:
在这里插入图片描述
常用HTTP状态码
这也是一个面试经常问的题目,背下来就行了.
状态码 | 类别 |
---|---|
1XX | 信息性状态码 |
2XX | 成功状态码 |
3XX | 重定向状态码 |
4XX | 客户端错误状态码 |
5XX | 服务端错误状态码 |
常见的HTTP状态码
1XX
- 100 Continue:表示正常,客户端可以继续发送请求
- 101 Switching Protocols:切换协议,服务器根据客户端的请求切换协议。
2XX
- 200 OK:请求成功
- 201 Created:已创建,表示成功请求并创建了新的资源
- 202 Accepted:已接受,已接受请求,但未处理完成。
- 204 No Content:无内容,服务器成功处理,但未返回内容。
- 205 Reset Content:重置内容,服务器处理成功,客户端应重置文档视图。
- 206 Partial Content:表示客户端进行了范围请求,响应报文应包含Content-Range指定范围的实体内容
3XX
- 301 Moved Permanently:永久性重定向
- 302 Found:临时重定向
- 303 See Other:和301功能类似,但要求客户端采用get方法获取资源
- 304 Not Modified:所请求的资源未修改,服务器返回此状态码时,不会返回任何资源。
- 305 Use Proxy:所请求的资源必须通过代理访问
- 307 Temporary Redirect: 临时重定向,与302类似,要求使用get请求重定向。
4XX
- 400 Bad Request:客户端请求的语法错误,服务器无法理解。
- 401 Unauthorized:表示发送的请求需要有认证信息。
- 403 Forbidden:服务器理解用户的请求,但是拒绝执行该请求
- 404 Not Found:服务器无法根据客户端的请求找到资源。
- 405 Method Not Allowed:客户端请求中的方法被禁止
- 406 Not Acceptable:服务器无法根据客户端请求的内容特性完成请求
- 408 Request Time-out:服务器等待客户端发送的请求时间过长,超时
5XX
- 500 Internal Server Error:服务器内部错误,无法完成请求
- 501 Not Implemented:服务器不支持请求的功能,无法完成请求
常见的HTTP方法
方法 | 作用 |
---|---|
GET | 获取资源 |
POST | 传输实体主体 |
PUT | 上传文件 |
DELETE | 删除文件 |
HEAD | 和GET方法类似,但只返回报文首部,不返回报文实体主体部分 |
PATCH | 对资源进行部分修改 |
OPTIONS | 查询指定的URL支持的方法 |
CONNECT | 要求用隧道协议连接代理 |
TRACE | 服务器会将通信路径返回给客户端 |
为了方便记忆,可以将PUT、DELETE、POST、GET理解为客户端对服务端的增删改查。
- PUT:上传文件,向服务器添加数据,可以看作增
- DELETE:删除文件
- POST:传输数据,向服务器提交数据,对服务器数据进行更新。
- GET:获取资源,查询服务器资源
HTTP 1.0、HTTP 1.1及HTTP 2.0的主要区别是什么
HTTP 1.0和HTTP 1.1的区别
- 长连接
HTTP 1.1支持长连接和请求的流水线操作。长连接是指不在需要每次请求都重新建立一次连接,HTTP 1.0默认使用短连接,每次请求都要重新建立一次TCP连接,资源消耗较大。请求的流水线操作是指客户端在收到HTTP的响应报文之前可以先发送新的请求报文,不支持请求的流水线操作需要等到收到HTTP的响应报文后才能继续发送新的请求报文。
- 缓存处理
在HTTP 1.0中主要使用header中的If-Modified-Since,Expires作为缓存判断的标准,HTTP 1.1引入了Entity tag,If-Unmodified-Since, If-Match等更多可供选择的缓存头来控制缓存策略。
- 错误状态码
在HTTP 1.1新增了24个错误状态响应码
- HOST域
在HTTP 1.0 中认为每台服务器都会绑定唯一的IP地址,所以,请求中的URL并没有传递主机名。但后来一台服务器上可能存在多个虚拟机,它们共享一个IP地址,所以HTTP 1.1中请求消息和响应消息都应该支持Host域。
- 带宽优化及网络连接的使用
在HTTP 1.0中会存在浪费带宽的现象,主要是因为不支持断点续传功能,客户端只是需要某个对象的一部分,服务端却将整个对象都传了过来。在HTTP 1.1中请求头引入了range头域,它支持只请求资源的某个部分,返回的状态码为206。
HTTP 2.0的新特性
- 新的二进制格式:HTTP 1.x的解析是基于文本,HTTP 2.0的解析采用二进制,实现方便,健壮性更好。
- 多路复用:每一个request对应一个id,一个连接上可以有多个request,每个连接的request可以随机混在一起,这样接收方可以根据request的id将request归属到各自不同的服务端请求里。
- header压缩:在HTTP 1.x中,header携带大量信息,并且每次都需要重新发送,HTTP 2.0采用编码的方式减小了header的大小,同时通信双方各自缓存一份header fields表,避免了header的重复传输。
- 服务端推送:客户端在请求一个资源时,会把相关资源一起发给客户端,这样客户端就不需要再次发起请求。
POST和GET有哪些区别?
- 请求参数:GET 把参数包含在 URL 中,用&连接起来;POST 通过 request body 传递参数。
- 请求缓存:GET请求会被主动Cache,而POST请求不会,除非手动设置。
- 收藏为书签:GET请求支持收藏为书签,POST请求不支持。
- 安全性:POST比GET安全,GET请求在浏览器回退时是无害的,而POST会再次请求。
- 历史记录:GET请求参数会被完整保留在浏览历史记录里,而POST中的参数不会被保留。
- 编码方式:GET请求只能进行url编码,而POST支持多种编码方式。
- 参数数据类型:GET只接受ASCII字符,而POST没有限制数据类型。
- 数据包: GET产生一个TCP数据包;POST可能产生两个TCP数据包。
Session、Cookie和Token的主要区别
HTTP协议是无状态的,即服务器无法判断用户身份。Session和Cookie可以用来进行身份辨认。
- Cookie
Cookie是保存在客户端一个小数据块,其中包含了用户信息。当客户端向服务端发起请求,服务端会像客户端浏览器发送一个Cookie,客户端会把Cookie存起来,当下次客户端再次请求服务端时,会携带上这个Cookie,服务端会通过这个Cookie来确定身份。
- Session
Session是通过Cookie实现的,和Cookie不同的是,Session是存在服务端的。当客户端浏览器第一次访问服务器时,服务器会为浏览器创建一个sessionid,将sessionid放到Cookie中,存在客户端浏览器。比如浏览器访问的是购物网站,将一本《图解HTTP》放到了购物车,当浏览器再次访问服务器时,服务器会取出Cookie中的sessionid,并根据sessionid获取会话中的存储的信息,确认浏览器的身份是上次将《图解HTTP》放入到购物车那个用户。
- Token
客户端在浏览器第一次访问服务端时,服务端生成的一串字符串作为Token发给客户端浏览器,下次浏览器在访问服务端时携带token即可无需验证用户名和密码,省下来大量的资源开销。看到这里很多人感觉这不是和sessionid作用一样吗?其实是不一样的,但是本文章主要针对面试,知识点很多,篇幅有限,几句话也解释不清楚,大家可以看看这篇文章,我觉得说的非常清楚了。cookie、session与token的真正区别
下面为了方便记忆,做了一个表格进行对比。
存放位置 | 占用空间 | 安全性 | 应用场景 | |
---|---|---|---|---|
Cookie | 客户端浏览器 | 小 | 较低 | 一般存放配置信息 |
Session | 服务端 | 多 | 较高 | 存放较为重要的信息 |
如果客户端禁止 cookie 能实现 session 还能用吗?
可以,Session的作用是在服务端来保持状态,通过sessionid来进行确认身份,但sessionid一般是通过Cookie来进行传递的。如果Cooike被禁用了,可以通过在URL中传递sessionid。
forward 和 redirect 的区别?
1、从地址栏显示来说:forward是服务器内部重定向,
客户端浏览器的网址不会发生变化;redirect发生一个状态码,告诉服务器去重新请求那个网址,显示的的新的网址
2、数据共享:在定向过程中forward使用的是同一个request,可以共享;redirect不可以。
3、应用场景:forward一般用于用户登录:redirect用于用户注销登录返回主页面或者跳转其他页面
4、forward效率更高
5、本质上说:forward转发是服务器上的行为,而redirect是客户端行为
6、次数:forward只有一次,redirect两次
- 直接转发方式(Forward) ,客户端和浏览器只发出一次请求,Servlet、HTML、JSP或其它信息资源,由第二个信息资源响应该请求,在请求对象request中,保存的对象对于每个信息资源是共享的。
- 间接转发方式(Redirect) 实际是两次HTTP请求,服务器端在响应第一次请求的时候,让浏览器再向另外一个URL发出请求,从而达到转发的目的。
举个通俗的例子:
- 直接转发就相当于:“A找B借钱,B说没有,B去找C借,借到借不到都会把消息传递给A”;
- 间接转发就相当于:"A找B借钱,B说没有,让A去找C借"。
在浏览器中输⼊url地址到显示主⻚的过程
面试超高频的一道题,一般能说清楚流程就可以。
- 对输入到浏览器的url进行DNS解析,将域名转换为IP地址。
- 和目的服务器建立TCP连接
- 向目的服务器发送HTTP请求
- 服务器处理请求并返回HTTP报文
- 浏览器解析并渲染页面
为什么会产生粘包和拆包呢?
- 要发送的数据小于TCP发送缓冲区的大小,TCP将多次写入缓冲区的数据一次发送出去,将会发生粘包;
- 接收数据端的应用层没有及时读取接收缓冲区中的数据,将发生粘包;
- 要发送的数据大于TCP发送缓冲区剩余空间大小,将会发生拆包;
- 待发送数据大于MSS(最大报文长度),TCP在传输前将进行拆包。即TCP报文长度-TCP头部长度>MSS。
解决方案:
- 发送端将每个数据包封装为固定长度
- 在数据尾部增加特殊字符进行分割
- 将数据分为两部分,一部分是头部,一部分是内容体;其中头部结构大小固定,且有一个字段声明内容体的大小。
DNS域名系统,简单描述其工作原理。
当DNS客户机需要在程序中使用名称时,它会查询DNS服务器来解析该名称。客户机发送的每条查询信息包括三条信息:包括:指定的DNS域名,指定的查询类型,DNS域名的指定类别。基于UDP服务,端口53. 该应用一般不直接为用户使用,而是为其他应用服务,如HTTP,SMTP等在其中需要完成主机名到IP地址的转换。
对称加密与非对称加密
对称密钥加密,又称私钥加密,即信息的发送方和接收方用同一个密钥去加密和解密数据。
- 它的最大优势是加/解密速度快,适合于对大数据量进行加密,但密钥管理困难且较为不安全。
- 进行一对一的双向保密通信。
- 常见的对称加密算法:DES,AES等。
img
非对称密钥加密,又称公钥加密,它需要使用一对密钥来分别完成加密和解密操作,一个公开发布,即公开密钥,另一个由用户自己秘密保存,即私用密钥。信息发送者用公开密钥去加密,而信息接收者则用私用密钥去解密。
- 从功能角度而言非对称加密比对称加密功能强大且较为安全,但加密和解密速度却比对称密钥加密慢得多。
- 多对一的单向保密通信。
- 最常用的非对称加密算法:RSA
由于非对称加密的方式不需要发送用来解密的私钥,所以可以保证安全性;但是和对称加密比起来,它非常的慢,所以我们还是要用对称加密来传送消息,但对称加密所使用的密钥我们可以通过非对称加密的方式发送出去。
img
数字签名
数字签名必须保证实现以下三点功能:
- 报文鉴别。即接受者能够核实发送者对报文的签名。
- 报文的完整性。即接受者确信所收到的数据和发送者发送的完全一样而没有被篡改过。
- 不可否认。发送者事后不能抵赖对报文的签名。
数字签名图解:
img
该图只是进行了数字签名并没有对报文进行加密。
数字签名过程:A用私钥SKA对明文X进行D运算签名成为密文DSKA,B用A的公钥PKA对密文DSKA进行E运算还原出明文X。
那么这个过程是如何满足报文鉴别、报文的完整性、不可否认三个特点的呢?
- 只有A拥有私钥SKA,只有他能生成密文DSKA,所以只要B用A的公钥能成功还原出可读的明文X就说明密文DSKA一定是A发来的。这里体现出报文的鉴别的特点。
- 同理如果中途密文DSKA被篡改,那么篡改者没有A的私钥SKA来对篡改过后的报文进行加密,那么B对被篡改过的报文进行解密时就会得到不可读的明文,就知道收到的报文被修改过了。这里体现了报文的完整性的特点。
- 若A抵赖曾发过该报文给B,B可把X和密文DSKA出示给进行公证的第三者,第三者很容易用PKA去证实A确实发送了X给B。这里体现了不可否认的特点。
具有保密性的数字签名图解:
img
什么是XSS攻击,如何避免?
XSS 攻击,全称跨站脚本攻击(Cross-Site Scripting),这会与层叠样式表(Cascading Style Sheets, CSS)的缩写混淆,因此有人将跨站脚本攻击缩写为XSS。它指的是恶意攻击者往Web页面里插入恶意html代码,当用户浏览该页之时,嵌入其中Web里面的html代码会被执行,从而达到恶意攻击用户的特殊目的。XSS攻击一般分三种类型:存储型 、反射型 、DOM型XSS
如何预防SQL注入问题
1). 使用#{}而不是${}
在MyBatis中,使用#{}
而不是${}
,可以很大程度防止sql注入。
- 因为
#{}
是一个参数占位符,对于字符串类型,会自动加上"",其他类型不加。由于Mybatis采用预编译,其后的参数不会再进行SQL编译,所以一定程度上防止SQL注入。 ${}
是一个简单的字符串替换,字符串是什么,就会解析成什么,存在SQL注入风险
2). 不要暴露一些不必要的日志或者安全信息,比如避免直接响应一些sql异常信息。
如果SQL发生异常了,不要把这些信息暴露响应给用户,可以自定义异常进行响应
3). 不相信任何外部输入参数,过滤参数中含有的一些数据库关键词关键词
可以加个参数校验过滤的方法,过滤union,or
等数据库关键词
4). 适当的权限控制
在你查询信息时,先校验下当前用户是否有这个权限。比如说,实现代码的时候,可以让用户多传一个企业Id什么的,或者获取当前用户的session信息等,在查询前,先校验一下当前用户是否是这个企业下的等等,是的话才有这个查询员工的权限。
什么是DoS、DDoS、DRDoS攻击?
- DOS: (Denial of Service),中文名称是拒绝服务,一切能引起DOS行为的攻击都被称为DOS攻击。最常见的DoS攻击有计算机网络宽带攻击和连通性攻击。
- DDoS: (Distributed Denial of Service),中文名称是分布式拒绝服务。是指处于不同位置的多个攻击者同时向一个或数个目标发动攻击,或者一个攻击者控制了位于不同位置的多台机器并利用这些机器对受害者同时实施攻击。常见的DDos有SYN Flood、Ping of Death、ACK Flood、UDP Flood等。
- DRDoS: (Distributed Reflection Denial of Service),中文名称是分布式反射拒绝服务,该方式靠的是发送大量带有被害者IP地址的数据包给攻击主机,然后攻击主机对IP地址源做出大量回应,形成拒绝服务攻击。
面向连接和非面向连接的服务的特点是什么?
面向连接的服务,通信双方在进行通信之前,要先在双方建立起一个完整的可以彼此沟通的通道,在通信过程中,整个连接的情况一直可以被实时地监控和管理。非面向连接的服务,不需要预先建立一个联络两个通信节点的连接,需要通信的时候,发送节点就可以往网络上发送信息,让信息自主地在网络上去传,一般在传输的过程中不再加以监控。
Servlet是线程安全的吗
Servlet不是线程安全的,多线程的读写会导致数据不同步的问题。
什么是CSRF攻击,如何避免
什么是CSRF 攻击?
CSRF,跨站请求伪造(英语:Cross-site request forgery),简单点说就是,攻击者盗用了你的身份,以你的名义发送恶意请求。跟跨网站脚本(XSS)相比,XSS 利用的是用户对指定网站的信任,CSRF 利用的是网站对用户网页浏览器的信任。
CSRF是如何攻击的呢?
- Tom 登陆银行,没有退出,浏览器包含了Tom在银行的身份认证信息。
- 黑客Jerry将伪造的转账请求,包含在在帖子
- Tom在银行网站保持登陆的情况下,浏览帖子
- 将伪造的转账请求连同身份认证信息,发送到银行网站
- 银行网站看到身份认证信息,以为就是Tom的合法操作,最后造成Tom资金损失。
如何解决CSRF攻击
检查 Referer 字段
大家都知道 HTTP 头中有一个 Referer 字段,这个字段用以标明请求来源于哪个地址。 通过在网站中校验请求的该字段,我们能知道请求是否是从本站发出的。我们可以拒绝一 切非本站发出的请求,这样避免了 CSRF 的跨站特性。这种方式利用了客户端无法构造 Referrer 的特性,虽然简单,不过当网站域名有多个, 或者经常变换域名的时候会变得非常的麻烦,同时也具有一定的局限性。
Token 验证
由于 CSRF 是利用了浏览器自动传递 cookie 的特性,另外一个防御思路就是校验信息不 通过 cookie 传递,在其他参数中增加随机加密串进行校验。为每一个提交增加一个指定 串参数,该参数服务端通过页面下发,放到 localStorage 中,每次请求的时候补充到提 交参数中,服务端通过校验该参数是否一致来判断是否是用户请求。由于 CSRF 攻击中攻 击者是无从事先得知该字符串,所以服务端就可以通过校验该值拒绝可以请求。
操作系统的主要功能
一般来说,现代操作系统主要提供下面几种功能
进程管理
: 进程管理的主要作用就是任务调度,在单核处理器下,操作系统会为每个进程分配一个任务,进程管理的工作十分简单;而在多核处理器下,操作系统除了要为进程分配任务外,还要解决处理器的调度、分配和回收等问题内存管理
:内存管理主要是操作系统负责管理内存的分配、回收,在进程需要时分配内存以及在进程完成时回收内存,协调内存资源,通过合理的页面置换算法进行页面的换入换出设备管理
:根据确定的设备分配原则对设备进行分配,使设备与主机能够并行工作,为用户提供良好的设备使用界面。文件管理
:有效地管理文件的存储空间,合理地组织和管理文件系统,为文件访问和文件保护提供更有效的方法及手段。提供用户接口
:操作系统提供了访问应用程序和硬件的接口,使用户能够通过应用程序发起系统调用从而操纵硬件,实现想要的功能。
什么是用户态和内核态
用户态和内核态是操作系统的两种运行状态。
内核态
:处于内核态的 CPU 可以访问任意的数据,包括外围设备,比如网卡、硬盘等,处于内核态的 CPU 可以从一个程序切换到另外一个程序,并且占用 CPU 不会发生抢占情况,一般处于特权级 0 的状态我们称之为内核态。用户态
:处于用户态的 CPU 只能受限的访问内存,并且不允许访问外围设备,用户态下的 CPU 不允许独占,也就是说 CPU 能够被其他程序获取。
那么为什么要有用户态和内核态呢?
这个主要是访问能力的限制的考量,计算机中有一些比较危险的操作,比如设置时钟、内存清理,这些都需要在内核态下完成,如果随意进行这些操作,那你的系统得崩溃多少次
什么是实时系统
实时操作系统对时间做出了严格的要求,实时操作系统分为两种:硬实时和软实时
硬实时操作系统
规定某个动作必须在规定的时刻内完成或发生,比如汽车生产车间,焊接机器必须在某一时刻内完成焊接,焊接的太早或者太晚都会对汽车造成永久性伤害。
软实时操作系统
虽然不希望偶尔违反最终的时限要求,但是仍然可以接受。并且不会引起任何永久性伤害。比如数字音频、多媒体、手机都是属于软实时操作系统。
你可以简单理解硬实时和软实时的两个指标:是否在时刻内必须完成以及是否造成严重损害。
什么是上下文切换
对于单核单线程 CPU 而言,在某一时刻只能执行一条 CPU 指令。上下文切换 (Context Switch) 是一种 将 CPU 资源从一个进程分配给另一个进程的机制。从用户角度看,计算机能够并行运行多个进程,这恰恰是操作系统通过快速上下文切换造成的结果。在切换的过程中,操作系统需要先存储当前进程的状态 (包括内存空间的指针,当前执行完的指令等等),再读入下一个进程的状态,然后执行此进程。
线程同步的方式有哪些?
- 互斥量:采用互斥对象机制,只有拥有互斥对象的线程才有访问公共资源的权限。因为互斥对象只有一个,所以可以保证公共资源不会被多个线程同时访问。
- 信号量:它允许同一时刻多个线程访问同一资源,但是需要控制同一时刻访问此资源的最大线程数量。
- 事件(信号):通过通知操作的方式来保持多线程同步,还可以方便的实现多线程优先级的比较操作。
进程之间通信的方式6种
1)管道
管道分为有名管道和无名管道
无名管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用.进程的亲缘关系一般指的是父子关系。无明管道一般用于两个不同进程之间的通信。当一个进程创建了一个管道,并调用fork创建自己的一个子进程后,父进程关闭读管道端,子进程关闭写管道端,这样提供了两个进程之间数据流动的一种方式。
有名管道也是一种半双工的通信方式,但是它允许无亲缘关系进程间的通信。
2)信号量
信号量是一个计数器,可以用来控制多个线程对共享资源的访问.,它不是用于交换大批数据,而用于多线程之间的同步.它常作为一种锁机制,防止某进程在访问资源时其它进程也访问该资源.因此,主要作为进程间以及同一个进程内不同线程之间的同步手段.
3)信号
信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生.
4)消息队列
消息队列是消息的链表,存放在内核中并由消息队列标识符标识.消息队列克服了信号传递信息少,管道只能承载无格式字节流以及缓冲区大小受限等特点.消息队列是UNIX下不同进程之间可实现共享资源的一种机制,UNIX允许不同进程将格式化的数据流以消息队列形式发送给任意进程.对消息队列具有操作权限的进程都可以使用msget完成对消息队列的操作控制.通过使用消息类型,进程可以按任何顺序读信息,或为消息安排优先级顺序.
5)共享内存
共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问.共享内存是最快的IPC(进程间通信)方式,它是针对其它进程间通信方式运行效率低而专门设计的.它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步与通信.
6)套接字:可用于不同及其间的进程通信
进程之间的调度算法7种
1. 先来先服务(FCFS)调度算法
先来先服务调度算法是最简单的调度方法。其基本原则是,按照进程进入就绪队列的先后次序进行选择。对于进程调度来说,一旦一个进程得到处理机,它就一直运行下去,直到该进程完成任务或者因等待某事件而不能继续运行,才会让出处理机。先来先服务调度算法属于非剥夺方式。
从表面上看,这个方法对于所有进程都是公平的,并且一个进程的等待时间是可以预先估计的。但是从另一方面来说,这个方法并非公平,因为当一个大进程先到达就绪状态时,就会使许多小进程等待很长时间,增加了进程的平均周转时间,会引起许多小进程用户的不满。
今天,先来先服务调度算法已很少用作主要的调度算法,尤其是分时和实时系统中。但它常被结合在其他的调度算法中使用。例如,在使用优先级作为调度依据的系统中,往往对许多具有相同优先级的进程使用先来先服务的原则。
2. 优先级调度算法
按照进程的优先级高低来进行调度,使高优先级进程优先得到处理机的调度算法称为优先级调度算法。进程的优先级可以由操作系统按一定原则赋予,也可以在操作系统外部安非,甚至可由用户支付高额费用来购买。
但在许多采用优先级调度算法的系统中,通常使用动态优先级。一个进程的优先级不是固定的,可能会随许多因素的变化而变化,例如,进程的等待时间、已使用的处理机时间或其他资源的使用情况。
优先级调度算法又可分为下述两种:
①非剥夺的优先级调度算法。一旦某个高优先级的进程得到处理机,就一直运行下去,直到由于其自身的原因(任务完成或等待事件)而主动让出处理机,才让另一个高优先级进程运行。
②可剥夺的优先级调度算法。任何时刻都严格按照优先级高的进程在处理机上运行的原则进行调度,或者说,在处理机上运行的进程永远是就绪进程队列中优先级最高的进程。在进程运行过程中,一旦有另一个优先级更高的进程出现(如一个高优先级的等待状态进程因事件的到来而成为就绪状态),进程调度程序就迫使原运行进程让出处理机给更高优先级的进程使用,或称为抢占处理机。在UNIX系结中,其讲程调度算法属于“可剥夺的优先级调度算法”。每个进程的优先级都是动态优先级,由系统为各进程每隔一个时间间隔计算一次优先级。
3. 时间片轮转调度算法
时间片轮转调度算法也多用于进程调度。采用此算法的系统,其进程就绪队列往往按进程到达的时间来排序。进程调度程序总是选择就绪队列中的第一个进程,也就是说,按照先来先服务原则进行调度,但进程仅占用处理机一个时间片。在使用完一个时间片后,即使进程还没有完成其运行,它也必须让出(被剥夺)处理机给下一个就绪的进程。而被剥夺的进程返回就绪队列的末尾重新排队,等候再次运行。时间片轮转调度算法特别适合分时系统使用。当多个进程驻留主存时,在进程间转接的开销一般不是很大。
由于时间片值对计算机系统的有效操作影响很大,所以在设计此算法时,应考虑下列问题:时间片值如何选择?它是固定值还是可变值?它对所有用户都相同还是随不同用户而不同?显然,如果时间片值很大,大到一个进程足以完成其全部任务所需的时间,那么此时间片轮转调度算法就退化为先来先服务调度算法了。如果时间片值很小,那么处理机在进程间的切换工作过于频繁,使处理机的开销变得很大,而处理机真正用于运行用户程序的时间将会减少。通常,最佳的时间片值应能使分时用户得到好的响应时间,因此时间片值应大于大多数分时用户的询间时间,即当一个交互进程正在执行时,给它的时间片值相对来说略大些,使它足以产生一个IO请求:或者时间片值略大于大多数进程从计算到IO请求之间的间隔时间。这样可使用户进程工作在最高速度上,并且也减少了进程间切换的不必要的开销,提高了处理机和I/O设备的利用率,同时也能提供较好的响应时间。
各系统的最佳时间片值是不同的,而且随着系统负荷不同而有所变化。关于时间片值的更进一步考虑和时间片轮转调度算法参阅“多级反馈队列调度算法”。
特别要注意的是,时间片是否用完的判定程序是由时钟中断处理程序激活的,因此时间片值必须大于时钟中断间隔。
4. 短进程优先(SPF)调度算法
短进程优先调度算法从进程的就绪队列中挑选那些运行时间(估计时间)最短的进程进入主存运行。这是一个非剥夺算法。它一旦选中某个短进程,就应该保证该进程尽可能快地完成运行并退出系统。这样减少了在就绪队列中等待的进程数,同时也缩短了进程的平均等待时间,提高了系统的吞吐量。但从另一方面来说,各进程的等待时间的变化范围较大,并且进程(尤其是大进程)的等待时间难以预先估计。也就是说,用户对他的进程什么时候完成心里没底。这样,当后续短进程过多时,大进程可能没有机会运行,导致“饿死”。而在先来先服务调度算法中,进程的等待和完成时间是可以预期的。
短进程优先调度算法要求事先能正确地了解一道作业或进程将运行多长时间。但通常一个进程没有这方面可供使用的信息,只能估计。在生产环境中,对于一道类似的作业可以提供大致合理的估计;而在程序开发环境中,用户难以知道他的程序大致将运行多长时间。
正因为此算法明显偏向短进程,而且进程的运行时间是估计的,所以用户可能把他的进程运行时间估计得过短,从而争取优先运行。为此,当一个进程的运行时间超过所估计的时间时,系统将停止这个进程,或对超时部分加价收费。
短进程优先调度算法和先来先服务调度算法都是非剥夺算法,因此均不适用于分时系统,因为不能保证对用户及时响应。
5. 最短剩余时间优先调度算法
最短剩余时间优先调度算法是将短进程优先调度算法用于分时环境的变形。其基本思想是,让“运行到任务完成时所需的运行时间最短”的进程优先得到处理,包括新进入系统的进程。在最短进程优先调度算法中,一个进程一旦得到处理机,就一直运行到任务完成(或等待事件)而不能被剥夺(除非主动让出处理机)。而最短剩余时间优先调度算法允许被一个新进入系统的且其运行时间短于当前运行进程的剩余运行时间的进程所抢占。
该算法的优点是,可以用于分时系统,保证及时响应用户要求。缺点是,系统开销增加。首先,要保存进程的运行情况记录,以比较其剩余时间长短:其次,剥夺本身也要消耗处理机时间。毫无疑问,这个算法使短进程一进入系统就能立即得到服务,从而缩短进程的平均等待时间。
6. 最高响应比优先调度算法
Hansen针对短进程优先调度算法的缺点提出了最高响应比优先调度算法。这是一个非剥夺的算法。按照此算法,每个进程都有一个动态优先数,该优先数不但是要求的服务时间的函数,而且是该进程得到服务所花费的等待时间的函数。进程的动态优先数计算公式如下:
优先数=(等待时间+要求的服务时间)/要求的服务时间
要求的服务时间是分母,所以对短进程是有利的,因为区的优先数高,可优先运行。另外,因为等待时间是分子,所以长进程由于其等待了较长时间,从而提高了其优先致,终于得到了处理机。进程一旦得到处理机,它就一直运行到进程完成任务(或因等待事件而主动让出处理机),中间不被抢占。
可以看出,“等待时间+要求的服务时间”是系统对作业的响应时间,所以在优先数公式中,优先数的值实际上也是响应时间与服务时间的比值,称为响应比。响应比高者得到优先调度。
7. 多级反馈队列调度算法
短进程优先调度算法或最短剩余时间优先调度算法均是在估计的进程运行时间基础上进行调度的,但在程序开发环境或其他情况下,往往难以估计进程的运行时间。这里所研究的算法是时间片轮转调度算法的发展,不必估计进程运行时间。但是本算法仍然基于以下考虑:
①为提高系统吞吐量和缩短进程的平均等待时间而照顺短进程。
②为得到较好的I/O设备利用率和对交互用户的及时响应而照顾I/O型进程。
③在进程运行过程中,按进程运行情况动态地考虑进程的性质(I/O型进程还是处理机型
进程),并且要尽可能快地决定进程当时的运行性质(以I/O为主还是以计算为主),同时进行相应的调度。
具体来说,多级反馈队列的概念如下图所示。系统中有多个进程就绪队列,每个就绪队列对应一个调度级别。第1级队列的优先级最高,以下各级队列的优先级逐次降低。调度时,选择高优先级队列中的第1个就绪进程。
各级队列中的进程具有不同的时间片值。优先级最高的第1级队列中的进程的时间片值最少;题看队列级别的增高,其进程的优先级降低了,但时间片值却增大了。通常,下放一级,其时间片值增大1倍。各级队列均按先来先服务原则排序。
调度方法:当一个新进程进入系统后,它被放入第1级队列的末尾。该队列中的进程按先来先服务原则得到处理机,并使用一个相应于该队列的时间片。假如进程在这个时间片内完成了其全部任务,或因等待事件或/O而主动放弃了处理机,该进程就撤离系统(任务完成)或进入相应的等待队列,从而离开就绪队列。若进程使用完了整个时间片后,其运行任务并未完成(也没有产生V/O请求),仍然要求运行,则该进程被剥夺处理机,同时它被放入下一级队列的末是,当第1级队列为空后,调度程序才去调度第2级队列中的进程。其调度方法同前。当第1、2爱队列全部为空后,才去调度第3级队列中的进程……当前面各级队列全部为空后,才去调度最后第n级队列中的进程。第n级(最低优先级)队列中的进程采用时间片轮转调度算法进行调度。当比运行进程更高级别的队列中到来一个新的进程时,它将抢占处理机,而被抢占的进程回到原队列的末尾。
多级反馈队列的调度操作如上所述,它根据进程运行情况的反馈信息而对队列进行组织并调度进程。但对此调度算法需要说明如下。
①照顾I/O型进程的目的在于充分利用外部设备,以及对终端交互用户及时地予以响应。
为此,通常L/O型进程会进入最高优先级队列,从而能很快得到处理机。另一方面,第1级队列
中进程的时间片值也应大于大多数I/O型进程产生一个I/O请求所需的运行时间。这样,既能
使I/O型进程得到及时处理,也避免了不必要的过多的在进程间转接处理机的操作,以减少系统开销。
②处理机型(计算型)进程由于总是用尽时间片(有些计算型进程一直运行几小时也不会产生一个IO请求),而由最高优先级队列逐次进入低优先级队列。虽然运行优先级降低了,等待时间也较长,但终究会得到较大的时间片值来运行,直至在最低一级队列中轮转。
③在有些分时系统中,一个进程由于IO操作完成而要求重新进入就绪队列,并不是将它
放入最高优先级队列,而是让它进入因I/O请求而离开的原来那一级队列。这就需要对进程所在的队列序号进行记录。这样做的好处是,有些计算型进程偶然产生一次I/O请求,I/O操作完成后仍然需要很长的处理机运行时间,为减少进程的调度次数和系统开销,就不要让它们从最高级队列逐次下移,而是直接放入原来所在队列。
但在一个大的程序中,不同的程序段有不同的运行特点。有时计算多,有时I/O操作多。
也就是说,一个计算型进程有时也可以看成IO型进程,为此在有些系统中,当进程每次由于I/0操作完成而重新进入就绪队列时,就将它放入比原来高一级的就绪队列,这样就能体现出进程由计算型向I/O型变化的状态。
进程间状态模型
当一个进程开始运行时,它可能会经历下面这几种状态
调度算法都有哪些
调度算法分为三大类:批处理中的调度、交互系统中的调度、实时系统中的调度
批处理中的调度
先来先服务
很像是先到先得。。。可能最简单的非抢占式调度算法的设计就是 先来先服务(first-come,first-serverd)
。使用此算法,将按照请求顺序为进程分配 CPU。最基本的,会有一个就绪进程的等待队列。当第一个任务从外部进入系统时,将会立即启动并允许运行任意长的时间。它不会因为运行时间太长而中断。当其他作业进入时,它们排到就绪队列尾部。当正在运行的进程阻塞,处于等待队列的第一个进程就开始运行。当一个阻塞的进程重新处于就绪态时,它会像一个新到达的任务,会排在队列的末尾,即排在所有进程最后。
img
这个算法的强大之处在于易于理解和编程,在这个算法中,一个单链表记录了所有就绪进程。要选取一个进程运行,只要从该队列的头部移走一个进程即可;要添加一个新的作业或者阻塞一个进程,只要把这个作业或进程附加在队列的末尾即可。这是很简单的一种实现。
不过,先来先服务也是有缺点的,那就是没有优先级的关系,试想一下,如果有 100 个 I/O 进程正在排队,第 101 个是一个 CPU 密集型进程,那岂不是需要等 100 个 I/O 进程运行完毕才会等到一个 CPU 密集型进程运行,这在实际情况下根本不可能,所以需要优先级或者抢占式进程的出现来优先选择重要的进程运行。
最短作业优先
批处理中,第二种调度算法是 最短作业优先(Shortest Job First)
,我们假设运行时间已知。例如,一家保险公司,因为每天要做类似的工作,所以人们可以相当精确地预测处理 1000 个索赔的一批作业需要多长时间。当输入队列中有若干个同等重要的作业被启动时,调度程序应使用最短优先作业算法
img
如上图 a 所示,这里有 4 个作业 A、B、C、D ,运行时间分别为 8、4、4、4 分钟。若按图中的次序运行,则 A 的周转时间为 8 分钟,B 为 12 分钟,C 为 16 分钟,D 为 20 分钟,平均时间内为 14 分钟。
现在考虑使用最短作业优先算法运行 4 个作业,如上图 b 所示,目前的周转时间分别为 4、8、12、20,平均为 11 分钟,可以证明最短作业优先是最优的。考虑有 4 个作业的情况,其运行时间分别为 a、b、c、d。第一个作业在时间 a 结束,第二个在时间 a + b 结束,以此类推。平均周转时间为 (4a + 3b + 2c + d) / 4 。显然 a 对平均值的影响最大,所以 a 应该是最短优先作业,其次是 b,然后是 c ,最后是 d 它就只能影响自己的周转时间了。
需要注意的是,在所有的进程都可以运行的情况下,最短作业优先的算法才是最优的。
最短剩余时间优先
最短作业优先的抢占式版本被称作为 最短剩余时间优先(Shortest Remaining Time Next)
算法。使用这个算法,调度程序总是选择剩余运行时间最短的那个进程运行。当一个新作业到达时,其整个时间同当前进程的剩余时间做比较。如果新的进程比当前运行进程需要更少的时间,当前进程就被挂起,而运行新的进程。这种方式能够使短期作业获得良好的服务。
交互式系统中的调度
交互式系统中在个人计算机、服务器和其他系统中都是很常用的,所以有必要来探讨一下交互式调度
轮询调度
一种最古老、最简单、最公平并且最广泛使用的算法就是 轮询算法(round-robin)
。每个进程都会被分配一个时间段,称为时间片(quantum)
,在这个时间片内允许进程运行。如果时间片结束时进程还在运行的话,则抢占一个 CPU 并将其分配给另一个进程。如果进程在时间片结束前阻塞或结束,则 CPU 立即进行切换。轮询算法比较容易实现。调度程序所做的就是维护一个可运行进程的列表,就像下图中的 a,当一个进程用完时间片后就被移到队列的末尾,就像下图的 b。
img
优先级调度
事实情况是不是所有的进程都是优先级相等的。例如,在一所大学中的等级制度,首先是院长,然后是教授、秘书、后勤人员,最后是学生。这种将外部情况考虑在内就实现了优先级调度(priority scheduling)
img
它的基本思想很明确,每个进程都被赋予一个优先级,优先级高的进程优先运行。
但是也不意味着高优先级的进程能够永远一直运行下去,调度程序会在每个时钟中断期间降低当前运行进程的优先级。如果此操作导致其优先级降低到下一个最高进程的优先级以下,则会发生进程切换。或者,可以为每个进程分配允许运行的最大时间间隔。当时间间隔用完后,下一个高优先级的进程会得到运行的机会。
最短进程优先
对于批处理系统而言,由于最短作业优先常常伴随着最短响应时间,一种方式是根据进程过去的行为进行推测,并执行估计运行时间最短的那一个。假设每个终端上每条命令的预估运行时间为 T0
,现在假设测量到其下一次运行时间为 T1
,可以用两个值的加权来改进估计时间,即aT0+ (1- 1)T1
。通过选择 a 的值,可以决定是尽快忘掉老的运行时间,还是在一段长时间内始终记住它们。当 a = 1/2 时,可以得到下面这个序列
img
可以看到,在三轮过后,T0 在新的估计值中所占比重下降至 1/8。
有时把这种通过当前测量值和先前估计值进行加权平均从而得到下一个估计值的技术称作 老化(aging)
。这种方法会使用很多预测值基于当前值的情况。
彩票调度
有一种既可以给出预测结果而又有一种比较简单的实现方式的算法,就是 彩票调度(lottery scheduling)
算法。他的基本思想为进程提供各种系统资源的彩票
。当做出一个调度决策的时候,就随机抽出一张彩票,拥有彩票的进程将获得资源。比如在 CPU 进行调度时,系统可以每秒持有 50 次抽奖,每个中奖进程会获得额外运行时间的奖励。
可以把彩票理解为 buff,这个 buff 有 15% 的几率能让你产生
速度之靴
的效果。
公平分享调度
如果用户 1 启动了 9 个进程,而用户 2 启动了一个进程,使用轮转或相同优先级调度算法,那么用户 1 将得到 90 % 的 CPU 时间,而用户 2 将之得到 10 % 的 CPU 时间。
为了阻止这种情况的出现,一些系统在调度前会把进程的拥有者考虑在内。在这种模型下,每个用户都会分配一些CPU 时间,而调度程序会选择进程并强制执行。因此如果两个用户每个都会有 50% 的 CPU 时间片保证,那么无论一个用户有多少个进程,都将获得相同的 CPU 份额。
影响调度程序的指标是什么
会有下面几个因素决定调度程序的好坏
- CPU 使用率:
CPU 正在执行任务(即不处于空闲状态)的时间百分比。
- 等待时间
这是进程轮流执行的时间,也就是进程切换的时间
- 吞吐量
单位时间内完成进程的数量
- 响应时间
这是从提交流程到获得有用输出所经过的时间。
- 周转时间
从提交流程到完成流程所经过的时间。
什么是 RR 调度算法
RR(round-robin)
调度算法主要针对分时系统,RR 的调度算法会把时间片以相同的部分并循环的分配给每个进程,RR 调度算法没有优先级的概念。这种算法的实现比较简单,而且每个线程都会占有时间片,并不存在线程饥饿的问题。
什么是按需分页
在操作系统中,进程是以页为单位加载到内存中的,按需分页是一种虚拟内存
的管理方式。在使用请求分页的系统中,只有在尝试访问页面所在的磁盘并且该页面尚未在内存中时,也就发生了缺页异常
,操作系统才会将磁盘页面复制到内存中。
页面置换算法都有哪些
在地址映射过程中,如果在页面中发现所要访问的页面不在内存中,那么就会产生一条缺页中断。当发生缺页中断时,如果操作系统内存中没有空闲页面,那么操作系统必须在内存选择一个页面将其移出内存,以便为即将调入的页面让出空间。而用来选择淘汰哪一页的规则叫做页面置换算法。
下面我汇总的这些页面置换算法比较齐全,只给出简单介绍,算法具体的实现和原理读者可以自行了解。
最优算法
在当前页面中置换最后要访问的页面。不幸的是,没有办法来判定哪个页面是最后一个要访问的,因此实际上该算法不能使用
。然而,它可以作为衡量其他算法的标准。NRU
算法根据 R 位和 M 位的状态将页面分为四类。从编号最小的类别中随机选择一个页面。NRU 算法易于实现,但是性能不是很好。存在更好的算法。FIFO
会跟踪页面加载进入内存中的顺序,并把页面放入一个链表中。有可能删除存在时间最长但是还在使用的页面,因此这个算法也不是一个很好的选择。第二次机会
算法是对 FIFO 的一个修改,它会在删除页面之前检查这个页面是否仍在使用。如果页面正在使用,就会进行保留。这个改进大大提高了性能。时钟
算法是第二次机会算法的另外一种实现形式,时钟算法和第二次算法的性能差不多,但是会花费更少的时间来执行算法。LRU
算法是一个非常优秀的算法,但是没有特殊的硬件(TLB)
很难实现。如果没有硬件,就不能使用 LRU 算法。NFU
算法是一种近似于 LRU 的算法,它的性能不是非常好。老化
算法是一种更接近 LRU 算法的实现,并且可以更好的实现,因此是一个很好的选择- 最后两种算法都使用了工作集算法。工作集算法提供了合理的性能开销,但是它的实现比较复杂。
WSClock
是另外一种变体,它不仅能够提供良好的性能,而且可以高效地实现
AVL 树是平衡二叉查找树,增加和删除节点后通过树形旋转重新达到平衡。右旋是以某个节点为中心,将它沉入当前右子节点的位置,而让当前的左子节点作为新树的根节点,也称为顺时针旋转。同理左旋是以某个节点为中心,将它沉入当前左子节点的位置,而让当前的右子节点作为新树的根节点,也称为逆时针旋转。
什么是红黑树?
红黑树是 1972 年发明的,称为对称二叉 B 树,1978 年正式命名红黑树。主要特征是在每个节点上增加一个属性表示节点颜色,可以红色或黑色。红黑树和 AVL 树类似,都是在进行插入和删除时通过旋转保持自身平衡,从而获得较高的查找性能。与 AVL 树相比,红黑树不追求所有递归子树的高度差不超过 1,保证从根节点到叶尾的最长路径不超过最短路径的 2 倍,所以最差时间复杂度是 O(logn)。红黑树通过重新着色和左右旋转,更加高效地完成了插入和删除之后的自平衡调整。
红黑树在本质上还是二叉查找树,它额外引入了 5 个约束条件: ① 节点只能是红色或黑色。 ② 根节点必须是黑色。 ③ 所有 NIL 节点都是黑色的。 ④ 一条路径上不能出现相邻的两个红色节点。 ⑤ 在任何递归子树中,根节点到叶子节点的所有路径上包含相同数目的黑色节点。
这五个约束条件保证了红黑树的新增、删除、查找的最坏时间复杂度均为 O(logn)。如果一个树的左子节点或右子节点不存在,则均认定为黑色。红黑树的任何旋转在 3 次之内均可完成。
AVL 树和红黑树的区别?
红黑树的平衡性不如 AVL 树,它维持的只是一种大致的平衡,不严格保证左右子树的高度差不超过 1。这导致节点数相同的情况下,红黑树的高度可能更高,也就是说平均查找次数会高于相同情况的 AVL 树。
在插入时,红黑树和 AVL 树都能在至多两次旋转内恢复平衡,在删除时由于红黑树只追求大致平衡,因此红黑树至多三次旋转可以恢复平衡,而 AVL 树最多需要 O(logn) 次。AVL 树在插入和删除时,将向上回溯确定是否需要旋转,这个回溯的时间成本最差为 O(logn),而红黑树每次向上回溯的步长为 2,回溯成本低。因此面对频繁地插入与删除红黑树更加合适。
B 树和B+ 树的区别?
B 树中每个节点同时存储 key 和 data,而 B+ 树中只有叶子节点才存储 data,非叶子节点只存储 key。InnoDB 对 B+ 树进行了优化,在每个叶子节点上增加了一个指向相邻叶子节点的链表指针,形成了带有顺序指针的 B+ 树,提高区间访问的性能。
B+ 树的优点在于: ① 由于 B+ 树在非叶子节点上不含数据信息,因此在内存页中能够存放更多的 key,数据存放得更加紧密,具有更好的空间利用率,访问叶子节点上关联的数据也具有更好的缓存命中率。 ② B+树的叶子结点都是相连的,因此对整棵树的遍历只需要一次线性遍历叶子节点即可。而 B 树则需要进行每一层的递归遍历,相邻的元素可能在内存中不相邻,所以缓存命中性没有 B+树好。但是 B 树也有优点,由于每个节点都包含 key 和 value,因此经常访问的元素可能离根节点更近,访问也更迅速。
排序有哪些分类?
排序可以分为内部排序和外部排序,在内存中进行的称为内部排序,当数据量很大时无法全部拷贝到内存需要使用外存,称为外部排序。
内部排序包括比较排序和非比较排序,比较排序包括插入/选择/交换/归并排序,非比较排序包括计数/基数/桶排序。
插入排序包括直接插入/希尔排序,选择排序包括直接选择/堆排序,交换排序包括冒泡/快速排序。
img
大顶堆怎么插入删除
插入: 在一个大顶堆之后插入新的元素可能会破坏堆的结构,此时需要找到新插入节点的父节点,对堆进行自下而上的调整使其变成一个大顶堆。 删除: 将堆的最后一个元素填充到删除元素的位置,然后调整堆结构构造出新的大顶堆
请问海量数据如何去取最大的k个
- 1.直接全部排序(只适用于内存够的情况) 当数据量较小的情况下,内存中可以容纳所有数据。则最简单也是最容易想到的方法是将数据全部排序,然后取排序后的数据中的前K个。 这种方法对数据量比较敏感,当数据量较大的情况下,内存不能完全容纳全部数据,这种方法便不适应了。即使内存能够满足要求,该方法将全部数据都排序了,而题目只要求找出topK个数据,所以该方法并不十分高效,不建议使用。
- 2.快速排序的变形 (只使用于内存够的情况) 这是一个基于快速排序的变形,因为第一种方法中说到将所有元素都排序并不十分高效,只需要找出前K个最大的就行。 这种方法类似于快速排序,首先选择一个划分元,将比这个划分元大的元素放到它的前面,比划分元小的元素放到它的后面,此时完成了一趟排序。如果此时这个划分元的序号index刚好等于K,那么这个划分元以及它左边的数,刚好就是前K个最大的元素;如果index>K,那么前K大的数据在index的左边,那么就继续递归的从index-1个数中进行一趟排序;如果index < K,那么再从划分元的右边继续进行排序,直到找到序号index刚好等于K为止。再将前K个数进行排序后,返回TopK个元素。这种方法就避免了对除了Top K个元素以外的数据进行排序所带来的不必要的开销。
- 3.最小堆法 这是一种局部淘汰法。 先读取前K个数,建立一个最小堆。然后将剩余的所有数字依次与最小堆的堆顶进行比较,如果小于或等于堆顶数据,则继续比较下一个;否则,删除堆顶元素,并将新数据插入堆中,重新调整最小堆。当遍历完全部数据后,最小堆中的数据即为最大的K个数。
- 4.分治法 将全部数据分成N份,前提是每份的数据都可以读到内存中进行处理,找到每份数据中最大的K个数。此时剩下NK个数据,如果内存不能容纳NK个数据,则再继续分治处理,分成M份,找出每份数据中最大的K个数,如果M*K个数仍然不能读到内存中,则继续分治处理。直到剩余的数可以读入内存中,那么可以对这些数使用快速排序的变形或者归并排序进行处理。
- 5.Hash法 如果这些数据中有很多重复的数据,可以先通过hash法,把重复的数去掉。这样如果重复率很高的话,会减少很大的内存用量,从而缩小运算空间。处理后的数据如果能够读入内存,则可以直接排序;否则可以使用分治法或者最小堆法来处理数据。
6、谈一谈,如何得到一个数据流中的中位数?
数据是从一个数据流中读出来的,数据的数目随着时间的变化而增加。如果用一个数据 容器来保存从流中读出来的数据,当有新的数据流中读出来时,这些数据就插入到数据容器中。 数组是最简单的容器。如果数组没有排序,可以用 Partition函数找出数组中的中位数。在没有排序的数组中插入一个数字和找出中位数的时间复杂度是 O(1)和 O(n)。 我们还可以往数组里插入新数据时让数组保持排序,这是由于可能要移动 O(n)个数,因此需要O(n)时间才能完成插入操作。在已经排好序的数组中找出中位数是一个简单的操作,只需要 O(1)时间即可完成。 排序的链表时另外一个选择。我们需要O(n)时间才能在链表中找到合适的位置插入新的数据。如果定义两个指针指向链表的中间结点(如果链表的结点数目是奇数,那么这两个指针指向同一个结点),那么可以在O(1)时间得出中位数。此时时间效率与及基于排序的数组的时间效率一样。 如果能够保证数据容器左边的数据都小于右边的数据,这样即使左、右两边内部的数据没有排序,也可以根据左边最大的数及右边最小的数得到中位数。如何快速从一个容器中找出最大数?用最大堆实现这个数据容器,因为位于堆顶的就是最大的数据。同样,也可以快速从最小堆中找出最小数。 因此可以用如下思路来解决这个问题:用一个最大堆实现左边的数据容器,用最小堆实现右边的数据容器。往堆中插入一个数据的时间效率是 O(logn)。由于只需 O(1)时间就可以得到位于堆顶的数据,因此得到中位数的时间效率是 O(1)。
7、对一千万个整数排序,整数范围在[-1000,1000]间,用什么排序最快?
在以上的情景下最好使用计数排序,计数排序的基本思想为在排序前先统计这组数中其它数小于这个数的个数,其时间复杂度为O(n+k),其中n为整数的个数,k为所有数的范围,此场景下的n>>k,所以计数排序要比其他基于的比较排序效果要好。
8、堆排序的思想
将待排序的序列构成一个大顶堆,这个时候整个序列的最大值就是堆顶的根节点,将它与末尾节点进行交换,然后末尾变成了最大值,然后剩余n-1个元素重新构成一个堆,这样得到这n个元素的次大值,反复进行以上操作便得到一个有序序列。
9、topK给出3种解法
- 1)局部淘汰法 – 借助“冒泡排序”获取TopK思路:
(1)可以避免对所有数据进行排序,只排序部分;
(2)冒泡排序是每一轮排序都会获得一个最大值,则K轮排序即可获得TopK。
时间复杂度空间复杂度:
(1)时间复杂度:排序一轮是O(N),则K次排序总时间复杂度为:O(KN)。
(2)空间复杂度:O(K),用来存放获得的topK,也可以O(1)遍历原数组的最后K个元素即可。
- 2)局部淘汰法 – 借助数据结构"堆"获取TopK思路: (
1)堆:分为大顶堆(堆顶元素大于其他所有元素)和小顶堆(堆顶其他元素小于所有其他元素)。
(2)我们使用小顶堆来实现。
(3)取出K个元素放在另外的数组中,对这K个元素进行建堆。
(4)然后循环从K下标位置遍历数据,只要元素大于堆顶,我们就将堆顶赋值为该元素,然后重新调整为小顶堆。
(5)循环完毕后,K个元素的堆数组就是我们所需要的TopK。
- 时间复杂度与空间复杂度:
(1)时间复杂度:每次对K个元素进行建堆,时间复杂度为:O(KlogK),加上N-K次的循环,则总时间复杂度为O((K+(N-K))logK),即O(NlogK),其中K为想要获取的TopK的数量N为总数据量。
(2)空间复杂度:O(K),只需要新建一个K大小的数组用来存储topK即可
- 3)分治法 – 借助”快速排序“方法获取TopK思路:
(1)比如有10亿的数据,找处Top1000,我们先将10亿的数据分成1000份,每份100万条数据。
(2)在每一份中找出对应的Top1000,整合到一个数组中,得到100万条数据,这样过滤掉了999%%的数据。
(3)使用快速排序对这100万条数据进行”一轮“排序,一轮排序之后指针的位置指向的数字假设为S,会将数组分为两部分,一部分大于S记作Si,一部分小于S记作Sj。
(4)如果Si元素个数大于1000,我们对Si数组再进行一轮排序,再次将Si分成了Si和Sj。如果Si的元素小于1000,则我们需要在Sj中获取1000-count(Si)个元素的,也就是对Sj进行排序
(5)如此递归下去即可获得TopK。
时间复杂度与空间复杂度:
(1)时间复杂度:一份获取前TopK的时间复杂度:O((N/n)logK)。则所有份数为:O(NlogK),但是分治法我们会使用多核多机的资源,比如我们有S个线程同时处理。则时间复杂度为:O((N/S)logK)。之后进行快排序,一次的时间复杂度为:O(N),假设排序了M次之后得到结果,则时间复杂度为:O(MN)。所以,总时间复杂度大约为O(MN+(N/S)logK) 。
(2)空间复杂度:需要每一份一个数组,则空间复杂度为O(N)
数据库三大范式是什么
- 第一范式:每个列都不可以再拆分。
- 第二范式:在第一范式的基础上,非主键列完全依赖于主键,而不能是依赖于主键的一部分。
- 第三范式:在第二范式的基础上,非主键列只依赖于主键,不依赖于其他非主键。
MySQL存储引擎MyISAM与InnoDB区别
- 存储引擎Storage engine:MySQL中的数据、索引以及其他对象是如何存储的,是一套文件系统的实现。
- 常用的存储引擎有以下:
- Innodb引擎:Innodb引擎提供了对数据库ACID事务的支持。并且还提供了行级锁和外键的约束。它的设计的目标就是处理大数据容量的数据库系统。
- MyIASM引擎(原本Mysql的默认引擎):不提供事务的支持,也不支持行级锁和外键。
- MEMORY引擎:所有的数据都在内存中,数据的处理速度快,但是安全性不高。
MyISAM索引与InnoDB索引的区别?
- InnoDB索引是聚簇索引,MyISAM索引是非聚簇索引。
- InnoDB的主键索引的叶子节点存储着行数据,因此主键索引非常高效。
- MyISAM索引的叶子节点存储的是行数据地址,需要再寻址一次才能得到数据。
- InnoDB非主键索引的叶子节点存储的是主键和其他带索引的列数据,因此查询时做到覆盖索引会非常高效。
索引有哪些优缺点?
索引的优点
- 可以大大加快数据的检索速度,这也是创建索引的最主要的原因。
- 通过使用索引,可以在查询的过程中,使用优化隐藏器,提高系统的性能。
索引的缺点
- 时间方面:创建索引和维护索引要耗费时间,具体地,当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,会降低增/改/删的执行效率;
- 空间方面:索引需要占物理空间。
简述有哪些索引和作用
索引的作用:通过索引可以大大的提高数据库的检索速度,改善数据库性能
- 唯一索引:不允许有俩行具有相同的值
- 主键索引:为了保持数据库表与表之间的关系
- 聚集索引:表中行的物理顺序与键值的逻辑(索引)顺序相同。
- 非聚集索引:聚集索引和非聚集索引的根本区别是表记录的排列顺序和与索引的排列顺序是否一致
- 复合索引:在创建索引时,并不是只能对一列进行创建索引,可以与主键一样,讲多个组合为索引
- 全文索引: 全文索引为在字符串数据中进行复杂的词搜索提供有效支持
使用索引查询一定能提高查询的性能吗?为什么
通常,通过索引查询数据比全表扫描要快。但是我们也必须注意到它的代价。
- 索引需要空间来存储,也需要定期维护, 每当有记录在表中增减或索引列被修改时,索引本身也会被修改。 这意味着每条记录的INSERT,DELETE,UPDATE将为此多付出4,5 次的磁盘I/O。 因为索引需要额外的存储空间和处理,那些不必要的索引反而会使查询反应时间变慢。使用索引查询不一定能提高查询性能,索引范围查询(INDEX RANGE SCAN)适用于两种情况:
- 基于一个范围的检索,一般查询返回结果集小于表中记录数的30%
- 基于非唯一性索引的检索
前缀索引
- 语法:
index(field(10))
,使用字段值的前10个字符建立索引,默认是使用字段的全部内容建立索引。 - 前提:前缀的标识度高。比如密码就适合建立前缀索引,因为密码几乎各不相同。
- 实操的难度:在于前缀截取的长度。
- 我们可以利用
select count(*)/count(distinct left(password,prefixLen));
,通过从调整prefixLen
的值(从1自增)查看不同前缀长度的一个平均匹配度,接近1时就可以了(表示一个密码的前prefixLen
个字符几乎能确定唯一一条记录)
什么是最左前缀原则?什么是最左匹配原则
- 顾名思义,就是最左优先,在创建多列索引时,要根据业务需求,where子句中使用最频繁的一列放在最左边。
- 最左前缀匹配原则,非常重要的原则,mysql会一直向右匹配直到遇到范围查询(>、<、between、like)就停止匹配,比如a = 1 and b = 2 and c > 3 and d = 4 如果建立(a,b,c,d)顺序的索引,d是用不到索引的,如果建立(a,b,d,c)的索引则都可以用到,a,b,d的顺序可以任意调整。
- =和in可以乱序,比如a = 1 and b = 2 and c = 3 建立(a,b,c)索引可以任意顺序,mysql的查询优化器会帮你优化成索引可以识别的形式
B树和B+树的区别
- 在B树中,你可以将键和值存放在内部节点和叶子节点;但在B+树中,内部节点都是键,没有值,叶子节点同时存放键和值。
- B+树的叶子节点有一条链相连,而B树的叶子节点各自独立。
在这里插入图片描述
使用B树的好处
- B树可以在内部节点同时存储键和值,因此,把频繁访问的数据放在靠近根节点的地方将会大大提高热点数据的查询效率。这种特性使得B树在特定数据重复多次查询的场景中更加高效。
使用B+树的好处
- 由于B+树的内部节点只存放键,不存放值,因此,一次读取,可以在内存页中获取更多的键,有利于更快地缩小查找范围。 B+树的叶节点由一条链相连,因此,当需要进行一次全数据遍历的时候,B+树只需要使用O(logN)时间找到最小的一个节点,然后通过链进行O(N)的顺序遍历即可。而B树则需要对树的每一层进行遍历,这会需要更多的内存置换次数,因此也就需要花费更多的时间
Hash索引和B+树所有有什么区别或者说优劣呢?
- 首先要知道Hash索引和B+树索引的底层实现原理:
- hash索引底层就是hash表,进行查找时,调用一次hash函数就可以获取到相应的键值,之后进行回表查询获得实际数据。B+树底层实现是多路平衡查找树。对于每一次的查询都是从根节点出发,查找到叶子节点方可以获得所查键值,然后根据查询判断是否需要回表查询数据。
数据库为什么使用B+树而不是B树
- B树只适合随机检索,而B+树同时支持随机检索和顺序检索;
- B+树空间利用率更高,可减少I/O次数,磁盘读写代价更低。一般来说,索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储的磁盘上。这样的话,索引查找过程中就要产生磁盘I/O消耗。B+树的内部结点并没有指向关键字具体信息的指针,只是作为索引使用,其内部结点比B树小,盘块能容纳的结点中关键字数量更多,一次性读入内存中可以查找的关键字也就越多,相对的,IO读写次数也就降低了。而IO读写次数是影响索引检索效率的最大因素;
- B+树的查询效率更加稳定。B树搜索有可能会在非叶子结点结束,越靠近根节点的记录查找时间越短,只要找到关键字即可确定记录的存在,其性能等价于在关键字全集内做一次二分查找。而在B+树中,顺序检索比较明显,随机检索时,任何关键字的查找都必须走一条从根节点到叶节点的路,所有关键字的查找路径长度相同,导致每一个关键字的查询效率相当。
- B-树在提高了磁盘IO性能的同时并没有解决元素遍历的效率低下的问题。B+树的叶子节点使用指针顺序连接在一起,只要遍历叶子节点就可以实现整棵树的遍历。而且在数据库中基于范围的查询是非常频繁的,而B树不支持这样的操作。
- 增删文件(节点)时,效率更高。因为B+树的叶子节点包含所有关键字,并以有序的链表结构存储,这样可很好提高增删效率。
B+树在满足聚簇索引和覆盖索引的时候不需要回表查询数据,
- 在B+树的索引中,叶子节点可能存储了当前的key值,也可能存储了当前的key值以及整行的数据,这就是聚簇索引和非聚簇索引。 在InnoDB中,只有主键索引是聚簇索引,如果没有主键,则挑选一个唯一键建立聚簇索引。如果没有唯一键,则隐式的生成一个键来建立聚簇索引。
当查询使用聚簇索引时,在对应的叶子节点,可以获取到整行数据,因此不用再次进行回表查询。
什么是聚簇索引?何时使用聚簇索引与非聚簇索引
- 聚簇索引:将数据存储与索引放到了一块,找到索引也就找到了数据
- 非聚簇索引:将数据存储于索引分开结构,索引结构的叶子节点指向了数据的对应行,myisam通过key_buffer把索引先缓存到内存中,当需要访问数据时(通过索引访问数据),在内存中直接搜索索引,然后通过索引找到磁盘相应数据,这也就是为什么索引不在key buffer命中时,速度慢的原因
澄清一个概念:innodb中,在聚簇索引之上创建的索引称之为辅助索引,辅助索引访问数据总是需要二次查找,非聚簇索引都是辅助索引,像复合索引、前缀索引、唯一索引,辅助索引叶子节点存储的不再是行的物理位置,而是主键值
非聚簇索引一定会回表查询吗?
- 不一定,这涉及到查询语句所要求的字段是否全部命中了索引,如果全部命中了索引,那么就不必再进行回表查询。
- 举个简单的例子,假设我们在员工表的年龄上建立了索引,那么当进行
select age from employee where age < 20
的查询时,在索引的叶子节点上,已经包含了age信息,不会再次进行回表查询。
联合索引是什么?为什么需要注意联合索引中的顺序?
- MySQL可以使用多个字段同时建立一个索引,叫做联合索引。在联合索引中,如果想要命中索引,需要按照建立索引时的字段顺序挨个使用,否则无法命中索引。
- 原子性: 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;
- 一致性: 执行事务前后,数据保持一致,多个事务对同一个数据读取的结果是相同的;
- 隔离性: 并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的;
- 持久性: 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。
什么是脏读?幻读?不可重复读?
- 脏读(Drity Read):某个事务已更新一份数据,另一个事务在此时读取了同一份数据,由于某些原因,前一个RollBack了操作,则后一个事务所读取的数据就会是不正确的。
- 不可重复读(Non-repeatable read):在一个事务的两次查询之中数据不一致,这可能是两次查询过程中间插入了一个事务更新的原有的数据。
- 幻读(Phantom Read):在一个事务的两次查询中数据笔数不一致,例如有一个事务查询了几列(Row)数据,而另一个事务却在此时插入了新的几列数据,先前的事务在接下来的查询中,就会发现有几列数据是它先前所没有的。
什么是事务的隔离级别?MySQL的默认隔离级别是什么?
为了达到事务的四大特性,数据库定义了4种不同的事务隔离级别,由低到高依次为Read uncommitted、Read committed、Repeatable read、Serializable,这四个级别可以逐个解决脏读、不可重复读、幻读这几类问题。
隔离级别 | 脏读 | 不可重复读 | 幻影读 |
---|---|---|---|
READ-UNCOMMITTED | √ | √ | √ |
READ-COMMITTED | × | √ | √ |
REPEATABLE-READ | × | × | √ |
SERIALIZABLE | × | × | × |
SQL 标准定义了四个隔离级别:
- READ-UNCOMMITTED(读取未提交): 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。
- READ-COMMITTED(读取已提交): 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。
- REPEATABLE-READ(可重复读): 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
- SERIALIZABLE(可串行化): 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。
注意:
- 这里需要注意的是:Mysql 默认采用的 REPEATABLE_READ隔离级别 Oracle 默认采用的 READ_COMMITTED隔离级别
- 事务隔离机制的实现基于锁机制和并发调度。其中并发调度使用的是MVVC(多版本并发控制),通过保存修改的旧版本信息来支持并发一致性读和回滚等特性。
- 因为隔离级别越低,事务请求的锁越少,所以大部分数据库系统的隔离级别都是READ-COMMITTED(读取提交内容):,但是你要知道的是InnoDB 存储引擎默认使用 REPEATABLE-READ(可重读)并不会有任何性能损失。
- InnoDB 存储引擎在 分布式事务 的情况下一般会用到SERIALIZABLE(可串行化)隔离级别。
从锁的类别上来讲,有共享锁和排他锁。
- 共享锁: 又叫做读锁。 当用户要进行数据的读取时,对数据加上共享锁。共享锁就是让多个线程同时获取一个锁。
- 排他锁: 又叫做写锁。 当用户要进行数据的写入时,对数据加上排他锁。排它锁也称作独占锁,一个锁在某一时刻只能被一个线程占有,其它线程必须等待锁被释放之后才可能获取到锁。排他锁只可以加一个,他和其他的排他锁,共享锁都相斥
行级锁,表级锁和页级锁对比
- 行级锁 行级锁是Mysql中锁定粒度最细的一种锁,表示只针对当前操作的行进行加锁。行级锁能大大减少数据库操作的冲突。其加锁粒度最小,但加锁的开销也最大。行级锁分为共享锁 和 排他锁。
- 特点:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
- 表级锁 表级锁是MySQL中锁定粒度最大的一种锁,表示对当前操作的整张表加锁,它实现简单,资源消耗较少,被大部分MySQL引擎支持。最常使用的MYISAM与INNODB都支持表级锁定。表级锁定分为表共享读锁(共享锁)与表独占写锁(排他锁)。
- 特点:开销小,加锁快;不会出现死锁;锁定粒度大,发出锁冲突的概率最高,并发度最低。
- 页级锁 页级锁是MySQL中锁定粒度介于行级锁和表级锁中间的一种锁。表级锁速度快,但冲突多,行级冲突少,但速度慢。所以取了折衷的页级,一次锁定相邻的一组记录。
- 特点:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般
为什么要使用视图?什么是视图?
- 为了提高复杂SQL语句的复用性和表操作的安全性,MySQL数据库管理系统提供了视图特性。所谓视图,本质上是一种虚拟表,在物理上是不存在的,其内容与真实的表相似,包含一系列带有名称的列和行数据。但是,视图并不在数据库中以储存的数据值形式存在。行和列数据来自定义视图的查询所引用基本表,并且在具体引用视图时动态生成。
- 视图使开发者只关心感兴趣的某些特定数据和所负责的特定任务,只能看到视图中所定义的数据,而不是视图所引用表中的数据,从而提高了数据库中数据的安全性。
什么是子查询
- 条件:一条SQL语句的查询结果做为另一条查询语句的条件或查询结果
- 嵌套:多条SQL语句嵌套使用,内部的SQL查询语句称为子查询。
mysql中 in 和 exists 区别
- mysql中的in语句是把外表和内表作hash 连接,而exists语句是对外表作loop循环,每次loop循环再对内表进行查询。一直大家都认为exists比in语句的效率要高,这种说法其实是不准确的。这个是要区分环境的。
- 如果查询的两个表大小相当,那么用in和exists差别不大。
- 如果两个表中一个较小,一个是大表,则子查询表大的用exists,子查询表小的用in。
- not in 和not exists:如果查询语句使用了not in,那么内外表都进行全表扫描,没有用到索引;而not extsts的子查询依然能用到表上的索引。所以无论那个表大,用not exists都比not in要快。
varchar与char的区别
char的特点
- char表示定长字符串,长度是固定的;
- 如果插入数据的长度小于char的固定长度时,则用空格填充;
- 因为长度固定,所以存取速度要比varchar快很多,甚至能快50%,但正因为其长度固定,所以会占据多余的空间,是空间换时间的做法;
- 对于char来说,最多能存放的字符个数为255,和编码无关
varchar的特点
- varchar表示可变长字符串,长度是可变的;
- 插入的数据是多长,就按照多长来存储;
- varchar在存取方面与char相反,它存取慢,因为长度不固定,但正因如此,不占据多余的空间,是时间换空间的做法;
- 对于varchar来说,最多能存放的字符个数为65532
总之,结合性能角度(char更快)和节省磁盘空间角度(varchar更小),具体情况还需具体来设计数据库才是妥当的做法。
varchar(50)中50的涵义
- 最多存放50个字符,varchar(50)和(200)存储hello所占空间一样,但后者在排序时会消耗更多内存,因为order by col采用fixed_length计算col长度(memory引擎也一样)。在早期 MySQL 版本中, 50 代表字节数,现在代表字符数。
int(20)中20的涵义
- 是指显示字符的长度。20表示最大显示宽度为20,但仍占4字节存储,存储范围不变;
- 不影响内部存储,只是影响带 zerofill 定义的 int 时,前面补多少个 0,易于报表展示
mysql为什么这么设计
- 对大多数应用没有意义,只是规定一些工具用来显示字符的个数;int(1)和int(20)存储和计算均一样;
mysql中int(10)和char(10)以及varchar(10)的区别
- int(10)的10表示显示的数据的长度,不是存储数据的大小;chart(10)和varchar(10)的10表示存储数据的大小,即表示存储多少个字符。
- char(10)表示存储定长的10个字符,不足10个就用空格补齐,占用更多的存储空间
- varchar(10)表示存储10个变长的字符,存储多少个就是多少个,空格也按一个字符存储,这一点是和char(10)的空格不同的,char(10)的空格表示占位不算一个字符
SQL优化
说出一些数据库优化方面的经验?
- 有外键约束的话会影响增删改的性能,如果应用程序可以保证数据库的完整性那就去除外键
- Sql语句全部大写,特别是列名大写,因为数据库的机制是这样的,sql语句发送到数据库服务器,数据库首先就会把sql编译成大写在执行,如果一开始就编译成大写就不需要了把sql编译成大写这个步骤了
- 如果应用程序可以保证数据库的完整性,可以不需要按照三大范式来设计数据库
- 其实可以不必要创建很多索引,索引可以加快查询速度,但是索引会消耗磁盘空间
- 如果是jdbc的话,使用PreparedStatement不使用Statement,来创建SQl,PreparedStatement的性能比Statement的速度要快,使用PreparedStatement对象SQL语句会预编译在此对象中,PreparedStatement对象可以多次高效的执行
怎么优化SQL查询语句吗
- 对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引
- 用索引可以提高查询
- SELECT子句中避免使用*号,尽量全部大写SQL
- 应尽量避免在 where 子句中对字段进行 is null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,使用 IS NOT NULL
- where 子句中使用 or 来连接条件,也会导致引擎放弃使用索引而进行全表扫描
- in 和 not in 也要慎用,否则会导致全表扫描
你怎么知道SQL语句性能是高还是低
- 查看SQL的执行时间
- 使用explain关键字可以模拟优化器执行SQL查询语句,从而知道MYSQL是如何处理你的SQL语句的。分析你的查询语句或是表结构的性能瓶颈。
如何定位及优化SQL语句的性能问题?创建的索引有没有被使用到?或者说怎么才可以知道这条语句运行很慢的原因?
- 对于低性能的SQL语句的定位,最重要也是最有效的方法就是使用执行计划,MySQL提供了explain命令来查看语句的执行计划。 我们知道,不管是哪种数据库,或者是哪种数据库引擎,在对一条SQL语句进行执行的过程中都会做很多相关的优化,对于查询语句,最重要的优化方式就是使用索引。 而执行计划,就是显示数据库引擎对于SQL语句的执行的详细情况,其中包含了是否使用索引,使用什么索引,使用的索引的相关信息等。
大表数据查询,怎么优化
- 优化shema、sql语句+索引;
- 第二加缓存,memcached, redis;
- 主从复制,读写分离;
- 垂直拆分,根据你模块的耦合度,将一个大的系统分为多个小的系统,也就是分布式系统;
- 水平切分,针对数据量大的表,这一步最麻烦,最能考验技术水平,要选择一个合理的sharding key, 为了有好的查询效率,表结构也要改动,做一定的冗余,应用也要改,sql中尽量带sharding key,将数据定位到限定的表上去查,而不是扫描全部的表;
关心过业务系统里面的sql耗时吗?统计过慢查询吗?对慢查询都怎么优化过?
- 在业务系统中,除了使用主键进行的查询,其他的我都会在测试库上测试其耗时,慢查询的统计主要由运维在做,会定期将业务中的慢查询反馈给我们。
- 慢查询的优化首先要搞明白慢的原因是什么? 是查询条件没有命中索引?是load了不需要的数据列?还是数据量太大?
所以优化也是针对这三个方向来的,
- 首先分析语句,看看是否load了额外的数据,可能是查询了多余的行并且抛弃掉了,可能是加载了许多结果中并不需要的列,对语句进行分析以及重写。
- 分析语句的执行计划,然后获得其使用索引的情况,之后修改语句或者修改索引,使得语句可以尽可能的命中索引。
- 如果对语句的优化已经无法进行,可以考虑表中的数据量是否太大,如果是的话可以进行横向或者纵向的分表。
为什么要尽量设定一个主键?
- 主键是数据库确保数据行在整张表唯一性的保障,即使业务上本张表没有主键,也建议添加一个自增长的ID列作为主键。设定了主键之后,在后续的删改查的时候可能更加快速以及确保操作数据范围安全。
如何优化查询过程中的数据访问
- 访问数据太多导致查询性能下降
- 确定应用程序是否在检索大量超过需要的数据,可能是太多行或列
- 确认MySQL服务器是否在分析大量不必要的数据行
- 避免犯如下SQL语句错误
- 避免查询不需要的数据。解决办法:使用limit解决
- 多表关联返回全部列。解决办法:指定列名
- 总是返回全部列。解决办法:避免使用SELECT *
- 重复查询相同的数据。解决办法:可以缓存数据,下次直接读取缓存
- 使用explain进行分析,如果发现查询需要扫描大量的数据,但只返回少数的行,可以通过如下技巧去优化:
- 使用索引覆盖扫描,把所有的列都放到索引中,这样存储引擎不需要回表获取对应行就可以返回结果。
- 改变数据库和表的结构,修改数据表范式
- 重写SQL语句,让优化器可以以更优的方式执行查询。
如何优化长难的查询语句
- 分析是一个复杂查询还是多个简单查询速度快
- MySQL内部每秒能扫描内存中上百万行数据,相比之下,响应数据给客户端就要慢得多
- 使用尽可能小的查询是好的,但是有时将一个大的查询分解为多个小的查询是很有必要的。
- 将一个大的查询分为多个小的相同的查询
- 一次性删除1000万的数据要比一次删除1万,暂停一会的方案更加损耗服务器开销。
- 分解关联查询,让缓存的效率更高。
- 执行单个查询可以减少锁的竞争。
- 在应用层做关联更容易对数据库进行拆分。
- 查询效率会有大幅提升。
- 较少冗余记录的查询。
优化特定类型的查询语句
- count(*)会忽略所有的列,直接统计所有列数,不要使用count(列名)
- MyISAM中,没有任何where条件的count(*)非常快。
- 当有where条件时,MyISAM的count统计不一定比其它引擎快。
- 可以使用explain查询近似值,用近似值替代count(*)
- 增加汇总表
- 使用缓存
优化关联查询
- 确定ON或者USING子句中是否有索引。
- 确保GROUP BY和ORDER BY只有一个表中的列,这样MySQL才有可能使用索引。
优化子查询
- 用关联查询替代
- 优化GROUP BY和DISTINCT
- 这两种查询据可以使用索引来优化,是最有效的优化方法
- 关联查询中,使用标识列分组的效率更高
- 如果不需要ORDER BY,进行GROUP BY时加ORDER BY NULL,MySQL不会再进行文件排序。
- WITH ROLLUP超级聚合,可以挪到应用程序处理
优化LIMIT分页
- LIMIT偏移量大的时候,查询效率较低
- 可以记录上次查询的最大ID,下次查询时直接根据该ID来查询
优化UNION查询
- UNION ALL的效率高于UNION
优化WHERE子句
- 多数数据库都是从左往右的顺序处理条件的,把能够过滤更多数据的条件放到前面,把过滤少的条件放在后面
SQL语句优化的一些方法
- 1.对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。
- 2.应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描
数据库结构优化
- 一个好的数据库设计方案对于数据库的性能往往会起到事半功倍的效果。
- 需要考虑数据冗余、查询和更新的速度、字段的数据类型是否合理等多方面的内容。
将字段很多的表分解成多个表
- 对于字段较多的表,如果有些字段的使用频率很低,可以将这些字段分离出来形成新表。
- 因为当一个表的数据量很大时,会由于使用频率低的字段的存在而变慢。
增加中间表
- 对于需要经常联合查询的表,可以建立中间表以提高查询效率。
- 通过建立中间表,将需要通过联合查询的数据插入到中间表中,然后将原来的联合查询改为对中间表的查询。
增加冗余字段
- 设计数据表时应尽量遵循范式理论的规约,尽可能的减少冗余字段,让数据库设计看起来精致、优雅。但是,合理的加入冗余字段可以提高查询速度。
- 表的规范化程度越高,表和表之间的关系越多,需要连接查询的情况也就越多,性能也就越差。
注意:
冗余字段的值在一个表中修改了,就要想办法在其他表中更新,否则就会导致数据不一致的问题
大表怎么优化?分库分表了是怎么做的?分表分库了有什么问题?有用到中间件么?他们的原理知道么?
当MySQL单表记录数过大时,数据库的CRUD性能会明显下降,一些常见的优化措施如下:
- 限定数据的范围: 务必禁止不带任何限制数据范围条件的查询语句。比如:我们当用户在查询订单历史的时候,我们可以控制在一个月的范围内。;
- 读/写分离: 经典的数据库拆分方案,主库负责写,从库负责读;
- 缓存: 使用MySQL的缓存,另外对重量级、更新少的数据可以考虑使用应用级别的缓存;
还有就是通过分库分表的方式进行优化,主要有垂直分区、垂直分表和水平分区、水平分表
1、垂直分区
- 根据数据库里面数据表的相关性进行拆分。 例如,用户表中既有用户的登录信息又有用户的基本信息,可以将用户表拆分成两个单独的表,甚至放到单独的库做分库。
- 简单来说垂直拆分是指数据表列的拆分,把一张列比较多的表拆分为多张表。 如下图所示,这样来说大家应该就更容易理解了。
在这里插入图片描述
- 垂直拆分的优点: 可以使得行数据变小,在查询时减少读取的Block数,减少I/O次数。此外,垂直分区可以简化表的结构,易于维护。
- 垂直拆分的缺点: 主键会出现冗余,需要管理冗余列,并会引起Join操作,可以通过在应用层进行Join来解决。此外,垂直分区会让事务变得更加复杂;
2、垂直分表
- 把主键和一些列放在一个表,然后把主键和另外的列放在另一个表中
在这里插入图片描述
适用场景
- 1、如果一个表中某些列常用,另外一些列不常用
- 2、可以使数据行变小,一个数据页能存储更多数据,查询时减少I/O次数
缺点
- 有些分表的策略基于应用层的逻辑算法,一旦逻辑算法改变,整个分表逻辑都会改变,扩展性较差
- 对于应用层来说,逻辑算法增加开发成本
- 管理冗余列,查询所有数据需要join操作
3、水平分区
- 保持数据表结构不变,通过某种策略存储数据分片。这样每一片数据分散到不同的表或者库中,达到了分布式的目的。 水平拆分可以支撑非常大的数据量。
- 水平拆分是指数据表行的拆分,表的行数超过200万行时,就会变慢,这时可以把一张的表的数据拆成多张表来存放。举个例子:我们可以将用户信息表拆分成多个用户信息表,这样就可以避免单一表数据量过大对性能造成影响。
在这里插入图片描述
- 水品拆分可以支持非常大的数据量。需要注意的一点是:分表仅仅是解决了单一表数据过大的问题,但由于表的数据还是在同一台机器上,其实对于提升MySQL并发能力没有什么意义,所以 水平拆分最好分库 。
- 水平拆分能够 支持非常大的数据量存储,应用端改造也少,但 分片事务难以解决 ,跨界点Join性能较差,逻辑复杂。
《Java工程师修炼之道》的作者推荐 尽量不要对数据进行分片,因为拆分会带来逻辑、部署、运维的各种复杂度 ,一般的数据表在优化得当的情况下支撑千万以下的数据量是没有太大问题的。如果实在要分片,尽量选择客户端分片架构,这样可以减少一次和中间件的网络I/O。
4、水平分表:
- 表很大,分割后可以降低在查询时需要读的数据和索引的页数,同时也降低了索引的层数,提高查询次数
在这里插入图片描述
适用场景
- 1、表中的数据本身就有独立性,例如表中分表记录各个地区的数据或者不同时期的数据,特别是有些数据常用,有些不常用。
- 2、需要把数据存放在多个介质上。
水平切分的缺点
- 1、给应用增加复杂度,通常查询时需要多个表名,查询所有数据都需UNION操作
- 2、在许多数据库应用中,这种复杂度会超过它带来的优点,查询时会增加读一个索引层的磁盘次数
数据库分片的两种常见方案:
- 客户端代理:分片逻辑在应用端,封装在jar包中,通过修改或者封装JDBC层来实现。 当当网的 Sharding-JDBC 、阿里的TDDL是两种比较常用的实现。
- 中间件代理:在应用和数据中间加了一个代理层。分片逻辑统一维护在中间件服务中。 我们现在谈的 Mycat 、360的Atlas、网易的DDB等等都是这种架构的实现。
分库分表后面临的问题
- 事务支持 分库分表后,就成了分布式事务了。如果依赖数据库本身的分布式事务管理功能去执行事务,将付出高昂的性能代价; 如果由应用程序去协助控制,形成程序逻辑上的事务,又会造成编程方面的负担。
- 跨库join
只要是进行切分,跨节点Join的问题是不可避免的。但是良好的设计和切分却可以减少此类情况的发生。解决这一问题的普遍做法是分两次查询实现。在第一次查询的结果集中找出关联数据的id,根据这些id发起第二次请求得到关联数据。 分库分表方案产品
- 跨节点的count,order by,group by以及聚合函数问题 这些是一类问题,因为它们都需要基于全部数据集合进行计算。多数的代理都不会自动处理合并工作。解决方案:与解决跨节点join问题的类似,分别在各个节点上得到结果后在应用程序端进行合并。和join不同的是每个结点的查询可以并行执行,因此很多时候它的速度要比单一大表快很多。但如果结果集很大,对应用程序内存的消耗是一个问题。
- 数据迁移,容量规划,扩容等问题 来自淘宝综合业务平台团队,它利用对2的倍数取余具有向前兼容的特性(如对4取余得1的数对2取余也是1)来分配数据,避免了行级别的数据迁移,但是依然需要进行表级别的迁移,同时对扩容规模和分表数量都有限制。总得来说,这些方案都不是十分的理想,多多少少都存在一些缺点,这也从一个侧面反映出了Sharding扩容的难度。
- ID问题
- 一旦数据库被切分到多个物理结点上,我们将不能再依赖数据库自身的主键生成机制。一方面,某个分区数据库自生成的ID无法保证在全局上是唯一的;另一方面,应用程序在插入数据之前需要先获得ID,以便进行SQL路由. 一些常见的主键生成策略
- UUID 使用UUID作主键是最简单的方案,但是缺点也是非常明显的。由于UUID非常的长,除占用大量存储空间外,最主要的问题是在索引上,在建立索引和基于索引进行查询时都存在性能问题。 Twitter的分布式自增ID算法Snowflake 在分布式系统中,需要生成全局UID的场合还是比较多的,twitter的snowflake解决了这种需求,实现也还是很简单的,除去配置信息,核心代码就是毫秒级时间41位 机器ID 10位 毫秒内序列12位。
- 跨分片的排序分页问题
一般来讲,分页时需要按照指定字段进行排序。当排序字段就是分片字段的时候,我们通过分片规则可以比较容易定位到指定的分片,而当排序字段非分片字段的时候,情况就会变得比较复杂了。为了最终结果的准确性,我们需要在不同的分片节点中将数据进行排序并返回,并将不同分片返回的结果集进行汇总和再次排序,最后再返回给用户。如下图所示:
在这里插入图片描述
MySQL的复制原理以及流程
- 主从复制:将主数据库中的DDL和DML操作通过二进制日志(BINLOG)传输到从数据库上,然后将这些日志重新执行(重做);从而使得从数据库的数据与主数据库保持一致。
主从复制的作用
- 主数据库出现问题,可以切换到从数据库。
- 可以进行数据库层面的读写分离。
- 可以在从数据库上进行日常备份。
MySQL主从复制解决的问题
- 数据分布:随意开始或停止复制,并在不同地理位置分布数据备份
- 负载均衡:降低单个服务器的压力
- 高可用和故障切换:帮助应用程序避免单点失败
- 升级测试:可以用更高版本的MySQL作为从库
MySQL主从复制工作原理
- 在主库上把数据更高记录到二进制日志
- 从库将主库的日志复制到自己的中继日志
- 从库读取中继日志的事件,将其重放到从库数据中
基本原理流程,3个线程以及之间的关联
- 主:binlog线程——记录下所有改变了数据库数据的语句,放进master上的binlog中;
- 从:io线程——在使用start slave 之后,负责从master上拉取 binlog 内容,放进自己的relay log中;
- 从:sql执行线程——执行relay log中的语句;
复制过程
在这里插入图片描述
- Binary log:主数据库的二进制日志
- Relay log:从服务器的中继日志
- master在每个事务更新数据完成之前,将该操作记录串行地写入到binlog文件中。
- salve开启一个I/O Thread,该线程在master打开一个普通连接,主要工作是binlog dump process。如果读取的进度已经跟上了master,就进入睡眠状态并等待master产生新的事件。I/O线程最终的目的是将这些事件写入到中继日志中。
- SQL Thread会读取中继日志,并顺序执行该日志中的SQL事件,从而与主数据库中的数据保持一致。
读写分离有哪些解决方案?
- 读写分离是依赖于主从复制,而主从复制又是为读写分离服务的。因为主从复制要求
slave
不能写只能读(如果对slave
执行写操作,那么show slave status
将会呈现Slave_SQL_Running=NO
,此时你需要按照前面提到的手动同步一下slave
)。
方案一
- 使用mysql-proxy代理
- 优点:直接实现读写分离和负载均衡,不用修改代码,master和slave用一样的帐号,mysql官方不建议实际生产中使用
- 缺点:降低性能, 不支持事务
方案二
- 使用AbstractRoutingDataSource+aop+annotation在dao层决定数据源。
- 如果采用了mybatis, 可以将读写分离放在ORM层,比如mybatis可以通过mybatis plugin拦截sql语句,所有的insert/update/delete都访问master库,所有的select 都访问salve库,这样对于dao层都是透明。 plugin实现时可以通过注解或者分析语句是读写方法来选定主从库。不过这样依然有一个问题, 也就是不支持事务, 所以我们还需要重写一下DataSourceTransactionManager, 将read-only的事务扔进读库, 其余的有读有写的扔进写库。
方案三
- 使用AbstractRoutingDataSource+aop+annotation在service层决定数据源,可以支持事务.
- 缺点:类内部方法通过this.xx()方式相互调用时,aop不会进行拦截,需进行特殊处理。
备份计划,mysqldump以及xtranbackup的实现原理
- (1)备份计划
- 视库的大小来定,一般来说 100G 内的库,可以考虑使用 mysqldump 来做,因为 mysqldump更加轻巧灵活,备份时间选在业务低峰期,可以每天进行都进行全量备份(mysqldump 备份出来的文件比较小,压缩之后更小)。
- 100G 以上的库,可以考虑用 xtranbackup 来做,备份速度明显要比 mysqldump 要快。一般是选择一周一个全备,其余每天进行增量备份,备份时间为业务低峰期。
- (2)备份恢复时间
- 物理备份恢复快,逻辑备份恢复慢
- 这里跟机器,尤其是硬盘的速率有关系,以下列举几个仅供参考
- 20G的2分钟(mysqldump)
- 80G的30分钟(mysqldump)
- 111G的30分钟(mysqldump)
- 288G的3小时(xtra)
- 3T的4小时(xtra)
- 逻辑导入时间一般是备份时间的5倍以上
- (3)备份恢复失败如何处理
- 首先在恢复之前就应该做足准备工作,避免恢复的时候出错。比如说备份之后的有效性检查、权限检查、空间检查等。如果万一报错,再根据报错的提示来进行相应的调整。
(4)mysqldump和xtrabackup实现原理
- mysqldump
mysqldump 属于逻辑备份。加入–single-transaction 选项可以进行一致性备份。后台进程会先设置 session 的事务隔离级别为 RR(SET SESSION TRANSACTION ISOLATION LEVELREPEATABLE READ),之后显式开启一个事务(START TRANSACTION /!40100 WITH CONSISTENTSNAPSHOT /),这样就保证了该事务里读到的数据都是事务事务时候的快照。之后再把表的数据读取出来。如果加上–master-data=1 的话,在刚开始的时候还会加一个数据库的读锁(FLUSH TABLES WITH READ LOCK),等开启事务后,再记录下数据库此时 binlog 的位置(showmaster status),马上解锁,再读取表的数据。等所有的数据都已经导完,就可以结束事务
- Xtrabackup:
xtrabackup 属于物理备份,直接拷贝表空间文件,同时不断扫描产生的 redo 日志并保存下来。最后完成 innodb 的备份后,会做一个 flush engine logs 的操作(老版本在有 bug,在5.6 上不做此操作会丢数据),确保所有的 redo log 都已经落盘(涉及到事务的两阶段提交
- 概念,因为 xtrabackup 并不拷贝 binlog,所以必须保证所有的 redo log 都落盘,否则可能会丢最后一组提交事务的数据)。这个时间点就是 innodb 完成备份的时间点,数据文件虽然不是一致性的,但是有这段时间的 redo 就可以让数据文件达到一致性(恢复的时候做的事情)。然后还需要 flush tables with read lock,把 myisam 等其他引擎的表给备份出来,备份完后解锁。这样就做到了完美的热备。
数据表损坏的修复方式有哪些?
使用 myisamchk 来修复,具体步骤:
- 1 修复前将mysql服务停止。
- 2 打开命令行方式,然后进入到mysql的/bin目录。
- 3 执行myisamchk –recover 数据库所在路径/*.MYI
使用repair table 或者 OPTIMIZE table命令来修复,REPAIR TABLE table\_name 修复表 OPTIMIZE TABLE tab
本文标签: 常见问题
版权声明:本文标题:面试常见问题汇总 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.freenas.com.cn/jishu/1729002060h1305502.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论