提示:写文章的时候,我还在找工作!!!
提示:学了这些也找不到工作,计算机真的崩了,如果看到这篇文章,别干java,别干大数据,肺腑之言。
OOP(Object Oriented Programming)即面向对象编程,面向对象的三大特征:封装、继承、多态
方法的重载是编译时多态,方法的重写是运行时多态
重载:重载发生在一个类中,同名的方法,不同的参数类型或者不同的参数个数
重写:重写发送在子类和父类之间,要求子类的重写方法和父类的被重写方法的方法名字、方法参数个数、方法类型、返回类型相同,访问权限不能比父类的低。
java基本类型八个,六种数字类型(四个整数型,两个浮点型),一种字符串型,还有一种布尔型。
byte、short、int、long、float、double、boolean、char
注意:string是引用类型
String:字符串由final修饰的常量,存在常量池中,所以他是线程安全的。
StringBuffer:它使用了synchronize关键字,对方法进行了同步处理,所以线程是安全的。
StringBuilder:执行效率虽然高,但是因为线程不安全,所以不建议在多线程的环境下对同一个StringBuilder对象进行操作
抽象类(abstract):需要子类继承、单继承、可以有构造方法,访问修饰符可以有public,protected和default、可以有成员变量,可以有main方法。
接口(interface):需要子类实现接口、多实现、不能有构造方法,访问修饰符只能是public、只能有常量,不能有main方法
底层是通过 Integer.valueOf()和 Integer.intValue() 方法实现 。 Integer 的默认值是
null, int 的默认值是 0
所以在[-128,127]区间内,==比较的时候,值总是相等的(指向的是同一对象),在这个区间外是不等的,
//① 在-128~127之外的数 false
Integer i1 =200;
Integer i2 =200;
System.out.println("i1==i2:"+(i1==i2));
//② 在-128~127之内的数 true
Integer i3 =100;
Integer i4 =100;
System.out.println("i3==i4:"+(i3==i4));
//③ 不使用自动装箱功能时 false
Integer i5 = new Integer(100);
Integer i6 = new Integer(100);
System.out.println("i5==i6:"+(i5==i6));
//与int比较的时候数值相同则始终是true 因为java自动拆箱功能
==:如果比较的是基本数据类型, 那么比较的是变量的值 如果比较的是引用数据类型, 那么比较的是地址值 (两个对象是否指向同一块内 存)
equals:如果没重写equals方法比较的是两个对象的地址值
如果重写了比较的是内容,equals是从object类中继承的,默认实现方法是双等
final修饰符(关键字):被final修饰的类,就意味着不能再派生出新的子类,不能作为父类而被子类继承。因此一个类不能既被abstract声明,又被final声明。将变量或方法声明为final,可以保证他们在使用的过程中不被修改。被声明为final的变量必须在声明时给出变量的初始值,而在以后的引用中只能读取。被final声明的方法也同样只能使用,即不能方法重写。
finally是在异常处理时提供finally块来执行任何清除操作。不管有没有异常被抛出、捕获,finally块都会被执行。try块中的内容是在无异常时执行到结束。catch块中的内容,是在try块内容发生catch所声明的异常时,跳转到catch块中执行。finally块则是无论异常是否发生,都会执行finally块的内容,所以在代码逻辑中有需要无论发生什么都必须执行的代码,就可以放在finally块中。
finalize是方法名。java技术允许使用finalize()方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的。它是在object类中定义的,因此所有的类都继承了它。子类覆盖finalize()方法以整理系统资源或者被执行其他清理工作。finalize()方法是在垃圾收集器删除对象之前对这个对象调用的
protected Object clone()—>创建并返回此对象的一个副本 。
boolean equals(Object obj)—>指示某个其他对象是否与此对象“相等
protected void finalize()—>当垃圾回收器确定不存在对该对象的更多引用时, 由 对象的垃圾回收器调用此方法 。
Class getClass()—>返回一个对象的运行时类。
int hashCode()—>返回该对象的哈希码值 。 void notify()—>唤醒在此对象监视器上等待的单个线程。
void notifyAll()—>唤醒在此对象监视器上等待的所有线程。 String toString()—>返回该对象的字符串表示
ArrayList 是实现了基于动态数组的数据结构, LinkedList 基于链表的数据结构
对于随机访问 get 和 set, ArrayList 效率优于 LinkedList, 因为 LinkedList 要移 动指针 。
对于新增和删除操作 add 和 remove, LinkedList 比较占优势, 因为 ArrayList 要 移动数据 。 这一点要看实际情况的。若只对单条数据插入或删除, ArrayList 的速 度反而优于 LinkedList 。但若是批量随机的插入删除数据, LinkedList 的速度大大 优于 ArrayList. 因为 ArrayList 每插入一条数据, 要移动插入点及之后的所有数 据 。
HashMap 底层是 数组+链表+红黑树 数组 Node
[] table ,哈希表, 根据对象的 key 的hash 值判断在数组里面是哪个节点
链表的作用是解决 hash 冲突, 将 hash 值取模之后的对象存在一个链表放在 hash值对应的槽位
红黑树 JDK8 使用红黑树来替代超过 8 个节点的链表,发送哈希碰撞,会带来链化,效率会变低,引入红黑树会提高查找效率从原来的 O(n)到 O(logn)
当链表长度大于8,且总数据量大于64的时候,链表就会转化成红黑树,
当前存入数据大于阈值即发生扩容或者存入数据到某一条链表时,此时该链表数据个数大于8,且数组总数量小于64即发生扩容每次扩容为初始容量的2倍
HashTable 是线程安全的,而 HashMap 不是。
HashMap 的性能要比 HashTable 更好,因为HashTable 采用了全局同步Synchronized来保证安全性,对性能影响较大
HashMap 只有 containsValue 和 containsKey 方法; HashTable 有 contains 、 containsKey 和 containsValue 三个方法, 其中 contains 和 containsValue 方法功 能相同 。
Hashtable 中, key 和 value 都不允许出现 null 值 。HashMap 中, null 可以作为 键, 这样的键只有一个; 可以有一个或多个键所对应的值为 null。
HashTable 在不指定容量的情况下的默认容量为 11, 而 HashMap 为 16 , Hashtable 不要求底层数组的容量一定要为 2 的整数次幂, 而 HashMap 则要求一 定为 2 的整数次幂。
Hashtable 扩容时, 将容量变为原来的 2 倍加 1, 而 HashMap 扩容时, 将容量变 为原来的 2 倍 。
线程的5种状态: 新建状态、就绪状态、运行状态、阻塞状态、死亡状态
1、继承 Thread 类创建线程
2、实现 Runnable 接口创建线程
3、实现callable接口和创建Future类创建线程,有返回值
4、使用线程池创建线程
5、使用匿名线程创建
java.lang.NullPointerException 空指针异常; 出现原因: 调用了未经初始化的对象或者是不存在的对象 。
java.lang.ClassNotFoundException 指定的类找不到; 出现原因: 类的名称和路径加载错误; 通常都是程序试图通过 字符串来加载某个类时可能引发异常 。
java.lang.NumberFormatException字符串转换为数字异常; 出现原因: 字符型数据中包含非数字型字符 。
java.lang.IndexOutOfBoundsException 数组角标越界异常, 常见于操作数组对象时发生 。
java.lang.IllegalArgumentException 方法传递参数错误
java.lang.ClassCastException 数据类型转换异常。
在运行状态下,任何一个类,你都能知道它的所有方法和属性,对任何一个对象,你都能调用它的方法和属性,这种动态调用和获取的方法叫做反射机制
获取Class对象的三种方式
1 Object ——> getClass();
2 任何数据类型(包括基本数据类型)都有一个“静态”的class属性
3 通过Class类的静态方法:forName(String className)(常用)
序列化是一种用来处理对象流的机制, 所谓对象流也就是将对象的内容进行流 化 。可以对流化后的对象进行读写操作, 也可将流化后的对象传输于网络之间
序列化的实现: 将需要被序列化的类实现 Serializable 接 口, 该接口没有 需 要 实 现 的 方 法, implements Serializable 只是为了标注该对象是可被序列化的,然后使用一个输出流(如: FileOutputStream)来构造一个 ObjectOutputStream(对象流) 对象, 接着, 使用 ObjectOutputStream 对象的 writeObject(Object obj)方法就可以将 参数为 obj 的对象写出(即保存其状态), 要恢复的话则用输入流。
乐观锁:采取在写时先读出当前版本号,然后加锁操作(比较跟上一次的版本号,如果一样则更新),如果失败则要重复读-比较-写的操作
悲观锁:每次去拿数据的时候都认为别人会修改,所以每次在读写数据的时候都会上锁,这样别人想读写这个数据就会 block 直到拿到锁
自旋锁:线程不需要进入阻塞状态,直接在自旋,等待锁释放直接获取,减少线程切换的消耗,但自旋消耗cpu。
非公平锁:JVM 随机就近原则分配锁的机制则称为不公平锁,非公平锁实际执行的效率要远超公平锁,除非程序有特殊需要,否则最常用非公平锁的分配机制。
公平锁:公平锁指的是锁的分配机制是公平的,通常先对锁提出获取请求的线程会先被分配到锁加锁前检查是否有排队等待的线程,优先排队等待的线程,先来先得
同步锁: 当多个线程同时访问同一个数据时,很容易出现问题。为了避免这种情况出现,我们要保证线程同步互斥,就是指并发执行的多个线程。在同一时间内只允许一个线程访问共享数据
死锁:就是多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。以下是死锁的4个必要条件,防止死锁只要破坏其中一条
1、互斥条件:一个资源每次只能被一个线程使用
2、不可剥夺条件:一个线程已经获得的资源, 在未使用完之前, 不能被强行剥夺
3、请求和保持条件:一个线程在阻塞等待某个资源时, 不释放已占有资源
4、循环等待条件:若干线程形成头尾相接的循环等待资源关系
锁优化通常可以:减小锁粒度比如分段锁,实现锁分离,让读写分离,实现锁粗化让锁的时间尽量短
Synchronized,它就是一个:非公平,悲观,独享,互斥,可重入的重量级锁
ReentrantLock,它是一个:默认非公平但可实现公平的,悲观,独享,互斥,可重入,重量级锁。
ReentrantReadWriteLocK,它是一个,默认非公平但可实现公平的,悲观,写独享,读共享,读写,可重入,重量级锁。
sleep:不释放锁资源
wait:释放锁资源,一值等待需要notify唤醒,必须配合 synchronized 一起使用, 不然在运行时就会抛出 IllegalMonitorStateException 异常
1、方法区(永久代):存储类的信息、常量、静态变量等,被线程共享
2、虚拟机栈(java栈):存储局部变量和引用、运算结果和运算操作数、动态链接(将常量池中的符号引用在运行期转化为直接引用)、方法出口等信息,不被线程共享
3、本地方法栈:本地方法栈和虚拟机栈类似, 只不过本地方法栈为 Native 方法服务
4、堆:java 堆是所有线程所共享的一块内存, 在虚拟机启动时创建, 几乎所有的对象实例 都在这里创建, 因此该区域经常发生垃圾回收操作 。
5、程序计数器:内存空间小, 字节码解释器工作时通过改变这个计数值可以选取下一条需要执行的字节码指令, 分支 、循环 、跳转 、异常处理和线程恢复等功能都需要依赖这个计数 器完成 。该内存区域是唯一一个 java虚拟机规范没有规定任何 OOM 情况的区域 。
采用分区分代回收思想:
1、复制算法:年轻代中使用的是 Minor GC, 这种 GC 算法采用的是复制算法 (Copying),效率高,内存占用大,使用在占空间比较小 、刷新次数多的新生区
2、标记清除法:老年代一般是由标记清除或者是标记清除与标记整理的混合实现,效率比较低, 会差生碎片。
3、标记整理法:效率低速度慢,需要移动对象,但不会产生碎片 。
cms:
CMS采用 标记-清理 的算法,标记出垃圾对象,清除垃圾对象。算法是基于老年代执行的,因为新生代产生无法接受该算法产生的碎片垃圾。优点:并发收集,低停顿不足:无法处理浮动垃圾,并发收集会造成内存碎片过多由于并发标记和并发清理阶段都是并发执行,所以会额外消耗CPU资源
g1:
G1的出现就是为了替换jdk1.5种出现的CMS,这一点已经在jdk9的时候实现了,jdk9默认使用了G1回收器,移除了所有CMS相关的内容。G1和CMS相比,有几个特点:控制回收垃圾的时间:这个是G1的优势,可以控制回收垃圾的时间,还可以建立停顿的时间模型,选择一组合适的Regions作为回收目标,达到实时收集的目的空间整理:和CMS一样采用标记-清理的算法,但是G1不会产生空间碎片,这样就有效的使用了连续空间,不会导致连续空间不足提前造成GC的触发G1把Java内存拆分成多等份,多个域(Region),逻辑上存在新生代和老年代的概念,但是没有严格区分
1、引用计数法:每个对象设置一个计数器,每当有一个对象引用+1,失效后-1,为0时没有引用被回收,无法解决循环依赖问题。
2、可达性算法(引用链法):提供gcroots的对象作为起点,向下搜索,当一个对象到gcroots没有任何引用时,证明不可达,回收掉
线程池是事先将线程放在同一个容器中,当使用的时候不用new线程,而是去线程池中拿线程,节省了线程开销,提供了代码效率。
1、newCachedThreadPool 创建一个可缓存线程池,如果线程池长度超过处理需要, 可灵活回收空闲线程, 若无可回收, 则新建线程。
ExecutorServicenewCachedThreadPool=Executors.newCachedThreadPool();
2、newFixedThreadPool 创建一个指定工作线程数量的线程池 。每当提交一个任务就创建一个工作线程, 如果工作线程数量达到线程池初始的最大数, 则将提交的任务存入到池队列中 。
ExecutorServicenewFixedThreadPool=Executors.newFixedThreadPool(4);
3、newSingleThreadExecutor 创建一个单线程化的 Executor, 即只创建唯一的工作者线程来执行任务, 它只会用唯一的工作线程来执行任务, 保证所有任务按照指定顺序。
ExecutorServicenewSingleThreadExecutor=Executors.newSingleThreadExecutor();
4、newScheduleThreadPool 创建一个定长的线程池, 而且支持定时的以及周期性的任务执行 。例如延迟 3 秒 执行。
ScheduledExecutorService newScheduledThreadPool =Executors.newScheduledThreadPool(4);
饿汉式:是立即加载的方式,无论是否会用到这个对象,都会加载。
public class GiantDragon {
//私有化构造方法使得该类无法在外部通过new 进行实例化
private GiantDragon(){
System.out.println("私有化构造方法");
}
//准备一个类属性,指向一个实例化对象。 因为是类属性,所以只有一个
private static GiantDragon instance = new GiantDragon();
//public static 方法,提供给调用者获取12行定义的对象
public static GiantDragon getInstance(){
return instance;
}
}
懒汉式:是延迟加载的方式,只有使用的时候才会加载。并且有线程安全的考量。用双端检测其实是因为每次访问只有在第一次访问的时候才要加锁,其他时间直接判断是不是null就返回。
public class GiantDragon2 {
//GiantDragon2 进行实例化
private GiantDragon2(){
System.out.println("私有化构造方法");
}
//准备一个类属性,用于指向一个实例化对象,但是暂时指向null
private static GiantDragon2 instance;
//public static 方法,返回实例对象 (非线程安全的,不推荐使用该写法)
public static GiantDragon2 getInstance(){
//第一次访问的时候,发现instance没有指向任何对象,这时实例化一个对象
if(null==instance){
instance = new GiantDragon2();
}
//返回 instance指向的对象
return instance;
}
// 线程安全的,并且通过非空判断提升性能(因为如果只上锁,那么每次调用的时候都会上锁,事实上只有第一次创建对象的时候才需要加锁)
public static GiantDragon2 getInstance(){
if(instance == null){
synchronized(instance){
//第一次访问的时候,发现instance没有指向任何对象,这时实例化一个对象
if(null==instance){
instance = new GiantDragon2();
}
}
}
//返回 instance指向的对象
return instance;
}
//以上线程安全的写法在java单例设计模式中并非完美的写法,因为在JVM执行过程中可能会存在问题,感兴趣的小伙伴可以去找一下相关的资料。
}
1、线程池有任务时,创建线程,也可以调用prestartAllCoreThreads() 或 者 prestartCoreThread() 方法预创建 corePoolSize 个线程
2、调用execute()提交任务时,如果当前工作线程数3、如果工作线程数量>corepoolsize,会将任何放入队列中缓存
4、如果队列已满,并且线程数量5、如果队列已满,达到了maximumpoolsize,这个时候执行拒绝策略,java默认拒绝策略是abortpolicy,直接抛出异常
拒绝策略有:
1、 AbortPolicy: 直接抛出异常, 默认策略;
2、CallerRunsPolicy: 用调用者所在的线程来执行任务;
3、DiscardOldestPolicy: 丢弃阻塞队列中靠最前的任务, 并执行当前任务;
4、DiscardPolicy: 直接丢弃任务; 当然也可以根据应用场景实现 RejectedExecutionHandler 接口, 自定义饱和策略, 如记录日志或持久化存储不能处 理的任务
线程池设定最佳线程数目 = ( (线程池设定的线程等待时间+线程 CPU 时间)/线程 CPU 时间 ) * CPU 数目
CopyOnWriteArrayList、CopyOnWriteArraySet、ConcurrentHashMap
CopyOnWriteArrayList、CopyOnWriteArraySet采用写时复制实现线程安全
ConcurrentHashMap 采用分段锁的方式实现线程安全
通过Collections类构造同步的List:
List< String > list = Collections.synchronizedList(new ArrayList<>());
CAS算法的作用:解决多线程条件下使用锁造成性能损耗问题的算法,保证了原子性,这个原子操作是由CPU来完成的
CAS的原理:CAS算法有三个操作数,通过内存中的值(V)、预期原始值(A)、修改后的新值。
(1)如果内存中的值和预期原始值相等,就将修改后的新值保存到内存中。
(2)如果内存中的值和预期原始值不相等,说明共享数据已经被修改,放弃已经所做的操作,然后重新执行刚才的操作,直到重试成功。
Java 的原子类都存放在并发包 java.util.concurrent.atomic 下:
AtomicInteger 类利用 CAS (Compare and Swap) + volatile + native 方法来保证
原子操作, 从而避免 synchronized 的高开销, 执行效率大为提升 。
synchronized原理:代码块的同步是利用 monitorenter 和 monitorexit 这两个字节码指令 。它们分别位于 同步代码块的开始和结束位置
Lock原理:
1、Lock 的存储结构: 一个 int 类型状态值 (用于锁的状态变更), 一个双向链表 (用于存储等待中的线程)
2、 Lock 获取锁的过程: 本质上是通过 CAS 来获取状态值修改, 如果当场没获取 到, 会将该线程放在线程等待链表中 。
3、 Lock 释放锁的过程: 修改状态值, 调整等待链表 。
4、Lock 大量使用 CAS+自旋 。因此根据 CAS 特性, lock 建议使用在低锁冲突的 情况下 。
synchronized不需要手动释放锁,lock需要在锁用完后进行unlock;
synchronized只能是默认的非公平锁,lock可以指定使用公平锁或者非公平锁;
lock提供的Condition(条件)可以指定唤醒哪些线程,而synchronized只能随机唤醒一个或者全部唤醒;
ConcurrentHashMap 是线程安全的 Map 容器, JDK8 之前, ConcurrentHashMap 使用 锁分段技术, 将数据分成一段段存储, 每个数据段配置一把锁, 即 segment 类, 这个 类继承 ReentrantLock 来保证线程安全, JKD8 的版本取消 Segment 这个分段锁数据 结构, 底层也是使用 Node 数组+链表+红黑树, 从而实现对每一段数据就行加锁, 也 减少了并发冲突的概率 。hashtable 类基本上所有的方法都是采用 synchronized 进行线程安全控制, 高并发情 况下效率就降低, ConcurrentHashMap 是采用了分段锁的思想提高性能, 锁粒度更细 化
volatile 是 Java 提供的最轻量级的同步机制, 保证了共享变量的可见性, 被 volatile 关键字修饰的变量, 如果值发生了变化, 其他线程立刻可见, 避免出现脏 读现象 。
volatile 禁止了指令重排, 可以保证程序执行的有序性, 但是由于禁止了指令重 排, 所以 JVM 相关的优化没了, 效率会偏弱
1、加载:提供类的全限定名将类加载成二进制字节
2、验证:确保class文件的字节流不会危害虚拟机
3、准备:为静态变量分配内存并初始化默认值
4、解析 :完成符合引用到直接引用的过程,可能在初始之后
5、初始化:真正执行java程序代码
类加载器就是把类文件加载到虚拟机中, 也就是说通过一个类的全限定名来获取描述 该类的二进制字节流 。
启动类加载器(BootstrapClassLoader)用来加载 java 核心类库, 无法被 java 程序 直接引用
扩展类加载器(extension class loader):它用来加载 Java 的扩展库 。Java 虚拟机的 实现会提供一个扩展库目录 。该类加载器在此目录里面查找并加载 Java类
系统类加载器 (system class loader) 也叫应用类加载器: 它根据 Java 应用的类 路径(CLASSPATH) 来加载 Java 类 。一般来说, Java 应用的类都是由它来完成加 载的 。可以通过ClassLoader.getSystemClassLoader()来获取它
用户自定义类加载器, 通过继承java.lang.ClassLoader 类的方式实现
&&:短路与 只计算左边第一个条件,不符合就不会继续计算第二个条件
&:逻辑与 2个条件都会计算到无论是否符合
||:短路或 只计算左边第一个条件,不符合就不会继续计算第二个条件
|:逻辑或 2个条件都会计算到无论是否符合
jps:
jstat:
jmap:
jhat:
jstack:
jinfo:
<<:左移,左边最高位丢失,右边补齐0 ,m<
>>:右移,最高位是0,左边补齐0;最高为是1,左边补齐1,m>>n代表m除以2的n次方,原来是正数的还是正数,负数还是负数,取整都是想下取整,-1.5会变成-2 ,1.5会变成1
>>>:无符号右移,同右移,但是结果全变正数。
BIO:同步并阻塞(传统阻塞型),一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销
NIO:同步非阻塞,服务器实现模式为一个线程处理多个请求(连接),即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有 I/O 请求就进行处理
AIO:异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理,一般适用于连接数较多且连接时间较长的应用
(1)、100匹马如何同时起跑,如何实现?
CountDownLatch或者CyclicBarrier
/**
*
*
Title:testCountDownLatch.java
* Description:
* Date:2020/10/20 14:53
*
* @author wsh
* @version 1.0
*/
public class testCountDownLatch {
CountDownLatch countDownLatch = new CountDownLatch(1);
private void runThread(){
ExecutorService executorService = Executors.newFixedThreadPool(10);
for(int i=0;i<10 ;i++){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
executorService.submit(new Runnable() {
@Override
public void run() {
try {
countDownLatch.await();
System.out.println("Thread:"+Thread.currentThread().getName()+",time: "+System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
countDownLatch.countDown();
}
public static void main(String[] args) {
testCountDownLatch test = new testCountDownLatch();
test.runThread();
}
}
/**
*
*
Title:CyclicBarrierTest.java
* Description: 做到10个线程同时start
* Date:2020/10/20 14:41
*
* @author wsh
* @version 1.0
*/
public class CyclicBarrierTest {
/**
* CyclicBarrier 适用再多线程相互等待,直到到达一个屏障点。并且CyclicBarrier是可重用的。
*/
CyclicBarrier cyclicBarrier = new CyclicBarrier(10);
private void runThread() {
ExecutorService executorService = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
executorService.submit(createThread(i));
}
}
private Thread createThread(int i) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
cyclicBarrier.await();
System.out.println("Thread:" + Thread.currentThread().getName() + "准备完毕,time:" + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
});
thread.setName("name" + i);
return thread;
}
public static void main(String[] args) {
CyclicBarrierTest test = new CyclicBarrierTest();
test.runThread();
}
}
原子性(Atomicity):事务过程中,如果其中操作出现错误,所有操作全部回滚,要么全嘎要么全做。
一致性(Consistency):事务开始和结束,事务完整约束没有被破坏。比如 A 向 B 转账, 不可能 A 扣了钱, B 却没收到
隔离性(Isolation):同一时间,只允许一个事务请求同一数据,不同的事务之间彼此没有干扰,比如 A 正在从一张银行卡中取钱, 在A 取钱的过程结束前, B 不能向这张卡转账。
持久性(Durability):事务完成后,数据保存好,无法回滚。
注意:RR的快照读是通过mvcc解决幻读问题但都不能完全解决,当前读是加了间隙锁解决幻读
脏读:事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到数据的脏数据。
不可重复读:事务A多次读同一数据,事务B在多次读取中修改了数据,事务A就出现了结果不一样。
幻读:事务A修改多个数据时,事务B插入了一条新的数据,此事出现了一条数据没有改过来,出现了幻读。
注意:不可重复读的和幻读很容易混淆, 不可重复读侧重于修改, 幻读侧重于新增或删除 。解决不可重复读的问题只需锁住满足条件的行, 解决幻读需要锁表。
InnoDB是聚集索引,使用B+Tree作为索引结构,数据文件是和(主键)索引绑在一起的(表数据文件本身就是按B+Tree组织的一个索引结构),必须要有主键,通过主键索引效率很高。但是辅助索引需要两次查询,先查询到主键,然后再通过主键查询到数据。因此,主键不应该过大,因为主键太大,其他索引也都会很大。使用的是行锁和表锁。
MyISAM是非聚集索引,也是使用B+Tree作为索引结构,索引和数据文件是分离的,索引保存的是数据文件的指针。主键索引和辅助索引是独立的。只能使用表锁。
也就是说:InnoDB的B+树主键索引的叶子节点就是数据文件,辅助索引的叶子节点是主键的值;而MyISAM的B+树主键索引和辅助索引的叶子节点都是数据文件的地址指针。
1、普通索引:最基本的索引,没有任何限制
创建sql create index index_name on table(column(length))
2、唯一索引:索引列值必须唯一,允许null
创建sql create unique index indexName on table(column(length))
3、主键索引:是一种特殊的唯一索引,一个表只能有一个主键,不允许空值,在创建的时候一般会同时创建
创建sql 创建表时加上primary key (id)
4、组合索引:在多个字段上创建索引,只有在查询条件中使用创建索引时的第一个字段,索引才会被使用
创建sql alter table table_name add index name_city_age (name,city,age);
5、全文索引:主要用来查找文本中的关键字,而不是直接与索引中的值相比较
创建sql create fulltext index index_content ON article(content)
悲观锁:使用 select…for update 锁数据,需要注意锁的级别,MySQL InnoDB 默认行级锁。行级锁都是基于索引的,如果一条 SQL 语句用不到索引是不会使用行级锁的,会使用表级锁把整张表锁住,这点需要注意。
乐观锁:在更新之前,先查询一下库存表中当前库存数(quantity),然后在做 update 的时候,以库存数作为一个修改条件。当提交更新的时候,判断数据库表对应记录的当前库存数与第一次取出来的库存数进行比对,如果数据库表当前库存数与第一次取出来的库存数相等,则予以更新,否则认为是过期数据。
MVCC是一种多并发版本控制器,一般用于数据库中,mysql的innodb中使用mvcc解决脏读、幻读、不可重复读等事务隔离问题,但不能解决更新丢失问题。
实现原理: MVCC的实现原理是依靠记录中的3个隐含字段、undo log日志、Read View来实现的。
隐含字段有三种分别是
1、记录事务id的DB_TRX_ID、大小为6bt。
2、用来记录上一个版本数据记录的回滚指针、大小为7bt。
3、隐含的自增ID,作用是在没有设置主键的情况下自动以DB_ROW_ID产生一个簇拥索引、大小为6bt。
undo log日志分2种
insert undo log:事务进行插入操作时产生、在事务回滚时需要,提交事务后可以被立即丢弃。
update undo log:进行update、delete时产生的undo log、不仅在回滚事务时需要、在快照读时也需要。所以不能随便删除,只有在快速读或事务回滚不涉及该日志时,对应的日志才会被purge线程统一清除
Read View(读视图):
read view读视图就是在进行快照读时会产生一个read view视图、在该事务执行的快照读的那一刻,会生成数据库系统当前的一个快照,记录并维护系统当前活跃事务的ID(当每个事务开启时,都会被分配一个ID, 这个ID是递增的,所以最新的事务,ID值越大)。说白了就是用来记录发生快照读那一刻所有的记录,当你下次就算有执行新的事务记录改变了read view没变读出来的数据依然是不变的。
而隔离级别中的RR(可重复读)、和RC(提交读)不同就是差在快照读时 前者创建一个快照和Read
View并且下次快照读时使用的还是同一个Read View所以其他事务修改数据对他是不可见的、解决了不可重复读问题。后者则是每次快照读时都会产生新的快照和Read View、所以就会产生不可重复读问题。
1、字符串类型不匹配导致索引失效
where 条件字段类型与实际表中字段类型不匹配的时候,Mysql会进行隐式的数据类型转换,而类型转换会使用到内置函数,导致在进行数据查询的时候并没有使用索引
2、被索引字段使用了表达式计算
在where 中条件使用了条件表达式的时候,数据表中的索引就失效了,实际是因为 Mysql需要将索引字段取出来之后再进行表达式的条件判断,因而进行了全表扫描,导致索引失效。
3、被索引字段使用了内置函数
索引保存的是索引列的原始值,如果经过函数计算,Mysql的解释器无法判断计算后的索引在原来的索引树上是否可以被索引到,因此它就直接放弃使用索引查询了。
4、like 使用了 %X 模糊匹配
使用左模糊匹配以及左右模糊匹配都会导致索引失效,但是使用右模糊匹配,还是可以走索引查询的。由于B+树按照索引值进行排序的,实际是按照最左前缀进行比较,而使用了 %作为最左前缀,Mysql 无法判断其有序性,因此只能进行全表扫描查询。
5、索引字段不是联合索引字段的最左字段
如果数据库表中有联合索引的话,我们在 SQL查询语句中使用的索引字段又不是联合索引的最左字段,那么就会导致索引失效。实际上在 Mysql 中的索引检索是遵循最左匹配原则的,同时B+索引树的叶子节点的有序性也是建立在最左匹配原则之上
6、or 分割的条件,如果 or左边的条件存在索引,而右边的条件没有索引,不走索引
因为 OR的含义就是两个只要满足一个即可,因此只有一个条件列进行了索引是没有意义的,只要有条件列没有进行索引,就会进行全表扫描,因此索引的条件列也会失效。
7、in、not in 可能会导致索引失效
这里需要说明的是使用 in 以及 not in 走不走索引,实际和 Mysql的版本以及表中的数据量有关系,在 8.0 之后的版本是走索引的。
原理:分批次的将磁盘块加载进内存中进行检索,若查到数据,则直接返回,若查不到,则释 放内存,并重新加载同等数据量的索引进内存,重新遍历
B:
1、允许每个节点有更多的子节点即可(多叉树)
2、节点排序
B+:
1、拥有B树得特性
2、叶子之间有指针
3、非叶子节点上的元素在叶子节点上都冗余了, 也就是叶子节点中存储了所有的数据, 并且排好顺序。
聚簇索引: 将数据存储与索引放到了一块 、并且是按照一定的顺序组织的, 找到索引 也就找到了数据, 数据的物理存放顺序与索引顺序是一致的, 即:只要索引是相邻的, 那么对应的数据一定也是相邻地存放在磁盘上的
非聚簇索引:叶子节点不存储数据 、存储的是数据行地址, 也就是说根据索引查找到 数据行的位置再取磁盘查找数据, 这个就有点类似一本书的目录, 比如我们要找第三 章第一节, 那我们先在这个目录里面找, 找到对应的页码后再去对应的页码看文章 。
优势:
1 、查询通过聚簇索引可以直接获取数据, 相比非聚簇索引需要第二次查询(非覆 盖索引的情况下)效率要高
2 、聚簇索引对于范围查询的效率很高, 因为其数据是按照大小排列的
3 、聚簇索引适合用在排序的场合, 非聚簇索引不适合
劣势:
1、维护索引很昂贵, 特别是插入新行或者主键被更新导至要分页(pagesplit)的时候 。 建议在大量插入新行后, 选在负载较低的时间段, 通过 OPTIMIZETABLE 优化 表, 因为必须被移动的行数据可能造成碎片 。使用独享表空间可以弱化碎片
2、表因为使用 uuId(随机 ID)作为主键, 使数据存储稀疏, 这就会出现聚簇索引有可 能有比全表扫面更慢, 所以建议使用 int 的 auto_increment 作为主键
3、如果主键比较大的话, 那辅助索引将会变的更大, 因为辅助索引的叶子存储的是 主键值, 过长的主键值, 会导致非叶子节点占用占用更多的物理空间
(1) 尽量选择较小的列
(2) 将 where 中用的比较频繁的字段建立索引
(3) select 子句中避免使用‘*’
(4) 避免在索引列上使用计算 、not in 和<>等操作
(5) 当只需要一行数据的时候使用 limit 1
(6) 保证单表数据不超过200W, 适时分割表 。针对查询较慢的语句, 可以使用 explain 来分析该语句具体的执行情况 。
(7) 避免改变索引列的类型。
(8) 选择最有效的表名顺序, from 字句中写在最后的表是基础表, 将被最先处理, 在 from 子句中包含多个表的情况下, 你必须选择记录条数最少的表作为基础表 。
(9) 避免在索引列上面进行计算 。
(10) 尽量缩小子查询的结果
例 1: where 子句中可以对字段进行 null 值判断吗? 可以, 比如 select id from t where numis null 这样的 sql 也是可以的。但是最好不要 给数据库留 NULL, 尽可能的使用 NOT NULL 填充数据库 。不要以为NULL 不需要空 间, 比如: char(100) 型, 在字段建立时, 空间就固定了, 不管是否插入值 (NULL 也包含在内), 都是占用 100 个字符的空间的, 如果是 varchar 这样的变长字段, null 不占用空间 。可以在 num上设置默认值 0, 确保表中 num 列没有 null 值, 然后这样 查询: select id from t where num=0。
例 2: 如何优化?下面的语句? select * from admin left join log on admin.admin_id = log.admin_id where log.admin_id>10 优化为: select * from (select * from admin where admin_id>10) T1 lef join log on T1.admin_id = log.admin_id。 使用 JOIN 时候, 应该用小的结果驱动大的结果 (left join左边表结果尽量小如果有条 件应该放到左边先处理, right join 同理反向), 同时尽量把牵涉到多表联合的查询拆 分多个query (多个连表查询效率低, 容易到之后锁表和阻塞) 。
例 3: limit 的基数比较大时使用 between 例如: select * from admin order by admin_id limit 100000,10 优化为: select * from admin where admin_id between 100000 and 100010 order by admin_id。
例 4: 尽量避免在列上做运算, 这样导致索引失效 例如: select * from admin where year(admin_time)>2014 优化为: select * from admin where admin_time> '2014-01-01′
1、用一条 SQL 语句查询出每门课都大于 80 分的学生姓名
name | kecheng | fenshu |
---|---|---|
张三 | 语文 | 81 |
张三 | 数学 | 75 |
李四 | 语文 | 76 |
李四 | 数学 | 90 |
王五 | 语文 | 81 |
王五 | 数学 | 100 |
王五 | 英语 | 90 |
答案1:
select name from table group by name having min(fenshu)>80
答案2:
select distinct name from table where name not in (
select distinct name from table where fenshu<=80
)
2、删除除了自动编号不同, 其他都相同的学生冗余信息
自动编号 | 学号 | 姓名 | 课程编号 | 课程名称 | 分数 |
---|---|---|---|---|---|
1 | 2005001 | 张三 | 0001 | 数学 | 69 |
2 | 2005002 | 李四 | 0001 | 数学 | 89 |
3 | 2005001 | 张三 | 0001 | 数学 | 69 |
答案:
delete tablename where 自动编号 not in(
select min(自动编号) from tablename
group by 学号, 姓名, 课程编号, 课程名称, 分数
)
3、一个叫 team 的表, 里面只有一个字段 name,一共有 4 条纪录, 分别是 a,b,c,d,对应 四个球队, 现在四个球队进行比赛, 用一条 sql 语句显示所有可能的比赛组合.
答案:
select a.name, b.name from team a, team b where a.name < b.name
4、 怎么把这样一个表
year | month | amount |
---|---|---|
1991 | 1 | 1.1 |
1991 | 2 | 1.2 |
1991 | 3 | 1.3 |
1991 | 4 | 1.4 |
1992 | 1 | 2.1 |
1992 | 2 | 2.2 |
1992 | 3 | 2.3 |
1992 | 4 | 2.4 |
查成这样一个结果
year | m1 | m2 | m3 | m4 |
---|---|---|---|---|
1991 | 1.1 | 1.2 | 1.3 | 1.4 |
1992 | 2.1 | 2.2 | 2.3 | 2.4 |
答案:
select year,
(select amount from aaa m where month=1 and m.year=aaa.year) as m1,
(select amount from aaa m where month=2 and m.year=aaa.year) as m2,
(select amount from aaa m where month=3 and m.year=aaa.year) as m3,
(select amount from aaa m where month=4 and m.year=aaa.year) as m4
from aaa group by year
5、说明: 复制表(只复制结构,源表名: a 新表名: b)
select * into b froma where 1<>1 (where1=1, 拷贝表结构和数据内容)
spring 是一个轻量级ioc和aop容器框架,简化应用开发, 核心组件包括: Spring Context、Spring Mvc、Spring Web、Spring Core、Spring Aop、Spring Dao、Spring Orm
控制反转(IOC):传统的java开发中,当需要对象时,我们会new和getInstance等直接调用构造方法创建一个对象。而在spring开发模式中,spring容器使用工厂模式为我们创建了所需要的对象,不需要我们自己创建了,直接调用spring提供的对象就可以了,这是控制反转的思想。
依赖注入(DI):spring使用javaBean对象的set方法或者带参数的构造方法为我们在创建所需对象时将其属性自动设置所需要的值的过程,就是依赖注入的思想。
面向切面编程(AOP): 我们将一个个的对象某些类似的方面横向抽成一个切面, 对这个切面进行一些如权限控制 、事物管理, 记录日志等公用操作处理的过程就 是面向切面编程的思想,AOP底层是动态代理,如果是接口采用JDK动态代理,如果是类采用cglib方式实现动态代理
1、代理模式:spring中两种代理方式,若目标实现了接口,spring使用的是jdk的proxy代理类,若目标没有实现接口,spring使用的是cglib库生成目标子类
2、单列模式:在spring配置文件设置bean默认为单列模式
3、模板方式模式:用来解决代码重复问题比如: RestTemplate、JmsTemplate 、JpaTemplate
4、工厂模式:创建对象时不会暴露逻辑,spring中使用beanfactory来创建实列。
@Component注解标识一个受Spring管理的组件
@Controller标识一个表示层的组件
@Service 标识一个业务层组件
@Repository 标识一个持久层组件
@Autowired 自动装配
@Qualifier(“”)具体指定要装配的组件id值
@RequestMapping()完成请求映射 @PathVariable 映射请求url中占位符到请求方法的形参
循环依赖:从字面上来理解就是A依赖B的同时B也依赖了A,就像下面这样
@Component
public class A {
// A中注入了B
@Autowired
private B b;
}
@Component
public class B {
// B中也注入了A
@Autowired
private A a;
}
出现循环依赖,我们肯定想着怎么解决,而spring有默认解决循环依赖的问题,三级缓存
1、A创建过程需要B,A将自己放入三级缓存,去实列化B
2、B实例化需要A,查一级缓存,没有就查二级缓存,还是没有查三级缓存,找到了A,A移动到二级缓存,
3、B创建完毕进入一级缓存,
4、A开始填充数据,从一级缓存获取B完成创建进入一级缓存。
但是spring解决循环依赖问题有几个前提:1、出现循环依赖的Bean必须要是单例
2、依赖注入的方式不能全是构造器注入的方式(很多博客上说,只能解决setter方法的循环依赖,这是错误的)
在先调用A的情况下(Spring在创建Bean时默认会根据自然排序进行创建,所以A会先于B进行创建):
> 原理:由于 spring 中的 bean 的创建过程为先实例化 再初始化(在进行对象实例化的过程中不必赋值)将实例化好的对象暴露出去,供其他对象调用,然而使用构造器注入,必须要使用构造器完成对象的初始化的操作,就会陷入死循环的状态,所以A必须是setter注入才能解决循环依赖。
编程式事务管理:通过 TransactionTemplate或者TransactionManager手动管理事务,实际应用中很少使用
声明式事务管理:使用场景最多,也是最推荐使用的方式,直接加上@Transactional注解即可
事务传播机制一共有七种:
propagation_required:Spring的默认传播级别,如果上下文中存在事务则加入当前事务,如果不存在事务则新建事务执行。
propagation_supports:如果上下文中存在事务则加入当前事务,如果没有事务则以非事务方式执行。
propagtion_mandatory:该传播级别要求上下文中必须存在事务,否则抛出异常。
propagation_requires_new:每次执行都会创建新事务,并同时将上下文中的事务挂起,执行完当前线程后再恢复上下文中事务。
propagation_not_supported:以非事务方式运行,如果有事务存在,挂起当前事务
propagation_never:该传播级别要求上下文中不能存在事务,否则抛出异常。
propagation_nested: 嵌套事务,如果上下文中存在事务则嵌套执行,如果不存在则新建事务
Redis 全称 (Remote Dictionary Server) 本质上是一个 Key-Value 类型的内存数据库 , 数据库统统加载在内存当中进行操作, 定期通过异步操作把数据库数据 flush 到硬盘 上进行保存
TYPE | CONTEXT |
---|---|
string | 字符串(一个字符串类型最大存储容量为512M) |
list | 可以重复的集合 |
set | 不可以重复的集合 |
hash | 类似于Map |
zset(sortedset) | 带分数的set |
RDB :RDB持久化方式能够在指定的时间间隔能对你的数据进行快照存储 。
AOF:持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数 据, AOF 命令以 redis协议追加保存每次写的操作到文件末尾.Redis 还能对 AOF 文件进行后台重写,使得 AOF 文件的体积不至于过大 。
如果你只希望你的数据在服务器运行的时候存在, 你也可以不使用任何持久化方式 。
你也可以同时开启两种持久化方式, 在这种情况下, 当 redis 重启的时候会优先载入 AOF 文件来恢复原 始的数据,因为在通常情况下 AOF 文件保存的数据集要比 RDB文件保存的数据集要完整 。
(1) RDB 持久化:
每隔一段时间, 将内存中的数据集写到磁盘 Redis 会单独创建 (fork) 一个子进程来进行持久化,
会先将数据写入到个临时文件中, 待持久化过程都 结束了, 再用这个临时文件替换上次持久化好的文件 。整个过程中, 主进程是不进行任何 IO 操作的, 这 就确保了极高的性能如果需要进行大规模数据的恢复, 且对于数据恢复的完整性不是非常敏感, 那 RDB 方式要比 AOF 方式更加的高效。
保存策略:
save 900 1 900 秒内如果至少有 1 个 key 的值变化, 则保存
save 300 10 300 秒内如果至少有 10 个 key 的值变化, 则保存
save 60 1 0000 60 秒内如果至 10000 个 key 的值变化, 则保存
(2) AOF 持久化: 以日志形式记录每个更新 ((总结 、改) 操作
Redis 重新启动时读取这个文件, 重新执行新建 、修改数据的命令恢复数据 。
保存策略:
appendfsync always: 每次产生一条新的修改数据的命令都执行保存操作; 效率低, 但是安全!
appendfsync everysec: 每秒执行一次保存操作 。如果在未保存当前秒内操作时发生了断电, 仍然会导致 一部分数据丢失 (即 1 秒钟的数据) 。
appendfsync no: 从不保存, 将数据交给操作系统来处理 。更快, 也更不安全的选择 。
推荐 (并且也是默认) 的措施为每秒 fsync 一次, 这种 fsync 策略可以兼顾速度和安全性 。缺点:
1 比起 RDB 占用更多的磁盘空间
2 恢复备份速度要慢
3 每次读写都同步的话, 有一定的性能压力
4 存在个别 Bug, 造成恢复不能
官方 FAQ 表示, 因为 Redis 是基于内存的操作, CPU 不是 Redis 的瓶颈, Redis 的瓶 颈最有可能是机器内存的大小或者网络带宽 。既然单线程容易实现, 而且 CPU 不会成 为瓶颈, 那就顺理成章地采用单线程的方案了 Redis 利用队列技术将并发访问变为串 行访问
1) 绝大部分请求是纯粹的内存操作
2) 采用单线程,避免了不必要的上下文切换和竞争条件
对于 Redis 而言, 命令的原子性指的是: 一个操作的不可以再分, 操作要么执行, 要 么不执行 。
Redis的操作之所以是原子性的, 是因为 Redis 是单线程的。 Redis 本身提供的所有 API 都是原子操作, Redis中的事务其实是要保证批量操作的原 子性。 多个命令在并发中也是原子性的吗? 不一定, 将 get 和 set 改成单命令操作, incr 。使用 Redis 的事务, 或者使用 Redis+Lua==的方式实现.
Redis 是有事务的, redis 中的事务是一组命令的集合, 这组命令要么都执行, 要不都 不执行, redis 事务的实现, 需要用到 MULTI (事务的开始) 和 EXEC (事务的结束) 命令 ;
当输入 MULTI 命令后, 服务器返回 OK 表示事务开始成功, 然后依次输入需要在本次 事务中执行的所有命令, 每次输入一个命令服务器并不会马上执行, 而是返 回”QUEUED”, 这表示命令已经被服务器接受并且暂时保存起来, 最后输入 EXEC 命令 后, 本次事务中的所有命令才会被依次执行, 可以看到最后服务器一次性返回了两个 OK, 这里返回的结果与发送的命令是按顺序一一对应的, 这说明这次事务中的命令全 都执行成功了 。 Redis的事务除了保证所有命令要不全部执行, 要不全部不执行外, 还能保证一个事务 中的命令依次执行而不被其他命令插入 。同时, redis的事务是不支持回滚操作的 。
一 、 延时双删策略:
一般我们在更新数据库数据时,需要同步redis中缓存的数据,所以存在两种方法:
第一种方案:先更新数据库,再删除redis。
第二种方案:先删除redis,再更新数据库。
这两种方案的弊端是当存在并发请求时,很容易出现以下问题:
第一种方案:当请求1执行完更新数据库操作后,还未来得及删除redis,此时请求2查询到并使用了redis中的旧数据。
第二种方案:当请求1执行完删除redis后,还未进行更新数据库操作,此时请求2查询到了数据库的旧数据并写入了redis。
所以采用双删策略,在写库前后都进行 redis.del(key)操作, 并且设定合理的超时时间 。具体步骤 是:
1) 先删除缓存
2) 再写数据库
3) 休眠 500 毫秒 (根据具体的业务时间来定)
4) 再次删除缓存 。 那么, 这个 500 毫秒怎么确定的, 具体该休眠多久呢? 需要评估自己的项目的读数据业务逻辑的耗时 。这么做的目的, 就是确保读请求 结束, 写请求可以删除读请求造成的缓存脏数据 。 当然, 这种策略还要考虑 redis 和数据库主从同步的耗时 。最后的写数据的休眠 时间: 则在读数据业务逻辑的耗时的基础上, 加上几百 ms 即可 。比如: 休眠 1 秒 。
二 、设置缓存的过期时间 从理论上来说, 给缓存设置过期时间, 是保证最终一致性的解决方案 。所有的写 操作以数据库为准, 只要到达缓存过期时间, 则后面的读请求自然会从数据库中读取 新值然后回填缓存 结合双删策略+缓存超时设置, 这样最差的情况就是在超时时间内数据存在不一 致, 而且又增加了写请求的耗时。
三 、如何写完数据库后, 再次删除缓存成功?
上述的方案有一个缺点, 那就是操作完数据库后, 由于种种原因删除缓存失败, 这时, 可能就会出现数据不一致的情况 。这里, 我们需要提供一个保障重试的方案 。
1 、方案一具体流程
(1) 更新数据库数据;
(2) 缓存因为种种问题删除失败;
(3) 将需要删除的 key 发送至消息队列;
(4) 自己消费消息, 获得需要删除的 key;
(5) 继续重试删除操作, 直到成功。 然而, 该方案有一个缺点, 对业务线代码造成大量的侵入 。于是有了方案二, 在 方案二中, 启动一个订阅程序去订阅数据库的binlog, 获得需要操作的数据 。在应用 程序中, 另起一段程序, 获得这个订阅程序传来的信息, 进行删除缓存操作 。
2、方案二具体流程
(1) 更新数据库数据;
(2) 数据库会将操作信息写入 binlog 日志当中;
(3) 订阅程序提取出所需要的数据以及 key;
(4) 另起一段非业务代码, 获得该信息;
(5) 尝试删除缓存操作, 发现删除失败;
(6) 将这些信息发送至消息队列;
(7) 重新从消息队列中获得该数据, 重试操作 。
1、缓存穿透:
是指查询一个不存在的数据, 由于缓存无法命中, 将去查询数据库, 但是数据库也无 此记录, 并且出于容错考虑, 我们没有将这次查询的 null 写入缓存, 这将导致这个不存在的数据每次请求都要到存储层去查询, 失去了缓存的意义 。在流量大时, 可能 DB 就挂掉了, 要是有人利用不存在的 key 频繁攻击我们的应用, 这就是漏洞 。 解决方案: 空结果也进行缓存, 可以设置一个空对象, 但它的过期时间会很短, 最长 不超过五分钟 。 或者用布隆过滤器也可以解决, Redisson 框架中有布隆过滤器
2、 缓存雪崩:
是指在我们设置缓存时采用了相同的过期时间, 导致缓存在某一时刻同时失效, 请求 全部转发到 DB, DB 瞬时压力过重雪崩。
解决方案: 原有的失效时间基础上增加一个随机值, 比如 1-5 分钟随机, 这样每一个 缓存的过期时间的重复率就会降低, 就很难引发集体失效的事件 。
3、缓存击穿:
是指对于一些设置了过期时间的 key, 如果这些 key 可能会在某些时间点被超高并发 地访问, 是一种非常“热点”的数据 。这个时候,需要考虑一个问题: 如果这个 key 在 大量请求同时进来之前正好失效, 那么所有对这个 key 的数据查询都落到 DB, 我们 称为缓存击穿 。 解决方案:在分布式的环境下, 应使用分布式锁来解决, 分布式锁的实现方案有多 种, 比如使用 Redis 的setnx 、使用 Zookeeper 的临时顺序节点等来实现
如果 Master 异常, 则会进行 Master-Slave 切换, 将其中一 Slae 作为 Master, 将之 前的Master 作为 Slave
下线:
①主观下线: Subjectively Down, 简称 SDOWN, 指的是当前 Sentinel 实例对某个 redis 服务器做出的下线判断 。
②客观下线: Objectively Down, 简称 ODOWN, 指的是多个 Sentinel 实例在对 Master Server 做出 SDOWN 判断, 并且通过 SENTINEL is-master-down-by-addr 命令 互相交流之后, 得出的 Master Server 下线判断, 然后开启 failover.
工作原理:
(1) 每个 Sentinel 以每秒钟一次的频率向它所知的 Master, Slave 以及其他 Sentinel 实例发送一个 PING 命令;
(2) 如果一个实例 (instance) 距离最后一次有效回复 PING 命令的时间超过 down- after-milliseconds 选项所指定的值, 则这个实例会被 Sentinel 标记为主观下线;
(3) 如果一个 Master 被标记为主观下线, 则正在监视这个 Master 的所有 Sentinel 要以每秒一次的频率确认 Master 的确进入了主观下线状态;
(4) 当有足够数量的 Sentinel (大于等于配置文件指定的值) 在指定的时间范围内确 认 Master 的确进入了主观下线状态, 则 Master 会被标记为客观下线 ;
(5) 在一般情况下, 每个 Sentinel 会以每 10 秒一次的频率向它已知的所有 Master, Slave 发送 INFO 命令
(6) 当 Master 被 Sentinel 标记为客观下线时, Sentinel 向下线的 Master 的所有 Slave 发送 INFO 命令的频率会从 10 秒一次改为每秒一次 ;
(7) 若没有足够数量的 Sentinel 同意 Master 已经下线, Master 的客观下线状态就 会被移除;
若 Master 重新向 Sentinel 的 PING 命令返回有效回复, Master 的主观下线状态就会 被移除;
1)完全基于内存, 绝大部分请求是纯粹的内存操作, 非常快速。数据存在内存中, 类 似于 HashMap, HashMap 的优势就是查找和操作的时间复杂度都是 O(1)
2)数据结构简单, 对数据操作也简单, Redis 中的数据结构是专门进行设计的
3)采用单线程, 避免了不必要的上下文切换和竞争条件, 也不存在多进程或者多线程 导致的切换而消耗 CPU, 不用去考虑各种锁的问题,不存在加锁释放锁操作, 没有因为可能出现死锁而导致的性能消耗
4)使用多路 I/O 复用模型, 非阻塞 IO
5)使用底层模型不同, 它们之间底层实现方式以及与客户端之间通信的应用协议不一 样, Redis 直接自己构建了 VM 机制, 因为一般的系统调用系统函数的话, 会浪费一 定的时间去移动和请求
(1) Master 最好不要做任何持久化工作, 如 RDB 内存快照和 AOF 日志文件
(2) 如果数据比较重要, 某个 Slave开启 AOF 备份数据, 策略设置为每秒同步一次
(3) 为了主从复制的速度和连接的稳定性, Master 和 Slave最好在同一个局域网内
(4) 尽量避免在压力很大的主库上增加从库
(5) 主从复制不要用图状结构, 用单向链表结构更为稳定, 即: Master <- Slave1 <- Slave2 <- Slave3… 这样的结构方便解决单点故障问题, 实现 Slave 对
Master 的替换 。如果 Master 挂 了, 可以立刻启用 Slave1 做 Master, 其他不变。
Redis 内存淘汰策略 redis 内存数据集大小上升到一定大小的时候, 就会施行数据淘汰策略。
数据淘汰策略:
noeviction:返回错误当内存限制达到并且客户端尝试执行会让更多内存被使用的命令 (大部分的写入指令, 但 DEL 和几个例外)
allkeys-lru: 尝试回收最少使用的键 (LRU), 使得新添加的数据有空间存放 。
volatile-lru: 尝试回收最少使用的键 (LRU), 但仅限于在过期集合的键,使得新添加的 数据有空间存放 。
allkeys-random: 回收随机的键使得新添加的数据有空间存放 。
volatile-random: 回收随机的键使得新添加的数据有空间存放, 但仅限于在过期集合的 键 。
volatile-ttl: 回收在过期集合的键, 并且优先回收存活时间 (TTL) 较短的键,使得新添 加的数据有空间存放 。
Redis 集群没有使用一致性 hash,而是引入了哈希槽的概念, Redis 集群有 16384 个哈 希槽, 每个 key 通过 CRC16 校验后对 16384 取模来决定放置哪个槽, 集群的每个节 点负责一部分 hash 槽 。
#{}:是预编译,将sql替换成?调用 PreparedStatement 的 set 方法来赋值,防止sql注入
${}:是字符串替换,
一级缓存:是sqlsession级别的缓存,默认是开启的
二级缓存:是namespace级别(mapper)的缓存,多个sqlSession可以共享,使用时需要进行配置开启。
查询顺序: 二级缓存 => 一级缓存 => 数据库
在< insert >标签中使用 useGeneratedKeys 和 keyProperty 两个属性来获取自动生成的 主键值 。
<insert id="insertname" usegeneratedkeys="true"keyproperty="id">
insert into names (name) values (#{name})
insert>
动态 SQL 主要是来解决查询条件不确定的情况, 在程序运行期间, 根据提交的条件动 态的完成查询
常用的标签:
< if > : 进行条件的判断
< where >: 在< if >判断后的 SQL 语句前面添加 WHERE 关键字, 并处理 SQL 语句开 始位置的 AND 或者 OR 的问题
< trim >: 可以在 SQL 语句前后进行添加指定字符 或者去掉指定字符.
< set > : 主要用于修改操作时出现的逗号问题
< choose > < when > < otherwise >: 类似于 java 中的 switch 语句.在所有的条件中选 择其一
< foreach >: 迭代操作
Spring Boot 是 Spring 开源组织下的子项目, 是 Spring 组件一站式解决方案, 主要是 简化了使用 Spring
的难度, 简省了繁重的配置, 提供了各种启动器, 开发者能快速上手 。
@SpringBootApplication:启动类也是核心注解
@SpringBootConfiguration: 组合了@Configuration 注解, 实现配置文件的功能
@EnableAutoConfiguration:打开自动配置的功能, 也可以关闭某个自动配置 的选项。
@ComponentScan:Spring 组件扫描 。
注解 @EnableAutoConfiguration, @Configuration, @ConditionalOnClass 就是自动配置的核心。
首先它得是一个配置文件, 其次根据类路径下是否有这个类去自动配置 。
@EnableAutoConfiguration 是实现自动配置的注解
@Configuration 表示这是一个配置文件
1.Nacos:作为注册中心和配置中心,实现服务注册发现和服务健康监测及配置信息统一管理
2.Gateway:作为网关,作为分布式系统统一的出入口,进行服务路由,统一鉴权等
3.OpenFeign:作为远程调用的客户端,实现服务之间的远程调用
4.Sentinel:实现系统的熔断限流
5.Sleuth:实现服务的链路追踪
nacos是注册中心分server和client,为client提供发现服务与配置服务 服务注册:
每过5秒向server发送一次心跳,心跳带上服务名、服务ip、服务端口等信息,同时server也会向client主动发动健康检测,15秒内无心跳或检测失败认为不健康,30秒内失败则剔除实列。
1.创建远程接口的本地代理实例
2.封装Request对象并进行编码
3.feign.Client发送请求并对获取结果进行解码
hadoop是一个可扩展,可靠的,用于分布式计算和存储的系统架构,里面包含主要核心组件有:
1、hdfs分布式文件系统
2、分布式计算系统mapreduce
3、分布式资源管理系统yarn
hdfs读流程:
1、客户端向namenode请求元数据,
2、namenode返回这个文件块的元数据节点位置列表
3、就近选择一台datanode建立输入流,
4、datanode向流中写入数据,以packet为单位校验
5、关闭输入流
hdfs写流程:
1、客户端请求namenode上次文件
2、namenode检测目录和权限是否合格
3、不合格报错,合格返回可以上次文件
4、向namenode请求上传第一个块,请求返回上传的位置
5、namenode返回副本个数的节点给客户端
6、客户端向返回来的datanode节点建立管道,每传输一个datanode都会和下一个datanode建议管道,直到最后一个返回ack确定信息。
1、储存方面:1个文件快占用namenode150个字节,如果小文件过多,namenode内存占用就越高。
2、计算层面:每个小文件都会启动一个maptask,1个maptask默认占用1g,浪费资源
解决方案:
1、采用har归档,将小文件归档
2、采用combineTextInputFormat
3、自己写一个MR程序处理小文件合并成大文件,如果是hive和spark有merge自动帮我们合成
4、开启jvm重用,但是没有小文件不要开启,这样会一直占用task卡槽。
1、hadoop2.x的namenode配置默认值2000m
2、hadoop3.x的namenode内存是动态分配的,最小值是1g,每增加100w个文件block,加1g内存。
我们知道MapReduce计算模型主要由三个阶段构成:Map、shuffle、Reduce。
Shuffle过程包含在Map和Reduce两端,即Map shuffle和Reduce shuffle
1、将数据分片,map读取每行数据,格式化数据成kv键值对。
2、进入mapshuffle阶段,将kv键值对通过key进行分区,然后进入环形缓冲区,环形缓冲区默认大小是100M,阈值是百分之80%,超过阈值溢写到磁盘,然后进行排序,然后进行合并
3、进入reducetask,从远程maptask中复制数据进来到内存中,再次合并数据,然后进行一次归并排序,最后交给reduce处理数据。
0、Mr程序提交到客户端节点,
1、yarnrunner向resourcemanager申请一个application
2、resourcemanager返回给yarnrunner资源提交的路径
3、yarnrunner将所需资源提交到路径上
4、提交完成yarnrunner申请运行mrappMaster
5、RM将用户的请求初始化成一个task
6、其中一个NodeManager领取到task任务
7、该NodeManager创建容器Container,并产生MRAppmaster
8、Container从HDFS上拷贝资源到本地
9、MRAppmaster向RM申请运行maptask资源
10、RM将运行maptask任务分配给另外两个NodeManager分别领取任务并创建容器
11、MR向2个接收的任务的NodeManager发送程序启动脚本,启动maptask
12、MrappMaster向RM申请容器运行reducetask
13、reducetask向matask获取相应分区的数据
14、程序完成,mr向rm申请注销自己
Hadoop 调度器重要分为三类 FIFO、Capacity Scheduler(容量调度器)和 Fair Sceduler(公平调度器)。 Apache 默认的资源调度器是容量调度器。 CDH 默认的资源调度器是公平调度器。
FIFO:支持单队列、先进先出、生产环境不会用。
容量调度器:支持多队列,队列资源分配,优化选择资源占用率最低的队列分配资源;按照作业的优先级和提交时间顺序分配资源;容器资源分配,本地原则(同一节点/同一机架/不同节点不同机架)。
公平调度器:支持多队列,保证每个任务公平享有队列资源。资源不够时可以按照缺额分配。
大厂:如果对并发度要求比较高,选择公平,要求服务器性能必须 OK。
中小公司:集群服务器资源不太充裕选择容量。
1、因为担心员工不小心,写递归死循环代码,把所有资源全部耗尽。
2、实现任务的降级使用,特殊时期保证重要的任务队列资源充足。
块大小
1、1.x 64M
2、2.x 3.x 128M
3、本地 32M
4、企业 128M 256M 512M
块大小决定因素
磁盘读写速度
普通机械硬盘 100m/s =》128m
固态硬盘普通的 300m/s =》256m
内存镜像 500-600m/s =>512m
1、出现脑裂的原因: Leader 出现故障,系统开始改朝换代,当 Follower 完成全部工作并且成为 Leader 后, 原 Leader 又复活了(它的故障可能是暂时断开或系统暂时变慢,不能及时响应,但其 NameNode 进程还在),并且由于某种原因它对应的ZKFC 并没有把它设置为 Standby,所以 原 Leader 还认为自己是Leader,客户端向它发出的请求仍会响应,于是脑裂就发生了。
2、Hadoop 通常不会出现脑裂。
①新增一条心跳线,防止namennode状态无法正常传达。
②使用隔离机制,通过调用活跃节点中的隔离方法,让其主动转换为standby状态,如果该方法失效则使用远程调用执行kill -9 命令杀死相应进程,如果该方法仍然无法成功隔离,管理人员可以事先在每台namenode节点中编写一个shell脚本,当出现脑裂问题时,执行该脚本来切断电源,已达到隔离目的。
ls、get、create、delete、deleteall
半数机制(过半机制):2n + 1,安装奇数台。
10 台服务器:3 台。
20 台服务器:5 台。
100 台服务器:11 台。
台数多,好处:提高可靠性;坏处:影响通信延时。
(1)作为 HA 的协调者:如 HDFS 的 HA、YARN 的 HA。
(2)被组件依赖:如 Kafka、HBase、CK
1、TailDir Source
(1)断点续传、多目录
(2)taildir 底层原理
(3)Taildir 挂了怎么办?
不会丢数:断点续传
重复数据:有可能
(4)存在的问题及解决方案
①问题:
新文件判断条件 = iNode 值 + 绝对路径(包含文件名)
日志框架凌晨修改了文件名称=》导致会再次重读一次昨天产生的数据
②解决:
方案一:建议生成的文件名称为带日期的。同时配置日志生成框架为不更名的;
方案二:修改 TairDirSource 源码,只按照 iNode 值去确定文件
修改源码视频地址:
https://www.bilibili.com/video/BV1wf4y1G7EQ?p=14&vd_source=891aa1a36311
1d4914eb12ace2e039af
2、file channel /memory channel/kafka channel
(1)File Channel
数据存储于磁盘,优势:可靠性高;劣势:传输速度低
默认容量:100 万个 event
注意:FileChannel 可以通过配置 dataDirs 指向多个路径,每个路径对应不同的硬盘,增大 Flume 吞吐量。
(2)Memory Channel
数据存储于内存,优势:传输速度快;劣势:可靠性差
默认容量:100 个 event
(3)Kafka Channel
数据存储于 Kafka,基于磁盘;
优势:可靠性高;
传输速度快 Kafka Channel 大于 Memory Channel + Kafka Sink 原因省去了
Sink 阶段
(4)生产环境如何选择
如果下一级是 Kafka,优先选择 Kafka Channel。
如果是金融、对钱要求准确的公司,选择 File Channel。
如果就是普通的日志,通常可以选择 Memory Channel。
3、HDFS Sink
(1)时间(半个小时) or 大小 128m 且 设置 Event 个数等于 0,该值默认 10
具体参数:hdfs.rollInterval=1800,hdfs.rollSize=134217728
4、事务
Source 到 Channel 是 Put 事务
Channel 到 Sink 是 Take 事务
时间戳拦截器:主要是解决零点漂移问题
自定义拦截器步骤
(1)实现 Interceptor
(2)重写四个方法
initialize 初始化
public Event intercept(Event event) 处理单个 Event
public List intercept(List events) 处理多个 Event,在这个方法中调
用 Event intercept(Event event)
close 方法
(3)静态内部类,实现 Interceptor.Builder
Replicating:默认选择器。功能:将数据发往下一级所有通道。
Multiplexing:选择性发往指定通道。
1)监控到异常现象
采用 Ganglia 监控器,监控到 Flume 尝试提交的次数远远大于最终成功的次数,说明
Flume 运行比较差。主要是内存不够导致的。
2)解决办法?
(1)自身:默认内存是 20m,考虑增加 flume 内存,在 flume-env.sh 配置文件中修改
flume 内存为 4-6g
(2)找朋友:增加服务器台数
搞活动 618 =》增加服务器 =》用完在退出
日志服务器配置:8-16g 内存、磁盘 8T
5、 Flume 采集数据会丢失吗?
如果是 kafka channel 或者 FileChannel 不会丢失数据,数据存储可以存储在磁盘中。
如果是 MemoryChannel 有可能丢。
6、Flume 如何提高吞吐量
调整 taildir source 的 batchSize 大小可以控制吞吐量,默认大小 100 个 Event。
吞吐量的瓶颈一般是网络带宽。
DataX 与 Sqoop 都是主要用于离线系统中批量同步数据处理场景。
(1)DataX 底层是单进程多线程;Sqoop 底层是 4 个 Map;
(2)数据量大的场景优先考虑 Sqoop 分布式同步;数据量小的场景优先考虑 DataX,完全基于内存;DataX 数据量大,可以使用多个 DataX 实例,每个实例负责一部分(手动划分)。
(3)Sqoop 是为 Hadoop 而生的,对 Hadoop 相关组件兼容性比较好;Datax 是插件化
开发,支持的 Source 和 Sink 更多一些。
(4)Sqoop 目前官方不在升级维护;DataX 目前阿里在升级维护
(5)关于运行日志与统计信息,DataX 更丰富,Sqoop 基于 Yarn 不容易采集
2)生效优先级:
(1)全局 Byte 限速 / 单 Channel Byte 限速
(2)全局 Record 限速 / 单 ChannelRecord 限速 两个都设置,取结果小的
(3)上面都没设置,总 Channel 数的设置生效
3)项目配置
只设置 总 channel 数=5,基本可以跑满网卡带宽。
建议将内存设置为 4G 或者 8G,这个也可以根据实际情况来调整。 调整 JVM xms xmx 参数的两种方式:一种是直接更改 datax.py 脚本;另一种是在启动 的时候,加上对应的参数,如下:
python datax/bin/datax.py --jvm="-Xms8G -Xmx8G" /path/to/your/job.json
在hive中,null的存储时为"\N",Mysql的null值就是null,所以再进行数据迁移的时候需要考虑该问题
1、MySQL(null) => Hive (\N) 要求 Hive 建表语句
解决该问题的方案有两个:
(1)修改 DataX HDFS Writer 的源码,增加自定义 null 值存储格式的逻辑,
(2)在 Hive 中建表时指定 null值存储格式为空字符串(‘’),例如:
DROP TABLE IF EXISTS base_province;
CREATE EXTERNAL TABLE base_province
(
`id` STRING COMMENT '编号',
`name` STRING COMMENT '省份名称',
`region_id` STRING COMMENT '地区 ID',
`area_code` STRING COMMENT '地区编码',
`iso_code` STRING COMMENT '旧版 ISO-3166-2 编码,供可视化使用',
`iso_3166_2` STRING COMMENT '新版 IOS-3166-2 编码,供可视化使用' ) COMMENT '省份表'
ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t'
NULL DEFINED AS ''
LOCATION '/base_province/';
2、Hive(\N) => MySQL (null)
reader": {
"name": "hdfsreader",
"parameter": {
"defaultFS": "hdfs://hadoop102:8020",
"path": "/base_province",
"column": [ "*"],
"fileType": "text",
"compress": "gzip",
"encoding": "UTF-8",
"nullFormat": "\\N",
"fieldDelimiter": "\t",
}
}
获取今天新增和变化的数据:通过 sql 过滤,创建时间是今天或者操作时间等于今天。