上海网站搭建,厦门高端网站建设定制,做医院网站公司电话,安徽省建设信息网文章目录 JavaJava中四种引用类型及使用场景集合HashMap源码及扩容策略HashMap死循环问题ConcurrentHashMap与HashtableConCurrentHashMap 1.8 相比 1.7 判断单链表是否有环#xff0c;并且找出环的入口IO线程池线程池的几种创建方式判断线程是否可以回收线程池的7大核心参数线… 文章目录 JavaJava中四种引用类型及使用场景集合HashMap源码及扩容策略HashMap死循环问题ConcurrentHashMap与HashtableConCurrentHashMap 1.8 相比 1.7 判断单链表是否有环并且找出环的入口IO线程池线程池的几种创建方式判断线程是否可以回收线程池的7大核心参数线程池的状态包括以下五种线程池怎么判断线程回收 JVMGC垃圾回收哪些内存需要回收方法区的垃圾回收垃圾收集算法垃圾收集器年轻代深入老年代条件 调优jmapjstackjstat JMMvolitale缓存一致性协议(MESI) 锁并发包 常用框架SpringSpringMVC注入DispatcherServlet几种方式DispatcherServlet调用逻辑 SpringBoot启动流程配置文件加载顺序配置文件加载时机自动配置原理 DubboSpringCloudNettySeataZookeeperXXL-JOB 设计模式数据库MQRabbitMq知识点RocketMq知识点Kafka知识点角色消息模型死信队列怎么避免消息丢失/消息可靠性顺序消费消息幂等性消息持久化数据积压集群 DockerRedis持久化机制 MongoDB负载均衡分布式锁EsSolr计算机网络HTTP常见响应码 开发相关其他 Java
JREJava Runtime Environment是JAVA运行时环境它是运行已编译Java程序所需的所有内容的集合包括Java虚拟机JVMJava核心类库和一些基础的构件。 JDKJava Development Kit是Java的开发工具包它不仅提供了Java程序运行所需的JRE还提供了一系列的编译运行等工具如javacjavajavaw等。 JDK JRE JVM所以我们在安装JDK时通常不需要考虑JREJVM之类的只要你安装好了JDK其他两个就都有了。 Java中四种引用类型及使用场景
强引用 new 一个对象的时候就是强引用。只要还有强引用指向一个对象垃圾收集器就不会回收这个对象。 显式地设置 置引用为 null或者超出对象的生命周期此时就可以回收这个对象。具体回收时机还是要看垃圾收集策略。
Object object new Object();软引用 非必须但仍有用的对象内存不足时才会回收。第一次gc不会回收第一次回收内存还不够才会回收。在系统将要发生内存溢出异常前会把这些对象列进回收范围之中进行第二次回收如果这次回收还没有足够的内存才会抛出内存溢出异常。
Object object new Object();
SoftReferenceObject softReference new SoftReference(object)应用场景缓存
弱引用 不管内存状态如何总会被回收的对象。
Object object new Object();
WeakReferenceObject weakReference new WeakReference(object);应用场景Java源码中的java.util.WeakHashMap中的key就是使用弱引用。
虚引用 引用与没有引用关系一样随时会被回收虚引用必须和引用队列一起使用。
虚引用与软引用和弱引用的一个区别在于虚引用必须和引用队列ReferenceQueue联合使用。当垃圾回收器准备回收一个对象时如果发现它还有虚引用就会在回收对象的内存之前把这个虚引用加入到与之关联的引用队列中。
Object obj new Object();
ReferenceQueue refQueue new ReferenceQueue();
PhantomReferenceObject phantomReference new PhantomReferenceObject(obj,refQueue);public class PhantomReferenceT extends ReferenceT {/*** Returns this reference objects referent. Because the referent of a* phantom reference is always inaccessible, this method always returns* codenull/code.** return codenull/code*/public T get() {return null;}public PhantomReference(T referent, ReferenceQueue? super T q) {super(referent, q);}
}应用场景对象销毁前的一些操作比如说资源释放等。
跟踪对象被垃圾回收的状态。提供机制确保对象被 finalize() 处理后执行额外清理操作。与引用队列一起使用在对象被回收时收到通知或执行清理操作。
使用实例
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;/*** author liuchao* date 2023/3/12*/
public class PhantomReferenceTest {/*** 当前对象的声明*/public static PhantomReferenceTest obj;/*** 引用队列*/static ReferenceQueuePhantomReferenceTest phantomQueue null;public static class CheckRefQueue extends Thread {Overridepublic void run() {while (true) {if (phantomQueue ! null) {PhantomReferencePhantomReferenceTest objt null;try {objt (PhantomReferencePhantomReferenceTest) phantomQueue.remove();} catch (InterruptedException e) {throw new RuntimeException(e);}if (objt ! null) {System.out.println(追踪垃圾回收过程PhantomReferenceTest实例被GC了);}}}}}/*** 通过此方法 复活obj对象** throws Throwable*/Overrideprotected void finalize() throws Throwable {super.finalize();System.out.println(调用当前类的finalize方法);//复活对象obj this;}public static void main(String[] args) {Thread t new CheckRefQueue();//设置为守护进程t.setDaemon(true);t.start();phantomQueue new ReferenceQueue();obj new PhantomReferenceTest();//构造PhantomReferenceTest 虚引用并指定引用队列PhantomReferencePhantomReferenceTest phantomReference new PhantomReference(obj, phantomQueue);try {//不可获取虚引用中对象System.out.println(phantomReference.get());//销毁强引用obj null;//进程GC由于对象可复活所以GC无法回收对象 通过finalize 复活System.gc();Thread.sleep(1000);//验证obj是否存活if (obj null) {System.out.println(obj 被销毁);} else {System.out.println(obj 被复活);}System.out.println(第二次GC);//再次销毁强引用obj null;System.gc();Thread.sleep(1000);//验证obj是否存活if (obj null) {System.out.println(obj 被销毁);} else {System.out.println(obj 被复活);}} catch (Exception e) {throw new RuntimeException(e);}}
}集合
HashMap源码及扩容策略
如果创建HashMap时不指定Capacity初始值HashMap的默认初始化大小为16之后每次扩充容量会变为两倍。
HashMap会在第一次Put的时候调用resize()初始化数组,每次put完成后再检查当前容量是否大于数组长度的0.75倍数如果大于则再调用resize()扩容每次扩容为当前数组长度的两倍。
put方法底层会对当前key进行hash运算再调用putVal去设置值如果计算出hash对应的数组下标无值则之间新建一个Node节点放入 如果有值
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {NodeK, V[] tab;NodeK, V p;int n, i;/** 如果是空的table那么默认初始化一个长度为16的Node数组*/if ((tab table) null || (n tab.length) 0) {n (tab resize()).length; // 1、创建table数组}/** 如果计算后的下标i在tab数组中没有数据那么则新增Node节点*/if ((p tab[i (n - 1) hash]) null) {tab[i] newNode(hash, key, value, null); // 2、无哈希冲突直接添加元素} else { // 3、存在哈希冲突向红黑树或链表赋值/** 如果计算后的下标i在tab数组中已存在数据则执行以下逻辑 */NodeK, V e;K k;if (p.hash hash ((k p.key) key || (key ! null key.equals(k)))) { /** 如果与已存在的Node是相同的key值*/e p;}else if (p instanceof TreeNode) { /** 如果与已存在的Node是相同的key值并且是树节点*/e ((TreeNodeK, V) p).putTreeVal(this, tab, hash, key, value);} else { /** 如果与已存在的Node是相同的key值并且是普通节点则循环遍历链式Node并对比hash和 key如果都不相同则将新的Node拼装到链表的末尾。如果相同则进行更新。*/for (int binCount 0; ; binCount) {/** 获得p节点的后置节点赋值给e。直到遍历到横向链表的最后一个节点即该节点的next后置指针为null */if ((e p.next) null) {p.next newNode(hash, key, value, null);/** binCount从0开始横向链表中第2个node对应binCount0如果Node链表大于8个Node那么试图变为红黑树 */if (binCount TREEIFY_THRESHOLD - 1) {treeifyBin(tab, hash);}break;}/** 针对链表中的每个节点都来判断一下是否待插入的key与已存在的链表节点相同如果相同则跳出循环并在后续的操作中将该节点内容更新为最新的插入值 */if (e.hash hash ((k e.key) key || (key ! null key.equals(k)))) {break;}p e;}}/** 如果存在相同的key值*/if (e ! null) {V oldValue e.value;if (!onlyIfAbsent || oldValue null) {/** 则将新的value值进行更新*/e.value value;}afterNodeAccess(e); return oldValue;}}modCount;if (size threshold) {resize(); // 4、超过阈值则进行扩容}afterNodeInsertion(evict); return null;}链表转红黑树两个条件必须同时满足两个条件才能进行转换
条件1单个链表长度大于等于8条件2hashMap的总长度大于64个、且树化的节点位置不能为空
红黑树转链表两个条件必须同时满足两个条件才能进行转换
条件1树内节点数小于等于6条件2根节点为空根节点的左右子树为空根节点的左子树的左子树为空
HashMap源码解析 HashMap死循环问题
HashMap死循环只发生在JDK1.7版本中。
主要原因:头插法 链表 多线程并发 扩容 累加到一起就会形成死循环
多线程下:建议采用ConcurrentHashMap替代
JDK1.8中HashMap改成尾插法解决了链表死循环的问题
博客详解
ConcurrentHashMap与Hashtable
对比
HashTable
HashTable 解决线程安全问题非常简单粗暴就是在方法前加 synchronize 关键词HasTable 不仅给写操作加锁 put remove clone 等还给读操作加了锁 get
ConcurrentHashMap
ConcurrentHashMap 没有大量使用 synchronsize 这种重量级锁。而是在一些关键位置使用乐观锁(CAS), 线程可以无阻塞的运行。读方法没有加锁扩容时老数据的转移是并发执行的这样扩容的效率更高。
ConCurrentHashMap 1.8 相比 1.7
去除 Segment HashEntry Unsafe 的实现改为 Synchronized CAS Node Unsafe 的实现,其实 Node 和 HashEntry 的内容一样但是HashEntry是一个内部类。 用 Synchronized CAS 代替 Segment 这样锁的粒度更小了并且不是每次都要加锁了CAS尝试失败了在加锁。put()方法中 初始化数组大小时1.8不用加锁因为用了个 sizeCtl 变量将这个变量置为-1就表明table正在初始化。
ConcurrentHashMap 1.8放弃了分段锁转而采用了一种新的实现方式。这种改变的原因是出于对分段锁局限性的考虑。以下是放弃分段锁的主要原因
锁竞争分段锁使得每个线程需要在不同的段上争夺锁这样增加了锁的竞争可能导致性能下降。扩容时的性能影响当ConcurrentHashMap需要进行扩容时需要重新分配段数组并复制原有数据。这个过程需要停止所有读写操作并持有整个ConcurrentHashMap的全局锁这会影响性能。热点数据问题分段锁可能会导致某些小的数据结构经常被访问从而在这些数据结构上产生激烈的锁竞争这同样会影响整体ConcurrentHashMap的性能。锁的粒度和重入锁的粒度大小不当或者锁的重入都可能引起性能问题。分段锁的设计可能需要调整锁的粒度和处理重入问题这本身就带来了额外的复杂性和性能开销。
为了克服上述问题Java 8中的ConcurrentHashMap采用了CASCompareAndSwap和synchronized的组合方式来保证并发安全而不是依赖分段锁。这样的改进不仅简化了内部实现还提高了并发性能。此外放弃分段锁后ConcurrentHashMap的存储空间需求也有所减少因为不再需要维护独立的segment结构 put()get()resize()方法都做了改变 ConCurrentHashMap 1.8 相比 1.7参考博客
判断单链表是否有环并且找出环的入口
快慢指针svt路程速度*时间用方程思想列等式解 使用HashSet将遍历过的元素存入。
第一次相遇的时候将一个指针指向头节点两个指针再同时向后每移动一个数据再次相遇就是环的入口。
参考博客
简易解析
红黑树IO
线程池
线程池的几种创建方式
ThreadPoolExecutor ThreadPoolExecutor最原始的创建线程池的方式它包含了 7 个参数可供设置后面会详细讲。Executors Executors.newFixedThreadPool创建一个固定大小的线程池可控制并发的线程数超出的线程会在队列中等待Executors.newCachedThreadPool创建一个可缓存的线程池若线程数超过处理所需缓存一段时间后会回收若线程数不够则新建线程Executors.newSingleThreadExecutor创建单个线程数的线程池它可以保证先进先出的执行顺序Executors.newScheduledThreadPool创建一个可以执行延迟任务的线程池Executors.newSingleThreadScheduledExecutor创建一个单线程的可以执行延迟任务的线程池Executors.newWorkStealingPool创建一个抢占式执行的线程池任务执行顺序不确定【JDK 1.8 添加】。
判断线程是否可以回收
工作线程回收需要满足三个条件
参数allowCoreThreadTimeOut为true该线程在keepAliveTime时间内获取不到任务即空闲这么长时间(空闲超时)当前线程池大小 核心线程池大小corePoolSize。
线程池通过设置参数来控制线程的回收策略其中主要的参数包括 核心线程数corePoolSize 表示线程池中保持的最小线程数即使它们是空闲的。 最大线程数maximumPoolSize 表示线程池中允许的最大线程数包括核心线程数和非核心线程数。 线程空闲时间keepAliveTime 表示非核心线程在空闲状态下的最长等待时间超过这个时间线程可能会被回收。 工作队列workQueue 用于存放尚未执行的任务当线程池中的线程数超过核心线程数时任务会被放入工作队列。如果工作队列已满新的任务可能触发创建新线程但线程数量不会超过最大线程数。
线程池的7大核心参数
示例
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueueRunnable workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler){}一、corePoolSize 线程池核心线程大小 线程池中会维护一个最小的线程数量即使这些线程处理空闲状态他们也不会被销毁除非设置了allowCoreThreadTimeOut。这里的最小线程数量即是corePoolSize。任务提交到线程池后首先会检查当前线程数是否达到了corePoolSize如果没有达到的话则会创建一个新线程来处理这个任务。
二、maximumPoolSize 线程池最大线程数量 当前线程数达到corePoolSize后如果继续有任务被提交到线程池会将任务缓存到工作队列后面会介绍中。如果队列也已满则会去创建一个新线程来出来这个处理。线程池不会无限制的去创建新线程它会有一个最大线程数量的限制这个数量即由maximunPoolSize指定。
三、keepAliveTime 空闲线程存活时间 一个线程如果处于空闲状态并且当前的线程数量大于corePoolSize那么在指定时间后这个空闲线程会被销毁这里的指定时间由keepAliveTime来设定
四、unit 空闲线程存活时间单位 keepAliveTime的计量单位
五、workQueue 工作队列 新任务被提交后会先进入到此工作队列中任务调度时再从队列中取出任务。jdk中提供了四种工作队列
①ArrayBlockingQueue 基于数组的有界阻塞队列按FIFO排序。新任务进来后会放到该队列的队尾有界的数组可以防止资源耗尽问题。当线程池中线程数量达到corePoolSize后再有新任务进来则会将任务放入该队列的队尾等待被调度。如果队列已经是满的则创建一个新线程如果线程数量已经达到maxPoolSize则会执行拒绝策略。
②LinkedBlockingQuene 基于链表的无界阻塞队列其实最大容量为Interger.MAX按照FIFO排序。由于该队列的近似无界性当线程池中线程数量达到corePoolSize后再有新任务进来会一直存入该队列而基本不会去创建新线程直到maxPoolSize很难达到Interger.MAX这个数因此使用该工作队列时参数maxPoolSize其实是不起作用的。
③SynchronousQuene 一个不缓存任务的阻塞队列生产者放入一个任务必须等到消费者取出这个任务。也就是说新任务进来时不会缓存而是直接被调度执行该任务如果没有可用线程则创建新线程如果线程数量达到maxPoolSize则执行拒绝策略。
④PriorityBlockingQueue 具有优先级的无界阻塞队列优先级通过参数Comparator实现。
④DelayedWorkQueue 具有优先级的无界阻塞队列
六、threadFactory 线程工厂 创建一个新线程时使用的工厂可以用来设定线程名、是否为daemon线程等等
七、handler 拒绝策略 当工作队列中的任务已到达最大限制并且线程池中的线程数量也达到最大限制这时如果有新任务提交进来该如何处理呢。这里的拒绝策略就是解决这个问题的jdk中提供了4中拒绝策略
CallerRunsPolicy 该策略下在调用者线程中直接执行被拒绝任务的run方法除非线程池已经shutdown则直接抛弃任务AbortPolicy 该策略下直接丢弃任务并抛出RejectedExecutionException异常。DiscardPolicy 该策略下直接丢弃任务什么都不做。DiscardOldestPolicy 该策略下抛弃进入队列最早的那个任务然后尝试把这次拒绝的任务放入队列
线程池的状态包括以下五种
RUNNING运行状态线程池新建或调用execute()方法后处于运行状态能够接收新的任务表示线程池可以接受新的任务并处理等待队列中的任务。SHUTDOWN关闭状态线程池不再接受新的任务提交但会继续处理等待队列中的任务。当调用线程池的shutdown()方法时线程池会从RUNNING状态转变为SHUTDOWN状态。STOP停止状态线程池既不接受新的任务提交也不处理等待队列中的任务并且会中断正在执行的任务。当调用线程池的shutdownNow()方法时线程池会从(RUNNING或SHUTDOWN)状态转变为STOP状态。TIDYING整理状态当线程池在SHUTDOWN状态下阻塞队列为空并且线程池中执行的任务也为空时(所有任务都销毁了workCount 为 0)线程池会从SHUTDOWN状态转变为TIDYING状态。在TIDYING状态下所有的任务都已终止线程池会执行terminated()方法执行完该方法后线程池会从TIDYING状态转变为TERMINATED状态。 SHUTDOWN 状态下任务数为 0 其他所有任务已终止线程池会变为 TIDYING 状态会执行 terminated() 方法。线程池中的 terminated() 方法是空实现可以重写该方法进行相应的处理。线程池在 SHUTDOWN 状态任务队列为空且执行中任务为空线程池就会由 SHUTDOWN 转变为 TIDYING 状态。线程池在 STOP 状态线程池中执行中任务为空时就会由 STOP 转变为 TIDYING 状态。 TERMINATED终止状态/销毁状态线程池在TIDYING状态执行完terminated()方法后会从TIDYING状态转变为TERMINATED状态。
fdsa
转换成TIDYING的不同: 线程池怎么判断线程回收
线程回收学习博客
线程池参数的合理设置
拒绝策略
线程池状态
怎么中断
创建销毁的过程线程池创建基本使用
线程的5大状态 1、 新建状态(New): 线程对象被创建后就进入了新建状态。例如Thread thread new Thread()。 2、 就绪状态(Runnable): 也被称为“可执行状态”。线程对象被创建后其它线程调用了该对象的start()方法从而来启动该线程。例如thread.start()。处于就绪状态的线程随时可能被CPU调度执行。
3、运行状态(Running): 线程获取CPU权限进行执行。需要注意的是线程只能从就绪状态进入到运行状态。
4、 阻塞状态(Blocked): 阻塞状态是线程因为某种原因放弃CPU使用权暂时停止运行。直到线程进入就绪状态才有机会转到运行状态。阻塞的情况分三种
(01) 等待阻塞 – 通过调用线程的wait()方法让线程等待某工作的完成。(02) 同步阻塞 – 线程在获取synchronized同步锁失败(因为锁被其它线程所占用)它会进入同步阻塞状态。(03) 其他阻塞 – 通过调用线程的sleep()或join()或发出了I/O请求时线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时线程重新转入就绪状态。
5、死亡状态(Dead): 线程执行完了或者因异常退出了run()方法该线程结束生命周期。
JVM
包含内容
类装载子系统(Class Load SubSystem)运行时数据区(Run-Time Data Areas) 堆栈 局部变量表操作数栈动态链接方法返回地址 程序计数器方法区 本地方法接口(Native Method Stack)PC寄存器(Programe Counter Register)执行引擎(Execution Engine) 字节码解释器 对字节码采用逐行解释的方式执行JIT(Just In Time)编译器
JIT(Just In Time)编译器
方法调用计数器统计方法调用次数 统计方法调用的次数。默认阈值时Client模式下1500次在Server模式下是10000次。超过这个阈值就会触发JIT编译。这个阈值可以通过-XX:CompileThreshold设定回边计数器统计循环体执行的循环次数
jvm内存分配
栈上分配与TLAB/内存分配的两种方法
GC垃圾回收
jdk1.8默认垃圾回收器 JDK1.8中Parallel Scavenge 被设置为年轻代Young Generation的默认垃圾回收器而 Parallel Old 是用于老年代Tenured Generation的垃圾回收器
GC日志内容
日志内容解析及GC案例
哪些内存需要回收
所谓“要回收的垃圾”无非就是那些不可能再被任何途径使用的对象。 寻找回收对象的两种方式。
引用计数法 给对象中添加一个引用计数器每当一个地方引用这个对象时计数器值1当引用失效时计数器值-1。任何时刻计数值为0的对象就是不可能再被使用的。可达性分析法 通过一系列称为GC Roots的对象作为起始点从这些节点向下搜索搜索所走过的路径称为引用链当一个对象到GC Roots没有任何引用链即GC Roots到对象不可达时则证明此对象是不可用的。
可以作为GCRoots的对象包括下面几种
虚拟机栈栈帧中的局部变量区也叫做局部变量表中引用的对象。方法区中的类静态属性引用的对象。方法区中常量引用的对象。本地方法栈中JNI(Native方法)引用的对象。
方法区的垃圾回收
方法区的垃圾回收主要回收两部分内容
废弃常量。 以字面量回收为例如果一个字符串“abc”已经进入常量池但是当前系统没有任何一个String对象引用了叫做“abc”的字面量那么如果发生垃圾回收并且有必要时“abc”就会被系统移出常量池。常量池中的其他类接口、方法、字段的符号引用也与此类似。无用的类。既然进行垃圾回收就需要判断哪些是废弃常量哪些是无用的类需要满足以下三个条件 该类的所有实例都已经被回收即Java堆中不存在该类的任何实例。加载该类的ClassLoader已经被回收。该类对应的java.lang.Class对象没有在任何地方被引用无法在任何地方通过反射访问该类的方法。
垃圾收集算法
标记-清除Mark-Sweep算法复制Copying算法标记-整理Mark-Compact算法分代收集算法
垃圾收集器
Serial收集器() 需要STWStop The World停顿时间长。 简单高效对于单个CPU环境而言Serial收集器由于没有线程交互开销可以获取最高的单线程收集效率。Serial Old收集器 Serial收集器的老年代版本ParNew收集器 ParNew收集器其实就是Serial收集器的多线程版本Parallel Scavenge收集器Parallel Old收集器CMS收集器G1收集器
年轻代深入老年代条件
躲过15次gc达到15岁高龄之后进入老年代动态年龄判定规则如果Survivor区域内年龄1年龄2年龄3年- 龄n的对象总和大于Survivor区的50%此时年龄n以上的对象会进入老年代不一定要达到15岁如果一次Young GC后存活对象太多无法放入Survivor区此时直接计入老年代大对象直接进入老年代
调优
GC频率不高GC耗时不高那么没有必要进行GC优化如果GC时间超过1-3秒或者频繁GC则必须优化。指标参考 a.Minor GC执行时间不到50msb.Minor GC执行不频繁约10秒一次c.Full GC执行时间不到1sd.Full GC执行频率不算频繁不低于10分钟1次GC内存最大化原则处理吞吐量和延迟问题时候垃圾处理器能使用的内存越大垃圾收集的效果越好应用程序也会越来越流畅。
在性能属性里面吞吐量、延迟、内存占用我们只能选择其中两个进行调优不可三者兼得。
总结 开启gc日志打印jmap -dump下载虚拟机文件jstack分析死锁jstat分析内存占用情况 jstat -gc 查看gc日志
CPU使用率飙高问题
1.使用top命令常看当前服务器中所有进程jps命令可以查看当前服务器运行java进程,找到当前cpu使用率最高的进程获取到对应的pid
2.然后使用top -Hp pid查看该进程中的各个线程信息的cpu使用找到占用cpu高的线程pid
3.使用jstack pid打印它的线程信息需要注意的是通过jstack命令打印的线程号和通过top -Hp打印的线程号进制不一样需要进行转换才能进行匹配jstack中的线程号为16进制而top -Hp打印的是10进制。
jmap
显示Java堆详细信息
jmap -heap pid显示堆中对象的统计信息
jmap -histo:live pid打印类加载器信息
jmap -clstats pid生成堆转储快照dump文件
jmap -dump:formatb,fileheapdump.dump pidjstack
jinfo pid可以查看当前进行虚拟机的相关信息列举出来如下图 jstat -gc pid ms多长毫秒打印一次gc信息打印信息如下里面包含gc测试年轻代/老年带gc信息等 jmap -histo pid | head -20查找当前进程堆中的对象信息加上管道符后面的信息以后代表查询对象数量最多的20个 jmap -dump:formatb,filexxx pid可以生成堆信息的文件
jstack、jconsle检查死锁
jstat
jstat -options [-t] [-hlines] vmid [interval [count]]options可选值
-class显示ClassLoader的相关信息
-compiler显示JIT编译的相关信息
-gc显示与GC相关信息
-gccapacity显示各个代的容量和使用情况
-gccause显示垃圾收集相关信息同-gcutil同时显示最后一次或当前正在发生的垃圾收集的诱发原因
-gcnew显示新生代信息
-gcnewcapacity显示新生代大小和使用情况
-gcold显示老年代信息
-gcoldcapacity显示老年代大小
-gcpermcapacity显示永久代大小
-gcutil显示垃圾收集信息
-printcompilation输出JIT编译的方法信息
-t在输出信息前加上一个Timestamp列显示程序的运行时间
-h可以在周期性数据输出后输出多少行数据后跟着一个表头信息
interval用于指定输出统计数据的周期单位为毫秒
count用于指定一个输出多少次数据jstat -gc 7063 500 47063 是进程ID 采样时间间隔为500ms采样数为4
JVM基本调优
GC及垃圾收集器简单解析
JMM
Java内存模型(Java Memory Model)是一种抽象的概念并不真实存在它描述的是一组规则或规范定义了程序中各个变量的访问方式。
JMM定义了关于主内存与工作内存之间具体的交互协议即一个变量如何从主内存拷贝到工作内存、如何从 工作内存同步回主内存这一类的实现细节Java内存模型中定义的8种每个线程自己的工作内存与主物理内存之间的原子操作Java虚拟机实 现时必须保证下面提及的每一种操作都是原子的、不可再分的。 主内存 线程的共享数据区域主要存储的是Java实例对象所有线程创建的实例对象都存放在主内存中(包括局部变量、类信息、常量、静态变量)。 工作内存 线程私有主要存储当前方法的所有本地变量信息(主内存中的变量副本拷贝) 每个线程只能访问自己的工作内存即线程中的本地变量对其它线程是不可见的即使访问的是同一个共享变量。 数据同步八大原子操作
lock(锁定) 作用于主内存的变量把一个变量标记为一条线程独占状态unlock(解锁) 作用于主内存的变量把一个处于锁定状态的变量释放出来释放后 的变量才可以被其他线程锁定read(读取) 作用于主内存的变量把一个变量值从主内存传输到线程的工作内存 中以便随后的load动作使用load(载入) 作用于工作内存的变量它把read操作从主内存中得到的变量值放入工 作内存的变量副本中use(使用) 作用于工作内存的变量把工作内存中的一个变量值传递给执行引擎assign(赋值) 作用于工作内存的变量它把一个从执行引擎接收到的值赋给工作内 存的变量store(存储) 作用于工作内存的变量把工作内存中的一个变量的值传送到主内存 中以便随后的write的操作write(写入) 作用于工作内存的变量它把store操作从工作内存中的一个变量的值 传送到主内存的变量中 对于double和long类型的变量来说load、store、read和write操作在某些平台上允许有例外 JMM 离不开原子性、可见性、有序性展开。
原子性可见性有序性 虚拟机在进行代码编译时对改变顺序后不会对最终结果造成影响的代码虚拟机不一定会按我们写的代码顺序运行有可能进行重排序。实际上虽然重排后不会对变量值有影响但会造成线程安全问题。
volitale
volatile关键字的作用主要有以下几点
确保内存可见性当一个线程修改了一个volatile变量的值其他线程会立即看到这个改变。这确保了所有线程看到的是一致的内存映像。防止指令重排序JVM会在指令级别对程序进行重排序以便更好地优化执行效率。但在某些情况下这可能导致变量读取/写入操作被误排序从而无法正确地反映出程序的意图。volatile关键字可以防止这种重排序的发生。禁止共享变量缓存大多数现代处理器都有一种名为“缓存”的技术这种技术会缓存一部分主内存中的数据以提高程序的运行效率。但是如果一个变量被声明为volatile那么处理器就会知道这个变量是用于同步的因此不能被缓存从而确保所有线程都能看到最新的值。 volitale 能保证可见性、有序性不能保证原子性。 内存屏障是什么
硬件层的内存屏障分为两种Load Barrier 和 Store Barrier即读屏障和写屏障。 内存屏障有两个作用
阻止屏障两侧的指令重排序强制把写缓冲区/高速缓存中的脏数据等写回主内存让缓存中相应的数据失效。
对于Load Barrier来说在指令前插入Load Barrier可以让高速缓存中的数据失效强制从新从主内存加载新数据 对于Store Barrier来说在指令后插入Store Barrier能让写入缓存中的最新数据更新写入主内存让其他线程可见。
如果你的字段是volatileJava内存模型将在写操作后插入一个写屏障指令在读操作前插入一个读屏障指令。
下面是基于保守策略的JMM内存屏障插入策略 在每个volatile写操作的前面插入一个StoreStore屏障。 在每个volatile写操作的后面插入一个StoreLoad屏障。 在每个volatile读操作的前面插入一个LoadLoad屏障。 在每个volatile读操作的后面插入一个LoadStore屏障。
volatile的实现原理-内存屏障
缓存一致性协议(MESI)
MESI协议只能保证并发编程中的可见性并未解决原子性和有序性的问题所以只靠MESI协议是无法完全解决多线程中的所有问题。
volitale锁
synchronize 和 lock 锁
创建对象判断对象头是否开启偏向锁开启偏向锁尝试将线程ID与对象头 volitale并发包
JUC中常用类
ReentrantLock 可重入锁
ReentrantReadWriteLock
Semaphroe 信号量
CountDownLatch
CopyOnWriteArrayList
CyclicBarrier
Atomic原子类
AtomicReferenceAQSAbstractQueuedSynchronizer的核心原理主要围绕三个方面
同步状态State。AQS使用一个volatile修饰的int类型的成员变量来表示同步状态通过该状态来控制共享资源的访问。队列Queue。AQS使用一个基于FIFO先进先出原则的队列来实现线程的排队和同步。这个队列是由一系列节点Node组成每个节点包含线程本身、前驱节点、后继节点以及等待状态等信息。并发操作。AQS提供了多种原子操作来对同步状态进行修改例如compareAndSetState方法用于原子地更新同步状态。
当一个线程试图获取共享资源时它会尝试通过CAS操作来修改同步状态。如果获取成功线程将获得资源并继续执行如果获取失败线程会被加入到队列的末尾并阻塞直到同步状态发生改变例如有线程释放了资源。当资源被释放时位于队列头部的线程会被唤醒尝试再次获取资源。这种机制使得AQS能够支持如ReentrantLock这样的同步器。1
常用框架
Spring
Spring的启动流程 加载Spring配置文件。即读入并解析配置文件构建出Spring IoC容器的初始状态。 初始化IoC容器。IoC容器的初始化包括对BeanFactory进行初始化、注册BeanDefinition、实例化Bean、依赖注入等过程。 实例化和初始化Bean。在初始化Bean时Spring 根据Bean的定义以及配置信息实现对Bean的实例化、属性赋值、以及初始化等操作。 完成IoC容器的准备工作。所有单例的Bean都已经被实例化、初始化并装配到容器中后容器的准备工作就完成了此时Spring框架已经可以对外提供服务。 执行定制化的后置处理器。Spring容器中可能会存在一些实现了BeanPostProcessor接口的定制化组件。这些组件会参与到IoC容器中Bean的生命周期过程比如AOP、事务处理等。 执行自定义的初始化方法和销毁方法。容器中某些Bean可能需要在容器启动时执行自定义的初始化方法。这些方法在容器启动时就会被调用同理某些Bean在容器关闭时需要调用自定义的销毁方法以清理资源。 容器启动后整个应用将进入正常的工作状态。
Spring启动流程
IOC是根据什么找到对应的对象的 IOC是根据BeanName找到对应的对象的。 IOC内存放着所有Class对应BeanName的一个关系表,如果通过Class获取实例则先从此表查询BeanName再从容器中获取实例。 DefaultListableBeanFactory#getBeanNamesForType将Class类型转成BeanName最终通过BeanName从三级缓存中获取实例。
Spring其他相关面试题及答案
Spring中Bean的生命周期
实例化bean属性赋值各种aware接口BeanPostProcessor#postProcessBeforeInitializationInitializingBean#afterpropertiseSetinit-method 初始化方法BeanPostProcessor#postProcessAfterInitialization使用beanDiposableBean#destory / destory-method
SpringAOP原理
理解 Spring AOP 需要了解以下几个关键概念
切面Aspect 切面是一个模块化的单元它包含一个横跨应用程序的关注点Concern。在 Spring AOP 中切面通常描述了一类横切关注点比如日志记录、性能统计、事务管理等。切面可以被认为是一个交叉关注点cross-cutting concern与业务逻辑独立存在可以在应用的多个地方进行重用。连接点Join Point 连接点是在应用程序执行过程中可能被拦截的点。在 Spring AOP 中连接点通常是方法的执行点。这些点可以是方法调用、方法执行过程中的特定位置或者是异常处理的点等。通知Advice 通知是切面在连接点上执行的动作。通知定义了在连接点处何时执行什么操作。在 Spring AOP 中有以下几种类型的通知 前置通知Before Advice在连接点之前执行的通知。后置通知After Advice在连接点之后执行的通知不考虑方法的返回结果。返回通知After Returning Advice在连接点正常执行后执行的通知考虑方法的返回结果。异常通知After Throwing Advice在连接点抛出异常后执行的通知。环绕通知Around Advice包围连接点的通知可以在连接点前后自定义操作。 切点Pointcut 切点是一个表达式它定义了哪些连接点将被匹配到并应用通知。切点表达式允许开发者选择性地将通知应用于特定的连接点。切点使用 AspectJ 切点表达式语言来定义。引入Introduction 引入允许我们在现有的类中添加新的方法或属性。通过引入我们可以为一个类添加一些在源代码中不存在的方法或属性从而改变类的行为。织入Weaving 织入是指将切面与应用程序的目标对象连接起来并创建一个通知增强的代理对象。织入可以在编译时、类加载时或运行时进行Spring AOP 采用运行时织入的方式。
spring源码解析之AOP原理详解
Spring中的AOP代理模式
Spring中的AOP责任链执行
BeanFactory和ApplicationContext有什么区别
SpringMVC
拦截器和过滤器的区别及实现
过滤器Filter依赖于servlet容器只能在 servlet容器web环境下使用 拦截器依赖于spring容器可以在springweb中调用不管此时Spring处于什么环境 过滤器Filter能拿到http请求但是拿不到处理请求方法的信息。 拦截器Interceptor既能拿到http请求信息也能拿到处理请求方法的信息但是拿不到方法的参数信息。 切片Aspect能拿到方法的参数信息但是拿不到http请求信息。
注入DispatcherServlet几种方式
一 实现WebApplicationInitializer接口并将实现类注入容器。
Configuration
ComponentScan(cn.example.springmvc.boke)
public class WebConfig {
}//使用基于Java的配置,注册并初始化一个DispatcherServlet
public class MyWebApplicationInitializer implements WebApplicationInitializer {Overridepublic void onStartup(ServletContext servletContext) throws ServletException {//声明一个Spring-web容器AnnotationConfigWebApplicationContext ctx new AnnotationConfigWebApplicationContext();ctx.register(WebConfig.class);//创建并注册DispatcherServletDispatcherServlet servlet new DispatcherServlet(ctx);//动态的添加ServletServletRegistration.Dynamic registration servletContext.addServlet(dispatcherServlet, servlet);registration.setLoadOnStartup(1);//指定由DispatcherServlet拦截所有请求(包括静态资源但不拦截.jsp)registration.addMapping(/);}
}基于web.xml来配置DispatcherServlet,如下
web-app ....servletservlet-namedispatcherServlet/servlet-nameservlet-classorg.springframework.web.servlet.DispatcherServlet/servlet-classinit-paramparam-namecontextConfigLocation/param-nameparam-valueclasspath:springmvc.xml/param-value/init-paramload-on-startup1/load-on-startup/servletservlet-mappingservlet-namedispatcherServlet/servlet-nameurl-pattern//url-pattern/servlet-mapping
/web-app三 继承AbstractAnnotationConfigDispatcherServletInitializer并将实现类注入Spring容器。
//配置父子容器其中容器使用基于注解的配置方式
public class IocInit extends AbstractAnnotationConfigDispatcherServletInitializer {//配置 DispatcherServlet 拦截的路径Overrideprotected String[] getServletMappings() {return new String[] {/};}//设置根容器的配置类Overrideprotected Class?[] getRootConfigClasses() {return new Class[] {RootConfig.class};}//设置子容器的配置类//如果不想形成父子容器那么只需将下面这个getServletConfigClasses()方法返回null即可Overrideprotected Class?[] getServletConfigClasses() {return new Class[] {WebConfig.class};}
}//由于我们采用的是父子容器因此这就要求我们编写父子容器的配置文件时根容器的配置文件(RootConfig)配置非web组件的bean而子容器的配置文件(WebConfig)配置web组件的bean同时也要防止同一组件在不同容器中分别注册初始化从而出现两个相同bean
//根容器配置类使用excludeFilters排除掉Controller注解标注的类和Configuration注解标注的类这里之所以要排除掉Configuration注解标注的类是为了防止根容器扫描到子容器的配置类WebConfig
Configuration
ComponentScan(value cn.example.springmvc.boke,excludeFilters {ComponentScan.Filter(type FilterType.ANNOTATION, value Controller.class),ComponentScan.Filter(type FilterType.ANNOTATION, value Configuration.class)})
public class RootConfig {
}//子容器配置类使用includeFilters指定只扫描由Controller注解标注的类
Configuration
ComponentScan(value cn.example.springmvc.boke,includeFilters ComponentScan.Filter(value Controller.class, type FilterType.ANNOTATION))
public class WebConfig {
}DispatcherServlet调用逻辑
根据请求获取HandlerExecutionChain对象根据处理器获取HandlerAdapter执行handle前调用拦截器的preHandle方法若返回false处理结束调用handler实际处理请求获取ModelAndView对象调用拦截器的postHandle方法处理分发结果渲染视图调用拦截器的afterCompletion 方法
SpringBoot
SpringBoot之启动加载器
ApplicationRunnerCommandLineRunner
SpringBoot自定义的类加载器
SpringBoot里面默认使用的哪种代理
SpringBoot里面默认使用动态代理配置在AopAutoConfiguration类中类中主要方法
Spring Boot 2.0.0.RELEASE之前
Configuration
ConditionalOnClass({ EnableAspectJAutoProxy.class, Aspect.class, Advice.class })
ConditionalOnProperty(prefix spring.aop, name auto, havingValue true, matchIfMissing true)
public class AopAutoConfiguration {ConfigurationEnableAspectJAutoProxy(proxyTargetClass false)ConditionalOnProperty(prefix spring.aop, name proxy-target-class, havingValue false,matchIfMissing true)public static class JdkDynamicAutoProxyConfiguration {}ConfigurationEnableAspectJAutoProxy(proxyTargetClass true)ConditionalOnProperty(prefix spring.aop, name proxy-target-class, havingValue true,matchIfMissing false)public static class CglibAutoProxyConfiguration {}}
Spring Boot 2.0.0.RELEASE之后
Configuration(proxyBeanMethods false)
ConditionalOnProperty(prefix spring.aop, name auto, havingValue true, matchIfMissing true)
public class AopAutoConfiguration {Configuration(proxyBeanMethods false)ConditionalOnClass(Advice.class)static class AspectJAutoProxyingConfiguration {Configuration(proxyBeanMethods false)EnableAspectJAutoProxy(proxyTargetClass false)ConditionalOnProperty(prefix spring.aop, name proxy-target-class, havingValue false,matchIfMissing false)static class JdkDynamicAutoProxyConfiguration {}Configuration(proxyBeanMethods false)EnableAspectJAutoProxy(proxyTargetClass true)ConditionalOnProperty(prefix spring.aop, name proxy-target-class, havingValue true,matchIfMissing true)static class CglibAutoProxyConfiguration {}}
}可以看到2.0之前都是用的JDK代理2.0之后用的Cglib。 原因一CGlib不需要接口 Spring动态代理默认使用CGlib是因为它可以代理那些没有实现任何接口的类而JDK动态代理仅能代理实现了接口的类。 原因二CGlib效率高 CGlib相对于JDK动态代理来说在代理类的创建和执行的速度上更快因此在某些情况下使用CGlib代理可以提高系统性能。 原因三JDK代理会导致注解失效 如果Spring是JDK代理那么就会导致某些注解失效。
强制切换代理
Spring可以设置EnableAspectJAutoProxy中proxyTargetClass属性为false来强制使用JDK代理。SpringBoot的AOP 默认使用 cglib且无法通过proxyTargetClass进行修改。 如果想修改的话在Spring配置文件中添加spring.aop.proxy-target-classfalse。
启动流程
SpringApplication.run方法创建一个SpringApplicationSpringApplication构造函数里设置类加载器判断当前容器类型(就是判断当前容器是否包含指定的类)通过getSpringFactoriesInstances加载并设置初始化器(Initializer)和监听器(Listener),并设置主类。 再调用一个重载的run方法获取SpringApplicationRunListeners监听器并启动准备环境对象在此方法里面发布一个创建Spring容器准Spring容器设置一些参数执行Spring容器的刷新方法。 重写finishRefresh创建启动Tomcat或相关容器发布ServletWebServerInitializedEvent事件。 发布ApplicationReadyEvent事件.
1、调用有SpringBootApplication注解的启动类的main方法
2、通过调用SpringApplication内部的run()方法构建SpringApplication对象。创建SpringApplication对象2.1 PrimarySources 不为空将启动类赋值给primarySources 对象。2.2 从classpath类路径推断Web应用类型有三种Web应用类型NONE、SERVLET、REACTIVE2.3 初始化bootstrapRegistryInitializers2.4 初始化ApplicationContextInitializer集合2.5 初始化ApplicationListener2.6 获取StackTraceElement数组遍历通过反射获取堆栈中有main方法A的。
3、调用SpringBootApplication的run方法。
4、long startTime System.nanoTime(); 记录项目启动时间。
5、通过BootstrapRegistryInitializer来初始化DefaultBootstrapContext
6、getRunListeners(args)获取SpringApplicationRunListeners监听器
7、 listeners.starting()触发ApplicationStartingEvent事件
8、prepareEnvironment(listeners, bootstrapContext, applicationArguments) 将配置文件读取到容器中返回ConfigurableEnvironment 对象。
9、printBanner(environment) 打印Banner图即SpringBoot启动时的图案。
10、根据WebApplicationType从ApplicationContextFactory工厂创建ConfigurableApplicationContext并设置ConfigurableApplicationContext中的ApplicationStartup为DefaultApplicationStartup
11、 调用prepareContext()初始化context等打印启动日志信息启动Profile日志信息并为BeanFactory中的部分属性赋值。
12、刷新容器在该方法中集成了Tomcat容器
13、加载SpringMVC.
14、刷新后的方法空方法给用户自定义重写afterRefresh
15、Duration timeTakenToStartup Duration.ofNanos(System.nanoTime() - startTime)算出启动花费的时间。
16、打印日志Started xxx in xxx seconds (JVM running for xxxx)
17、listeners.started(context, timeTakenToStartup)触发ApplicationStartedEvent事件监听。上下文已刷新应用程序已启动。
18、调用ApplicationRunner和CommandLineRunner
19、返回上下文。配置文件加载顺序
classpath:/,classpath:/config/,file:./,file:./config/ 如果配置了spring.config.location则按配置的来。
配置文件加载时机
SpringBoot获取到环境上下文对象时候会发布一个环境已准备的事件其中一个事件监听者ConfigFileApplicationListener会执行配置文件的加载并设置进环境遍历上下文中。
自动配置原理
SpringBoot的启动类上有SpringBootApplication这个注解。
Target({ElementType.TYPE})
Retention(RetentionPolicy.RUNTIME)
Documented
Inherited
SpringBootConfiguration
EnableAutoConfiguration
ComponentScan(excludeFilters {Filter(type FilterType.CUSTOM,classes {TypeExcludeFilter.class}
), Filter(type FilterType.CUSTOM,classes {AutoConfigurationExcludeFilter.class}
)}
)
public interface SpringBootApplication {......
}由SpringBootConfigurationEnableAutoConfigurationComponentScan注解组成。 SpringBootConfiguration其实就是一个Configuration表明这是一个配置类可以向容器注入组件。 EnableAutoConfiguration由AutoConfigurationPackage和Import({AutoConfigurationImportSelector.class})注解组成。
Target({ElementType.TYPE})
Retention(RetentionPolicy.RUNTIME)
Documented
Inherited
AutoConfigurationPackage
Import({AutoConfigurationImportSelector.class})
public interface EnableAutoConfiguration {......
}AutoConfigurationPackage内部用到了Import导入Registrar
Target({ElementType.TYPE})
Retention(RetentionPolicy.RUNTIME)
Documented
Inherited
Import({Registrar.class})
public interface AutoConfigurationPackage {......
}被Configuration标注的类会在Spring刷新时候invokeBeanFactoryPostProcessors方法中被解析解析入口是BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistryConfigurationClassPostProcessor类实现。 Dubbo
入门使用案例
SpringCloud
常用组件 Netflix Eureka服务注册中心 Netflix Ribbon客户端负载均衡 OpenFeign声明式的 HTTP 客户端 Netflix Hystrix断路器模式 Spring Cloud Gateway网关路由 Spring Cloud Sleuth分布式链路追踪 Spring Cloud Config配置中心 Spring Cloud Bus消息总线 Spring Cloud Security安全框架
Netty
netty工作流程 Netty 起多少线程?何时启动
Netty的默认启动了电脑可用线程数的两倍在调用了bind方法的时候执行。
Seata
Seata术语 TC (Transaction Coordinator) - 事务协调者 维护全局和分支事务的状态驱动全局事务提交或回滚。 TM (Transaction Manager) - 事务管理器发起者同时也是RM的一种 定义全局事务的范围开始全局事务、提交或回滚全局事务。 RM (Resource Manager) - 资源管理器每个参与事务的微服务 管理分支事务处理的资源与TC交谈以注册分支事务和报告分支事务的状态并驱动分支事务提交或回滚。
通过TM向TC注册并开启全局事务后调用StockRM执行本地事务并记录它的回滚日志通知TC。 然后调用OrderRM执行本地事务并记录回滚日志通知TC然后有Order服务调用Account服务Account记录本地日志后。 通知TC当TC收到所有的事务参与者StockOrderAccount的执行成功消息然后就向各个事务参与者发送提交的消息如果有任何一个服务有失败或超时TC将向所有的事务参与者发送Rollback的消息。
XA 模式
一阶段 事务协调者通知每个事物参与者执行本地事务 本地事务执行完成后报告事务执行状态给事务协调者此时事务不提交继续持有数据库锁二阶段 事务协调者基于一阶段的报告来判断下一步操作 如果一阶段都成功则通知所有事务参与者提交事务 如果一阶段任意一个参与者失败则通知所有事务参与者回滚事务
是一种强一致性的标准
AT模式
阶段一RM的工作 注册分支事务 记录undo-log数据快照 执行业务sql并提交 报告事务状态阶段二提交时RM的工作 删除undo-log即可阶段二回滚时RM的工作 根据undo-log恢复数据到更新前
AT模式下当前分支事务执行流程如下
一阶段
1TM发起并注册全局事务到TC
2TM调用分支事务
3分支事务准备执行业务SQL
4RM拦截业务SQL根据where条件查询原始数据形成快照。 5RM执行业务SQL提交本地事务释放数据库锁。此时 money 90
6RM报告本地事务状态给TC
二阶段
1TM通知TC事务结束
2TC检查分支事务状态
a如果都成功则立即删除快照
b如果有分支事务失败需要回滚。读取快照数据{“id”: 1, “money”: 100}将快照恢复到数据库。此时数据库再次恢复为100
A服务的TM向TC申请开启一个全局事务TC就会创建一个全局事务并返回一个唯一的XID。 服务的RM向TC注册分支事务并及其纳入XID对应全局事务的管辖。 A服务执行分支事务向数据库做操作。 A服务开始远程调用B服务此时XID会在微服务的调用链上传播。 B服务的RM向TC注册分支事务并将其纳入XID对应的全局事务的管辖。 B服务执行分支事务向数据库做操作。 全局事务调用链处理完毕TM根据有无异常向TC发起全局事务的提交或者回滚。 TC协调其管辖之下的所有分支事务决定是否回滚。
Seata入门
Seata入门
Zookeeper
基本命令
ls / 查看Zookeeper中包含的key
create : 在树中的某个位置创建一个节点
delete : 删除一个节点存在测试节点是否存在于某个位置
get data : 从节点读取数据
set data 将数据写入节点
get children : 检索节点的子节点列表
sync : 等待数据被传播命令基本语法功能描述help显示所有操作命令ls path使用 ls 命令来查看当前 znode 的子节点 [可监听]-w监听子节点变化-s 附加次级信息create普通创建s 含有序列-e 临时重启或者超时消失get path获得节点的值 [可监听]-w 监听节点内容变化-s 附加次级信息set设置节点的具体值stat查看节点状态delete删除节点deleteall递归删除节点
Zookeeper入门
Zookeeper的应用场景
统一配置管理统一命名服务分布式锁集群管理分布式队列数据发布订阅
zk中节点znode的类型 1、持久节点创建出的节点在会话结束后依然存在。保存数据
2、持久序号节点创建出的节点根据先后顺序会在节点之后带上一个数值越后执行数值越大适用于分布式锁的应用场景-单调递增
3、临时节点临时节点是在会话结束后自动被删除的通过这个特性zk可以实现服务注册与发现的效果。
4、临时序号节点跟持久序号节点相同适用于临时的分布式锁 5、Container节点3.5.3版本新增Container容器节点当容器中没有任何子节点该容器节点会被zk定期删除
6、TTL节点可以指定节点的到期时间到期后被zk定时删除。只能通过系统配置zookeeper.extendedTypeEnableetrue开启 创建顺序节点命令加上 “-s”参数create -s /module1/app app 意思是我们创建了一个持久顺序节点“/module1/app0000000001” 如果再执行上面命令 会生成节点 “/module1/app0000000002”同理 如果我们 create -s后面添加 -e 参数就表示我们创建了一个临时节点。 zookeeper集群中的节点有三种角色
Leader处理集群的所有事务请求集群中只有一个LeaderFollwoer只能处理读请求参与Leader选举Observer只能处理读请求提升集群读的性能但不能参与Leader选举
ZAB协议定义的四种节点状态
Looking选举状态FollowingFollowing节点从节点所处的状态LeadingLeader节点主节点所处状态
ZooKeeper特性
1、集群角色 2、原子性更新成功或失败没有部分结果。 3、高性能 4、高可靠一旦应用更新了它将从那时起一直存在直到客户端覆盖更新。 5、顺序一致性Zookeeper保证 来自客户端的更新将按发送顺序处理。 6、及时性 系统的客户视图保证在特定时间范围内是最新的。 7、数据模型和分层命名空间树结构 8、watch机制数据变更监听机制
ZooKeeper是弱一致性能保证最终一致性。
zookeeper使用的ZAB协议进行主从数据同步ZAB协议认为只要是过半数节点写入成为数据就算写成功了然后会告诉客户端A数据写入成功如果这个时候客户端B恰好访问到还没同步最新数据的zookeeper节点那么读到的数据就是不一致性的因此zookeeper无法保证写数据的强一致性只能保证最终一致性而且可以保证同一客户端的顺序一致性。
但也可以支持强一致性通过sync()方法与Leader节点同步后可保证当前节点数据与Leader一致。
加锁使用
XXL-JOB
实现方式
继承抽象类IJobHandler中的execute()方法IJobHandler还有init()和destory()方法放入bean容器为Job方法添加注解 “XxlJob(value“自定义jobhandler名称”, init “JobHandler初始化方法”, destroy “JobHandler销毁方法”)”
路由策略当执行器集群部署时提供丰富的路由策略包括
FIRST第一个固定选择第一个机器
LAST最后一个固定选择最后一个机器
ROUND轮询
RANDOM随机随机选择在线的机器
CONSISTENT_HASH一致性HASH每个任务按照Hash算法固定选择某一台机器且所有任务均匀散列在不同机器上。
LEAST_FREQUENTLY_USED最不经常使用使用频率最低的机器优先被选举
LEAST_RECENTLY_USED最近最久未使用最久未使用的机器优先被选举
FAILOVER故障转移按照顺序依次进行心跳检测第一个心跳检测成功的机器选定为目标执行器并发起调度
BUSYOVER忙碌转移按照顺序依次进行空闲检测第一个空闲检测成功的机器选定为目标执行器并发起调度
SHARDING_BROADCAST(分片广播)广播触发对应集群中所有机器执行一次任务同时系统自动传递分片参数可根据分片 参数开发分片任务
任务超时时间支持自定义任务超时时间任务运行超时将会主动中断任务
失败重试次数支持自定义任务失败重试次数当任务失败时将会按照预设的失败重试次数主动进行重试
设计模式
Spring设计模式
1工厂模式Spring使用工厂模式通过BeanFactory和ApplicationContext来创建对象
2单例模式Bean默认为单例模式
3策略模式例如Resource的实现类针对不同的资源文件实现了不同方式的资源获取策略
4代理模式Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术
5模板方法可以将相同部分的代码放在父类中而将不同的代码放入不同的子类中用来解决代码重复的问题。比如RestTemplate, JmsTemplate, JpaTemplate
6适配器模式Spring AOP的增强或通知Advice使用到了适配器模式Spring MVC中也是用到了适配器模式适配Controller
7观察者模式Spring事件驱动模型就是观察者模式的一个经典应用。
8桥接模式可以根据客户的需求能够动态切换不同的数据源。比如我们的项目需要连接多个数据库客户在每次访问中根据需要会去访问不同的数据库
责任链
spring中的设计模式 Mybatis设计模式
工厂方法模式Mybatis中的SqlSessionFactory就是使用工厂方法模式实现的。 代理模式使用动态代理为Mapper接口创建代理对象代理对象自动实现Mapper接口的所有方法并将方法与SqlSession相关联。 装饰器模式Mybatis中使用装饰器模式来实现插件功能插件可以动态地增加或修改Sql语句的执行逻辑。 模板方法模式MappedStatement和BaseStatementHandler都实现了一套通用的Sql语句处理逻辑而将一些特定的处理逻辑留给子类来实现。因此这两个模块是模板方法模式的一个典型实现。 单例模式 组合模式例如SqlNode和各个子类ChooseSqlNode等 1、Builder模式例如SqlSessionFactoryBuilder、XMLConfigBuilder、XMLMapperBuilder、XMLStatementBuilder、CacheBuilder
2、工厂模式例如SqlSessionFactory、ObjectFactory、MapperProxyFactory
3、单例模式例如LogFactory、ErrorContext
4、代理模式mybatis实现的核心比如MapperProxy、ConnectionLogger、用的jdk的动态代理还有executor.loader包使用了cglib或者javassist达到延迟加载的效果
5、组合模式例如SqlNode和各个子类ChooseSqlNode等
6、模板方法模式例如BaseExecutor和SimpleExecutor还有BaseTypeHandler和所有的子类例如IntegerTypeHandler
7、适配器模式例如Log的Mybatis接口和它对jdbc、log4j等各种日志框架的适配实现
8、装饰者模式例如Cache包中的cache.decorators子包中的各个装饰者的实现
9、迭代器模式例如迭代器模式PropertyTokenizer
原型模式:复制 静态内部类单例 工厂模式 策略模式登录接口不同
动态代理的理解和具体实现
JDK实现动态代理
jdk动态代理是jdk原生就支持的一种代理方式它的实现原理就是通过让target类和代理类实现同一接口代理类持有target对象来达到方法拦截的作用这样通过接口的方式有两个弊端一个是必须保证target类有接口第二个是如果想要对target类的方法进行代理拦截那么就要保证这些方法都要在接口中声明实现上略微有点限制
首先创建一个InvocationHandler 实现类调用Proxy#newProxyInstance方法传入和被代理对象使用相同的类加载器,和被代理对象具有相同的行为。实现相同的接口,InvocationHandler实现类即可返回目标类的动态代理。
Cglib实现动态代理
它的底层使用ASM在内存中动态的生成被代理类的子类使用CGLIB即使代理类没有实现任何接口也可以实现动态代理功能。CGLIB具有简单易用它的运行速度要远远快于JDK的Proxy动态代理。 cglib有两种可选方式继承和引用。第一种是基于继承实现的动态代理所以可以直接通过super调用target方法但是这种方式在spring中是不支持的因为这样的话这个target对象就不能被spring所管理所以cglib还是才用类似jdk的方式通过持有target对象来达到拦截方法的效果。
CGLIB的核心类
net.sf.cglib.proxy.Enhancer – 主要的增强类net.sf.cglib.proxy.MethodInterceptor – 主要的方法拦截类它是Callback接口的子接口需要用户实现net.sf.cglib.proxy.MethodProxy – JDK的java.lang.reflect.Method类的代理类可以方便的实现对源对象方法的调用
使用cglib需要引入依赖
dependencygroupIdcglib/groupIdartifactIdcglib/artifactIdversion2.2.2/version
/dependency示例代码 import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;public class CglibProxyTest {static class CglibProxyService {public CglibProxyService(){}void sayHello(){System.out.println( hello !);}}static class CglibProxyInterceptor implements MethodInterceptor {Overridepublic Object intercept(Object sub, Method method,Object[] objects, MethodProxy methodProxy)throws Throwable {System.out.println(before hello);Object object methodProxy.invokeSuper(sub, objects);System.out.println(after hello);return object;}}public static void main(String[] args) {// 通过CGLIB动态代理获取代理对象的过程Enhancer enhancer new Enhancer();// 设置enhancer对象的父类enhancer.setSuperclass(CglibProxyService.class);// 设置enhancer的回调对象enhancer.setCallback(new CglibProxyInterceptor());// 创建代理对象CglibProxyService proxy (CglibProxyService)enhancer.create();System.out.println(CglibProxyService.class);System.out.println(proxy.getClass());// 通过代理对象调用目标方法proxy.sayHello();}
}都无法完成Static和private方法的代理。 JDK动态代理和CGLIB动态代理的区别是什么
支持的接口和类
JDK动态代理要求目标类必须实现一个或多个接口。CGLIB动态代理则不依赖于接口即使目标类没有实现接口也可以通过设置回调接口间接实现代理。
性能
在早期CGLIB动态代理的性能通常比JDK动态代理高因为CGLIB动态代理是通过继承目标类并修改其字节码来实现代理的。但是从JDK 1.7开始JDK动态代理的反射底层进行了优化使得性能得到了显著提升在某些情况下甚至超过了CGLIB动态代理。
对目标类的限制
JDK动态代理要求目标类不能被final修饰且目标类中的方法也不能被final修饰。CGLIB动态代理则要求目标类不能被final修饰但目标类中的方法可以。
生成代理的方式
JDK动态代理利用反射机制生成一个包含被代理对象所有接口的代理类并覆盖接口中的所有方法。 JDK动态代理是基于Java反射机制实现的它要求目标类必须实现一个或多个接口代理对象在运行时动态创建通过实现目标类接口的方式来代理目标类。CGLIB动态代理则是通过继承被代理类并修改其字节码来生成代理类从而实现代理。 CGLIB代理则是基于ASM字节码框架实现的它可以代理没有实现接口的目标类。CGLIB在运行时通过动态生成目标类的子类来实现代理。
适用场景
JDK动态代理适合于需要代理的类已经实现了接口的情况。CGLIB动态代理则更适合于没有实现接口的目标类或者需要对目标类进行更深层次修改的情况。
总结 一个是接口实现方式一个是类的继承方式动态生成类的子类对象
动态代理案例解析博客
数据库
MySQL中myisam与innodb的区别
1.InnoDB支持事物而MyISAM不支持事物
2.InnoDB支持行级锁而MyISAM支持表级锁
3.InnoDB支持MVCC, 而MyISAM不支持
4.InnoDB支持外键而MyISAM不支持
5.InnoDB不支持全文索引而MyISAM支持。
事物的4种隔离级别 读未提交(RU) 读已提交(RC) 可重复读(RR) 串行
Explain
通过EXPLAIN可以分析出以下结果
表的读取顺序 数据读取操作的操作类型 哪些索引被实际使用 表之间的引用 每张表有多少行被优化器查询 id表示查询语句的序号自动分配顺序递增值越大执行优先级越高。id相同时优先级由上而下。select_type列 select_type表示查询类型常见的有SIMPLE简单查询、PRIMARY主查询、SUBQUERY子查询、UNION联合查询、UNION RESULT联合临时表结果等。table列 table表示SQL语句查询的表名、表别名、临时表名。partitions列 partitions表示SQL查询匹配到的分区没有分区的话显示NULL。type列 type表示表连接类型或者数据访问类型就是表之间通过什么方式建立连接的或者通过什么方式访问到数据的。具体有以下值性能由好到差依次是 system const eq_ref ref ref_or_null index_merge range index ALL system 当表中只有一行记录也就是系统表是 const 类型的特列。const 表示使用主键或者唯一性索引进行等值查询最多返回一条记录。性能较好推荐使用。eq_ref 表示表连接使用到了主键或者唯一性索引下面的SQL就用到了user表主键id。ref 表示使用非唯一性索引进行等值查询。ref_or_null 表示使用非唯一性索引进行等值查询并且包含了null值的行。index_merge 表示用到索引合并的优化逻辑即用到的多个索引。range 表示用到了索引范围查询。index 表示使用索引进行全表扫描。ALL 表示全表扫描性能最差。 possible_keys列 表示可能用到的索引列实际查询并不一定能用到。key列 表示实际查询用到索引列。key_len列 表示索引所占的字节数。ref列 表示where语句或者表连接中与索引比较的参数常见的有const常量、func函数、字段名。如果没用到索引则显示为NULL。rows列 表示执行SQL语句所扫描的行数。filtered列 表示按条件过滤的表行的百分比。Extra列 表示一些额外的扩展信息不适合在其他列展示却又十分重要。 Using where 表示使用了where条件搜索但没有使用索引。Using index 表示用到了覆盖索引即在索引上就查到了所需数据无需二次回表查询性能较好。Using filesort 表示使用了外部排序即排序字段没有用到索引。Using temporary 表示用到了临时表下面的示例中就是用到临时表来存储查询结果。Using join buffer 表示在进行表关联的时候没有用到索引使用了连接缓存区存储临时结果。Using index condition 表示用到索引下推的优化特性。
explain案例解析
explain案例解析
Mysql面试题
MVCC
readView
MySQL中有哪些锁
全局锁 锁整个数据库表锁 锁整个表行锁 Record lock记录锁锁定单个行记录。 Gap lock间隙锁锁定某个记录范围之间的间隙。 Next-key lock下一个键锁是记录锁和间隙锁的组合锁定一个记录和它之前的间隙。
分库分表会带来哪些问题
事务一致性问题数据组装跨节点分页、排序、函数问题全局主键避重问题
分库分表后如何保证一致性分布式事务
索引失效原因有哪些 模糊查询的前导通配符 当使用模糊查询如 LIKE ‘%abc’时索引失效因为通配符在前面会导致索引无法使用。 未使用索引字段进行过滤 如果查询条件没有使用到创建的索引字段数据库可能不会使用该索引。 数据类型不匹配 如果查询条件的数据类型与索引字段的数据类型不匹配数据库无法使用索引。 使用函数操作 如果查询条件中对字段进行了函数操作如 LOWER(column)索引可能失效因为数据库无法直接使用索引。 OR 运算 在 OR 运算中如果其中一个条件使用了索引而另一个条件没有使用索引整个查询可能会导致索引失效 使用 NOT 运算 NOT 运算通常会使索引失效因为数据库无法使用索引来高效处理 NOT 运算。 表连接中的索引失效 如果在表连接查询中连接条件中的字段没有索引可能导致索引失效。
引擎
常用的通用 SQL 函数
事务
ACID的理解
MySQL中哪个引擎支持ACID哪个不支持
表中有100万数据如何从数据库层面做优化
如何做读写分离Mysql主从复制 主数据库有个bin log二进制文件纪录了所有增删改SQL语句。binlog线程 从数据库把主数据库的bin log文件的SQL 语句复制到自己的中继日志 relay logio线程 从数据库的relay log重做日志文件再执行一次这些sql语句。Sql执行线程
从复制过程分了五个步骤进行 主库的更新SQL(update、insert、delete)被写到binlog 从库发起连接连接到主库。 此时主库创建一个binlog dump thread把bin log的内容发送到从库。 从库启动之后创建一个I/O线程读取主库传过来的bin log内容并写入到relay log 从库还会创建一个SQL线程从relay log里面读取内容从ExecMasterLog_Pos位置开始执行读取到的更新事件将更新内容写入到slave的db
搭建博客
MQ
相关知识点
RabbitMq知识点
生产者消息如何运转 1、 Producer先连接到Broker,建立连接Connection,开启一个信道(Channel)。
2、 Producer声明一个交换器并设置好相关属性。
3、 Producer声明一个队列并设置好相关属性。
4、 Producer通过路由键将交换器和队列绑定起来。
5、 Producer发送消息到Broker,其中包含路由键、交换器等信息。
6、 相应的交换器根据接收到的路由键查找匹配的队列。
7、 如果找到将消息存入对应的队列如果没有找到会根据生产者的配置丢弃或者退回给生产者。
8、 关闭信道。
9、 管理连接。
消息什么时候刷到磁盘 写入文件前会有一个Buffer,大小为1M,数据在写入文件时首先会写入到这个Buffer如果Buffer已满则会将Buffer写入到文件未必刷到磁盘。 有个固定的刷盘时间25ms,也就是不管Buffer满不满每个25msBuffer里的数据及未刷新到磁盘的文件内容必定会刷到磁盘。 每次消息写入后如果没有后续写入请求则会直接将已写入的消息刷到磁盘使用Erlang的receive x after 0实现只要进程的信箱里没有消息则产生一个timeout消息而timeout会触发刷盘操作。
Rabbit死信的来源
消息 TTL 过期队列达到最大长度(队列满了无法再添加数据到 mq 中)消息被拒绝(basic.reject 或 basic.nack)并且 requeuefalse
SpringBoot发布确认
在配置文件当中需要添加 spring.rabbitmq.publisher-confirm-typecorrelated
NONE 禁用发布确认模式是默认值CORRELATED 发布消息成功到交换器后会触发回调方法SIMPLE
我们先来介绍下RabbitMQ三种部署模式 单节点模式 最简单的情况非集群模式节点挂了消息就不能用了。业务可能瘫痪只能等待。 普通模式 消息只会存在与当前节点中并不会同步到其他节点当前节点宕机有影响的业务会瘫痪只能等待节点恢复重启可用必须持久化消息情况下。 镜像模式 消息会同步到其他节点上可以设置同步的节点个数但吞吐量会下降。属于RabbitMQ的HA方案
惰性队列
消息村磁盘不存内存。
一些面试题整理
RocketMq知识点
nameserver 默认使⽤ 9876 端⼝ master 默认使⽤ 10911 端⼝ slave 默认使⽤11011 端⼝
RocketMq启动流程
启动NameServerNameServer起来后监听端⼝等待Broker、Producer、Consumer连上来相当于⼀个路由控制中⼼。Broker启动跟所有的NameServer保持⻓连接定时发送⼼跳包。⼼跳包中包含当前Broker信息(IP端⼝等)以及存储所有Topic信息。注册成功后NameServer集群中就有Topic跟Broker的映射关系。收发消息前先创建Topic创建Topic时需要指定该Topic要存储在哪些Broker上也可以在发送消息时⾃动创建Topic。Producer发送消息启动时先跟NameServer集群中的其中⼀台建⽴⻓连接并从NameServer中获取当前发送的Topic存在哪些Broker上轮询从队列列表中选择⼀个队列然后与队列所在的Broker建⽴⻓连接从⽽向Broker发消息。Consumer跟Producer类似跟其中⼀台NameServer建⽴⻓连接获取当前订阅Topic存在哪些Broker上然后直接跟Broker建⽴连接通道开始消费消息。
Dleger⾼可⽤集群搭建搭建集群配置文件:
#所属集群名字名字⼀样的节点就在同⼀个集群内
brokerClusterNamerocketmq-cluster
#broker名字名字⼀样的节点就是⼀组主从节点。
brokerNamebroker-a
#brokerid,0就表示是Master0的都是表示 Slave
brokerId0
#nameServer地址分号分割
namesrvAddrworker1:9876;worker2:9876;worker3:9876
#在发送消息时⾃动创建服务器不存在的topic默认创建的队列数
defaultTopicQueueNums4
#是否允许 Broker ⾃动创建Topic建议线下开启线上关闭
autoCreateTopicEnabletrue
#是否允许 Broker ⾃动创建订阅组建议线下开启线上关闭
autoCreateSubscriptionGrouptrue
#Broker 对外服务的监听端⼝
listenPort10911
#删除⽂件时间点默认凌晨 4点
deleteWhen04
#⽂件保留时间默认 48 ⼩时
fileReservedTime120
#commitLog每个⽂件的⼤⼩默认1G
mapedFileSizeCommitLog1073741824
#ConsumeQueue每个⽂件默认存30W条根据业务情况调整
mapedFileSizeConsumeQueue300000
#destroyMapedFileIntervalForcibly120000
#redeleteHangedFileInterval120000
#检测物理⽂件磁盘空间
diskMaxUsedSpaceRatio88
#存储路径
storePathRootDir/opt/rocketmq/store
#commitLog 存储路径
storePathCommitLog/opt/rocketmq/store/commitlog
#消费队列存储路径存储路径
storePathConsumeQueue/opt/rocketmq/store/consumequeue
#消息索引存储路径
storePathIndex/opt/rocketmq/store/index
#checkpoint ⽂件存储路径
storeCheckpoint/opt/rocketmq/store/checkpoint
#abort ⽂件存储路径
abortFile/opt/rocketmq/store/abort
#限制的消息⼤⼩
maxMessageSize65536
#flushCommitLogLeastPages4
#flushConsumeQueueLeastPages2
#flushCommitLogThoroughInterval10000
#flushConsumeQueueThoroughInterval60000
#Broker 的⻆⾊
#- ASYNC_MASTER 异步复制Master
#- SYNC_MASTER 同步双写Master
#- SLAVE
brokerRoleASYNC_MASTER
#刷盘⽅式
#- ASYNC_FLUSH 异步刷盘
#- SYNC_FLUSH 同步刷盘
flushDiskTypeASYNC_FLUSH
#checkTransactionMessageEnablefalse
#发消息线程池数量
#sendMessageThreadPoolNums128
#拉消息线程池数量
#pullMessageThreadPoolNums128topic消息主题一级消息类型通过Topic对消息进行分类。 Tag消息标签二级消息类型用来进一步区分某个Topic下的消息分类。 key消息的业务标识由消息生产者Producer设置唯一标识某个业务逻辑
Tag 和 Key 的主要差别是使用场景不同Tag 用在 Consumer 代码中用于服务端消息过滤Key 主要用于通过命令进行查找消息。 RocketMQ 并不能保证 message id 唯一在这种情况下生产者在 push 消息的时候可以给每条消息设定唯一的 key, 消费者可以通过 message key 保证对消息幂等处理。
高可用
普通集群 只保存引用不保存队列实际内容镜像集群 保存队列实际内容主节点宕机从节点会替换。仲裁队列 主从同步一致性
RocketMQ原生API使用 gitcode
整合SpringAPI
几种消息机制: 批量消息 事务消息 顺序消息 延迟消息 消息重试 重试的消息会进⼊⼀个 “%RETRY%”ConsumeGroup 的队列中。然后RocketMQ默认允许每条消息最多重试16次。时间间隔与延时消息一致。 死信队列
普通消息
顺序消息
广播消息
延时消息
批量消息
事务消息消息存储文件 commitLog rocketmq 的消息存储在 commitLog 中broker 默认会给 commitLog 申请 1G的磁盘空间。这是为了保证存储空间是有序的。 如果 commitLog 已经满了会继续创建第二个 commitLog且它的文件名最后几位是上一个 commitLog 的最大偏移量 masOffset。查找的时候如果第一个文件中没找到那么会计算它的最大偏移量获取下一个要查找的 commitLog 的名称。
索引文件 IndexFile索引文件是用来支持快速地查找消息的。 rocketmq 支持两种查询方式根据key根据time RocketMQ消息消费本质上是基于的拉pull模式 Kafka知识点
Consumer GroupCG消费者组由多个consumer组成。形成一个消费者组的条件是所有消费者的groupid相同。
消费者组内每个消费者负责消费不同分区的数据一个分区只能由一个组内消费者消费。消费者组之间互不影响。所有的消费者都属于某个消费者组即消费者组是逻辑上的一个订阅者。
生产者如何提高吞吐量
• batch.size批次大小默认16k • linger.ms等待时间修改为5-100ms一次拉一个来了就走 • compression.type压缩snappy 生产经验——生产者如何提高吞吐量 • RecordAccumulator缓冲区大小修改为64m // batch.size批次大小默认 16Kproperties.put(ProducerConfig.BATCH_SIZE_CONFIG, 16384);// linger.ms等待时间默认 0properties.put(ProducerConfig.LINGER_MS_CONFIG, 1);// RecordAccumulator缓冲区大小默认 32Mbuffer.memoryproperties.put(ProducerConfig.BUFFER_MEMORY_CONFIG,33554432);// compression.type压缩默认 none可配置值 gzip、snappy、 lz4 和 zstdproperties.put(ProducerConfig.COMPRESSION_TYPE_CONFIG,snappy);Kafka总体工作流程
1 Kafka集群的每个broker启动之后都会向zookeeper进行注册。
2 注册完毕之后开始选择controller节点争先抢占方式。
3 选举出来的controller监听/brokers/ids/节点的变化。
4 监控完毕之后根据选举规则开始真正的选举Leader。
5 Controller将节点的Leader信息和isr信息写到zookeeper上。
6 其它的controller节点会冲zookeeper上拉取数据进行同步防止controllerLeader挂了随时上位。
7 生产者往集群发送数据发送数据之后Leader主动与Follower进行同步底层通过LOG进行存储实际为segment分为.log文件和.index文件再进行应答。
8 当Leader节点挂了之后controller监控到节点变化。
9 Controller从zookeeper上拉取Leader信息和isr信息。
10 Controller根据拉取的信息和选举规则再重新选举Leader。
11 选举出来新的Leader之后更新zookeeper中的信息。 Kafka 分区中的所有副本统称为 ARAssigned Repllicas。 AR ISR OSR ISR表示和 Leader 保持同步的 Follower 集合。如果 Follower 长时间未向 Leader 发送通信请求或同步数据则该 Follower 将被踢出 ISR。该时间阈值由replica.lag.time.max.ms 参数设定默认 30s。Leader 发生故障之后就会从 ISR 中选举新的 Leader。 OSR表示 Follower 与 Leader 副本同步时延迟过多的副本(速率和leader相差大于10秒的follower)。 Kafka 中默认的日志保存时间为 7 天可以通过调整如下参数修改保存时间。
log.retention.hours最低优先级小时默认 7 天。log.retention.minutes分钟。log.retention.ms最高优先级毫秒。log.retention.check.interval.ms负责设置检查周期默认 5 分钟。
delete 日志删除将过期数据删除 log.cleanup.policy delete 所有数据启用删除策略
基于时间默认打开。以 segment 中所有记录中的最大时间戳作为该文件时间戳。基于大小默认关闭。超过设置的所有日志总大小删除最早的 segment。
Kafka分区分配策略
Range策略(范围)。这是最常用的分配策略它确保每个消费者线程消费的分区数量大致相等。Range策略首先对所有分区按照序号排序然后对消费者线程按照字典顺序排序。接下来它为每个消费者线程分配一定数量的分区直到所有分区被分配完毕。如果还有剩余分区那么排序靠前的消费者线程会被分配额外的分区。Round Robin策略(循环)。在这种策略下每个消费者线程依次按顺序获得一个分区。当消费者数量多于分区数量时多余的消费者将没有分配到任何分区。把所有的 partition 和所有的consumer 都列出来然后按照 hashcode 进行排序最后 通过轮询算法来分配 partition 给到各个消费者。Sticky策略(粘性)。这种策略下消费者会尽量保持与之前分配的分区相同。如果有新的消费者加入或有消费者退出分区的重新分配会尽量减少这对于需要保持状态的应用程序比较有用。Cooperative策略。这是Kafka 2.4.0版本引入的新策略它通过考虑消费者的健康状况、处理速度、网络延迟等因素动态地进行分区分配以实现更好的负载均衡和消费者协作。
默认情况下Kafka使用Range分配策略但也可以通过配置参数partition.assignment.strategy来自定义分配策略 roducer将消息推送到brokerconsumer从broker拉取消息。 Kafka面试题
角色
RabbitMq
生产者Publisher发送消息的应用。消费者Consumer接收消息的应用。连接Connection它使用TCP进行可靠的传输连接RabbitMQ和应用服务器。信道Channel连接里的一个虚拟通道通过消息队列发送或者接收消息时都是通过信道进行的。Connection内部建立的逻辑连接通常每个线程创建单独的Channel。交换机Exchange交换机负责从生产者那里接收消息并根据交换类型分发到对应的消息队列里。换种理解快递分拣中心。队列Queue它们存储应用程序发送的消息。代理Broker接收和分发消息的应用RabbitMQ Server就是Message Broker。消息Message由生产者通过RabbitMQ发送给消费者的信息。绑定Binding绑定是交换机用来将消息路由到队列的规则。路由键Routing Key消息的目标地址。换种理解寄快递填写的地址。
RocketMQ Producer消息发布的⻆⾊⽀持分布式集群⽅式部署。Producer通过MQ的负载均衡模块选择相 应的Broker集群队列进⾏消息投递投递的过程⽀持快速失败并且低延迟。 Consumer消息消费的⻆⾊⽀持分布式集群⽅式部署。⽀持以push推pull拉两种模式对消息进⾏消费。同时也⽀持集群⽅式和⼴播⽅式的消费它提供实时消息订阅机制可以满⾜⼤多数⽤户的需求。 NameServerNameServer是⼀个⾮常简单的Topic路由注册中⼼其⻆⾊类似Dubbo中的zookeeper⽀持Broker的动态注册与发现主要包括两个功能 Broker管理NameServer接受Broker集群的注册信息并且保存下来作为路由信息的基本数据。然后提供⼼跳检测机制检查Broker是否还存活路由信息管理每个NameServer将保存关于Broker集群的整个路由信息和⽤于客户端查询的队列信息。然后Producer和Conumser通过NameServer就可以知道整个Broker集群的路由信息从⽽进⾏消息的投递和消费。 NameServer通常也是集群的⽅式部署各实例间相互不进⾏信息通讯。Broker是向每⼀台NameServer注册⾃⼰的路由信息所以每⼀个NameServer实例上⾯都保存⼀份完整的路由信息。当某个NameServer因某种原因下线了Broker仍然可以向其它NameServer同步其路由信息Producer,Consumer仍然可以动态感知Broker的路由的信息。多个Namesrv实例组成集群但相互独⽴没有信息交换。 BrokerServerBroker主要负责消息的存储、投递和查询以及服务⾼可⽤保证为了实现这些功能Broker包含了以下⼏个重要⼦模块。 Remoting Module整个Broker的实体负责处理来⾃clients端的请求。Client Manager负责管理客户端(Producer/Consumer)和维护Consumer的Topic订阅信息Store Service提供⽅便简单的API接⼝处理消息存储到物理硬盘和查询功能。HA Service⾼可⽤服务提供Master Broker 和 Slave Broker之间的数据同步功能。Index Service根据特定的Message key对投递到Broker的消息进⾏索引服务以提供消息的快速 查询。
Kafka
Broker代理
Kafka集群通常由多个代理组成以保持负载平衡。 Kafka代理是无状态的所以他们使用ZooKeeper来维护它们的集群状态。 一个Kafka代理实例可以每秒处理数十万次读取和写入每个Broker可以处理TB的消息而没有性能影响。 Kafka经纪人领导选举可以由ZooKeeper完成。
ZooKeeper
ZooKeeper用于管理和协调Kafka代理。 ZooKeeper服务主要用于通知生产者和消费者Kafka系统中存在任何新代理或Kafka系统中代理失败。 根据Zookeeper接收到关于代理的存在或失败的通知然后生产者和消费者采取决定并开始与某些其他代理协调他们的任务。
Producers生产者
生产者将数据推送给经纪人。 当新代理启动时所有生产者搜索它并自动向该新代理发送消息。 Kafka生产者不等待来自代理的确认并且发送消息的速度与代理可以处理的一样快。
Consumers消费者
因为Kafka代理是无状态的这意味着消费者必须通过使用分区偏移来维护已经消耗了多少消息。 如果消费者确认特定的消息偏移则意味着消费者已经消费了所有先前的消息。 消费者向代理发出异步拉取请求以具有准备好消耗的字节缓冲区。 消费者可以简单地通过提供偏移值来快退或跳到分区中的任何点。 消费者偏移值由ZooKeeper通知。
消息模型
RabbitMq
消息类型
direct如果路由键完全匹配消息就被投递到相应的队列fanout如果交换器收到消息将会广播到所有绑定的队列上topic 可以使来自不同源头的消息能够到达同一个队列。 使用 topic 交换器时可以使用通配符 *(星号)可以代替一个单词#(井号)可以替代零个或多个单词
当一个队列绑定键是#,那么这个队列将接收所有数据就有点像 fanout 了 如果队列绑定键当中没有#和*出现那么该队列绑定类型就是 direct 了
队列具备两种模式default 和 lazy(惰性队列 )。默认的为 default 模式。
队列的其他参数设置
MapString, Object args new HashMapString, Object();
args.put(x-queue-mode, lazy);
//设置队列的最大优先级 最大可以设置到 255 官网推荐 1-10 如果设置太高比较吃内存和 CPU
params.put(x-max-priority, 10);
channel.queueDeclare(myqueue, false, false, false, args);RocketMQ
Producer消息的发送者 负责⽣产消息举例发信者Consumer消息接收者负责消费消息举例收信者Broker暂存和传输消息负责存储消息举例邮局 Broker 在实际部署过程中对应⼀台服务器每个Broker 可以存储多个Topic的消息每个Topic的消息也可以分⽚存储于不同的 Broker。NameServer管理Broker举例各个邮局的管理机构Topic区分消息的种类⼀个发送者可以发送消息给⼀个或者多个Topic⼀个消息的接收者可以订阅⼀个或者多个Topic消息。 每个 topic 默认包含4个队列每个队列对应一个持久化文件queuelog它存储的是每条消息在commitlog中的位置等信息。Message Queue相当于是Topic的分区⽤于并⾏发送和接收消息。⼀个queueId就代表了⼀个MessageQueue。每个Topic中的消息地址存储于多个 Message Queue 中。
RocketMQ的概念模型详解
Kafka
死信队列
消息变成死信一般是由于以下几种情况
重试次数超限 消息在处理过程中多次重试仍然失败达到预定的重试次数上限消息被拒绝 Basic.Reject/Basic.Nack 并且设置 requeue 参数为 false 3.消息过期消息在队列中等待时间过长超过了设置的过期时间 队列满 当消息队列的长度达到上限时新的消息可能成为死信。 RocketMQ、kafka没有提供相应的设计。 kafka中的重试队列是我们通过业务实现的,自己新加几个重试主题,消费者消费失败后就将消息发给重试队列 怎么避免消息丢失/消息可靠性
消息丢失的三种情况
消息在传入过程中丢失MQ收到消息暂存内存中还没消费自己挂掉了内存中的数据搞丢消费者消费到了这个消息但还没来得及处理就挂了MQ以为消息已经被处理
也就是生产者丢失消息、消息列表丢失消息、消费者丢失消息
RabbitMq
针对生产者
一、开启RabbitMQ事务
可以选择用 RabbitMQ 提供的事务功能就是生产者发送数据之前开启 RabbitMQ 事务channel.txSelect然后发送消息如果消息没有成功被 RabbitMQ 接收到那么生产者会收到异常报错此时就可以回滚事务channel.txRollback然后重试发送消息如果收到了消息那么可以提交事务channel.txCommit。如果 txCommit 提交成功了则消息一定到达了 broker 了如果在 txCommit执行之前 broker 异常崩溃或者由于其他原因抛出异常这个时候我们便可以捕获异常通过 txRollback 回滚事务。
// 开启事务
channel.txSelect
try {// 这里发送消息
} catch (Exception e) {channel.txRollback// 这里再次重发这条消息}// 提交事务
channel.txCommit缺点 RabbitMQ 事务机制是同步的你提交一个事务之后会阻塞在那儿采用这种方式基本上吞吐量会下来因为太耗性能。
二、使用confirm机制
事务机制和 confirm 机制最大的不同在于事务机制是同步的你提交一个事务之后会阻塞在那儿但是confirm机制是异步的。 已经在事务模式的 channel 是不能再设置成 confirm 模式的即这两种模式是不能共存的。 在生产者开启了confirm模式之后每次写的消息都会分配一个唯一的id然后如果写入了rabbitmq之中rabbitmq会给你回传一个ack消息告诉你这个消息发送OK了如果rabbitmq没能处理这个消息会回调你一个nack接口告诉你这个消息失败了你可以进行重试。而且你可以结合这个机制知道自己在内存里维护每个消息的id如果超过一定时间还没接收到这个消息的回调那么你可以进行重发。 confirm 模式又包含三种模式 普通 confirm 模式每发送一条消息后调用 waitForConfirms()方法等待服务器端confirm。实际上是一种串行 confirm 了。 批量 confirm 模式每发送一批消息后调用 waitForConfirms()方法等待服务器端confirm。 异步 confirm 模式提供一个回调方法服务端 confirm 了一条或者多条消息后 Client 端会回调这个方法
confirm机制代码示例-知乎
confirm机制代码示例 针对RabbitMQ服务端
一、消息持久化
RabbitMQ 的消息默认存放在内存上面如果不特别声明设置消息不会持久化保存到硬盘上面的如果节点重启或者意外crash掉消息就会丢失。
所以就要对消息进行持久化处理。如何持久化下面具体说明下
要想做到消息持久化必须满足以下三个条件缺一不可。 Exchange 设置持久化 Queue 设置持久化 Message持久化发送发送消息设置发送模式deliveryMode2代表持久化消息
持久化基础参数示例 Spirng持久化示例
消息在正确存入RabbitMQ之后还需要有一段时间这个时间很短但不可忽视才能存入磁盘之中RabbitMQ并不是为每条消息都做fsync的处理可能仅仅保存到cache中而不是物理磁盘上在这段时间内RabbitMQ broker发生crash, 消息保存到cache但是还没来得及落盘那么这些消息将会丢失。那么这个怎么解决呢镜像队列
二、设置集群镜像模式
从consumer端来说如果这时autoAcktrue那么当consumer接收到相关消息之后还没来得及处理就crash掉了那么这样也算数据丢失这种情况也好处理只需将autoAck设置为false(方法定义如下)然后在正确处理完消息之后进行手动ackchannel.basicAck
三、消息补偿机制
生产端首先将业务数据以及消息数据入库需要在同一个事务中消息数据入库失败则整体回滚。 根据消息表中消息状态失败则进行消息补偿措施重新发送消息处理。 针对消费者
ACK确认机制
使用rabbitmq提供的ack机制服务端首先关闭rabbitmq的自动ack然后每次在确保处理完这个消息之后在代码里手动调用ack。这样就可以避免消息还没有处理完就ack。才把消息从内存删除。
这样就解决了即使一个消费者出了问题但不会同步消息给服务端会有其他的消费端去消费保证了消息不丢的case。
public static void main(String[] args) throws Exception {Channel channel RabbitMqUtils.getChannel();System.out.println(C1 等待接收消息处理时间较短);//消息消费的时候如何处理消息DeliverCallback deliverCallback(consumerTag, delivery)-{String message new String(delivery.getBody());System.out.println(接收到消息:message);/*** 1.消息标记 tag* 2.是否批量应答未应答消息*/channel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);//channel.basicNack();//channel.basicReject();};CancelCallback cancelCallback consumerTag - System.out.println(consumerTag消费者取消消费接口回调逻辑);//采用手动应答boolean autoAckfalse;channel.basicConsume(ACK_QUEUE_NAME,autoAck,deliverCallback,cancelCallback);
}总结 如果需要保证消息在整条链路中不丢失那就需要生产端、mq自身与消费端共同去保障。 生产端 对生产的消息进行状态标记开启confirm机制依据mq的响应来更新消息状态使用定时任务重新投递超时的消息多次投递失败进行报警。 mq自身 开启持久化并在落盘后再进行ack。如果是镜像部署模式需要在同步到多个副本之后再进行ack。 消费端 开启手动ack模式在业务处理完成后再进行ack并且需要保证幂等。
RocketMQ
Broker在把消息写入日志文件的过程中如果在刚收到消息时Broker异常宕机了那么内存中尚未写入磁盘的消息就会丢失了。 因此RocketMQ持久化消息分为两种同步刷盘和异步刷盘默认配置。 异步刷盘是指Broker收到消息后先存储到PageCache然后立即通知Producer消息已存储成功可以继续处理业务逻辑。 此后Broker会启动一个异步线程将消息持久化到磁盘。然而如果Broker在持久化到磁盘之前发生故障消息将会丢失。
生产者使用同步发送
SendReceipt sendReceipt producer.send(message);消费手动提交
consumer.registerMessageListener(new MessageListenerOrderly() {Overridepublic ConsumeOrderlyStatus consumeMessage(ListMessageExt msgs, ConsumeOrderlyContext context) {//自动提交context.setAutoCommit(false);for(MessageExt msg:msgs){System.out.println(收到消息内容 new String(msg.getBody()));}return ConsumeOrderlyStatus.SUCCESS;}
});RocketMQ可靠性
Kafka 重平衡会导致消息丢失同步异步提交代码块异步提交捕获异常处理异常再同步提交 consumer使用同步提交Offsetconsumer.commitSync() Producer设置ACK:
properties.put(ProducerConfig.ACKS_CONFIG, all);producer 级别acksall或者 request.required.acks-1同时发生模式为同步 producer.typesynctopic 级别设置 replication.factor3并且 min.insync.replicas2broker 级别关闭不完全的 Leader 选举即 unclean.leader.election.enablefalse
总结
rabbit
生产者 开启confirm、Message持久化发送
服务端 Exchange 、Queue消息持久化、设置集群镜像模式
消费者 ACK确认机制rocketmq同步刷盘、消费者确认、同步发送kafka
生产者 设置ACK
服务端设置ACK 01-1
消费者consumer.commitSync()同步提交offset顺序消费
要保证最终消费到的消息是有序的需要从Producer、Broker、Consumer三个步骤都保证消息有序才⾏。
RabbitMq :
需要保障以下几点
1、发送的顺序消息必须保证在投递到同一个队列且这个消费者只能有一个独占模式
2、然后同意提交可以合并一个大消息或拆分多个消息最好是拆分并且所有消息的会话ID一致
3、添加消息属性顺序表及的序号、本地顺序消息的size属性进行落库操作
4、并行进行发送给自身的延迟消息带上关键属性会话ID、SIZE进行后续处理消费
5、当收到延迟消息后根据会话ID、SIZE抽取数据库数据进行处理即可
6、定时轮询补偿机制对于异常情况
RocketMq
发生方使用 MessageQueueSelector 消费方使用MessageListenerOrderly
Message msg new Message(OrderTopicTest, order_orderId,KEY orderId,(order_orderId step j).getBytes(RemotingHelper.DEFAULT_CHARSET));
//消息队列的选择器
SendResult sendResult producer.send(msg, new MessageQueueSelector() {//第一个参数所有的消息第二个参数发送的消息第三个参数根据什么发送这里面传的是orderIdOverridepublic MessageQueue select(ListMessageQueue mqs, Message msg, Object arg) {Integer id (Integer) arg;int index id % mqs.size();return mqs.get(index);}//同一个订单id可以放到同一个队列里面去
}, orderId);consumer.registerMessageListener(new MessageListenerOrderly() {Overridepublic ConsumeOrderlyStatus consumeMessage(ListMessageExt msgs, ConsumeOrderlyContext context) {//自动提交context.setAutoCommit(true);for(MessageExt msg:msgs){System.out.println(收到消息内容 new String(msg.getBody()));}return ConsumeOrderlyStatus.SUCCESS;}
});Kafka
KafkaConsumerString, String kafkaConsumer new KafkaConsumer(properties);
// 消费某个主题的某个分区数据
ArrayListTopicPartition topicPartitions new ArrayList();
topicPartitions.add(new TopicPartition(first, 0));
kafkaConsumer.assign(topicPartitions);消息幂等性
消息传输保证层级 At most once:最多一次。消息可能会丢失单不会重复传输。 At least once最少一次。消息觉不会丢失但可能会重复传输。 Exactly once: 恰好一次每条消息肯定仅传输一次。
在互联⽹应⽤中尤其在⽹络不稳定的情况下消息队列 的消息有可能会出现重复这个重复简单可以概括为以下情况
发送时消息重复投递时消息重复负载均衡时消息重复包括但不限于⽹络抖动、Broker 重启以及订阅⽅应⽤重启
RocketMQ
RocketMQ支持消息查询的功能只要去RocketMQ查询一下是否已经发送过该条消息就可以了不存在则发送存在则不发送引入Redis在发送消息到RocketMQ成功之后向Redis中插入一条数据如果发送重试则先去Redis查询一个该条消息是否已经发送过了存在的话就不重复发送消息了
发送的时候为Message设置一个唯一的keys
RocketMQ幂等性
Kafka服务端使用幂等性 :开启参数 enable.idempotence 默认为 truefalse 关闭。 具有PID, Partition, SeqNumber相同主键的消息提交时Broker只会持久化一条。其中PID是Kafka每次重启都会分配一个新的Partition 表示分区号Sequence Number是单调自增的。所以Kafka幂等性只能保证的是在单分区单会话内不重复。 消费端 需要Kafka消费端将消费过程和提交offset过程做原子绑定。此时我们需要将Kafka的offset保存到支持事务的自定义介质比如MySQL
消息持久化
rabbitmq 定义队列、消息都为持久化才行
RocketMq
RocketMQ消息的存储分为三个部分
CommitLog存储消息的元数据。所有消息都会顺序存⼊到CommitLog⽂件当中。CommitLog由多个⽂件组成每个⽂件固定⼤⼩1G。以第⼀条消息的偏移量为⽂件名。ConsumerQueue存储消息在CommitLog的索引。⼀个MessageQueue⼀个⽂件记录当前MessageQueue被哪些消费者组消费到了哪⼀条CommitLog。IndexFile为了消息查询提供了⼀种通过key或时间区间来查询消息的⽅法这种通过IndexFile来查找消息的⽅法不影响发送与消费消息的主流程
Kafka
设置异步发送回调函数发送失败了会有异常 设置消息重试
数据积压
Kafka
1如果是Kafka消费能力不足则可以考虑增加Topic的分区数并且同时提升消费组的消费者数量消费者数 分区数。两者缺一不可
2如果是下游的数据处理不及时提高每批次拉取的数量。批次拉取数据过少拉取数据/处理时间 生产速度使处理的数据小于生产的数据也会造成数据积压。从一次最多拉取500条调整为一次最多拉取1000条
为了处理MQ队列堆积的问题可以采取以下几种措施 增加消费者的数量通过增加消费者的数量可以提高消息的处理速度从而减少堆积。这有助于减少消费时间避免因单个消费者处理速度慢而导致整个队列的堵塞。 优化消费者的处理逻辑对消费者的处理逻辑进行优化提高处理效率减少消费时间。这样可以更快地处理消息减少堆积的可能性。 监控和预警建立监控系统及时监测消息队列的堆积情况当消息堆积达到一定阈值时及时发出预警以便及时处理。 增加消息队列的容量如果消息队列的容量不足以处理高峰期的消息量可以考虑增加消息队列的容量以便更好地处理消息堆积。 增加消息队列的可用性通过多副本或者备份机制提高消息队列的可用性减少由于故障或宕机导致的消息堆积。 重试机制在消费者处理消息失败时可以设置重试机制将失败的消息重新放回队列等待后续处理防止消息丢失。 调整超时设置调整超时设置可以缩短等待时间并提高消费速度。例如在某些情况下因为某些原因例如网络延迟MQ消费者需要等待更长时间才能接收到新的消息。 批量操作通过批量操作来减少每条信息之间的交互次数也是一种有效减少MQ堆积问题的方法。例如在生产者端
集群
Docker
docker网络
bridge
使用docker run创建Docker容器时可以用 --net 或 --network 选项指定容器的网络模式
●host模式使用 --nethost 指定。 ●none模式使用 --netnone 指定。 ●container模式使用 --netcontainer:NAME_or_ID 指定。 ●bridge模式使用 --netbridge 指定默认设置可省略。
Redis
持久化机制
Redis提供了两种持久化方式RDBsave、bgsave和AOF。
RDB是一种快照snapshot持久化方式通过将当前数据集的所有数据写入磁盘文件实现数据的持久化。在RDB持久化过程中Redis会生成一个压缩过的二进制文件该文件包含了某个时间点上数据库中所有的键值对。 使用场景定期备份数据数据集比较大时启动时加载数据速度更快。
AOF是一种追加写日志文件的持久化方式它会将每一个写操作都记录到日志文件中。Redis在启动时会通过重新执行这个日志文件中的命令来恢复数据。使用场景实时持久化适用于需要实时同步数据到磁盘的场景对于需要追溯操作历史的场景AOF更有优势。Always、everysec、no
RDB中的save和bgsave
save的优缺点
优点
简单易于理解和操作生成的RDB文件相对较小。
缺点 阻塞期间Redis不能处理任何请求影响性能 需要谨慎使用特别是在数据量较大的情况下可能导致阻塞时间过长。
bgsave命令是在后台异步执行的不会阻塞主进程。执行bgsave时Redis会fork出一个子进程由子进程负责将数据集写入到磁盘文件。
优点 不会阻塞主进程不影响Redis的正常服务 在大数据集的情况下相对save更具优势。
缺点 生成的RDB文件相对较大占用磁盘空间 子进程执行bgsave时可能会占用一定的系统资源。
同步步骤
集群和哨兵
数据一致性
数据同步策略
数据删除和淘汰/脑裂
Redis的过期策略以及内存淘汰机制
缓存一致性
缓存雪崩、击穿和穿透
redissonredisson使用
// 1.设置分布式锁
RLock lock redisson.getLock(lock);
// 2.占用锁
lock.lock();
// 3.执行业务
...
// 4.释放锁
lock.unlock();分布式读写锁
RReadWriteLock rwlock redisson.getReadWriteLock(anyRWLock);
// 最常见的使用方法
rwlock.readLock().lock();
// 或
rwlock.writeLock().lock();// 10秒钟以后自动解锁
// 无需调用unlock方法手动解锁
rwlock.readLock().lock(10, TimeUnit.SECONDS);
// 或
rwlock.writeLock().lock(10, TimeUnit.SECONDS);// 尝试加锁最多等待100秒上锁以后10秒自动解锁
boolean res rwlock.readLock().tryLock(100, 10, TimeUnit.SECONDS);
// 或
boolean res rwlock.writeLock().tryLock(100, 10, TimeUnit.SECONDS);
...
lock.unlock();分布式信号量 用 Redis 客户端添加了一个 key“park”值等于 3代表信号量为 park总共有三个值。
// 获取信号量停车场
RSemaphore park redisson.getSemaphore(park);
// 获取一个信号停车位
park.acquire();
// 释放一个信号停车位
park.release();注意多次执行释放信号量操作剩余信号量会一直增加而不是到 3 后就封顶了。 公平锁Fair Lock
// 1、获取key为fairLock的锁对象
RLock lock redissonClient.getFairLock(fairLock);
// 2、加锁
lock.lock();
try {// 进行具体的业务操作...
} finally {// 3、释放锁lock.unlock();
}其他分布式锁
联锁MultiLock红锁RedLock读写锁ReadWriteLock可过期性信号量PermitExpirableSemaphore闭锁CountDownLatchMongoDB
负载均衡
Nginx
分布式锁
Es
Solr
计算机网络
HTTP常见响应码
100 Continue 这个临时响应表明迄今为止的所有内容都是可行的客户端应该继续请求如果已经完 成则忽略它。101 Switching Protocol 该代码是响应客户端的 Upgrade 标头发送的并且指示服务器也正在切换的协议。102 Processing (WebDAV) 此代码表示服务器已收到并正在处理该请求但没有响应可用。成功响应节200 OK 请求成功。201 Created 该请求已成功并因此创建了一个新的资源。这通常是在PUT请求之后发送的响应。202 Accepted 请求已经接收到但还未响应没有结果。意味着不会有一个异步的响应去表明当前请求的结果预期另外的进程和服务去处理请求或者批处理。301 Moved Permanently 被请求的资源已永久移动到新位置并且将来任何对此资源的引用都应该使用本响应返回的若干个 URI 之一。如果可能拥有链接编辑功能的客户端应当自动把请求的地址修改为从服务器反馈回来的地址。除非额外指定否则这个响应也是可缓存的。302 Found 请求的资源现在临时从不同的 URI 响应请求。由于这样的重定向是临时的客户端应当继续向原有地址发送以后的请求。只有在Cache-Control或Expires中进行了指定的情况下这个响应才是可缓存的。400 Bad Request 1、语义有误当前请求无法被服务器理解。除非进行修改否则客户端不应该重复提交这个请求。 2、请求参数有误。401 Unauthorized 当前请求需要用户验证。404 Not Found 请求失败请求所希望得到的资源未被在服务器上发现。没有信息能够告诉用户这个状况到底是暂时的还是永久的。假如服务器知道情况的话应当使用410状态码来告知旧资源因为某些内部的配置机制问题已经永久的不可用而且没有任何可以跳转的地址。404这个状态码被广泛应用于当服务器不想揭示到底为何请求被拒绝或者没有其他适合的响应可用的情况下。405 Method Not Allowed 请求行中指定的请求方法不能被用于请求相应的资源。该响应必须返回一个Allow 头信息用以表示出当前资源能够接受的请求方法的列表。 鉴于 PUTDELETE 方法会对服务器上的资源进行写操作因而绝大部分的网页服务器都不支持或者在默认配置下不允许上述请求方法对于此类请求均会返回405错误。500 Internal Server Error 服务器遇到了不知道如何处理的情况。502 Bad Gateway 此错误响应表明服务器作为网关需要得到一个处理这个请求的响应但是得到一个错误的响应。504 Gateway Timeout 当服务器作为网关不能及时得到响应时返回此错误代码。
HTTPS 是如何保证安全传输的
HTTP是RPC吗
HTTP 与 RPC 接口区别 HTTPHypertext Transfer Protocol是一种应用层协议它主要用于在 Web 浏览器和服务器之间传递数据。HTTP 的核心是客户端向服务器发起请求并等待服务器响应。 RPCRemote Procedure Call是一种远程过程调用协议它允许客户端应用程序通过网络调用远程服务器上的过程或函数。RPC 接口通常使用二进制协议来进行通信例如 Protocol Buffers、Thrift、Msgpack 等。
HTTP 接口与 RPC 接口的区别和相同之处 通信协议不同HTTP 使用文本协议RPC 使用二进制协议。 调用方式不同HTTP 接口通过 URL 进行调用RPC 接口通过函数调用进行调用。 参数传递方式不同HTTP 接口使用 URL 参数或者请求体进行参数传递RPC 接口使用函数参数进行传递。 接口描述方式不同HTTP 接口使用 RESTful 架构描述接口RPC 接口使用接口定义语言IDL描述接口。 性能表现不同RPC 接口通常比 HTTP 接口更快因为它使用二进制协议进行通信而且使用了一些性能优化技术例如连接池、批处理等。此外RPC 接口通常支持异步调用可以更好地处理高并发场景。
HTTP 接口和 RPC 接口的相同之处在于它们都是用于接口通信的协议。它们都需要定义接口、参数和返回值等信息并通过网络进行通信。此外它们都支持多种数据格式的编解码可以根据需求进行灵活的选择。
运用场景
HTTP 接口适用于 Web 应用程序和浏览器之间的通信。它通常用于传输 HTML、CSS、JavaScript 和其他 Web 资源以及 RESTful 风格的 API 服务。RPC 接口适用于分布式系统之间的通信。它可以在多种编程语言之间进行通信支持多种协议和数据格式。RPC 接口通常用于处理高并发、高吞吐量的场景例如大型的分布式计算、大数据处理等。
在项目中有使用哪些RPC框架 Apache DubboApache Dubbo原阿里巴巴Dubbo是一款高性能、轻量级的RPC框架适用于大规模分布式系统。Dubbo使用Java注解进行服务声明支持多种负载均衡策略和集群容错机制提供了丰富的监控和管理功能。 Spring CloudSpring Cloud 是一套构建分布式系统的开源框架。它提供了多个模块包括服务发现与注册、负载均衡、断路器、智能路由等功能基于HTTP或RPC实现了服务间的通信和调用。Spring Cloud整合了多种RPC框架如RestTemplate、Feign、Ribbon等使得开发者可以方便地构建分布式系统。 gRPCgRPC 是由 Google 开发的高性能、开源的RPC框架。它使用 Protocol Buffersprotobuf作为接口定义语言IDL支持多种编程语言如Java、C、Python等。gRPC基于HTTP/2协议支持双向流通信、多种序列化格式如protobuf和JSON等以及负载均衡等特性。 Apache ThriftApache Thrift 是由 Facebook 开发和开源的跨语言RPC框架。它使用自己的IDL语言支持多种编程语言如Java、C、Python、Ruby等。Thrift提供了比gRPC更丰富的功能包括异步IO、连接池、复合类型等适用于多种场景。 Apache Axis2Apache Axis2 是一款基于Web服务标准的RPC框架。它支持SOAP协议通过WSDL描述服务接口支持多种编程语言如Java、C、Python等。Axis2提供了高度可扩展的架构、安全性和可靠性并支持发布和发现服务。
开发相关
接口密等性
加密算法(md5加盐、aes、rsa)
断点续传实现shiro和securityJWT和Token
TOKEN
概念 令牌就是加密的字符串 是访问资源的凭证。Token需要查库验证token 是否有效。
客户端使用用户名跟密码请求登录。服务端收到请求去验证用户名与密码。验证成功服务端会签发一个Token保存到(Session,redis,mysql…)中然后再把这个 Token 发送给客户端。客户端收到 Token 以后可以把它存储起来比如放在 Cookie 里或者 Local Storage 里。客户端每次向服务端请求资源的时候需要带着服务端签发的 Token。服务端收到请求验证密客户端请求里面带着的Token和服务器中保存的Token进行对比效验, 如果验证成功就向客户端返回请求的数据。
JWT包含三个部分
Header头部Payload负载(载荷payload该token里携带的有效信息。比如用户id、名字、年龄等等)Signature签名。
由三部分生成JwtToken三部分之间用“.”号做分割。
在头部信息中声明加密算法和常量然后把header使用json转化为字符串。
在载荷中声明用户信息同时还有一些其他的内容再次使用json把在和部分进行转化转化为字符串。
使用在header中声明的加密算法来进行加密把第一部分字符串和第二部分的字符串结合和每个项目随机生成的secret字符串进行加密生成新的字符串此字符串是独一无二的。
解密的时候只要客户端带着jwt来发起请求服务端就直接使用secret进行解密解签证解出第一部分和第二部分然后比对第二部分的信息和客户端传过来的信息是否一致。如果一致验证成功否则验证失败。
校验也是JWT内部自己实现的
1、客户端使用用户名跟密码请求登录
2、服务端收到请求去验证用户名与密码
3、验证成功服务端会签发一个JwtToken,无须存储到服务器直接再把这个JwtToken发送给客户端
4、客户端收到JwtToken以后可以把它存储起来比如放在 Cookie 里或者 Local Storage 里
5、客户端每次向服务端请求资源的时候需要带着服务端签发的JwtToken
6、服务端收到请求验证密客户端请求里面带着的 JwtToken如果验证成功就向客户端返回请求的数据
其他
项目哪个场景用到了动态代理举例说明如何使用配置
如何设计代码实现可读性、可维护性和可替代性
如何实现断点续传功能
paxso算法BASE理论
基本可用(Basically Available) 基本可用是指分布式系统在出现故障的时候允许损失部分可用性即保证核心可用。 电商大促时为了应对访问量激增部分用户可能会被引导到降级页面服务层也可能只提供降级服务。这就是损失部分可用性的体现。软状态(Soft State) 软状态是指允许系统存在中间状态而该中间状态不会影响系统整体可用性。分布式存储中一般一份数据至少会有三个副本允许不同节点间副本同步的延时就是软状态的体现。mysql replication的异步复制也是一种体现。最终一致性(Eventual Consistency) 最终一致性是指系统中的所有数据副本经过一定时间后最终能够达到一致的状态。弱一致性和强一致性相反最终一致性是弱一致性的—种特殊情况。