– 共享:资源可被多个并发执行的进程使用
– 并发:可以在同一时间间隔处理多个进程,需要硬件支持
– 虚拟:将物理实体映射成为多个虚拟设备
– 异步:进程执行走走停停,每次进程执行速度可能不同,但OS需保证进程每次执行结果相同
程序段、数据段、PCB(Process Control Block)
并发:同一间隔 并行:同一时刻
保持处理机上下文 -> 更新PCB -> 把PCB移入相应队列(就绪、阻塞) -> 选择另一个进程并更新其PCB -> 更新内存管理的数据结构 -> 恢复处理机上下文
1、低级通信方式
PV操作(信号量机制)。
– P:wait(S)原语,申请S资源
– V:signal(S)原语,释放S资源
2、高级通信方式:以较高效率传输大量数据的通信方式
– 共享存储(使用同步互斥工具操作共享空间)
– 消息传递(进程间以格式化的消息进行数据交换,有中间实体,分为直接和间接两种,底层通过发送消息和接收消息两个原语实现)
– 管道通信(两个进程中间存在一个特殊的管道文件,进程的输入输出都通过管道,半双工通信)
由一组数据及对这组数据操作的定义组成的模块。同一时间只能有一个进程使用管程,即管程是互斥使用的,进程释放管程后需唤醒申请管程资源的等待队列上的进程。进程只有通过进入管程并使用管程内部的操作才能访问其中数据。
– 互斥条件:资源在某一时刻只能被一个进程占有
– 不剥夺条件:进程所持有的资源在主动释放前不能被其他进程强行夺走
– 请求和占用条件:死锁进程必然是既持有资源又在申请资源的
– 循环等待条件:存在等待链,互相申请,互不释放
避免死锁:不让循环等待条件发生。使用银行家算法。
– 都是资源分配问题
– 死锁是等待永远不会释放的资源,而饥饿申请的资源会被释放,只是永远不会分配给自己
– 一旦产生死锁,则死锁进程必然是多个,而饥饿进程可以只有一个
– 饥饿的进程可能处于就绪状态,而死锁进程一定是阻塞进程
线程被称作轻量级进程,在进程中包含线程。进程有独立的内存空间,不同进程间不能直接共享其他进程资源,而同一个进程内的线程共享进程内存空间;相比进程,线程切换对系统开销更小一些;进程是资源分配的最小单位,线程是程序执行的最小单位。
文件指针:上次读写位置。
文件打开数:多少个进程打开了此文件。
文件磁盘位置。
文件的访问权限:创建、只读、读写等。
所选择的被淘汰页面将是以后永不使用的,或者是在最长时间内不再被访问的页面,这样可以保证获得最低的缺页率。但由于人们目前无法预知进程在内存下的若千页面中哪个是未来最长时间内不再被访问的,因而该算法无法实现。
优先淘汰最早进入内存的页面,亦即在内存中驻留时间最久的页面。该算法实现简单,只需把调入内存的页面根据先后次序链接成队列,设置一个指针总指向最早的页面。但该算法与进程实际运行时的规律不适应,因为在进程中,有的页面经常被访问。
最近最长时间未访问过的页面予以淘汰,它认为过去一段时间内未访问过的页面,在最近的将来可能也不会被访问。该算法为每个页面设置一个访问字段,来记录页面自上次被访问以来所经历的时间,淘汰页面时选择现有页面中值最大的予以淘汰。
LRU算法的性能接近于OPT,但是实现起来比较困难,且开销大;FIFO算法实现简单,但性能差。所以操作系统的设计者尝试了很多算法,试图用比较小的开销接近LRU的性能,这类算法都是CLOCK算法的变体。
简单的CLOCK算法是给每一帧关联一个附加位,称为使用位。当某一页首次装入主存时,该帧的使用位设置为1;当该页随后再被访问到时,它的使用位也被置为1。对于页替换算法,用于替换的候选帧集合看做一个循环缓冲区,并且有一个指针与之相关联。当某一页被替换时,该指针被设置成指向缓冲区中的下一帧。当需要替换一页时,操作系统扫描缓冲区,以查找使用位被置为0的一帧。每当遇到一个使用位为1的帧时,操作系统就将该位重新置为0;如果在这个过程开始时,缓冲区中所有帧的使用位均为0,则选择遇到的第一个帧替换;如果所有帧的使用位均为1,则指针在缓冲区中完整地循环一周,把所有使用位都置为0,并且停留在最初的位置上,替换该帧中的页。由于该算法循环地检查各页面的情况,故称为CLOCK算法,又称为最近未用(Not Recently Used, NRU)算法。
改进型的CLOCK算法优于简单CLOCK算法之处在于替换时首选没有变化的页。由于修改过的页在被替换之前必须写回,因而这样做会节省时间。
就是按照各个作业进入系统的自然次序来调度作业。这种调度算法的优点是实现简单,公平。其缺点是没有考虑到系统中各种资源的综合使用情况,往往使短作业的用户不满意,因为短作业等待处理的时间可能比实际运行时间长得多。
就是优先调度并处理短作业,所谓短是指作业的运行时间短。而在作业未投入运行时,并不能知道它实际的运行时间的长短,因此需要用户在提交作业时同时提交作业运行时间的估计值。
FCFS可能造成短作业用户不满,SPF可能使得长作业用户不满,于是提出HRN,选择响应比最高的作业运行。响应比=1+作业等待时间/作业处理时间。
每一个作业规定一个表示该作业优先级别的整数,当需要将新的作业由输入井调入内存处理时,优先选择优先数最高的作业。
进程有三种状态阻塞就绪,运行。
按照进程进入就绪队列的先后次序来选择。即每当进入进程调度,总是把就绪队列的队首进程投入运行。
分时系统的一种调度算法。轮转的基本思想是,将CPU的处理时间划分成一个个的时间片,就绪队列中的进程轮流运行一个时间片。当时间片结束时,就强迫进程让出CPU,该进程进入就绪队列,等待下一次调度,同时,进程调度又去选择就绪队列中的一个进程,分配给它一个时间片,以投入运行。
进程调度每次将处理机分配给具有最高优先级的就绪进程。最高优先级算法可与不同的CPU方式结合形成可抢占式最高优先级算法和不可抢占式最高优先级算法。
几种调度算法的结合形式多级队列方式。
让离当前磁道最近的请求访问者启动磁盘驱动器,即是让查找时间最短的那个作业先执行,而不考虑请求访问者到来的先后次序,这样就克服了先来先服务调度算法中磁臂移动过大的问题
总是从磁臂当前位置开始,沿磁臂的移动方向去选择离当前磁臂最近的那个柱面的访问者。如果沿磁臂的方向无请求访问时,就改变磁臂的移动方向。在这种调度方法下磁臂的移动类似于电梯的调度,所以它也称为电梯调度算法。
循环扫描调度算法是在扫描算法的基础上改进的。磁臂改为单项移动,由外向里。当前位置开始沿磁臂的移动方向去选择离当前磁臂最近的哪个柱面的访问者。如果沿磁臂的方向无请求访问时,再回到最外,访问柱面号最小的作业请求。
分配给文件的所有盘块号都放在FAT中,用以记录了文件的物理位置。
中断是怎么操作的?
中断请求-中断响应-断点保护—执行中断服务程序—断点恢复—中断返回
中断:解决处理器速度和硬件速度不匹配,是多道程序设计的必要条件。每个中断都有自己的数字标识,当中断发生时,指令计数器PC和处理机状态字PSW中的内容自动压入处理器堆栈,同时新的PC和PSW的中断向量也装入各自的寄存器中。这时,PC中包含的是该中断的中断处理程序的入口地址,它控制程序转向相应的处理,当中断处理程序执行完毕,该程序的最后一条iret(中断返回),它控制着恢复调用程序的环境。 中断和系统调用的区别: 中断是由外设产生, 无意的, 被动的 系统调用是由应用程序请求操作系统提供服务产生, 有意的, 主动的。要从用户态通过中断进入内核态。(联系) 中断过程:中断请求 中断响应 断点保护 执行中断服务程序 断点恢复 中断返回 系统调用过程:应用程序在用户态执行时请求系统调用,中断,从用户态进入内核态,在内核态执行相应的内核代码。
根据程序执行的互斥性和空间与时间局域性两个特点,允许作业装入时候只装入一部分,另一部分存放在磁盘上,调用时候将常用的放入内存,其他暂时不用的放入外存中。这样一个小的主存空间也可以运行一个比它大的作业。常用的虚拟存储技术有分页分段存储管理。
window:fat32.linux:ext2,fat32.
是一种将程序指令存储器和数据存储器合并在一起的存储器结构。
五个组成部分是:输入输出,计算单元,控制单元,存储单元。
输入数据和程序的输入设备;
记忆程序和数据的存储器;
完成数据加工处理的运算器;
控制程序执行的控制器;
输出处理结果的输出设备。
连接CPU和内存。
寄存器是暂时存储的CPU组成部分,cache用来做高度CPU和低速的主存之间加速带。
CISC(Complex Instruction Set Computer)复杂指令系统计算器,是一种执行较多类型计算机指令的微处理器。RISC(Reduced Instruction Set Computer)是精简指令系统计算器,是一种执行较少类型计算机指令的微处理器。
将重复性的过程分为若干个子过程来完成。
总线是指数据通信的连接线,有地址,数据,控制指令。
I/O输入/输出(Input/Output),分为IO设备和IO接口两个部分.
I/O的方式有:DIO(Direct I/O),AIO(Asynchronous I/O,异步I/O),Memory-Mapped I/O(内存映射I/O)等,不同的I/O方式有不同的实现方式和性能,在不同的应用中可以按情况选择不同的I/O方式。
DMA(Direct Memory Access,直接存储器访问) 是所有现代电脑的重要特色,它允许不同速度的硬件装置互相沟通,而不需要依赖于 CPU 的大量中断负载。否则,CPU 需要从来源把每一片段的资料复制到暂存器,然后把它们再次写回到新的地方。在这个时间中,CPU 对于其他的工作来说就无法使用。
一次编译到处运行,没有指针,完全对象化,面向对象(封装、继承、多态)。
JavaEE:Java Platform Enterprise Edition,是Sun公司为企业级应用推出的标准平台。
J2EE:Java 2 Platform,Enterprise Edition,是JavaEE以前的叫法。
JDBC:Java DataBase Connectivity,Java数据库连接。
JNDI:Java Naming and Directory Interface,提供一个目录系统,使得开发人员可以通过名称来访问资源。
EJB:Enterprise JavaBean,用来构建一个可以管理的服务器组件。
Servlet:Java编写的服务端程序,可以动态修改web内容。
JSP:Java Server Pages,Sun主导的一种动态网页技术标准,JSP部署于网络服务器上,可以响应客户端请求,并返回相应内容。
RMI:Remote Method Invocation,使得客户端可以像调用本地对象一样调用远程服务器上的方法。和RPC(Remote Procedure Call Protocol)不同,RMI只适用于Java,返回结果也有区别。RML在文件传输时,需要进行序列化serial,转换为二进制才能被servlet传输
XML:Extensible Markup Language,用于传输和存储数据
JMS:Java Message Service,Java平台面向消息中间件的一个服务,用于在分布式系统或应用程序间传递服务。
JTA:Java Transaction API,事务管理组件。
Weblogic:Oracle公司推出的商业化JavaEE服务器
java把内存分为堆栈空间存储,在堆中new的空间不用自己收回,自动垃圾收回。
不同于C++需要编程人员手动释放内存,Java有虚拟机,因此Java不需要程序员主动去释放内存,而是通过虚拟机自身的垃圾回收器(Garbage Collector-GC)来进行对象的回收。Java语言由于有虚拟机的存在,实现了平台无关性,在任意平台都是通过将代码转换为字节码文件,从而在平台下的虚拟机中运行代码的。
内存区域分布
虚拟机栈:存放每个方法执行时的栈帧,一个方法调用到完成就对应栈帧在虚拟机栈中入栈和出栈的过程。
本地方法栈:和虚拟机栈类似,不过是为Java中native方法服务的。平时所说的“栈内存”指的就是虚拟机栈和本地方法栈的合称。
程序计数器:当前线程执行字节码的行号指示器,字节码解释器工作依赖于它。占用较小的内存空间,不会出现OOM。
堆:即所谓的“堆内存”。JVM所管理最大的一块内存,被所有线程共享。唯一作用就是给对象实例分配内存空间,在分代回收算法中的新生代老年代就在于堆中。
方法区(也称为永久代):不在堆中,被各个线程共享,存储已被JVM加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。其中包括常量池。
另:直接内存,不属于JVM内存区域,与NIO联系紧密,不受JVM内存大小限制。
何时进行垃圾回收?
GC本质上是一道守护进程(Daemon Thread),不停的检测堆中是否有不可达对象并释放内存,因此GC在何时发生其实我们是无法预测的。GC通过调用对象的finalize()方法来摧毁对象。
不可达对象的判定:根搜索算法。JVM中有一系列设定的GC Roots,当一个对象到任意一个GC Root都没有引用链时,则说明此对象不可达。
1、标记-清除算法
最基础的算法,GC会判断堆中对象是否不可达,如果满足清理条件(查看该对象是否有必要执行finalize()方法,有无必要的标准是该对象有没有被调用过finalize方法或该对象有没有覆盖finalize()方法,因为finalize()只能被调用一次),则给这个对象进行标记,将对象放在F-queue队列。此时除非对象在finalize()方法中重新获得了引用,否则它就会被清除掉。
2、复制算法
将内存分为大小相等的两块,当对象不可达后并不是及时清理,而是等待正在使用的内存满了之后,将该内存内还存活的对象整体复制到另一块内存中,复制结束后再清理掉原内存块中的所有内容。这种方法的优点是快速,但牺牲了一半的内存。方法的改进版(事实上也是虚拟机的做法)是只在新生代空间使用复制算法,并且由于新生代对象生命周期往往很短,因此又将新生代区域分为Eden和Survivor空间。其中Eden分配的空间又比Survivor大出很多,从而节省内存空间。如果存活对象过多,使得Survivor区也满,那么就会转移Survivor区对象到老年代。
3、标记-整理算法
标记过程与1一样,将1中的清除过程换成了整理,即将内存中存活的对象归拢到一边,使得内存更“紧凑”一些,整理之后将边界之外的对象清理掉。这种算法是为了防止2算法中出现存活率100%的极端情况,那么复制就没有止境了。
4、分代算法
新生代采用2算法,老年代采用1或3算法。这是由他们的特点决定的,新生代注定了其中很多对象生命周期转瞬即逝,因此复制算法移动的存货对象并不是很多。而老年代存活率较高,只能采用1、3来执行,提高效率。
可以调整堆内新生代老年代比例
可以调整对象移入老年代的年龄
可以调整堆内存大小
可以设置每个栈大小
可以设置堆内分区大小
可选择垃圾回收方式
双亲委派模型。
类加载器(ClassLoader)用来实现类的加载动作。JVM中只存在两种不同的类加载器:启动类加载器和其他类加载器。
启动类加载器:即Bootstrap ClassLoader。由C++编写,在JVM内部。其他类加载器都由Java编写,在JVM外部,全部继承于抽象类java.lang.ClassLoader。
类加载器之间的层次关系,称为双亲委派模型。
顶层为启动类加载器,下边为扩展类加载器,再下为应用程序类加载器,其中包含多种自定义类加载器。
如果一个类加载器收到了加载类的请求,它首先不会自己去加载,而是委派给它的父加载器去执行。层层委派之后,到了顶层由启动类加载器加载,只有当父加载器反馈无法加载此请求,才会让子加载器去加载。这种结构使得Java类型体系中的加载机制清晰准确,不易造成混乱。
有一种双亲委派模型的异常情况,即类似启动类加载器这种基础的类加载器,本应默认为所有类适用的加载器,但由于一些环境下调用SPI(Service Provider Interface),绕过双亲委派模型的层次结构使得父加载器委派子加载器去完成类加载动作。
还有一种情况,即为了实现模块的动态性、热部署,不再使用双亲委派模型,而是使用更加复杂的网状结构。OSGi技术即是类加载器网状结构的一个最佳实践。
可以打印当前的内存快照,可以看到快照中有deadLock对象,所在的方法,请求被锁的资源,从而进一步分析死锁原因。
ThreadLocal,很多地方叫做线程本地变量,也有些地方叫做线程本地存储,其实意思差不多。可能很多朋友都知道ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。示例:
class ConnectionManager {
private static Connection connect = null;
public static Connection openConnection() {
if(connect == null){
connect = DriverManager.getConnection();
}
return connect;
}
public static void closeConnection() {
if(connect!=null)
connect.close();
}
}
假设有这样一个数据库链接管理类,这段代码在单线程中使用是没有任何问题的,但是如果在多线程中使用呢?很显然,在多线程中使用会存在线程安全问题:第一,这里面的2个方法都没有进行同步,很可能在openConnection方法中会多次创建connect;第二,由于connect是共享变量,那么必然在调用connect的地方需要使用到同步来保障线程安全,因为很可能一个线程在使用connect进行数据库操作,而另外一个线程调用closeConnection关闭链接。
最常见的ThreadLocal使用场景为 用来解决数据库连接、Session管理等。
c++是类,c中是基本类型函数。
封装,继承,多态。支持面向对象和面向过程的开发。
抛出异常和捕捉异常进行处理。(实际开发)
c是纯过程,c++是对象加过程,java是纯面向对象的
被virtual修饰的成员函数,再基类不能实现,而他的实现放到派生类中实现。
内存泄漏(Memory Leak)是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
指针是一个存储地址的变量,该地址为内存的一个存储单元;引用是原变量的一个别名;
指针可以为空,而引用不能为空;
指针可以有多级,而引用只能有一级;
指针可以重新赋值,而引用只能初始化一次
sizeof引用得到的是变量大小,而sizeof指针得到的是指针本身大小
将引用作为函数返回值的好处是?
在内存中不会产生被返回值的副本,同时不能返回局部变量的引用,因为随着调用结束局部变量会被销毁。
值传递
传递的是实参的一个拷贝,修改形参不会改变实参值。
地址传递
传递的是实参地址的一个拷贝,修改形参会改变实参值。
引用传递
传递的是实参的一个别名,修改形参会导致改变实参。
被调用函数的形参只有在被调用时才会临时分配存储单元,一旦调用结束则释放内存。
被const修饰符修饰的变量不能被修改。const *x表明x数据是常量,不能修改;*const x表明指针本身是常量,x的指针不能指向其他内存地址,x本身可被修改;*const *x表明x本身数据和地址都不能被修改。
被static修饰符修饰的变量在整个文件中都是可见的,而在文件外是不可见的。该变量在全局数据区分配内存。C++中由程序运行new出的动态数据存放在堆区,而函数内部局部变量存放在栈区。
静态局部变量:在函数内部定义static变量,第一次运行到这里初始化,存储到全局数据区,下一次执行到这里不会再重新初始化。
static变量如果没有显式初始化,默认初始值为0。
静态函数不能被其他函数所调用。
1、封装:将客观事物封装成类,隐藏实现细节,模块化代码。
2、多态:实现多态的两种方式——覆盖(重写)和重载。
覆盖是子类重新定义父类的虚函数,与多态真正相关。当子类重新定义了父类的虚函数后,父类指针根据赋给它的不同的子类指针,动态的调用属于子类的该函数,这样的函数调用在编译期间是无法确定的(调用的子类的虚函数的地址无法给出)。因此,这样的函数地址是在运行期绑定的(晚绑定)。
重载是存在多个同名函数,但是函数的参数个数不同。这些函数实际上成为了不同的函数,对它们的调用在编译期间就已经确定,属于早绑定,与多态无关。
3、继承:子类继承父类功能,对父类功能进行扩展。
不是。不同类型间指针可以强制互转。
const修饰常量受到强制保护,程序更健壮,而且const修饰的常量有数据类型,有类型安全检查。
为了防止派生类析构函数未执行,造成资源泄露。
<>是标准头文件,“”是非系统头文件
为了支持运算符的重载。更加方便。
声明指针记得初始化,暂时不用就指向NULL;使用malloc分配内存,必须经过显式释放(free),避免内存泄漏。
有5层结构,分层处理。
1、应用层:应用层是TCP/IP协议的第一层,是直接为应用进程提供服务的。
(1)对不同种类的应用程序它们会根据自己的需要来使用应用层的不同协议,邮件传输应用使用了SMTP协议、万维网应用使用了HTTP协议、远程登录服务应用使用了有TELNET协议。 [1]
(2)应用层还能加密、解密、格式化数据。 [1]
(3)应用层可以建立或解除与其他节点的联系,这样可以充分节省网络资源。
2、运输层:解决的是计算机程序到计算机程序之间的通信问题,即所谓的“端”到 “端”的通信。引入传输层的原因: 增加复用和分用的功能、 消除网络层的不可靠性、 提供从源端主机到目的端主机的可靠的、与实际使用的网络无关的信息传输。运输层是ISO/OSI的第四层,处于通信子网和资源子网之间,是整个协议层次中最核心的一层。作为TCP/IP协议的第二层,运输层在整个TCP/IP协议中起到了中流砥柱的作用。且在运输层中,TCP和UDP也同样起到了中流砥柱的作用。
3、网络层:网络层在TCP/IP协议中的位于第三层。在TCP/IP协议中网络层可以进行网络连接的建立和终止以及IP地址的寻找等功能。
4、网络接口层:网络接口层实际上并不是因特网协议组中的一部分,但是它是数据包从一个设备的网络层传输到另外一个设备的网络层的方法。这个过程能够在网卡的软件驱动程序中控制,也可以在韧体或者专用芯片中控制。这将完成如添加报头准备发送、通过物理媒介实际发送这样一些数据链路功能。另一端,链路层将完成数据帧接收、去除报头并且将接收到的包传到网络层,在TCP/IP协议中,网络接口层位于第四层。由于网络接口层兼并了物理层和数据链路层所以,网络接口层既是传输数据的物理媒介,也可以为网络层提供一条准确无误的线路。
5、网际层:提供独立于硬件的逻辑寻址,从而让数据能够在具有不同物理结构的子网之间传递。提供路由功能来降低数据流量,支持Intemet 上的数据传递(互联网络是指多个局域网互相连接而形成的网络,比如大公司里的网络或是Intemet)。
TCP是可靠的面向连接的传输控制协议,UDP是不可靠的无连接传输数据报文协议。
IP是网络层,MAC是数据链路层且地址是全球唯一的。
ARRP(获得网关地址) - DNS(获得IP地址) - TCP
hub是集线器属于物理层,交换机是数据链路层,router是路由器网络层的,负责不同网络结合。
在国际互联网(Internet)上有成千百万台主机(host),为了区分这些主机,人们给每台主机都分配了一个专门的“地址”作为标识,称为IP地址。子网掩码的作用是用来区分网络上的主机是否在同一网络段内。子网掩码不能单独存在,它必须结合IP地址一起使用。子网掩码只有一个作用,就是将某个IP地址划分成网络地址和主机地址两部分。
IPV6更安全,更大的存储空间。
跨平台的标记语言,重在储存数据。HTML重在存储界面显示内容
Opening System Interconnection - Reference Model
七层模型: 应用层▪ 表示层▪ 会话层▪ 传输层▪ 网络层▪ 数据链路层
第一层是物理层(也即OSI模型中的第一层也是最底层)
在课堂上经常是容易被忽略的。它看起来似乎很简单。但是,这一层的某些方面有时需要特别留意。物理层实际上就是布线、光纤、网卡和其它用来把两台网络通信设备连接在一起的东西。甚至一个信鸽也可以被认为是一个1层设备。网络故障的排除经常涉及到1层问题。我们不能忘记用五类线在整个一层楼进行连接的传奇故事。由于办公室的椅子经常从电缆线上压过,导致网络连接出现断断续续的情况。遗憾的是,这种故障是很常见的,而且排除这种故障需要耗费很长时间。
第二层是数据链路层
运行以太网等协议。请记住,我们要使这个问题简单一些。第2层中最重要的是你应该理解网桥是什么。交换机可以看成网桥,人们都这样称呼它。网桥都在2层工作,仅关注以太网上的MAC地址。如果你在谈论有关MAC地址、交换机或者网卡和驱动程序,你就是在第2层的范畴。集线器属于第1层的领域,因为它们只是电子设备,没有2层的知识。第2层的相关问题在本网络讲座中有自己的一部分,因此先不详细讨论这个问题的细节。只需要知道第2层把数据帧转换成二进制位供1层处理就可以了。
第三层是网络层
在计算机网络中进行通信的两个计算机之间可能会经过很多个数据链路,也可能还要经过很多通信子网。网络层的任务就是选择合适的网间路由和交换结点, 确保数据及时传送。网络层将数据链路层提供的帧组成数据包,包中封装有网络层包头,其中含有逻辑地址信息- -源站点和目的站点地址的网络地址。
如果你在谈论一个IP地址,那么你是在处理第3层的问题,这是“数据包”问题,而不是第2层的“帧”。IP是第3层问题的一部分,此外还有一些路由协议和地址解析协议(ARP)。有关路由的一切事情都在第3层处理。地址解析和路由是3层的重要目的。
第四层是处理信息的传输层。
第4层的数据单元也称作数据包(packets)。但是,当你谈论TCP等具体的协议时又有特殊的叫法,TCP的数据单元称为段(segments)而UDP协议的数据单元称为“数据报(datagrams)”。这个层负责获取全部信息,因此,它必须跟踪数据单元碎片、乱序到达的数据包和其它在传输过程中可能发生的危险。理解第4层的另一种方法是,第4层提供端对端的通信管理。像TCP等一些协议非常善于保证通信的可靠性。有些协议并不在乎一些数据包是否丢失,UDP协议就是一个主要例子。
第五层是会话层
这一层也可以称为会晤层或对话层,在会话层及以上的高层次中,数据传送的单位不再另外命名,统称为报文。会话层不参与具体的传输,它提供包括访问验证和会话管理在内的建立和维护应用之间通信的机制。如服务器验证用户登录便是由会话层完成的。
第六层是表示层
这一层主要解决用户信息的语法表示问题。它将欲交换的数据从适合于某一用户的抽象语法,转换为适合于OSI系统内部使用的传送语法。即提供格式化的表示和转换数据服务。数据的压缩和解压缩, 加密和解密等工作都由表示层负责。
第七层是应用层。
是专门用于应用程序的。应用层确定进程之间通信的性质以满足用户需要以及提供网络与用户应用软件之间的接口服务如果你的程序需要一种具体格式的数据,你可以发明一些你希望能够把数据发送到目的地的格式,并且创建一个第7层协议。SMTP、DNS和FTP都是第7层协议。
应用层协议,使用UDP。分为迭代查询和递归查询。采用分布式集群的工作方式,防止单点故障,增加通信容量。
迭代:主机访问本地域名服务器,若缓存没有IP则本地域名服务器进一步向其他根域名服务器查询。
递归:主机分别向多个服务器发出查询请求。
发送前无需连接,减少了开销和时延,首部开销小,无拥塞控制,方便实时应用,不保证可靠交付,无需维持连接状态表。UDP的可靠性要通过应用层来控制。
字符填充法、字符计数法、比特填充法、违规编码法。
RIP(Routing Information Protocol)路由信息协议。在应用层,最大站点数为15
OSPF(Open Shortest Path First)网络层,洪泛法,迪杰斯特拉算法
同一个函数重复用。
单元,集成,黑盒,白盒,灰盒,。
需求,设计,开发,测试。
系统流程建模工具
运用工程化的方法管理软件开发。
继承:类继承另一个类的功能
实现:类实现接口的功能
依赖:A类的某个方法使用到了B类
关联:强依赖关系,B类作为一个属性出现在了A类
聚合:一种特别的关联,公司与个人的关系
组合:强聚合关系,整体与部分的联系更紧密,如汽车与轮胎
问题定义
可行性研究
需求分析
总体设计
详细设计
编码和单元测试
综合测试
软件维护
黑盒测试:不考虑软件内部原理,以用户角度测试软件输入输出
白盒测试:知道软件内部工作过程,确定每个分支都能按照预定正常工作
灰盒测试:集合白盒黑盒
冒烟测试:测试软件基本功能,快速
系统测试:验证系统是否满足需求规格的黑盒类测试
性能测试:负载测试和压力测试
安全测试:假扮黑客侵入系统
兼容性测试:不同平台不同环境下的测试
自顶向下:从程序入口主控模块开始,按照系统程序结构,沿着控制层次从上而下测试各模块。方便把握整体结构,早期可发现顶层错误。
自底向上:从最底层模块,即叶子结点开始,按照调用从下而上的测试各模块。最后一个模块提交后才能完整系统测试,某些模块可以提前测试。
方法、工具、过程。
1、瀑布模型:前一阶段工作结束才可以进行下一阶段工作。基于文档,易于维护,但加大了工作量。
2、快速原型:快速建立可以运行的程序,完成的功能是最终软件的一个子集。不带反馈环,满足用户真实需求,但会导致系统设计差,难以维护。
3、增量模型:每个阶段不交付完整产品,软件由一系列增量构件组成。降低开发风险,易于维护,但不容易控制整体过程
4、螺旋模型:结合快速原型和瀑布模型,有利于软件重用,减少风险,风险人员需要一定经验。
5、喷泉模型:迭代,无缝,节省开发时间。
6、敏捷
永远不会被执行到的代码。
内聚:指一个好的内聚模块内应当尽量只做一件事,描述的是模块内的功能联系。
耦合:各模块之间相互连接的一种度量,耦合强弱取决于模块间接口的复杂程度。
(1)内聚类型高→低:功能内聚、信息内聚、通信内聚、过程内聚、时间内聚、逻辑内聚、偶然内聚
(2)耦合类型高→低:内容耦合、公共耦合、外部耦合、控制耦合、标记耦合、数据耦合、非直接耦合
顺序结构是指内存连续的存储单元进行存储,而链式结构是指 内存不连续的结构,通过一个节点指向另外一个节点的地址。
栈是先进后出的特殊线性表,队列是先进先出的线性表。
复杂度包括时间复杂度和空间复杂度,用来评价一个算法的好坏。
头节点是指向初始地址的一个节点,它本身数据段没有内容,通过它可以标识这个链表。
树,二叉树:有左右子树的区分和度不超过2.
二叉排序树:左子树均小于根,根均小于右节点。。
线索二叉树:设置两个标识标记左右指针指向的是孩子还是前躯节点。
平衡二叉树:左右子树高度差绝对值小于等于1。
哈夫曼树:压缩用的。权值大小排列。
完全二叉树:只能从右边为空。
二叉树有左右子树的定义。
孩子链存储结构和双亲存储结构。
先序中序后序三种。递归实现。
邻接矩阵和邻接表,是多对多的关系,分为有向图和无向图。
直接查找和有序表的二分查找。
插入排序有直接插入和折半插入。都是在有序表里插入进去的。
交换排序:冒泡,快速:以一个数字划分两个区域,然后分别对两个区域继续划分,直到区间为一。注意快排是不稳定。
选择排序:简单的选择排序,堆排序
归并排序:将两个有序表归并到一个有序表。将两个有序表放到一起进行各个比较,比较完之后放回原来数组内。
一个序列中,关键字相同的数排序后相对位置不变即稳定,比如1、3、2、4、5、2序列,第三个位置2和最后一个位置2排序后,他们的位置先后不变化则稳定。
进队:入A栈。
出队:若B栈不为空,则B栈全部出栈;否则将A栈中数据全部入B栈,再依次出B栈。
两个队列模仿一个栈?
入栈:入A队
出栈:将A队除队尾元素全部转移到B队,出A队,算法思想就是两个队列倒来倒去,只留一个元素时出栈。
设置快慢指针,快指针每次前进两步,当两指针重合则有环,快指针为null则无环。
1、将遍历过的结点都入set,如果当前结点在set里有,则此结点即为入口。
2、快慢指针重合后,重置fast指针,此时fast每次走一步,再次重合结点即为入口。
DP。由最长公共子序列问题的最优子结构性质可知,要找出X=
可以。先将链表排序,将各个结点的值记入数组,再二分查找。
给定一颗二叉树的头结点,和这颗二叉树中2个节点n1和n2,求这两个节点的最近公共祖先
后序遍历方法
public class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root == null || root == p || root == q) return root;
TreeNode left = lowestCommonAncestor(root.left, p, q);
TreeNode right = lowestCommonAncestor(root.right, p, q);
if(left != null && right != null) return root;
return left != null ? left : right;
}
}
左括号入栈,右括号出栈进行匹配,栈空仍未匹配到则失败。
被删除的节点是叶子节点,这时候只要把这个节点删除,再把指向这个节点的父节点指针置为空就行。
被删除的节点有左子树,或者有右子树,而且只有其中一个,那么只要把当前删除节点的父节点指向被删除节点的左子树或者右子树就行。
被删除的节点既有左子树而且又有右子树,这时候需要把左子树的最右边的节点或者右子树最左边的节点提到被删除节点的位置。
O(1)和O(n),n为表长。
两次DFS,第一次找出距离root最远点,第二次以第一次结果为起点找出第二个点,这两点的距离即为直径。
例:单例模式是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
抽象工厂模式是围绕一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
在抽象工厂模式中,接口是负责创建一个相关对象的工厂,不需要显式指定它们的类。每个生成的工厂都能按照工厂模式提供对象。
建造者模式建造者模式(Builder Pattern)使用多个简单的对象一步一步构建成一个复杂的对象。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。一个 Builder 类会一步一步构造最终的对象。该 Builder 类是独立于其他对象的。
工厂模式是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。
原型模式
是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。例如,一个对象需要在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用。
。
例:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式。
例:模版方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式、访问者模式。
1、开闭原则(Open Close Principle)
开闭原则的意思是:对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。简言之,是为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类,后面的具体设计中我们会提到这点。
2、里氏代换原则(Liskov Substitution Principle)
里氏代换原则是面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。LSP 是继承复用的基石,只有当派生类可以替换掉基类,且软件单位的功能不受到影响时,基类才能真正被复用,而派生类也能够在基类的基础上增加新的行为。里氏代换原则是对开闭原则的补充。实现开闭原则的关键步骤就是抽象化,而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。
3、依赖倒转原则(Dependence Inversion Principle)
这个原则是开闭原则的基础,具体内容:针对接口编程,依赖于抽象而不依赖于具体。
4、接口隔离原则(Interface Segregation Principle)
这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。它还有另外一个意思是:降低类之间的耦合度。由此可见,其实设计模式就是从大型软件架构出发、便于升级和维护的软件设计思想,它强调降低依赖,降低耦合。
5、迪米特法则,又称最少知道原则(Demeter Principle)
最少知道原则是指:一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。
6、合成复用原则(Composite Reuse Principle)
合成复用原则是指:尽量使用合成/聚合的方式,而不是使用继承。
改造关系模式,通过分解关系模型来消除其中不合适的数据依赖,以决绝插入异常,删除异常,数据用余。
类似查询一次的命令,要求全部执行完。
原子性,一致性,隔离性,持久性。
数据库结构化,共享性好,独立性。有界面接口。
关系模型,层次模型,网状模型
恰当把握,多的话占空间,少的话查询不足,速度达不到。
原子性Atomicity:一个事务被视为一个最小单元,要么全部提交,要么全部回滚
一致性Consistency:事务总是由一种状态转换为另一种状态,数据库事务只会是执行前的状态或是执行后的状态,不会出现执行中的状态。即如果一个事务执行了十秒,那么第一秒读到的结果和第九秒得到的应该是相同的。
隔离性Isolation:一个事务的执行不会被另一个事务影响,互不干扰。
持久性Durability:事务只要提交了,那么数据库中的数据也永久的发生了变化。
1NF(Normal Form):R的所有属性都不能再分解为更基本的数据单位。
2NF:R的所有非主属性都依赖于R的关键属性,所有列都依赖于任意一组候选关键字。
3NF:每一列都与任意候选关键字直接相关而不是间接相关,没有传递依赖。
BCNF:3NF基础上,关系R只有一个单属性,或R的子集都是单属性,则R满足BCNF。
100数量级小,可以随意插入;100万数量级大,如果表里有索引,则索引更新代价很高,可以采取先删除索引再插入,插入完成后再建索引的策略。
可以。大小受到主机内存的制约。数据量大时要先删索引。减少提交次数,即减少IO次数。
表分区,备份,入带库。
WHERE 子句用来筛选 FROM 子句中指定的操作所产生的行。
GROUP BY 子句用来分组 WHERE 子句的输出。
HAVING 子句用来从分组的结果中筛选行。
在查询过程中聚合语句(sum,min,max,avg,count)要比having子句优先执行.而where子句在查询过程中执行优先级别优先于聚合语句(sum,min,max,avg,count)。
产生死锁的原因主要是:
(1) 因为系统资源不足。
(2) 进程运行推进的顺序不合适。
(3) 资源分配不当等。
产生死锁的四个必要条件:
(1)互斥条件:一个资源每次只能被一个进程使用。
(2)请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
(3)不可剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
(4)循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
死锁的预防是通过破坏产生条件来阻止死锁的产生,但这种方法破坏了系统的并行性和并发性。
死锁产生的前三个条件是死锁产生的必要条件,也就是说要产生死锁必须具备的条件,而不是存在这3个条件就一定产生死锁,那么只要在逻辑上回避了第四个条件就可以避免死锁。
避免死锁采用的是允许前三个条件存在,但通过合理的资源分配算法来确保永远不会形成环形等待的封闭进程链,从而避免死锁。该方法支持多个进程的并行执行,为了避免死锁,系统动态的确定是否分配一个资源给请求的进程。
预防死锁:具体的做法是破坏产生死锁的四个必要条件之一。
银行家算法:该算法需要检查申请者对各类资源的最大需求量,如果现存的各类资源可以满足当前它对各类资源的最大需求量时,就满足当前的申请。换言之,仅当申请者可以在一定时间内无条件归还它所申请的全部资源时,才能把资源分配给它。这样申请者就可以很快完成其计算,然后释放它占用的资源,从而保证了系统中的所有进程都能完成,所以可以避免死锁的发生。这种算法的主要问题是,要求每个进程必须先知道资源的最大需求量,而且在系统的运行过程中,考察每个进程对各类资源的申请需花费较多的时间。另外,这一算法本身也有些保守,因为它总是考虑最坏可能的情况。
1、什么是Redis?简述它的优缺点?
Redis本质上是一个Key-Value类型的内存数据库,很像memcached,整个数据库统统加载在内存当中进行操作,定期通过异步操作把数据库数据flush到硬盘上进行保存。
因为是纯内存操作,Redis的性能非常出色,每秒可以处理超过 10万次读写操作,是已知性能最快的Key-Value DB。
Redis的出色之处不仅仅是性能,Redis最大的魅力是支持保存多种数据结构,此外单个value的最大限制是1GB,不像 memcached只能保存1MB的数据,因此Redis可以用来实现很多有用的功能。
比方说用他的List来做FIFO双向链表,实现一个轻量级的高性 能消息队列服务,用他的Set可以做高性能的tag系统等等。
另外Redis也可以对存入的Key-Value设置expire时间,因此也可以被当作一 个功能加强版的memcached来用。 Redis的主要缺点是数据库容量受到物理内存的限制,不能用作海量数据的高性能读写,因此Redis适合的场景主要局限在较小数据量的高性能操作和运算上。
2、Redis相比memcached是一套分布式的高速缓存系统,有哪些优势?
(1) memcached所有的值均是简单的字符串,redis作为其替代者,支持更为丰富的数据类型
(2) redis的速度比memcached快很多
(3) redis可以持久化其数据
3、Redis支持哪几种数据类型?
String、List、Set、Sorted Set、hashes
4、Redis主要消耗什么物理资源?
内存。
5、Redis的全称是什么?
Remote Dictionary Server。
6、Redis有哪几种数据淘汰策略?
6中淘汰策略。
noeviction:返回错误当内存限制达到并且客户端尝试执行会让更多内存被使用的命令(大部分的写入指令,但DEL和几个例外)
allkeys-lru: 尝试回收最少使用的键(LRU),使得新添加的数据有空间存放。
volatile-lru: 尝试回收最少使用的键(LRU),但仅限于在过期集合的键,使得新添加的数据有空间存放。
allkeys-random: 回收随机的键使得新添加的数据有空间存放。
volatile-random: 回收随机的键使得新添加的数据有空间存放,但仅限于在过期集合的键。
volatile-ttl: 回收在过期集合的键,并且优先回收存活时间(TTL)较短的键,使得新添加的数据有空间存放。
7、Redis官方为什么不提供Windows版本?
因为目前Linux版本已经相当稳定,而且用户量很大,无需开发windows版本,反而会带来兼容性等问题。
8、一个字符串类型的值能存储最大容量是多少?
512M
9、为什么Redis需要把所有数据放到内存中?
Redis为了达到最快的读写速度将数据都读到内存中,并通过异步的方式将数据写入磁盘。
所以redis具有快速和数据持久化的特征。如果不将数据放在内存中,磁盘I/O速度为严重影响redis的性能。
在内存越来越便宜的今天,redis将会越来越受欢迎。 如果设置了最大使用的内存,则数据已有记录数达到内存限值后不能继续插入新值。
10、Redis集群方案应该怎么做?都有哪些方案?
1.codis。
目前用的最多的集群方案,基本和twemproxy一致的效果,但它支持在 节点数量改变情况下,旧节点数据可恢复到新hash节点。
2.redis cluster3.0自带的集群,特点在于他的分布式算法不是一致性hash,而是hash槽的概念,以及自身支持节点设置从节点。具体看官方文档介绍。
4.在业务代码层实现,起几个毫无关联的redis实例,在代码层,对key 进行hash计算,然后去对应的redis实例操作数据。 这种方式对hash层代码要求比较高,考虑部分包括,节点失效后的替代算法方案,数据震荡后的自动脚本恢复,实例的监控,等等。
11、Redis集群方案什么情况下会导致整个集群不可用?
有A,B,C三个节点的集群,在没有复制模型的情况下,如果节点B失败了,那么整个集群就会以为缺少5501-11000这个范围的槽而不可用。
12、MySQL里有2000w数据,redis中只存20w的数据,如何保证redis中的数据都是热点数据?
redis内存数据集大小上升到一定大小的时候,就会施行数据淘汰策略。
13、Redis有哪些适合的场景?
(1)会话缓存(Session Cache)
最常用的一种使用Redis的情景是会话缓存(session cache)。用Redis缓存会话比其他存储(如Memcached)的优势在于:Redis提供持久化。当维护一个不是严格要求一致性的缓存时,如果用户的购物车信息全部丢失,大部分人都会不高兴的,现在,他们还会这样吗?
幸运的是,随着 Redis 这些年的改进,很容易找到怎么恰当的使用Redis来缓存会话的文档。甚至广为人知的商业平台Magento也提供Redis的插件。
(2)全页缓存(FPC)
除基本的会话token之外,Redis还提供很简便的FPC平台。回到一致性问题,即使重启了Redis实例,因为有磁盘的持久化,用户也不会看到页面加载速度的下降,这是一个极大改进,类似PHP本地FPC。
再次以Magento为例,Magento提供一个插件来使用Redis作为全页缓存后端。
此外,对WordPress的用户来说,Pantheon有一个非常好的插件 wp-redis,这个插件能帮助你以最快速度加载你曾浏览过的页面。
(3)队列
Reids在内存存储引擎领域的一大优点是提供 list 和 set 操作,这使得Redis能作为一个很好的消息队列平台来使用。Redis作为队列使用的操作,就类似于本地程序语言(如Python)对 list 的 push/pop 操作。
如果你快速的在Google中搜索“Redis queues”,你马上就能找到大量的开源项目,这些项目的目的就是利用Redis创建非常好的后端工具,以满足各种队列需求。例如,Celery有一个后台就是使用Redis作为broker,你可以从这里去查看。
(4)排行榜/计数器
Redis在内存中对数字进行递增或递减的操作实现的非常好。集合(Set)和有序集合(Sorted Set)也使得我们在执行这些操作的时候变的非常简单,Redis只是正好提供了这两种数据结构。
所以,我们要从排序集合中获取到排名最靠前的10个用户–我们称之为“user_scores”,我们只需要像下面一样执行即可:
当然,这是假定你是根据你用户的分数做递增的排序。如果你想返回用户及用户的分数,你需要这样执行:
ZRANGE user_scores 0 10 WITHSCORES
Agora Games就是一个很好的例子,用Ruby实现的,它的排行榜就是使用Redis来存储数据的,你可以在这里看到。
(5)发布/订阅
最后(但肯定不是最不重要的)是Redis的发布/订阅功能。发布/订阅的使用场景确实非常多。我已看见人们在社交网络连接中使用,还可作为基于发布/订阅的脚本触发器,甚至用Redis的发布/订阅功能来建立聊天系统!
14、Redis支持的Java客户端都有哪些?官方推荐用哪个?
Redisson、Jedis、lettuce等等,官方推荐使用Redisson。
15、Redis和Redisson有什么关系?
Redisson是一个高级的分布式协调Redis客服端,能帮助用户在分布式环境中轻松实现一些Java的对象 (Bloom filter, BitSet, Set, SetMultimap, ScoredSortedSet, SortedSet, Map, ConcurrentMap, List, ListMultimap, Queue, BlockingQueue, Deque, BlockingDeque, Semaphore, Lock, ReadWriteLock, AtomicLong, CountDownLatch, Publish / Subscribe, HyperLogLog)。
16、Jedis与Redisson对比有什么优缺点?
Jedis是Redis的Java实现的客户端,其API提供了比较全面的Redis命令的支持;
Redisson实现了分布式和可扩展的Java数据结构,和Jedis相比,功能较为简单,不支持字符串操作,不支持排序、事务、管道、分区等Redis特性。Redisson的宗旨是促进使用者对Redis的关注分离,从而让使用者能够将精力更集中地放在处理业务逻辑上。
17、Redis如何设置密码及验证密码?
设置密码:config set requirepass 123456
授权密码:auth 123456
18、说说Redis哈希槽的概念?
Redis集群没有使用一致性hash,而是引入了哈希槽的概念,Redis集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽,集群的每个节点负责一部分hash槽。
19、Redis集群的主从复制模型是怎样的?
为了使在部分节点失败或者大部分节点无法通信的情况下集群仍然可用,所以集群使用了主从复制模型,每个节点都会有N-1个复制品.
20、Redis集群会有写操作丢失吗?为什么?
Redis并不能保证数据的强一致性,这意味这在实际中集群在特定的条件下可能会丢失写操作。
21、Redis集群之间是如何复制的?
异步复制
22、Redis集群最大节点个数是多少?
16384个。
23、Redis集群如何选择数据库?
Redis集群目前无法做数据库选择,默认在0数据库。
24、怎么测试Redis的连通性?
ping
25、Redis中的管道有什么用?
一次请求/响应服务器能实现处理新的请求即使旧的请求还未被响应。这样就可以将多个命令发送到服务器,而不用等待回复,最后在一个步骤中读取该答复。
这就是管道(pipelining),是一种几十年来广泛使用的技术。例如许多POP3协议已经实现支持这个功能,大大加快了从服务器下载新邮件的过程。
26、怎么理解Redis事务?
事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。
27、Redis事务相关的命令有哪几个?
MULTI、EXEC、DISCARD、WATCH
28、Redis key的过期时间和永久有效分别怎么设置?
EXPIRE和PERSIST命令。
29、Redis如何做内存优化?
尽可能使用散列表(hashes),散列表(是说散列表里面存储的数少)使用的内存非常小,所以你应该尽可能的将你的数据模型抽象到一个散列表里面。
比如你的web系统中有一个用户对象,不要为这个用户的名称,姓氏,邮箱,密码设置单独的key,而是应该把这个用户的所有信息存储到一张散列表里面。
30、Redis回收进程如何工作的?
一个客户端运行了新的命令,添加了新的数据。
Redi检查内存使用情况,如果大于maxmemory的限制, 则根据设定好的策略进行回收。
一个新的命令被执行,等等。
所以我们不断地穿越内存限制的边界,通过不断达到边界然后不断地回收回到边界以下。
如果一个命令的结果导致大量内存被使用(例如很大的集合的交集保存到一个新的键),不用多久内存限制就会被这个内存使用量超越。
redis分布式锁?
setnx(set if not exist)和expire、delete联合实现(假设各个客户端时钟大致相同,误差处于可接受范围)释放锁的操作用lua脚本实现来保证原子性。在集群环境下分布式锁的key值应当是随机的不可重复的,否则如果一个客户端获得了锁,但发生了阻塞,当锁过期redis自动释放了资源,这时第二个客户端获得了锁,客户端1此时从阻塞中恢复释放了锁,就会造成混乱。
为什么Redis是单线程的
单线程指的是网络请求模块使用了一个线程(所以不需考虑并发安全性),即一个线程处理所有网络请求,其他模块仍用了多个线程(epoll模型)。
因为Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且CPU不会成为瓶颈,那就顺理成章地采用单线程的方案了。
Redis为什么这么快
1、完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1);
2、数据结构简单,对数据操作也简单,Redis中的数据结构是专门进行设计的;
3、采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;
4、使用多路I/O复用模型,非阻塞IO;
5、使用底层模型不同,它们之间底层实现方式以及与客户端之间通信的应用协议不一样,Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求;
(1)多路 I/O 复用模型
多路I/O复用模型是利用 select、poll、epoll 可以同时监察多个流的 I/O 事件的能力,在空闲的时候,会把当前线程阻塞掉,当有一个或多个流有 I/O 事件时,就从阻塞态中唤醒,于是程序就会轮询一遍所有的流(epoll 是只轮询那些真正发出了事件的流),并且只依次顺序的处理就绪的流,这种做法就避免了大量的无用操作。
这里“多路”指的是多个网络连接,“复用”指的是复用同一个线程。采用多路 I/O 复用技术可以让单个线程高效的处理多个连接请求(尽量减少网络 IO 的时间消耗),且 Redis 在内存中操作数据的速度非常快,也就是说内存内的操作不会成为影响Redis性能的瓶颈,主要由以上几点造就了 Redis 具有很高的吞吐量。
缓存雪崩、缓存穿透、缓存热点?
一、缓存穿透预防及优化
缓存穿透是指查询一个根本不存在的数据,缓存层和存储层都不会命中,但是出于容错的考虑,如果从存储层查不到数据则不写入缓存层,如图 11-3 所示整个过程分为如下 3 步:
下面给出了缓存空对象的实现伪代码:
2)布隆过滤器拦截
如下图所示,在访问缓存层和存储层之前,将存在的 key 用布隆过滤器提前保存起来,做第一层拦截。
例如: 一个个性化推荐系统有 4 亿个用户 ID,每个小时算法工程师会根据每个用户之前历史行为做出来的个性化放到存储层中,但是最新的用户由于没有历史行为,就会发生缓存穿透的行为,为此可以将所有有个性化推荐数据的用户做成布隆过滤器。如果布隆过滤器认为该用户 ID 不存在,那么就不会访问存储层,在一定程度保护了存储层。
开发提示:
有关布隆过滤器的相关知识,可以参考: Bloom Filter(布隆过滤器)的概念和原理
可以利用 Redis 的 Bitmaps 实现布隆过滤器,GitHub 上已经开源了类似的方案,读者可以进行参考:
https://github.com/erikdubbelboer/Redis-Lua-scaling-bloom-filter
使用布隆过滤器应对穿透问题
这种方法适用于数据命中不高,数据相对固定实时性低(通常是数据集较大)的应用场景,代码维护较为复杂,但是缓存空间占用少。
两种方案对比
前面介绍了缓存穿透问题的两种解决方法 ( 实际上这个问题是一个开放问题,有很多解决方法 ),下面通过下表从适用场景和维护成本两个方面对两种方案进行分析。
缓存空对象和布隆过滤器方案对比
三、缓存雪崩问题优化
从下图可以很清晰出什么是缓存雪崩:由于缓存层承载着大量请求,有效的保护了存储层,但是如果缓存层由于某些原因整体不能提供服务,于是所有的请求都会达到存储层,存储层的调用量会暴增,造成存储层也会挂掉的情况。缓存雪崩的英文原意是 stampeding herd(奔逃的野牛),指的是缓层宕掉后,流量会像奔逃的野牛一样,打向后端存储。
预防和解决缓存雪崩问题,可以从以下三个方面进行着手。
1)保证缓存层服务高可用性。
和飞机都有多个引擎一样,如果缓存层设计成高可用的,即使个别节点、个别机器、甚至是机房宕掉,依然可以提供服务,例如前面介绍过的 Redis Sentinel 和 Redis Cluster 都实现了高可用。
2)依赖隔离组件为后端限流并降级。
无论是缓存层还是存储层都会有出错的概率,可以将它们视同为资源。作为并发量较大的系统,假如有一个资源不可用,可能会造成线程全部 hang 在这个资源上,造成整个系统不可用。降级在高并发系统中是非常正常的:比如推荐服务中,如果个性化推荐服务不可用,可以降级补充热点数据,不至于造成前端页面是开天窗。
在实际项目中,我们需要制定如下目标:
云计算是一种按使用量付费的模式,这种模式提供可用的、便捷的、按需的网络访问, 进入可配置的计算资源共享池,这些资源能够被快速提供,只需投入很少的管理工作,或与服务供应商进行很少的交互。基础设施、安装配置好开发环境、应用服务为云计算三个层面。
1.Volume:数据量巨大
体量大是大数据区分于传统数据最显著的特征。一般关系型数据库处理的数据量在TB级,大数据所处理的数据量通常在PB级以上。
2.Variety:数据类型多
大数据所处理的计算机数据类型早已不是单一的文本形式或者结构化数据库中的表,它包括订单、日志、BLOG、微博、音频、视频等各种复杂结构的数据。
3.Velocity:数据流动快
速度是大数据区分于传统数据的重要特征。在海量数据面前,需要实时分析获取需要的信息,处理数据的效率就是组织的生命。
4.Value:数据潜在价值大
在研究和技术开发领域,上述三个特征已经足够表征大数据的特点。但在商业应用领域,第四个特征就显得非常关键!投入如此巨大的研究和技术开发的努力,就是因为大家
都洞察到了大数据的潜在巨大价值。如何通过强大的机器学习和高级分析更迅速地完成数据的价值“提纯”,挖掘出大数据的潜在价值,这是目前大数据应用背景下苛待解决的难题。
没有成熟的方法采集和处理大数据。
数据涉及到隐私,法律法规还没有完善。
大量不同类别的数据不知道怎么存储。
数据的独占性:有价值的数据别人不一定会分享。
人工智能
算力和数据是核心,以及神经网络、遗传算法、深度学习。
区块链:blockchain
存储数据的一个个块,通过链char256算法将块链接起来。
KNN算法的优点:
1、思想简单,理论成熟,既可以用来做分类也可以用来做回归;
2、可用于非线性分类;
3、训练时间复杂度为O(n);
4、准确度高,对数据没有假设,对outlier不敏感;
缺点:
1、计算量大;
2、样本不平衡问题(即有些类别的样本数量很多,而其它样本的数量很少);
3、需要大量的内存;
其伪代码如下:
Windows 能够流行起来,很大一个原因是它有友好的用户图形界面,操作方便简单,容易上手。在Windows环境下打开一个程序,只要双击软件的图标就行了,那么它是如何启动起来的?
当我们启动电脑进入桌面时,系统会创建 Explorer.exe 进程。Explorer.exe是Windows程序管理器 或者叫 文件资源管理器,用于管理Windows图形壳,删除该程序会导致 Windows 图形界面无法使用。所以,如果有时候我们电脑的桌面空白了,或者蓝屏,可以通过 Alt+Ctrl+delete(或者在dos中输入 taskmgr 命令) 打开任务管理器, 点击“文件”-> “新建任务”,输入 “explorer.exe”,就可以找回我们的桌面了。
当双击某个图标时,Explorer.exe进程的一个线程会侦测到这个操作,它根据注册表中的信息取得文件名,然后Explorer.exe 以这个文件名调用 CreateProcess 函数。注册表中有相关的项保存着双击操作的信息,如 exe 文件关联、启动 exe 的 Shell 是哪个。PC中的大多其它的进程都是 Explorer.exe 的子进程,因为它们都是由Explorer.exe 进程创建的。
(1)CreateProcess 实际上是通过 NtCreateProcess 函数实现的, 此时,系统会创建一个进程内核对象。进程内核对象可以看作是操作系统用来管理进程的小的数据结构,它是在内核堆区分配的一个结构体,是系统用来存放关于进程统计信息的地方。进程内核对象维护了一个句柄表的结构,当进程被初始化之后,其句柄表是空的。当进程内的一个线程通过指定的函数创建了一个内核对象时,内核会为对象分配一块内存区域并初始化这块区域,然后内核会在进程的句柄表中查找一个空的入口,找到之后会初始化句柄表的以索引定位的区域。初始化的主要过程就是填充句柄表的一个单元,包括指定内核对象地址,指定访问码,指定标记等。
(2)进程内核对象创建后,它的引用计数被置为1。然后系统为刚刚创建的进程分配的进程虚拟地址空间。要注意了,之所以称为虚拟地址空间,就是因为这块地址空间并不在内存之中,它只是在硬盘上划分的被称为“页”的文件。每个进程都有自己的虚拟地址空间,在进程初始化的时候,其所有的程序和数据会被加载到这个地址空间中。等到真正运行的时候,系统为每个进程配置的页表会把虚拟地址映射为真正的物理地址(这个过程,我会在后面的博客中详细介绍如何映射)。
(3)初始化虚拟地址空间。进程地址空间创建后,Windows的装载器(loader,也称为PE装载器)开始工作,Loader会读取EXE文件的信息(PE文件)。此时 loader 会检查PE文件的有效性,如果PE文件有错误,进程也就无法启动了。如果PE文件没有错误,装载器就把PE文件的内容(二进制代码)映射到进程的地址空间中,然后读取 PE文件的导入地址表(Import Table),这里存放有exe文件需要导入的模块文件(DLL),系统会一一加载这些DLL到进程的地址空间中,具体做法是调用 LoadLibrary 函数加载程序代码到某个地址,然后系统会映射这些代码到进程的地址空间中,要知道DLL只需加载一次就可映射到所有进程的地址空间(映射过程我会在后面详细阐述)中,并为每个DLL维护一个引用计数,当引用计数为 0 时,DLL就从内存中卸载,释放占用的内存。DLL里面可能又引用了其它的DLL,因此加载DLL时是递归形式的,直到加载完Import Table 里描述的所有DLL模块,此时进程初始化部分完成。
(4)创建进程的主线程。当进程的初始化完成后,开始创建进程的主线程,一个进程至少要有一个主线程才能运行,可以说进程只是充当一个容器的作用,而线程才是执行代码的载体。线程是用 CreateThread 这个函数创建的。创建线程时,也和进程相似,系统会创建线程内核对象,初始化线程堆栈。线程堆栈有两个,一个是核心堆栈,由核心态维护;另一个是用户堆栈,运行在用户态下。同样的,线程的引用计数也置为1。
(5)C/C++运行期库初始化。当进程的主线程初始化完成后,并且线程得到了CPU时间片,CPU把CS:IP指向程序入口(OEP),这个地址相当重要,因为这是程序运行时第一条指令所在的地址(我们可以使用一些PE辅助工具来查看PE文件的地址信息,注意真实地址==偏移地址 + 基址)。其实,CS:IP指向的地址处是一条JMP指令,它跳转到程序真正的入口函数,入口函数有以下4种形式:
···
mainCRTStartup (用于 ANSI 版本的控制台应用程序 )
wmainCRTStartup ( 用于 Unicode 版本的控制台应用程序 )
WinMainCRTStartup ( 用于 ANSI 版本的窗口应用程序 )
wWinMainCRTStartup ( 用于 Unicode 版本的窗口应用程序)
·
信息是个很抽象的概念。人们常常说信息很多,或者信息较少,但却很难说清楚信息到底有多少。比如一本五十万字的中文书到底有多少信息量。直到1948年,香农提出了“信息熵”的概念,才解决了对信息的量化度量问题。
公式为:H(x)=E[I(xi)]=E[ log(2,1/p(xi)) ]=-∑p(xi)log(2,p(xi)) (i=1,2,…n)该值越大表示信息量越大
在一个系统中,该系统越混乱,那么就越难把它搞清楚,需要的信息量就越大,信息熵就越大,回到数据挖掘中用决策树进行分类中,在分类的之前,我们需要建立一个决策树,在建立决策树的时候属性的选择是一个非常关键的问题,我们选择的属性的标准是让划分尽量纯(落在给定划分中的元祖都属于相同类的越多,那么就越纯),结合上面我们可以推理出如果按照某个属性划分后,每个该属性属性值所对应的元组越统一(元组所属的类别越统一),那么我们这个属性的选择就越符合我们的需求。和信息熵结合,就是选择该属性之后,所有属性值对应的分类的信息熵之和越小,那么我们元组分类所需要的平均信息越少,该属性就越符合我们的要求
决策树模型是一类算法的集合,在数据挖掘十大算法中,具体的决策树算法占有两席位置,即C4.5和CART算法,本文都会介绍到它们。
决策树基础
决策树是一种用于对实例进行分类的树形结构。决策树由节点(node)和有向边(directed edge)组成。节点的类型有两种:内部节点和叶子节点。其中,内部节点表示一个特征或属性的测试条件(用于分开具有不同特性的记录),叶子节点表示一个分类。
一旦我们构造了一个决策树模型,以它为基础来进行分类将是非常容易的。具体做法是,从根节点开始,地实例的某一特征进行测试,根据测试结构将实例分配到其子节点(也就是选择适当的分支);沿着该分支可能达到叶子节点或者到达另一个内部节点时,那么就使用新的测试条件递归执行下去,直到抵达一个叶子节点。当到达叶子节点时,我们便得到了最终的分类结果。