发送短信时要对手机号码做频次限制
第一版设计为一分钟发两次,超过两次加黑名单三分钟,加黑名单逻辑会存在分布式锁的问题。
第二版优化为超过限制后直接落错误表,不发送即可。
维度:模板 业务类型 子业务类型 ALL
1.lua
返回key对应的当前值,如果是第一次发送,设置过期时间,lua脚本会保证事务性。
local times = redis.call('incr',KEYS[1])
if times == 1
then redis.call('expire',KEYS[1], ARGV[1])
end
return times
2.脚本加载
package com.bestpay.messagecenter.product.core.redis.impl;
import com.bestpay.messagecenter.product.common.util.StreamUtil;
import com.bestpay.messagecenter.product.core.model.oss.FreqFilterBO;
import com.bestpay.messagecenter.product.core.redis.FreqRedisService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.connection.jedis.JedisConnection;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.stereotype.Service;
import redis.clients.jedis.Jedis;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* 通用频次计数器
*
* @author linxing
* @version Id: FreqRedisServiceImpl.java, v 0.1 2017/6/8 16:20 lxn Exp $$
*/
@Slf4j
@Service
public class FreqRedisServiceImpl implements FreqRedisService {
/**
* lua 脚本
*/
private String scriptShal;
/**
* Jedis连接操作
*/
@Resource
private JedisConnectionFactory jedisConnectionFactory;
/**
* 启动载入lua脚本到redis
*/
@PostConstruct
public void loadScript() {
JedisConnection connection = jedisConnectionFactory.getConnection();
Jedis jedis = connection.getNativeConnection();
String script = StreamUtil.convertStreamToString(FreqRedisServiceImpl.class.getClassLoader().
getResourceAsStream("freqRedisCounter.lua"));
this.scriptShal = jedis.scriptLoad(script);
log.info("频率计数器脚本载入成功,sha1:{}", this.scriptShal);
connection.close();
}
public Long getLockTimeForDiffKeyAndSameLimitTime(Map<String, Long> redisKeysMap, Map<String, List<FreqFilterBO>> freqFilterMap) {
JedisConnection connection = jedisConnectionFactory.getConnection();
Jedis jedis = connection.getNativeConnection();
Long lockTime = 0L;
for (Map.Entry<String, Long> eachKey : redisKeysMap.entrySet()) {
String redisKey = eachKey.getKey();
String limitTime = eachKey.getValue().toString();
List<String> keys = new ArrayList<>();
List<String> values = new ArrayList<>();
keys.add(redisKey);
values.add(limitTime);
Object evalResult = jedis.evalsha(scriptShal, keys, values);
Long currentCount = Long.parseLong(evalResult.toString());
log.info("redis key:{},当前值为:{}",redisKey,currentCount);
List<FreqFilterBO> freqFilterBOS = freqFilterMap.get(redisKey);
for (FreqFilterBO eachFilter : freqFilterBOS) {
if (currentCount > eachFilter.getLimitCount()) {
//这里1L代表任意一个大于0,要进行过滤的场景,没有实际意义
lockTime = 1L;
}
}
}
connection.close();
return lockTime;
}
}
/**
* 计算最大锁定时间
*
* @param bos
* @return
*/
public Long getBlackListTimeMax(List<FreqFilterBO> bos, String phone) {
if (CollectionUtils.isEmpty(bos)) {
log.info("手机号:{},没有找到过滤规则。",phone);
return -1L;
}
Long lockTime;
Map<String, Long> redisKeyMap = new HashMap<>();
Map<String, List<FreqFilterBO>> freqFilterMap = new HashMap<>();
for (FreqFilterBO each : bos) {
String redisKey = RedisProductKeys.getRecvFreqCounter(phone, each.getRelatedItems(),
each.getRelatedValue(), each.getLimitTime().toString());
redisKeyMap.put(redisKey, each.getLimitTime() * 60);
List<FreqFilterBO> redisKeyFilter = freqFilterMap.get(redisKey);
if (null == redisKeyFilter) {
redisKeyFilter = new ArrayList<>();
}
redisKeyFilter.add(each);
freqFilterMap.put(redisKey, redisKeyFilter);
}
lockTime = freqRedisService.getLockTimeForDiffKeyAndSameLimitTime(redisKeyMap,
freqFilterMap);
redisKeyMap.clear();
freqFilterMap.clear();
return lockTime;
}