react-Suspense工作原理分析

article/2025/10/13 3:52:41

Suspense 基本应用

Suspense 目前在 react 中一般配合 lazy 使用,当有一些组件需要动态加载(例如各种插件)时可以利用 lazy 方法来完成。其中 lazy 接受类型为 Promise<() => {default: ReactComponet}> 的参数,并将其包装为 react 组件。ReactComponet 可以是类组件函数组件或其他类型的组件,例如:

 const Lazy = React.lazy(() => import("./LazyComponent"))<Suspense fallback={"loading"}><Lazy/> // lazy 包装的组件</Suspense>

由于 Lazy 往往是从远程加载,在加载完成之前 react 并不知道该如何渲染该组件。此时如果不显示任何内容,则会造成不好的用户体验。因此 Suspense 还有一个强制的参数为 fallback,表示 Lazy 组件加载的过程中应该显示什么内容。往往 fallback 会使用一个加载动画。当加载完成后,Suspense 就会将 fallback 切换为 Lazy 组件的内容。一个完整的例子如下:

function LazyComp(){console.info("sus", "render lazy")return "i am a lazy man"
}function delay(ms){return new Promise((resolve, reject) => {setTimeout(resolve, ms)})
}// 模拟动态加载组件
const Lazy = lazy(() => delay(5000).then(x => ({"default": LazyComp})))function App() {const context = useContext(Context)console.info("outer context")return (<Suspense fallback={"loading"}><Lazy/></Suspense>)
}

这段代码定义了一个需要动态加载的 LazyComp 函数式组件。会在一开始显示 fallback 中的内容 loading,5s 后显示 i am a lazy man。

Suspense 原理

虽然说 Suspense 往往会配合 lazy 使用,但是 Suspense 是否只能配合 lazy 使用?lazy 是否又必须配合Suspense? 要搞清楚这两个问题,首先要明白 Suspense 以及 lazy 是在整个过程中扮演的角色,这里先给出一个简单的结论:

  • Suspense: 可以看做是 react 提供用了加载数据的一个标准,当加载到某个组件时,如果该组件本身或者组件需要的数据是未知的,需要动态加载,此时就可以使用 Suspense。Suspense 提供了加载 -> 过渡 -> 完成后切换这样一个标准的业务流程。
  • lazy: lazy 是在 Suspense 的标准下,实现的一个动态加载的组件的工具方法。

从上面的描述即可以看出,Suspense 是一个加载数据的标准,lazy 只是该标准下实现的一个工具方法。那么说明 Suspense 除配合了 lazy 还可以有其他应用场景。而 lazy 是 Suspense 标准下的一个工具方法,因此无法脱离 Suspense 使用。接下来通过 lazy + Suspense 方式来给大家分析具体原理,搞懂了这部分,我们利用 Suspense 实现自己的数据加载也不是难事。

基本流程

在深入了解细节之前,我们先了解一下 lazy + Suspense 的基本原理。这里需要一些 react 渲染流程的基本知识。为了统一,在后续将动态加载的组件称为 primary 组件,fallback 传入的组件称为 fallback 组件,与源码保持一致。

  1. 当 react 在 beginWork 的过程中遇到一个 Suspense 组件时,会首先将 primary 组件作为其子节点,根据 react 的遍历算法,下一个遍历的组件就是未加载完成的 primary 组件。
  2. 当遍历到 primary 组件时,primary 组件会抛出一个异常。该异常内容为组件 promise,react 捕获到异常后,发现其是一个 promise,会将其 then 方法添加一个回调函数,该回调函数的作用是触发 Suspense 组件的更新。并且将下一个需要遍历的元素重新设置为 Suspense,因此在一次 beginWork 中,Suspense 会被访问两次。
  3. 又一次遍历到 Suspense,本次会将 primary 以及 fallback 都生成,并且关系如下:

参考React实战视频讲解:进入学习

image.png 虽然 primary 作为 Suspense 的直接子节点,但是 Suspense 会在 beginWork 阶段直接返回 fallback。使得直接跳过 primary 的遍历。因此此时 primary 必定没有加载完成,所以也没必要再遍历一次。本次渲染结束后,屏幕上会展示 fallback 的内容

  1. 当 primary 组件加载完成后,会触发步骤 2 中 then,使得在 Suspense 上调度一个更新,由于此时加载已经完成,Suspense 会直接渲染加载完成的 primary 组件,并删除 fallback 组件。

这 4 个步骤看起来还是比较复杂。相对于普通的组件主要有两个不同的流程:

  1. primary 会组件抛出异常,react 捕获异常后继续 beginWork 阶段。
  2. 整个 beginWork 节点,Suspense 会被访问两次

不过基本逻辑还是比较简单,即是:

  1. 抛出异常
  2. react 捕获,添加回调
  3. 展示 fallback
  4. 加载完成,执行回调
  5. 展示加载完成后的组件

整个 beginWork 遍历顺序为:

 Suspense -> primary -> Suspense -> fallback

源码解读 - primary 组件

整个 Suspend 的逻辑相对于普通流程实际上是从 primary 组件开始的,因此我们也从 react 是如何处理 primary 组件开始探索。找到 react 在 beginWork 中处理处理 primary 组件的逻辑的方法 mountLazyComponent,这里我摘出一段关键的代码:

  const props = workInProgress.pendingProps;const lazyComponent: LazyComponentType<any, any> = elementType;const payload = lazyComponent._payload;const init = lazyComponent._init;let Component = init(payload); // 如果未加载完成,则会抛出异常,否则会返回加载完成的组件

其中最关键的部分莫过于这个 init 方法,执行到这个方法时,如果没有加载完成就会抛出 Promise 的异常。如果加载完成就直接返回完成后的组件。我们可以看到这个 init 方法实际上是挂载到 lazyComponent._init 方法,lazyComponent 则就是 React.lazy() 返回的组件。我们找到 React.lazy() :

export function lazy<T>(ctor: () => Thenable<{default: T, ...}>,
): LazyComponent<T, Payload<T>> {const payload: Payload<T> = {// We use these fields to store the result._status: Uninitialized,_result: ctor,};const lazyType: LazyComponent<T, Payload<T>> = {$$typeof: REACT_LAZY_TYPE,_payload: payload,_init: lazyInitializer,};

这里的 lazyType 实际上就是上面的 lazyComponent。那么这里的 _init 实际上来自于另一个函数 lazyInitializer:

function lazyInitializer<T>(payload: Payload<T>): T {if (payload._status === Uninitialized) {console.info("sus", "payload status", "Uninitialized")const ctor = payload._result;const thenable = ctor(); // 这里的 ctor 就是我们返回 promise 的函数,执行之后得到一个加载组件的 promise// 加载完成后修改状态,并将结果挂载到 _result 上thenable.then(moduleObject => {if (payload._status === Pending || payload._status === Uninitialized) {// Transition to the next state.const resolved: ResolvedPayload<T> = (payload: any);resolved._status = Resolved;resolved._result = moduleObject;}},error => {if (payload._status === Pending || payload._status === Uninitialized) {// Transition to the next state.const rejected: RejectedPayload = (payload: any);rejected._status = Rejected;rejected._result = error;}},);if (payload._status === Uninitialized) {// In case, we're still uninitialized, then we're waiting for the thenable// to resolve. Set it as pending in the meantime.const pending: PendingPayload = (payload: any);pending._status = Pending;pending._result = thenable;}}// 如果已经加载完成,则直接返回组件if (payload._status === Resolved) {const moduleObject = payload._result;console.info("sus", "get lazy resolved result")return moduleObject.default; // 注意这里返回的是 moduleObject.default 而不是直接返回 moduleObject} else {// 否则抛出异常console.info("sus, raise a promise", payload._result)throw payload._result;}
}

因此执行这个方法大致可以分为两个状态:

  1. 未加载完成时抛出异常
  2. 加载完成后返回组件

到这里,整个 primary 的逻辑就搞清楚了。下一步则是搞清楚 react 是如何捕获并且处理异常的。

源码解读 - 异常捕获

react 协调整个阶段都在 workLoop 中执行,代码如下:

  do {try {workLoopSync();break;} catch (thrownValue) {handleError(root, thrownValue);}} while (true);

可以看到 catch 了 error 后,整个处理过程在 handleError 中完成。当然,如果是如果 primary 组件抛出的异常,这里的 thrownValue 就为一个 priomise。在 handleError 中有这样一段相关代码:

throwException(root,erroredWork.return,erroredWork,thrownValue,workInProgressRootRenderLanes,
);
completeUnitOfWork(erroredWork);

核心代码需要继续深入到 throwException:

// 首先判断是否是为 promise
if (value !== null &&typeof value === 'object' &&typeof value.then === 'function') {const wakeable: Wakeable = (value: any);resetSuspendedComponent(sourceFiber, rootRenderLanes);// 获取到 Suspens 父组件const suspenseBoundary = getNearestSuspenseBoundaryToCapture(returnFiber);if (suspenseBoundary !== null) {suspenseBoundary.flags &= ~ForceClientRender;// 给 Suspens 父组件 打上一些标记,让 Suspens 父组件知道已经有异常抛出,需要渲染 fallbackmarkSuspenseBoundaryShouldCapture(suspenseBoundary,returnFiber,sourceFiber,root,rootRenderLanes,);// We only attach ping listeners in concurrent mode. Legacy Suspense always// commits fallbacks synchronously, so there are no pings.if (suspenseBoundary.mode & ConcurrentMode) {attachPingListener(root, wakeable, rootRenderLanes);}// 将抛出的 promise 放入Suspens 父组件的 updateQueue 中,后续会遍历这个 queue 进行回调绑定attachRetryListener(suspenseBoundary, root, wakeable, rootRenderLanes);return;} }

可以看到 throwException 逻辑主要是判断抛出的异常是不是 promise,如果是的话,就给 Suspens 父组件打上 ShoulCapture 的 flags,具体用处下面会讲到。并且把抛出的 promise 放入 Suspens 父组件的 updateQueue 中。

throwException 完成后会执行一次 completeUnitOfWork,根据 ShoulCapture 打上 DidCapture 的 flags。 并将下一个需要遍历的节点设置为 Suspense,也就是下一次遍历的对象依然是 Suspense。这也是之前提到的 Suspens 在整个 beginWork 阶段会遍历两次

源码解读 - 添加 promise 回调

在 Suspense 的 update queue 中,在 commit 阶段会遍历这个 updateQueue 添加回调函数,该功能在 commitMutationEffectsOnFiber 中。找到关于 Suspense 的部分,会有以下代码:

 if (flags & Update) {try {commitSuspenseCallback(finishedWork);} catch (error) {captureCommitPhaseError(finishedWork, finishedWork.return, error);}attachSuspenseRetryListeners(finishedWork);}return;

主要逻辑在 attachSuspenseRetryListeners 中:

function attachSuspenseRetryListeners(finishedWork: Fiber) {const wakeables: Set<Wakeable> | null = (finishedWork.updateQueue: any);if (wakeables !== null) {finishedWork.updateQueue = null;let retryCache = finishedWork.stateNode;if (retryCache === null) {retryCache = finishedWork.stateNode = new PossiblyWeakSet();}wakeables.forEach(wakeable => {// Memoize using the boundary fiber to prevent redundant listeners.const retry = resolveRetryWakeable.bind(null, finishedWork, wakeable);// 判断一下这个 promise 是否已经绑定过一次了,如果绑定过则可以忽略if (!retryCache.has(wakeable)) {retryCache.add(wakeable);if (enableUpdaterTracking) {if (isDevToolsPresent) {if (inProgressLanes !== null && inProgressRoot !== null) {// If we have pending work still, associate the original updaters with it.restorePendingUpdaters(inProgressRoot, inProgressLanes);} else {throw Error('Expected finished root and lanes to be set. This is a bug in React.',);}}}// 将 retry 绑定 promise 的 then 回调wakeable.then(retry, retry);}});}
}

attachSuspenseRetryListeners 整个逻辑就是绑定 promise 回调,并将绑定后的 promise 放入缓存,以免重复绑定。这里绑定的回调为 resolveRetryWakeable.bind(null, finishedWork, wakeable),在这个方法中又调用了 retryTimedOutBoundary 方法:

 if (retryLane === NoLane) {// TODO: Assign this to `suspenseState.retryLane`? to avoid// unnecessary entanglement?retryLane = requestRetryLane(boundaryFiber);}// TODO: Special case idle priority?const eventTime = requestEventTime();const root = markUpdateLaneFromFiberToRoot(boundaryFiber, retryLane);if (root !== null) {markRootUpdated(root, retryLane, eventTime);ensureRootIsScheduled(root, eventTime);}

看到 markUpdateLaneFromFiberToRoot 逻辑就比较清晰了,即在 Suspense 的组件上调度一次更新。也就是说,当动态组件的请求完成后,会执行 resolveRetryWakeable -> retryTimedOutBoundary,并且最终让 Suspense 进行一次更新。

源码解读-Suspense

之所以是将 Suspense 放在最后来分析,是因为对 Suspense 的处理涉及到多个状态,这些状态在之前的步骤中或许会被修改,因此在了解其他步骤之后再来看 Suspense 或许更容易理解。对于 Suspense 来说,在 workLoop 中可能会有 3 种不同的处理方式。每一次 beginWork Suspense 又会被访问两次,在源码中称为 first pass 和 second pass 。这两次会根据在 Suspense 的 flags 上是否存在 DidCapture 来进行不同操作。整个处理逻辑都在 updateSuspenseComponent 中。

首次渲染

beginWork - first pass,此时 DidCapture 不存在,Suspense 将 primary 组件作为子节点,访问子节点后会抛出异常。catch 时会设置 DidCapture 到 flags 上。对应的函数为 mountSuspensePrimaryChildren:

function mountSuspensePrimaryChildren(workInProgress,  primaryChildren,  renderLanes,
) {const mode = workInProgress.mode;const primaryChildProps: OffscreenProps = {mode: 'visible',children: primaryChildren,};const primaryChildFragment = mountWorkInProgressOffscreenFiber(primaryChildProps,mode,renderLanes,);primaryChildFragment.return = workInProgress;workInProgress.child = primaryChildFragment; // 子节点为 primaryChildFragment,下一次访问会抛出异常return primaryChildFragment;
}

beginWork - second pass,由于此时 DidCapture 存在,会将 primary 组件作为子节点,并将 fallback 组件作为 primary 组件的兄弟节点。但是直接返回 primary 组件,跳过 fallback 组件。对应的函数为 mountSuspenseFallbackChildren:

function mountSuspenseFallbackChildren(workInProgress,  primaryChildren,  fallbackChildren,  renderLanes,
) {const mode = workInProgress.mode;const progressedPrimaryFragment: Fiber | null = workInProgress.child;const primaryChildProps: OffscreenProps = {mode: 'hidden',children: primaryChildren,};let primaryChildFragment;let fallbackChildFragment;primaryChildFragment.return = workInProgress;fallbackChildFragment.return = workInProgress;primaryChildFragment.sibling = fallbackChildFragment;workInProgress.child = primaryChildFragment; // 注意这里的子节点是 primaryChildFragmentreturn fallbackChildFragment; // 但返回的却是 fallbackChildFragment,目的是为了跳过 primaryChild 的遍历
}

commit: 将挂载到 updateQueue 上的 promise 绑定回调

primary 组件加载完成前的渲染

在首次渲染以及 primary 组件加载完成的期间,还可能会有其他组件更新而触发触发渲染,其逻辑为:

beginWork - first pass - DidCapture 不存在: 将 primary 组件作为子节点,如果 fallback 组件存在,则将其添加到 Suspense 组件的 deletions 中。访问子节点后会抛出异常。catch 时会设置 DidCapture 到 flags 上。 对应的函数为 updateSuspensePrimaryChildren:

function updateSuspensePrimaryChildren(current,  workInProgress,  primaryChildren,  renderLanes,
) {
const currentPrimaryChildFragment: Fiber = (current.child: any);const currentFallbackChildFragment: Fiber | null =currentPrimaryChildFragment.sibling;const primaryChildFragment = updateWorkInProgressOffscreenFiber(currentPrimaryChildFragment,{mode: 'visible',children: primaryChildren,},);if ((workInProgress.mode & ConcurrentMode) === NoMode) {primaryChildFragment.lanes = renderLanes;}primaryChildFragment.return = workInProgress;primaryChildFragment.sibling = null;// 如果 currentFallbackChildFragment 存在,需要添加到 deletions 中if (currentFallbackChildFragment !== null) {const deletions = workInProgress.deletions;if (deletions === null) {workInProgress.deletions = [currentFallbackChildFragment];workInProgress.flags |= ChildDeletion;} else {deletions.push(currentFallbackChildFragment);}}workInProgress.child = primaryChildFragment;return primaryChildFragment;
}

beginWork - second pass - DidCapture 存在: 将 primary 组件作为子节点,将 fallback 组件作为 primary 组件的兄弟节点。并且清除deletions。因为此时 primary 组件还未加载完成,所以需要确保 fallback 组件不会被删除。对于的函数为:

function updateSuspenseFallbackChildren(current,  workInProgress,  primaryChildren,  fallbackChildren,  renderLanes,
) {const progressedPrimaryFragment: Fiber = (workInProgress.child: any);primaryChildFragment = progressedPrimaryFragment;primaryChildFragment.childLanes = NoLanes;primaryChildFragment.pendingProps = primaryChildProps;if (enableProfilerTimer && workInProgress.mode & ProfileMode) {primaryChildFragment.actualDuration = 0;primaryChildFragment.actualStartTime = -1;primaryChildFragment.selfBaseDuration =currentPrimaryChildFragment.selfBaseDuration;primaryChildFragment.treeBaseDuration =currentPrimaryChildFragment.treeBaseDuration;}// 清除 deletions,确保 fallback 可以展示workInProgress.deletions = null;let fallbackChildFragment;if (currentFallbackChildFragment !== null) {fallbackChildFragment = createWorkInProgress(currentFallbackChildFragment,fallbackChildren,);} else {fallbackChildFragment = createFiberFromFragment(fallbackChildren,mode,renderLanes,null,);fallbackChildFragment.flags |= Placement;}fallbackChildFragment.return = workInProgress;primaryChildFragment.return = workInProgress;primaryChildFragment.sibling = fallbackChildFragment;workInProgress.child = primaryChildFragment; // 同样的操作,workInProgress.child 为 primaryChildFragmentreturn fallbackChildFragment; // 但是返回 fallbackChildFragment}

commit: 清除 DidCapture。

primary 组件加载完成时的渲染

加载完成之后会触发 Suspense 的更新,此时为:

beginWork - first pass - DidCapture 不存在: 将 primary 组件作为子节点,如果 fallback 组件存在,则将其添加到 Suspense 组件的 deletions 中。由于此时 primary 组件加载完成,访问子节点不会抛出异常。处理的函数同样为 updateSuspensePrimaryChildren,这里就不再贴出来。

可以看出,primary 组件加载完成后就不会抛出异常,因此不会进入到 second pass,那么就不会有清除 deletions 的操作,因此本次完成后 fallback 仍然在删除列表中,最终会被删除。达到了切换到 primary 组件的目的。整体流程为:

image.png

利用 Suspense 自己实现数据加载

在我们明白了 lazy + Suspense 的原理之后,可以自己利用 Suspense 来进行数据加载,其无非就是三种状态:

  1. 初始化:查询数据,抛出 promise
  2. 加载中: 直接抛出 promise
  3. 加载完成:设置 promise 返回的数据

按照这样的思路,设计一个简单的数据加载功能:

// 模拟请求 promise
function mockApi(){return delay(5000).then(() => "data fetched")
}// 处理请求状态变更
function fetchData(){let status = "uninit"let data = nulllet promise = nullreturn () => {switch(status){// 初始状态,发出请求并抛出 promisecase "uninit": {const p = mockApi().then(x => {status = "resolved"data = x})status = "loading"promise = pthrow promise};// 加载状态,直接抛出 promisecase "loading": throw promise;// 如果加载完成直接返回数据case "resolved": return data;default: break;}}
}const reader = fetchData()function TestDataLoad(){const data = reader()return (<p>{data}</p>)
}function App() {const [count, setCount] = useState(1)useEffect(() => {setInterval(() => setCount(c => c > 100 ? c: c + 1), 1000)}, [])return (<><Suspense fallback={"loading"}><TestDataLoad/></Suspense><p>count: {count}</p></>)
}

结果为一开始显示 fallback 中的 loading,数据加载完成后显示 data fetched


http://chatgpt.dhexx.cn/article/DlbjCGeh.shtml

相关文章

vue3新增Suspense组件

在开始介绍Vue的Suspense组件之前&#xff0c;我们有必要先了解一下React的Suspense组件&#xff0c;因为他们的功能类似。 React React 16.6 新增了 <Suspense> 组件&#xff0c;让你可以“等待”目标代码加载&#xff0c;并且可以直接指定一个加载的界面&#xff08;…

Suspense组件

先上官网&#xff1a;https://cn.vuejs.org/guide/built-ins/suspense.html 注意一下 <Suspense> 是一项实验性功能。它不一定会最终成为稳定功能&#xff0c;并且在稳定之前相关 API 也可能会发生变化。 在使用了之后在浏览器控制台会有如下打印&#xff0c;至少目前是…

详解Vue3 Suspense:是什么?能干什么?如何用?

本篇文章带大家深入了解一下Vue3 Suspense&#xff0c;聊聊Suspense是什么、能干什么&#xff0c;以及如何使用它&#xff0c;希望对大家有所帮助&#xff01; Suspense 不是你想的那样。是的&#xff0c;它帮助我们处理异步组件&#xff0c;但它的作用远不止于此。&#xff0…

Vue3.0的新特性(8)Suspense

Suspense是Vue3推出的一个内置组件&#xff0c;它允许我们的程序在等待异步组件时渲染一些后备的内容&#xff0c;可以让我们创建一个平滑的用户体验&#xff1b;Vue中加载异步组件其实在Vue2.x中已经有了&#xff0c;我们用的vue-router中加载的路由组件其实也是一个异步组件&…

实现分布式锁的解决方案

目录 1. 分布式锁1.1 什么是分布式锁1.2 为什么要使用分布式锁1.3 分布式锁应具有的特性 2 分布式锁实现方案2.1 数据库实现分布式锁2.2 ZooKeeper实现分布式锁2.3 Redis实现分布式锁2.3.1 版本一2.3.2 版本二2.3.3 版本三 3. Redisson3.1 Redisson介绍3.2 Redisson分布式锁使用…

什么是分布式锁,分布式锁有什么作用?

1 、什么是分布式锁 为了防止分布式系统中的多个进程之间相互干扰&#xff0c;我们需要一种分布式协调技术来对这些进程进行调度。而这个分布式协调技术的核心就是来实现这个分布式锁。 2、为什么要使用分布式锁 成员变量 A 存在 JVM1、JVM2、JVM3 三个 JVM 内存中&#xff1b…

分布式锁以及三种加锁方式

在很多场景中&#xff0c;我们为了保证数据的最终一致性&#xff0c;需要很多的技术方案来支持&#xff0c;比如分布式事务、分布式锁等。那具体什么是分布式锁&#xff0c;分布式锁应用在哪些业务场景、如何来实现分布式锁呢&#xff1f; 一 为什么要使用分布式锁 我们在开发…

分布式架构 --- 分布式锁

分布式锁 1. 研究背景及其意义2. 分布式锁的介绍2.1 分布式锁2.2 为什么需要分布式锁2.3 分布式锁的基本要求 3. 分布式锁的实现3.1 基于数据库的分布式锁3.1.1选用数据库实现分布式锁的原因3.1.2 基于数据库实现分布式锁的缺点3.1.3分布式锁的实现 3.2 基于Redis的分布式锁3.2…

分布式锁的区别

分布式锁&#xff0c;是一种思想&#xff0c;它的实现方式有很多。比如&#xff0c;我们将沙滩当做分布式锁的组件&#xff0c;那么它看起来应该是这样的 加锁 在沙滩上踩一脚&#xff0c;留下自己的脚印&#xff0c;就对应了加锁操作。其他进程或者线程&#xff0c;看到沙滩上…

分布式锁的实现方式

背景 分布式场景中的数据一致性问题一直是一个比较重要的话题。分布式的CAP理论告诉我们“任何一个分布式系统都无法同时满足一致性&#xff08;Consistency&#xff09;、可用性&#xff08;Availability&#xff09;和分区容错性&#xff08;Partition tolerance&#xff09…

分布式锁-Redisson

分布式锁 1、分布式锁1.1 本地锁的局限性1.1.1 测试代码1.1.2 使用ab工具测试(单节点)1.1.3 本地锁问题演示(集群情况) 1.2 分布式锁实现的解决方案1.3 使用Redis实现分布式锁(了解即可)1.3.1 编写代码1.3.2 压测 1.4 使用Redisson解决分布式锁1.4.1 实现代码1.4.1 压测1.4.2 可…

Redis 分布式锁

文章目录 一、分布式锁概念二、使用setnx实现锁三、编写代码测试分布式锁1. 使用Java代码测试分布式锁2. 优化之设置锁的过期时间 四、优化之给lock设置UUID防误删五、使用LUA脚本保证删除的原子性 一、分布式锁概念 随着业务发展的需要&#xff0c;原单机部署的系统被演化成分…

关于分布式锁

先别说了别的&#xff0c;先来一个总结。 synchronized 单机版可以&#xff0c;但是上了分布式就不行了。 nginx 分布式服务单机锁就不行 取消单机锁&#xff0c;上redis分布式锁setnx 注意的问题&#xff1a; 如果只加了锁&#xff0c;没有释放锁&#xff0c;出现异常的话…

Redisson分布式锁详解

概述 setnx分布式锁的问题 重入问题 重入问题是指获得锁的线程可以再次进入到相同的锁的代码块中&#xff0c;可重入锁的意义在于防止死锁&#xff0c;比如HashTable这样的代码中&#xff0c;它的方法都是使用synchronized修饰的&#xff0c;假如它在一个方法内&#xff0c;…

redission实现分布式锁

在开始提到Redis分布式锁之前&#xff0c;先说一下redis中的两个命令。 SETNX key valuesetnx 是SET if Not eXists(如果不存在&#xff0c;则 SET)的简写。 用法如图&#xff0c;如果不存在set成功返回int的1&#xff0c;这个key存在了返回0。 SETEX key seconds value上面…

Java分布式锁

文章目录 1.什么是锁&#xff1f;2.什么是分布式&#xff1f;分布式场景 3.什么是分布式锁&#xff1f;4.我们应该怎么设计分布式锁&#xff1f;5.基于数据库的分布锁5.1 基于表主键唯一做分布式锁5.2 基于表字段版本号做分布式锁 6.基于 Redis 做分布式锁6.1 基于 REDIS 的 SE…

Redis分布式锁

概述 日常开发中&#xff0c;秒杀下单、抢红包等等业务场景&#xff0c;都需要用到分布式锁。而Redis非常适合作为分布式锁使用。本文将分七个方案展开&#xff0c;跟大家探讨Redis分布式锁的正确使用方式。如果有不正确的地方&#xff0c;欢迎大家指出哈&#xff0c;一起学习一…

Zookeeper分布式锁

实现一把分布式锁通常有很多方法&#xff0c;比较常见的有 Redis 和 Zookeeper。 Redis分布式锁可参考之前的文章&#xff1a; Redisson 分布式锁原理分析&#xff1a;https://blog.csdn.net/qq_42402854/article/details/123342331 Zookeeper能实现分布式锁&#xff0c;是因…

分布式锁

分布式锁实践 在不同进程需要互斥地访问共享资源时&#xff0c;分布式锁是一种非常有用的技术手段。有很多三方库和文章描述如何用Redis实现一个分布式锁管理器&#xff0c;但是这些库实现的方式差别很大&#xff0c;而且很多简单的实现其实只需采用稍微增加一点复杂的设计就可…

分布式系列之分布式锁几种实现机制

在分布式系统中&#xff0c;分布式锁用来解决分布式系统中多线程、多进程在不同机器上共享资源访问的问题。本文简要介绍分布式锁的四种实现机制&#xff0c;包括数据库、Redis缓存、Zookeeper和Etcd&#xff0c;以加深了解。 1、分布式锁介绍 在单体应用中&#xff0c;通过锁…