redis异步锁实现方法

本文共有3316个字,关键词:

问题:如何给redis增加锁,防止多并发请求时产生重复发送短信?

解决:使用 Redis 分布式锁

方法:

1、redis配置文件 db/redis.py

import redis
import redis.asyncio as async_redis
from .config import config

redis_conn = redis.StrictRedis(
    host=config['RQ_SVR'],
    db=config['RQ_DB'],
    password=config['DBCACHE_PASS']
)

async_redis_conn = async_redis.StrictRedis(
    host=config['RQ_SVR'],
    db=config['RQ_DB'],
    password=config['DBCACHE_PASS']
)

cache_conn = redis.StrictRedis(
    host=config['DBCACHE_SVR'],
    db=config['DBCACHE_DB'],
    password=config['DBCACHE_PASS']
)


# 获取锁工具函数
async def acquire_lock(lock_key: str, expire_time: int = 10) -> bool:
    """ 
    使用 setnx 实现加锁
    """
    is_locked = await async_redis_conn.set(lock_key, "locked", nx=True, ex=expire_time)
    return bool(is_locked)


# 释放锁(可选)
async def release_lock(lock_key: str):
    await async_redis_conn.delete(lock_key)

2、发送验证码接口 api/account.py

@router.post(
    path='/api/bind/send/smscode',
    name='绑定手机发送验证码',
    description='用户通过手机号获取手机绑定验证码,用户更改绑定的手机,未绑定手机号')
async def bind_send_smscode(
    schema_data: SMSCodeSchema,
    db: AsyncSession=Depends(get_async_db),
    current_user: UserSchema = Depends(get_current_user)
    ):   
    old_phone = schema_data.old_phone
    phone = schema_data.phone
    phone = Decryption(phone).replace('"', '')
    crypt_phone = InfoEncrypt(phone)
    if old_phone:
        old_phone = Decryption(old_phone).replace('"', '')
        crypt_old_phone = InfoEncrypt(old_phone)
        if crypt_old_phone != current_user.phone:
            logger.info(f'bind_send_smscode: 原手机号错误')
            return {'code': 1, 'msg': '手机号错误'}

    # 判断手机号是否有重复
    obj_get = {
        'phone': crypt_phone,
        'is_delete': 0
    }    
    user = await crud_user.get(db, obj_get)
    if user:
        logger.info(f'bind_send_smscode: 手机号已被绑定')
        return {"code": 1, "msg": "手机号错误"}

    lock_key = f"lock:bind-phone:{phone}"
    code_key = f"bind-phone-{phone}"

    # 获取分布式锁
    lock_acquired = await acquire_lock(lock_key, expire_time=10)

    if not lock_acquired:
        logger.info(f'bind_send_smscode: 请不要重复发送')
        return {"code": 1, "msg": "手机号错误"}

    try:
        # 再次检查是否已经发送过验证码(防止缓存穿透)
        ttl_time = await async_redis_conn.ttl(code_key)
        if ttl_time > (settings.CODE_EXPIRE_MINUTES - 1) * 60:
            logger.info(f'bind_send_smscode: 请不要重复发送')
            return {"code": 1, "msg": "手机号错误"}
        # 发送短信
        sms_result = await send_smscode(phone)
        if sms_result["code"] != 0:
            return sms_result

        code = sms_result["smscode"]

        # 存储验证码到 Redis,并设置过期时间
        await async_redis_conn.set(code_key, code, ex=settings.CODE_EXPIRE_MINUTES * 60)

        return {"code": 0}
    finally:
        # 释放锁
        await release_lock(lock_key)
版权声明:本文为作者原创,如需转载须联系作者本人同意,未经作者本人同意不得擅自转载。
添加新评论
暂无评论