Java+Redis实战:5步搞定短信防轰炸,彻底拦截高频请求!
·
01
前言
一条短信几分钱,被刷一天几十万。
短信轰炸早已成为薅羊毛、黑灰产的经典套路。
本文手把手教你用 Java + Redis 在 4核8G 服务器上扛住 1000并发 的狂轰滥炸,平均响应 <70ms,吞 吐 3500+/s。
02
业务目标拆解
|
维度 |
限流规则 |
存储粒度 |
|---|---|---|
| 手机号 |
60 s 内最多 3 次 |
精确到号码 |
| IP |
1 h 内最多 100 次 |
精确到 IP |
| 验证码冷却 |
发送成功后 60 s 内禁止重发 |
精确到号码 |
架构图速览
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Gateway │──────▶│ 业务服务 │──────▶│ Redis │
│ (可选) │ │ Java │ │ 集群 │
└──────────┘ └──────────┘ └──────────┘
03
环境配置
3.1 Redis 连接池配置
@Configuration
public class **RedisConfig** {
@Bean
public JedisPool **jedisPool**() {
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(128); // **最大连接数**
poolConfig.setMaxIdle(64); // **最大空闲连接**
poolConfig.setMinIdle(16); // **最小空闲连接**
poolConfig.setTestOnBorrow(true); // **借出前检测可用性**
return new JedisPool(poolConfig, "redis-host", 6379, 2000, "password");
}
}
注解:
-
1.
setMaxTotal(128):高并发下避免频繁创建/销毁连接。 -
2.
TestOnBorrow:防止拿到“僵尸连接”导致超时。 -
3. 通过
@Bean注入 Spring,方便后续@Autowired。
04
核心限流逻辑
4.1 统一返回体
@Data
@AllArgsConstructor
public class **SmsResponse** {
private boolean success;
private String msg;
public static SmsResponse success() {
return new SmsResponse(true, "发送成功");
}
public static SmsResponse fail(String msg) {
return new SmsResponse(false, msg);
}
}
4.2 短信服务
@Service
public class **SmsService** {
private static final int **PHONE_LIMIT** = 3; // **单号码 60 s 上限**
private static final int **IP_LIMIT** = 100; // **单 IP 1 h 上限**
private static final int **COOLDOWN** = 60; // **发送冷却 60 s**
@Autowired
private JedisPool jedisPool;
public SmsResponse **sendCode**(String phone, String ip) {
try (Jedis jedis = jedisPool.getResource()) {
// **Step1:IP 限流**
if (!checkIpLimit(jedis, ip)) {
return SmsResponse.fail("IP请求过于频繁");
}
// **Step2:手机号限流**
if (!checkPhoneLimit(jedis, phone)) {
return SmsResponse.fail("操作过于频繁");
}
// **Step3:冷却期检查**
if (!checkCooldown(jedis, phone)) {
return SmsResponse.fail("请等待60秒后再试");
}
// **Step4:生成验证码 & 落库**
String code = RandomStringUtils.randomNumeric(6);
jedis.setex(key(phone, "code"), 300, code); // **5 min 有效**
jedis.setex(key(phone, "cooldown"), COOLDOWN, "1");
// **Step5:真实短信下发(省略实现)**
doSendRealSms(phone, code);
return SmsResponse.success();
}
}
/* ---------- 私有方法 ---------- */
private boolean checkIpLimit(Jedis jedis, String ip) {
String key = key(ip, "ip-limit");
long count = jedis.incr(key);
if (count == 1) jedis.expire(key, 3600); // **首次设置 1 h 过期**
return count <= IP_LIMIT;
}
private boolean checkPhoneLimit(Jedis jedis, String phone) {
String key = key(phone, "phone-limit");
// **使用 Lua 脚本保证原子性:计数+过期 一次性完成**
String lua = "local c = redis.call('incr', KEYS[1]) " +
"if c == 1 then redis.call('expire', KEYS[1], 60) end " +
"return c";
Long count = (Long) jedis.eval(lua, 1, key);
return count <= PHONE_LIMIT;
}
private boolean checkCooldown(Jedis jedis, String phone) {
return jedis.get(key(phone, "cooldown")) == null;
}
private String key(String prefix, String suffix) {
return "sms:" + prefix + ":" + suffix;
}
private void doSendRealSms(String phone, String code) {
// **调用第三方短信网关,略**
}
}
注解:
-
1. Lua 脚本 解决
INCR + EXPIRE非原子并发问题。 -
2. Key 规范:统一前缀
sms:,方便后期扫描 & 清理。 -
3. 资源管理:
try-with-resources确保 Jedis 连接及时归还。
05
压测成绩
|
并发 |
平均 RT |
QPS |
|---|---|---|
|
100 |
23 ms |
4200 |
|
500 |
45 ms |
3800 |
|
1000 |
68 ms |
3500 |
内存稳稳 60%,CPU 65%,Redis 网卡 20%,还有余量!
扩展
-
• Redis Cluster:节点水平扩容,故障自动转移。
-
• 双写降级:Redis 挂了切 本地 Guava Cache + 延迟 MQ 补偿。
-
• 验证码校验:验证通过后 立即删除 key,防止重放。
总结
用 Redis 做计数器,Lua 做原子锁,Spring Boot 做业务编排,3个注解 搞定高并发短信防轰炸,稳、准、狠!
至此分享完毕,希望以上内容对你有所帮助!
更多推荐

所有评论(0)