让秋招飞,Java 岗高频面试题盘点,站着就把 offer 给拿了
前言
咱们先随便打开一个招聘网站,看看对 Java 工程师的技能要求。
抛开其它的经验能力等等,单纯从技术,或者说知识上来讲,可以发现一些共通的地方。
Java 基础
计算机基础
数据库
常用开源框架
分布式/微服务
中间件,缓存、消息中间件
所以我们今天就分别从这些方面逐一来分析一下这些面试题,相对应的一些资料也给大伙准备好了,需要的朋友可以直接点击领取。
好了,话不多少,坐稳扶好,发车喽!
一、Java 基础篇
1. 面向对象和面向过程的区别
面向过程
优点: 性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源;比如单片机、嵌入式开发、Linux/Unix 等一般采用面向过程开发,性能是最重要的因素。缺点: 没有面向对象易维护、易复用、易扩展
面向对象
优点: 易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护缺点: 性能比面向过程低
2. Java 语言有哪些特点?
简单易学;
面向对象(封装,继承,多态);
平台无关性(Java 虚拟机实现平台无关性);
可靠性;
安全性;
支持多线程(C++语言没有内置的多线程机制,因此必须调用操作系统的多线程功能来进行多线程程序设计,而 Java 语言却提供了多线程支持);
支持网络编程并且很方便(Java 语言诞生本身就是为简化网络编程设计的,因此 Java 语言不仅支持网络编程而且很方便);
编译与解释并存;
3. 什么是 JDK?什么是 JRE?什么是 JVM?三者之间的联系与区别
这几个是 Java 中很基本很基本的东西,但是我相信一定还有很多人搞不清楚!为什么呢?因为我们大多数时候在使用现成的编译工具以及环境的时候,并没有去考虑这些东西。
JDK: 顾名思义它是给开发者提供的开发工具箱,是给程序开发者用的。它除了包括完整的 JRE(Java Runtime Environment),Java 运行环境,还包含了其他供开发者使用的工具包。
JRE: 普通用户而只需要安装 JRE(Java Runtime Environment)来运行 Java 程序。而程序开发者必须安装 JDK 来编译、调试程序。
JVM: 当我们运行一个程序时,JVM 负责将字节码转换为特定机器代码,JVM 提供了内存管理/垃圾回收和安全机制等。这种独立于硬件和操作系统,正是 java 程序可以一次编写多处执行的原因。
区别与联系:
JDK 用于开发,JRE 用于运行 java 程序 ;
JDK 和 JRE 中都包含 JVM ;
JVM 是 java 编程语言的核心并且具有平台独立性。
4. 什么是字节码?采用字节码的最大好处是什么?
先看下 java 中的编译器和解释器:
Java 中引入了虚拟机的概念,即在机器和编译程序之间加入了一层抽象的虚拟的机器。这台虚拟的机器在任何平台上都提供给编译程序一个的共同的接口。
编译程序只需要面向虚拟机,生成虚拟机能够理解的代码,然后由解释器来将虚拟机代码转换为特定系统的机器码执行。在 Java 中,这种供虚拟机理解的代码叫做 字节码(即扩展名为 .class 的文件),它不面向任何特定的处理器,只面向虚拟机。
每一种平台的解释器是不同的,但是实现的虚拟机是相同的。Java 源程序经过编译器编译后变成字节码,字节码由虚拟机解释执行,虚拟机将每一条要执行的字节码送给解释器,解释器将其翻译成特定机器上的机器码,然后在特定的机器上运行。这也就是解释了 Java 的编译与解释并存的特点。
Java 源代码---->编译器---->jvm 可执行的 Java 字节码(即虚拟指令)---->jvm---->jvm 中解释器----->机器可执行的二进制机器码---->程序运行。
采用字节码的好处:
Java 语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以 Java 程序运行时比较高效,而且,由于字节码并不专对一种特定的机器,因此,Java 程序无须重新编译便可在多种不同的计算机上运行。
5. Java 和 C++的区别
我知道很多人没学过 C++,但是面试官就是没事喜欢拿咱们 Java 和 C++比呀!没办法!!!就算没学过 C++,也要记下来!
都是面向对象的语言,都支持封装、继承和多态
Java 不提供指针来直接访问内存,程序内存更加安全
Java 的类是单继承的,C++支持多重继承;虽然 Java 的类不可以多继承,但是接口可以多继承。
Java 有自动内存管理机制,不需要程序员手动释放无用内存
6. 什么是 Java 程序的主类?应用程序和小程序的主类有何不同?
一个程序中可以有多个类,但只能有一个类是主类。在 Java 应用程序中,这个主类是指包含 main()方法的类。而在 Java 小程序中,这个主类是一个继承自系统类 JApplet 或 Applet 的子类。应用程序的主类不一定要求是 public 类,但小程序的主类要求必须是 public 类。主类是 Java 程序执行的入口点。
7. Java 应用程序与小程序之间有那些差别?
简单说应用程序是从主线程启动(也就是 main()方法)。applet 小程序没有 main 方法,主要是嵌在浏览器页面上运行(调用 init()线程或者 run()来启动),嵌入浏览器这点跟 flash 的小游戏类似。
8. 字符型常量和字符串常量的区别
形式上: 字符常量是单引号引起的一个字符 字符串常量是双引号引起的若干个字符
含义上: 字符常量相当于一个整形值(ASCII 值),可以参加表达式运算 字符串常量代表一个地址值(该字符串在内存中存放位置)
占内存大小上: 字符常量只占一个字节 字符串常量占若干个字节(至少一个字符结束标志)
9. 构造器 Constructor 是否可被 override
在讲继承的时候我们就知道父类的私有属性和构造方法并不能被继承,所以 Constructor 也就不能被 override,但是可以 overload,所以你可以看到一个类中有多个构造函数的情况。
10. 重载和重写的区别
重载: 发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同,发生在编译时。 重写: 发生在父子类中,方法名、参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类;如果父类方法访问修饰符为 private 则子类就不能重写该方法。
11. Java 面向对象编程三大特性:封装、继承、多态
封装
封装把一个对象的属性私有化,同时提供一些可以被外界访问的属性的方法,如果不想被外界方法,我们大可不必提供方法给外界访问。但是如果一个类没有提供给外界访问的方法,那么这个类也没有什么意义了。继承
继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承我们能够非常方便地复用以前的代码。关于继承如下 3 点请记住:
子类拥有父类非 private 的属性和方法。
子类可以拥有自己属性和方法,即子类可以对父类进行扩展。
子类可以用自己的方式实现父类的方法。(以后介绍)。
多态
所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。在 Java 中有两种形式可以实现多态:继承(多个子类对同一方法的重写)和接口(实现接口并覆盖接口中同一方法)。
12. String 和 StringBuffer、StringBuilder 的区别是什么?String 为什么是不可变的?
可变性
String 类中使用字符数组保存字符串,private final char value[],所以 string 对象是不可变的。StringBuilder 与 StringBuffer 都继承自 AbstractStringBuilder 类,在 AbstractStringBuilder 中也是使用字符数组保存字符串,char[]value,这两种对象都是可变的。
线程安全性
String 中的对象是不可变的,也就可以理解为常量,线程安全。AbstractStringBuilder 是 StringBuilder 与 StringBuffer 的公共父类,定义了一些字符串的基本操作,如 expandCapacity、append、insert、indexOf 等公共方法。StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。
性能
每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象。StringBuffer 每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用 StirngBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。
对于三者使用的总结:
如果要操作少量的数据用 = String 单线程操作字符串缓冲区 下操作大量数据 = StringBuilder 多线程操作字符串缓冲区 下操作大量数据 = StringBuffer
13. 自动装箱与拆箱
装箱:将基本类型用它们对应的引用类型包装起来;拆箱:将包装类型转换为基本数据类型;
14. 在一个静态方法内调用一个非静态成员为什么是非法的?
由于静态方法可以不通过对象进行调用,因此在静态方法里,不能调用其他非静态变量,也不可以访问非静态变量成员。
15. 在 Java 中定义一个不做事且没有参数的构造方法的作用
Java 程序在执行子类的构造方法之前,如果没有用 super()来调用父类特定的构造方法,则会调用父类中“没有参数的构造方法”。
因此,如果父类中只定义了有参数的构造方法,而在子类的构造方法中又没有用 super()来调用父类中特定的构造方法,则编译时将发生错误,因为 Java 程序在父类中找不到没有参数的构造方法可供执行。解决办法是在父类里加上一个不做事且没有参数的构造方法。
16. import java 和 javax 有什么区别
刚开始的时候 JavaAPI 所必需的包是 java 开头的包,javax 当时只是扩展 API 包来说使用。然而随着时间的推移,javax 逐渐的扩展成为 Java API 的组成部分。
但是,将扩展从 javax 包移动到 java 包将是太麻烦了,最终会破坏一堆现有的代码。因此,最终决定 javax 包将成为标准 API 的一部分。所以,实际上 java 和 javax 没有区别。这都是一个名字。
17. 接口和抽象类的区别是什么?
接口的方法默认是 public,所有方法在接口中不能有实现,抽象类可以有非抽象的方法
接口中的实例变量默认是 final 类型的,而抽象类中则不一定
一个类可以实现多个接口,但最多只能实现一个抽象类
一个类实现接口的话要实现接口的所有方法,而抽象类不一定
接口不能用 new 实例化,但可以声明,但是必须引用一个实现该接口的对象 从设计层面来说,抽象是对类的抽象,是一种模板设计,接口是行为的抽象,是一种行为的规范。
18. 成员变量与局部变量的区别有那些?
从语法形式上,看成员变量是属于类的,而局部变量是在方法中定义的变量或是方法的参数;成员变量可以被 public,private,static 等修饰符所修饰,而局部变量不能被访问控制修饰符及 static 所修饰;但是,成员变量和局部变量都能被 final 所修饰;
从变量在内存中的存储方式来看,成员变量是对象的一部分,而对象存在于堆内存,局部变量存在于栈内存
从变量在内存中的生存时间上看,成员变量是对象的一部分,它随着对象的创建而存在,而局部变量随着方法的调用而自动消失。
成员变量如果没有被赋初值,则会自动以类型的默认值而赋值(一种情况例外被 final 修饰但没有被 static 修饰的成员变量必须显示地赋值);而局部变量则不会自动赋值。
19. 创建一个对象用什么运算符?对象实体与对象引用有何不同?
new 运算符,new 创建对象实例(对象实例在堆内存中),对象引用指向对象实例(对象引用存放在栈内存中)。一个对象引用可以指向 0 个或 1 个对象(一根绳子可以不系气球,也可以系一个气球);一个对象可以有 n 个引用指向它(可以用 n 条绳子系住一个气球)。
20. 什么是方法的返回值?返回值在类的方法里的作用是什么?
方法的返回值是指我们获取到的某个方法体中的代码执行后产生的结果!(前提是该方法可能产生结果)。返回值的作用:接收出结果,使得它可以用于其他的操作!
21. 一个类的构造方法的作用是什么?若一个类没有声明构造方法,改程序能正确执行吗?为什么?
主要作用是完成对类对象的初始化工作。可以执行。因为一个类即使没有声明构造方法也会有默认的不带参数的构造方法。
22. 构造方法有哪些特性?
名字与类名相同;
没有返回值,但不能用 void 声明构造函数;
生成类的对象时自动执行,无需调用。
23. 静态方法和实例方法有何不同?
在外部调用静态方法时,可以使用"类名.方法名"的方式,也可以使用"对象名.方法名"的方式。而实例方法只有后面这种方式。也就是说,调用静态方法可以无需创建对象。
静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),而不允许访问实例成员变量和实例方法;实例方法则无此限制.
24. 对象的相等与指向他们的引用相等,两者有什么不同?
对象的相等 比的是内存中存放的内容是否相等而引用相等 比较的是他们指向的内存地址是否相等。
25. 在调用子类构造方法之前会先调用父类没有参数的构造方法,其目的是?
帮助子类做初始化工作。
26. ==与 equals(重要)
== : 它的作用是判断两个对象的地址是不是相等。即,判断两个对象是不是同一个对象。(基本数据类型==比较的是值,引用数据类型==比较的是内存地址)equals() : 它的作用也是判断两个对象是否相等。但它一般有两种使用情况:
情况 1:类没有覆盖 equals()方法。则通过 equals()比较该类的两个对象时,等价于通过“==”比较这两个对象。
情况 2:类覆盖了 equals()方法。一般,我们都覆盖 equals()方法来两个对象的内容相等;若它们的内容相等,则返回 true(即,认为这两个对象相等)。
举个例子:
说明:
String 中的 equals 方法是被重写过的,因为 object 的 equals 方法是比较的对象的内存地址,而 String 的 equals 方法比较的是对象的值。
当创建 String 类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个 String 对象。
27. hashCode 与 equals(重要)
面试官可能会问你:“你重写过 hashcode 和 equals 么,为什么重写 equals 时必须重写 hashCode 方法?”
hashCode()介绍
hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个 int 整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在 JDK 的 Object.java 中,这就意味着 Java 中的任何类都包含有 hashCode() 函数。
散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码!(可以快速找到所需要的对象)
为什么要有 hashCode
我们以“HashSet 如何检查重复”为例子来说明为什么要有 hashCode:
当你把对象加入 HashSet 时,HashSet 会先计算对象的 hashcode 值来判断对象加入的位置,同时也会与其他已经加入的对象的 hashcode 值作比较,如果没有相符的 hashcode,HashSet 会假设对象没有重复出现。
但是如果发现有相同 hashcode 值的对象,这时会调用 equals()方法来检查 hashcode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。(摘自我的 Java 启蒙书《Head fist java》第二版)。这样我们就大大减少了 equals 的次数,相应就大大提高了执行速度。
hashCode()与 equals()的相关规定
如果两个对象相等,则 hashcode 一定也是相同的
两个对象相等,对两个对象分别调用 equals 方法都返回 true
两个对象有相同的 hashcode 值,它们也不一定是相等的
因此,equals 方法被覆盖过,则 hashCode 方法也必须被覆盖
hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写 hashCode(),则该 class 的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)
28. Java 中的值传递和引用传递
值传递是指对象被值传递,意味着传递了对象的一个副本,即使副本被改变,也不会影响源对象。(因为值传递的时候,实际上是将实参的值复制一份给形参。)
引用传递是指对象被引用传递,意味着传递的并不是实际的对象,而是对象的引用。因此,外部对引用对象的改变会反映到所有的对象上。(因为引用传递的时候,实际上是将实参的地址值复制一份给形参。)
29. 简述线程,程序、进程的基本概念。以及他们之间关系是什么?
线程
与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。
程序
是含有指令和数据的文件,被存储在磁盘或其他的数据存储设备中,也就是说程序是静态的代码。
进程
是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。简单来说,一个进程就是一个执行中的程序,它在计算机中一个指令接着一个指令地执行着,同时,每个进程还占有某些系统资源如 CPU 时间,内存空间,文件,文件,输入输出设备的使用权等等。
换句话说,当程序在执行时,将会被操作系统载入内存中。 线程是进程划分成的更小的运行单位。线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。从另一角度来说,进程属于操作系统的范畴,主要是同一段时间内,可以同时执行一个以上的程序,而线程则是在同一程序内几乎同时执行一个以上的程序段。
30. 线程有哪些基本状态?这些状态是如何定义的?
新建(new):新创建了一个线程对象。
可运行(runnable):线程对象创建后,其他线程(比如 main 线程)调用了该对象的 start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获 取 cpu 的使用权。
运行(running):可运行状态(runnable)的线程获得了 cpu 时间片(timeslice),执行程序代码。
阻塞(block):阻塞状态是指线程因为某种原因放弃了 cpu 使用权,也即让出了 cpu timeslice,暂时停止运行。直到线程进入可运行(runnable)状态,才有 机会再次获得 cpu timeslice 转到运行(running)状态。阻塞的情况分三种:(一). 等待阻塞:运行(running)的线程执行 o.wait()方法,JVM 会把该线程放 入等待队列(waitting queue)中。(二). 同步阻塞:运行(running)的线程在获取对象的同步锁时,若该同步锁 被别的线程占用,则 JVM 会把该线程放入锁池(lock pool)中。(三). 其他阻塞: 运行(running)的线程执行 Thread.sleep(long ms)或 t.join()方法,或者发出了 I/O 请求时,JVM 会把该线程置为阻塞状态。当 sleep()状态超时 join()等待线程终止或者超时、或者 I/O 处理完毕时,线程重新转入可运行(runnable)状态。
死亡(dead):线程 run()、main()方法执行结束,或者因异常退出了 run()方法,则该线程结束生命周期。死亡的线程不可再次复生。
计算机基础篇
1、OSI,TCP/IP,五层协议的体系结构,以及各层协议
答:OSI 分层 (7 层):物理层、数据链路层、网络层、传输层、会话层、表示层、应用层。TCP/IP 分层(4 层):网络接口层、 网际层、运输层、 应用层。五层协议 (5 层):物理层、数据链路层、网络层、运输层、 应用层。每一层的协议如下:物理层:RJ45、CLOCK、IEEE802.3 (中继器,集线器)数据链路:PPP、FR、HDLC、VLAN、MAC (网桥,交换机)网络层:IP、ICMP、ARP、RARP、OSPF、IPX、RIP、IGRP、 (路由器)传输层:TCP、UDP、SPX 会话层:NFS、SQL、NETBIOS、RPC 表示层:JPEG、MPEG、ASII 应用层:FTP、DNS、Telnet、SMTP、HTTP、WWW、NFS 每一层的作用如下:物理层:通过媒介传输比特,确定机械及电气规范(比特 Bit)数据链路层:将比特组装成帧和点到点的传递(帧 Frame)网络层:负责数据包从源到宿的传递和网际互连(包 PackeT)传输层:提供端到端的可靠报文传递和错误恢复(段 Segment)会话层:建立、管理和终止会话(会话协议数据单元 SPDU)表示层:对数据进行翻译、加密和压缩(表示协议数据单元 PPDU)应用层:允许访问 OSI 环境的手段(应用协议数据单元 APDU)
2、IP 地址的分类
答:A 类地址:以 0 开头, 第一个字节范围:0~126(1.0.0.0 - 126.255.255.255);B 类地址:以 10 开头, 第一个字节范围:128~191(128.0.0.0 - 191.255.255.255);C 类地址:以 110 开头, 第一个字节范围:192~223(192.0.0.0 - 223.255.255.255);10.0.0.0—10.255.255.255, 172.16.0.0—172.31.255.255, 192.168.0.0—192.168.255.255。(Internet 上保留地址用于内部)IP 地址与子网掩码相与得到网络号
3、ARP 是地址解析协议,简单语言解释一下工作原理。
答:1:首先,每个主机都会在自己的 ARP 缓冲区中建立一个 ARP 列表,以表示 IP 地址和 MAC 地址之间的对应关系。2:当源主机要发送数据时,首先检查 ARP 列表中是否有对应 IP 地址的目的主机的 MAC 地址,如果有,则直接发送数据,如果没有,就向本网段的所有主机发送 ARP 数据包,该数据包包括的内容有:源主机 IP 地址,源主机 MAC 地址,目的主机的 IP 地址。3:当本网络的所有主机收到该 ARP 数据包时,首先检查数据包中的 IP 地址是否是自己的 IP 地址,如果不是,则忽略该数据包,如果是,则首先从数据包中取出源主机的 IP 和 MAC 地址写入到 ARP 列表中,如果已经存在,则覆盖,然后将自己的 MAC 地址写入 ARP 响应包中,告诉源主机自己是它想要找的 MAC 地址。4:源主机收到 ARP 响应包后。将目的主机的 IP 和 MAC 地址写入 ARP 列表,并利用此信息发送数据。如果源主机一直没有收到 ARP 响应数据包,表示 ARP 查询失败。广播发送 ARP 请求,单播发送 ARP 响应。
4、各种协议的介绍
答:ICMP 协议: 因特网控制报文协议。它是 TCP/IP 协议族的一个子协议,用于在 IP 主机、路由器之间传递控制消息。TFTP 协议: 是 TCP/IP 协议族中的一个用来在客户机与服务器之间进行简单文件传输的协议,提供不复杂、开销不大的文件传输服务。HTTP 协议: 超文本传输协议,是一个属于应用层的面向对象的协议,由于其简捷、快速的方式,适用于分布式超媒体信息系统。DHCP 协议: 动态主机配置协议,是一种让系统得以连接到网络上,并获取所需要的配置参数手段。NAT 协议:网络地址转换属接入广域网(WAN)技术,是一种将私有(保留)地址转化为合法 IP 地址的转换技术,DHCP 协议:一个局域网的网络协议,使用 UDP 协议工作,用途:给内部网络或网络服务供应商自动分配 IP 地址,给用户或者内部网络管理员作为对所有计算机作中央管理的手段。
5、描述 RARP 协议
答:RARP 是逆地址解析协议,作用是完成硬件地址到 IP 地址的映射,主要用于无盘工作站,因为给无盘工作站配置的 IP 地址不能保存。
工作流程:在网络中配置一台 RARP 服务器,里面保存着 IP 地址和 MAC 地址的映射关系,当无盘工作站启动后,就封装一个 RARP 数据包,里面有其 MAC 地址,然后广播到网络上去,当服务器收到请求包后,就查找对应的 MAC 地址的 IP 地址装入响应报文中发回给请求者。因为需要广播请求报文,因此 RARP 只能用于具有广播能力的网络。
6、TCP 三次握手和四次挥手的全过程
答:三次握手:
第一次握手:客户端发送 syn 包(syn=x)到服务器,并进入 SYN_SEND 状态,等待服务器确认;第二次握手:服务器收到 syn 包,必须确认客户的 SYN(ack=x+1),同时自己也发送一个 SYN 包(syn=y),即 SYN+ACK 包,此时服务器进入 SYN_RECV 状态;第三次握手:客户端收到服务器的 SYN+ACK 包,向服务器发送确认包 ACK(ack=y+1),此包发送完毕,客户端和服务器进入 ESTABLISHED 状态,完成三次握手。握手过程中传送的包里不包含数据,三次握手完毕后,客户端与服务器才正式开始传送数据。理想状态下,TCP 连接一旦建立,在通信双方中的任何一方主动关闭连接之前,TCP 连接都将被一直保持下去。
四次挥手:
与建立连接的“三次握手”类似,断开一个 TCP 连接则需要“四次握手”。第一次挥手:主动关闭方发送一个 FIN,用来关闭主动方到被动关闭方的数据传送,也就是主动关闭方告诉被动关闭方:我已经不 会再给你发数据了(当然,在 fin 包之前发送出去的数据,如果没有收到对应的 ack 确认报文,主动关闭方依然会重发这些数据),但是,此时主动关闭方还可 以接受数据。第二次挥手:被动关闭方收到 FIN 包后,发送一个 ACK 给对方,确认序号为收到序号+1(与 SYN 相同,一个 FIN 占用一个序号)。第三次挥手:被动关闭方发送一个 FIN,用来关闭被动关闭方到主动关闭方的数据传送,也就是告诉主动关闭方,我的数据也发送完了,不会再给你发数据了。第四次挥手:主动关闭方收到 FIN 后,发送一个 ACK 给被动关闭方,确认序号为收到序号+1,至此,完成四次挥手。
8、TCP 和 UDP 的区别?
答:1)、TCP 提供面向连接的、可靠的数据流传输,而 UDP 提供的是非面向连接的、不可靠的数据流传输。2)、TCP 传输单位称为 TCP 报文段,UDP 传输单位称为用户数据报。3)、TCP 注重数据安全性,UDP 数据传输快,因为不需要连接等待,少了许多操作,但是其安全性却一般。
TCP 对应的协议和 UDP 对应的协议
TCP 对应的协议:(1) FTP:定义了文件传输协议,使用 21 端口。(2) Telnet:一种用于远程登陆的端口,使用 23 端口,用户可以以自己的身份远程连接到计算机上,可提供基于 DOS 模式下的通信服务。(3) SMTP:邮件传送协议,用于发送邮件。服务器开放的是 25 号端口。(4) POP3:它是和 SMTP 对应,POP3 用于接收邮件。POP3 协议所用的是 110 端口。(5)HTTP:是从 Web 服务器传输超文本到本地浏览器的传送协议。
UDP 对应的协议:(1) DNS:用于域名解析服务,将域名地址转换为 IP 地址。DNS 用的是 53 号端口。(2) SNMP:简单网络管理协议,使用 161 号端口,是用来管理网络设备的。由于网络设备很多,无连接的服务就体现出其优势。(3) TFTP(Trival File Tran 敏感词 er Protocal),简单文件传输协议,该协议在熟知端口 69 上使用 UDP 服务。
9、DNS 域名系统,简单描述其工作原理。
答:当 DNS 客户机需要在程序中使用名称时,它会查询 DNS 服务器来解析该名称。客户机发送的每条查询信息包括三条信息:包括:指定的 DNS 域名,指定的查询类型,DNS 域名的指定类别。基于 UDP 服务,端口 53. 该应用一般不直接为用户使用,而是为其他应用服务,如 HTTP,SMTP 等在其中需要完成主机名到 IP 地址的转换。
面向连接和非面向连接的服务的特点是什么?
面向连接的服务,通信双方在进行通信之前,要先在双方建立起一个完整的可以彼此沟通的通道,在通信过程中,整个连接的情况一直可以被实时地监控和管理。非面向连接的服务,不需要预先建立一个联络两个通信节点的连接,需要通信的时候,发送节点就可以往网络上发送信息,让信息自主地在网络上去传,一般在传输的过程中不再加以监控。
10、TCP 的三次握手过程?为什么会采用三次握手,若采用二次握手可以吗?
答:建立连接的过程是利用客户服务器模式,假设主机 A 为客户端,主机 B 为服务器端。(1)TCP 的三次握手过程:主机 A 向 B 发送连接请求;主机 B 对收到的主机 A 的报文段进行确认;主机 A 再次对主机 B 的确认进行确认。(2)采用三次握手是为了防止失效的连接请求报文段突然又传送到主机 B,因而产生错误。失效的连接请求报文段是指:主机 A 发出的连接请求没有收到主机 B 的确认,于是经过一段时间后,主机 A 又重新向主机 B 发送连接请求,且建立成功,顺序完成数据传输。考虑这样一种特殊情况,主机 A 第一次发送的连接请求并没有丢失,而是因为网络节点导致延迟达到主机 B,主机 B 以为是主机 A 又发起的新连接,于是主机 B 同意连接,并向主机 A 发回确认,但是此时主机 A 根本不会理会,主机 B 就一直在等待主机 A 发送数据,导致主机 B 的资源浪费。(3)采用两次握手不行,原因就是上面说的实效的连接请求的特殊情况。
11、了解交换机、路由器、网关的概念,并知道各自的用途
答:
1)交换机
在计算机网络系统中,交换机是针对共享工作模式的弱点而推出的。交换机拥有一条高带宽的背部总线和内部交换矩阵。交换机的所有的端口都挂接在这条背 部总线上,当控制电路收到数据包以后,处理端口会查找内存中的地址对照表以确定目的 MAC(网卡的硬件地址)的 NIC(网卡)挂接在哪个端口上,通过内部 交换矩阵迅速将数据包传送到目的端口。目的 MAC 若不存在,交换机才广播到所有的端口,接收端口回应后交换机会“学习”新的地址,并把它添加入内部地址表 中。
交换机工作于 OSI 参考模型的第二层,即数据链路层。交换机内部的 CPU 会在每个端口成功连接时,通过 ARP 协议学习它的 MAC 地址,保存成一张 ARP 表。在今后的通讯中,发往该 MAC 地址的数据包将仅送往其对应的端口,而不是所有的端口。因此,交换机可用于划分数据链路层广播,即冲突域;但它不 能划分网络层广播,即广播域。
交换机被广泛应用于二层网络交换,俗称“二层交换机”。
交换机的种类有:二层交换机、三层交换机、四层交换机、七层交换机分别工作在 OSI 七层模型中的第二层、第三层、第四层盒第七层,并因此而得名。
2)路由器
路由器(Router)是一种计算机网络设备,提供了路由与转送两种重要机制,可以决定数据包从来源端到目的端所经过 的路由路径(host 到 host 之间的传输路径),这个过程称为路由;将路由器输入端的数据包移送至适当的路由器输出端(在路由器内部进行),这称为转 送。路由工作在 OSI 模型的第三层——即网络层,例如网际协议。
路由器的一个作用是连通不同的网络,另一个作用是选择信息传送的线路。 路由器与交换器的差别,路由器是属于 OSI 第三层的产品,交换器是 OSI 第二层的产品(这里特指二层交换机)。
3)网关
网关(Gateway),网关顾名思义就是连接两个网络的设备,区别于路由器(由于历史的原因,许多有关 TCP/IP 的文献曾经把网络层使用的路由器(Router)称为网关,在今天很多局域网采用都是路由来接入网络,因此现在通常指的网关就是路由器的 IP),经常在家 庭中或者小型企业网络中使用,用于连接局域网和 Internet。
网关也经常指把一种协议转成另一种协议的设备,比如语音网关。
在传统 TCP/IP 术语中,网络设备只分成两种,一种为网关(gateway),另一种为主机(host)。网关能在网络间转递数据包,但主机不能 转送数据包。在主机(又称终端系统,end system)中,数据包需经过 TCP/IP 四层协议处理,但是在网关(又称中介系 统,intermediate system)只需要到达网际层(Internet layer),决定路径之后就可以转送。在当时,网关 (gateway)与路由器(router)还没有区别。在现代网络术语中,网关(gateway)与路由器(router)的定义不同。网关(gateway)能在不同协议间移动数据,而路由器(router)是在不同网络间移动数据,相当于传统所说的 IP 网关(IP gateway)。
网关是连接两个网络的设备,对于语音网关来说,他可以连接 PSTN 网络和以太网,这就相当于 VOIP,把不同电话中的模拟信号通过网关而转换成数字信号,而且加入协议再去传输。在到了接收端的时候再通过网关还原成模拟的电话信号,最后才能在电话机上听到。
对于以太网中的网关只能转发三层以上数据包,这一点和路由是一样的。而不同的是网关中并没有路由表,他只能按照预先设定的不同网段来进行转发。网关最重要的一点就是端口映射,子网内用户在外网看来只是外网的 IP 地址对应着不同的端口,这样看来就会保护子网内的用户。
三、数据库篇
mysql
1、数据库 sql 语句查询,跨表查询有哪几种方式
内连接(inner 可以不写)
这就是内连接,它要求数据必须 On 条件必须百分百匹配才会符合条件并返回。当不满足时,他会返回空。
外连接是用左\右侧的数据去关联另一侧的数据,即使关联不上任何数据也得把左\右侧的数据返回回来。
外连接分(左外连接)和(右外连接)
左外连接( left join)
右外连接(right join--空值的会显示出来)
全外连接(full outer(可以不写) join--空值的会显示出来)
交叉连接(笛卡尔积)查询所有的值
2、数据库的索引用到的是什么数据结构?
答:B+树
问:那么 B+树的特点是什么?为什么要用这个数据结构?
B+树是 B 树的变种,他们可以是 23 树,234 树,2345 树等等,当单个节点允许伸出 1200 节点时,三层就可以有 17 亿,因此它体型扁平。。。有利益磁盘 IO
B+树非叶子结点不存储数据,B 树存储数据,所以相同大小数据块,能存更多 B+索引
B+树叶子结点上有双向链表串联,有利于进行范围搜索
B+树为什么有利于磁盘 IO?
首先了解一下计算机的空间局部性原理:当一个数据被用到时,其附近的数据也通常会马上被使用。即使只需要一个字节,磁盘也会从这个位置开始,顺序向后读取一定长度的数据放入内存。
使用红黑树(平衡二叉树)结构的话,每次磁盘预读中的很多数据是用不上的数据。因此,它没能利用好磁盘预读的提供的数据。然后又由于深度大(较 B 树而言),所以进行的磁盘 IO 操作更多。
B 树的每个节点可以存储多个关键字,它将节点大小设置为磁盘页的大小,充分利用了磁盘预读的功能。每次读取磁盘页时就会读取一整个节点。也正因每个节点存储着非常多个关键字,树的深度就会非常的小。进而要执行的磁盘读取操作次数就会非常少,更多的是在内存中对读取进来的数据进行查找。
B 树的查询,主要发生在内存中,而平衡二叉树的查询,则是发生在磁盘读取中。因此,虽然 B 树查询查询的次数不比平衡二叉树的次数少,但是相比起磁盘 IO 速度,内存中比较的耗时就可以忽略不计了。因此,B 树更适合作为索引。
比 B 树更适合作为索引的结构是 B+树。MySQL 中也是使用 B+树作为索引。它是 B 树的变种,因此是基于 B 树来改进的。为什么 B+树会比 B 树更加优秀呢?
B 树:有序数组+平衡多叉树。
B+树:有序数组链表+平衡多叉树。
B+树的关键字全部存放在叶子节点中,这样非叶子结点就能在相同的空间存储更多的信息,非叶子节点用来做索引,而叶子节点中有一个指针指向一下个叶子节点。做这个优化的目的是为了提高区间访问的性能。而正是这个特性决定了 B+树更适合用来存储外部数据。
3、mylsam、innodb 的区别
1.InnoDB 和 MyISAM 都是 B+数的结构。
2.InnoDB 采用 MVCC 来支持高并发,并且实现了四个标准的隔离级别。其默认级别是 REPETABLE READ (可重复读),并且通过间隙锁策略防止幻读的出现。
3.InnoDB 表是基于聚簇索引建立的。
4.InnoDB 支持事务。
5.InnoDB 具有自动崩溃恢复功能。
6.InnoDB 支持外键。
MyISAM
1.MyISAM 不支持事务和行级锁。
2.崩溃后无法安全恢复。
3.对于只读的数据,或者表比较小,可以忍受修复操作的可以使用。
4.MyISAM 会将表存储在两个文件中,数据文件和索引文件,分别以.MYD 和.MYI 为扩展名。
5.MyISAM 支持全文索引。
4、MySQL 的 Gtid 复制原理是什么?
mysql 主从复制原理就是主库创建一个专门用于给从库拉取 binlog 的账号,并且给这个账号授权,让他可以拉取哪个 DB 的那个表的 binlog,具体的授权 SQL 是:
这样从库就能登陆主库拉取 binlog,那拉取 binlog 就得知道从哪个 binlog 的哪个位点拉取,现有的有两个方案:fileName + position 还有就是通过 gtid 自动找点。
什么是 GTID?原理?
5、同步、半同步、异步复制原理是什么?
同步、半同步、异步复制说的是 从库在主库上拉取 binlog 日志的模式。
同步:
主库写 redolog 事物处于 prepare 状态、主库写 binlog,然后从库拉取 binlog 去回放,从库回放成功后返回给主库 ack 确认,所有的从库都完成回放后主库提交事物。这样是可以保证主从数据一致的但是缺点就是速度太慢了。
半同步:
主库写 redolog 事物处于 prepare 状态、主库写 binlog,然后从库拉取 binlog 后返回给主库 ack,在众多从库中只要收到一个 ack 主库就提交事物
异步复制:
主库根本不管从库有没有拉取回放 binlog,直接写 redo、binlog、然后提交事物
首先不允许出现主从数据不一致的情况:如果主从不一致对业务来说是有损的,一旦发生主从数据不一致的情况,从库就会出现断开连接的可能。
6、说说你了解的 MySQL 慢查询?
MySQL 有监控项:slow query , MySQL 会将所有执行时间超过阈值的 SQL 记录到慢查日志中
我们的监控系统可以监控: 当检测到有慢查时触发报警
通常出现慢查到情况如下:
1、表中的数据量很大,而且 SQL 的执行没有走索引
2、数据量太大了,即使走了索引依然超过了阈值
3、大量的慢查占据 MySQL 连接,导致正常的 SQL 得不到连接执行从而变成慢查 SQL
4、优化器选错了索引
查看慢查时间阈值
查看执行时间最长的 10 条 SQL
7、说说 MySQL 的执行计划
什么是执行计划
每次提交一个 SQL 到 MySQL,MySQL 内核的查询优化器会针对这个 SQL 的语意生成一个执行计划,这个执行计划就代表了他会查哪些表?用哪些索引,如何做排序和分组
执行不同的 sql 有哪几种情况
单表查询举例:
示例 1:
id 是主键、name 是唯一索引像这种可以直接根据聚簇索引或者二级索引+回表就能查询到我们想要的数据的方式在执行计划中称为 const
要求二级索引必须是唯一索引,才属于 const
示例 2:
name 是普通索引查询的过程是:从 name 这个 B+树中查询出一条记录后,得到记录的主键 id,然后去聚簇索引中回表,这种查询方式速度也很快,在执行计划中叫:ref
示例 3:
name、age、xx 为普通索引这种 sql 要求 where 条件之后的 SQL 全得是等值比较,在执行计划中才算做是 ref
示例 4:
name 为普通索引这种 sql 就是在二级索引中同时搜索 name = x 和 name = null 的值,然后去主库中回表。这种在执行计划中被称为 ref_or_null
示例 5:
age 是普通索引像这样使用普通索引做范围查询,在执行计划中称为 range
示例 6:index 方式
index 方式并不是说执行计划使用了索引,从聚簇索引中一路二分往下走。
假设有联合索引:key(x1,x2,x3)
查询语句如下:
想使用联合索引得遵循左前缀原则,但是上面直接使用 x2,很显然不符合左前缀原则,所以就用不上联合索引,但是他查询的 x1、x2、x3 其实对应联合索引中的 x1、x2、x3 所以他会去扫描 联合索引:key(x1,x2,x3)形成的 B+树,而不是全表扫描,在执行计划中这就做 index
所以说,index 其实是去遍历二级索引,故他的效率肯定比 ref,const、ref_or_null 慢,但是比全表扫描快一些
示例 7:all
比如你去查找数据但是不加 where 条件,就会进行全表扫描
示例 8:
然后你的联合索引是 key1(x1,x3) key2(x2,x4)
这时查询优化器只能在 key1 和 key2 中二选一使用,具体选哪一个就看使用哪个索引扫描行数少
比如使用 x1 扫描行数少,就先拿着 x1 去过滤一部分数据出来(ref 的方式)然后去聚簇索引中回表查询所有的数据在内存中根据第二个条件 x2 > yy 再过滤一次
示例 9:
只有 c1 有索引查询优化器会根据 x1,通过 ref 的方式查找到一批数据,然后去聚簇索引中回表,将所有符合条件的数据加载进内存,然后在内存中根据剩下的条件继续过滤。
示例 10:
x1 和 x2 都有普通索引情况 1: 查询优化器使用 x1 索引在二级索引中查询中一批数据,然后将这些数据放到聚簇索引中回表,将数据所有字段查询出来,然后在内存中根据 x2=xxx 再过滤。
情况 2:查询优化器使用 x1 索引在二级索引中查询中一批数据 A,再使用 x2 索引在二级索引中查询中一批数据 B,两者做交集,再去聚簇索引中回表,这样的效率会更高。
多表:
示例 1:
第一步:查询优化器会根据 t1.x1 = xxx 这个条件查询出一部分数据,具体通过 ref、index、conf、all 根据你索引的情况而定。
假设第一步拿出来了两条记录,然后拿着这两条记录的 x2 值和 x3 值去 t2 表中去匹配有没有一样的,有的话就关联起来返回,其中 t1 叫做驱动表,t2 叫做被驱动表。
示例 2:
嵌套循环查询:简单来说就是从驱动表中查询一批数据,然后遍历这批数据挨个去被驱动表中查询。
这时如果被驱动表中的使用的该字段没有加索引,每次查询都是 all,就会导致连表查询速度很慢,因此最好两者都建立索引。
explain 时你会关注哪几个字段?
答:6 个,如下
id:每一个 selct 语句都有有一个 id,复杂的 SQL 有多个 select,就会对应有多个 id
select_type: 当前 sql 的查询类型
type:ref、index、all、const
possible_keys 可以使用的索引都会放在这里
rows:扫描的行数
table:查询的哪张表
8、说说 MySQL 支持的数据类型
INT(6),6 即是其宽度指示器,该宽度指示器并不会影响 int 列存储字段的大小,也就是说,超过 6 位它不会自动截取,依然会存储,只有超过它本身的存储范围才会截取;此处宽度指示器的作用在于该字段是否有 zerofill,如果有就未满足 6 位的部分就会用 0 来填充),
CHAR 类型用于定长字符串,并且必须在圆括号内用一个大小修饰符来定义。这个大小修饰符的范围从 0-255。比指定长度大的值将被截短,而比指定长度小的值将会用空格作填补。
CHAR 类型的一个变体是 VARCHAR 类型。它是一种可变长度的字符串类型,并且也必须带有一个范围指示器。
CHAR 和 VARCHGAR 不同之处在于 MYSQL 数据库处理这个指示器的方式:CHAR 把这个大小视为值的大小,不长度不足的情况下就用空格补足。而 VARCHAR 类型把它视为最大值并且只使用存储字符串实际需要的长度(增加一个额外字节来存储字符串本身的长度)来存储值。所以短于指示器长度的 VARCHAR 类型不会被空格填补,但长于指示器的值仍然会被截短。
9. 了解数据库如何备份吗
备份整个数据库
备份数据库中的某个表
备份多个数据库
备份系统中所有数据库
10. Oracle 和 Mysql 的区别
宏观上:
Mysql 是小型数据库, 开源, 免费, Oracle 收费
Oracle 支持大并发, 大访问量
MySql 中安装后占用的内存小, Oracle 不仅占用内存大, 而且越用越大
微观上:
Mysql 对事务默认不支持, 但是它的存储引擎 InnoDB 支持事务, Oracle 对事务完全支持
并发性: MySQL 早期的数据引擎 MyISAM 是支持表级锁, 后来的 InnoDB 才支持行级锁, Oracle 支持行级锁
Oracle 会将提交的 sql 写入连接日志中, 然后写入磁盘, 保证不会丢失数据, MySql 在执行更新的操作时可能会丢失数据
隔离级别不同:
a. Oracle 默认使用 read commited 读已经提交
b. MySQL 默认使用的是 repeatable read 可重复读
提交方式
a. Oracle 默认不会自动提交事务
b. MySQL 默认自动提交事务
逻辑备份
a. Mysql 的数据备份会锁定数据, 影响正常的 DML
b. Oracle 在数据备份时, 不会锁定任何数据
数据插入
a. Mysql 会更加灵活一点, 比如 limit 分页, insert 插入多行数据
b. Oracle 的分页使用伪列+子查询实现 , 插入数据也只能一行行插入
权限控制:
a. Oracle 的权限控制是中规中矩的, 和系统用户无关
b. MySQL 的权限控制和主机相关, 感觉没啥意义
性能诊断
a. Oracle 有大量的性能诊断工具, 可以实现自动分析
b. Mysql 性能诊断方法很少, 主要就是通过通过慢查询日志去排查
分区表和分区索引
数据复制
11. 事务的四种特性
ACID:
Atomic 原子性: 事务不能被分割, 要么都做, 要么都不做。
Consistency 一致性: 可以用转账的例子解释一致性。
Isolation 隔离性 : 不同的事务, 彼此隔离, 互不干扰。
Durability 持久性: 也叫做用就行, 事务一旦被提交, 对数据库做出的修改将被持久化 。
12. 四种隔离级别以及什么是脏读,幻读,不可重复读
read uncommitted 读未提交: 在事务 A 中读取到了事务 B 中未提交的数据, 也叫做脏读。
read commited 读已提交: Oracle 默认使用的隔离级别, 读已提交, 说白了, 事务 A 先开启, 然后事务 B 再开启, 然后事务 Bcommit 一个事务操作, 修改数据 , 那么这个修改是能被事务 A 读取到的, 这就叫做读已提交, 也是所谓的不可重复读,(因为重复读之后, 数据可能会发生变化)。
repeatable read : 可重复读, 这也是 Mysql 默认的事务隔离级别, 事务 A 开启后, 无论读取多少次, 得到的结果都和第一次得到的结果是一样的, 但是如果事务 B 在事务 A 第一次读取的范围内插入了一条数据的话, 会发生幻读, 两次读取结果又不一致了, Mysql 的 InnoDB 引擎通过多版本并发控制 MVCC 解决了这个问题。
serializable : 可串行化, 最高的事务隔离级别, 到是也是效率最低的事务隔离级别。
13. MySQL 中 主键索引、普通索引、唯一索引的区别
主键索引 primary key:
一个表只能有一个主键索引。
主键索引不能为空。
主键索引可以做外键。
唯一索引 unique key:
一张表可以存在多个唯一索引。
唯一索引可以是一列或者多列。
唯一索引不可重复的。
因为这个原因, 限制唯一索引做多有一个 null。
普通索引 normal key :
普通一般是为了加快数据的访问速度而建立的。
针对那些经常被查询, 或者经常被排序的字段建立。
被索引的数据允许出现重复的值。
14. 数据库三大范式
第一大范式:
关系模式 R 中的所有属性都不能再分解, 称关系模式 R 满足第一范式, 比如 address 字段就可以继续拆分成 省市区, 我们就可以认为 address 不满足第一范式。
第二大范式:
在满足第一范式的基础上更进一步, 它要求所有的非主属性都必须完全依赖于第一范式中确定下来的主属性, 换句话说, 比如联合主键就不符合第二范式, 因为很有可能这个表中的一部分非主属性和联合主键中的一部分列是有依赖关系的, 而和另外一部分并没有依赖关系。
第三大范式:
在第一范式 R 的基础上, 更进一步, 要求所有的字段都可主键直接相关而不能间接相关, 比如用户表里面不要出现订单表中的订单信息。
15. sql 语句各种条件的执行顺序,如 select, where, order by, group by
16. 求表的 size,或做数据统计可用什么存储引擎
查询数据表所占的容量
查询所有数据的大小, 用兆的方式输出结果
17. 读多写少可用什么引擎
MyISAM 它在设计之时就考虑到 数据库被查询的次数要远大于更新的次数。因此,ISAM 执行读取操作的速度很快,而且不占用大量的内存和存储资源。
所以, 如果系统中的写操作真的很少,并且不使用 mysql 的事务等高级操作的话, 建议使用 MYISAM。
18. 假如要统计多个表应该用什么引擎
考虑报表引擎
19.MySQL Explain 各字段意思
20.索引设计的原则?
适合索引的列是出现在 where 子句中的列,或者连接子句中指定的列。
基数较小的类,索引效果较差,没有必要在此列建立索引。
使用短索引,如果对长字符串列进行索引,应该指定一个前缀长度,这样能够节省大量索引空间。
不要过度索引。索引需要额外的磁盘空间,并降低写操作的性能。在修改表内容的时候,索引会进行更新甚至重构,索引列越多,这个时间就会越长。所以只保持需要的索引有利于查询即可。
Redis
1. redis 能存哪些类型
2、redis 为什么这么快? 高并发如何处理的?
高并发的原因:
1.redis 是基于内存的,内存的读写速度非常快;
2.redis 是单线程的,省去了很多上下文切换线程的时间;
3.redis 使用多路复用技术,可以处理并发的连接。非阻塞 IO 内部实现采用 epoll,采用了 epoll+自己实现的简单的事件框架。epoll 中的读、写、关闭、连接都转化成了事件,然后利用 epoll 的多路复用特性,绝不在 io 上浪费一点时间。
为什么 Redis 是单线程的:
官方答案: 因为 Redis 是基于内存的操作,CPU 不是 Redis 的瓶颈,Redis 的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且 CPU 不会成为瓶颈,那就顺理成章地采用单线程的方案了。
不需要各种锁的性能消耗
Redis 的数据结构并不全是简单的 Key-Value,还有 list,hash 等复杂的结构,这些结构有可能会进行很细粒度的操作,比如在很长的列表后面添加一个元素,在 hash 当中添加或者删除
一个对象。这些操作可能就需要加非常多的锁,导致的结果是同步开销大大增加。
总之,在单线程的情况下,就不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗。
CPU 消耗:
采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU。
但是如果 CPU 成为 Redis 瓶颈,或者不想让服务器其他 CUP 核闲置,那怎么办?
可以考虑多起几个 Redis 进程,Redis 是 key-value 数据库,不是关系数据库,数据之间没有约束。只要客户端分清哪些 key 放在哪个 Redis 进程上就可以了。
3.过期键的删除策略
我们都知道,Redis 是 key-value 数据库,我们可以设置 Redis 中缓存的 key 的过期时间。Redis 的过期策略就是指当 Redis 中缓存的 key 过期了,Redis 如何处理。
过期策略通常有以下三种:
定时过期:每个设置过期时间的 key 都需要创建一个定时器,到过期时间就会立即清除。该策略可以立即清除过期的数据,对内存很友好;但是会占用大量的 CPU 资源去处理过期的数据,从而影响缓存的响应时间和吞吐量。
惰性过期:只有当访问一个 key 时,才会判断该 key 是否已过期,过期则清除。该策略可以最大化地节省 CPU 资源,却对内存非常不友好。极端情况可能出现大量的过期 key 没有再次被访问,从而不会被清除,占用大量内存。
定期过期:每隔一定的时间,会扫描一定数量的数据库的 expires 字典中一定数量的 key,并清除其中已过期的 key。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得 CPU 和内存资源达到最优的平衡效果。
(expires 字典会保存所有设置了过期时间的 key 的过期时间数据,其中,key 是指向键空间中的某个键的指针,value 是该键的毫秒精度的 UNIX 时间戳表示的过期时间。键空间是指该 Redis 集群中保存的所有键。)
Redis 中同时使用了惰性过期和定期过期两种过期策略。
4.Redis 的内存淘汰策略有哪些
Redis 的内存淘汰策略是指在 Redis 的用于缓存的内存不足时,怎么处理需要新写入且需要申请额外空间的数据。
全局的键空间选择性移除
noeviction:当内存不足以容纳新写入数据时,新写入操作会报错。
allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 key。(这个是最常用的)
allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个 key。
设置过期时间的键空间选择性移除
volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的 key。
volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个 key。
volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的 key 优先移除。
5. RedLock
Redis 官方站提出了一种权威的基于 Redis 实现分布式锁的方式名叫 Redlock,此种方式比原先的单节点的方法更安全。它可以保证以下特性:
安全特性:互斥访问,即永远只有一个 client 能拿到锁。
避免死锁:最终 client 都可能拿到锁,不会出现死锁的情况,即使原本锁住某资源的 client crash 了或者出现了网络分区。
容错性:只要大部分 Redis 节点存活就可以正常提供服务。
6. Redis 缓存异常--缓存雪崩
缓存雪崩是指缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉。
解决方案
缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
一般并发量不是特别多的时候,使用最多的解决方案是加锁排队。
给每一个缓存数据增加相应的缓存标记,记录缓存的是否失效,如果缓存标记失效,则更新数据缓存。
7. Redis 缓存异常--缓存穿透
缓存穿透是指缓存和数据库中都没有的数据,导致所有的请求都落到数据库上,造成数据库短时间内承受大量请求而崩掉。
解决方案
接口层增加校验,如用户鉴权校验,id 做基础校验,id<=0 的直接拦截;
从缓存取不到的数据,在数据库中也没有取到,这时也可以将 key-value 对写为 key-null,缓存有效时间可以设置短点,如 30 秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个 id 暴力攻击
采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的 bitmap 中,一个一定不存在的数据会被这个 bitmap 拦截掉,从而避免了对底层存储系统的查询压力
附加
对于空间的利用到达了一种极致,那就是 Bitmap 和布隆过滤器(Bloom Filter)。
Bitmap: 典型的就是哈希表
缺点是:Bitmap 对于每个元素只能记录 1bit 信息,如果还想完成额外的功能,恐怕只能靠牺牲更多的空间、时间来完成了。
布隆过滤器(推荐)
就是引入了k(k>1)k(k>1)
个相互独立的哈希函数,保证在给定的空间、误判率下,完成元素判重的过程。
它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。
Bloom-Filter 算法的核心思想就是利用多个不同的 Hash 函数来解决“冲突”。
Hash 存在一个冲突(碰撞)的问题,用同一个 Hash 得到的两个 URL 的值有可能相同。为了减少冲突,我们可以多引入几个 Hash,如果通过其中的一个 Hash 值我们得出某元素不在集合中,那么该元素肯定不在集合中。只有在所有的 Hash 函数告诉我们该元素在集合中时,才能确定该元素存在于集合中。这便是 Bloom-Filter 的基本思想。
Bloom-Filter 一般用于在大数据量的集合中判定某元素是否存在。
8. Redis 缓存异常--缓存击穿
缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。和缓存雪崩不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
解决方案
设置热点数据永远不过期。
加互斥锁,互斥锁。
9. 缓存预热
缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据!
解决方案
直接写个缓存刷新页面,上线时手工操作一下;
数据量不大,可以在项目启动的时候自动进行加载;
定时刷新缓存;
10.如何保证数据库和缓存双写一致性
你只要用缓存,就可能会涉及到缓存与数据库双存储双写,你只要是双写,就一定会有数据一致性的问题,那么你如何解决一致性问题?
一般来说,就是如果你的系统不是严格要求缓存+数据库必须一致性的话,缓存可以稍微的跟数据库偶尔有不一致的情况,最好不要做这个方案,读请求和写请求串行化,串到一个内存队列里去,这样就可以保证一定不会出现不一致的情况。
串行化之后,就会导致系统的吞吐量会大幅度的降低,用比正常情况下多几倍的机器去支撑线上的一个请求。
还有一种方式就是可能会暂时产生不一致的情况,但是发生的几率特别小,就是先更新数据库,然后再删除缓存。
篇幅所限这篇文章就先写这么多吧,
本文写了 Java 基础、计算机网络基础以及数据库三个方面的高频面试题,剩下的常用开源框架、分布式、微服务和中间件有时间下次再写吧,感兴趣的可以点个关注,到时候可以第一时间看到。
面试资料领取☟:
往期热文:
end
版权声明: 本文为 InfoQ 作者【北游学Java】的原创文章。
原文链接:【http://xie.infoq.cn/article/4afffb425e2a9d03930d6cc0c】。文章转载请联系作者。
评论