正文
一、常见限流算法
1.漏桶算法(不推荐)
1.原理:将请求缓存到一个队列中,然后以固定的速度处理,从而达到限流的目的
2.实现:将请求装到一个桶中,桶的容量为固定的一个值,当桶装满之后,就会将请求丢弃掉,桶底部有一个洞,以固定的速率流出。
3.举例:桶的容量为1W,有10W并发请求,最多只能将1W请求放入桶中,其余请求全部丢弃,以固定的速度处理请求
4.缺点:处理突发流量效率低(处理请求的速度不变,效率很低)
2.令牌桶算法(推荐)
1.原理:将请求放在一个缓冲队列中,拿到令牌后才能进行处理
2.实现:装令牌的桶大小固定,当令牌装满后,则不能将令牌放入其中;每次请求都会到桶中拿取一个令牌才能放行,没有令牌时即丢弃请求/继续放入缓存队列中等待
3.举例:桶的容量为10w个,生产1w个/s,有10W的并发请求,以每秒10W个/s速度处理,随着桶中的令牌很快用完,速度又慢慢降下来啦,而生产令牌的速度趋于一致1w个/s
4.缺点:处理突发流量提供了系统性能,但是对系统造成了一定的压力,桶的大小不合理,甚至会压垮系统(处理1亿的并发请求,将桶的大小设置为1,这个系统一下就凉凉啦)
二、网关限流(springcloud gateway + redis实战)
1.pom.xml配置
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis-reactive</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> </dependency>
2.yaml配置
spring: application: name: laokou-gateway cloud: gateway: routes: - id: LAOKOU-SSO-DEMO uri: lb://laokou-sso-demo predicates: - Path=/sso/** filters: - StripPrefix=1 - name: RequestRateLimiter #请求数限流,名字不能乱打 args: key-resolver: "#{@ipKeyResolver}" redis-rate-limiter.replenishRate: 1 #生成令牌速率-设为1方便测试 redis-rate-limiter.burstCapacity: 1 #令牌桶容量-设置1方便测试 redis: database: 0 cluster: nodes: x.x.x.x:7003,x.x.x.x:7004,x.x.x.x:7005,x.x.x.x:7003,x.x.x.x:7004,x.x.x.x:7005 password: laokou #密码 timeout: 6000ms #连接超时时长(毫秒) jedis: pool: max-active: -1 #连接池最大连接数(使用负值表示无极限) max-wait: -1ms #连接池最大阻塞等待时间(使用负值表示没有限制) max-idle: 10 #连接池最大空闲连接 min-idle: 5 #连接池最小空间连接
3.创建bean
@Bean(value = "ipKeyResolver") KeyResolver ipKeyResolver() { return exchange -> { String ip = exchange.getRequest().getRemoteAddress().getAddress().getHostAddress(); log.info("ip:{}",ip); return Mono.just(ip); }; }
三、测试限流(编写java并发测试)
@Slf4j public class HttpUtil { public static void apiConcurrent(String url,Map<String,String> params) { Integer count = 200; //创建线程池 ThreadPoolExecutor pool = new ThreadPoolExecutor(5, 200, 0L, TimeUnit.SECONDS, new SynchronousQueue<>()); //同步工具 CountDownLatch latch = new CountDownLatch(count); Map<String,String> dataMap = new HashMap<>(1); dataMap.put("authorize","XXXXXXX"); for (int i = 0; i < count; i++) { pool.execute(() -> { try { //访问网关的API接口 HttpUtil.doGet("http://localhost:1234/sso/laokou-demo/user",dataMap); } catch (IOException e) { e.printStackTrace(); }finally { latch.countDown(); } }); } try { latch.await(); } catch (InterruptedException e) { e.printStackTrace(); } } public static String doGet(String url, Map<String, String> params) throws IOException { //创建HttpClient对象 CloseableHttpClient httpClient = HttpClients.createDefault(); String resultString = ""; CloseableHttpResponse response = null; try { //创建uri URIBuilder builder = new URIBuilder(url); if (!params.isEmpty()) { for (Map.Entry<String, String> entry : params.entrySet()) { builder.addParameter(entry.getKey(), entry.getValue()); } } URI uri = builder.build(); //创建http GET请求 HttpGet httpGet = new HttpGet(uri); List<NameValuePair> paramList = new ArrayList<>(); RequestBuilder requestBuilder = RequestBuilder.get().setUri(new URI(url)); requestBuilder.setEntity(new UrlEncodedFormEntity(paramList, Consts.UTF_8)); httpGet.setHeader(new BasicHeader("Content-Type", "application/json;charset=UTF-8")); httpGet.setHeader(new BasicHeader("Accept", "*/*;charset=utf-8")); //执行请求 response = httpClient.execute(httpGet); //判断返回状态是否是200 if (response.getStatusLine().getStatusCode() == 200) { resultString = EntityUtils.toString(response.getEntity(), "UTF-8"); } } catch (Exception e) { log.info("调用失败:{}",e); } finally { if (response != null) { response.close(); } httpClient.close(); } log.info("打印:{}",resultString); return resultString; } }
说明这个网关限流配置是没有问题的