2026-01-08 09:16:53 +08:00
|
|
|
|
// 项目入口文件
|
|
|
|
|
|
import config from './config/config.js';
|
|
|
|
|
|
import { KafkaConsumer } from './kafka/consumer.js';
|
|
|
|
|
|
import { HeartbeatProcessor } from './processor/heartbeatProcessor.js';
|
|
|
|
|
|
import { DatabaseManager } from './db/databaseManager.js';
|
2026-01-14 17:58:45 +08:00
|
|
|
|
import { RedisIntegration } from './redis/redisIntegration.js';
|
2026-01-17 18:37:44 +08:00
|
|
|
|
import { StatsCounters, StatsReporter } from './stats/statsManager.js';
|
2026-01-08 09:16:53 +08:00
|
|
|
|
|
|
|
|
|
|
class WebBLSHeartbeatServer {
|
|
|
|
|
|
constructor() {
|
|
|
|
|
|
this.config = config;
|
|
|
|
|
|
this.kafkaConsumer = null;
|
|
|
|
|
|
this.heartbeatProcessor = null;
|
|
|
|
|
|
this.databaseManager = null;
|
2026-01-14 17:58:45 +08:00
|
|
|
|
this.redis = null;
|
|
|
|
|
|
this.consumers = null;
|
2026-01-17 18:37:44 +08:00
|
|
|
|
this.stats = new StatsCounters();
|
|
|
|
|
|
this.statsReporter = null;
|
2026-01-08 09:16:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async start() {
|
|
|
|
|
|
try {
|
2026-01-14 17:58:45 +08:00
|
|
|
|
// 初始化 Redis(按协议写入心跳与控制台日志)
|
|
|
|
|
|
this.redis = new RedisIntegration(this.config.redis);
|
|
|
|
|
|
await this.redis.connect();
|
|
|
|
|
|
this.redis.startHeartbeat();
|
2026-01-17 18:37:44 +08:00
|
|
|
|
this.statsReporter = new StatsReporter({ redis: this.redis, stats: this.stats });
|
|
|
|
|
|
this.statsReporter.start();
|
2026-01-14 17:58:45 +08:00
|
|
|
|
|
2026-01-08 09:16:53 +08:00
|
|
|
|
// 初始化数据库连接
|
2026-01-14 17:58:45 +08:00
|
|
|
|
this.databaseManager = new DatabaseManager({ ...this.config.db, maxConnections: 1 });
|
2026-01-08 09:16:53 +08:00
|
|
|
|
await this.databaseManager.connect();
|
|
|
|
|
|
console.log('数据库连接成功');
|
2026-01-14 17:58:45 +08:00
|
|
|
|
await this.redis?.info('数据库连接成功', { module: 'db' });
|
2026-01-08 09:16:53 +08:00
|
|
|
|
|
2026-01-16 16:19:31 +08:00
|
|
|
|
// 打印 Kafka 配置摘要,便于排查连接问题
|
|
|
|
|
|
console.log('正在初始化 Kafka 消费者...');
|
|
|
|
|
|
console.log('Kafka 配置:', {
|
|
|
|
|
|
brokers: this.config.kafka?.brokers,
|
|
|
|
|
|
topics: this.config.kafka?.topics,
|
|
|
|
|
|
groupId: this.config.kafka?.groupId,
|
|
|
|
|
|
fromOffset: this.config.kafka?.fromOffset ?? 'latest',
|
|
|
|
|
|
ssl: !!this.config.kafka?.sslEnabled,
|
2026-01-17 18:37:44 +08:00
|
|
|
|
sasl: this.config.kafka?.saslEnabled ? `enabled (mechanism: ${this.config.kafka?.saslMechanism})` : 'disabled'
|
2026-01-16 16:19:31 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
2026-01-14 17:58:45 +08:00
|
|
|
|
// 初始化处理器(共享批处理队列)
|
2026-01-17 18:37:44 +08:00
|
|
|
|
this.heartbeatProcessor = new HeartbeatProcessor(this.config.processor, this.databaseManager, {
|
|
|
|
|
|
redis: this.redis,
|
|
|
|
|
|
stats: this.stats,
|
|
|
|
|
|
});
|
2026-01-08 09:16:53 +08:00
|
|
|
|
|
2026-01-14 17:58:45 +08:00
|
|
|
|
// 在单进程内启动 N 个消费者实例(与分区数匹配)
|
|
|
|
|
|
const instances = Math.max(1, Number(this.config.kafka?.consumerInstances ?? 1));
|
|
|
|
|
|
this.consumers = [];
|
|
|
|
|
|
for (let i = 0; i < instances; i++) {
|
|
|
|
|
|
const consumer = new KafkaConsumer(
|
|
|
|
|
|
{ ...this.config.kafka, consumerInstanceIndex: i },
|
|
|
|
|
|
this.heartbeatProcessor.processMessage.bind(this.heartbeatProcessor)
|
|
|
|
|
|
);
|
|
|
|
|
|
await consumer.connect();
|
|
|
|
|
|
await consumer.subscribe();
|
|
|
|
|
|
await consumer.startConsuming();
|
|
|
|
|
|
this.consumers.push({ consumer });
|
|
|
|
|
|
}
|
|
|
|
|
|
console.log(`Kafka消费者启动成功,共 ${instances} 个实例`);
|
|
|
|
|
|
await this.redis?.info('Kafka消费者启动成功', { module: 'kafka', topic: this.config.kafka?.topic, instances });
|
2026-01-08 09:16:53 +08:00
|
|
|
|
|
|
|
|
|
|
console.log('BLS心跳接收端启动成功');
|
2026-01-14 17:58:45 +08:00
|
|
|
|
await this.redis?.info('BLS心跳接收端启动成功', { module: 'app' });
|
2026-01-08 09:16:53 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('启动失败:', error);
|
2026-01-14 17:58:45 +08:00
|
|
|
|
await this.redis?.error('启动失败', { module: 'app', error: String(error?.message ?? error) });
|
2026-01-08 09:16:53 +08:00
|
|
|
|
process.exit(1);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async stop() {
|
|
|
|
|
|
try {
|
2026-01-17 18:37:44 +08:00
|
|
|
|
if (this.statsReporter) {
|
|
|
|
|
|
this.statsReporter.stop();
|
|
|
|
|
|
this.statsReporter = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-14 17:58:45 +08:00
|
|
|
|
if (this.consumers && Array.isArray(this.consumers)) {
|
|
|
|
|
|
for (const { consumer } of this.consumers) {
|
|
|
|
|
|
await consumer.stopConsuming();
|
|
|
|
|
|
await consumer.disconnect();
|
|
|
|
|
|
}
|
|
|
|
|
|
this.consumers = null;
|
2026-01-08 09:16:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (this.databaseManager) {
|
|
|
|
|
|
await this.databaseManager.disconnect();
|
|
|
|
|
|
}
|
2026-01-14 17:58:45 +08:00
|
|
|
|
|
|
|
|
|
|
if (this.redis) {
|
|
|
|
|
|
await this.redis.info('BLS心跳接收端已停止', { module: 'app' });
|
|
|
|
|
|
await this.redis.disconnect();
|
|
|
|
|
|
}
|
2026-01-08 09:16:53 +08:00
|
|
|
|
|
|
|
|
|
|
console.log('BLS心跳接收端已停止');
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('停止失败:', error);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 启动服务器
|
|
|
|
|
|
const server = new WebBLSHeartbeatServer();
|
|
|
|
|
|
server.start();
|
|
|
|
|
|
|
|
|
|
|
|
// 处理进程终止信号
|
|
|
|
|
|
process.on('SIGINT', () => {
|
|
|
|
|
|
server.stop();
|
|
|
|
|
|
process.exit(0);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
process.on('SIGTERM', () => {
|
|
|
|
|
|
server.stop();
|
|
|
|
|
|
process.exit(0);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-01-14 17:58:45 +08:00
|
|
|
|
export { WebBLSHeartbeatServer };
|