from & refer: https://download.csdn.net/download/zhiyuan411/12085383
Hotspot JVM 中的 Java 线程与原生操作系统线程有直接的映射关系。
线程私有数据区域生命周期与线程相同, 依赖用户线程的启动/结束 而 创建/销毁。
线程共享区域随虚拟机的启动/关闭而创建/销毁。
2.2.1. 程序计数器(线程私有)
2.2.2. 虚拟机栈(线程私有)
2.2.3. 本地方法区(线程私有)
本地方法区和 Java Stack 作用类似, 区别是虚拟机栈为执行 Java 方法服务, 而本地方法栈则为 Native 方法服务。
HotSpot VM 直接就把本地方法栈和虚拟机栈合二为一。
2.2.4. 堆(Heap-线程共享)-运行时数据区
2.2.5. 方法区/永久代(线程共享)
2.3.1. 新生代
是用来存放新生的对象。一般占据堆的 1/3 空间。
由于频繁创建对象,所以新生代会频繁触发MinorGC 进行垃圾回收。
新生代又分为 Eden 区、ServivorFrom、ServivorTo 三个区。
2.3.1.1. Eden 区
Java 新对象的出生地(如果新创建的对象占用内存很大,则直接分配到老 年代)。
当 Eden 区内存不够的时候就会触发 MinorGC,对新生代区进行 一次垃圾回收。
2.3.1.2. ServivorFrom
上一次 GC 的幸存者,作为这一次 GC 的被扫描者。
2.3.1.3. ServivorTo
保留了一次 MinorGC 过程中的幸存者。
2.3.1.4. MinorGC 的过程(复制->清空->互换)
MinorGC 采用复制算法。
2.3.2 老年代
主要存放应用程序中生命周期长的内存对象。
老年代的对象比较稳定,所以 MajorGC 不会频繁执行。
MajorGC 采用标记清除算法。MajorGC 的耗时比较长,还会产生内存碎片。
当老年代也满了装不下的 时候,就会抛出 OOM(Out of Memory)异常。
2.3.3 永久代
指内存的永久保存区域,主要存放 Class 和 Meta(元数据)的信息,Class 在被加载的时候被 放入永久区域,它和和存放实例的区域不同,GC 不会在主程序运行期对永久区域进行清理。
所以这也导致了永久代的区域会随着加载的 Class 的增多而胀满,最终抛出 OOM 异常。
在 Java8 中,永久代已经被移除,被一个称为“元数据区”(元空间)的区域所取代。
元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用 本地内存。
2.4.1. 如何确定垃圾
引用计数法:一个对象如果没有任何与之关联的引用,即他们的引用计数为 0,则说明对象不太可能再被用到,那么这个对象就是可回收对象。
可达性分析:用于解决引用计数法的循环引用问题。如果在“GC roots”和一个对象之间没有可达路径,则称该对象是不可达的。两次标记后仍然是不可达对象,则将面临回收。
2.4.2. 标记清除算法(Mark-Sweep)
标记阶段标记出所有需要回收的对象,清 除阶段回收被标记的对象所占用的空间。该算法最大的问题就是内存碎片化严重。
2.4.3. 复制算法(copying)
按内存容量将内存划分为等大小 的两块。每次只使用其中一块,当这一块内存满后将尚存活的对象复制到另一块上去,把已使用 的内存清掉。
该算法最大的问题是可用内存被压缩到了原 本的一半。且存活对象增多的话,Copying 算法的效率会大大降低。
2.4.4. 标记整理算法(Mark-Compact)
标记阶段和 Mark-Sweep 算法相同,标记后不是清 理对象,而是将存活对象移向内存的一端。然后清除端边界外的对象。
2.4.5. 分代收集算法
分代收集法是目前大部分 JVM 所采用的方法,其核心思想是根据对象存活的不同生命周期将内存 划分为不同的域,不同区域选择不同的算法。
一般情况下将 GC 堆划分为老生代(Tenured/Old Generation)和新生代(Young Generation)。
新生代都采取 Copying 算法。老年代采用 Mark-Compact 算法。
2.5.1. 强引用
最常见的就是强引用,把一个对象赋给一个引用变量,这个引用变量就是一个强引 用。
2.5.2. 软引用
软引用需要用 SoftReference 类来实现。
2.5.3. 弱引用
弱引用需要用 WeakReference 类来实现。
2.5.4. 虚引用
虚引用需要 PhantomReference 类来实现。
当前主流 VM 垃圾收集都采用”分代收集”(Generational Collection)算法。
分区算法则将整个堆空间划分为连续的不同小区间, 每个小区间独立使用, 独立回收. 这样做的好处是可以控制一次回收多少个小区间, 根据目标停顿时间, 每次合理地回收若干个小区间(而不是整个堆), 从而减少一次 GC 所产生的停顿。
java 虚拟中针对新生代和年老代分别提供了多种不同的垃圾收集器。
2.7.1. Serial 垃圾收集器(单线程、复制算法)
2.7.2. ParNew垃圾收集器(Serial+多线程)
2.7.3. Parallel Scavenge 收集器(多线程复制算法、高效、自适应调节策略)
2.7.4. SerialOld收集器(单线程标记整理算法)
2.7.5. ParallelOld收集器(多线程标记整理算法)
2.7.6. CMS收集器(多线程标记清除算法、最短垃圾回收停顿时间)
2.7.7. G1收集器(标记整理算法、低停顿垃圾回收)
2.8.1. 阻塞 IO 模型
读写数据过程中用户线程会发生阻塞现象。
2.8.2. 非阻塞 IO 模型
用户线程需要不断地询问内核数据是否就绪,也就说非阻塞 IO 不会交出 CPU,而会一直占用 CPU。
2.8.3. 多路复用 IO 模型
多路复用 IO 模型是目前使用得比较多的模型。Java NIO 实际上就是多路复用 IO。
有一个线程(在内核进行)不断去轮询多个 socket 的状态,只有当 socket 真正有读写事件时,才真正调用实际的 IO 读写操作。
2.8.4. 信号驱动 IO 模型
当用户线程发起一个 IO 请求操作,会给对应的 socket 注册一个信号函 数,然后用户线程会继续执行,当内核数据就绪时会发送一个信号给用户线程,用户线程接收到 信号之后,便在信号函数中调用 IO 读写操作来进行实际的 IO 请求操作。
2.8.5. 异步 IO 模型
异步 IO 模型才是最理想的 IO 模型。异步 IO 是需要操作系统的底层支持,在 Java 7 中,提供了 Asynchronous IO。
用户线程只需要先发起一个请求,当接收内核返回的成功信号时表示 IO 操作已经完成,可以直接去使用数据了。
2.8.6. JAVA IO包
字节流使用InputStream/OutputStream及其子类来处理;字符流使用Reader/Writer及其子类来处理。
2.8.7. JAVA NIO包
NIO 主要有三大核心部分:Channel(通道),Buffer(缓冲区), Selector。
NIO 基于 Channel 和 Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区 中,或者从缓冲区写入到通道中。Selector(选择区)用于监听多个通道的事件(比如:连接打开, 数据到达)。
NIO 和传统 IO 之间第一个最大的区别是,IO 是面向流的,NIO 是面向缓冲区的。
JVM 类加载机制分为五个部分:加载,验证,准备,解析,初始化。
虚拟机设计团队把加载动作放到 JVM 外部实现,以便让应用程序决定如何获取所需的类,JVM 提 供了 3 种类加载器,从上到下依次为: 启动类加载器(Bootstrap ClassLoader,JAVA_HOME\lib 目录),扩展类加载器(Extension ClassLoader,JAVA_HOME\lib\ext 目录),应用程序类加载器(Application ClassLoader,用户路径(classpath)上的类库)。
我们也可以通过继承 java.lang.ClassLoader实现自定义的类加载器。
JVM 通过双亲委派模型进行类的加载:当一个类收到了类加载请求,他首先不会尝试自己去加载这个类,而是把这个请求委派给父类去完成,每一个层次类加载器都是如此。只有当父类加载器反馈自己无法完成这个请求的时候(在它的加载路径下没有找到所需加载的Class),子类加载器才会尝试自己去加载。
OSGI(Open Service Gateway Initiative),是面向 Java 的动态模型系统,可以实现模块级 的热插拔功能,同时也引入了额外的复杂度,因为它不遵守了类加载的双亲委托模型。
集合类存放于 Java.util 包中,主要有 3 种:
3.2.1. ArrayList(数组)
3.2.2. Vector(数组实现、线程同步)
3.2.3. LinkList(链表)
3.3.1.1. HashSet(Hash 表)
3.3.1.2. TreeSet(二叉树)
3.3.1.3. LinkHashSet(HashSet+LinkedHashMap)
3.4.1. HashMap(数组+链表+红黑树)
Java8 对 HashMap 进行了一些修改,最大的不同就是利用了红黑树,所以其由 数组+链表+红黑树 组成。
3.4.2. ConcurrentHashMap
支持并发操作。
ConcurrentHashMap 是一个 Segment 数组,Segment 通过继承 ReentrantLock 来进行加锁。每个 Segment 很像之前介绍的 HashMap,不过它要保证线程安全,所以实现会更复杂。
Java8 对 ConcurrentHashMap 进行了比较大的改动, Java8 也引入了红黑树。
3.4.3. HashTable(线程安全)
遗留类,任一时间只有一个线程能写 Hashtable,并发性不如 ConcurrentHashMap。
3.4.4. TreeMap(可排序)
用 Iterator 遍历 TreeMap 时,得到的记录是排过序的。
3.4.5. LinkHashMap(记录插入顺序)
用 Iterator 遍历LinkedHashMap 时,得到的记录是按照插入顺序排序的。
4.1.2.1. 继承 Thread 类
Thread 类本质上是实现了 Runnable 接口的一个实例,代表一个线程的实例。
启动线程的方法就是通过 Thread 类的 start()实例方法,它将启动一个新线 程,并执行 run()方法。
4.1.2.2. 实现 Runnable 接口
启动 自定义的MyThread类,需要首先实例化一个 Thread,并传入自己的 MyThread 实例。
4.1.2.3. ExecutorService、Callable
有返回值的任务必须实现 Callable 接口,类似的,无返回值的任务必须 Runnable 接口。
执行 Callable 任务后,可以获取一个 Future 的对象,在该对象上调用 get 就可以获取到 Callable 任务 返回的 Object 了。
4.1.2.4. 基于线程池的方式
线程和数据库连接这些资源都是非常宝贵的资源。那么每次需要的时候创建,不需要的时候销 毁,是非常浪费资源的。那么我们就可以使用缓存的策略,也就是使用线程池。
4.1.3.1. newCachedThreadPool
调用 execute 将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线程并添加到池中。
终止并从缓存中移除那些已有 60 秒钟未被使用的线程。
4.1.3.2. newFixedThreadPool
创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。
4.1.3.3. newScheduledThreadPool
创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
4.1.3.4. newSingleThreadExecutor
这个线程池只有一个线程, 并可以在线程死后(或发生异常时)重新启动一个线程来替代原来的线程继续执行下去。
在线程的生命周期中,它要经过新建(New)、就绪(Runnable)、运行(Running)、阻塞 (Blocked)和死亡(Dead)5 种状态。
启动后,线程状态也会多次在运行、阻塞之间切换。
4.1.5.1. 正常运行结束
4.1.5.2. 使用退出标志退出线程(volatile定义标志变量)
4.1.5.3. Interrupt() 方法结束线程
4.1.5.4. stop() 方法终止线程(线程不安全)
sleep()方法导致了程序暂停执行指定的时间,该方法是属于 Thread 类的,线程也不会释放对象锁。
调用 wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用 notify()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。
守护线程–也称“服务线程”,他是后台线程,它有一个特性,即为用户线程 提供 公共服务,它的优先级比较低,在没有用户线程可服务时会自动离开(线程则是 JVM 级别的)。
通过 setDaemon(true)来设置一个用户线程为“守护线程”;在 Daemon 线程中产生的新线程也是 Daemon 的。
4.1.9.1. 乐观锁
读时不会上锁;写时会加锁:先读出当前版本号,然后比较跟上一次的版本号,如果一样则更新,失败则重复读-比较-写的操作。
java 中的乐观锁基本都是通过 CAS 操作实现的,CAS 是一种更新的原子操作,比较当前值跟传入值是否一样,一样则更新,否则失败。
4.1.9.2. 悲观锁
读时会加锁。
java 中的悲观锁就是 Synchronized。AQS 框架下的锁则是先尝试 cas 乐观锁去获取锁,获取不到,
才会转换为悲观锁,如 RetreenLock。
4.1.9.3. 自旋锁
如果持有锁的线程能在很短时间内释放锁资源,那么那些等待竞争锁的线程就不需要做内核态和用户态之间的切换进入阻塞挂起状态,它们只需要等一等(自旋),等持有锁的线程释放锁后即可立即获取锁,这样就避免用户线程和内核的切换的消耗。 超过自旋等待的最大时间后进入阻塞状态。
适用于锁的竞争不激烈,且占用锁时间非常短的代码块。
在 JDK 1.6 引入了适应性自旋锁,适应性自旋锁意味着自旋的时间不在是固定的了,而是由前一次在同一个锁上的自旋时间以及CPU负荷来决定。
4.1.9.4. Synchronized 同步锁
synchronized 它可以把任意一个非 NULL 的对象当作锁。他属于独占式的悲观锁,同时属于可重入锁,非公平锁。
Java1.6,1.7 与 1.8 中,均对该关键字的实现机理做了优化。例如适应自旋、偏向锁和轻量级锁等,效率有了本质上的提高。
4.1.9.5. ReentrantLock
ReentrantLock 是一种独占式的可重入锁,除了能完成 synchronized 所能完成的所有工作外,还提供了诸如可响应中断锁、可轮询锁请求、定时锁等避免多线程死锁的方法。默认为非公平锁,可以初始化时设置是否公平锁。
4.1.9.6. Semaphore 信号量
Semaphore 是一种基于计数的信号量。它可以设定一个阈值,基于此,多个线程竞争获取许可信
号,做完自己的申请后归还,超过阈值后,线程申请许可信号将会被阻塞。
Semaphore 可以用来构建一些对象池,资源池之类的。计数为 1 的 Semaphore,可以作为一种类似互斥锁的机制。
Semaphore 基本能完成 ReentrantLock 的所有工作,使用方法也与之类似。
4.1.9.7. AtomicInteger
AtomicInteger,是一个提供原子操作的 Integer 的类,常见的还有AtomicBoolean、AtomicInteger、AtomicLong、AtomicReference 等,他们的实现原理相同,区别在与运算对象类型的不同。令人兴奋地,还可以通过 AtomicReference
4.1.9.8. 可重入锁(递归锁)
可重入锁,也叫做递归锁,指的是同一线程 外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但不受影响。
4.1.9.9. 公平锁与非公平锁
公平锁:加锁前检查是否有排队等待的线程,优先排队等待的线程,先来先得。
非公平锁:加锁时不考虑排队等待问题,直接尝试获取锁,获取不到自动到队尾等待。
4.1.9.10. ReadWriteLock 读写锁
读写锁分为读锁和写锁,多个读锁不互斥,读锁与写锁互斥。
4.1.9.11. 共享锁和独占锁
独占锁模式下,每次只能有一个线程能持有锁。
共享锁则允许多个线程同时获取锁,并发访问 共享资源。
4.1.9.12. 重量级锁(Mutex Lock)
依赖于操作系统 Mutex Lock 所实现的锁我们称之为“重量级锁”。因为操作系统实现线程之间的切换这就需要从用户态转换到核心态,这个成本非常高,时间非常长。
Synchronized 是通过对象内部的一个叫做监视器锁(monitor)来实现的。但是监视器锁本质又是依赖于底层的操作系统的 Mutex Lock 来实现的。JDK 中对 Synchronized 做的种种优化,其核心都是为了减少这种重量级锁的使用。
4.1.9.13. 轻量级锁
锁的状态总共有四种:无锁状态、偏向锁、轻量级锁和重量级锁。
随着锁的竞争,锁可以从偏向锁升级到轻量级锁,再升级的重量级锁(但是锁的升级是单向的,也就是说只能从低到高升级,不会出现锁的降级)。
轻量级锁不使用操作系统互斥量来实现,而是依赖多次 CAS 原子指令。
轻量级锁并不是用来代替重量级锁的,它的本意是在线程交替执行同步块的前提下,减少传统的重量级锁使用产生的性能消耗。
4.1.9.14. 偏向锁
偏向锁的目的是在某个线程获得锁之后,消除这个线程锁重入(CAS)的开销,看起来让这个线程得到了偏护。
引入偏向锁是为了在只有一个线程执行同步块的情况下尽量减少不必要的轻量级锁执行路径。
4.1.9.15. 分段锁
是一种思想,ConcurrentHashMap 是学习分段锁的最好实践。
4.1.9.16. 锁优化
4.1.10.1. 线程等待(wait)
调用该方法的线程进入 WAITING 状态,并释放对象的锁。
只有等待另外线程的通知或被中断才会返回。
4.1.10.2. 线程睡眠(sleep)
线程进入 TIMED-WATING 状态,并不释放当前占有的锁。
4.1.10.3. 线程让步(yield)
当前线程让出 CPU 执行时间片,与其他线程一起重新竞争 CPU 时间片。
4.1.10.4. 线程中断(interrupt)
中断一个线程,其本意是给这个线程一个通知信号,会影响这个线程内部的一个中断标识位。对线程的行为影响会因为线程对该标识位处理方法的实现而不同。
4.1.10.5. Join 等待其他线程终止
等待其他线程终止,在当前线程中调用一个线程的 join() 方法,则当前线程转为阻塞状态,等到指定线程结束,当前线程再由阻塞状态变为就绪状态。
很多情况下,主线程生成并启动了子线程,需要用到子线程返回的结果,也就是需要主线程需要在子线程结束后再结束,这时候就要用到 join() 方法。
4.1.10.7. 线程唤醒(notify)
唤醒在此对象监视器上等待的单个线程,如果所有线程都在此对象上等待,则会选择唤醒其中任意一个线程,被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争。
类似的方法还有 notifyAll() ,唤醒再次监视器上等待的所有线程。
巧妙地利用了时间片轮转的方式, CPU 给每个任务都服务一定的时间,然后把当前任务的状态保存下来,在加载下一任务的状态后,继续服务下一任务,任务的状态保存及再加载, 这段过程就叫做上下文切换。
上下文切换可以认为是内核(操作系统的核心)在 CPU 上对于进程(包括线程)进行切换,上下文切换过程中的信息是保存在进程控制块(PCB, process control block)中的。PCB 还经常被称作“切换桢”(switchframe)。
除了当前执行任务的时间片用完,IO阻塞,用户代码挂起当前任务,硬件中断等都会引起上下文切换。
线程池做的工作主要是:线程复用;控制最大并发数;管理线程。
线程池的实现原理:继承重写Thread 类,在其 start 方法中添加不断循环调用传递过来的 Runnable 对象。 循环方法中不断获取 Runnable 是用 Queue 实现的,在获取下一个 Runnable 之前可以是阻塞的。
在阻塞队列中:
当队列中没有数据的情况下,消费者端的所有线程都会被自动阻塞(挂起),直到有数据放 入队列。
当队列中填满数据的情况下,生产者端的所有线程都会被自动阻塞(挂起),直到队列中有 空的位置,线程被自动唤醒。
Java 中的阻塞队列:(无界队列是指队列没有固定大小,不存在队列满负荷情况)
4.1.15.1. CountDownLatch(线程计数器 )
4.1.15.2. CyclicBarrier(回环栅栏-调用await()等待至某个状态再全部同时执行,这个状态称为 barrier 状态)
4.1.15.3. Semaphore(信号量-控制同时访问的线程个数)
CountDownLatch 和 CyclicBarrier 都能够实现线程之间的等待。Semaphore更类似于锁,用于控制对某组资源的访问权限。
volatile 变量具备两种特性:
volatile 变量是一 种比 sychronized 关键字更轻量级的同步机制。
volatile 适合这种场景:一个变量被多个线程共 享,线程直接给这个变量赋值;且写操作不能依赖于当前值(如i++),不依赖于其他volatile变量。
Java 里面进行多线程通信的主要方式就是共享内存的方式。
共享内存要保证:可见性和有序性原子性。Java 内存模型(JMM)解决了可见性和有序性的问题,而锁解决了原子性的问题。
常规实现方法:
ThreadLocal 的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度。
4.1.21.1. 抢占式调度
抢占式调度指的是每条线程执行的时间、线程的切换都由系统控制。一个线程的堵塞不会导致整个进程堵塞。
4.1.21.2. 协同式调度
协同式调度指某一线程执行完后主动通知系统切换到另一线程上执行。线程的执行时间由线程本身控制,线程切换可以预知,不存在多线程同步问题,但处理不当可能会导致整个进程堵塞。
4.1.21.3. JVM 的线程调度实现(抢占式调度)
java 使用的线程调使用抢占式调度,Java 中线程会按优先级分配 CPU 时间片运行。
4.1.22.1. 优先调度算法
例如:先来先服务调度算法(FCFS),短作业(进程)优先调度算法
4.1.22.2. 高优先权优先调度算法
例如:非抢占式优先权算法,抢占式优先权调度算法,高响应比优先调度算法(作业的优先级随着等待时
间的增加而提高)
4.1.22.3. 基于时间片的轮转调度算法
例如:时间片轮转法,多级反馈队列调度算法(设置多个就绪队列,并为各个队列赋予不同的优先级和时间片大小)
CAS(Compare And Swap/Set)比较并交换,CAS 算法的过程是这样:
它包含 3 个参数 CAS(V,E,N)。V 表示要更新的变量(内存值),E 表示预期值(旧的),N 表示新值。当且仅当 V 值等于 E 值时,才会将 V 的值设为 N,如果 V 值和 E 值不同,则说明已经有其他线程做了更新,则当 前线程什么都不做。最后,CAS 返回当前 V 的真实值。
并且,通过版本号的方式来保证了完成读数据和比较并交换的操作的原子性。
AbstractQueuedSynchronizer 类如其名,抽象的队列式的同步器,AQS 定义了一套多线程访问共享资源的同步器框架,许多同步类实现都依赖于它,如常用的 ReentrantLock/Semaphore/CountDownLatch。
Throwable 是 Java 语言中所有错误或异常的超类。下一层分为 Error 和 Exception。
Exception 又有两个分支,一个是运行时异常 RuntimeException(可能在 Java 虚拟机正常运行期间抛出的异常的超类),一个是 CheckedException(一般是外部错误,这种异常都发生在编译阶段,Java 编译器会强制程序去捕获此类异常)。
反射机制是指在运行状态中,对于任意一个类都能够知道这个类所有的属性和方法;并且对于任意一个对象,都能够调用它的任意一个方法。
反序列化是基于反射实现的。
// 获取 Person 类的 Class 对象
// Person p=new Person();
// Class clazz=p.getClass();
// Class clazz=Person.class;
// Class clazz=Class.forName("类的全路径"); (最常用,安全&性能最好)
Class clazz=Class.forName("reflection.Person");
// 获取 Person 类的所有方法信息
Method[] method=clazz.getDeclaredMethods(); for(Method m:method){
System.out.println(m.toString()); }
// 获取 Person 类的所有成员属性信息
Field[] field=clazz.getDeclaredFields(); for(Field f:field){
System.out.println(f.toString()); }
// 获取 Person 类的所有构造方法信息
Constructor[] constructor=clazz.getDeclaredConstructors(); for(Constructor c:constructor){
System.out.println(c.toString());
}
Annatation(注解)是一个接口,程序可以通过反射来获取指定程序中元素的 Annotation 对象,然后通过该Annotation 对象来获取注解中的元数据信息。
元注解的作用是负责注解其他注解。 Java5.0 定义了 4 个标准的 meta-annotation 类型,它们被用来提供对其它 annotation 类型作说明。
内置的3种标准注解:
自定义注解的实例:
// 1: 定义注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
// 使用关键字 @interface。在底层实现上,所有定义的注解都会自动继承java.lang.annotation.Annotation接口
public @interface FruitProvider {
public int id() default -1;
public String name() default "";
public String address() default "";
}
// 2: 使用注解
public class Apple {
@FruitProvider(id = 1, name = "陕西红富士集团", address = "陕西省西安市延安路")
private String appleProvider;
public void setAppleProvider(String appleProvider) {
this.appleProvider = appleProvider;
}
public String getAppleProvider() {
return appleProvider;
}
}
// 3: 解释注解(注解处理器)
public class FruitInfoUtil {
public static void getFruitInfo(Class> clazz) {
String strFruitProvicer = "供应商信息:";
//通过反射获取处理注解,因为我们的注解是定义在Filed上的
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(FruitProvider.class)) {
FruitProvider fruitProvider = (FruitProvider) field.getAnnotation(FruitProvider.class); //注解信息的处理地方
strFruitProvicer = " 供应商编号:" + fruitProvider.id() + " 供应商名称:"
+ fruitProvider.name() + " 供应商地址:" + fruitProvider.address();
System.out.println(strFruitProvicer);
}
}
}
}
// 测试
public class FruitRun {
public static void main(String[] args) {
FruitInfoUtil.getFruitInfo(Apple.class);
/***********输出结果***************/
// 供应商编号:1 供应商名称:陕西红富士集团 供应商地址:陕西省西安市延
}
}
根据定义的方式不同,内部类分为静态内部类,成员内部类,局部内部类,匿名内部类四种。
5.1.4.1. 静态内部类
5.1.4.2. 成员内部类
5.1.4.3. 局部内部类(定义在方法中的类)
5.1.4.4. 匿名内部类(要继承一个父类或者实现一个接口、直接使用 new 来生成一个对象的引用)
泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。
Java 中的泛型基本上都是在编译器这个层次来实现的。在生成的 Java 字节代码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会被编译器在编译的时候去掉。这个过程就称为类型擦除。
类型通配符一般是使用?代替具体的类型参数,?泛型对象是只读的。
5.1.7.1. 直接赋值复制
A a1 = a2,这实际上复制的是引用,也就是说 a1 和 a2 指向的是同一个对象。因此,当 a1 变化的时候,a2 里面的成员变量也会跟着变化。
同理,对象作为参数传递时,是把对象在内存中的地址拷贝了一份传给了参数。
String类的特殊性在于其操作符重载时new了新对象,所以,有类似于基本类型一样的传值效果:String str = "Hello";
等价于String str = new String("Hello");
,str = str + " world!";
等价于str = new String((new StringBuffer(str)).append(" world!"));
5.1.7.2. 浅复制(复制引用但不复制引用的对象)
如果字段是值类型的,那么对该字段执行复制;如果该字段是引用类型的话,则复制引用但不复制引用的对象。
5.1.7.3. 深复制(复制对象和其应用对象)
深拷贝不仅复制对象本身,而且复制对象包含的引用指向的所有对象。
可以利用序列化/反序列化机制来深复制一个对象:先使对象实现 Serializable 接口,然后把这个对象写到一个流里,再从流里读出来,便可以重建对象。
Spring 是一个全面的、企业应用开发一站式的解决方案,贯穿表现层、业务层、持久层。但是 Spring 仍然可以和其他的框架无缝整合。
Spring 具有轻量级,控制反转,面向切面,是容器和框架集合的特点。
2014 年至 2017 年期间发布了许多 Spring 框架 4.xx 系列版本,包含了对Java 8 的全面支持;Spring 5.0 GA版本于2017年9月28日发布,不再支持JDK8以下的版本。
Spring的一个核心功能是IOC,就是将Bean初始化加载到容器中,Bean是如何加载到容器的,可以使用Spring注解方式,就和XML配置方式一样。
@RequestMapping(“/hello/{name}”)
可以定义参数@PathVariable String name
来提取。(放在参数前)持久层:
6.1.7.1. 概念
Spring 通过一个配置文件描述 Bean 及 Bean 之间的依赖关系,利用 Java 语言的反射功能实例化 Bean 并建立 Bean 之间的依赖关系。 Spring 的 IoC 容器在完成这些底层工作的基础上,还提供 了 Bean 实例缓存、生命周期管理、 Bean 实例代理、事件发布、资源装载等高级服务。
6.1.7.3. IOC 容器实现
6.1.7.4. Spring Bean 作用域
6.1.7.5. Spring Bean 生命周期
6.1.7.6. Spring 依赖注入四种方式
6.1.7.7. 5 种不同方式的自动装配
Spring 装配包括手动装配和自动装配,手动装配是有基于 xml 装配、构造方法、setter 方法等,自动装配方式来进行依赖注入的方法如下:
业务处理的主要流 程是核心关注点,与之关系不大的部分是横切关注点(比如,比如权限认证、日志、事务)。
AOP 的作用在于将核心关注点和横切关注点分离开来。
所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来的可重用模块。
切面便于减少系统的重复代码,降低模块之间的耦合度,并有利于未 来的可操作性和可维护性。
Spring 提供了两种方式来生成代理对象: JDKProxy 和 CGLib,默认的策略是如果目标类是接口, 则使用 JDK 动态代理技术,否则使用 CGLib 来生成代理。
AOP实例:
@Aspect
public class TransactionDemo {
// 基于注解的方式:@Pointcut("@annotation(com.annotations.MyAnnotation)")
@Pointcut(value = "execution(* com.core.service.*.*.*(..))")
public void point() {
}
@Before(value = "point()")
public void before() {
System.out.println("transaction begin");
}
@AfterReturning(value = "point()")
public void after() {
System.out.println("transaction commit");
}
@Around("point()")
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("transaction begin");
joinPoint.proceed();
System.out.println("transaction commit");
}
}
Spring 的模型-视图-控制器(MVC)框架是围绕一个 DispatcherServlet 来设计的,流程如下:
Spring Boot只是Spring框架的扩展,它消除了设置Spring应用程序所需的XML配置,使开发、测试和部署更加快捷、方便和高效。
6.1.11.2. 本地事务
紧密依赖于底层资源管理器(例如数据库连接 ),举例:
public void transferAccount() {
Connection conn = null;
Statement stmt = null;
try {
conn = getDataSource().getConnection();
// 将自动提交设置为 false,若设置为 true 则数据库将会把每一次数据更新认定为一个事务并自动提交
conn.setAutoCommit(false);
stmt = conn.createStatement();
stmt.execute("update t_account set amount = amount - 500 where account_id = 'A'");
stmt.execute("update t_account set amount = amount + 500 where account_id = 'B'");
// 提交事务
conn.commit();
// 至此,事务提交成功: 转账的两步操作同时成功
} catch (SQLException sqle) {
// 发生异常,回滚在本事务中的操作
conn.rollback();
// 至此,事务回滚成功: 转账的两步操作完全撤销
stmt.close();
conn.close();
}
}
6.1.11.1. 分布式事务
Java 事务编程接口(JTA:Java Transaction API)和 Java 事务服务 (JTS;Java Transaction Service) 为 J2EE 平台提供了分布式事务服务。
分布式事务(Distributed Transaction)包括事务 管理器(Transaction Manager)和一个或多个支持 XA 协议的资源管理器 ( Resource Manager )。
在实现上,使用两阶段提交(准备阶段和提交阶段)来保证分布式事务的原子性。
在使用中,我们可以将资源管理器看做任意类型的持久化数据存储。
事务管理器承担着所有事务 参与单元的协调与控制,我们不必关心。
实例如下:
public void transferAccount() {
UserTransaction userTx = null;
Connection connA = null;
Statement stmtA = null;
Connection connB = null;
Statement stmtB = null;
try {
// 获得 Transaction 管理对象
userTx = (UserTransaction) getContext().lookup("java:comp/UserTransaction");
connA = getDataSourceA().getConnection();
connB = getDataSourceB().getConnection();
// 启动事务
userTx.begin();
stmtA = connA.createStatement();
stmtA.execute("update t_account set amount = amount - 500 where account_id = 'A'");
stmtB = connB.createStatement();
stmtB.execute("update t_account set amount = amount + 500 where account_id = 'B'");
// 提交事务
userTx.commit();
// 至此,事务提交成功: 转账的两步操作同时成功(数据库 A 和数据库 B 中的数据被同时更新)
} catch (SQLException sqle) {
// 发生异常,回滚在本事务中的操作
userTx.rollback();
// 至此,事务回滚成功: 数据库 A 和数据库 B 中的数据更新被同时撤销
} catch (Exception ne) {
}
}
Mybatis 中,默认开启一级缓存且不能关闭。
当下用于服 务注册的工具非常多,如 ZooKeeper,还有Consul,Eureka,SmartStack,Etcd 等。
7.1.1.1. 客户端注册(zookeeper)
客户端注册是服务自身要负责注册与注销的工作。
期间客户端还需要和注册中心保持心跳,心跳可以由客户端或者注册中心负责。
7.1.1.2. 第三方注册(独立的服务 Registrar)
第三方注册由一个独立的服务 Registrar 负责注册与注销。
当服务启动后以某种方式通知 Registrar, 然后 Registrar 负责向注册中心发起注册工作。同时 Registrar 要维护与服务之间的心跳,当服务不 可用时,向注册中心注销服务。
7.1.1.3. 客户端发现
客户端发现是指客户端负责查询可用服务的实际地址并请求服务,以及负责负载均衡的工作。
7.1.1.4. 服务端发现
服务端发现需要额外的 Router 服务,请求先打到 Router,然后 Router 负责查询服务与负载均衡。
API Gateway 负责请求转发、聚合结果和协议转换。
所有来自客户端的请求都要先经过 API Gateway,然后路由这些请求到对应的一个或多个微服务,并聚合结果。
还可能有 其他功能,如安全认证、监控、负载均衡、缓存、请求分片和管理、静态响应处理等。
配置中心一般用作系统的参数配置,它需要满足如下几个要求:高效获取、实时感知、分布式访
问。
基于 zookeeper 配置中心的实现架构中,采取数据加载到内存方式解决高效获取的问题,借助 zookeeper 的节点监听机制来实现实时感知。
消息服务和事件的统一调度,常用用 kafka ,activemq 等。
Spring Cloud Sleuth 通过在日志中引入唯一标识 Trace ID,将请求过程的所有日志关联起来,可以跟踪一个请求从一个微服务到下一个微服务的传播过程。
当 Hystrix Command 请求后端服务失败数量超过一定比例(默认 50%), 断路器会 切换到开路状态(Open). 这时所有请求会直接失败而不会发送到后端服务.
断路器保持在开路状态 一段时间后(默认 5 秒), 自动切换到半开路状态(HALF-OPEN). 这时会判断下一次请求的返回情况, 如果请求成功, 断路器切回闭路状态(CLOSED), 否则重新切换到开路状态(OPEN).
服务熔断可以避免服务雪崩效应,避免发送大量无效 请求影响系统吞吐量, 并且断路器有自我检测并恢复的能力。
Swagger,完成定义接口及接口相关信息的描述文件,就可以自动生成接口文档(多格式)和客户端、服务端的代码(多语言),以及在线接口调试页面等等。
而有了Spring框架的支持,通过在项目中引入Springfox(前身为Spring-swagger项目),可以扫描相关的代码,反向生成描述文件,进而生成与代码一致的接口文档和客户端代码。
Netty 是一个高性能、异步事件驱动的 NIO 框架,基于 JAVA NIO 提供的 API 实现。
它提供了对 TCP、UDP 和文件传输的支持,作为一个异步 NIO 框架,Netty 的所有 IO 操作都是异步非阻塞 的,通过 Future-Listener 机制,用户可以方便的主动获取或者通过通知机制获得 IO 操作结果。
8.1.3.2. 关键技术
8.1.3.4. 消息编解码
request消息数据结构:接口名称+方法名+参数类型和参数值+超时时间+ requestID
response消息数据结构:返回值+状态 code+requestID
8.1.3.1. 通讯过程
Java RMI(Java Remote Method Invocation,Java 远程方法调用)是 Java 编程语言里,一种用 于实现RPC(Remote Procedure Call,远程过程调用)的应用程序编程接口。
Protocol Buffer 是 google 的一个开源项目,它是用于结构化数据串行化的灵活、高效、自动的方法。具有语言无关,平台无关,体积小,序列化速度快,扩展性和兼容性好等优点。缺点是它以二进制流方式存储(不可读),从而自解释性差,通用性差。
Apache Thrift 是 Facebook 实现的一种高效的、支持多种编程语言的远程服务调用的框架。
它采用接口描述语言定义并创建服务,支持可扩展的跨语言服务开发,所包含的代码生成引擎可以在多种语言中创建高效的、无缝的服务,其传输数据采用二进制格式, 对于高并发、大数据量和多语言的环境更有优势。
TCP/IP 协议不是 TCP 和 IP 这两个协议的合称,而是指因特网整个 TCP/IP 协议族:
HTTP 是一个无状态的协议。无状态是指客户端和服务器之间不需要建立持久的连接,客户端发送请求,服务器返回响应,然后连接就被关闭了。
9.1.4.1. 传输流程
请求和响应信息示例:
curl -v -d '{"jsonData":"test"}' "http://www.baidu.com/index.html?param=test"
* Trying 220.181.38.150...
* TCP_NODELAY set
* Connected to www.baidu.com (220.181.38.150) port 80 (#0)
> POST /index.html?param=test HTTP/1.1 ===== 请求方法URI协议/版本
> Host: www.baidu.com ===== 请求头,从此处开始的若干行
> User-Agent: curl/7.64.1
> Accept: */*
> Content-Length: 19
> Content-Type: application/x-www-form-urlencoded
>
* upload completely sent off: 19 out of 19 bytes ===== 请求正文
< HTTP/1.1 200 OK ===== 状态行
< Accept-Ranges: bytes ===== 响应头,从此处开始的若干行
< Cache-Control: max-age=1
< Connection: Keep-Alive
< Content-Length: 7382
< Content-Type: text/html
< Date: Wed, 29 Jan 2020 13:57:28 GMT
< Etag: "1cd6-5480030886bc0"
< Expires: Wed, 29 Jan 2020 13:57:29 GMT
< Last-Modified: Wed, 08 Feb 2017 07:55:35 GMT
< P3p: CP=" OTI DSP COR IVA OUR IND COM "
< Server: Apache
< Set-Cookie: BAIDUID=4C832C030A4D45EE53C111F5FEE4B72C:FG=1; expires=Thu, 28-Jan-21 13:57:28 GMT; max-age=31536000; path=/; domain=.baidu.com; version=1
< Vary: Accept-Encoding,User-Agent
<
从略... ===== 响应正文
* Connection #0 to host www.baidu.com left intact
* Closing connection 0
9.1.4.2. HTTP 状态
9.1.4.3. HTTPS
HTTPS即 HTTP 下加入 SSL 层,其端口号是443。
CDN一般包含分发服务系统、负载均衡系统和管理系统:
slf4j 的全称是 Simple Loging Facade For Java,即它仅仅是一个为 Java 程序提供日志输出的统一接口,并不是一个具体的日志实现方案。
slf4j 在底层整合了众多日志框架(logback, log4j等),可以方便的切换底层的日志框架,而不用修改代码。
Log4j 是 Apache 的一个开源项目,是目前最流行的日志框架之一。
它使用Logger来控制启用哪些日志并设置级别,使用Appenders指定输出端,使用Layout控制日志格式。
Logback 被认为是 Log4J 的继承人。
Logback原生实现了 SLF4J API(Log4J 还需要有一个中间转换层),并且速度更快,测试更充分,文档更齐全。
还提供了配置文件自动热加载,透明和快速的从I/O错误中恢复,自动压缩字和删除旧日志等更多功能。
ELK 是软件集合 Elasticsearch、Logstash、Kibana 的简称,由这三个软件及其相关的组件可以打造大规模日志实时处理系统。
Zookeeper 是一个分布式协调服务,可用于服务发现,配置管理等。
一个 Zookeeper 集群同一时间只会有一个实际工作的 Leader,它会发起并维护与各 Follwer 及 Observer 间的心跳。所有的写操作必须要通过 Leader 完成再由 Leader 将写操作广播给其它服务器进行同步,只要有超过半数节点写入成功,该写请求就会被提交。
一个 Zookeeper 集群可能同时存在多个 Follower,负责直接处理并返回客户端的读请求,写请求需要转发给 Leader 处理,并在 Leader 处理写请求时对请求进行投票。
Observer 与 Follower 类似,但是无投票权。加入更多 Observer 节点,提高伸缩性,同时不影响吞吐率。
Zookeeper 的核心是原子广播,这个机制保证了各个 server 之间的同步。实现这个机制的协议叫做 Zab 协议。
Zab 协议有两种模式,它们分别是恢复模式和广播模式。
当服务启动或者在领导者崩溃后,Zab 就进入了恢复模式,当领导者被选举出来,且大多数 server 的完成了和 leader 的状态同步以后,恢复模式就结束了,就进入了广播模式。
当 leader 崩溃或者 leader 失去大多数的 follower,这时候 zk 进入恢复模式,无法提供正常服务,直到恢复模式结束,此过程大约需要30s或者更多。
Kafka 是一种高吞吐量、分布式、基于发布/订阅的消息系统。
partition的数据文件包含属性:offset,MessageSize,data。
数据文件分段segment存储,顺序读写,二分查找定位Message。
数据文件索引是分段索引,稀疏存储保存在内存中。
负载均衡:partition会均衡分布到不同broker(Kafka服务器)上。
一次请求批量发送消息。
支持压缩消息(GZIP或Snappy)。
同一 Consumer Group 中的多个 Consumer 实例,不同时消费同一个 partition。
RabbitMQ 是一个由 Erlang 语言开发的 AMQP(Advanced Message Queue,高级消息队列协议) 的开源实现。
Exchange 交换器,负责将消息路由给队列,然后消费者从队列中获取消息。
目前有4种Exchange类型:
Hbase 是一个通过大量廉价的机器解决海量数据的高速存储和读取的分布式数据库解决方案。
HDFS 为 Hbase 提供可靠的底层数据存储服务,MapReduce 为 Hbase 提供高性能的计算能力,Zookeeper 为 Hbase 提供稳定服务和 Failover 机制。
Hbase 是根据列族来存储数据的。
MongoDB 是一个基于分布式的面向文档存储的开源数据库系统。
Apache Cassandra 是高度可扩展的,高性能的分布式 NoSQL 数据库。
在一个有界网络中, 每个节点都随机地与其他节点通信,经过一番杂乱无章的通信,最终所有节点的状态都会达成一致。
因为 Gossip 不要求节点知道所有其他节点,因此又具有去中心化的特点,节点之间完全对等。
Gossip是弱一致性,是“最终一致性”,适合的领域有: 失败检测、 路由同步、Pub/Sub、动态负载均衡。
协调者(coordinator)将 write 请求发送到拥有对应 row 的所有 replica 节点,只要节点可用便获取并执行写请求。
写一致性级别(write consistency level)确定要有多少个 replica 节点必须返回成功的确认信息。
协调者首先与一致性级别确定的所有 replica 联系,获得请求的数据,然后使用含 最新数据的 replica 向 client 返回结果。
协调者在后台联系和比较来自其余拥有对应 row 的 replica 的数据,若不一致,会向过时的 replica 发写请求用最新的数据进行更新 read repair。
写请求分别到磁盘的 CommitLog 和内存的 MemTable, 并且 MemTable 的数据会不定时刷写到磁盘 SSTable 上。
Cassandra 中,无论是 insert 还是 remove 操作,都是在已有的数据后面进行追加,而不修改已有的数据。
这种设计称为 Log structured 存储,就是系统中的数据是以日志的形式存在的。
这种设计使得数据的写和删除效率极高,错误恢复简单;但是读的复杂度很高。
删除一个 column 其实只是插入一个关于这个 column 的墓碑(tombstone),并不直接删除原 有的 column。
cassandra 读取的数据是 memtable 中的数据和 SStables 中数据的合并结果。
定义一个用于创建产品的接口,由子类决定生产什么产品。
提供一个创建产品族的接口,其每个子类可以生产一系列相关的产品。
某个类只能生成一个实例,该类提供了一个全局访问点供外部获取该实例,其拓展是有限多例模式。
将一个复杂对象分解成多个相对简单的部分,然后根据不同需要分别创建它们,最后构建成该复杂对象。
将一个对象作为原型,通过对其进行复制而克隆出多个和原型类似的新实例。
将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。
动态的给对象增加一些职责,即增加其额外的功能。
为某对象提供一种代理以控制对该对象的访问。即客户端通过代理间接地访问该对象,从而限制、增强或修改该对象的一些特性。
为多个复杂的子系统提供一个一致的接口,使这些子系统更加容易被访问。
将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。
将对象组合成树状层次结构,使用户对单个对象和组合对象具有一致的访问性。
运用共享技术来有效地支持大量细粒度对象的复用。
定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的改变不会影响使用算法的客户。
定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。
多个对象间存在一对多关系,当一个对象发生改变时,把这种改变通知给其他多个对象,从而影响其他对象的行为。
提供一种方法来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。
把请求从链中的一个对象传到下一个对象,直到请求被响应为止。通过这种方式去除对象之间的耦合。
将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。
在不破坏封装性的前提下,获取并保存一个对象的内部状态,以便以后恢复它。
允许一个对象在其内部状态发生改变时改变其行为能力。
在不改变集合元素的前提下,为一个集合中的每个元素提供多种访问方式,即每个元素有多个访问者对象访问。
定义一个中介对象来简化原有对象之间的交互关系,降低系统中对象间的耦合度,使原有对象之间不必相互了解。
提供如何定义语言的文法,以及对语言句子的解释方法,即解释器。
18.1.1.1. 四层负载均衡(目标地址和端口交换)
例如,nginx,F5,lvs,haproxy
18.1.1.2. 七层负载均衡(内容交换)
例如,haproxy,nginx,apache,Mysql proxy
18.1.2.1. 轮循均衡(RoundRobin)
18.1.2.2. 权重轮循均衡(WeightedRoundRobin)
18.1.2.3. 随机均衡(Random)
18.1.2.4. 权重随机均衡(WeightedRandom)
18.1.2.5. 响应速度均衡(ResponseTime探测时间)
18.1.2.6. 最少连接数均衡(LeastConnection)
18.1.2.7. 处理能力均衡(CPU、内存等换算)
18.1.2.8. DNS响应均衡(FlashDNS)
18.1.2.9. 哈希算法
18.1.2.10. IP 地址散列(保证客户端服务器对应关系稳定)
18.1.2.11.URL 散列
LVS(Linux Virtual Server) 的 IP 负载均衡技术是通过 IPVS 模块来实现的,IPVS虚拟出一个 IP 地址 VIP,即 Virtual IP,访问的请求首先经过 VIP 到达负载调度器,然后由负载调度器从 Real Server 列表中选取一个服务节点响应用户的请求。
调度器转发请求到后端Real Server的模式有以下几种:
Keepalive加入了 vrrp(virtual router redundancy protocal, 虚拟路由器冗余协议) 的功 能,因此,一方面具有 LVS cluster node healthcheck 功能,另一方面也具有 LVS director failover。
LVS 实现的功能只是对请求数据包的转发、传递,Real Server 看到的请求还是来自客户端的真实用户;反向代理服务器在接收访问用户请求后,会代理用户重新发起请求代理下的 Real Server, 最后把数据返回给客户端用户,Real Server 看到的请求是来自代理服务器。
Nginx 采用的是多进程(单线程) & 多路IO复用模型,会有一个 master 进程,它fork出来多个相互独立的 worker 子进程,worker 进程数,一般会设置成机器 cpu 核数。
HAProxy提供高可用性、负载均衡以及基于TCP和HTTP应用的代理,支持虚拟主机,它是免费、快速并且可靠的一种解决方案。
HAProxy实现了一种非阻塞,事件驱动, 单一进程模型,此模型支持非常大的并发连接数。支持多进程,但官方不推荐,因为每个进程有自己的内存区域会造成一些问题。
数据库存储引擎是数据库底层软件组织,不同的存储引擎提供不同的存储机制、索引技巧、锁定水平等功能:
第一范式(1st NF -列都是不可再分):确保每列的原子性
第二范式(2nd NF-非主键列不存在对主键的部分依赖):每个表只描述一件事情
第三范式(3rd NF- 不存在对非主键列的传递依赖)
事务(TRANSACTION)是一个不可分割的工作逻辑单元,具备ACID属性:原子性、一致性、隔离性、永久性。
存储过程经过第一次编译后再次调用不需要再次编译。注意点如下:
触发器是当对某一个表进行操作时触发,是一种特殊的存储过程。
19.1.7.1. 乐观锁
乐观锁直到你准备提交所作的更改时才将对象锁住,当你读取以及改变该对象时并不加锁。
19.1.7.2. 悲观锁
悲观锁开始读取以及改变该对象之前就将对象锁住,并且直到提交了所作的更改之后才释放锁。
悲观锁所说的加“锁”,分别是:排它锁(写锁)和共享锁(读锁)。
19.1.7.3. 时间戳
时间戳不使用锁机制,它在数据库表中单独加一列时间戳,比如“TimeStamp”,每次读出来的时候,把该字段也读出来,当写回去的时候,把该字段加 1,提交之前 ,跟数据库的该字段比较一次,如果比数据库的值大的话,就允许保存,否则不允许保存。
又分为:行级锁、表级锁、页级锁
两阶段提交协议是分布式事务的一种算法,参与者将操作成败通知协调者,再由协调者根据所有参与者的反馈情报决定各参与者是否要提交操作还是中止操作,分为准备阶段和提交阶段。
与两阶段提交不同的是:
柔性事务遵循BASE理论,包括 基本可用(Basically Available)、柔性状态(Soft State)、最终一致性 (Eventual Consistency)。分为以下几种:
CAP 原则指的是在一个分布式系统中, Consistency(一致性)、 Availability (可用性)、Partition tolerance(分区容错性),三者不可兼得。
在分布式系统中,为保证每个节点执行相同的命令序列,需要在每一条指令上执 行一个“一致性算法”以保证每个节点看到的指令一致。
Paxos 算法解决的问题是一个分布式系统如何就某个值(决议)达成一致。其过程分为两个阶段:
Zab( ZooKeeper Atomic Broadcast , ZooKeeper 原子消息广播协议)协议,会经历三个步骤达到消息广播状态:
Raft 和 Paxos 一样只要保证 n/2+1 节 点正常就能够提供服务。
Raft 的选举由定时器来触发,每个节点中定时器的时间都是随机的。Safety 就是用于保证选举出来的 Leader 一定包含先前 commited Log 的机制。当请求投票的该 Candidate 的 Term 较大 或 Term 相同 Index 更大则投票,从而,拥有最新的log的follower会成为新的leader。
N: 在分布式存储系统中,有多少份备份数据
W: 代表一次成功的更新操作要求至少有 w 份数据写入成功
R: 代表一次成功的读数据操作要求至少有 R 份数据成功读取
NWR 值的不同组合会产生不同的一致性效果。根据鸽巢原理,当且仅当 W+R>N 的时候,整个系统对于客户端来讲才能保证强一致性。
Gossip 算法又被称为反熵(Anti-Entropy),形象地说明了其特点:在一个有界网络中,每个节点都随机地与其他节点通信,经过一番杂乱无章的通信,最终所有节点的状态都会达成一致。
一致性哈希算法(Consistent Hashing Algorithm)是一种分布式算法,常用于负载均衡。
具有以下特点:
实现原理:
为了解决分布不够均匀和多米诺宕机的问题,引入了虚拟节点:
虚拟节点( virtual node )是实际节点在 hash 空间的复制品( replica ),一个实际节点对应了若干个“虚拟节点”,“虚拟节点”在 hash 空间中以 hash 值排列。
映射关系也变为了:对象 -> 虚拟节点 -> 实际节点。
定义一个间隔序列来表示排序过程中进行比较的元素之间有多远的间隔,每次将具有相同间隔的数分为一组,进行插入排序;间隔序列里的值单向递减,最终到1,例如:[n/2, n/4, /n8, …, 1]。 桶排序(Bucket sort),将数组按照递增的数值区间依次分到有限数量的桶里。每个桶再分别排序(其他排序算法或者递归桶排序),最后依次把各个桶中的记录列出来就得到有序序列。 桶排序和基数排序都是分配排序(Distributive Sort),分配排序的基本思想:排序过程无须比较关键字,而是通过“分配”和“收集”过程来实现排序。所以,他们不受到O(n log n)下限的影响。 将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后,数列就变成一个有序序列。 常用的构造散列函数的方法有: 缓存雪崩是因为缓存大面积地失效,从而导致所有请求都会去查数据库,导致数据库、CPU和内存负载过高,甚至宕机,进而造成一系列连锁反应,整个系统崩溃。 缓存穿透是指用户查询数据,在数据库和缓存都没有,这就导致进行了两次无用的查询。 缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。 当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然 需要保证核心服务还是可用的,即使是有损服务。 HADOOP 就是一个大数据解决方案。它提供了一套分布式系统基础架构。 核心内容包含 hdfs 和 mapreduce,hdfs 是提供数据存储的,mapreduce 是方便数据计算的。 Spark 提供了一个全面、统一的框架用于管理各种有着不同性质(文本数据、图表数据等)的数据集和数据源(批量数据或实时的流数据)的大数据处理的需求。 Spark 的核心是建立在统一的抽象弹性分布式数据集(Resiliennt Distributed Datasets,RDD)之上的,这使得 Spark 的各个组件,如SQLContext、HiveContext、StreamingContext可以无缝地进行集成,能够在同一个应用程序中完成大数据处理。 Storm 是一个免费并开源的分布式实时计算系统。利用 Storm 可以很容易做到可靠地处理无限的 数据流,像 Hadoop 批量处理大数据一样,Storm 可以实时处理数据。 在 Storm 中,一个实时应用的计算任务被打包作为 Topology 发布,计算任务 Topology 是由多个 Spouts 和 Bolts,通过数据流(Stream)连接起来的图。spout 会从外部数据源中读取数据,然后转 换为 topology 内部的源数据,接着,数据以 tuple(一组消息的单元) 的方式发送到 bolt,bolt 执行用户想要的操作,多个 bolt 可以相互连接起来,每个bolt可以有多个输入和输出。 YARN 是一个资源管理、任务调度的框架,主要包含三大模块: ResourceManager(RM)、 NodeManager(NM)、ApplicationMaster(AM)。 在进行逐步应答过程中,典型的决策树分析会使用分层变量或决策节点,例如,可将一个给定用户分类成信用可靠或不可靠。 随机森林算法通过使用多个带有随机选取的数据子集的树(tree)改善了决策树的精确性。例如,在基因表达层面上考察大量与乳腺癌复发相关的基因,并计算出复发风险。 回归可以勾画出因变量与一个或多个因变量之间的状态关系。例如,将垃圾邮件和非垃圾邮件进行区分。 朴素贝叶斯分类器用于计算可能条件的分支概率。每个独立的特征都是「朴素」或条件独立的,因此它们不会影响别的对象。 例如,在一个装有共 5 个黄色和红色小球的罐子里,连续拿到两个黄色小球的概率是多少?从图中最上方分支可见,前后抓取两个黄色小球的概率为 1/10。朴素贝叶斯分类器可以计算多个特征的联合条件概率。 在任意神经网络中,每个神经元都通过 1 个或多个隐藏层来将很多输入转换成单个输出。循环神经网络(RNN)会将值进一步逐层传递,让逐层学习成为可能。换句话说,RNN 存在某种形式的记忆,允许先前的输出去影响后面的输入。 显马尔可夫过程是完全确定性的——一个给定的状态经常会伴随另一个状态。交通信号灯就是一个例子。相反,隐马尔可夫模型通过分析可见数据来计算隐藏状态的发生。随后,借助隐藏状态分析,隐马尔可夫模型可以估计可能的未来观察模式。例如,高或低气压的概率(这是隐藏状态)可用于预测晴天、雨天、多云天的概率。 SaaS 是 Software-as-a-Service(软件即服务) 云计算时代把服务器平台或者开发环境作为服务进行提供就成为了 PaaS(Platform as a Service,平台即服务)。 IaaS(Infrastructure as a Service),即基础设施即服务。 通过 Docker 我们可以将程序运行的环境也纳入到版本控制中,排除因为环境造成开发环境和生产环境环境不同运行结果的可能。 docker 创建新进程时传入 CLONE_NEWPID 实现的进程隔离,也就是使用 Linux 的命名空间实现 进程的隔离,Docker 容器内部的任意进程都对宿主机器的进程一无所知。 当镜像被 docker run 命令创建时就会在镜像的最上层添加一个可写的层,也就是容器层。容器和镜像的区别就在于,所有的镜像都是只读的,而每一个容器其实等于镜像加上一个可读写的层。 OpenStack作为一个开源的云计算平台,利用虚拟化技术和底层存储服务,提供了可扩展,灵活,适应性强的云计算服务。 Kubernetes简称K8s,是容器管理编排引擎,那么底层实现自然是容器技术。它支持自动化部署、大规模可伸缩、应用容器化管理。在Kubernetes中,我们可以创建多个容器,每个容器里面运行一个应用实例,然后通过内置的负载均衡策略,实现对这一组应用实例的管理、发现、访问,而这些细节都不需要运维人员去进行复杂的手工配置和处理。
空间复杂度21.1.2. 归并排序算法
21.1.3. 桶排序算法
当要被排序的数组内的数值是均匀分配的时候,桶排序使用线性时间O(n),但空间复杂度比较高。21.1.4. 基数排序算法
实际操作时,是将依次将个位、十位、百位的相同的数放入同一个桶中,高位不足补零。然后再从桶中收集元素。
在大多数情况下,效率为O(n*logn),最好情况是O(n)。空间复杂度较高。21.1.5. 剪枝算法
21.1.6. 回溯算法
21.1.7. 最短路径算法
21.1.8. 最大子数组算法
21.1.9. 最长公共子序算法
21.1.10. 最小生成树算法
22. 数据结构
22.1.1. 栈(stack)
22.1.2. 队列(queue)
22.1.3. 链表(Link)
22.1.4. 散列表(Hash Table)
22.1.5. 排序二叉树
22.1.6. 红黑树
22.1.7. B-TREE
22.1.8. 位图
23. 加密算法
23.1.1. AES
23.1.2. RSA
23.1.3. CRC
23.1.4. MD5
24. 分布式缓存
24.1.1. 缓存雪崩
24.1.2. 缓存穿透
24.1.3. 缓存预热
24.1.4. 缓存更新
24.1.5. 缓存降级
25. HADOOP
25.1.1. 概念
25.1.2. HDFS
25.1.3. MapReduce
26. SPARK
27. STORM
28. YARN
28.1.1. 概念
其中,ResourceManager 负责所有资源的监控、分配和管理; ApplicationMaster 负责每一个具体应用程序的调度和协调; NodeManager 负责每一个节点的维护。对于所有的 applications,RM 拥有绝对的控制权和对资源的分配权。而每个 AM 则会和 RM 协商资源,同时和 NodeManager 通信来执行和监控 task。29. 机器学习
29.1.1. 决策树
29.1.2. 随机森林算法
29.1.3. 逻辑回归
29.1.4. SVM
29.1.5. 朴素贝叶斯
29.1.6. K 最近邻算法
29.1.7. K 均值算法
29.1.8. Adaboost 算法
29.1.9. 神经网络
29.1.10. 马尔可夫
30. 云计算
30.1.1. SaaS
30.1.2. PaaS
30.1.3. IaaS
提供给消费者的服务是对所有设施的利用,包括处理、存储、网络和其它基本的计算资源,用户能够部署和运行任意软件,包括操作系统和应用程序。30.1.4. Docker
Docker 整个网络部分的功能都是通过 Docker 拆分出来的 libnetwork 实现的,它提供了一个连接不同容器的实现,同时也能够为应用给出一个能够提供一致的编程接口和网络层抽象的容器网络模型。
Control Groups(简称 CGroups)能够隔离宿主机器上的物理资源,例如 CPU、内存、磁盘 I/O 和网 络带宽。30.1.5. Openstack和Kubernetes
简单的说,虚拟化使得在一台物理的服务器上可以跑多台虚拟机,虚拟机共享物理机的CPU、内存、IO硬件资源,但逻辑上虚拟机之间是相互隔离的。宿主机一般使用hypervisor程序实现硬件资源虚拟化,并提供给客户机使用。
容器是一种轻量级、可移植、自包含的软件打包技术,打包的应用程序可以在几乎任何地方以相同的方式运行。以容器典型代表docker为例,docker起源于2013年3月,是基于LXC为基础构建的容器引擎,通过namespace和cgourp实现了资源隔离和调配,使用分层存储来构建镜像。它基于Google公司推出的Go语言实现。docker相比KVM虚拟化技术最明显的特点就是启动快,资源占用小。虚拟化启动虚拟机是分钟级别的,而docker是秒级别的。