Skip to content

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. 通知时间策略

  • 工作时间:即时通讯+邮件
  • 非工作时间:短信+电话
  • 节假日:分级通知

相关链接