[关闭]
@liuximing 2019-03-22T10:01:38.000000Z 字数 3771 阅读 91

限流组件设计与实现

PHP


计数器算法

原理:如果服务的QPS为1000,那么在1s的时间范围内,每次请求到来计数器加1,超过1000则拒绝请求。

实现:

  1. <?php
  2. /**
  3. * Creator: donovanliu
  4. */
  5. class Counter
  6. {
  7. private static $rateLimit = 3000;
  8. public static function getRedisName($ip) {
  9. $redisNameMap = array(
  10. 'doki',
  11. 'user_property'
  12. );
  13. $num = crc32($ip);
  14. $num = abs($num % 2);
  15. return $redisNameMap[$num];
  16. }
  17. public static function pass($num) {
  18. $ip = $_SERVER['SERVER_ADDR'];
  19. $redis = TMPHPRedisClientFactory::getClient(self::getRedisName($ip));
  20. $key = 'rate_limit_' . $ip . '_' . time();
  21. if ($redis->incrBy($key, $num) > self::$rateLimit) {
  22. $redis->decrBy($key, $num);
  23. return false;
  24. }
  25. return true;
  26. }
  27. }

缺点:如果在1s内的前100ms,已经通过了1000个请求,那后面的900ms将无法通过任何请求,从而产生突刺现象。

漏斗桶算法

原理:漏斗桶算法可以通过限制平均速率消除突刺现象。每个请求到来时,就向水滴一样加入漏斗桶,满了就溢出,也就是拒绝请求。漏斗桶以固定的速率漏出水滴,通过请求。

实现:

  1. <?php
  2. /**
  3. * Creator: donovanliu
  4. */
  5. class FunnelBucket
  6. {
  7. private static $rateLimit = 3000;
  8. public static function getRedisName($ip) {
  9. $redisNameMap = array(
  10. 'doki',
  11. 'user_property'
  12. );
  13. $num = crc32($ip);
  14. $num = abs($num % 2);
  15. return $redisNameMap[$num];
  16. }
  17. public static function pass($num) {
  18. $ip = $_SERVER['SERVER_ADDR'];
  19. $redis = TMPHPRedisClientFactory::getClient(self::getRedisName($ip));
  20. $key = 'rate_limit_' . $ip;
  21. if ($redis->incrBy($key, $num) > self::$rateLimit) {
  22. $redis->decrBy($key, $num);
  23. return false;
  24. }
  25. $lock = 'rate_limit_' . $ip . '_lock';
  26. while (!$redis->setNX($lock, 'whatever')) {
  27. usleep(10);
  28. }
  29. $redis->pexpire($key, $num);
  30. $redis->decrBy($key, $num);
  31. return true;
  32. }
  33. }

缺点:无法应对短时间内的突发流量。

令牌桶算法

原理:令牌桶算法可以在限制平均速率的同时允许一定程度的并发,从而应对突发流量。令牌桶算法以固定的速率向桶里放入令牌,超过上限就丢弃。请求到来时,需要先获取令牌,拿到令牌就可以通过,否则就拒绝请求。

实现:

  1. <?php
  2. /**
  3. * Creator: donovanliu
  4. */
  5. class TokenBucket
  6. {
  7. private static $ipArr = array(
  8. );
  9. private static $rateLimit = 3000;
  10. private static $timeInterval = 100;
  11. public static function getRedisName($ip) {
  12. $redisNameMap = array(
  13. 'doki',
  14. 'user_property'
  15. );
  16. $num = crc32($ip);
  17. $num = abs($num % 2);
  18. return $redisNameMap[$num];
  19. }
  20. public static function getToken($num) {
  21. $runStartTime = microtime_float();
  22. $ip = $_SERVER['SERVER_ADDR'];
  23. $redis = TMPHPRedisClientFactory::getClient(self::getRedisName($ip));
  24. $key = 'rate_limit_' . $ip;
  25. if ($redis->decrBy($key, $num) < 0) {
  26. $redis->incrBy($key, $num);
  27. $runEndTime = microtime_float();
  28. modstr_passive_report("ratelimiter", "chicken_$num", 708, ($runEndTime-$runStartTime)*1000000, -1, '');
  29. return false;
  30. }
  31. $runEndTime = microtime_float();
  32. modstr_passive_report("ratelimiter", "chicken_$num", 708, ($runEndTime-$runStartTime)*1000000, 0, '');
  33. return true;
  34. }
  35. public static function check() {
  36. $ipArr = self::$ipArr;
  37. foreach ($ipArr as $ip) {
  38. $redis = TMPHPRedisClientFactory::getClient(self::getRedisName($ip));
  39. $key = 'rate_limit_' . $ip;
  40. echo $redis->get($key) . "\n";
  41. }
  42. }
  43. public static function init() {
  44. $ipArr = self::$ipArr;
  45. $rateLimit = self::$rateLimit;
  46. if (!is_dir("/data/logs/ratelimiter/")) {
  47. mkdir("/data/logs/ratelimiter/", 0777);
  48. }
  49. $log = "/data/logs/ratelimiter/producer_init.log";
  50. foreach ($ipArr as $ip) {
  51. $redis = TMPHPRedisClientFactory::getClient(self::getRedisName($ip));
  52. $key = 'rate_limit_' . $ip;
  53. $redis->set($key, $rateLimit);
  54. error_log(date('Y-m-d H:i:s') . " | produce token | $rateLimit\r\n", 3, $log);
  55. }
  56. error_log(date('Y-m-d H:i:s') . " | produce token end\r\n", 3, $log);
  57. }
  58. public static function produce() {
  59. $ipArr = self::$ipArr;
  60. $rateLimit = self::$rateLimit;
  61. $timeInterval = self::$timeInterval;
  62. $addInterval = $rateLimit / $timeInterval;
  63. $date = date( 'Ymd_H' );
  64. $log = "/data/logs/ratelimiter/producer_$date.log";
  65. foreach ($ipArr as $ip) {
  66. $redis = TMPHPRedisClientFactory::getClient(self::getRedisName($ip));
  67. $key = 'rate_limit_' . $ip;
  68. $rateRest = $redis->get($key);
  69. $canAdd = $rateLimit - $rateRest;
  70. if ($canAdd < $addInterval) {
  71. $redis->incrBy($key, $canAdd);
  72. if (!empty($canAdd)) {
  73. error_log(date('Y-m-d H:i:s') . " | produce token | $canAdd\r\n", 3, $log);
  74. }
  75. } else {
  76. $redis->incrBy($key, $addInterval);
  77. error_log(date('Y-m-d H:i:s') . " | produce token | $addInterval\r\n", 3, $log);
  78. }
  79. }
  80. }
  81. }
  1. <?php
  2. require_once(ROOT_PATH . "/components/ratelimiter/tokenBucket.php");
  3. try{
  4. TMConfig::initialize();
  5. //TokenBucket::check();die;
  6. TokenBucket::init();
  7. swoole_timer_tick(100, function ($timer_id) {
  8. //echo "tick-100ms\n";
  9. TokenBucket::produce();
  10. });
  11. }catch(TMException $e){
  12. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注