Java面试指南

JVM系列

1. jvm 体系结构

classfiles ----- classLoader (类加载器系统) --- 运行时数据区 ------执行引擎 ----本地方法接口 -----本地方法库

classLoader : 类加载其主要作用 加载class 文件 至于能否运行有 执行引擎决定 。

  • Bootstreap classloader (启动类加载器) : c++ 加载 java_home /jre/lib/rt.jar (里面包含所有产够用类库)

  • Extension classLoader (扩展类加载器) : java java_home /jre /etc/*.jar

  • Application classLoader (应用类扩展器) java 加载classpath(用户自定义类)

  • 用户自定义类加载器( ClassLoader 的子类 是一个抽象类)

    双亲委派机制 + 缓存

类加载顺序:https://segmentfault.com/a/1190000012527652

加载 ----连接(验证 ,准备 , 解析)---初始化 ---使用 ---卸载

准备阶段进行static 初始化

类的三种加载方式:

  • JVM 加载
  • class.forname()
  • classloader.getclass()

运行时数据区分类:

主要结构分为: 堆内存 , 栈内存(本地方法栈 ,java栈) ,方法区 。

其次还包含:程序计数器:

  • java 堆(Heap)( 包含 新生代 和老年代) 线程共享区域

    • jvm 管理的内存中最大的一块 , 虚拟机启动创建 , 唯一的目的:存放对象实例 , 为每一个对象分配内存空间.(new 的 对象)

    • java 堆是垃圾收集器只要管理的区域, 从内存回收的角度分析,采用的是分代收集算法,所以可以新分为:

      ​ 新生代 : Eden 空间 , Form Survivor(幸存者) , To survivor 按比例 81

      ​ 老年代:

      对空间是物理上不连续 ,逻辑上连续的磁盘空间,(内存大小可通过 -Xmx -Xms控制). 在堆内存中没有内存完成对像的实例分配 , 平且也不能横向扩展内存的时候会抛出 : OOM (Out of Memory Error) 异常.

  • 方法区 :(Method Area) 线程共享区域 (永久代)

    • 和堆一样 , 线程共享区域 他存储的是 类信息 , 常量 , 静态变量 , 以及代码等片段. 当方法区无法满足内存分配的时候,会抛出 (Out of Memory 异常.)
  • 程序计数器: (线程私有)

    一块比较小的内存空间 , 作用 :当前线程执行字节码的指示器 , 字节码解释器工作就是通过改变计数器的值来选取下一条需要执行的字节码指令, 分支 , 循环 , 跳转 , 异常处理,线程恢复等功能.(执行本地方法的时候计数器的值为 Undefinded)

    唯一一个jvm规范中没有标记 OOM 异常的区域.

  • JVM 栈:(线程私有)(特点 : FILO)

    ​ 生命周期与线程相同, 与线程的生命周期一致 , 方法执行的内存模型, 每个方法在执行的时候都会创建一个栈帧 , 用来存放 局部变量, 操作栈 , 动态链接 , 方法出口 ,等信息. 方法执行的过程 ,对应的是栈帧从压栈到弹栈的过程.

    ​ JVm 规范: 两种异常: 当线程所申请的栈的深度大于jvm索允许的深度,报出 sof (stack over flow) ,

    ​ 如果JVM可动态扩展 , 扩展是无法申请到足够空间 ,抛出OOM 异常

  • 本地方法栈:

    • 执行native 方法 , 也会抛出两种异常.

    判断哪里出的OOM异常

    • 内存溢出的两种原因:
      • 内存泄漏:本该被回收的对象,但是拥有强引用链导致不能被回收.
      • 内存溢出: 内存中对象过多 ,导致内存不足
  1. 是否应用中的类和引用变量过多使用了static修饰。
  2. 是否应用中使用了大量的递归或无线递归(递归中用到了大量的新建的对象)
  3. 是否应用中使用了大量循环或死循环(循环中用到了大量的新建对象)
  4. 检查应用中是否使用了像数据库查询所有记录的方法。如果数据量超过十万多条,就可能会造成内存溢出。相对的,可以采用高分页查询。
  5. 检查是否有数组,list,map中存放的是对象的引用而不是对象。因为这些引用会让对应的对象不能被GC回收而大量存储于内存中。
  6. 检查是否使用了非字面量字符串进行+的操作。因为String类的内容是不可变的,每次运行+就会产生新的对象。如果过多会造成内存溢出的情况。

而对于栈溢出的原因则可能有一下可能:

  1. 是否有递归调用

  2. 是否有大量循环或死循环

  3. 全局变量是否过多

  4. 数组,list,map数据是否过大

  5. 使用DDMS工具进行查找大概出现栈溢出的位置。

    快速查找OOM内存溢出原因分析:https://blog.csdn.net/alli0968/article/details/52460008

    1. DUMP文件分析:-XX:+HeapDumpOnOutOfMemoryError 让虚拟机产生异常时生成的dump文件

    受用Jamp + MAT 进行分析:

    1. 登陆linux 服务器 获取tomcat pid ps-ef|grep java
    2. 利用jmap 初步分析内存映射 ,command : jmap - histo :live 3514 | head -7
    3. 获取离线文件: jmap -dump :live ,foramt -b ,file = heap.hprof 3514
    4. 使用MAT (memory anal'yzer tool) 分析dump文件
    5. 使用Histogram 窗口分析 类似于 jmap -histo
    6. shallow heap 对象自身占用内存 retained heap = shallow heap + 引用占用内存
    7. 打开源码: open source file
  1. java VisualVM jvisualvm.exe 在JDK 的 bin 目录下。 http://www.cnblogs.com/0616--ataozhijia/p/4136312.html

    java 常用的5个命令工具:

    1. javainfo :

    2. jps : Java 运行id

    3. jstat : 一个强大的Java监视命令详细查看堆内各个部分的使用量,以及加载类的数量。使用时,需加上查看进程的进程id ,和所选参数。以下详细介绍各个参数的意义。

    4. jmap : 查看java 内存结构。

      推荐使用 jdk自带的两个工具: java VisualVM java console

堆内存分析:堆内存大小 : 新生代 + 老年代

  • 新生代 :对象创建 --存活 --销毁的地方(8:1:1)

    • 伊甸园区(edan )创建对象的地方 所有的对象都是在这块被new 出来的
    • from (幸存者0区) 当edan 区用完时发生 (minor gc )还在创建对象 就会 将原来的对象放到这块
    • to (幸存者1区) 0区 也满的时候发生(minor gc )然后放到这块
  • 老年代 : 发生full Gc 的地方 三个去都满的时候放到这块

    • 1.8 元空间(方法区 永久代) 逻辑上属于 实际上不属于

    常用参数:

    -Xms :堆内存最小空间 默认 电脑内存的 1/64

    -Xmx : 堆内存最大空间 :默认电脑内存的 1/ 4

    -Xmn : 新生代的空间设置

    -XX:PermSize :元空间大小

    -XX:maxPermSize : 元空间最大大小

数据结构:

1.什么是队列 , 栈 , 链表。

队列: 特点: FIFO (先进先出) 每次新插入的数据都插入到队尾 , 遍历的时候从头开始遍历,实现先进先出。

栈: 特点: FILO(先进后出)每次插入的数据会放到栈顶,再添加元素的时候会将栈顶的元素往下压,从栈顶获取,所以实现先进后出。

链表: (单链表和双链表)每一个对象(Entry)

单链表:存放一个下一个对象的指针 加 数据对象

双链表:都存放一个Node(里面存放 前一个索引 , 后一个索引 , 以及数据区域。

2.栈和队列的相同和不同之处?

相同点: 都是线性表 ,都从头插入或者删除元素。

不同点: 读取数据不同,

​ 队列 特点: FIFO (先进先出)

​ 栈 特点: FILO(先进后出)

3.栈通常采用的两种存储结构??? (存储结构概念不清楚???)

考核的是栈的实现

​ 存储结构主要是指存储方式,包含线性存储与链式存储,它是从计算机存储的角度去考虑。

逻辑结构指的是数据之间的关系,有线性关系和链式关系等,主要是从人为定义角度去考虑。

数组是一种被人们定义为线性关系的表,至于其存储结构,可以用线性存储也可以是链式存储去存储。

(1) ArrayList 实现 线性存储 , 以数组实现

(2) LinkedList 实现, 链式存储 ,以链表实现。

4.两个栈实现队列 ,两个队列实现栈????

  1. 思路: s1 栈用来存储 数据 形成先进后出 ,然后放入到 s2 栈中,形成 queue 的模式。

    模式思路容易理解 但是挪来挪去 有问题。

算法:(缺失 ,待补充)

计算机网络:

1.tcp 和 udp 区别:

​ 熟悉一点sokect api ,写过一个基于tcp 的 局域网对话 具体没有深入了解过。

tcp : 面向链接, (会出现三次握手 和 四次挥手) 对系统要求资源较高,不会出现丢包的情况 ,但是相对于Udp 不安全 (容易导致dos 攻击) 例如 打电话。出现dos 攻击 ddos 攻击

udp : 面向数据包模式 , 网络数据大多为短消息 ,响应速度要求高 。例如 发送qq 消息 。

扩展:

(1)第一次握手: Client将标志位SYN置为1,随机产生一个值seq=J,并将该数据包发送给Server,Client进入SYN_SENT状态,等待Server确认。 (2)第二次握手: Server收到数据包后由标志位SYN=1知道Client请求建立连接,Server将标志位SYN和ACK都置为1,ack=J+1,随机产生一个值seq=K,并将该数据包发送给Client以确认连接请求,Server进入SYN_RCVD状态。 (3)第三次握手: Client收到确认后,检查ack是否为J+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=K+1,并将该数据包发送给Server,Server检查ack是否为K+1,ACK是否为1,如果正确则连接建立成功,Client和Server进入ESTABLISHED状态,完成三次握手,随后Client与Server之间可以开始传输数据了。

a. tcp : 三次握手 和 四次挥手(握手三次的原因是 ack 和 syn 在一个包中)

1.第一次握手: client 发送一个 syn = 1 的标志位 , 随机产生一个值 seq = J ,发送到server端 , 然后处于 SYN_Sent 状态。

  1. 第二次握手: Server 收到一个syn = 1的标志位 ,知道client 请求建立链接, server 会将 syn =1 and ACk =J+1 ,会将seq = k ,并将这些返回给 client 进入 server_recd状态。
  2. 第三次握手 , cliect 收到以后会检测 ack =j+1 ,ack是否为1 如果正确,会将这些在发送给server 确认一遍。

b. 4 次挥手: (挥手四次的原因是 ack 和 syn 不在一个包中)

(1). 第一次挥手 , client 发送一个FIN , ,用来关闭 client 和server之间的发送数据链接, client 处于 client_wait = 1 的状态。 (申请关闭 ,关闭发送流 client --server 之前的通道)

(2). 第二次挥手 , server 收到Fin 以后, 发送一个ACK 到Client , 确认序号为收到序号+1 , server 进入server_wait 状态。(确认应答)

(3). 第三次挥手, server 发送一个FIN 标志 ,用来关闭 server---client 之间的通信。Server进入LAST_ACK状态。

(4).第四次挥手 ,client收到一个FIN 标志, 发送一个Time_Wait 状态, 紧接着发送一个ACK 到Server ,servler 状态改为closed 完成四次挥手。

2.输入一个URL 到的页面的经历了下面过程(HTTP):

1.DNS 解析:(域名解析)

​ 本地域名服务器中查询IP地址(c:盘下面的hosts文件),如果没有找到的情况下,本地域名服务器会向根域名服务器发送一个请求,如果根域名服务器也不存在该域名时,本地域名会向com顶级域名服务器发送一个请求,依次类推下去 。没找到 404 页面

​ com -> google.com -> www.google.com。

2.TCP 链接:

​ Http 协议是基于TCP作为其传输协议的。

​ HTTP 报文是包在TCP报文中发送的 , (HTTP报文)属于明文,被截取存在信息泄露安全。

​ HTTPS 协议是 HTTP + SSL(TLS) 在进入TCP报文之前, 会进行加密。

3.发送HTTP 请求:

​ HTTP 请求包含三个部分 : 请求行 ,请求头 ,请求体。

请求行

格式如下:
Method Request-URL HTTP-Version CRLF

常用的方法有: GET, POST, PUT, DELETE, OPTIONS, HEAD。

请求报头

请求报头允许客户端向服务器传递请求的附加信息和客户端自身的信息。
PS: 客户端不一定特指浏览器,有时候也可使用Linux下的CURL命令以及HTTP客户端测试工具等。
常见的请求报头有: Accept, Accept-Charset, Accept-Encoding, Accept-Language, Content-Type, Authorization, Cookie, User-Agent等。

请求正文(需要发送的参数,以及文本内容 常用方法中post方法存在 getm没有)

当使用POST, PUT等方法时,通常需要客户端向服务器传递数据。这些数据就储存在请求正文中。在请求包头中有一些与请求正文相关的信息,例如: 现在的Web应用通常采用Rest架构,请求的数据格式一般为json。这时就需要设置Content-Type: application/json。

4.服务器处理请求和返回HTTP 请求

​ 后端工程师:TCP连接进行处理,对HTTP协议进行解析,并按照报文格式进一步封装成HTTP Request对象,供上层使用 。(常用web服务器: tomcat WebSphere(IBM)) (JBOSS ) (WebLogic)

响应包括三个部分:

​ 响应头:

​ 状态码:

​ 1XX : 指请求已接受, 在处理。

​ 2XX: 处理成功

​ 3XX:302 重定向 , 要完成请求 必须进行更一步操作。

​ 301 永久性重定向(301表示旧地址A的资源已经被永久地移除了 )

​ 302 暂时性重定向 (302保存旧地址)

​ 4XX : 客户端错误, 404 资源未找到 , 405 请求方式不匹配。400 请求参数异常

​ 5XX: 服务端异常 500

​ 响应体:一般是HTML 信息

5.浏览器解析和渲染页

6.链接结束

3.get 和 post 区别:

get:

​ 没有请求体 ,参数拼装在地址栏后面 所以有大小限制(2048个字符)所以不建议提交密码等参数(参数不安全)只允许 ASCII 字符。

​ 会保存浏览历史记录 , 后退刷新不会重复提交(刷新无害)

post :

​ 有请求体 ,可用于文件上传 , 参数放到请求体中 ,按理没有大小限制。

不会保存到历史记录中。 后退刷新会重复提交,(刷新有害)

4.forword 和redirect 的区别:

forword 请求转发 :request 1 次请求 服务器内部跳转 ,效率高 , 只能访问同WEB容器下的URL ,地址栏不会跳转 ,

redirect 重定向: response : 2 次请求 页面地址栏跳转 ,客户端需要重定向,效率低 ,相当于重新输入地址。

5.HTTPS 和 HTTP/2:

HTTPS : HTTP +SSL(TLS)

https可以称为http安全版,主要是http下增加了SSL(安全套接层)或者TSL(传输层安全),在SSL或TSL在传输层对数据进行了加密处理。

HTTPS特点 :

  1. 保证传输信息安全 ,

    1. 需要申请证书

    2. 防止运营商截获

    3. 介于增加安全加密环节, 传续效率变慢

      SPdy的特点

    • 1、可以降低延迟
    • 2、可以设置请求优先级
    • 3、header压缩
    • 4、基于https保障传输安全
    • 5、支持server push

HTTP2:(基于spdy演化版本)

​ 特点:

  • 新的二进制格式

  • 多路复用

    • 客户端与服务端在某个域名的TCP通道已建立 (一个TCP 链接可以发送多个请求,减少创建链接的消耗)
    • 新创建的客户端请求通过已连接的TCP通道进行请求发送与响应处理
  • header压缩

    • HTTP2为了解决HTTP1.x中头信息过大导致效率低下的问题,提出的解决方案便是压缩头部信息。具体的压缩方式,则引入了HPACK。

      HPACK压缩算法是专门为HTTP2头部压缩服务的。为了达到压缩头部信息的目的,HPACK将头部字段缓存为索引,通过索引ID代表头部字段。客户端与服务端维护索引表,通信过程中尽可能采用索引进行通信,收到索引后查询索引表,才能解析出真正的头部信息。

      HPACK索引表划分为动态索引表与静态索引表,动态索引表是HTTP2协议通信过程中两端动态维护的索引表,而静态索引表是硬编码进协议中的索引表。

      作为分析HPACK压缩头信息的基础,需要先介绍HPACK对索引以及头部字符串的表示方式。

  • 支持server push

    • HTTP/2引入了server push,它允许服务端推送资源给浏览器,在浏览器明确地请求之前。一个服务器经常知道一个页面需要很多附加资源,在它响应浏览器第一个请求的时候,可以开始推送这些资源
### 线程

#### 1.线程与进程的区别

   进程 : 类似一个应用程序 , 可以使用 任务管理器查看PID (进程id) 进行结束进程  (QQ)

   线程: cpu 运行和调度应用程序的基本单位  , 一个进程包含多个线程。(QQ 中的 打字聊天 和 语音通话等)

#### 2.并发与并行

举例:

  你吃饭的时候来了一个电话 ,你吃完饭 才去接电话   说明: 你既不支持并发 也不支持并行。

 你吃饭的时候来了一个电话 ,你放下饭去接电话 说明 你支持并发 同时干多件事。

你吃饭的时候来了一个电话, 你一边吃饭 一边接电话 说明你可以支持并行。

#### 3.线程安全和synchronized的概念

线程安全 就是多线程 在访问同一个类的 变量或者方法时 , 这个类始终表现出正确的行为, 称为线程安全。

#### 4.创建线程的四种方式:

1) extends Thread

​          优点:代码简单, 逻辑清楚。

​      缺点:不利于扩展。

2) implements Runnable ; 

​     优点 : 利于扩展 

3):implements  Callable ; (跟 Runnable 的区别  有返回值 , 可以抛出异常)

(future'Task 类 里包装 callbale 对象 , )调用的是call() 方法 ,future'Task .get()  获取返回值。

4)   : ThreadLocal 线程池。



##### 线程间的通信方式

 (1).同步:

​     多个线程通过synchornized 关键字进行通讯 , 这种方式本质是“共享内存” , 多线程之间 谁拿到锁 谁可以执行。

(2).while (轮询方式)

​     通过不断改变进入线程中的方式(参数) ,来让其他线程判断是否可以进入。

(3)wait / notify 机制:

 当前线程在执行期间调用 wait()  放弃cup的执行权和释放锁 。其他线程进入, 可使用notify 随机唤醒线程池的一个线程。

(4)管道通信:

(5) 网络中socket套接字也可以用于线程间通信。 

##### ThreadLocal 的原理分析 以及**ThreadLocal为什么会出现OOM,出现的深层次原理** 

ThreadLocal 并不是一个Thread , 而是Thread 的一个局部变量

作用: 提供线程内的局部变量 , 为每个线程提供副本, 所以每个线程都可以都可以改变自己的副本,不影响其他线程.

ThreadLocal公有的方法就四个,分别为:**get、set、remove、intiValue**:

关系:

Thread   中存在一个局部变量(ThreadLocalMap ) ThreadLocal.ThreadLocalMap threadLocals = null;

ThreadLocal  中有一个静态内部类: ThreadLocalMap 

ThreadLocalMap  的key 为ThreaLocal

一个Thead 对应一个ThreadLocalMap ,每个ThreadlocalMap 中有存放多个ThreadLocal 对象.

##### ThradLocal内存回收:

- ThradLocal 层面回收 线程死亡: 线程死亡的话 ,存放在线程中的局部变量就会被回收.ThreadLocalMap会自动回收

- ThreadLocalMap 层面的内存回收:当线程存活时间过长时 , 存放的局部变量就越多, ,导致Map 会越大 , 垃圾回收器就会回收那些 该本清理的对象 , ThreadLocalMap 中的Entry 的key 是弱引用包装类型 , (默认初始化16个对象) ,当到达10个的时候 ,就会进行回收.

  ##### ThreadLocal可能引起的OOM内存溢出问题简要分析

  ThreadLocalMap中使用ThreadLocal 的弱引用作为key ,  如果没有外部强引用引用ThreadLocal 的时候,在gc的时候势必会回收 ThreadLocal 对象 ,  这样一来 ThreadLocalMap 中的会出key 为null的entry ,  这样就永远访问不到这些 value值. 如果当前线程迟迟不退出的话, 就会形成一个强引用链:

  Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value  ,导致内存泄漏.

  ThreadLocalMap的设计中已经考虑到这种情况,也加上了一些防护措施:**在ThreadLocal的get(),set(),remove()的时候都会清除线程ThreadLocalMap里所有key为null的value**。 

  可能出现的问题的原因:

  (1)使用static的ThreadLocal,延长了ThreadLocal的生命周期,可能导致内存泄漏。 (2)分配使用了ThreadLocal又不再调用get(),set(),remove()方法,那么就会导致内存泄漏,因为这块内存一直存在。 

   ###### 为什么使用弱引用 ,不适用强引用

  key 为强引用的时候: ThreadLocal 对象被回收 , 但是ThreadLocalMap 持有ThreadLocal 对想的强引用,如果不手动删除, 就不是被回收 ,导致内存泄漏.

  key为弱引用 ,  ThreadLocal 对象被回收 , 但是ThreadLocalMap 持有ThreadLocal 对想的弱引用,会在内存快溢出的时候回收弱引用, `value`在下一次`ThreadLocalMap`调用`set、get、remove`的时候会被清除。 .



  ThreadLocal内存泄漏的根源是:**由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用。** 

  最佳方法 可以使用:

  每次使用完ThreadLocal,都调用它的remove()方法,清除数据。 





##### 总结:

1)ThreadLocal只是操作Thread中的ThreadLocalMap对象的集合; 

2)ThreadLocalMap变量属于线程的内部属性,不同的线程拥有完全不同的ThreadLocalMap变量; 

3)线程中的ThreadLocalMap变量的值是在ThreadLocal对象进行set或者get操作时创建的; 

4)使用当前线程的ThreadLocalMap的关键在于使用当前的ThreadLocal的实例作为key来存储value值; 

5) ThreadLocal模式至少从两个方面完成了数据访问隔离,即纵向隔离(线程与线程之间的ThreadLocalMap不同)和横向隔离(不同的ThreadLocal实例之间的互相隔离); 

6)一个线程中的所有的局部变量其实存储在该线程自己的同一个map属性中; 

7)线程死亡时,线程局部变量会自动回收内存; ThreadLocalMap会自动回收

8)线程局部变量时通过一个 Entry (代表一个ThreadLocal)保存在map中,该Entry 的key是一个 WeakReference包装的ThreadLocal, value为线程局部变量,key 到 value 的映射是通过:`ThreadLocal.threadLocalHashCode & (INITIAL_CAPACITY - 1)` 来完成的; 

9)当线程拥有的局部变量超过了容量的2/3(没有扩大容量时是10个),会涉及到ThreadLocalMap中Entry的回收;

对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响

```java
public class Thread implements Runnable {
    /* Make sure registerNatives is the first thing  does. */
    private static native void registerNatives();
    static {
        registerNatives();
    }

    //省略其他代码

    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

    /*
     * InheritableThreadLocal values pertaining to this thread. This map is
     * maintained by the InheritableThreadLocal class.
     */
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
}
```

#### 线程池

#####     线程的生命周期 ,以及状态的转移:

1. 新建状态:

   ​    new Thread()  创建一个线程的时候 处于新建状态.

2. 就绪状态

      new Thread().start() 方法的时候,获取cpu的执行资格,等待cpu 执行叫做 就绪状态.    

3. 运行状态

     获取了cpu的执行资格以后,  (获取了cpu的时间片) ,执行代码    

4. 阻塞状态

   ​    线程放弃cpu的执行权  ,让出cpu的执行时间片段.暂时停止运行 , 知道线程进入可运行状态.

   阻塞分为三种:

   ​      (1):等待阻塞: 运行的线程执行了 wait()方法 ,  jvm会把该线程放入到 等待队列中.

   ​      (2).同步阻塞: 运行的线程在获取对象的锁时,已有线程获取到了该对象的锁 ,JVM会将该线程放到锁池中.

   ​      (3). 其他阻塞: 运行的线程在运行的时候,执行到了sleep()或者join()方法 ,一级页面输入,请求连接等或者I/O请求的时候, 需要等到这些线程处理完以后,才会进行到可运行状态.

5. 死亡状态

   ​    线程执行完毕  main()执行完毕,虚拟机退出 ,线程进入死亡状态 不能复生.

   提到: 线程池 ,锁池 ,等待池.

   #### 同步锁机制:

   ##### 1.什么是线程安全 , 如何保证线程安全:

   线程安全指的是 再多线程操作一个类的属性或者方法的时候,执行的结果总是正确的. 

   多线程的情况下访问一段代码的时候,不会产生不确定的结果, 执行的结果数据是一致的

   ###### 确保线程安全

        1. 不跨线程共享资源 , 线程共享的变量变为方法的局部变量
       2.  使变量的状态不可变 + final 修饰.(便为常量)
       3. 在任何操作该属性的时候使用同步锁: synchornized 或者使用同步代码块.
       4. 每个共享的可变变量使用一个唯一的可确定的锁保护  ---lock锁

   ##### 2.产生死锁的四个条件(互斥 , 请求于保持 , 不剥夺 , 循环等待)

   (1)互斥条件:进程对所分配到的资源不允许其他进程进行访问,若其他进程访问该资源,只能等待,直至占有该资源的进程使用完成后释放该资源

   (2)请求和保持条件:进程获得一定的资源之后,又对其他资源发出请求,但是该资源可能被其他进程占有,此事请求阻塞,但又对自己获得的资源保持不放

   (3)不可剥夺条件:是指进程已获得的资源,在未完成使用之前,不可被剥夺,只能在使用完后自己释放

   (4)环路等待条件:是指进程发生死锁后,必然存在一个进程--资源之间的环形链



   资源A,B; 进程C,D

   资源A,B都是不可剥夺资源:一个进程申请了之后,不能强制收回,只能进程结束之后自动释放。内存就是可剥夺资源

   进程C申请了资源A,进程D申请了资源B。

   接下来C的操作用到资源B,D的资源用到资源A。但是C,D都得不到接下来的资源,那么就引发了死锁。

    ##### 如何查看死锁

   https://blog.csdn.net/u014039577/article/details/52351626



   ##### volatile 实现原理 (禁止指令重排和刷新内存)

   volatile可以被理解为轻量级的synchornized,他保证变量的可见性,多线程中一个线程对变量进行修改,另一个同样共享变量的线程可以读到这个被修改后的值.

   java官方给出的定义:java允许 多线程共同访问同一个变量, 为了确保一个变量的一致和更新,可以使用排他锁获取该变量.  如果使用volatile 关键字修饰变量的话, java线程内存模式保证所有的线程看到的变量是一定的.

   如果使用volatile修饰变量 , jvm虚拟机在操作改变量的时候 , 会向处理器发送一条带有Lock前缀的指令.

   ##### 为什么使用volatile

   Volatile变量修饰符如果使用**恰当**的话,它比synchronized的**使用和执行成本会更低**,因为它不会引起线程上下文的切换和调度。

   ##### 并发中的三个概念 :

   原子性:

   ​     一个操作或者多个操作要么全部执行,并且执行不会被其他因素打断,要么都不执行.

   可见性:

   ​    可见性指多线程对一个共享变量进行进行修改,其他线程用到是修改后的值.

   有序性:

   ​       程序执行的顺序,按照代码的先后顺序.



   volatile 关键字 定义的变量可以保证:可见性和有序性, 不能保证原子性,所以使用是应该注意操作的变量保证原子性,否则会造成多线程安全问题.

synchornized 实现原理 以及 Lock的区别

​ synchornized 可以确保它修饰的方法 / 代码块 ,在运行期间,只有一个方法(一个线程执行,其他线程等待)可以进入临界区 ,同时 它可以保证可变变量在执行期间 内存的可见性.

​ synchornized 可以修饰方法和代码块 分为:

同步代码块: 锁可以为任意对象

​ 使用javap 反编译查看class文件信息:

​ 同步代码块中, monitorenter 指令(监听器进入)插入在代码开始之前 , monitorexit 插入在代码结束位置,jvm确保每一个monitorenter 有一个与之对用的monitorexit ,任何对象都有一个对象监听器monitor与之对应,且当一个monitor 被持有后, 将处于锁定状态,也就是获取到了锁. 当代码执行到monitorenter的指令时候, 会尝试获取对象的 monitor 的所有权, 或尝试获取对象锁,这也就确保了一个锁在释放前 只有一个线程可以执行(获取到监听器的所有权)

static方法 : class 对象

普通方法: 当前对象实例this

同步方法则被翻译成普通方法调用指令执行,而是在class 文件中讲该方法的access_flags 字段表示为1 说明该方法在执行的时候jvm会将内部对象kclass作为对象锁.

lock锁 与 synchornized 的区别


package com.brickworkers;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class LockTest {
    private Lock lock = new ReentrantLock();

    //需要参与同步的方法
    private void method(Thread thread){
        lock.lock();
        try {
            System.out.println("线程名"+thread.getName() + "获得了锁");
        }catch(Exception e){
            e.printStackTrace();
        } finally {
            System.out.println("线程名"+thread.getName() + "释放了锁");
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        LockTest lockTest = new LockTest();

        //线程1
        Thread t1 = new Thread(new Runnable() {

            @Override
            public void run() {
                lockTest.method(Thread.currentThread());
            }
        }, "t1");

        Thread t2 = new Thread(new Runnable() {

            @Override
            public void run() {
                lockTest.method(Thread.currentThread());
            }
        }, "t2");

        t1.start();
        t2.start();
    }
}
//执行情况:线程名t1获得了锁
//         线程名t1释放了锁
//         线程名t2获得了锁
//         线程名t2释放了锁
  • lock():获取锁,如果锁被暂用则一直等待
  • unlock():释放锁
  • tryLock(): 注意返回类型是boolean,如果获取锁的时候锁被占用就返回false,否则返回true
  • tryLock(long time, TimeUnit unit):比起tryLock()就是给了一个时间期限,保证等待参数时间
  • lockInterruptibly():用该锁的获得方式,如果线程在获取锁的阶段进入了等待,那么可以中断此线程,先去做别的事
类别 synchronized Lock
存在层次 Java的关键字,在jvm层面上 是一个接口( util的concurrent包下的接口,拥有自己的实现类 ) ReentrantLock();
锁的释放 1、以获取锁的线程执行完同步代码,释放锁 2、线程执行发生异常,jvm会让线程释放锁 在finally中需要释放锁,不然容易造成线程死锁
锁的获取 假设A线程获得锁,B线程等待。如果A线程阻塞,B线程会一直等待 分情况而定,Lock有多个锁获取的方式,具体下面会说道,大致就是可以尝试获得锁,线程可以不用一直等待
锁状态 无法判断 可以判断
锁类型 可重入 不可中断 非公平 可重入 可判断 可公平(两者皆可)
性能 适用少量同步 大量同步
锁机制 synchronized是一种悲观锁 (每次访问都会上锁) Lock底层是CAS乐观锁。 (每次访问的时候不会上锁,采用 CAS compare and swap ) 算法进行判断是否有同步线程修改了可变变量 ,如果修改则修改失败 可重试 , 没有修改 则进行修改.
  • 可重入锁:在执行对象中所有同步方法不用再次获得锁

  • 可中断锁:在等待获取锁过程中可中断

  • 公平锁: 按等待获取锁的线程的等待时间进行获取,等待时间长的具有优先获取锁权利
  • 读写锁:对资源读取和写入的时候拆分为2部分处理,读的时候可以多线程一起读,写的时候必须同步地写

框架部分

1. struts2的流程:

(1)用户发来一个请求 ,请求会被Tomcat web 服务器收到 , 他会根据请求的URL 找到具体的web工程,获取下面的web.xml配置文件 .

(2) 根据web.xml 配置的配置的strust2 过滤器来进行处理,(struts2prapareandexecutefilter) ,根据Filter

找到Filter'Dispatcher(struts2)的调度中心 .

(3)然后获取Filter'Dispatcher(struts2)的实例 , 回掉 doFilter方法 .(ps:web.xml 中还有其它过滤器的时候 ,Filter'Dispatcher )应放到最后.

(4)这是Filter'Dispatcher会将请求转发给ActionMapper , 由ActionMapper确认是否需要 struts2处理该请求,如果需要会将处理请求返回给Filter'Dispatcher .Filter'Dispatcher会停止过滤连以后的部分, 这也就是为啥Filter'Dispatcher需要配置在最后的原因.

(5).Filter'Dispatcher会创建一个ActionProxy(代理对象 是action 和xwork之间的中间层) 代理Action运行过程.

(6).ActionProxy 需要向ConfigurationManager(读取配置文件 struts.xml 文件) 获取action的信息 .

(7). ActionProxy 创建 一个ActioInvocation对像,作用是代替ActionProxy运行action的整个流程.

(8).调用目标方法之前先调用拦截器链(默认20个拦截器), intecptor.invoke() 方法之前的代码是前拦截器,之后的代码是后拦截器. intecptor.invoke() 调用拦截器链, 没有的话就调用目标方法.

(9) 前拦截器处理完成以后 , 然后执行action 返回一个Result(相当于逻辑视图)跟后根据struts.xml配置

请求或者转发到目标视图上,得到一个Template(模板),然后再调用,后拦截器,得到一个相应对象,将试图结果返回到用户.

2.struts2值栈

值栈是对应每一个请求的数据存储中心 , (作用存储数据.) 客ts户端发起一次请求会创建一个Action实例,(struts2的Action是多例的) ,同时也会创建一个值栈. valuestack 是一个接口 , ognlvaluestack 实现了它.

valuestack 包含两个部分 :

context : Map结构 ,ognlContext 上下文对象,存储一些引用 ,parameters ,request,session ,application ...

root : compoundroot 继承arrayList, 实现压栈和出栈的功能, CompoundRoot作为OgnlContext的Root对象,并且在CompoundRoot中action实例位于栈顶, 当读取achon的属性值时会先从栈顶对象中查找对应的属性,如果找不到则继续查找栈中的其它对象, 如果未找到则到ContextMap中去查找,未找到,则返回null。

  • struts2中的核心拦截器StrutsprepareAndExcutorFilter中的doFilter 方法中 ,创建ActionContext;分为四个部分:

Java面试指南_第1张图片

(1) : 创建值栈

(2) :给值栈设置上下文对象 , (加入各个域对象)

(3):创建ActionContext, 将上下文对象当作构造器参数传入创建对象

(4):ActionContext.setContext 意思是 ActionContext 中保存一个ContextMap(valuestack的上下文引用)

总结: 一次请求创建一个Action 实例 和一个值栈; 值栈包含两个部分:root ,contextmap .

root中存放contextmap 引用

context 存放 root 和 valuestack引用.

ActionContext 存放一个contextmap 引用

ActionContext绑定到了ThreadLocal上.ThreadLocal的set方法是将ThreadLocal对象和数据对象作为键值对存入线程对象内部的一个Map类型的数据结构里.因此,由于ActionContext被绑定在ThreadLocal对象上,所以ActionContext是线程安全的.

springmvc 和 struts2的区别:

先报出struts2的安全漏洞:

  • 2017年3月6日 Apache Struts 2被曝存在远程命令执行漏洞,漏洞编号S2-045,CVE编号CVE-2017-5638,在使用基于Jakarta插件的文件上传功能时,有可能存在远程命令执行,导致系统被黑客入侵。
  • 恶意用户可在上传文件时通过修改HTTP请求头中的Content-Type值来触发该漏洞,进而执行系统命令。

漏洞解决方案:

如果这个版本在Struts2.3.5 到 Struts2.3.31 以及 Struts2.5 到 Struts2.5.10之间则存在漏洞,请升级到struts 2.3.32或2.5.10.1版本

  1. 禁用jakarta框架
  • 在default.properties文件中修改配置,将struts.multipart.parser=jakarta修改为struts.multipart.parser=pell,相当于禁用了jakarta框架
  1. 添加action拦截器,过滤非法请求

    https://blog.csdn.net/mirror97black/article/details/78854411

struts2和springmvc对比:

1.机制:

​ spring mvc 和 struts2的加载机制不同:spring mvc的入口是servlet (DispaherServlet),而struts2是filter(StrutsPrepareAndExcutorFilter);

2.性能:

struts2 基于类的设计 , 每一次请求创建一个Action 对象 ,所以Action是多例的,一个Action 对应一个 request 上下文对象. 并且对于原生的serlvet对象是存储到值栈中.每次请求都需要创建Action 对象,封装属性,相比较springmvc效率比较慢.而且消耗内存.

springmvc 是基于方法设计的 每一个handler 是单例的 ,但具体url对应的方法是多例的,每一个方法对应一个request上下文,因此更适合restful优雅风格的编程.

扩展:什么是优雅的restful风格:

其目的是为了提高系统的可伸缩性,降低应用之间的耦合度,便于框架分布式处理程序。

REST 即 Representational State Transfer的缩写,可译为"表现层资源状态转化”

​ 1.资源 Resource : 服务器上获取到的东西就是资源(图片 用户名 , 密码)

​ 2.资源的表述 Representation : 资源的格式, html ,xml ,json ...

​ 3.状态转移 State Transfer :url 定位资源 ,使用http请求动词(get ,post ,put ,delete )描述操作.

​ 4.统一接口 Uniform Interface :REST必须通过统一的接口对资源进行操作

​ 5.超文本驱动 Hypertext Driven :资源之间通过超链接相互关联,超链接代表资源之间的关系,也代表可执行 的状态迁移

​ Restful API就是按照REST架构的思想设计出来的API

​ 核心就是:将API拆分为逻辑上的资源,这些资源通过http(GET/POST/PUT/DELETE等)动作被操作

3.参数传递:

struts2 :参数传递 使用的是 属性驱动,或者模型驱动.成员属性来接受,说明参数是多个方法共享的.返回的时候需要引入第三方jar包,有程序员进行json转换.返回客户端.(压入值栈 ,或者调用转json的方法)

springmvc : 是基于方法上的参数获取, 获取json数据的时候只需要添加 requestbody(前提添加 mvc:annotion-driven 配置).返回json 只需要添加resonpse注解 然后将需要返回的对象返回即可.

接收参数: 只需要使用注解 requestparams 绑定单个参数,使用对象作为形参的话,接受对象.

4.框架集成

spring MVC和Spring是无缝(无缝:无须数据格式转换,直接访问来自数据源数据格式)的。从这个项目的管理和安全上也比Struts2高;

5.数据校验:

springmvc继承jsr303 处理起来相对更加灵活方便,而Struts2验证比较繁琐;

6.拦截器机制:

springmvc有自己独立的aop功能实现.

struts2是有自己的拦截器机制.

springmvc拦截器:

SpringMVC的拦截器HandlerInterceptorAdapter对应提供了三个preHandle,postHandle,afterCompletion方法 :


  
      
        
        
        
        
          
    
    

/**
 * 
 */
package com.alibaba.interceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import com.alibaba.util.RequestUtil;


/**
 * @author tfj
 * 2014-8-1
 */
public class CommonInterceptor extends HandlerInterceptorAdapter{
    private final Logger log = LoggerFactory.getLogger(CommonInterceptor.class);
    public static final String LAST_PAGE = "com.alibaba.lastPage";
    /*
     * 利用正则映射到需要拦截的路径    

    private String mappingURL;

    public void setMappingURL(String mappingURL) {    
               this.mappingURL = mappingURL;    
    }   
  */
    /** 
     * 在业务处理器处理请求之前被调用 
     * 如果返回false 
     *     从当前的拦截器往回执行所有拦截器的afterCompletion(),再退出拦截器链
     * 如果返回true 
     *    执行下一个拦截器,直到所有的拦截器都执行完毕 
     *    再执行被拦截的Controller 
     *    然后进入拦截器链, 
     *    从最后一个拦截器往回执行所有的postHandle() 
     *    接着再从最后一个拦截器往回执行所有的afterCompletion() 
     */  
    @Override  
    public boolean preHandle(HttpServletRequest request,  
            HttpServletResponse response, Object handler) throws Exception {  
        if ("GET".equalsIgnoreCase(request.getMethod())) {
            RequestUtil.saveRequest();
        }
        log.info("==============执行顺序: 1、preHandle================");  
        String requestUri = request.getRequestURI();
        String contextPath = request.getContextPath();
        String url = requestUri.substring(contextPath.length());

        log.info("requestUri:"+requestUri);  
        log.info("contextPath:"+contextPath);  
        log.info("url:"+url);  

        String username =  (String)request.getSession().getAttribute("user"); 
        if(username == null){
            log.info("Interceptor:跳转到login页面!");
            request.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(request, response);
            return false;
        }else
            return true;   
    }  

    /**
     * 在业务处理器处理请求执行完成后,生成视图之前执行的动作   
     * 可在modelAndView中加入数据,比如当前时间
     */
    @Override  
    public void postHandle(HttpServletRequest request,  
            HttpServletResponse response, Object handler,  
            ModelAndView modelAndView) throws Exception {   
        log.info("==============执行顺序: 2、postHandle================");  
        if(modelAndView != null){  //加入当前时间  
            modelAndView.addObject("var", "测试postHandle");  
        }  
    }  

    /** 
     * 在DispatcherServlet完全处理完请求后被调用,可用于清理资源等  
     *  
     * 当有拦截器抛出异常时,会从当前拦截器往回执行所有的拦截器的afterCompletion() 
     */  
    @Override  
    public void afterCompletion(HttpServletRequest request,  
            HttpServletResponse response, Object handler, Exception ex)  
            throws Exception {  
        log.info("==============执行顺序: 3、afterCompletion================");  
    }  

}

strust2拦截器:

public interface Interceptor extends Serializable {

    void destroy();

    void init();
 // 在 invocation.invoke()之前的是前处理,之后的时候处理.
    String intercept(ActionInvocation invocation) throws Exception;
}

springmvc:

###### springmvc 运行流程:
  1. 用户发送一个请求 ,首先经过DispatchServlet截获,然后DispatchServlet根据请求的URL进行解析,是否具体的handler跟请求有对应关系。(判断是静态资源 还是需要处理的请求资源)

  2. 如果没有 ,查看spring_mvc.xml(配置的springmvc xml文件) 是否配置静态资源处理器 ,

    和 (没有配置的话 会提示 no mapping....)如果有配置访问目标资源 ,如果没找到 报错 404 页面

    找到目标资源 ,就直接访问目标资源。

  3. 如果找到的话(需要处理资源请求) ,DispatchServlet会获取 HandlerMapping 对象(包含 handler , handlerexcutoerchain , interceporchain)返回一个handler 对象 对具体的handler处理 。

  4. handler 会选择合适的 HandlerAdapter具体处理handler的运行流程。(原始的 HandlerAdapter 有三种

    ​ 没有 的时候:

    ​ HttpRequstHandlerAdapter :

    ​ SimpleControllerHandlerAdapter:(处理静态资源)

    ​ (AnnotationMethodHandlerAdapater: 没有配置 配置 的时候 是没有 AnnotationMethodHandlerAdapater的 ,两个都没配制的时候才有 )

    ​ 加入 的时候: 不确定 哈哈

    ​ HttpRequstHandlerAdapter :

    ​ SimpleControllerHandlerAdapter:(处理静态资源)

    ​ RequestMappingHandlerAdapter :(处理动态请求资源)

    关于 会自动注册 RequestMappingHandlerMapping RequestMappingHandlerAdapter 与 ExceptionHandlerExceptionResolver 三个bean。
    还将提供以下支持:
    支持使用 ConversionService 实例对表单参数进行类型转换
    支持使用 @NumberFormat annotation、@DateTimeFormat 注解完成数据类型的格式化
    支持使用 @Valid 注解对 JavaBean 实例进行 JSR 303 验证
    支持使用 @RequestBody 和 @ResponseBody 注解

  5. 处理handler的目标方法之前 首先调用拦截器链的preHandler() 方法:一般做数据校验 格式转换 ,记录日志的等作用,(数据校验的时候 出错 将返回一个 bindresult 对象 进行数据结果的封装)

  6. 根据 映射到的 @requestMapping("/url") 调用目标方法 处理目标方法。 创建一个(获取一个)modelandview 对象。(包含三个部分 ,model , view( 逻辑视图) , 以及状态)

  7. 然后调用 拦截器的post 方法 , 可以对modelandview 可以进行修改。(post 在解析试图之前)

  8. modelandview 对象根据我们配置的springmvcxml 中的视图解析器 对试图进行处理 ,返回正真的视图(也就是前后缀的拼装)将model 数据再保存到request 域中(调用viewResolver组件进行渲染视图: 默认是配置的Internerresourceviewresolver 如果加了jstl 使用JstlResovler解析得到的是 jstlview 获取到路径名称;)

  9. 调用拦截器的After方法 一般作用释放资源。(解析视图之后)

  10. 然后默认是将视图请求转发回客户端。

springmvc 常用注解:

https://blog.csdn.net/chenpeng19910926/article/details/70837756

  1. @Controller 标注该类是一个控制层。(需要开启包扫描) 注意: springmvc 容器 和spring容器 可以 2 选1 或者分别扫描不同的包来控制对象的创建 (否则会出件对象创建两次的问题 //指定扫描的路径 //排除扫描的路径
  2. @Component 将该类植入到springmvc(spring)容器中
  3. @Repository 注解dao层
  4. @Service 标记是个Service 层
  5. @RequestMapping 请求映射的注解 :
    • 标注在类上 : 相当于namespace 注解 ,将该地址作为工程下面的父路径,一般区分业务。
    • 标志在方法上 : 对请求细分 , 调用具体方法。可以是Restful风格
  • 参数: value, method;
  • consumes,produces (请求参数是什么格式 )
  • params (具体需要那个参数,和不用哪个参数),headers
  1. @resource 和 @AutoWired

    • @resource 按照名称自动装配
    • @AutoWired按照类型自动装配 , 按照名称需要 + @qualifier 注解一起使
  2. @ModelAttribute

    • 注解在方法上 : 注解的该方法在每个目标方法执行之前都会执行。 讲返回的对象放入到seesion 或者 模型属性中。
    • 注解在参数上 : 会将该注解的value 作为要封装的对象进行数据封装 (属性名称) ,若未标记 则将对象的属性名称类名小写 作为参数进行封装
  3. @See'sionAttribute : 作用在类上 , 存放session域对象 :

    参数有 class 类型 和 value 名称两种

  1. @PathVariable(支持rest 风格) 将url的模板参数映射到参数列表上。

  2. @RequstHander : 可以将请求头参数绑定到参数上

  3. @CookieValue : 将cookie值绑定到参数上

  4. @requstbody 需要加入 ,请求参数什格式:application/json, application/xml

    @RequestMapping(value = "/something", method = RequestMethod.PUT)  
    public void handle(@RequestBody String body, Writer writer) throws IOException {  
      writer.write(body);  
    }
    

13.@responsebody: 返回对象的数据类型 ,标注在方法上 返回的对象为j'son ,xml格式。

###### 扩展(返回json 添加json jar包 ,返回xml 添加xml jar 包 两个都有的话 可以使用 @request Mapping 中的produce 属性指定类型。)

返回json 数据不为空的字段设置:https://blog.csdn.net/yxwb1253587469/article/details/71195207


    
        
            
                
                    
                        NON_NULL
                    
                
            
        
    

  1. 使用注解 (类上添加 jackson): @JsonSerialize(include=JsonSerialize.Inclusion.NON_NULL)

    返回json数据 :忽略指定字段 :https://blog.csdn.net/a491857321/article/details/78761857

     FastJson  (阿里旗下)转Json字符串时,忽略指定属性 :  
    
  • @JSONField(serialize = false)

  • filter 指定序列化列

     SimplePropertyPreFilter filter = new SimplePropertyPreFilter(FastJsonInputBean.class, "contractTemplateId");
     System.out.println("filter忽略contractTemplateId属性:"+JSONObject.toJSONString(inputBean, filter));
    

    jackson :@JsonIgnoreProperties主键或者在字段上使用@JsonIgnore

  1. @ExceptionHandler : 注解到方法上, 出现异常会执行该方法。

  2. @ControllerAdvice : 注解到类上 ,使一个Contoller成为全局的异常处理类 类中用@ExceptionHandler方法注解的方法可以处理所有Controller发生的异常

  3. initbinder 处理data类型参数:

    //the parameter was converted in initBinder
    @RequestMapping("/date")
    public String date(Date date){
        System.out.println(date);
        return "hello";
    }
    
    //At the time of initialization,convert the type "String" to type "date"
    @InitBinder
    public void initBinder(ServletRequestDataBinder binder){
        binder.registerCustomEditor(Date.class, new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd"),
                true));
    }
    

get 和 post请求乱码处理:

post 处理 : web.xml 配置过滤器:

CharacterEncodingFilter

get 处理:重新将参数进行编码 : String userName e = New String(Request.getParameter(“userName”).getBytes(“ISO8859 “ ,”utf-8“)

spring :(待扩展)

spring的结构:(大致分为 5部分)

Test部分: spring 整合了junit 方便测试: @RunWith() ; @configcationcontext(location:)

Core Container: core ,bean (重点) , context (IOC 容器)

AOP : (包含事务等东西):

DataAccess : Jdbc , Orm , Jms ,Transactions 。

Web : jsp ,struts2 ,springmvc。

Hibernate :

hibernate 工作流程:

session:负责被持久化对象CRUD操作

sessionFactory:负责初始化hibernate,创建session对象

configuration:负责配置并启动hibernate,创建SessionFactory

Transaction:负责事物相关的操作

Query和Criteria接口:负责执行各种数据库查询

hibernate工作原理:

1.通过Configuration config = new Configuration().configure();//读取并解析hibernate.cfg.xml配置文件

2.由hibernate.cfg.xml中的读取并解析映射信息

3.通过SessionFactory sf = config.buildSessionFactory();//创建SessionFactory (线程共享的 ,创建session 具体操作crud的对象 属于二级缓存)

4.Session session = sf.openSession();//打开Sesssion (一级缓存)

5.Transaction tx = session.beginTransaction();//创建并启动事务Transation

6.persistent operate操作数据,持久化操作

7.tx.commit();//提交事务

8.关闭Session

getCurrentSession () 和 openSession ()的区别(都是获取Session对象的)

    1. 多次获取session对象:

      getCurrentSession () :

      ① (线程安全 )创建session 的时候autoCloseSessionEnabled ,flushBeforeCompletionEnabled 都为true , 并且session 会和 sessionFactory组成一个map key为 sessionFactory 绑定到当前线程上。

      ②从上下文中查找session,((配置文件current_session_context_class: thread 使用Connection自动管理;jta(java transaction api) 由Application Server提供的分布式事务管理,Tomcat本身不具备此能力,JBoss、WebLogic具备) ) ,如果有 则用旧的 ,若果没有 创建新的session

      ③ 事务提交后 自动关闭。

      ​ 使用:

      如果使用的是本地事务(jdbc事务)
      thread
      如果使用的是全局事务(jta事务)
      jta
      
 openSession():

 ① :线程不安全 , 原因:SessionFactory和Session都是接口,SessionFactoryImpl和SessionImpl是其实现。 里面的openSession 在创建具体实现的时候 给全局变量赋值。所以线程不安全。

 autoCloseSessionEnabled 为 false ; 

 ② ,每次创建的时候都会获取一个全新的Session

 ③ 事务提交后需要手动关闭 , 不然导致违法断开数据库链接,资源耗尽就会宕机。

 #####Hibernate缓存机制

   ###### 探讨N+1 问题:https://www.cnblogs.com/xiaoluo501395377/p/3377604.html

 N+1问题 :在多对一的关系中 , 我们再多的一方查找一的一方时 ,我们需要发送一个主查询(发一次sql) 查询多的一方数据(查出N条数据) 然后再根据外键关联去查询一的一方的数据,需要根据n条数据发送N条sql  这句是经典的N+1 问题。

 解决: 

 ① 使用延迟加载  

 ​    设置@ManyToOne的fetch属性值为fetchType.LAZY,这种方式解决后,后面的n条sql语句按需而发。但是有个弊端,就是如果需要级联查询就无法获取级联对象了。 

 ② 使用连接查询。在hqp语句中使用用join fetch,事实上Criteria用的就是这种方法。

 ③ 设置@BatchSize(size=5)(该注解要加在类上面,跟@Entity在同一位置),这样发出的sql语句减少。这个设置在一定程度上提高了效率。 

 hibernate中的N+1问题 :

 通过list()方法查找所有对象的时候会查询出所有对象 发送N 条sql 语句。

 通过 iterator()  方法查找的时候会先查出所有对象的id  (1条) , 在使用对象的时候又会发送N条sql去查询具体对象数据。

 ###### 一级缓存 : (基于session)

   对象状态: 

 ​    瞬时状态: new 对象 ,没有oid 也没有与session 关联

 ​        瞬时状态 ---->  持久状态 : save ,saveorupdate;

 ​        瞬时状态 ---->  托管状态 : 设置 oid 

 ​    持久状态 : 有oid 和 session 关联

 ​         持久状态 ---->  瞬时状态 : delete 方法。

 ​        瞬时状态 ---->  托管状态 :  去除关联session evict(清除一级缓存中的某个对象) 

 ​    ,close(关闭session ) ,clear (清除一级缓存中的所有东西)

    游离状态(托管状态): 有oid 没有与session 关联

 ​        游离状态 :---- >持久状态:update , saveorupdate , lock

 ​        游离状态 -----> 瞬时状态 : setoId(null) ,删除oid. 

 开启一级缓存:session的save/saveOrUpdate/get/load/list/iterator 

 快照引起的自动更新, 在查找对象的时候会将查询到的数据存放到一级缓存 和 快照中,在事务提交的时候会 比较快照和缓存之间的数据是否一致 ,如果不一致会自动发送sql 更新数据。

 缓存相关的方法:

 evict(Object o):从Session中删除指定对象

 clear():无参数,将Session缓存清空

 contains(Object o):判断指定对象是否在Session中存在

 flush():无参数,将Session中对象状态同步到DB中 (同步一级缓存 ,快照 , 数据库三者的数据)



 ###### 二级缓存:(SessionFactory)

 https://www.cnblogs.com/xiaoluo501395377/p/3377604.html

 hibernate并没有提供二级缓存组件 , 需要加入额外的缓存jar包。

 当一级缓存关闭的时候数据会存到二级缓存。 二级缓存只能保存对象 ,不能保存对象的属性,

 当我们如果通过 list() 去查询两次对象时 (  session = HibernateUtil.openSession();            List ls = session.createQuery("from Student")                    .setFirstResult(0).setMaxResults(50).list();),二级缓存虽然会缓存查询出来的对象,但是我们看到发出了两条相同的查询语句,这是因为二级缓存不会缓存我们的hql查询语句,要想解决这个问题,我们就要配置我们的查询缓存了。 

 hibernate.cfg.xml中加入一条配置即可: **查询缓存缓存的也仅仅是对象的id**  需要同时开启二级缓存。

 ```xml
  
    true

 List ls = session.createQuery("from Student where name like ?")
                     .setCacheable(true)  //开启查询缓存,查询缓存也是SessionFactory级别的缓存
                     .setParameter(0, "%王%")
                     .setFirstResult(0).setMaxResults(50).list()
 ```

 缓存清理策略:

 ​         session的查询方法                     commit()                        flush()

 auto :            true                                                true                                  true

 commit :       false                                               true                                   true  

 never :              false                                                false                            true

 ##### get 和 load 的区别:

 相同点: 都可以通过实体类对象的id 查询数据库返回对象实例。

 不同点: 

   ①  加载方式:

 ​      get  :立即加载

 ​          load  : 延迟加载             

   ②  返回结果 :

 ​              get 查不到 返回null ; get 先查找一级缓存 , 没有查询二级缓存 ,还没有在查询数据库。返回对象实例。

 ​              load 查不到返回的是异常:ObjectNotFoundException  ,

 load 分情况: 根据配置文件中的lazy 属性配置 (默认为true)分情况讨论:

 ​          true :  首先在session 缓存中查找 (一级缓存),id 对象的对像是否存在, 不存在使用延迟加载 , 返回实体类的代理对象(cglib创建的代理对象,属于实体类的子类。) 在具体使用的时候才会查询二级缓存,如果没有查询数据库 ,还有没抛出异常。

 (方法原理:代理对象试运行是动态生成的类 , 代理对象包含原目标对象的所有方法和属性,  除了ID 不为空意外 , 其他属性都为空 ,当调用getXXX()方法的时候,才会真正查询数据库)

 ​          false : 跟get 查询的方式一样 ,只是在返回结果的时候 抛出的是异常。

 ##### hibernate加载策略以及出现懒加载的问题原因和解决方法:

   ###### 加载策略:https://blog.csdn.net/xzm_rainbow/article/details/15028985

 (1)类级别: 

 ​              load :   延迟加载  查询对象id , 只获取id的值 , 只要使用其他字段的时候才会发送sql 语句。 在配置文件中 标签 lazy = false  取消延迟加载。

 ​               get : 立即加载  , 查询id就发送sql语句。

 (2):集合级别:

 ​       标签可以在上面添加lazy = false | true 

 (3) : 一对多和多对多: 两个属性 lazy fetch

 ​    (1) fetchjoin : 迫切左链接 , 查询所有结果 , 发送一条sql (没有迫切右链接)

 ​    (2) fetchselect : lazy = true , 懒加载 ,发送一条sql .

 ​                            lazy = false , 立即加载 , 发送两条sql , 主表和从表  (N+1问题)

 ​    (3) fetchsubselect :迫切子查询 , 生成的sql不一样 , 使用的是 in(select.....)

 (4) :多对一 和 1 对 1 的时候: 两个属性 : lazy  | fetch

 fetch 取值: join ,select 

 lazy取值: false ,proxy ,no-proxy

 - fetch = ”join“ 迫切左链接 , 立即查询

 - fetch = "select " , lazy = ”false“  立即查询

 - fetch = ”select“  lazy = ”proxy“ 表示主动权交给类级别。

      eg : customer  (主)  , Order(从) 配置fetch = ”select“  lazy = ”proxy“,  表示主动权交给主表 。

   customer中配置的lazy = true 表示懒加载 ,false 表示立即加载。

 ###### 出现懒加载报错原因:org.hibernate.LazyInitializationException:could not initialize proxy - no Session 

 ​         原因: 在多对一的查询中, 中的lazy默认为proxy , 这样hibernate 在查询的时候采用的是延迟加载 , 而在具体使用getXXX()方法的时候查询具体数据,

 ​     而hibernate是由SessionFactory 管理Session的 ,每次都会创建新的session 开启事务, 等操作完成以后,会提交事务, 关闭session . 而我们一般转json调用getXXX()的时候在action层 , 那个时候session 已经关闭 ,所以出现上面的异常。

 ​      解决方案:

 ​             分为三种 : 一种是取消延迟加载 , 一种是延长session生命周期。一种是session关闭之前提前使用数据 (相当于立即加载)

 ​    (1) . 取消延迟加载 , 使用迫切链接 (fetch = "join")或者修改 一的一方 , lazy = false ,缺点 所有都会采用立即加载,浪费内存空间。

    (2).取消延迟加载 , 不关联一方数据, 只查询多方的属性。采用转json的 忽略属性注解(看情况而定) 

    (3),  :相当于立即加载 ,  在service 层查询出数据 立即使用getXXX获取一的一方 数据

  , 或者syso 打印数据。 

  (4)延长session的生命周期  推荐使用: OpenSessionInViewFilter

 ```xml
  
     hibernateFilter 
      
     org.springframework.orm.hibernate3.support.OpenSessionInViewFilter 
      
  
  
     hibernateFilter 
     *.html 
  
 ```

 ##### hibernate 对应之间的关系:

          1. 单向一对一: eg : 人 (people) 和 身份证 (idcard)。 

               两种策略体现:  

             -  主键关联 :两个对象具有相同的主键值,以表明它们之间的一一对应的关系;数据库表不会有额外的字段来维护它们之间的关系,仅通过表的主键来关联 

               ![1529374900886](C:\Users\Lisa\AppData\Local\Temp\1529374900886.png)

               ![1529374923863](C:\Users\Lisa\AppData\Local\Temp\1529374923863.png)

             - 唯一外键关联:外键关联,本来是用于多对一的配置,但是加上唯一的限制之后(采用标签来映射,指定多的一端unique为true,这样就限制了多的一端的多重性为一),也可以用来表示一对一关联关系,其实它就是多对一的特殊情况

                注解:

               ![1529374978703](C:\Users\Lisa\AppData\Local\Temp\1529374978703.png)

               ![1529374985935](C:\Users\Lisa\AppData\Local\Temp\1529374985935.png)

             2.  多对一 或者一对多。

 //多的一方:

 ![1529374506311](C:\Users\Lisa\AppData\Local\Temp\1529374506311.png)

 ![1529374591943](C:\Users\Lisa\AppData\Local\Temp\1529374591943.png)



 //一方:

 ![1529374536159](C:\Users\Lisa\AppData\Local\Temp\1529374536159.png)

 ![1529374630059](C:\Users\Lisa\AppData\Local\Temp\1529374630059.png)

 ![1529374640114](C:\Users\Lisa\AppData\Local\Temp\1529374640114.png)



 注解:![1529375256976](C:\Users\Lisa\AppData\Local\Temp\1529375256976.png)



 ![1529375174678](C:\Users\Lisa\AppData\Local\Temp\1529375174678.png)



 cascade : 级联关系:

 ​              取值为: nono  默认值

 ​                               save-update  : 底层使用save -update 完成对象的操作。

 ​                               delete : 级联删除  

 ​                delete-ophan : 删除当前对象解除关系的对象

 ​                all : 包含 save-update delete 

 ​                all-delete-ophan :包含all和 delete-ophan            

 inverse :  true| false 表示放弃维护主键  相当于注解的 @mapperby





 3.多对多:

 ![1529375018543](C:\Users\Lisa\AppData\Local\Temp\1529375018543.png)

 ![1529375024015](C:\Users\Lisa\AppData\Local\Temp\1529375024015.png)



 使用 一方使用 @manytomany 注解  以及mapperby参数 表示放弃维护外键。

  @manytomany   和 @joinTable 

 mybatis :

https://blog.csdn.net/u014297148/article/details/78696096

​ mybatis底层还是采用原生jdbc来对数据库进行操作的,只是通过 SqlSessionFactory,SqlSession Executor,StatementHandler,ParameterHandler,ResultHandler和TypeHandler等几个处理器封装了这些过程

参数处理器: ParameterHandler (getParameterObject, setParameters)

sql 查询处理器   :StatementHandler  (prepare, parameterize, batch, update, query) 

执行处理器 : Excutor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)

结果处理器:ResultSetHandler (handleResultSets, handleOutputParameters)

流程图 : --ParameterHandler

​ sqlSessionFactroy --- SqlSession ---- Excutor ----StatementHandler ----ResultSetHandler --- TypeHandler --原生jdbc :

流程分析:

   ###### 创建sqlSessionFatory : 
  1. SqlSessionFactoryBuilder 对象 : new SqlSessionFactoryBuilder() ---build(inputStream)

  2. 创建一个xmlConfigBuilder 对象: 调用 parse()解析全局配置文件 dom4j解析

  3. 具体解析 parseConfigration()解析全局配置文件交给 Configuration 对象 然后给对象设置值。

    里面有一个 节点解析 解析的是映射文件。(mappedstatement 对象 每一个对象代表一个增删改查的详细信息) 将解析到的所有对象节点信息保存到Configuration 并返回该对象。

  4. 创建SqlSessionFactory 调用build(Configuration configuration) 返回一个DefalutSqlSessionFactory 对象。

    创建sqlsession 对象

    1. DefalutSqlSessionFactory 调用 openSession()方法 ----调用的openSessionFromDataSource(ExecutorType ,null , null) ExecutorType 有三种类型: simple ,reuse , betch 默认为simple.
    2. 根据ExecutorType类型创建Execuor 对象 ,(SimpleExecutor , ReuseExecutor , BetchExcutor) tx 事务对象,获取其他属性。
    3. 根据是否开启二级缓存 :cacheEnable 取值 (true |false )判断是否需要增强 Excutor 对象 ,(缓存会包装Executor 对象为CachingExecutor)然后调用 (Executor) interceptorChain.pluginAll(executor)方法 包装返回executor对象。
    4. 然后根据 new DefaultSqlSession(configuration, executor , autocommit); 创建sqlSession对象(DefaultSqlSession)里面包含configuration ,executor

    创建代理对象 getMapper(),返回代理对象mapperproxy 的代理对象

    1. 调用defaultsqlSession.getMapper ()实际调用的是 configuration.getMapper(type ,this)方法 (type 接口全类名 Class ,this -- defaultsqlSession)
    2. ,实际调用的是 MapperRegister(configuration中的两个重要属性的其中一个: mappedstatement (每一个crud 信息),MapperRegister 保存MapperProxyFactory 对象的信息。 )的getMapper()方法,获取一个MapperProxyFactory 对象。
    3. MapperProxyFactory 对象调用的是mapperProxyFactory.newInstance(sql'Session)
    4. 创建MapperProxy implments InvocationHandler 对象 , (包含sqlSession , Class mapperInteface , Map methodcache)。是jdk动态代理对象。
    5. 然后调用newInstance()方法,实际上是动态代理的操作.
    6. 创建mapperproxy 的代理对象, Proxy,newProxyInstance( mapperInterface.getClassLoader() , new Class[] {mapperInterface} , MapperProxy )

    执行crud 操作:

  5. 执行代理对象的invoke() 方法 , 判断是否为Object 方法 : 是 放行 ,不是:(代理接口方法)

  6. 是接口方法的话 Mappermethod mapperMethod = cacheMapperMethod(method);

    1. Mappermethod 是mybatis能认识的方法类。

    2. 调用 Mappermethod.execute(sqlSession , args)

    3. 进入这个方法以后 首先判断执行的操作类型: (sql类型)crud 类型 ,再判断返回值类型,

      void , Object , List

    4. 然后调用sqlSession的 执行方法 已查询selectOne为例 : sqlSession.selectOne()其实还是调用selectList 然后取第一个值 【0】.

    5. selectList()方法首先会获取mapperStatement(保存crud所有信息)。调用exector.query(mapperStatement , wrapCollection(parament),rowbounds , executor)。wrapCollection(parament)包装参数 就是咱们当时保存时怎么取出来。

    6. BoundSql boundSql = ms.getBoundSql(parameterObject); CacheKey key = this.createCacheKey(ms, parameterObject, rowBounds, boundSql);

    7. 这里判断是否存在缓存,调用executor 的qurery方法:首先从一级缓存中查询 ,如果有直接返回,没有直接查询BaseExecutor的 query() 。

      这里是调用Executor里的query方法 如果开启了缓存这掉CachingExecutor的 如果没有则是调用BaseExecutor的 query() 。 在调用查询数据库之前queryFromDatabase ()之前 首先给一级缓存保存一个key 值 贼长 。 做占位符用。 查询出list以后放入一级缓存。

       try {
              //此处是调用子Executor的方法,ExecutorType默认是使用的SimpleExecutor
                  list = this.doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
              } finally {
                  this.localCache.removeObject(key);
              }
      
              this.localCache.putObject(key, list);
      
    8. 然后调用BaseExecutor.doquery() 方法.

      1. 首先创建Statement statement = null (原生jdbc Statement 对象)

        configuration.getStatementhandler 创建 Statementhandler 对象

        Statementhandler 给 Statement 赋值。(默认产生一个预编译的Statementhandler 默认 prepareStatementhandler )

      2. 创建perparestamenthandler 产生预编译语句。

      3. 创建paramHandler : configuration.getparamterHandler () 方法 里面是拦截器链的调用 interceptor.pluginAll();

      4. 创建 ResultSetHandler configuration.getResultSetHandler () 方法 里面是拦截器链的调用 interceptor.pluginAll();

      5. 创建完对象以后 调用 setparamter设置参数 ,

      6. 然后调用perparestamenthandler.query() 查询具体语句。

      7. 调用ResultSetHandler 封装数据。(TypeHandler获取value值)

      8. 返回list[0]第一条数据

      9. 关闭资源

       Configuration configuration = ms.getConfiguration();
                  //创建StateMentHandler处理器
                  StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
                  //调用下3的方法
                  stmt = this.prepareStatement(handler, ms.getStatementLog());
      

      Xml映射文件的常见标签以及表之间关系的封装

    https://blog.csdn.net/qq_29233973/article/details/51433924

    定义sql:

    ​ insert :delete: update:

    ​ select :

    属性: id : 唯一标识符。

    ​ parameterType:参数类型

    ​ resultType: 返回值类型或别名 (一般为map或者对象全类名)

    结果封装:

    resultMap: 参数返回值封装 不能与resultType 一起使用。

    一对一使用:

    
     
     
     
     
     
         
         
         
         
     
    
    

    一对多使用: 关联关系中可以嵌套使用 : collection 包含 association

    
     
     
     
     
     
     
          
         
             
             
             
          
    
    

  ###### 自增主键配置:https://blog.csdn.net/zhenwodefengcaii/article/details/73195902

Oracle :selectKey

keyProperty :查出的主键值封装给javaBean的哪个属性
          order="BEFORE":当前sql在插入sql之前运行
                AFTER:当前sql在插入sql之后运行
          resultType:查出的数据的返回值类型
 
         
             select HIBERNATE_SEQUENCE.nextval from dual
         
         insert into t_employee (id ,  name , gender, company_id)
         values (#{id} , #{name} , #{gender} , #{companyId})
     




select HIBERNATE_SEQUENCE.currval from dual

insert into t_employee (id , name , gender, company_id)
values ( HIBERNATE_SEQUENCE.nextval, #{name} , #{gender} , #{companyId})

 ```

 Mysql :

 ```xml
 
     useGeneratedKeys :开启主键自增策略
     keyProperty : 主键id
 ```


 ###### 控制动态sql拼接:

       foreach:属性:

         collection :foreach标签可迭代任何对象(如列表、集合等)和任何的字典或者数组对象传递给foreach作为集合参数,当使用可迭代对象或者数组时,index是当前迭代的次数,item的值是本次迭代获取的元素。当使用字典(或者Map.Entry对象的集合)时,index是键,item是值。collection标签可以填('list','array','map')。 

         item  :表示集合中每一个元素进行迭代时的别名 

         index :指 定一个名字,用于表示在迭代过程中,每次迭代到的位置; 

         open : 表示该语句以什么开始, 一般都是 (

         separator :表示在每次进行迭代之间以什么符号作为分隔符; 

         close :以什么关闭

 ```xml
  
        delete from product where product_Id in
        
             #{productId,jdbcType = VARCHAR}
        
   
 ```

      if :  test 属性 判断条件 :一般用做条件判断  if+where 可以动态拼接条件。

     choose : 标签组合使用: when other 用法一致。

 

     

     

  

  ###### 格式化:

      where  条件标签

       set   update中使用的标签

     trim  :完善where set标签 

     查询语句 

     update   

     prefix:前缀      

   prefixoverride:去掉第一个and或者是or

     suffixoverride:去掉最后一个逗号(也可以是其他的标记,就像是上面前缀中的and一样)

   suffix:后缀

 ###### 配置关联关系

 联合resultMap 使用:

     associaction :

     collection :

 ###### 定义常量

     sql  , include : 搭配使用

 ```xml
    123
 
 ```

 ##### mybatis 分页:

     数组分页:一次查询出所有数据 然后使用 sublist(begin ,end ) 来截取需要的数据

     rowbounds 分页:逻辑跟数组分页一致。很可能导致内存溢出。

     sql分页 : 根据数据库语句分页  limit 条件

     https://blog.csdn.net/chenbaige/article/details/70846902:

           自定义拦截器分页 : 待实现 暂时不懂。

      pageHelper :https://blog.csdn.net/qq_33624284/article/details/72828977

 ##### mybatis 缓存:    

     一级缓存:

     sqlSession 本地缓存 ,是一个Map 储存 ,由Executor 维护。(PerpetualCache 类中的一个map)

        4种失效情况:

         1.SqlSession 不同 ( 一级缓存生命周期  openSqlSession  sqlSession.close()) 

         2. SqlSession  相同 , 执行查询语句以后,有执行insert update delete 语句。    insert update delete    flushCache="true"   ,每次执行都会更新缓存 , select 为false ,每次查询都不会更新缓存。                    

         3.sqlSession 相同 ,查询条件不同 。

   4. 清除一级缓存 ,sqlSession.clearCache();

              ###### 补充: 关于缓存参数:

          sqlSession.clearCache()清除一级缓存 ,二级缓存可用

          flushCache :true 刷新缓存  一二级缓存都失效

          usecache : true 只对二级缓存有效。

     二级缓存:(基于namespace的 当一级缓存关闭,才会存储到二级缓存)

           开启二级缓存 :

     全局配置文件:

         

     映射文件配置缓存 :

     

     eviction 参数:缓存过期策略:默认 lru

     lru : 最近最少使用

     fifo : 先进先出

     soft : 软引用  (虚拟机在内存快要耗尽的时候会清除)

     weak : 弱引用 (发起一次gc 会清除)。

 ##### mybatis 与 hibernate 区别:

   1. mybatis 半自动 , hibernate全自动。

hibernate 可以通过对象数据模型实现数据库操作 , 可以完全通过 javabean对象与数据库映射关系生成sql .

mybatis 仅仅是映射字段, 具体的表关系与(对象实际关系) 还需要通过sql实现。

  1. 移植性 :

    hibernate的移植性远强于mybatis , hibernate 只需要切换数据源就可以实现数据库的切换 ,sql语句由hibernate底层编写。

    mybatis 需要手写sql 所有需要 切换数据的收需要 修改sql 语句。

  2. 开发速度:

    ​ Hibernate的真正掌握要比Mybatis困难,Hibernate比mybatis更加重量级一些。

    ​ Mybatis需要我们手动编写SQL语句,回归最原始的方式,所以可以按需求指定查询的字段,提高程序的查询效率。

  3. 缓存机制:

    相同点:

    ​ Hibernate和Mybatis的二级缓存除了采用系统默认的缓存机制外,都可以通过实现你自己的缓存或为其他第三方缓存方案,创建适配器来完全覆盖缓存行为。

    MyBatis的二级缓存配置都是在每个具体的表-对象映射中进行详细配置,这样针对不同的表可以自定义不同的缓存机制。并且Mybatis可以在命名空间中共享相同的缓存配置和实例,通过Cache-ref来实现。

    而Hibernate对查询对象有着良好的管理机制,用户无需关心SQL。所以在使用二级缓存时如果出现脏数据,系统会报出错误并提示。

    orcale 分页 和 mysql 分页:

    ​ mysql limit 关键字分页:

    ​ oracle : 带条件分页 : 原理 使用rownum 先按照 page过滤一次条件 ,别名称一张表 , 然后再使用rownum 过滤一次

    SELECT *
    FROM (SELECT ROWNUM AS rowno,r.*
           FROM(SELECT * FROM DONORINFO t
                    WHERE t.BIRTHDAY BETWEEN TO_DATE ('19800101', 'yyyymmdd')
                    AND TO_DATE ('20060731', 'yyyymmdd')
                    ORDER BY t.BIRTHDAY desc
                   ) r
           where ROWNUM <= page*size 
          ) table_alias
    WHERE table_alias.rowno > (page-1)*size;
    

    springboot (带扩展)

@springbootconfiguration (组合注解):springboot 配置类 ; 标注这个注解说明该类为一个配置类 。

@configration (组合注解) spring的配置类 ,标注这个注解相当于是个配置类(配置文件) ,里面包含@compement spring 容器组件。

@EnableAutoconfigration 开启自动配置功能 。springboot 自动配置才能生效

  • @autoconfigrationpackage: 自动配置包 (租用是将组配置类所在的包下面所有注解扫描进去)

    • @import (AutoconfigurationPackage.Registrar.class); springboot 底层注解 ,class 类是需要导入的组件。

    • @enableautoconfigurationimportSelector :导入那些选择器。 (回导入96个自动配置类

      XXXautoconfigutation)

    springboot 在启动的时候会从类路径下MATA-INF/spring.factories 中获取EnableAutoConfigruation指定的值 ,将这些值作为自动配置类导入到容器中。

@confgrationproperties (从配置文件中取值)和 @value 区别。

@propetySource(classpath : ) 加载类路径下配置文件

@improtResource(classpath : ) 加载spring配置文件 , 必须放在配置类上。 (applicationcontext.containBean() 判断spring容器是否包含改bean对象)

springboot 核心功能:

 1. 独立运行的spring项目:可以以jar包方式运行。节省服务器资源
    2. 内置servlet 容器:内置

springboot 自动配置原理

springboot启动原理:

数据库:

关系型数据库:

mysql:

oracle:

ddl dml dcl dql 分别指的是什么???

ddl : data define language : 数据库定义语言 create, alter, drop等。 隐氏提交 ,不能rollback 。

dml : data mamipulation language 数据库操作语言 : update insert delete

dql : data query language : 数据库查询语言 : select from where

dcl : data control language 数据库控制语言 : grant revoke geny (拒绝);

数据库事务:ACID (原子性 Atomicity ,一致性 Consistency , 隔离性 Isolation, 持久性 Durability)

​ 原子性: 表示一个事务内的操作 ,要么成功,要么失败回滚。

​ 一致性: 事务执行的前后,数据完整性保持一致

​ 隔离性: 多个事务之间互不影响。

​ 持久性:一旦事务结束 , 数据就被持久化保存到数据库中。

事务的隔离级别:

  • 读未提交 :readUncommited : 可能产生脏读 ,不可重复读 ,幻读等。(一个事物读取到另一个事务未提交的数据)

  • 读已提交 :readcommited : 可能产生 不可重复度 ,幻读 (一个事务读取到另一个事务已提交的数据)

    能解决脏读 , 不能解决幻读, 和 不可重复读问题(原本有1000元 在消费之前开启事务 ,别人已经消费了500 ,再刷卡的时候读取到的就只有500元了 读到别人已提交的数据。update 操作)

    oracle 默认的事务级别

  • Repeatable read : 重复读 ,解决了不可重复读问题 ,但是不能解决幻读 (幻读是插入操作。)

​ mysql 默认的是事务级别。

  • Serializable 序列化 ,事务最高级别 ,可解决其他问题 ,但是效率低;

mysql 常见瓶颈:

​ cpu:CPU饱和的时候一版数据发生在装入内存或从磁盘上读取时候

​ i/o : 磁盘i/o平颈发生在装入数据远大于内存容量的时候 。

​ 服务器性能:top , free , iostat 和 vmstat 。来查看系统性能状态。

expl ain : 使用explain 关键字可以模拟mysql 执行计划,从而知道mysql 如何处理你的sql语句。

表的读取数据顺序:

​ from where order by group by select having

explain +sql :(id , type ,key ,rows , extra )

  • id :

      select 查询序列号 , 包含一组数字 , 表示查询中执行select 自己或者操作表的顺序。
    

分为:

​ id 相同: 从上到下顺序执行 根据 table 字段 的顺序执行

​ id 不同 : id 越大 优先级越高 , 先执行 (包含子查询)

​ id 相同又不同: id 越大 越先执行 , 平级 根据table 字段从上到下执行。(derived2)延伸虚表 , 2 表示的是 id.

  • select_type :

      查询类型 6 种:
    
      simple :  简单查询  ,没有嵌套查询 ,子查询 ,联合查询
    
      primary:  主查询 , 查询中包含任何子查询 ,最外层就是主查询, 最后加载。
    
      subquery: 子查询 : select  / where 列表包含的子查询
    
      derived: 衍生表 ,from 子查询 ,相当于临时表 起了个别名。
    
      union : 联合查询, select 查询出现在union之后 则标记为union ,如果from 子查询作为联合查询的表的时候 ,select 标记为 derived。
    
      union result: 两种union合并的结果。
    
  • table :

    那张表

  • type : 访问类型 sql 是否优化过息息相关,。(一般达到 index 就行 ,最好到 rang )

    • system : 表只有一行数据 ,相当于系统表 ,就是创建mysql是自带的表。 const 的特例。

    • const : 常量的意思用于比较 primary_key 或者 unique 索引 ,(相当于匹配主键) ,索引用在where语句 就会翻译成常量。 例如: select from(select from where id = 1) t1 ;

    • eq_ref : select * from t1 , t2 where t1.id = t2.id 。唯一索引扫描 , 表中只有一条数据与之匹配。

    • ref :非唯一索引扫描 , 返回匹配某一个单独值的所有行。

      ​ 没有创建唯一索引。 select * from t1 where id = 'ac' ;

    • range : 质检所给定范围的行, between , > , < , in 等查询语句 开始索引的某一点 ,结束某一点。

    • index : 全索引扫描 ,只遍历索引树 : id 在索引上 , select id from t1;

    • all : 全表扫描 。

  • possible_keys :

    ​ 是否使用到索引 , 多个索引竞争最后使用到那个索引。 可能使用到的索引

  • key :

    ​ 实际使用到的索引 ,没有用到 或者 索引 失效 为 null

    ​ 如果出现索引覆盖 ,则该所引致出现在key列表中 (select id ,name from t1 ;)查询的列 刚好是建立复合索引的列。

  • key_len :

    同样的查询结果 , ken_len长度越小越好 ,表示的使用到的索引字节数。(与精度冲突)

  • ref : 显示索引的那一列被使用,如果可能的话 最好是个常数。那些索引被用于查询索引列的值。

  • rows: 根据索引和表的优化,查询数据大概需要查询多少行 ,越小越好。

  • extra:扩展 :包含不在其他列显示 , 但十分重要的额外信息。

    • Using filesort :说明产生一个外部索引排列 , 无法利用索引进行优化 , 必然需要优化。

      ​ 例子: explain select * from t1 where col1 = 'ac' order by col3\G(\G表示展示键值对) ;

      ​ 索引为:col1 col2 col3的组合索引 。(排序的时候吧col2抽调了 会产生Using filesort )

    • Using Temporary : 使用临时表保存中间结果,对临时表进行排序的时候,常见排序order by 和 group by.

      例子:select col1 from t1 where col1 in ('ac' , 'bc' , 'ab') group by col2 ;

      ​ 索引: key :idx_col1_col2 ;产生 Using temporary ,Using filesort ;

      ​ 优化:select col1 from t1 where col1 in ('ac' , 'bc' , 'ab') group by col1 , col2 ;

    • Using index :表示select 操作 使用到覆盖索引,

      ​ 有Using where 的时候 说明 索引被用来键值查找。 select col1 from t1 col2 = ‘ac’ ; index : col1 col2 ;

      ​ 没有Using where 的时候 ,说明直接从索引读取数据 select col1 ,col2 from t1 ;

      覆盖索引:建的索引和查的字段一致。

    索引建立和优化:

    单表:单表建立索引 的时候 在 范围上的字段会导致后面的order by 索引失效。

    ​ select col1 from t1 where t1.col1 = 'ac' and t1.col2 > 2 order by t1.col3 ;

    ​ 创建索引的时候 创建:

    ​ create index index_col1_col3 on t1(col1 ,col3);

    ​ alter table t1 add index index_col1_col3 on(col1 ,col3);

    两表:select * from t1 left join t2 on t1.oid = t2.id;

    ​ 创建索引的原则 ,左连接 往右边的表字段创建索引 , 右链接往左边表创建索引

    ​ 内连接 往一个基表创建索引。

    多表:两个左连接 将往两边的右链接创建索引。

索引失效:

  1. 全职匹配我最爱 :
  1. 最佳左前缀法则:

    索引: index _name_password_age : 查询从最左边的索引一定不能缺失(name 必须有) ,要不导致索引失效(带头大哥不能死 ,中间兄弟不能断)。缺少password 的时候 会走索引 ,不过只有name 走了

  2. 不在索引列上做任何操作(计算, 函数 (手动/自动)类型转换会导致索引失效 :

    1. 存储引擎不能使用索引范围条件右边的列:
  3. 尽量使用覆盖索引 , 减少select *

  4. mysql 在使用 不等于 != , <> 时 会导致索引失效

  5. is null ,is not null 会导致索引失效

  6. like 以通配符开头“%abc” :(% like 加右边)

    ​ index_age_name 两边都是%%的时候可用使用覆盖索引解决

  7. 字符串不加单引号 导致索引失效

  8. 少用or ,用它来链接会导致索引失效


    1.观察:至少跑一天 ,看看慢sql 情况。

    1. 开启慢查询的日志 , 设置阙值(5s)抓取数据。
    2. 使用explain + 慢sql分析
    3. show profile
    4. 运维 dba 进行参数调优

    规则: (RBO)

  9. 小表驱动大表 。

    ​ 子查询数据量小于主查询的时候 使用in;

    ​ 子查询数据量大于主查询的时候 使用exsits;

    优化排序:

    ​ 使用order by 禁用 select *

    ​ 增大sort_buffer_size 参数

    ​ 增大max_length_for_sort_data 参数

    mysql 开启慢查询:

    ​ 一般不建议开启慢查询 会带来性能影响。

    ​ show variables like ' %slow_query_log%'; set global slow_query_log = 1 开启慢查询 只对当前数

    ​ 开启慢查询 , 创建function 报错 this function has none of deferministic。

    ​ 必须给函数指定参数:

    ​ show variables like 'log_bin_trust_function_creators' ;

    ​ set global log_bin_trust_function_creators = 1 ;

    据库有效 ,重启数据库 会失效。

    ​ 设置慢查询语句阙值: set global long_query_time = 3 ;

    ​ 查看超过慢查询的状态语句 : show global status like 'slow_queries' ;

    mysql 自动查询分析工具:

​ mysqldumpslow 工具使用:

​ s: 按照什么方式排序 。 c: 访问次数 。l : 锁定时间 。 r: 返回记录。 t : 查询时间 。

​ al : 平均锁定时间 。 ar : 平均返回记录数 。 at : 平均查询时间。 t : 返回前面多少条数据。

g : 增则表达式。

返回记录做多的10个sql :

eg: mysqldumpslow -s r -t 10 ....log

返回访问次数最多的10个sql :

eg : mysqldumpslow -s c-t 10 ....log

按照时间排序前10条里面包含左连接的

eg : mysqldumpslow -s t -t 10 -g 'left join' .........log;

show profile: sql 数据调优 ,排查。

​ 是mysql 提供可以用来分析当前会话执行的资源消耗情况 。(连接 ,服务 , 引擎 ,存储)。

默认保存15次运行结果。

怎么使用:

  1. 首先查看mysql 版本是否支持. show varilables like 'profiling' ;

  2. 开启功能 默认关闭。 set profiling = on ;

  3. 运行sql

  4. 查看结果 , show profiles ;

  5. 诊断sql , show profile cpu , block io for query id ( show profiles 中查询出来的id)

  6. 日常结论总结 :

    ​ converting Heap to myisam 查询结果太大,需要将数据移至到磁盘上

    ​ Creating tem table 创建临时表 , 拷贝数据 ,用完再删除临时表

    ​ Copying to tem table on disk :把内存中的临时表复制到磁盘上;

    ​ locked 枷锁。

一条sql的执行流程:

 1. ​      starting  开启连接 
    2. waiting  for query  cache  lock 等待查询缓存枷锁
    3. checking  query  cache for query  ,  查询缓存。
    4. check permissions : 检查权限
    5. Opening table :  开启表 
    6. System lock : 枷锁
    7. waiting  for query  cache  lock 等待查询缓存枷锁
    8. inin  初始化
    9. optimizing 优化器
    10. statistics  统计
    11. prepareing  预编译
    12. executing  执行
    13. Sending data 发送数据
    14. end 结束
    15. query end 查询 结束   此处省略几步(查缓存 以及 将数据写入缓存 freeing item)
    16. logging slow query  记录慢查询日志
    17. cleaning up 清除。

全局查询日志:

​ 永远不要在生产化境使用。两种 一种是配置文件 一种是编码格式。

set global general_log = 1;

set global log_output ='table'

mysql 锁机制:

​ 数据操作:

​ 读锁(共享锁) :同一份数据多个读操作可以共享

​ 写锁 (排他锁):同一份操作,写操作只能一个线程使用。

​ 表锁(偏读):myisam 存储引擎 , 开销小, 加锁快 ,锁粒度大 无死锁 。并发效率低。

​ 手动增加表锁: lock table table_name read(write) , ......多个表;

​ show open tables: 查看表是否加锁 。 0 表示没有加锁 , 1 表示枷锁。

​ unlock tables ;解锁

​ 读锁: session1 : lock table tab1 read ;

​ session1 : 可以读 tab1 ,不能读其他表 (报错), 不能修改 tab1 (报错)。

​ session 2 : 可以读 tab1 ,可以读其他表。 不能修改tab1 会产生死锁。(除非释放session1的 读锁)

​ 写锁:session1 : lock table tab1 write;

​ session1 : 可以读 tab1 ,可以改tab1 , 不能读取其他表。

​ session2 :可以都其他表 ,读取tab1的时候阻塞。

如何分析表锁定:show status like 'table %';tables_locks_immediate.

​ 两个参数:table_locks_waited 和

​ 行锁(偏写):

​ 偏向innodb (支持事务, 行级锁),开销大 ,枷锁慢,出现死锁。并发高。

​ 行锁,只锁单行 ,两个session同是操作一条数据 ,一个没有提交的时候,另一个session 阻塞。

操作不同数据的时候,不阻塞。

​ 索引失效 :导致行锁变表锁。

​ 间隙锁: 范围操作,会将该范围所有数据都加锁,例如 >1 and <6 , 会将2345 都加锁。

​ 就算你没有2数据 另一个session 也不能操作这个范围内的。

mysql的主从复制原理 及延迟解决:

​ 主机: master ---data changes-----binlog

​ 从机: slave i/o_running 线程读取到 binlog -----写到relay log日志 ---sql线程写到服务器上。

​ mysql 主从复制都是单线程操作,主要是ddl 数据库定义语言,dml 数据库操作语言产生 binlog 文件。

延迟主要原因:

谈到MySQL数据库主从同步延迟原理,得从mysql的数据库主从复制原理说起,mysql的主从复制都是单线程的操作,主库对所有DDL和 DML产生binlog,binlog是顺序写,所以效率很高,slave的Slave_IO_Running线程到主库取日志,效率很比较高,下一步, 问题来了,slave的Slave_SQL_Running线程将主库的DDL和DML操作在slave实施。DML和DDL的IO操作是随即的,不是顺 序的,成本高很多,还可能可slave上的其他查询产生lock争用,由于Slave_SQL_Running也是单线程的,所以一个DDL卡主了,需要 执行10分钟,那么所有之后的DDL会等待这个DDL执行完才会继续执行,这就导致了延时。有朋友会问:“主库上那个相同的DDL也需要执行10分,为什 么slave会延时?”,答案是master可以并发,Slave_SQL_Running线程却不可以 .

(1)当主库的TPS并发较高时,产生的DDL数量超过slave一个sql线程所能承受的范围,那么延时就产生了

(2) 当然还有就是可能与slave的大型query语句产生了锁等待。

解决方案:

​ 最简单的减少slave同步延时的方案就是在架构上做优化,尽量让主库的DDL快速执行。还有就是主库是写,对数据安全性较高,比如 sync_binlog=1,innodb_flush_log_at_trx_commit = 1 之类的设置,而slave则不需要这么高的数据安全,完全可以讲sync_binlog设置为0或者关闭binlog,innodb_flushlog也 可以设置为0来提高sql的执行效率。另外就是使用比主库更好的硬件设备作为slave。 (做负载均衡处理。多台slave分担请求)

Redis:

k-v键值的 非关系型数据库 :作用 它可以用作数据库,缓存和消息中间件。

redis 支持的数据类型:

  • String : (k - v ) k :string , value :字符串, 整数 , 浮点数。(存储一般信息)

      command:
    
      set :  存值 。 set   name  zhangsan
    
      mset :一次存储多个; mset  k1 v1   k2 v3
    
      get : 取值    get name
    
      mget :(m表示more)
    
      del : 删除值  del name
    

自增 /自减:

incr : 健存储的值 + 1 ; incr name ;

decr : 见存储的值 -1 : decr name ;

incrby : 键储存的值 + 整数 : incrby name 5 ;

decr : 建存储的制 - 整数 : decrby name 5 ;

incrfloat : 追加浮点数: incrfloat name 5.0;

追加:

append : 追加命令: append name abc ;

  • List : (作用 消息队列 可以 push pop ,)

    链表 :(双端链表) 元素可重复:

    lpush : 左边加入 从首元素加入 lpush key v1; lpush key v2

    rpush : 右边加入 rpush key v1 rpush key v2;

    lpop: 左边取(一个值):lpop key 返回 v2

    rpop:右边取 (一个值):rpop key 返回v2

    lrange : 获取范围: lrange key 0 -1 (start stop) -1表示获取所有

    lindex : 获取指定索引上的 ; lindex key 1

  • hash:(适合存储对象) 和string 命令差不多 不一一列举.

    ​ hset: hset key field value (field 表示域 给某个域设值)

    ​ hget:hget key field 从某个域取值

    ​ hdel: hdel filed1 filed2 ....(可以删除一个域或者多个域)

    ​ hgetall: 获取所有域和值 hgetall key

  • set:(用于存放不重复的数据 ,场景 共同好友 ,获取独立ip 类似于 set k1 v1 v2 v3)

    ​ sadd: 将给定元素添加到集合 : sadd key item; SADD language Ruby Python Clojure

    ​ smembers : 返回集合中所有元素 : smembers key ; SMEMBERS language

    ​ 1) "Python"2) "Ruby"3)"Clojure"

    ​ Sismember : 检查是否某个key存在, Sismember key value 返回的是 0 (不存在) 1 (cunzai)

    ​ srem :移除某个元素 : srem key value

  • Zset:(有序的散列集合 , 以浮点score分数 进行排序 作用可用于排行傍)

    ​ zadd :添加一个或多个 (2.4之前只能添加一个), zadd key score member [score member]

    ​ zrange : 获取指定范围的值 : zrange key start stop zrange name 0 -1 (0到-1表示查询所有 )

    ​ 如果这个区间没有数据的话 回返回 empty list or set

    ​ zrangebyscore : 获取给定分数的值:zrangebyscore key min max [withscore][limit offset count]

配置文件:

​ 配置文件重点:


################################## INCLUDES ###################################
#这在你有标准配置模板但是每个redis服务器又需要个性设置的时候很有用。
# include /path/to/local.conf
# include /path/to/other.conf

################################ GENERAL #####################################

#是否在后台执行,yes:后台运行;no:不是后台运行(老版本默认)
daemonize yes

  #3.2里的参数,是否开启保护模式,默认开启。要是配置里没有指定bind和密码。开启该参数后,redis只会本地进行访问,拒绝外部访问。要是开启了密码   和bind,可以开启。否   则最好关闭,设置为no。
  protected-mode yes
#redis的进程文件
pidfile /var/run/redis/redis-server.pid

#redis监听的端口号。
port 6379

#指定 redis 只接收来自于该 IP 地址的请求,如果不进行设置,那么将处理所有请求
bind 127.0.0.1
# 此参数为设置客户端空闲超过timeout,服务端会断开连接,为0则服务端不会主动断开连接,不能小于0。
timeout 0

#tcp keepalive参数。如果设置不为0,就使用配置tcp的SO_KEEPALIVE值,使用keepalive有两个好处:检测挂掉的对端。降低中间设备出问题而导致网络看似连接却已经与对端端口的问题。在Linux内核中,设置了keepalive,redis会定时给对端发送ack。检测到对端关闭需要两倍的设置值。
tcp-keepalive 0

#指定了服务端日志的级别。级别包括:debug(很多信息,方便开发、测试),verbose(许多有用的信息,但是没有debug级别信息多),notice(适当的日志级别,适合生产环境),warn(只有非常重要的信息)
loglevel notice

#指定了记录日志的文件。空字符串的话,日志会打印到标准输出设备。后台运行的redis标准输出是/dev/null。
logfile /var/log/redis/redis-server.log

#是否打开记录syslog功能
# syslog-enabled no

#syslog的标识符。
# syslog-ident redis

#日志的来源、设备
# syslog-facility local0

#数据库的数量,默认使用的数据库是DB 0。可以通过”SELECT “命令选择一个db
databases 16

################################ SNAPSHOTTING ################################
# 快照配置
# 注释掉“save”这一行配置项就可以让保存数据库功能失效
# 设置sedis进行数据库镜像的频率。
# 900秒(15分钟)内至少1个key值改变(则进行数据库保存--持久化) 
# 300秒(5分钟)内至少10个key值改变(则进行数据库保存--持久化) 
# 60秒(1分钟)内至少10000个key值改变(则进行数据库保存--持久化)
save 900 1
save 300 10
save 60 10000

#当RDB持久化出现错误后,是否依然进行继续进行工作,yes:不能进行工作,no:可以继续进行工作,可以通过info中的rdb_last_bgsave_status了解RDB持久化是否有错误
stop-writes-on-bgsave-error yes

#使用压缩rdb文件,rdb文件压缩使用LZF压缩算法,yes:压缩,但是需要一些cpu的消耗。no:不压缩,需要更多的磁盘空间
rdbcompression yes

#是否校验rdb文件。从rdb格式的第五个版本开始,在rdb文件的末尾会带上CRC64的校验和。这跟有利于文件的容错性,但是在保存rdb文件的时候,会有大概10%的性能损耗,所以如果你追求高性能,可以关闭该配置。
rdbchecksum yes

#rdb文件的名称
dbfilename dump.rdb

#数据目录,数据库的写入会在这个目录。rdb、aof文件也会写在这个目录
dir /var/lib/redis

################################# REPLICATION (复制)#################################
#复制选项,slave复制对应的master。(服务器只读)
# slaveof   主从复制  slaveof ip + port 

#如果master设置了requirepass,那么slave要连上master,需要有master的密码才行。masterauth就是用来配置master的密码,这样可以在连上master后进行认证。
# masterauth 

#当从库同主机失去连接或者复制正在进行,从机库有两种运行方式:1) 如果slave-serve-stale-data设置为yes(默认设置),从库会继续响应客户端的请求。2) 如果slave-serve-stale-data设置为no,除去INFO和SLAVOF命令之外的任何请求都会返回一个错误”SYNC with master in progress”。
slave-serve-stale-data yes

#作为从服务器,默认情况下是只读的(yes),可以修改成NO,用于写(不建议)。
slave-read-only yes

#当master不可用,Sentinel会根据slave的优先级选举一个master。最低的优先级的slave,当选master。而配置成0,永远不会被选举。
slave-priority 100

################################## SECURITY(安全) ###################################
#requirepass配置可以让用户使用AUTH命令来认证密码,才能使用其他命令。这让redis可以使用在不受信任的网络中。为了保持向后的兼容性,可以注释该命令,因为大部分用户也不需要认证。使用requirepass的时候需要注意,因为redis太快了,每秒可以认证15w次密码,简单的密码很容易被攻破,所以最好使用一个更复杂的密码。
# requirepass foobared

#把危险的命令给修改成其他名称。比如CONFIG命令可以重命名为一个很难被猜到的命令,这样用户不能使用,而内部工具还能接着使用。
# rename-command CONFIG b840fc02d524045429941cc15f59e41cb7be6c52

#设置成一个空的值,可以禁止一个命令
# rename-command CONFIG ""
################################### LIMITS (限制 有重点)####################################
#内存容量超过maxmemory后的处理策略。
#volatile-lru:利用LRU算法移除设置过过期时间的ke
#volatile-random:随机移除设置过过期时间的key。
#volatile-ttl:移除即将过期的key,根据最近过期时间来删除(辅以TTL)
#allkeys-lru:利用LRU算法移除任何key。
#allkeys-random:随机移除任何key。
#noeviction:不移除任何key,只是返回一个写错误。
#上面的这些驱逐策略,如果redis没有合适的key驱逐,对于写命令,还是会返回错误。redis将不再接收写请求,只接

############################## APPEND ONLY MODE ###############################
#开启aof持久化
appendonly no

#aof文件名
appendfilename "appendonly.aof"

#aof持久化策略的配置
#no表示不执行fsync,由操作系统保证数据同步到磁盘,速度最快。
#always表示每次写入都执行fsync,以保证数据同步到磁盘。
#everysec表示每秒执行一次fsync,可能会导致丢失这1s数据。

  appendfsync everysec

# 在aof重写或者写入rdb文件的时候,会执行大量IO,此时对于everysec和always的aof模式来说,执行fsync会造成阻塞过长时间,no-appendfsync-on-rewrite字段设置为默认设置为no。如果对延迟要求很高的应用,这个字段可以设置为yes,否则还是设置为no,这样对持久化特性来说这是更安全的选择。设置为yes表示rewrite期间对新写操作不fsync,暂时存在内存中,等rewrite完成后再写入,默认为no,建议yes。Linux的默认fsync策略是30秒。可能丢失30秒数据。
no-appendfsync-on-rewrite no

#自动重写设置
auto-aof-rewrite-percentage 100
#设置允许重写的最小aof文件大小,避免了达到约定百分比但尺寸仍然很小的情况还要重写
auto-aof-rewrite-min-size 64m

################################ REDIS CLUSTER ###############################
#集群开关,默认是不开启集群模式。
# cluster-enabled yes

#集群配置文件的名称,每个节点都有一个集群相关的配置文件,持久化保存集群的信息。这个文件并不需要手动配置,这个配置文件有Redis生成并更新,每个Redis集群节点需要一个单独的配置文件,请确保与实例运行的系统中配置文件名称不冲突
# cluster-config-file nodes-6379.conf

#节点互连超时的阀值。集群节点超时毫秒数
# cluster-node-timeout 15000

#在进行故障转移的时候,全部slave都会请求申请为master,但是有些slave可能与master断开连接一段时间了,导致数据过于陈旧,这样的slave不应该被提升为master。该参数就是用来判断slave节点与master断线的时间是否过长。判断方法是:
#比较slave断开连接的时间和(node-timeout * slave-validity-factor) + repl-ping-slave-period
#如果节点超时时间为三十秒, 并且slave-validity-factor为10,假设默认的repl-ping-slave-period是10秒,即如果超过310秒slave将不会尝试进行故障转移 
# cluster-slave-validity-factor 10

#master的slave数量大于该值,slave才能迁移到其他孤立master上,如这个参数若被设为2,那么只有当一个主节点拥有2 个可工作的从节点时,它的一个从节点会尝试迁移。
# cluster-migration-barrier 1

#默认情况下,集群全部的slot有节点负责,集群状态才为ok,才能提供服务。设置为no,可以在slot没有全部分配的时候提供服务。不建议打开该配置,这样会造成分区的时候,小分区的master一直在接受写请求,而造成很长时间数据不一致。
# cluster-require-full-coverage yes

持久化:https://blog.csdn.net/jackpk/article/details/30073097

rdb:指定时间内通过快照的记录数据 ,然后再回复的时候将快照的文件读取会内存。

保存文件名称: dump.rdb。

save 900 1; (15分钟1次)

save 300 10 ; (5分钟 10次)

save 60 10000 (1分钟 10000次)

如何触发rdb 产生dump文件:

​ Save : 只管复制 ,不能读写

​ Bgsave :后台复制 ,可以读写

​ flushall : 删除所有数据。(空文件 没有任何意义)

如何恢复: config get dir #文件保存目录 或者将文件复制到启动的目录下。

redis单线程操作,每次创建子线程的进行快照备份的话 ,主线程是不需要进行任何I/O操作的。(有子进程来进行保存操作)可以确保极高的性能,

确定就是每次切片不能保证系统是否正常,所以可能丢失最后一次切片的数据。

fork一个进程,遍历hash table,利用copy on write,把整个db dump保存下来。 save, shutdown, slave 命令会触发这个操作。 粒度比较大,如果save, shutdown, slave 之前crash了,则中间的操作没办法恢复。

aof :以日志的形式进行追加到文件中,然后回复的时候进行回写。(保存文件名:appendonly.aof)

​ 开启:appendonly no 默认关闭 ,开启是yes。

​ 将有数据的aof文件复制一份保存到对应对的目录 :config get dir.

​ 重启redis 然后重新加载。

aof文件被破坏: redis-check-aof --fix 进行修复

aof文件重写:aof文件过大的时候 默认64m重写切实原来文件的一倍的时候触发 , 首先会fork一个新进程(redis单线程操作 ,所以需要fork子进程 然后进行重写操作,这个进程会一直被挂起 知道操作完毕。有点类似于快照操作。)对文件进行压缩,保留最小的指令集 作用 :避免aof文件体积过大

手动开启重写: bgrewriteaof

重点:Aof 同步策略:

​ no: 不同步

​ everysec:每秒同步(一般采用每秒同步)

​ always: 每条指令 都同步

rbd 和 aof 优缺点对比:

​ rdb:

优点: 以时间片段来进行文件备份, 相比较aof 粒度大,更适合备份,容灾恢复能力强, 而且恢复大数据的时候效率高。rbd 在进行备份的时候fork一个子进程,父进程不需要进程任何i/o操作。

​ (每小时备份一个文件 , 每月的每一天也备份一个文件 ,这样可以具体恢复到几月几号几点)

缺点: 可能丢失最后一个时间片上的数据,不适合数据要求较高的系统。fork 子进程的时候服务器会停止多少ms不做任何操作,如果cup占用率过高的话,可能导致时间过长。 aof虽然也会fork,但是耐久性好不会有任何损失。

aof:

优点:相比较rdb ,耐久性好 ,并且备份粒度细 ,适合细粒度备份。后台执行备份。

aof重写 :每次不断的讲命令追加到末尾,命令过多的时候会采用执行bgrewriteaof命令 将fork一个子进程 ,然后记录所有命令,保存到一个temp文件(以最简洁的命令记录,此时不是读取原来aof文件,新旧文件都是记录所有命令,然后重命名新文件退换旧文件),所有命令重写 ,所以不会出现fork时间过长导致服务器丢失数据。

缺点:文件体积大,恢复慢与rbd.

which one :

两个同时开启 , 在这种情况下,当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整.RDB的数据不实时,同时使用两者时服务器重启也只会找AOF文件。

redis:缓存过期策略:

volatile(易变的 ,不稳定的) -lru (least recently use)( 移除设置过 过期策略的key)

volatile - ttl(移除设置过过期时间即将过期的)

volatile -random (随机移除即将过期的key)

allkeys -lru(所有key 移除最近最少使用)

allkeys-random (随机移除所有key )

noeviction :(不做操作。)

事务:

​ redis是半支持事务的,依次执行多个命令,所有命令都被序列化,按顺序被串行化执行,不会被其他命令插入。(半支持事务: 事务不保证原子性。

事务的性质:acid (durability原子性:atomicity ,一致性:consistency , 隔离性:isolation ,持久性:durability )

​ 而 redis 事务在执行期间 如果命令没有异常,但是操作结果报错 ,会将该条命令不执行,其他命令执行。(例如字符串自增的时候)

事务特性:1 . 命令先序列化,然后再串行化执行,中途不会被其他命令请求打断。

 2. 命令是加入到队列中 queue ,没有隔离级别 ,事务外不能查询有哪些命令

    3.不能保证原子性

事务命令:

​ multi : 开启事务

​ exec: 执行事务:

​ discard :放弃事务

​ watch : 监控事务。

​ u'n'wa't'ch: 不监控事务

case1: 正常操作:multi set k1 v1 set k2 v2 exec

case2:放弃事务:multi set k3 v3 set k4 v4 discard

case3:全体连坐:multi set k5 v5 setgetmset 123 (这条命令错误)

case4 :冤头债主 multi set k6 v6 incr k6 1 (将字符串+1 编译时不管,运行是报错)

case5 :watch监控: 乐观锁模式。(cas check and set)

乐观锁 :每次操作不会枷锁 ,会议版本号的形式进行检测 ,如果本次修改的话 版本号以改变,那么修改失败,需要重修操作。直到成功为止。

先监控 ,再开启事务,(监控多个key) 在事务开启后 key被修改,(加塞),在exec的时候放弃操作,同时返回nullmulti-bulk应答事务操作失败。

发布订阅:(消息中间件)

​ 一般不用做消息中间件 这块不总结了。

复制:

​ 主从复制,读写分离。 master 以写为主 , slave 以读为主。

作用:读写分离 ,容灾恢复。

如何玩?

 1. 配从 不配主。
2. slaveof  +  主库 ip + 主库端口 (从库每次断开都需要从新配置主库 ,可以采用在配置文件中方式配好。)
3. 开启多个redis 服务器。

一般操作:

​ 一主二仆 :

​ 一个主库 配置两个从库 ,主库进行写的操作,从库会第一次全部记录所有命令,后续每次追加。

一个master 两个slave ,master 挂掉 服务暂时停止 ,知道master 恢复。关系依然存在。

​ slave 挂掉 需要重新配置。

​ 薪火相传:

​ 一个主库,下面加一个从库,从库下面再加从库。

​ 反客为主:

​ 主库挂掉以 后 ,使用slaveof no one 是当前redis 停止与其他master数据同步,将从库选出一个,作为主库。

复制原理:

  1. slave 启动链接后,会向master 发送一个同步命令sync ,

  2. master 收集所有存盘命令,(修改mingling),后台备份文件 ,传给slave 完成同步。

  3. slave 全量复制
  4. 后续会根据master进行同步的追加命令。(增量复制)

哨兵模式:

​ 反客为主的自动化。

步骤:

​ 1、在Master对应redis.conf同目录下新建sentinel.conf文件,名字绝对不能错;

​ 2、配置哨兵,在sentinel.conf文件中填入内容:

​ sentinel monitor 被监控数据库名字(自己起名字) ip port 1

​ 说明:上面最后一个数字1,表示主机挂掉后slave投票看让谁接替成为主机,得票数多少后成为主机。

​ 3、启动哨兵模式:

​ 命令键入:redis-sentinel /myredis/sentinel.conf

消息队列:

为什么使用消息队列:

​ 解耦:

​ 传统模式:系统之间依赖性太强 ,一个系统出问题会导致另一个系统不可用。(下单 和 库存 系统 库存系统出现问题 影响下单系统)

​ 消息中间件: (下单系统 --- 消息中间件 MQ ---库存系统) 下单系统进行下单 将 订单持久化到数据库中,然后发布到MQ中 , 就算库存系统出现异常,也不影响到下单系统。等库存系统恢复正常然后从消息队列中取出数据即可完成操作

​ 异步:

​ 一些并行操作的系统太耗费时间 , 例如发送邮件 ,需要 service 直接跟第三方邮件平台交互。这样操作,需要用户等到交互完成才能进行下部操作。

​ 加入消息中间件以后 进行异步操作 ,服务端只需要将邮件推送到MQ中 ,然后就进行下面服务操作。

再有MQ异步将邮件推送到第三方邮箱平台。

​ 削峰:

​ 并发量大的时候请求全部发到数据库可能导致数据库链接异常

​ 加入消息中间件以后 ,服务器就可以通过数据库能处理的请求慢慢从消息中间建中拉去。

##### 消息队列的缺点:

系统性能降低: 多了一层消息中间件 ,所以系统性能会降低啊

系统维护性能增加:还需要多维护一个消息集群。

消息队列如何选型:

ActiveMQ , RabbitMq , RocketMq , Kafka。

如何选型:

Nginx:

​ nginx x-表示自由开源高性能的http服务器,和反向代理代理服务器,也是邮箱服务器(imap pop3 smtp)。

​ 解决 c10k (每秒连接10000个客户端) 。

nginx 主要功能: 反向代理 + 动静分离 + 负载均衡

反向代理:

​ 正向代理 ,基于客户端 ,例如 : 请求过来 经过 直接连接到要跳转的网页

​ 反向代理: 基于服务端:客户端相反向代理的服务空间发送请求 , 接着反向代理判断向何处发送请求 ,然后着转发,请求完毕 ,再交给反向代理 ,有反向代理发给客户端。(类似 客服)

动静分离:

​ 动态网页jsp 等 保存到Tomcat 服务器上 ,

​ 静态页面 图片等 保存到 nginx 上。

负载均衡:

​ 保证客户端能都跟服务端进行交互。(可以将请求分摊到多个服务端上)

nignx 内部进程模型:

ningx 是以多进程方式工作的。启动的时候会有一个master 进程和 多个worker 进程,master进程主要是管理worker进程 ,接受信号,给worker进程发送信号 , 监控woker运行状态。

处理请求的步骤: 启动时解析配置文件,获取监听的端口和 ip地址。然后再master进程中初始化好这个需要监控的socket(创建socket 设置addressuse等选项 绑定到指定的IP地址端口,并监听),然后再fork多好子线程竞争连接。

nginx 搭建负载均衡:

​ nginx作为负载均衡服务器,用户请求先到达nginx,再由nginx根据负载配置将请求转发至 tomcat服务器。 nginx负载均衡服务器:192.168.14.129 tomcat1服务器:192.168.14.131 tomcat2服务器:192.168.14.134

我们将两个相同的项目分别放到131 , 134 服务器上;

#配置提供服务的服务器
upstream smsserver{
    #weigth       表示权重,权值越大,分配几率越大  
    #max_fails    当有max_fails个请求失败,就表示后端的服务器不可用,默认为1,将其设置为0可以关闭检查 
    #fail_timeout 在以后的fail_timeout时间内nginx不会再把请求发往已检查出标记为不可用的服务器 
     server 192.168.14.131:8080 weight=1; 
     server 192.168.14.134:8080 weight=1; 
}
 server {  
        listen       8090;  监听的端口
        server_name  localhost  监听的服务名;  
        #规则一  根据location配置静态分离。
            location / {  
            proxy_pass http://localhost:80;  
        }  
    }

负载均衡策略:https://www.cnblogs.com/wpjamer/articles/6443332.html

内置策略 : 加权轮循 ,ip_hash

扩展策略 : 第三方提供 fair hash 等

1.轮循:(配置参数即可)
upstream backserver { 
server 192.168.0.14; 
server 192.168.0.15; 
} 
可靠性低和负载分配不均衡
2.指定权重
指定轮询几率,weight和访问比率成正比,用于后端服务器性能不均的情况。 
upstream backserver { 
server 192.168.0.14 weight=10; 
server 192.168.0.15 weight=10; 
} 
3. ip_hash
upstream backserver { 
ip_hash; 
server 192.168.0.14:88; 
server 192.168.0.15:80; 
} 
每个请求按访问ip的hash结果分配,这样每个访客固定访问一个后端服务器,可以解决session的问题。

分布式环境下session的处理方案:(保证session 一致性)

  1. session 复制 : 及 经过nginx 转发的请求 session 只能存在一个服务器上 ,那么通过tomcat将 session 进行基于IP组播(multicast)来完成的 一个节点发生变化 ,他会将节点session信息放到ip广播组中 ,ip广播组会将这些信息分发给其他node

  2. 使用 cookie存储session (缺点 cookie 有大小限制 4k 20个)而且不安全。

    原理 :客户端存储cookie ,每次请求过来就需要讲session带上 ,占用外网带宽 ,不安全 需要加密。

  3. ip_hash 解决 ,每个请求落到一个服务器上 ,所以不存在session 不一致问题。

  4. session 缓存服务器:

    原理 : 将session保存到redis服务器中 ,每次从服务器中获取即可。

Dubbo

#### 什么是dubbo????

​ 是分布式服务器 取代webservice中的wsdl ,通过消费者 ,服务者在 注册中心 的 方法来实现远程服务调用

单一应用框架:orm ,只需要一个应用 ssm 框架

垂直应用框架: mvc : 将系统分为多个子系统 ,但是在垂直架构中相同逻辑代码需要不断的复制,不能复用 。

分布式框架 rpc:将核心业务抽取出来作为一个独立的服务,形成一个稳定的服务中心。

流动式框架 soa:随着服务化的进一步发展,服务越来越多,服务之间的调用和依赖关系也越来越复杂,诞生了面向服务的架构体系(SOA),也因此衍生出了一系列相应的技术,如对服务提供、服务调用、连接处理、通信协议、序列化方式、服务发现、服务路由、日志输出等行为进行封装的服务框架

img

provider : 生产者 提供服务 向注册中心暴露接口

consumer : 消费者,远程调用服务的消费者。

registry : 注册中心 , 服务注册于发现的注册中心。

monitor : 监控中心 ,监控服务调用次数 。

使用:

spring 配置中暴露服务:

xml:配置方式
服务方:

    
    
    
    
    
    
    
    
    

消费方:
 
    
  
    
  

注解配置方式:
    开启注解扫描 :服务方 使用service 注解 (dubbo 下面的)
    消费方 :自动注入: @Reference

dubbo 服务集群容错和负载均衡:

​ 负载均衡配置:

​ random loadbalance : 按权重设置随机概率。

​ roundrobin loadbalance : 轮询

​ leastActive loadbalance : 最少活跃数调用计数差。

​ consistenthash loadbalance : 一致性hash ,相同参数请求总是发到同一个提供者。



集群容错:

  • failover cluster(默认) :失败自动切换 ,当出现失败 切换其他服务器。通常用于读操作,但重试会带来更长延迟。 可通过retries="2"来设置重试次数(不含第一次)。

    
    或:
    
    或:
    
     
    
  • failfast cluster : 只发起一次调用 ,失败就报错,通常用于非幂等性 写操作。比如 新增记录

    或:
    
  • Failsafe Cluster 失败安全 ,出现一场直接忽略 。通常用在审计操作

    或者
    
    
  • Failback Cluster 失败自动回复 ,后台记录失败请求 ,定时重发

  • Forking Cluster 并行调用多个服务,只要一个成功即返回,通常用于实时要求较高的读操作,但需要浪费更多的服务器资源。可通过forks="2"来设置最大并发数。

    Dubbo中zookeeper做注册中心,如果注册中心集群都挂掉,发布者和订阅者之间还能通信么?

    zookeeper中的三个角色 : leader follower observer ;(类似 redis 主从复制)

    zookeeper 核心是原子广播,这个机制保证了server之间的同步。zab 协议 ,选主 和 恢复。

    Zookeeper 的核心是原子广播,这个机制保证了各个Server之间的同步。实现这个机制的协议叫做Zab协议。Zab协议有两种模式,它们分别是恢复模式(选主)和广播模式(同步)。当服务启动或者在领导者崩溃后,Zab就进入了恢复模式,当领导者被选举出来,且大多数Server完成了和 leader的状态同步以后,恢复模式就结束了。状态同步保证了leader和Server具有相同的系统状态。

    当leader崩溃或者leader失去大多数的follower,这时候zk进入恢复模式,恢复模式需要重新选举出一个新的leader,让所有的Server都恢复到一个正确的状态。

    首先启动dubbo时 ,消费者会从zookeeper拉去生产者的地址 接口 ,缓存到本地服务中,每次调用按照本次缓存调用。

    zookeeper采取的是选主模式 ,一台服务挂掉的话 ,如果是主服务 会进入恢复模式 进行选主 ,如果是从服务 不影响服务的运行 。

你可能感兴趣的:(JavaEE)