feat(syslog): 优化大日志通道并增加异常连续消费兜底
- 128 KiB 及以上日志默认进入大日志 Topic - InfluxDB 原文及达梦大字段超过 256 KiB 时截断保存 - 截断内容追加 HB_XDR_TRUNCATED 标记 - InfluxDB 写入失败时继续执行规则解析和达梦写入 - 未知或系统性异常优先写入普通/大日志 DLT - DLT 写入失败时记录特殊告警并提交 offset,避免阻塞后续消息 - DLT 失败等待时间默认限制为 1 秒 - 增加连续性放行、DLT 成功及失败监控指标 - 更新 Docker Compose、v3 升级文档及相关测试
This commit is contained in:
@@ -0,0 +1,193 @@
|
||||
# v3 日统计异步投影与大日志通道升级说明
|
||||
|
||||
## 升级内容
|
||||
|
||||
v3 将以下统计表的热点更新移出 `syslog-consumer-rule` 事实事务:
|
||||
|
||||
- `device_daily_stats`
|
||||
- `normalize_rule_daily_stats`
|
||||
- `device_collect_stats`
|
||||
|
||||
事实事务只批量写事实表并追加一条聚合 Outbox。独立的 Kafka 投影消费者串行更新日统计,之后继续通过
|
||||
已有的 `STATS.MATERIALIZE` Topic 刷新 `device_device`、`dm_normalize_rule` 和
|
||||
`device_collect_task`。这样三台 `syslog-consumer-rule` 可以并行写事实,不再同时争抢相同日统计行锁。
|
||||
|
||||
v3 同时增加大日志独立处理链路:
|
||||
|
||||
- `syslog-serve` 在进入 WAL 前执行单条硬上限检查;超过上限明确拒绝,不把原文写入应用日志;
|
||||
- 接纳后的消息仍先进入 `syslog-serve` 本地 WAL,WAL 入队和 Kafka 投递同时受条数、字节数约束;
|
||||
- 超过分流阈值的消息进入独立 `.large` Topic,其 Retry、DLT、Replay 也使用独立 Topic;
|
||||
- `syslog-consumer-rule` 使用独立的大日志 listener、消费组、ConsumerFactory、poll 批量和规则计算线程池;
|
||||
- 规则解析使用完整内容,写 InfluxDB 和达梦前才按 UTF-8 字节数截断,结尾带
|
||||
`HB_XDR_TRUNCATED` 标记;InfluxDB 另存原长度、保存长度和原文 SHA-256。
|
||||
|
||||
## 文件与执行顺序
|
||||
|
||||
| 顺序 | 文件 | 作用 |
|
||||
| --- | --- | --- |
|
||||
| 1 | `database/01-stats-async-projection-precheck.sql` | 只读预检 |
|
||||
| 2 | `database/02-stats-async-projection-upgrade.sql` | 创建 Outbox 和消费幂等表 |
|
||||
| 3 | `database/03-stats-async-projection-verify.sql` | 只读验证表、约束和索引 |
|
||||
| 4 | `kafka/01-create-stats-projection-topic.sh` | 创建独立统计投影 Topic |
|
||||
| 5 | `kafka/02-create-large-log-topics.sh` | 创建大日志主 Topic 及独立 Retry、DLT、Replay Topic |
|
||||
| 6 | `config/01-new-environment-variables.md` | 升级应用前配置新增环境变量,并核对 Kafka Broker 参数 |
|
||||
|
||||
数据库脚本必须使用 `syslog-consumer-rule` 实际连接的达梦用户执行。
|
||||
|
||||
如果现场已经执行过早期 v3 日统计异步投影升级,只执行 `kafka/02-create-large-log-topics.sh` 和本页的
|
||||
大日志应用配置、验证步骤;不要重复执行数据库升级脚本。
|
||||
|
||||
## 第一步:达梦
|
||||
|
||||
依次执行:
|
||||
|
||||
```text
|
||||
01-stats-async-projection-precheck.sql
|
||||
02-stats-async-projection-upgrade.sql
|
||||
03-stats-async-projection-verify.sql
|
||||
```
|
||||
|
||||
预检判断:
|
||||
|
||||
- 第一组应返回 `DEVICE_DAILY_STATS`、`NORMALIZE_RULE_DAILY_STATS`、`DEVICE_COLLECT_STATS` 三行;
|
||||
- 后面三组必须为空;
|
||||
- 如果目标表、约束或索引已经存在,不要执行 `02`,先执行 `03` 判断是否已经升级完成。
|
||||
|
||||
验证判断:
|
||||
|
||||
- 返回两张新表;
|
||||
- 三个约束状态为 `ENABLED`;
|
||||
- 两个索引状态为 `VALID`;
|
||||
- 首次建表时两张表的行数都是 0。
|
||||
|
||||
## 第二步:Kafka
|
||||
|
||||
### 统计投影 Topic
|
||||
|
||||
在 Kafka 主机的 `kafka_2.13-3.6.1/bin` 目录执行:
|
||||
|
||||
```bash
|
||||
bash /脚本所在路径/01-create-stats-projection-topic.sh
|
||||
```
|
||||
|
||||
默认创建:
|
||||
|
||||
```text
|
||||
agent-01-syslog-topic.STATS.PROJECTION
|
||||
```
|
||||
|
||||
新建时为 1 个分区,副本数沿用 `agent-01-syslog-topic`。如果现场此前已经建成 6 个分区,脚本只告警,
|
||||
不会修改或删除;应用使用固定 Kafka key,投影事件仍然只落入其中一个稳定分区。
|
||||
|
||||
必须看到:
|
||||
|
||||
```text
|
||||
PASS: agent-01-syslog-topic.STATS.PROJECTION 已创建/确认。
|
||||
```
|
||||
|
||||
`STATS.PROJECTION` 不能与已有的 `STATS.MATERIALIZE` 配成同一个 Topic。
|
||||
|
||||
### 大日志 Topic
|
||||
|
||||
先确认所有 Kafka Broker 的 `replica.fetch.max.bytes` 不小于 `33554432`,并按现场 Kafka 版本要求使
|
||||
配置生效。然后执行:
|
||||
|
||||
```bash
|
||||
bash /脚本所在路径/02-create-large-log-topics.sh
|
||||
```
|
||||
|
||||
脚本默认从 `agent-01-syslog-topic` 读取分区数和副本数,并创建:
|
||||
|
||||
```text
|
||||
agent-01-syslog-topic.large
|
||||
agent-01-syslog-topic.large.RETRY.1M
|
||||
agent-01-syslog-topic.large.RETRY.10M
|
||||
agent-01-syslog-topic.large.RETRY.1H
|
||||
agent-01-syslog-topic.large.DLT
|
||||
agent-01-syslog-topic.large.REPLAY
|
||||
```
|
||||
|
||||
必须看到:
|
||||
|
||||
```text
|
||||
PASS: 大日志 Topic 已创建并统一配置。
|
||||
```
|
||||
|
||||
## 第三步:升级应用
|
||||
|
||||
升级 `syslog-serve` 和三台 `syslog-consumer-rule`;`syslog-consumer` 不需要因为本次改造升级。
|
||||
|
||||
升级前先按 [新增变量配置清单](./config/01-new-environment-variables.md) 修改现场实际使用的 Compose 文件。
|
||||
三台 `syslog-consumer-rule` 的 Topic、消费组和截断上限必须保持一致。
|
||||
|
||||
统计投影默认已经启用。确认现场没有显式配置:
|
||||
|
||||
```text
|
||||
STATS_PROJECTION_ASYNC_ENABLED=false
|
||||
```
|
||||
|
||||
先逐台升级 `syslog-consumer-rule`,可以启动一台观察后再升级另外两台;确认大日志 listener 正常后再
|
||||
升级 `syslog-serve`。新旧版本滚动期间,同一条原始 Kafka 消息只会被消费组中的一个实例处理,不会因为
|
||||
滚动升级重复统计。
|
||||
|
||||
## 升级后验证
|
||||
|
||||
查看处理日志:
|
||||
|
||||
```bash
|
||||
docker logs --since 2m syslog-consumer-rule-dm 2>&1 \
|
||||
| grep -E 'Syslog批次完成|统计投影批次完成|统计物化批次完成|Outbox|Deadlock' \
|
||||
| tail -100
|
||||
```
|
||||
|
||||
查看 Outbox 状态:
|
||||
|
||||
```sql
|
||||
SELECT status, COUNT(*)
|
||||
FROM stats_projection_outbox
|
||||
GROUP BY status
|
||||
ORDER BY status;
|
||||
```
|
||||
|
||||
正常表现:
|
||||
|
||||
- 日志持续出现 `统计投影批次完成`;
|
||||
- `APPLIED` 持续增长;
|
||||
- `PENDING`、`PUBLISHED` 只短暂停留,不持续增长;
|
||||
- `FAILED` 不持续增长;
|
||||
- `Syslog批次完成` 中 `dmWriteMs` 明显下降;
|
||||
- 不再出现 daily stats 相关死锁。
|
||||
|
||||
查看新消费组积压:
|
||||
|
||||
```bash
|
||||
./kafka-consumer-groups.sh \
|
||||
--bootstrap-server 10.141.121.35:9092,10.141.121.50:9092,10.141.121.59:9092 \
|
||||
--group agent-01-syslog-group-dm-stats-projection \
|
||||
--describe
|
||||
```
|
||||
|
||||
验证大日志链路:
|
||||
|
||||
- `GET /syslogserve/api/syslog/wal/status`:检查 `wal_admission_oversized`、`wal_pending_bytes`、
|
||||
`dispatch_inflight_bytes`、`kafka_large_dispatched`;
|
||||
- `GET /xdrrule/api/syslog/raw-storage/metrics`:检查 `raw_message_truncated_total` 及截断前后字节累计值;
|
||||
- `GET /xdrrule/api/syslog/storage-limits/metrics`:检查达梦正常/非标字段截断计数;
|
||||
- 发送一条略大于 256 KiB、但小于 6 MiB 的测试消息,确认 `.large` Topic 有数据且普通 Topic 无该事件;
|
||||
- 查询 InfluxDB/达梦,确认被截断值以 `...[HB_XDR_TRUNCATED original_utf8_bytes=...]` 结尾。
|
||||
|
||||
## 回滚
|
||||
|
||||
回滚最后一台新版本前,先确认 Outbox 没有未应用事件:
|
||||
|
||||
```sql
|
||||
SELECT status, COUNT(*)
|
||||
FROM stats_projection_outbox
|
||||
WHERE status <> 'APPLIED'
|
||||
GROUP BY status;
|
||||
```
|
||||
|
||||
查询为空后再回滚最后一台。两张新表、Kafka Topic 和消费组 offset 全部保留,不要删除或重置。
|
||||
|
||||
回滚旧版本前,还要先停止新生产者并排空 `.large` 主 Topic;大日志 Topic 和消费组 offset 同样保留,
|
||||
不要删除或重置。
|
||||
@@ -0,0 +1,147 @@
|
||||
# v3 新增变量配置
|
||||
|
||||
应用侧只需要修改两个 Docker Compose 文件。变量放到对应服务的 `environment:` 下,不要修改 JAR 中的
|
||||
`application.properties`;Kafka Broker 参数单独按第 4 节核对。
|
||||
|
||||
## 1. syslog-serve
|
||||
|
||||
配置文件:
|
||||
|
||||
```text
|
||||
syslog-serve/docker-compose/docker-compose-serve-prod.yaml
|
||||
```
|
||||
|
||||
在 `services.syslog-serve.environment` 中增加:
|
||||
|
||||
```yaml
|
||||
# 开启 Syslog TCP/UDP 监听,字符集按现场设备实际编码调整。
|
||||
- SYSLOG_SERVER_ENABLED=true
|
||||
- SYSLOG_CHARSET=UTF-8
|
||||
|
||||
# 单条最多接收 6 MiB,超过 256 KiB 时发送到大日志 Topic。
|
||||
- SYSLOG_MAX_FRAME_LENGTH=6291456
|
||||
- SYSLOG_MESSAGE_MAX_BYTES=6291456
|
||||
- SYSLOG_LARGE_MESSAGE_THRESHOLD_BYTES=262144
|
||||
|
||||
# WAL 和 Kafka 投递最多各占用 256 MiB 在途内存。
|
||||
- SYSLOG_WAL_MAX_PENDING_BYTES=268435456
|
||||
- SYSLOG_DISPATCH_MAX_INFLIGHT_BYTES=268435456
|
||||
|
||||
# 大日志 Kafka Topic 及 Producer 请求上限。
|
||||
- KAFKA_PRODUCER_LARGE_TOPIC=agent-01-syslog-topic.large
|
||||
- KAFKA_PRODUCER_MAX_REQUEST_SIZE=33554432
|
||||
```
|
||||
|
||||
其中:
|
||||
|
||||
- `SYSLOG_MAX_FRAME_LENGTH`:TCP 单帧最大长度;
|
||||
- `SYSLOG_MESSAGE_MAX_BYTES`:所有来源的单条日志硬上限,超过后拒绝;
|
||||
- `SYSLOG_LARGE_MESSAGE_THRESHOLD_BYTES`:超过该值后走大日志 Topic;
|
||||
- `SYSLOG_WAL_MAX_PENDING_BYTES`、`SYSLOG_DISPATCH_MAX_INFLIGHT_BYTES`:限制 JVM 在途数据量。
|
||||
|
||||
`SYSLOG_MAX_FRAME_LENGTH` 必须大于或等于 `SYSLOG_MESSAGE_MAX_BYTES`。如果设备发送 GBK,应将
|
||||
`SYSLOG_CHARSET` 改为 `GBK`。
|
||||
|
||||
## 2. syslog-consumer-rule
|
||||
|
||||
配置文件:
|
||||
|
||||
```text
|
||||
syslog-consumer-rule/docker-compose/docker-compose-consumer-rule-prod.yaml
|
||||
```
|
||||
|
||||
在 `services.syslog-consumer-rule.environment` 中增加,三台实例保持一致:
|
||||
|
||||
```yaml
|
||||
# 大日志独立消费通道。
|
||||
- KAFKA_CONSUMER_LARGE_TOPIC=agent-01-syslog-topic.large
|
||||
- SYSLOG_LARGE_LISTENER_ENABLED=true
|
||||
- SYSLOG_LARGE_LISTENER_CONCURRENCY=1
|
||||
- SYSLOG_LARGE_LISTENER_MAX_POLL_RECORDS=5
|
||||
- SYSLOG_LARGE_LISTENER_MAX_PARTITION_FETCH_BYTES=33554432
|
||||
- SYSLOG_LARGE_LISTENER_FETCH_MAX_BYTES=67108864
|
||||
|
||||
# 大日志规则处理批次和独立线程池。
|
||||
- SYSLOG_LARGE_PROCESSING_BATCH_MAX_BYTES=8388608
|
||||
- SYSLOG_LARGE_PROCESSOR_CORE_POOL_SIZE=1
|
||||
- SYSLOG_LARGE_PROCESSOR_MAX_POOL_SIZE=2
|
||||
- SYSLOG_LARGE_PROCESSOR_QUEUE_CAPACITY=10
|
||||
|
||||
# Retry、DLT、Replay 也允许传输大日志。
|
||||
- KAFKA_RELIABILITY_MAX_REQUEST_SIZE=33554432
|
||||
|
||||
# InfluxDB 原文最多保存 1 MiB,单批写入最多 8 MiB。
|
||||
- INFLUXDB_RAW_MESSAGE_MAX_UTF8_BYTES=1048576
|
||||
- INFLUXDB_RAW_WRITE_BATCH_MAX_BYTES=8388608
|
||||
|
||||
# 达梦正常日志大字段最多保存 64 KiB,非标日志原文最多保存 256 KiB。
|
||||
- DM_NORMAL_LARGE_FIELD_MAX_UTF8_BYTES=65536
|
||||
- DM_NON_NORMAL_LARGE_FIELD_MAX_UTF8_BYTES=262144
|
||||
```
|
||||
|
||||
被截断的数据会以以下标记结尾:
|
||||
|
||||
```text
|
||||
...[HB_XDR_TRUNCATED original_utf8_bytes=原始字节数]
|
||||
```
|
||||
|
||||
以下三个存储上限配置为 `0` 时表示不截断:
|
||||
|
||||
```text
|
||||
INFLUXDB_RAW_MESSAGE_MAX_UTF8_BYTES
|
||||
DM_NORMAL_LARGE_FIELD_MAX_UTF8_BYTES
|
||||
DM_NON_NORMAL_LARGE_FIELD_MAX_UTF8_BYTES
|
||||
```
|
||||
|
||||
规则解析始终使用完整原文,只在写入 InfluxDB 或达梦时截断。
|
||||
|
||||
## 3. 日统计投影
|
||||
|
||||
日统计投影默认开启,一般不需要新增配置。只需确认现场没有配置:
|
||||
|
||||
```text
|
||||
STATS_PROJECTION_ASYNC_ENABLED=false
|
||||
```
|
||||
|
||||
如果现场需要显式写明,可以在 `syslog-consumer-rule` 的 `environment` 中配置:
|
||||
|
||||
```yaml
|
||||
- STATS_PROJECTION_ASYNC_ENABLED=true
|
||||
- STATS_PROJECTION_TOPIC=agent-01-syslog-topic.STATS.PROJECTION
|
||||
- STATS_PROJECTION_GROUP_ID=agent-01-syslog-group-dm-stats-projection
|
||||
```
|
||||
|
||||
## 4. Kafka 配置
|
||||
|
||||
每个 Kafka Broker 的 `replica.fetch.max.bytes` 必须不小于 32 MiB:
|
||||
|
||||
```properties
|
||||
replica.fetch.max.bytes=33554432
|
||||
```
|
||||
|
||||
然后执行:
|
||||
|
||||
```bash
|
||||
bash upgrade-doc/v3/kafka/02-create-large-log-topics.sh
|
||||
```
|
||||
|
||||
生产者和消费者的大日志 Topic 名称必须完全相同:
|
||||
|
||||
```text
|
||||
KAFKA_PRODUCER_LARGE_TOPIC=agent-01-syslog-topic.large
|
||||
KAFKA_CONSUMER_LARGE_TOPIC=agent-01-syslog-topic.large
|
||||
```
|
||||
|
||||
## 5. 使配置生效
|
||||
|
||||
在现场对应的部署目录先检查 Compose,再重建容器:
|
||||
|
||||
```bash
|
||||
docker compose -f docker-compose-serve-prod.yaml config
|
||||
docker compose -f docker-compose-serve-prod.yaml up -d --force-recreate syslog-serve
|
||||
|
||||
docker compose -f docker-compose-consumer-rule-prod.yaml config
|
||||
docker compose -f docker-compose-consumer-rule-prod.yaml up -d --force-recreate syslog-consumer-rule
|
||||
```
|
||||
|
||||
建议先升级三台 `syslog-consumer-rule`,确认大日志消费者正常后,再升级 `syslog-serve`。
|
||||
@@ -0,0 +1,40 @@
|
||||
-- v3 达梦升级预检:只读,不修改任何数据。
|
||||
-- 第一组应返回 3 行;第二、三、四组应全部为空。
|
||||
|
||||
-- v1 的三张 DIRECT 统计表必须已经存在。
|
||||
SELECT table_name
|
||||
FROM user_tables
|
||||
WHERE table_name IN (
|
||||
'DEVICE_DAILY_STATS',
|
||||
'NORMALIZE_RULE_DAILY_STATS',
|
||||
'DEVICE_COLLECT_STATS'
|
||||
)
|
||||
ORDER BY table_name;
|
||||
|
||||
-- v3 目标表必须尚未创建。若有返回,停止执行 02,先确认是否执行过 v3 或曾中途失败。
|
||||
SELECT table_name
|
||||
FROM user_tables
|
||||
WHERE table_name IN (
|
||||
'STATS_PROJECTION_OUTBOX',
|
||||
'STATS_PROJECTION_APPLIED_EVENT'
|
||||
)
|
||||
ORDER BY table_name;
|
||||
|
||||
-- 约束名不得与现有对象冲突。
|
||||
SELECT constraint_name, table_name
|
||||
FROM user_constraints
|
||||
WHERE constraint_name IN (
|
||||
'PK_STATS_PROJECTION_OUTBOX',
|
||||
'CK_STATS_PROJECTION_OUTBOX_STATUS',
|
||||
'PK_STATS_PROJECTION_APPLIED'
|
||||
)
|
||||
ORDER BY constraint_name;
|
||||
|
||||
-- 索引名不得与现有对象冲突。
|
||||
SELECT index_name, table_name
|
||||
FROM user_indexes
|
||||
WHERE index_name IN (
|
||||
'IDX_STATS_PROJECTION_OUTBOX_DUE',
|
||||
'IDX_STATS_PROJECTION_APPLIED_TIME'
|
||||
)
|
||||
ORDER BY index_name;
|
||||
@@ -0,0 +1,41 @@
|
||||
-- v3 达梦升级:为事实事务外的可靠日统计投影建立 Outbox 和消费幂等表。
|
||||
-- 仅在 01 预检通过后执行一次;不要重复执行。
|
||||
|
||||
CREATE TABLE stats_projection_outbox (
|
||||
event_id VARCHAR2(64) NOT NULL,
|
||||
event_schema VARCHAR2(64) NOT NULL,
|
||||
payload CLOB NOT NULL,
|
||||
status VARCHAR2(16) DEFAULT 'PENDING' NOT NULL,
|
||||
attempt_count INT DEFAULT 0 NOT NULL,
|
||||
next_attempt_at TIMESTAMP DEFAULT SYSDATE,
|
||||
published_at TIMESTAMP,
|
||||
applied_at TIMESTAMP,
|
||||
last_error VARCHAR2(2000),
|
||||
created_at TIMESTAMP DEFAULT SYSDATE NOT NULL,
|
||||
updated_at TIMESTAMP DEFAULT SYSDATE NOT NULL,
|
||||
CONSTRAINT pk_stats_projection_outbox PRIMARY KEY(event_id),
|
||||
CONSTRAINT ck_stats_projection_outbox_status
|
||||
CHECK(status IN ('PENDING','FAILED','PUBLISHED','APPLIED'))
|
||||
);
|
||||
|
||||
CREATE INDEX idx_stats_projection_outbox_due
|
||||
ON stats_projection_outbox(status,next_attempt_at,created_at);
|
||||
|
||||
CREATE TABLE stats_projection_applied_event (
|
||||
event_id VARCHAR2(64) NOT NULL,
|
||||
event_schema VARCHAR2(64) NOT NULL,
|
||||
applied_at TIMESTAMP DEFAULT SYSDATE NOT NULL,
|
||||
CONSTRAINT pk_stats_projection_applied PRIMARY KEY(event_id)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_stats_projection_applied_time
|
||||
ON stats_projection_applied_event(applied_at);
|
||||
|
||||
COMMENT ON TABLE stats_projection_outbox IS
|
||||
'事实批次统计 Outbox;Kafka 发布或消费失败时可持续恢复';
|
||||
COMMENT ON COLUMN stats_projection_outbox.status IS
|
||||
'PENDING/FAILED/PUBLISHED/APPLIED;只有 APPLIED 才允许到期清理';
|
||||
COMMENT ON TABLE stats_projection_applied_event IS
|
||||
'统计投影消费幂等标记;保留时间必须长于 STATS.PROJECTION Topic';
|
||||
|
||||
COMMIT;
|
||||
@@ -0,0 +1,44 @@
|
||||
-- v3 达梦升级验证:只读。
|
||||
|
||||
-- 应返回 2 行。
|
||||
SELECT table_name
|
||||
FROM user_tables
|
||||
WHERE table_name IN (
|
||||
'STATS_PROJECTION_OUTBOX',
|
||||
'STATS_PROJECTION_APPLIED_EVENT'
|
||||
)
|
||||
ORDER BY table_name;
|
||||
|
||||
-- 核对关键字段;OUTBOX 应包含 11 列,APPLIED_EVENT 应包含 3 列。
|
||||
SELECT table_name, column_id, column_name, data_type, nullable
|
||||
FROM user_tab_columns
|
||||
WHERE table_name IN (
|
||||
'STATS_PROJECTION_OUTBOX',
|
||||
'STATS_PROJECTION_APPLIED_EVENT'
|
||||
)
|
||||
ORDER BY table_name, column_id;
|
||||
|
||||
-- 三个约束都应为 ENABLED。
|
||||
SELECT table_name, constraint_name, constraint_type, status
|
||||
FROM user_constraints
|
||||
WHERE constraint_name IN (
|
||||
'PK_STATS_PROJECTION_OUTBOX',
|
||||
'CK_STATS_PROJECTION_OUTBOX_STATUS',
|
||||
'PK_STATS_PROJECTION_APPLIED'
|
||||
)
|
||||
ORDER BY constraint_name;
|
||||
|
||||
-- 两个索引都应为 VALID。
|
||||
SELECT table_name, index_name, uniqueness, status
|
||||
FROM user_indexes
|
||||
WHERE index_name IN (
|
||||
'IDX_STATS_PROJECTION_OUTBOX_DUE',
|
||||
'IDX_STATS_PROJECTION_APPLIED_TIME'
|
||||
)
|
||||
ORDER BY index_name;
|
||||
|
||||
-- 刚建表时应返回 0、0;应用启动后数字会正常增长。
|
||||
SELECT
|
||||
(SELECT COUNT(*) FROM stats_projection_outbox) AS outbox_rows,
|
||||
(SELECT COUNT(*) FROM stats_projection_applied_event) AS applied_rows
|
||||
FROM dual;
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Docker 单文件版:直接在 Kafka 容器内创建并校验统计投影 Topic。
|
||||
# 默认值适配测试环境;如容器名、Kafka 地址或安装目录不同,可通过环境变量覆盖。
|
||||
|
||||
DOCKER_BIN="${DOCKER_BIN:-docker}"
|
||||
KAFKA_CONTAINER="${KAFKA_CONTAINER:-kafka_ycgy-bt-kafka-1}"
|
||||
BOOTSTRAP="${BOOTSTRAP:-localhost:9092}"
|
||||
KAFKA_BIN="${KAFKA_BIN:-/opt/kafka/bin}"
|
||||
RAW_TOPIC="${RAW_TOPIC:-agent-01-syslog-topic}"
|
||||
PROJECTION_TOPIC="${PROJECTION_TOPIC:-${RAW_TOPIC}.STATS.PROJECTION}"
|
||||
RETENTION_MS="${RETENTION_MS:-604800000}"
|
||||
COMMAND_CONFIG="${COMMAND_CONFIG:-}"
|
||||
|
||||
die() {
|
||||
echo "ERROR: $*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
command -v "${DOCKER_BIN}" >/dev/null 2>&1 || die "未找到 Docker CLI: ${DOCKER_BIN}"
|
||||
|
||||
RUNNING="$("${DOCKER_BIN}" inspect -f '{{.State.Running}}' "${KAFKA_CONTAINER}" 2>/dev/null || true)"
|
||||
[[ "${RUNNING}" == "true" ]] || die "Kafka 容器不存在或未运行: ${KAFKA_CONTAINER}"
|
||||
|
||||
"${DOCKER_BIN}" exec "${KAFKA_CONTAINER}" test -x "${KAFKA_BIN}/kafka-topics.sh" \
|
||||
|| die "容器内找不到可执行文件: ${KAFKA_BIN}/kafka-topics.sh"
|
||||
"${DOCKER_BIN}" exec "${KAFKA_CONTAINER}" test -x "${KAFKA_BIN}/kafka-configs.sh" \
|
||||
|| die "容器内找不到可执行文件: ${KAFKA_BIN}/kafka-configs.sh"
|
||||
"${DOCKER_BIN}" exec "${KAFKA_CONTAINER}" bash -c 'exit 0' \
|
||||
|| die "容器内缺少 bash,无法执行原始升级脚本"
|
||||
|
||||
DOCKER_ENV=(
|
||||
-e "BOOTSTRAP=${BOOTSTRAP}"
|
||||
-e "KAFKA_BIN=${KAFKA_BIN}"
|
||||
-e "RAW_TOPIC=${RAW_TOPIC}"
|
||||
-e "PROJECTION_TOPIC=${PROJECTION_TOPIC}"
|
||||
-e "RETENTION_MS=${RETENTION_MS}"
|
||||
)
|
||||
KAFKA_COMMON=(--bootstrap-server "${BOOTSTRAP}")
|
||||
|
||||
if [[ -n "${COMMAND_CONFIG}" ]]; then
|
||||
"${DOCKER_BIN}" exec "${KAFKA_CONTAINER}" test -r "${COMMAND_CONFIG}" \
|
||||
|| die "容器内无法读取 COMMAND_CONFIG: ${COMMAND_CONFIG}"
|
||||
DOCKER_ENV+=(-e "COMMAND_CONFIG=${COMMAND_CONFIG}")
|
||||
KAFKA_COMMON+=(--command-config "${COMMAND_CONFIG}")
|
||||
fi
|
||||
|
||||
echo "检查 Kafka 连接及原始 topic ..."
|
||||
"${DOCKER_BIN}" exec "${KAFKA_CONTAINER}" \
|
||||
"${KAFKA_BIN}/kafka-topics.sh" \
|
||||
"${KAFKA_COMMON[@]}" \
|
||||
--describe \
|
||||
--topic "${RAW_TOPIC}" >/dev/null \
|
||||
|| die "无法连接 Kafka 或原始 topic 不存在: ${RAW_TOPIC}"
|
||||
|
||||
echo "Kafka 容器: ${KAFKA_CONTAINER}"
|
||||
echo "Bootstrap: ${BOOTSTRAP}"
|
||||
echo "原始 topic: ${RAW_TOPIC}"
|
||||
echo "投影 topic: ${PROJECTION_TOPIC}"
|
||||
echo "开始执行 Docker 版升级 ..."
|
||||
|
||||
exec "${DOCKER_BIN}" exec -i "${DOCKER_ENV[@]}" "${KAFKA_CONTAINER}" \
|
||||
bash -s <<'KAFKA_UPGRADE_SCRIPT'
|
||||
set -euo pipefail
|
||||
|
||||
if [[ "${PROJECTION_TOPIC}" == "${RAW_TOPIC}.STATS.MATERIALIZE" ]]; then
|
||||
echo "ERROR: STATS.PROJECTION 不能与 STATS.MATERIALIZE 使用同一个 Topic" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TOPICS="${KAFKA_BIN}/kafka-topics.sh"
|
||||
CONFIGS="${KAFKA_BIN}/kafka-configs.sh"
|
||||
COMMON=(--bootstrap-server "${BOOTSTRAP}")
|
||||
if [[ -n "${COMMAND_CONFIG:-}" ]]; then
|
||||
[[ -r "${COMMAND_CONFIG}" ]] || { echo "ERROR: 无法读取 ${COMMAND_CONFIG}" >&2; exit 1; }
|
||||
COMMON+=(--command-config "${COMMAND_CONFIG}")
|
||||
fi
|
||||
|
||||
echo "Kafka=${BOOTSTRAP}"
|
||||
echo "原始 Topic=${RAW_TOPIC}"
|
||||
echo "统计投影 Topic=${PROJECTION_TOPIC}"
|
||||
|
||||
RAW_INFO=$("${TOPICS}" "${COMMON[@]}" --describe --topic "${RAW_TOPIC}")
|
||||
REPLICAS=$(printf '%s\n' "${RAW_INFO}" | awk '
|
||||
/ReplicationFactor:/ {for(i=1;i<=NF;i++) if($i=="ReplicationFactor:"){print $(i+1);exit}}
|
||||
')
|
||||
if [[ ! "${REPLICAS}" =~ ^[1-9][0-9]*$ ]]; then
|
||||
echo "ERROR: 无法从 ${RAW_TOPIC} 读取副本数" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[1/3] 创建 Topic(已经存在时不重复创建)"
|
||||
"${TOPICS}" "${COMMON[@]}" --create --if-not-exists \
|
||||
--topic "${PROJECTION_TOPIC}" \
|
||||
--partitions 1 \
|
||||
--replication-factor "${REPLICAS}" \
|
||||
--config cleanup.policy=delete \
|
||||
--config "retention.ms=${RETENTION_MS}"
|
||||
|
||||
echo "[2/3] 更新保留策略"
|
||||
if ! "${CONFIGS}" "${COMMON[@]}" --alter \
|
||||
--entity-type topics --entity-name "${PROJECTION_TOPIC}" \
|
||||
--add-config "cleanup.policy=delete,retention.ms=${RETENTION_MS}"; then
|
||||
echo "WARN: Kafka 配置更新超时;新建命令已携带相同策略,继续验证 Topic" >&2
|
||||
fi
|
||||
|
||||
echo "[3/3] 验证 Topic"
|
||||
INFO=$("${TOPICS}" "${COMMON[@]}" --describe --topic "${PROJECTION_TOPIC}")
|
||||
printf '%s\n' "${INFO}"
|
||||
PARTITIONS=$(printf '%s\n' "${INFO}" | awk '
|
||||
/PartitionCount:/ {for(i=1;i<=NF;i++) if($i=="PartitionCount:"){print $(i+1);exit}}
|
||||
')
|
||||
ACTUAL_REPLICAS=$(printf '%s\n' "${INFO}" | awk '
|
||||
/ReplicationFactor:/ {for(i=1;i<=NF;i++) if($i=="ReplicationFactor:"){print $(i+1);exit}}
|
||||
')
|
||||
if [[ ! "${PARTITIONS}" =~ ^[1-9][0-9]*$ ]]; then
|
||||
echo "ERROR: ${PROJECTION_TOPIC} 没有有效分区" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "${PARTITIONS}" != "1" ]]; then
|
||||
echo "WARN: Topic 已有 ${PARTITIONS} 个分区;不会修改分区数,固定 key 仍只使用其中一个分区" >&2
|
||||
fi
|
||||
if [[ "${ACTUAL_REPLICAS}" != "${REPLICAS}" ]]; then
|
||||
echo "WARN: Topic 副本数=${ACTUAL_REPLICAS},原始 Topic 副本数=${REPLICAS}" >&2
|
||||
fi
|
||||
|
||||
echo "PASS: ${PROJECTION_TOPIC} 已创建/确认。分区数=${PARTITIONS},保留时间=${RETENTION_MS}ms。"
|
||||
echo "脚本未删除消息,也未修改任何消费组 offset。"
|
||||
KAFKA_UPGRADE_SCRIPT
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# v3 统计投影 Topic。新建时使用 1 个分区;如果现场已经建成多个分区则保留现状,
|
||||
# 应用使用固定 Kafka key,投影消息仍只会落入其中一个稳定分区。
|
||||
BOOTSTRAP="${BOOTSTRAP:-10.141.121.35:9092,10.141.121.50:9092,10.141.121.59:9092}"
|
||||
RAW_TOPIC="${RAW_TOPIC:-agent-01-syslog-topic}"
|
||||
PROJECTION_TOPIC="${PROJECTION_TOPIC:-${RAW_TOPIC}.STATS.PROJECTION}"
|
||||
RETENTION_MS="${RETENTION_MS:-604800000}"
|
||||
|
||||
if [[ "${PROJECTION_TOPIC}" == "${RAW_TOPIC}.STATS.MATERIALIZE" ]]; then
|
||||
echo "ERROR: STATS.PROJECTION 不能与 STATS.MATERIALIZE 使用同一个 Topic" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z "${KAFKA_BIN:-}" ]]; then
|
||||
for candidate in \
|
||||
"$(pwd)" \
|
||||
"$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" \
|
||||
"/opt/kafka/kafka_2.13-3.6.1/bin" \
|
||||
"/opt/kafka/bin" \
|
||||
"/usr/local/kafka/bin"; do
|
||||
if [[ -x "${candidate}/kafka-topics.sh" ]] \
|
||||
&& [[ -x "${candidate}/kafka-configs.sh" ]]; then
|
||||
KAFKA_BIN="${candidate}"
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
if [[ -z "${KAFKA_BIN:-}" ]] && command -v kafka-topics.sh >/dev/null 2>&1; then
|
||||
KAFKA_BIN=$(dirname -- "$(command -v kafka-topics.sh)")
|
||||
fi
|
||||
if [[ -z "${KAFKA_BIN:-}" ]]; then
|
||||
echo "ERROR: 未找到 kafka-topics.sh;请在 Kafka bin 目录执行,或设置 KAFKA_BIN" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TOPICS="${KAFKA_BIN}/kafka-topics.sh"
|
||||
CONFIGS="${KAFKA_BIN}/kafka-configs.sh"
|
||||
COMMON=(--bootstrap-server "${BOOTSTRAP}")
|
||||
if [[ -n "${COMMAND_CONFIG:-}" ]]; then
|
||||
[[ -r "${COMMAND_CONFIG}" ]] || { echo "ERROR: 无法读取 ${COMMAND_CONFIG}" >&2; exit 1; }
|
||||
COMMON+=(--command-config "${COMMAND_CONFIG}")
|
||||
fi
|
||||
|
||||
echo "Kafka=${BOOTSTRAP}"
|
||||
echo "原始 Topic=${RAW_TOPIC}"
|
||||
echo "统计投影 Topic=${PROJECTION_TOPIC}"
|
||||
|
||||
RAW_INFO=$("${TOPICS}" "${COMMON[@]}" --describe --topic "${RAW_TOPIC}")
|
||||
REPLICAS=$(printf '%s\n' "${RAW_INFO}" | awk '
|
||||
/ReplicationFactor:/ {for(i=1;i<=NF;i++) if($i=="ReplicationFactor:"){print $(i+1);exit}}
|
||||
')
|
||||
if [[ ! "${REPLICAS}" =~ ^[1-9][0-9]*$ ]]; then
|
||||
echo "ERROR: 无法从 ${RAW_TOPIC} 读取副本数" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[1/3] 创建 Topic(已经存在时不重复创建)"
|
||||
"${TOPICS}" "${COMMON[@]}" --create --if-not-exists \
|
||||
--topic "${PROJECTION_TOPIC}" \
|
||||
--partitions 1 \
|
||||
--replication-factor "${REPLICAS}" \
|
||||
--config cleanup.policy=delete \
|
||||
--config "retention.ms=${RETENTION_MS}"
|
||||
|
||||
echo "[2/3] 更新保留策略"
|
||||
if ! "${CONFIGS}" "${COMMON[@]}" --alter \
|
||||
--entity-type topics --entity-name "${PROJECTION_TOPIC}" \
|
||||
--add-config "cleanup.policy=delete,retention.ms=${RETENTION_MS}"; then
|
||||
echo "WARN: Kafka 配置更新超时;新建命令已携带相同策略,继续验证 Topic" >&2
|
||||
fi
|
||||
|
||||
echo "[3/3] 验证 Topic"
|
||||
INFO=$("${TOPICS}" "${COMMON[@]}" --describe --topic "${PROJECTION_TOPIC}")
|
||||
printf '%s\n' "${INFO}"
|
||||
PARTITIONS=$(printf '%s\n' "${INFO}" | awk '
|
||||
/PartitionCount:/ {for(i=1;i<=NF;i++) if($i=="PartitionCount:"){print $(i+1);exit}}
|
||||
')
|
||||
ACTUAL_REPLICAS=$(printf '%s\n' "${INFO}" | awk '
|
||||
/ReplicationFactor:/ {for(i=1;i<=NF;i++) if($i=="ReplicationFactor:"){print $(i+1);exit}}
|
||||
')
|
||||
if [[ ! "${PARTITIONS}" =~ ^[1-9][0-9]*$ ]]; then
|
||||
echo "ERROR: ${PROJECTION_TOPIC} 没有有效分区" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "${PARTITIONS}" != "1" ]]; then
|
||||
echo "WARN: Topic 已有 ${PARTITIONS} 个分区;不会修改分区数,固定 key 仍只使用其中一个分区" >&2
|
||||
fi
|
||||
if [[ "${ACTUAL_REPLICAS}" != "${REPLICAS}" ]]; then
|
||||
echo "WARN: Topic 副本数=${ACTUAL_REPLICAS},原始 Topic 副本数=${REPLICAS}" >&2
|
||||
fi
|
||||
|
||||
echo "PASS: ${PROJECTION_TOPIC} 已创建/确认。分区数=${PARTITIONS},保留时间=${RETENTION_MS}ms。"
|
||||
echo "脚本未删除消息,也未修改任何消费组 offset。"
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Docker 单文件版:直接在 Kafka 容器内创建并校验大日志相关 Topic。
|
||||
# 默认值适配测试环境;如容器名、Kafka 地址或安装目录不同,可通过环境变量覆盖。
|
||||
|
||||
DOCKER_BIN="${DOCKER_BIN:-docker}"
|
||||
KAFKA_CONTAINER="${KAFKA_CONTAINER:-kafka_ycgy-bt-kafka-1}"
|
||||
BOOTSTRAP="${BOOTSTRAP:-localhost:9092}"
|
||||
KAFKA_BIN="${KAFKA_BIN:-/opt/kafka/bin}"
|
||||
RAW_TOPIC="${RAW_TOPIC:-agent-01-syslog-topic}"
|
||||
LARGE_TOPIC="${LARGE_TOPIC:-${RAW_TOPIC}.large}"
|
||||
MAX_MESSAGE_BYTES="${MAX_MESSAGE_BYTES:-33554432}"
|
||||
RETENTION_MS="${RETENTION_MS:-604800000}"
|
||||
DLT_RETENTION_MS="${DLT_RETENTION_MS:-2592000000}"
|
||||
COMMAND_CONFIG="${COMMAND_CONFIG:-}"
|
||||
|
||||
die() {
|
||||
echo "ERROR: $*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
command -v "${DOCKER_BIN}" >/dev/null 2>&1 || die "未找到 Docker CLI: ${DOCKER_BIN}"
|
||||
|
||||
RUNNING="$("${DOCKER_BIN}" inspect -f '{{.State.Running}}' "${KAFKA_CONTAINER}" 2>/dev/null || true)"
|
||||
[[ "${RUNNING}" == "true" ]] || die "Kafka 容器不存在或未运行: ${KAFKA_CONTAINER}"
|
||||
|
||||
"${DOCKER_BIN}" exec "${KAFKA_CONTAINER}" test -x "${KAFKA_BIN}/kafka-topics.sh" \
|
||||
|| die "容器内找不到可执行文件: ${KAFKA_BIN}/kafka-topics.sh"
|
||||
"${DOCKER_BIN}" exec "${KAFKA_CONTAINER}" test -x "${KAFKA_BIN}/kafka-configs.sh" \
|
||||
|| die "容器内找不到可执行文件: ${KAFKA_BIN}/kafka-configs.sh"
|
||||
"${DOCKER_BIN}" exec "${KAFKA_CONTAINER}" bash -c 'exit 0' \
|
||||
|| die "容器内缺少 bash,无法执行原始升级脚本"
|
||||
|
||||
DOCKER_ENV=(
|
||||
-e "BOOTSTRAP=${BOOTSTRAP}"
|
||||
-e "KAFKA_BIN=${KAFKA_BIN}"
|
||||
-e "RAW_TOPIC=${RAW_TOPIC}"
|
||||
-e "LARGE_TOPIC=${LARGE_TOPIC}"
|
||||
-e "MAX_MESSAGE_BYTES=${MAX_MESSAGE_BYTES}"
|
||||
-e "RETENTION_MS=${RETENTION_MS}"
|
||||
-e "DLT_RETENTION_MS=${DLT_RETENTION_MS}"
|
||||
)
|
||||
KAFKA_COMMON=(--bootstrap-server "${BOOTSTRAP}")
|
||||
|
||||
if [[ -n "${COMMAND_CONFIG}" ]]; then
|
||||
"${DOCKER_BIN}" exec "${KAFKA_CONTAINER}" test -r "${COMMAND_CONFIG}" \
|
||||
|| die "容器内无法读取 COMMAND_CONFIG: ${COMMAND_CONFIG}"
|
||||
DOCKER_ENV+=(-e "COMMAND_CONFIG=${COMMAND_CONFIG}")
|
||||
KAFKA_COMMON+=(--command-config "${COMMAND_CONFIG}")
|
||||
fi
|
||||
|
||||
echo "检查 Kafka 连接及原始 topic ..."
|
||||
"${DOCKER_BIN}" exec "${KAFKA_CONTAINER}" \
|
||||
"${KAFKA_BIN}/kafka-topics.sh" \
|
||||
"${KAFKA_COMMON[@]}" \
|
||||
--describe \
|
||||
--topic "${RAW_TOPIC}" >/dev/null \
|
||||
|| die "无法连接 Kafka 或原始 topic 不存在: ${RAW_TOPIC}"
|
||||
|
||||
echo "Kafka 容器: ${KAFKA_CONTAINER}"
|
||||
echo "Bootstrap: ${BOOTSTRAP}"
|
||||
echo "原始 topic: ${RAW_TOPIC}"
|
||||
echo "大日志 topic: ${LARGE_TOPIC}"
|
||||
echo "开始执行 Docker 版升级 ..."
|
||||
|
||||
exec "${DOCKER_BIN}" exec -i "${DOCKER_ENV[@]}" "${KAFKA_CONTAINER}" \
|
||||
bash -s <<'KAFKA_UPGRADE_SCRIPT'
|
||||
set -euo pipefail
|
||||
|
||||
TOPICS="${KAFKA_BIN}/kafka-topics.sh"
|
||||
CONFIGS="${KAFKA_BIN}/kafka-configs.sh"
|
||||
COMMON=(--bootstrap-server "${BOOTSTRAP}")
|
||||
if [[ -n "${COMMAND_CONFIG:-}" ]]; then
|
||||
[[ -r "${COMMAND_CONFIG}" ]] || { echo "ERROR: 无法读取 ${COMMAND_CONFIG}" >&2; exit 1; }
|
||||
COMMON+=(--command-config "${COMMAND_CONFIG}")
|
||||
fi
|
||||
|
||||
RAW_INFO=$("${TOPICS}" "${COMMON[@]}" --describe --topic "${RAW_TOPIC}")
|
||||
PARTITIONS=$(printf '%s\n' "${RAW_INFO}" | awk '
|
||||
/PartitionCount:/ {for(i=1;i<=NF;i++) if($i=="PartitionCount:"){print $(i+1);exit}}
|
||||
')
|
||||
REPLICAS=$(printf '%s\n' "${RAW_INFO}" | awk '
|
||||
/ReplicationFactor:/ {for(i=1;i<=NF;i++) if($i=="ReplicationFactor:"){print $(i+1);exit}}
|
||||
')
|
||||
if [[ ! "${PARTITIONS}" =~ ^[1-9][0-9]*$ ]] || [[ ! "${REPLICAS}" =~ ^[1-9][0-9]*$ ]]; then
|
||||
echo "ERROR: 无法读取 ${RAW_TOPIC} 的分区数或副本数" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
declare -a TOPIC_NAMES=(
|
||||
"${LARGE_TOPIC}"
|
||||
"${LARGE_TOPIC}.RETRY.1M"
|
||||
"${LARGE_TOPIC}.RETRY.10M"
|
||||
"${LARGE_TOPIC}.RETRY.1H"
|
||||
"${LARGE_TOPIC}.DLT"
|
||||
"${LARGE_TOPIC}.REPLAY"
|
||||
)
|
||||
|
||||
echo "Kafka=${BOOTSTRAP},分区=${PARTITIONS},副本=${REPLICAS},单条上限=${MAX_MESSAGE_BYTES}"
|
||||
for topic in "${TOPIC_NAMES[@]}"; do
|
||||
topic_retention="${RETENTION_MS}"
|
||||
[[ "${topic}" == "${LARGE_TOPIC}.DLT" ]] && topic_retention="${DLT_RETENTION_MS}"
|
||||
"${TOPICS}" "${COMMON[@]}" --create --if-not-exists \
|
||||
--topic "${topic}" --partitions "${PARTITIONS}" --replication-factor "${REPLICAS}" \
|
||||
--config cleanup.policy=delete \
|
||||
--config "retention.ms=${topic_retention}" \
|
||||
--config "max.message.bytes=${MAX_MESSAGE_BYTES}"
|
||||
"${CONFIGS}" "${COMMON[@]}" --alter --entity-type topics --entity-name "${topic}" \
|
||||
--add-config "cleanup.policy=delete,retention.ms=${topic_retention},max.message.bytes=${MAX_MESSAGE_BYTES}"
|
||||
"${TOPICS}" "${COMMON[@]}" --describe --topic "${topic}"
|
||||
done
|
||||
|
||||
echo "PASS: 大日志 Topic 已创建并统一配置。"
|
||||
echo "注意:Broker 的 replica.fetch.max.bytes 也必须不小于 ${MAX_MESSAGE_BYTES}。"
|
||||
echo "脚本未删除消息,也未修改任何消费组 offset。"
|
||||
KAFKA_UPGRADE_SCRIPT
|
||||
@@ -0,0 +1,77 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# 创建大日志主 Topic 及独立 Retry/DLT/Replay Topic。不删除消息、不修改消费组 offset。
|
||||
BOOTSTRAP="${BOOTSTRAP:-10.141.121.35:9092,10.141.121.50:9092,10.141.121.59:9092}"
|
||||
RAW_TOPIC="${RAW_TOPIC:-agent-01-syslog-topic}"
|
||||
LARGE_TOPIC="${LARGE_TOPIC:-${RAW_TOPIC}.large}"
|
||||
MAX_MESSAGE_BYTES="${MAX_MESSAGE_BYTES:-33554432}"
|
||||
RETENTION_MS="${RETENTION_MS:-604800000}"
|
||||
DLT_RETENTION_MS="${DLT_RETENTION_MS:-2592000000}"
|
||||
|
||||
if [[ -z "${KAFKA_BIN:-}" ]]; then
|
||||
for candidate in \
|
||||
"$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" \
|
||||
"/opt/kafka/kafka_2.13-3.6.1/bin" \
|
||||
"/opt/kafka/bin" \
|
||||
"/usr/local/kafka/bin"; do
|
||||
if [[ -x "${candidate}/kafka-topics.sh" ]] && [[ -x "${candidate}/kafka-configs.sh" ]]; then
|
||||
KAFKA_BIN="${candidate}"
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
if [[ -z "${KAFKA_BIN:-}" ]] && command -v kafka-topics.sh >/dev/null 2>&1; then
|
||||
KAFKA_BIN=$(dirname -- "$(command -v kafka-topics.sh)")
|
||||
fi
|
||||
if [[ -z "${KAFKA_BIN:-}" ]]; then
|
||||
echo "ERROR: 未找到 Kafka CLI;请设置 KAFKA_BIN" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TOPICS="${KAFKA_BIN}/kafka-topics.sh"
|
||||
CONFIGS="${KAFKA_BIN}/kafka-configs.sh"
|
||||
COMMON=(--bootstrap-server "${BOOTSTRAP}")
|
||||
if [[ -n "${COMMAND_CONFIG:-}" ]]; then
|
||||
[[ -r "${COMMAND_CONFIG}" ]] || { echo "ERROR: 无法读取 ${COMMAND_CONFIG}" >&2; exit 1; }
|
||||
COMMON+=(--command-config "${COMMAND_CONFIG}")
|
||||
fi
|
||||
|
||||
RAW_INFO=$("${TOPICS}" "${COMMON[@]}" --describe --topic "${RAW_TOPIC}")
|
||||
PARTITIONS=$(printf '%s\n' "${RAW_INFO}" | awk '
|
||||
/PartitionCount:/ {for(i=1;i<=NF;i++) if($i=="PartitionCount:"){print $(i+1);exit}}
|
||||
')
|
||||
REPLICAS=$(printf '%s\n' "${RAW_INFO}" | awk '
|
||||
/ReplicationFactor:/ {for(i=1;i<=NF;i++) if($i=="ReplicationFactor:"){print $(i+1);exit}}
|
||||
')
|
||||
if [[ ! "${PARTITIONS}" =~ ^[1-9][0-9]*$ ]] || [[ ! "${REPLICAS}" =~ ^[1-9][0-9]*$ ]]; then
|
||||
echo "ERROR: 无法读取 ${RAW_TOPIC} 的分区数或副本数" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
declare -a TOPIC_NAMES=(
|
||||
"${LARGE_TOPIC}"
|
||||
"${LARGE_TOPIC}.RETRY.1M"
|
||||
"${LARGE_TOPIC}.RETRY.10M"
|
||||
"${LARGE_TOPIC}.RETRY.1H"
|
||||
"${LARGE_TOPIC}.DLT"
|
||||
"${LARGE_TOPIC}.REPLAY"
|
||||
)
|
||||
|
||||
echo "Kafka=${BOOTSTRAP},分区=${PARTITIONS},副本=${REPLICAS},单条上限=${MAX_MESSAGE_BYTES}"
|
||||
for topic in "${TOPIC_NAMES[@]}"; do
|
||||
topic_retention="${RETENTION_MS}"
|
||||
[[ "${topic}" == "${LARGE_TOPIC}.DLT" ]] && topic_retention="${DLT_RETENTION_MS}"
|
||||
"${TOPICS}" "${COMMON[@]}" --create --if-not-exists \
|
||||
--topic "${topic}" --partitions "${PARTITIONS}" --replication-factor "${REPLICAS}" \
|
||||
--config cleanup.policy=delete \
|
||||
--config "retention.ms=${topic_retention}" \
|
||||
--config "max.message.bytes=${MAX_MESSAGE_BYTES}"
|
||||
"${CONFIGS}" "${COMMON[@]}" --alter --entity-type topics --entity-name "${topic}" \
|
||||
--add-config "cleanup.policy=delete,retention.ms=${topic_retention},max.message.bytes=${MAX_MESSAGE_BYTES}"
|
||||
"${TOPICS}" "${COMMON[@]}" --describe --topic "${topic}"
|
||||
done
|
||||
|
||||
echo "PASS: 大日志 Topic 已创建并统一配置。"
|
||||
echo "注意:Broker 的 replica.fetch.max.bytes 也必须不小于 ${MAX_MESSAGE_BYTES}。"
|
||||
echo "脚本未删除消息,也未修改任何消费组 offset。"
|
||||
Reference in New Issue
Block a user