一些面试题

数据结构

bTree和b+Tree

先从二叉树说起,二叉树会退化,所以提出了平衡二叉树,有可以通过每一层的节点多一些从而减少遍历高度(磁盘IO次数),引申出m叉树,m叉树同样有退化现象,引出m叉平衡树(b树)。b树同样可以通过增加每一层的节点数量减少遍历高度,即将每个节点只存key值,value放在其叶子节点,叶子节点的value值指向相邻节点的指针,这就是优化后的b+树。

海量数据处理

https://blog.csdn.net/wantflydacheng/article/details/81531994

spring

springMVC常用注解

@autowired是spring提供的自动注入注解,默认按类型注入。可以注入变量/setter/构造器
@resource则是jsr-250标准对java依赖注入的定义,默认按name注入,若找不到名字,则按类型注入
@inject是jsr-330规范提供的注解,等同于@autowired
@qualifier用于当接口存在多个实现类的bean时指定使用的bean,避免@autowired产生异常
@modelAttribute用于将controller的返回值写入session

@requestMapping/@requestBody/@responseBody/@requestParam等等等等

@profile用于标注当前运行环境(测试环境/开发/线上),并根据不同运行环境使用不同的配置文件

 

springMVC执行流程

1.http请求进入dispatcherServlet
2.HandlerMapping将会把请求映射为HandlerExecutionChain对象(包含一个Handler处理器(页面控制器)对象、多个HandlerInterceptor拦截器)对象,通过这种策略模式,很容易添加新的映射策略;
把url映射到指定的controller
3.HandlerAdapter将会把处理器包装为适配器,从而支持多种类型的处理器,即适配器设计模式的应用,从而很容易支持很多类型的处理器。根据适配的结果交给相应的控制器处理。
适配定位具体的controller处理类
4.返回modelAndView对象,由dispatcherServlet交给viewResolver进行解析

Spring Bean的作用域之间有什么区别?

singleton:这种bean范围是默认的,这种范围确保不管接受到多少个请求,每个容器中只有一个bean的实例,单例的模式由bean factory自身来维护。
prototype:原形范围与单例范围相反,为每一个bean请求提供一个实例。
request:在请求bean范围内会每一个来自客户端的网络请求创建一个实例,在请求完成以后,bean会失效并被垃圾回收器回收。
Session:与请求范围类似,确保每个session中有一个bean的实例,在session过期后,bean会随之失效。
global-session:global-session和Portlet应用相关。当你的应用部署在Portlet容器中工作时,它包含很多portlet。如果你想要声明让所有的portlet共用全局的存储变量的话,那么这全局变量需要存储在global-session中。
全局作用域与Servlet中的session作用域效果相同。

 

请举例说明如何在Spring中注入一个Java Collection?

Spring提供了以下四种集合类的配置元素:
:   该标签用来装配可重复的list值。
:    该标签用来装配没有重复的set值。
:   该标签可用来注入键和值可以为任何类型的键值对。
: 该标签支持注入键和值都是字符串类型的键值对。

 

构造方法注入和设值注入有什么区别?

设值注入不会重写构造方法的值。同时使用了构造方法注入又使用了设置注入的话,构造方法将不能覆盖由设值方法注入的值。很明显,因为构造方法尽在对象被创建时调用。
在使用设值注入时有可能还不能保证某种依赖是否已经被注入,也就是说这时对象的依赖关系有可能是不完整的。而在另一种情况下,构造器注入则不允许生成依赖关系不完整的对象。
在设值注入时如果对象A和对象B互相依赖,Spring用设值注入的方法解决了循环依赖的问题,因对象的设值方法是在对象被创建之前被调用的。

IOC的循环依赖问题

如上文所描述,通过构造器注入时,对象创建过程是原子性的,不允许生成不完整对象。而设值注入和注解则可以生成不完整对象,即经过对象创建的几个步骤后(检查class加载、分配内存、初始化对象头、初始化全局变量等、修改引用指针)再去用set注入对象。IOC底层有一个持有早期引用的hashMap,用于保存不完整对象,如果IOC检测到循环依赖,则会从这个hashMap中拿到不完整对象B然后注入到对象A的引用中,继而拆开循环依赖的死结。

FileSystemResource和ClassPathResource有何区别?

简而言之,ClassPathResource在环境变量中读取配置文件,FileSystemResource在配置文件中读取配置文件。

 

Spring 框架中都用到了哪些设计模式?

代理模式—在AOP和remoting中被用的比较多。
单例模式—在spring配置文件中定义的bean默认为单例模式。
模板方法—用来解决代码重复的问题。比如. RestTemplate, JmsTemplate, JpaTemplate。
前端控制器—Spring提供了DispatcherServlet来对请求进行分发。
视图帮助(View Helper )—Spring提供了一系列的JSP标签,高效宏来辅助将分散的代码整合在视图里。
依赖注入—贯穿于BeanFactory / ApplicationContext接口的核心理念。
工厂模式—BeanFactory用来创建对象的实例。

IOC理解:
依赖注入,控制反转。oop中的一种设计模式,可以有效降低代码耦合,减少重复代码。核心是把对象的创建过程交给容器去做,同时由容器管理其生命周期,如果要使用某个对象,需要去向容器申请,可以降低复杂依赖关系的系统复杂度,减少无用对象的创建,降低内存占用,降低程序员的心智负担。使用反射在系统初始化时创建好对象,然后保存在线程安全的map中,随用随取。
spring core注入的bean有几个等级,prototype、request、session。

AOP:
面向切面编程,与正常的编码逻辑有所区别,更关注系统context的一个点上,横向的去处理业务逻辑,而不是像正常业务代码那样纵向延申,可以减少大量重复代码,降低系统耦合。
可用于日志、安全、拦截器、事务、统一异常、统一加解密等等。
spring的aop有两种,动态代理和静态代理,静态代理使用aspectj,编译期写入字节码,动态代理则在运行时写入,先是jdk动态代理,在是cglib动态代理。

mybatis

 

preparestatement与statement区别

1.preparestatement支持参数化查询,使用占位符?
2.preparestatement比statement性能要高一些,sql语句会被预编译在DB中,执行计划会被缓存起来
3.preparestatement可以防止sql注入,使用占位符避免SQL语义被混淆
 

mybatis常用标签











关联查询
查询集合
和set/when一个作用,拼接sql,去除多余符号

mybatis接口编程

配置文件使用全限定名+方法名定位接口,因此接口不能重载
在mapper文件中,每一个标签均会被解析为MappedStatement对象,标签内的sql会被解析为BoundSql对象。
 

mybatis和hibernate

Hibernate是纯OOP思路的ORM框架,查询关联对象或者关联集合对象时,可以根据ORM模型直接获取,向高层隐藏了sql的实现。
而Mybatis在查询关联对象或关联集合对象时,需要手动编写sql来完成,更方便做sql优化和修改。

MyBatis的缓存

Mybatis中有一级缓存和二级缓存,默认情况下一级缓存是开启的,而且是不能关闭的。一级缓存是指SqlSession级别的缓存,当在同一个SqlSession中进行相同的SQL语句查询时,第二次以后的查询不会从数据库查询,而是直接从缓存中获取,一级缓存最多缓存1024条SQL。二级缓存是指可以跨SqlSession的缓存。

 

多线程

线程的生命周期

runnable就绪状态,线程已经创建成功,可以开始运行   

running运行时 

wait线程等待,不吃cpu时间片   

wait_time指定时间的线程等待 

blocking线程阻塞

dead线程消亡/退出

 

wait/notify原理
wait/notify并非一直在wait,底层有等待队列和同步队列,如果不满足条件,wait线程获取到锁,将本线程加入到等待队列,然后释放锁
之后条件满足,通知线程随后获取到锁,将等待队列中的线程移到同步线程,此时wait线程为阻塞状态,notify线程释放锁后,wait线程马上获取到锁并继续执行。

 

reentrantlock和synchronied的区别

reentrantLock性能高于synchronied锁,使用可重入锁需要手动unlock,灵活性较高,而且顾名思义,它可以多次加锁,线程也可以多次持有可重入锁。sychronized也是可重入锁
reentrantLock的锁中断可以解决死锁问题,当线程被interrupt中断时,该线程会放弃对锁的申请,从而解决死锁
reentrantLock可以限时锁申请,通过tryLock方法设置线程最长等待时间
reentrantLock使用公平锁,和synchronized不同,并非随机响应线程的申请,而是根据先来后到

子线程异常处理

子线程与主线程是相互独立的,主线程无法catch到子线程发生的异常,这时可以用thread.setUncaughtExceptionHandler()来catch子线程的异常来进行处理。

CAS的实现原理以及问题

与synachronized悲观锁不同,CAS基于比较交换的策略,不停尝试更新变量直到成功。
但可能产生ABA问题,即第一次修改A不成功,将修改后的B重置为A,这个过程中变量发生了变化。但CAS是不知道的,ABA问题的解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A 就会变成1A-2B-3A。

ThreadLocal作用

threadLocal线程变量副本,为不同线程提供了相互隔离的变量。set方法通过获取当前Threadlocal对象作为key,存入到ThreadLocalMap中(类似一个hashmap,但它属于thread的内部类,可以保持其在线程运行时一直存在)
注意get取出变量并完成操作后要remove清理该线程的变量副本,因为在一些特殊情况(比如固定容量的线程池)线程可能不会直接退出,对象得不到清理可能会产生内存泄漏
Spring中绝大部分Bean都可以声明成Singleton作用域,采用ThreadLocal进行封装,因此有状态的Bean就能够以singleton的方式在多线程中正常工作了。
 

failfast机制

一种iterator的机制,多线程环境下多个线程同时对一个集合进行遍历/修改,会抛出concurrentModitycationException异常。
hashmap的迭代器即fail-fast的,hashtable则不是,因为hashtable在对元素的修改会先做一份集合拷贝,没有并发问题
 

volatile关键字

修饰一个变量时,该变量内存可见。同时不经过编译器优化,不会发生指令重排。但volatile不保证原子性。
 

线程池作用

提高线程的可管理性,降低资源消耗,提高性能
newSingleThreadExecutor:只有一个固定容量的线程池,若任务过多则会被放在队列中FIFO
newFixedThreadPool:固定容量的线程池,同样FIFO
newCachedThreadPool:容量不固定的线程池,根据实际情况调整
newSingleThreadScheduledExecutor:可以定时任务的线程池,容量为1

 

jvm

类加载器组成

根加载器:用于加载java核心jar
扩展类加载器:用于加载java扩展类库
应用类加载器:负责蝗灾classPath下的jar包
以上类加载器继承顺序由上至下

 

双亲委派模型

全盘负责指使用一个类加载器加载类时,除非显示地调用另一个类加载器,否则该类多依赖的其他类均由该类加载器加载
双亲委派指加载类时遵循classloader继承关系,从跟加载器开始加载,避免恶意的基础类混淆攻击

 

jvm主内存与工作内存

jvm中内存分为主内存和工作内存两个部分。
主内存主要包括本地方法区和堆。每个线程都有一个工作内存,工作内存中主要包括两个部分,一个是属于该线程私有的栈和对主存部分变量拷贝的寄存器(包括程序计数器PC和cup工作的高速缓存区)。  
1.所有的变量都存储在主内存中(虚拟机内存的一部分),对于所有线程都是共享的。进程间的内存则相互隔离。
2.每条线程都有自己的工作内存,工作内存中保存的是主存中某些变量的拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存中的变量。
3.线程之间无法直接访问对方的工作内存中的变量,线程间变量的传递均需要通过主内存来完成。

 

jvm结构

1.程序计数器:线程私有。是一个数据结构,用于保存当前正常执行的程序的内存地址(字节码指令)。Java虚拟机的多线程就是通过线程轮流切换并分配处理器时间来实现的,为了线程切换后能恢复到正确的位置,每条线程都需要一个独立的程序计数器,互不影响,该区域为“线程私有”。
2.Java虚拟机栈:线程私有。与线程生命周期相同,用于存储局部变量表,操作栈,方法返回值。局部变量表放着基本数据类型,还有对象的引用。
3.本地方法栈:线程私有。跟虚拟机栈很像,不过它是为虚拟机使用到的Native方法服务。
4.Java堆:线程共享。所有线程共享的一块内存区域,对象实例几乎都在这分配内存。
5.方法区:线程共享。各个线程共享的区域,储存虚拟机加载的类信息,常量,静态变量,编译后的代码。
6.运行时常量池:保存各种字面量(基本数据类型)和符号引用(以符号表示的引用地址)。
除此之外还有一种字符串常量池,专门用于存储字符串常量。JDK1.6之前字符串常量池位于方法区之中。 JDK1.7字符串常量池已经被挪到堆之中。

 

类加载机制的步骤,每一步做了什么,static和final修改的成员变量的加载时机

1.装载:导入加载class文件 
2.链接:校验class文件正确性,为静态变量分配空间,将符号引用转为直接引用
3.初始化:初始化静态变量/静态代码块

 

反射动态擦除泛型、反射动态调用方法

反射即自省,即在程序运行时动态加载对象,java使用擦除法实现反射,即编译器泛型,在运行时是被擦除了类型信息的,与C#不同,c#是生成了反射对象的类模板,也称膨胀法。
调用方法:class.forname("xx.xx")/ clazz.newInstance/clazz.getMethod("xx").invoke(args);

 

对象的创建过程

一般可以使用三种方式创建对象,反序列化/对象克隆/new关键字。在使用new关键字时,jvm按以下步骤初始化对象:
1.首先确定new指令的参数是否能在常量池中定位到一个符号引用。检查这个符号引用的类是否已被加载、解析、初始化。若没有,则要先执行类的加载过程。
2.jvm为对象分配内存。内存分配完成后jvm将该部分内存置为0值。因此在程序中不为基本数据类型赋值也能访问到其初始的0值。为对象分配则有两种方法:
指针碰撞:当堆中的内存是规整分布的,则简单地将指针指向空闲空间,移动一段与对象大小相等的距离。
空闲列表:如果内存分布不规整,jvm则会维护一个列表,用于记录内存块的可用情况,在分配时从表中找到一块足够大的空间划分给对象实例。
(但堆内存对象分布是否规整取决于GC的内存压缩整理算法)
(需要注意,jvm分配对象内存空间可能产生并发问题,jvm通过两种方法解决该问题:
  CAS失败重试,以及类似concurrentHashMap的锁分段技术,Thread local Allocation Buffer即每个线程在队中预先分配一小块内存,线程中对象的初始化始终在TLAB中执行)
3.jvm对对象进行必要的设置。如类的meta info,对象的hashcode、GC分带年龄等。这些信息位于对象的对象头(object header)之中。

4.jvm执行方法,为对象的字段赋值,按程序员的意愿进行初始化。

 

堆中的新生代内存分区

hotspot jvm把新生代分为三个部分:eden区/两个survivor区(from/to),新创建的对象会被分到eden区,经过第一次minor GC后,若存活,则会被分到survivor(from)区,在这里对象每熬过一次full GC,年龄增长一岁,最后会被移动到老年代中。
由于新生代对象生命周期短,因此使用复制算法进行内存块交换。直到survivor(to)区被填满,则会将所有对象移动至老年代。

 

几种GC回收算法

标记清除法:分为两个阶段,标记阶段/清楚阶段。在标记阶段开始标记可达到的对象,未被标记的对象即为垃圾对象,第二个阶段会清楚所有被标记的对象,该算法可能会产生内存碎片。回收后的空间是不连续的。在对象的堆空间分配中,大对象的内存分配,不连续的内存空间的效率低于连续的空间。这也是该算法的最大缺点。
复制算法:将分配的内存分为两块,每次只使用一块,垃圾回收时,将正在使用的内存中的存活对象复制到未被使用的内存中,之后清除原内存中的所有对象,两块内存交换空间,完成GC。缺点是内存会被浪费。jvm堆中的新生代一般使用此算法,由于大部分新生代对象的生命周其极短,因此内存占用的问题不是太大。
标记压缩法:复制算法适用于存货对象少,垃圾对象多的新生代,而对于老年代,大部分对象生命周其都很长,可使用标记压缩法。也是对对象做可达性分析,然后标记存活对象,之后将所有存活对象压缩到内存的一端,之后清除边界外的所有空间,避免了两块内存的交换成本,同时避免了内存的碎片化,性价比较高。

 

分区算法:即将堆分为新生代和老年代,根据分区不同选择最适合的GC算法。

 

 

GC回收方式选择

一种为minor gc,只清理新生代,另一种为full gc清理所有。
几种GC触发条件详见:http://www.importnew.com/15820.html
jvm 有client/server参数设置。
client模式下,新生代选择的是串行gc,旧生代选择的是串行gc
server模式下,新生代选择的是并行回收gc,旧生代选择的是并行gc

一般来说系统应用选择有两种方式:吞吐量优先和暂停时间优先,对于吞吐量优先的采用server默认的并行gc方式,对于暂停时间优先的选用并发gc(CMS)方式。

 

JVM怎么判断一个对象已经消亡可以被回收

引用计数算法:给对象中添加一个引用计数器,每当有一个地方引用它时,计数器就加1;当引用失效时,计数器值就减1;任何时刻计数器都为0的对象就是不可能再被使用的。Java语言没有选用引用计数法来管理内存,因为引用计数法不能很好的解决循环引用的问题。    
根搜索算法:在主流的商用语言中,都是使用根搜索算法来判定对象是否存活的。GC Root Tracing 算法思路就是通过一系列的名为"GC  Roots"的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连,即从GC Roots到这个对象不可达,则证明此对象是不可用的。

 

jvm参数设置

-server:设置运行环境为server/client
-Xms64m:设置jvm最大使用内存
-Xmx64m:堆的最大大小
-XX:NewSize=50m:设置堆中的新生代大小
-XX:+UseConcMarkSweepGC:设置老年代为并发收集,采用标记清除GC算法
-XX:CMSInitiatingOccupancyFraction=58:
-XX:PermSize=64m:设置非堆内存
-XX:MaxPermSize=256m:最大非堆内存
-XX:ThreadStackSize=256:栈深度
-Dsun.rmi.dgc.server.gcInterval=3600000:存在rmi调用时,默认会每分钟执行一次System.gc,可以通过-Dsun.rmi.dgc.server.gcInterval=3600000来设置大点的间隔。
-Dsun.rmi.dgc.client.gcInterval=3600000:同理,应用于client
-Dsun.rmi.server.exceptionTrace=true":栈异常跟踪

 

 

设计模式

单例模式

懒汉:懒加载,在if判断中new,锁加在方法体上。懒加载/线程安全
饿汉:new一个全局变量,返回该变量。非懒加载/线程安全
静态内部类:在静态内部类中new,if判断中返回class.singleten。懒加载/线程安全
双重校验锁:为了线程安全,也可以采用volatile修饰懒汉单例。线程安全/懒加载/效率略低
枚举单例:注意构造方法私有

模板方法 :父类定义公共方法,不同子类重写父类抽象方法,得到不同结果

工厂 :不同条件下创建不同实例

享元 :缓存重用重型资源,如DB连接池、线程池

单例 :保证一个类仅有一个实例,适用于只需要一个对象的情况,java需注意线程安全

门面 :封装底层实现,隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的高层接口

适配器 :使得原本由于接口不兼容而不能一起工作的那些类可以一起工作,适合后期加功能等

责任链 : 依次引用,依次执行,如spring拦截器、servlet规范的filter、netty的pipeline。

观察者 : 目标方法被调用,通知所有观察者。如mq client端的消息分发。

策略 : 定义多个不同的实现类,这些类实现公共接口,通过调用接口调用不同实例得到不同结果。

命令 : 将"行为请求者"与"行为实现者"解耦:调用者依赖命令,命令依赖接收者,调用者Invoker→命令Command→接收者Receiver,各司其职,逐层调用,如MVC架构。

状态:  通过改变状态,改变行为,如switch case、状态枚举等。

代理 :  为其他对象提供一种代理以控制对这个对象的访问,如spring aop的动态/静态代理。

建造者 : 将一个复杂的构建过程与其具表示细节相分离。如对象的builder模式,由构造器保证原子性,隐藏构造细节。

原型 : 通过拷贝原型创建新的对象,典型如对象深/浅clone

装饰器 : 调用接口,添加功能

javaSE

接口和抽象类的区别,什么时候使用

接口是一种规范,规范了实现该接口的类必须进行的操作。接口在jdk1.8之前不允许存在方法体,只允许存在抽象方法,1.8之后可以设置默认方法的实现,便于使用lambda表达式
而抽象类中允许存在非抽象方法,抽象类对接口进行解耦,细化接口功能以应对不同的需求

 

HashMap和LinkedHashMap区别

HashMap和LinkedHashMap的区别是LinkedHashMap在HashMap的基础上,采用双向链表(doubly-linked list)的形式将所有entry(hashmap里是node,实现了entry接口)连接起来,这样是为保证元素的迭代顺序跟插入顺序相同。
除此之外,两者的遍历也不同,HashMap需要遍历整个table,包括数组链表和红黑树。而LinkedHashMap由于是双向链表,只需要从链表头部遍历链表即可。
 

HashMap和HashTable的区别

1.Hashtable线程安全。而hashmap是非线程安全的,
2.hashmap允许null
3.hashmap的迭代器是fail-fast的,迭代过程中其他线程修改hashmap的结构,hashmap会抛出ConcurrentModificationException。hashtble不是,在迭代的时候会去底层集合做一个拷贝,所以你在修改上层集合的时候是不会受影响的,不会抛出ConcurrentModification异常。
 

ArrayList和LinkedList和CopyonWriteArrayList

1.ArrayList是实现了基于数组的数据结构,LinkedList基于双向链表的数据结构。 
2.对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。  
3.对于新增和删除操作add和remove,LinedList可能比较占优势,但不一定,如果add的位置在list头部,arrayList要复制大量数组,效率较低。当add的位置在list尾部,反而是arrayList较快。
CopyonWriteArrayList则是concurrent包下面提供的并发工具,顾名思义,它在写的时候会先复制一份数组,这个过程效率低会产生线程阻塞,而在读的时候完全不加锁,因此适合读多写少的场景,
 

Vector和ArrayList的区别 

1)Vector的方法都是同步的(Synchronized),是线程安全的(thread-safe),而ArrayList的方法不是,由于线程的同步必然要影响性能,因此,ArrayList的性能比Vector好。 
2)当Vector或ArrayList中的元素超过它的初始大小时,Vector会将它的容量翻倍,而ArrayList只增加50%的大小,这样,ArrayList就有利于节约内存空间。
 

ConcurrentHashMap与LinkedHashMap的区别

ConcurrentHashMap是使用了锁分段技术技术来保证线程安全的,锁分段技术:首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问
ConcurrentHashMap 是在每个段(segment)中线程安全的,但在整个数组结构上不一定线程安全,比如clear方法分段擦除元素,多线程情况下可能清理不干净

LinkedHashMap维护一个双链表,可以将里面的数据按写入的顺序读出,实现了lru算法,可以做本地缓存

 

concurrentHashMap原理
锁分段,里面有segment[],segment数组里存放hashEntry[],这个数组里存放hashEntry链表头的引用,类似hashmap,基于hash分段的思想,通过segment加锁解锁。
segment的段大小可以通过构造方法的concurrentLevel调整。
put方法需要进行两次hash,第一次确定存放segment的数组下标,其散列算法保证在segment[]上的均匀分布,第二次确定hashEntry数组下标或链表尾部。
单线程put的情况下调用get方法不需要加锁,因为其value被定义为volatile,多线程put情况下可能有并发问题。
扩容原则:仅扩容segment,而不会对整个容器扩容,减少数组复制粒度,提高效率。

 

String常量池在jdk6和jdk7的区别

jdk7以下,新建string对象时会在堆区创建一个新对象,同时将该对象复制到方法区的字符串常量池中,而由于jdk7以上版本将字符串常量池由方法区挪到了堆中,且不再进行对象的复制,而是保存对象地址的引用。

这两个版本的差异导致了String的intern方法出现一些差异。


HashMap循环链表:
发生在两条线程并发resize哈希表的情况下,resize挪动链表是倒序挪动的,若线程1挪到一半的时候线程2开始挪动线程1新创建的数组链表,则可能导致两个节点互相引用,形成循环链表。
jdk1.8加的红黑树不是为了解决该问题,而是为了解决单链表引用链过长效率过低的问题。

 

红黑树特点:
每个节点或者是黑色,或者是红色。
根节点是黑色。
每个叶子节点(NIL)是黑色。 [注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点!]
如果一个节点是红色的,则它的子节点必须是黑色的。
从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。[这里指到叶子节点的路径]

linux常用命令

cd   
cp -r     
tar -zxvf/-czvf    
mv  
rm -r 
ps aux|grep "xx"
tail -f
awk
sed
cat
vim
find
ls/ll
top
chmod
netstat -apn|grep 8080
printf
pwd
clear
mkdir
echo
sudo
ping
telnet
kill
wget

rpm

Redis

redis与memcache

相比memcache仅支持string,redis支持string/set/list/map/sortSet。用处广泛,set可用来做tag系统,list可以做排行榜或者做轻量级消息队列
性能略高于memcache
redis支持持久化,aof/rdb,rdb每隔一段时间写数据到临时文件,不使用缓存主进程,不影响性能。而aof则类似日志系统,将缓存中的操作追加到日志文件中。可靠性较高。
 

数据结构

list map set sortset string hyperLoglog geo

每种数据结构都有至少两种的实现,以平衡存储数据时间和空间效率。

常用的缓存淘汰策略

volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
no-enviction(驱逐):禁止驱逐数据

内存优化


redis的数据结构都提供了两种以上的实现,如str的实现有三种SDS动态字符串编码,emstr内存优化的字符串编码,int数字类型。哈希表的实现有hashtable,ziplist压缩列表。
业务上的缓存尽量贴合redis的数据结构,不要一股脑全部用序列化+string解决,可以减小内存占用

事务


MULTI开启事务  EXEC提交事务  DISCARD事务回滚  WATCH监听key动态判断是否提交/回滚

过期/拒绝策略


ttl基于lazy check获取时检查和定时随机检查。
内存大于maxmemory的拒绝策略取决于你设置的缓存淘汰策略

集群


redis cluster:散列算法CRC16 % 16384,redis集群有116384个hash槽,缺点是如果一个node宕机,其上的所有缓存都不可用,因此一般会为每个node做主从。redis cluster会自动选举master。
redis sharding:一致性hash算法。详见:https://www.cnblogs.com/moonandstar08/p/5405991.html

主从


主从复制分为全量复制和增量复制。同步使用sync命令。全量复制bgsave命令,和rdb持久化命令一样,此时redis的写命令全部写到buffer里,slaver拿到rdb文件并同步完成后请求master把buffer的命令发给slaver。
全量复制rdb有两种情况:一是第一次同步,二是在增量复制时slaver心跳断线,重连后重新做同步。重新同步需要重新生成rdb文件,所以redis2.8提供psync替换sync命令psync会检查slaver断线情况,如满足一定条件,做增量复制即可

RDB持久化


不建议master做持久化,slaver做aof持久化即可。
RDB:redis Fork一个子进程,redis主进程继续处理client请求,子进程则将内存buffer写到RDB文件,基于OS的copy on write,父子进程共享内存,每当父进程修改共享内存,都会创建一个副本去处理而不会修改共享内存。
缺点:fork进程开销庞大,RDB需要处理所有缓存文件,对redis性能影响很大。

AOF持久化
client调用的命令都会通过write系统函数追加到appendonly.aof中,write函数会先写到内核buffer中,然后写到磁盘缓存,类似es,并非实时存储。其写入方式有三种,一种强制写入硬盘,一种每秒写一次,最后一种由OS控制写入。一般每秒写一次即可。
缺点:aof持久化并非实时保存,宕机可能导致丢失1s左右的数据。另外可能导致aof文件过大,比如调用100次set命令,aof会保存100条命令,此时需要进行aof重写,即压缩成新的aof文件。这个aof重写redis自动在后台运行。另外注意主从时的同时配置RDB和AOF持久化可能导致缓存清空,master配置aof并重启会导致RDB文件被清空(因为aof和rdb都开启时,redis优先用aof)该状态会同步到slaver,导致slaver的RDB也被清空。因此一般不要在master上配置持久化,在slaver上配置aof或RDB即可,而且要注意重启时一定要先重启slaver再重启master。

Rabbitmq

rabbitmq解决的问题

削峰、解耦、异步化

rabbitmq支持的队列模式

点对点队列(单consumer)、一对多队列(多consumer)、发布/订阅模式、路由模式(基于绑定队列和交换机时的路由键,选择性的分发给consumer)、主题模式(根据路由键模糊匹配key并分发消息)

rabbitmq的交换机种类

direct直接队列、fanout发布订阅、topic通配符匹配和 headers消息头匹配。上面所列的队列模式都是基于这几种实现的。

消费者/生产者模式实现

生产者:新建rabbitmq连接 —— 获取channel 并声明(持久化、排他、自动确认、自动删除、路由键)—— 声明交换机 ——声明队列 ——(通过routingKey)绑定队列到交换机 —— 推送消息 —— 关闭channel ——关闭连接。

消费者:新建rabbitmq连接 —— 获取channel并声明(持久化、排他、自动确认、自动删除、路由键) —— 注册消费者 ——

消费消息并确认(自动确认/手动)—— 关闭channel ——关闭连接。

rabbitmq自动重连

rabbitmq client支持断线自动重连,比如在java api里,用connection注册recorverListener即可获取到重连状态(开始重连/重连完成)。

数据库

索引

普通索引 create index on table没有任何限制
唯一索引 unique/primary key 主键索引或unique唯一索引

全文索引 fulltext 大容量的表不建议进行全文索引,应使用搜索引擎。要在查询频繁的字段上建立索引,不宜建太多,索引会降低insert/update操作性能。

索引缺点

mysql索引的实现是b+tree和hash,建立大量索引会降低插入性能,因为索引其实是mysql提供的额外的数据结构,以空间换时间的方法增强查询效率,插入会对索引的数据结构增加了额外的操作,不利于并发插入的性能。另一方面,有些离散度低的字段如性别等等,加索引甚至可能降低读性能,比如100w条记录里查50w条记录的性别,索引需要额外的IO,然后拿到数据保存的地址,这样增加了额外的IO次数,读性能反而不如直接做全表扫描了。

引擎
innodb:支持表锁行锁,事务,MVCC和外键,不支持全文索引。但其行锁基于索引,不加索引依旧锁表。由于其支持行锁,带来了脏读、重复读、幻读等问题。
myisam:支持表锁顿、全文索引,不支持行锁,事务,MVCC和外键。

事务

事务具有四个特征:原子性( Atomicity )、一致性( Consistency )、隔离性( Isolation )和持续性( Durability )。这四个特性简称为 ACID 特性。

事务传播级别

一共有七个,直接三个,感觉别的都不太常用吧

PROPAGATION_REQUIRED ,默认的spring事务传播级别,使用该级别的特点是,如果上下文中已经存在事务,那么就加入到事务中执行,如果当前上下文中不存在事务,则新建事务执行。所以这个级别通常能满足处理大多数的业务场景。PROPAGATION_SUPPORTS ,从字面意思就知道,supports,支持,该传播级别的特点是,如果上下文存在事务,则支持事务加入事务,如果没有事务,则使用非事务的方式执行。所以说,并非所有的包在transactionTemplate.execute中的代码都会有事务支持。这个通常是用来处理那些并非原子性的非核心业务逻辑操作。应用场景较少。

PROPAGATION_MANDATORY , 该级别的事务要求上下文中必须要存在事务,否则就会抛出异常!配置该方式的传播级别是有效的控制上下文调用代码遗漏添加事务控制的保证手段。比如一段代码不能单独被调用执行,但是一旦被调用,就必须有事务包含的情况,就可以使用这个传播级别。

PROPAGATION_REQUIRES_NEW ,从字面即可知道,new,每次都要一个新事务,该传播级别的特点是,每次都会新建一个事务,并且同时将上下文中的事务挂起,执行当前新建事务完成以后,上下文事务恢复再执行。

 

事务隔离等级

一些面试题_第1张图片

脏读:
脏读就是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。脏读指事务读到了另一事务未提交的数据。

不可重复读:
在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。不可重复读是有于一个事务对另一个事务中的数据进行修改导致的。
幻读:

 是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样。幻读是由于一个事务对另一个事务中的数据进行增/删导致的。

 

sql优化

sql优化很多时候是建立在索引的基础之上的,mysql索引的实现有b+Tree和hash,如果不走索引,需要查询全文并做匹配,除了数据结构上的优化,innodb引擎支持行锁,而mysql的行锁是基于索引的,因此可以通过降低锁粒度提高查询性能。

1.对查询进行优化,要尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。
2.应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如:
select id from t where num is null
最好不要给数据库留NULL,尽可能的使用 NOT NULL填充数据库.
备注、描述、评论之类的可以设置为 NULL,其他的,最好不要使用NULL。
不要以为 NULL 不需要空间,比如:char(100) 型,在字段建立时,空间就固定了, 不管是否插入值(NULL也包含在内),都是占用 100个字符的空间的,如果是varchar这样的变长字段, null 不占用空间。
可以在num上设置默认值0,确保表中num列没有null值,然后这样查询:
select id from t where num = 0
3.应尽量避免在 where 子句中使用 != 或 <> 操作符,否则将引擎放弃使用索引而进行全表扫描。
4.应尽量避免在 where 子句中使用 or 来连接条件,如果一个字段有索引,一个字段没有索引,将导致引擎放弃使用索引而进行全表扫描,如:
select id from t where num=10 or Name = 'admin'
可以这样查询:
select id from t where num = 10
union all
select id from t where Name = 'admin'
5.in 和 not in 也要慎用,否则会导致全表扫描,如:
select id from t where num in(1,2,3)
对于连续的数值,能用 between 就不要用 in 了:
select id from t where num between 1 and 3
很多时候用 exists 代替 in 是一个好的选择:
select num from a where num in(select num from b)
用下面的语句替换:
select num from a where exists(select 1 from b where num=a.num)
6.下面的查询也将导致全表扫描:
select id from t where name like ‘%abc%’
若要提高效率,可以考虑全文检索。
7.如果在 where 子句中使用参数,也会导致全表扫描。因为SQL只有在运行时才会解析局部变量,但优化程序不能将访问计划的选择推迟到运行时;它必须在编译时进行选择。然 而,如果在编译时建立访问计划,变量的值还是未知的,因而无法作为索引选择的输入项。如下面语句将进行全表扫描:
select id from t where num = @num
可以改为强制查询使用索引:
select id from t with(index(索引名)) where num = @num
应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描。如:
select id from t where num/2 = 100
应改为:
select id from t where num = 100*2
9.应尽量避免在where子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描。如:
select id from t where substring(name,1,3) = ’abc’       -–name以abc开头的id
select id from t where datediff(day,createdate,’2005-11-30′) = 0    -–‘2005-11-30’    --生成的id
应改为:
select id from t where name like 'abc%'
select id from t where createdate >= '2005-11-30' and createdate < '2005-12-1'
10.不要在 where 子句中的“=”左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引。
11.在使用索引字段作为条件时,如果该索引是复合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引,否则该索引将不会被使用,并且应尽可能的让字段顺序与索引顺序相一致。
12.不要写一些没有意义的查询,如需要生成一个空表结构:
select col1,col2 into #t from t where 1=0
这类代码不会返回任何结果集,但是会消耗系统资源的,应改成这样:
create table #t(…)
13.Update 语句,如果只更改1、2个字段,不要Update全部字段,否则频繁调用会引起明显的性能消耗,同时带来大量日志。
14.对于多张大数据量(这里几百条就算大了)的表JOIN,要先分页再JOIN,否则逻辑读会很高,性能很差。
15.select count(*) from table;这样不带任何条件的count会引起全表扫描,并且没有任何业务意义,是一定要杜绝的。
16.索引并不是越多越好,索引固然可以提高相应的 select 的效率,但同时也降低了 insert 及 update 的效率,因为 insert 或 update 时有可能会重建索引,所以怎样建索引需要慎重考虑,视具体情况而定。一个表的索引数最好不要超过6个,若太多则应考虑一些不常使用到的列上建的索引是否有 必要。
17.应尽可能的避免更新 clustered 索引数据列,因为 clustered 索引数据列的顺序就是表记录的物理存储顺序,一旦该列值改变将导致整个表记录的顺序的调整,会耗费相当大的资源。若应用系统需要频繁更新 clustered 索引数据列,那么需要考虑是否应将该索引建为 clustered 索引。
18.尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并会增加存储开销。这是因为引擎在处理查询和连 接时会逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了。
19.尽可能的使用 varchar/nvarchar 代替 char/nchar ,因为首先变长字段存储空间小,可以节省存储空间,其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。
20.任何地方都不要使用 select * from t ,用具体的字段列表代替“*”,不要返回用不到的任何字段。
21.尽量使用表变量来代替临时表。如果表变量包含大量数据,请注意索引非常有限(只有主键索引)。
22. 避免频繁创建和删除临时表,以减少系统表资源的消耗。临时表并不是不可使用,适当地使用它们可以使某些例程更有效,例如,当需要重复引用大型表或常用表中的某个数据集时。但是,对于一次性事件, 最好使用导出表。
23.在新建临时表时,如果一次性插入数据量很大,那么可以使用 select into 代替 create table,避免造成大量 log ,以提高速度;如果数据量不大,为了缓和系统表的资源,应先create table,然后insert。
24.如果使用到了临时表,在存储过程的最后务必将所有的临时表显式删除,先 truncate table ,然后 drop table ,这样可以避免系统表的较长时间锁定。
25.尽量避免使用游标,因为游标的效率较差,如果游标操作的数据超过1万行,那么就应该考虑改写。
26.使用基于游标的方法或临时表方法之前,应先寻找基于集的解决方案来解决问题,基于集的方法通常更有效。
27.与临时表一样,游标并不是不可使用。对小型数据集使用 FAST_FORWARD 游标通常要优于其他逐行处理方法,尤其是在必须引用几个表才能获得所需的数据时。在结果集中包括“合计”的例程通常要比使用游标执行的速度快。如果开发时 间允许,基于游标的方法和基于集的方法都可以尝试一下,看哪一种方法的效果更好。
28.在所有的存储过程和触发器的开始处设置 SET NOCOUNT ON ,在结束时设置 SET NOCOUNT OFF 。无需在执行存储过程和触发器的每个语句后向客户端发送 DONE_IN_PROC 消息。
29.尽量避免大事务操作,提高系统并发能力。
30.尽量避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理。
实际案例分析:拆分大的 DELETE 或INSERT 语句,批量提交SQL语句
如果你需要在一个在线的网站上去执行一个大的 DELETE 或 INSERT 查询,你需要非常小心,要避免你的操作让你的整个网站停止相应。因为这两个操作是会锁表的,表一锁住了,别的操作都进不来了。
Apache 会有很多的子进程或线程。所以,其工作起来相当有效率,而我们的服务器也不希望有太多的子进程,线程和数据库链接,这是极大的占服务器资源的事情,尤其是内存。
如果你把你的表锁上一段时间,比如30秒钟,那么对于一个有很高访问量的站点来说,这30秒所积累的访问进程/线程,数据库链接,打开的文件数,可能不仅仅会让你的WEB服务崩溃,还可能会让你的整台服务器马上挂了。

http协议

ip:连到Internet上的每台计算机都有一个唯一的IP地址。IP地址含4个字节,32个二进制位。在书写时,通常每个字节用十进制表示,而每个字节之间用小黑点“.”来分开。

Http间隔符CRLF,其url传参编码方式为 % + 16进制的参数askii码,表单传输编码为UTF-8。
Http请求方法:

hard:server仅返回头部。 trace:server返回原始请求信息,用于诊断通信链路。operation:返回server支持的请求方法。Http协议支持扩展请求方法,在某些服务器上可自定义请求方法,tomcat不支持自定义请求Method。

 

OSI七层网络模型

物理层     网线等物理介质
数据链路层 物理寻址
网络层     通过大型网络路由数据
传输层     tcp/udp提供端对端的可靠连接/SSL协议(也称TLS,Transport Layer Security传输层安全协议)
会话层     创建端对端的对话
表示层     对来自应用层的数据进行解释,然后传给会话层,如加密解密/转换翻译/解压缩
应用层     各种应用程序协议,如http,ftp,stmp,
 

tcp/udp

1.tcp面向连接,udp是无连接的,发送数据前无需建立连接。http基于tcp/ip协议,属于短连接,也可使用websocket实现长连接
2.tcp传输可靠性强,传输无差错,不丢失,按顺序到达,udp不保证可靠性
3.tcp开销较大,udp长度则只有8个字节
4.一般tcp用于对可靠性要求较高的场景,而udp适用于速度优先的场景如语音通话

tcp和udp均位于OSI网络模型中的传输层,tcp要求在不可靠信道上实现可靠的传输,要确定双向通信正常,最少使用三次握手,兼顾效率,也只能使用三次握手

 

http协议组成

由三部分组成,请求行/请求头/请求正文
请求行:请求方法,请求uri,http版本信息
请求头:规定了具体的请求参数
请求正文:客户端发给服务器的信息
 

常见http状态码

100服务器信息。200成功信息。300重定向信息。400请求异常。500服务器异常。
100:server已接受了一些信息,需要client继续发送未完成信息。
101:协议转换,如websocket。
301:永久重定向,需要client读取请求头Location做转发。
302:临时重定向,以后client仍然访问旧的URL。
304:http缓存生效,如果未生效,则返回200(新的数据)
400:请求参数错误。
401:无权访问。
403:拒绝服务。
404:找不到资源。
405:请求方法不符合。
415:请求MIME类型(content-type)无法解析。

常见HTTP首部字段

a、通用首部字段(请求报文与响应报文都会使用的首部字段)
Date:创建报文时间
Connection:连接的管理
Cache-Control:缓存的控制
Transfer-Encoding:报文主体的传输编码方式
b、请求首部字段(请求报文会使用的首部字段)
Host:请求资源所在服务器
Accept:可处理的媒体类型
Accept-Charset:可接收的字符集
Accept-Encoding:可接受的内容编码
Accept-Language:可接受的自然语言
c、响应首部字段(响应报文会使用的首部字段)
Accept-Ranges:可接受的字节范围
Location:令客户端重新定向到的URI
Server:HTTP服务器的安装信息
d、实体首部字段(请求报文与响应报文的的实体部分使用的首部字段)
Allow:资源可支持的HTTP方法
Content-Type:实体主类的类型
Content-Encoding:实体主体适用的编码方式
Content-Language:实体主体的自然语言
Content-Length:实体主体的的字节数
Content-Range:实体主体的位置范围,一般用于发出部分请求时使用

一些面试题_第2张图片

一些面试题_第3张图片

 

http缓存机制

http缓存分为两种:强制缓存和对比缓存
    强制缓存为client直接从本地读缓存,如果缓存没过期就直接用。Http1.1之前是expire字段,后来统一为cache-control响应头控制。cache-control包含参数有是否缓存、失效时间、是否需要使用对比缓存、是否允许client和代理server缓存等控制。
    对比缓存有两种,一种基于timeout,一种基于资源唯一ID。基于timeout的,由if-modified-since请求头/last-modified响应头控制,每次都发请求头if-modified-since,server对比这个时间,决定返回304还是代理过去,返回时也会带着last-modified头部。
基于资源唯一ID的,由if-none-match响应头/ETag请求头控制,ETag相当于缓存文本的哈希值,由server生成并在client第一次访问时给client,每次client从本地拿到缓存后,以if-none-match请求头把这个哈希值传给server,server判断是否修改过,若未修改,则返回304,其他同上。
ETag的优先级要高于timeout的。

详情:https://www.cnblogs.com/chenqf/p/6386163.html

 

 

Http与Https的区别

所谓https即http over tls,建立在传输层安全协议上的http协议

ssl:Secure Sockets Layer安全套接层,加密协议,IETF将其标准化为tls协议

tls:Transport Layer Security传输层安全协议,ssl协议的规范化标准

ssh:Secure Shell应用层安全协议,主要用作登录及其他网络服务

1. HTTP 的URL 以http:// 开头,而HTTPS 的URL 以https:// 开头
2. HTTP 是不安全的,而 HTTPS 是安全的(ssl)
3. HTTP 标准端口是80 ,而 HTTPS 的标准端口是443
4. 在OSI 网络模型中,HTTP工作于应用层,而HTTPS 的安全传输机制工作在传输层

5. HTTP 无法加密,而HTTPS 对传输的数据进行加密

详情:http://www.techug.com/post/https-ssl-tls.html

https握手流程

client发送自己的加密规则给server

server选出一套对称加密算法和hash算法发给client,证书中包含公钥

client收到证书,校验hash值防止篡改,通过根证书判断证书是否合法,若合法,则生成随机数作为对称加密密钥K,用约定好的hash算法计算握手消息,然后用生成的密钥K进行加密作为token,然后一起发送给服务器

server收到后用私钥解出密钥K,用K加密握手消息的hash值并与client发来token的做比对

握手成功

http1.0/1.1/2.0的区别

http1.0需要keep-alive参数告知server端建立长连接,而http1.1默认支持长连接
http1.1支持只发送header信息,不带body。若服务端认为客户端有权限访问服务器,返回100,客户端才会把请求body发送给服务端。无权限则返回401
http2.0使用多路复用技术,一个连接处理更多请求。
http2.0对header部分进行了压缩,减少数据体积。
http2.0支持服务器推。
详见:https://www.zhihu.com/question/34074946

 

TCP协议

TCP三次握手和四次挥手
https://blog.csdn.net/guanghuichenshao/article/details/81916277
    
TCP报文重发


    每个TCP段都有一个id与数据完整性校验和,server接收到完好的消息时,会回复client确认分组,若server在消息窗口时间内未确认,则认为消息丢失,将由client重发。TCP的确认报文很小,所以一般会实现"延迟确认"算法,先将确认报文放在buffer里,在100ms-200ms的窗口时间内寻找单独的输出数据分组,找到了则把确认报文"捎带"过去,找不到则单独发送确认报文。可以减少TCP报文的IO次数。

TCP慢启动


    TCP连接会随着时间延长逐渐提高到其最大速率。比如HTTP报文数据量太大,无法在一个分组里发送,TCP会尝试先发一个分组,server确认后开始发两个,两个都确认后发四个,所以新TCP连接往往比老连接慢一些,这叫"预热"。

Nagle算法与TCP NODELAY


    Nagle算法即聚合大量TCP数据以减少网络IO次数,但可能引起小数据包时延,而且可能会受TCP"延时确认"特性的影响导致延时更长。一般而言会通过设置TCP-NODELAY参数禁用nagle算法降低数据延时。

TCP粘包/分包
应用程序写入的字节大小大于套接字发送缓冲区的大小,会发生拆包现象,而应用程序写入数据小于套接字缓冲区大小,网卡将应用多次写入的数据发送到网络上,这将会发生粘包现象;
进行MSS大小的TCP分段,当TCP报文长度-TCP头部长度>MSS的时候将发生拆包
以太网帧的payload(净荷)大于MTU(1500字节)进行ip分片。

 

TIME_WAIT与端口耗尽


    由于连接处于TIME_WAIT状态或CLOSE_WAIT状态导致死链耗尽fd。TIME_WAIT主要发生在Http client或者爬虫client,也可以发生在并发访问量较大的server端。产生的主要原因是关闭连接后,TCP会维护一个内存块用来保存最近关闭的IP和port,防止在2MSL(2min)时间窗口内不会重复创建相同ip与port的tcp连接,之所以留这个冗余时间,主要有两点如下:
    1.防止上一次连接中的包,迷路后重新出现,影响新连接(经过2MSL,上一次连接中所有的重复包都会消失)
    2. 可靠的关闭TCP连接。在主动关闭方发送的最后一个ack(fin) ,有可能丢失,这时被动方会重新发fin, 如果这时主动方处于CLOSED状态 ,就会响应rst而不是ack。所以主动方要处于TIME_WAIT状态,而不能是CLOSED 。另外这么设计TIME_WAIT 会定时的回收资源,并不会占用很大资源的,除非短时间内接受大量请求或者受到攻击。
HTTP协议1.1版规定default行为是Keep-Alive,也就是会重用TCP连接传输多个 request/response,一个主要原因就是发现了这个问题。可以通过配置linux网络参数,快速释放TIME_WAIT的资源。至于CLOSE_WAIT,属于代码层面的问题,即关闭连接后client未发送ack信号,导致server一直挂在CLOSE_WAIT的状态,典型的如Http client或爬虫请求后未关闭连接。

串行事务与延时


    一般浏览器不会与服务器串行建立连接。大概有几种方法提高性能,降低延时。
1.并行多条连接。连接数过多对server不够友好,在带宽不足的情况下不能有效降低延时,由于TCP慢启动,通信速率并不理想。一般浏览器使用的并行连接最多不超过4个。
2.共享连接,并发通信。基于pipeline,在发送第一次请求后,第2,3次请求也马上发送,相比并行连接,出现server未处理完全部请求就关闭连接的概率较高,http1.1会通过content-length字段确定请求末尾,如果content-length出现错误,server可能关闭连接。
3.重用连接(池化)。进行一次HTTP请求后并不释放连接,而是供下一次请求使用,而且可以避免TCP慢启动。
一般来说,并行连接配合持久化连接一同使用比较高效。具体实现如下:
Http1.0 + keepalive:已经被淘汰了,http1.0时代的实验性产物。通过请求头connection字段指定keep-Alive激活,keepAlive参数可指定最大持久化连接数和超时时长。
Http1.1:http1.1版本默认支持且激活并行连接 + 持久化,所以如果不需要持久化,需要在响应头中指定connection:close。且client最多维护2条持久化连接
减少对代理服务器负载。

连接关闭
    可以抽象为两部分:关闭输出通道和关闭输入通道。在输入通道关闭后又收到了client端发送的消息会导致连接被reset,reset会将之前收到的buffer全部清空重置,因此安全的做法是client和server端都先关闭输出通道,再关闭输入通道,但server无法保证client的状态,所以server会先关闭输出通道,然后轮询检查输入通道的状态(如流的末尾)直到确认client不再发送或超时强制关闭连接。

Docker

常用命令
docker主要为了解决多环境情况下配置不统一导致的部署问题,基于虚拟化技术,统一多环境情况下项目的依赖、打包部署。
docker images 查看镜像
docker run 运行镜像
docker search 搜索镜像
docker pull 拉取镜像
docker push 推送镜像

 

Git

常用命令
git clone
git init 
git status
git add
git commit 
git push
git revert
git reflog
git log
git diff
git checkout
git merge
git reset
git pull
git branch
git rebase

git stash

git stash apply

 

 其他

对restful的理解

restful是一种风格,全称是Representational State Transfer。表征状态转移。
restful把事务抽象化为资源。资源需要一个唯一标识符,在web中即为URI,他主要有以下几个特点:
1.使用http动作get/post/delete/update/hard定义资源操作
2.使用名词而不是动词表示资源,URI值表示资源名称,而不表示对资源的操作
3.使用/表示资源的层级关系,使用_强化资源的可读性,使用?用于资源过滤
4.一般使用json/xml传输数据

6.restful是无状态的,可使用token/oauth(oauth允许第三方应用访问用户在某web服务器上存储的私密资源,而无需通过用户名密码)

 

对称加密与非对称加密以及hash算法

对称加密算法一般有DES/AES算法,所谓对称即加密和解密使用相同的密钥,其缺陷是密钥在网络传输过程中可能会恶意截取,从而导致加密被破解。
非对称加密算法典型的有RSA算法,所谓非对称即加密和解密使用不同的密钥,由公钥和私钥组成,私钥由服务端持有,不能外泄,而公钥可以发给任何请求它的人。非对称加密使用公钥加密相关信息,而解密则需要私钥。
为了解决对称加密算法的密钥这个问题,通常的做法是将对称加密使用的密钥进行非对称加密,然后传送给需要他的人(拥有私钥)。

hash算法则与上面两者不同,是一种信息摘要算法。

六大软件设计原则

单一指责原则:一个类只负责一个功能内的职责。
开闭原则:对扩展开放,对修改封闭。
里氏替换原则:所有引用父类的地方都应该可以用子类代替,反之则不行。
依赖倒置原则:抽象不应该依赖于实现,具体的实现应依赖于抽象。
接口隔离原则:即每一个接口应该承担一种相应的角色,而不需要大而全的接口

迪米特法则:软件实体应尽量减少与其他实体发生的作用。

其他常见面试题

https://www.zhihu.com/question/63981591/answer/543920582
http://www.zhihu.com/question/60949531?utm_source=qq&utm_medium=social&utm_oi=973694038780645376
https://blog.csdn.net/wantflydacheng/article/details/81531994

你可能感兴趣的:(其他,面试题)