面经汇总

spring方面:

1.java反射:

反射(Reflection)是Java 程序开发语言的特征之一,它允许运行中的 Java 程序获取自身的信息,并且可以操作类或对象的内部属性。我们可以在运行时获得程序或程序集中每一个类型的成员和成员的信息,重点:是运行时而不是编译时

反射能够配置类的全限定名,方法和参数,完成对象的初始化,还能反射某些方法

主要应用反射完成对象的构建(有参无参)以及方法的调用

通过反射来生成对象主要有两种方式。

(1)使用Class对象的newInstance()方法来创建Class对象对应类的实例。

注意:利用newInstance创建对象:调用的类必须有无参的构造器

(2)有参的话先通过Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建实例。这种方法可以用指定的构造器构造类的实例。

下面演示一个参数 多个参数的情况

两个参数 

 利用反射获取方法

1 反射知道类存在的时候可以直接拿对象的class

2 反射只知道对象 要getclass 这也是反射得到对象+反射得到方法的一个整合实例

getClass通过引用用处就是这是根据构造好的实例来得到的,你反射出来的方法可能会包含实例的内容(比如构造方法是给字段赋值的,你调用的方法刚好用到了那个字段) 

 class.forName 和 .class就不行

反射还可以用于获取字段 数组 注解等 

如何获取私有方法:

2 动态代理

静态代理:

  • ①定义业务接口;
  • ②被代理类实现业务接口;
  • ③定义代理类并实现业务接口;
  • ④最后便可通过客户端进行调用。

既然有了静态代理,为什么会出现动态代理呢?那我们就说一下静态代理的一些缺点吧:

①代理类和被代理类实现了相同的接口,导致代码的重复,如果接口增加一个方法,那么除了被代理类需要实现这个方法外,代理类也要实现这个方法,增加了代码维护的难度。

②代理对象只服务于一种类型的对象,如果要服务多类型的对象。势必要为每一种对象都进行代理,静态代理在程序规模稍大时就无法胜任了。比如上面的例子,只是对用户的业务功能(IUserService)进行代理,如果是商品(IItemService)的业务功能那就无法代理,需要去编写商品服务的代理类。

于是乎,动态代理的出现就能帮助我们解决静态代理的不足。所谓动态代理是指:在程序运行期间根据需要动态创建代理类及其实例来完成具体的功能。动态代理主要分为JDK动态代理和cglib动态代理两大类,本文主要对JDK动态代理进行探讨。

动态代理:

动态:在运行时,通过反射机制实现

代理:生成一个代理对象,控制对真实对象的访问;

动态代理技术包括:jdk动态代理,cglib, asm, javassist;最常用的就是jdk动态代理,cglib,

jdk动态代理是jdk自带的,cglib是第三方的

spring应的是jdk动态代理,cglib; mybatis用的是javassit;

jdk动态代理,cglib区别:jdk用接口 cglib不需要

思想都是一样的 我们分两个步骤: 代理对象和真实对象建立代理关系;实现代理对象的代理逻辑方法

jdk动态代理:

  • ①创建被代理的接口和类;
  • ②创建InvocationHandler接口的实现类,在invoke方法中实现代理逻辑;
  • ③通过Proxy的静态方法newProxyInstance( ClassLoaderloader, Class[] interfaces, InvocationHandler h)创建一个代理对象
  • ④使用代理对象。

InvocationHandler的作用

在动态代理中InvocationHandler是核心,每个代理实例都具有一个关联的调用处理程序InvocationHandler。对代理实例调用方法时,将对方法调用进行编码并将其指派到它的调用处理程序InvocationHandler的 invoke 方法。所以对代理方法的调用都是通InvocationHadler的invoke来实现中,而invoke方法根据传入的代理对象,方法和参数来决定调用代理的哪个方法。 
invoke方法签名:invoke(Object Proxy,Method method,Object[] args)

这无所谓被代理对象的类型

底部也是生成代理类的实例 但是类文件的生成的关键是getProxyClass0这个方法,getProxyClass0(loader, intfs)得到代理类的Class对象,然后通过Class对象得到构造方法,进而创建代理对象。用到了反射(底部有用到conCurrentHashMap)

想想这个invoke函数是不是和aop很像 其实这就是aop的实现原理

 

JDK动态代理为什么必须针对接口

查看jdk的动态代理源码发现:
动态代理实际上是程序在运行中,根据被代理的接口来动态生成代理类的class文件,并加载class文件运行的过程,通过反编译被生成的$Proxy0.class文件发现:

由于java的单继承,动态生成的代理类已经继承了Proxy类的,就不能再继承其他的类,所以只能靠实现被代理类的接口的形式,故JDK的动态代理必须有接口。

为何调用代理类的方法就会自动进入InvocationHandler 的 invoke()方法呢?
其实是因为在动态代理类的定义中,构造函数是含参的构造,参数就是我们invocationHandler 实例,而每一个被代理接口的方法都会在代理类中生成一个对应的实现方法,并在实现方法中最终调用invocationHandler 的invoke方法,这就解释了为何执行代理类的方法会自动进入到我们自定义的invocationHandler的invoke方法中,然后在我们的invoke方法中再利用jdk反射的方式去调用真正的被代理类的业务方法,而且还可以在方法的前后去加一些我们自定义的逻辑。

 

cglib代理:

DK实现动态代理需要实现类通过接口定义业务方法,对于没有接口的类,如何实现动态代理呢,这就需要CGLib了。CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。JDK动态代理与CGLib动态代理均是实现Spring AOP的基础。

1 被代理的类

2 自定义拦截器

 

上面我们看了CGLib动态代理的用法、实际生成的代理类以及FastClass机制,下面我们就以最前面的那个例子中调用eat()方法来看看主要的调用步骤;

  第一步:是经过一系列操作实例化出了Enhance对象,并设置了所需要的参数然后enhancer.create()成功创建出来了代理对象,这个就不多说了...

  第二步:调用代理对象的eat()方法,会进入到方法拦截器的intercept()方法,在这个方法中会调用proxy.invokeSuper(obj, args);方法

  第三步:invokeSuper中,通过FastClass机制调用目标类的方法

 

实现CGLIB动态代理必须实现MethodInterceptor(方法拦截器)接口,public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args

这个接口只有一个intercept()方法,这个方法有4个参数:

1)obj表示增强的对象,即实现这个接口类的一个对象;

2)method表示要被拦截的方法;

3)args表示要被拦截方法的参数;

4)proxy表示要触发父类的方法对象; 

往下不断执行最后通过反射生产代理对象,得到的字节码反编译后观察

从代理对象反编译源码可以知道,代理对象继承于HelloService拦截器调用intercept()方法,

intercept()方法由自定义MyMethodInterceptor实现,所以,最后调用MyMethodInterceptor中

的intercept()方法,从而完成了由代理对象访问到目标对象的动态代理实现。

 

3.springboot和springmvc和spring区别

回答思路 :spirng 涉及的ioc di aop(思想 动态代理) 区别

springmvc就是web框架

springboot tomact 无需xml 注解注入bean 整合了依赖 简化maven配置

Spring 容器的主要核心是:
控制反转(IOC),传统的 java 开发模式中,当需要一个对象时,我们会自己使用 new 或者 getInstance 等直接
或者间接调用构造方法创建一个对象。而在 spring 开发模式中,spring 容器使用了工厂模式为我们创建了所需要的对
象,不需要我们自己创建了,直接调用 spring 提供的对象就可以了,这是控制反转的思想。
依赖注入(DI),spring 使用 javaBean 对象的 set 方法或者带参数的构造方法或者说注解的方式为我们在创建所需对象时将其属性自动设置所需要的值的过程,就是依赖注入的思想。
面向切面编程(AOP),在面向对象编程(oop)思想中,我们将事物纵向抽成一个个的对象。而在面向切面编程
中,我们将一个个的对象某些类似的方面横向抽成一个切面,对这个切面进行一些如权限控制、事物管理,记录日志等
公用操作处理的过程就是面向切面编程的思想。AOP 底层是动态代理,如果是接口采用 JDK 动态代理,如果是类采用
CGLIB 方式实现动态代理。

而SpringMVC是SpringMVC是基于Spring功能之上添加的Web框架,想用SpringMVC必须先依赖Spring。 

SpringMVC是一个类似于struts的MVC模式的WEB开发框架;

Spring是一个通用解决方案, 最大的用处就是通过Ioc/AOP解耦, 降低软件复杂性, 所以Spring可以结合SpringMVC等很多其他解决方案一起使用, 不仅仅只适用于WEB开发

SpringBoot不是Spring官方的框架模式,而是一个团队在Spring4.0版本上二次开发并开源公布出来的。简而言之,SpringBoot就是一个轻量级,简化配置和开发流程的web整合框架,我们可以说是因为SpringBoot才有了Spring这么火。

那么SpringBoot和Spring有什么区别呢?

1.Spring Boot可以建立独立的Spring应用程序;
2.内嵌了如Tomcat,Jetty和Undertow这样的容器,也就是说可以直接跑起来,用不着再做部署工作了;
3.无需再像Spring那样搞一堆繁琐的xml文件的配置;
4.可以自动配置(核心)Spring。SpringBoot将原有的XML配置改为Java配置,将bean注入改为使用注解注入的方式(@Autowire),5.并将多个xml、properties配置浓缩在一个appliaction.yml配置文件中。
6.提供了一些现有的功能,如量度工具,表单数据验证以及一些外部配置这样的一些第三方功能;
整合常用依赖(开发库,例如spring-webmvc、jackson-json、validation-api和tomcat等),提供的POM可以简化Maven的配置。当我们引入核心依赖时,SpringBoot会自引入其他依赖。


 

虚拟机:

1. 类加载机制

 

加载 验证 准备 解析 初始化

加载
简单来说,加载指的是把class字节码文件从各个来源通过类加载器装载入内存中。(用到那个装哪个)

加载过程完成以下三件事:

  • 通过类的完全限定名称获取定义该类的二进制字节流
  • 将该字节流表示的静态存储结构转换为方法区的运行时存储结构
  • 在内存中生成一个代表该类的 Class 对象,作为方法区中该类各种数据的访问入口

其中二进制字节流(字节码)可以从以下方式中获取:

  • 从 ZIP 包读取,成为 JAR、EAR、WAR 格式的基础。
  • 从网络中获取,最典型的应用是 Applet。
  • 运行时计算生成,例如动态代理技术,在 java.lang.reflect.Proxy 使用 ProxyGenerator.generateProxyClass 的代理类的二进制字节流。
  • 由其他文件生成,例如由 JSP 文件生成对应的 Class 类。

类加载器。一般包括启动类加载器,扩展类加载器,应用类加载器,以及用户的自定义类加载器。

注:为什么会有自定义类加载器?

一方面是由于java代码很容易被反编译,如果需要对自己的代码加密的话,可以对编译后的代码进行加密,然后再通过实现自己的自定义类加载器进行解密,最后再加载。

另一方面也有可能从非标准的来源加载代码,比如从网络来源,那就需要自己实现一个类加载器,从指定源进行加载。

验证
主要是为了保证加载进来的字节流符合虚拟机规范,不会造成安全错误。

包括对于文件格式的验证,比如常量中是否有不被支持的常量?文件中是否有不规范的或者附加的其他信息?

对于元数据的验证,比如该类是否继承了被final修饰的类?类中的字段,方法是否与父类冲突?是否出现了不合理的重载?

对于字节码的验证,保证程序语义的合理性,比如要保证类型转换的合理性。

对于符号引用的验证,比如校验符号引用中通过全限定名是否能够找到对应的类?校验符号引用中的访问性(private,public等)是否可被当前类访问?

准备
主要是为类变量(注意,不是实例变量)分配内存(方法区内存),并且赋予初值。

特别需要注意,初值,不是代码中具体写的初始化的值,而是Java虚拟机根据不同变量类型的默认初始值。

比如8种基本类型的初值,默认为0;引用类型的初值则为null;常量的初值即为代码中设置的值,final static tmp = 456, 那么该阶段tmp的初值就是456

解析
将常量池内的符号引用替换为直接引用的过程。

两个重点:

符号引用。即一个字符串,但是这个字符串给出了一些能够唯一性识别一个方法,一个变量,一个类的相关信息。

直接引用可以理解为一个内存地址,或者一个偏移量。比如类方法,类变量的直接引用是指向方法区的指针;而实例方法,实例变量的直接引用则是从实例的头指针开始算起到这个实例变量位置的偏移量

举个例子来说,现在调用方法hello(),这个方法的地址是1234567,那么hello就是符号引用,1234567就是直接引用。

在解析阶段,虚拟机会把所有的类名,方法名,字段名这些符号引用替换为具体的内存地址或偏移量,也就是直接引用。

初始化
这个阶段主要是对类变量初始化,是执行类构造器的过程。

换句话说,只对static修饰的变量或语句进行初始化。

如果初始化一个类的时候,其父类尚未初始化,则优先初始化其父类。

如果同时包含多个静态变量和静态代码块,则按照自上而下的顺序依次执行。

接口中不可以使用静态语句块,但仍然有类变量初始化的赋值操作,因此接口与类一样都会生成 <clinit>() 方法。但接口与类不同的是,执行接口的 <clinit>() 方法不需要先执行父接口的 <clinit>() 方法。只有当父接口中定义的变量使用时,父接口才会初始化。

虚拟机会保证一个类的 <clinit>() 方法在多线程环境下被正确的加锁和同步

2 垃圾回收算法

垃圾收集

垃圾收集主要是针对堆和方法区进行。程序计数器、虚拟机栈和本地方法栈这三个区域属于线程私有的,只存在于线程的生命周期内,线程结束之后就会消失,因此不需要对这三个区域进行垃圾回收。

判断一个对象是否可被回收

1. 引用计数算法

为对象添加一个引用计数器,当增加一个对象的引用时计数器加 1,引用失效时计数器减 1。引用计数为 0 的对象可被回收。

在两个对象出现循环引用的情况下,此时引用计数器永远不为 0,导致无法对它们进行回收。正是因为循环引用的存在,因此 Java 虚拟机不使用引用计数算法。

public class Test {

    public Object instance = null;

    public static void main(String[] args) {
        Test a = new Test();
        Test b = new Test();
        a.instance = b;
        b.instance = a;
        a = null;
        b = null;
        doSomething();
    }
}

在上述代码中,a 与 b 引用的对象实例互相持有了对象的引用,因此当我们把对 a 对象与 b 对象的引用去除之后,由于两个对象还存在互相之间的引用,导致两个 Test 对象无法被回收。

2. 可达性分析算法

以 GC Roots 为起始点进行搜索,可达的对象都是存活的,不可达的对象可被回收。

Java 虚拟机使用该算法来判断对象是否可被回收,GC Roots 一般包含以下内容:

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象。(可以理解为:引用栈帧中的本地变量表的所有对象)
  • 方法区中静态属性引用的对象(可以理解为:引用方法区该静态属性的所有对象)
  • 方法区中常量引用的对象(可以理解为:引用方法区中常量的所有对象)
  • 本地方法栈中(Native方法)引用的对象(可以理解为:引用Native方法的所有对象)

可以理解为:

(1)首先第一种是虚拟机栈中的引用的对象,我们在程序中正常创建一个对象,对象会在堆上开辟一块空间,同时会将这块空间的地址作为引用保存到虚拟机栈中,如果对象生命周期结束了,那么引用就会从虚拟机栈中出栈,因此如果在虚拟机栈中有引用,就说明这个对象还是有用的,这种情况是最常见的。

(2)第二种是我们在类中定义了全局的静态的对象,也就是使用了static关键字,由于虚拟机栈是线程私有的,所以这种对象的引用会保存在共有的方法区中,显然将方法区中的静态引用作为GC Roots是必须的。

(3)第三种便是常量引用,就是使用了static final关键字,由于这种引用初始化之后不会修改,所以方法区常量池里的引用的对象也应该作为GC Roots。最后一种是在使用JNI技术时,有时候单纯的Java代码并不能满足我们的需求,我们可能需要在Java中调用C或C++的代码,因此会使用native方法,JVM内存中专门有一块本地方法栈,用来保存这些对象的引用,所以本地方法栈中引用的对象也会被作为GC Roots。

 

3. 方法区的回收

因为方法区主要存放永久代对象,而永久代对象的回收率比新生代低很多,所以在方法区上进行回收性价比不高。

主要是对常量池的回收和对类的卸载。

为了避免内存溢出,在大量使用反射和动态代理的场景都需要虚拟机具备类卸载功能。

类的卸载条件很多,需要满足以下三个条件,并且满足了条件也不一定会被卸载:

  • 该类所有的实例都已经被回收,此时堆中不存在该类的任何实例。
  • 加载该类的 ClassLoader 已经被回收。
  • 该类对应的 Class 对象没有在任何地方被引用,也就无法在任何地方通过反射访问该类方法。

4. finalize()

类似 C++ 的析构函数,用于关闭外部资源。但是 try-finally 等方式可以做得更好,并且该方法运行代价很高,不确定性大,无法保证各个对象的调用顺序,因此最好不要使用。

当一个对象可被回收时,如果需要执行该对象的 finalize() 方法,那么就有可能在该方法中让对象重新被引用,从而实现自救。自救只能进行一次,如果回收的对象之前调用了 finalize() 方法自救,后面回收时不会再调用该方法。

引用类型

无论是通过引用计数算法判断对象的引用数量,还是通过可达性分析算法判断对象是否可达,判定对象是否可被回收都与引用有关。

Java 提供了四种强度不同的引用类型。

1. 强引用

被强引用关联的对象不会被回收。

使用 new 一个新对象的方式来创建强引用。

Object obj = new Object();

2. 软引用

被软引用关联的对象只有在内存不够的情况下才会被回收。

使用 SoftReference 类来创建软引用。

Object obj = new Object();
SoftReference<Object> sf = new SoftReference<Object>(obj);
obj = null;  // 使对象只被软引用关联

3. 弱引用

被弱引用关联的对象一定会被回收,也就是说它只能存活到下一次垃圾回收发生之前。

使用 WeakReference 类来创建弱引用。

Object obj = new Object();
WeakReference<Object> wf = new WeakReference<Object>(obj);
obj = null;

4. 虚引用

又称为幽灵引用或者幻影引用,一个对象是否有虚引用的存在,不会对其生存时间造成影响,也无法通过虚引用得到一个对象。

为一个对象设置虚引用的唯一目的是能在这个对象被回收时收到一个系统通知。

使用 PhantomReference 来创建虚引用。

Object obj = new Object();
PhantomReference<Object> pf = new PhantomReference<Object>(obj, null);
obj = null;

垃圾收集算法

1. 标记 - 清除

 

在标记阶段,程序会检查每个对象是否为活动对象,如果是活动对象,则程序会在对象头部打上标记。

在清除阶段,会进行对象回收并取消标志位,另外,还会判断回收后的分块与前一个空闲分块是否连续,若连续,会合并这两个分块。回收对象就是把对象作为分块,连接到被称为 “空闲链表” 的单向链表,之后进行分配时只需要遍历这个空闲链表,就可以找到分块。

在分配时,程序会搜索空闲链表寻找空间大于等于新对象大小 size 的块 block。如果它找到的块等于 size,会直接返回这个分块;如果找到的块大于 size,会将块分割成大小为 size 与 (block - size) 的两部分,返回大小为 size 的分块,并把大小为 (block - size) 的块返回给空闲链表。

不足:

  • 标记和清除过程效率都不高;
  • 会产生大量不连续的内存碎片,导致无法给大对象分配内存。

2. 标记 - 整理

让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。

优点:

  • 不会产生内存碎片

不足:

  • 需要移动大量对象,处理效率比较低。

3. 复制

 

将内存划分为大小相等的两块,每次只使用其中一块,当这一块内存用完了就将还存活的对象复制到另一块上面,然后再把使用过的内存空间进行一次清理。

主要不足是只使用了内存的一半。

现在的商业虚拟机都采用这种收集算法回收新生代,但是并不是划分为大小相等的两块,而是一块较大的 Eden 空间和两块较小的 Survivor 空间,每次使用 Eden 和其中一块 Survivor。在回收时,将 Eden 和 Survivor 中还存活着的对象全部复制到另一块 Survivor 上,最后清理 Eden 和使用过的那一块 Survivor。

HotSpot 虚拟机的 Eden 和 Survivor 大小比例默认为 8:1,保证了内存的利用率达到 90%。如果每次回收有多于 10% 的对象存活,那么一块 Survivor 就不够用了,此时需要依赖于老年代进行空间分配担保,也就是借用老年代的空间存储放不下的对象。

4. 分代收集

现在的商业虚拟机采用分代收集算法,它根据对象存活周期将内存划分为几块,不同块采用适当的收集算法。

一般将堆分为新生代和老年代。

  • 新生代使用:复制算法
  • 老年代使用:标记 - 清除 或者 标记 - 整理 算法

垃圾收集器

 

以上是 HotSpot 虚拟机中的 7 个垃圾收集器,连线表示垃圾收集器可以配合使用。

  • 单线程与多线程:单线程指的是垃圾收集器只使用一个线程,而多线程使用多个线程;
  • 串行与并行:串行指的是垃圾收集器与用户程序交替执行,这意味着在执行垃圾收集的时候需要停顿用户程序;并行指的是垃圾收集器和用户程序同时执行。除了 CMS 和 G1 之外,其它垃圾收集器都是以串行的方式执行。

1. Serial 收集器

 

Serial 翻译为串行,也就是说它以串行的方式执行。

它是单线程的收集器,只会使用一个线程进行垃圾收集工作。

它的优点是简单高效,在单个 CPU 环境下,由于没有线程交互的开销,因此拥有最高的单线程收集效率。

它是 Client 场景下的默认新生代收集器,因为在该场景下内存一般来说不会很大。它收集一两百兆垃圾的停顿时间可以控制在一百多毫秒以内,只要不是太频繁,这点停顿时间是可以接受的。

2. ParNew 收集器

 

 

它是 Serial 收集器的多线程版本。

它是 Server 场景下默认的新生代收集器,除了性能原因外,主要是因为除了 Serial 收集器,只有它能与 CMS 收集器配合使用。

3. Parallel Scavenge 收集器

与 ParNew 一样是多线程收集器。

其它收集器目标是尽可能缩短垃圾收集时用户线程的停顿时间,而它的目标是达到一个可控制的吞吐量,因此它被称为“吞吐量优先”收集器。这里的吞吐量指 CPU 用于运行用户程序的时间占总时间的比值。

停顿时间越短就越适合需要与用户交互的程序,良好的响应速度能提升用户体验。而高吞吐量则可以高效率地利用 CPU 时间,尽快完成程序的运算任务,适合在后台运算而不需要太多交互的任务。

缩短停顿时间是以牺牲吞吐量和新生代空间来换取的:新生代空间变小,垃圾回收变得频繁,导致吞吐量下降。

可以通过一个开关参数打开 GC 自适应的调节策略(GC Ergonomics),就不需要手工指定新生代的大小(-Xmn)、Eden 和 Survivor 区的比例、晋升老年代对象年龄等细节参数了。虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或者最大的吞吐量。

4. Serial Old 收集器

 

 

是 Serial 收集器的老年代版本,也是给 Client 场景下的虚拟机使用。如果用在 Server 场景下,它有两大用途:

  • 在 JDK 1.5 以及之前版本(Parallel Old 诞生以前)中与 Parallel Scavenge 收集器搭配使用。
  • 作为 CMS 收集器的后备预案,在并发收集发生 Concurrent Mode Failure 时使用。

5. Parallel Old 收集器

 

 

是 Parallel Scavenge 收集器的老年代版本。

在注重吞吐量以及 CPU 资源敏感的场合,都可以优先考虑 Parallel Scavenge 加 Parallel Old 收集器。

6. CMS 收集器

 

 

CMS(Concurrent Mark Sweep),Mark Sweep 指的是标记 - 清除算法。

分为以下四个流程:

  • 初始标记:仅仅只是标记一下 GC Roots 能直接关联到的对象,速度很快,需要停顿。
  • 并发标记:进行 GC Roots Tracing 的过程,它在整个回收过程中耗时最长,不需要停顿。
  • 重新标记:为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,需要停顿。
  • 并发清除:不需要停顿。

在整个过程中耗时最长的并发标记和并发清除过程中,收集器线程都可以与用户线程一起工作,不需要进行停顿。

具有以下缺点:

  • 吞吐量低:低停顿时间是以牺牲吞吐量为代价的,导致 CPU 利用率不够高。
  • 无法处理浮动垃圾,可能出现 Concurrent Mode Failure。浮动垃圾是指并发清除阶段由于用户线程继续运行而产生的垃圾,这部分垃圾只能到下一次 GC 时才能进行回收。由于浮动垃圾的存在,因此需要预留出一部分内存,意味着 CMS 收集不能像其它收集器那样等待老年代快满的时候再回收。如果预留的内存不够存放浮动垃圾,就会出现 Concurrent Mode Failure,这时虚拟机将临时启用 Serial Old 来替代 CMS。
  • 标记 - 清除算法导致的空间碎片,往往出现老年代空间剩余,但无法找到足够大连续空间来分配当前对象,不得不提前触发一次 Full GC。

7. G1 收集器

G1(Garbage-First),它是一款面向服务端应用的垃圾收集器,在多 CPU 和大内存的场景下有很好的性能。HotSpot 开发团队赋予它的使命是未来可以替换掉 CMS 收集器。

堆被分为新生代和老年代,其它收集器进行收集的范围都是整个新生代或者老年代,而 G1 可以直接对新生代和老年代一起回收。

G1 把堆划分成多个大小相等的独立区域(Region),新生代和老年代不再物理隔离。

通过引入 Region 的概念,从而将原来的一整块内存空间划分成多个的小空间,使得每个小空间可以单独进行垃圾回收。这种划分方法带来了很大的灵活性,使得可预测的停顿时间模型成为可能。通过记录每个 Region 垃圾回收时间以及回收所获得的空间(这两个值是通过过去回收的经验获得),并维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的 Region。

每个 Region 都有一个 Remembered Set,用来记录该 Region 对象的引用对象所在的 Region。通过使用 Remembered Set,在做可达性分析的时候就可以避免全堆扫描。

 

如果不计算维护 Remembered Set 的操作,G1 收集器的运作大致可划分为以下几个步骤:

  • 初始标记
  • 并发标记
  • 最终标记:为了修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录,虚拟机将这段时间对象变化记录在线程的 Remembered Set Logs 里面,最终标记阶段需要把 Remembered Set Logs 的数据合并到 Remembered Set 中。这阶段需要停顿线程,但是可并行执行。
  • 筛选回收:首先对各个 Region 中的回收价值和成本进行排序,根据用户所期望的 GC 停顿时间来制定回收计划。此阶段其实也可以做到与用户程序一起并发执行,但是因为只回收一部分 Region,时间是用户可控制的,而且停顿用户线程将大幅度提高收集效率。

具备如下特点:

  • 空间整合:整体来看是基于“标记 - 整理”算法实现的收集器,从局部(两个 Region 之间)上来看是基于“复制”算法实现的,这意味着运行期间不会产生内存空间碎片。
  • 可预测的停顿:能让使用者明确指定在一个长度为 M 毫秒的时间片段内,消耗在 GC 上的时间不得超过 N 毫秒。

java容器

1.arraylist和linkedlist区别

(1)ArrayList的实现是基于数组,LinkedList的实现是基于双向链表。

(2) 对于随机访问,ArrayList优于LinkedList,ArrayList可以根据下标以O(1)时间复杂度对元素进行随机访问。而LinkedList的每一个元素都依靠地址指针和它后一个元素连接在一起,在这种情况下,查找某个元素的时间复杂度是O(n) 。

(3)对于插入和删除操作,LinkedList优于ArrayList,因为当元素被添加到LinkedList末尾位置的时候,不需要像ArrayList那样 需要进行复制操作 O(1)。

(4) LinkedList比ArrayList更占内存,因为LinkedList的节点除了存储数据,还存储了两个引用,一个指向前一个元素,一个指向后一个元素。
 

操作系统

进程,线程与协程的区别

进程是资源分配的基本单位

线程是独立调度的基本单位

 

进程 线程区别

根本区别:进程是操作系统资源分配的基本单位,而线程是任务调度和执行的基本单位

在开销方面:每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销;线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小。

所处环境:在操作系统中能同时运行多个进程(程序);而在同一个进程(程序)中有多个线程同时执行(通过CPU调度,在每个时间片中只有一个线程执行)

内存分配方面:系统在运行的时候会为每个进程分配不同的内存空间;而对线程而言,除了CPU外,系统不会为线程分配内存(线程所使用的资源来自其所属进程的资源),线程组之间只能共享资源。

包含关系:没有线程的进程可以看做是单线程的,如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的;线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程。
 

协程

写成是一种用户态的轻量级线程,协程的调度完全由用户控制。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方。在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。

2、协程与线程的区别

(1)一个线程可以有多个协程,一个进程也可以单独拥有多个协程。

(2)进程线程都是同步机制,而协程则是异步

(3)协程能够保留上一次调用时的状态,每次过程重入时,就相当于进入上一次调用状态

计算机网络

1 tcp udp区别以及应用场景

  • 用户数据报协议 UDP(User Datagram Protocol)是无连接的,尽最大可能交付,没有拥塞控制,面向报文(对于应用程序传下来的报文不合并也不拆分,只是添加 UDP 首部),支持一对一、一对多、多对一和多对多的交互通信。

  • 传输控制协议 TCP(Transmission Control Protocol)是面向连接的,提供可靠交付,有流量控制,拥塞控制,提供全双工通信,面向字节流(把应用层传下来的报文看成字节流,把字节流组织成大小不等的数据块),每一条 TCP 连接只能是点对点的(一对一)。

  • TCP、UDP使用场景

    TCP协议需要三次握手通信成功后进行建立连接.

    场景:互联网和企业网上的客户端应用 淘宝 京东、数据传输的性能必须让位于数据传输的完整性、可控制性和可靠性时,TCP协议是当然的选择。
    UDP协议是直接发送,不会判断是否接收和发送成功.

    场景: 当强调传输性能而不是传输的完整性时,如:音频和多媒体应用,UDP是最好的选择。QQ使用的是UDP协议聊天,因为用户不需要等待就会接收。
     

2 TCP如何保证可靠性?

(1)数据被分割成TCP认为最合适发送的数据块 
(2)重传机制:TCP发出一个段后,启动定时器,如果不能及时收到报文段,则重发一个报文段 
(3)当TCP收到一个段后,会发送一个确认 
(4)TCP将保持它首部和数据的校验和 
(5)TCP对收到的数据进行重新排序,保证数据的有序 
(6)TCP会丢弃重复的数据 
(7)TCP提供流量控制

3 http状态码

服务器返回的 响应报文 中第一行为状态行,包含了状态码以及原因短语,用来告知客户端请求的结果。

状态码

类别

含义

1XX

Informational(信息性状态码)

接收的请求正在处理

2XX

Success(成功状态码)

请求正常处理完毕

3XX

Redirection(重定向状态码)

需要进行附加操作以完成请求

4XX

Client Error(客户端错误状态码)

服务器无法处理请求

5XX

Server Error(服务器错误状态码)

服务器处理请求出错

1XX 信息

100 Continue :表明到目前为止都很正常,客户端可以继续发送请求或者忽略这个响应。

2XX 成功

200 OK

204 No Content :请求已经成功处理,但是返回的响应报文不包含实体的主体部分。一般在只需要从客户端往服务器发送信息,而不需要返回数据时使用。

206 Partial Content :表示客户端进行了范围请求,响应报文包含由 Content-Range 指定范围的实体内容。

3XX 重定向

301 Moved Permanently :永久性重定向

302 Found :临时性重定向

303 See Other :和 302 有着相同的功能,但是 303 明确要求客户端应该采用 GET 方法获取资源。

  • 注:虽然 HTTP 协议规定 301、302 状态下重定向时不允许把 POST 方法改成 GET 方法,但是大多数浏览器都会在 301、302 和 303 状态下的重定向把 POST 方法改成 GET 方法。

304 Not Modified :如果请求报文首部包含一些条件,例如:If-Match,If-Modified-Since,If-None-Match,If-Range,If-Unmodified-Since,如果不满足条件,则服务器会返回 304 状态码。

 

307 Temporary Redirect :临时重定向,与 302 的含义类似,但是 307 要求浏览器不会把重定向请求的 POST 方法改成 GET 方法。

4XX 客户端错误

400 Bad Request :请求报文中存在语法错误。

401 Unauthorized :该状态码表示发送的请求需要有认证信息(BASIC 认证、DIGEST 认证)。如果之前已进行过一次请求,则表示用户认证失败。

403 Forbidden :请求被拒绝。

404 Not Found

5XX 服务器错误

500 Internal Server Error :服务器正在执行请求时发生错误。

503 Service Unavailable :服务器暂时处于超负载或正在进行停机维护,现在无法处理请求。

4 TCP 的三次握手

 

以下描述不讨论序号和确认号,因为序号和确认号的规则比较简单。并且不讨论 ACK,因为 ACK 在连接建立之后都为 1。

A 发送连接释放报文,FIN=1。

B 收到之后发出确认,此时 TCP 属于半关闭状态,B 能向 A 发送数据但是 A 不能向 B 发送数据。

当 B 不再需要连接时,发送连接释放报文,FIN=1。

A 收到后发出确认,进入 TIME-WAIT 状态,等待 2 MSL(最大报文存活时间)后释放连接。

B 收到 A 的确认后释放连接。

四次挥手的原因

客户端发送了 FIN 连接释放报文之后,服务器收到了这个报文,就进入了 CLOSE-WAIT 状态。这个状态是为了让服务器端发送还未传送完毕的数据,传送完毕之后,服务器会发送 FIN 连接释放报文。

TIME_WAIT

客户端接收到服务器端的 FIN 报文后进入此状态,此时并不是直接进入 CLOSED 状态,还需要等待一个时间计时器设置的时间 2MSL。这么做有两个理由:

确保最后一个确认报文能够到达。如果 B 没收到 A 发送来的确认报文,那么就会重新发送连接释放请求报文,A 等待一段时间就是为了处理这种情况的发生。

等待一段时间是为了让本连接持续时间内所产生的所有报文都从网络中消失,使得下一个新的连接不会出现旧的连接请求报文。

 

5 web页面的请求过程

 

主机如果没有ip地址先通过dhcp获取;

浏览器中输入URL

 

浏览器要将URL解析为IP地址,解析域名就要用到DNS协议,首先主机会查询DNS的缓存,如果没有就给本地DNS发送查询请求。DNS查询分为两种方式,一种是递归查询,一种是迭代查询。如果是迭代查询,本地的DNS服务器,向根域名服务器发送查询请求,根域名服务器告知该域名的一级域名服务器,然后本地服务器给该一级域名服务器发送查询请求,然后依次类推直到查询到该域名的IP地址。DNS服务器是基于UDP的,因此会用到UDP协议。

得到IP地址后,浏览器就要与服务器建立一个http连接(应用层)。因此要用到http协议。http生成一个get请求报文,将该报文传给TCP层处理,所以还会用到TCP协议--传输层。如果采用https还会使用https协议先对http数据进行加密。TCP层如果有需要先将HTTP数据包分片,分片依据路径MTU和MSS。TCP的数据包然后会发送给IP层,用到IP协议  网络层。IP层通过路由选路,一跳一跳发送到目的地址。当然在一个网段内的寻址是通过以太网协议实现(也可以是其他物理层协议,比如PPP,SLIP),以太网协议需要直到目的IP地址的物理地址,有需要ARP协议。

服务器“处理”请求

服务器端收到请求后的由web服务器(准确说应该是http服务器)处理请求,诸如Apache、Ngnix、IIS等。web服务器解析用户请求,知道了需要调度哪些资源文件,再通过相应的这些资源文件处理用户请求和参数,并调用数据库信息,最后将结果通过web服务器返回给浏览器客户端。

服务器发回一个HTML响应

关闭Tcp链接

为了避免服务器与客户端双方的资源占用和损耗,当双方没有请求或响应传递时,任意一方都可以发起关闭请求。与创建TCP连接的3次握手类似,关闭TCP连接,需要4次握手。

浏览器解析HTML

准确地说,浏览器需要加载解析的不仅仅是HTML,还包括CSS、JS。以及还要加载图片、视频等其他媒体资源。

浏览器布局渲染

 

用到的协议:

DNS协议,http协议,https协议属于应用层

TCP/UDP属于传输层

IP协议,ARP协议属于网络层

RIP协议 OSPF BHGP 网络层

 

6http怎样保持有状态

HTTP协议自身不对请求和响应之间的通信状态进行保存。 也就是说在HTTP这个级别, 协议对于发送过的请求或响应都不做持久化处理。发送的请求不记录用户的状态,不记录用户的信息。就相当于它被访问了2次,不知道是哪两人访问的,或者是一个人访问两次。

 

正是因为HTTP协议的这一特点,用户的活动发生在多个请求和响应之中,作为web服务器,必须能够采用一种机制来唯一地标识一个用户,同时记录该用户的状态。比如说我们平时上的qq.可以设置登录状态,记住密码,之后我们在一段时间内就可以不用输入密码直接输入,那么Http协议是无状态的,我们怎么可以让他记住我们的状态呢,就是通过会话跟踪技术

会话跟踪技术有哪几种?

Cookie    Session    隐藏表单域(hidden) URL重写

网址重写


网址重写是一种Session追踪技术,需要将一个或多个token作为一个查询字符串添加到一个URL中

token格式为url?key-1=value-1&key-2=value-2

网址重写只适用于那些既需要保持,却又不跨越太多页面,并且又不太重要的信息。

任何相对的URL(没有协议部分的URL)都会被当作是相对于当前页面的URL。

举例:分页中的下一页

隐藏域
将值放在HTML表单的隐藏域中。当用户提交表单时,隐藏域中的值也传送到服务器。只有当页面包含表单,或者可以在页面中添加表单时,才适合使用隐藏域。这种技术胜过网址重写技术的地方在于,可以将更多的字符传到服务器,并且不需要进行字符编码。但是像网址重写一样,也只有当要传递的信息不需要跨越多个页面时,才适合使用这种技术。

举例:在html页面中修改内容,要将id放在隐藏域中

7 URI 和 URL

URI:

 

  • 协议方案名:使用http:或https:等协议方案名获取访问资源时要指定协议类型。 不区分字母大小写, 最后附一个冒号(:)。也可使用data:或javascript:这类指定数据或脚本程序的方案名。

  • 登录信息(认证) :指定用户名和密码作为从服务器端获取资源时必要的登录信息(身份认证) 。 此项是可选项。

  • 服务器地址 :使用绝对URI必须指定待访问的服务器地址。 地址可以是类似baidu.com这种DNS可解析的域名, 或是192.168.1.1这类IPv4地址, 还可以是[0:0:0:0:0:0:0:1]这样用方括号括起来的IPv6地址。

  • 服务器端口号 :指定服务器连接的网络端口号。 此项也是可选项, 若用户省略则自动使用默认端口号。

  • 带层次的文件路径 :指定服务器上的文件路径来定位特指的资源。 这与UNIX系统的文件目录结构相似。

  • 查询字符串 :针对已指定的文件路径内的资源, 可以使用查询字符串传入任意参数。 此项可选。

  • 片段标识符 :使用片段标识符通常可标记出已获取资源中的子资源(文档内的某个位置) 。 但在RFC中并没有明确规定其使用方法。 该项也为可选项。

 

Cookie

HTTP 协议是无状态的,主要是为了让 HTTP 协议尽可能简单,使得它能够处理大量事务。HTTP/1.1 引入 Cookie 来保存状态信息。

Cookie实际上是一小段的文本信息。客户端请求服务器,如果服务器需要记录该用户状态,就使用response向客户端浏览器颁发一个Cookie。客户端浏览器会把Cookie保存起来。当浏览器再请求该网站时,浏览器把请求的网址连同该Cookie一同提交给服务器。服务器检查该Cookie,以此来辨认用户状态。服务器还可以根据需要修改Cookie的内容。

Cookie 是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器之后向同一服务器再次发起请求时被携带上,用于告知服务端两个请求是否来自同一浏览器。由于之后每次请求都会需要携带 Cookie 数据,因此会带来额外的性能开销(尤其是在移动环境下)

cookie生命周期:

如果不设置过期时间,则表示这个cookie生命周期为浏览器会话期间,只要关闭浏览器窗口,cookie就消失了。

这种生命期为浏览会话期的cookie被称为会话cookie。会话cookie一般不保存在硬盘上而是保存在内存里。

如果设置了过期时间,浏览器就会把cookie保存到硬盘上,关闭后再次打开浏览器,这些cookie依然有效直到超过设定的过期时间。存储在硬盘上的cookie可以在不同的浏览器进程间共享,比如两个IE窗口。而对于保存在内存的cookie,不同的浏览器有不同的处理方式。

缺点:

1 、 cookie 数量和长度有限。每个特定的域名下最多生成 20 个 cookie ,每个 cookie 长度不能超过 4KB

2 、额外开销。  cookie 在每次发起 HTTP 请求的时候都会被发送给服务器,会增加开销

3 、安全性问题。 cookie 不安全,拦截之后会暴露 session 信息

 

优点:极高的扩展性和可用性

1、 通过良好的编程,可控制保存 cookie 中的 session 对象的大小。

2、 通过加密和安全传输技术( SSL ),可减少 cookie 被破解的可能性。

3、 只在 cookie 中存放不敏感数据,即使被盗也不会有重大损失。

4、cookie 的生命期可控。通过控制 cookie 的生命期,使之不会永远有效

用途

  • 会话状态管理(如用户登录状态、购物车、游戏分数或其它需要记录的信息)
  • 个性化设置(如用户自定义设置、主题等)
  • 浏览器行为跟踪(如跟踪分析用户行为等)

Session

什么是Session
Session是另一种记录客户状态的机制,不同的是Cookie保存在客户端浏览器中,而Session保存在服务器上。客户端浏览器访问服务器的时候,服务器把客户端信息以某种形式记录在服务器上。这就是Session。客户端浏览器再次访问时只需要从该Session中查找该客户的状态就可以了。

如果说Cookie机制是通过检查客户身上的“通行证”来确定客户身份的话,那么Session机制就是通过检查服务器上的“客户明细表”来确认客户身份。Session相当于程序在服务器上建立的一份客户档案,客户来访的时候只需要查询客户档案表就可以了。

利用 Session 存储在服务器端,存储在服务器端的信息更加安全。

Session 可以存储在服务器上的文件、数据库或者内存中。也可以将 Session 存储在 Redis 这种内存型数据库中,效率会更高。

session生命周期:

Session:Session 是在服务器端生成的,存储在服务器端,即存在内存中。可以对生成的 Session 设置过期时间,如果不设置过期时间,默认的 Session 过期时间是30 分钟(在不同的服务器中,它的过期时间略有不同,本文是以 Tomcat 来说的)  但是,Sesssion 的生成的同时,会生成一个与之相关联的的 SessionID ,此 SessionID的存储是需要 Cookie 来完成的。 SessionID 是以名称为 JSESSIONID,其值应该是一个既不会重复,又不容易被找到规律以仿造的字符串。SessionID会随着此次 Http 响应,一并返回到客户端,并保存在客户端中。到当前请求再次发出后,该 SessionID会随着 Http 头部,传到服务器中,服务器依据当前 SessionID 得到与之对应的 Session.

其中:通过 Cookie 的方式存储 Session 状态,只是其中一种方式。如果客户端禁用了 Cookie 的话,很多网站任然可以存储用户的信息。一种处理的方式是URL 重写,将 SesseionID 直接附加在请求地址的后面。另一种处理的方式是,使用隐藏自动的方式。就是服务器自动的在表单中,添加一个隐藏字段,以便在表单提交时,将 SesseionID 一起传到服务器,进行识别。

 

使用 Session 维护用户登录状态的过程如下:

  • 用户进行登录时,用户提交包含用户名和密码的表单,放入 HTTP 请求报文中;
  • 服务器验证该用户名和密码,如果正确则把用户信息存储到 Redis 中,它在 Redis 中的 Key 称为 Session ID;
  • 服务器返回的响应报文的 Set-Cookie 首部字段包含了这个 Session ID,客户端收到响应报文之后将该 Cookie 值存入浏览器中;
  • 客户端之后对同一个服务器进行请求时会包含该 Cookie 值,服务器收到之后提取出 Session ID,从 Redis 中取出用户信息,继续之前的业务操作。

应该注意 Session ID 的安全性问题,不能让它被恶意攻击者轻易获取,那么就不能产生一个容易被猜到的 Session ID 值。此外,还需要经常重新生成 Session ID。在对安全性要求极高的场景下,例如转账等操作,除了使用 Session 管理用户状态之外,还需要对用户进行重新验证,比如重新输入密码,或者使用短信验证码等方式。

 

6 Cookie 与 Session 选择

  • Cookie 只能存储 ASCII 码字符串,而 Session 则可以存储任何类型的数据,因此在考虑数据复杂性时首选 Session;
  • Cookie 存储在浏览器中,容易被恶意查看。如果非要将一些隐私数据存在 Cookie 中,可以将 Cookie 值进行加密,然后在服务器进行解密;
  • 对于大型网站,如果用户所有的信息都存储在 Session 中,那么开销是非常大的,因此不建议将所有的用户信息都存储到 Session 中。

 

购物车用哪个

1.Session(Memcached)方式

优点:购物车信息保存在服务端,可以保存1M 信息。
缺点:对于大型网站会占有过多的服务器内存资源,造成服务器压力过大。Session保存的信息会在用户退出登录后丢失。用户下次登录,购物车中商品信息丢失,用户只能从新选择。

2.Cookie方式

优点:购物车信息存储在客户端,不占用服务器资源,基本可以到达持久化存储。
缺点:Cookie有大小的限制,不能超过4K,而且不够安全。如果是个人PC机,Cookie能很好的保存购物车信息,但如果是公共办公环境,Cookie保存的信息基本就失效了(会被其他人购物车信息覆盖)。对一个大型的电子商务网站,我们需要对用户的购买行为进行分析,需要对用户推荐用户感兴趣的商品,如果把购物车信息保存在Cookie中,则不能对用户购买行为分析统计。

3.数据库存储

优点:持久化存储,可以分析用户购买行为。
缺点: 网站速度变慢,成本和维护增加。

一般来说,可以使用session,cookie和数据库来记录购物车数据
1,不过不提倡使用session,这货占用服务器资源,还有过期时间,客户关掉浏览器时session即消失,下次再上来,又得重新选产品。
2,cookie这东西不错,放在客户端的,给个一年的过期时间,只要客户不清掉,每次来都能记得上次的购物车信息。大家可以看看京东,

3,数据库这东西好啊,不会像cookie那种容易丢失,也没有客户端的限制,你想怎么存,存多少都行。

 

7  说说你对get和post请求,并且说说它们之间的区别

作用

GET 用于获取资源,而 POST 用于传输实体主体。

参数

GET 和 POST 的请求都能使用额外的参数,但是 GET 的参数是以查询字符串出现在 URL 中,而 POST 的参数存储在实体主体中。不能因为 POST 参数存储在实体主体中就认为它的安全性更高,因为照样可以通过一些抓包工具(Fiddler)查看。

因为 URL 只支持 ASCII 码,因此 GET 的参数中如果存在中文等字符就需要先进行编码。例如 中文 会转换为 %E4%B8%AD%E6%96%87,而空格会转换为 %20。POST 参数支持标准字符集。

GET /test/demo_form.asp?name1=value1&name2=value2 HTTP/1.1

 

POST /test/demo_form.asp HTTP/1.1

Host: w3schools.com

name1=value1&name2=value2

 


数据库mysql

1 数据库100亿条数据分库,你怎么设计

2 高并发服务器

3user(id,name) 查出来 有重名的名字和重名的人的个数 写sql语句

SELECT myname, COUNT(*) FROM tablename1 GROUP BY myname HAVING COUNT(*) > 1;

3 数据库慢了怎么办

1 建立缓存

2 sql优化(索引, 优化子查询等等)

3 可能数据多了 分库 分表

 

4高并发下,如何缓解服务器压力

整个架构:可采用分布式架构,利用微服务架构拆分服务部署在不同的服务节点,避免单节点宕机引起的服务不可用

数据库:采用主从复制,读写分离,甚至是分库分表,

加缓存:使用诸如memcache,redis,ehcache等缓存数据库定义表

加图片服务器:图片等大文件

负载均衡:使用nginx等对访问量过大的服务采用负载均衡,实现服务集群,提高服务的最大并发数,防止压力过大导致单个服务的崩溃!

使用消息中间件:对服务之间的数据传输,使用诸如rabbit mq,kafka等等分布式消息队列异步传输,防止同步传输数据的阻塞和数据丢失!

多线程:现在的服务器都是多核心处理模式

避免阻塞:避免bio,blockingqueue等常常引起长久阻塞的技术,而改为nio等异步处理机制

MySQL主从复制原理

1、MySQL的主从复制是一个异步的复制过程(虽然一般情况下感觉是实时的),数据将从一个MySQL数据库(我们称之为Master)复制到另一个MySQL数据库(我们称之为Slave),在Master与Salve之间实现整个主从复制的过程是由三个线程参与完成的。其中有两个线程(SQL线程和IO线程)在Slave端,另外一个线程(I/O线程)在Master端。 
2、要实现MySQL的主从复制,首先必须打开Master端的binlog记录功能,否则就无法实现。因为整个复制过程实际上就是Slave从Master端获取binlog日志,然后再在Slave上以相同顺序执行获取的binlog日志中所记录的各种SQL操作。注意binlog日志只记录增删改(不记录查询语句) 
3、从库IO在第一次把信息放入到relay中后会触发SQL线程,然后后边就交给SQL线程来处理,把binlog文件中的SQL语句解析出来后变成SQL语句放入数据库中

Redis

1 .redis意外处理

如果Redis挂了怎么办? 
配置Redis集群,一个Master和一个Salve,另外还用了Redis Sentinel(哨兵)监控来提高Redis的可用性,当住Redis挂了以后哨兵会将会将从Redis提升为主Redis 
数据会不会丢失? 
数据丢失必须两个Redis服务都挂了才可能了,这种毕竟是小概率事件,不过即使真挂了也不会有太大影响,我们配置了rdb和aof两种策略来对redis中的数据进行持久化,aof配置的每秒同步一次,虽然会稍微影响点性能,不过在我们目前的用户量下还是不会有太大影响,这样数据也有了足够的保障


 

并发 多线程

1 wait和sleep的区别

wait() 和 sleep() 的区别

  • wait() 是 Object 的方法,而 sleep() 是 Thread 的静态方法;
  • wait() 会释放锁,sleep() 不会。

2

聚集索引:

给表上了主键,那么表在磁盘上的存储结构就由整齐排列的结构转变成了树状结构,也就是「平衡树」结构,换句话说,就是整个表就变成了一个索引。没错, 再说一遍, 整个表变成了一个索引,也就是所谓的「聚集索引」。 这就是为什么一个表只能有一个主键, 一个表只能有一个「聚集索引」,因为主键的作用就是把「表」的数据格式转换成「索引(平衡树)」的格式放置。

非聚集索引:

非聚集索引和聚集索引一样, 同样是采用平衡树作为索引的数据结构。索引树结构中各节点的值来自于表中的索引字段, 假如给user表的name字段加上索引 , 那么索引就是由name字段中的值构成,在数据改变时, DBMS需要一直维护索引结构的正确性。如果给表中多个字段加上索引 , 那么就会出现多个独立的索引结构,每个索引(非聚集索引)互相之间不存在关联。

区别在于, 通过聚集索引可以查到需要查找的数据, 而通过非聚集索引可以查到记录对应的主键值 , 再使用主键的值通过聚集索引查找到需要的数据。

覆盖索引:

如果一个索引包含所有需要查询的字段的值,我们就称之为“覆盖索引”。因此不需要通过主键ID值的查找数据行的真实所在, 直接取得叶节点中name的值返回即可。

个人理解:

非聚集索引就是一般常用的索引,索引树的根节点是表的主键。

聚集索引就是主键组成的数,根节点是数据库真实数据的位置。

mysql开启了ICP的话,可以减少存储引擎访问基表的次数

下面来简单的介绍一下这三者的区别

using index :使用覆盖索引的时候就会出现

using where:在查找使用索引的情况下,需要回表去查询所需的数据

using index condition:查找使用了索引,但是需要回表查询数据

using index & using where:查找使用了索引,但是需要的数据都在索引列中能找到,所以不需要回表查询数据

 

 

Redis 

1 redis数据类型

数据类型

可以存储的值

操作

STRING

字符串、整数或者浮点数

对整个字符串或者字符串的其中一部分执行操作
对整数和浮点数执行自增或者自减操作

LIST

列表

从两端压入或者弹出元素 
对单个或者多个元素进行修剪,
只保留一个范围内的元素

SET

无序集合

添加、获取、移除单个元素
检查一个元素是否存在于集合中
计算交集、并集、差集
从集合里面随机获取元素

HASH

包含键值对的无序散列表

添加、获取、移除单个键值对
获取所有键值对
检查某个键是否存在

ZSET

有序集合

添加、获取、删除元素
根据分值范围或者成员来获取元素
计算一个键的排名

2 redis缓存/内存淘汰策略

常用的淘汰算法:

FIFO:First In First Out,先进先出。判断被存储的时间,离目前最远的数据优先被淘汰。

LRU:Least Recently Used,最近最少使用。判断最近被使用的时间,目前最远的数据优先被淘汰。

LFU:Least Frequently Used,最不经常使用。在一段时间内,数据被使用次数最少的,优先被淘汰。

Redis 具体有 6 种淘汰策略:

策略

描述

volatile-lru

从已设置过期时间的数据集中挑选最近最少使用的数据淘汰

volatile-ttl

从已设置过期时间的数据集中挑选将要过期的数据淘汰

volatile-random

从已设置过期时间的数据集中任意选择数据淘汰

allkeys-lru

从所有数据集中挑选最近最少使用的数据淘汰

allkeys-random

从所有数据集中任意选择数据进行淘汰

noeviction

禁止驱逐数据

作为内存数据库,出于对性能和内存消耗的考虑,Redis 的淘汰算法实际实现上并非针对所有 key,而是抽样一小部分并且从中选出被淘汰的 key。

使用 Redis 缓存数据时,为了提高缓存命中率,需要保证缓存数据都是热点数据。可以将内存最大使用量设置为热点数据占用的内存量,然后启用 allkeys-lru 淘汰策略,将最近最少使用的数据淘汰。

Redis 4.0 引入了 volatile-lfu 和 allkeys-lfu 淘汰策略,LFU 策略通过统计访问频率,将访问频率最少的键值对淘汰。

3 key过期键删除策略

通过前面的介绍,大家应该都知道数据库键的过期时间都保存在过期字典里,那假如一个键过期了,那么这个过期键是什么时候被删除的呢?现在来看看redis的过期键的删除策略: 
a、定时删除:在设置键的过期时间的同时,创建一个定时器,在定时结束的时候,将该键删除; 
b、惰性删除:放任键过期不管,在访问该键的时候,判断该键的过期时间是否已经到了,如果过期时间已经到了,就执行删除操作; 
c、定期删除:每隔一段时间,对数据库中的键进行一次遍历,删除过期的键。 

4 redis缓存三大问题及解决方案

缓存穿透 缓存雪崩 缓存击穿
1. 缓存穿透
在大多数互联网应用中,缓存的使用方式如下图所示:

当业务系统发起某一个查询请求时,首先判断缓存中是否有该数据;
如果缓存中存在,则直接返回数据;
如果缓存中不存在,则再查询数据库,然后返回数据。
了解了上述过程后,下面说说缓存穿透。

1.1 什么是缓存穿透?
业务系统要查询的数据根本就存在!当业务系统发起查询时,按照上述流程,首先会前往缓存中查询,由于缓存中不存在,然后再前往数据库中查询。由于该数据压根就不存在,因此数据库也返回空。这就是缓存穿透。

综上所述:业务系统访问压根就不存在的数据,就称为缓存穿透。

1.2 缓存穿透的危害
如果存在海量请求查询压根就不存在的数据,那么这些海量请求都会落到数据库中,数据库压力剧增,可能会导致系统崩溃(你要知道,目前业务系统中最脆弱的就是IO,稍微来点压力它就会崩溃,所以我们要想种种办法保护它)。

1.3 为什么会发生缓存穿透?
发生缓存穿透的原因有很多,一般为如下两种:

恶意攻击
代码逻辑错误


1.4 缓存穿透的解决方案
缓存空数据
之所以发生缓存穿透,是因为缓存中没有存储这些空数据的key,导致这些请求全都打到数据库上。

那么,我们可以稍微修改一下业务系统的代码,将数据库查询结果为空的key也存储在缓存中。当后续又出现该key的查询请求时,缓存直接返回null,而无需查询数据库。

缓存空对象会有两个问题: 
第一,空值做了缓存,意味着缓存层中存了更多的键,需要更多的内存空间,比较有效的方法是针对这类数据设置一个较短的过期时间,让其自动剔除。 
第二,缓存层和存储层的数据会有一段时间窗口的不一致,可能会对业务有一定影响。例如过期时间设置为 5 分钟,如果此时存储层添加了这个数据,那此段时间就会出现缓存层和存储层数据的不一致,此时可以利用消息系统或者其他方式清除掉缓存层中的空对象。

 BloomFilter
第二种避免缓存穿透的方式即为使用BloomFilter。

它需要在缓存之前再加一道屏障,里面存储目前数据库中存在的所有key,如下图所示:

当业务系统有查询请求的时候,首先去BloomFilter中查询该key是否存在。若不存在,则说明数据库中也不存在该数据,因此缓存都不要查了,直接返回null。若存在,则继续执行后续的流程,先前往缓存中查询,缓存中没有的话再前往数据库中的查询。

这种方法适用于数据命中不高,数据相对固定实时性低(通常是数据集较大)的应用场景,代码维护较为复杂,但是缓存空间占用少。 

1.4.3 两种方案的比较
对于一些恶意攻击,查询的key往往各不相同,而且数据贼多。此时,第一种方案就显得提襟见肘了。因为它需要存储所有空数据的key,而这些恶意攻击的key往往各不相同,而且同一个key往往只请求一次。因此即使缓存了这些空数据的key,由于不再使用第二次,因此也起不了保护数据库的作用。
因此,对于空数据的key各不相同、key重复请求概率低的场景而言,应该选择第二种方案。而对于空数据的key数量有限、key重复请求概率较高的场景而言,应该选择第一种方案。

2. 缓存雪崩
2.1 什么是缓存雪崩?
通过上文可知,缓存其实扮演了一个保护数据库的角色。它帮数据库抵挡大量的查询请求,从而避免脆弱的数据库受到伤害。

如果缓存因某种原因发生了宕机,那么原本被缓存抵挡的海量查询请求就会像疯狗一样涌向数据库。此时数据库如果抵挡不了这巨大的压力,它就会崩溃。

这就是缓存雪崩。

2.2 如何避免缓存雪崩?
2.2.1 使用缓存集群,保证缓存高可用
和飞机都有多个引擎一样,如果缓存层设计成高可用的,即使个别节点、个别机器、甚至是机房宕掉,依然可以提供服务,例如前面介绍过的 Redis Sentinel 和 Redis Cluster 都实现了高可用。 

2.2.2 使用Hystrix
Hystrix是一款开源的“防雪崩工具”,它通过 熔断、降级、限流三个手段来降低雪崩发生后的损失。

Hystrix就是一个Java类库,它采用命令模式,每一项服务处理请求都有各自的处理器。所有的请求都要经过各自的处理器。处理器会记录当前服务的请求失败率。一旦发现当前服务的请求失败率达到预设的值,Hystrix将会拒绝随后该服务的所有请求,直接返回一个预设的结果。这就是所谓的“熔断”。当经过一段时间后,Hystrix会放行该服务的一部分请求,再次统计它的请求失败率。如果此时请求失败率符合预设值,则完全打开限流开关;如果请求失败率仍然很高,那么继续拒绝该服务的所有请求。这就是所谓的“限流”。而Hystrix向那些被拒绝的请求直接返回一个预设结果,被称为“降级”。

3. 缓存击穿(热点数据集中失效)
3.1 什么是热点数据集中失效?
我们一般都会给缓存设定一个失效时间,过了失效时间后,该数据库会被缓存直接删除,从而一定程度上保证数据的实时性。

但是,对于一些请求量极高的热点数据而言,一旦过了有效时间,此刻将会有大量请求落在数据库上,从而可能会导致数据库崩溃。

3.2 解决方案
3.2.1 互斥锁
此方法只允许一个线程重建缓存,其他线程等待重建缓存的线程执行完,重新从缓存获取数据即可

当某一个热点数据失效后,只有第一个数据库查询请求发往数据库,其余所有的查询请求均被阻塞,从而保护了数据库。但是,由于采用了互斥锁,其他请求将会阻塞等待,此时系统的吞吐量将会下降。这需要结合实际的业务考虑是否允许这么做。

互斥锁可以避免某一个热点数据失效导致数据库崩溃的问题,而在实际业务中,往往会存在一批热点数据同时失效的场景。那么,对于这种场景该如何防止数据库过载呢?

设置不同的失效时间

当我们向缓存中存储这些数据的时候,可以将他们的缓存失效时间错开。这样能够避免同时失效。如:在一个基础时间上加/减一个随机数,从而将这些缓存的失效时间错开

3.3.2 永远不过期
“永远不过期”包含两层意思: 
从缓存层面来看,确实没有设置过期时间,所以不会出现热点 key 过期后产生的问题,也就是“物理”不过期。 
从功能层面来看,为每个 value 设置一个逻辑过期时间,当发现超过逻辑过期时间后,会使用单独的线程去构建缓存。 
整个过程如下图所示: 

 从实战看,此方法有效杜绝了热点 key 产生的问题,但唯一不足的就是重构缓存期间,会出现数据不一致的情况,这取决于应用方是否容忍这种不一致。

3.3.3 两种方案的比较
互斥锁 (mutex key):这种方案思路比较简单,但是存在一定的隐患,如果构建缓存过程出现问题或者时间较长,可能会存在死锁和线程池阻塞的风险,但是这种方法能够较好的降低后端存储负载并在一致性上做的比较好。
” 永远不过期 “:这种方案由于没有设置真正的过期时间,实际上已经不存在热点 key 产生的一系列危害,但是会存在数据不一致的情况,同时代码复杂度会增大。

Web基础

1. resutful架构

什么是rest? 什么是restful? 傻傻分不清楚。。

一.概念和区别:
1.关于rest
rest  :representational state transfer      表述性状态转移,是一种架构风格。
rest原则:

   <1>网络上的所有事物都被抽象为资源

   <2> 每个资源都有一个唯一的资源标识符

   <3> 同一个资源具有多种表现形式(xml,json等)

   <4>  对资源的各种操作不会改变资源标识符

    <5> 所有的操作都是无状态的

2.关于restful
restful: 遵守了rest 原则 的web服务

理解:rest与restful相比,多了一个ful,就英语层面来说是一个形容词,restful翻译为中文为: “rest式的”

是rest式的什么呢?答案是 rest式的应用,rest风格的web服务也是rest式的应用,rest式的web服务是一种ROA(The Resource-Oriented Architecture)(面向资源的架构),ROA听起来很高大上有没有。。


3.两者的联系与区别
restful是由rest派生出来的。

二.restful用法:
在Restful之前的操作:
http://127.0.0.1/user/query/1 GET  根据用户id查询用户数据
http://127.0.0.1/user/save POST 新增用户
http://127.0.0.1/user/update POST 修改用户信息
http://127.0.0.1/user/delete GET/POST 删除用户信息

RESTful用法:
http://127.0.0.1/user/1 GET  根据用户id查询用户数据
http://127.0.0.1/user  POST 新增用户
http://127.0.0.1/user  PUT 修改用户信息
http://127.0.0.1/user  DELETE 删除用户信息

之前的操作是没有问题的,大神认为是有问题的,有什么问题呢?你每次请求的接口或者地址,都在做描述,例如查询的时候用了query,新增的时候用了save,其实完全没有这个必要,我使用了get请求,就是查询.使用post请求,就是新增的请求,我的意图很明显,完全没有必要做描述,这就是为什么有了restful.

三.优点:
精简,一看就明白。


什么是RESTful API

URI

即统一资源标识符,服务器上每一种资源,比如文档、图像、视频片段、程序 都由一个通用资源标识符(Uniform Resource Identifier, 简称"URI")进行定位。

HTTP动词

常用的HTTP动词有下面五个

  • GET(SELECT):从服务器取出资源(一项或多项)。
  • POST(CREATE):在服务器新建一个资源。
  • PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)。
  • PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)。
  • DELETE(DELETE):从服务器删除资源。

RESTful架构

服务器上每一种资源,比如一个文件,一张图片,一部电影,都有对应的url地址,如果我们的客户端需要对服务器上的这个资源进行操作,就需要通过http协议执行相应的动作来操作它,比如进行获取,更新,删除。

简单来说就是url地址中只包含名词表示资源,使用http动词表示动作进行操作资源

为什么用RESTful

前后端分离主要是以API为界限进行解耦的,这就会产生大量的API,采用RESTful来设计API主要有以下好处:

1、表现力更强,更易于理解

2、RESRful是无状态,所以不管前端是何种设备何种状态都可以无差别的请求资源

缺点: 对于参数请求过多 比如一次请求1000个订单 那么url就需要1000个参数 这样就不友好

 

2. responsebody是用web接收还是对象接收?

转为json对象返回? 不知道

 

3.用什么东西接收从前端接收的参数

参数传递形式: 
Get请求 
参数放到url后面,用&隔开即可

Post请求 

传递的参数的形式就比较多了。。

一、普通的键值对参数:

get请求实质也是键值对的形式

二、list传值

三、Map类型或实体类类型传值,即json格式传值

 

4. HttpRequest对象

概述:

1、服务器接收http请求后,会根据报文创建HttpRequest对象

2、视图的第一个参数就是HttpRequest对象

3、django创建的,之后调用试图时传递给视图

属性:

1、path:请求的完整路径(不包括域名和段口)

2、method:表示请求的方式。常用的有GET、POST

3、encoding:表示浏览器提交的数据的编码方式,一般为utf-8

4、GET:类似字典的对象,包含了get请求的所有参数

5、FILES:类似字典的对象,包含了所有上传的文件

6、Cookies:字典,包含所有的Cookies

7、session:类似字典的对象,表示当前会话

方法:

is_ajax():如果是通过XMLHttpRequest发起请求的,返回True

QueryDict对象

概述:request对象中的GET、POST都属于QueryDict对象

方法:

1、get()

作用:根据键获取值
只能获取一个值
www.baidu.com/abc?a=1&b=2&c=3
2、getlist()

将键的值以列表的形式返回
可以获取多个值
 

Java基础

1 访问修饰符的范围

 

访问修饰符有四个,权限从大到小:public、protected、default、private ,访问范围如下:           

  • public            本类、同包、子类和全局(只要导入包就能访问)
  • protected      本类、同包和子类
  • default          本类、同包(当类、方法和变量不添加权限修饰符时,默认使用)
  • private          本类(类的内部)

2 Integer和int的区别

①、Integer 是 int 包装类,int 是八大基本数据类型之一(byte,char,short,int,long,float,double,boolean)

②、Integer 是类,默认值为null,int是基本数据类型,默认值为0;

③、Integer 表示的是对象,用一个引用指向这个对象,而int是基本数据类型,直接存储数值

④、Integer会缓存-128~~127的值,但是高于128的话就以创建对象的形式展现

true

false

 false

  true   比较的时候拆箱

Integer最后都变成Integer.valueOf(val);
详情看看 https://blog.csdn.net/teacher_lee_zzsxt/article/details/79230501

3 匿名内部类访问外部类变量要求是什么

匿名内部类的生命周期可能比外部的类要长,因此访问外部局部变量有可能是访问不到的。

4 .string类方法有什么,不可变原因

用字符数组value创建一个String对象   public String(char[] value)

用字符数组以x开始的n个字符创建一个String对象 

5 常用的原子类

根据修改的数据类型,可以将JUC包中的原子操作类可以分为4类。

1. 基本类型: AtomicInteger, AtomicLong, AtomicBoolean ;
2. 数组类型: AtomicIntegerArray, AtomicLongArray, AtomicReferenceArray ;
3. 引用类型: AtomicReference, AtomicStampedRerence, AtomicMarkableReference ;
4. 对象的属性修改类型: AtomicIntegerFieldUpdater, AtomicLongFieldUpdater, AtomicReferenceFieldUpdater 。

 

其他

1.Maven怎么导入一个jar包

法一.手动导入:项目右键—>Build Path—>Configure Build Path—>选中Libraries—>点击Add External Jars—>选中已事先下好的Jar包导入即可。

法二.通过pom.xml文件的Dependencies标签进行添加

 

算法题:

1两个链表的第一个公共节点

2两个叶子节点最近的公共祖先

3数组的子集

4矩阵中的最短路径(从左上角到右下角)

 

 

操作系统

1 堆和栈

1 堆是在内存中是一段不连续的内存空间 而 栈是连续的内存空间 这样连续空间的指针移动快一些

2 可能是第一次访问该内存的时候有可能更快些,理由是当你调用函数是,只想的栈内存一定已经加载到物理内存里面了,页表也已经建好了(在该页没有被置换出去的情况下);指向堆的内存不一定已经映射到物理内存,因此需要处理缺页,建立页表。

 

2逻辑地址、线性地址和物理地址的关系

 许多年以前,当人们还在使用DOS或是更古老的操作系统的时候,计算机的内存还非常小,一般都是以K为单位进行计算,相应的,当时的程序规模也不大,所以内存容量虽然小,但还是可以容纳当时的程序。但随着图形界面的兴起还有用户需求的不断增大,应用程序的规模也随之膨胀起来,终于一个难题出现在程序员的面前,那就是应用程序太大以至于内存容纳不下该程序,通常解决的办法是把程序分割成许多称为覆盖块(overlay)的片段。覆盖块0首先运行,结束时他将调用另一个覆盖块。虽然覆盖块的交换是由OS完成的,但是必须先由程序员把程序先进行分割,这是一个费时费力的工作,而且相当枯燥。人们必须找到更好的办法从根本上解决这个问题。不久人们找到了一个办法,这就是虚拟存储器(virtual memory).虚拟存储器的基本思想是程序,数据,堆栈的总的大小可以超过物理存储器的大小,操作系统把当前使用的部分保留在内存中,而把其他未被使用的部分保存在磁盘上。比如对一个16MB的程序和一个内存只有4MB的机器,操作系统通过选择,可以决定各个时刻将哪4M的内容保留在内存中,并在需要时在内存和磁盘间交换程序片段,这样就可以把这个16M的程序运行在一个只具有4M内存机器上了。而这个16M的程序在运行前不必由程序员进行分割。

 

二、物理地址、虚拟地址(线性地址)、逻辑地址

     任何时候,计算机上都存在一个程序能够产生的地址集合,我们称之为地址范围。这个范围的大小由CPU的位数决定,例如一个32位的CPU,它的地址范围是0~0xFFFFFFFF (4G),而对于一个64位的CPU,它的地址范围为0~0xFFFFFFFFFFFFFFFF (64T).这个范围就是我们的程序能够产生的地址范围,我们把这个地址范围称为虚拟地址空间,该空间中的某一个地址我们称之为虚拟地址。与虚拟地址空间和虚拟地址相对应的则是物理地址空间和物理地址,大多数时候我们的系统所具备的物理地址空间只是虚拟地址空间的一个子集。这里举一个最简单的例子直观地说明这两者,对于一台内存为256M的32bit x86主机来说,它的虚拟地址空间范围是0~0xFFFFFFFF(4G),而物理地址空间范围是0x000000000~0x0FFFFFFF(256M)。

      这里有一个虚拟内存的概念,虚拟内存(virtual memory)是对整个内存(不要和机器上插那条对上号)的抽像描述。他是相对于物理内存来讲的,能直接理解成“不直实的”,“假的”内存,例如,一个0x08000000内存地址,他并不对就物理地址上那个大数组中0x08000000 - 1那个地址元素;之所以是这样,是因为现代操作系统都提供了一种内存管理的抽像,即虚拟内存(virtual memory)。进程使用虚拟内存中的地址,由操作系统协助相关硬件,把他“转换”成真正的物理地址。这个“转换”,是所有问题讨论的关键。
有了这样的抽像,一个程序,就能使用比真实物理地址大得多的地址空间(拆东墙,补西墙,银行也是这样子做的),甚至多个进程能使用相同的地址。不奇怪,因为转换后的物理地址并非相同的。

  物理地址,CPU地址总线传来的地址,由硬件电路控制(现在这些硬件是可编程的了)其具体含义。物理地址中很大一部分是留给内存条中的内存的,但也常被映射到其他存储器上(如显存、BIOS等)。在没有使用虚拟存储器的机器上,虚拟地址被直接送到内存总线上,使具有相同地址的物理存储器被读写;而在使用了虚拟存储器的情况下,虚拟地址不是被直接送到内存地址总线上,而是送到存储器管理单元MMU,把虚拟地址映射为物理地址。

        线性地址(Linear Address)也叫虚拟地址(virtual address)是逻辑地址到物理地址变换之间的中间层。在分段部件中逻辑地址是段中的偏移地址,然后加上基地址就是线性地址。是一个32位无符号整数,可以用来表示高达4GB的地址,也就是,高达4294967296个内存单元。线性地址通常用十六进制数字表示,值得范围从0x00000000到0xfffffff)程序代码会产生逻辑地址,通过逻辑地址变换就可以生成一个线性地址。如果启用了分页机制,那么线性地址可以再经过变换以产生一个物理地址。如果没有启用分页机制,那么线性地址直接就是物理地址。

        逻辑地址是在有地址变换功能的计算机中,访内指令给出的地址 (操作数) 叫逻辑地址,也叫相对地址,也就是是机器语言指令中,用来指定一个操作数或是一条指令的地址。要经过寻址方式的计算或变换才得到内存储器中的实际有效地址即物理地址。一个逻辑地址由两部份组成,段标识符: 段内偏移量。段标识符是由一个16位长的字段组成,称为段选择符。其中前13位是个索引号,后面3位包含一些硬件细节 。

       CPU将一个逻辑地址转换为物理地址,需要进行两步:首先将给定一个逻辑地址(其实是段内偏移量,这个一定要理解!!!),CPU要利用其段式内存管理单元,先将为个逻辑地址转换成一个线程地址,再利用其页式内存管理单元,转换为最终物理地址。这样做两次转换,的确是非常麻烦而且没有必要的,因为直接可以把线性地址抽像给进程。之所以这样冗余,Intel完全是为了兼容而已(Intel为了兼容,将远古时代的段式内存管理方式保留了下来,x86体系的处理器刚开始时只有20根地址线,寻址寄存器是16位。我们知道16位的寄存器可以访问64K的地址空间,如果程序要想访问大于64K的内存,就需要把内存分段,每段64K,用段地址+偏移量的方式来访问,这样使20根地址线全用上,最大的寻址空间就可以到1M字节,这在当时已经是非常大的内存空间了。)。

       现代的多用户多进程操作系统,需要MMU, 才能达到每个用户进程都拥有自己独立的地址空间的目标。使用MMU, 操作系统划分出一段地址区域, 在这块地址区域中, 每个进程看到的内容都不一定一样。例如MICROSOFT WINDOWS操作系统将地址范围4M-2G划分为用户地址空间,进程A在地址0X400000(4M)映射了可执行文件,进程B同样在地址0X400000(4M)映射了可执行文件,如果A进程读地址0X400000, 读到的是A的可执行文件映射到RAM的内容,而进程B读取地址0X400000时,则读到的是B的可执行文件映射到RAM的内容。这就是MMU在当中进行地址转换所起的作用。

 

三、分页机制:页和页帧

大多数使用虚拟存储器的系统都使用一种称为分页(paging)机制。虚拟地址空间划分成称为页(page)的单位,而相应的物理地址空间也被进行划分,单位是页桢(frame).页和页桢的大小必须相同。在这个例子中我们有一台可以生成32位地址的机器,它的虚拟地址范围从0~0xFFFFFFFF(4G),而这台机器只有256M的物理地址,因此他可以运行4G的程序,但该程序不能一次性调入内存运行。这台机器必须有一个达到可以存放4G程序的外部存储器(例如磁盘或是FLASH),以保证程序片段在需要时可以被调用。在这个例子中,页的大小为4K,页桢大小与页相同——这点是必须保证的,因为内存和外围存储器之间的传输总是以页为单位的。对应4G的虚拟地址和256M的物理存储器,他们分别包含了1M个页和64K个页桢。  

Socket 它介于传输层与应用层之间

1.定义

 通常一台主机上总是有很多个进程需要网络资源进行网络通讯。网络通讯的对象准确的讲 不是主机,而应该是主机中运行的进程。只有通过主机名或IP地址和端口号的组合才能唯一的确定网络通讯中的对象: 进程。

原理

套接字工作原理

    要通过互联网进行通信,你至少需要一对套接字,其中一个运行于客户机端,我们称之为ClientSocket,另一个运行于服务器端,我们称之为ServerSocket。 
   根据连接启动的方式以及本地套接字要连接的目标,套接字之间的连接过程可以分为三个步骤:服务器监听,客户端请求,连接确认。

    所谓服务器监听,是服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态。 
    所谓客户端请求,是指由客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。 
    所谓连接确认,是指当服务器端套接字监听到或者说接收到客户端套接字的连接请求,它就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给客户端,一旦客户端确认了此描述,连接就建立好了。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。

 

 

如何保证缓存与数据库的双写一致性?

最初级的缓存不一致问题及解决方案

问题:先修改数据库,再删除缓存。如果删除缓存失败了,那么会导致数据库中是新数据,缓存中是旧数据,数据就出现了不一致。

解决思路:先删除缓存,再修改数据库。如果数据库修改失败了,那么数据库中是旧数据,缓存中是空的,那么数据不会不一致。

因为读的时候缓存没有,则读数据库中旧数据,然后更新到缓存中。

比较复杂的数据不一致问题分析

数据发生了变更,先删除了缓存,然后要去修改数据库,还没来得及修改,一个请求过来,去读缓存,发现缓存空了,去查询数据库,查到了修改前的旧数据,放到了缓存中。随后数据变更的程序完成了数据库的修改。

完了,数据库和缓存中的数据不一样了。。。

为什么上亿流量高并发场景下,缓存会出现这个问题?

只有在对一个数据在并发的进行读写的时候,才可能会出现这种问题。其实如果说你的并发量很低的话,特别是读并发很低,每天访问量就 1 万次,那么很少的情况下,会出现刚才描述的那种不一致的场景。

但是问题是,如果每天的是上亿的流量,每秒并发读是几万,每秒只要有数据更新的请求,就可能会出现上述的数据库+缓存不一致的情况。

解决方案如下:

更新数据的时候,根据数据的唯一标识,将操作路由之后,发送到一个 jvm 内部队列中。

读取数据的时候,如果发现数据不在缓存中,那么将重新读取数据+更新缓存的操作,根据唯一标识路由之后,也发送同一个 jvm 内部队列中。

一个队列对应一个工作线程,每个工作线程串行拿到对应的操作,然后一条一条的执行。

这样的话,一个数据变更的操作,先删除缓存,然后再去更新数据库,但是还没完成更新。

此时如果一个读请求过来,读到了空的缓存,那么可以先将缓存更新的请求发送到队列中,此时会在队列中积压,然后同步等待缓存更新完成。

这里有一个优化点,一个队列中,其实多个更新缓存请求串在一起是没意义的,因此可以做过滤

如果发现队列中已经有一个更新缓存的请求了,那么就不用再放个更新请求操作进去了,直接等待前面的更新操作请求完成即可。

待那个队列对应的工作线程完成了上一个操作的数据库的修改之后,才会去执行下一个操作,也就是缓存更新的操作,此时会从数据库中读取最新的值,然后写入缓存中。

如果请求还在等待时间范围内,不断轮询发现可以取到值了,那么就直接返回;如果请求等待的时间超过一定时长,那么这一次直接从数据库中读取当前的旧值。

高并发的场景下,该解决方案要注意的问题:

1、读请求长时阻塞

由于读请求进行了非常轻度的异步化,所以一定要注意读超时的问题,每个读请求必须在超时时间范围内返回。

该解决方案,最大的风险点在于,可能数据更新很频繁,导致队列中积压了大量更新操作在里面,然后读请求会发生大量的超时,最后导致大量的请求直接走数据库。务必通过一些模拟真实的测试,看看更新数据的频率是怎样的。

另外一点,因为一个队列中,可能会积压针对多个数据项的更新操作,因此需要根据自己的业务情况进行测试,可能需要部署多个服务,每个服务分摊一些数据的更新操作。

如果一个内存队列里积压 100 个商品的库存修改操作,每个库存修改操作要耗费 10ms 去完成,那么最后一个商品的读请求,可能等待 10 * 100 = 1000ms = 1s 后,才能得到数据,这个时候就导致读请求的长时阻塞。

因此,一定要根据实际业务系统的运行情况,去进行一些压力测试和模拟线上环境,去看看最繁忙的时候,内存队列可能会积压多少更新操作,可能会导致最后一个更新操作对应的读请求,会 hang 多少时间。

如果读请求在 200ms 返回,如果你计算过后,哪怕是最繁忙的时候,积压 10 个更新操作,最多等待 200ms,那还可以的。

如果一个内存队列中可能积压的更新操作特别多,那么你就要加机器,让每个机器上部署的服务实例处理更少的数据,那么每个内存队列中积压的更新操作就会越少。

其实根据之前的项目经验,一般来说,数据的写频率是很低的,因此实际上正常来说,在队列中积压的更新操作应该是很少的。

像这种针对读高并发、读缓存架构的项目,一般来说写请求是非常少的,每秒的 QPS 能到几百就不错了。

实际粗略测算一下,如果一秒有 500 的写操作,分成 5 个时间片,每 200ms 就 100 个写操作,放到 20 个内存队列中,每个内存队列,可能就积压 5 个写操作。

每个写操作性能测试后,一般是在 20ms 左右就完成,那么针对每个内存队列的数据的读请求,也就最多 hang 一会儿,200ms 以内肯定能返回了。

经过刚才简单的测算,我们知道,单机支撑的写 QPS 在几百是没问题的,如果写 QPS 扩大了 10 倍,那么就扩容机器,扩容 10 倍的机器,每个机器 20 个队列。

2、读请求并发量过高

这里还必须做好压力测试,确保恰巧碰上上述情况的时候,还有一个风险,就是突然间大量读请求会在几十毫秒的延时 hang 在服务上,看服务能不能扛的住,需要多少机器才能扛住最大的极限情况的峰值。

但是因为并不是所有的数据都在同一时间更新,缓存也不会同一时间失效,所以每次可能也就是少数数据的缓存失效了,然后那些数据对应的读请求过来,并发量应该也不会特别大。

3、多服务实例部署的请求路由

可能这个服务部署了多个实例,那么必须保证说,执行数据更新操作,以及执行缓存更新操作的请求,都通过 Nginx 服务器路由到相同的服务实例上。

比如说,对同一个商品的读写请求,全部路由到同一台机器上。可以自己去做服务间的按照某个请求参数的 hash 路由,也可以用 Nginx 的 hash 路由功能等等。

4、热点商品的路由问题,导致请求的倾斜

万一某个商品的读写请求特别高,全部打到相同的机器的相同的队列里面去了,可能会造成某台机器的压力过大。

因为只有在商品数据更新的时候才会清空缓存,然后才会导致读写并发,所以要根据业务系统去看,如果更新频率不是太高的话,这个问题的影响并不是特别大,但是可能某些机器的负载会高一些。


作者:石杉的架构笔记
链接:http://www.imooc.com/article/288559?block_id=tuijian_wz
来源:慕课网
本文原创发布于慕课网 ,转载请注明出处,谢谢合作

DDOS攻击

    分布式拒绝服务攻击采取的攻击手段就是分布式的,在攻击的模式改变了传统的点对点的攻击模式,使攻击方式出现了没有规律的情况,而且在进行攻击的时候,通常使用的也是常见的协议和服务,这样只是从协议和服务的类型上是很难对攻击进行区分的。在进行攻击的时候,攻击数据包都是经过伪装的,在源IP 地址上也是进行伪造的,这样就很难对攻击进行地址的确定,在查找方面也是很难的。这样就导致了分布式拒绝服务攻击在检验方法上是很难做到的。

防御手段:

总体来说,从下面几个方面考虑:

硬件
单个主机
整个服务器系统
 

硬件:

1. 增加带宽

带宽直接决定了承受攻击的能力,增加带宽硬防护是理论最优解,只要带宽大于攻击流量就不怕了,但成本非常高。

 

2、提升硬件配置

在有网络带宽保证的前提下,尽量提升CPU、内存、硬盘、网卡、路由器、交换机等硬件设施的配置,选用知名度高、 口碑好的产品。

 

3、 硬件防火墙

将服务器放到具有DDoS硬件防火墙的机房。专业级防火墙通常具有对异常流量的清洗过滤功能,可对抗SYN/ACK攻击、TCP全连接攻击、刷脚本攻击等等流量型DDoS攻击

 

单个主机:

1、及时修复系统漏洞,升级安全补丁。

2、关闭不必要的服务和端口,减少不必要的系统加载项及自启动项,尽可能减少服务器中执行较少的进程,更改工作模式

3、iptables

4、严格控制账户权限,禁止root登录,密码登录,修改常用服务的默认端口

 

整个服务器系统:

1. 负载均衡

使用负载均衡将请求被均衡分配到各个服务器上,减少单个服务器的负担。


2、CDN
CDN是构建在网络之上的内容分发网络,依靠部署在各地的边缘服务器,通过中心平台的分发、调度等功能模块,使用户就近获取所需内容,降低网络拥塞,提高用户访问响应速度和命中率,因此CDN加速也用到了负载均衡技术。相比高防硬件防火墙不可能扛下无限流量的限制,CDN则更加理智,多节点分担渗透流量,目前大部分的CDN节点都有200G 的流量防护功能,再加上硬防的防护,可以说能应付目绝大多数的DDoS攻击了。

3. 分布式集群防御

分布式集群防御的特点是在每个节点服务器配置多个IP地址,并且每个节点能承受不低于10G的DDoS攻击,如一个节点受攻击无法提供服务,系统将会根据优先级设置自动切换另一个节点,并将攻击者的数据包全部返回发送点,使攻击源成为瘫痪状态,从更为深度的安全防护角度去影响企业的安全执行决策。

 4、安全域划分

 

 

 

 

 

 

 

先说明一下,这不是我自己写的,是我在搜面经答案时顺便记录下来的,链接实在太多了,就不贴了。谢谢那些写博客的大佬

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值