13、Hyperf 3 微服务系列 - Hyperf 3 服务限流

作者: 温新

分类: 【Hyperf 3 微服务系列】

阅读: 2040

时间: 2023-05-29 15:44:58

hi,我是温新,一名 PHPer

Hyperf 3 微服务代码已上传至 Github:https://github.com/ziruchu/hyperf3-microservice-code

什么是服务限流

服务限流指在高并发情况下,为了保护系统正常运行,从而对象访问服务的请求进行限制,从而保证服务的高可用。

为什么需要服务限流

把系统拆分为微服务之后,每个微服务可能会存在相互调用的关系,若其中某个服务被突如其来的大流量击垮,可能会引发雪崩,导致相关的微服务都不可用,从而影响业务。

令牌桶限流算法

Hyperf 使用令牌桶对请求进行限流,图如下:

令牌桶算法,是添加一个固定大小的容器(令牌桶),系统以恒定速率的速度向令牌桶中添加令牌,若有请求过来,需要先从令牌桶中获取一个令牌,拿到令牌的请求才有资格访问资源。当令牌桶满时,后面生成的令牌会被丢弃。

令牌桶算法有如下几种情况:

1、请求速度大于令牌生成速度:令牌桶中令牌会被取完,后续再进来的请求由于拿不令牌而被限制访问资源;

2、请求速度等于令牌生成速度:此时系统处于平稳状态;

3、请求速度小于令牌生成速度:此时系统访问量低于系统并发能力,请求正常处理。

Hyperf 服务限流实现

案例一:服务提供者中实现

我们可以轻松的使用 Hyperf 提供的组件对服务进行限流,它是针对请求接口的 QPS 进行限流。

本案例将在 192.168.31.92 服务器的 shop_provider_user_9605 应用上进行。(当前也可以在消费者中进行)。

第一步:安装限流组件
# 安装限流组件
composer require hyperf/rate-limit
# 该限流组件默认使用 redis 作为存储,也以把 redis 装上
composer require hyperf/redis
第二步:生成配置文件
# 限流组件
# 该组件会在 config/autoload 目录下生成 rate_limit.php 文件
php bin/hyperf.php vendor:publish hyperf/rate-limit
# redis 组件
# 该组件会在 config/autoload 目录下生成 redis.php 文件
php bin/hyperf.php vendor:publish hyperf/redis

rate_limit.php 内容如下:

<?php
declare(strict_types=1);
return [
    // 每秒生成令牌数
    'create' => 1,
    // 每次请求消耗令牌数
    'consume' => 1,
    // 令牌桶最大容量
    'capacity' => 2,
    // 触发限流时回调方法
    'limitCallback' => [],
    // 排队超时时间,至少是1秒
    'waitTimeout' => 1,
];

配置说明

配置 默认值 类型 备注
create 1 int 每秒生成令牌数
consume 1 int 每次请求消耗令牌数
capacity 2 int 令牌桶最大容量
limitCallback [] null|callable 触发限流时回调方法
waitTimeout 1 int 排队超时时间
key 当前请求 url 地址 callable|string 限流的 key
第三步:限流代码编写

下面对 UserService.php 文件进行更改:

<?php
// app/JsonRpc/Service/UserService.php;

use Hyperf\RateLimit\Exception\RateLimitException;
use Hyperf\Di\Aop\ProceedingJoinPoint;
use Hyperf\RateLimit\Annotation\RateLimit;    

#[RateLimit(create=1,limitCallback: [UserService::class, "limitCallback"])]
public function getUserInfo(int $id)
{
    $user = User::query()->find($id);
    if (empty($user)) {
        throw new \RuntimeException('没有该用户');
    }

    return ResponseTool::success($user->toArray());
}

// 被限流后调用
public static function limitCallback(float $seconds, ProceedingJoinPoint $proceedingJoinPoint)
{
    throw new RateLimitException('Frequent requests, try again later',500);
}

需要注意:该限流是针对请求进行的,而不是针对具体用户。如最大支持 1000 个请求,假如说某一个用户一瞬间请求了 1000 次,后面后续的用户都将触发限流机制。

如果要针对用户进行限流,达到 A 用户被限流,B 用户正常请求,可以根据用户 ID 进行。

第四步:测试

我里使用了 jmeter 进行测试,被限流后输出如下:

{
	"jsonrpc": "2.0",
	"id": "",
	"error": {
		"code": -32000,
		"message": "node_provider_user_9605-0.0.0.0:9605-Frequent requests, try again later",
		"data": {
			"class": "Hyperf\\RateLimit\\Exception\\RateLimitException",
			"code": 500,
			"message": "Frequent requests, try again later"
		}
	},
	"context": []
}

注意看 message 字段的消息。

案例二:控制器中实现对用户限流

步骤基本和案例一,因此这里就快速实现了

本篇案例将在 192.168.31.90 服务器中的 note_consumer_user_9502 项目进行。

第一步:安装限流组件
composer require hyperf/rate-limit
composer require hyperf/redis
第二步:生成配置文件
php bin/hyperf.php vendor:publish hyperf/rate-limit
php bin/hyperf.php vendor:publish hyperf/redis
第三步:限流代码编写
<?php
// app/Controller/UserController.php
    
use Hyperf\Utils\ApplicationContext;
use Hyperf\RateLimit\Annotation\RateLimit;
use Hyperf\HttpServer\Contract\RequestInterface;
use Hyperf\Di\Aop\ProceedingJoinPoint;

#[GetMapping('/users/test')]
#[RateLimit(create: 1, consume: 1, waitTimeout: 1, limitCallback: [UserController::class, 'limitCallback'], key: [UserController::class, 'getUserId'])]
public function test()
{
    return ResponseTool::success($this->userService->test());
}

public static function limitCallback(float $seconds, ProceedingJoinPoint $proceedingJoinPoint)
{
    throw new RateLimitException('请求过于频繁,请稍后再试!!!', 500);
}

// 针对用户进行显示
public static function getUserId(ProceedingJoinPoint $proceedingJoinPoint)
{
    $request = ApplicationContext::getContainer()->get(RequestInterface::class);
    echo $request->input('user_id') . PHP_EOL;

    // 业务逻辑处理
}
第四步:测试

1、基础正常测试

$ curl http://192.168.31.90:9502/users/test?user_id=1

输出结果
{
    "code": 200,
    "message": "success",
    "data": {
        "code": 200,
        "message": "success",
        "data": {
            "app_name": "node_provider_user_9605",
            "host": "0.0.0.0"
        }
    }
}

请求发送后,192.168.31.90:9502 这台服务服务器终端可以看到输出了用户 ID。

2、使用 jmeter 测试

1 秒并发 10 个请求,触发限流,信息如下:

{
	"code": 500,
	"message": "请求过于频繁,请稍后再试!!!"
}

贴一个 jmeter 测试的结果

关于 jmeter 大家自己安装了,挺容易出现问题的,我 2 台电脑安装 jmeter 都出了问题了。对于 java,我个人...

我是温新,本篇文章结束。

请登录后再评论