本文共 2490 字,大约阅读时间需要 8 分钟。
令牌桶算法
场景:秒杀(也可以用于平滑限流某个接口请求)
import java.io.IOException;import java.nio.charset.Charset;import org.springframework.core.io.ClassPathResource;import com.google.common.io.Files;import redis.clients.jedis.Jedis;/*** *@author dzb *@date 2019/11/3 22:09 *@Description: 令牌桶 * */public class JedisLuaTokenTimeLimiter { /**执行脚本**/ private String luaScript; /**值**/ private String key; /**限流次数**/ private String limit; /**时间**/ private String expire; public JedisLuaTokenTimeLimiter(String key, String limit, String expire, String scriptFile) { super(); this.key = key; this.limit = limit; this.expire = expire; try { luaScript = Files.asCharSource(new ClassPathResource(scriptFile).getFile(), Charset.defaultCharset()) .read(); } catch (IOException e) { e.printStackTrace(); } } /*** * 释放获取 * */ public boolean acquire() { Jedis jedis = new Jedis("localhost", 6379); return (Long) jedis.eval(luaScript, 1, key, limit, expire) == 1L; }}
--令牌秒杀 Lua脚本 tokenTimeLimiter.lualocal key = KEYS[1] --限流KEY(一秒一个)local limit = tonumber(ARGV[1]) --限流大小local exprie = ARGV[2] --过期时间-- 获取当前计数值local current = tonumber(redis.call('get', key) or "0")if current + 1 > limit then --如果超出限流大小 return 0else current = tonumber(redis.call("INCRBY", key, "1")) --请求数+1 if current == 1 then --第一次访问需要设置过期时间 redis.call("expire", key,exprie) --设置过期时间 endendreturn 1 --返回1代表不限流
Controller层//模拟秒杀场景 JedisLuaTokenTimeLimiter jtwlm = new JedisLuaTokenTimeLimiter("商品A", "5", "1", "tokenTimeLimiter.lua"); public String doQuery(String name) throws Exception { // 从redis 上获得 自增后的值 if (!jtwlm.acquire()) { return System.currentTimeMillis() / 1000 + "秒杀结束,谢谢参与!"; } return System.currentTimeMillis() / 1000 + "恭喜,秒杀成功!"; }
测试代码
//模拟场景并发 @Test public void tokenTimeLimiter() throws Exception { CountDownLatch cdl = new CountDownLatch(10); CyclicBarrier cyb = new CyclicBarrier(10); for (int i = 0; i < 10; i++) { new Thread(() -> { try { cyb.await(); } catch (InterruptedException | BrokenBarrierException e) { e.printStackTrace(); } try { System.out.println(Thread.currentThread().getName() + " " + orderc.doQuery("商品A")); } catch (Exception e) { e.printStackTrace(); } cdl.countDown(); }).start(); } try { cdl.await(); } catch (InterruptedException e) { e.printStackTrace(); } TimeUnit.SECONDS.sleep(1); System.out.println(Thread.currentThread().getName() + " " + orderc.doQuery("商品A")); }
结果:
注:本人只是简单的模拟了分布式环境并发的场景,其细节还有很多就没有过多给出。
转载地址:http://nujdi.baihongyu.com/