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

企业网站seo贵不贵在外汇管理网站做

企业网站seo贵不贵,在外汇管理网站做,如何做p2p网站,苏州相城区做网站公司目录 多线程 介绍一下线程的生命周期及状态#xff1f; 线程的sleep、wait、join、yield如何使用#xff1f; sleep与yield方法的区别在于#xff0c; 进程调度算法 创建线程有哪些方式#xff1f; 什么是守护线程#xff1f; ThreadLocal的原理是什么#xff0c;…目录 多线程 介绍一下线程的生命周期及状态 线程的sleep、wait、join、yield如何使用 sleep与yield方法的区别在于 进程调度算法 创建线程有哪些方式 什么是守护线程 ThreadLocal的原理是什么使用场景有哪些 ThreadLocal有哪些内存泄露问题如何避免 AQS 内置的FIFO队列和状态变量 AQS实现加锁和解锁 加锁acquire过程 解锁release过程 AQS 状态的介绍 独占模式Exclusive mode 共享模式Shared mode AQS是如何实现reentrantLock、CountDownLatch、Semaphore 这三种功能的 ReentrantLock CountDownLatch Semaphore 对线程安全的理解 如何预防死锁 为什么要使用线程池 线程池种类 线程池的7大核心参数是什么 线程池线程复用的原理是什么 描述一下线程安全活跃态问题 线程安全的竞态条件有哪些 程序开多少线程合适 线程池拒绝策略 线程池的队列满了之后 创建多少线程呢 线程池execute提交任务做了什么事  线程池的状态 Java线程池一运行阶段可以修改参数吗 调整核心线程数 ReentrantLock和synchronized的区别 synchronized和lock有哪些区别 ABA问题遇到过吗详细说一下 ̵Synchronized原理 Synchronized修饰静态变量和普通变量的区别 volatile和Synchronized 除了Synchronized还能怎么保证线程安全 volatile的可见性和禁止指令重排序怎么实现的 Happens-Before规则是什么 分布式系统 处理分布式常用的方法 分布式id生成方案有哪些 雪花算法生成的ID由哪些部分组成? 分布式锁在项目中有哪些应用场景 分布锁有哪些解决方案 基于 ZooKeeper 的分布式锁实现原理是什么? ZooKeeper和Reids做分布式锁的区别 zookeeper的watcher特性 MySQL如何做分布式锁 计数器算法是什么 滑动时间窗口算法是什么 漏桶限流算法是什么 令牌桶限流算法是什么 你设计微服务时遵循什么原则 CAP定理是什么 BASE理论是什么 2PC提交协议是什么 2PC提交协议有什么缺点 3PC提交协议是什么 2PC和3PC的区别是什么 TCC解决方案是什么? TCC空回滚是解决什么问题的 如何解决TCC幂等问题 如何解决TCC中悬挂问题 可靠消息服务方案是什么 最大努力通知方案的关键是什么 什么是分布式系统中的幂等 幂等有哪些技术解决方案 对外提供的API如何保证幂等 分布式系统如何设计 分布式微服务项目你是如何设计的 认证 (Authentication) 和授权 (Authorization)的区别是什么 Cookie 和 Session 有什么区别如何使用Session进行身份验证 为什么Cookie 无法防止CSRF攻击而token可以 什么是 Token?什么是 JWT?如何基于Token进行身份验证 单体架构的缺陷 分布式架构下Session 共享有什么方案? seata是什么 它如何保证分布式事务问题 多线程 介绍一下线程的生命周期及状态 1.创建 当程序使用new关键字创建了一个线程之后该线程就处于一个新建状态初始状态此时它和其他Java对象一样仅仅由Java虚拟机为其分配了内存并初始化了其成员变量值。此时的线程对象没有表现出任何线程的动态特征程序也不会执行线程的线程执行体。 2.就绪 当线程对象调用了Thread.start()方法之后该线程处于就绪状态。Java虚拟机会为其创建方法调用栈和程序计数器处于这个状态的线程并没有开始运行它只是表示该线程可以运行了。从start()源码中看出start后添加到了线程列表中接着在native层添加到VM中至于该线程何时开始运行取决于JVM里线程调度器的调度(如果OS调度选中了就会进入到运行状态)。 3.运行 当线程对象调用了Thread.start()方法之后该线程处于就绪状态。添加到了线程列表中如果OS调度选中了就会进入到运行状态 4.阻塞 阻塞状态是线程因为某种原因放弃CPU使用权暂时停止运行。直到线程进入就绪状态才有机会转到运行状态。阻塞的情况大概三种 1、等待阻塞运行的线程执行wait()方法JVM会把该线程放入等待池中。(wait会释放持有的锁) 2、同步阻塞运行的线程在获取对象的同步锁时若该同步锁被别的线程占用则JVM会把该线程放入锁池中。 3、其他阻塞运行的线程执行sleep()或join()方法或者发出了I/O请求时JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时线程重新转入就绪状态。注意,sleep是不会释放持有的锁。 线程睡眠Thread.sleep(long millis)方法使线程转到阻塞状态。millis参数设定睡眠的时间以毫秒为单位。当睡眠结束后就转为就绪Runnable状态。sleep()平台移植性好。 线程等待Object类中的wait()方法导致当前的线程等待直到其他线程调用此对象的 notify() 方法或 notifyAll() 唤醒方法。这个两个唤醒方法也是Object类中的方法行为等价于调用 wait(0) 一样。唤醒线程后就转为就绪Runnable状态。 线程让步Thread.yield() 方法暂停当前正在执行的线程对象把执行机会让给相同或者更高优先级的线程。 线程加入join()方法等待其他线程终止。在当前线程中调用另一个线程的join()方法则当前线程转入阻塞状态直到另一个进程运行结束当前线程再由阻塞转为就绪状态。 线程I/O线程执行某些IO操作因为等待相关的资源而进入了阻塞状态。比如说监听system.in但是尚且没有收到键盘的输入则进入阻塞状态。 线程唤醒Object类中的notify()方法唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待则会选择唤醒其中一个线程选择是任意性的并在对实现做出决定时发生。类似的方法还有一个notifyAll()唤醒在此对象监视器上等待的所有线程。 5.死亡 线程会以以下三种方式之一结束结束后就处于死亡状态: run()方法执行完成线程正常结束。 线程抛出一个未捕获的Exception或Error。 直接调用该线程的stop()方法来结束该线程——该方法容易导致死锁通常不推荐使用 线程的sleep、wait、join、yield如何使用 sleep:让线程睡眠期间会出让cpu在同步代码块中不会释放锁thread类的方法时间到后不用去争夺锁 wait(必须先获得对应的锁才能调用):让线程进入等待状态,释放当前线程持有的锁资源线程只有在notify 或者notifyAll方法调用后才会被唤醒,然后去争夺锁.Object类中的wait()方法 join:线程之间协同方式,使用场景: 线程A必须等待线程B运行完毕后才可以执行,那么就可以在线程A的代码中加入ThreadB.join(); yield:让当前正在运行的线程回到可运行状态就绪状态以允许具有相同优先级的其他线程获得运行的机会。因此使用yield()的目的是让具有相同优先级的线程之间能够适当的轮换执行。但是实际中无法保证yield()达到让步的目的因为让步的线程可能被线程调度程序再次选中。 sleep与yield方法的区别在于 当线程调用sleep方法时调用线程会被阻塞挂起指定的时间在这期间线程调度器不会去调度该线程。而调用yield方法时线程只是让出自己剩余的时间片并没有被阻塞挂起而是处于就绪状态线程调度器下一次调度时就有可能调度到当前线程执行。 进程调度算法 创建线程有哪些方式 1继承Thread类创建线程 2实现Runnable接口创建线程 3使用Callable和Future创建线程 4使用线程池例如用Executor框架 什么是守护线程 在Java中有两类线程User Thread(用户线程)、Daemon Thread(守护线程)  任何一个守护线程都是整个JVM中所有非守护线程的保姆 只要当前JVM实例中尚存在任何一个非守护线程没有结束守护线程就全部工作只有当最后一个非守护线程结束时守护线程随着JVM一同结束工作。Daemon的作用是为其他线程的运行提供便利服务守护线程最典型的应用就是 GC (垃圾回收器)它就是一个很称职的守护者。 User和Daemon两者几乎没有区别唯一的不同之处就在于虚拟机的离开如果 User Thread已经全部退出运行了只剩下Daemon Thread存在了虚拟机也就退出了。 因为没有了被守护者Daemon也就没有工作可做了也就没有继续运行程序的必要了。 注意事项: (1) thread.setDaemon(true)必须在thread.start()之前设置否则会出现一个IllegalThreadStateException异常。只能在线程未开始运行之前设置为守护线程。 (2) 在Daemon线程中产生的新线程也是Daemon的。 (3) 不要认为所有的应用都可以分配给Daemon来进行读写操作或者计算逻辑因为这会可能回到数据不一致的状态。 ThreadLocal的原理是什么使用场景有哪些 Thread类中有两个变量threadLocals和inheritableThreadLocals二者都是ThreadLocal内部类ThreadLocalMap类型的变量我们通过查看内部内ThreadLocalMap可以发现实际上它类似于一个HashMap。在默认情况下每个线程中的这两个变量都为null: ThreadLocal.ThreadLocalMap threadLocals null; ThreadLocal.ThreadLocalMap inheritableThreadLocals null; 只有当线程第一次调用ThreadLocal的set或者get方法的时候才会创建他们。 public T get() {Thread t Thread.currentThread();ThreadLocalMap map getMap(t);if (map ! null) {ThreadLocalMap.Entry e map.getEntry(this);if (e ! null) {SuppressWarnings(unchecked)T result (T)e.value;return result;}}return setInitialValue(); }ThreadLocalMap getMap(Thread t) {return t.threadLocals; } 除此之外每个线程的本地变量不是存放在ThreadLocal实例中而是放在调用线程的ThreadLocals变量里面。也就是说ThreadLocal类型的本地变量是存放在具体的线程空间上其本身相当于一个装载本地变量的载体通过set方法将value添加到调用线程的threadLocals中当调用线程调用get方法时候能够从它的threadLocals中取出变量。如果调用线程一直不终止那么这个本地变量将会一直存放在他的threadLocals中所以不使用本地变量的时候需要调用remove方法将threadLocals中删除不用的本地变量,防止出现内存泄漏。 public void set(T value) {Thread t Thread.currentThread();ThreadLocalMap map getMap(t);if (map ! null)map.set(this, value);elsecreateMap(t, value); } public void remove() {ThreadLocalMap m getMap(Thread.currentThread());if (m ! null)m.remove(this); } ThreadLocal有哪些内存泄露问题如何避免 ThreadLocal 是 Java 中的一个线程本地变量工具类它允许我们在每个线程中存储和访问特定于该线程的数据。虽然 ThreadLocal 提供了一种方便的方式来在多线程环境下共享数据但同时也会带来一些潜在的内存泄漏问题。 ThreadLocal 出现内存泄漏的根本原因可以归结为以下两点 弱引用的键ThreadLocal 内部使用线程对象作为键来存储每个线程的值。当线程结束时其对应的 ThreadLocal 实例仍然保留了对线程对象的引用由于 ThreadLocal 实例是被 ThreadLocalMapThreadLocal 的内部数据结构引用的这就导致了线程对象无法被垃圾回收。如果在长时间的运行过程中线程一直存在而没有被正确清理那么就会导致 ThreadLocalMap 中的键即线程对象一直存在从而造成内存泄漏。 外部强引用持有ThreadLocal 对象本身可能会被外部强引用持有导致 ThreadLocal 对象本身无法被垃圾回收。如果在长时间的运行过程中不再需要使用 ThreadLocal 对象但仍然持有对其的强引用那么 ThreadLocal 对象就无法被释放其中包含的 ThreadLocalMap 也无法被释放从而导致相关值无法释放。 这两个原因共同导致了 ThreadLocal 内存泄漏的问题。当没有及时清理 ThreadLocal 的引用时ThreadLocalMap 中的键和对应的值会一直存在并且无法被垃圾回收从而造成内存泄漏。 为避免 ThreadLocal 内存泄漏问题需要手动调用 remove() 方法清理对应的值并确保在不再需要使用 ThreadLocal 对象时解除对其的强引用。合理的管理和生命周期控制对每个 ThreadLocal 对象非常重要以确保它们能够被垃圾回收并释放相关内存。 内存泄漏问题主要出现在以下情况下 长时间不清理如果一个 ThreadLocal 对象被设置了值但在不再需要时忘记进行清理那么该线程中存储的对象可能会一直存在内存中导致内存泄漏。 线程池使用不当在使用线程池的情况下如果没有手动清理或重用 ThreadLocal 对象在线程池中的线程结束后ThreadLocal 对象并不会自动清理可能导致内存泄漏。 为避免 ThreadLocal 内存泄漏问题我们可以采取以下几种措施 及时清理在使用完 ThreadLocal 对象后手动调用其 remove() 方法将对应的值清除。尽量在能够确定值不再需要的时候进行清理操作。 使用弱引用可以使用 java.lang.ref.WeakReference 来包装 ThreadLocal 对象这样在线程结束后ThreadLocal 对象会被垃圾回收器回收。 手动解除引用当线程使用完 ThreadLocal 对象后可以通过 ThreadLocal.set(null) 将 ThreadLocal 对象与值的引用解除这样可以提醒垃圾回收器回收 ThreadLocal 对象。 使用 InheritableThreadLocal如果在使用线程池并且希望将 ThreadLocal 的值从父线程传递到子线程时可以考虑使用 InheritableThreadLocal它可以自动继承父线程中的 ThreadLocal 值并传递给子线程。但要注意InheritableThreadLocal 会带来一定的性能开销。 总之在使用 ThreadLocal 时要保证正确地清理和及时释放资源以避免内存泄漏问题的发生。合理使用 ThreadLocal 可以带来很大的便利但也需要在代码中仔细处理以确保资源的正常释放。 AQS AQSAbstractQueuedSynchronizer是Java并发包中的一个抽象基类提供了一种实现同步器synchronizer的框架。AQS以队列的方式管理线程通过内置的FIFO队列和状态变量提供了一种简化、可扩展的机制用于构建各种高性能的同步器如ReentrantLock、CountDownLatch、Semaphore等。 AQS的核心思想是基于状态的获取和释放。每个AQS子类都维护一个表示状态的整数变量线程在尝试获取资源时首先通过检查状态来判断是否可以获取。如果状态符合获取条件则线程可以获取到资源否则线程会被加入等待队列暂时阻塞等待。当释放资源时状态会被修改并且被阻塞的线程会根据某种策略如FIFO被唤醒继续竞争资源。 AQS框架主要由以下几个核心方法组成 acquire(int arg)尝试获取资源若获取失败则进入等待队列一直阻塞直到被唤醒并成功获取资源。 release(int arg)释放资源将状态变量修改并唤醒等待队列中的线程。 tryAcquire(int arg)尝试获取资源成功返回true失败返回false。 tryRelease(int arg)尝试释放资源成功返回true失败返回false。 tryAcquireShared(int arg)尝试以共享模式获取资源。 tryReleaseShared(int arg)尝试以共享模式释放资源。 isHeldExclusively()判断当前线程是否独占资源。 通过继承AQS类并实现自定义的同步器可以灵活地构建各种并发操作。通常自定义同步器需要重写上述方法来实现特定的并发控制策略。AQS提供了内部队列和状态变量的基本操作方法简化了同步器的实现。 总结起来AQS框架是Java并发包中的一个重要组件通过提供内置的队列和状态变量为构建各种高性能同步器提供了一种简洁、可扩展的框架。通过继承AQS类并实现自定义的同步器可以根据业务需求实现不同的并发控制策略。 内置的FIFO队列和状态变量 内置的FIFO队列和状态变量是 AQSAbstractQueuedSynchronizer中的两个核心组件用于管理线程的等待和唤醒。 FIFO队列 AQS内部通过一个双向链表来实现FIFO队列。该队列用于存储等待获取资源的线程按照线程的等待顺序进行排队。在AQS中队列中的每个节点都会持有一个线程对象的引用并通过prev和next指针与其他节点相连。 当线程尝试获取资源时如果获取失败线程会被封装成一个节点并被插入到队列的尾部。等到资源被释放时队列头部的节点会被唤醒并尝试重新获取资源。FIFO队列的设计保证了等待获取资源时间较长的线程先被唤醒实现了公平性。 状态变量 AQS通过一个整型变量来表示同步器的状态。状态变量一般用于表达可获取资源的数量或控制同步行为的状态。 在获取和释放资源的过程中线程会基于状态变量进行判断。例如一个计数器型的同步器可以使用状态变量来表示当前可用的资源数量。线程在尝试获取资源时会根据状态变量判断是否可以获取。当一个线程成功获取资源时状态变量会相应减少当线程释放资源时状态变量会相应增加。 状态变量的修改一般通过CASCompare-and-Swap原子操作来实现保证了并发环境下的正确性。 通过内置的FIFO队列和状态变量AQS能够实现并发控制的核心功能。FIFO队列管理等待获取资源的线程保证了公平性状态变量用于表示同步器的状态并在获取和释放资源时进行状态的更新和控制。这两个组件共同协作实现了线程的等待和唤醒机制提供了强大的同步能力。 AQS实现加锁和解锁 -在 AQSAbstractQueuedSynchronizer中实现加锁和解锁的核心方法是 acquire 和 release。这两个方法会被子类进行重写以实现具体同步器的加锁和解锁逻辑。下面详细介绍一下在 AQS 中加锁和解锁的实现方式 加锁acquire过程 尝试获取资源tryAcquire在加锁过程中首先会调用自定义的 tryAcquire 方法尝试获取资源。如果成功获取资源则直接返回线程可以继续执行临界区代码如果获取失败则进入阻塞状态。 阻塞并入队enqueue如果 tryAcquire 失败当前线程会将自己加入 AQS 维护的阻塞队列中同时会以一种 node 节点的形式表示线程的状态并最终以 FIFO先进先出的方式将线程排队等待获取资源。 自旋与阻塞在队列中等待的线程可能采用自旋spin方式或者使用阻塞的方式来等待资源具体的策略由具体的同步器决定。 状态记录AQS 内部会根据线程的状态和队列中的顺序记录和维护资源的状态以便后续恢复和协调资源的分配。 解锁release过程 尝试释放资源tryRelease在释放锁时会调用自定义的 tryRelease 方法来尝试释放资源。这个方法通常会更新同步器的状态和状态队列并唤醒可能正在等待资源的线程。 线程唤醒如果成功释放资源会唤醒阻塞队列中等待资源的线程让它们有机会再次尝试获取资源。 状态更新在加锁和解锁的过程中同步器的状态会被更新和维护以确保线程的正确同步和竞争。状态的更新可能包括资源的数量、线程状态等。 通过以上加锁和解锁的过程AQS 通过内部的队列和状态记录实现了多线程对资源的协调和同步控制。通过合理实现这些方法可以定制出适应不同场景需求的高效同步器帮助开发者实现复杂的并发控制逻辑。 AQS 状态的介绍 在 AQSAbstractQueuedSynchronizer中状态的作用非常重要它通常用来表示同步器所管理的资源的状态或可用数量以及线程的获取和释放状态。具体来说状态的作用包括 资源状态标识状态可以用来标识同步器管理的资源的状态例如一个锁管理的是独占资源时状态可以表示被锁住的状态。这个状态的具体含义可以根据具体的同步器来定义比如 0 表示未被占用1 表示已被占用。 线程等待判断通过状态可以判断线程是否可以获取资源。在加锁时线程会通过状态的值来决定是否可以获取资源从而决定是继续执行还是被阻塞。在解锁时更新状态可能会唤醒正在等待的线程。 协调线程操作状态的改变会影响到同步器内部队列的操作进而影响到线程的等待和唤醒。状态的变化通常会触发队列中线程的唤醒和重新竞争资源的过程从而实现多线程之间的协调和竞争。 实现特定的同步语义状态的设计可以使得同步器实现特定的同步语义比如可重入锁的状态表示当前锁被重复获取的次数读写锁的状态可以表明读线程和写线程的数量等。 综上所述AQS 中的状态起着非常关键和核心的作用通过状态的维护和更新AQS 可以实现多线程间的同步协调和资源管理。开发者可以根据具体的需求和场景来合理设计和使用状态从而实现高效的同步控制和并发处理。 在 AQSAbstractQueuedSynchronizer中一般情况下可以将状态分为两种独占模式Exclusive mode和共享模式Shared mode。这些状态通常是通过一个整型变量来表示的具体取值和含义可能会根据实际使用情况而定。以下是常见情况下状态的表示和含义 独占模式Exclusive mode 0表示当前资源未被任何线程占用可以被当前线程获取。 0表示当前资源已经被占用值通常表示占用资源的线程个数或者深度。 -1表示当前资源已经被占用且当前线程已经获取了资源通常用于可重入锁。 共享模式Shared mode 0表示当前没有任何线程占用资源可以被多个线程同时获取。 0表示当前资源已经被占用值通常表示占用资源的线程个数或者深度。 -1表示当前资源已经被占用并且当前线程已经获取了资源通常用于计数信号量。 以上状态仅供参考具体同步器的实现中这些状态的含义和取值可能会有所差异。在使用 AQS 或者自定义同步器时需要根据具体场景和需求来理解和使用状态确保状态的变化和含义符合预期从而实现正确的多线程同步和协调。 AQS是如何实现reentrantLock、CountDownLatch、Semaphore 这三种功能的 在 Java 中ReentrantLock、CountDownLatch 和 Semaphore 三种功能的实现都是基于 AQSAbstractQueuedSynchronizer的。下面简单介绍一下它们是如何利用 AQS 实现的 ReentrantLock ReentrantLock 是一个可重入的互斥锁在 AQS 的基础上实现了锁的功能。ReentrantLock 内部通过继承 AbstractQueuedSynchronizer 并重写其 tryAcquire 和 tryRelease 方法来实现独占锁的功能。具体实现中tryAcquire 在获取锁的时候会判断锁的重入次数tryRelease 在释放锁的时候会减少重入次数直到重入次数为 0 才会真正释放锁。 CountDownLatch CountDownLatch 是一种同步工具等待一个或多个线程执行完毕后才能继续执行。CountDownLatch 内部利用 AQS 的状态实现等待线程数量的计数功能当计数为零时释放阻塞线程。主要是通过继承 AbstractQueuedSynchronizer并在其中实现 tryAcquireShared 和 tryReleaseShared 方法来实现计数和等待的逻辑。 Semaphore Semaphore 是一种控制同时访问特定资源的线程数量的同步工具它可以用来实现资源池或者限流。Semaphore 内部也是基于 AQS 进行实现的通过状态变量表示可用资源的数量当获取资源时会尝试获取许可当释放资源时会释放许可。重点是实现 tryAcquireShared 和 tryReleaseShared 方法以实现资源数量的控制和线程等待逻辑。 总的来说ReentrantLock、CountDownLatch 和 Semaphore 这三种功能的实现都是建立在 AQS 的基础上通过重写 AQS 的相关方法实现了不同类型的同步功能。开发者在使用这些类时无需关注具体的实现细节只需要了解其提供的功能及如何正确使用即可。 对线程安全的理解 不是线程安全、应该是内存安全堆是共享内存可以被所有线程访问 堆是进程和线程共有的空间分全局堆和局部堆。全局堆就是所有没有分配的空间局部堆就是用户分 配的空间。堆在操作系统对进程初始化的时候分配运行过程中也可以向系统要额外的堆但是用完了 要还给操作系统要不然就是内存泄漏。 在Java中堆是Java虚拟机所管理的内存中最大的一块是所有线程共享的一块内存区域在虚 拟机启动时创建。堆所存在的内存区域的唯一目的就是存放对象实例几乎所有的对象实例以及 数组都在这里分配内存。 栈是每个线程独有的保存其运行状态和局部自动变量的。栈在线程开始的时候初始化每个线程的栈 互相独立因此栈是线程安全的。操作系统在切换线程的时候会自动切换栈。栈空间不需要在高级语 言里面显式的分配和释放。 目前主流操作系统都是多任务的即多个进程同时运行。为了保证安全每个进程只能访问分配给自己 的内存空间而不能访问别的进程的这是由操作系统保障的。 在每个进程的内存空间中都会有一块特殊的公共区域通常称为堆内存。进程内的所有线程都可以 访问到该区域这就是造成问题的潜在原因。 如何预防死锁 首先需要将死锁发生的是个必要条件讲出来: 互斥条件 同一时间只能有一个线程获取资源。 不可剥夺条件 一个线程已经占有的资源在释放之前不会被其它线程抢占 请求和保持条件 线程等待过程中不会释放已占有的资源 循环等待条件 多个线程互相等待对方释放资源 死锁预防那么就是需要破坏这四个必要条件 由于资源互斥是资源使用的固有特性无法改变我们不讨论 破坏不可剥夺条件 一个进程不能获得所需要的全部资源时便处于等待状态等待期间他占有的资源将被隐式的释放重新加入到系统的资源列表中可以被其他的进程使用而等待的进程只有重新获得自己原有的资源以及新申请的资源才可以重新启动执行 破坏请求与保持条件 第一种方法静态分配即每个进程在开始执行时就申请他所需要的全部资源 第二种是动态分配即每个进程在申请所需要的资源时他本身不占用系统资源 破坏循环等待条件 采用资源有序分配其基本思想是将系统中的所有资源顺序编号将紧缺的稀少的采用较大的编号在申请资源时必须按照编号的顺序进行一个进程只有获得较小编号的进程才能申请较大编号的进程。 为什么要使用线程池 为了减少创建和销毁线程的次数让每个线程可以多次使用,可根据系统情况调整执行的线程数量防止消耗过多内存,所以我们可以使用线程池. 线程池种类 线程池的7大核心参数是什么 corePoolSize 核心线程数目 - 池中会保留的最多线程数。 maximumPoolSize 最大线程数目 - 核心线程救急线程的最大数目。 keepAliveTime 生存时间 - 救急线程的生存时间生存时间内没有新任务此线程资源会释放。 unit 时间单位 - 救急线程的生存时间单位如秒、毫秒等。 workQueue - 当没有空闲核心线程时新来任务会加入到此队列排队队列满会创建救急线程执行任务。 threadFactory 线程工厂 - 可以定制线程对象的创建例如设置线程名字、是否是守护线程等。 handler 拒绝策略 - 当所有线程都在繁忙workQueue 也放满时会触发拒绝策略。 1抛异常 java.util.concurrent.ThreadPoolExecutor.AbortPolicy。 2由调用者执行任务 java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy。 3丢弃任务 java.util.concurrent.ThreadPoolExecutor.DiscardPolicy。 4丢弃最早排队任务 java.util.concurrent.ThreadPoolExecutor.DiscardOldestPolicy。 线程池线程复用的原理是什么 思考这么一个问题任务结束后会不会回收线程 答案是allowCoreThreadTimeOut控制 /java/util/concurrent/ThreadPoolExecutor.java:1127 final void runWorker(Worker w) {Thread wt Thread.currentThread();Runnable task w.firstTask;w.firstTask null;w.unlock(); // allow interruptsboolean completedAbruptly true;try {while (task ! null || (task getTask()) ! null) {...执行任务...}completedAbruptly false;} finally {processWorkerExit(w, completedAbruptly);}} 首先线程池内的线程都被包装成了一个个的java.util.concurrent.ThreadPoolExecutor.Worker,然后这个worker会马不停蹄的执行任务,执行完任务之后就会在while循环中去取任务,取到任务就继续执行,取不到任务就跳出while循环(这个时候worker就不能再执行任务了)执行 processWorkerExit方法,这个方法呢就是做清场处理,将当前woker线程从线程池中移除,并且判断是否是异常的进入processWorkerExit方法,如果是非异常情况,就对当前线程池状态(RUNNING,shutdown)和当前工作线程数和当前任务数做判断,是否要加入一个新的线程去完成最后的任务(防止没有线程去做剩下的任务). 那么什么时候会退出while循环呢?取不到任务的时候(getTask() null).下面看一下getTask方法 ​ private Runnable getTask() {boolean timedOut false; // Did the last poll() time out? ​for (;;) {int c ctl.get();int rs runStateOf(c); ​//(rs SHUTDOWN workQueue.isEmpty()) || rs STOP//若线程池状态是SHUTDOWN 并且 任务队列为空,意味着已经不需要工作线程执行任务了,线程池即将关闭//若线程池的状态是 STOP TIDYING TERMINATED,则意味着线程池已经停止处理任何任务了,不在需要线程if (rs SHUTDOWN (rs STOP || workQueue.isEmpty())) {//把此工作线程从线程池中删除decrementWorkerCount();return null;} ​int wc workerCountOf(c); ​//allowCoreThreadTimeOut:当没有任务的时候,核心线程数也会被剔除,默认参数是false,官方推荐在创建线程池并且还未使用的时候,设置此值//如果当前工作线程数 大于 核心线程数,timed为trueboolean timed allowCoreThreadTimeOut || wc corePoolSize;//(wc maximumPoolSize || (timed timedOut)):当工作线程超过最大线程数,或者 允许超时并且超时过一次了//(wc 1 || workQueue.isEmpty()):工作线程数至少为1个 或者 没有任务了//总的来说判断当前工作线程还有没有必要等着拿任务去执行//wc maximumPoolSize wc1 : 就是判断当前工作线程是否超过最大值//或者 wc maximumPoolSize workQueue.isEmpty():工作线程超过最大,基本上不会走到这,// 如果走到这,则意味着wc1 ,只有1个工作线程了,如果此时任务队列是空的,则把最后的线程删除//或者(timed timedOut) wc1:如果允许超时并且超时过一次,并且至少有1个线程,则删除线程//或者 (timed timedOut) workQueue.isEmpty():如果允许超时并且超时过一次,并且此时工作 队列为空那么妥妥可以把最后一个线程因为上面的wc1不满足则可以得出来wc1删除if ((wc maximumPoolSize  || (timed timedOut)) (wc 1 || workQueue.isEmpty())) {if (compareAndDecrementWorkerCount(c))//如果减去工作线程数成功,则返回null出去,也就是说 让工作线程停止while轮训,进行收尾return null;continue;} ​try {//判断是否要阻塞获取任务Runnable r timed ?workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :workQueue.take();if (r ! null)return r;timedOut true;} catch (InterruptedException retry) {timedOut false;}}}//综上所述,如果allowCoreThreadTimeOut为true,并且在第1次阻塞获取任务失败了,那么当前getTask会返回null,不管是不是核心线程;那么runWorker中将推出while循环,也就意味着当前工作线程被销毁 ​ 通过上面这个问题可以得出一个结论当你的线程池参数配置合理的时候执行完任务的线程是不会被销毁的而是会从任务队列中取出任务继续执行 描述一下线程安全活跃态问题 线程安全的活跃性问题可以分为 死锁、活锁、饥饿 活锁 就是有时线程虽然没有发生阻塞但是仍然会存在执行不下去的情况活锁不会阻塞线程线程会一直重复执行某个相同的操作并且一直失败重试 我们开发中使用的异步消息队列就有可能造成活锁的问题在消息队列的消费端如果没有正确的ack消息并且执行过程中报错了就会再次放回消息头然后再拿出来执行一直循环往复的失败。这个问题除了正确的ack之外往往是通过将失败的消息放入到延时队列中等到一定的延时再进行重试来解决。 解决活锁的方案很简单尝试等待一个随机的时间就可以会按时间轮去重试 饥饿 就是 线程因无法访问所需资源而无法执行下去的情况 饥饿 分为两种情况 一种是其他的线程在临界区做了无限循环或无限制等待资源的操作让其他的线程一直不能拿到锁进入临界区对其他线程来说就进入了饥饿状态 另一种是因为线程优先级不合理的分配导致部分线程始终无法获取到CPU资源而一直无法执行 解决饥饿的问题有几种方案: 保证资源充足很多场景下资源的稀缺性无法解决 公平分配资源在并发编程里使用公平锁例如FIFO策略线程等待是有顺序的排在等待队列前面的线程会优先获得资源 避免持有锁的线程长时间执行很多场景下持有锁的线程的执行时间也很难缩短 死锁 线程在对同一把锁进行竞争的时候未抢占到锁的线程会等待持有锁的线程释放锁后继续抢占如果两个或两个以上的线程互相持有对方将要抢占的锁互相等待对方先行释放锁就会进入到一个循环等待的过程这个过程就叫做死锁 线程安全的竞态条件有哪些 同一个程序多线程访问同一个资源如果对资源的访问顺序敏感就称存在竞态条件代码区成为临界区。 大多数并发错误一样竞态条件不总是会产生问题还需要不恰当的执行时序 最常见的竞态条件为 先检测后执行执行依赖于检测的结果而检测结果依赖于多个线程的执行时序而多个线程的执行时序通常情况下是不固定不可判断的从而导致执行结果出现各种问题见一种可能 的解决办法就是在一个线程修改访问一个状态时要防止其他线程访问修改也就是加锁机制保证原子性 延迟初始化典型为单例 程序开多少线程合适 CPU 密集型程序一个完整请求I/O操作可以在很短时间内完成CPU还有很多运算要处理也就是说 CPU 计算的比例占很大一部分线程等待时间接近0 单核CPU 一个完整请求I/O操作可以在很短时间内完成 CPU还有很多运算要处理也就是说 CPU 计算的比例占很大一部分线程等待时间接近0。单核CPU处理CPU密集型程序这种情况并不太适合使用多线程。 多核 如果是多核CPU 处理 CPU 密集型程序我们完全可以最大化的利用 CPU 核心数应用并发编程来提高效率。CPU 密集型程序的最佳线程数就是理论上线程数量 CPU 核数逻辑但是实际上数量一般会设置为 CPU 核数逻辑 1经验值,计算(CPU)密集型的线程恰好在某时因为发生一个页错误或者因其他原因而暂停刚好有一个“额外”的线程可以确保在这种情况下CPU周期不会中断工作 I/O 密集型程序与 CPU 密集型程序相对一个完整请求CPU运算操作完成之后还有很多 I/O 操作要做也就是说 I/O 操作占比很大部分等待时间较长线程等待时间所占比例越高需要越多线程线程CPU时间所占比例越高需要越少线程 I/O 密集型程序的最佳线程数就是 最佳线程数 CPU核心数 (1/CPU利用率) CPU核心数 (1 (I/O耗时/CPU耗时)) 如果几乎全是 I/O耗时那么CPU耗时就无限趋近于0所以纯理论你就可以说是 2NNCPU核数当然也有说 2N 1的1应该是backup 一般我们说 2N 1 就即可 线程池拒绝策略 的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize时如果还有任务到来就会采取任务拒绝策略通常有以下四种策略 线程池拒绝策略是在线程池无法继续接受新任务时如何拒绝新任务并告知调用者的一种策略。当线程池的工作队列已满且线程数达到上限时可以采用以下几种拒绝策略 AbortPolicy默认策略 这是线程池默认的拒绝策略。当线程池无法接受新任务时会抛出RejectedExecutionException异常给调用者。 CallerRunsPolicy 当线程池无法接受新任务时新任务会由提交任务的线程执行。这样做可以一定程度上降低对系统的压力但也会影响提交任务的线程的性能。 DiscardOldestPolicy 当线程池无法接受新任务时会丢弃工作队列中最旧的任务即最先加入队列的任务。然后尝试添加新任务。 DiscardPolicy 当线程池无法接受新任务时会默默地丢弃新任务不给任何提示和告知。 除了上述内置的拒绝策略也可以自定义拒绝策略实现RejectedExecutionHandler接口并实现其中的rejectedExecution()方法根据具体需求进行拒绝策略的定制。 选择合适的拒绝策略需要根据实际应用场景和需求。例如如果对任务提交的性能要求较高可以使用CallerRunsPolicy如果对任务的可靠性要求较高可以使用DiscardOldestPolicy如果不希望丢失任务可以自定义拒绝策略进行记录和处理。 线程池的队列满了之后 瞬间任务特别多你可以无限制的不停地创建额外的线程出来一台机器上可能有很多很多很多线程每个线程都有自己的栈内存占用一定的内存资源会导致内存资源耗尽系统也会崩溃。 即使内存没有崩溃也会导致机器的cpu loadcpu负载特别高。 假设【maximumPoolSize】最大线程数设置为200。可能会导致任务被拒绝掉很多任务无法被执行。 无界队列用的比较多实际情况下得看系统业务的具体负载。具体情况具体分析 可以自定义一个拒绝策略: 自定义一个reject策略如果线程池无法执行更多的任务了此时建议你可以把这个任务信息持久化写入磁盘里去后台专门启动一个线程后续等待你的线程池的工作负载降低了他可以慢慢的从磁盘里读取之前持久化的任务重新提交到线程池里去执行 8核16G的内存 一般可以 创建多少线程呢 根据你的业务场景来去设计,核心线程数CPU核数*(执行时间/(执行时间等待时间)) 执行时间:代码中运算 等待时间:比如调用dubbo接口等待响应 2、如果使用无界队列那么可能会导致OOM甚至宕机。 如果使用有界队列然后设置max线程数max那么会导致创建很多线程也可能导致服务器崩溃。 所以要根据具体的场景以及具体的压测数据来设定这些参数。最后就是我们可以手动去实现一个拒绝策略将请求持久化一下然后后台线程去等线程池负载降下来了后再读出来继续执行。 线程池execute提交任务做了什么事  线程池的状态 这个问题有一张很经典的图可以说明execute的执行流程为任务分配线程 Java线程池一运行阶段可以修改参数吗 可以 poolExecutor.execute(() - { // 执行修改 poolExecutor.setCorePoolSize(10); poolExecutor.setMaximumPoolSize(cpuSize * 5); poolExecutor.setKeepAliveTime(60, TimeUnit.SECONDS); poolExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy()); }); 调整核心线程数 上硬核代码 public void setCorePoolSize(int corePoolSize) {if (corePoolSize 0)throw new IllegalArgumentException();int delta corePoolSize - this.corePoolSize;this.corePoolSize corePoolSize;// case 1. 如果当前正在使用的核心线程数多余修改后的核心线程数中断一部分线程if (workerCountOf(ctl.get()) corePoolSize)interruptIdleWorkers();else if (delta 0) {// case 2. 如果是增大核心线程的数量视情况则增加工作线程int k Math.min(delta, workQueue.size());while (k-- 0 addWorker(null, true)) {if (workQueue.isEmpty())break;}} } 复制代码 细说一下首先核心线程数量corePoolSize的数量肯定会设置成为我们想要的数值就case 1来说调用interruptIdleWorkers来中断线程但是中断是有条件的如果当前线程在执行任务此时是不可中断的我理解这样的线程最终会在keepAliveTime时间内结束处理的任务不管有没有正确完成在后面的某一个时间点内核心线程会调整到我们具体设置的值上: private void interruptIdleWorkers() {interruptIdleWorkers(false); } ​ private void interruptIdleWorkers(boolean onlyOne) {// 只可有一个线程操作final ReentrantLock mainLock this.mainLock;mainLock.lock();try {// 循环线程集合将处于空闲状态的线程中断掉for (Worker w : workers) {Thread t w.thread;if (!t.isInterrupted() w.tryLock()) {try {t.interrupt();} catch (SecurityException ignore) {} finally {w.unlock();}}if (onlyOne)break;}} finally {mainLock.unlock();} } ​ // 其中workers定义 private final HashSetWorker workers new HashSetWorker(); 复制代码 如果是调大核心线程数比如由5个调整到10个框架并不是立马又启动5个线程而是结合观察阻塞队列里面的任务数根据代处理的任务数来创建新的worker线程如果阻塞队列里面有两个任务代处理那么会新增两个核心线程如果为0个那一个都不创建。 ReentrantLock和synchronized的区别 废话区别单词不一样。。。 核心区别ReentrantLock是个类synchronized是关键字当然都是在JVM层面实现互斥锁的方式 效率区别如果竞争比较激烈推荐ReentrantLock去实现不存在锁升级概念。而synchronized是存在锁升级概念的如果升级到重量级锁是不存在锁降级的。 底层实现区别实现原理是不一样ReentrantLock基于AQS实现的synchronized是基于ObjectMonitor 功能向的区别ReentrantLock的功能比synchronized更全面。ReentrantLock支持公平锁和非公平锁ReentrantLock可以指定等待锁资源的时间。 选择哪个如果你对并发编程特别熟练推荐使用ReentrantLock功能更丰富。如果掌握的一般般使用synchronized会更好 synchronized和lock有哪些区别 区别类型synchronizedLock存在层次Java的关键字在jvm层面上是JVM的一个接口锁的获取假设A线程获得锁B线程等待。如果A线程阻塞B线程会一直等待情况而定Lock有多个锁获取的方式大致就是可以尝试获得锁线程可以不用一直等待(可以通过tryLock判断有没有锁)锁的释放1、以获取锁的线程执行完同步代码释放锁2、线程执行发生异常jvm会让线程释放在finally中必须释放锁不然容易造成线程死锁锁类型锁可重入、不可中断、非公平可重入、可判断 可公平两者皆可性能少量同步适用于大量同步支持锁的场景1. 独占锁1. 公平锁与非公平锁 Lock对比Synchronized 支持以非阻塞方式获取锁 可以响应中断 可以限时 ABA问题遇到过吗详细说一下 有两个线程同时去修改一个变量的值比如线程1、线程2都更新变量值将变量值从A更新成B。 首先线程1获取到CPU的时间片线程2由于某些原因发生阻塞进行等待此时线程1进行比较更新CompareAndSwap成功将变量的值从A更新成B。 更新完毕之后恰好又有线程3进来想要把变量的值从B更新成A线程3进行比较更新成功将变量的值从B更新成A。 线程2获取到CPU的时间片然后进行比较更新发现值是预期的A然后有更新成了B。但是线程1并不知道该值已经有了A-B-A这个过程这也就是我们常说的ABA问题。 ̵Synchronized原理 synchronized块是Java提供的一种原子性内置锁Java中的每个对象都可以把它当作一个同步锁来使用这些Java内置的使用者看不到的锁被称为内部锁也叫作监视器锁。线程的执行代码在进入synchronized代码块前会自动获取内部锁这时候其他线程访问该同步代码块时会被阻塞挂起。拿到内部锁的线程会在正常退出同步代码块或者抛出异常后或者在同步块内调用了该内置锁资源的wait系列方法时释放该内置锁。内置锁是排它锁也就是当一个线程获取这个锁后其他线程必须等待该线程释放锁后才能获取该锁。另外由于Java中的线程是与操作系统的原生线程一一对应的所以当阻塞一个线程时需要从用户态切换到内核态执行阻塞操作这是很耗时的操作而synchronized的使用就会导致上下文切换。 讲解synchronized的一个内存语义这个内存语义就可以解决共享变量内存可见性问题。进入synchronized块的内存语义是把在synchronized块内使用到的变量从线程的工作内存中清除这样在synchronized块内使用到该变量时就不会从线程的工作内存中获取而是直接从主内存中获取。退出synchronized块的内存语义是把在synchronized块内对共享变量的修改刷新到主内存。 Synchronized修饰静态变量和普通变量的区别 这里主要涉及到类对象static方法对象方法非static方法 我们知道当synchronized修饰一个static方法时多线程下获取的是类锁即Class本身注意不是实例 当synchronized修饰一个非static方法时多线程下获取的是对象锁即类的实例对象 所以当synchronized修饰一个static方法时创建线程不管是new JoinThread()还是new Thread(new JoinThread())在run方法中执行inc()方法都是同步的 相反当synchronized修饰一个非static方法时如果用new JoinThread()还是new Thread(new JoinThread())方式创建线程就无法保证同步操作因为这时 inc()是属于对象方法每个线程都执有一个独立的对象实例new JoinThread()所以多线程下执行inc()方法并不会产生互斥也不会有同步操作 1.Synchronized修饰非静态方法实际上是对调用该方法的对象加锁俗称“对象锁”。 Java中每个对象都有一个锁并且是唯一的。假设分配的一个对象空间里面有多个方法相当于空间里面有多个小房间如果我们把所有的小房间都加锁因为这个对象只有一把钥匙因此同一时间只能有一个人打开一个小房间然后用完了还回去再由JVM 去分配下一个获得钥匙的人。 情况1同一个对象在两个线程中分别访问该对象的两个同步方法 结果会产生互斥。 解释因为锁针对的是对象当对象调用一个synchronized方法时其他同步方法需要等待其执行结束并释放锁后才能执行。 情况2不同对象在两个线程中调用同一个同步方法 结果不会产生互斥。 解释因为是两个对象锁针对的是对象并不是方法所以可以并发执行不会互斥。形象的来说就是因为我们每个线程在调用方法的时候都是new 一个对象那么就会出现两个空间两把钥匙 2.Synchronized修饰静态方法实际上是对该类对象加锁俗称“类锁”。 情况1用类直接在两个线程中调用两个不同的同步方法 结果会产生互斥。 解释因为对静态对象加锁实际上对类.class加锁类对象只有一个可以理解为任何时候都只有一个空间里面有N个房间一把锁因此房间同步方法之间一定是互斥的。 注上述情况和用单例模式声明一个对象来调用非静态方法的情况是一样的因为永远就只有这一个对象。所以访问同步方法之间一定是互斥的。 情况2用一个类的静态对象在两个线程中调用静态方法或非静态方法 结果会产生互斥。 解释因为是一个对象调用同上。 情况3一个对象在两个线程中分别调用一个静态同步方法和一个非静态同步方法 结果不会产生互斥。 volatile和Synchronized 除了Synchronized还能怎么保证线程安全 1 Lock 和 ReadWriteLock主要实现类分别为 ReentrantLock 和 ReentrantReadWriteLock 2 atomic 原子性1 3 threadlocal 注意复杂对象及集合的clear防止内存溢出 4 volatile 内存可见不要将volatile用在getAndOperate场合这种场合不原子需要再加锁仅仅set或者get的场景是适合volatile的 5 可重入读写锁 ReentrantReadWriteLock(1、只有一个线程可以获取到写锁。在获取写锁时只有没有任何线程持有任何锁才能获取成功 2、如果有线程正持有写锁其他任何线程都获取不到任何锁 3、没有线程持有写锁时可以有多个线程获取到读锁。) volatile的可见性和禁止指令重排序怎么实现的 可见性 volatile的功能就是被修饰的变量在被修改后可以立即同步到主内存被修饰的变量在每次是用之前都从主内存刷新。本质也是通过内存屏障来实现可见性 写内存屏障Store Memory Barrier可以促使处理器将当前store buffer存储缓存的值写回主存。读内存屏障Load Memory Barrier可以促使处理器处理invalidate queue失效队列。进而避免由于Store Buffer和Invalidate Queue的非实时性带来的问题。 禁止指令重排序 volatile是通过内存屏障来禁止指令重排序 JMM内存屏障的策略 在每个 volatile 写操作的前面插入一个 StoreStore 屏障。 在每个 volatile 写操作的后面插入一个 StoreLoad 屏障。 在每个 volatile 读操作的后面插入一个 LoadLoad 屏障。 在每个 volatile 读操作的后面插入一个 LoadStore 屏障。 Happens-Before规则是什么 Happens-Before 规则是并发编程中的一个重要概念用来描述不同操作之间的先后顺序关系。在 Java 内存模型中Happens-Before 规则定义了一组规则用于确定一个操作是否能观察到另一个操作的结果从而确保多线程程序的正确性和一致性。 具体来说Happens-Before 规则包括以下几个方面 程序顺序规则一个线程中的每一个操作happens-before于该线程中的任意后续操作。 监视器规则对一个锁的解锁happens-before于随后对这个锁的加锁。 volatile规则对一个volatile变量的写happens-before于任意后续对一个volatile变量的读。 传递性若果A happens-before BB happens-before C那么A happens-before C。 线程启动规则Thread对象的start()方法happens-before于这个线程的任意后续操作。 线程终止规则线程中的任意操作happens-before于该线程的终止监测。我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值等手段检测到线程已经终止执行。 线程中断操作对线程interrupt()方法的调用happens-before于被中断线程的代码检测到中断事件的发生可以通过Thread.interrupted()方法检测到线程是否有中断发生。 对象终结规则一个对象的初始化完成happens-before于这个对象的finalize()方法的开始。 Happens-Before 规则帮助程序员理解并发程序中操作之间的执行顺序确保在多线程环境中操作能够按照预期顺序执行避免出现数据不一致或不确定的结果。合理地遵循 Happens-Before 规则可以减少竞态条件Race Condition等并发编程中常见的问题提高程序的稳定性和可靠性。 分布式系统 处理分布式常用的方法 作为一个 Java 开发工程师处理分布式事务是一个常见的挑战。以下是一些处理分布式事务的常见方法 两阶段提交Two-Phase Commit2PC2PC 是一种常见的分布式事务协议它包括协调器和参与者两个角色。在该协议中协调器协调所有参与者的提交或回滚操作。尽管 2PC 有效地保证了数据的一致性但它依赖于协调器的单点故障和网络延迟的问题。 补偿事务Compensating Transaction补偿事务是通过反向操作来撤销之前已经执行的操作。当一个操作无法成功提交时可以执行相应的补偿操作来回滚已经执行的操作。这种方法要求开发人员预先定义补偿操作并确保补偿操作的一致性和正确性。 消息队列Message Queue使用消息队列来处理分布式事务可以提高系统的可靠性和性能。将事务操作封装为消息并将其发送到消息队列中然后由后续的消费者进行处理。如果某个消费者失败可以重新消费该消息或者将其发送到死信队列以便进行后续处理。 分布式数据库选择合适的分布式数据库可以简化事务处理。一些分布式数据库系统具有内置的分布式事务管理功能例如 Google Spanner、Apache Cassandra 或 TiDB 等。它们能够处理分布式事务并提供一致性和可用性保证。 基于消息的最终一致性Eventual Consistency在某些场景下可以接受一定的数据不一致性并基于最终一致性来处理分布式事务。通过异步更新数据副本最终达到一致的状态。这种方式可以提高系统的吞吐量和性能但需要在业务逻辑上进行妥善处理。 需要根据具体需求选择合适的事务处理方法。每种方法都有其优缺点需要权衡各个方面的因素来做出决策。此外还可以考虑使用一些分布式事务管理框架如 Seata、HumiFly 等来简化分布式事务的处理过程。 分布式id生成方案有哪些 UUID,数据库主键自增Redis自增ID雪花算法。 描述优点缺点UUIDUUID是通用唯一标识码的缩写其目的是让分布式系统中的所有元素都有唯一的辨识信息而不需要通过中央控制器来指定唯一标识。1. 降低全局节点的压力使得主键生成速度更快 2. 生成的主键全局唯一 3. 跨服务器合并数据方便。1. UUID占用16个字符空间占用较多 2. 不是递增有序的数字数据写入IO随机性很大且索引效率下降数据库主键自增MySQL数据库设置主键且主键自动增长1. INT和BIGINT类型占用空间较小 2. 主键自动增长IO写入连续性好 3. 数字类型查询速度优于字符串1. 并发性能不高受限于数据库性能 2. 分库分表需要改造复杂 3. 自增数据和数据量泄露Redis自增Redis计数器原子性自增使用内存并发性能好1. 数据丢失 2. 自增数据量泄露雪花算法snowflake大名鼎鼎的雪花算法分布式ID的经典解决方案1. 不依赖外部组件 2. 性能好时钟回拨 雪花算法生成的ID由哪些部分组成? 符号位占用1位。 时间戳占用41位可以支持69年的时间跨度。 机器ID占用10位。 序列号占用12位。一毫秒可以生成4095个ID。 分布式锁在项目中有哪些应用场景 使用分布式锁的场景一般需要满足以下场景 系统是一个分布式系统,集群集群java的锁已经锁不住了。 操作共享资源比如库里唯一的用户数据。 同步访问即多个进程同时操作共享资源。 分布锁有哪些解决方案 Reids的分布式锁很多大公司会基于Reidis做扩展开发。setnx key value ex 10sRedisson。 watch dog. 基于Zookeeper。临时节点顺序节点。 基于数据库比如Mysql。主键或唯一索引的唯一性。 基于 ZooKeeper 的分布式锁实现原理是什么? 顺序节点特性 使用 ZooKeeper 的顺序节点特性假如我们在/lock/目录下创建3个节点ZK集群会按照发起创建的顺序来创建节点节点分别为/lock/0000000001、/lock/0000000002、/lock/0000000003最后一位数是依次递增的节点名由zk来完成。 临时节点特性 ZK中还有一种名为临时节点的节点临时节点由某个客户端创建当客户端与ZK集群断开连接则该节点自动被删除。EPHEMERAL_SEQUENTIAL为临时顺序节点。 根据ZK中节点是否存在可以作为分布式锁的锁状态以此来实现一个分布式锁下面是分布式锁的基本逻辑 客户端1调用create()方法创建名为“/业务ID/lock-”的临时顺序节点。 客户端1调用getChildren(“业务ID”)方法来获取所有已经创建的子节点。 客户端获取到所有子节点path之后如果发现自己在步骤1中创建的节点是所有节点中序号最小的就是看自己创建的序列号是否排第一如果是第一那么就认为这个客户端1获得了锁在它前面没有别的客户端拿到锁。 如果创建的节点不是所有节点中需要最小的那么则监视比自己创建节点的序列号小的最大的节点进入等待。直到下次监视的子节点变更的时候再进行子节点的获取判断是否获取锁。 ZooKeeper和Reids做分布式锁的区别 Reids Redis只保证最终一致性副本间的数据复制是异步进行Set是写Get是读Reids集群一般是读写分离架构存在主从同步延迟情况主从切换之后可能有部分数据没有复制过去可能会 「丢失锁」 情况故强一致性要求的业务不推荐使用Reids推荐使用zk。 Redis集群各方法的响应时间均为最低。随着并发量和业务数量的提升其响应时间会有明显上升公网集群影响因素偏大但是极限qps可以达到最大且基本无异常 ZooKeeper 使用ZooKeeper集群锁原理是使用ZooKeeper的临时顺序节点临时顺序节点的生命周期在Client与集群的Session结束时结束。因此如果某个Client节点存在网络问题与ZooKeeper集群断开连接Session超时同样会导致锁被错误的释放导致被其他线程错误地持有因此ZooKeeper也无法保证完全一致。 ZK具有较好的稳定性响应时间抖动很小没有出现异常。但是随着并发量和业务数量的提升其响应时间和qps会明显下降。 总结 Zookeeper每次进行锁操作前都要创建若干节点完成后要释放节点会浪费很多时间 而Redis只是简单的数据操作没有这个问题。 zookeeper的watcher特性 ZooKeeper的Watcher观察者特性是其分布式协调服务中非常重要的一部分。Watcher允许客户端能够接收和处理ZooKeeper服务端上节点的变化事件实现实时的数据同步和协调。 具体来说ZooKeeper的Watcher特性有以下几个重要点 监听节点变化在创建ZooKeeper客户端时可以注册一个Watcher对象用于监听指定节点的变化。当该节点的数据发生变化、被创建、被删除或其子节点发生变化时ZooKeeper服务端会将这些事件通知给注册了Watcher的客户端。 一次性触发每个Watcher只能被触发一次也就是说当一个Watcher接收到节点变化的通知后它就会被移除需要重新注册才能继续监听。 顺序性ZooKeeper保证了Watcher的有序性。具体来说如果在一个节点上注册了多个Watcher那么这些Watcher将按照注册的先后顺序被通知。这样可以确保处理节点变化事件的顺序一致避免不一致的问题。 实现实时同步通过Watcher客户端可以实时地感知到节点的变化从而能够及时地更新自己的数据或采取相应的行动。这对于分布式系统中需要实现数据同步和协调的场景非常重要。 Watcher特性的作用是实现分布式系统中的实时数据同步和协调。通过注册Watcher客户端能够及时地获取节点变化的通知从而可以根据实际业务需求进行相应的处理。Watcher在ZooKeeper中广泛应用于分布式锁、配置管理、命名服务等场景为分布式应用程序的开发和运维提供了便利。 Watcher在ZooKeeper中被设计为一种轻量级的通知机制。它的轻量性表现在以下几个方面 建立和维护开销较低当客户端注册Watcher时它们只需要发送一个请求给ZooKeeper服务端在服务端进行注册即可。客户端在接收到节点变化通知后不需要保持持久的连接因此不需要额外的资源用于维护连接状态。 数据传输量低Watcher通知中只包含发生变化的节点的相关信息如节点路径、变化类型等并不包含节点的具体数据内容。因此Watcher通知的数据传输量通常非常小可以在网络中快速传输。 但需要注意以下几点 Watcher的触发是异步的当节点发生变化时并不能保证Watcher能够立即被触发。ZooKeeper服务端会将通知推送给客户端但触发的时间可能会受到网络延迟等因素的影响。 Watcher的处理应尽快完成由于Watcher在同一个会话中只能触发一次因此客户端在处理Watcher通知时应尽快完成相应的逻辑以保持及时的响应能力。 客户端注册 Watcher 实现 创建ZooKeeper连接 首先需要创建一个ZooKeeper连接对象并指定ZooKeeper服务端的地址和会话超时时间。例如 ZooKeeper zooKeeper new ZooKeeper(localhost:2181, 5000, null); 注册Watcher 在需要监听节点变化的地方可以通过调用ZooKeeper对象的方法来注册Watcher。例如注册一个用于监听指定节点/myNode的变化的Watcher zooKeeper.exists(/myNode, new Watcher() {Overridepublic void process(WatchedEvent event) {// 处理节点变化事件的逻辑System.out.println(Node changed: event.getPath());} }); 处理Watcher事件 定义Watcher的process()方法中可以编写具体的逻辑来处理节点变化事件。例如在上述的Watcher中当指定的节点发生变化时会打印出节点路径。 可以根据实际需求进行相应的数据更新、业务操作等。 需要注意的是注册Watcher的方法中通常还会包含其他参数用于控制Watcher的行为如是否触发默认的Watch设为true时会在节点变化时收到通知、指定Watcher的路径是否存在等。 MySQL如何做分布式锁 在Mysql中创建一张表设置一个 主键或者UNIQUE KEY 这个 KEY 就是要锁的 KEY商品ID所以同一个 KEY 在mysql表里只能插入一次了这样对锁的竞争就交给了数据库处理同一个 KEY 数据库保证了只有一个节点能插入成功其他节点都会插入失败。 DB分布式锁的实现通过主键id 或者 唯一索性 的唯一性进行加锁说白了就是加锁的形式是向一张表中插入一条数据该条数据的id就是一把分布式锁例如当一次请求插入了一条id为1的数据其他想要进行插入数据的并发请求必须等第一次请求执行完成后删除这条id为1的数据才能继续插入实现了分布式锁的功能。 这样 lock 和 unlock 的思路就很简单了伪代码 def lock exec sql: insert into locked—table (xxx) values (xxx)if result true :return trueelse :return false ​ def unlock exec sql: delete from lockedOrder where order_idorder_id 计数器算法是什么 计数器算法是指在指定的时间周期内累加访问次数达到设定的阈值时触发限流策略。下一个时间周期进行访问时访问次数清零。此算法无论在单机还是分布式环境下实现都非常简单使用redis的incr原子自增性再结合key的过期时间即可轻松实现。 从上图我们来看我们设置一分钟的阈值是100在0:00到1:00内请求数是60当到1:00时请求数清零从0开始计算这时在1:00到2:00之间我们能处理的最大的请求为100超过100个的请求系统都拒绝。 这个算法有一个临界问题比如在上图中在0:00到1:00内只在0:50有60个请求而在1:00到2:00之间只在1:10有60个请求虽然在两个一分钟的时间内都没有超过100个请求但是在0:50到1:10这20秒内确有120个请求虽然在每个周期内都没超过阈值但是在这20秒内已经远远超过了我们原来设置的1分钟内100个请求的阈值。 滑动时间窗口算法是什么 为了解决计数器算法的临界值的问题发明了滑动窗口算法。在TCP网络通信协议中就采用滑动时间窗口算法来解决网络拥堵问题。 滑动时间窗口是将计数器算法中的实际周期切分成多个小的时间窗口分别在每个小的时间窗口中记录访问次数然后根据时间将窗口往前滑动并删除过期的小时间窗口。最终只需要统计滑动窗口范围内的小时间窗口的总的请求数即可。 在上图中假设我们设置一分钟的请求阈值是100我们将一分钟拆分成4个小时间窗口这样每个小的时间窗口只能处理25个请求我们用虚线方框表示滑动时间窗口当前窗口的大小是2也就是在窗口内最多能处理50个请求。随着时间的推移滑动窗口也随着时间往前移动比如上图开始时窗口是0:00到0:30的这个范围过了15秒后窗口是0:15到0:45的这个范围窗口中的请求重新清零这样就很好的解决了计数器算法的临界值问题。 在滑动时间窗口算法中我们的小窗口划分的越多滑动窗口的滚动就越平滑限流的统计就会越精确。 漏桶限流算法是什么 漏桶算法的原理就像它的名字一样我们维持一个漏斗它有恒定的流出速度不管水流流入的速度有多快漏斗出水的速度始终保持不变类似于消息中间件不管消息的生产者请求量有多大消息的处理能力取决于消费者。 漏桶的容量漏桶的流出速度*可接受的等待时长。在这个容量范围内的请求可以排队等待系统的处理超过这个容量的请求才会被抛弃。 在漏桶限流算法中存在下面几种情况 当请求速度大于漏桶的流出速度时也就是请求量大于当前服务所能处理的最大极限值时触发限流策略。 请求速度小于或等于漏桶的流出速度时也就是服务的处理能力大于或等于请求量时正常执行。 漏桶算法有一个缺点当系统在短时间内有突发的大流量时漏桶算法处理不了。 令牌桶限流算法是什么 令牌桶算法是增加一个大小固定的容器也就是令牌桶系统以恒定的速率向令牌桶中放入令牌如果有客户端来请求先需要从令牌桶中拿一个令牌拿到令牌才有资格访问系统这时令牌桶中少一个令牌。当令牌桶满的时候再向令牌桶生成令牌时令牌会被抛弃。 在令牌桶算法中存在以下几种情况 请求速度大于令牌的生成速度那么令牌桶中的令牌会被取完后续再进来的请求由于拿不到令牌会被限流。 请求速度等于令牌的生成速度那么此时系统处于平稳状态。 请求速度小于令牌的生成速度那么此时系统的访问量远远低于系统的并发能力请求可以被正常处理。 令牌桶算法由于有一个桶的存在可以处理短时间大流量的场景。这是令牌桶和漏桶的一个区别。 你设计微服务时遵循什么原则 单一职责原则让每个服务能独立有界限的工作每个服务只关注自己的业务。做到高内聚。 服务自治原则每个服务要能做到独立开发、独立测试、独立构建、独立部署独立运行。与其他服务进行解耦。 轻量级通信原则让每个服务之间的调用是轻量级并且能够跨平台、跨语言。比如采用RESTful风格利用消息队列进行通信等。 粒度进化原则对每个服务的粒度把控其实没有统一的标准这个得结合我们解决的具体业务问题。不要过度设计。服务的粒度随着业务和用户的发展而发展。 总结一句话软件是为业务服务的好的系统不是设计出来的而是进化出来的。 CAP定理是什么 CAP定理又叫布鲁尔定理。指的是在一个分布式系统中最多只能同时满足一致性Consistency、可用性Availability和分区容错性Partition tolerance这三项中的两项。 C一致性Consistency数据在多个副本中保持一致可以理解成两个用户访问两个系统A和B当A系统数据有变化时及时同步给B系统让两个用户看到的数据是一致的。 A可用性Availability系统对外提供服务必须一直处于可用状态在任何故障下客户端都能在合理时间内获得服务端非错误的响应。 P分区容错性Partition tolerance在分布式系统中遇到任何网络分区故障系统仍然能对外提供服务。网络分区可以这样理解在分布式系统中不同的节点分布在不同的子网络中有可能子网络中只有一个节点在所有网络正常的情况下由于某些原因导致这些子节点之间的网络出现故障导致整个节点环境被切分成了不同的独立区域这就是网络分区。 我们来详细分析一下CAP为什么只能满足两个。看下图所示 用户1和用户2分别访问系统A和系统B系统A和系统B通过网络进行同步数据。理想情况是用户1访问系统A对数据进行修改将data1改成了data2同时用户2访问系统B拿到的是data2数据。 但是实际中由于分布式系统具有八大谬论 网络相当可靠 延迟为零 传输带宽是无限的 网络相当安全 拓扑结构不会改变 必须要有一名管理员 传输成本为零 网络同质化 我们知道只要有网络调用网络总是不可靠的。我们来一一分析。 当网络发生故障时系统A和系统B没法进行数据同步也就是我们不满足P同时两个系统依然可以访问那么此时其实相当于是单机系统就不是分布式系统了所以既然我们是分布式系统P必须满足。 当P满足时如果用户1通过系统A对数据进行了修改将data1改成了data2也要让用户2通过系统B正确的拿到data2那么此时是满足C就必须等待网络将系统A和系统B的数据同步好并且在同步期间任何人不能访问系统B让系统不可用否则数据就不是一致的。此时满足的是CP。 当P满足时如果用户1通过系统A对数据进行了修改将data1改成了data2也要让系统B能继续提供服务那么此时只能接受系统A没有将data2同步给系统B牺牲了一致性。此时满足的就是AP。 我们在前面学过的注册中心Eureka就是满足 的AP它并不保证C。而Zookeeper是保证CP它不保证A。在生产中A和C的选择没有正确的答案是取决于自己的业务的。比如12306是满足CP因为买票必须满足数据的一致性不然一个座位多卖了对铁路运输都是不可以接受的。 BASE理论是什么 由于CAP中一致性C和可用性A无法兼得eBay的架构师提出了BASE理论它是通过牺牲数据的强一致性来获得可用性。它由于如下3种特征 Basically Available基本可用分布式系统在出现不可预知故障的时候允许损失部分可用性保证核心功能的可用。 Soft state软状态软状态也称为弱状态和硬状态相对是指允许系统中的数据存在中间状态并认为该中间状态的存在不会影响系统的整体可用性即允许系统在不同节点的数据副本之间进行数据同步的过程存在延时。、 Eventually consistent最终一致性最终一致性强调的是系统中所有的数据副本在经过一段时间的同步后最终能够达到一个一致的状态。因此最终一致性的本质是需要系统保证最终数据能够达到一致而不需要实时保证系统数据的强一致性。 BASE理论并没有要求数据的强一致性而是允许数据在一定的时间段内是不一致的但在最终某个状态会达到一致。在生产环境中很多公司会采用BASE理论来实现数据的一致因为产品的可用性相比强一致性来说更加重要。比如在电商平台中当用户对一个订单发起支付时往往会调用第三方支付平台比如支付宝支付或者微信支付调用第三方成功后第三方并不能及时通知我方系统在第三方没有通知我方系统的这段时间内我们给用户的订单状态显示支付中等到第三方回调之后我们再将状态改成已支付。虽然订单状态在短期内存在不一致但是用户却获得了更好的产品体验。 2PC提交协议是什么 二阶段提交(Two-phaseCommit)是指在计算机网络以及数据库领域内为了使基于分布式系统架构下的所有节点在进行事务提交时保持一致性而设计的一种算法(Algorithm)。通常二阶段提交也被称为是一种协议(Protocol))。在分布式系统中每个节点虽然可以知晓自己的操作时成功或者失败却无法知道其他节点的操作的成功或失败。当一个事务跨越多个节点时为了保持事务的ACID特性需要引入一个作为协调者的组件来统一掌控所有节点(称作参与者)的操作结果并最终指示这些节点是否要把操作结果进行真正的提交(比如将更新后的数据写入磁盘等等)。因此二阶段提交的算法思路可以概括为参与者将操作成败通知协调者再由协调者根据所有参与者的反馈情报决定各参与者是否要提交操作还是中止操作。 所谓的两个阶段是指第一阶段准备阶段(投票阶段)和第二阶段提交阶段执行阶段。 准备阶段 事务协调者(事务管理器)给每个参与者(资源管理器)发送Prepare消息每个参与者要么直接返回失败(如权限验证失败)要么在本地执行事务写本地的redo和undo日志但不提交到达一种“万事俱备只欠东风”的状态。 可以进一步将准备阶段分为以下三个步骤 1协调者节点向所有参与者节点询问是否可以执行提交操作(vote)并开始等待各参与者节点的响应。 2参与者节点执行询问发起为止的所有事务操作并将Undo信息和Redo信息写入日志。注意若成功这里其实每个参与者已经执行了事务操作 3各参与者节点响应协调者节点发起的询问。如果参与者节点的事务操作实际执行成功则它返回一个”同意”消息如果参与者节点的事务操作实际执行失败则它返回一个”中止”消息。 提交阶段 如果协调者收到了参与者的失败消息或者超时直接给每个参与者发送回滚(Rollback)消息否则发送提交(Commit)消息参与者根据协调者的指令执行提交或者回滚操作释放所有事务处理过程中使用的锁资源。(注意:必须在最后阶段释放锁资源) 接下来分两种情况分别讨论提交阶段的过程。 当协调者节点从所有参与者节点获得的相应消息都为”同意”时: 1协调者节点向所有参与者节点发出”正式提交(commit)”的请求。 2参与者节点正式完成操作并释放在整个事务期间内占用的资源。 3参与者节点向协调者节点发送”完成”消息。 4协调者节点受到所有参与者节点反馈的”完成”消息后完成事务。 如果任一参与者节点在第一阶段返回的响应消息为”中止”或者 协调者节点在第一阶段的询问超时之前无法获取所有参与者节点的响应消息时 1协调者节点向所有参与者节点发出”回滚操作(rollback)”的请求。 2参与者节点利用之前写入的Undo信息执行回滚并释放在整个事务期间内占用的资源。 3参与者节点向协调者节点发送”回滚完成”消息。 4协调者节点受到所有参与者节点反馈的”回滚完成”消息后取消事务。 不管最后结果如何第二阶段都会结束当前事务。 2PC提交协议有什么缺点 同步阻塞问题。执行过程中所有参与节点都是事务阻塞型的。当参与者占有公共资源时其他第三方节点访问公共资源不得不处于阻塞状态。 单点故障。由于协调者的重要性一旦协调者发生故障。参与者会一直阻塞下去。尤其在第二阶段协调者发生故障那么所有的参与者还都处于锁定事务资源的状态中而无法继续完成事务操作。如果是协调者挂掉可以重新选举一个协调者但是无法解决因为协调者宕机导致的参与者处于阻塞状态的问题 数据不一致。在二阶段提交的阶段二中当协调者向参与者发送commit请求之后发生了局部网络异常或者在发送commit请求过程中协调者发生了故障这回导致只有一部分参与者接受到了commit请求。而在这部分参与者接到commit请求之后就会执行commit操作。但是其他部分未接到commit请求的机器则无法执行事务提交。于是整个分布式系统便出现了数据部一致性的现象。 二阶段无法解决的问题协调者再发出commit消息之后宕机而唯一接收到这条消息的参与者同时也宕机了。那么即使协调者通过选举协议产生了新的协调者这条事务的状态也是不确定的没人知道事务是否被已经提交。 3PC提交协议是什么 CanCommit阶段 3PC的CanCommit阶段其实和2PC的准备阶段很像。协调者向参与者发送commit请求参与者如果可以提交就返回Yes响应否则返回No响应。 1.事务询问 协调者向参与者发送CanCommit请求。询问是否可以执行事务提交操作。然后开始等待参与者的响应。 2.响应反馈 参与者接到CanCommit请求之后正常情况下如果其自身认为可以顺利执行事务则返回Yes响应并进入预备状态。否则反馈No PreCommit阶段 协调者根据参与者的反应情况来决定是否可以进行事务的PreCommit操作。根据响应情况有以下两种可能。 假如协调者从所有的参与者获得的反馈都是Yes响应那么就会执行事务的预执行。 1.发送预提交请求 协调者向参与者发送PreCommit请求并进入Prepared阶段。 2.事务预提交 参与者接收到PreCommit请求后会执行事务操作并将undo和redo信息记录到事务日志中。 3.响应反馈 如果参与者成功的执行了事务操作则返回ACK响应同时开始等待最终指令。 假如有任何一个参与者向协调者发送了No响应或者等待超时之后协调者都没有接到参与者的响应那么就执行事务的中断。 1.发送中断请求 协调者向所有参与者发送abort请求。 2.中断事务 参与者收到来自协调者的abort请求之后或超时之后仍未收到协调者的请求执行事务的中断。 pre阶段参与者没收到请求rollback。 doCommit阶段 该阶段进行真正的事务提交也可以分为以下两种情况。 执行提交 1.发送提交请求 协调接收到参与者发送的ACK响应那么他将从预提交状态进入到提交状态。并向所有参与者发送doCommit请求。 2.事务提交 参与者接收到doCommit请求之后执行正式的事务提交。并在完成事务提交之后释放所有事务资源。 3.响应反馈 事务提交完之后向协调者发送Ack响应。 4.完成事务 协调者接收到所有参与者的ack响应之后完成事务。 中断事务 协调者没有接收到参与者发送的ACK响应可能是接受者发送的不是ACK响应也可能响应超时那么就会执行中断事务。 1.发送中断请求 协调者向所有参与者发送abort请求 2.事务回滚 参与者接收到abort请求之后利用其在阶段二记录的undo信息来执行事务的回滚操作并在完成回滚之后释放所有的事务资源。 3.反馈结果 参与者完成事务回滚之后向协调者发送ACK消息 4.中断事务 协调者接收到参与者反馈的ACK消息之后执行事务的中断。 2PC和3PC的区别是什么 1、引入超时机制。同时在协调者和参与者中都引入超时机制。 2、三阶段在2PC的第一阶段和第二阶段中插入一个准备阶段。保证了在最后提交阶段之前各参与节点的状态是一致的。 TCC解决方案是什么? TCCTry-Confirm-Cancel是一种常用的分布式事务解决方案它将一个事务拆分成三个步骤 TTry业务检查阶段这阶段主要进行业务校验和检查或者资源预留也可能是直接进行业务操作。 CConfirm业务确认阶段这阶段对Try阶段校验过的业务或者预留的资源进行确认。 CCancel业务回滚阶段这阶段和上面的CConfirm是互斥的用于释放Try阶段预留的资源或者业务。 TCC空回滚是解决什么问题的 在没有调用TCC资源Try方法的情况下调用了二阶段的Cancel方法。比如当Try请求由于网络延迟或故障等原因没有执行结果返回了异常那么此时Cancel就不能正常执行因为Try没有对数据进行修改如果Cancel进行了对数据的修改那就会导致数据不一致。 ​ 解决思路是关键就是要识别出这个空回滚。思路很简单就是需要知道Try阶段是否执行如果执行了那就是正常回滚如果没执行那就是空回滚。建议TM在发起全局事务时生成全局事务记录全局事务ID贯穿整个分布式事务调用链条。再额外增加一张分支事务记录表其中有全局事务ID和分支事务ID第一阶段Try方法里会插入一条记录表示Try阶段执行了。Cancel接口里读取该记录如果该记录存在则正常回滚如果该记录不存在则是空回滚。 如何解决TCC幂等问题 为了保证TCC二阶段提交重试机制不会引发数据不一致要求TCC的二阶段Confirm和Cancel接口保证幂等这样不会重复使用或者释放资源。如果幂等控制没有做好很有可能导致数据不一致等严重问题。 解决思路在上述 分支事务记录中增加执行状态每次执行前都查询该状态。 分布式锁。 如何解决TCC中悬挂问题 悬挂就是对于一个分布式事务其二阶段Cancel接口比Try接口先执行。 出现原因是在调用分支事务Try时由于网络发生拥堵造成了超时TM就会通知RM回滚该分布式事务可能回滚完成后Try请求才到达参与者真正执行而一个Try方法预留的业务资源只有该分布式事务才能使用该分布式事务第一阶段预留的业务资源就再也没有人能够处理了对于这种情况我们就称为悬挂即业务资源预留后无法继续处理。 解决思路是如果二阶段执行完成那一阶段就不能再继续执行。在执行一阶段事务时判断在该全局事务下判断分支事务记录表中是否已经有二阶段事务记录如果有则不执行Try。 可靠消息服务方案是什么 可靠消息最终一致性方案指的是当事务的发起方事务参与者消息发送者执行完本地事务后同时发出一条消息事务参与方事务参与者消息的消费者一定能够接受消息并可以成功处理自己的事务。 这里面强调两点 可靠消息发起方一定得把消息传递到消费者。 最终一致性最终发起方的业务处理和消费方的业务处理得完成达成最终一致。 最大努力通知方案的关键是什么 有一定的消息重复通知机制。因为接收通知方上图中的我方支付系统可能没有接收到通知此时要有一定的机制对消息重复通知。 消息校对机制。如果尽最大努力也没有通知到接收方或者接收方消费消息后要再次消费此时可由接收方主动向通知方查询消息信息来满足需求。 什么是分布式系统中的幂等 幂等idempotent、idempotence是一个数学与计算机学概念常见于抽象代数中。 在编程中一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。幂等函数或幂等方法是指可以使用相同参数重复执行并能获得相同结果的函数。这些函数不会影响系统状态也不用担心重复执行会对系统造成改变。 例如“getUsername()和 setTrue()”函数就是一个幂等函数. 更复杂的操作幂等保证是利用唯一交易号(流水号)实现. 我的理解幂等就是一个操作不论执行多少次产生的效果和返回的结果都是一样的。 操作查询set固定值。逻辑删除。set 固定值。 流程分布式系统中网络调用重试机制。 幂等有哪些技术解决方案 1.查询操作 查询一次和查询多次在数据不变的情况下查询结果是一样的。select 是天然的幂等操作 2.删除操作 删除操作也是幂等的删除一次和多次删除都是把数据删除。(注意可能返回结果不一样删除的数据不存在返回 0删除的数据多条返回结果多个。 3.唯一索引 防止新增脏数据。比如支付宝的资金账户支付宝也有用户账户每个用户只能有一个资金账户怎么防止给用户创建多个资金账户那么给资金账户表中的用户 ID 加唯一索引所以一个用户新增成功一个资金账户记录。要点唯一索引或唯一组合索引来防止新增数据存在脏数据当表存在唯一索引并发时新增报错时再查询一次就可以了数据应该已经存在了返回结果即可。 4.token 机制 防止页面重复提交。 业务要求页面的数据只能被点击提交一次 发生原因由于重复点击或者网络重发或者 nginx 重发等情况会导致数据被重复提交 解决办法集群环境采用 token 加 redis(redis 单线程的处理需要排队)单 JVM 环境采用 token 加 redis 或 token 加 jvm 锁。 处理流程 数据提交前要向服务的申请 tokentoken 放到 redis 或 jvm 内存token 有效时间 提交后后台校验 token同时删除 token生成新的 token 返回。 token 特点要申请一次有效性可以限流。 注意redis 要用删除操作来判断 token删除成功代表 token 校验通过。 traceId 操作时唯一的。 对外提供的API如何保证幂等 举例说明 银联提供的付款接口需要接入商户提交付款请求时附带source 来源seq 序列号。 sourceseq 在数据库里面做唯一索引防止多次付款(并发时只能处理一个请求) 。重点对外提供接口为了支持幂等调用接口有两个字段必须传一个是来源 source一个是来源方序列号 seq这个两个字段在提供方系统里面做联合唯一索引这样当第三方调用时先在本方系统里面查询一下是否已经处理过返回相应处理结果没有处理过进行相应处理返回结果。 注意为了幂等友好一定要先查询一下是否处理过该笔业务不查询直接插入业务系统会报错但实际已经处理。 分布式系统如何设计 从严格意义上来说一个系统由多个独立的进程组成而且进程之间有数据交互的逻辑那么不管这几个进程是否被部署在一台主机上这样的系统都可以叫作分布式系统。 分布式微服务项目你是如何设计的 我一般设计成两层业务层和能力层中台业务层接受用户请求然后通过调用能力层来完成业务逻辑。 认证 (Authentication) 和授权 (Authorization)的区别是什么 Authentication认证 是验证您的身份的凭据例如用户名/用户ID和密码通过这个凭据系统得以知道你就是你也就是说系统存在你这个用户。所以Authentication 被称为身份/用户验证。 Authorization授权 发生在 Authentication认证 之后。授权它主要掌管我们访问系统的权限。比如有些特定资源只能具有特定权限的人才能访问比如admin有些对系统资源操作比如删除、添加、更新只能特定人才具有。 这两个一般在我们的系统中被结合在一起使用目的就是为了保护我们系统的安全性。 Cookie 和 Session 有什么区别如何使用Session进行身份验证 Session 的主要作用就是通过服务端记录用户的状态。 典型的场景是购物车当你要添加商品到购物车的时候系统不知道是哪个用户操作的因为 HTTP 协议是无状态的。服务端给特定的用户创建特定的 Session 之后就可以标识这个用户并且跟踪这个用户了。 Cookie 数据保存在客户端(浏览器端)Session 数据保存在服务器端。相对来说 Session 安全性更高。如果使用 Cookie 的一些敏感信息不要写入 Cookie 中最好能将 Cookie 信息加密然后使用到的时候再去服务器端解密。 那么如何使用Session进行身份验证 很多时候我们都是通过 SessionID 来实现特定的用户SessionID 一般会选择存放在 Redis 中。举个例子用户成功登陆系统然后返回给客户端具有 SessionID 的 Cookie当用户向后端发起请求的时候会把 SessionID 带上这样后端就知道你的身份状态了。关于这种认证方式更详细的过程如下 用户向服务器发送用户名和密码用于登陆系统。 服务器验证通过后服务器为用户创建一个 Session并将 Session信息存储 起来。 服务器向用户返回一个 SessionID写入用户的 Cookie。 当用户保持登录状态时Cookie 将与每个后续请求一起被发送出去。 服务器可以将存储在 Cookie 上的 Session ID 与存储在内存中或者数据库中的 Session 信息进行比较以验证用户的身份返回给用户客户端响应信息的时候会附带用户当前的状态。 使用 Session 的时候需要注意下面几个点 依赖Session的关键业务一定要确保客户端开启了Cookie。 注意Session的过期时间 为什么Cookie 无法防止CSRF攻击而token可以 CSRFCross Site Request Forgery一般被翻译为 跨站请求伪造 。那么什么是 跨站请求伪造 呢说简单用你的身份去发送一些对你不友好的请求。举个简单的例子 小壮登录了某网上银行他来到了网上银行的帖子区看到一个帖子下面有一个链接写着“科学理财年盈利率过万”小壮好奇的点开了这个链接结果发现自己的账户少了10000元。这是这么回事呢原来黑客在链接中藏了一个请求这个请求直接利用小壮的身份给银行发送了一个转账请求,也就是通过你的 Cookie 向银行发出请求。 a srchttp://www.mybank.com/Transfer?bankId11money10000科学理财年盈利率过万/ 进行Session 认证的时候我们一般使用 Cookie 来存储 SessionId,当我们登陆后后端生成一个SessionId放在Cookie中返回给客户端服务端通过Redis或者其他存储工具记录保存着这个Sessionid客户端登录以后每次请求都会带上这个SessionId服务端通过这个SessionId来标示你这个人。如果别人通过 cookie拿到了 SessionId 后就可以代替你的身份访问系统了。 Session 认证中 Cookie 中的 SessionId是由浏览器发送到服务端的借助这个特性攻击者就可以通过让用户误点攻击链接达到攻击效果。 但是我们使用 token 的话就不会存在这个问题在我们登录成功获得 token 之后一般会选择存放在 local storage 中。然后我们在前端通过某些方式会给每个发到后端的请求加上这个 token,这样就不会出现 CSRF 漏洞的问题。因为即使有个你点击了非法链接发送了请求到服务端这个非法请求是不会携带 token 的所以这个请求将是非法的。 什么是 Token?什么是 JWT?如何基于Token进行身份验证 我们知道 Session 信息需要保存一份在服务器端。这种方式会带来一些麻烦比如需要我们保证保存 Session 信息服务器的可用性、不适合移动端依赖Cookie等等。 有没有一种不需要自己存放 Session 信息就能实现身份验证的方式呢使用 Token 即可JWT JSON Web Token 就是这种方式的实现通过这种方式服务器端就不需要保存 Session 数据了只用在客户端保存服务端返回给客户的 Token 就可以了扩展性得到提升。 JWT 本质上就一段签名的 JSON 格式的数据。由于它是带有签名的因此接收者便可以验证它的真实性。 下面是 RFC 7519 对 JWT 做的较为正式的定义。 JSON Web Token (JWT) is a compact, URL-safe means of representing claims to be transferred between two parties. The claims in a JWT are encoded as a JSON object that is used as the payload of a JSON Web Signature (JWS) structure or as the plaintext of a JSON Web Encryption (JWE) structure, enabling the claims to be digitally signed or integrity protected with a Message Authentication Code (MAC) and/or encrypted. ——JSON Web Token (JWT) JWT 由 3 部分构成: Header :描述 JWT 的元数据。定义了生成签名的算法以及 Token 的类型。 Payload负载:用来存放实际需要传递的数据 Signature签名服务器通过Payload、Header和一个密钥(secret)使用 Header 里面指定的签名算法默认是 HMAC SHA256生成。 在基于 Token 进行身份验证的的应用程序中服务器通过Payload、Header和一个密钥(secret)创建令牌Token并将 Token 发送给客户端客户端将 Token 保存在 Cookie 或者 localStorage 里面以后客户端发出的所有请求都会携带这个令牌。你可以把它放在 Cookie 里面自动发送但是这样不能跨域所以更好的做法是放在 HTTP Header 的 Authorization字段中Authorization: Bearer Token。 用户向服务器发送用户名和密码用于登陆系统。 身份验证服务响应并返回了签名的 JWT上面包含了用户是谁的内容。 用户以后每次向后端发请求都在Header中带上 JWT。 服务端检查 JWT 并从中获取用户相关信息。 单体架构的缺陷 好处显而易见通常只建立一个Project工程即可当系统比较小时开发、部署、测试等工作都更加简单快捷容易实现项目上线的目标。但随着系统的快速迭代就会产生一些难以调和的矛盾和发现先天的缺陷。 ● 过高耦合的风险服务越来越多不停地变化由于都在一个进程中所以一个服务的失败或移除都将导致整个系统无法启动或正常运行的系统性风险越来越大。 ● 新语言与新技术引入的阻力单体架构通常只使用一种开发语言并且完全使用一种特定的框架运行在一个进程内从而导致新语言和新技术很难被引入。在互联网应用时代多语言协作开发是主流特别是对于复杂的大系统、大平台。各种新技术层出不穷拒绝新技术就意味着技术上的落后从而可能逐步被市场抛弃。 ● 水平扩展的问题单体架构从一开始就没有考虑分布式问题或者即使考虑了但仍然开发为单体架构所以遇到单机性能问题时通常难以水平扩展往往需要推倒重来代价比较大。 ● 难以可持续发展随着业务范围的快速拓展单体架构通常难以复用原有的服务一个新业务的上线通常需要重新开发新服务、新接口整个团队长期被迫加班是必然的结果老板则怀疑技术团队及Leader的能力。 分布式架构下Session 共享有什么方案? 不要有session但是确实在某些场景下是可以没有session的其实在很多接口类系统当中都提倡【API无状态服务】也就是每一次的接口访问都不依赖于session、不依赖于前一次的接口访问 存入cookie中将session存储到cookie中但是缺点也很明显例如每次请求都得带着session数据存储在客户端本地是有风险的 session同步对个服务器之间同步session这样可以保证每个服务器上都有全部的session信息不过当服务器数量比较多的时候同步是会有延迟甚至同步失败 使用Nginx或其他复杂均衡软硬件中的ip绑定策略同一个ip只能在指定的同一个机器访问但是这样做风险也比较大而且也是去了负载均衡的意义 我们现在的系统会把session放到Redis中存储虽然架构上变得复杂并且需要多访问一次Redis但是这种方案带来的好处也是很大的实现session共享可以水平扩展增加Redis服务器服务器重启session不丢失不过也要注意session在Redis中的刷新/失效机制不仅可以跨服务器session共享甚至可以跨平台例如网页端和APP端。 seata是什么 Seata原名 Fescar是一个开源的分布式事务解决方案旨在解决分布式事务问题。Seata 提供了一套高性能、高可靠性的分布式事务服务可以确保分布式系统中的多个微服务参与的业务操作要么全部成功要么全部失败即 ACID 特性。 Seata 的核心设计思想是将分布式事务划分为全局事务(Global Transaction) 和分支事务(Branch Transaction)。全局事务负责协调所有分支事务的提交或回滚从而保证分布式系统中数据的一致性。 Seata 提供了四种事务模式 1. AT 模式Automatic Transfer原子操作即自动参与式的分布式事务需要业务代码进行相应注解处理。 2. TCC 模式Try-Confirm-Cancel尝试、确认、取消模式需要业务代码实现对应的 try、confirm、cancel 接口。 3. SAGA 模式面向服务的SAGA模式通过补偿事务来确保最终一致性。 4. XA 模式对业务支持X/Open XA接口的存储服务。 Seata 可以与各种数据库、消息中间件和框架集成支持 Spring Cloud、Dubbo、RestTemplate 等常见微服务框架。通过使用 Seata开发者可以更方便地实现分布式事务管理确保业务数据的一致性和完整性。 总而言之Seata 是一个功能强大的开源分布式事务解决方案为开发者提供了可靠的手段来管理复杂的分布式事务。希望这个简要介绍能够帮助你了解 Seata。如果有任何进一步的问题请随时提出。 它如何保证分布式事务问题 Seata 通过以下机制来保证分布式事务问题 1. **全局事务协调**Seata 将分布式事务分为全局事务和分支事务全局事务由事务协调器负责协调管理。事务协调器协调各个参与者的分支事务的提交和回滚以确保全局事务的一致性。 2. **事务日志**Seata 使用事务日志来记录全局事务的提交和回滚状态以便在故障发生时进行恢复。通过日志记录Seata 可以保证全局事务的状态可追溯和一致性。 3. **分布式事务补偿**    - **TCC 模式**利用 Try-Confirm-Cancel 模式Seata 要求业务需要实现 try、confirm、cancel 三个接口来实现分布式事务的尝试、确认和取消操作以确保最终一致性。    - **SAGA 模式**SAGA 模式通过一系列的补偿事务来确保最终一致性即在出现异常情况时通过执行逆向操作来补偿前一步操作从而保证数据的完整性。 4. **分支事务超时机制**Seata 支持配置全局事务和分支事务的超时时间确保在一定时间内能够正常完成事务提交或回滚避免悬挂事务。 5. **可靠性设计**Seata 各个组件的设计考虑了高可用和容错性例如注册中心采用高可用的方式部署事务协调器具备集群和故障转移能力。 通过以上机制和设计Seata 能够有效保证分布式事务的一致性和可靠性解决了跨多个微服务的业务操作中可能出现的分布式事务问题。开发者可以利用 Seata 提供的各种模式和机制来管理分布式事务确保各个微服务节点之间的数据操作符合 ACID 特性。希望这些信息能帮助你理解 Seata 是如何保证分布式事务问题的。如果还有其他问题请随时提出。
http://www.zqtcl.cn/news/663874/

相关文章:

  • 网站规划与开发技术专业优化措施二十条
  • 通州区网站快速排名方案视频网站视频预览怎么做
  • 同创企业网站源码建筑行业公司排名
  • 温州网站建设服务建设商务网站公司
  • 导视设计网站推荐创业平台的选择
  • 营销网站建设设计义乌 网站制作
  • 南通企业网站建设公司庆阳网站建设与制作
  • 做k12网站wordpress调用第一张图片不显示
  • 网站建设和维护要点网站建设完提交百度
  • app开发人员网站上海保洁服务网站建设
  • 周口网站制作公司哪家好苏州高新区住建局官网
  • 建设特效网站自助网站建设系统
  • 用软件做的网站权限管理如何让自己的网站被百度收录
  • 简历做的很棒的网站杭州公司网站建设电话
  • 购买腾讯云主机可以直接做网站舒兰网站建设
  • 环保主题静态网站php 手机网站源码
  • 做网站找哪家好要钱吗小程序开发合同
  • 速成美站东莞网站建设 包装材料
  • 丹阳网站建设案例自己做个网站怎么赚钱
  • 净水机企业网站源码浏览器下载安装2022最新版
  • 高端网站建设四川网页版微信怎么下载
  • 青岛做网站皆赴青岛博采wordpress怎么改密码忘记
  • 深圳最好的网站建设广西论坛网站建设
  • html5网站设计网站建设 广西
  • 顺德手机网站设计价位网站开发学习流程图
  • 班级网站设计合肥蜀山网站开发
  • 杭州网站建设培训ck播放器整合WordPress
  • 网站建设是什么软件品牌策划公司哪家好推荐
  • 网站转跳怎么做餐饮vi设计
  • 刘连康seo培训哪家强网站优化推广平台