├── .gitignore ├── LICENSE ├── README.md ├── example ├── Makefile └── main.cpp └── src ├── Makefile ├── rate_limiter.cpp ├── rate_limiter.h ├── sequence.h ├── spinlock.h ├── spinlock_guard.h └── utils.h /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.so 3 | .vscode 4 | rate_limiter_example -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 刘雨康 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 限流器 2 | 1、概述:
3 | 基于令牌桶算法实现,实现qps限制。blog: http://www.liuyukang.com/archives/cppxianliuqi 4 |
5 |
6 | 2、特点:
7 | (1)接口使用简单,无业务侵入,接入成本极低 8 | (2)线程安全,CPU友好,性能强劲 9 | (3)极轻量,核心代码150行 10 |
11 |
12 | 3、编译:
13 | 进入src,make之后即可生成libratelimiter.so。
14 |
15 |
16 | 4、使用:
17 | example中提供示例,将libratelimiter.so放入/usr/lib/中后进入example,make即可生成可执行文件。下面这段代码将会执行5s:
18 | ``` 19 | int main() 20 | { 21 | RateLimiter r(100);//100qps限流器 22 | 23 | for(int i = 0; i < 500; ++i) 24 | { 25 | r.pass();//通过的速率为100/s 26 | } 27 | 28 | return 0; 29 | } 30 | ``` 31 | -------------------------------------------------------------------------------- /example/Makefile: -------------------------------------------------------------------------------- 1 | 2 | TARGET =rate_limiter_example 3 | CC =g++ -L../src -g -O3 -Wall -std=c++11 4 | INCLUDE =$(shell find ./ -name "*.h") 5 | SOURCE =$(shell find ./ -name "*.cpp") 6 | OBJS =$(SOURCE:%.cpp=%.o) 7 | LIBS =-lratelimiter 8 | CFLAGS =-I../src 9 | 10 | $(TARGET):$(OBJS) 11 | $(CC) -o $(TARGET) $(OBJS) $(LIBS) 12 | 13 | %.o: %.cpp $(INCLUDE) 14 | $(CC) $(CFLAGS) -c $< -o $@ $(LIBS) 15 | 16 | all:$(TARGET) 17 | 18 | clean: 19 | rm -rf $(OBJS) $(TARGET) 20 | -------------------------------------------------------------------------------- /example/main.cpp: -------------------------------------------------------------------------------- 1 | //@author liuyukang 2 | #include "../src/rate_limiter.h" 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | int64_t now() 9 | { 10 | struct timeval tv; 11 | ::gettimeofday(&tv, 0); 12 | int64_t seconds = tv.tv_sec; 13 | return seconds * NS_PER_SECOND + tv.tv_usec * NS_PER_USECOND; 14 | } 15 | 16 | int main() 17 | { 18 | RateLimiter r(100);//100qps限流器 19 | sleep(1); 20 | 21 | auto start = now(); 22 | 23 | for(int i = 0; i < 500; ++i) 24 | { 25 | r.pass();//通过的速率为100/s 26 | } 27 | 28 | auto end = now(); 29 | std::cout << (end - start) / 1000000 << "ms" << std::endl;//此时打印的时间为5000ms 30 | return 0; 31 | } -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | 2 | TARGET =libratelimiter.so 3 | CC =g++ -fPIC -g -O3 -Wall -std=c++11 4 | INCLUDE =$(shell find ./ -name "*.h") 5 | SOURCE =$(shell find ./ -name "*.cpp") 6 | OBJS =$(SOURCE:%.cpp=%.o) 7 | 8 | $(TARGET):$(OBJS) 9 | $(CC) -shared -o $(TARGET) $(OBJS) $(LIBS) 10 | 11 | %.o: %.cpp $(INCLUDE) 12 | $(CC) -c $< -o $@ $(LIBS) 13 | 14 | all:$(TARGET) 15 | 16 | clean: 17 | rm -rf $(OBJS) $(TARGET) 18 | -------------------------------------------------------------------------------- /src/rate_limiter.cpp: -------------------------------------------------------------------------------- 1 | //@author liuyukang 2 | 3 | #include "rate_limiter.h" 4 | #include "spinlock_guard.h" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #define RETRY_IMMEDIATELY_TIMES 100//不睡眠的最大重试获得令牌的次数 11 | 12 | //qps限制最大为十亿 13 | RateLimiter::RateLimiter(int64_t qps) : 14 | bucketSize_(1), tokenLeft_(0), supplyUnitTime_(NS_PER_SECOND / qps), lastAddTokenTime_(0) 15 | { 16 | assert(qps <= NS_PER_SECOND); 17 | assert(qps >= 0); 18 | lastAddTokenTime_ = now(); 19 | } 20 | 21 | int64_t RateLimiter::now() 22 | { 23 | struct timeval tv; 24 | ::gettimeofday(&tv, 0); 25 | int64_t seconds = tv.tv_sec; 26 | return seconds * NS_PER_SECOND + tv.tv_usec * NS_PER_USECOND; 27 | } 28 | 29 | //对外接口,能返回说明流量在限定值内 30 | void RateLimiter::pass() 31 | { 32 | return mustGetToken(); 33 | } 34 | 35 | //尝试获得令牌 36 | //成功获得则返回true 37 | //失败则返回false 38 | bool RateLimiter::tryGetToken() 39 | { 40 | supplyTokens(); 41 | 42 | //获得一个令牌 43 | auto token = tokenLeft_.fetch_add(-1); 44 | if(token <= 0) 45 | {//已经没有令牌了,归还透支的令牌 46 | tokenLeft_.fetch_add(1); 47 | return false; 48 | } 49 | 50 | return true; 51 | } 52 | 53 | //必定成功获得令牌 54 | //其中会进行重试操作 55 | void RateLimiter::mustGetToken() 56 | { 57 | bool isGetToken = false; 58 | for(int i = 0; i < RETRY_IMMEDIATELY_TIMES; ++i) 59 | { 60 | isGetToken = tryGetToken(); 61 | if(isGetToken) 62 | { 63 | return; 64 | } 65 | } 66 | 67 | while(1) 68 | { 69 | isGetToken = tryGetToken(); 70 | if(isGetToken) 71 | { 72 | return; 73 | } 74 | else 75 | { 76 | //让出CPU 77 | sleep(0); 78 | } 79 | } 80 | } 81 | 82 | void RateLimiter::supplyTokens() 83 | { 84 | auto cur = now(); 85 | if (cur - lastAddTokenTime_ < supplyUnitTime_) 86 | { 87 | return; 88 | } 89 | 90 | { 91 | SpinlockGuard lock(lock_); 92 | //等待自旋锁期间可能已经补充过令牌了 93 | int64_t newTokens = (cur - lastAddTokenTime_) / supplyUnitTime_; 94 | if (newTokens <= 0) 95 | { 96 | return; 97 | } 98 | 99 | //更新补充时间,不能直接=cur,否则会导致时间丢失 100 | lastAddTokenTime_ += (newTokens * supplyUnitTime_); 101 | 102 | auto freeRoom = bucketSize_ - tokenLeft_.load(); 103 | if(newTokens > freeRoom || newTokens > bucketSize_) 104 | { 105 | newTokens = freeRoom > bucketSize_ ? bucketSize_ : freeRoom; 106 | } 107 | 108 | tokenLeft_.fetch_add(newTokens); 109 | } 110 | 111 | } -------------------------------------------------------------------------------- /src/rate_limiter.h: -------------------------------------------------------------------------------- 1 | //@author liuyukang 2 | #pragma once 3 | 4 | #include "sequence.h" 5 | #include "spinlock.h" 6 | 7 | #include 8 | 9 | #define NS_PER_SECOND 1000000000//一秒的纳秒数 10 | #define NS_PER_USECOND 1000//一微秒的纳秒数 11 | 12 | //限流器 13 | //最大qps为1,000,000,000,最小为1 14 | //使用: 15 | // RateLimiter r(100); 16 | // r.pass(); 17 | // 能通过r.pass()函数即可保证流速 18 | //特点: 19 | // 1、接口使用简单,无业务侵入,接入成本极低 20 | // 2、线程安全,CPU友好,性能强劲 21 | // 3、极轻量,核心代码150行 22 | //原理: 23 | // 基于令牌桶算法实现 24 | class RateLimiter 25 | { 26 | public: 27 | //qps限制最大为十亿 28 | RateLimiter(int64_t qps); 29 | 30 | DISALLOW_COPY_MOVE_AND_ASSIGN(RateLimiter); 31 | 32 | //对外接口,能返回说明流量在限定值内 33 | void pass(); 34 | 35 | private: 36 | 37 | //获得当前时间,单位ns 38 | int64_t now(); 39 | 40 | //更新令牌桶中的令牌 41 | void supplyTokens(); 42 | 43 | //尝试获得令牌 44 | //成功获得则返回true 45 | //失败则返回false 46 | bool tryGetToken(); 47 | 48 | //必定成功获得令牌 49 | //其中会进行重试操作 50 | void mustGetToken(); 51 | 52 | //令牌桶大小 53 | const int64_t bucketSize_; 54 | 55 | //剩下的token数 56 | AtomicSequence tokenLeft_; 57 | 58 | //补充令牌的单位时间 59 | const int64_t supplyUnitTime_; 60 | 61 | //上次补充令牌的时间,单位纳秒 62 | int64_t lastAddTokenTime_; 63 | 64 | //自旋锁 65 | Spinlock lock_; 66 | }; -------------------------------------------------------------------------------- /src/sequence.h: -------------------------------------------------------------------------------- 1 | //@Author LiuYukang 2 | #pragma once 3 | #include 4 | 5 | #define CACHELINE_SIZE_BYTES 256 6 | #define CACHELINE_PADDING_FOR_ATOMIC_INT64_SIZE (CACHELINE_SIZE_BYTES - sizeof(std::atomic_int64_t)) 7 | #define CACHELINE_PADDING_FOR_INT64_SIZE (CACHELINE_SIZE_BYTES - sizeof(int64_t)) 8 | 9 | //��std::atomic_int64_t�����˷�װ���ڴ油�뱣֤_seq��һ���������� 10 | class AtomicSequence 11 | { 12 | public: 13 | AtomicSequence(int64_t num = 0L) : _seq(num) {}; 14 | ~AtomicSequence() {}; 15 | AtomicSequence(const AtomicSequence&) = delete; 16 | AtomicSequence(const AtomicSequence&&) = delete; 17 | void operator=(const AtomicSequence&) = delete; 18 | 19 | void store(const int64_t val)//, std::memory_order _order = std::memory_order_seq_cst) 20 | { 21 | _seq.store(val);//,_order); 22 | } 23 | 24 | int64_t load()//std::memory_order _order = std::memory_order_seq_cst) 25 | { 26 | return _seq.load();// _order); 27 | } 28 | 29 | int64_t fetch_add(const int64_t increment)//, std::memory_order _order = std::memory_order_seq_cst) 30 | { 31 | return _seq.fetch_add(increment);// _order); 32 | } 33 | 34 | private: 35 | //���߶����룬�Ա�֤_seq������������������һ�������� 36 | char _frontPadding[CACHELINE_PADDING_FOR_ATOMIC_INT64_SIZE]; 37 | std::atomic_int64_t _seq; 38 | char _backPadding[CACHELINE_PADDING_FOR_ATOMIC_INT64_SIZE]; 39 | }; 40 | 41 | //��int64_t�����˷�װ���ڴ油�뱣֤_seq��һ���������� 42 | class Sequence 43 | { 44 | public: 45 | Sequence(int64_t num = 0L) : _seq(num) {}; 46 | ~Sequence() {}; 47 | Sequence(const Sequence&) = delete; 48 | Sequence(const Sequence&&) = delete; 49 | void operator=(const Sequence&) = delete; 50 | 51 | void store(const int64_t val) 52 | { 53 | _seq = val; 54 | } 55 | 56 | int64_t load() 57 | { 58 | return _seq; 59 | } 60 | 61 | private: 62 | //���߶����룬�Ա�֤_seq������������������һ�������� 63 | char _frontPadding[CACHELINE_PADDING_FOR_INT64_SIZE]; 64 | volatile int64_t _seq; 65 | char _backPadding[CACHELINE_PADDING_FOR_INT64_SIZE]; 66 | }; -------------------------------------------------------------------------------- /src/spinlock.h: -------------------------------------------------------------------------------- 1 | //@Author Liu Yukang 2 | #pragma once 3 | #include 4 | #include "utils.h" 5 | 6 | //���std::atomic_int����Ķ�Ԫ�ź���ʹ�ã�Ϊ1��ʾ��Դ����ʹ�ã�Ϊ0��ʾ��Դ����ʹ�� 7 | class Spinlock 8 | { 9 | public: 10 | Spinlock() 11 | : sem_(1) 12 | { } 13 | 14 | ~Spinlock() { unlock(); } 15 | 16 | DISALLOW_COPY_MOVE_AND_ASSIGN(Spinlock); 17 | 18 | void lock() 19 | { 20 | int exp = 1; 21 | while (!sem_.compare_exchange_strong(exp, 0)) 22 | { 23 | exp = 1; 24 | } 25 | } 26 | 27 | void unlock() 28 | { 29 | sem_.store(1); 30 | } 31 | 32 | private: 33 | std::atomic_int sem_; 34 | 35 | }; 36 | -------------------------------------------------------------------------------- /src/spinlock_guard.h: -------------------------------------------------------------------------------- 1 | //@author Liu Yukang 2 | #pragma once 3 | #include "spinlock.h" 4 | #include "utils.h" 5 | 6 | //���std::atomic_int����Ķ�Ԫ�ź���ʹ�ã�Ϊ1��ʾ��Դ����ʹ�ã�Ϊ0��ʾ��Դ����ʹ�� 7 | class SpinlockGuard 8 | { 9 | public: 10 | SpinlockGuard(Spinlock& l) 11 | : lock_(l) 12 | { 13 | lock_.lock(); 14 | } 15 | 16 | ~SpinlockGuard() 17 | { 18 | lock_.unlock(); 19 | } 20 | 21 | DISALLOW_COPY_MOVE_AND_ASSIGN(SpinlockGuard); 22 | 23 | private: 24 | Spinlock& lock_; 25 | 26 | }; 27 | 28 | -------------------------------------------------------------------------------- /src/utils.h: -------------------------------------------------------------------------------- 1 | //@Author Liu Yukang 2 | #pragma once 3 | #define DISALLOW_COPY_MOVE_AND_ASSIGN(TypeName) TypeName(const TypeName&) = delete; TypeName(const TypeName&&) = delete; TypeName& operator=(const TypeName&) = delete --------------------------------------------------------------------------------