处置一个互斥疑问 系统并发用户数优化了10倍!

互斥锁在成功并发场景下业务操作的原子性以及处置互斥访问疑问方面,是极为有效的手腕之一。因其经常使用模式相对方便且安保,所以在互联网的散布式系统以及嵌入式并发场景中都有着宽泛的运行。

但是,若互斥锁的选择与经常使用不当,极有或许成为系统性能的瓶颈之一。因此,对互斥锁启动正当优化,是系统性能优化的关键路径。我将为你分享一个互联网场景下互斥锁优化的案例。依照优化前的软件成功、性能瓶颈剖析以及优化处置方案的思绪,带你深化剖析我是如何优化业务中的互斥锁,以及如何将业务的 RPS(Requests per second,恳求吞吐量)性能目的优化 10 倍以上的。

在这个案例中够片面了解剖析与优化互斥锁的详细环节,从而在业务中准确识别出哪些场景下的互斥锁可以优化,哪些场景下无法以。此外,你还将把握一种手动成功事务的机制(允许业务操作回滚机制),以此代替业务中的互斥锁,进一步优化软件性能。

接上去,咱们先看看该案例中业务优化前的成功状况以及存在的性能疑问。

优化前的业务虚现为什么会有性能疑问?

这共性能优化案例的业务场景如下:用户向在线表单提交一条记载,该记载蕴含泛滥字段内容。其中,局部字段在拔出时有一个规定要求,即不能与已有的字段值重复。为了便于了解,这里我用一张图来形容原业务系统中成功字段拔出值不重复规定的成功逻辑,详细状况如下所示。

可见,在该业务中经常使用了一个 Redis 锁来成功互斥访问,从而成功了被加锁的业务逻辑口头的原子性,所以这局部计算逻辑在系统中是串行口头的。而被加锁的业务逻辑关键有三个关键操作,区分是:

对拔出的字段值启动检测,检查其在数据库中能否有重复状况。若出现重复值,则拔出失败并间接分开;若未出现重复值,则口头下一步操作。在这个环节中,系统会遍历一切要求值不能重复的字段项,只需其中任何一个字段项出现值重复,就会分开。

即用户提交记载环节中的一些关键业务操作。这些操作具备不能被拆分口头且不能被回滚的特点。若操作成功,则口头下一步操作;否则,也会间接分开。

由于上述三个操作经过加锁保障了原子性口头,所以前面检测的 “字段值不重复” 的条件依然有效。在这一步,会将有的字段启动拔出。

除此之外,在优化前的代码成功中,须要启动重复性校验的字段都会记载在 Redis 中。所以,图中的操作 1、操作 3 都是基于 Redis 来成功的。

在看完这个业务虚现逻辑图后,你或许会感到猎奇:这种字段惟一性检测机制为何不经常使用相关数据库中的字段惟一性检测机制来成功呢?这确实是个好疑问,我在刚看到这个业务逻辑成功时也雷同猎奇,起初深化剖析业务后才了解其如此成功的要素。

实践上,在这个业务系统中大概有 1000 万张表单,且每张表单的字段惟一性规定或许各不相反,用户还能轻易修正这个规定。所以,该系统在设计成功时,将一切表单中的一切字段都放到了一张很大的数据库表中,因此无法经常使用数据库表上的字段惟一性规定来处置这个疑问。

原来的 Redis 加锁成功模式较为方便,且是依照单个表单来启动加锁的,所以在单个表单并发提交恳求吞吐量不是很大的状况下,不会对系统性能发生太大影响。

但是,随着系统业务规模逐渐增大,会出现大批表单的并发恳求吞吐量暴增的状况。此时,当单个表单提交恳求超越并发恳求吞吐量的下限值后,就会引发两个较为重大的性能疑问:

其一,针对超越并发恳求吞吐量性能下限值的那个表单,用户在提交表单的页面会出现卡死现象,造成提交数据失败;其二,由于后端服务系统是基于进程模型的,而进程资源的数目有限,一旦一般表单提交数据恳求的处置进程被阻塞,占用少量进程资源,就会造成整个系统无法反常处置一切的业务恳求。

因此,优化单个表单提交恳求吞吐量的性能目的,就成为了这个软件系统性能优化的关键疑问。那么接上去,咱们就要先搞明确,这个互斥锁是如何影响这个表单的恳求吞吐量性能的。

互斥锁是如何影响最大恳求吞吐量的?

接上去,我就经常使用一个公式来形容下在这个案例中,经常使用了 Redis 互斥锁,来计算 Max RPS(最大恳求吞吐量)的计算方法,详细公式如下所示:

在这个公式中,由于 Lock 和 Unlock 是经过 Redis 的互斥锁来成功的,其经常使用的 Redis 的 script 脚本成功如图所示。经在实在系统中测量,Lock time 与 Unlock time 的操作期间之和约为 3ms。接着,可经过上方的公式启动计算。若两边加锁的计算逻辑(resource competition)口头开支约为 30ms,那么对应的 Max RPS = 1s / (3ms + 30ms),即大概为 30RPS 左右。也就是说,只要当把加锁的计算逻辑降落极限值为 0 时,对应的 Max RPS 才可以到达 300RPS 左右。这里须要留意的是,由于业务中的互斥锁是全局控制的,所以当系统到达最大 RPS 时,即使经过弹性裁减机制部署再多的后端服务虚例进程,也无法再优化这共性能目的了。

至此,在这共性能优化案例中,咱们经过测量得悉加锁的计算逻辑口头期间为 30RPS,而后依据上方的公式,计算出的最大 RPS 值也约为 30RPS 左右,这与实在的性能测试失掉的性能目的值齐全分歧。

好的,如今疑问曾经比拟清楚了。那么,有没有方法可以优化优化这个系统的性能呢?上方咱们来看一下。

果这个业务逻辑没有参与互斥锁,在 99.9% 的状况下业务逻辑也是正确的。

所以,针对这种场景,咱们可以采用手动成功事务机制,优化掉业务代码中的互斥锁,以优化恳求吞吐量的性能。

咱们已知在这个案例中,经常使用互斥锁处置的外围疑问是判别字段不重复和字段拔出操作的原子性疑问。因此,咱们可以思考采用一些优化机制,独自成功这两个操作组合的原子性。

但要留意,假设在互斥锁的经常使用场景中,被加锁的业务操作还有更复杂的分歧性要求,比如存在数据库写抵触的疑问等,那么这种互斥锁成功就不能被方便地优化掉了。

那么关于这个案例中的互斥锁而言,咱们应该怎么优化呢?

我来说说我想到的优化思绪。这里呢,为了更明晰地形容该处置方案,我用了一个流程图来给你详细地引见下性能优化后的详细成功环节,如下图所示。

也就是说,咱们可以将 “字段不重复检测”“单个字段拔出”“其余操作” 这三个操作绑定在一同,成功一种事务机制的才干,以便在前面操作失败的状况下,能够回滚到前面的操作中。实践上,原来的 Redis 互斥锁关键是为了成功 “字段不重复检测” 和 “字段的拔出操作” 的原子性。而在手动成功事务机制之后,咱们可以把这两步操作放到开局处口头,而后经常使用 Redis 的 Pipeline 机制保障这两步操作组合的原子性,从而不会被其余 Redis 操作搅扰。

这样,关于接上去的其余操作(即用户提交数据环节中的一些无法拆分的关键业务操作),假设操作成功,就提交义务成功完结;假设操作失败,则须要回滚之前的字段拔出操作。另外,为了成功事务的机制和才干,咱们还须要在前面字段拔出时,同时记载拔出前的形态和拔出后的变卦形态,从而成功失败后的回滚机制。

其实,这里我还思考过另外两种成功方案,区分是基于 Redis 的事务机制和基于 MongoDB 上的事务机制。但是,我最后在成功时并没有采用,这面前有很多要素。比如,经常使用 MongoDB 的事务须要启动数据迁徙,而且须要更新系统的 MongoDB 集群的数据库版本等。以及经常使用 Redis 事务机制的代码成功并不敌平等等。不过,这里有一个最关键的要素就是,不论是经常使用 Redis 事务还是 MongoDB 上的事务,它们都把对字段拔出操作的抵触期间,拉长到了步骤 3 “其余操作” 完结之后,而这样就清楚增大了事务抵触失败的概率。

所以最后,咱们采用前面这种优化后的成功机制。由于去除了互斥锁,所以用户间的提交记载可以更大水高山并行。而且优化后的成功模式,只要 Pipeline 操作会排队处置,而由于单个 Pipeline 的口头时长在 1ms - 3ms 之间,所以最后优化后的表单最大恳求吞吐量,就从原来的 30RPS,优化到了 300RPS 左右,这样就成功了性能优化超越 10 倍的目的。

您可能还会对下面的文章感兴趣: