当前位置: 首页 > news >正文

有哪些网站可以做推广包包邢台制作

有哪些网站可以做推广包包,邢台制作,您的网站对百度设置了ip封禁,国内最新军事新闻最新消息文章目录 基础String、StringBuffer、StringBuilder的区别jvm堆和栈的区别垃圾回收标记阶段清除阶段 异常类型双亲委派机制hashmap和hashtable concurrentHashMap 1.7和1.8的区别java的数据结构排序算法#xff0c;查找算法堆排序 ThreadLocal单例模式常量池synchronizedsynch… 文章目录 基础String、StringBuffer、StringBuilder的区别jvm堆和栈的区别垃圾回收标记阶段清除阶段 异常类型双亲委派机制hashmap和hashtable concurrentHashMap 1.7和1.8的区别java的数据结构排序算法查找算法堆排序 ThreadLocal单例模式常量池synchronizedsynchronize和lock的区别和使用场景LOCK的实现类如何避免死锁hashMap 存储大量数据为什么HashMap中的键往往都使用String线程池ifelse嵌套优化数组和链表的区别hashMap和hashtable的区别常用注解线程的状态volatile 如何保证可见性和指令重排异步开发set如何保证不重复深拷贝和浅拷贝设计模式工厂模式抽象工厂模式 CASTCP如何保证可靠性三次握手和四次挥手HTTP分布式和微服务的区别优化链表查询速度 数据库如何优化联表查询sql优化方案如何查看有没有走索引mysqloracle 如何优查询速度建表索引的建立规则索引以及实现方式索引的类型聚簇索引和非聚簇索引联合索引的命中规则数据库事务select for update什么情况下会发生锁表幻读mysql redo和undomysql和oracle的区别myisam和InnoDB的区别mybatis的条件语句parameter 入参类型mybatis的缓存机制mybatis如何封装返回结果数据库死锁写sql springspringbootspringcloudSpringBoot启动过程spring.factories文件 IOC和AOPJDK动态代理和CGLIB代理有什么区别Spring AOP 和 AspectJ AOP 有什么区别spring事务Spring事务失效的场景 spring对象生命周期SpringBoot启动原理Spring Factories拦截器和过滤器如何解决循环依赖Spring SpringBoot SpringCloud的区别Spring Filter 怎么写获取Spring容器对象SpringAOP redis哪里用到的redis,时效redis数据结构Stirng 底层实现 redis的淘汰策略缓存击穿缓存雪崩redis的锁机制redis分布式锁redis线程模型部署方式 kafka使用消息队列的原因kafka 如何解决消息重复kafka如何保证消息不丢失kafka什么时候进行rebalancekafka 消息积压怎么办kafka分区和消费者的关系分区数量消费者分区分配策略 其他微服务组件eureka注册中心机制限流算法一致性哈希 无状态的服务消费降级 linux命令其他数据结构-树树特点概念树的表示 二叉树概念特点 特殊的二叉树二叉树的性质二叉树的存储结构二叉树的遍历堆优先级队列 数据格式标准化 基础 String、StringBuffer、StringBuilder的区别 jvm 概念 1.JVM是java虚拟机用来执行字节码文件二进制 class文件的虚拟计算机。除了javaScalaGroovy和Python等其他语言经过处理也可以转换成字节码文件。 2.JVM运行在操作系统上和硬件没有任何关系。 跨平台原理编译后的字节码文件和平台无关在java虚拟机上运行。统一的class文件结构就是jvm的基石。 JVM分类 类加载器子系统运行时数据区执行引擎 JIT编译器主要影响性能编译执行解释器负责响应时间逐行解释字节码 程序执行方式有三种静态编译执行动态编译执行动态解释执行。 在java中程序的执行以动态解释为主动态编译为辅。静态编译如C直接编译成可执行文件exe 机器码和字节码的区别 机器码是CPU直接读取速度快字节码需要直译器转译后才能变成机器码。 JDK包括了编译器等开发工具和JRE JRE包括了运行类库和JVM JVM有两种运行方式 client和server Client启动快运行慢。Server启动慢运行快。 JVM流程 .java文件编译器解释为class文件交给jvm执行引擎执行执行时会用空间存储数据就是JVM内存。 JVM内存主要为堆栈方法区本地方法区程序技术器。 程序计数器 当前线程执行字节码的行数指示器用来记录虚拟机字节指令地址线程私有。执行本地方法时为空。也称为PC寄存器。 字节码解释器在工作时通过改变计数器的值来选取下一跳执行的代码分支循环跳转异常处理线程恢复等功能都依赖程序计数器完成。 Java虚拟机的多线程的实现方式通过轮流切换并分配处理器执行时间实现。本地方法栈 和虚拟机栈作用类似区别是一个执行java方法一个执行native方法。线程私有。 与虚拟机栈一样本地方法栈区域也会抛出StackOverflowError 和OutOfMemoryError异常。方法区1.8为元数据区 主要是存储类信息静态变量编译后的代码字节码等数据常量池。线程共享栈 栈创建线程时创建存储栈帧线程私有。栈桢在执行方法时创建包括局部变量表操作数栈动态链接方法出口等信息。 局部变量表用来存储方法参数和方法中定义的局部变量 操作数栈用于保存计算中的临时变量和中间结果是JVM执行引擎的一个工作区通过入栈和出栈进行数据访问。Java虚拟机的解释引擎是基于栈的执行引擎其中的栈指的就是操作数栈。 动态链接指向方法区的运行时常量池 在Hotspot的演变过程中 Java6及之前方法区存在永久代保存有静态变量Java7进行去永久代工作虽然还保留着但静态常量池如字符串常量池已经移动到堆中Java8移除永久代类型信息、域Field信息、方法Method信息存放在元数据区字符串常量池、静态变量存放在堆区 堆中分为老年代和年轻代年轻代中分为eden区和存活区区中分为s0和s1 新生成的对象在Eden区 触发Minor GC后幸存的对象存入s0,再次触发Minor GC后eden区和s0的对象存入s1中s0清空。 每次移动递增计数器超过默认值15 (通过 -XX:MaxTenuringThreshold 设置)移动到老年代中eden中没有足够内存分配也会分配到老年代。 老年代靠major GC。 新生代的回收机制采用复制算法老生代采用的回收算法是标记整理算法。 堆和栈的区别 栈创建线程时创建存储栈帧线程私有。栈桢在执行方法时创建包括局部变量表操作数栈动态链接方法出口等信息。栈中数据生命周期短出栈即失效。栈超过虚拟机允许最大深度StackOverflow 堆存储对象线程共享。堆中数据声明周期长由垃圾回收机制不定期回收。空间不够扩展申请不到足够的内存oom 垃圾回收 参考【Java】垃圾回收 作用区域频繁发生在年轻代较少发生在老年代极少发生在方法区永久代/元空间 引用类型才需要垃圾回收基本数据类型不需要。 内存泄漏这个对象不再使用但是GC没法回收。 垃圾回收分为标记和清除阶段 标记阶段 引用计数法 引用对象1引用失效-1。为0则认为可以进行回收。 优点实现简单垃圾容易辨识判定效率高回收没有延迟 缺点 1.需要单独的字段存储计时器增加空间开销 2.每次赋值都需要进行加减法增加时间开销 3.无法处理循环引用的情况 可达性分析算法 通过被称为引用链GC Roots的对象作为起点从这些节点开始向下搜索走过的路径被称为引用链。当一个对象到GC roots没有任何引用相连时证明该对象不可用。 同样具备实现简单和执行高效的特点能有效解决循环依赖的问题防止内存泄漏的发生。 可达性分析必须在一个能保证一致性的环境下进行。这点也是导致GC必须进行stop the world的一个重要原因。 所谓GC roots根集合就是一组必须活跃的引用。可以是 1.虚拟机栈中引用的对象。 2.静态变量引用的对象除非类卸载否则他的引用对象一直存在。 3.所有被同步锁持有的对象。同步锁要是被销毁同步就失效了 清除阶段 JVM中常见的清除方法1.标记清除法 2.标记复制法 3.标记压缩法 标记清除法: 把存活的对象进行标记清除死亡对象。 当堆中有效空间被用完就会stw。然后进行标记和清除。要把用户线程停止保持一致性防止用户线程产生垃圾。 缺点 1、效率不高需要遍历 2、进行GC时需要停止整个应用程序用户体验差。 3、清理出来的空闲空间不是连续的会产生碎片。 标记复制法 内存分为两块每次只用其中的一块。垃圾回收时将存活的对象复制到未使用的一块清除不可达的对象。 年轻代S0和S1也是用的复制算法。 优点1.没有标记和清除的过程实现简单运行高效2.复制后能保证空间的连续性 缺点损失一半空间。 适合回收对象多的场景复制少。适用于年轻代。 标记压缩算法 1.第一阶段和标记清除算法相同从根节点开始标记被引用对象。 2.第二阶段将所有的存活对象压缩到内存的一端按顺序排放之后清理边界外所有的空间。 标记压缩算法等同于标记清除算法加压缩。 优点1.没有碎片 2.不会内存减半 缺点 1.效率低因为会进行整理压缩 2.移动对象时如果对象被其他对象引用需要调整引用的地址。 3.移动过程中会stw 分代收集算法 不同生命周期对象采用不同的算法提高效率。 1.年轻代区域小生存周期短回收频繁。 采用复制算法内存利用率不高hotspot中两个survivor的设计得以缓解 2.老年代区域大生命周期长回收不频繁。 采用标记清除或者标记清楚整理算法。 Old GC: 只收集 old gen 的 GC。只有垃圾收集器 CMS 的 concurrent collection 是这个模式 Mixed GC: 收集整个 young gen 以及部分 old gen 的 GC。只有垃圾收集器 G1 有这个模式 年轻代的gc称为Minor GC。新生代Eden区满的时候触发Minor GC Full GC是回收整个堆。触发条件为 1.System.gc 2.老年代空间不足 3.方法区空间不足 4.Minor GC后进入老年代的平均大小大于老年代的可用大小 5.由Eden区from 区向to区复制对象大于to的内存也大于老年代的内存。 单例模式是静态的生命周期长如果中间引用了别的对象那么这个对象一直不会被回收。 Major GC通常是跟full GC是等价的收集整个GC堆,但也有说法是old GC。 异常类型 为了及时有效的处理异常java引入了异常类。所有的异常都是Thorwable的子类。 Throwable下有两个分支Excepiton和Error。 异常类主要分为三种类型 系统错误Error 系统错误是由虚拟机抛出的用户无法处理如 OutOfMemoryError 内存耗尽 NoClassDefFoundError 无法加载某个Class StackOverflowError 栈溢出 编译时异常Exception 除了其子类RuntimeException ​ 在编译时期抛出的异常在编译期间检查程序可能出现的问题如果有提前防范捕获处理 应用逻辑处理 NumberFormatException 数值类型的格式错误FileNotFoundException 未找到文件SocketException 读取网络失败。 编写逻辑造成 NullPointerException 对某个 null 的对象调用方法或字段IndexOutOfBoundsException 数组索引越界 运行时异常 RuntimeException java虚拟机正常运行期间抛出的异常。这类异常只有在运行时才能发现是否有异常。 RuntimeExceptionError以及他们的子类都被称为免检类其他异常被称为必检类Checked Exception编译器会强制程序员检查并try-catch处理或者在方法头进行声明。如数组越界和空指针。 双亲委派机制 参考【Code皮皮虾】带你盘点双亲委派机制【原理、优缺点】以及如何打破它 双亲委派机制是在JDK1.2后才引入的。 加载类时不直接加载委托给自己的父类加载器递归直到加载成功。否则自己加载。 目的1.防止类的重复加载。2.避免核心类遭到修改 Java提供四种类加载器 BootStrap 启动类加载器加载java核心类库 javahome/lib下的jar包rt.jar等Ext 扩展类加载器加载java_home/ext/libApplication 应用程序类加载器主要用来加载当前应用claspath下的所有类。User 用户自定义类加载器用户自定义加载指定路径下的类 什么时候破坏这个机制 JDBC Connection conn DriverManager.getConnection(jdbc:mysql://localhost:3306/mysql, root, 0000);获取连接时的DriverManager因为处于rt下会被启动类加载器加载。 类加载时会执行静态方法。其中会加载所有实现了Driver接口的实现类但是这些实现类都是第三方提供的启动类加载器无法加载因此引入了ThreadContextClassLoader线程上下文类加载器默认情况下是AppClassLoader来使用应用程序加载器破坏双亲委派机制。 tomcat 比如tomcat web容器里面部署很多应用程序但是每个应用依赖的第三方类库版本不同但是类的全路径名可能相同。 双亲委派无法加载多个相同的class文件因此tomcat给每个web容器单独同一个webAppClassLoader加载器。实现隔离性优先加载Web应用自己定义的类加载不到再交给CommonClassLoader加载这和双亲委派机制恰好相反。 如何打破双亲委派机制 1.自定义类加载器继承ClassLoader不想打破只需要重写findClass,想打破重写整个loadClass方法设定自己的类加载逻辑。 2.使用线程上下文类加载器 public class Main {public static void main(String[] args) {ClassLoader contextClassLoader Thread.currentThread().getContextClassLoader();}}hashmap和hashtable concurrentHashMap 1.7和1.8的区别 hashmap 1.7 数组链表 1.8 数组链表红黑树 ConcurrentHashMap简介 hashmap线程不安全。 hashtable线程安全方法直接加synchronize锁性能低下。 concurrentHashMap 对数组增加了voliate关键字 1.7 segmenthashentry 分段锁 1.8 cassynchronized getSize 1.7获取三次两次一致返回三次拿不到加锁进行计算 1.8 获取basecount put方法 计算哈希值 当前map是否为空为空先初始化 判断哈希值所在位置是否有值没值直接cas替换 有值判断key是否相等相等不变 不相等判断是红黑树还是链表进行循环key相同替换未找到相同的进行增加 java的数据结构 16 张图带你搞懂 Java 数据结构 排序算法查找算法 常见面试的查找和排序算法 面试高频考点 – 常见的排序算法(7种) 插入排序从第二个元素开始每选择一个元素这个元素前面就是有序区间后面就是无序区间。在有序区间选择合适位置插入。 On^2 稳定希尔排序将数据分成n组进行排序主键缩小n值 On^1.3~1.5 不稳定选择排序每次将最大或者最小放到最后知道所有都排完 On^2不稳定冒泡排序在无序区间通过相邻数的比较将最大的数据放入一侧持续整个过程直到数组整体有序。 时间复杂度 最坏情况 O(N^2) 最好情况 O(N) (一趟就有序了) 空间复杂度 O(1) 稳定性 稳定 堆排序 将数组放入堆调整每一颗子树使之变成大根堆。 O(n * logN) O(1) 不稳定 package com.ln.mybatis.sort;import java.util.Comparator; import java.util.PriorityQueue;//求前k个最小的元素 public class sortTest {public static void main(String[] args) {int test[]{10,11,12,32,434,1,2,3,4,5,6,7,8,9,10,11,12,32,434,54645,6565,757,323,32};int[] topktopK(test,3);for (int i 0; i topk.length; i) {System.out.println(topk[i]);} // System.out.println(topK(test,3));}// 找出数组中最小的元素建立大根堆然后对根节点进行比较大则不放小则替换public static int[] topK(int[] arr,int k){ // 创建一个大小为k的大根堆PriorityQueueInteger maxHeap new PriorityQueue(k, new ComparatorInteger() {Overridepublic int compare(Integer o1, Integer o2) {return 02-01;}});for (int i 0; i arr.length; i) {if(ik){maxHeap.offer(arr[i]);}else {if(maxHeap.peek()arr[i]){maxHeap.poll();maxHeap.offer(arr[i]);}}}int[] retnew int[k];for (int i 0; i k ; i) {ret[i]maxHeap.poll();}return ret;} } 冒泡排序 最坏 On^2 最好 On 稳定快速排序重要 1.找一个基准值存储 2.比较左边和右边小于的放到右边大于的放到左边 3.然后对左右区间按同样的方式处理 nlogn 最坏n^2 ThreadLocal 线程局部变量在多并发的情况下使用。 在使用完成之后需要remove掉避免内存泄漏。ThreadLocal变量的key为弱引用使用完成后TheadLocal没有使用的强引用后会释放但是value是强引用只要线程存活一直存在强引用需要通过remove删除Entry.线程池尤为严重。 单例模式 public class SingleTonObj {private static volatile SingleTonObj singleTonObj;private SingleTonObj() {}public static SingleTonObj getObj() {if (singleTonObj null) {synchronized (SingleTonObj.class) {if (singleTonObj null) {singleTonObj new SingleTonObj();}}}return singleTonObj;} volatile 其中volatile关键字是为了防止指令重排 jvm创建对象分三步1.分配空间2.实例化对象3.将对象指向空间 如果不使用volatilejvm会优化将3移动到2前面这样a线程走到了3还没2。b线程第一个判null已经不成立返回了没有实例化完成的对象第二个判空的原因是 a,b都经过了第一次判空但是a先拿到了class的锁进行了实例化然后进行释放锁b拿到锁之后需要在进行判空防止重复实例化破坏单例Happens-Beforene内存模型和程序模型顺序 程序A在B前线程中A就会在B前执行 Happens-Beforene不会破坏代码中的先后顺序但是在不同代码或者不同线程中的顺序无关 常量池 深度剖析Java常量池 通过javap命令生成更可读的JVM字节码指令文件javap -v Math.class Class常量池可以理解为Class文件中的资源仓库。Class文件中除了包含版本字段方法接口还有一项信息就是常量池用于存放编译器生成的各种字面量和符号引用。 int a1 1为字面量a为符号引用 字面量是指由字母数字构成的字符串和数值常量字面量只可以右值出现。 符号引用是编译原理中的概念是相对于直接引用来说的主要包括了以下三大类 类和接口的全限定类名字段的名称和描述符方法的名称和描述符 运行时常量池只有运行时被加载到内存中这些符号才有对应的内存地址那么这些常量池一旦被装入内存就变成运行时常量池对应的符号引用会转变为加载到内存区域的代码的直接引用也就是动态链接 字符串常量池 jdk1.6以及之前运行时常量池在永久代运行时常量池包含字符串常量池。 jdk1.7: 有永久代但是逐渐去永久代字符串常量池从永久代的运行时常量池分配到堆中。 jdk1.8:无永久代运行时常量池在元空间字符串常量池依然在堆中。 intern 1.6 在常量池中寻找equal()相等的字符串存在则返回常量池中的引用。不存在就在永久代的常量池中新建一个实例放入常量池中并返回。 1.7不存在永久代常量池存在返回不存在可以直接指向堆上的实例。 String s0zhigan; String s1zhigan; String s2zhi gan; System.out.println( s0s1 ); //true System.out.println( s0s2 );//true字面量声明的字符串常量在编译期就能确定会存储在常量池中地址相同。 String s0zhigan; String s1new String(zhigan); String s2“zhi”new String(gan); System . out . println ( s0 s1 ); // false System . out . println ( s0 s2 ); // false System . out . println ( s1 s2 ); // falsenew String() 的字符串不是常量不能在编译期确定不放入常量池他们有自己的地址空间。 String aa3.4 String ba3.4; System . out . println ( a b ); // truejvm对于加号连接在编译器就会进行优化,将常量字符串连接。 String aab; String bbb; String babb; System . out . println ( a b ); // false在中带有引用JVM无法优化因为引用无法在编译期确认只能在程序运行是动态分配并将连接后的新地址赋值给B因此为false如果bb是一个方法的返回结果同样的原因如果bb用final修饰那么他在编译期会被解析为常量比较的结果为true。 String s a b c ; // 就等价于 String s abc; String a a ; String b b ; String c c ; String s1 a b c ;s1这个就不一样可以通过观察器JVM指令码发现s1的操作会变成如下: StringBuilder tempnew StringBuilder(); temp.append(a).append(b).append( c ); String stemp.toString(); Java八大基本对象的包装类型除了两个浮点型其他都有常量池。另外ByteShortIntLong,Character这五种整形的包装类也只是对应值小于127才可以使用对象池。 synchronized 参考 面试官请详细说下synchronized的实现原理 - 知乎 概念在多线程情况下多个线程访问共享资源会出现问题而synchronized关键字则是用来保证线程同步的 synchronized解决可见性的方式是每次都清除工作内存从主内存中重新获取。 synchronized可以保证并发编程的三大特性原子性可见性有序性。 synchronized可以实现悲观锁非公平锁可重入锁独占锁或者排它锁。 实现原理 Java虚拟机是通过进入和退出Monitor对象来实现代码块同步和方法同步的代码块同步使用的是monitorenter和 monitorexit 指令实现的而方法同步是通过Access flags后面的标识来确定该方法是否为同步方法。 jDK1.6对synchronized做了哪些优化 引入偏向锁和轻量级锁随着竞争的激烈而升级。 引入偏向锁的目的减少只有一个线程执行同步代码块时的性能消耗即在没有其他线程竞争的情况下一个线程获得了锁。 使用CAS操作将当前线程的ID记录到对象的Mark Word中。 引入轻量级锁的目的在多线程交替执行同步代码块时未发生竞争避免使用互斥量重量锁带来的性能消耗。但多个线程同时进入临界区发生竞争则会使得轻量级锁膨胀为重量级锁。 将对象的Mark Word复制到当前线程的Lock Record中并将对象的Mark Word更新为指向Lock Record的指针。 锁消除是指java编译时消除不可能发生共享资源竞争的锁。 锁粗化是指java编译时将不必要的重复加锁粗化到整个操作的外部。 for(int i0;in;i){synchronized(lock){} } //粗化后 synchronized(lock){for(int i0;in;i){} }synchronize和lock的区别和使用场景 参考粗谈synchronize和Lock锁的区别及使用场景 区别 synchronize是java关键字内置特性。Lock是一个接口通过这个接口的实现类可以实现同步访问。synchronize 在代码执行结束后或者代码执行异常后会自动释放而lock必须要用户手动去释放锁否则会造成死锁。synchronize可以锁住代码块类对象lock只能锁代码块synchronize只能是非公平锁而lock可以是公平锁也能是非公平锁。synchronize等待不中断而lock可中断。synchronize不知道线程有没有获得锁但是lock可以知道。synchronize是隐式锁。Lock是显示锁。synchronize是悲观锁的一种实现.lock的实现类ReentrantLock主要用到unsafe的CAS和park两个功能实现锁乐观锁的一种实现。性能比较竞争不激烈时synchronize的性能优于ReentrantLock但是在竞争激烈的情况下synchronize性能下降几十倍ReentrantLock的性能可以维持常态。 重入锁提供多样化的同步如时间限制的同步被打断的同步。 synchronize的释放占有锁线程代码执行完成占有锁线程出现了异常占有锁线程调用wait方法进入waiting状态需要释放锁执行完成后可以通过notifyAll或notify等object对象的api来唤醒其他等待线程立马执行。 public interface Lock {void lock();void lockInterruptibly() throws InterruptedException;boolean tryLock();boolean tryLock(long time, TimeUnit unit) throws InterruptedException;void unlock();Condition newCondition(); }接口方法 lock()用来获取锁如果锁被其他线程获取则进行等待 Lock lock ...; //声明锁 lock.lock(); //获得锁 try{//处理任务 }catch(Exception ex){}finally{lock.unlock(); //释放锁 }tryLock()尝试获取锁立即返回tryLock(long time, TimeUnit unit) 尝试在一定时间内获取锁 Lock lock ...; if(lock.tryLock()) {try{//处理任务}catch(Exception ex){}finally{lock.unlock(); //释放锁} }else {//如果不能获取锁则直接做其他事情 }lockInterruptibly() 它是对于那些未竞争的到锁而 可以被外部调用interrupt()来中断从而达到不在等候锁资源不再去竞争锁 synchronize和reentrantlock都是可重入锁。 就是一个线程不用释放可以重复的获取一个锁n次只是在释放的时候也需要相应的释放n次。synchronize不用手动释放。 实现 ReentrantLock的基本实现可以概括为先通过CAS尝试获取锁。如果此时已经有线程占据了锁那就加入AQS队列并且被挂起。当锁被释放之后排在CLH队列队首的线程会被唤醒然后CAS再次尝试获取锁。在这个时候如果 非公平锁如果同时还有另一个线程进来尝试获取那么有可能会让这个线程抢先获取 公平锁如果同时还有另一个线程进来尝试获取当它发现自己不是在队首的话就会排到队尾由队首的线程获取到锁。 Synchronized进过编译会在同步块的前后分别形成monitorenter和monitorexit这个两个字节码指令。在执行monitorenter指令时首先要尝试获取对象锁。如果这个对象没被锁定或者当前线程已经拥有了那个对象锁把锁的计算器加1相应的在执行monitorexit指令时会将锁计算器就减1当计算器为0时锁就被释放了。如果获取对象锁失败那当前线程就要阻塞直到对象锁被另一个线程释放为止 LOCK的实现类 Lock定义了标准Lock的API AQS(AbstractQueuedSynchronizer) ReentrantLock重入锁支持公平和非公平 ReentrantReadWriteLock在ReentrantLock上基础上支持读写分离是和多读少写的场景CountDownLatch Semphore Java的Lock实现类介绍 如何避免死锁 1.死锁预防 1.1.破坏占有并且等待 1.一次性申请运行过程中需要的所有资源 2.允许只获得初期资源就开始运行运行后逐步释放使用完毕的资源然后再去请求新的资源。 1.2.破坏不可抢占条件 当获取锁失败释放之前获取的资源 1.3.破坏循环等待的条件 定义资源的线性顺序来预防 2.避免死锁在使用前进行判断只允许不会产生死锁的进程申请资源. 一般采用银行家算法来避免。需要知道进程请求资源的最大数目。 hashMap 存储大量数据 参考准备用HashMap存1W条数据构造时传10000还会触发扩容吗存1000呢 不指定容量会随着数据的增加不断扩容影响性能。 在指定调用容量的构造方法时会重新调用另一个构造方法传入默认的负载因子0.75 public HashMap(int initialCapacity) {this(initialCapacity, DEFAULT_LOAD_FACTOR);}public HashMap(int initialCapacity, float loadFactor) {if (initialCapacity 0)throw new IllegalArgumentException(Illegal initial capacity: initialCapacity);if (initialCapacity MAXIMUM_CAPACITY)initialCapacity MAXIMUM_CAPACITY;if (loadFactor 0 || Float.isNaN(loadFactor))throw new IllegalArgumentException(Illegal load factor: loadFactor);this.loadFactor loadFactor;this.threshold tableSizeFor(initialCapacity);}构造方法中初始化了两个成员变量。threadHold 扩容阈值和 loadFactor 负载因子。 tableSizeFor就是找到大于入参的2的整数次方如传入10会得到16. 设置容量为2的整数次方是为了减少哈希冲突。 推荐在集合初始化的过程中指定集合初始化大小为 需要存储的元素个数/0.751 为什么HashMap中的键往往都使用String 参考 为什么HashMap中的键往往都使用String 1.String重写了hashCode,两个不同引用的String类型只要值相等hashcode就相等而非地址相等。设计 hashCode() 时最重要的因素就是对同一个对象调用 hashCode() 都应该产生相同的值。 2.String不可变每当创建一个字符串对象他的hashcode就被缓存下来所以存储hashMap不用重新计算相比于其他对象更快。 线程池 参考并发编程三线程池基本面试题必背题目 1. 作用限制系统中执行线程的数量。 1.降低资源消耗复用线程。 2.提高效率提前创建使用从中获取节省创建时间。 3.增加线程的可管理型。线程是稀缺资源使用线程池可以进行统一分配调优和监控。 2. 常见线程池 1.SingleThreadExecutor 只有一个线程保证任务按顺序执行FIFO,LIFO,优先级 任务队列为链表结构的有界队列 2.FixedThreadPool 定长线程池超出等待。只有核心线程执行完成后回收。 任务队列为链表结构的有界队列。 3.CachedThreadPool 超出回收空闲线程没有则创建线程。核心线程固定非核心线程无限。使用完成后闲置10分钟回收。任务队列为延时阻塞队列。 4.ScheduledThreadPool 定时执行任务线程池 无核心线程非核心线程数量无限执行完闲置 60s 后回收任务队列为不存储元素的阻塞队列。 5.WorkStealingPool一个拥有多个任务队列的线程池可以减少连接数创建当前可用cpu数量的线程来并行执行。 3. 线程池中的几个重要参数 corePoolSize 核心线程数量用不到也不会回收。allowCoreThreadTimeout 设置为 true 时核心线程也会超时回收。maximumPoolSize 最大线程数量活跃线程数量达到该值阻塞新任务。keepAliveTime 非核心线程最长存活时间 如果将 allowCoreThreadTimeout 设置为 true 时核心线程也会超时回收。unit必需指定 keepAliveTime 参数的时间单位。常用的有TimeUnit.MILLISECONDS毫秒、TimeUnit.SECONDS秒、TimeUnit.MINUTES分。workQueue必需任务队列。通过线程池的 execute() 方法提交的 Runnable 对象将存储在该参数中。其采用阻塞队列实现。threadFactory 线程工厂指定线程池创建新线程的方式。handler 拒绝策略达到最大线程要执行的饱和策略。 //TreadPoolExecutor自定义参数线程池推荐使用 public class ThreadPoolDemo {public static void main(String[] args) {//1. 使用ThreadPoolExecutor指定具体参数的方式创建线程池ThreadPoolExecutor poolExecutor new ThreadPoolExecutor(2, //核心线程数5, //池中允许的最大线程数2, //空闲线程最大存活时间TimeUnit.SECONDS, //秒new ArrayBlockingQueue(10),//被添加到线程池中但尚未被执行的任务Executors.defaultThreadFactory(), //创建线程工厂,默认new ThreadPoolExecutor.AbortPolicy()//如何拒绝任务);//2. 执行具体任务poolExecutor.submit(new MyRunnable());poolExecutor.submit(new MyRunnable());//3. 关闭线程池poolExecutor.shutdown();} } public class MyRunnable implements Runnable{Overridepublic void run() {System.out.println(Thread.currentThread().getName()执行了);} } 4. 拒绝策略 任务不断过来系统无法及时处理就要拒绝。 AbortPolicy(默认 抛出RejectedExecutionExceptionCallerRunsPolicy 由调用线程处理该任务。DiscardOleddestPolicy 该策略将丢弃最早的未处理任务并尝试再次提交当前任务DiscardPolicy该策略默默的丢弃无法处理的任务不予任何处理。 可以通过实现RejectedExecutionHandler接口自定义接口。 5. execute和submit的区别 execute适用于不需要关注返回值的场景只需要将线程丢到线程池中去执行就可以了。 submit方法适用于需要关注返回值的场景 6. 线程池的关闭 shutdownNow对正在执行的任务全部发出interrupt()停止执行对还未开始执行的任务全部取消并且返回还没开始的任务列表。 shutdown当我们调用shutdown后线程池将不再接受新的任务但也不会去强制终止已经提交或者正在执行中的任务。 7. 线程数的选择 计算密集型应为 cpu核数1 减少上下文切换 即使当密集型的线程由于偶尔的内存页失效或其他原因导致阻塞时这个额外的线程也能确保 CPU 的时钟周期不会被浪费从而保证 CPU 的利用率。 IO密集型 线程数 CPU 核心数 * (1 IO 耗时/ CPU 耗时) IO比cpu慢设置过少线程数会造成cpu资源的浪费。 等待时间越长线程越多。 8. 线程池都有哪几种工作队列 1、ArrayBlockingQueue 是一个基于数组结构的有界阻塞队列此队列按 FIFO先进先出原则对元素进行排序。 2、LinkedBlockingQueue 一个基于链表结构的阻塞队列在未指定容量时容量默认为 Integer.MAX_VALUE.此队列按FIFO 先进先出 排序元素吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列 3、SynchronousQueue 一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作否则插入操作一直处于阻塞状态吞吐量通常要高于LinkedBlockingQueue静态工厂方法Executors.newCachedThreadPool使用了这个队列。 4、PriorityBlockingQueue 一个具有优先级的无限阻塞队列。 5、DelayQueue类似于PriorityBlockingQueue是二叉堆实现的无界优先级阻塞队列。要求元素都实现 Delayed 接口通过执行时延从队列中提取任务时间没到任务取不出来。 9. 线程工作流程 提交任务线程是否达到核心线程数未达到创建核心线程否则下一步 查看任务队列是否已满未满放入任务队列否则进入下一步 线程是否到达最大线程数未到创建非核心线程执行任务否则执行饱和策略默认抛出异常。 随着任务的增加增加活跃线程数活跃线程数核心线程数 10.线程池优化 ThreadPoolExecutor自定义线程池任务量不大使用无界队列。任务量大使用有界队列防止OOM。如果任务量很大还要求每个任务都处理成功要对提交的任务进行阻塞提交重写拒绝机制改为阻塞提交。保证不抛弃一个任务最大线程数一般设为2N1最好N是CPU核数核心线程数看应用如果是任务一天跑一次设置为0合适因为跑完就停掉了如果是常用线程池看任务量是保留一个核心还是几个核心线程数如果要获取任务执行结果用CompletionService但是注意获取任务的结果的要重新开一个线程获取如果在主线程获取就要等任务都提交后才获取就会阻塞大量任务结果队列过大OOM所以最好异步开个线程获取结果。 ifelse嵌套优化 参考Java—优化 if-else 代码的 8 种方案 1.提前return,去除不必要的else if(xx){}else{return}--------if(!xx){return}2.使用三元表达式 3.使用枚举类 String OrderStatusDes;if(orderStatus0){OrderStatusDes订单未支付; }else if(OrderStatus1){OrderStatusDes订单已支付; }else if(OrderStatus2){OrderStatusDes已发货; }--------------------- String OrderStatusDes OrderStatusEnum.0f(orderStatus).getDesc();4.合并条件表达式 结果相同合并表达式 5.使用Optional优化ifelse String str jayhuaxiao;if(str ! null) {System.out.println(str); } else{System.out.println(Null); } ----------------------------- OptionalString strOptional Optional.of(jayhuaxiao);strOptional.ifPresentOrElse(System.out::println, () - System.out.println(Null));6.表驱动法 又称之为表驱动、表驱动方法。表驱动方法是一种使你可以在表中查找信息而不必用很多的逻辑语句if或case来把它们找出来的方法。以下的demo把map抽象成表在map中查找信息而省去不必要的逻辑语句。 if(param.equals(value1)) {doAction1(someParams); } else if(param.equals(value2)) {doAction2(someParams); } elseif(param.equals(value3)) {doAction3(someParams); }--------------- // 这里泛型 ? 是为方便演示实际可替换为你需要的类型 Map?, Function? action actionMappings newHashMap(); // 初始化 actionMappings.put(value1, (someParams) - { doAction1(someParams)}); actionMappings.put(value2, (someParams) - { doAction2(someParams)}); actionMappings.put(value3, (someParams) - { doAction3(someParams)});// 省略多余逻辑语句 actionMappings.get(param).apply(someParams);7.使用策略模式 例如支付场景下支持多种支付方式 public class PaymentService {CreditService creditService;WeChatService weChatService;AlipayService alipayService;public void payment(PaymentType paymentType, BigDecimal amount) {if (PaymentType.Credit paymentType) {creditService.payment();} else if (PaymentType.WECHAT paymentType) {weChatService.payment();} else if (PaymentType.ALIPAY paymentType) {alipayService.payment();} else {throw new NotSupportPaymentException(paymentType not support);}} }enum PaymentType {Credit, WECHAT, ALIPAY; }作者小黑说Java 链接https://juejin.cn/post/7030976391596212255 来源稀土掘金 著作权归作者所有。商业转载请联系作者获得授权非商业转载请注明出处。这种不满足开闭原则对修改关闭对扩展开放修改后需要对其他支付方式进行测试。 策略设计模式是一种行为设计模式。当在处理一个业务时有多种处理方式并且需要再运行时决定使哪一种具体实现时就会使用策略模式。 抽象支付方式为一个策略接口 public interface PaymentStrategy {public void payment(BigDecimal amount);}针对具体的支付方式做实现 public class CreditPaymentStrategy implements PaymentStrategy{Overridepublic void payment(BigDecimal amount) {System.out.println(使用银行卡支付 amount);// 去调用网联接口} } public class WechatPaymentStrategy implements PaymentStrategy{Overridepublic void payment(BigDecimal amount) {System.out.println(使用微信支付 amount);// 调用微信支付API} }重新实现支付服务paymentservice public class PaymentService {/*** 将strategy作为参数传递给支付服务*/public void payment(PaymentStrategy strategy, BigDecimal amount) {strategy.payment(amount);} }策略模式优化后 public class StrategyTest {public static void main(String[] args) {PaymentService paymentService new PaymentService();// 使用微信支付paymentService.payment(new WechatPaymentStrategy(), new BigDecimal(100));//使用支付宝支付paymentService.payment(new AlipayPaymentStrategy(), new BigDecimal(100));} }在使用了策略模式之后在我们的支付服务PaymentService中便不需要写复杂的if…else如果需要新增加一种支付方式只需要新增一个新的支付策略实现这样就满足了开闭原则并且对其他支付方式的业务逻辑也不会造成影响扩展性很好。 数组和链表的区别 数组随机查询快增删慢内存连续 链表随机查询慢增删快空间分散不需要连续 数组固定大小在编译期间分配内存链表动态灵活在执行或者运行时分配内存 链表因为要存储上一个和下一个的引用元素因此需要更多的内存 链表内存利用率更高 对于想要快速访问数据不经常有插入和删除元素的时候选择数组。 对于需要经常的插入和删除元素而对访问元素时的效率没有很高要求的话选择链表。 ArrayList,LinkedList,Vector的区别 ArrayList动态数组默认容量10扩容为1.5倍,新建数组复制数据。 LinkedList双向链表 LinkedList还实现了Deque接口所以LinkedList还可以用作队列 Vector也是数组线程安全每次扩容一倍。 hashMap和hashtable的区别 参考HashMap和Hashtable的区别 1.hashMap线程不安全 hashtable线程安全用synchronized关键字实现 2.hashMap允许null作为键或者ValueHashTable会抛出异常 3.hashtbale使用的是key的hashcodehashMap计算hash对key的hashcode进行了二次hash以获得更好的散列值然后对table数组长度取摸。 4. HsahMap在数组链表的结构中引入了红黑树Hashtable没有 5.HashMap初始容量为16Hashtable初始容量为11 5. HsahMap扩容是当前容量翻倍Hashtable是当前容量翻倍1 6. HsahMap只支持Iterator遍历Hashtable支持Iterator和Enumeration 使用场景 非并发场景单线程使用HashMap并发场景多线程可以使用Hashtable但是推荐使用ConcurrentHashMap锁粒度更低、效率更高。另外使用在使用HashMap时要注意null值的判断 Hashtable也要注意防止put null key和 null value。 常用注解 Autowired Component RestController Cacheable RequestMapping Value Bean Import Autowired和Resource的区别 A默认byType,可以通过Qualify指定bean名称。是spring的注解默认必须存在bean可以用requiredfalse设置 R默认ByName,有name和type两种属性先找namename没有匹配type。是java的注解。 线程的状态 新建 就绪 运行 阻塞 死亡 wait和sleep的区别 wait释放锁需要notify唤醒sleep不释放锁自动唤醒。 run和start的区别 run直接运行start是进入就绪状态。 volatile 如何保证可见性和指令重排 造成可见性的原因是JAVA内存模型JMM在java内存模型中共享变量存放在主内存中每个线程都有自己的工作内存操作共享变量需要从主内存中获取但是何时写回主内存不可预知这就导致每个线程变量的操作是封闭的其他线程不可见的。 CPU快内存慢一般都用寄存器解决。 可以加synchronized关键字进入synchronize代码块会清除缓存从主内存中获取共享变量进行操作刷新回主内存然后释放锁。 效率低下出现了缓存一致性协议有MSI,MESI,MOSI等最出名的是Intel的MESI协议保证了每个缓存中使用的共享变量的副本是一致的。 使用volatile等于告诉CPU需要MESI协议和嗅探机制来保证可见性。 MESI机制 1.Modify:当缓存中的数据被修改时该缓存设置为M状态 2.Eclusive独占当只有一个缓存使用某行数据时设置为E状态 3.Share共享当多个CPU有数据的缓存该数据的缓存设置为S状态 4.Invalid无效当某个数据的缓存修改时其他持有该数据的缓存更新为I状态 核心思想CPU修改数据发现该数据是共享变量会发出通知让其他CPU将该变量的缓存置为无效状态因此当其他CPU需要读取这个变量的时候发现自己的缓存行是无效的那么他就会重新读取。 监听和通知基于总线机制。总线嗅探机制就是一个监听器。 2.有volatile修饰的共享变量在写之前会多出一条lock指令 lock前缀会触发 1.将当前缓存行的数据写会主内存 2.这个写回操作会使其他CPU中缓存了该内存地址的数据无效。 JMM 1.lock前缀会将线程工作内存中的缓存数据写回主内存 2.通过缓存一致性协议其他线程如果工作内存中使用了该变量的值就会失效 3.其他线程会重新从主内存获取新的值、 大量使用volatile会导致总线风暴。 volatile保证数据的可见性但不保证数据操作的原子性。在多线程环境下使用volatile变量是线程不安全的可以使用锁机制或者原子类。 禁止指令重排 编译器不会对volatile读以及volatile后面的任务内存操作重排序。 通过内存屏障来实现。 异步开发 线程异步可以使用线程池CompletableFuture异步它是基于异步函数式编程。相对阻塞式等待返回结果CompletableFuture 可以通过回调的方式来处理计算结果实现了异步非阻塞性能更优。SpringBoot Async 异步 set如何保证不重复 1.通过hashcode方法获取hash值 2.在hash表中查找如果不存在则添加成功。如果hash表中含有该值则进行equals比较相同添加失败不同添加到已有对象链末尾。 深拷贝和浅拷贝 浅拷贝 对象的引用变量还是指向原对象地址 深拷贝 对象的引用变量指向不同会新建引用对象 一般都是浅拷贝深拷贝的是实现方式1.重写clone方法克隆引用成员变量2.字节流写入文件再读出来3.构造函数传参等。 设计模式 工厂模式 参考工厂模式 工厂模式属于创建型模式。 意图定义一个创建接口的接口让其子类字节决定实例化哪个类工厂模式使其创建过程延迟到子类执行 主要解决接口选择的问题 如何解决让子类实现工厂接口返回的也是也是一个抽象的产品 关键代码创建过程在其子类实现 在任何需要生成复杂对象的地方都可以使用工厂方法模式。简单对象只需要new就能完成创建的对象无需工厂模式。使用工厂模式需要引入一个工厂类增加系统的复杂度。 举例抽象一个形状接口圆方块实现这个接口定义一个工厂类提供给获取形状的方法根据入参判断实例化圆/方块 抽象工厂模式 public class AbstractFactoryPatternDemo {public static void main(String[] args) {//获取形状工厂AbstractFactory shapeFactory FactoryProducer.getFactory(SHAPE);//获取形状为 Circle 的对象Shape shape1 shapeFactory.getShape(CIRCLE);//调用 Circle 的 draw 方法shape1.draw();//获取形状为 Rectangle 的对象Shape shape2 shapeFactory.getShape(RECTANGLE);//调用 Rectangle 的 draw 方法shape2.draw();//获取形状为 Square 的对象Shape shape3 shapeFactory.getShape(SQUARE);//调用 Square 的 draw 方法shape3.draw();//获取颜色工厂AbstractFactory colorFactory FactoryProducer.getFactory(COLOR);//获取颜色为 Red 的对象Color color1 colorFactory.getColor(RED);//调用 Red 的 fill 方法color1.fill();//获取颜色为 Green 的对象Color color2 colorFactory.getColor(GREEN);//调用 Green 的 fill 方法color2.fill();//获取颜色为 Blue 的对象Color color3 colorFactory.getColor(BLUE);//调用 Blue 的 fill 方法color3.fill();} }在公共微服务调用模块使用根据接口标识获取类名从spring工厂中获取spring对象进行处理。 单例模式spring bean 默认单例代理模式AOP的实现方式是通过代理实现Spring主要使用JDK动态代理和CGLIB代理模板模式方法对数据库类的操作JDBCtemplate,数据库建立连接执行查询关闭连接几个过程非常适合模板方法 redisTemplaterestFulTemplate CAS compare and swap 乐观锁假设不会发生冲突去完成操作因为冲突失败就重试知道成功为止。 CAS中使用了3个基本操作数 共享变量的内存地址V 工作内存中共享变量的副本值也就是旧的预期值A 更改的值B 只有当内存地址V的值和预期值A相等的时候才进行更新否则提交失败重新尝试这个过程称为自旋。 缺点 ABA问题通过增加版本号解决如AtomicStampedReference类使用pair内部类实现包括版本号和引用都相等才更新 竞争激烈自旋可能会消耗较高的CPU。可以使用AtomicLong的替代类LongAdder。 不能保证代码的原子性只能保证共享变量操作的原子性而不能保证代码块的原子性 优点 保证变量操作的原子性并发量不是很高的情况下使用CAS机制比使用锁机制效率更高在线程对共享资源占用时间较短的情况下使用CAS效率也会较高 Java提供的CAS操作类unsafe类Atomic系类底层调用unsafe的API CAS使用场景 使用变量统计网站访问量Atomic类操作数据库乐观锁更新 TCP如何保证可靠性 序列号和确认号机制TCP发送数据会带一个序列号服务端在检测数据完整后会发送一个确认号表示确认收到了数据包 超时重发机制tcp发送数据包后会启动一个定时器如果一定时间没有接收到接收端的确认将会重新发送 去重从IP网络传输层到TCP层数据可能重复TCP会对数据去重 顺序从IP网络传输层到TCP层数据可能乱序TCP会对数据重新排序 流量控制客户端和服务端的缓存大小一定为了防止数据溢出通过滑动窗口协议保证数据大小 三次握手和四次挥手 握手c发送序列号x s响应 确认号x1,序列号y c发送确认号y1 挥手c发送fins响应acks发送finc响应ack HTTP HTTP是基于TCP的应用层的超文本传输协议 优点 简单报文格式为headerbody头部信息也是kv格式灵活易扩展http协议中的请求方法url状态码header都没固定死允许开发人员自定义应用广泛跨平台 缺点 无状态没有记忆能力不安全明文传输 分布式和微服务的区别 水平拆分应用分为表示层数据访问层业务逻辑层可以将这三层拆分到不同的服务器上 垂直拆分根据业务逻辑拆分 分布式拆将项目拆分为多个模块分开部署就是分布式 微服务细粒度的垂直拆分不一定在不同的服务器上。 优化链表查询速度 数据库 如何优化联表查询 1.在条件字段加索引 2.创建临时表 3.在a表中加入b表的字段单查a表 sql优化方案 不使用select * 避免多余的网络开销和回表操作 当以主键索引为查询条件时select * 不会触发回表因为已经主键索引的B树已经包含了所有数据当以非主键索引作为查询条件时查询结果只有查询条件和主键还需要根据主键再去查询其他字段就是回表查询。 覆盖索引是指select到from的查询列都是主键或者索引。能union all就不用union。去重的过程中需要遍历排序和比较小表驱动大表 in 左大右小 exist 左小右大用连接查询代替子查询联合索引的使用遵循左前缀法则 如何查看有没有走索引 mysql 参考MySQL如何查看SQL查询是否用到了索引 explainsql EXPLAIN select * from tb_brand where id1;type 性能 由好到坏system const eq_ref ref fulltext ref_or_null index_merge unique_subquery index_subquery range index ALL 至少range,最好ref possible_keys 查询用到的索引没有的话值为null key 实际决定查询结果使用的索引没有的话值为null rows为执行查询时必须检查的行数 oracle 参考Oracle通过执行计划查看查询语句是否使用索引 explain plan for sql select * from table(dbms_xplan.display)TABLE ACCESS FULL为全表扫描; index range scan为索引范围扫描; 常见的索引类型扫描 index unique scan 索引唯一扫描 当查询条件可以用到主键唯一键具有外键约束的键或者只是访问索引所在的数据的时候优化器会选择这种扫描类型index range scan 索引范围扫描 当优化器发现在唯一列上使用了between会使用范围扫描在组合列上只使用部分进行查询导致查询出多行数据。index full scan 全索引扫描 查询的数据可以全部从索引中获取则使用全索引扫描index fast full scan 索引快速扫描 与全索引扫描类型区别是返回的数据是不排序的 如何优查询速度 慢查询SQL 是指超过指定时间的sql。在mysql中默认关闭手动开启 show variables like slow_query%; show variables like long_query_time;参数说明如下 slow_query_log慢查询开启状态 slow_query_log_file慢查询日志存放的位置一般设置为 MySQL 的数据存放目录 long_query_time查询超过多少秒才记录 mysql set global slow_query_logON; Query OK, 0 rows affected (0.05 sec)mysql set global long_query_time0.01; Query OK, 0 rows affected (0.00 sec)超过指定时间就会记录在sql中 建表 尽量避免用text类型采用es或者oss存储 字段尽量设置非null会影响索引稳定 记得写注释comment 数据重要的情况下采用innodb而且支持事务操作 减少索引大小可以采用前缀索引但是前缀索引不能消除group by ,order by带来排序开销 经验新核心迁移编码不一致扩容 gbk 到utf-8 索引的建立规则 建立索引常用的规则 索引以及实现方式 索引是帮助数据库高效获取数据的数据结构。 数据库除了维护数据还维护着满足特定查找算法的数据结构数据结构以某种方式指向引用数据称为索引。 MySQL索引的数据结构 MySQL索引的数据结构 Oracle是B树Mysql是B树 参考面试官你知道多少种索引 索引目录提高查询效率。 索引常用的实现方式 B树B树 。hash也可以。 B树就是平衡树时间复杂度为logn 多路查找树叶子节点位于同一层每个节点不仅包含数据的键值还包括data值每个节点相当于一个磁盘块 B树基于B树实现是有序的。 每个叶子节点存储字段键值以及对应的数据非叶子节点只存储索引键值以及指向子节点的指针不存储数据每个节点相当于一个键盘块同一层级的叶子节点之间以双向链表的形式相连 为什么有B树还要有B树 B树的叶子节点会指向下一节点遍历查找更快。 非叶子节点不存储数据key更紧密数据查询更稳定和迅速因为更好的利用空间局部性原理。 为什么使用B树 数据库访问通过页尽量减少IO操作次数因此树的层级要尽可能的少。 B树相比于二叉树B树的非叶子结点可以有多个子树因此B树高度远远小于AVL树和红黑树磁盘IO数大大减少。 B树相比于B树 1.非叶子节点不存储数据存储的数据更多因此B树的高度更低更少的磁盘IO操作。由于每个节点存储的记录更多对局部性原理的利用更好缓存的命中率更高。 2.更适合范围查找B树只需要对链表进行遍历但是B树需要找到查询下限然后进行中序遍历直到找到查询的下限。 3.更稳定的查询效率B树的查询复杂度稳定为树高因为所有数据都在叶子节点。 哈希也可以但是会有两个缺点1.哈希冲突。2.哈希计算的是个值无法进行范围查询。 Mysql的innodb使用的是B树。 索引的类型 1普通索引普通索引是最基本的索引它没有任何限制值可以为空仅加速查询。 2唯一索引唯一索引与普通索引类似不同的就是索引列的值必须唯一但允许有空值。如果是组合索引则列值的组合必须唯一。 3主键索引主键索引是一种特殊的唯一索引一个表只能有一个主键不允许有空值。 4组合索引组合索引指在多个字段上创建的索引只有在查询条件中使用了创建索引时的第一个字段索引才会被使用。使用组合索引时遵循最左前缀集合。 5全文索引应用场景为百度和淘宝的搜索框。关键字查找不支持大小写索引创建慢。mysql中的MyIsam支持innnodb不支持。不推荐使用一般用es实现。 6.聚集索引 7.辅助索引根据条件查询出主键通过主键返回聚集索引根据聚集索引的主键排序找到条件对应的数据 聚簇索引和非聚簇索引 mysql默认引擎innodb分为两种索引聚簇索引和非聚簇索引每个索引对应一颗b树两者的区别主要是叶子节点存储的数据不同聚簇索引存储行数据非聚簇索引存储聚簇索引因此需要进行第二次查询称为回表查询。 聚簇索引一般是指主键索引一张表只能有一个。查询效率高。 非聚簇索引也称二级索引数量不限制查询效率低。 聚簇索引数据和索引都在一块存储。非聚簇索引叶子节点指向存储节点的位置数据和索引分开存储。 在innodb中在聚簇索引上创建的索引称为辅助索引像非聚簇索引复合索引前缀索引唯一索引。辅助索引叶子结点不存储数据存储聚簇索引总是需要二次查找。 聚簇索引具有唯一性因为索引和数据在一块存储。 表中行的物理顺序和索引中的行物理顺序是相同的。 聚簇索引默认是主键如果没有主键会选择一个非空的列来做聚簇索引类似oracle中的rowid。如果想指定非主键做聚簇索引删除主键设置聚簇索引然后重新设置主键即可。 myisam使用非聚簇索引主键叶子结点存储主键辅助键索引存储辅助键都指向表数据对数据来说这两种键没有区别。索引树独立无法通过辅助键访问主键。 聚簇索引优势 1.数据和索引叶子结点存储在一起每页有多行数据查询其中一条会加载到buffer缓存器中访问同页其他行就不会访问磁盘直接返回查询更快。 一次iO读写会获取16k的资源读取到的数据区域称为Page,b树一个叶子节点上有多条数据和索引值因此数据在叶子节点上不需要重复查询走缓存。只有页分裂即数据不存在才重新申请IO, 2.当行数据发生变化只需维护聚簇索引。非聚簇索引叶子节点存储主键更节约空间。 3.myisam使用非聚簇索引地址凌乱拿到地址按照合适的算法进行IO读取聚簇索引只需一次。 4.涉及大数据量的排序全表扫描count非聚簇索引更快因为索引小这些操作都是在内存中完成的。 聚簇索引中物理顺序和索引顺序一致因此建议使用自增主键不用uuid。默认会在索引树的末尾增加主键值对索引树的结构影响最小。索引紧凑磁盘碎片少效率高。 主键大小也会影响因为辅助索引中存储的主键导致索引存储内存增多。影响IO操作读取的数据量。 优点 1.查询快索引和数据在一块 2.主键排序和范围查找很快 缺点 1.插入速度依赖于插入顺序一般主键自增 2.更新主键代价高需要移动行 3.二级索引访问需要进行两次索引查找 mylsam查询比innodb速度快的原因 1.innodb不会压缩索引 查询时需要缓存数据块而mylsam数据和索引是分开的可以压缩索引在相同容量的内存加载更多的数据 2.innodb寻址要映射到块,再到行个人猜想主要是查询行的版本号,MYISAM记录的直接是文件的OFFSET,定位比INNODB要快 注释 SELECT InnoDB必须每行数据来保证它符合两个条件: 1、InnoDB必须找到一个行的版本,它至少要和事务的版本一样老(也即它的版本号不大于事务的版本号)。这保证了不管是事务开始之前,或者事务创建时,或者修改了这行数据的时候,这行数据是存在的。2、这行数据的删除版本必须是未定义的或者比事务版本要大。这可以保证在事务开始之前这行数据没有被删除 3INNODB还需要维护MVCC一致;虽然你的场景没有,但他还是需要去检查和维护MVCC (Multi-Version Concurrency Control)多版本并发控制 联合索引的命中规则 在test表上对列abc建立联合索引 联合索引采用左前缀命中规则 从左到右匹配直到匹配终止条件。 终止条件为范围操作符函数等不能应用索引的情况。 和顺序无关mysql优化器会进行优化。 a‹› range索引 or 不使用索引 order by 没有影响 a and b‹› 走索引 数据库事务 数据库事务是一个不可分割的数据库操作序列也是数据库控制并发的基本单位其执行结果为使数据库从一种状态到另一种状态 事务的特性 ACID 原子性 Atomicity事务中的操作要么全部成功要么全都失败 一致性 Consistency事务执行前后数据库中的数据具有一致性 隔离性 Isolation多个事务之间不会相互影响 持久性 Durability事务提交后对数据库的改变是永久性的 脏读读到了未提交的数据 不可重复读两次读取到的数据内容不一致这是update引起的 幻读两次读取到的数据数量不一致这是insert或者delete引起的 事务的隔离性 读取未提交会发生脏读、不可重复度、幻读 读取已提交避免脏读 Oracle默认 可重复读避免脏读不可重复读 Mysql默认 可串行化最高隔离级别事务顺序执行不会相互影响 注意 Mysql默认采用MyIsam不支持事务事务隔离机制的实现基于锁机制和并发调度。隔离级别越低事务请求的锁越少。MySQL的innodb默认使用可重复读并不会有性能损失。innoDB存储引擎在分布式事务下一般会用到可串行化隔离级别 --mysql start transaction; -- 开启事务savepoint abc;--设置回滚点rollback to abc;--回滚到abc commit; -- 提交事务 rollback; -- 回滚事务--查看MYSQL中事务是否自动提交 show variables like %commit%; --关闭自动提交 set autocommit 0;-- 0:OFF 1:ONset session transaction isolation level --隔离级别; --eg: 设置事务隔离级别为:read uncommitted,read committed,repeatable read,serializable set session transaction isolation level read uncommitted; --查询当前事务隔离级别 select tx_isolation; select for update 对行加锁不能增删改其他人也不能select for update for update 使用场景高并发情况下对数据有很强的要求 排他锁的申请前提没有线程对结果集的数据进行排他锁和行级锁否则会申请阻塞 共享更新锁的释放条件1.commit 2.推出数据库 log off 3.程序停止运行 数据一致性 悲观锁先锁后更 乐观锁先更后比较 悲观锁适合频繁更新的情况乐观锁适合频繁查询的情况 行锁和表锁 innoDb默认行级锁指定主键是行级锁否则是表级锁 for update仅适用于InnoDB必须在实务块中才能生效Begin Commit) 总结 1.innodb行锁是根据索引项加锁实现的只有通过索引查询数据才会产生行锁否则只会产生表锁 2.由于mysql的行锁是针对索引加的锁不是对记录进行加锁因此即便查询不同行的记录但是索引相同也会发生锁冲突 3.当表有多个索引不同的事务可以用不同的索引锁定不同的行。不论是使用主键索引普通索引还是唯一索引innodb都会使用行锁来进行加锁 4.即便查询条件中用到了索引但是因为sql执行计划不一定采用索引因此分析锁冲突时需要检查sql执行计划判断是否真的走了索引 oracle select for update 当发现有人修改时等待修改完成之后再去查询 for update nowait 发现有人修改直接异常 for update wait 3 超过三秒未更新结束抛出异常 什么情况下会发生锁表 insertdeleteupdateselect for update 未提交事务的情况下 alter table 修改表结构 truncate table清空表数据 mysql手动加锁 GET_LOCK()和RELEASE_LOCK()函数来获取和释放表锁。 锁表发生在并发而非并行。 减少锁表概率缩短数据库事务开启和提交时间具体批量执行改为单个执行优化sql执行速度。 幻读 什么时候发生幻读如何解决 幻读产生的条件 在可重复读的隔离界别下普通的查询都是快照读查询不到别的事务新插入的数据所以幻读是在当前读的状态下产生。 快照读读取的是快照数据不加锁的查询读取的都是快照数据 当前读读取数据库最新数据加锁的查询和增删改都会进行行当前读 解决幻读innodb加入了间隙锁间隙锁不仅可以锁住数据行的实体也可以锁住数据行之间的间隙。 会造成并发下降。 解决方案 1.降低为读取已提交但是为了解决数据和日志不一样的问题需要把binlog格式设置为row。 –日志不一样的原因binlog日志是以commit顺序为准如果第一个事务更新数据但未提交第二个事务更新数据满足第一个事务中的更新条件但是提交了日志中第二个日志在前第一个日志在后。看着就是把第一个事务中的数据也更新了 RC情况下默认是statement记录顺序是以commit方式提交的。 row会记录每一列之后的值。 2.判断有无数据有则删除无数据不删除因为删除不存在的数据一定要加间隙锁 3.间隙锁只针对写锁只要两个线程锁定的区间有交叉就会出现死锁。可以根据条件查询出所有主键根据主键删除数据这样只会加行锁。 间隙锁产生的条件 执行当前读where条件没有命中索引命中索引加行锁没有索引加表锁 innodb_locks_unsafe_for_bin_log 默认false 启用间隙锁。 间隙锁和行锁的组合称为netxt-key lock 临间锁每个next-key lock 临间锁 是前开后闭区间n行数据产生n1个next-key 锁 数据 1,2,3 select * from table for update 产生四个next-key锁-∞1],(1,2],(2,3],(4,∞]此时另一个事务进行插入是无法插入的避免了幻读的产生 间隙锁是在可重复读的隔离级别下生效。 只有可重复读的隔离界别的当前读才会出险幻读。 增加排他锁时会对结果集的行增加间隙锁。 mysql共享锁 sql lock in share mode 1.允许其他事务增加共享锁 2.不允许其他事务增加排他锁 3.多个事务共同添加必须等待先执行的事务commit后才行。 排他锁 sql for update 1.事务之间不允许其他排他锁和共享锁读取修改更不允许 2.所有事务只有一个排他锁执行commit后其他事务才可以执行 mvcc 多版本并发控制解决读写时的线程安全问题线程不用去争抢读写锁。 隔离性通过加锁当前读和MVCC快照读实现。 一致性通过undologredolog隔离性共同实现。 mvcc的实现基于undolog版本链readView。 在mysql存储过程中会隐式的定义几个字段 trx_id:事务id每次进行一次事务操作自增1 roll_pointer:回滚指针用于找到上一个版本的数据结合undolog进行回滚 使用select读取数据这是时刻的数据会有多个版本通过readview来判断能够读取哪个版本 readview中包含以下字段 m_ids活跃的事务id列表 活跃的事务是指还没有commit的事务 min_trx_id_:活跃事务中的最小值 max_trx_id_:下一个事务id ctreator_trx_id:执行select读这个操作的事务id readview如何判断哪个版本可用 trx_idctreator_trx_id 可以访问这个版本 :读取自己创建的记录 trx_idmin_trx_id_ :可以访问这个版本 :要读取的事务已提交可以访问 trx_idmax_trx_id_不可访问 :读取的事务id已经不在该版本链中故无法访问 min_trx_id_trx_idmax_trx_id_: trx_id在当前事务在活跃事务中不可以访问这个版本反之可以 :读取的事务id不在活跃列表可以读取 mvcc如何实现RC和RP的隔离级别 1.rc时每个快照读都会生成并获取最新的readview 2.rr时只有在同一个事务的第一个快照读才会创建readview 幻读问题 快照读通过mvccrr的隔离界别解决了幻读问题因为每次都是同一个readview 当前读通过临键锁rr隔离级别并不能解决幻读问题 mysql redo和undo mysql redo和undo 事务的原子性隔离性由锁机制实现 事务的一致性和持久性由redo和undo日志来保证 redo log是重做日志保证持久性。 undo log是回滚日志保证事务的一致性。 redo是记录尚未完成的操作数据库崩溃则用其重做。 redo log分为两部分 内存中的重做日志缓冲易失的硬盘中的重做日志文件持久的 innodb采用write ahead log (预先日志持久化策略)先写入日志在写入磁盘 第一步InnoDB 会先把记录从硬盘读入内存 第二部修改数据的内存拷贝 第三步生成一条重做日志并写入redo log buffer记录的是数据被修改后的值 第四步当事务commit时将redo log buffer中的内容刷新到 redo log file对 redo log file采用追加写的方式 第五步定期将内存中修改的数据刷新到磁盘中(注意注意注意不是从redo log file刷入磁盘而是从内存刷入磁盘redo log file只在崩溃恢复数据时才用)如果数据库崩溃则依据redo log buffer、redo log file进行重做恢复数据,这才是redo log file的价值所在 在commit时写入redo日志 为什么不直接写而是先写日志 磁盘写入时间长先写入日志日志只需要考虑修改的数据。 如何保证每次都写入redo log file 没开启0_direct 直接写磁盘操作。每次写到内存在刷到磁盘。 每次将redo buffer写入os cache 文件缓存innodb都调用fsync操作将缓存写入redo log file。 buffer pool中的数据未刷新到磁盘称为脏页。 redo log满时会把脏页刷入磁盘。 除了redo满时什么时候刷脏页 系统内存不足淘汰数据页为脏页时。 mysql认为空闲时。 mysql正常关闭前会把所有的脏页刷到磁盘。 脏页刷入会带来性能问题吗 在生产环境中如果我们开启了慢 SQL 监控你会发现偶尔会出现一些用时稍长的 SQL。**这是因为脏页在刷新到磁盘时可能会给数据库带来性能开销**导致数据库操作抖动。 2.5 参数innodb_flush_log_at_trx_commit 上面提到的Force Log at Commit机制就是靠InnoDB存储引擎提供的参数innodb_flush_log_at_trx_commit来控制的 该参数控制 commit提交事务 时如何将 redo log buffer 中的日志刷新到 redo log file 中。 1、当设置参数为1时默认为1建议表示事务提交时必须调用一次 fsync 操作最安全的配置保障持久性 2、当设置参数为2时则在事务提交时只做 write 操作只保证将redo log buffer写到系统的页面缓存中不进行fsync操作因此如果MySQL数据库宕机时 不会丢失事务但操作系统宕机则可能丢失事务 3、当设置参数为0时表示事务提交时不进行写入redo log操作这个操作仅在master thread 中完成而在master thread中每1秒进行一次重做日志的fsync操作因此实例 crash 最多丢失1秒钟内的事务。master thread是负责将缓冲池中的数据异步刷新到磁盘保证数据的一致性 undo log 用于记录更改的前一份copy undo log的存储位置 在InnoDB存储引擎中undo存储在回滚段(Rollback Segment)中,每个回滚段记录了1024个undo log segment而在每个undo log segment段中进行undo 页的申请在5.6以前Rollback Segment是在共享表空间里的5.6.3之后可通过 innodb_undo_tablespace设置undo存储的位置。 purge线程的作用清除undo页和清除page中带delete_bit的标识。数据删除只是打标记并不是真的删除。 undo日志两种insert和update insert执行后就删除。 update对应delete和insert操作需要提供mvcc操作执行并不删除等待purge线程操作。 undo log是逻辑日志。redo log是物理日志。 undo和rollback的区别undo会使用rollback。 mysql和oracle的区别 mysql插入’‘就是’’ oracle插入’就是null隔离界别 mysql 可重复读 oracle读取已提交在linux下mysql区分大小写oracle不区分 myisam和InnoDB的区别 InnoDB支持事务MyISAM不支持InnoDB每条sql语句都默认封装成事务进行提交影响速度优化方式是将多条sql放入begin和commit之间组成一个事务。InnoDB支持外键而MyISAM不支持innodb支持行锁表锁myisam只支持表锁innodb必须有主键myisam可以没有innodb聚簇索引myisam非聚簇索引innodb不会保留表的行数myisam会保存表的行数 一张表修改要求比较高的事务处理选择InnoDB查询要求比较高选择MyISAM mybatis的条件语句 MyBatis动态SQL 多条件查询if、where、trim标签 if where 判断非空 choose、when、otherwise标签 多条件选择 foreach 拼接list parameter 入参类型 1.基本数据类型int,string,long,Date #{value}或${value} 获取参数中的值 2.复杂数据类型类或者Map #{属性名}或{属性名} map中则是#{key}或 {key} for each的collection属性 单个ListLong idList为list 单个array数组属性为array map中放array或者list时属性为数组的key java对象中放array或者list时属性为对象的属性名resultType 1.基本数据类型 2.pojo类型 mybatis的缓存机制 一级缓存 sqlsession共享 二级缓存 同一个命名空间共享 mybatis如何封装返回结果 resultType设置为java对象 1.反射创建对象 2.同名的列赋值给同名的属性通过反射找到set方法赋值 3.得到java对象如果是集合放入list中 当列名和java对象属性名不一致时使用resultMap建立结果映射 数据库死锁 出现原因 1.外键未加索引 在更新从表会对主表加写锁在更新主表时如果从表没有索引会对整个表加锁。 容易发生死锁 2.用户1锁A请求锁B用户2锁B请求锁A 多表操作尽量按照相同的顺序进行处理。 避免方法 1.所有的update和delete必须走唯一索引 schema的操作 2.sql语句中不要有太复杂的关联操作使用explain sql检查sql全表扫描尽量优化为索引 3.把select放在update之前 4.避免事务中的用户相互等待。 现场解决方法 1.重启系统 2.撤销进程剥夺资源。终止参与死锁的进程收回资源从而解除死锁。 3.进程回退策略 级联删除和更新 ON DELETE CASCADE ON UPDATE CASCADE Oracle不支持只能用触发器实现 写sql 1.商品id 单价 数量 按照销售额排序 前十 PRODUCTIDNUMBER 商品数量 PRICE 商品单价 productid 商品id select sum(PRODUCTIDNUMBER*PRICE) ,productid from TESTPRODUCT where rownum11 Group BY productid ORDER BY sum(PRODUCTIDNUMBER*PRICE) ;spring springboot 内嵌servlet服务器TomcatJetty等不需要打成war包部署到容器中只需要打成可执行的jar包即可简化配置添加对应功能的starter依赖简化maven配置 springcloud springcloud是一系列架构的有序集合。基于springboot简化分布式基础设置的开发如服务发现配置中心路由zuul消息总线负载均衡feginribbon断路器hystrix数据监控等。 SpringBoot启动过程 1.创建springApplication对象运行run方法 通过类加载器加载classpath下所有的spring.factories配置文件创建初始化对象 创建环境对象environment读取环境配置如application.yml 2.创建程序上下文createApplicationContext创建bean工厂对象 3.刷新上下文启动核心refreshContext(工厂对象配置bean处理器配置类的扫描解析bean的定义bean类信息缓存tomcat创建bean实例化动态代理对象创建) 4.通知监听者启动程序完成 spring.factories文件 当需要将类加载到spring中而类并不在spring启动类的包下除了在启动类Import,还可以在resource中新建META-INF目录新建spring.factories。通过这种方式来加载非默认扫描路径自己写的或者第三方的类。 通过Spring的spi扩展实现 参考springboot核心基础之spring.factories机制 IOC和AOP IOC控制反转自己new对象改为由Spring注入解耦。 AOP面向切面编程提取公共代码进行增强处理解耦。 JDK动态代理和CGLIB代理有什么区别 参考 java动态代理 静态代理代理类和被代理类实现相同的接口代理类持有被代理对象调用接口方法中进行前置和后置操作调用被代理类的方法。 类已经写好编译后就能生成class文件。 public interface Person {//租房public void rentHouse(); }public class Renter implements Person{Overridepublic void rentHouse() {System.out.println(租客租房成功);}}public class RenterProxy implements Person{private Person renter;public RenterProxy(Person renter){this.renter renter;}Overridepublic void rentHouse() {System.out.println(中介找房东租房转租给租客);renter.rentHouse();System.out.println(中介给租客钥匙租客入住);}}public class StaticProxyTest {public static void main(String[] args) {Person renter new Renter();RenterProxy proxy new RenterProxy(renter);proxy.rentHouse();}}动态代理 代理类在程序运行时创建的代理方式被称为动态代理。动态代理相较于静态代理可以对代理类的所有函数进行统一管理不需要对每个方法都写一遍。 通过reflect包下的proxy类和invocationHandler 生成jdk动态代理类和代理对象。 public interface Person {//租房public void rentHouse(); }public class Renter implements Person{Overridepublic void rentHouse() {System.out.println(租客租房成功);}}创建代理类实现invocationHandler接口需要实现invoke方法代理类中存在被代理对象。 调用代理类的方法都会调用invoke方法在invoke中通过反射调用被代理对象的方法。 在执行被代理对象的方法调用前后增加自己的处理这就是spring aop的主要原理。 public class RenterInvocationHandlerT implements InvocationHandler{//被代理类的对象private T target;public RenterInvocationHandler(T target){this.target target;}/*** proxy:代表动态代理对象* method代表正在执行的方法* args代表调用目标方法时传入的实参*/Overridepublic Object invoke(Object proxy, Method method, Object[] args)throws Throwable {//代理过程中插入其他操作System.out.println(租客和中介交流);Object result method.invoke(target, args);return result;}} public class ProxyTest {public static void main(String[] args) {//创建被代理的实例对象Person renter new Renter();//创建InvocationHandler对象InvocationHandler renterHandler new RenterInvocationHandlerPerson(renter);//创建代理对象,代理对象的每个执行方法都会替换执行Invocation中的invoke方法Person renterProxy (Person)Proxy.newProxyInstance(Person.class.getClassLoader(),new Class?[]{Person.class}, renterHandler);renterProxy.rentHouse();//也可以使用下面的方式创建代理类对象Proxy.newProxyInstance其实就是对下面代码的封装/*try {//使用Proxy类的getProxyClass静态方法生成一个动态代理类renterProxy Class? renterProxyClass Proxy.getProxyClass(Person.class.getClassLoader(), new Class?[]{Person.class});//获取代理类renterProxy的构造器参数为InvocationHandlerConstructor? constructor renterProxyClass.getConstructor(InvocationHandler.class);//使用构造器创建一个代理类实例对象Person renterProxy (Person)constructor.newInstance(renterHandler);renterProxy.rentHouse();//} catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace();}*/}}JDK动态代理生成的代理类中的方法都会调用RenterInvocationHandler中的invoke方法而invoke方法中调用了被代理对象的指定方法。 没有实现接口采用cglib代理 通过继承目标类在子类中采用方法拦截的方式拦截所有父类方法的调用然后加入自己需要的操作。因为使用的是继承因此类不能为final。 创建被代理类 public class UserService {public void getName(){System.out.println(张三);}}创建代理工厂类ProxyFactory public class ProxyFactoryT implements MethodInterceptor {private T target;public ProxyFactory(T target) {this.target target;}// 创建代理对象public Object getProxyInstance() {// 1.cglib工具类Enhancer en new Enhancer();// 2.设置父类en.setSuperclass(this.target.getClass());// 3.设置回调函数en.setCallback(this);return en.create();}//拦截方法Overridepublic Object intercept(Object obj, Method method, Object[] args,MethodProxy methodProxy) throws Throwable {System.out.println(开始事务...);// 执行目标对象的方法Object result method.invoke(target, args);System.out.println(提交事务...);return result;}}JDK和CGLib动态代理都是实现SpringAOP的基础。如果加入容器的目标对象有实现接口用动态代理如果目标对象没有实现接口用CGlib代理。 Spring AOP 和 AspectJ AOP 有什么区别 Spring AOP是动态代理运行时增强 AspectJ属于编译增强 Spring AOP 只能在运行时织入不需要单独编译性能相比 AspectJ 编译织入的方式慢而 AspectJ 只支持编译前后和类加载时织入性能更好功能更加强大。 spring事务 Spring事务基于数据库事务 Spring隔离级别和数据库事务隔离级别一样多了个默认采用数据库默认的隔离界别 Spring事务的两种方式编程式事务声明式事务 编程式事务精确到代码级别声明式事务精确到方法级别。 Spirng事务的传播机制 a调用b propagation_required a没有事务b就开启事务a有事务b就和他合并到一起。propagation_requires_new a没有事务b新建事务a有事务挂起b新建事务。 b影响aa不影响bpropagation_nested 有事务就加入没有事务就新建。a require b nested b异常a不回滚a影响bb不影响apropagation_supports 如果a有事务就加入事务没有事务就以非事务的方式进行propagation_not_support 有事务挂起以非事务运行propagation_mandatory 有事务就加入a没有事务就报错propagation_never 以非事务的方式运行存在事务报错 Target({ElementType.TYPE, ElementType.METHOD}) Retention(RetentionPolicy.RUNTIME) Inherited Documented public interface Transactional {Propagation propagation() default Propagation.REQUIRED;Isolation isolation() default Isolation.DEFAULT;//事务超时时间超时回滚int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;//如果事务只读可以利用事务的只读属性开启优化措施boolean readOnly() default false;} //事务的回滚策略指定异常回滚 Transactional(rollbackFor MyException.class) //事务的回滚策略指定异常不回滚 Transactional(noRollbackFor MyException.class)Spring事务失效的场景 作用于非public方法中声明式事务基于aop会进行判断是否是公共方法数据库不支持事务事务方法未被Spring管理同一个类中的方法调用 a调用bb添加事务注解a不添加事务注解未配置事务管理器 DataSourceTransactionManager方法的事务传播类型不支持事务不正确的捕获异常在执行sql时即使发生异常但是由于捕获异常而不会回滚错误的标注异常类型Spring事务回滚的异常类型为RuntimeException,如果在事务方法中捕获并抛出Exception异常回滚会失效 spring对象生命周期 ApplicaitonContext代表spring的IOC容器负责实例化配置装配bean。 生产 加载BeanDefinition Spring启动调用loadBeanDefinition方法通过注解xml等方式扫描BeanDefinition到beanDefinitionMap然后遍历map中的对象createBean创建Bean对象 实例化createBeanInstance 通过反射获取构造方法进行构造实例优先选择使用Autowired注解和无参构造方法。参数从单例池中查找先根据类型查找多个实例用参数名匹配。填充属性populateBean方法 通过三级缓存进行属性注入初始化Bean对象initializeBean方法进行初始化 1.初始化容器相关信息invokeAwareMethods方法为实现了各种aware接口的Bean设置注入BeanNamebeanFactory等容器信息。 2.invokeInitMethods方法执行bean的初始化方法是通过实现initializingBean接口而实现的afterPropertiesSet方法之后执行Bean上自定义的initMethod方法 3.在执行执行初始化方法initMethod之前执行BeanPostProcessor方法进行初始化前后的处理包括负责AOP处理的AnnotationAwareAspectJAutoProxyCreator负责构造后PostContstruct和销毁前PreDestory方法系统级处理器。通过实现PriorityOrdered接口来执行顺序。注册销毁方法registerDisposableBean在销毁时调用destory方法 addSingleTon:将完整的bean对象放入单例池singleTonObject中。 使用销毁 close销毁前执行postProcessorBeforeDestruction 会执行preDestroy方法 通过destoryBeans方法逐一销毁所有的Bean会执行destory方法 destroy执行之后通过客户销毁方法 invokeCustomDestoryMethod方法来执行丁自定义的Destroy方法 SpringBoot启动原理 有SpringBootApplication注解的启动类 该注解有EnableAutoConfiguration会导入AutoConfigurationImportSelector这个类会将所有符合条件的Configuration配置都进行加载SpringBootConfiguration等同于Configuration,ComponmentScan扫描加载这三个注解构成。不需要增加配置内容和扫描路径只使用EnableAutoConfiguration即可。 执行run 服务构建 服务指SpringApplication对象1.首先将资源加载器主方法类放入内存中 2.逐一判断服务类是否存在来确定web服务的类型默认servlet即基于servlet的web服务如tomcat还有响应式非阻塞服务reactive如spring-webflux,还有什么都不用用的none。 3.加载初始化类读取所有META-INF/spring.factory文件中的注册初始化bootstrapRegistryInitializer上下文初始化(ApplicationContextInitializer)和监听器(ApplicationListener)三种配置。 没有默认的注册初始化配置spring-boot和spring-boot-autoconfiguration这两个工程中配置了7个上下文初始化和8个监听器。也可以自定义这三个配置。 4.通过运行栈stackTrace判断main方法所在的类大概率是启动类本身 环境准备 2.1 new一个BootStrapcontext逐一调用启动注册初始化器bootstrapRegistryInitializer中的初始化initialize方法 2.2 将java.awt.headless这个设置为true表示缺少显示器键盘等输入设备也可以正常启动 2.3 启动运行监听器SpringApplicationRunListeners发布启动事件获取并加载spring-boot工程springfactories配置文件中的EventPublishingRunListener,在启动时会将8个监听器引入 2.4 prepareEnvironment 构造可配置环境ConfigurationEnvironment,根据不同的Web服务类型会构造不同的环境默认servlet构造之后会加载很多系统环境变量如systemEnvironment,jvm系统属性systemProperties等在内的4组配置信息将配置信息加载到propertySource的内存集合中。通过配置环境configureEnvironment方法将我们启动时传入的参数args进行设置例如启动时传入的“开发/生产”环境哦配置都会在这一步进行加载同时在propertySource集合的首个位置添加一个值为空的配置内容configurationProperties后续使用 发布环境准备完成这个事件8个监听器监听到事件部分会进行相应处理注入环境配置后置处理监听器会加载spring.factories配置文件中的“环境配置后处理器”监听器通过观察者模式设计逐一串行执行 可配置环境在过程中可能会有变化通过更新保证匹配 将spring.beaninfo.ignore设为true表示不加载Bean的元数据信息同时打印Banner图 容器创建 createApplicationContext来创建容器 根据服务器类型创建容器ConfigurableApplicaitonContext,默认是Servlet创建注解配置的servlet-web服务器容器即AnnotionConfigServletWebServerApplicationContext,在这个过程中会构造存放和构建bean实例的Bean工厂 DefaultListableBeanFactory 将这三个都放入容器中通过prepareContext方法对容器中部分属性进行初始化了。 先用postProcessApplicaitonContext方法设置Bean名称生成器资源加载器类型转换器等接着执行上下文初始化ApplicationContextInitializer默认加载7个。发布容器准备完成监听事件后陆续为容器注册启动参数bannerbean引用策略和懒加载机制等等通过bean定义加载器将启动类在内的资源加载到bean定义池beanDefinitionMap中以便后续根据bean定义创建bean对象然后发布资源加载完成事件 填充容器 生产自身提供的和自定义的bean对象放入容器也就是自动装配 Spring Factories 在META-INF下META-INF目录是提供jar文件描述信息的文件目录。 SpringFactories模仿java SPI机制 SPI机制是service provice interface面向对象基于接口编程如JDBCjava定义接口数据库公司实现接口换数据库不需要修改java代码。类似还有xml解析日志模块的方案。 java SPI 就是为某个接口寻找服务的实现的机制。 对于在maven中引用的其他外部包加入容器的过程需要用到spring.factories。 在SpringBoot启动时SpringFactoriesLoader类会通过loadFactories或者loadFactoryNames寻找每个jar包下的META-INF/spring.factories AutoConfigurationImportSelector的selectImports方法返回的类名来自spring.factories文件内的配置信息。通过该方法加载外部的Bean 拦截器和过滤器 实现原理过滤器基于回调函数拦截器基于java反射使用范围Filter在servlet中定义依赖servlet只能在web中使用。拦截器可以在webapplicationswing触发时机不同Filter在请求进出servlet前后。拦截器可以深入到方法前后异常抛出前后。范围不同过滤器过滤所有请求拦截器只对action请求起作用。只会对Controller中请求或访问static目录下的资源请求起作用。进入Bean的情况不同拦截器先于ApplicationContext加载所以拦截器无法注入Spring容器管理的bean。 解决办法拦截器不使用Component加载改为使用ConfigurationBean加载。执行顺序不同过滤器用Order控制级别级别越小越先执行拦截器默认执行顺序为注册顺序也可以通过Order手动执行级别越小越先执行 Servlet中的过滤器Filter是实现了javax.servlet.Filter接口的服务器端程序主要的用途是过滤字符编码、做一些业务逻辑判断等。 SpringMVC 中的Interceptor 拦截器的主要作用就是拦截用户的 url 请求,并在执行 handler 方法的前中后加入某些特殊请求,比如通过它来进行权限验证或者是来判断用户是否登陆。 拦截器的实现方式实现HandlerInterceptor 或者WebRequestInterceptor 过滤器用于属性甄别对象收集不可改变过滤对象的属性和行为 拦截器用于对象拦截行为干预可以改变拦截对象的属性和行为 过滤器是JavaWeb的三大组件之一是实现Filter的java类。 过滤器实现对请求资源的过滤功能在请求资源前响应前进行操作。 过滤器主要用来如参数过滤、防止SQL注入、防止页面攻击、过滤敏感字符、解决网站乱码、空参数矫正、Token验证、Session验证、点击率统计等。 过滤器 Filter init()该方法在容器启动初始化过滤器时被调用它在Filter的整个生命周期只会被调用一次这个方法必须执行成功否则过滤器会不起作用。 doFilter()容器中的每一次请求都会调用该方法FilterChain用来调用下一个过滤器Filter。 destroy()容器销毁时被调用。一般在方法中销毁或关闭资源也只会被调用一次。 拦截器核心API SpringMVC拦截器提供三个方法分别是preHandle、postHandle、afterCompletion我们就是通过重写这几个方法来对用户的请求进行拦截处理的。 preHandle() 这个方法将在请求处理之前进行调用。「注意」如果该方法的返回值为false 将视为当前请求结束不仅自身的拦截器会失效还会导致其他的拦截器也不再执行。 postHandle()只有在 preHandle() 方法返回值为true 时才会执行。会在Controller 中的方法调用之后DispatcherServlet 返回渲染视图之前被调用。「有意思的是」postHandle() 方法被调用的顺序跟 preHandle() 是相反的先声明的拦截器 preHandle() 方法先执行而postHandle()方法反而会后执行。 afterCompletion()只有在 preHandle() 方法返回值为true 时才会执行在整 个请求结束之后 DispatcherServlet 渲染了对应的视图之后执行。 如何解决循环依赖 一级缓存实例化初始化的对象 二级缓存实例化还未初始化的对象 三级缓存对象工厂用来创建二级缓存中的对象 AB相互依赖 实例化A放入三级缓存属性注入时发现需要B去实例化B发现需要依赖A从缓存中依次查找从三级缓存中删除放入二级缓存完成初始化后将B放入一级缓存。接着初始化A完成后删除二级缓存中的A放入一级缓存。 使用三级缓存的原因是保证使用的是同一个对象。 单用二级缓存在二级缓存中放入一个普通Bean之后BeanPostPorcessor生成代理对象覆盖多线程环境取不到一致的对象。 保证获取的是同一个对象。A3和A2都依赖A1A1依赖A2,A3在进行A1的依赖注入时创建A2时给A2注入了A1的代理对象但是A3进行依赖注入时如果不缓存A1的代理对象aop会重新生成代理对象导致单例被破坏 Spring SpringBoot SpringCloud的区别 Spring IOC和AOP SpringBoot 约定大于配置快速开发 SopringCloud 微服务治理提供解决方案 SpringBoot可以依赖SpringCloud开SpringCloud不能离开SpringBoot,属于依赖关系。 Spring Filter 怎么写 参考 SpringBoot——SpringBoot使用过滤器Filter 1.注解实现 编写一个过滤器在启动类上添加ServletComponentScan注解扫描过滤器的对应包 2.Spring Boot 的配置类实现 新增过滤器不添加注解通过FilterRegistrationBean注册自己的过滤器 获取Spring容器对象 实现BeanFactoryAware ApplicationContextAwareApplicationListener接口 获取spring容器对象 SpringAOP 参考Spring AOP代码实现实例演示与注解全解 Aspect Oriented Programming 面向切面编程用于处理代码中公共非业务逻辑如日志鉴权等。 PointCut在什么时候切入 分为execution和annotation方法。前者用路径表达式指定那些类织入切面后者指定被那些注解修饰的代码织入切面。Advice 处理时机和处理内容在什么时间做什么事。Aspect切面即PointCut和AdviceJointPoint连接点是程序执行的一个点。例如一个方法的执行或者一个异常的处理Weaving织入就是通过动态代理在目标对象方法中执行处理内容的过程。 MethodSignature signature (MethodSignature) joinPoint.getSignature(); Method method signature.getMethod();//拿到方法 Object[] allArgs joinPoint.getArgs();//拿到参数 Aspect Component public class LogAdvice {// 定义一个切点所有被GetMapping注解修饰的方法会织入advicePointcut(annotation(org.springframework.web.bind.annotation.GetMapping))// Pointcut(execution(* com.example.demo.controller..*(..)))private void logAdvicePointcut(){}Before(logAdvicePointcut())public void logAdvice(){// 这里只是一个示例你可以写任何处理逻辑System.out.println(get请求的advice触发了);} } redis 哪里用到的redis,时效 数据库存储 存储登录信息 token 30分钟 实现value的序列化存储的对象缓存 路由转发接口匹配 redismanager默认30天 工作流暂存 10分钟为了缓解数据库压力开始的流程是异步进程但是要全量进程的结果幂等性的实现 在消费消息时将消息的uuid存储到redis中防止重复消费 redis数据结构 String List Hash Set ZSet Stirng 底层实现 动态字符串sds sds结构一共有五种header定义目的是为了满足不同长度字符串使用节省内存。 header主要包括 len长度alloc字符串最大容量不包括header和最后的空终止字符flags标志位第三位表示类型其余五位未使用buf字符数组encoding int:可以使用long类型的整数表示的会用long型来存储raw长度大于44字节的字符串使用sds存储embstr长度小于等于44字节的字符串效率比较高且数据都保存在一块内存区域。 List底层是链表 hash底层是dict ZSet的实现方式 ziplist压缩链表 元素数量小于128每个元素长度小于64 skiplist跳跃链表不满足上述条件就采用跳表具体来说组合了map和skiplist map用来存储 member到score的映射 时间复杂度为O1skiplist按照从小到大存储分数每个元素的值都是[scorevalue]对 跳表就是在有序节点上增加多级索引 n个节点第一层索引为n/2第二层索引为n/4,第k层索引为n/2^k 空间换时间的折半查找 redis排行榜 zset 添加 zadd key score value ZADD broadcast:20210108231 1 lisi 加分 zincrby key increment member ZINCRBY broadcast:20210108231 2 lisi 排名 ZRANGE broadcast:20210108231 0 -1 WITHSCORES redis的淘汰策略 过期键淘汰策略是定期删除和惰性删除 定期删除是指redis服务器定期操作redis.c/serverCron函数执行时redis.c/activeExpireCycle会被调用。actvieExpire函数在规定时间内分多次遍历服务期内的多个数据库从过期字典中检查一部分key的过期时间并删除。 current_db记录当前检查数据库函数处理2号数据库时间超限返回后下次检查会从3号数据库开始检查。所有数据库检查完毕current_db重置为0,然后再次开启一轮的检查工作。 惰性删除是指用户请求此时会检查过期过期清除不会返回 大量key堆积内存耗尽如何处理 内存淘汰机制 缓存击穿缓存雪崩 缓存穿透缓存和数据库中都没有数据多次重复访问导致数据库崩溃 解决 业务层校验对于不合规的入参直接返回 不存在数据设置短过期时间 布隆过滤器缓存击穿热点key到点失效大量请求进入从而全部到达数据库压垮数据库 解决 永不过期 定时更新过期时间1h每到59min去更新 互斥锁redis根据key获取到的value为空时先加锁去数据库加载加载完毕释放锁。其他线程请求发现获取锁失败则睡眠一段时间。雪崩缓存大面积失效或者Redis宕机大量请求进入数据库压垮数据库 解决 设计有效期均匀分布避免缓存设置相近的有效期可以设置有效期时增加随机值或者统一规划有效期使得过期时间均匀分布。 缓存预热流量大时提前访问一遍将数据存储到redis中并且设置不同的过期时间 保证Redis的高可用 redis的锁机制 redis一般用作缓存多读少写只支持乐观锁 Redis事务命令主要包括 WATCH, EXEC, DISCARD, MULTI。 事务使用MULTI开启这时可以执行多条命令Redis在这些事务中加入命令当用户执行Exec命令时才真正的执行队列当中的命令。执行discard丢弃队列中的命令。 Watch命令是Exec执行的条件watch的key没有修改则执行事务否则事务不会被执行。 Watch命令可以被调用多次一个watch命令可以监控多个key。watch命令调用则开启监视功能直到exec命令终止。 Redis的watch命令给事务CAS机制如果key在执行exec前有变动则整个事务被取消。 事务中采用watch加锁unwatch解锁执行EXEC命令或者Discard命令后锁自动释放不需要进行unwatch操作。 redis分布式锁 参考Redis实现分布式锁的7种方案 分布式锁需要保证 1.互斥性只能有一个客户端获得锁 2.安全性锁只能被持有该锁的客户端删除 3.死锁锁超时释放避免死锁 4.容错当redis部分节点宕机客户端仍能获取锁和释放锁 1.一种实现 setnx k v; 成功返回1 失败返回0 getset k v;返回key的旧值 expire k secondsl;给key设置超时时间 del key [key …] 1.1setnx key 当前时间过期时间 1.2. 失败获取锁失败成功 expire 超时时间 1.3.执行业务 del key 问题如果获取锁成功未设置超时时间的时候进行重启会产生死锁。 如果不是kill线程而是shutdown可以用springBean的predestory注解进行删除key操作 优化获取锁失败后查看当前key的value值如果不为空并且当前时间大于该值说明当前锁失效通过getset获取值。如果getset获取的值和一开始的旧值相同则获取锁成功。 问题多节点时间需要尽可能的保持一致。getset即便失败也会延长之前锁的时间。 2.使用lua脚本 保证setnx和expire的原子性 3.set扩展命令 SET key value[EX seconds][PX milliseconds][NX|XX] NX :表示key不存在的时候才能set成功也即保证只有第一个客户端请求才能获得锁而其他客户端请求只能等其释放锁才能获取。EX seconds :设定key的过期时间时间单位是秒。PX milliseconds: 设定key的过期时间单位为毫秒XX: 仅当key存在时设置值 问题业务没执行完锁就释放了锁被别的线程误删。 4.SET EX PX NX 校验唯一随机值,再删除 还是会存在业务没有执行完成锁释放的问题。 5.Redisson框架 开源框架Redisson 加锁后启动一个watchdog线程每隔10s检查是否持有锁持有锁就延长防止锁过期提前释放。 6.redlockredisson redis节点全为master避免master加锁后未同步到slave然后宕机导致加锁失败的情况 按顺序向5个master节点请求加锁 根据设置的超时时间来判断是不是要跳过该master节点。 如果大于等于3个节点加锁成功并且使用的时间小于锁的有效期即可认定加锁成功啦。 如果获取锁失败解锁 redis线程模型 参考 深入学习redis 的线程模型 redis内部采用事件处理器是单线程的因此redis叫做单线程模型。采用IO多路复用机制同时监听多个socket将产生的事件的socket压缩到内存队列中事件分派器根据事件不同的类型选择对应的事件处理器进行处理。 文件事件处理器的结构 多个socketIO多路复用程序文件事件分派器事件处理器连接应答处理器命令请求处理器命令回复处理器 线程模型 多个socket可能并发产生不同的操作每个操作对应不同的事件但是IO多路复用程序会监听多个socket将产生事件的socket放入队列中排队事件分派器每次从队列中取出一个socket根据socket对应的事件类型交给对应的事件处理器进行处理。 建立连接 redis服务端进程初始化时将server socket 的AE_READABLE事件与连接应答处理器关联。客户端socket01向redis进程serverSocket请求建立连接此时server socket 产生一个AE_READBLE事件IO多路复用程序监听到server socket产生的事件后将socket压入队列。文件事件分派器从队列中获取socket交给连接应答处理器。连接应答处理器会创建一个能与客户端通信的socket01并将socket01的AE_READABLE事件与命令请求处理器关联 执行一个set请求客户端发送了一个set key value请求此时redis中的socket01会产生AE_READABLE事件IO多路复用程序将socket01压入队列此时事件分派器从队列中获取到socket01产生的AE_READABLE事件由于前面socket01的AE_READABLE事件已经和命令请求处理器关联事件分派器将命令交给请求处理器来处理。命令处理器读取k v并在自己内存中完成设置操作完成后将socket01的AE_WRITEABLE事件与命令回复处理器关联如果此时客户端准备好了接受数据那么redis中的socket01会产生一个AE_READABLE事件同样压入队列中事件派分器找到相关联的命令回复处理器由命令回复处理器对socket01输入本次操作的一个结果,比如ok之后解除socket01的AE_READABLE事件与命令回复处理器的关联。 为什么redis效率高 1.纯内存操作 2.核心是基于非阻塞的IO多路复用机制 3.C语言实现语言更接近操作系统执行速度相对会快 4.单线程反而避免了多线程的频繁上下文切换问题预防了多线程可能产生的竞争问题。 redis的吞吐量 单点TPS达到8万/秒QPS达到10万/秒 qps是指每秒最大能接受的用户访问量tps是指每秒钟最大能处理的请求数。 部署方式 单机主从哨兵 kafka 使用消息队列的原因 1.流量削峰消息过多处理有限 落库采用异步消息的方式减缓数据库压力 2.服务解耦接口故障不影响生产和消费消息发出去发消息的应用down了也不会影响后序微服务的消费 3.异步提高响应速度用户无感知 kafka 如何解决消息重复 生产者生产者发送的消息没有收到正确的broker响应导致producer重试。 启动kafka的幂等性要启动kafka的幂等性设置 enable.idempotencetrue 以及 ackall 以及 retries 1 。ack0不重试。可能会丢消息适用于吞吐量指标重要性高于数据丢失例如日志收集 幂等生产者的原理每个生产者都有唯一Pid发次发送消息会累加seq。broker为每个topic的每个分区都维护了一个当前当前写成功的消息最大PID-seq消息落盘1当收到小于当前最大PID-seq时就会丢弃该消息。 消费者 offset手动提交业务成功处理后提交offset 幂等性多次操作结果一致 获取消息的唯一id会将id存入redis记录为处理中以后的消息就不会进行重复处理 kafka如何保证消息不丢失 1.生产者发送给服务器 acks机制 0 发送就成功1 生产者收到leader分区的响应则认为成功 -1 当所有ISR中的副本全部收到消息生产者才认为是成功的 2.服务器通过副本保存消息 3.消费者关闭自动提交在接收到消息进行业务处理完毕后再提交偏移量 kafka什么时候进行rebalance 参考Kafka的Rebalance机制可能造成的影响及解决方案 每当有新的消费者加入或者订阅的topic数发生变化会触发rebalance再均衡在同一个消费组当中分区的所有权从一个消费者转到另一个消费者机制Rebalance顾名思义就是重新均衡消费者消费。 过程如下 1.所有消费者向coordinator发送请求请求加入comsumer group。一旦所有成员都发送了请求Coordinator会从中选择一个consumer作为leader并将组员信息发给leader 2.leader分配消费方案指定哪个消费者消费哪些topic的哪些分区。发给coordinatorcoordinator发送给消费者组内成员知道自己该消费哪些分区了。 coordinator每个consumer group会选择一个服务器作为自己的coordinator负责监控整个消费者组内各个分区的心跳以及判断是否宕机和开启rebalance的 partition每个topic分区备份在不同的服务器上 如何选择coordinator 对groupid进行hash,然后对_consumer_offsets的分区数量进行取模默认分区数量为50可以配置。 发生的时机 1.分区数量增加。 2.对topic的订阅发生变化 3.消费者组成员的加入或者离开。 影响1.重复消费2.集群不稳定3.影响消费速度 kafka 消息积压怎么办 参考 1.修复消费者然后停掉所有消费者 2.临时建立10倍或者20倍的queue数量新建topic分区是原来的10倍 3.写一个临时分发消息的consumerxiaofei消费挤压数据不作处理均匀写入临时建好10数量的queue中 4.征用10倍的机器来部署consumer每一批consumer消费一个临时queue的消息 5.这种做法相当于临时将queue资源和consumer资源扩大10倍以10倍速度来消费消息 6.消费完成之后回复原有的部署架构重新用原来的consumer机器来消费消息。 kafka分区和消费者的关系 1.生产者中的分区合理消费消费者的线程对象和分区保持一致多余的线程不会进行消费 2.消费者默认即为一个线程对象 3.消费者服务器数*线程数 partition个数 分区数量 分区多可以增加吞吐量 分区过多 客户端服务端需要的内存变多客户端生产者有个参数batchsize默认16kb。为每个分区缓存消息满了打包发送。 分区越多消费者获取数据所需的内存就增多。同时消费者线程数要匹配最大分区数线程切换开销很大。 服务器端维护许多分区界别的缓存。文件句柄的开销每个分区在底层文件系统都有属于自己的目录。分区越多要同时打开的文件句柄数就越多。降低高可用性kafka通过副本来保证高可用。分区多服务器上的副本就多服务器挂掉在副本之间进行重新选举的消耗就大。 目标吞吐量TT,消费者吞吐量TC,生产者吞吐量TP分区数 TT/max(TP,TC) 分区和消费者的关系是多对一 一个分区只能对应一个消费者的原因是保证消息顺序性。 因此消息挤压部署多台消费者实例是不能加快消费的最多增加到和分区数量一致超过的组员只会占有资源二不起作用。 消费者分区分配策略 1.range策略 将消费者按名称排序分区按照数字排序分区总数/消费者除得尽均匀分配除不尽位于前面的消费多负责分区 2.roundrobin 轮询 为了保证均匀分配需要满足两个条件 1.同一个消费者组里每个消费者订阅的主题必须相同 2.同一个消费者里面所有消费者的num.streams必须相等 3.sticky分配策略 主要实现两个目的如果发生冲突优先实现第一个 1.分区分配尽可能均匀 2.分区的分配尽可能的与上次分配的保持相同 0.11.X版本引入最复杂最优秀 区别于轮询在消费者挂掉之后不会全部分区重新轮询分配而是将挂掉消费者消费的分区进行重新分配。 其他微服务组件 eureka注册中心机制 参考高频面试之Eureka 注册中心包括服务发现治理等功能。 EnableEurekaServer作为注册中心EnableEurekaClient作为服务的提供者或消费者。 注册服务 EurekaClient将服务信息封装成Intanceinfo对象通过EurekaHttpClient调用register方法发送post请求执行服务注册 EurekaServer提供了基于Jersey的Rest风格接口在ApplicationResource类中提供了addInstance方法来接受注册信息。如果注册信息通过校验将服务信息保存到本地注册表。数据结构为双层HashMap,key为应用名内层mapKey是应用实例信息编号value是InstanceInfo EurekaClient接受并解析注册结果判断httpResponse的statusCode如果是204则代表注册成功 调用replicateToPeers方法将此次注册信息复制到对等的Eureka节点定时任务 拉取服务器注册实例 任务通过ScheduledExecutorService来实现任务调度执行周期默认为60秒一次 获取方式有两种全量获取和增量获取。第一次全量获取后序增量获取获取到服务器注册实例信息后保存或更新到本地 - 续约 任务通过ScheduledExecutorService来实现任务调度执行周期默认为30秒一次 通过Renew方法发起续约请求。将appnameappid以及intanceinfo作为参数通过EurekaHttpClien发送Http请求 EurekaServer通过renewLease()方法接受续约请求 根据AppName从注册表获取对应的服务信息并更新一些属性如renewsLastMin,lastUpdateTimestamp EurekaServer返回结果200或者204 EurekaClient接受续约结果如果是404重新发起如果是200则表示续约成功更LastSuccessHeartBeatTimestamp变量 剔除服务 通过 JDK 自带的 Timer 来实现任务调度通过 evict() 方法执行具体的操作 首先判断是否开启了实例自我保护机制如果开启自我保护则不做任何操作 如果未开启根据 lastUpdateTimestamp 收集已过期的服务加入到List集合中 通过 internalCancel() 方法在该方法中从 registry 中剔除已经过期的实例。具体的剔除过程会通过打乱过期服务列表并通过 Random 随机剔除保证服务器剔除的均匀性 限流算法 一致性哈希 一致性哈希相较于普通哈希具有更好的可扩展性和容错性。 哈希是对节点个数取模在进行扩容或者节点下线时需要重新映射所有数据。 一致性哈希是对2^32取模将哈希值空间映射到虚拟的闭环上称为哈希环。取模所得值进行顺时针寻找到的第一个节点为哈希环中的位置。节点扩容和下线时只需要迁移相邻节点的数据。 当分布不均衡时采用虚拟节点来解决问题。 无状态的服务消费降级 linux命令 cat 文件 | grep 关键字 | wc -l 参考 Linux系统中统计文件中某个字符出现次数命令详细教程 不分大小写统计 grep -o -i ‘a’ aaa.txt | wc -l统计多个文件中某个字符出现次数总和 grep -o -i ‘a’ aaa.txt bbb.txt | wc -l 其他 数据结构-树 参考树、二叉树、完全二叉树、满二叉树的概念和性质 树 特点 只有一个根节点每个子树根节点只有一个前驱可以有0或者多个后驱。 注意子树之间不能有交集 概念 节点的度一个节点含有子树的个数称为该节点的度叶节点度为为0树的度最大节点的度节点的层次根为1层往下2层以此类推树的高度或者深度最大层数兄弟节点同根堂兄节点同层不同跟 树的表示 二叉树 概念 一颗二叉树是一个节点的有序集合该集合为空或者由一个根节点加上两颗别称为左子树和右子树的二叉树组成。 特点 1.二叉树不存在子节点大于2的度 2.二叉树有左右之分次序不能颠倒因此二叉树是有序树。 对于任意二叉树都是由以下几种情况复合而成的。 特殊的二叉树 满二叉树 所有叶子结点都在最后一层。所有分支节点都有两个孩子。节点为2^k-1。k为层数。 完全二叉树 前n-1层是满的。不满的那层子从左往右是连续的。 二叉树的性质 ①若规定根结点的层数为1则一棵非空二叉树的第i层上最多有2i-1个结点。\n②若规定根结点的层数为1则深度为h的二叉树的最大结点数为2h-1个。\n③对任何一棵二叉树如果度为0的叶结点个数为n0度为2的分支结点个数为n2则有n0 n21。常用这个性质解选择题\n④若规定根结点的层数为1则具有N个结点的满二叉树的深度h log2(N1)。\n⑤对于具有N个结点的完全二叉树如果按照从上至下、从左至右的数组顺序对所有结点从0开始编号则对于序号为i的结点\n1、若 i 0则该结点的父结点序号为( i - 1) / 2若 i 0则无父结点。\n2、若2i 1 \u003C N则该结点的左孩子序号为2i 1若2i 1 N则无左孩子。\n3、若2i 2 \u003C N则该结点的右孩子序号为2i 2若2i 2 N则无右孩子。 二叉树的存储结构 顺序存储一般用来存储完全二叉树否则会造成空间浪费。现实只有堆会用数组存储。 链式存储用链表表示二叉树。每个节点为左右指针域和数据域。 二叉树的遍历 深度遍历 前序 根左右中序 左根右后序 左右跟 广度遍历层次遍历 堆 参考堆的应用 – Top-K问题巨详细 堆是二叉树一般的二叉树用链表存储用数组存储会浪费空间但是堆是完全二叉树用数组存储。 已知父节点下标n, 他的右节点为2n2,左节点2n1 已知子节点n,他的父节点为(n-1)/2 大根堆和小根堆 大根堆根节点大于左右孩子节点 小根堆根节点小于左右孩子节点 优先级队列 java提供这种数据结构每次添加或者删除元素都会变成小跟堆。 // 默认得到一个小根堆 PriorityQueueInteger smallHeap new PriorityQueue(); smallHeap.offer(23); smallHeap.offer(2); smallHeap.offer(11); System.out.println(smallHeap.poll());// 弹出2剩余最小的元素就是11会被调整到堆顶下一次弹出 System.out.println(smallHeap.poll());// 弹出11// 如果需要得到大根堆在里面传一个比较器PriorityQueueInteger BigHeap new PriorityQueue(new ComparatorInteger() {Overridepublic int compare(Integer o1, Integer o2) {return o2 - o1;}});top-K问题 数组找出前三个最小元素 1.排序 2.放入小跟堆 3.在大根堆中放三个元素循环数组每次往里放一个数据弹出最大的数据最后堆里就是结果。 堆的向下调整算法、堆的向上调整算法、堆的实现、Topk问题 数据格式标准化 xml到标准对象 thoughtworks.xstream 流 json到标准对象 hutools工具,底层反射调用set方法 javaBean java语言中的可重用组件。 满足1.类是公共的2.有一个无参的公共构造器3.有属性且有对应的get和set方法。 其他开发者通过JSPServlet 其他JavaBeanapplet程序或者应用来使用这些对象。用户可以认为JavaBean提供了一种随时随地复制粘贴的功能。 javaBean的任务就是一次性编写任何地方执行任何地方重用。 难点堆排序redis跳表 八股文我背了第一层技术hr问第二层给我讲解完事推荐我可以看书 深入理解java虚拟机 周志明 MySQL实战宝典姜承尧
http://www.w-s-a.com/news/318962/

相关文章:

  • 做企业网站联系网站开发具体的工作内容
  • 联合易网北京网站建设公司怎么样网站页面开发流程
  • 2015做那些网站能致富网站建设审批表
  • 深圳 网站设计个人名片模板
  • 网站建设费用选网络专业网站在线推广
  • 天津建设网站c2成绩查询用记事本制作html网页代码
  • 织梦二次开发手机网站如何成为一名设计师
  • 网站公司建设网站镇江本地网站
  • 网页设计后面是网站建设吗凡客诚品的配送方式
  • 万链网站做的怎么样?深圳门户网站开发
  • 在线设计工具的网站怎么做wordpress多语言版本号
  • 建设购物网站要求优秀网站大全
  • 平顶山做网站公司用源码网站好优化吗
  • 网上电商游戏优化大师手机版
  • 个人微信公众号怎么做微网站吗网站域名需要续费吗
  • 有效的网站建设公丹阳做网站的
  • 哪些行业做网站的多学企业网站开发
  • 外贸seo网站制作网站备案的流程
  • 网站布局教程wordpress 侧边栏位置
  • 谁有手机网站啊介绍一下dedecms 网站重复文章
  • 博客网站快速排名微信机器人免费版wordpress
  • 孝感网站建设xgshwordpress网站基础知识
  • 百度为什么会k网站长沙做网站找哪家好
  • 揭阳商城网站建设新闻稿发布平台
  • 电商网站建设免费在线优化网站
  • 厦门网站建设咨询挣钱最快的小游戏
  • 郑州网站网络营销莱芜雪野湖别墅
  • 安装iis8 添加网站河南省建设执业资格中心网站
  • 个人网站电商怎么做广州市营销型网站建设
  • 空间站做网站什么版本wordpress 勾子