问题:如何给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)