redisはどのように秒殺の超過販売問題を解決します

18615 ワード

1.reidsを用いたwatch+multi指令実現
  • watch+multi超販売問題を解決
  • #! /usr/bin/env python
    # -*- coding: utf-8 -*-
    import redis
    def sale(rs):
        while True:
            with rs.pipeline() as p:
                try:
                    p.watch('apple')                   #   key  apple       
                    count = int(rs.get('apple'))
                    print('         : %d' % count)
                    p.multi()                          #     
                    if count> 0 :                      #         
                        p.set('apple', count - 1)
                        p.execute()                    #     
                    p.unwatch()
                    break                              #                    
                except Exception as e:                 #    watch        ,WatchError    
                    print('[Error]: %s' % e)
                    continue                           #       
    
    rs = redis.Redis(host='127.0.0.1', port=6379)      #   redis
    rs.set('apple',1000)                               # #    redis      apple     value  1000
    sale(rs)
    

    1)原理
  • ユーザが購入すると、WATCHによりユーザの在庫を傍受し、在庫がwatch傍受後に変化すると異常をキャプチャして在庫減額操作
  • を放棄する.
  • 在庫が変更を傍受することなく、数量が1より大きい場合、在庫数量は1減少し、タスク
  • を実行する.
    2)弊害
  • Redisトランザクションを完了しようとすると、トランザクションの失敗によって
  • の再実行が繰り返される可能性があります.
  • 商品の在庫量が正確であることを保証することは重要であるが、WATCHを単純に使用するというメカニズムはサーバに圧力がかかりすぎる
  • である.
    2、reidsのwatch+multi+setnx指令を用いて実現
    1)なぜ自分で鍵を作るのか
  • には、同様のSETNXコマンドがRedis内のロックを実現する機能があるが、他のロックが提供するメカニズムは完全ではない
  • である.
  • でsetnxも分散ロックのいくつかの高度な特性を備えていないか、
  • を手動で構築する必要があります.
    2)redisロックを作成する
  • Redisでは、SETNXコマンドを使用するロックを構築することができる:rs.setnx(lock_name,uuid値)
  • .
  • でロックすることは、ランダムに生成する128ビットのUUIDにビットキーの値を設定し、他のプロセスによって
  • を取得することを防止することである.
    3)ロック解除
  • ロックの削除操作は簡単で、対応するロックのkey値が取得したuuid結果を判断検証する
  • のみである.
  • 適合条件(uuid値を判断)deleteによりredisから削除すればよい、pipe.delete(lockname)
  • また、他のユーザが同名のロックを持っている場合、uuidの違いにより、検証後に他人のロック
  • を誤って解放することはない.
    4)ロックが解除できない問題の解決
  • 以前のロックでは、あるプロセスがロックを持つと突然プログラムがクラッシュし、ロックが
  • を解放できないという問題も発生する.
  • で他のプロセスがロックを持って作業を継続することができず、このような問題を解決するために、ロックを取得する際にロックを付加するタイムアウト機能
  • が可能となる.
  • setnx+watch+multi超販売問題を解決
  • #! /usr/bin/env python
    # -*- coding: utf-8 -*-
    import redis
    import uuid
    import time
    
    # 1.       
    def get_conn(host,port=6379):
        rs = redis.Redis(host=host, port=port)
        return rs
    
    # 2.   redis 
    def acquire_lock(rs, lock_name, expire_time=10):
        '''
        rs:     
        lock_name:    
        acquire_time:       
        return -> False      or True     
        '''
        identifier = str(uuid.uuid4())
        end = time.time() + expire_time
        while time.time() < end:
            #              ,     ,      ,  False
            if rs.setnx(lock_name, identifier): #      
                return identifier
            time.sleep(.001)
            return False
    
    # 3.    
    def release_lock(rs, lockname, identifier):
        '''
        rs:     
        lockname:    
        identifier:   value ,    
        '''
        pipe = rs.pipeline(True)
        try:
            pipe.watch(lockname)
            if rs.get(lockname).decode() == identifier:  #             
                pipe.multi()           #     
                pipe.delete(lockname)
                pipe.execute()
                return True            #    
            pipe.unwatch()              #     
        except Exception as e:
            pass
        return False                    #     
    
    
    '''            '''
    def sale(rs):
        start = time.time()            #       
        with rs.pipeline() as p:
            '''
                      
                    ,       
            '''
            while True:
                lock = acquire_lock(rs, 'lock')
                if not lock: #     
                    continue
                try:
                    count = int(rs.get('apple')) #   
                    p.set('apple', count-1)      #   
                    p.execute()
                    print('     : %s' % count)
                    break
                finally:
                    release_lock(rs, 'lock', lock)
            print('[time]: %.2f' % (time.time() - start))
    
    rs = redis.Redis(host='127.0.0.1', port=6379)      #   redis
    rs.set('apple',1000)                               # #    redis      apple     value  1000
    sale(rs)
    
  • 最適化:分散ロックにタイムアウト時間を加えるデッドロック防止
  • # 3.       ↑
    def acquire_expire_lock(rs, lock_name, expire_time=10, locked_time=10):
        '''
        rs:     
        lock_name:    
        acquire_time:       
        locked_time:       
        return -> False      or True     
        '''
        identifier = str(uuid.uuid4())
        end = time.time() + expire_time
        while time.time() < end:
            #              ,     ,      ,  False
            if rs.setnx(lock_name, identifier): #      
                # print('    : %s' % identifier)
                rs.expire(lock_name, locked_time)
                return identifier
            time.sleep(.001)
        return False