python 带参装饰器 +reids ,处理高频操作

因业务需要写了个python的装饰器与redis同时使用,用于处理高并发操作,防止记录重复操作

装饰器部分代码

import hashlib
from functools import wraps
from api.api_state import APIState
from api.base_api import APIResponse
from utils.redis import acquire_lock_with_timeout, release_lock



def lock_operate(param_type: str = None, param_key: str = None, prefix: str = 'lockoperate:'):
    """锁操作装饰器

    :param param_type: 取参类型
    :param param_key: 取参键名
    :param prefix: 锁名前缀
    :return:

    eg:
    @lock_operate(param_type='data', param_key='serial_no', prefix='lockoperate:draw:')
    @lock_operate(param_type='query_params', param_key='orderid', prefix='lockoperate:draw:')
    @lock_operate(param_type='kwargs', param_key='pk', prefix='lockoperate:draw:')
    @lock_operate(param_type='user', param_key='id', prefix='lockoperate:draw:')
    """

    def wrapper(func):
        def _wrapper(*args, **kwargs):
            view, request, *_args = args

            lockname = ''
            options = {"query_params": request.query_params, "data": request.data, "user": request.user,
                       "kwargs": kwargs}
            obj = options.get(param_type, None)
            if obj:
                if isinstance(obj, (dict, QueryDict)):
                    lockname = obj.get(param_key)
                else:
                    lockname = getattr(obj, param_key)

            lockname = f'{prefix}{lockname}'
            logger.debug(f'lock_operate _wrapper lockname: {lockname}')
            identifier = acquire_lock_with_timeout(lockname)
            if not identifier:
                return Response({'msg': '请勿重复发送请求'})

            try:
                return func(*args, **kwargs)
            finally:
                release_lock(lockname, identifier)

        return _wrapper

    return wrapper

reids 处理部分

import logging
import math
import time
import uuid

import redis
from django.conf import settings

logger = logging.getLogger(__name__)

conn = redis.StrictRedis(host=settings.REDIS_CONFIG["HOST"],
                         port=settings.REDIS_CONFIG["PORT"],
                         db=settings.REDIS_CONFIG["DB"],
                         password=settings.REDIS_CONFIG["PASSWORD"])


def acquire_lock_with_timeout(lockname: str, acquire_timeout: int = 1, lock_timeout: int = 10,
                              prefix: str = "lock:") -> str:
    """获取带有超时功能的锁

    :param lockname: 锁名
    :param acquire_timeout: 获取锁时超时的时间
    :param lock_timeout: 锁超时时间
    :param prefix: 锁名前缀
    :return:
    """
    full_lockname = f"{prefix}{lockname}"
    logger.debug(f"acquire_lock_with_timeout: {full_lockname}")
    # 128位随机标识符
    identifier = uuid.uuid4().hex
    # 确保传给EXPIRE的都是整数
    lock_timeout = int(math.ceil(lock_timeout))

    end = time.time() + acquire_timeout
    while time.time() < end:
        # 获取锁并设置过期时间
        if conn.setnx(full_lockname, identifier):
            conn.expire(full_lockname, lock_timeout)
            return identifier
        # 检查过期时间, 并在有需要时对其进行更新
        elif not conn.ttl(full_lockname):
            conn.expire(full_lockname, lock_timeout)

        time.sleep(.001)

    logger.info(f"can not acquire_lock_with_timeout: {full_lockname}")
    return ""


def release_lock(lockname: str, identifier: str, prefix: str = "lock:", decode_responses=True) -> bool:
    """释放锁

    :param lockname: 锁名
    :param identifier: 锁值
    :param prefix: 锁名前缀
    :param decode_responses: 是否需要解码 redis 取出的值
    :return:
    """
    full_lockname = f"{prefix}{lockname}"
    logger.debug(f"release_lock: {full_lockname}")

    pipe = conn.pipeline(True)
    while True:
        try:
            # 检查并确认进程还持有着锁
            pipe.watch(full_lockname)

            lock_value = pipe.get(full_lockname)
            if decode_responses:
                lock_value = lock_value.decode()

            if lock_value == identifier:
                # 释放锁
                pipe.multi()
                pipe.delete(full_lockname)
                pipe.execute()
                return True

            pipe.unwatch()
            break

        # 有其他客户端修改了锁, 重试
        except redis.exceptions.WatchError:
            pass

    # 进程已经失去了锁
    return False


def get_redis_value(key):
    return conn.get(key)


def set_redis_value(key, value, timeout=(12 * 60 * 60)):
    conn.set(key, value, ex=timeout)


使用方式:

class OrderLogisticsDetailsView(APIView):
    """
    get
        express_id:
        express_no:
    """

    @lock_operate(param_type="user", param_key="express_no", prefix="lockoperate:user:")
    def get(self, request, *args, **kwargs):
        express_no = kwargs.get("express_no", None)
        express_id = kwargs.get("express_id", None)
        if (not express_id is None) and (not express_no is None):
            Logisticsinfo_ins = TLogisticsinfo.objects.filter(ls_id=express_id).first()
            ret = query_logistics_information(Logisticsinfo_ins, express_no)

            if ret:
                return ret

        return APIResponse(status=APIState.PARAMTER_ERROR.value)

参考链接:
https://mp.weixin.qq.com/s/nPNowhyNPQah-eQBbsj29Q

posted @ 2021-01-08 10:21  随心的啊陈  阅读(136)  评论(0编辑  收藏  举报