Files
Web_BLS_Heartbeat_Server/src/index.js
XuJiacheng 2f8857f98e feat: 添加 G5 独立写入功能
- 新增 G5 数据库连接配置与可关闭的写入开关
- 在现有 legacy/G4 写入成功路径后,追加独立的 G5 写入流程
- G5 使用与 G4 相同的数据结构映射,但不写入 guid,由数据库自生成 int4 guid
- room_status 新增 G5 独立 upsert 写入路径,并保留旧表与 G5 表的独立开关
- 新增 G5 写入统计与启动摘要输出
- 更新 StatsCounters 和 StatsReporter 以支持 G5 统计
- 增加测试覆盖,确保 G5 写入逻辑与 room_status 的独立执行
- 新增 G5 相关数据库表结构 SQL 文件
2026-03-10 16:29:24 +08:00

170 lines
6.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 项目入口文件
import config from './config/config.js';
import { KafkaConsumer } from './kafka/consumer.js';
import { HeartbeatProcessor } from './processor/heartbeatProcessor.js';
import { DatabaseManager } from './db/databaseManager.js';
import { RedisIntegration } from './redis/redisIntegration.js';
import { StatsCounters, StatsReporter } from './stats/statsManager.js';
class WebBLSHeartbeatServer {
constructor() {
this.config = config;
this.kafkaConsumer = null;
this.heartbeatProcessor = null;
this.databaseManager = null;
this.g5DatabaseManager = null;
this.redis = null;
this.consumers = null;
this.stats = new StatsCounters();
this.statsReporter = null;
}
async start() {
try {
// 初始化 Redis按协议写入心跳与控制台日志
this.redis = new RedisIntegration(this.config.redis);
await this.redis.connect();
this.redis.startHeartbeat();
this.statsReporter = new StatsReporter({ redis: this.redis, stats: this.stats });
this.statsReporter.start();
// 初始化数据库连接
this.databaseManager = new DatabaseManager({ ...this.config.db, maxConnections: 1 });
await this.databaseManager.connect();
console.log('数据库连接成功');
await this.redis?.info('数据库连接成功', { module: 'db' });
if (this.config.g5db?.enabled) {
try {
this.g5DatabaseManager = new DatabaseManager({ ...this.config.g5db, maxConnections: 1 });
await this.g5DatabaseManager.connect();
console.log('G5数据库连接成功');
await this.redis?.info('G5数据库连接成功', { module: 'db', table: this.config.g5db?.g5Table });
} catch (error) {
this.g5DatabaseManager = null;
console.warn('G5数据库连接失败已跳过 G5 写入:', error);
await this.redis?.warn('G5数据库连接失败已跳过 G5 写入', {
module: 'db',
table: this.config.g5db?.g5Table,
error: String(error?.message ?? error),
});
}
}
// 打印双写配置摘要
const dbCfg = this.config.db;
const dualWriteSummary = {
legacyHeartbeat: dbCfg.legacyHeartbeatEnabled ? `ON → ${dbCfg.legacyTable}` : 'OFF',
g4HotHeartbeat: dbCfg.g4HotHeartbeatEnabled ? `ON → ${dbCfg.g4HotTable}` : 'OFF',
g5Heartbeat: this.config.g5db?.enabled ? `ON → ${this.config.g5db?.g5Table}` : 'OFF',
roomStatus: dbCfg.roomStatusEnabled !== false ? `ON → ${dbCfg.roomStatusTable}` : 'OFF',
g5RoomStatus: this.config.g5db?.roomStatusEnabled ? `ON → ${this.config.g5db?.roomStatusTable}` : 'OFF',
};
console.log('双写配置摘要:', dualWriteSummary);
await this.redis?.info('双写配置摘要', { module: 'db', ...dualWriteSummary });
// 打印 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,
sasl: this.config.kafka?.saslEnabled ? `enabled (mechanism: ${this.config.kafka?.saslMechanism})` : 'disabled'
});
// 初始化处理器(共享批处理队列)
this.heartbeatProcessor = new HeartbeatProcessor(this.config.processor, this.databaseManager, {
g5DatabaseManager: this.g5DatabaseManager,
redis: this.redis,
stats: this.stats,
onDbOffline: () => {
if (this.consumers) {
this.consumers.forEach(c => c.consumer.pause());
}
},
onDbOnline: () => {
if (this.consumers) {
this.consumers.forEach(c => c.consumer.resume());
}
}
});
// 在单进程内启动 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 });
console.log('BLS心跳接收端启动成功');
await this.redis?.info('BLS心跳接收端启动成功', { module: 'app' });
} catch (error) {
console.error('启动失败:', error);
await this.redis?.error('启动失败', { module: 'app', error: String(error?.message ?? error) });
process.exit(1);
}
}
async stop() {
try {
if (this.statsReporter) {
this.statsReporter.stop();
this.statsReporter = null;
}
if (this.consumers && Array.isArray(this.consumers)) {
for (const { consumer } of this.consumers) {
await consumer.stopConsuming();
await consumer.disconnect();
}
this.consumers = null;
}
if (this.databaseManager) {
await this.databaseManager.disconnect();
}
if (this.g5DatabaseManager) {
await this.g5DatabaseManager.disconnect();
}
if (this.redis) {
await this.redis.info('BLS心跳接收端已停止', { module: 'app' });
await this.redis.disconnect();
}
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);
});
export { WebBLSHeartbeatServer };