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)
每一段路,每一段人生,我都以自己的方式走下去,结果怎样,我并不在意