1 package com.hsjry.plutus.customer.web;
2
3 import lombok.extern.slf4j.Slf4j;
4 import org.springframework.scheduling.annotation.Scheduled;
5 import org.springframework.stereotype.Component;
6 import org.springframework.web.bind.annotation.RequestMapping;
7 import org.springframework.web.bind.annotation.RestController;
8
9 import java.util.Calendar;
10 import java.util.Date;
11 import java.util.concurrent.LinkedBlockingQueue;
12 import java.util.concurrent.atomic.AtomicInteger;
13
14
15 /**
16 * 限流器 每分钟限制请求600次,用于解决推送,每分钟六百次的接口请求次数限制问题
17 * @Auther: 屈艳锋
18 * @Date: 2019/5/23 15:12
19 */
20 @Component
21 @RestController
22 @Slf4j
23 public class CurrentLimiter {
24
25 /**
26 * 限流请求次数记录容器,用于记录前一分钟,当前分钟,后一分钟的请求次数
27 */
28 static AtomicInteger[] limitRequestCountRecordArray = {new AtomicInteger(0), new AtomicInteger(0), new AtomicInteger(0)};
29
30 /**
31 * 限流队列,用于存放超过请求次数限制的请求
32 */
33 static LinkedBlockingQueue limitScheduleQueue = new LinkedBlockingQueue<Integer>(1000);
34
35
36 /**
37 * 定时任务,每逢五十秒时,将后一分钟的请求次数初始化为0
38 */
39 @Scheduled(cron = "0/50 * * * * ?")
40 public void initNextMiniuteValue() {
41
42 //限流容器长度
43 int limitArrayLength = limitRequestCountRecordArray.length;
44
45 //获取当前分钟数据所在下标
46 int currentIndex = (int) (System.currentTimeMillis() / 60000) % limitArrayLength;
47
48 //获取后一分钟数据所在下标
49 int nextIndex = 0;
50 if (currentIndex < limitArrayLength - 1) {
51 nextIndex = currentIndex + 1;
52 }
53
54 //将后一分钟的请求次数初始化为0
55 limitRequestCountRecordArray[nextIndex] = new AtomicInteger(0);
56 log.info("执行是定时任务,当前分钟下标为{},请求次数为{}",currentIndex,limitRequestCountRecordArray[currentIndex].get());
57 }
58
59
60 /**
61 * 测试每分钟六百次限流
62 *
63 * @return
64 * @throws InterruptedException
65 */
66 @RequestMapping("/test")
67 public String testLimiter() throws InterruptedException {
68
69 for (int i = 0; i < 1000; i++) {
70
71 //获取当前分钟数数据所在限流容器的下标
72 int currentIndex = (int) (System.currentTimeMillis() / 60000) % limitRequestCountRecordArray.length;
73
74 //如果超过阀值,则放入队列
75 if (limitRequestCountRecordArray[currentIndex].get() > 599) {
76 limitScheduleQueue.put(i);
77 } else {
78 //当前分钟数数据加一
79 limitRequestCountRecordArray[currentIndex].incrementAndGet();
80 log.info("{}分钟正常调用{}", getMinute(new Date()), i);
81 }
82
83 }
84
85 if (limitScheduleQueue.size() > 0) {
86 new Thread(this::limitHandler).start();
87 }
88 return "";
89 }
90
91
92 /**
93 * 限流处理
94 *
95 */
96 void limitHandler() {
97
98 log.info("=========>当前时间:{}", getMinute(new Date()));
99 for (int i = 0; i < limitScheduleQueue.size(); i++) {
100
101 //获取当前分钟数数据所在限流容器的下标
102 int currentIndex = (int) (System.currentTimeMillis() / 60000) % limitRequestCountRecordArray.length;
103
104 //如果超过阀值,则不处理
105 if (limitRequestCountRecordArray[currentIndex].get() > 599) {
106 try {
107 log.info("{}分钟已超过限制,休息一会,稍微继续~~~",getMinute(new Date()));
108 Thread.sleep(60000);
109 } catch (InterruptedException e) {
110 }
111 } else {
112 //当前分钟数数据加一
113 limitRequestCountRecordArray[currentIndex].incrementAndGet();
114 log.info("{}分钟正常调用中:{}", getMinute(new Date()), limitScheduleQueue.poll());
115 }
116
117 }
118
119 if (limitScheduleQueue.size() > 0) {
120 limitHandler();
121 }
122 }
123
124 /**
125 * 返回当前分钟数
126 *
127 * @param date 日期
128 * @return 返回分钟
129 */
130 public static int getMinute(Date date) {
131 Calendar calendar = Calendar.getInstance();
132 calendar.setTime(date);
133 return calendar.get(Calendar.MINUTE);
134 }
135
136 }