1. JDK基础
1.1 HashMap源码1.2 线程池原理1.3 [Java8新特性](https://developer.51cto.com/article/647804.html)1.4 [锁机制](https://tech.meituan.com/2018/11/15/java-lock.html)(CAS/AQS/重量级 & 轻量级/偏向锁/独占锁/具体实现/ConcurrentHashMap)1.5 [动态代理机制](https://javaguide.cn/java/basis/proxy/)1.6 [NIO](https://javaguide.cn/java/basis/io/#aio-asynchronous-i-o)工作方式(IO模型 / Channel / Selector / Buffer / Epoll)1.7 面向对象设计模式1.8 [Fork/Join](https://www.twle.cn/c/yufei/javatm/javatm-basic-forkjoin.html) 2. JVM基础
2.1 JVM内存区域2.2 Java对象创建过程2.3 对齐填充2.4 GC算法2.5 性能调优 / 问题排查 3. 缓存相关
3.1 Redis数据结构3.2 缓存穿透/雪崩/预热/更新3.3 高可用保证3.4 集群分片机制(路由/扩容/收缩)3.5 具体使用场景 4. 消息相关
4.1 [消息的推拉模式](https://blog.csdn.net/wabiaozia/article/details/102057941)4.2 整体技术架构4.3 消息的可靠性保证4.4 部署模式(主从)4.5 具体使用场景 5. 数据库相关
5.1 索引数据结构5.2 聚簇索引和非聚簇索引5.3 单机事务5.4 隔离级别5.5 高可用方案5.6 水平分库分表(路由策略/水平扩容)5.7 乐观锁/悲观锁5.8 搜索引擎工作原理 6. 分布式技术7. 应用框架相关
7.1 Spring IOC&AOP原理(织入方式)7.2 Spring MVC的工作流程7.3 SpringBoot源码理解7.4 Spring事务控制7.5 [MyBatis](https://javaguide.cn/system-design/framework/mybatis/mybatis-interview/)工作原理 8.Web开发
8.1 WEB容器(Tomcat/[Netty](https://www.163.com/dy/article/FRNK1U3K0511X1MK.html))8.2 [HTTP协议(1.1/2.0)](https://juejin.cn/post/6963931777962344455)8.3 [WebSocket协议](http://www.noobyard.com/article/p-weguifxd-ru.html)8.4 [TCP/UDP](https://javaguide.cn/cs-basics/network/other-network-questions/)8.5 [Restful规范](https://javaguide.cn/system-design/basis/RESTfulAPI/#)8.6 [登录认证(OAuth2/JWT)](https://javaguide.cn/system-design/security/basis-of-authority-certification/)8.7 [权限设计](https://blog.51cto.com/u_13260163/3095454)8.8 [RBAC模型](https://www.cnblogs.com/hoanfir/p/9088668.html) 9. 开发工具10. 其他
1. JDK基础 1.1 HashMap源码JDK1.8 之前 HashMap 由数组+链表组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突⽽存在的(“拉链法”解决冲突)。
HashMap 通过key 的 hashCode 经过扰动函数处理过后得到 hash 值,然后通过 (n - 1) & hash 判断当前元素存放的位置(这⾥的 n 指的是数组的⻓度),如果当前位置存在元素的话,就判断该元素与要存
⼊的元素的 hash 值以及 key 是否相同,如果相同的话,直接覆盖,不相同就通过拉链法解决冲突。所谓扰动函数指的就是 HashMap 的 hash ⽅法。使⽤ hash ⽅法也就是扰动函数是为了防⽌⼀
些实现⽐较差的 hashCode() ⽅法 换句话说使⽤扰动函数之后可以减少碰撞。所谓 拉链法 就是:将链表和数组相结合。也就是说创建⼀个链表数组,数组中每⼀格就是⼀
个链表。若遇到哈希冲突,则将冲突的值加到链表中即可。 JDK1.8 以后在解决哈希冲突时有了较⼤的变化,当链表⻓度⼤于阈值(默认为 8)(将链表转换成红⿊树前会判断,如果当前数组的⻓度⼩于 64,那么会选择先进⾏数组扩容,⽽不是转换为红⿊树)时,将链表转化为红⿊树,以减少搜索时间。
TreeMap、 TreeSet 以及 JDK1.8 之后的 HashMap 底层都⽤到了红⿊树。红⿊树就是为了解决⼆叉查找树的缺陷,因为⼆叉查找树在某些情况下会退化成⼀个线性结构。HashMap 的⻓度为什么是2的幂次⽅:为了能让 HashMap 存取⾼效,尽量较少碰撞,也就是要尽量把数据分配均匀。我们上⾯也讲到了过了, Hash 值的范围值-2147483648到2147483647,前后加起来⼤概40亿的映射空间,只要哈希函数映射得⽐较均匀松散,⼀般应⽤是很难出现碰撞的。但问题是⼀个40亿⻓度的数组,内存是放不下的。所以这个散列值是不能直接拿来⽤的。⽤之前还要先做对数组的⻓度取模运算,得到的余数才能⽤来要存放的位置也就是对应的数组下标。这个数组下标的计算⽅法是“ (n - 1) & hash ”。(n代表数组⻓度)。这也就解释了 HashMap 的⻓度为什么是2的幂次⽅。 1.2 线程池原理
池化技术的思想主要是为了减少每次获取资源的消耗,提⾼对资源的利⽤率。ThreadPoolExecutor 3 个最重要的参数:
corePoolSize : 核⼼线程数线程数定义了最⼩可以同时运⾏的线程数量。maximumPoolSize : 当队列中存放的任务达到队列容量的时候,当前可以同时运⾏的线程数量变为最⼤线程数。workQueue : 当新任务来的时候会先判断当前运⾏的线程数量是否达到核⼼线程数,如果达到的话,新任务就会被存放在队列中。 ThreadPoolExecutor 其他常⻅参数:
- keepAliveTime :当线程池中的线程数量⼤于 corePoolSize 的时候,如果这时没有新的任务提交,核⼼线程外的线程不会⽴即销毁,⽽是会等待,直到等待的时间超过了keepAliveTime 才会被回收销毁;unit : keepAliveTime 参数的时间单位。threadFactory :executor 创建新线程的时候会⽤到。handler :饱和策略。关于饱和策略下⾯单独介绍⼀下。
ThreadPoolExecutor.AbortPolicy :抛出 RejectedExecutionException 来拒绝新任务的处理。ThreadPoolExecutor.CallerRunsPolicy :调⽤执⾏⾃⼰的线程运⾏任务。您不会任务请求。但是这种策略会降低对于新任务提交速度,影响程序的整体性能。另外,这个策略喜欢增加队列容量。如果您的应⽤程序可以承受此延迟并且你不能任务丢弃任何⼀个任务请求的话,你可以选择这个策略。ThreadPoolExecutor.DiscardPolicy : 不处理新任务,直接丢弃掉。ThreadPoolExecutor.DiscardOldestPolicy : 此策略将丢弃最早的未处理的任务请求。 解释:我们在代码中模拟了 10 个任务,我们配置的核⼼线程数为 5 、等待队列容量为 100 ,所以每次只可能存在 5 个任务同时执⾏,剩下的 5 个任务会被放到等待队列中去。当前的 5 个任务之⾏完成后,才会之⾏剩下的 5 个任务。 1.3 Java8新特性
Lambda 表达式 − Lambda 允许把函数作为一个方法的参数(函数作为参数传递到方法中)。方法引用 − 方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。默认方法 − 默认方法就是一个在接口里面有了一个实现的方法。新工具 − 新的编译工具,如:Nashorn引擎 jjs、 类依赖分析器jdeps。Stream API −新添加的Stream API(java.util.stream) 把真正的函数式编程风格引入到Java中。Date Time API − 加强对日期与时间的处理。Optional 类 − Optional 类已经成为 Java 8 类库的一部分,用来解决空指针异常。Nashorn, Javascript 引擎 − Java 8提供了一个新的Nashorn javascript引擎,它允许我们在JVM上运行特定的javascript应用。 1.4 锁机制(CAS/AQS/重量级 & 轻量级/偏向锁/独占锁/具体实现/ConcurrentHashMap)
锁的种类
CAS:乐观锁的主要实现方式。
CAS全称 Compare And Swap(比较与交换),是一种无锁算法。在不使用锁(没有线程被阻塞)的情况下实现多线程之间的变量同步。java.util.concurrent包中的原子类就是通过CAS来实现了乐观锁。
AQS:抽象队列同步器AbstractQueuedSynchronizer。AQS 核⼼思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的⼯作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占⽤,那么就需要⼀套线程阻塞等待以及被唤醒时锁分配的机制,这个机制 AQS 是⽤ CLH 队列实现的,即将暂时获取不到锁的线程加⼊到队列中。
ConcurrentHashMap:是线程安全的HashMap。实现的思想是使用的分段加锁,使用分段加锁的作用当然就是可以有效提升并发量,可以对比一下所有操作都加锁的HashTable,就能明白分段加锁的好处了。
1.5 动态代理机制 1.6 NIO工作方式(IO模型 / Channel / Selector / Buffer / Epoll)
从应用程序的视角来看,应用程序对操作系统的内核发起 IO 调用(系统调用),操作系统负责的内核执行具体的 IO 操作。也就是说,我们的应用程序实际上只是发起了 IO 操作的调用而已,具体 IO 的执行是由操作系统的内核来完成的。
当应用程序发起 I/O 调用后,会经历两个步骤:
- 内核等待 I/O 设备准备好数据内核将数据从内核空间拷贝到用户空间。
Java中常见的三种IO模型:
BIO(Blocking I/O):属于同步阻塞 IO 模型。在同步阻塞 IO 模型中,应用程序发起 read 调用后,会一直阻塞,直到内核把数据拷贝到用户空间。
NIO(Non-blocking/New I/O):属于 I/O 多路复用模型、同步非阻塞 IO 模型。
Java 中的 NIO 于 Java 1.4 中引入,对应 java.nio 包,提供了 Channel , Selector,Buffer 等抽象。NIO 中的 N 可以理解为 Non-blocking,不单纯是 New。它支持面向缓冲的,基于通道的 I/O 操作方法。 对于高负载、高并发的(网络)应用,应使用 NIO 。
同步非阻塞 IO 模型中,应用程序会一直发起 read 调用,等待数据从内核空间拷贝到用户空间的这段时间里,线程依然是阻塞的,直到在内核把数据拷贝到用户空间。相比于同步阻塞 IO 模型,同步非阻塞 IO 模型确实有了很大改进。通过轮询操作,避免了一直阻塞。但是,这种 IO 模型同样存在问题:**应用程序不断进行 I/O 系统调用轮询数据是否已经准备好的过程是十分消耗 CPU 资源的。**这个时候,I/O 多路复用模型 就上场了。
IO 多路复用模型中,线程首先发起 select 调用,询问内核数据是否准备就绪,等内核把数据准备好了,用户线程再发起 read 调用。read 调用的过程(数据从内核空间->用户空间)还是阻塞的。
目前支持 IO 多路复用的系统调用,有 select,epoll 等等。select 系统调用,是目前几乎在所有的操作系统上都有支持
select 调用 :内核提供的系统调用,它支持一次查询多个系统调用的可用状态。几乎所有的操作系统都支持。epoll 调用 :linux 2.6 内核,属于 select 调用的增强版本,优化了 IO 的执行效率。
IO 多路复用模型,通过减少无效的系统调用,减少了对 CPU 资源的消耗。Java 中的 NIO ,有一个非常重要的选择器 ( Selector ) 的概念,也可以被称为 多路复用器。通过它,只需要一个线程便可以管理多个客户端连接。当客户端数据到了之后,才会为其服务。
AIO(Asynchronous I/O):是异步 IO模型。AIO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。
1.7 面向对象设计模式面向对象思想的设计原则
- 单一职责原则开闭原则里氏替换原则依赖注入原则接口分离原则迪米特原则
设计模式按用途分类可分为
创建型模式:关注对象创建的过程;结构型模式:关注类和对象如何组合在一起;行为型模式:关注类与对象如何交互,如何分担职责。 1.8 Fork/Join
Fork/Join是一种基于**“分治”**的算法:通过分解任务,并行执行,最后合并结果得到最终结果。
ForkJoinPool线程池可以把一个大任务分拆成小任务并行执行,任务类必须继承自RecursiveTask或RecursiveAction。
使用Fork/Join模式可以进行并行计算以提高效率。
2. JVM基础 2.1 JVM内存区域
线程私有的:程序计数器、虚拟机栈、本地⽅法栈。
程序计数器是⼀块较⼩的内存空间,可以看作是当前线程所执⾏的字节码的⾏号指示器。 字节码解释器⼯作时通过改变这个计数器的值来选取下⼀条需要执⾏的字节码指令,分⽀、循环、跳转、异常处理、线程恢复等功能都需要依赖这个计数器来完成。另外, 为了线程切换后能恢复到正确的执⾏位置,每条线程都需要有⼀个独⽴的程序计数器,各线程之间计数器互不影响,独⽴存储,我们称这类内存区域为“线程私有”的内存。与程序计数器⼀样,Java 虚拟机栈也是线程私有的,它的⽣命周期和线程相同,描述的是 Java⽅法执⾏的内存模型,每次⽅法调⽤的数据都是通过栈传递的。Java 内存可以粗糙的区分为堆内存(Heap)和栈内存 (Stack),其中栈就是现在说的虚拟机栈,或者说是虚拟机栈中局部变量表部分。 (实际上, Java 虚拟机栈是由⼀个个栈帧组成,⽽每个栈帧中都拥有:局部变量表、操作数栈、动态链接、⽅法出⼝信息。局部变量表主要存放了编译期可知的各种数据类型(boolean、 byte、 char、 short、 int、 float、long、 double)、 对象引⽤(reference 类型,它不同于对象本身,可能是⼀个指向对象起始地址的引⽤指针,也可能是指向⼀个代表对象的句柄或其他与此对象相关的位置。)本地方法栈和虚拟机栈所发挥的作⽤⾮常相似,区别是: 虚拟机栈为虚拟机执⾏ Java ⽅法 (也就是字节码)服务,⽽本地⽅法栈则为虚拟机使⽤到的 Native ⽅法服务。 在 HotSpot 虚拟机中和 Java虚拟机栈合⼆为⼀。本地⽅法被执⾏的时候,在本地⽅法栈也会创建⼀个栈帧,⽤于存放该本地⽅法的局部变量表、操作数栈、动态链接、出⼝信息。
线程共享的:堆、⽅法区、直接内存 (⾮运⾏时数据区的⼀部分)。
Java 堆是所有线程共享的⼀块内存区域,在虚拟机启动时创建。 此内存区域的唯⼀⽬的就是存放对象实例,⼏乎所有的对象实例以及数组都在这⾥分配内存。⽅法区与 Java 堆⼀样,是各个线程共享的内存区域,它⽤于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然 Java 虚拟机规范把⽅法区描述为堆的⼀个逻辑部分,但是它却有⼀个别名叫做 Non-Heap(⾮堆) ,⽬的应该是与 Java 堆区分开来。
元空间:JDK 8 版本之后⽅法区(HotSpot 的永久代)被彻底移除了(JDK1.7 就已经开始了),取⽽代之是元空间,元空间使⽤的是直接内存。
为什么要将永久代 (PermGen) 替换为元空间 (metaSpace) 呢?
- 整个永久代有⼀个 JVM 本身设置固定⼤⼩上限,⽆法进⾏调整,⽽元空间使⽤的是直接内存,受本机可⽤内存的限制,虽然元空间仍旧可能溢出,但是⽐原来出现的⼏率会更⼩。元空间⾥⾯存放的是类的元数据,这样加载多少类的元数据就不由 MaxPermSize 控制了,⽽由系统的实际可⽤空间来控制,这样能加载的类就更多了。在 JDK8,合并 HotSpot 和 JRockit 的代码时, JRockit 从来没有⼀个叫永久代的东⻄, 合并之后就没有必要额外的设置这么⼀个永久代的地⽅了。
常量池:运⾏时常量池是⽅法区的⼀部分。 Class ⽂件中除了有类的版本、字段、⽅法、接⼝等描述信息外,还有常量池表(⽤于存放编译期⽣成的各种字⾯量和符号引⽤)。既然运⾏时常量池是⽅法区的⼀部分,⾃然受到⽅法区内存的限制,当常量池⽆法再申请到内存时会抛出 OutOfMemoryError 错误。
2.2 Java对象创建过程2.2.1 类加载检查:虚拟机遇到⼀条 new 指令时,⾸先将去检查这个指令的参数是否能在常量池中定位到这个类的符号引⽤,并且检查这个符号引⽤代表的类是否已被加载过、解析和初始化过。如果没有,那必须先执⾏相应的类加载过程。2.2.2 分配内存:在类加载检查通过后,接下来虚拟机将为新⽣对象分配内存。对象所需的内存⼤⼩在类加载完成后便可确定,为对象分配空间的任务等同于把⼀块确定⼤⼩的内存从 Java 堆中划分出来。 分配⽅式有 “指针碰撞” 和 “空闲列表” 两种, 选择哪种分配⽅式由 Java 堆是否规整决定,⽽ Java堆是否规整⼜由所采⽤的垃圾收集器是否带有压缩整理功能决定。2.2.3 初始化零值:内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),这⼀步操作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使⽤,程序能访问到这些字段的数据类型所对应的零值。2.2.4 设置对象头:初始化零值完成之后, 虚拟机要对对象进⾏必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息。 这些信息存放在对象头中。 另外,根据虚拟机当前运⾏状态的不同,如是否启⽤偏向锁等,对象头会有不同的设置⽅式。2.2.5 执⾏ init ⽅法:在上⾯⼯作都完成之后,从虚拟机的视⻆来看,⼀个新的对象已经产⽣了,但从 Java 程序的视⻆来看,对象创建才刚开始, ⽅法还没有执⾏,所有的字段都还为零。所以⼀般来说,执⾏ new 指令之后会接着执⾏ ⽅法,把对象按照程序员的意愿进⾏初始化,这样⼀个真正可⽤的对象才算完全产⽣出来。 2.3 对齐填充
由于HotSpot虚拟机的自动内存管理系统要求对象起始地址必须是8字节的整数倍,换句话说就是任何对象的大小都必须是8字节的整数倍。对象头部分已经被精心设计成正好是8字节的倍数(1倍或者2倍),因此,如果对象实例数据部分没有对齐的话,就需要通过对齐填充来补全。
总的一句话来说,“数据项仅仅能存储在地址是数据项大小的整数倍的内存位置上(分别为偶地址、被4整除的地址、被8整除的地址)”比如int类型占用4个字节,地址仅仅能在0,4,8等位置上。
2.4 GC算法
Java 堆是垃圾收集器管理的主要区域,因此也被称作GC 堆(Garbage Collected Heap) 。从垃圾回收的⻆度,由于现在收集器基本都采⽤分代垃圾收集算法,所以 Java 堆还可以细分为:新⽣代和⽼年代:再细致⼀点有: Eden 空间、 From Survivor、 To Survivor 空间等。 进⼀步划分的⽬的是更好地回收内存,或者更快地分配内存。
Full GC:收集整个堆,包括新生代,老年代,永久代(在 JDK 1.8 及以后,永久代被移除,换为 metaspace 元空间)等所有部分的模式。就是对 JVM 进行一次整体的垃圾回收,把各个内存区域的垃圾都回收掉。
对象可达性分析(如何判断对象是否死亡)
引⽤计数法:给对象中添加⼀个引⽤计数器,每当有⼀个地⽅引⽤它,计数器就加1;当引⽤失效,计数器就减1;任何时候计数器为0的对象就是不可能再被使⽤的。可达性分析算法:这个算法的基本思想就是通过⼀系列的称为 “GC Roots” 的对象作为起点,从这些节点开始向下搜索,节点所⾛过的路径称为引⽤链,当⼀个对象到 GC Roots 没有任何引⽤链相连的话,则证明此对象是不可⽤的。
STW:Java中Stop-The-World机制简称STW,是在执行垃圾收集算法时,Java应用程序的其他所有线程都被挂起(除了垃圾收集帮助器之外)。Java中一种全局暂停现象,全局停顿,所有Java代码停止,native代码可以执行,但不能与JVM交互;这些现象多半是由于gc引起。
CMS:
CMS(Concurrent Mark Sweep)收集器是⼀种以获取最短回收停顿时间为⽬标的收集器。它⾮常符合在注重⽤户体验的应⽤上使⽤。CMS(Concurrent Mark Sweep)收集器是 HotSpot 虚拟机第⼀款真正意义上的并发收集器,它第⼀次实现了让垃圾收集线程与⽤户线程(基本上)同时⼯作。从名字中的Mark Sweep这两个词可以看出, CMS 收集器是⼀种 “标记-清除”算法实现的,它的运作过程相⽐于前⾯⼏种垃圾收集器来说更加复杂⼀些。整个过程分为四个步骤:
初始标记: 暂停所有的其他线程,并记录下直接与 root 相连的对象,速度很快 ;并发标记: 同时开启 GC 和⽤户线程,⽤⼀个闭包结构去记录可达对象。但在这个阶段结束,这个闭包结构并不能保证包含当前所有的可达对象。因为⽤户线程可能会不断的更新引⽤域,所以 GC 线程⽆法保证可达性分析的实时性。所以这个算法⾥会跟踪记录这些发⽣引⽤更新的地⽅。重新标记: 重新标记阶段就是为了修正并发标记期间因为⽤户程序继续运⾏⽽导致标记产⽣变动的那⼀部分对象的标记记录,这个阶段的停顿时间⼀般会⽐初始标记阶段的时间稍⻓,远远⽐并发标记阶段时间短。并发清除: 开启⽤户线程,同时 GC 线程开始对未标记的区域做清扫。 优点:并发收集、低停顿缺点:对 CPU 资源敏感;⽆法处理浮动垃圾;它使⽤的回收算法-“标记-清除”算法会导致收集结束时会有⼤量空间碎⽚产⽣。
G1:
G1 (Garbage-First) 是⼀款⾯向服务器的垃圾收集器,主要针对配备多颗处理器及⼤容量内存的机器,以极⾼概率满⾜ GC 停顿时间要求的同时,还具备⾼吞吐量性能特征,被视为 JDK1.7 中 HotSpot 虚拟机的⼀个重要进化特征。它具备以下特点:
并⾏与并发: G1 能充分利⽤ CPU、多核环境下的硬件优势,使⽤多个 CPU(CPU 或者CPU 核⼼)来缩短 Stop-The-World 停顿时间。部分其他收集器原本需要停顿 Java 线程执⾏的 GC 动作, G1 收集器仍然可以通过并发的⽅式让 java 程序继续执⾏。分代收集:虽然 G1 可以不需要其他收集器配合就能独⽴管理整个 GC 堆,但是还是保留了分代的概念。空间整合:与 CMS 的“标记–清理”算法不同, G1 从整体来看是基于“标记整理”算法实现的收集器;从局部上来看是基于“复制”算法实现的。可预测的停顿:这是 G1 相对于 CMS 的另⼀个⼤优势,降低停顿时间是 G1 和 CMS 共同的关注点,但 G1 除了追求低停顿外,还能建⽴可预测的停顿时间模型,能让使⽤者明确指定在⼀个⻓度为 M 毫秒的时间⽚段内。 G1 收集器的运作⼤致分为以下⼏个步骤:初始标记、并发标记、最终标记、筛选回收。G1 收集器在后台维护了⼀个优先列表,每次根据允许的收集时间,优先选择回收价值最⼤的Region(这也就是它的名字 Garbage-First 的由来)。这种使⽤ Region 划分内存空间以及有优先级的区域回收⽅式,保证了 G1 收集器在有限时间内可以尽可能⾼的收集效率(把内存化整为零)。
ZGC:与 CMS 中的 ParNew 和 G1 类似, ZGC 也采⽤标记-复制算法,不过 ZGC 对该算法做了重⼤改进。在 ZGC 中出现 Stop The World 的情况会更少!
2.5 性能调优 / 问题排查 3. 缓存相关 3.1 Redis数据结构String
- 介绍 : string 数据结构是简单的 key-value 类型。虽然 Redis 是⽤ C 语⾔写的,但是 Redis并没有使⽤ C 的字符串表示,⽽是⾃⼰构建了⼀种 简单动态字符串(simple dynamic string, SDS)。相⽐于 C 的原⽣字符串, Redis 的 SDS 不光可以保存⽂本数据还可以保存⼆进制数据,并且获取字符串⻓度复杂度为 O(1)(C 字符串为 O(N)) ,除此之外,Redis 的SDS API 是安全的,不会造成缓冲区溢出。常⽤命令: set,get,strlen,exists,dect,incr,setex 等等。应⽤场景 :⼀般常⽤在需要计数的场景,⽐如⽤户的访问次数、热点⽂章的点赞转发数量等等。
- 介绍 : list 即是 链表。链表是⼀种⾮常常⻅的数据结构,特点是易于数据元素的插⼊和删除并且且可以灵活调整链表⻓度,但是链表的随机访问困难。许多⾼级编程语⾔都内置了链表的实现⽐如 Java 中的 linkedList,但是 C 语⾔并没有实现链表,所以 Redis 实现了⾃⼰的链表数据结构。 Redis 的 list 的实现为⼀个 双向链表,即可以⽀持反向查找和遍历,更⽅便操作,不过带来了部分额外的内存开销。常⽤命令: rpush,lpop,lpush,rpop,lrange llen 等。应⽤场景: 发布与订阅或者说消息队列、慢查询。
- 介绍 : hash 类似于 JDK1.8 前的 HashMap,内部实现也差不多(数组 + 链表)。不过,Redis 的 hash 做了更多优化。另外, hash 是⼀个 string 类型的 field 和 value 的映射表,特别适合⽤于存储对象,后续操作的时候,你可以直接仅仅修改这个对象中的某个字段的值。 ⽐如我们可以 hash 数据结构来存储⽤户信息,商品信息等等。常⽤命令: hset,hmset,hexists,hget,hgetall,hkeys,hvals 等。应⽤场景: 系统中对象数据的存储。
- 介绍 : set 类似于 Java 中的 HashSet 。 Redis 中的 set 类型是⼀种⽆序集合,集合中的元素没有先后顺序。当你需要存储⼀个列表数据,⼜不希望出现重复数据时, set 是⼀个很好的选择,并且 set 提供了判断某个成员是否在⼀个 set 集合内的重要接⼝,这个也是 list 所不能提供的。可以基于 set 轻易实现交集、并集、差集的操作。⽐如:你可以将⼀个⽤户所有的关注⼈存在⼀个集合中,将其所有粉丝存在⼀个集合。 Redis 可以⾮常⽅便的实现如共同关注、共同粉丝、共同喜好等功能。这个过程也就是求交集的过程。常⽤命令: sadd,spop,smembers,sismember,scard,sinterstore,sunion 等。应⽤场景: 需要存放的数据不能重复以及需要获取多个数据源交集和并集等场景。
- 介绍: 和 set 相⽐, sorted set 增加了⼀个权重参数 score,使得集合中的元素能够按 score进⾏有序排列,还可以通过 score 的范围来获取元素的列表。有点像是 Java 中 HashMap和 TreeSet 的结合体。常⽤命令: zadd,zcard,zscore,zrange,zrevrange,zrem 等。应⽤场景: 需要对数据根据某个权重进⾏排序的场景。⽐如在直播系统中,实时排⾏信息包含直播间在线⽤户列表,各种礼物排⾏榜,弹幕消息(可以理解为按消息维度的消息排⾏榜)等信息。
缓存穿透:缓存穿透说简单点就是⼤量请求的 key 根本不存在于缓存中,导致请求直接到了数据库上,根本没有经过缓存这⼀层。举个例⼦:某个⿊客故意制造我们缓存中不存在的 key 发起⼤量请求,导致⼤量请求落到数据库。
解决办法:
最基本的就是⾸先做好参数校验,⼀些不合法的参数请求直接抛出异常信息返回给客户端。⽐如查询的数据库 id 不能⼩于 0、传⼊的邮箱格式不对的时候直接返回错误消息给客户端等等。布隆过滤器:布隆过滤器是⼀个⾮常神奇的数据结构,通过它我们可以⾮常⽅便地判断⼀个给定数据是否存在于海量数据中。我们需要的就是判断 key 是否合法,有没有感觉布隆过滤器就是我们想要找的那个“⼈”。具体是这样做的:把所有可能存在的请求的值都存放在布隆过滤器中,当⽤户请求过来,先判断⽤户发来的请求的值是否存在于布隆过滤器中。不存在的话,直接返回请求参数错误信息给客户端,存在的话才会去缓存和数据库中寻找。 缓存雪崩:缓存在同⼀时间⼤⾯积失效,后⾯的请求都直接落到了数据库上,造成数据库短时间内承受⼤量请求。 这就好⽐雪崩⼀样,数据库的压⼒可想⽽知,可能直接就被这么多请求弄宕机了。还有⼀种缓存雪崩的场景是: 有⼀些被⼤量访问数据(热点缓存)在某⼀时刻⼤⾯积失效,导致对应的请求直接落到了数据库上。
解决办法:
针对 Redis 服务不可⽤的情况:1. 采⽤ Redis 集群,避免单机出现问题整个缓存服务都没办法使⽤。2. 限流,避免同时处理⼤量的请求。针对热点缓存失效的情况:1. 设置不同的失效时间⽐如随机设置缓存的失效时间。2. 缓存永不失效。 缓存预热:
缓存预热就是系统上线后,提前将相关的缓存数据直接加载到缓存系统。避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据。具体实现:日常例行统计数据访问记录,统计访问频度较高的热点数据;利用LRU数据删除策略, 构建数据留存队列;将统计结果中的数据分类, 根据级别 redis 优先加载级别较高的热点数据;利用分布式多服务器同时进行数据读取,提速数据加载过程。 缓存更新(缓存淘汰):
缓存淘汰的策略有两种:
定时去清理过期的缓存。当有用户请求过来时,再判断这个请求所用到的缓存是否过期,过期的话就去底层系统得到新数据并更新缓存。 3.3 高可用保证
Redis使用主从模式和哨兵集群模式来尽量保证缓存服务的高可用。面试连环炮系列(一):如何保证Redis高可用和高并发 :哨兵机制Redis集群如何保证高可用? 3.4 集群分片机制(路由/扩容/收缩)
https://zhuanlan.zhihu.com/p/66934705https://blog.csdn.net/Seky_fei/article/details/107611850 3.5 具体使用场景
缓存:缓存现在几乎是所有中大型网站都在用的必杀技,合理的利用缓存不仅能够提升网站访问速度,还能大大降低数据库的压力。Redis提供了键过期功能,也提供了灵活的键淘汰策略,所以,现在Redis用在缓存的场合非常多。排行榜:很多网站都有排行榜应用的,如京东的月度销量榜单、商品按时间的上新排行榜等。Redis提供的有序集合数据类构能实现各种复杂的排行榜应用。计数器:什么是计数器,如电商网站商品的浏览量、视频网站视频的播放数等。为了保证数据实时效,每次浏览都得给+1,并发量高时如果每次都请求数据库操作无疑是种挑战和压力。Redis提供的incr命令来实现计数器功能,内存操作,性能非常好,非常适用于这些计数场景。分布式会话:集群模式下,在应用不多的情况下一般使用容器自带的session复制功能就能满足,当应用增多相对复杂的系统中,一般都会搭建以Redis等内存数据库为中心的session服务,session不再由容器管理,而是由session服务及内存数据库管理。分布式锁:在很多互联网公司中都使用了分布式技术,分布式技术带来的技术挑战是对同一个资源的并发访问,如全局ID、减库存、秒杀等场景,并发量不大的场景可以使用数据库的悲观锁、乐观锁来实现,但在并发量高的场合中,利用数据库锁来控制资源的并发访问是不太理想的,大大影响了数据库的性能。可以利用Redis的setnx功能来编写分布式的锁,如果设置返回1说明获取锁成功,否则获取锁失败,实际应用中要考虑的细节要更多。社交网络:点赞、踩、关注/被关注、共同好友等是社交网站的基本功能,社交网站的访问量通常来说比较大,而且传统的关系数据库类型不适合存储这种类型的数据,Redis提供的哈希、集合等数据结构能很方便的的实现这些功能。最新列表:Redis列表结构,LPUSH可以在列表头部插入一个内容ID作为关键字,LTRIM可用来限制列表的数量,这样列表永远为N个ID,无需查询最新的列表,直接根据ID去到对应的内容页即可。消息系统:消息队列是大型网站必用中间件,如ActiveMQ、RabbitMQ、Kafka等流行的消息队列中间件,主要用于业务解耦、流量削峰及异步处理实时性低的业务。Redis提供了发布/订阅及阻塞队列功能,能实现一个简单的消息队列系统。另外,这个不能和专业的消息中间件相比。 4. 消息相关 4.1 消息的推拉模式 4.2 整体技术架构 4.3 消息的可靠性保证 4.4 部署模式(主从) 4.5 具体使用场景 5. 数据库相关 5.1 索引数据结构
MySQL索引使⽤的数据结构主要有BTree索引 和 哈希索引 。对于哈希索引来说,底层的数据结构就是哈希表,因此在绝⼤多数需求为单条记录查询的时候,可以选择哈希索引,查询性能最快;其余⼤部分场景,建议选择BTree索引。 5.2 聚簇索引和非聚簇索引
聚集索引:即索引结构和数据一起存放的索引。主键索引属于聚集索引。
非聚集索引:即索引结构和数据分开存放的索引。
MySql的BTree索引使⽤的是B树中的B+Tree,但对于两种存储引擎的实现⽅式是不同的。
MyISAM: B+Tree叶节点的data域存放的是数据记录的地址。在索引检索的时候,⾸先按照B+Tree搜索算法搜索索引,如果指定的Key存在,则取出其 data 域的值,然后以 data 域的值为地址读取相应的数据记录。这被称为**“⾮聚簇索引”**。InnoDB: 其数据⽂件本身就是索引⽂件。相⽐MyISAM,索引⽂件和数据⽂件是分离的,其表数据⽂件本身就是按B+Tree组织的⼀个索引结构,树的叶节点data域保存了完整的数据记录。这个索引的key是数据表的主键,因此InnoDB表数据⽂件本身就是主索引。这被称为**“聚簇索引(或聚集索引) ”**。⽽其余的索引都作为辅助索引,辅助索引的data域存储相应记录主键的值⽽不是地址,这也是和MyISAM不同的地⽅。 在根据主索引搜索时,直接找到key所在的节点即可取出数据;在根据辅助索引查找时,则需要先取出主键的值,再⾛⼀遍主索引。 因此,在设计表的时候,不建议使⽤过⻓的字段作为主键,也不建议使⽤⾮单调的字段作为主键,这样会造成主索引频繁分裂。 5.3 单机事务
事务是逻辑上的⼀组操作,要么都执⾏,要么都不执⾏。事务的四大特征
原⼦性(Atomicity): 事务是最⼩的执⾏单位,不允许分割。事务的原⼦性确保动作要么全部完成,要么完全不起作⽤;⼀致性(Consistency): 执⾏事务前后,数据保持⼀致,多个事务对同⼀个数据读取的结果相同;隔离性(Isolation): 并发访问数据库时,⼀个⽤户的事务不被其他事务所⼲扰,各并发事务之间数据库是独⽴的;持久性(Durability): ⼀个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发⽣故障也不应该对其有任何影响。 并发事务可能带来的问题
脏读(Dirty read) : 当⼀个事务正在访问数据并且对数据进⾏了修改,⽽这种修改还没有提交到数据库中,这时另外⼀个事务也访问了这个数据,然后使⽤了这个数据。因为这个数据是还没有提交的数据,那么另外⼀个事务读到的这个数据是“脏数据”,依据“脏数据”所做的操作可能是不正确的。丢失修改(Lost to modify) : 指在⼀个事务读取⼀个数据时,另外⼀个事务也访问了该数据,那么在第⼀个事务中修改了这个数据后,第⼆个事务也修改了这个数据。这样第⼀个事务内的修改结果就被丢失,因此称为丢失修改。 例如:事务1读取某表中的数据A=20,事务2也读取A=20,事务1修改A=A-1,事务2也修改A=A-1,最终结果A=19,事务1的修改丢失。不可重复读(Unrepeatableread) : 指在⼀个事务内多次读同⼀数据。在这个事务还没有结束时,另⼀个事务也访问该数据。那么,在第⼀个事务中的两次读数据之间,由于第⼆个事务的修改导致第⼀个事务两次读取的数据可能不太⼀样。这就发⽣了在⼀个事务内两次读到的数据是不⼀样的情况,因此称为不可重复读。幻读(Phantom read) : 幻读与不可重复读类似。它发⽣在⼀个事务(T1)读取了⼏⾏数据,接着另⼀个并发事务(T2)插⼊了⼀些数据时。在随后的查询中,第⼀个事务(T1)就会发现多了⼀些原本不存在的记录,就好像发⽣了幻觉⼀样,所以称为幻读。不可重复读和幻读区别:不可重复读的重点是修改⽐如多次读取⼀条记录发现其中某些列的值被修改,幻读的重点在于新增或者删除⽐如多次读取⼀条记录发现记录增多或减少了。 5.4 隔离级别
SQL 标准定义了四个隔离级别
READ-UNCOMMITTED(读取未提交): 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。READ-COMMITTED(读取已提交): 允许读取并发事务已经提交的数据, 可以阻⽌脏读,但是幻读或不可重复读仍有可能发⽣。REPEATABLE-READ(可重复读): 对同⼀字段的多次读取结果都是⼀致的,除⾮数据是被本身事务⾃⼰所修改, 可以阻⽌脏读和不可重复读,但幻读仍有可能发⽣。SERIALIZABLE(可串⾏化): 最⾼的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执⾏,这样事务之间就完全不可能产⽣⼲扰,也就是说, 该级别可以防⽌脏读、不可重复读以及幻读。 MySQL InnoDB 存储引擎的默认⽀持的隔离级别是 REPEATABLE-READ(可重读) 。 5.5 高可用方案
https://zhuanlan.zhihu.com/p/25960208https://www.cnblogs.com/robbinluobo/p/8294782.htmlhttps://blog.csdn.net/u013399093/article/details/70597712 5.6 水平分库分表(路由策略/水平扩容)
⽔平拆分是指数据表⾏的拆分,表的⾏数超过200万⾏时,就会变慢,这时可以把⼀张的表的数据拆成多张表来存放。举个例⼦:我们可以将⽤户信息表拆分成多个⽤户信息表,这样就可以避免单⼀表数据量过⼤对性能造成影响。需要注意的⼀点是:分表仅仅是解决了单⼀表数据过⼤的问题,但由于表的数据还是在同⼀台机器上,其实对于提升MySQL并发能⼒没有什么意义,所以⽔平拆分最好分库 。https://cloud.tencent.com/developer/article/1893163 5.7 乐观锁/悲观锁
https://www.jianshu.com/p/f5ff017db62ahttps://segmentfault.com/a/1190000022839728 5.8 搜索引擎工作原理
索引 6. 分布式技术
CAP理论一致性算法(raft/paxos/gossip)
Raft算法Gossip算法 分布式事务(两阶段提交/柔性事务/强一致/最终一致)服务注册中心原理(服务续约/心跳检测/leader选举)服务网关负载均衡RPC调用/序列化/协议限流熔断降级全局唯一ID
在很多中小项目中,我们往往直接使用数据库自增特性来生成主键ID,这样确实比较简单。而在分库分表的环境中,数据分布在不同的分片上,不能再借助数据库自增长特性直接生成,否则会造成不同分片上的数据表主键会重复。简单介绍下使用和了解过的几种ID生成算法。
Twitter的Snowflake(又名“雪花算法”):该算法生成的是64位唯一Id(由41位的timestamp+ 10位自定义的机器码+ 13位累加计数器组成)。UUID/GUID(一般应用程序和数据库均支持)MongoDB ObjectID(类似UUID的方式)Ticket Server(数据库生存方式,Flickr采用的就是这种方式) 分布式锁分布式架构演进分布式任务调度APM:APM全称Application Performance Management ,目前市面的系统基本都是参考Google的Dapper(大规模分布式系统的跟踪系统)来做的, 核心思想是在服务各节点彼此调用的时候,记录并传递一个应用级别的标记,这个标记可以用来关联各个服务节点之间的关系,最终形成一个分布式跟踪的服务调用链。
微服务架构下,服务按照不同的维度进行拆分,一次请求请求往往需要涉及到多个服务。尤其是大规模互联网应用,服务由不同的团队开发,用不同编程语言来实现、并部署在成千上万态几千台服务器上面。我们需要可以帮助我们理解系统行为和进行分析性能问题的工具,以便发生故障的时候,能够快速定位和解决问题。 7. 应用框架相关 7.1 Spring IOC&AOP原理(织入方式)
IOC
IoC(Inverse of Control,控制反转)是⼀种设计思想,就是将原本在程序中⼿动创建对象的控制权交由Spring框架来管理。 IoC 在其他语⾔中也有应⽤,并⾮ Spring 特有。 IoC 容器是Spring ⽤来实现 IoC 的载体, IoC 容器实际上就是个Map(key, value) ,Map 中存放的是各种对象。将对象之间的相互依赖关系交给 IoC 容器来管理,并由 IoC 容器完成对象的注⼊。这样可以很⼤程度上简化应⽤的开发,把应⽤从复杂的依赖关系中解放出来。 IoC 容器就像是⼀个⼯⼚,当我们需要创建⼀个对象的时候,只需要配置好配置⽂件/注解即可,完全不⽤考虑对象是如何被创建出来的。 在实际项⽬中⼀个 Service 类可能有⼏百甚⾄上千个类作为它的底层,假如我们需要实例化这个Service,你可能要每次都要搞清这个 Service 所有底层类的构造函数,这可能会把⼈逼疯。如果利⽤ IoC 的话,你只需要配置好,然后在需要的地⽅引⽤就⾏了,这⼤⼤增加了项⽬的可维护性且降低了开发难度。Spring 时代我们⼀般通过 XML ⽂件来配置 Bean,后来开发⼈员觉得 XML ⽂件来配置不太好,于是 SpringBoot 注解配置就慢慢开始流⾏起来。 AOP
AOP(Aspect-Oriented Programming:⾯向切⾯编程)能够将那些与业务⽆关, 却为业务模块所共同调⽤的逻辑或责任(例如事务处理、⽇志管理、权限控制等)封装起来,便于减少系统的重复代码, 降低模块间的耦合度,并有利于未来的可拓展性和可维护性。Spring AOP就是基于动态代理的。如果要代理的对象实现了某个接⼝,那么Spring AOP会使⽤JDK Proxy,去创建代理对象。⽽对于没有实现接⼝的对象,就⽆法使⽤ JDK Proxy 去进⾏代理了。这时候Spring AOP会使⽤ Cglib ⽣成⼀个被代理对象的⼦类来作为代理,如下图所示:
使⽤ AOP 之后我们可以把⼀些通⽤功能抽象出来,在需要⽤到的地⽅直接使⽤即可,这样⼤⼤简化了代码量。我们需要增加新功能时也⽅便,这样也提⾼了系统扩展性。⽇志功能、事务管理等等场景都⽤到了 AOP 。 7.2 Spring MVC的工作流程
谈到这个问题,我们不得不提提之前 Model1 和 Model2 这两个没有 Spring MVC 的时代。
Model1 时代 : 很多学 Java 后端⽐较晚的朋友可能并没有接触过 Model1 模式下的JavaWeb 应⽤开发。在 Model1 模式下,整个 Web 应⽤⼏乎全部⽤ JSP ⻚⾯组成,只⽤少量的 JavaBean 来处理数据库连接、访问等操作。这个模式下 JSP 即是控制层⼜是表现层。显⽽易⻅,这种模式存在很多问题。⽐如①将控制逻辑和表现逻辑混杂在⼀起,导致代码重⽤率极低;②前端和后端相互依赖,难以进⾏测试并且开发效率极低;Model2 时代 :学过 Servlet 并做过相关 Demo 的朋友应该了解“Java Bean(Model)+JSP(View,) +Servlet(Controller) ”这种开发模式,这就是早期的 JavaWeb MVC 开发模式。 Model:系统涉及的数据,也就是 dao 和 bean。 View:展示模型中的数据,只是⽤来展示。 Controller:处理⽤户请求都发送给 、返回数据给 JSP 并展示给⽤户。 Model2 模式下还存在很多问题, Model2的抽象和封装程度还远远不够,使⽤Model2进⾏开发时不可避免地会重复造轮⼦,这就⼤⼤降低了程序的可维护性和复⽤性。于是很多JavaWeb开发相关的 MVC 框架应运⽽⽣⽐如Struts2,但是 Struts2 ⽐较笨重。随着 Spring 轻量级开发框架的流⾏, Spring ⽣态圈出现了 Spring MVC 框架, Spring MVC 是当前最优秀的 MVC 框架。相⽐于Struts2, Spring MVC 使⽤更加简单和⽅便,开发效率更⾼,并且 Spring MVC 运⾏速度更快。MVC 是⼀种设计模式,Spring MVC 是⼀款很优秀的 MVC 框架。 Spring MVC 可以帮助我们进⾏更简洁的Web层的开发,并且它天⽣与 Spring 框架集成。 Spring MVC 下我们⼀般把后端项⽬分为 Service层(处理业务)、 Dao层(数据库操作)、 Entity层(实体类)、 Controller层(控制层,返回数据给前台⻚⾯)。
流程说明(重要):
- 客户端(浏览器)发送请求,直接请求到 DispatcherServlet 。DispatcherServlet 根据请求信息调⽤ HandlerMapping ,解析请求对应的 Handler 。解析到对应的 Handler (也就是我们平常说的 Controller 控制器)后,开始由HandlerAdapter 适配器处理。HandlerAdapter 会根据 Handler 来调⽤真正的处理器开处理请求,并处理相应的业务逻辑。处理器处理完业务后,会返回⼀个 ModelAndView 对象, Model 是返回的数据对象, View 是个逻辑上的 View 。ViewResolver 会根据逻辑 View 查找实际的 View 。DispaterServlet 把返回的 Model 传给 View (视图渲染)。把 View 返回给请求者(浏览器)。
Spring 管理事务的⽅式有⼏种?
编程式事务,在代码中硬编码。 (不推荐使⽤);
声明式事务,在配置⽂件中配置(推荐使⽤)。声明式事务⼜分为两种:基于XML的声明式事务、基于注解的声明式事务
Spring 事务中的隔离级别有哪⼏种?
TransactionDefinition 接⼝中定义了五个表示隔离级别的常量:
TransactionDefinition.ISOLATION_DEFAULT:使⽤后端数据库默认的隔离级别, Mysql默认采⽤的 REPEATABLE_READ隔离级别,Oracle 默认采⽤的 READ_COMMITTED隔离级别。TransactionDefinition.ISOLATION_READ_UNCOMMITTED:最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。TransactionDefinition.ISOLATION_READ_COMMITTED:允许读取并发事务已经提交的数据, 可以阻⽌脏读,但是幻读或不可重复读仍有可能发⽣。TransactionDefinition.ISOLATION_REPEATABLE_READ:对同⼀字段的多次读取结果都是⼀致的,除⾮数据是被本身事务⾃⼰所修改, 可以阻⽌脏读和不可重复读,但幻读仍有可能发⽣。TransactionDefinition.ISOLATION_SERIALIZABLE:最⾼的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执⾏。这样事务之间就完全不可能产⽣⼲扰,也就是说, 该级别可以防⽌脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会⽤到该级别。
Spring 事务中哪⼏种事务传播⾏为?
事务传播行为:一般发生在事务嵌套的场景中,比如一个有事务的方法里面调用了另外一个有事务的方法。这个时候就会产生事务边界控制的问题,即两个方法是各自作为独立的事务提交还是内层的事务合并到外层的事务一起提交。Spring规定了七大传播机制来解决边界控制的问题。
⽀持当前事务的情况:
TransactionDefinition.PROPAGATION_REQUIRED: 如果当前存在事务,则加⼊该事务;如果当前没有事务,则创建⼀个新的事务。TransactionDefinition.PROPAGATION_SUPPORTS: 如果当前存在事务,则加⼊该事务;如果当前没有事务,则以⾮事务的⽅式继续运⾏。TransactionDefinition.PROPAGATION_MANDATORY: 如果当前存在事务,则加⼊该事务;如果当前没有事务,则抛出异常。(mandatory:强制性) 不⽀持当前事务的情况:
TransactionDefinition.PROPAGATION_REQUIRES_NEW: 创建⼀个新的事务,如果当前存在事务,则把当前事务挂起。TransactionDefinition.PROPAGATION_NOT_SUPPORTED: 以⾮事务⽅式运⾏,如果当前存在事务,则把当前事务挂起。TransactionDefinition.PROPAGATION_NEVER: 以⾮事务⽅式运⾏,如果当前存在事务,则抛出异常。 其他情况:
TransactionDefinition.PROPAGATION_NESTED: 如果当前存在事务,则创建⼀个事务作为当前事务的嵌套事务来运⾏;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。
@Transactional(rollbackFor = Exception.class)注解了解吗?
我们知道: Exception分为运⾏时异常RuntimeException和⾮运⾏时异常。事务管理对于企业应⽤来说是⾄关重要的,即使出现异常情况,它也可以保证数据的⼀致性。当 @Transactional 注解作⽤于类上时,该类的所有 public ⽅法将都具有该类型的事务属性,同时,我们也可以在⽅法级别使⽤该标注来覆盖类级别的定义。如果类或者⽅法加了这个注解,那么这个类⾥⾯的⽅法抛出异常,就会回滚,数据库⾥⾯的数据也会回滚。在 @Transactional 注解中如果不配置 rollbackFor 属性,那么事物只会在遇到 RuntimeException 的时候才会回滚,加上 rollbackFor=Exception.class ,可以让事物在遇到⾮运⾏时异常时也回滚。 7.5 MyBatis工作原理 8.Web开发 8.1 WEB容器(Tomcat/Netty) 8.2 HTTP协议(1.1/2.0)
HTTP/1.x 是基于文本的,只能整体去传;HTTP/2 是基于二进制流的,可以分解为独立的帧,交错发送;HTTP/1.x keep-alive 必须按照请求发送的顺序返回响应;HTTP/2 多路复用不按序响应;HTTP/1.x keep-alive 为了解决队头阻塞,将同一个页面的资源分散到不同域名下,开启了多个 TCP 连接;HTTP/2 同域名下所有通信都在单个连接上完成;HTTP/1.x keep-alive 单个 TCP 连接在同一时刻只能处理一个请求(两个请求的生命周期不能重叠);HTTP/2 单个 TCP 同一时刻可以发送多个请求和响应。 8.3 WebSocket协议 8.4 TCP/UDP 8.5 Restful规范 8.6 登录认证(OAuth2/JWT) 8.7 权限设计 8.8 RBAC模型 9. 开发工具
Git项目构建(Maven/Gradle)
maven 是一个项目管理工具,主要作用是在项目开发阶段对Java项目进行依赖管理和项目构建。
依赖管理:就是对jar包的管理。通过导入maven坐标,就相当于将仓库中的jar包导入当前项目中。项目构建:通过maven一个命令就可以完成项目从清理、编译、测试、报告、打包、部署整个过程。 Gradle优点:简化Maven繁琐xml配置、强大支持多工程构建、Groove语言性能。 CI/CD:CI/CD 是一种通过在应用开发阶段引入自动化来频繁向客户交付应用的方法。CI/CD 的核心概念是持续集成、持续交付和持续部署。作为一个面向开发和运营团队的解决方案,CI/CD 主要针对在集成新代码时所引发的问题。具体而言,CI/CD 可让持续自动化和持续监控贯穿于应用的整个生命周期(从集成和测试阶段,到交付和部署)。这些关联的事务通常被统称为“CI/CD 管道”,由开发和运维团队以敏捷方式协同支持。
CI/CD 中的“CI”始终指持续集成,它属于开发人员的自动化流程。CI/CD 中的“CD”指的是持续交付和/或持续部署。
持续交付通常是指开发人员对应用的更改会自动进行错误测试并上传到存储库(如 GitHub 或容器注册表),然后由运维团队将其部署到实时生产环境中。这旨在解决开发和运维团队之间可见性及沟通较差的问题。因此,持续交付的目的就是确保尽可能减少部署新代码时所需的工作量。持续部署(另一种“CD”)指的是自动将开发人员的更改从存储库发布到生产环境,以供客户使用。它主要为了解决因手动流程降低应用交付速度,从而使运维团队超负荷的问题。持续部署以持续交付的优势为根基,实现了管道后续阶段的自动化。 API文档
postmanswagger 10. 其他
Gitflow:通过利用 Git 创建和管理分支的能力,为每个分支设定具有特定的含义名称,并将软件生命周期中的各类活动归并到不同的分支上,实现了软件开发过程不同阶段的相互隔离。这种软件开发的活动模型被 Vincent 称为 “Git Flow”。开发流程:软件开发流程即软件设计思路和方法的一般过程,包括对软件先进行需求分析,设计软件的功能和实现的算法和方法、软件的总体结构设计和模块设计、编码和调试、程序联调和测试以及编写、提交程序等一系列操作以满足客户的需求并且解决客户的问题。如果有更高需求,还需要对软件进行维护、升级处理,报废处理。加解密(对称/非对称)日志容器技术自动化运维软件架构阿里巴巴Java开发规范考试(阿里云大学上面有)



