做法
使用redis的lua脚本功能来限频
在redis中定时刷新系统时间来作为一个全局的时钟
限频脚本:
/**
* 获取令牌的lua脚本
*/
public final static String SCRIPT = "local epoch = redis.call("hget",KEYS[1],'Epoch');
" +
"local currentEpoch = tonumber(ARGV[1]);
" +
"if(epoch == false and currentEpoch ~= epoch) then
" +
" redis.call('hset', KEYS[1], 'Epoch',currentEpoch);
" +
" redis.call("hset", KEYS[1], 'Count', tonumber(ARGV[2]));
" +
" redis.call('pexpire', KEYS[1], 2000);
" +
" return 1;
" +
"end;
" +
"local currentCount = redis.call("HINCRBY", KEYS[1],'Count', tonumber(ARGV[3]));
" +
"if(currentCount > 0) then
" +
" return 1;
" +
"else
" +
" return 2;
" +
"end;";
/**
* 同步系统时间到redis 中的lua脚本
*/
public final static String SET_SYSTEM_TIME_SCRIPT = "local clientId = redis.call("hget",KEYS[1],'Client');
" +
"if (clientId == false or clientId == ARGV[1]) then
" +
" redis.call('hset', KEYS[1], 'Client', ARGV[1]);
" +
" redis.call('hset', KEYS[1], 'Time' ,ARGV[2]);
" +
" redis.call('pexpire', KEYS[1], tonumber(ARGV[3]));
" +
" return 1;
" +
"end;
" +
"return 2;";
java调用代码:
List<String> keys = new ArrayList<String>();
keys.add("JRateLimit-Key-" + source);
List<String> args = new ArrayList<String>();
args.add(currentEpoch + ""); //currentEpoch是通过获取全局时钟来做的
args.add(permitsPerEpoch + "");
args.add((0 - permits) + "");
Object result = client.evalsha(LIMIT_LUA_SCRIPT_SHA, keys, args, false); //result的类型即为lua脚本中的返回值类型,Long
策略
通过redis实现全局时钟
通过一次多取几个令牌放到内存中来解决超大调用频率导致的redis请求单机ops极限问题