咱们先随便打开一个招聘网站,看看对Java工程师的技能要求。
抛开其它的经验能力等等,单纯从技术,或者说知识上来讲,可以发现一些共通的地方。
所以我们今天就分别从这些方面逐一来分析一下这些面试题,相对应的一些资料也给大伙准备好了,需要的朋友可以直接点击领取。
好了,话不多少,坐稳扶好,发车喽!
面向过程
优点: 性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源;比如单片机、嵌入式开发、Linux/Unix等一般采用面向过程开发,性能是最重要的因素。
缺点: 没有面向对象易维护、易复用、易扩展
面向对象
优点: 易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护
缺点: 性能比面向过程低
这几个是Java中很基本很基本的东西,但是我相信一定还有很多人搞不清楚!为什么呢?因为我们大多数时候在使用现成的编译工具以及环境的时候,并没有去考虑这些东西。
JDK: 顾名思义它是给开发者提供的开发工具箱,是给程序开发者用的。它除了包括完整的JRE(Java Runtime Environment),Java运行环境,还包含了其他供开发者使用的工具包。
JRE: 普通用户而只需要安装JRE(Java Runtime Environment)来运行Java程序。而程序开发者必须安装JDK来编译、调试程序。
JVM: 当我们运行一个程序时,JVM负责将字节码转换为特定机器代码,JVM提供了内存管理/垃圾回收和安全机制等。这种独立于硬件和操作系统,正是java程序可以一次编写多处执行的原因。
区别与联系:
先看下java中的编译器和解释器:
Java中引入了虚拟机的概念,即在机器和编译程序之间加入了一层抽象的虚拟的机器。这台虚拟的机器在任何平台上都提供给编译程序一个的共同的接口。
编译程序只需要面向虚拟机,生成虚拟机能够理解的代码,然后由解释器来将虚拟机代码转换为特定系统的机器码执行。在Java中,这种供虚拟机理解的代码叫做 字节码(即扩展名为 .class的文件),它不面向任何特定的处理器,只面向虚拟机。
每一种平台的解释器是不同的,但是实现的虚拟机是相同的。Java源程序经过编译器编译后变成字节码,字节码由虚拟机解释执行,虚拟机将每一条要执行的字节码送给解释器,解释器将其翻译成特定机器上的机器码,然后在特定的机器上运行。这也就是解释了Java的编译与解释并存的特点。
Java源代码---->编译器---->jvm可执行的Java字节码(即虚拟指令)---->jvm---->jvm中解释器----->机器可执行的二进制机器码---->程序运行。
采用字节码的好处:
Java语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以Java程序运行时比较高效,而且,由于字节码并不专对一种特定的机器,因此,Java程序无须重新编译便可在多种不同的计算机上运行。
我知道很多人没学过C++,但是面试官就是没事喜欢拿咱们Java和C++比呀!没办法!!!就算没学过C++,也要记下来!
一个程序中可以有多个类,但只能有一个类是主类。在Java应用程序中,这个主类是指包含main()方法的类。而在Java小程序中,这个主类是一个继承自系统类JApplet或Applet的子类。应用程序的主类不一定要求是public类,但小程序的主类要求必须是public类。主类是Java程序执行的入口点。
简单说应用程序是从主线程启动(也就是main()方法)。applet小程序没有main方法,主要是嵌在浏览器页面上运行(调用init()线程或者run()来启动),嵌入浏览器这点跟flash的小游戏类似。
在讲继承的时候我们就知道父类的私有属性和构造方法并不能被继承,所以Constructor也就不能被override,但是可以overload,所以你可以看到一个类中有多个构造函数的情况。
重载: 发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同,发生在编译时。
重写: 发生在父子类中,方法名、参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类;如果父类方法访问修饰符为private则子类就不能重写该方法。
封装
封装把一个对象的属性私有化,同时提供一些可以被外界访问的属性的方法,如果不想被外界方法,我们大可不必提供方法给外界访问。但是如果一个类没有提供给外界访问的方法,那么这个类也没有什么意义了。
继承
继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承我们能够非常方便地复用以前的代码。
关于继承如下3点请记住:
多态
所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。
在Java中有两种形式可以实现多态:继承(多个子类对同一方法的重写)和接口(实现接口并覆盖接口中同一方法)。
可变性
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
装箱:将基本类型用它们对应的引用类型包装起来;
拆箱:将包装类型转换为基本数据类型;
由于静态方法可以不通过对象进行调用,因此在静态方法里,不能调用其他非静态变量,也不可以访问非静态变量成员。
Java程序在执行子类的构造方法之前,如果没有用super()来调用父类特定的构造方法,则会调用父类中“没有参数的构造方法”。
因此,如果父类中只定义了有参数的构造方法,而在子类的构造方法中又没有用super()来调用父类中特定的构造方法,则编译时将发生错误,因为Java程序在父类中找不到没有参数的构造方法可供执行。解决办法是在父类里加上一个不做事且没有参数的构造方法。
刚开始的时候JavaAPI所必需的包是java开头的包,javax当时只是扩展API包来说使用。然而随着时间的推移,javax逐渐的扩展成为Java API的组成部分。
但是,将扩展从javax包移动到java包将是太麻烦了,最终会破坏一堆现有的代码。因此,最终决定javax包将成为标准API的一部分。
所以,实际上java和javax没有区别。这都是一个名字。
从语法形式上,看成员变量是属于类的,而局部变量是在方法中定义的变量或是方法的参数;成员变量可以被public,private,static等修饰符所修饰,而局部变量不能被访问控制修饰符及static所修饰;但是,成员变量和局部变量都能被final所修饰;
从变量在内存中的存储方式来看,成员变量是对象的一部分,而对象存在于堆内存,局部变量存在于栈内存
从变量在内存中的生存时间上看,成员变量是对象的一部分,它随着对象的创建而存在,而局部变量随着方法的调用而自动消失。
成员变量如果没有被赋初值,则会自动以类型的默认值而赋值(一种情况例外被final修饰但没有被static修饰的成员变量必须显示地赋值);而局部变量则不会自动赋值。
new运算符,new创建对象实例(对象实例在堆内存中),对象引用指向对象实例(对象引用存放在栈内存中)。一个对象引用可以指向0个或1个对象(一根绳子可以不系气球,也可以系一个气球);一个对象可以有n个引用指向它(可以用n条绳子系住一个气球)。
方法的返回值是指我们获取到的某个方法体中的代码执行后产生的结果!(前提是该方法可能产生结果)。返回值的作用:接收出结果,使得它可以用于其他的操作!
主要作用是完成对类对象的初始化工作。可以执行。因为一个类即使没有声明构造方法也会有默认的不带参数的构造方法。
在外部调用静态方法时,可以使用"类名.方法名"的方式,也可以使用"对象名.方法名"的方式。而实例方法只有后面这种方式。也就是说,调用静态方法可以无需创建对象。
静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),而不允许访问实例成员变量和实例方法;实例方法则无此限制.
对象的相等 比的是内存中存放的内容是否相等而引用相等 比较的是他们指向的内存地址是否相等。
帮助子类做初始化工作。
== : 它的作用是判断两个对象的地址是不是相等。即,判断两个对象是不是同一个对象。(基本数据类型比较的是值,引用数据类型比较的是内存地址)
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的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)
值传递是指对象被值传递,意味着传递了对象的一个副本,即使副本被改变,也不会影响源对象。(因为值传递的时候,实际上是将实参的值复制一份给形参。)
引用传递是指对象被引用传递,意味着传递的并不是实际的对象,而是对象的引用。因此,外部对引用对象的改变会反映到所有的对象上。(因为引用传递的时候,实际上是将实参的地址值复制一份给形参。)
线程
与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。
程序
是含有指令和数据的文件,被存储在磁盘或其他的数据存储设备中,也就是说程序是静态的代码。
进程
是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。简单来说,一个进程就是一个执行中的程序,它在计算机中一个指令接着一个指令地执行着,同时,每个进程还占有某些系统资源如CPU时间,内存空间,文件,文件,输入输出设备的使用权等等。
换句话说,当程序在执行时,将会被操作系统载入内存中。 线程是进程划分成的更小的运行单位。线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。从另一角度来说,进程属于操作系统的范畴,主要是同一段时间内,可以同时执行一个以上的程序,而线程则是在同一程序内几乎同时执行一个以上的程序段。
新建(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()方法,则该线程结束生命周期。死亡的线程不可再次复生。
答:
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)
答:
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地址与子网掩码相与得到网络号
答:
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响应。
答:
ICMP协议: 因特网控制报文协议。它是TCP/IP协议族的一个子协议,用于在IP主机、路由器之间传递控制消息。
TFTP协议: 是TCP/IP协议族中的一个用来在客户机与服务器之间进行简单文件传输的协议,提供不复杂、开销不大的文件传输服务。
HTTP协议: 超文本传输协议,是一个属于应用层的面向对象的协议,由于其简捷、快速的方式,适用于分布式超媒体信息系统。
DHCP协议: 动态主机配置协议,是一种让系统得以连接到网络上,并获取所需要的配置参数手段。
NAT协议:网络地址转换属接入广域网(WAN)技术,是一种将私有(保留)地址转化为合法IP地址的转换技术,
DHCP协议:一个局域网的网络协议,使用UDP协议工作,用途:给内部网络或网络服务供应商自动分配IP地址,给用户或者内部网络管理员作为对所有计算机作中央管理的手段。
答:
RARP是逆地址解析协议,作用是完成硬件地址到IP地址的映射,主要用于无盘工作站,因为给无盘工作站配置的IP地址不能保存。
工作流程:在网络中配置一台RARP服务器,里面保存着IP地址和MAC地址的映射关系,当无盘工作站启动后,就封装一个RARP数据包,里面有其MAC地址,然后广播到网络上去,当服务器收到请求包后,就查找对应的MAC地址的IP地址装入响应报文中发回给请求者。因为需要广播请求报文,因此RARP只能用于具有广播能力的网络。
答:
三次握手:
第一次握手:客户端发送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,至此,完成四次挥手。
答:
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服务。
答:
当DNS客户机需要在程序中使用名称时,它会查询DNS服务器来解析该名称。客户机发送的每条查询信息包括三条信息:包括:指定的DNS域名,指定的查询类型,DNS域名的指定类别。基于UDP服务,端口53. 该应用一般不直接为用户使用,而是为其他应用服务,如HTTP,SMTP等在其中需要完成主机名到IP地址的转换。
面向连接和非面向连接的服务的特点是什么?
面向连接的服务,通信双方在进行通信之前,要先在双方建立起一个完整的可以彼此沟通的通道,在通信过程中,整个连接的情况一直可以被实时地监控和管理。非面向连接的服务,不需要预先建立一个联络两个通信节点的连接,需要通信的时候,发送节点就可以往网络上发送信息,让信息自主地在网络上去传,一般在传输的过程中不再加以监控。
答:
建立连接的过程是利用客户服务器模式,假设主机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)采用两次握手不行,原因就是上面说的实效的连接请求的特殊情况。
答:
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地址对应着不同的端口,这样看来就会保护子网内的用户。
内连接(inner可以不写)
select e.name e.age p.product_name p.saled
from employee e,product p
where e.id = p.id
select e.name e.age p.product_name p.saled
from employee inner
join e,product p on e.id = p.id
这就是内连接,它要求数据必须On条件必须百分百匹配才会符合条件并返回。当不满足时,他会返回空。
外连接是用左\右侧的数据去关联另一侧的数据,即使关联不上任何数据也得把左\右侧的数据返回回来。
外连接分(左外连接)和(右外连接)
左外连接( left join)
select g2.Name,Price,ProductionDate,Amount,g1.name
FROM Goods G1
left join GoodsType G2 on G1.Typeld=G2.IO
右外连接(right join–空值的会显示出来)
select g2.Name,Price,ProductionDate,Amount,g1.name
FROM Goods G1 right join GoodsType G2 on G1.Typeld=G2.IO
全外连接(full outer(可以不写) join–空值的会显示出来)
select g1.name,g2.Name,price,productiondate,g2.Amount
FROM GoodsType g1 full outer join Goods g2 on g1.IO=g2.Typeld
交叉连接(笛卡尔积)查询所有的值
select g1.name,g2.Name,price,productiondate,g2.Amount
FROM GoodsType g1 cross join Goods g2 where g1.IO=g2.Typeld
答: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+树更适合用来存储外部数据。
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 支持全文索引。
MyISAM | Innodb | |
---|---|---|
存储结构 | 每张表被存放在三个文件:frm-表格定义、MYD(MYData)-数据文件、MYI(MYIndex)-索引文件 | 所有的表都保存在同一个数据文件中(也可能是多个文件,或者是独立的表空间文件),InnoDB表的大小只受限于操作系统文件的大小,一般为2GB |
存储空间 | MyISAM可被压缩,存储空间较小 | InnoDB的表需要更多的内存和存储,它会在主内存中建立其专用的缓冲池用于高速缓冲数据和索引 |
可移植性、备份及恢复 | 由于MyISAM的数据是以文件的形式存储,所以在跨平台的数据转移中会很方便。在备份和恢复时可单独针对某个表进行操作 | 免费的方案可以是拷贝数据文件、备份 binlog,或者用 mysqldump,在数据量达到几十G的时候就相对痛苦了 |
文件格式 | 数据和索引是分别存储的,数据.MYD,索引.MYI | 数据和索引是集中存储的,.ibd |
记录存储顺序 | 按记录插入顺序保存 | 按主键大小有序插入 |
外键 | 不支持 | 支持 |
事务 | 不支持 | 支持 |
锁支持(锁是避免资源争用的一个机制,MySQL锁对用户几乎是透明的) | 表级锁定 | 行级锁定、表级锁定,锁定力度小并发能力高 |
SELECT | MyISAM更优 | |
INSERT、UPDATE、DELETE | InnoDB更优 | |
select count(*) | myisam更快,因为myisam内部维护了一个计数器,可以直接调取。 | |
索引的实现方式 | B+树索引,myisam 是堆表 | B+树索引,Innodb 是索引组织表 |
哈希索引 | 不支持 | 支持 |
全文索引 | 支持 | 不支持 |
mysql主从复制原理就是主库创建一个专门用于给从库拉取binlog的账号,并且给这个账号授权,让他可以拉取哪个DB的那个表的binlog,具体的授权SQL是:
grant repliacation slave on xx.xx to username@ip identify by 'password'
这样从库就能登陆主库拉取binlog,那拉取binlog就得知道从哪个binlog的哪个位点拉取,现有的有两个方案:fileName + position 还有就是通过gtid自动找点。
什么是GTID?原理?
同步、半同步、异步复制说的是 从库在主库上拉取binlog日志的模式。
同步:
主库写redolog 事物处于prepare状态、主库写binlog,然后从库拉取binlog去回放,从库回放成功后返回给主库ack确认,所有的从库都完成回放后主库提交事物。这样是可以保证主从数据一致的但是缺点就是速度太慢了。
半同步:
主库写redolog 事物处于prepare状态、主库写binlog,然后从库拉取binlog后返回给主库ack,在众多从库中只要收到一个ack主库就提交事物
异步复制:
主库根本不管从库有没有拉取回放binlog,直接写redo、binlog、然后提交事物
首先不允许出现主从数据不一致的情况:如果主从不一致对业务来说是有损的,一旦发生主从数据不一致的情况,从库就会出现断开连接的可能。
MySQL有监控项:slow query , MySQL会将所有执行时间超过阈值的SQL记录到慢查日志中
我们的监控系统可以监控: 当检测到有慢查时触发报警
通常出现慢查到情况如下:
1、表中的数据量很大,而且SQL的执行没有走索引
2、数据量太大了,即使走了索引依然超过了阈值
3、大量的慢查占据MySQL连接,导致正常的SQL得不到连接执行从而变成慢查SQL
4、优化器选错了索引
查看慢查时间阈值
mysql> show global variables like '%long_query_time%';
+-----------------+-----------+
| Variable_name | Value |
+-----------------+-----------+
| long_query_time | 10.000000 |
+-----------------+-----------+
1 row inset (0.00 sec)
查看执行时间最长的10条SQL
mysqldumpslow -s a1 -n 10 mysql.slow_log
什么是执行计划
每次提交一个SQL到MySQL,MySQL内核的查询优化器会针对这个SQL的语意生成一个执行计划,这个执行计划就代表了他会查哪些表?用哪些索引,如何做排序和分组
执行不同的sql有哪几种情况
单表查询举例:
示例1:
select * fromtablewhere id = x ;
select * fromtablewhere name = x ;
id是主键、name是唯一索引像这种可以直接根据聚簇索引或者二级索引+回表就能查询到我们想要的数据的方式在执行计划中称为 const
要求二级索引必须是唯一索引,才属于const
示例2:
select * fromtablewhere name = x ;
name是普通索引查询的过程是:从name这个B+树中查询出一条记录后,得到记录的主键id,然后去聚簇索引中回表,这种查询方式速度也很快,在执行计划中叫:ref
示例3:
select * fromtablewhere name = x and age = y and xx=yy ;
name、age、xx为普通索引这种sql要求where条件之后的SQL全得是等值比较,在执行计划中才算做是ref
示例4:
select * fromtablewhere name = x and name is NULL ;
name为普通索引这种sql就是在二级索引中同时搜索name = x和name = null的值,然后去主库中回表。这种在执行计划中被称为ref_or_null
示例5:
select * fromtablewhere age >=x and age <=y
age是普通索引像这样使用普通索引做范围查询,在执行计划中称为 range
示例6:index方式
index方式并不是说执行计划使用了索引,从聚簇索引中一路二分往下走。
假设有联合索引:key(x1,x2,x3)
查询语句如下:
select x1,x2,x3 fromtablewhere x2=xxx;
想使用联合索引得遵循左前缀原则,但是上面直接使用x2,很显然不符合左前缀原则,所以就用不上联合索引,但是他查询的x1、x2、x3其实对应联合索引中的x1、x2、x3所以他会去扫描 联合索引:key(x1,x2,x3)形成的B+树,而不是全表扫描,在执行计划中这就做 index
所以说,index其实是去遍历二级索引,故他的效率肯定比ref,const、ref_or_null慢,但是比全表扫描快一些
示例7:all
比如你去查找数据但是不加where条件,就会进行全表扫描
示例8:
select * fromtablewhere x1 = xxx or x2 >= yy ;
然后你的联合索引是 key1(x1,x3) key2(x2,x4)
这时查询优化器只能在key1和key2中二选一使用,具体选哪一个就看使用哪个索引扫描行数少
比如使用x1扫描行数少,就先拿着x1去过滤一部分数据出来(ref的方式)然后去聚簇索引中回表查询所有的数据在内存中根据第二个条件x2 > yy 再过滤一次
示例9:
select * fromtablewhere x1 = xxx and c1=xxx and c2 >= yy and c3 is null;
只有c1有索引查询优化器会根据x1,通过ref的方式查找到一批数据,然后去聚簇索引中回表,将所有符合条件的数据加载进内存,然后在内存中根据剩下的条件继续过滤。
示例10:
select * fromtablewhere x1 = xxx and x2=xxx;
x1和x2都有普通索引情况1: 查询优化器使用x1索引在二级索引中查询中一批数据,然后将这些数据放到聚簇索引中回表,将数据所有字段查询出来,然后在内存中根据x2=xxx再过滤。
情况2:查询优化器使用x1索引在二级索引中查询中一批数据A,再使用x2索引在二级索引中查询中一批数据B,两者做交集,再去聚簇索引中回表,这样的效率会更高。
多表:
示例1:
select * from t1,t2 where t1.x1 = xxx and t1.x2 = t2.x2and t2.x3 = yyy;
第一步:查询优化器会根据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:查询的哪张表
INT(6),6即是其宽度指示器,该宽度指示器并不会影响int列存储字段的大小,也就是说,超过6位它不会自动截取,依然会存储,只有超过它本身的存储范围才会截取;此处宽度指示器的作用在于该字段是否有zerofill,如果有就未满足6位的部分就会用0来填充),
CHAR 类型用于定长字符串,并且必须在圆括号内用一个大小修饰符来定义。这个大小修饰符的范围从 0-255。比指定长度大的值将被截短,而比指定长度小的值将会用空格作填补。
CHAR 类型的一个变体是 VARCHAR 类型。它是一种可变长度的字符串类型,并且也必须带有一个范围指示器。
CHAR 和 VARCHGAR 不同之处在于 MYSQL 数据库处理这个指示器的方式:CHAR 把这个大小视为值的大小,不长度不足的情况下就用空格补足。而 VARCHAR 类型把它视为最大值并且只使用存储字符串实际需要的长度(增加一个额外字节来存储字符串本身的长度)来存储值。所以短于指示器长度的 VARCHAR 类型不会被空格填补,但长于指示器的值仍然会被截短。
备份整个数据库
$> mysqldump -u root -h host -p dbname > backdb.sql
备份数据库中的某个表
$> mysqldump -u root -h host -p dbname tbname1, tbname2 > backdb.sql
备份多个数据库
$> mysqldump -u root -h host -p --databases dbname1, dbname2 > backdb.sql
备份系统中所有数据库
$> mysqldump -u root -h host -p --all-databases > backdb.sql
宏观上:
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性能诊断方法很少, 主要就是通过通过慢查询日志去排查
分区表和分区索引
a. Oracle的分区表和分区索引相对来说比较成熟
b. Mysql 分区表和分区索引就不成熟
a. 在搭建的主从复制的模式中, 主库出现了问题, 可能会导致从库有一定数据的丢失, 需要手动的切换的到主库
b. Oracle 则更强大, 既有传统的推/拉式的数据复制, 同时也有 dataguard双机或者多机的容灾机制, 而且主库出现问题, 自动切换到备库, 但是配置相对复杂
ACID:
Atomic 原子性: 事务不能被分割, 要么都做, 要么都不做。
Consistency 一致性: 可以用转账的例子解释一致性。
Isolation 隔离性 : 不同的事务, 彼此隔离, 互不干扰。
Durability 持久性: 也叫做用就行, 事务一旦被提交, 对数据库做出的修改将被持久化 。
read uncommitted 读未提交: 在事务A中读取到了事务B中未提交的数据, 也叫做脏读。
read commited 读已提交: Oracle默认使用的隔离级别, 读已提交, 说白了, 事务A先开启, 然后事务B再开启, 然后事务Bcommit一个事务操作, 修改数据 , 那么这个修改是能被事务A读取到的, 这就叫做读已提交, 也是所谓的不可重复读,(因为重复读之后, 数据可能会发生变化)。
repeatable read : 可重复读, 这也是Mysql默认的事务隔离级别, 事务A开启后, 无论读取多少次, 得到的结果都和第一次得到的结果是一样的, 但是如果事务B在事务A第一次读取的范围内插入了一条数据的话, 会发生幻读, 两次读取结果又不一致了, Mysql的InnoDB引擎通过多版本并发控制MVCC解决了这个问题。
serializable : 可串行化, 最高的事务隔离级别, 到是也是效率最低的事务隔离级别。
主键索引 primary key:
一个表只能有一个主键索引。
主键索引不能为空。
主键索引可以做外键。
唯一索引unique key:
一张表可以存在多个唯一索引。
唯一索引可以是一列或者多列。
唯一索引不可重复的。
因为这个原因, 限制唯一索引做多有一个null。
普通索引 normal key :
普通一般是为了加快数据的访问速度而建立的。
针对那些经常被查询, 或者经常被排序的字段建立。
被索引的数据允许出现重复的值。
第一大范式:
关系模式R中的所有属性都不能再分解, 称关系模式R 满足第一范式, 比如 address 字段就可以继续拆分成 省市区, 我们就可以认为address不满足第一范式。
第二大范式:
在满足第一范式的基础上更进一步, 它要求所有的非主属性都必须完全依赖于第一范式中确定下来的主属性, 换句话说, 比如联合主键就不符合第二范式, 因为很有可能这个表中的一部分非主属性和联合主键中的一部分列是有依赖关系的, 而和另外一部分并没有依赖关系。
第三大范式:
在第一范式R的基础上, 更进一步, 要求所有的字段都可主键直接相关而不能间接相关, 比如用户表里面不要出现订单表中的订单信息。
from where group by having order by limit select
查询数据表所占的容量
select sum(DATA_LENGTH) + sum(INDEX_LENGTH)
from information_schema.tables where table_schema = '数据库名
查询所有数据的大小, 用兆的方式输出结果
select concat(round(sum(DATA_LENGTH/1024/1024),2),'MB') asdata
from information_schema.tables
where table_schema='blog'andtable_name='catalog'
MyISAM 它在设计之时就考虑到 数据库被查询的次数要远大于更新的次数。因此,ISAM执行读取操作的速度很快,而且不占用大量的内存和存储资源。
所以, 如果系统中的写操作真的很少,并且不使用mysql的事务等高级操作的话, 建议使用MYISAM。
考虑报表引擎
字段名 | 含义 |
---|---|
id | 选择标识符 |
select_type | 表示查询的类型 |
table | 输出结果集的表 |
partitions | 匹配的分区 |
type | 表示表的连接类型 |
possible_keys | 表示查询时,可能使用的索引 |
key | 表示实际使用的索引 |
key_len | 索引字段的长度 |
ref | 列与索引的比较 |
rows | 扫描出的行数(估算的行数) |
filtered | 按表条件过滤的行百分比 |
Extra | 执行情况的描述和说明 |
适合索引的列是出现在where子句中的列,或者连接子句中指定的列。
基数较小的类,索引效果较差,没有必要在此列建立索引。
使用短索引,如果对长字符串列进行索引,应该指定一个前缀长度,这样能够节省大量索引空间。
不要过度索引。索引需要额外的磁盘空间,并降低写操作的性能。在修改表内容的时候,索引会进行更新甚至重构,索引列越多,这个时间就会越长。所以只保持需要的索引有利于查询即可。
表名 | 含义 |
---|---|
user权限表 | 记录允许连接到服务器的用户帐号信息,里面的权限是全局级的。 |
db权限表 | 记录各个帐号在各个数据库上的操作权限。 |
table_priv权限表 | 记录数据表级的操作权限。 |
columns_priv权限表 | 记录数据列级的操作权限。 |
host权限表 | 配合db权限表对给定主机上数据库级操作权限作更细致的控制。这个权限表不受GRANT和REVOKE语句的影响。 |
string == Map
map == Map>
list == Map>
set == Map>
zset == Map>
高并发的原因:
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进程上就可以了。
我们都知道,Redis是key-value数据库,我们可以设置Redis中缓存的key的过期时间。Redis的过期策略就是指当Redis中缓存的key过期了,Redis如何处理。
过期策略通常有以下三种:
(expires字典会保存所有设置了过期时间的key的过期时间数据,其中,key是指向键空间中的某个键的指针,value是该键的毫秒精度的UNIX时间戳表示的过期时间。键空间是指该Redis集群中保存的所有键。)
Redis中同时使用了惰性过期和定期过期两种过期策略。
Redis的内存淘汰策略是指在Redis的用于缓存的内存不足时,怎么处理需要新写入且需要申请额外空间的数据。
全局的键空间选择性移除
noeviction:当内存不足以容纳新写入数据时,新写入操作会报错。
allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key。(这个是最常用的)
allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key。
设置过期时间的键空间选择性移除
volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key。
volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key。
volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除。
Redis 官方站提出了一种权威的基于 Redis 实现分布式锁的方式名叫 Redlock,此种方式比原先的单节点的方法更安全。它可以保证以下特性:
安全特性:互斥访问,即永远只有一个 client 能拿到锁。
避免死锁:最终 client 都可能拿到锁,不会出现死锁的情况,即使原本锁住某资源的 client crash 了或者出现了网络分区。
容错性:只要大部分 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一般用于在大数据量的集合中判定某元素是否存在。
缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。和缓存雪崩不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
解决方案
设置热点数据永远不过期。
加互斥锁,互斥锁。
缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据!
解决方案
直接写个缓存刷新页面,上线时手工操作一下;
数据量不大,可以在项目启动的时候自动进行加载;
定时刷新缓存;
你只要用缓存,就可能会涉及到缓存与数据库双存储双写,你只要是双写,就一定会有数据一致性的问题,那么你如何解决一致性问题?
一般来说,就是如果你的系统不是严格要求缓存+数据库必须一致性的话,缓存可以稍微的跟数据库偶尔有不一致的情况,最好不要做这个方案,读请求和写请求串行化,串到一个内存队列里去,这样就可以保证一定不会出现不一致的情况。
串行化之后,就会导致系统的吞吐量会大幅度的降低,用比正常情况下多几倍的机器去支撑线上的一个请求。
还有一种方式就是可能会暂时产生不一致的情况,但是发生的几率特别小,就是先更新数据库,然后再删除缓存。
问题场景 | 描述 | 解决 |
---|---|---|
先写缓存,再写数据库,缓存写成功,数据库写失败 | 缓存写成功,但写数据库失败或者响应延迟,则下次读取(并发读)缓存时,就出现脏读 | 这个写缓存的方式,本身就是错误的,需要改为先写数据库,把旧缓存置为失效;读取数据的时候,如果缓存不存在,则读取数据库再写缓存 |
先写数据库,再写缓存,数据库写成功,缓存写失败 | 写数据库成功,但写缓存失败,则下次读取(并发读)缓存时,则读不到数据 | 缓存使用时,假如读缓存失败,先读数据库,再回写缓存的方式实现 |
需要缓存异步刷新 | 指数据库操作和写缓存不在一个操作步骤中,比如在分布式场景下,无法做到同时写缓存或需要异步刷新(补救措施)时候 | 确定哪些数据适合此类场景,根据经验值确定合理的数据不一致时间,用户数据刷新的时间间隔 |
篇幅所限这篇文章就先写这么多吧,
本文写了Java基础、计算机网络基础以及数据库三个方面的高频面试题,剩下的常用开源框架、分布式、微服务和中间件有时间下次再写吧,感兴趣的可以点个关注,到时候可以第一时间看到。
面试资料领取☟:
往期热文:
end