The ultimate integrated plan for a PHP microservices architecture:
graph TD
A[客户端] --> B{Kong网关}
B -->|路由| C[用户服务]
B -->|路由| D[订单服务]
B -->|路由| E[支付服务]
C -->|注册| F[Consul集群]
D -->|注册| F
E -->|注册| F
C -->|配置| G[Apollo]
D -->|配置| G
E -->|配置| G
D -->|消息| H[RabbitMQ集群]
H --> E
H --> I[通知服务]
F --> J[Prometheus]
G --> J
H --> J
J --> K[Grafana]
L[Jaeger] --> M[服务追踪]
Fast infrastructure deployment
Full-stack Docker Compose configuration1
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
44version: '3.8'
services:
# Registry
consul-server:
image: consul:1.15
ports: ["8500:8500"]
command: "agent -server -bootstrap-expect=1 -ui -client 0.0.0.0"
# API gateway
kong:
image: kong:3.4
depends_on: [consul-server]
environment:
KONG_DATABASE: "off"
KONG_DECLARATIVE_CONFIG: "/etc/kong/kong.yml"
KONG_PROXY_ACCESS_LOG: "/dev/stdout"
KONG_ADMIN_ACCESS_LOG: "/dev/stdout"
KONG_PROXY_ERROR_LOG: "/dev/stderr"
ports: ["8000:8000", "8001:8001"]
volumes:
- ./kong:/etc/kong
# Config center
apollo:
image: apolloconfig/apollo-portal:2.1.0
ports: ["8070:8070"]
environment:
SPRING_DATASOURCE_URL: "jdbc:mysql://apollo-db:3306/ApolloPortalDB"
# Message queue
rabbitmq:
image: rabbitmq:3.11-management
ports: ["5672:5672", "15672:15672"]
environment:
RABBITMQ_DEFAULT_USER: "admin"
RABBITMQ_DEFAULT_PASS: "secret"
# Monitoring system
prometheus:
image: prom/prometheus:v2.47
ports: ["9090:9090"]
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.ymlPHP implementation package for the core services
Key composer.json dependencies
1
2
3
4
5
6
7
8
9
10
11
12
13
14{
"require": {
"php": ">=8.2",
"ext-swoole": "*",
"ext-redis": "*",
"ext-amqp": "*",
"sensiolabs/consul-php-sdk": "^2.0",
"guzzlehttp/guzzle": "^7.8",
"php-amqplib/php-amqplib": "^3.2",
"resilience-php/resilience-php": "^1.3",
"apolloconfig/apollo-client": "^2.0"
}
}Complete service registration and discovery implementation
Service registration (bootstrap.php)
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
28use SensioLabs\Consul\ServiceFactory;
$consul = new ServiceFactory(['base_uri' => 'http://consul-server:8500']);
// Get this machine's IP automatically
$ip = trim(shell_exec("hostname -i"));
// Register the service
$consul->get(AgentInterface::class)->registerService([
'ID' => 'order-service-'.gethostname(),
'Name' => 'order-service',
'Address' => $ip,
'Port' => 8000,
'Check' => [
'HTTP' => "http://{$ip}:8000/health",
'Interval' => '5s',
'Timeout' => '2s',
'DeregisterCriticalServiceAfter' => '30s'
],
'Tags' => ['v2', 'primary']
]);
// Health check endpoint
$app->get('/health', function() {
check_database();
check_redis();
return json_response(['status' => 'UP']);
});Service discovery and load balancing
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
37class ServiceDiscovery {
private static $cache = [];
private static $ttl = 5; // cache for 5 seconds
public static function getInstance(string $service): string {
$now = time();
// Return straight away while the cache is still valid
if (isset(self::$cache[$service]) &&
$now - self::$cache[$service]['timestamp'] < self::$ttl) {
return self::selectInstance(self::$cache[$service]['instances']);
}
// Fetch fresh instances from Consul
$instances = $consul->getCatalog()->service($service)->json();
self::$cache[$service] = [
'instances' => $instances,
'timestamp' => $now
];
return self::selectInstance($instances);
}
private static function selectInstance(array $instances): string {
// Weighted random algorithm
$total = array_sum(array_column($instances, 'Weight'));
$rand = mt_rand(1, $total);
$current = 0;
foreach ($instances as $instance) {
$current += $instance['Weight'];
if ($rand <= $current) {
return "http://{$instance['ServiceAddress']}:{$instance['ServicePort']}";
}
}
}
}Hooking up the unified config center
Apollo config listening
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23$apollo = new \ApolloClient\Client([
'config_server' => 'http://apollo:8070',
'app_id' => 'order-service',
'cluster' => 'default'
]);
// Load the initial config
$mysqlConfig = $apollo->get('mysql');
DB::connect($mysqlConfig);
// Dynamic listening
$apollo->listen(['mysql', 'redis'], function($namespace, $config) {
switch ($namespace) {
case 'mysql':
DB::reconnect($config);
break;
case 'redis':
Redis::setConfig($config);
break;
}
Logger::info("Config updated: $namespace");
});The complete service communication solution
Synchronous calls (HTTP)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22class HttpServiceClient {
use CircuitBreaker;
public function call(string $service, string $endpoint, array $data) {
return $this->protect(function() use ($service, $endpoint, $data) {
$baseUrl = ServiceDiscovery::getInstance($service);
$client = new GuzzleHttp\Client([
'base_uri' => $baseUrl,
'timeout' => 2.0
]);
return $client->post($endpoint, [
'json' => $data,
'headers' => [
'X-Trace-Id' => Trace::getId()
]
]);
}, function() { // fallback handling
return ['status' => 'degraded'];
});
}
}Asynchronous messaging (RabbitMQ)
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
32class EventPublisher {
private $channel;
public function __construct() {
$conn = new AMQPStreamConnection('rabbitmq', 5672, 'admin', 'secret');
$this->channel = $conn->channel();
// Declare the dead-letter exchange
$this->channel->exchange_declare('dlx', 'direct', false, true);
$this->channel->queue_declare('dlq', false, true);
$this->channel->queue_bind('dlq', 'dlx');
}
public function publish(string $event, array $data) {
$this->channel->tx_select();
try {
$message = new AMQPMessage(json_encode($data), [
'delivery_mode' => AMQPMessage::DELIVERY_MODE_PERSISTENT,
'message_id' => Uuid::uuid4(),
'timestamp' => time()
]);
$this->channel->basic_publish($message, 'events', $event);
DB::table('outbox')->insert(['message_id' => $message->get('message_id')]);
$this->channel->tx_commit();
} catch (Exception $e) {
$this->channel->tx_rollback();
throw $e;
}
}
}Service degradation and circuit breaking strategy
Multi-level degradation configuration
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
44class DegradeManager {
private static $levels = [
'order-service' => [
'full' => ['threshold' => 0.95, 'fallback' => 'cache'],
'cache' => ['threshold' => 0.8, 'fallback' => 'readonly'],
'readonly' => ['threshold' => 0.5, 'fallback' => 'static']
]
];
public static function handle(string $service, callable $func) {
$status = self::getServiceStatus($service);
try {
switch ($status) {
case 'full':
return $func();
case 'cache':
return Cache::remember("fallback:$service", 60, $func);
case 'readonly':
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
return $func();
}
throw new DegradeException('只读模式');
case 'static':
return ['status' => 'degraded'];
}
} catch (Exception $e) {
self::recordFailure($service);
return self::handle($service, $func); // degrade automatically
}
}
private static function getServiceStatus(string $service): string {
$failureRate = Prometheus::getFailureRate($service);
foreach (self::$levels[$service] as $level => $config) {
if ($failureRate <= $config['threshold']) {
return $level;
}
}
return 'static';
}
}Monitoring and alerting configuration
Prometheus metrics collection
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
44class Metrics {
private static $counter;
public static function init() {
$registry = new CollectorRegistry(new InMemory());
self::$counter = $registry->registerCounter(
'php',
'http_requests_total',
'Total HTTP requests',
['method', 'endpoint', 'status']
);
// Expose the metrics endpoint
$app->get('/metrics', function() use ($registry) {
$renderer = new RenderTextFormat();
return $renderer->render($registry->getMetricFamilySamples());
});
}
public static function countRequest($method, $path, $status) {
self::$counter->inc([
$method,
preg_replace('/\d+/', '{id}', $path),
$status
]);
}
}
// Called from the middleware
$app->addMiddleware(function($req, $handler) {
$start = microtime(true);
$response = $handler->handle($req);
$duration = microtime(true) - $start;
Metrics::countRequest(
$req->getMethod(),
$req->getUri()->getPath(),
$response->getStatusCode()
);
return $response;
});Production environment deployment recommendations
Service registry:
Deploy a 3-node Consul cluster
Enable ACL and TLS encryption
Set up an automatic backup policy
API gateway:
Kong cluster + Nginx load balancing
Enable the JWT plugin and rate limiting
Configure WAF rules to block attacks
Config center:
Apollo multi-environment isolation (DEV/TEST/PROD)
Encrypted storage for sensitive configuration
Set up an approval flow for config changes
Message queue:
RabbitMQ mirrored queues
Set sensible TTL and dead-letter policies
Monitor queue backlog
Monitoring system:
Prometheus federated cluster
Unified Grafana dashboards
Alerts on key metrics (P99 latency > 500ms, error rate > 1%)
Example of a complete architecture call flow
- Client request → Kong gateway (auth + rate limiting)
The gateway queries Consul for an order-service instance
While the order service handles the request:
Read the current config from Apollo
Call the payment service over HTTP (with circuit breaking)
Publish a message to RabbitMQ
After the payment service consumes the message:
Update the database
Record Prometheus metrics
Push the result to the notification service
Report full-link tracing data to Jaeger

