Java/JVM/Springboot/SpringCloud/Redis/Oracle详解

一、Java基础

1.集合(数据结构:数组 、链表、栈和队列、二叉树、堆和堆栈、散列表、红黑树

   1.1 List:元素按进入先后有序保存,可重复。

(1)ArrayList: 底层数据结构是数组,查询快,增删慢,线程不安全,效率高,可存贮重复元素;

(2)LinkList: 底层数据结构是链表,查询慢,增删快,线程不安全,效率高,可存贮重复元素;

(3)Vector: 底层数据结构是数组,查询快,增删慢,线程安全,效率低,可存贮重复元素;

 1.2 Set: 仅接收一次,不可重复,并做内部排序,唯一性需重写hashcode和equals方法。

(1) HashSet: 底层数据结构采用哈希表实现,元素无序且唯一,线程不安全,效率高;

(2) ListHashSet: 底层数据结构链表+hash,有序且唯一,线程不安全,效率高;

(3) TreeSet: 底层数据结构采用二叉树实现,有序且唯一;

 1.3 Map:

  (1) HashMap: 非线程安全 

      a. 底层数据结构: 基于hash表, 数组+ 链表(jdk1.8加入红黑树);当链表的长度 >= 8的时候,将链表转换成红黑树;

       红黑树数量 <= 6,红黑树转换为链表;

      b. HashMap主干是一个静态内部类Entry,包含key,value,next,  hash;

     c. 初始化容量initialCapacity默认16,加载因子默认0.75, 实际容量 = 负载因子 x 容量,也就是 12 = 0.75 x 16;

     d. 数组长度一定是2的次幂,  index = hashCode("xxx") & (Length - 1) 取模运算时方便&二进制中

与运算,  减少hash冲突;

     e. Hash冲突:一种是开放寻址法,另一种是链表法

      两个节点的key值相同(hash值一定相同),导致冲突
      两个节点的key值不同,由于hash函数的局限性导致hash值相同,导致冲突
      两个节点的key值不同,hash值不同,但hash值对数组长度取模后相同,导致冲突

     解决hash冲突:

     链表法也正是被应用HashMap中,每一个Entry对象通过next指针指向它的下一个Entry节点。当新来的Entry映射到与之冲突的数组位置时,只需要插入到对应的链表中即可。

     f. 允许使用 null 做为值(key)和键(value)

     g. 解决线程安全问题:

       Map<String, Integer> map = Collections.synchronizedMap(hashMap)

  (2) 内部数据结构

     在这里插入图片描述

  1. 判断数组是否为空,为空进行初始化;
  2. 不为空,计算 k 的 hash 值,通过(n - 1) & hash计算应当存放在数组中的下标 index;
  3. 查看 table[index] 是否存在数据,没有数据就构造一个Node节点存放在 table[index] 中;
  4. 存在数据,说明发生了hash冲突(存在二个节点key的hash值一样), 继续判断key是否相等,相等,用新的value替换原数据(onlyIfAbsent为false);
  5. 如果不相等,判断当前节点类型是不是树型节点,如果是树型节点,创造树型节点插入红黑树中;
  6. 如果不是树型节点,创建普通Node加入链表中;判断链表长度是否大于 8, 大于的话链表转换为红黑树;
  7. 插入完成之后判断当前节点数是否大于阈值,如果大于开始扩容为原数组的二倍。

  (3) HashTable: 线程安全,加入synchronized关键字

  (4) HashMap 底层数据数组原因:

1)数组效率高

在HashMap中,定位桶的位置是利用元素的key的哈希值对数组长度取模得到。此时,我们已得到桶的位置。显然数组的查找效率比LinkedList大。

2)可自定义扩容机制

采用基本数组结构,扩容机制可以自己定义,HashMap中数组扩容刚好是2的次幂,在做取模运算的效率高。

  (5)HashMap死锁原因及替代方案

      HashMap是非线程安全,在并发的时候,容量不足,扩容迁移到新的Hash表中(refresh),新线程进来也refresh),形成环形链表就会陷入死循环。

解决方案:HashTable也是线程安全,但HashTable锁定的是整个Hash表,效率相对比较低。使用ConcurrentHashMap进行替代。jdk1.8中的实现已经抛弃了Segment分段锁机制,利用CAS+Synchronized来保证并发更新的安全。其中value和next都用volatile修饰,保证并发的可见性。

2.多线程

2.1 多线程状态:

线程在一定条件下,状态会发生变化。线程变化的状态转换图如下:

200705181179465004843.png

(1)、新建状态(New):新创建了一个线程对象。

(2)、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。

(3)、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。

(4)、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:

(一)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。

(二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。

(三)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。

(5)、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

2.2 线程调度:

  • a. sleep 线程转到阻塞状态,结束后转为就绪;
  • b. wait 当前线程等待,notify或notifyAll唤醒;
  • c. yield 暂定当前正在执行线程对象,让相同或者优先级高的线程执行;
  • d. join 等待其他线程终止,当前线程调用另一个线程join,当前线程转入阻塞,直到另一个线程结束,当前线程阻塞转为就绪,如多个线程统计累加数量。

2.3 并发编程三大特性:原子性、可见性、有序性

(1) Volatile保证变量可见性与有序性,但不能保证原子性,原子性要借助synchronized这样的锁机制。

 (2)JMM内存模型

所有的变量都存储在主内存中, 总线MESI缓存一致性协议,store写回内存操作,会使其他CPU缓存地址数据无效

(3) Synchronized关键字: 锁是存在对象头里面。

  • 可见性:可见性是指多个线程访问一个资源时,该资源的状态、值信息等对于其他线程都是可见的
  • 原子性:原子性就是指一个操作或者多个操作,要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行(CAS全称Compare And Swap,AtomicInteger保证变量赋值的原子性)
  • 有序性:有序性值程序执行的顺序按照代码先后执行
  • 可重入性:synchronized和ReentrantLock都是可重入锁。一个线程拥有了锁仍然还可以重复申请锁,多个同步代码块(如:synchronized嵌套synchronized),可以重复申请锁。(计数器记录锁)

a. 三种方式:

  •   A. 修饰普通方法: new出来的实例对象锁;
  •   B. 修饰静态方法: 其锁就是当前类的class对象锁;
  •   C. 修饰代码块: 锁是括号里面的对象;

b. synchronized底层原理: 是由一对monitorenter和monitorexit指令实现的,Monitor对象是同步的基本实现单元。

  • 同步代码块是通过monitorentermonitorexit来实现,当线程执行到monitorenter的时候要先获得monitor锁,才能执行后面的方法。当线程执行到monitorexit的时候则要释放锁。
  • 同步方法是通过中设置ACC_SYNCHRONIZED标志来实现,当线程执行有ACC_SYNCHRONI标志的方法,需要获得monitor锁。
  • 每个对象维护一个加锁计数器,当前线程已经拥有了这个对象的锁,那么就把锁的计数器加1;当执行monitorexit指令时,锁的计数器也会减1,当为0表示可以被其他线程获得锁,不为0时,只有当前锁的线程才能再次获得锁。
  • 同步方法和同步代码块底层都是通过monitor来实现同步的。
  • 每个对象都与一个monitor相关联,线程可以占有或者释放monitor。

  c. Synchronized锁优化(性能优化,避免升级重量级锁)

   锁一共有四种状态,随着竞争情况逐渐升级,级别从低到高依次为:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态。

  • CAS 比较并替换(Compare and Swap),是原子操作的一种,可用于在多线程编程中实现不被打断的数据交换操作。该操作通过将内存中的值与指定数据进行比较,当数值一样时将内存中的数据替换为新的值。
  • 对象头 对象头是对象在内存中的其中一个部分,这个部分又分别由Mark Work和类型指针组成。
  • Mark Word 用于存储对象自身运行时的数据,包括hashcode、GC分代年龄、锁状态标记、偏向锁ID、偏向时间戳等数据,Mark Work占用的内存与虚拟机位长保持一致。

(4)线程池

 a. Executors四种线程池:

  • newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
  • newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
  • newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
  • newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

  b. 执行方法

  • execute(Runnable): 接收的是一个Runnable实例,并异步执行,run()方法是void,没有返回值;
  • submit(Runnable): 和execute一样,只是会返回一个Future对象,可以检测任务是否执行完毕;
  • submit(Callable): 接收的是一个Callable实例,Callable接口中的call()方法有返回值,可返回任务执行结果;
  • invokeAny(...): 接收的是一个Callable的集合,不会返回Future,会返回任意一个执行结果;
  • invokeAll(...): 和invokeAny一样,返回Future的List;

(5) 死锁: 指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象。

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

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

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

  • 环路等待条件:是指进程发生死锁后,若干进程之间形成一种头尾相接的循环等待资源关系、

(6) ThreadLocal

线程局部变量是局限于线程内部的变量,属于线程自身所有,不在多个线程间共享。

ThreadLocal支持线程局部变量,是一种实现线程安全的方式。

(7) synchronized 和 Lock 有什么区别?

  • 首先synchronized是java内置关键字,在jvm层面,Lock是个java类;

  • synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁(tryLock);

  • synchronized会自动释放锁(a 线程执行完同步代码会释放锁 ;b 线程执行过程中发生异常会释放锁),Lock需在finally中手工释放锁(unlock()方法释放锁),否则容易造成线程死锁;

  • 用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了;

  • synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可判断、可公平(两者皆可);

  • Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题。

(8) synchronized 和 ReentrantLock 区别是什么?

synchronized是和if、else、for、while一样的关键字,ReentrantLock是类,这是二者的本质区别。既然ReentrantLock是类,那么它就提供了比synchronized更多更灵活的特性,可以被继承、可以有方法、可以有各种各样的类变量,ReentrantLock比synchronized的扩展性体现在几点上: 

  • ReentrantLock可以对获取锁的等待时间进行设置,这样就避免了死锁 

  • ReentrantLock可以获取各种锁的信息

  • ReentrantLock可以灵活地实现多路通知

3.设计模式

(1) 设计原则:

  • 单一职责
  • 里氏置换原则
  • 依赖倒置原则
  • 接口隔离原则
  • 迪米特原则
  • 开闭原则

4.JVM

 对象包含对象头、实例数据和填充数据三部分。

 4.1 JVM构成:字节码命令:javap -v Test.class

4.2 五大内存区域

(1) 程序计数器:当前线程的行号指标号,记录虚拟机字节码指令的位置。

(2) 本地方法栈:使用native方法服务的,底层调用c或者c++;

(3) 栈:先进后出(FILO), 栈帧是一种数据结构, 入栈和出栈的一个单元。

  • 局部变量表:存放方法参数和方法内部定义的局部变量。
  • 操作数栈:把局部变量表里的数据、实例的字段等数据入栈。(字节码指令,压入栈)
  • 动态链接:方法之间调用,符号引用和直接引用在运行时进行解析和链接的过程
  • 方法出口:方法调用完成,返回给调用方法的值。

  (4) 堆:

   a. 堆内存分为年轻代、老年代

   b. 年轻代分为Eden(伊甸园区)和Survivor,Survivor又分为FromSpace和ToSpace。默认比例Eden:S0:S1=8:1:1。

   c. Eden存放new出来的新对象,Eden内存满了,会进行minor GC, 每经历一次GC年龄+1,默认年龄阈值到达15, 进入老年代。

  (5) 方法区(非堆内存):也称元空间(Metaspace),  jdk1.8以前叫永久代(已废弃),存储程序运行时长期存活的对象,比如类的元       数据、方法、常量、属性等。

  • MetaspaceSize :初始化元空间大小,控制发生GC阈值。
  • MaxMetaspaceSize : 限制元空间大小上限,防止异常占用过多物理内存。

4.3 JVM 调优:减少fullgc次数和时间

 1. 三大GC收集算法:效率:复制算法 > 标记/整理算法 > 标记/清除算法

  • 标记/清除算法(最基础):老年代GC算法)
    • 标记阶段:对象的header中, 遍历所有的GC Roots对象, 可达的对象(可达性分析算法)都打上一个标识。
    • 清除阶段:遍历堆内存,发现某个对象没有被标记为可达对象(通过读取对象header信息),则将其收回。
  • 复制算法:(新生代GC算法) 将可用内存按容量划分为大小相等的两块,每次使用其中的一块。当这一块的内存用完了,就将还存活的对象复制到另一块内存上,然后把这一块内存所有的对象一次性清理掉。Eden区中所有存活的对象都会被复制到To Survivor区,而在From Survivor区中,仍存活的对象会根据它们的年龄值决定去向,年龄值达到年龄阀值(默认为15,新生代中的对象每熬过一轮垃圾回收,年龄值就加1)的对象会被移到老年代中。
  • 标记整理算法: (老年代GC算法)跟标标记清除算法类似,让所有存活的对象都向一端移动,然后直接清理掉端边线以外的内存。

 2.调优工具:jconsole、VisualVM、jstat

   JAVA内存管理机制及配置参数:

  1. JAVA_OPTS="-server -Xms512m -Xmx2g -XX:+UseG1GC -XX:SurvivorRatio=6 -XX:MaxGCPauseMillis=400 -XX:G1ReservePercent=15 -XX:ParallelGCThreads=4 -XX:
  2. ConcGCThreads=1 -XX:InitiatingHeapOccupancyPercent=40 -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:../logs/gc.log"
  • 设置堆内存最小和最大值,最大值参考历史利用率设置
  • 设置GC垃圾收集器为G1
  • 启用GC日志,方便后期分析,idea配置参数(-XX:+PrintGCDetails)

 3.垃圾收集器

  • 串行收集器(Serial)
    比较老的收集器,单线程。收集时,必须暂停应用的工作线程,直到收集结束。
  • 并行收集器(Parallel)
    多条垃圾收集线程并行工作,在多核CPU下效率更高,应用线程仍然处于等待状态。
  • CMS收集器(Concurrent Mark Sweep)
    CMS收集器是缩短暂停应用时间为目标而设计的,是基于标记-清除算法实现,整个过程分为4个步骤,包括:
    • 初始标记(Initial Mark)
    • 并发标记(Concurrent Mark)
    • 重新标记(Remark)
    • 并发清除(Concurrent Sweep)

初始标记、重新标记这两个步骤仍然需要暂停应用线程(stop the world)

 4.内存溢出(OOM)

  • 老年代内存不足:java.lang.OutOfMemoryError:Javaheapspace
  • 永久代内存不足:java.lang.OutOfMemoryError:PermGenspace
  • 代码bug,占用内存无法及时回收。

5. jdk1.8特性:

Lambda实现原理: 匿名内部类, 

  1.  lambda$main$0() 私有静态方法的生成
  2. ResourceAnalysis$$Lambda$1.class 反编译类
  3. 发现 Lambda表达式,最终的实现还是基于匿名内部类的方式
  4. 调用lambda方法时使用了invokedynamic(动态调用点

Stream原理:

 1.  生成并构造一个流 (List.stream() 等方法)

 2.  中间操作:在流的处理过程中添加、绑定惰性求值流程  (map、filter、limit 等方法)

 3.  结束操作:对流使用强制求值函数,生成最终结果 (max、collect、forEach等方法)

Stream调试工具:

idea版本已经集成了Java Stream Debugger。

插件截图

二、Spring

1.Spring IOC(反射机制)

(1) 原理

  • 依赖注入:通过诸如实例化对象,替代new对象,由Spring 容器进行管理,解耦。
  • 控制反转:对象创建权交给是spring容器。
  • 注入三种方式:构造方法注入,setter注入,基于注解的注入。
  • 自动装配有三种模式:byType(类型模式),byName(名称模式)、constructor(构造函数模式)。

(2) bean作用域

  • singleton:单例模式,每次请求该Bean都将获得相同实例。(Spring默认作用域)
  • prototype:原型模式,每次请求该Bean,Spring都会新建一个Bean实例。
  • request:每次http请求产生一个新实例,只有在web应用中使用该作用域才有效。
  • session:每次http session请求产生一个新实例。

  (3) bean的生命周期

  1. 实例化 Instantiation
  2. 属性赋值 Populate
  3. 初始化 Initialization
  4. 销毁 Destruction

   主要逻辑都在doCreate()中,按顺序调用一下三个方法:

  1. eateBeanInstance() -> 实例化
  2. populateBean() -> 属性赋值
  3. initializeBean() -> 初始化

2.Spring AOP

(1) 面向切面编程,主要是日志记录、权限认证、安全控制、事务处理、异常处理。

(2) 底层原理:使用代理模式

  • JDK动态代理:必须是面向接口,目标业务类必须实现接口。通过反射代理目标信息,InvokeHandler来处理。
  • CGLIB动态代理:利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。

 如果目标对象实现了接口,默认使用JDK动态代理,也可以使用CGLIB代理;如果目标对象没有实现接口,则必须实现CGLIB。

(3) AOP注解方式:

@Aspect 注释修饰,声明为一个切面类。@Component注解注释为一个bean对象。

@Pointcut:定义切点。

@Around:环绕通知("execution(public * com.xxx.controller..*.*(..))")。

(4) 过滤器和拦截器

  • Filter:依赖Servlet容器,基于函数回调,filter一般实现统一设置编码、用户是否登录、权限访问等。拦截web访问url地址。
  • Interceptor:拦截器基于Java反射,使用代理模式。可以继承HandlerInterceptorAdapter类。拦截Action的访问。

3.Spring MVC

(1) 流程图

(2) 运行原理:

  • 客户端发起Http请求到DispatcherServlet
  • DispatcherServlet控制器寻找HanderMapping, 查询具体的Handler
  • HanderMapping调用HandlerAdapter执行handler
  • HandlerAdapter经过适配调用具体Controller,处理业务逻辑
  • Controller处理完成将ModelAndView返回DispatcherServlet
  • DispatcherServlet请求视图解析器ViewReslover解析ModelAndView

4.Spring 事务

(1) 四个原则:

  • 原子性:一个事务要么全部执行,要么不执行
  • 一致性:事务执行前后数据处于正确状态
  • 隔离性:并发事务之间互不影响
  • 持久性:事务一旦执行成功,数据永久存在

(2) 事务分类:

  • 编程式事务:在业务逻辑中自行实现事务
  • 声明式事务:通过XML配置或者注解@Transactional实现

(3) 隔离级别

  • ISOLATION_DEFAULT:用数据库默认隔离级别
  • ISOLATION_READ_UNCOMMITTED(未提交读):最低隔离级别,事务未提交,可被其他事务读取(幻读、脏读、不可重复读)
  • ISOLATION_READ_COMMITTED(提交读):一个事务修改的数据提交后才能被其他事务读取(幻读、不能重复读)(Oracle默认级别)
  • ISOLATION_REPEATABLE_READ(可重复读):保证多次读取同一数据是一致(幻读)(MySql默认级别)
  • ISOLATION_SERIALIZABLE(序列化):代价最高最可靠的隔离级别

(4) 事务传播性:

  • PROPAGATION_REQUIRED:支持当前事务,如果当前没有事务,就新建一个事务。(Spring 默认)
  • PROPAGATION_REQUIRES_NEW:新建事务,如果当前存在事务,把当前事务挂起。
  • PROPAGATION_SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行。
  • PROPAGATION_MANDATORY:支持当前事务,如果当前没有事务,就抛出异常
  • PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
  • PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
  • PROPAGATION_NESTED:如果一个活动的事务存在,则运行在一个嵌套的事务中。

4.Mybatis

(1) 封装JDBC操作,利用反射打通Java类与SQL语句之间的相互转换

(2) 工作原理:

  • 读取 MyBatis 配置文件:mybatis-config.xml
  • 加载映射文件。映射文件即 SQL 映射文件
  • 通过配置信息构建会话工厂 SqlSessionFactory
  • 由会话工厂创建 SqlSession 对象, 包含了执行 SQL 语句的所有方法
  • Executor 执行器:在 Executor 接口的执行方法中有一个 MappedStatement 类型的参数
  • 输入参数映射:输入参数类型可以是 Map、List 等集合类型,也可以是基本数据类型和 POJO 类型
  • 输出结果映射:输出结果类型可以是 Map、 List 等集合类型,也可以是基本数据类型和 POJO 类型

MyBatis框架的执行流程图

(3)mybaits中#和$的区别

  • #{}:占位符号,可以防止sql注入(替换结果会增加单引号‘’
  • ${}:sql拼接符号(替换结果不会增加单引号‘’,like和order by后使用,存在sql注入问题,需手动代码中过滤)表名作为变量时,必须使用 ${ }

5. Hibernate

(1) Hibernate缓存: 

  • Hibernate一级缓存: 一级缓存又称为“Session的缓存”, 不可以取消session缓存。
  • Hibernate二级缓存:  默认不会开启

       »  很少被修改的数据
  » 不是很重要的数据,允许出现偶尔并发的数据
  » 不会被并发访问的数据
  » 常量数据

(2) 缓存配置

在默认情况下,Hibernate会使用EHCache作为二级缓存组件。

<!-- a. 开启二级缓存 -->

  <property name="hibernate.cache.use_second_level_cache">true</property>

<!-- b. 指定使用哪一个缓存框架(默认提供的) -->

  <property name="hibernate.cache.provider_class">org.hibernate.cache.HashtableCacheProvider</property>

<!-- 开启查询缓存 -->

  <property name="hibernate.cache.use_query_cache">true</property>

 (3) 缓存算法,常见有三种: 

● LRU:(Least Rencently Used)新来的对象替换掉使用时间算最近很少使用的对象 

● LFU:(Least Frequently Used)替换掉按命中率高低算比较低的对象 

● LFU:(First In First Out)把最早进入二级缓存的对象替换掉 

(4) 二级缓存策略

● READ_ONLY:实体只读缓存(常用)

● NONSTRICT_READ_WRITE:实体非严格读/写缓存 

     允许更新,更新后缓存失效,需再查询一次。 

     允许新增,新增记录自动加到二级缓存中。 

     整个过程不加锁。 

  ● READ_WRITE:实体读/写缓存 

     允许更新,更新后自动同步到缓存。 

     允许新增,新增记录后自动同步到缓存。 

     保证read committed隔离级别及可重复读隔离级别(通过时间戳实现) 

     整个过程加锁,如果当前事务的时间戳早于二级缓存中的条目的时间戳,说明该条目已经被别的 

     事务修改了,此时重新查询一次数据库,否则才使用缓存数据,因此保证可重复读隔离级别。 

     读写缓存和不严格读写缓存在实现上的区别在于,读写缓存更新缓存的时候会把缓存里面的数据换成一个锁 

  ● TRANSACTIONAL:实体事务缓存 

      缓存支持事务,发生异常的时候,缓存也能够回滚,只支持jta环境 

  ● Collection集合缓存 

(5)查询缓存

  • 查询缓存缓存的不是对象而是 id
  • 只有当HQL 完全相同时,包括参数设置都相同时,查询缓存才会起效

三、Spring Boot/Spring Cloud

1、原理

Eureka:注册中心,心跳机制,30时间s,最长时间90s。底层是ConcurrentHashMap实现数据存取,有多层缓存机制,读缓存,写缓存等多级。

2、Eureka服务注册与发现

在这里插入图片描述

(1) Euraka Server:注册中心服务端

  • 服务注册: 服务提供者启动时,通过Euraka Client向Euraka Server注册元数据(IP 地址、端口、状态等信息)。Eureka采用的是ConcurrentHashMap来存储注册表信息,使用二层缓存机制来维护整个注册表。
  • 提供注册表: 服务消费者调用服务,若本地没有,会去服务端Euraka Server拉取存放本地。

(2) Euraka Client:Eureka Client 会拉取、更新和缓存 Eureka Server 中的信息。

(3) Renew: 服务续约,Eureka Client 会每隔 30 秒发送一次心跳来检测续约,告知 Eureka Server 该 Eureka Client 运行正常。

如果 Eureka Server 在 90 秒内没有收到 Eureka Client 的续约,Server 端会将实例从其注册表中删除。

(4) 自我保护机制:

Eureka Server 在运行期间会去统计心跳失败比例在 15 分钟之内是否低于 85%,如果低于 85%,Eureka Server 即会进入自我保护机制。

(5) Eureka集群:

Eureka Server 高可用集群,集群相互之间通过 Replicate(复制)来同步数据,不区分主从,如果某台Euraka Server宕机,Euraka Client的请求会自动切换新的Eureka Server。

Eureka 提供了 Region(地址位置的区域)和 Zone(具体机房) 两个概念来进行分区。

(6) 工作流程:

  1. Eureka Server 启动成功,等待服务端注册。在启动过程中如果配置了集群,集群之间定时通过 Replicate 同步注册表,每个 Eureka Server 都存在独立完整的服务注册表信息
  2. Eureka Client 启动时根据配置的 Eureka Server 地址去注册中心注册服务
  3. Eureka Client 会每 30s 向 Eureka Server 发送一次心跳请求,证明客户端服务正常
  4. 当 Eureka Server 90s 内没有收到 Eureka Client 的心跳,注册中心则认为该节点失效,会注销该实例
  5. 单位时间内 Eureka Server 统计到有大量的 Eureka Client 没有上送心跳,则认为可能为网络异常,进入自我保护机制,不再剔除没有上送心跳的客户端
  6. 当 Eureka Client 心跳请求恢复正常之后,Eureka Server 自动退出自我保护模式
  7. Eureka Client 定时全量或者增量从注册中心获取服务注册表,并且将获取到的信息缓存到本地
  8. 服务调用时,Eureka Client 会先从本地缓存找寻调取的服务。如果获取不到,先从注册中心刷新注册表,再同步到本地缓存
  9. Eureka Client 获取到目标服务器信息,发起服务调用
  10. Eureka Client 程序关闭时向 Eureka Server 发送取消请求,Eureka Server 将实例从注册表中删除

3、ribbon客服端负载均衡

实现客户端负载均衡,可以自定义Ribbon Client, 也可以yml中配置负载的策略(随机、轮询、权重),默认是轮询。

4、hystrix熔断

(1) 服务雪崩效应:分布式系统中经常会出现某个基础服务不可用造成整个系统不可用的情况(如:服务器宕机、缓存击穿、程序bug等)

(2) 服务之间相互调用,分布式环境中经常出现某个服务节点故障,调用失败。熔断器就是当服务调用失败时候提供保障服务正常运行不被卡死。@HystrixCommand(fallbackMethod = "fallback")

(3) 断路器机制:hystrix存在三种状态:CLOSED、OPEN、HALF_OPEN。默认closed时,服务在一定时间内(10s) 请求次数达到了某个阀值(20次),并且错误率也达到了某个阀值(>50%)此时断路器变成了OPEN的状态, 过了一定的时间(5s)将会放行一个请求,此时变成HALF_OPEN状态,如果可以访问就变成CLOSED否则变成OPEN状态。

5、feign 基于Ribbon和Hystrix的声明式服务调用组件

JAX-RS 注意不能使用GetMapping不支持,PathVariable需设置value

6、zuul网关(通过过滤器来实现)

生命周期:

  • pre Filter:这种过滤器再请求被路由之前调用。如:认证鉴权、限流等。
  • route:将请求路由到微服务,构建发送给微服务的请求,HttpClient或Ribbon请求微服务。
  • post:在路由到微服务执行之后执行,返回结果或者发生异常后执行的filter。
  • error:发生异常,执行改filter。

7、Springcloud config 配置中心(实现方式是git管理配置)

  • 环境部署之前,将所需的配置信息推送到配置仓库
  • 启动配置中心服务端,将配置仓库的配置信息拉取到服务端,配置服务端对外提供REST接口
  • 启动配置客户端,客户端根据 spring.cloud.config 配置的信息去服务器拉取相应的配置

8、zipkin服务追踪

分布式服务追踪,收集来自各个系统的监控数据。

  • Collector 接受或者收集各个应用传输的数据
  • Storage:负责存储接收到的数据,默认是存储在内存当中的,也可以支持存在MySQL当中
  • API:负责查询Storage中存储的数据,主要是提供给Web UI来使用
  • Web:主要是提供简单的web界面

四、redis 

1、数据结构

redis是一个key-value存储系统,常见数据结构:

  • String:常用命令(set,get,decr,incr,mget)value可以是String,也可以是数字。常规计数:微博数,粉丝数等
  • Hash:常用命令(hget,hset,hgetall)跟map一样,适合用于存储对象,如:用户信息,商品信息等。
  • List:常用命令(lpush,rpush,lpop,rpop,lrange)list就是双向链表,即可以支持反向查找和遍历。如:微博粉丝列表、最小消息排行等。
  • Set:常用命令(sadd,spop,smembers,sunion)与list类似是一个列表的功能,可以自动去重复数据。如:共同好友、好友推荐。
  • Sorted Set:常用命令(zadd,zrange,zrem,zcard)和set相比,sorted set增加了一个权重参数score,能够按score进行有序排列。如:礼物排行榜,弹幕消息。

  • hyperloglog:统计页面访问量等数据。

  • GEO:用于处理地理位置的信息,可以保存地理位置,可以计算地理位置的距离。

2、分布式锁

(1) 原理

  • 加锁:(setnx) key设置一个值(SET lock_key random_value NX PX 5000)
  • 解锁:删除key 先判断当前锁的字符串random_value是否与传入的值相等,是的话就删除Key,解锁成功。
  • 锁超时:避免死锁,给定一个过期时间。

(2) redisson实现分布式锁

  • 原子性,lua脚本发送给redis。
  • watch dog自动延期机制,后台守护线程,每隔10s检查。

3、缓存:数据保存在内存,存取速度快,并发能力强(mybatis二级缓存)

持久化方式:(开机的时候扫描持久化文件)

  • RDB:RDB 持久化可以在指定的时间间隔内生成数据集的时间点快照
  • AOF:AOF 持久化记录服务器执行的所有写操作命令

4、缓存失效

1. 缓存过了失效时间,缓存被删除,需要更新缓存。

  • 互斥锁:发起请求,将缓存中该数据上锁。无缓存,则等待数据库查询,并更新缓存。

  • 设置不同的失效时间:缓存失效时间错开,加个随机数等。

2.redis内存不足时的策略

(1)noeviction: 拒绝写操作, 读、删除可以正常使用。默认策略,不建议使用;
(2)allkeys-lru: 移除最近最少使用的key,最常用的策略;
(3)allkeys-random:随机删除某个key,不建议使用;
(4)volatile-lru:在设置了过期时间的key中,移除最近最少使用的key,不建议使用;
(5)volatile-random:在设置了过期时间的key中,随机删除某个key,不建议使用;
(6)volatile-ttl: 在设置了过期时间的key中,把最早要过期的key优先删除。

5、缓存雪崩

(1) 雪崩:网络不稳定或服务器宕机,服务调用者阻塞,引发雪崩连锁效应。

(2) 缓存雪崩:当缓存服务器重启或者大量缓存集中在某一个时间段失效,直接访问数据库,数据库宕机,从而造成系统的崩溃。

(3) 雪崩解决方案:

  • 熔断模式:使用Hystrix(熔断、降级、限流)一旦发现当前服务的请求失败率达到预设的值,Hystrix将会拒绝随后该服务的所有请求,直接返回一个预设的结果。这就是所谓的“熔断”
  • 隔离模式:缓存失效后,通过枷锁,对某个key只允许一个线程查询数据和写缓存。
  • 限流模式:对请求设置最高的QPS阈值,高于阈值直接返回。

6、缓存穿透

一般缓存系统,都是按照key去缓存查询数据,如果不存在对应的value,就去数据库查询。频繁访问数据库,这就叫缓存穿透。

1.缓存空数据:查询结果为空的key也存储在缓存中,该key的查询请求时,缓存直接返回null,而无需查询数据库。

2.BloomFilter(推荐):bloomFilter就类似一个hashset(bigmap),判断某个key是否存在,不存在直接返回null。

7、集群(高可用、可扩展性、分布式、容错)

  • 主从复制

    将Master(读写操作)的数据自动同步到Slave数据库。

    实现原理:

  1. 从服务器向主服务器发送SYNC命令
  2. 主服务器收到SYNC命令后,执行BGSAVE命令,在后台生成RDB文件,使用缓冲区记录从现在开始执行的所有的写命令。
  3. 当主服务器的BGSAVE命令执行完毕后,主服务器后将BGSAVE命令生成的RDB文件发送给从服务器,从服务器接收并载入这个RDB文件,将自己的数据库状态更新至主服务器执行BGSAVE命令时的数据库状态。
  4. 主服务器将记录在缓冲区里面的所有写命令发送给从服务器,从服务器执行这些写命令,将自己的数据库状态更新至主服务器数据库当前所处的状态。

  缺点:master 宕机需要手动将slave提升为master。

  • 哨兵模式

   Redis Sentinel(哨兵):节点数量要满足 2n+1奇数个。

   Sentinel功能:

  1. 监控主从数据库是否正常运行
  2. master出现故障时,自动将slave转化为master
  3. 多哨兵配置的时候,哨兵之间也会自动监控
  4. 多个哨兵可以监控同一个redis

  缺点:Redis较难支持在线扩容,选举leader(slave转化为master)时候服务无法工作。

阿里架构师进阶专题:Redis集群的5种使用方式,各自优缺点分析

  • Cluster集群(3.0版本)推荐使用 

   (1) Redis Cluster是多个Redis主从节点,在分区期间还提供了一定程度的可用性。

   (2) Redis集群数据分片,分片采用slot(槽)的概念,一共分成16384个槽。对于每个进入Redis的键值对,每个key通过CRC16校验,分配到这16384个slot中的某一个中。

   比如当前集群有3个节点:(slots)默认是平均分配槽,推荐工具redis client

  • 节点 A 包含 0 到 5500号哈希槽;

  • 节点 B 包含5501 到 11000 号哈希槽;

  • 节点 C 包含11001 到 16384号哈希槽;

  (3) 扩容

  • 增加节点的顺序是先增加Master主节点,然后在增加Slave从节点

  • 分配数据槽,从其他节点分配槽。

五、数据库

1.mysql

1. InnoDB:存储引擎,支持行锁(B+Tree)

局部内存,按照页取数据,每页16kb,放在内存中  每行数据最多存65535个字节 占位符3个

数据插入的时候是B+Tree聚集索引

按照主键创建索引,没有使用唯一索引,没有才使用rowId

640?

2. myISAM:非聚集性索引

3. mysql explain 详解

explain查看sql执行计划,查看sql有没有使用索引,有没有全表扫描。

  • id:选择标识符
  • select_type:表示查询的类型。
  • table:输出结果集的表
  • partitions:匹配的分区
  • type:表示表的连接类型
  • possible_keys:表示查询时,可能使用的索引
  • key:表示实际使用的索引
  • key_len:索引字段的长度
  • ref:列与索引的比较
  • rows:扫描出的行数(估算的行数)
  • filtered:按表条件过滤的行百分比
  • Extra:执行情况的描述和说明

 4. mysql读写分离原理及配置

通过主从复制的方式来同步数据,再通过读写分离来提升数据库的并发负载能力。

 复制的工作过程

1) 在每个事务更新数据完成之前,master在二进制日志记录这些改变。写入二进制日志完成后,master通知存储引擎提交事务。

2) Slave将master的binary log复制到其中继日志。首先slave开始一个工作线程(I/O),I/O线程在master上打开一个普通的连接,然后开始binlog dump process。binlog dump process从master的二进制日志中读取事件,如果已经跟上master,它会睡眠并等待master产生新的事件,I/O线程将这些事件写入中继日志。

3) Sql slave thread(sql从线程)处理该过程的最后一步,sql线程从中继日志读取事件,并重放其中的事件而更新slave数据,使其与master中的数据一致,只要该线程与I/O线程保持一致,中继日志通常会位于os缓存中,所以中继日志的开销很小。

2. Sql优化

  1. sql避免全表扫描,避免  ‘ * ‘ 或 is null 或 != 或 <> 或 or 等查询
  2. 尽量使用(NOT) EXISTS 替代( NOT)IN
  3. 通配符%,like '%c%'不会使用索引,而 like 'c%'会使用所有
  4. 合理的建立索引能够加速数据读取效率,不合理的建立索引反而会拖慢数据库的响应速度
  5. 索引越多,更新数据的速度越慢
  6. 尽量用union all代替union
  7. 对于联合索引来说,要遵守最左前缀法则,创建联合索引的时候一定要注意索引字段顺序,常用的查询字段放在最前面。

左右连接:

       left join (左连接):返回包括左表中的所有记录和右表中连接字段相等的记录。
  right join (右连接):返回包括右表中的所有记录和左表中连接字段相等的记录。
  inner join (等值连接或者叫内连接):只返回两个表中连接字段相等的行。
  full join (全外连接):返回左右表中所有的记录和左右表中连接字段相等的记录。
————————————————
版权声明:本文为CSDN博主「灰太狼_cxh」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_39220472/article/details/81193617

3. Oracle

(1) LATCH:Latch是用于保护SGA区数据结构的一种串行化锁定机制,对内存数据结构提供互斥访问的一种机制。

(2) Latch和Lock的区别:

  1.  Latch是对内存数据结构提供互斥访问的一种机制,而Lock是以不同的模式来套取共享资源对象,各个模式间存在着兼容或排斥,从这点看出,Latch的访问,包括查询也是互斥的,任何时候,只能有一个进程能pin住内存的某一块,幸好这个过程是相当的短暂,否则系统性能将没的保障,现在从9I开始,允许多个进程同时查询相同的内存块,但性能并没有想象中的好。
  2.  Latch只作用于内存中,他只能被当前实例访问,而L ock作用于数据库对象,在RAC体系中实例间允许Lock检测与访问
  3.  Latch是瞬间的占用,释放,Lock的释放需要等到事务正确的结束,他占用的时间长短由事务大小决定
  4.  Latch是非入队的,而Lock是入队的
  5.  Latch不存在死锁,而Lock中存在(死锁在Oracle中是非常少见的)

(3) 按锁的机制分类

       排他锁( X ):如果事务T对对象A加上排他锁,则只允许T对A对象读取和修改,其他事务不能对A增加任何锁,直到T释放加载A上的排他锁

  共享锁( S ):如果事务T对表A加上共享锁,则事务T可以读该表,但是不能修改该表数据。其他事务也只能对该对象加上共享锁,但是不能加上排他锁。这就保证了其他事务可以读A表数据,但是不能修改A表数据(这就不会影响到读该表的事务了,有利于并发操作)。

(4) 按操作行为分类

  • DML Locks  保证并发情况下的数据完整性
  1. Row Locks (TX) 称为行级锁。当事务执行 DML 语句(INSERT, UPDATE, DELETE, and SELECT with the FOR UPDATE clause)时会申请TX 类型的锁,TX 的机制是排他锁。
  2. Table Locks (TM) 称为表级锁。当Oracle执行 DML 语句时,系统自动在所要操作的表上申请TM类型的锁。
  • DDL Locks  用于保护数据库对象的结构
  1. 排他DDL锁(Exclusive DDL lock):阻塞其他会话得到该对象的DDL锁或DML锁。在DDL操作期间你可以查询一个表,但是无法以任何方式修改这个表。 
  2. 共享DDL锁(Share DDL lock):保护所引用对象的结构,使其结构不会被其他会话修改
  • System Locks 保护数据库的内部结构

4.数据库设计

(1)无限分级树形结构的数据库表设计:采用左右值编码来存储。

只用一条查询语句即可得到某个根节点及其所有子孙节点的先序遍历。由于消除了递归,在数据记录量较大时,可以大大提高列表效率。但是,这种编码方案由于层信息位数的限制,限制了每层能所允许的最大子节点数量及最大层数。同时,在添加新节点的时候必须先计算新节点的位置是否超过最大限制。

参考:https://blog.csdn.net/comiunknown/article/details/1586020

六、Zookeeper+Dubbo 

1. Dubbo: Dubbo是一个分布式服务框架,致力于提供高性能和透明化的RPC(Remote Procedure Call)远程服务调用方案,以及SOA服务治理方案.

2.服务注册与发现:

在这里插入图片描述

 

0 服务容器负责启动,加载,运行服务提供者。

1. 服务提供者(生产者)在启动时,向注册中心注册自己提供的服务。

2. 服务消费者在启动时,向注册中心订阅自己所需的服务。

3. 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。

4. 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。

5. 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值