面试整理
Java基础
介绍下序列化和反序列化
什么?
序列号就是将java对象转化为字节序列的过程,反之
作用?
将对象持久化磁盘、数据库中,或者与浏览器交互时需要序列号和反序列化
为什么要实现接口?
JVM在类加载的时候如果发现实现了Serializable接口,然后在对象实例化时会实现序列化和反序列化。如果没有实现接口,在对象序列化时会抛NoSerializableException异常
为什么要实现常量?
如果不指定serialVersionUID,会根据属性自动生成,反序列化时同样根据属性生成新的serialVersionUID和老的进行比较,相同则序列化成功,但如果序列化和反序列化时属性变动了,就会导致序列化失败,所以一般会指定。
arraylist原理
ArrayList 是 Java 中的一个动态数组实现,用于存储和操作元素的集合。它的原理主要基于数组和动态扩容。
具体来说,当创建一个 ArrayList 对象时,它内部会初始化一个初始容量的数组。默认情况下,初始容量为 10,但可以根据需要进行自定义设置。当 ArrayList 中的元素个数超过当前容量时,会触发动态扩容机制。
动态扩容的原理如下:
1.当添加新元素时,ArrayList 会检查当前元素个数是否超过了数组容量。
2.如果当前元素个数已经达到了容量限制,ArrayList 会创建一个新的更大容量的数组。
3.然后,ArrayList 将已有的元素逐个复制到新的数组中,并将新的元素添加到新数组的指定位置。
4.扩容后,ArrayList 的容量变大,并且可以容纳更多的元素。
这个过程会在每次扩容时重复进行,以适应不断增长的元素数量。
linkedList实现原理
LinkedList 是 Java 中的一个双向链表实现,用于存储和操作元素的集合。它的原理主要基于节点(Node)和链表结构。
在 LinkedList 中,每个元素都被封装在一个节点对象中。每个节点包含了对前一个节点和后一个节点的引用(前驱和后继)。通过这些引用,可以在链表中进行元素的插入、删除和访问操作。
具体来说,LinkedList 的原理如下:
1.节点(Node):LinkedList 中的每个元素都被封装在一个节点对象中。节点对象包含两个引用,一个指向前一个节点,一个指向后一个节点。
2.链表结构:节点通过前驱和后继的引用形成链表结构,即每个节点连接着前一个节点和后一个节点。这种链表结构使得元素在内存中并不是连续存储的,可以动态地插入和删除元素。
3.头节点和尾节点:LinkedList 还包含一个头节点和一个尾节点。头节点是链表的第一个节点,尾节点是链表的最后一个节点。它们分别用于标识链表的起始位置和结束位置。
4.插入操作:当需要向链表中插入元素时,LinkedList 会创建一个新的节点对象,并将其插入到相应的位置。通过更新前驱和后继节点的引用,节点之间形成新的连接关系。
5.删除操作:当需要删除链表中的某个元素时,LinkedList 会找到该元素对应的节点,并更新前驱和后继节点的引用,跳过需要删除的节点。从而将链表中的元素移除。
6.遍历操作:通过头节点或尾节点,可以从链表的起点或终点开始遍历链表。通过节点之间的引用,可以依次访问链表中的每个元素。
总结一下,LinkedList 的原理是基于双向链表结构来实现元素的存储和操作。具有动态插入和删除元素的能力,但在元素访问时需要遍历整个链表。LinkedList 在某些场景下可以提供更好的性能,例如频繁的插入和删除操作,但在访问元素时较 ArrayList 略慢。
arraylist、linkedarraylist区别
集合遍历时可以修改嘛?
遍历集合时进行增加或删除操作可能会导致ConcurrentModificationException异常。如果确实需要这样操作,应使用迭代器的相关方法来进行修改hashmap原理?
HashMap 内部维护了一个数组,称为哈希表,HashMap 使用哈希函数将键转换为哈希值。这个哈希值决定了键在哈希表中的存储位置,其中每个元素称为桶(bucket)。每个桶可以存储一个或多个键值对。
链表和红黑树:在 JDK 8 及之后的版本中,当一个桶中的链表长度超过一定阈值(默认为 8),链表会自动转换为红黑树。这样可以提高在大量键值对存在的情况下的查找效率。
存取操作:
-
存储操作:当插入一个键值对时,通过哈希函数计算出键的哈希值,并确定存放在哪个桶中。如果该桶为空,则直接将键值对放入桶中。如果桶中已存在键值对,则需要根据键的哈希值以及 equals 方法判断键是否已存在。如果键已经存在,则更新对应的值;如果键不存在,则将键值对添加到链表或红黑树尾部。
-
获取操作:当获取一个键的值时,通过哈希函数计算出键的哈希值,并确定查找的桶。然后在桶中搜索键,并返回对应的值。
hashmap put操作过程
1.首先,计算要插入的键的哈希值,并找到它在哈希表中的桶位置。如果该桶为空,则直接将键值对放入桶中,完成插入操作。
2.如果该桶不为空,则需要遍历该桶的链表以查找是否已经存在相同的键。如果存在相同的键,则更新其对应的值,完成插入操作。
3.如果不存在相同的键,则将键值对添加到链表的尾部(或红黑树中,如果链表长度超过阈值)。如果此时链表长度超过了阈值,则将链表转换为红黑树。
4.如果添加操作导致哈希表中键值对数量超过了阈值(即负载因子乘以哈希表大小),则进行扩容操作。
hashmap 扩容机制
当前存储的键值对数量超过负载因子阈值时触发的扩容的:创建一个新的数组,其长度为原数组的两倍。
遍历原数组中的每个非空桶,将其中的键值对重新计算哈希值,并确定在新数组中的位置。
如果新数组中该位置的桶为空,则直接将键值对放入该桶。
如果新数组中该位置的桶不为空,则将键值对放入链表或红黑树的尾部。
如果原数组中的桶发生了红黑树转换,在新数组中同样要构建红黑树的结构。
重复上述步骤,直到遍历完原数组中的所有非空桶。
将新数组设置为 HashMap 的哈希表,更新扩容阈值。
扩容完成后,原数组中的元素被替换为 null,释放对这些对象的引用,以便垃圾回收。
扩容完成。
hashmap和hashtable区别?和hashset区别?
线程安全性:Hashtable 是线程安全的,而 HashMap 不是。Hashtable 的操作方法都使用 synchronized 关键字进行同步,可以保证在多线程环境下的线程安全性。而 HashMap 在多线程环境中需要外部同步措施来保证线程安全。
允许键值对为 null:HashMap 允许键和值都为 null,而 Hashtable 不允许,如果尝试存储 null 键或 null 值,将会抛出 NullPointerException。
迭代器:HashMap 的迭代器是快速失败的(fail-fast),即在迭代过程中,如果其他线程对 HashMap 进行修改,会抛出 ConcurrentModificationException 异常。而 Hashtable 的迭代器则不是快速失败的,不会抛出异常。
初始容量和增长方式:HashMap 的初始容量和增长方式是可调的,而 Hashtable 的初始容量是固定的(11),增长方式是当前容量的两倍加一。
继承关系:HashMap 继承自 AbstractMap 类,而 Hashtable 继承自 Dictionary 类。
HashSet 是基于 HashMap 实现的,它是一种集合(Set)的实现,与 HashMap 不同的是,HashSet 只存储元素,不存储键值对。HashSet 中的元素是唯一的,不允许重复。与 HashMap 相比,HashSet 的操作方式和特性类似,只是不需要存储键值对而已。
多线程
start和run的区别
都是Thread类的方法,start表示启动一个线程,run方法表示线程启动以后要执行的代码
runable和callable区别
java线程状态有哪些
6种状态
1.new创建态
:当用new关键字创建一个线程时,还没调用start时
2.runnable可运行态
:程序无法判新某个时间到底是就绪还是运行态,所以这2个状态对程序就没有意义
3.waiting等待态
:处于这种状态的线程不会被分配CPU执行时间,它们要等待被显式地唤醒,否则会处于无限期等待的状态
4.timed_waiting超时等待态
:处于这种状态的线程不会被分配CPU执行时间,不过无需无期限等待被其他线程显式地唤醒,在达到一定时间后它们会自动唤醒
5.bolcked阻塞态
:线程在进入synchronized关键字修饰的方法或代码块(获取锁)时的状态
6.terminated销毁终止态
:
当线程的run()方法完成时,或者主线程的main()方法完成时,我们就认为它终止了。这个线程对象也许是活的,但是它已经不是一个单独执行的线程。线程一旦终止了,就不能复生
在一个终止的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常
Sleep、Wait区别
1、 sleep 来自 Thread 类,和 wait 来自 Object 类。
2、最主要是sleep方法没有释放锁,而wait方法释放了
锁,使得其他线程可以使用同步控制块或者方法。
3、wait,notify和 notifyAll 只能在同步控制方法或者同步控制块里面使用,而 sleep 可以在任何地方使用(使
用范围)
4、 sleep 必须捕获异常,而 wait , notify 和 notifyAll 不需要捕获异常
wait¬ify和Park&Unpark区别
wait和notify相比必须配合Object Monitor一起使用,而park,unpark则不用
park&unpark是以线程为单位来阻塞和唤醒线程的,而notify只能随机唤醒一个,notifyAll只能唤醒所有,不怎么精确
park&unpark是可以先unpark的类似于先打个预防针,而wait¬ify则不能
synchronized和Lock有什么区别?
死锁如何产生,如何排查?
ConcurrentHashMap的加锁方式
分段锁:ConcurrentHashMap内部被分为多个Segment(默认为16个),每个Segment拥有自己的锁。不同的线程可以同时访问不同的Segment,从而实现高并发。
每个Segment内部采用与HashMap类似的数据结构,使用链表或红黑树来存储键值对。
Segment级别的加锁:
在进行读操作时,不需要进行加锁,不会阻塞其他线程的读操作,实现了无阻塞的并发读取。
在进行写操作时,只会锁住当前操作的Segment,而不会影响其他Segment的操作。这样,在并发写入的情况下,不同的线程可以同时对不同的Segment进行修改。
CAS(Compare And Swap)操作:
在进行插入、删除等复合操作时,ConcurrentHashMap使用CAS操作来确保线程安全。CAS是一种非阻塞算法,可以实现多线程间的原子操作。
并行并发区别
并行:在同一时间段,多个请求访问不同资源,同时执行,互不影响,并行强调的是同一时间执行。 并发:在同一时间,多个请求同时访问同一个资源,并发强调是访问同一资源。线程池参数
线程池常见阻塞队列
LinkedBlockingQueue:这是一个基于链表实现的无界阻塞队列。当线程池的任务队列满时,新的任务将会被阻塞,直到队列有空闲位置。ArrayBlockingQueue:这是一个基于数组实现的有界阻塞队列。在创建时需要指定队列的容量,当队列满时,新的任务将会被阻塞,直到队列有空闲位置。
SynchronousQueue:这是一个没有容量的阻塞队列,每个插入操作必须等待一个相应的删除操作,反之亦然。常用于线程池中的任务直接交付给消费者线程处理的场景。
PriorityBlockingQueue:这是一个支持优先级排序的无界阻塞队列。元素按照优先级进行排序,优先级高的元素先被取出。
线程池拒绝策略有哪些
ThreadPoolExecutor.AbortPolicy(默认):当线程池无法处理新的任务时,会抛出RejectedExecutionException异常,表示拒绝执行该任务。
ThreadPoolExecutor.CallerRunsPolicy:当线程池无法处理新的任务时,新的任务会由调用线程来执行。也就是说,调用线程本身会参与到任务的执行中。
ThreadPoolExecutor.DiscardPolicy:当线程池无法处理新的任务时,会静默地丢弃该任务,不会抛出任何异常或警告。
ThreadPoolExecutor.DiscardOldestPolicy:当线程池无法处理新的任务时,会丢弃最早被添加到队列中但尚未被执行的任务,然后尝试重新提交当前任务。
此外,我们还可以根据具体业务需求自定义拒绝策略,通过实现RejectedExecutionHandler接口来定义自己的拒绝策略,以满足特定的处理逻辑,如记录日志或进行其他特定操作。
创建线程池时,如何确定线程核心数
CPU可用核心数:线程池的核心线程数可以根据CPU的可用核心数进行设置。一般来说,将核心线程数设置为CPU核心数的1-2倍是比较合理的选择。这样可以充分利用CPU资源,提高线程执行的效率。任务类型和性质:不同类型的任务对CPU的占用情况不同。如果任务是CPU密集型的,即任务需要大量的计算操作而不涉及阻塞等待,那么可以将核心线程数设置较小,以避免创建过多的线程。如果任务是I/O密集型的,即任务需要频繁地进行I/O操作(如读写文件、网络请求等),那么可以将核心线程数设置较大,以充分利用CPU等待I/O操作的时间。
预估的负载和并发量:需要根据预估的任务负载和并发量来确定线程池的核心线程数。如果任务量较多且并发量较大,可以适当增加核心线程数以提高并行度和处理能力。
可用的内存资源:线程在运行时会占用一定的内存资源,包括堆栈空间、线程局部变量等。如果可用的内存资源有限,需要考虑将核心线程数设置较小,以避免耗尽系统资源。
为什么不建议使用Executors
ThreadLocal如何做到线程隔离?内存泄漏?
CAS是什么,实现原理
CAS(Compare and Swap)是一种并发算法,用于实现多线程环境下的原子操作。在Java中,CAS主要通过java.util.concurrent.atomic包中的原子类来实现,如AtomicInteger、AtomicLong等。
CAS操作涉及到三个操作数:内存地址(或称为变量的引用)、期望值和新值。它的基本思想是,先比较内存地址处的值与期望值是否相等,如果相等,则将内存地址处的值更新为新值;否则,不做任何操作。整个操作是原子性的。
在底层实现方面,CAS依赖于处理器提供的原子指令,如x86架构下的CMPXCHG指令。这些指令可以保证在执行期间不被中断,从而实现了原子性操作。当多个线程同时尝试对同一个变量进行CAS操作时,只有一个线程能够成功,其他线程则需要重新尝试或采取其他策略。
具体实现步骤如下:首先,将需要进行CAS操作的内存地址映射到对应的物理内存地址。然后,在执行CAS操作之前,先通过硬件指令读取该内存地址处的值,并将其与期望值进行比较。如果比较结果相等,说明当前内存地址处的值与期望值相等,符合CAS操作的条件。此时,使用原子指令将新值写入该内存地址,并返回操作成功。如果比较结果不相等,说明当前内存地址处的值已经被其他线程修改,不符合CAS操作的条件。此时,返回操作失败。
在Java中,我们可以使用AtomicInteger类来演示CAS的应用。例如,我们可以使用AtomicInteger实现一个计数器,多个线程可以同时对其进行自增操作,而不需要显式地加锁。这样可以避免锁带来的性能开销,提供了一种高效的并发编程方式。
总结来说,CAS通过使用底层的硬件支持和原子指令,实现了多线程环境下的原子操作。它可以避免传统的锁机制所带来的性能开销,并提供了一种高效的并发编程方式。在Java中,CAS主要通过Atomic类来提供原子操作的支持,使得开发者可以方便地进行线程安全的操作。
以上就是关于Java CAS实现及底层原理的回答。希望能对您有所帮助!
谈谈对volatile的理解?
JVM
https://blog.csdn.net/weixin_45081813/article/details/115312135
JVM有哪些组成
JVM(Java虚拟机)是Java程序运行的核心组件,它负责将Java源代码编译成可执行的字节码,并在运行时提供环境来执行字节码。JVM由以下几个主要组成部分构成:类加载器(Class Loader):
类加载器负责将类的字节码加载到JVM中。它是JVM的子系统之一,负责从文件系统、网络等位置加载字节码,并转换成Java的运行时数据结构,使得这些类可以在JVM中被执行。
执行引擎(Execution Engine):
执行引擎负责执行已加载的字节码指令。它将字节码解释或者编译为本地机器码,以便底层操作系统能够执行。执行引擎可以是解释执行引擎(如传统的解释器),也可以是即时编译器(Just-In-Time Compiler, JIT),通过即时编译将热点代码转换成本地机器码来提高执行效率。
运行时数据区(Runtime Data Area):
运行时数据区是JVM用于存储数据的区域,包括以下几个部分:
方法区(Method Area):用于存储类的结构信息、静态变量、常量等。
堆(Heap):用于存放对象实例,是Java程序中动态分配内存的地方。
栈(Stack):存储线程的局部变量、方法调用和返回信息以及操作数栈等。
程序计数器(Program Counter Register):记录当前线程执行的字节码指令的地址。
本地方法栈(Native Method Stack):用于执行本地方法,与Java代码交互的接口。
垃圾回收系统(Garbage Collection System):
垃圾回收系统负责自动回收堆中不再被引用的对象所占用的内存空间,避免内存泄漏和溢出。垃圾回收系统通过标记-清除、复制、标记-整理等算法来进行垃圾回收。
JIT编译器(Just-In-Time Compiler):
JIT编译器是JVM的一个重要组件,它将热点代码(经过分析,被频繁执行的代码)转换为本地机器码,以提高代码执行效率。JIT编译器可以根据代码的实际运行情况进行动态优化,从而进一步提升性能。
什么是Java内存模型
Java内存模型(Java Memory Model,JMM)定义了Java程序在多线程环境下对内存的访问规则和操作行为。Java内存模型主要围绕着两个核心概念:主内存(Main Memory)和工作内存(Working Memory)。
主内存是Java虚拟机中的共享内存区域,用于存储所有的变量。而工作内存则是每个线程独有的内存区域,用于存储该线程使用到的变量副本。
在Java内存模型中,所有的变量都存储在主内存中,线程之间无法直接访问主内存中的变量。当一个线程需要使用某个变量时,首先会将变量从主内存复制到自己的工作内存中进行操作。然后,在操作完成后,再将变量的值写回主内存。这种操作过程涉及到以下三个规则:
可见性(Visibility Rule):当一个线程修改了变量的值后,需要将其刷新到主内存中,以便其他线程可以看到最新的值。
原子性(Atomicity Rule):对于某些具有原子特性的操作,比如读取或写入一个变量的值,必须是不可中断的。
有序性(Ordering Rule):Java并发编程中的指令重排序是允许的,然而在特定情况下,需要保证某些操作的执行顺序与代码的顺序一致。
为了确保线程之间的可见性、原子性和有序性,Java内存模型定义了一系列的内存操作,如锁定解锁、读取写入以及volatile变量的特殊规则等。通过这些规则和内存操作,Java内存模型提供了一种统一的方式来保证多线程程序的正确性。
总而言之,Java内存模型定义了多线程环境下对内存的访问规则和操作行为,确保了在多线程程序中变量的可见性、原子性和有序性。
什么是双亲委派模型
如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行;
如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器;
如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式。
类装载过程有哪些?
Java对象组成
Java对象由对象头、实例数据和对齐填充组成。对象头存储了一些元数据,如哈希码、锁状态和指向类元数据的指针。实例数据包含对象的成员变量值。对齐填充用于确保对象在内存中的对齐。
Java引用类型有哪些?
强引用(Strong Reference):
强引用是最常见的引用类型。当我们通过 new 关键字创建一个对象时,就会使用强引用来引用该对象。只要强引用存在,垃圾回收器就不会回收被引用的对象。
软引用(Soft Reference):
软引用用于描述一些还有用但非必需的对象。在内存不足时,垃圾回收器会根据软引用的情况决定是否回收这些对象。可以通过 SoftReference 类来创建软引用。
弱引用(Weak Reference):
弱引用也用于描述非必需对象,但是弱引用比软引用更脆弱。只要垃圾回收器运行,就有可能回收被弱引用关联的对象。可以通过 WeakReference 类来创建弱引用。
虚引用(Phantom Reference):
虚引用是最弱的引用类型,几乎没有实际作用。它主要用于跟踪对象被垃圾回收的状态。虚引用和引用队列(ReferenceQueue)一起使用,通过 PhantomReference 类来创建。
内存溢出排查思路?
CPU飙高的解决思路?
GC
什么是GC(垃圾回收)?
GC(垃圾回收)是Java中的一种自动内存管理机制。它通过检测和删除不再使用的对象以回收内存空间,从而减少内存泄漏和程序崩溃的风险。Java中的垃圾回收算法有哪些?
1.标记-清除算法(Mark and Sweep):
首先通过根节点(如活动线程的栈、静态变量等)标记出所有存活的对象。
然后对堆中未被标记的对象进行清除,将其空间回收。
该算法容易产生碎片,并且清除和标记操作的效率相对较低。
2.复制算法(Copying):
将堆内存划分为两个相等大小的区域,每次只使用其中一个区域。
当当前区域的内存用尽时,将存活的对象复制到另一个区域,然后清空当前区域。
该算法避免了碎片问题,但需要额外的内存空间。
3.标记-整理算法(Mark and Compact):
类似于标记-清除算法,首先标记所有存活的对象。
接着将存活的对象往一端移动,然后清理边界之外的内存。
该算法解决了标记-清除算法的碎片问题,但仍然存在标记和整理操作的开销。
4.分代算法(Generational):
基于分代假设,大部分对象的生命周期较短。
将堆内存划分为不同的代,如新生代(Young Generation)和老年代(Old Generation)。
使用不同的GC算法对不同代应用适当的回收策略,例如新生代采用复制算法,老年代采用标记-整理算法。
通过不同的分代策略和回收算法,可以提高垃圾回收的效率。
什么是Minor GC和Major GC?
Minor GC(新生代垃圾回收)和Major GC(老年代垃圾回收)是分代垃圾回收算法中的两个重要概念。Minor GC主要针对新生代进行垃圾回收,而Major GC主要针对老年代进行垃圾回收。Java中的垃圾收集器有哪些?
Java中常用的垃圾收集器包括Serial、Parallel、CMS、G1等。Serial收集器是最基本的收集器,适用于单线程环境;Parallel收集器使用多线程并行回收,适用于多核心环境;CMS收集器使用并发方式回收垃圾,适用于对响应时间有要求的场景;G1收集器是一种面向服务端应用的垃圾收集器,具有高效和可预测的特点。如何手动触发垃圾回收?
在Java中,可以使用System.gc()方法手动触发垃圾回收。但是,调用该方法并不一定会立即执行垃圾回收java常用gc
Serial回收器(Serial Garbage Collector):单线程工作,会暂停所有应用线程进行垃圾回收。适用于小型应用或者对暂停时间要求不高的场景。
Parallel回收器(Parallel Garbage Collector):多线程工作,与Serial回收器类似,但并行处理垃圾回收任务。适用于拥有多个处理器核心且对吞吐量要求较高的场景。
CMS回收器(Concurrent Mark-Sweep Garbage Collector):并发垃圾回收器,在应用线程运行的同时,通过多线程进行垃圾回收。适用于对暂停时间要求较高的应用,以减少垃圾回收导致的停顿时间。
G1回收器(Garbage-First Garbage Collector):并发垃圾回收器,将堆内存划分为多个段(Region),并根据垃圾产生的情况选择部分段进行回收。适用于大内存应用和具有可预测性暂停时间需求的场景。
默认GC
JDK 8及之前版本:年轻代: Serial收集器
老年代: Serial Old收集器
JDK 9及之后版本:
年轻代: G1收集器(G1 Young Generation)
老年代: G1收集器(G1 Old Generation)
Redis
Redis的数据类型有哪些?请简要介绍每种数据类型。
字符串(String):最基本的数据类型,可以存储字符串、整数或浮点数。
列表(List):按照插入顺序存储的字符串元素集合,支持在两端进行添加、删除和获取元素。
集合(Set):无序、不重复的字符串元素的集合,支持添加、删除和获取元素,并且可以进行交集、并集和差集的操作。
有序集合(Sorted Set):与集合类似,但是每个元素都有一个分数(score),根据分数进行排序。
哈希表(Hash):包含键值对的散列容器,可以存储多个字段和值的映射关系。
Redis有哪些常用命令?请列举几个例子。
SET:设置指定键的值。
GET:获取指定键的值。
DEL:删除指定键。
EXISTS:检查键是否存在。
INCR:将指定键的值加1。
DECR:将指定键的值减1。
LPUSH:在列表的左侧添加一个或多个元素。
RPUSH:在列表的右侧添加一个或多个元素。
SADD:向集合中添加一个或多个元素。
ZADD:向有序集合中添加一个或多个元素。
以上命令只是Redis的一小部分常用命令,Redis还有很多其他强大的命令可以用于不同的操作和场景。
Redis的持久化机制有哪些?请介绍它们的区别。
Redis的持久化机制有两种:RDB(Redis Database)和AOF(Append-Only File)。
RDB持久化:将Redis在某个时间点上的数据保存到磁盘上的一个二进制文件。它的优点是文件紧凑、恢复速度快,适用于大规模数据的备份和恢复。但是缺点是如果Redis意外宕机,会丢失最后一次持久化之后的数据。
AOF持久化:将Redis的每个写操作追加到一个文件中,通过重新执行这些写操作来恢复数据。它的优点是数据更可靠,即使Redis宕机也能保证较小粒度的数据恢复。但是缺点是AOF文件可能会变得很大,恢复速度相对较慢。
可以根据需求选择使用RDB持久化、AOF持久化或两者结合来保证数据的持久化和可靠性。
如何实现Redis的分布式锁?
可以使用Redis的SET命令结合NX(不存在则设置)和EX(过期时间)选项来实现分布式锁。
获取锁:使用SET命令设置一个键,如果该键不存在,则设置成功并获得锁。
shell
SET lock_key value NX EX 10
释放锁:使用DEL命令删除锁的键来释放锁。
shell
DEL lock_key
在获取锁时,考虑到并发情况下可能会出现死锁或误删其他线程的锁的情况,可以为每个线程设置一个唯一的标识符(如UUID),并将其作为锁值。在释放锁时,先比较锁的值是否与当前线程的标识符相同,再进行删除操作。
这仅是一种简单的实现方式,还有很多其他的分布式锁方案可以选择,例如使用Redlock、Watchdog等。
Redis 的发布与订阅功能是如何实现的?
Redis 的发布与订阅功能通过使用 PUBLISH 命令发布消息和使用 SUBSCRIBE 命令订阅频道来实现。
PUBLISH:用于向指定的频道发布消息。当有消息被发布到频道时,所有订阅了该频道的客户端都会接收到消息。
shell
PUBLISH channel message
SUBSCRIBE:用于订阅一个或多个频道,接收发布到这些频道的消息。可以同时订阅多个频道,并在接收到消息时进行相应的处理。
shell
SUBSCRIBE channel1 channel2 ...
Redis 的发布与订阅功能可以用于实现消息队列、实时通知等应用场景。
Redis 的主从复制是什么?如何设置和配置主从复制?
Redis 的主从复制是一种数据同步机制,即将一个 Redis 服务器的数据复制到多个从服务器上,以实现读取负载均衡和数据冗余备份。
配置主从复制的步骤如下:
在主服务器的配置文件中设置 slaveof 参数,指定从服务器的 IP 地址和端口号。
shell
slaveof master-ip master-port
启动从服务器,它会自动连接到主服务器并开始进行数据复制。
通过主从复制,当主服务器上的数据发生变化时,Redis 会将写操作记录在内存中的 AOF 文件中,并通过网络将这些写操作发送给从服务器,从服务器接收到写操作后会执行相同的操作,从而保持数据的一致性。
此外,还可以使用复制偏移量(replication offset)、全量同步和增量同步等机制来确保数据同步的可靠性和高效性。
Redis 的缓存淘汰策略有哪些?请简要介绍。
Redis 的缓存淘汰策略包括以下几种:
LRU(Least Recently Used):最近最少使用策略,淘汰最近最少被访问的数据。
LFU(Least Frequently Used):最不经常使用策略,淘汰一定时间内使用次数最少的数据。
Random:随机淘汰策略,随机选择一部分数据进行淘汰。
TTL(Time To Live):根据键的过期时间来淘汰数据。
可以根据实际业务需求和数据特点选择合适的缓存淘汰策略。另外,Redis 还提供了手动删除数据的命令,如 LRU 或 LFU 策略下的 LRU/LFU_REMOVE 命令。
Redis 的事务是如何实现的?
Redis 的事务通过 MULTI 和 EXEC 命令来实现。
使用 MULTI 命令开始一个事务,之后的命令都会被放入事务队列中。
shell
MULTI
执行需要在事务中进行的命令,这些命令会被顺序放入事务队列中。
使用 EXEC 命令执行事务中的所有命令,Redis 会按照之前的顺序执行队列中的命令,并返回执行结果。
shell
EXEC
在执行 EXEC 命令时,如果任意一个命令执行失败,整个事务将会回滚,即之前已经执行的命令不会生效。这可以确保事务的原子性。
另外,还可以使用 WATCH 命令监视一个或多个键,在事务执行期间,如果被监视的键被其他客户端修改,则事务会被中断,从而避免数据不一致性。
题目标题:Redis 集群是如何进行分片存储的?
题目答案:
Redis 集群使用哈希槽(Hash Slot)来实现分片存储。哈希槽将整个数据集划分为 16384 个小的部分,每个部分称为一个槽。每个节点可以负责多个槽,即一个节点可以存储多个槽的数据。
具体的分片过程如下:
根据键(Key)计算出一个哈希值。
将哈希值对 16384 进行取模运算,得到对应的槽编号。
将数据存储到负责该槽的节点上。
通过这种方式,Redis 集群将数据均匀地分布到多个节点上,实现了数据的分片存储和负载均衡。
题目标题:Redis 集群中的主从复制是如何工作的?
题目答案:
在 Redis 集群中,每个主节点都可以拥有多个从节点。主从复制机制用于将主节点的数据复制到从节点,实现数据的冗余和故障恢复。
具体的复制过程如下:
从节点向主节点发送 SYNC 命令,请求进行数据同步。
主节点执行 BGSAVE 命令,将数据持久化到磁盘并生成一个 RDB 文件。
主节点将生成的 RDB 文件发送给从节点,并通过缓冲区记录从节点在同步期间发生的写操作。
从节点接收到 RDB 文件后,将其加载到内存中,并通过缓冲区执行从节点在同步期间的写操作。
主节点将缓冲区中的写操作发送给从节点,使从节点与主节点达到数据的一致性。
通过主从复制机制,Redis 集群可以提供数据的冗余备份和故障恢复能力,确保系统的可用性和数据的一致性。
题目标题:Redis 集群的故障转移是如何工作的?
题目答案:
Redis 集群使用故障转移机制来确保主节点发生故障时的快速恢复。
具体的故障转移过程如下:
当一个主节点失去联系或被判定为不可用时,集群会开始一次自动故障转移。
集群会从该主节点的从节点中选举出新的主节点。
新的主节点会接管原先主节点负责的槽,并负责处理客户端的读写请求。
已经与原主节点进行了部分复制的从节点将成为新主节点的从节点,继续对数据进行复制。
通过故障转移机制,Redis 集群可以实现主节点的快速切换和故障恢复,确保系统的高可用性和稳定性。
题目标题:Redis 集群有哪些方式?
题目答案: Redis 集群可以通过以下两种方式进行部署和搭建:Redis Cluster 方式:
Redis Cluster 是 Redis 官方提供的集群解决方案,使用哈希槽(Hash Slot)的方式进行数据分片和负载均衡。它基于 gossip 协议实现节点之间的通信和数据同步。Redis Cluster 方式相对简单,适合构建中小规模的集群。每个节点既可以是主节点,也可以是从节点。
Redis Sentinel 方式:
Redis Sentinel 是 Redis 官方提供的高可用性解决方案,通过监控主节点的状态和进行故障检测,实现自动故障转移和主从切换。在 Redis Sentinel 方式下,可以将多个 Redis 实例配置为主从关系,并通过 Sentinel 监控实例的健康状况。当主节点发生故障时,Sentinel 会自动选举出新的主节点,并将其他节点切换为从节点。
缓存穿透、击穿、雪崩?
缓存穿透:
使用布隆过滤器(Bloom Filter):在缓存层之前引入布隆过滤器,用于快速过滤掉无效的请求,减轻对底层存储系统的访问压力。
引入空对象缓存:如果发现查询结果为空值,也将空结果缓存起来。这样,下次相同的请求可以直接从缓存中获取到空结果,避免重复到数据库进行查询。
缓存击穿:
设置短期随机缓存失效时间:在缓存方案中设置一个随机的短期失效时间,防止大量缓存同时过期而导致热点数据集中到达数据库。
使用互斥锁:在缓存失效的情况下,使用分布式锁来保护后续只能有一个请求从数据库中加载数据,并将加载到的数据刷新到缓存中,其他请求等待锁释放后再从缓存中获取数据。
缓存雪崩:
设置合理的缓存过期时间:将缓存的过期时间设置为随机值,避免大批量缓存同时过期导致数据库压力剧增。
使用热点数据预加载:提前异步加载热点数据到缓存中,避免大规模缓存失效时无法及时获取到数据。
分布式缓存部署策略:将缓存服务部署在不同的服务器上,避免单点故障,确保高可用性。
限流降级机制:当缓存失效后,对访问进行适度的流量控制,如使用限流算法和熔断机制,防止瞬间大量请求到达后端系统。
Redis双写一致性的解决方案?
rabbitmq
RabbitMQ是什么?它的主要特点有哪些?
RabbitMQ是一个开源的消息代理中间件,实现了AMQP(高级消息队列协议)标准。它主要用于通过消息传递在应用程序之间进行异步通信。
RabbitMQ的主要特点包括:
可靠性:RabbitMQ通过持久化消息和高可用性机制来确保消息的安全性和可靠性。
灵活的消息路由:它支持多种消息路由机制,如直接交换、主题交换、扇形交换等,可以满足不同场景下的消息传递需求。
高性能:RabbitMQ使用Erlang编写,具有轻量级和高并发处理能力,能够处理大量的消息。
多语言支持:RabbitMQ提供了多种语言的客户端库,如Java、Python、Ruby等,方便开发者在不同的编程语言中使用。
插件机制:RabbitMQ提供了丰富的插件机制,可以扩展其功能,如发布/订阅模式、消息优先级等。
RabbitMQ中的消息确认机制有哪些?
RabbitMQ中的消息确认机制有两种:
生产者确认(Publisher Confirmation):生产者发送消息后,等待RabbitMQ返回一个确认信号。如果收到确认信号,则表明消息已被成功接收;如果未收到确认信号,则表明消息可能未被成功接收。
单个确认模式:生产者发送一条消息后等待确认信号。
批量确认模式:生产者发送一批消息后等待确认信号。
消费者确认(Consumer Acknowledgement):消费者在处理完一条消息后,向RabbitMQ发送一个确认信号。RabbitMQ接收到确认信号后会将该消息从队列中删除。
手动确认模式:消费者手动调用确认方法来发送确认信号。
自动确认模式:消费者处理完消息后,自动发送确认信号。
使用消息确认机制可以增加消息的可靠性,确保消息能够正确地发送和处理。
RabbitMQ中的消息持久化是如何实现的?
消息持久化是指将消息存储在磁盘上,以便在RabbitMQ服务器重启后仍然能够有效地传递。要实现消息的持久化,需要进行以下两个步骤:
队列持久化:在声明队列时,将durable参数设置为true,表示创建一个持久化的队列。这样即使服务器重启,队列也能够恢复。
java
channel.queueDeclare("queue_name", true, false, false, null);
消息持久化:在发送消息时,将deliveryMode参数设置为2,表示消息是持久化的。
java
channel.basicPublish("", "queue_name", MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes());
通过队列持久化和消息持久化,可以确保即使在RabbitMQ服务器重启后,之前发送的消息也能够被正确地传递和处理。
RabbitMQ中的消息确认机制和消息持久化有何区别?
消息确认机制是指在消息发送和消费的过程中,通过确认信号来确保消息的可靠性。生产者可以通过接收到的确认信号来判断消息是否成功发送,消费者可以通过发送确认信号来告知RabbitMQ消息已被处理。
消息持久化是指将消息存储在磁盘上,以便在RabbitMQ服务器重启后能够恢复。通过将队列和消息标记为持久化,可以确保即使在服务器重启后,之前发送的消息仍然存在,并能够被正确地传递和处理。
区别在于:
消息确认机制主要关注的是消息的发送和消费过程中的可靠性,通过确认信号来保证消息的可靠传递。
消息持久化主要关注的是消息的存储,确保即使在服务器重启后,之前发送的消息仍然存在,并能够被正确地传递和处理。
消息确认机制和消息持久化是互补的措施,通过它们的组合可以提高消息系统的可靠性。
RabbitMQ中的交换器(Exchange)有哪几种类型?分别适用于什么场景?
RabbitMQ中的交换器有以下几种类型:
Direct(直接交换器):将消息发送到匹配的路由键上,适用于点对点的消息传递。消息通过完全匹配路由键来确定发送的队列。
Fanout(扇形交换器):将消息广播到所有与之绑定的队列上,适用于发布/订阅模式。不需要指定路由键,消息会被发送到所有与交换器绑定的队列。
Topic(主题交换器):将消息根据规则进行匹配,匹配的队列会接收到消息。可以使用通配符进行模糊匹配,适用于灵活的消息路由。
Headers(头交换器):根据消息的headers属性进行匹配,适用于复杂的消息匹配场景。
选择交换器类型要根据具体的业务需求和消息传递场景来决定,合理选择交换器类型可以提高消息传递的效率和灵活性。
RabbitMQ中的消费者如何处理消息的重试?
RabbitMQ中的消费者可以通过以下方式处理消息的重试:
重回队列(Requeue):在消费者处理消息失败后,可以将消息重新发送到原来的队列,让RabbitMQ重新分发消息给其他消费者进行处理。可以通过设置basic.reject或basic.nack方法的requeue参数为true来实现。
java
channel.basicReject(deliveryTag, true);
延迟重试:消费者可以将处理失败的消息发送到一个延迟队列中,在一定的时间后再重新尝试处理。可以通过使用RabbitMQ的插件或者结合定时任务来实现延迟重试的功能。
死信队列(Dead Letter Queue):在消费者无法处理某个消息时,可以将该消息发送到一个特定的死信队列中。然后可以设置另一个消费者去处理死信队列中的消息,进行重试或其他操作。
通过合理设置重试策略,可以提高消息的处理成功率和容错性。
RabbitMQ有哪些组成?
1.Producer(生产者):负责产生消息并发送到RabbitMQ的交换机(Exchange)。生产者将消息发送到特定的交换机,并指定一个路由键(Routing Key),用于标识消息的目的地。
2.Exchange(交换机):接收从生产者发送过来的消息,并根据规则将消息路由给一个或多个绑定的队列。Exchange有不同的类型,其中常见的有以下几种:
- Direct Exchange(直连交换机):将消息通过完全匹配的路由键发送到对应的队列。
- Topic Exchange(主题交换机):通过模式匹配的方式将消息发送到匹配的队列。主题交换机将路由键和队列之间建立灵活的绑定关系。
- Fanout Exchange(扇形交换机):将消息广播到所有绑定到该交换机的队列中。
- Headers Exchange(头部交换机):使用消息头部的属性进行匹配,并将消息发送到匹配的队列。
3.Queue(队列):用于存储消息的容器。当消息不满足交换机的路由规则时,会被丢弃或存储到队列中等待消费者处理。
4.Binding(绑定):连接交换机和队列的规则。绑定定义了消息如何从交换机路由到队列,通常需要指定一个路由键。
5.Consumer(消费者):从RabbitMQ的队列中接收消息并进行处理。消费者订阅一个或多个队列,在队列中有消息时将其取出并进行处理。
在RabbitMQ中,生产者发布消息到交换机,交换机根据绑定规则将消息路由到对应的队列,消费者从队列中获取消息并进行处理。这种发布-订阅的模型使得消息可以被灵活地路由和处理,满足不同的业务需求。
Spring
https://mp.weixin.qq.com/s/21YeQ5h85gXoa2kJg3nSgw
Spring事务是如何实现的?
Spring事务失效的场景?
Spring mvc流程
@Autowired注解@Resource区别
@Autowired注解: 1.来自于Spring框架,是Spring提供的注入方式之一。 2.默认按类型(byType)进行注入,在容器中查找匹配的Bean,如果找到多个匹配的Bean,则根据变量名进行匹配。 3.可以用在构造方法、字段、Setter方法或者任何方法上。 4.不强制要求依赖项必须存在,如果不存在对应的Bean,会抛出异常。可以通过设置@Autowired注解的required属性为false来避免抛出异常。@Resource注解:
1.是JavaEE规范的一部分,提供了依赖注入的功能。
2.默认按名称(byName)进行注入,先按照名称在容器中查找匹配的Bean,如果找不到,则按类型进行匹配。
3.可以用在字段、Setter方法或者任何方法上,但不支持构造方法注入。
4.强制要求依赖项必须存在,如果找不到对应的Bean,会抛出异常。
@SpringBootApplication 作用?
把bean注册到spring ioc容器。
@SpringBootApplication就只干了一件事通过3种方式来实现:
1. @SpringBootConfiguration 通过@Configuration 与@Bean结合,注册到Spring ioc 容器。
2. @ComponentScan 通过范围扫描的方式,扫描特定注解类,将其注册到Spring ioc 容器。
3. @EnableAutoConfiguration 通过spring.factories的配置,来实现bean的注册到Spring ioc 容器。
自动装配原理
SpringBootApplication原理
@SpringBootApplication是一个组合注解,它包含了以下三个注解:
@Configuration:表明该类是一个配置类,定义了一些Bean的创建和注册。
@EnableAutoConfiguration:开启自动配置功能,根据类路径中的依赖和配置,自动配置Spring Boot的特性。
@ComponentScan:指定要扫描的包路径,让Spring能够发现并注册Bean。
当启动Spring Boot应用程序时,会扫描主类所在的包及其子包,找到所有使用@Component及其衍生注解(如@Controller、@Service等)的类,并将其实例化为Bean。
使用@EnableAutoConfiguration注解时,Spring Boot会根据约定和条件自动配置应用程序的各项特性,如数据库连接、Web MVC、消息队列等。它会根据类路径中的依赖情况,自动装配相应的Bean。
@Configuration注解表示这是一个配置类,可以使用@Bean注解来定义和注册Bean。在Spring Boot应用程序中,通常会创建一些配置类来定义必要的Bean,以及进行一些自定义配置。
@ComponentScan注解会扫描指定的包路径,将使用了@Component及其衍生注解的类作为Bean注册到Spring容器中。这样就可以在应用程序中使用@Autowired等注解将Bean注入到其他组件中。
SpringCloud
什么是服务熔断和服务降级?
服务熔断:在分布式系统中,当某个服务出现故障或不可用时,为了避免故障扩散,可以通过服务熔断来防止对该服务的继续调用,并提供一个备用的响应或错误信息。
服务降级:在分布式系统中,当系统出现高峰期或故障时,为了保证核心功能的可用性,可以通过服务降级来减少对不重要或可选功能的调用,从而释放资源以应对高负载或故障情况。
Spring Cloud 中的服务调用方式有哪些?
Spring Cloud 提供了多种服务调用的方式,包括使用 RestTemplate、Feign、Ribbon、OpenFeign 等。其中,RestTemplate 是 Spring 提供的 HTTP 客户端工具,用于发送 HTTP 请求;Feign 和 OpenFeign 是声明式的 HTTP 客户端,可以通过注解方式进行声明,简化服务调用的过程;Ribbon 是负责客户端负载均衡的组件,可以让服务消费者轮流请求多个服务提供者。组成部分有哪些?
分布式事务中seata提供哪些解决方案?
AT(Auto-Commit)模式:AT 模式是 Seata 默认的事务模式。在 AT 模式下,Seata 通过对业务代码的透明封装,将分布式事务的提交和回滚操作自动化。当业务代码执行完毕后,Seata 会根据事务上下文中收集到的分支事务状态信息,自动决定是否提交或回滚整个分布式事务。TCC(Try-Confirm-Cancel)模式:TCC 模式是一种更为细粒度的补偿机制,适用于对数据一致性要求较高的场景。在 TCC 模式下,开发者需要显式地定义 Try、Confirm 和 Cancel 三个阶段的业务逻辑。Seata 通过调用各个服务的 Try、Confirm 和 Cancel 接口来协调分布式事务的提交和回滚。
Saga 模式:Saga 模式是一种长流程补偿模式,适用于分布式事务涉及多个阶段且每个阶段有自己的补偿逻辑的场景。Saga 模式将分布式事务拆分为多个阶段,并为每个阶段定义补偿逻辑。每个阶段都是一个本地事务,Seata 通过协调每个阶段的执行和补偿,最终达到整体事务的一致性。
XA 模式:XA 模式基于全局事务管理协议 X/Open XA 规范,支持使用分布式事务协调器(DTC)来实现分布式事务。Seata 提供了对 XA 模式的支持,可以与支持 XA 协议的数据库、消息中间件等进行集成,实现分布式事务的提交和回滚。
什么是服务熔断、降级?
微服务监控用的是什么?
Mybatis
mybatis执行流程
配置文件加载:MyBatis 首先加载配置文件(通常是 XML 格式),包括数据源配置、映射文件配置、全局设置等。SqlSessionFactory 构建:基于加载的配置文件,创建 SqlSessionFactory 对象,它是 MyBatis 中最重要的对象之一,负责创建 SqlSession 对象。
SqlSession 创建:通过 SqlSessionFactory 创建 SqlSession 对象,SqlSession 提供了对数据库进行操作的方法。开发人员在需要与数据库交互时,通过 SqlSession 执行 SQL 语句。
Mapper 接口绑定:将 Mapper 接口与对应的映射文件进行绑定,使得调用 Mapper 接口方法时可以执行对应的 SQL 语句。
SQL 执行:通过 Mapper 接口方法调用执行 SQL 语句。MyBatis 会根据方法名和参数类型,找到对应的映射文件,并执行其中定义的 SQL 语句。
参数处理:将传入的参数与 SQL 语句中的占位符进行映射,生成完整的 SQL 语句。
SQL 执行:将生成的完整 SQL 语句发送给数据库,并执行。数据库返回结果。
结果映射:将数据库返回的结果映射为 Java 对象,可以是单个对象、对象列表或者嵌套对象等。
延时加载如何开启,原理是什么?
一级二级缓存作用域?如何开启?何时清理
MyBatis 的工作原理是什么?
MyBatis 的核心组件是 SqlSessionFactory,它负责创建 SqlSession 对象。SqlSession 提供了操作数据库的方法,并且通过 Mapper 接口与对应的映射文件进行绑定。在执行 SQL 时,MyBatis 会将 SQL 语句和参数进行处理,并将结果映射为 Java 对象。MyBatis 的参数映射是如何工作的?
MyBatis 支持多种参数映射方式,包括基本类型、Map、Java 对象等。在 SQL 语句中,可以使用占位符(如 #{param})来引用参数。MyBatis 会根据参数类型和占位符名称,将参数映射到 SQL 语句中。什么是 MyBatis 的一级缓存和二级缓存?它们有什么区别?
一级缓存:一级缓存是当前 SqlSession 内部的缓存。当同一个 SqlSession 多次查询同一条记录时,第一次查询会将结果放入缓存中,后续查询直接从缓存中获取结果。一级缓存的作用域是 SqlSession,它的生命周期较短。
二级缓存:二级缓存是跨 SqlSession 的缓存。当多个 SqlSession 查询同一条记录时,第一次查询会将结果放入缓存中,后续 SqlSession 查询直接从缓存中获取结果。二级缓存的作用域是 Mapper 接口,它的生命周期较长。
动态 SQL 在 MyBatis 中如何使用?
动态 SQL 是 MyBatis 中强大的特性之一,可以根据不同的条件来动态生成 SQL 语句。MyBatis 提供了MyBatis 中的 resultMap 是什么?如何定义和使用它?
resultMap 是 MyBatis 中用于结果映射的配置元素。它定义了数据库列和 Java 对象属性之间的映射关系。可以使用MyBatis 如何开启日志?
配置日志级别。在application.properties或application.yml中设置日志级别。例如,可以将MyBatis的日志级别设置为DEBUG,以打印更详细的日志信息:
在application.properties中的配置:
logging.level.org.mybatis=DEBUG
MyBatis 如何配置多数据源?
首先 在application.properties或application.yml文件中配置多个数据源的连接信息和属性,包括URL、用户名、密码和驱动程序等。
接下来,在配置类中创建多个DataSource实例,并将其注入到DataSource类型的Bean中。可以使用@Configuration、@Bean和@Primary注解来完成。
最后,配置MyBatis的SqlSessionFactory和MapperScannerConfigurer,确保每个数据源都有对应的实例。
Docker
如何创建一个Docker容器?
可以使用Docker镜像来创建容器。首先,需要编写一个Dockerfile来定义容器的配置。然后,使用docker build命令基于Dockerfile构建镜像。最后,使用docker run命令基于镜像创建并运行容器。Docker镜像和容器的区别是什么?
Docker镜像是一个只读的模板,用于创建Docker容器。镜像包含了应用程序的代码、运行时环境、依赖项和配置等。而Docker容器则是运行中的实例,可以从镜像创建并启动。可以将容器看作是镜像的可执行版本。如何共享数据和文件夹给Docker容器?
可以使用Docker的数据卷(Volume)来共享数据和文件夹给容器。通过将主机目录挂载到容器的指定目录,可以实现主机与容器之间的数据共享。可以使用-v或--mount参数来指定挂载配置。如何将容器与容器连接和通信?
可以使用Docker的网络功能来连接和通信容器。可以创建自定义网络,并使用--network参数将多个容器连接到同一个网络中。这样,容器之间就可以通过容器名称或IP地址进行通信。什么是Docker Compose?它有什么作用?
Docker Compose是一个用于定义和运行多容器应用程序的工具。通过使用简单的YAML文件定义应用程序的服务、网络和卷等配置,可以一键启动、停止和管理多个相关的容器。K8s
https://mp.weixin.qq.com/s/-KRetiHriG-7zPx8O8v_3w
Kubernetes的三种外部访问方式
NodePort、LoadBalancer 和 Ingress
K8S的基本组成部分?
Master节点主要有五个组件,分别是kubectl、api-server、controller-manager、kube-scheduler 和 etcd;node节点主要有三个组件,分别是 kubelet、kube-proxy 和 容器运行时 docker 或者 rkt;
kubectl:客户端命令行工具,作为整个系统的操作入口。
apiserver:以REST API服务形式提供接口,作为整个系统的控制入口。
controller-manager:执行整个系统的后台任务,包括节点状态状况、Pod个数、Pods和Service的关联等。
kube-scheduler:负责节点资源管理,接收来自kube-apiserver创建Pods任务,并分配到某个节点。
etcd:负责节点间的服务发现和配置共享。
kube-proxy:运行在每个计算节点上,负责Pod网络代理。定时从etcd获取到service信息来做相应的策略。
kubelet:运行在每个计算节点上,作为agent,接收分配该节点的Pods任务及管理容器,周期性获取容器状态,反馈给kube-apiserver。
DNS:一个可选的DNS服务,用于为每个Service对象创建DNS记录,这样所有的Pod就可以通过DNS访问服务了。
Pod的状态?
1)Pending:已经创建了Pod,但是其内部还有容器没有创建;
2)Running:Pod内部的所有容器都已经创建,只有由一个容器还处于运行状态或者重启状态;
3)Succeeed:Pod内所有容器均已经成功执行并且退出,不会再重启;
4)Failed:Pod内所有容器都退出,但至少有一个为退出失败状态;
5)Unknown:由于某种原因不能获取该Pod的状态,可能是网络问题;
Mysql
MySQL中的存储引擎有哪些?
MySQL中的存储引擎包括InnoDB、MyISAM、Memory、CSV、Archive等。其中,InnoDB是MySQL的默认存储引擎。EXPLAIN关键字的作用是什么?
EXPLAIN关键字用于获取MySQL查询语句的执行计划,可以分析查询语句的性能瓶颈,并优化查询。MySQL中的索引类型有哪些?
MySQL中的索引类型包括主键索引、唯一索引、普通索引、全文索引等。每种类型都有不同的特点和适用场景。索引对数据库性能有什么影响?
索引可以加快数据库查询的速度,减少系统的IO开销。但过多或不合适的索引会增加数据插入、更新和删除操作的开销,并占用额外的存储空间。如何优化索引的使用?
优化索引的使用可以从以下几个方面入手:确保表中关键字段都有适当的索引。
避免创建过多的冗余索引。
使用合适的索引类型(如主键索引、唯一索引、普通索引)。
定期分析和优化索引的使用情况。
如何查看索引的使用情况?
可以使用EXPLAIN关键字来查看MySQL查询语句的执行计划,包括是否使用了索引以及索引的类型。通过分析执行计划可以优化查询语句和索引的使用。什么是覆盖索引?
覆盖索引是一种特殊的索引类型,它包含了查询所需的所有列,不需要再回表查询实际数据。覆盖索引可以提高查询性能,减少IO开销。创建索引原则有哪些?
聚簇、非聚簇、回表?
并发事务问题、和隔离级别
undolog、redolog、binlog
Undolog(回滚日志):也称为事务日志,记录了MySQL数据库中正在进行的事务所做的修改操作。它用于在需要回滚事务时撤销已提交的更改,以实现事务的一致性和原子性。Redolog(重做日志):也称为事务日志,记录了MySQL数据库中正在进行的事务所做的修改操作。它用于数据库的崩溃恢复,以保证在数据库故障或异常情况下,可以将数据还原到最近的一致状态。
Binlog(二进制日志):记录了MySQL数据库中所有的修改操作,包括数据的插入、更新和删除等。它以二进制格式保存,并用于数据库的备份、恢复、主从复制等功能。
这些日志在MySQL中起到了不同的作用:
Undolog保证了事务的原子性和一致性。
Redolog保证了数据库的持久性和故障恢复能力。
Binlog则提供了对数据库操作的详细记录和追踪,以实现数据备份、恢复和数据复制等功能。
事务隔离性、MVCC是什么?
MVCC(Multi-Version Concurrency Control)是一种数据库并发控制机制,用于管理数据库中多个事务并发执行时的数据一致性和隔离性。MVCC基于以下两个核心概念:
版本号:每个数据行都有一个版本号,用于标识该数据行在时间上的先后顺序。
快照读:事务在读取数据时,只能看到在事务开始之前已经存在的数据版本,而不会受到其他并发事务的影响。
MVCC的工作原理如下:
当事务开始时,系统会为该事务创建一个唯一的事务ID,并记录该事务ID的开始时间。
当事务对某个数据行进行修改时,系统会生成一个新的数据版本,并将新版本的事务ID和时间戳与该数据行关联起来。
在事务执行过程中,其他事务仍然可以读取旧版本的数据,保持了读写并发性。
在MVCC中,读取操作只能看到在其事务开始之前已经存在的数据版本,从而实现了事务之间的隔离性。
当事务提交或回滚时,相关的数据版本会被清理或无效化,释放相应的资源。
MVCC的优势在于减少了锁的使用,提高了并发性能。它通过时间戳和版本号的机制,使得事务之间可以同时读取数据而不互相干扰,提供了更好的隔离性和一致性。
主从同步原理
什么是B+树?
B+树是一种常用的平衡查找树数据结构,用于在数据库中索引和排序大量的数据。它是B树的一种变体,与B树相比具有更高的查询效率和更好的磁盘读写性能。B+树的特点有哪些?
B+树的特点包括:内部节点不存储数据,只存储键值信息,以提高存储空间利用率。
叶子节点通过链表连接在一起,方便范围查询和顺序访问。
所有叶子节点之间的高度是相同的,保证了较低的查询成本。
B+树的每个节点可以存储多个键值对,减少了树的高度,提高了查询效率。
B+树的节点大小固定,读取一个节点所需的IO次数更少,适合于磁盘存储。
MySQL索引有哪些限制?
唯一性限制:一个表中的索引不能包含重复的键值,即索引列的值必须是唯一的。这意味着如果创建了唯一索引,那么该列的值不能重复。
长度限制:MySQL对索引的长度有限制。对于InnoDB存储引擎,索引的最大长度为767字节,对于MyISAM存储引擎,最大长度为1000字节。需要注意的是,在使用多个列作为联合索引时,各列的长度之和不能超过限制。
元素类型限制:不是所有数据类型都可以用作索引列。例如,BLOB和TEXT类型的列无法直接创建索引,但可以创建前缀索引。
索引数量限制:每个表的索引数量是有限制的。对于InnoDB存储引擎,默认情况下每个表的索引数量上限为64个,对于MyISAM存储引擎默认为64个。这个限制可以通过修改MySQL的配置文件进行调整。
索引长度限制:对于单个索引,其列的数量是有限制的。对于InnoDB存储引擎,默认情况下每个索引的列数上限为16个,对于MyISAM存储引擎默认为16个。这个限制也可以通过修改MySQL的配置文件进行调整。
索引名字长度限制:索引名称的长度也是有限制的。对于InnoDB存储引擎,索引名称的最大长度为64个字符,对于MyISAM存储引擎默认为64个字符。
什么是前缀索引?
前缀索引(Prefix Index)是一种在MySQL数据库中用于优化查询性能的索引技术。它可以用于对较长字符串列创建索引,而不需要使用整个字符串作为索引键。
例如,以下语句将在users表的username列上创建一个前缀长度为5的索引:
CREATE INDEX idx_username ON users (username(5));