共计 2592 个字符,预计需要花费 7 分钟才能阅读完成。
背景痛点
在用户注册和身份验证的场景中,手机号验证是确保安全性的关键环节。然而,高并发环境下,传统的手机号验证服务往往面临以下问题:

- 验证码洪泛 :恶意用户通过自动化脚本频繁请求验证码,导致短信费用激增和服务不可用。
- 重复发送 :同一用户在短时间内多次请求验证码,造成资源浪费和用户体验下降。
- 性能瓶颈 :传统的数据库锁在高并发场景下性能急剧下降,响应时间变长。
这些问题不仅影响用户体验,还可能引发安全风险和服务稳定性问题。因此,设计一套高效、稳定且安全的手机号验证服务至关重要。
技术选型
针对上述问题,我们对比了几种常见的技术方案:
- 数据库锁 :
- 优点:实现简单,直接利用数据库的行锁或表锁。
-
缺点:性能较差,高并发下容易成为瓶颈,且无法跨服务节点生效。
-
分布式锁(Redis):
- 优点:性能高,支持跨节点同步,易于实现。
-
缺点:需要处理锁的超时和续约问题,实现复杂度稍高。
-
消息队列 :
- 优点:异步处理,削峰填谷,提高系统吞吐量。
- 缺点:引入额外组件,增加系统复杂度。
综合考虑性能、实现复杂度和系统稳定性,我们最终选择了基于 Redis 的分布式锁和消息队列的组合方案。
核心实现
1. 使用 Redis 实现分布式锁控制发送频率
为了防止同一用户在短时间内多次请求验证码,我们使用 Redis 的 SETNX 命令实现分布式锁。核心逻辑如下:
- 用户请求验证码时,首先尝试获取锁(Key 为手机号 + 业务类型)。
- 如果获取成功,设置锁的过期时间(例如 60 秒),并发送验证码。
- 如果获取失败,说明用户在短时间内已经请求过验证码,直接返回错误提示。
2. 消息队列异步处理验证请求
为了应对高并发下的瞬时流量,我们将验证码发送逻辑异步化,通过消息队列(如 Kafka 或 RabbitMQ)进行削峰处理。具体流程:
- 用户请求验证码时,将请求放入消息队列。
- 消费者从队列中取出请求,调用短信服务发送验证码。
- 验证码发送成功后,将验证码存储到 Redis 中,并设置过期时间(例如 5 分钟)。
3. 验证码存储和验证的原子性操作
为了保证验证码的验证过程的原子性,我们使用 Redis 的原子操作(如 SETEX 和 DEL)来存储和删除验证码。验证流程如下:
- 用户提交手机号和验证码。
- 从 Redis 中获取该手机号对应的验证码。
- 比较用户提交的验证码和 Redis 中的验证码。
- 如果一致,删除 Redis 中的验证码并返回成功;否则返回失败。
代码示例
分布式锁实现(Python)
import redis
import time
class DistributedLock:
def __init__(self, redis_client, lock_key, expire_time=60):
self.redis = redis_client
self.lock_key = lock_key
self.expire_time = expire_time
def acquire(self):
# 尝试获取锁
result = self.redis.setnx(self.lock_key, 1)
if result:
# 设置过期时间
self.redis.expire(self.lock_key, self.expire_time)
return True
return False
def release(self):
self.redis.delete(self.lock_key)
验证码生成和验证逻辑(Java)
public class VerificationService {
private final RedisTemplate<String, String> redisTemplate;
public VerificationService(RedisTemplate<String, String> redisTemplate) {this.redisTemplate = redisTemplate;}
public String generateCode(String phoneNumber) {
// 生成 6 位随机验证码
String code = String.format("%06d", new Random().nextInt(999999));
// 存储验证码,5 分钟过期
redisTemplate.opsForValue().set("verification:" + phoneNumber, code, 5, TimeUnit.MINUTES);
return code;
}
public boolean verifyCode(String phoneNumber, String code) {String storedCode = redisTemplate.opsForValue().get("verification:" + phoneNumber);
if (code.equals(storedCode)) {
// 验证成功后删除验证码
redisTemplate.delete("verification:" + phoneNumber);
return true;
}
return false;
}
}
性能考量
我们通过压测工具(如 JMeter)对系统进行了性能测试,结果如下:
- 单节点 QPS:在 4 核 8G 的服务器上,QPS 可达 5000+。
- 响应时间 :99% 的请求在 50ms 内完成。
- 资源占用 :Redis 的 CPU 使用率在高峰期约为 30%。
为了进一步提高性能,我们建议:
- 使用 Redis 集群分散压力。
- 对热点手机号进行限流(如每分钟最多发送 5 次验证码)。
- 使用本地缓存(如 Caffeine)缓存频繁请求的验证码。
安全防护
为了防止恶意攻击,我们采取了以下措施:
- IP 限流 :对同一 IP 的验证码请求进行限流(如每分钟最多 10 次)。
- 手机号限流 :对同一手机号的验证码请求进行限流(如每分钟最多 3 次)。
- 图形验证码 :在发送短信验证码前,要求用户输入图形验证码,防止自动化脚本攻击。
避坑指南
在生产环境部署时,需要注意以下问题:
- Redis 高可用 :确保 Redis 集群的高可用性,避免单点故障。
- 锁超时处理 :分布式锁的过期时间要合理设置,避免死锁或锁过早释放。
- 消息队列积压 :监控消息队列的积压情况,及时扩容消费者。
- 短信服务降级 :在短信服务不可用时,要有降级方案(如本地日志记录后补发)。
开放性问题
- 如何在不增加复杂度的前提下,进一步提高系统的吞吐量?
- 是否有更优的分布式锁实现方案(如基于 Zookeeper 的锁)?
- 如何在不影响用户体验的情况下,增强防刷策略?
欢迎大家在评论区分享你的想法和经验!
正文完
