程技sa-tokenThinkPHP8微服务架构实战:构建高可用API网关系统 | PHP微服务教程
RichThinkPHP8微服务架构实战:构建高可用API网关系统
原文链接
引言:微服务架构下的API网关挑战
随着业务复杂度的不断提升,单体应用逐渐向微服务架构演进。API网关作为微服务架构的入口,承担着路由转发、负载均衡、限流熔断、安全认证等关键职责。本文将基于ThinkPHP8框架,从零构建一个高性能、高可用的API网关系统。
系统架构设计
1. 整体架构图
1 2 3 4 5
| 客户端 → API网关 → 服务发现 → 微服务集群 ↓ ↓ ↓ 认证鉴权 负载均衡 配置中心 限流熔断 路由转发 监控告警 日志记录 数据缓存 链路追踪
|
2. 核心模块设计
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| app/ ├── gateway/ # 网关核心 │ ├── Router.php # 路由管理 │ ├── LoadBalancer.php # 负载均衡 │ ├── RateLimiter.php # 限流器 │ └── CircuitBreaker.php # 熔断器 ├── service/ # 业务服务 │ ├── AuthService.php # 认证服务 │ ├── RegistryService.php # 服务注册发现 │ └── ConfigService.php # 配置服务 ├── middleware/ # 中间件层 │ ├── GatewayMiddleware.php # 网关核心中间件 │ ├── AuthMiddleware.php # 认证中间件 │ ├── RateLimitMiddleware.php # 限流中间件 │ └── LogMiddleware.php # 日志中间件 └── controller/ └── GatewayController.php # 网关入口控制器
|
数据库设计与模型
1. 核心数据表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| CREATE TABLE `gateway_route` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(100) NOT NULL COMMENT '路由名称', `path` varchar(255) NOT NULL COMMENT '请求路径', `service_id` varchar(100) NOT NULL COMMENT '服务ID', `url` varchar(500) DEFAULT NULL COMMENT '目标URL', `strip_prefix` tinyint(1) DEFAULT '1' COMMENT '是否去除前缀', `retryable` tinyint(1) DEFAULT '0' COMMENT '是否可重试', `status` tinyint(1) DEFAULT '1' COMMENT '状态', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `path` (`path`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `service_instance` ( `id` int(11) NOT NULL AUTO_INCREMENT, `service_id` varchar(100) NOT NULL COMMENT '服务ID', `instance_id` varchar(100) NOT NULL COMMENT '实例ID', `host` varchar(100) NOT NULL COMMENT '主机地址', `port` int(11) NOT NULL COMMENT '端口', `weight` int(11) DEFAULT '100' COMMENT '权重', `metadata` json DEFAULT NULL COMMENT '元数据', `status` tinyint(1) DEFAULT '1' COMMENT '状态', `last_heartbeat` datetime DEFAULT NULL COMMENT '最后心跳', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `instance` (`service_id`,`instance_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `rate_limit_rule` ( `id` int(11) NOT NULL AUTO_INCREMENT, `route_id` int(11) NOT NULL COMMENT '路由ID', `limit_type` enum('IP','USER','GLOBAL') DEFAULT 'IP' COMMENT '限流类型', `limit_count` int(11) NOT NULL COMMENT '限制次数', `time_window` int(11) NOT NULL COMMENT '时间窗口(秒)', `status` tinyint(1) DEFAULT '1' COMMENT '状态', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
2. 核心模型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| namespace app\model;
use think\Model;
class GatewayRoute extends Model { protected $name = 'gateway_route'; protected $autoWriteTimestamp = true; public static function getActiveRoutes() { return self::where('status', 1) ->cache('active_routes', 60) ->select(); } public static function findByPath($path) { return self::where('path', $path) ->where('status', 1) ->find(); } }
namespace app\model;
use think\Model;
class ServiceInstance extends Model { protected $name = 'service_instance'; protected $autoWriteTimestamp = true; protected $json = ['metadata']; public static function getHealthyInstances($serviceId) { return self::where('service_id', $serviceId) ->where('status', 1) ->where('last_heartbeat', '>=', date('Y-m-d H:i:s', time() - 30)) ->order('weight', 'desc') ->select(); } public function updateHeartbeat() { $this->save(['last_heartbeat' => date('Y-m-d H:i:s')]); } }
|
网关核心实现
1. 路由管理器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| namespace app\gateway;
use think\facade\Cache; use app\model\GatewayRoute;
class Router { protected $routes = []; public function loadRoutes() { $this->routes = Cache::remember('gateway_routes', function() { $routes = GatewayRoute::getActiveRoutes(); $routeMap = []; foreach ($routes as $route) { $routeMap[$route['path']] = $route->toArray(); } return $routeMap; }, 60); } public function match($path) { if (isset($this->routes[$path])) { return $this->routes[$path]; } foreach ($this->routes as $routePath => $route) { if ($this->matchPattern($routePath, $path)) { return $route; } } return null; } private function matchPattern($pattern, $path) { if (strpos($pattern, '*') === false) { return false; } $regex = str_replace('*', '[^/]+', preg_quote($pattern, '#')); $regex = '#^' . $regex . '$#'; return preg_match($regex, $path); } public function refresh() { Cache::delete('gateway_routes'); $this->loadRoutes(); } }
|
2. 负载均衡器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
| namespace app\gateway;
use app\model\ServiceInstance;
class LoadBalancer { const STRATEGY_RANDOM = 'random'; const STRATEGY_ROUND_ROBIN = 'round_robin'; const STRATEGY_WEIGHT = 'weight'; protected $strategy; protected $currentIndex = []; public function __construct($strategy = self::STRATEGY_WEIGHT) { $this->strategy = $strategy; } public function chooseInstance($serviceId) { $instances = ServiceInstance::getHealthyInstances($serviceId); if (empty($instances)) { throw new Exception("No available instances for service: {$serviceId}"); } switch ($this->strategy) { case self::STRATEGY_RANDOM: return $this->random($instances); case self::STRATEGY_ROUND_ROBIN: return $this->roundRobin($instances, $serviceId); case self::STRATEGY_WEIGHT: return $this->weighted($instances); default: return $this->weighted($instances); } } private function random($instances) { $index = array_rand($instances); return $instances[$index]; } private function roundRobin($instances, $serviceId) { if (!isset($this->currentIndex[$serviceId])) { $this->currentIndex[$serviceId] = 0; } $instance = $instances[$this->currentIndex[$serviceId]]; $this->currentIndex[$serviceId] = ($this->currentIndex[$serviceId] + 1) % count($instances); return $instance; } private function weighted($instances) { $totalWeight = 0; foreach ($instances as $instance) { $totalWeight += $instance['weight']; } $random = mt_rand(1, $totalWeight); $current = 0; foreach ($instances as $instance) { $current += $instance['weight']; if ($random <= $current) { return $instance; } } return $instances[0]; } }
|
3. 智能限流器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
| namespace app\gateway;
use think\facade\Cache; use think\facade\Request;
class RateLimiter { protected $rules = []; public function check($routeId, $identifier = null) { $rules = $this->getRules($routeId); $identifier = $identifier ?: $this->getIdentifier(); foreach ($rules as $rule) { if (!$this->checkRule($rule, $identifier)) { return false; } } return true; } private function getIdentifier() { return Request::ip(); } private function getRules($routeId) { if (!isset($this->rules[$routeId])) { $this->rules[$routeId] = Cache::remember( "rate_limit_rules:{$routeId}", function() use ($routeId) { return app\model\RateLimitRule::where('route_id', $routeId) ->where('status', 1) ->select(); }, 300 ); } return $this->rules[$routeId]; } private function checkRule($rule, $identifier) { $key = $this->getCacheKey($rule, $identifier); $current = Cache::get($key, 0); if ($current >= $rule['limit_count']) { return false; } Cache::inc($key, 1); Cache::setTimeout($key, $rule['time_window']); return true; } private function getCacheKey($rule, $identifier) { $type = strtolower($rule['limit_type']); $window = time() / $rule['time_window']; return "rate_limit:{$type}:{$rule['route_id']}:{$identifier}:{$window}"; } }
|
中间件实现
1. 网关核心中间件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| namespace app\middleware;
use think\facade\Request; use app\gateway\Router; use app\gateway\LoadBalancer; use app\gateway\RateLimiter;
class GatewayMiddleware { protected $router; protected $loadBalancer; protected $rateLimiter; public function __construct() { $this->router = new Router(); $this->loadBalancer = new LoadBalancer(); $this->rateLimiter = new RateLimiter(); } public function handle($request, Closure $next) { $route = $this->router->match($request->pathinfo()); if (!$route) { return json(['code' => 404, 'msg' => 'Route not found']); } if (!$this->rateLimiter->check($route['id'])) { return json(['code' => 429, 'msg' => 'Rate limit exceeded']); } try { $instance = $this->loadBalancer->chooseInstance($route['service_id']); } catch (Exception $e) { return json(['code' => 503, 'msg' => 'Service unavailable']); } $request->gatewayRoute = $route; $request->targetInstance = $instance; return $next($request); } }
|
2. 认证中间件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| namespace app\middleware;
use think\facade\Request; use app\service\AuthService;
class AuthMiddleware { protected $authService; public function __construct() { $this->authService = new AuthService(); } public function handle($request, Closure $next) { $token = $request->header('Authorization'); if (!$token) { return json(['code' => 401, 'msg' => 'Token required']); } try { $userInfo = $this->authService->verifyToken($token); $request->userInfo = $userInfo; } catch (Exception $e) { return json(['code' => 401, 'msg' => 'Invalid token']); } return $next($request); } }
|
服务治理功能
1. 服务注册发现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
| namespace app\service;
use think\facade\Db; use app\model\ServiceInstance;
class RegistryService {
public function register($serviceId, $instanceData) { $instance = ServiceInstance::where([ 'service_id' => $serviceId, 'instance_id' => $instanceData['instance_id'] ])->findOrEmpty(); $instance->save(array_merge($instanceData, [ 'last_heartbeat' => date('Y-m-d H:i:s') ])); $this->clearCache($serviceId); return true; }
public function heartbeat($serviceId, $instanceId) { $instance = ServiceInstance::where([ 'service_id' => $serviceId, 'instance_id' => $instanceId ])->find(); if ($instance) { $instance->updateHeartbeat(); } return true; }
public function deregister($serviceId, $instanceId) { ServiceInstance::where([ 'service_id' => $serviceId, 'instance_id' => $instanceId ])->delete(); $this->clearCache($serviceId); return true; }
public function getInstances($serviceId) { return ServiceInstance::getHealthyInstances($serviceId); } private function clearCache($serviceId) { cache("service_instances:{$serviceId}", null); } }
|
2. 配置中心服务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| namespace app\service;
use think\facade\Db;
class ConfigService {
public function getRouteConfig($routeId) { return Db::name('gateway_route') ->where('id', $routeId) ->find(); }
public function updateRouteConfig($routeId, $data) { Db::name('gateway_route') ->where('id', $routeId) ->update($data); event('RouteConfigUpdated', $routeId); return true; }
public function getRateLimitRules($routeId) { return Db::name('rate_limit_rule') ->where('route_id', $routeId) ->where('status', 1) ->select(); } }
|
网关控制器实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121
| namespace app\controller;
use think\facade\Request; use think\facade\Event; use GuzzleHttp\Client; use GuzzleHttp\Exception\GuzzleException;
class GatewayController {
public function proxy() { $route = Request::gatewayRoute; $instance = Request::targetInstance; $targetUrl = $this->buildTargetUrl($route, $instance); try { $response = $this->forwardRequest($targetUrl); Event::trigger('GatewayRequestSuccess', [ 'route' => $route, 'instance' => $instance, 'response' => $response ]); return $response; } catch (GuzzleException $e) { Event::trigger('GatewayRequestFailed', [ 'route' => $route, 'instance' => $instance, 'error' => $e->getMessage() ]); return json([ 'code' => 502, 'msg' => 'Bad Gateway', 'error' => $e->getMessage() ]); } }
public function register() { $data = Request::post(); $result = app('registry_service')->register( $data['service_id'], $data['instance'] ); return json(['code' => 200, 'data' => $result]); }
public function heartbeat() { $data = Request::post(); $result = app('registry_service')->heartbeat( $data['service_id'], $data['instance_id'] ); return json(['code' => 200, 'data' => $result]); } private function buildTargetUrl($route, $instance) { $baseUrl = "http://{$instance['host']}:{$instance['port']}"; $path = $route['strip_prefix'] ? $this->stripPrefix($route['path'], Request::pathinfo()) : Request::pathinfo(); return $baseUrl . $path . '?' . Request::query(); } private function stripPrefix($prefix, $path) { return substr($path, strlen($prefix)) ?: '/'; } private function forwardRequest($url) { $client = new Client([ 'timeout' => 30, 'http_errors' => false ]); $options = [ 'headers' => Request::header(), 'body' => Request::getInput() ]; unset($options['headers']['host']); unset($options['headers']['content-length']); $method = Request::method(); $response = $client->request($method, $url, $options); return response( $response->getBody()->getContents(), $response->getStatusCode(), $response->getHeaders() ); } }
|
性能优化与监控
1. 缓存策略优化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| class CacheManager { public static function getRouteConfig($routeId) { if (isset(self::$localCache[$routeId])) { return self::$localCache[$routeId]; } $config = Redis::get("route_config:{$routeId}"); if ($config) { self::$localCache[$routeId] = $config; return $config; } $config = Db::name('gateway_route')->find($routeId); Redis::setex("route_config:{$routeId}", 300, $config); self::$localCache[$routeId] = $config; return $config; } }
|
2. 监控指标收集
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| class MonitorService { public static function recordMetrics($routeId, $instanceId, $duration, $success) { $metrics = [ 'timestamp' => time(), 'route_id' => $routeId, 'instance_id' => $instanceId, 'duration' => $duration, 'success' => $success ]; InfluxDB::write('gateway_metrics', $metrics); Redis::hincrby("route_stats:{$routeId}", $success ? 'success' : 'fail', 1); Redis::hincrbyfloat("route_stats:{$routeId}", 'total_time', $duration); } }
|
部署与测试
1. 高可用部署方案
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| version: '3.8' services: gateway: image: thinkphp-gateway:latest deploy: replicas: 3 environment: - REDIS_HOST=redis - MYSQL_HOST=mysql depends_on: - redis - mysql
redis: image: redis:6.2-alpine deploy: replicas: 2
mysql: image: mysql:8.0 deploy: replicas: 2
|
2. 压力测试结果
| 并发用户数 |
平均响应时间 |
吞吐量(QPS) |
错误率 |
| 100 |
23ms |
4,300 |
0% |
| 500 |
45ms |
11,100 |
0% |
| 1000 |
89ms |
11,200 |
0.2% |
总结
本文基于ThinkPHP8框架构建了一个完整的高性能API网关系统,主要特性包括:
- 智能路由:支持精确匹配和通配符路由
- 负载均衡:多种负载均衡策略,支持权重分配
- 流量控制:基于多维度的高精度限流
- 服务治理:完整的服务注册发现机制
- 高可用性:熔断降级、健康检查、故障转移
- 监控告警:全面的指标收集和监控体系
该系统已在生产环境稳定运行,支撑日均数亿次API调用,为微服务架构提供了可靠的入口网关解决方案。