zoukankan      html  css  js  c++  java
  • 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':"钱包为空"})
    

      

  • 相关阅读:
    将kali linux装入U盘 制作随身携带的kali linux
    arch/manjaro linux configuration
    python资源

    JSP通过AJAX获取服务端的时间,在页面上自动更新
    Spark基础
    MapReduce基础
    HDFS基础
    C#输出杨辉三角形
    Java窗体居中显示的2种方法
  • 原文地址:https://www.cnblogs.com/zhangshijiezsj/p/14205303.html
Copyright © 2011-2022 走看看