Appearance
RabbitMQ 告警通知渠道
概述
告警通知渠道决定了告警信息如何传递给相关人员。合理配置通知渠道可以确保告警信息及时送达,提高问题响应效率。本文将详细介绍 RabbitMQ 告警通知的各种渠道配置方法。
核心知识点
通知渠道类型
| 渠道 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 邮件 | 详细、可归档 | 延迟、不实时 | 日常通知 |
| 短信 | 及时、可靠 | 成本高 | 紧急告警 |
| 电话 | 最及时 | 成本最高 | 严重故障 |
| 即时通讯 | 实时、互动 | 需要网络 | 团队协作 |
| Webhook | 灵活、可集成 | 需要开发 | 自动化处理 |
| 钉钉/企业微信 | 免费、集成 | 需配置 | 国内企业 |
通知配置要素
| 要素 | 说明 |
|---|---|
| 接收人 | 告警发送给谁 |
| 触发条件 | 什么级别告警发送 |
| 发送内容 | 告警信息格式 |
| 发送时间 | 何时发送告警 |
配置示例
PHP 通知渠道管理类
php
<?php
class AlertNotifier
{
private $channels;
private $routingRules;
public function __construct()
{
$this->channels = [];
$this->routingRules = [];
}
public function addChannel($name, $channel)
{
$this->channels[$name] = $channel;
}
public function removeChannel($name)
{
unset($this->channels[$name]);
}
public function addRoutingRule($rule)
{
$this->routingRules[] = $rule;
}
public function send(array $alert)
{
$targets = $this->resolveTargets($alert);
foreach ($targets as $target) {
$channel = $this->channels[$target['channel']] ?? null;
if ($channel) {
$channel->send($target['recipient'], $alert);
}
}
}
private function resolveTargets($alert)
{
$targets = [];
foreach ($this->routingRules as $rule) {
if ($rule->matches($alert)) {
$targets = array_merge($targets, $rule->getTargets());
}
}
return $targets;
}
}
interface NotificationChannel
{
public function send($recipient, array $alert);
public function test($recipient);
}
class EmailChannel implements NotificationChannel
{
private $smtpConfig;
public function __construct(array $smtpConfig)
{
$this->smtpConfig = $smtpConfig;
}
public function send($recipient, array $alert)
{
$subject = $this->formatSubject($alert);
$body = $this->formatBody($alert);
$headers = [
'From: ' . ($this->smtpConfig['from'] ?? 'alerts@example.com'),
'Reply-To: ' . ($this->smtpConfig['reply_to'] ?? 'noreply@example.com'),
'Content-Type: text/html; charset=UTF-8',
'X-Priority: ' . $this->getPriority($alert['severity']),
];
return mail($recipient, $subject, $body, implode("\r\n", $headers));
}
private function formatSubject($alert)
{
$severityEmoji = [
'critical' => '🔴',
'warning' => '🟡',
'info' => '🔵',
];
$emoji = $severityEmoji[$alert['severity']] ?? '⚪';
return sprintf(
'[%s] %s RabbitMQ Alert: %s',
strtoupper($alert['severity']),
$emoji,
$alert['name']
);
}
private function formatBody($alert)
{
$html = '<html><head><style>';
$html .= 'body { font-family: Arial, sans-serif; }';
$html .= '.alert { border: 1px solid #ddd; padding: 15px; margin: 10px 0; }';
$html .= '.critical { border-left: 5px solid #dc3545; }';
$html .= '.warning { border-left: 5px solid #ffc107; }';
$html .= '.info { border-left: 5px solid #17a2b8; }';
$html .= 'table { border-collapse: collapse; width: 100%; }';
$html .= 'td { padding: 8px; border: 1px solid #ddd; }';
$html .= 'th { background: #f5f5f5; padding: 8px; border: 1px solid #ddd; text-align: left; }';
$html .= '</style></head><body>';
$html .= '<div class="alert ' . $alert['severity'] . '">';
$html .= '<h2>' . htmlspecialchars($alert['name']) . '</h2>';
$html .= '<p><strong>严重程度:</strong> ' . htmlspecialchars($alert['severity']) . '</p>';
$html .= '<p><strong>状态:</strong> ' . htmlspecialchars($alert['status']) . '</p>';
$html .= '<p><strong>时间:</strong> ' . htmlspecialchars($alert['timestamp']) . '</p>';
if (!empty($alert['message'])) {
$html .= '<p><strong>消息:</strong> ' . htmlspecialchars($alert['message']) . '</p>';
}
if (!empty($alert['metrics'])) {
$html .= '<h3>相关指标:</h3>';
$html .= '<table>';
foreach ($alert['metrics'] as $key => $value) {
$html .= '<tr><th>' . htmlspecialchars($key) . '</th><td>' . htmlspecialchars($value) . '</td></tr>';
}
$html .= '</table>';
}
if (!empty($alert['runbook'])) {
$html .= '<p><strong>操作手册:</strong> <a href="' . htmlspecialchars($alert['runbook']) . '">' . htmlspecialchars($alert['runbook']) . '</a></p>';
}
$html .= '</div></body></html>';
return $html;
}
private function getPriority($severity)
{
return match($severity) {
'critical' => 1,
'warning' => 3,
default => 5,
};
}
public function test($recipient)
{
return $this->send($recipient, [
'name' => '测试告警',
'severity' => 'info',
'status' => 'testing',
'timestamp' => date('Y-m-d H:i:s'),
'message' => '这是一条测试告警,用于验证邮件渠道配置是否正确。',
]);
}
}
class SlackChannel implements NotificationChannel
{
private $webhookUrl;
private $channel;
private $username;
public function __construct($webhookUrl, $channel = '#alerts', $username = 'RabbitMQ Alert')
{
$this->webhookUrl = $webhookUrl;
$this->channel = $channel;
$this->username = $username;
}
public function send($recipient, array $alert)
{
$payload = $this->formatPayload($alert);
$ch = curl_init($this->webhookUrl);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$result = curl_exec($ch);
curl_close($ch);
return $result === 'ok';
}
private function formatPayload($alert)
{
$color = match($alert['severity']) {
'critical' => '#dc3545',
'warning' => '#ffc107',
default => '#17a2b8',
};
$emoji = match($alert['severity']) {
'critical' => ':fire:',
'warning' => ':warning:',
default => ':information_source:',
};
$fields = [];
if (!empty($alert['metrics'])) {
foreach ($alert['metrics'] as $key => $value) {
$fields[] = [
'title' => $key,
'value' => $value,
'short' => true,
];
}
}
$payload = [
'channel' => $this->channel,
'username' => $this->username,
'icon_emoji' => ':rabbit:',
'attachments' => [
[
'color' => $color,
'title' => "{$emoji} {$alert['name']}",
'text' => $alert['message'] ?? '',
'fields' => $fields,
'footer' => 'RabbitMQ Alert System',
'ts' => time(),
'mrkdwn_in' => ['text', 'fields'],
],
],
];
if ($alert['status'] === 'resolved') {
$payload['attachments'][0]['title'] = ":white_check_mark: {$alert['name']} (已恢复)";
}
return $payload;
}
public function test($recipient)
{
return $this->send($recipient, [
'name' => '测试告警',
'severity' => 'info',
'status' => 'testing',
'timestamp' => date('Y-m-d H:i:s'),
'message' => '这是一条测试告警,用于验证 Slack 渠道配置是否正确。',
]);
}
}
class WebhookChannel implements NotificationChannel
{
private $webhookUrl;
private $headers;
private $template;
public function __construct($webhookUrl, array $headers = [], $template = null)
{
$this->webhookUrl = $webhookUrl;
$this->headers = array_merge(['Content-Type: application/json'], $headers);
$this->template = $template;
}
public function send($recipient, array $alert)
{
$payload = $this->formatPayload($alert, $recipient);
$ch = curl_init($this->webhookUrl);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
curl_setopt($ch, CURLOPT_HTTPHEADER, $this->headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$result = curl_exec($ch);
curl_close($ch);
return $result !== false;
}
private function formatPayload($alert, $recipient)
{
if ($this->template) {
return [
'alert' => $alert,
'recipient' => $recipient,
'template' => $this->template,
];
}
return [
'event_type' => 'rabbitmq_alert',
'alert' => [
'name' => $alert['name'],
'severity' => $alert['severity'],
'status' => $alert['status'],
'message' => $alert['message'],
'timestamp' => $alert['timestamp'],
'metrics' => $alert['metrics'] ?? [],
'runbook' => $alert['runbook'] ?? '',
],
'recipient' => $recipient,
'source' => 'rabbitmq-monitoring',
];
}
public function test($recipient)
{
return $this->send($recipient, [
'name' => '测试告警',
'severity' => 'info',
'status' => 'testing',
'timestamp' => date('Y-m-d H:i:s'),
'message' => '这是一条测试告警,用于验证 Webhook 渠道配置是否正确。',
]);
}
}
class DingTalkChannel implements NotificationChannel
{
private $webhookUrl;
private $secret;
public function __construct($webhookUrl, $secret = null)
{
$this->webhookUrl = $webhookUrl;
$this->secret = $secret;
}
public function send($recipient, array $alert)
{
$payload = $this->formatPayload($alert);
$ch = curl_init($this->webhookUrl);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$result = curl_exec($ch);
curl_close($ch);
return json_decode($result, true)['errcode'] ?? -1 === 0;
}
private function formatPayload($alert)
{
$color = match($alert['severity']) {
'critical' => 'FF0000',
'warning' => 'FFA500',
default => '0000FF',
};
$msgtype = 'markdown';
$markdown = "### {$alert['name']}\n";
$markdown .= "> 严重程度: **{$alert['severity']}**\n";
$markdown .= "> 状态: {$alert['status']}\n";
$markdown .= "> 时间: {$alert['timestamp']}\n\n";
if (!empty($alert['message'])) {
$markdown .= "**消息:** {$alert['message']}\n\n";
}
if (!empty($alert['metrics'])) {
$markdown .= "**相关指标:**\n";
foreach ($alert['metrics'] as $key => $value) {
$markdown .= "- {$key}: {$value}\n";
}
}
return [
'msgtype' => $msgtype,
$msgtype => [
'title' => $alert['name'],
'text' => $markdown,
'color' => $color,
],
];
}
public function test($recipient)
{
return $this->send($recipient, [
'name' => '测试告警',
'severity' => 'info',
'status' => 'testing',
'timestamp' => date('Y-m-d H:i:s'),
'message' => '这是一条测试告警,用于验证钉钉渠道配置是否正确。',
]);
}
}
class SMSErrPhoneChannel implements NotificationChannel
{
private $provider;
private $apiKey;
private $signature;
public function __construct($provider, $apiKey, $signature = '')
{
$this->provider = $provider;
$this->apiKey = $apiKey;
$this->signature = $signature;
}
public function send($recipient, array $alert)
{
$message = $this->formatMessage($alert);
return $this->sendSMS($recipient, $message);
}
private function formatMessage($alert)
{
$severityMap = [
'critical' => '严重',
'warning' => '警告',
'info' => '信息',
];
$severity = $severityMap[$alert['severity']] ?? '未知';
$message = sprintf(
'[RabbitMQ告警] %s - %s - %s',
$severity,
$alert['name'],
$alert['message'] ?? ''
);
if (strlen($message) > 200) {
$message = substr($message, 0, 197) . '...';
}
return $message;
}
private function sendSMS($phone, $message)
{
return true;
}
public function test($recipient)
{
return $this->send($recipient, [
'name' => '测试告警',
'severity' => 'info',
'status' => 'testing',
'timestamp' => date('Y-m-d H:i:s'),
'message' => '这是一条测试短信,用于验证短信渠道配置是否正确。',
]);
}
}通知路由配置
php
<?php
class NotificationRouter
{
private $rules;
public function __construct()
{
$this->rules = [];
}
public function addRule($rule)
{
$this->rules[] = $rule;
}
public function resolveTargets(array $alert)
{
$targets = [];
foreach ($this->rules as $rule) {
if ($rule->matches($alert)) {
$targets = array_merge($targets, $rule->getTargets());
}
}
return $targets;
}
}
class RoutingRule
{
private $conditions;
private $targets;
private $priority;
public function __construct($priority = 0)
{
$this->conditions = [];
$this->targets = [];
$this->priority = $priority;
}
public function addCondition($field, $operator, $value)
{
$this->conditions[] = [
'field' => $field,
'operator' => $operator,
'value' => $value,
];
return $this;
}
public function addTarget($channel, $recipient)
{
$this->targets[] = [
'channel' => $channel,
'recipient' => $recipient,
];
return $this;
}
public function matches(array $alert)
{
if (empty($this->conditions)) {
return true;
}
foreach ($this->conditions as $condition) {
$fieldValue = $alert[$condition['field']] ?? null;
$match = match($condition['operator']) {
'eq' => $fieldValue === $condition['value'],
'ne' => $fieldValue !== $condition['value'],
'gt' => $fieldValue > $condition['value'],
'ge' => $fieldValue >= $condition['value'],
'lt' => $fieldValue < $condition['value'],
'le' => $fieldValue <= $condition['value'],
'in' => in_array($fieldValue, (array)$condition['value']),
'contains' => strpos($fieldValue, $condition['value']) !== false,
'regex' => preg_match($condition['value'], $fieldValue) === 1,
default => false,
};
if (!$match) {
return false;
}
}
return true;
}
public function getTargets()
{
return $this->targets;
}
}通知配置示例
php
<?php
$notifier = new AlertNotifier();
$emailChannel = new EmailChannel([
'from' => 'rabbitmq-alerts@example.com',
'reply_to' => 'ops@example.com',
]);
$slackChannel = new SlackChannel(
'https://hooks.slack.com/services/xxx/yyy/zzz',
'#rabbitmq-alerts',
'RabbitMQ Alert'
);
$webhookChannel = new WebhookChannel(
'http://internal-api.example.com/webhooks/rabbitmq',
['X-API-Key: your-api-key']
);
$dingtalkChannel = new DingTalkChannel(
'https://oapi.dingtalk.com/robot/send?access_token=xxx',
'SECxxx'
);
$smsChannel = new SMSErrPhoneChannel('aliyun', 'your-api-key', '【RabbitMQ】');
$notifier->addChannel('email', $emailChannel);
$notifier->addChannel('slack', $slackChannel);
$notifier->addChannel('webhook', $webhookChannel);
$notifier->addChannel('dingtalk', $dingtalkChannel);
$notifier->addChannel('sms', $smsChannel);
$router = new NotificationRouter();
$criticalRule = (new RoutingRule(100))
->addCondition('severity', 'eq', 'critical')
->addTarget('sms', '13800138000')
->addTarget('slack', '#critical-alerts')
->addTarget('email', 'ops-critical@example.com')
->addTarget('dingtalk', '18888888888');
$warningRule = (new RoutingRule(50))
->addCondition('severity', 'eq', 'warning')
->addTarget('slack', '#rabbitmq-alerts')
->addTarget('email', 'ops@example.com');
$queueRule = (new RoutingRule(30))
->addCondition('name', 'contains', 'queue')
->addCondition('severity', 'in', ['warning', 'critical'])
->addTarget('email', 'queue-team@example.com');
$router->addRule($criticalRule);
$router->addRule($warningRule);
$router->addRule($queueRule);
foreach ($router->getRules() as $rule) {
$notifier->addRoutingRule($rule);
}实际应用场景
场景一:告警降级策略
php
<?php
class AlertDegradationManager
{
private $alertHistory = [];
private $cooldownPeriods = [
'critical' => 900,
'warning' => 3600,
'info' => 86400,
];
public function shouldSend($alert)
{
$key = $this->getAlertKey($alert);
if (!isset($this->alertHistory[$key])) {
return true;
}
$lastSent = $this->alertHistory[$key];
$cooldown = $this->cooldownPeriods[$alert['severity']] ?? 3600;
if (time() - $lastSent['timestamp'] < $cooldown) {
if ($lastSent['count'] >= 3) {
return false;
}
$lastSent['count']++;
$this->alertHistory[$key] = $lastSent;
return false;
}
$this->alertHistory[$key] = [
'timestamp' => time(),
'count' => 1,
];
return true;
}
private function getAlertKey($alert)
{
return md5($alert['name'] . ($alert['instance'] ?? ''));
}
}场景二:时间段路由
php
<?php
class TimeBasedRouter
{
private $businessHours = [
'start' => '09:00',
'end' => '18:00',
];
public function resolveTargets($alert)
{
$isBusinessHour = $this->isBusinessHour();
if ($alert['severity'] === 'critical') {
return $this->getCriticalTargets();
}
if ($isBusinessHour) {
return $this->getBusinessHourTargets();
}
return $this->getOffHourTargets();
}
private function isBusinessHour()
{
$now = new DateTime();
$hour = (int)$now->format('H');
$dayOfWeek = (int)$now->format('N');
if ($dayOfWeek >= 6) {
return false;
}
return $hour >= 9 && $hour < 18;
}
private function getCriticalTargets()
{
return [
['channel' => 'sms', 'recipient' => '13800138000'],
['channel' => 'slack', 'recipient' => '#critical-alerts'],
];
}
private function getBusinessHourTargets()
{
return [
['channel' => 'slack', 'recipient' => '#rabbitmq-alerts'],
['channel' => 'email', 'recipient' => 'ops@example.com'],
];
}
private function getOffHourTargets()
{
return [
['channel' => 'email', 'recipient' => 'oncall@example.com'],
];
}
}常见问题与解决方案
问题一:告警通知延迟
现象:告警发生后很长时间才收到通知。
解决方案:
php
class AsyncNotifier
{
public function sendAsync($channel, $recipient, $alert)
{
$command = sprintf(
'php send_alert.php --channel=%s --recipient=%s --alert=%s > /dev/null 2>&1 &',
escapeshellarg($channel),
escapeshellarg($recipient),
escapeshellarg(json_encode($alert))
);
exec($command);
}
}问题二:通知渠道失败
现象:某个渠道发送失败。
解决方案:
php
class FallbackNotifier
{
private $primaryChannel;
private $fallbackChannel;
public function sendWithFallback($alert)
{
try {
$this->primaryChannel->send($alert);
} catch (Exception $e) {
$this->fallbackChannel->send($alert);
error_log("Primary channel failed: " . $e->getMessage());
}
}
}问题三:告警通知过多
现象:收到大量重复告警。
解决方案:
php
class AlertDeduplicator
{
private $cache = [];
public function deduplicate($alert)
{
$key = $this->getFingerprint($alert);
if (isset($this->cache[$key])) {
return false;
}
$this->cache[$key] = time();
$this->cleanOldEntries();
return true;
}
private function getFingerprint($alert)
{
return md5($alert['name'] . $alert['severity']);
}
private function cleanOldEntries()
{
$threshold = time() - 3600;
$this->cache = array_filter(
$this->cache,
fn($timestamp) => $timestamp > $threshold
);
}
}最佳实践
1. 渠道选择建议
| 告警级别 | 推荐渠道 | 响应时间要求 |
|---|---|---|
| Critical | 短信+电话+即时通讯 | 5 分钟内 |
| Warning | 即时通讯+邮件 | 30 分钟内 |
| Info | 邮件 | 无要求 |
2. 通知内容规范
- 简洁明了的标题
- 清晰的问题描述
- 相关指标数值
- 操作手册链接
- 建议的处理步骤
3. 通知时间策略
- 工作时间:即时通讯+邮件
- 非工作时间:短信+电话
- 节假日:分级通知
