mysql 实现分布式锁

欢迎跳转到本文的原文链接:https://honeypps.com/architect/distribute-lock-based-on-database/

参照连接 https://blog.csdn.net/u013256816/article/details/92854794

概述

在单机时代,虽然不需要分布式锁,但也面临过类似的问题,只不过在单机的情况下,如果有多个线程要同时访问某个共享资源的时候,我们可以采用线程间加锁的机制,即当某个线程获取到这个资源后,就立即对这个资源进行加锁,当使用完资源之后,再解锁,其它线程就可以接着使用了。例如,在JAVA中,甚至专门提供了一些处理锁机制的一些API(synchronize/Lock等)。

但是到了分布式系统的时代,这种线程之间的锁机制,就没作用了,系统可能会有多份并且部署在不同的机器上,这些资源已经不是在线程之间共享了,而是属于进程之间共享的资源。

因此,为了解决这个问题,我们就必须引入分布式锁。分布式锁是指在分布式的部署环境下,通过锁机制来让多客户端互斥的对共享资源进行访问。

目前比较常见的分布式锁实现方案有以下几种:

  1. 基于数据库,如MySQL
  2. 基于缓存,如Redis
  3. 基于Zookeeper、etcd等

我们在讨论使用分布式锁的时候往往首先排除掉基于数据库的方案,本能的会觉得这个方案不够“高级”。从性能的角度考虑,基于数据库的方案性能确实不够优异,整体性能对比:缓存 > Zookeeper、etcd > 数据库。也有人提出基于数据库的方案问题很多,不太可靠。笔者认为采用哪种方案是要基于使用场景来看的,选择哪种方案,合适最重要。

我这里引用一下之前文章中的一个应用场景——分配任务场景。在这个场景中,由于是公司的业务后台系统,主要是用于审核人员的审核工作,并发量并不是很高,而且任务的分配规则设计成了通过审核人员每次主动的请求拉取,然后服务端从任务池中随机的选取任务进行分配。这个场景看到这里你会觉得比较单一,但是实际的分配过程中,由于涉及到了按用户聚类的问题,所以要比我描述的复杂,但是这里为了说明问题,大家可以把问题简单化理解。那么在使用过程中,主要是为了避免同一个任务同时被两个审核人员获取到的问题。在这个场景下使用基于数据库的方案就比较合理。

再补充一下,比如某一个服务它下游依赖数据库来做一些数据的读写操作,模型如下图所示:
在这里插入图片描述
一般服务也是多实例部署,如果多个实例需要操作同一份数据的时候(比如前面所说的同一个任务同时被两个审核人员获取到的问题),自然而然的引入了分布式锁。不过此时,我们并没有采用数据库的方案,而是引入了Redis,模型如下图所示:
在这里插入图片描述
引入Redis之后,正向的收益我就不赘述了,反向的收益是增加了系统的复杂度,对于整个服务而言,还需要多考虑1和2失效的情况。1失效是指服务模块与Redis的交互出现了异常,这种异常不单是指无法通信的异常,也有可能是服务模块发送请求只Redis的过程中或者Redis响应服务模块的过程中出现的异常,整体服务需要考虑这种情况:是重试、丢弃还是采取其他措施;2失效是指Redis本身出现了异常。数据链路一旦变长,系统复杂度一旦变大,在出现问题的时候会阻碍故障排查以及服务恢复,从而使得服务的整体可用性下调。

反观,如果采用数据库的方案,那么就可以省去了这部分的复杂度,如果数据库的方案能满足当下场景以及可视范围内的未来扩展,为什么还要平白地增加系统复杂度呢?大家要根据具体业务场景选择合适的技术方案,而不是随便找一个足够复杂、足够新潮的技术方案来解决业务问题。

下面我们来了解一下基于数据库(MySQL)的方案,一般分为3类:基于表记录、乐观锁和悲观锁。

基于表记录

要实现分布式锁,最简单的方式可能就是直接创建一张锁表,然后通过操作该表中的数据来实现了。当我们想要获得锁的时候,就可以在该表中增加一条记录,想要释放锁的时候就删除这条记录。

为了更好的演示,我们先创建一张数据库表,参考如下:

class Flock(models.Model):
    id = models.AutoField(primary_key=True)
    user_id=models.IntegerField()  # 设置唯一索引
    class Meta:
        db_table='flock'
from django.db import connection
#锁的使用
class Testlock(APIView):
    def post(self,request):
        uid=jwt_end(request.GET.get('jwt',None))
        number=request.GET.get('number')
        res=User.objects.get(pk=uid)
        if res.balance>0:
            with connection.cursor() as a:
                a.execute("INSERT INTO flock(user_id) VALUES (%s)"%res.id)
            # try:
            #     Flock.objects.create(user_id=res.id)
            # except Exception as e:
            #     return Response({'msg':"无法操作"})
            with connection.cursor() as c:
                c.execute('update user set balance=balance-%s where id=%s'%(int(number),res.id))

            with connection.cursor() as w:
                w.execute("DELETE FROM flock WHERE user_id=%s"%res.id)
            return Response({'msg':"ok"})
        else:
            return Response({'msg':"钱包为空"})

  

原文地址:https://www.cnblogs.com/zhangshijiezsj/p/14205303.html