第一层:合法性限流
首先对于合法性要有一个认识,哪些行为是合法的,哪些是非法的,比如:用户反复购买同一件商品行为(刷单),或者是机器人参与秒杀
1.验证码
针对不同的非法请求要用不同的方法来限制,对于机器人,首先想到的应该是增加验证码功能,验证码还能使用户的下单时长增加,比如:原来的下单时长是1秒,在增加验证码之后,用户的下单时间可能就会在1-3秒之间,这样原来一秒钟需要处理100万请求,现在每秒就需要处理33万,可以降低流量的峰值
2.IP黑名单
通过网络监控技术获取到某个IP请求服务的时间是毫秒级别或者,多次下单同一个商品,那么这个IP很有可能就是机器人或者刷单行为,这样就可以把这个IP加入黑名单中来减少不合法的请求
3.隐藏入口地址
比如:在秒杀开始之后才能打开秒杀页面等
第二层:负载限流
理论基础:网络七层模型
1)物理层:物理层
2)数据链路层:提供介质访问和链路管理
3)网络层:IP选址和路由选择
4)传输层:建立、管理和维护端到端的连接
5)会话层:建立、管理和维护对话
6)表示层:数据格式转化和数据加密
7)应用层:为应用程序提供服务
方法:
1.通过Nginx进行负载均衡,如果有3台服务器,那么每台服务器就会承载三分之一的请求
2.在数据链路层,通过MAC地址进行负载;在网络层,通过IP进行负载,在传输层,通过端口号进行负载
3.级联负载:也就是在第二层->第三层->第四层->第七层都进行负载,但是这种方法不推荐,因为每进行一次负载都会增加转发路径,这样会带来网络延迟的问题
注意:所以常见的负载限流是通过Nginx(应用层)或者 Nginx+LVS(传输层,通过网络端口进行负载)来进行负载,或者通过购买F5或者Array等硬件工具来进行负载
第三层:服务限流(请求已经抵达服务器)
方法:
1.通过算法来进行限流:如漏桶算法和令牌桶算法(php可以配合redis来实现)
漏桶算法理解:水(请求)先进入到漏桶里,漏桶以一定的速度出水(接口有响应速率),当水流入速度过大会直接溢出(访问频率超过接口响应速率),然后就拒绝请求,可以看出漏桶算法能强行限制数据的传输速率。
令牌桶算法理解:是和漏桶算法一样但是方向相反的算法,设置一个桶的大小,每隔一定时间往桶里加入一个token(如10ms),桶满了就不加了,每一个请求过来从桶里取一个token,如果没有Token可拿了就阻塞或者拒绝服务
代码示例:
<?php
namespace Api\Lib;
/**
* 限流控制
*/
class RateLimit
{
private $minNum = 60; //单个用户每分访问数
private $dayNum = 10000; //单个用户每天总的访问量
public function minLimit($uid)
{
$minNumKey = $uid . '_minNum';
$dayNumKey = $uid . '_dayNum';
$resMin = $this->getRedis($minNumKey, $this->minNum, 60);
$resDay = $this->getRedis($minNumKey, $this->minNum, 86400);
if (!$resMin['status'] || !$resDay['status']) {
exit($resMin['msg'] . $resDay['msg']);
}
}
public function getRedis($key, $initNum, $expire)
{
$nowtime = time();
$result = ['status' => true, 'msg' => ''];
$redisObj = $this->di->get('redis');
$redis->watch($key);
$limitVal = $redis->get($key);
if ($limitVal) {
$limitVal = json_decode($limitVal, true);
$newNum = min($initNum, ($limitVal['num'] - 1) + (($initNum / $expire) * ($nowtime - $limitVal['time'])));
if ($newNum > 0) {
$redisVal = json_encode(['num' => $newNum, 'time' => time()]);
} else {
return ['status' => false, 'msg' => '当前时刻令牌消耗完!'];
}
} else {
$redisVal = json_encode(['num' => $initNum, 'time' => time()]);
}
$redis->multi();
$redis->set($key, $redisVal);
$rob_result = $redis->exec();
if (!$rob_result) {
$result = ['status' => false, 'msg' => '访问频次过多!'];
}
return $result;
}
}
2.通过消息队列来进行限流:
如:有三个子系统每秒分别能处理2万,3万,5万请求,如果不用消息队列,那么每隔子系统就要处理3.3万个请求,前两个系统就会出现问题,如果使用消息队列,那么这三个系统就可以针对自己的能力去消息队列中拉取特定数量的请求进行处理
3.缓存策略:
静态缓存:如html和js代码可以缓存到浏览器中,如果是图片的话可以缓存到nginx中或者通过nginx转发到OSS中
动态缓存:可以缓存的本地服务器中,如果本地缓存失效,可以缓存到远程的redis集群中