Kotlin 协程踩坑之 Redis 分布式锁

本文不涉及技术细节,只是记录一点踩坑的过程和感受。

背景

项目中的两个不同的服务可能会同时修改一个资源的状态,决定使用 Redis 分布式锁。我这边的服务是用 Kotlin 写的,每当接收到一个 HTTP 请求,就会开一个协程来处理。在协程运行的过程中,会调用 Redisson 来获取一个锁,运行结束后(一般要十几秒),会释放锁。

问题

服务跑的时候发现,有时候,同时收到了两个请求,这两个请求会同时锁一个东西,然而它们竟然一起运行了,导致彼此之间产生了干扰。更奇怪的是,尝试复现的时候,有时候能复现,有时又不能。

分析

问题断断续续出现了几天,后来想到把获取锁的代码 log 下来。log 什么呢,记录当前线程的 id 和 name,即 Thread.currentThread() 的一些属性。

问题又复现了,赶紧看 log,发现了重点:当2个请求到来,后请求锁的协程拿锁,没有锁住的时候,它的当前线程的 id 和先请求锁的协程是一样的!而当后请求锁的协程拿锁,被锁住的时候,它的线程 id 是不一样的。

..好像知道怎么解决了。我作为 Kotlin 的入门菜鸡,在收到请求时,随手使用了

GlobalScope.launch {
    ......
}

来创建一个协程。而新的协程被调度到哪个线程中,我就没有管了。而 Redisson 的锁是可重入的,并且是基于线程的。如果多个协程都跑在同一个线程中,锁就不能锁住了。

发现了问题所在,就好解决了。创建协程时,改为

GlobalScope.launch(newSingleThreadContext("NewThread")) {
    ......
}

这样创建出来的协程就会在新的线程中跑,问题不再出现。

总结

这样一来,每一个新的请求都会创建一个新的线程,协程的优势岂不是没有了?大概协程世界的概念不应该用在线程的世界中吧,不然容易掉坑里。根源还是自己刚入门太菜,还需要继续学习。

相关参考

1. https://kotlinlang.org/docs/reference/coroutines/coroutine-context-and-dispatchers.html


发表评论