一个简易版支付路由实现
By 马冬亮(凝霜 Loki)
一个人的战争(http://blog.csdn.net/MDL13412)
今天跟小伙伴晚上出去吃饭,聊到:假如用户选着工行支付,总共有1000单,其中300单给财付通,100 单给百付宝,600给支付宝(支付渠道)
我们先看一种简单的情况:取一个大小为1000的数组,其中[0-300)分配给财富通,[300,400)分配给百付宝,[400,1000)分配给支付宝(当然可以约分后再计算,这里为了清晰,不进行约分),如下图所示:
对于这个数组,我们将对应区间内的内容填充为相应支付渠道的引用,使用一个计数器,每次有支付请求到来时,就将计数器加1并对1000取余,并用这个计数器去引用数组中的支付渠道,就可以按比例分配的目的。
这个算法的一些弊端:
- 支付渠道分配不均匀,一段时间内的请求,都会是某个支付渠道;
- 动态添加、删除支付渠道困难;
- 动态改变支付渠道所占比例困难;
- 在支付路由集群中,会造成比例不稳定;
下面我们对这一算法进行改进:
按照支付渠道的数量开一个数组,里面维护一些计算比例的基本信息,如下图所示:
这次,计数器在支付渠道之间自增、取余,而对应的支付渠道,利用hitRate和curHitCount两个变量来模拟上一个算法中区间的比例;
例如,当前有1001次支付请求:
第1次,选择财付通渠道,对应的财付通渠道curHitCount加1,其值小于对应的hitRate,因此可以选择;
第2次,选择百付宝渠道,符合条件,选取;
第3次,选择支付宝渠道,符合条件,选取;
第4次,选择财付通渠道,符合条件,选取;
第5次,选择百付宝渠道,符合条件,选取;
......
第300次,选择支付宝渠道,符合条件,选取;
第301次,选择百付宝渠道,因为此时其curHitCount = 100,大于等于其hitRate,按照其权重,其不再进行分配,选择下一渠道支付宝,符合条件,选取;
第302次,因为上次百付宝选取失败,选择了支付宝,因此这次计数器取余后的结果为0,因此选取财付通渠道,符合条件,选取;
第303次,选择百付宝渠道,同样因为权重问题,跳过,选择下一渠道支付宝,符合条件,选取;
....
在700次请求后,财付通渠道的权重用完,因此后续的300次支付全部使用支付宝渠道;
第1001次,因为所有支付渠道权重全部用完,因此需要重置个系统的curHitCount,开始新一轮的分配。
为了让算法更清晰,去掉了各种校验以及多线程的处理,让读者更容易理解。
#include <cstdlib> #include <iostream> #include <utility> #include <list> #include <vector> #include <map> using namespace std; class BankRouter { typedef map<string, int> BankHistRateIndexMapping_t; struct BankHitRateCounter { BankHitRateCounter(const string name, const unsigned int rate) : hitRate(rate), curHitCount(0), disabled(false), bankName(name) { } unsigned int hitRate; unsigned int curHitCount; bool disabled; const string bankName; }; typedef vector<BankHitRateCounter> BankRateArray_t; public: typedef pair<string, unsigned int> BankHitRate_t; public: BankRouter(list<BankHitRate_t> bankHitRate) { int index = 0; for (list<BankHitRate_t>::const_iterator iter = bankHitRate.begin(); bankHitRate.end() != iter; ++iter) { // 去重、校验等逻辑省略... bankRateArray_.push_back( BankHitRateCounter(iter->first, iter->second)); bankRateMapping_[iter->first] = index++; } bankRateLength_ = bankRateArray_.size(); } virtual ~BankRouter() { } string nextBank() { if (curBankPtr_ >= bankRateLength_) { curBankPtr_ = 0; } int curBankPtrBk = curBankPtr_; bool flag = false; while (true) { if (curBankPtr_ >= bankRateLength_) { curBankPtr_ = 0; flag = true; } // 如果遍历一遍还没有合适的银行可供选择,则说明所有银行均不可用 // 或者计数器已满,需要重置 if (curBankPtrBk == curBankPtr_ && flag) { // 检测是否全部为disabled,防止死循环,略... resetBankRateArray(); curBankPtr_ = 0; curBankPtrBk = 0; flag = false; } if (bankRateArray_[curBankPtr_].disabled) { ++curBankPtr_; } else { if (++bankRateArray_[curBankPtr_].curHitCount <= bankRateArray_[curBankPtr_].hitRate) { return bankRateArray_[curBankPtr_++].bankName; } else { ++curBankPtr_; } } } } void enableBank(string bankName) { bankRateArray_[bankRateMapping_[bankName]].disabled = false; } void dienableBank(string bankName) { bankRateArray_[bankRateMapping_[bankName]].disabled = true; } private: void resetBankRateArray() { for (BankRateArray_t::iterator iter = bankRateArray_.begin(); bankRateArray_.end() != iter; ++iter) { iter->curHitCount = 0; } } private: BankRouter(const BankRouter &); BankRouter &operator =(const BankRouter &); private: BankRateArray_t bankRateArray_; int curBankPtr_; int bankRateLength_; BankHistRateIndexMapping_t bankRateMapping_; }; void test(BankRouter &br, const int n) { for (int i = 0; i < n; ++i) { cout << br.nextBank() << endl; } } int main(int argc, const char *argv[]) { list<BankRouter::BankHitRate_t> l; l.push_back(make_pair("cft", 3)); l.push_back(make_pair("bfb", 1)); l.push_back(make_pair("zfb", 6)); BankRouter br(l); test(br, 20); cout << "--------------------" << endl; br.dienableBank("cft"); test(br, 14); cout << "--------------------" << endl; br.enableBank("cft"); test(br, 20); return 0; }
cft bfb zfb cft zfb cft zfb zfb zfb zfb cft bfb zfb cft zfb cft zfb zfb zfb zfb -------------------- bfb zfb zfb zfb zfb zfb zfb bfb zfb zfb zfb zfb zfb zfb -------------------- cft cft cft cft bfb zfb cft zfb cft zfb zfb zfb zfb cft bfb zfb cft zfb cft zfb