• 锁的应用+redis分布式锁


    1 引言

    • 思考:高并发情况,会不会出现问题?
    from django.db import connection
    
    # 锁的使用
    def testlock(request):
        res = User.objects.get(pk=1)
        # 查不到会报错
        if res.num > 0:
            time.sleep(5)
            # 人为延缓流程
            with connection.cursor() as c:
                c.execute('update user set num = num - 1 where id = 1')
            return HttpResponse('ok')
        else:
            return HttpResponse('钱包为空')
    
    • 什么是qbs

    引入xampp(Apache+MySQL+PHP+PERL),是一种很好的压测工具,不用认为压测,其中有一个指令对qbs的解释很好

    ab -c100 -n500
    # -c:并发   -n:500人
    # 500/100 5s完成请求(通常是1:4 1:5)
    

    这就说明了,高并发情况下,逻辑是对的,但是代码会出现问题

    2 锁的概念

    2.1 本地锁

    • 线程是不需要加锁的,因为在Cpython中存在GIL全局解释器锁
    # 本地锁
    def change_it(n):
        global num
        
        if lock.acquire():    
            try:
                for i in range(1000000):
                    num = num + n
                    num = num - n
            finally:
                lock.release()
        print(num)
    threads - [
        threading.Thread(target=change_it, args=(8,)),
        threading.Thread(target=change_it, args=(10,))
    ]
    lock = threading.Lock()
    [t.start() for t in threads]
    [t.join() for t in threads]
    

    2.2 分布式锁

    • 共用一把锁(全局锁,基于redis)
    setnx locknx test
    # key locknx value test
    setnx locknx task
    # 发现无法更改
    0
    # 证明:获取
    get locknx
    'test'
    # 释放锁
    del locknx
    1
    
    # 56行bind 127.0.0.1 注释掉,允许别人访问自己的redis
    # 不会引发资源冲突
    # 隐患:如果正在访问的时候宕机,永久锁入
    # 加一个延时锁
    expire locknx 10
    

    3 mysql + django 实现锁机制

    3.1 mysql中常见的锁

    3.1.1 乐观锁

    不操作不加锁,读取不加

    3.1.2 悲观锁

    读取就加锁,持悲观态度

    3.2 启发文件

    @contextmanager
    def add_lock(lock_id, expire_second, **kwargs):
    
        '''
        :param lock_id: 锁id
        :param expire_second: 过期时间
        :param kwargs: 用于以后自定义传入参数
        :return:
        '''
    
        dead_lock = False
    
        # 加锁
        while True:
            try:
                MyLock.objects.create(id=lock_id)
                break
            except django.db.utils.IntegrityError:
                # 出错看是在运行中还是死锁
                ctime = datetime.datetime.now() - datetime.timedelta(seconds=expire_second)
                if MyLock.objects.filter(Q(id=lock_id) & Q(create_time__gt=ctime)).exists():
                    print('当前任务执行中')
                    # time.sleep(5)
                    continue
                else:
                    # 排除刚好任务执行完的那一刻,还未释放锁就加锁的情况
                    if not dead_lock:
                        dead_lock = True
                        time.sleep(5)
                    # 删除死锁
                    MyLock.objects.filter(id=lock_id).delete()
                    print('死锁')
                    continue
        # 执行任务
        yield kwargs
        # 去锁
        MyLock.objects.filter(id=lock_id).delete()
    

    3.3 具体代码

    3.3.1 models.py
    class MyLock(models.Model):
        id = models.AutoField(auto_created=True, primary_key=True, serialize=False,verbose_name='ID')
        create_time = models.DateTimeField(auto_now=True, verbose_name='创建锁时间')
    
        class Meta:
            db_table = 'my_lock_model'
    
    3.3.2 views.py
    class WalletView(APIView):
        def get(self, request):
                dead_lock = False
                # 加锁
                while True:
                    try:
                        lock = MyLock.objects.filter(id=1)
                        if lock:
                            print('程序运行中,请等待')
                            time.sleep(5)
                            continue
                        else:
                            MyLock.objects.create(id=1)
                            break
                    except django.db.utils.IntegrityError:
                        # 出错看是在运行中还是死锁
                        ctime = datetime.datetime.now() - datetime.timedelta(seconds=60)
                        if MyLock.objects.filter(Q(id=1) & Q(create_time__gt=ctime)).exists():
                            print('当前任务执行中')
                            continue
                        else:
                            # 排除刚好任务执行完的那一刻,还未释放锁就加锁的情况
                            if not dead_lock:
                                dead_lock = True
                                time.sleep(5)
                            # 删除死锁
                            MyLock.objects.filter(id=1).delete()
                            print('死锁')
                            continue
                user_id = 3
                give_money = 1
                wallet = WalletModel.objects.get(user_id=user_id)
                if give_money <= wallet.money:
                    WalletModel.objects.filter(user_id=user_id).update(money=F('money') - give_money)
                print('释放锁')
                MyLock.objects.filter(id=1).delete()
    
                return Response({'msg': '提现成功', 'code': 200})
    

    3.4 xampp测试结果

    3.4.1 测试

    3.4.2 结果

    4 redis + django 实现分布式锁

    4.1 分布式锁

    分布式锁的本质是占一个坑,当别的进程也要来占坑时发现已经被占,就会放弃或者稍后重试。占坑一般使用setnx指令,只允许一个客户端占坑。先来先占,用完了再调用del指令释放坑。先来先占,用完了再调用del指令释放坑。为了解决死锁,引入expire过期时间。(这种竞争解决方案还可以考虑消息队列)

    4.2 具体代码

    4.2.1 views.py
    class WalletView(APIView):
        def post(self, request):
            user_info = decodeToken(request)
            user_id = user_info.get('user_id')
            user = WalletModel.objects.filter(user_id=user_id).first()
            try:
                money = float(request.data.get('money'))
            except Exception as e:
                return Response({'msg': '充值错误', 'code': 400, 'error': e})
            if user:
                   from decimal import Decimal
                   # decimal 和 float 不能叠加,要把 float 转换
                   user.money += Decimal(money)
                   user.save()
            else:
                WalletModel.objects.create(money=money, user_id=user_id)
    
            if money < 100:
                return Response({'msg': '充值成功', 'code': 200})
            else:
                coupon_code = create_code(4)
                if 100 <= money < 300:
                    CouponModel.objects.create(code=coupon_code, coupon='3', user_id=user_id)
                    return Response({'msg': '充值成功,同时您获取了10元无门槛红包哦', 'code': 200})
                elif 300 <= money < 500:
                    CouponModel.objects.create(code=coupon_code, coupon='1', user_id=user_id)
                    return Response({'msg': '充值成功,同时您获取了一张满300减50的优惠券', 'code': 200})
                elif 500 <= money < 1000:
                    CouponModel.objects.create(code=coupon_code, coupon='2', user_id=user_id)
                    return Response({'msg': '充值成功,同时您获取了终生八折优惠', 'code': 200})
    
    
        def put(self, request):
            import redis
            r = redis.Redis(db=15, decode_responses=True)
            dead_lock = True
            while True:
                try:
                    r.setnx('locknx', 'lock')
                    r.expire('locknx', 20)
                    break
                except django.db.utils.IntegrityError:
                    # 出错看是在运行中还是死锁
                    if r.get('locknx'):
                        print('当前任务执行中')
                        time.sleep(5)
                        continue
                    else:
                        # 排除刚好任务执行完的那一刻,还未释放锁就加锁的情况
                        if not dead_lock:
                            dead_lock = True
                            time.sleep(5)
                        # 删除死锁
                        r.delete('locknx')
                        print('死锁')
                        continue
            user_info = decodeToken(request)
            user_id = user_info.get('user_id')
            give_money = float(request.data.get('money'))
            wallet = WalletModel.objects.get(user_id=user_id)
            if give_money <= wallet.money:
                WalletModel.objects.filter(user_id=user_id).update(money=F('money')-give_money)
            r.delete('locknx')
            return Response({'msg': '提现成功', 'code': 200})
    

    4.3 xampp测试结果

    4.3.1 测试

    4.3.2 结果

    5 小结

    • 相比之下

    redis性能好,测试mysql有的时候会失败,而且跑完100并发需要86秒,但是redis只需要9秒,不同锁有不同的应用场景,这才是重中之中

  • 相关阅读:
    SQL学习记录
    Python 函数和变量作用域
    Python 使用socket实现一对多通信
    Flask wtforms validate_on_submit() 无法返回值问题
    Flask WTForm BooleanField用法
    Python3 中的nonlocal用法
    Python 实现二进制循环效果
    Python 各种类型转换
    第一章:数据结构
    Python Challenge
  • 原文地址:https://www.cnblogs.com/mapel1594184/p/14208449.html
Copyright © 2020-2023  润新知