NestJS小技巧30-MurLock:在NestJS中掌握分布式锁

1,364 阅读5分钟
by 雪隐 from https://juejin.cn/user/1433418895994094
本文欢迎分享与聚合,全文转载就不必了,尊重版权,圈子就这么大,若急用可联系授权

原文链接

当开发应用的时候,特别是微服务架构,并发的问题是不可避免的。多个请求同时更新相同的数据,导致前后矛盾的潜在问题。为了解决这个问题原文链接的作者开发了在NestJS框架下分布式锁的方案。在这篇文章中,我将会带大家实现,它是如何工作,和如何和NestJS结合在一起运用。为了深入探讨MurLock在分布式锁定领域的内容,请系好安全带!

MurLock的起源

在我们这个时代的广阔的数字领域中,竞态条件威胁着系统的平衡。想象一下,当多个服务实例同时尝试访问或修改一个共享资源时会发生的混乱。在分布式环境中,本地锁定机制失去了其效力。需求很明确:需要一个健壮的分布式锁定解决方案。于是,MurLock应运而生。

什么是MurLock

MurLock对NestJS框架提供了装饰器@MurLock(),来确保某些特定的代码段不会被同时执行,比如竞态条件。利用Redis的速度和效率,MurLock为跨多个实例或微服务的应用程序提供了一种可扩展的解决方案。

为什么要使用MurLock?

在典型的情境中,你可能会考虑使用简单的锁或互斥量。然而,在一个分布式环境中,不同的实例或服务可能位于不同的物理位置,传统的锁定方法就显得不足了。Redis,一个闪电般快速的内存数据结构存储,成为了这项任务的理想选择,因为它确保在多个服务或实例之间提供一致的锁。

NestJS:完美合作伙伴

NestJS,凭借其模块化的架构,是MurLock的理想伴侣。通过作为MurLockModule的一个模块进行集成,MurLock保持了NestJS所倡导的干净和模块化的结构

元数据:幕后的魔法

您有没有想过@MurLock()是如何施展它的魔法的?秘密成分就是reflect-metadata。通过允许运行时的内省,元数据为类、方法和属性提供了深入的了解。这使得@MurLock()修饰符具有动态性,允许它与不同的方法参数一起工作。 例如,考虑以下示例:

@MurLock(5000, 'user.id')  
async someFunction(user: User): Promise<void> {  
  // Critical section  
}

在这里,reflect-metadata捕获了‘user.id’属性,确保锁是针对单个用户的。因此,两个不同的用户可以同时访问该函数,但相同的用户则不能,从而防止了潜在的数据不一致性。

将MurLock与NestJS集成

开始先安装必要的包:

pnpm install --save murlock redis reflect-metadata

安装完毕后,你需要在你的主模块中导入并注册MurLockModule

import { MurLockModule } from 'murlock';  
  
@Module({  
  imports: [  
    MurLockModule.registerSync({  
      redisOptions: { host: 'localhost', port: 6379 },  
      // other options...  
    }),  
  ],  
})  
export class AppModule {}

NestJS的依赖注入(DI)系统确保了MurLock包背后的核心服务MurLockService在你的应用程序中是可访问的。

配置与使用:深入代码

MurLock的配置具有很强的灵活性。无论你需要同步的设置还是从其他服务中获取的异步设置,MurLock都能满足你。

这种适应性尤为关键,特别是当Redis的配置可能来自不同的来源时。 假设你的Redis配置在编译时不可用,而你需要从某些异步源(如数据库或配置服务)中获取它。别担心!MurLock会为你提供支持。

下面是一个例子,展示如何利用ConfigModuleConfigService进行异步配置:

import { MurLockModule } from 'murlock';  
  
@Module({  
  imports: [  
    MurLockModule.registerAsync({  
      imports: [ConfigModule],  
      useFactory: async (configService: ConfigService) => ({  
        redisOptions: configService.get('REDIS_OPTIONS'),  
        // other configurations...  
      }),  
      inject: [ConfigService],  
    }),  
  ],  
})  
export class AppModule {}

在您的服务中使用MurLock

import { MurLock } from 'murlock';  
  
@Injectable()  
export class AppService {  
  @MurLock(5000, 'user.id')  
  async someFunction(user: User): Promise<void> {  
    // Critical section  
  }  
}

处理死锁和避免陷阱

与任何锁机制的实践都一样,确保锁的生命周期短是很重要的。长时间持有锁可能导致死锁,尤其是在涉及多个锁的情境中。

MurLock通过允许开发者指定一个releaseTime来解决这个问题。如果在这段时间内锁没有被释放,它会自动被解锁。这不仅可以防止潜在的死锁,还确保了应用程序的高效性能。

另一个巧妙的功能是指数回退策略。当锁不可用时,MurLock可以配置为延迟越来越长的时间再次尝试获取锁。特别是在高并发的情况下,这个功能可能是一个救命的功能。

超越基础

尽管@MurLock 装饰器提供了一种轻松锁定方法的方式,但可能会出现您希望获得更多控制权的情况。此时,MurLockService进入了视野,这是一个可注入的服务,让开发者可以直接与锁定机制互动。无论你希望手动设置锁、检查状态,还是以编程方式释放它,MurLockService都能满足你的需求。

结论与鸣谢

分布式锁定是一个具有挑战性的领域,但使用像MurLock这样的工具,这个旅程变得更为简单。MurLock与NestJS的无缝集成,加上其丰富的功能集,使其成为开发人员工具库中不可或缺的工具。

要获得更详细的概述和可能的贡献,请查看官方GitHub仓库

请记住,每个工具都有其优点和局限性。知道何时以及如何使用它们是关键。祝您编码愉快,愿您的应用程序始终无死锁!🚀