Skip to content

Commit b38293f

Browse files
authored
Merge pull request #319 from hlzq-open/feat/frequency-control
feat:Frequency control component
2 parents d0c8839 + 0b7b8f2 commit b38293f

1 file changed

Lines changed: 254 additions & 0 deletions

File tree

util/include/util/tc_freqlimiter.h

Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
#ifndef __TC_FREQLIMITER_H__
2+
#define __TC_FREQLIMITER_H__
3+
4+
#include <mutex>
5+
#include <unordered_map>
6+
#include <string>
7+
#include "util/tc_hash_fun.h"
8+
#include "util/tc_timeprovider.h"
9+
#include "util/tc_thread_rwlock.h"
10+
11+
using namespace std;
12+
13+
namespace tars
14+
{
15+
16+
/**
17+
* 频率控制组件
18+
* 基于固定时间窗口实现
19+
* 线程安全
20+
* 在指定周期(period)内最多可以进行的最大次数(count)
21+
* maxCount 为最大允许的频率数
22+
* totalUser 为预估的key的总量,用来做过期数据清理,默认1,000,000, 对一般业务足够了,业务可以自己设置
23+
*
24+
* 使用举例:
25+
* TC_FreqLimiter limiter(60, 10); // 表示运行一个key 60s内最多10次
26+
* if(limiter.check("userA"))
27+
* {
28+
* pass;
29+
* }
30+
* else
31+
* {
32+
* refuse;
33+
* }
34+
*
35+
* 业务如果一个进程(服务)中需要用到多个TC_FreqLimiter, 那么可以用 TC_FreqLimiterFactory, 通过不同的name来区分,初始化时一定要主动 init,否则就相当于没有频率控制
36+
* 注意:这里一个进程不宜有太多的name,理论上控制在100个以内
37+
* 例如:
38+
* TC_FreqLimiterFactory limiter;
39+
* limiter.getLimiter("interfaceA")->init(60, 10); // 表示该接口每个key 60s可以10次
40+
* if(limiter.getLimiter("interfaceA")->check("userA"))
41+
* {
42+
* pass;
43+
* }
44+
* else
45+
* {
46+
* refuse;
47+
* }
48+
*/
49+
class TC_FreqLimiter
50+
{
51+
struct KeyItem
52+
{
53+
time_t beginTime;
54+
int count;
55+
KeyItem()
56+
{
57+
beginTime = 0;
58+
count = 0;
59+
}
60+
};
61+
struct KeyItemGroup
62+
{
63+
time_t checkTime;
64+
// 这里的map大小一直增长,有风险
65+
unordered_map<string, KeyItem> items;
66+
67+
KeyItemGroup()
68+
{
69+
checkTime = TNOW;
70+
}
71+
};
72+
73+
// 群锁
74+
#define USER_LOCK(x) std::lock_guard<std::mutex> lock(*_locks[tars::hash<string>()(x)%_locks.size()]);
75+
#define USER_INDEX_LOCK(i) std::lock_guard<std::mutex> lock(*_locks[i]);
76+
77+
78+
public:
79+
TC_FreqLimiter():_period(0), _maxCount(0), _totalUsers(1000000)
80+
{
81+
}
82+
83+
TC_FreqLimiter(unsigned int period, int count)
84+
{
85+
init(period, count);
86+
}
87+
88+
~TC_FreqLimiter()
89+
{
90+
for (size_t i = 0; i < _locks.size(); i++)
91+
{
92+
delete _locks[i];
93+
}
94+
95+
for (size_t i = 0; i < _counts.size(); i++)
96+
{
97+
delete _counts[i];
98+
}
99+
}
100+
101+
/**
102+
* @period: 周期
103+
* @count: 周期内最大允许的次数
104+
* @group: 这里主要用来做群锁分组用, 如果服务请求量比较大,这里建议设置为服务的业务处理线程数
105+
*/
106+
void init(unsigned int period, int count, int group = 1)
107+
{
108+
if (period == 0 || count <= 0 || group <= 0)
109+
{
110+
std::runtime_error("TC_FreqLimiter: Must request positive amount of permits");
111+
}
112+
113+
_period = period;
114+
_maxCount = count;
115+
116+
for (int i = 0; i < group; i++)
117+
{
118+
_locks.push_back(new std::mutex());
119+
_counts.push_back(new KeyItemGroup());
120+
}
121+
122+
setTotalUsers(1000000);
123+
}
124+
125+
bool check(const string& key)
126+
{
127+
if (_period == 0)
128+
{
129+
return true;
130+
}
131+
size_t index = tars::hash<string>()(key)%_counts.size();
132+
// USER_LOCK(key);
133+
USER_INDEX_LOCK(index);
134+
KeyItemGroup* group = _counts[index];
135+
if (group->items.find(key) != group->items.end())
136+
{
137+
if (group->items[key].beginTime + _period < TNOW)
138+
{
139+
group->items[key].beginTime = TNOW;
140+
group->items[key].count = 1;
141+
}
142+
else if(group->items[key].count < _maxCount)
143+
{
144+
group->items[key].count++;
145+
}
146+
else
147+
{
148+
return false;
149+
}
150+
}
151+
else
152+
{
153+
group->items[key].beginTime = TNOW;
154+
group->items[key].count = 1;
155+
}
156+
157+
checkTimeout(group);
158+
159+
return true;
160+
}
161+
162+
// 设置预估的总用户数, 用来做过期数据清理
163+
void setTotalUsers(unsigned int total)
164+
{
165+
if (total < _counts.size() || _counts.size() == 0 || total < 10000)
166+
{
167+
return;
168+
}
169+
_totalUsers = total;
170+
_eachUsers = _totalUsers / _counts.size();
171+
}
172+
173+
protected:
174+
175+
// 数据清理
176+
void checkTimeout(KeyItemGroup* group)
177+
{
178+
if (group->items.size() <= _eachUsers || group->checkTime + _period > TNOW)
179+
{
180+
// 做个保护, 如果在一个周期内就超过了总量, 也不会淘汰数据
181+
return;
182+
}
183+
184+
group->checkTime = TNOW;
185+
for (auto it = group->items.begin(); it != group->items.end();)
186+
{
187+
if (it->second.beginTime + _period < group->checkTime)
188+
{
189+
// cout << "clear-timeout key:" << it->first << "|" << it->second.beginTime << "|" << group->checkTime << endl;
190+
group->items.erase(it++);
191+
}
192+
else
193+
{
194+
it++;
195+
}
196+
}
197+
}
198+
199+
private:
200+
unsigned int _period;
201+
int _maxCount;
202+
203+
//time_t _beginTime;
204+
205+
vector<std::mutex*> _locks;
206+
//vector<std::unordered_map<string, int> > _keyCount;
207+
vector<KeyItemGroup*> _counts;
208+
unsigned int _totalUsers; // 预估最多的用户数, 超过该值会做数据清理
209+
unsigned int _eachUsers; // 每个group用户数
210+
};
211+
212+
class TC_FreqLimiterFactory
213+
{
214+
215+
public:
216+
TC_FreqLimiterFactory()
217+
{}
218+
219+
~TC_FreqLimiterFactory()
220+
{
221+
for (auto it = _limiter.begin(); it != _limiter.end(); it++)
222+
{
223+
delete it->second;
224+
}
225+
}
226+
227+
TC_FreqLimiter* getLimiter(const string& name)
228+
{
229+
{
230+
tars::TC_ThreadRLock lock(_rwLock);
231+
if (_limiter.find(name) != _limiter.end())
232+
{
233+
return _limiter[name];
234+
}
235+
}
236+
237+
tars::TC_ThreadWLock lock(_rwLock);
238+
if (_limiter.find(name) != _limiter.end())
239+
{
240+
return _limiter[name];
241+
}
242+
TC_FreqLimiter* limiter = new TC_FreqLimiter();
243+
_limiter[name] = limiter;
244+
return limiter;
245+
}
246+
247+
private:
248+
tars::TC_ThreadRWLocker _rwLock;
249+
std::unordered_map<string, TC_FreqLimiter*> _limiter;
250+
};
251+
252+
}
253+
254+
#endif // !__TC_FREQLIMITER_H__

0 commit comments

Comments
 (0)