news 2026/11/2 1:04:29

轻量级统一监控告警系统:PLFM_RADAR探针设计与实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
轻量级统一监控告警系统:PLFM_RADAR探针设计与实践

PLFM_RADAR 这个名字听起来挺唬人,其实拆开就两半:PLFM 在我这边的语境里是Platform 的缩写,RADAR 也不是真的无线电雷达,而是我们把监控系统做成了一部持续扫描平台健康的雷达。说白了,这就是一个面向多服务、多节点的统一监控告警系统,用来解决“服务到底挂没挂、挂在哪、为什么挂、要不要立刻处理”这一连串问题。

这个项目适合谁参考?如果你是后端开发、SRE、运维,或者一个人维护十几个服务的“全栈杂工”,那这篇文章里的思路和代码你基本能直接抄作业。它能帮你做三件事:把分散在各个服务里的健康状态统一收上来,通过周期性的探活和指标分析提前发现隐患,在故障发生时把告警准确送到对应的人手里。我实际用下来,最大的感受是:它不是替代 Prometheus、Zabbix 这类重型方案,而是给中小团队一个轻量、透明、看得懂的兜底雷达。

下面我就把整个 PLFM_RADAR 从立项思路、核心设计、实际部署到踩坑记录完整梳理一遍。这中间没有高大上的玄学,只有我自己一台服务器上反反复复折腾出来的真东西。

1. 项目定位与整体设计思路拆解

1.1 为什么需要一个“平台雷达”

先说说我当初为什么要写这个东西。团队规模不大,服务数量却不少:一个网关、三个业务 API、两个定时任务、一个消息队列消费者,再加上数据库和缓存的读写依赖。以前出故障怎么发现?靠用户反馈。等用户在群里喊“页面打不开了”,往往已经过去了十几分钟,日志翻半天才定位到是某个下游服务把连接池打满了。

后来我尝试过直接上 Prometheus + Grafana,发现对几个人来说维护成本其实不低:指标采集规则要写,Exporter 要配,告警规则要调,Grafana 面板要画,这套东西一旦没人持续维护,很快就会变成“监控真空”。PLFM_RADAR 的思路则完全不同:我不追求纳秒级指标,不追求海量维度,只追求“服务是否还活着、响应是否足够快、依赖是否还正常”这三件事,然后把这些信息用最简单的方式聚合、判断、通知出去。

这个定位听起来很朴素,但恰恰是大多数系统最需要的。打个比方,重型监控系统是医院的全身 CT,PLFM_RADAR 更像护士定时查房——不搞复杂影像,就量体温、测血压、问你哪里不舒服。查房的频率高、流程简单,大多数时候比 CT 更能第一时间发现问题。

1.2 技术选型背后的取舍

项目整体采用Python + FastAPI + SQLite + APScheduler + 一套自定义探针脚本的组合。为什么这么选,我解释一下当时的考量。

  • Python:团队主力语言,生态里请求库、调度库、运维脚本一应俱全,写探针和写告警逻辑都很快。
  • FastAPI:只用来提供一个极简的仪表盘接口和管理 API。它本身性能足够,而且自带文档页面,省去额外维护接口文档的功夫。
  • SQLite:监控数据写多读少、数据量小,单文件存储方便备份迁移。服务数量在几十个以内时,SQLite 完全扛得住,没必要上 MySQL。
  • APScheduler:管理周期任务的老牌调度库,支持 cron 表达式和固定间隔,既能跑探针也能跑告警扫描。
  • 自定义探针脚本:这是 PLFM_RADAR 区别于通用监控系统的关键。通用系统要你定义指标格式、上报方式、采集规则,而 PLFM_RADAR 反其道而行——探针就是一个普通的脚本,返回统一格式的 JSON,什么都测,测什么由你自己写。HTTP 探活是内置的,但比如你要检查 TLS 证书还有几天过期、消息队列堆积有没有超过阈值、数据库连接池剩余多少,这类需求写成自定义探针往往比配开源的 Exporter 还快。

选型过程中我也考虑过直接用现成的 uptime-kuma、grafana-oncall 这类开源工具,但逐一分析后放弃了。uptime-kuma 的探针类型足够,但它把业务探活和服务器资源监控隔离开了,我需要的是一个能在同一套规则里同时判断“服务存活”“响应时延”“证书剩余天数”的机制。自己写的好处是,告警判定的逻辑完全在自己手里,想怎么定规则就怎么定规则,不会被平台限制住。

1.3 系统模块划分

整个 PLFM_RADAR 从功能上分成五块:

  1. 探针注册与调度模块:管理哪些目标需要被监测,以什么频率监测。
  2. 探针执行器:负责调用探针脚本、解析结果、超时控制、异常兜底。
  3. 指标存储模块:把每一次探测的结果写入 SQLite,保留有效的趋势数据。
  4. 告警判定引擎:根据最近 N 次探测结果综合判断状态,避免单次抖动误报。
  5. 通知触达模块:把告警通过企业微信 Webhook、邮件、钉钉等方式发出去。

这里最值得展开的是告警判定引擎。很多初版监控系统犯的错误是“一次失败就告警”,导致半夜三点被一条瞬时网络抖动告警吵醒,等真正故障来了大家反而条件反射地屏蔽消息了。PLFM_RADAR 采用连续失败计数 + 滑动窗口恢复的策略:比如连续 3 次探测失败才触发告警;恢复时也要求连续 2 次成功才发送恢复通知。这个策略在减轻误报和及时发现问题之间取得了不错的平衡。

2. 核心细节解析与实操要点

2.1 探针数据结构设计

PLFM_RADAR 里所有探针,不管是内置的还是自定义的,最终都要输出一个统一的 JSON 结构。这个结构我反复调整过很多版,最后的版本长这样:

{ "ok": true, "latency_ms": 235, "check_time": "2024-11-20T10:30:00+08:00", "details": { "http_status": 200, "cert_expire_days": 35 } }
  • ok:布尔值,表示这次探测是否通过。这是告警引擎唯一无条件信任的字段。
  • latency_ms:探测耗时,用于时延趋势分析。
  • check_time:探测时间戳,避免因为调度延迟导致时间漂移。
  • details:附加信息,内容不限,可以是证书剩余天数、队列积压条数、磁盘剩余空间等。这些字段不会参与告警判定,但会展示在仪表盘上。

设计逻辑:探针只负责“取数据”和“给结论”,不负责“做决策”。连续性判断、告警阈值这些统统交给告警判定引擎。这样做的好处是探针脚本足够纯粹,哪怕换一套告警系统,探针本身还可以继续复用。

注意:自定义探针里最容易出问题的是异常处理。我见过不少同事写的自定义探针,一旦依赖的服务连接超时,脚本直接抛异常退出,PLFM_RADAR 只会拿到一个非零退出码,根本看不到 details 里的半截信息。我的经验是:无论如何都要捕获所有异常,把异常信息写进 details,并且把 ok 置为 false。这样出了故障,仪表盘上至少能看到“为什么失败”,而不是光秃秃的“不可用”。

2.2 内置 HTTP 探针的实现

内置探针是最常用的,因为 90% 的服务都能通过 HTTP 健康检查来确认状态。这是一段精简版的实现:

import requests import json import time import sys def http_probe(url, timeout=5): result = { "ok": False, "latency_ms": 0, "check_time": time.strftime("%Y-%m-%dT%H:%M:%S%z"), "details": {} } try: start = time.time() resp = requests.get(url, timeout=timeout, allow_redirects=True) cost = int((time.time() - start) * 1000) result["latency_ms"] = cost result["details"]["http_status"] = resp.status_code # 默认 2xx 和 3xx 视为健康,可在探针配置里覆盖 result["ok"] = resp.status_code < 400 except requests.exceptions.Timeout: result["details"]["error"] = "timeout_after_{}s".format(timeout) except requests.exceptions.ConnectionError: result["details"]["error"] = "connection_failed" except Exception as e: result["details"]["error"] = str(e) return result if __name__ == "__main__": # 实际使用时从环境变量或参数解析目标 URL url = sys.argv[1] if len(sys.argv) > 1 else "http://localhost:8080/health" print(json.dumps(http_probe(url), ensure_ascii=False))

需要注意两个细节。第一,超时时间必须小于探针调度周期。如果调度周期是 60 秒,而超时时间是 90 秒,那么上一个探针还没超时,下一个探针又启动了,会导致任务堆积。我的经验是超时时间设为调度周期的三分之一到二分之一,比如 60 秒的周期配 20 秒超时。第二,探针执行器必须设置整体超时,即使探针脚本本身忘记处理超时,执行器也要能强制杀掉探针进程,避免僵尸进程越积越多。

2.3 告警阈值与连续判定规则

PLFM_RADAR 的告警阈值分两层:全局默认阈值和目标独有阈值。全局默认适合大多数服务,目标独有则覆盖特殊情况。配置结构大致如下:

# targets.yaml global: interval_sec: 60 timeout_sec: 20 fail_trigger_count: 3 recover_success_count: 2 latency_threshold_ms: 800 targets: - name: "api-gateway" url: "http://127.0.0.1:8080/health" interval_sec: 30 fail_trigger_count: 2 - name: "cert-check" script: "./probes/cert_check.py" interval_sec: 3600 details_threshold: cert_expire_days: 7

details_threshold是我后来加上的能力:除了 ok 字段,还能对 details 里的数值字段设置阈值。比如证书剩余天数低于 7 天就告警,这个场景走自定义探针非常方便。但这里有一个坑,details 里的字段是每次探测动态生成的,如果某次探测没返回这个字段,阈值判断就会跳过。所以我在判定引擎里加了逻辑:字段缺失时视为信息不足,不参与告警判定,但记录一条 warning 日志,帮助排查探针的问题。

连续失败计数也要注意重置时机。一旦服务恢复,计数应该清空;当服务长时间稳定后,历史失败记录已经没有参考意义。我采用的是“窗口滑移法”:不对历史失败做大包大揽,只取最近五分钟内的探测结果来判断连续失败次数,超过五分钟的失败记录自动失效。这一条帮我解决了长假期间偶发网络抖动导致“连续失败计数永远不清零”的诡异问题。

3. 实操过程与核心环节实现

3.1 搭建项目骨架

整个项目的目录结构我尽量保持简单,便于后续维护:

plfm_radar/ ├── app/ │ ├── main.py # FastAPI 入口 │ ├── scheduler.py # APScheduler 调度器 │ ├── executor.py # 探针执行器 │ ├── storage.py # SQLite 存储层 │ ├── alert.py # 告警判定与通知 │ └── models.py # 数据模型 ├── probes/ │ ├── http_probe.py # 内置 HTTP 探针 │ └── cert_check.py # 自定义证书检查探针 ├── config/ │ ├── targets.yaml # 监控目标配置 │ └── notify.yaml # 通知渠道配置 ├── data/ │ └── radar.db # SQLite 数据库文件 ├── requirements.txt └── run.py # 启动入口

依赖就四个:

fastapi==0.104.1 uvicorn==0.24.0 apscheduler==3.10.4 requests==2.31.0 pyyaml==6.0.1

3.2 探针执行器:核心代码逐行说明

执行器是整个系统的核心,负责把探针跑起来并拿回结构化结果。这个模块要考虑的细节很多,我贴出关键部分:

import json import subprocess import shlex from datetime import datetime, timezone class Executor: def __init__(self, global_timeout=20): self.global_timeout = global_timeout def execute(self, target): """根据目标配置执行探针,返回统一 JSON 结果""" result = { "ok": False, "latency_ms": 0, "check_time": datetime.now(timezone.utc).isoformat(), "details": {"error": "not_executed"} } if "url" in target: # 内置 HTTP 探针 cmd = f"python probes/http_probe.py {shlex.quote(target['url'])}" elif "script" in target: # 自定义探针脚本 cmd = f"python {target['script']}" else: result["details"]["error"] = "no valid probe defined" return result try: proc = subprocess.run( shlex.split(cmd), capture_output=True, text=True, timeout=self.global_timeout ) if proc.returncode == 0: try: parsed = json.loads(proc.stdout.strip()) if isinstance(parsed, dict) and "ok" in parsed: result = parsed else: result["details"]["error"] = "invalid probe output" except json.JSONDecodeError: result["details"]["error"] = "probe output is not json" else: result["details"]["error"] = proc.stderr.strip() or "probe failed" except subprocess.TimeoutExpired: result["details"]["error"] = f"probe timed out after {self.global_timeout}s" except Exception as e: result["details"]["error"] = str(e) # 兜底:如果探针没返回 check_time,则使用当前时间 result.setdefault("check_time", datetime.now(timezone.utc).isoformat()) return result

有几个细节我实际踩过坑,必须强调。

第一,用 subprocess 而不是直接把探针函数 import 进来执行。有人会觉得,都写 Python 了,为什么不直接调用?因为探针脚本可能依赖独立环境、可能被单独调试、可能用不同 Python 版本,解耦的好处是“探针挂了不会把主进程拖死”。如果探针直接在主进程内跑,一个死循环可能把整个调度器卡死,subprocess 至少能强制 timeout 杀掉。

第二,解析探针输出时必须校验“ok”字段是否存在。有的探针输出虽然合法 JSON,但忘记包含核心字段,如果直接存储,仪表盘上会出现大量缺状态的脏数据。

第三,超时处理和探针本身的逻辑要分开。探针里 requests 设置了 5 秒超时,执行器还能兜底 20 秒,这种双重保护在服务端僵死的情况下很管用——很多半死状态的服务其实能建立 TCP 连接,但请求永远没响应,只有 requests 超时是不够的。

3.3 告警判定引擎的实现思路

告警判定引擎是最容易写坏的部分。我不能简单判断“上一次失败就告警”,因为网络抖动客观存在。我用的判定流程是:

  1. 从 SQLite 中取出该目标最近 10 条探测记录。
  2. 按时间排序后,从最新一条向前遍历,统计连续失败次数。
  3. 如果连续失败次数大于等于目标配置的fail_trigger_count,进入告警状态。
  4. 如果当前处于告警状态,且最近 5 条记录中连续成功数大于等于recover_success_count,则发送恢复通知并解除告警。
  5. 告警去重:同一目标处于告警状态期间,不再重复发送相同的告警消息,只有状态变化时才发通知。

这步的核心代码片段:

def evaluate_target(target, recent_records): try: trigger_count = target.get("fail_trigger_count", 3) recover_count = target.get("recover_success_count", 2) except AttributeError: return None if not recent_records: return None # 按时间从新到旧排序 records = sorted(recent_records, key=lambda r: r["check_time"], reverse=True) consecutive_fails = 0 for r in records: if not r["ok"]: consecutive_fails += 1 else: break consecutive_ok = 0 for r in records: if r["ok"]: consecutive_ok += 1 else: break if consecutive_fails >= trigger_count: return "alert" if consecutive_ok >= recover_count: return "recover" return None

这里我还会在取出记录时加一个时间窗口过滤,默认只取最近 5 分钟的数据,避免一天前的失败记录参与今天的判定。

提示:如果你的服务偶尔会出现单次失败但立刻自愈的情况,可以把fail_trigger_count调高到 3 或 4。但千万别调到 10 以上,真出故障时你已经睡死过去了,告警发出来也起不到“叫醒你”的作用。这个参数要在“误报率”和“响应速度”之间找平衡,我见过很多团队一开始把阈值定得过高,结果故障都恢复了才收到告警。

3.4 通知触达:企业微信 Webhook 的集成

告警只有发到人手里才有价值。PLFM_RADAR 把通知模块抽象出来了,接 Webhook 和邮件都行。我实际用得最多的是企业微信机器人 Webhook,因为它能直接推送到手机端,还能 @ 对应负责人。我贴一下发送告警的核心代码:

import requests import time import hmac import hashlib import base64 import urllib.parse def send_wecom_alert(webhook_url, title, message, secret=""): # 企业微信机器人支持加签模式,如果不加签则 secret 为空 if secret: timestamp = str(round(time.time() * 1000)) string_to_sign = f"{timestamp}\n{secret}" hmac_code = hmac.new( secret.encode("utf-8"), string_to_sign.encode("utf-8"), digestmod=hashlib.sha256 ).digest() sign = urllib.parse.quote_plus(base64.b64encode(hmac_code)) webhook_url = f"{webhook_url}&timestamp={timestamp}&sign={sign}" payload = { "msgtype": "text", "text": { "content": f"{title}\n{message}", "mentioned_list": ["@all"] } } resp = requests.post(webhook_url, json=payload, timeout=10) return resp.status_code == 200

在这里我遇到了一个比较隐蔽的问题:企业微信机器人有频率限制,如果一次性发太多消息,会直接被限流。当时我刚部署好 PLFM_RADAR,把 8 个服务全部配好,正好遇到一次服务器重启,8 个目标同时触发告警,机器人直接超限,后续所有通知都石沉大海。后来我加了通知队列 + 节流:同一分钟内最多发送 5 条消息,多出来的合并到一起,用“部分服务不可用,详见控制台”代替一条条刷屏。

关于邮件通知,PLFM_RADAR 用的是 SMTP 协议,原理类似,只是把 message 放到 HTML 内容里。如果你团队用的是钉钉或者飞书,改一下 payload 结构即可,通知模块和告警判定逻辑是解耦的。

3.5 使用 systemd 守护进程

部署阶段我遇到了一个实操问题:用nohup python run.py &启动服务,一旦 SSH 断开或者服务器重启,服务就没了。后来老老实实写了一个 systemd unit 文件:

[Unit] Description=PLFM_RADAR Monitor After=network.target [Service] WorkingDirectory=/opt/plfm_radar ExecStart=/usr/bin/python3 /opt/plfm_radar/run.py Restart=always RestartSec=5 User=monitor [Install] WantedBy=multi-user.target

关键配置是Restart=always和RestartSec=5,确保即使主进程意外崩溃,5 秒后也会自动拉起来。另外我特意用User=monitor创建了一个低权限账号来运行监控服务,而不是用 root。监控系统本身如果被攻破,至少不会直接拿到 root 权限,这个习惯我一直保留着。

4. 状态仪表盘:让数据看得见

监控系统的“监控”二字体现在两个地方:告警能主动找人,状态面板能让人找数据。PLFM_RADAR 的仪表盘我用 FastAPI 写了一个极简页面,功能不多但足够用:总览墙、状态详情、历史趋势。

总览墙就是把所有目标按“健康”“告警”“未知”三列展示,用大色块区分,属于一眼就能看出问题的设计。状态详情展示最近 30 次探测的原始记录,包括每次的耗时和 details 内容。历史趋势我暂时只做了时延折线图,用 ECharts 加载 SQLite 中的 latency_ms 数据,够看出服务的响应变化规律。

这里我踩的最大的坑是时区显示。数据库里存的是 UTC 时间,前端展示时如果直接格式化,会和本地时间差 8 小时。后来我在存储层统一存时间戳,展示层统一转成本地时区,避免了“明明 10 点故障,仪表盘显示 2 点”的困惑。

仪表盘不是必须项,但我强烈建议保留。有一次凌晨五点收到告警,我爬起来第一反应不是看手机消息,而是打开仪表盘看一眼所有服务的整体状态——那种“全局是否都挂了还是只有单个服务挂了”的判断,比一条条翻消息快得多。

5. 常见问题与排查技巧实录

5.1 探针调度堆积问题

项目跑了一周后,我偶然查看进程列表,发现有四五个 http_probe.py 同时处于运行状态。排查后发现原因:某个上游服务响应极慢,探针超时设置 20 秒,调度周期 30 秒,两者不够隔离,导致上一轮探针还没跑完,下一轮已经开始了。

解决方式有两个方向:一是把超时时间调成 10 秒,二是给调度器加“上一轮未结束则跳过本轮”的约束。我两个都做了,代码里可以给目标加一个skip_on_overlap: true配置项。监控系统自身不能成为系统的负担,探针堆积本身就是一种资源泄漏。

5.2 告警风暴

真实场景下告警风暴的原因往往是“一个原因导致多个目标同时失败”。比如数据库连接池耗尽,依赖数据库的 5 个 API 服务同时变慢,每个服务的探针都触发告警,5 条消息同时涌进来。PLFM_RADAR 的做法是在通知模块里增加告警分组聚合:如果在 30 秒窗口内检测到多个目标同时进入告警状态,就合并成一条聚合告警,列出受影响服务清单,并标记“疑似公共依赖故障”。

这个策略实施后,深夜被多条告警同时震醒的体验好了很多。排查时只需要顺着“公共依赖”这个线索去看数据库、消息队列这些基础组件,不用逐条服务逐个排查。

5.3 SQLite 并发写入锁

项目初期我遇到过database is locked的错误,原因是告警判定线程和探针写入线程同时操作 SQLite。SQLite 对并发写入的限制比较严格,多线程写入时容易出现锁冲突。解决方式就三个:

  1. 所有写入操作集中在同一个事件循环里,通过队列串行化。
  2. 打开连接时设置timeout=30,等待锁释放。
  3. 在关键写入路径上使用BEGIN IMMEDIATE事务,减少锁等待时间。

我最终采用的是方案一:写操作全部投递到单线程队列,读取可以走只读连接。监控数据吞吐量不大,单线程写入完全够用,而且彻底避免了锁问题。

在并发这块,我也测试过 sqlite 的 WAL 模式,开启后读写并发性能改善明显。如果后续探针数量增长,我会在 storage 模块中加入 WAL 配置。目前对几十个探针来说,单线程写入队列是最稳、最省心的。

5.4 通知发送失败不重试

这是一个很隐蔽的坑。如果企业微信 Webhook 因为网络问题发送失败,PLFM_RADAR 原来只会记录一条日志,不会重试。也就是说:服务真的挂了,但告警没发出去,监控等于白搭。

我给通知模块加了简单的重试机制:发送失败后进入待重试队列,指数退避 1 分钟、5 分钟、20 分钟重试三次。如果三次仍然失败,就写入本地文件alert_fail.log作为最后的兜底。虽然没法保证 100% 送达,但至少能在第二天排查时看到“昨天 22:15 的告警曾经尝试发送失败”这个事实。

文件兜底策略我建议保留,毕竟监控系统的监控能力依赖于通知通道的可靠性。宁可多一层手动的检查,也不能让告警静默丢失。

5.5 排查技巧速查表

现象可能原因排查步骤
探针从未执行调度器未启动或配置格式错误查看日志中是否有调度任务注册信息,运行python run.py --debug检查 YAML 配置
探针执行了但结果异常脚本退出码非零手动执行探针脚本,观察 stderr 输出
告警规则触发但没收到通知Webhook 地址错误或频率受限先手动调接口测试 Webhook,再检查通知模块的失败日志
SQLite 锁冲突多线程写入改为单线程写入队列,或开启 WAL 模式
仪表盘看不到数据check_time 字段缺失或时区错误查看存储层写入是否正常,检查探针 JSON 是否包含 check_time

6. 一些实践中的真实体会

用了 PLFM_RADAR 一段时间后,我最大的感受是:监控系统本身也是一种需要被监控的系统。它不像业务服务那样直接产生收入,很容易被忽略,但一旦它自己挂了,整个团队就变成“盲飞”状态。所以我后来专门加了一个对 PLFM_RADAR 自身的探活:每 5 分钟检查它的 API 是否正常响应,如果连监控都挂了,这个探活脚本会通过完全独立的渠道发一封邮件出来。

还有一个细节是配置管理的习惯。我最初把所有目标写在一个 YAML 文件里,后来服务数量增长后,我改成了按目录拆分:targets.d/api.yaml、targets.d/cron.yaml、targets.d/infra.yaml,每次新增服务只需要新建一个文件,不碰公共配置。这个习惯在多人协作时特别重要,避免每个人都往同一个文件里插一段配置最后冲突到无法合并。

最后说一个关于“探针粒度”的个人建议:宁可多写几个简单探针,也不要把复杂的检查逻辑全塞进一个探针里。一个探针只负责一个小维度,比如“证书是否过期”和“接口是否响应”拆开,故障时你能精确知道是哪个维度出了问题。我最初把两个维度写在一个探针里,告警内容里永远只显示 ok:false,根本不知道是证书问题还是服务挂机问题,排查效率极低。

PLFM_RADAR 这个名字,与其说是一个项目代号,不如说是我给自己团队装的一双“无死角的眼睛”。它不完美,不复杂,但足够可靠。如果你也在维护一批不算多也不算少的服务,与其纠结上不上重型监控平台,不如先花半天时间把一个轻量雷达搭起来。先让它转起来,再慢慢丰富探针和告警规则,你会发现,绝大多数故障都藏不住。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/2 1:03:25

TM4C123+MAX14431 SPI隔离设计:解决工业现场地环路与共模干扰

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/11/2 1:03:15

PLFM_RADAR:Python实现线性调频脉冲雷达信号处理全流程

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/11/2 1:03:03

PLFM雷达信号设计:X波段相控阵的伪线性调频工程实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/11/2 1:00:38

多平台数据感知监控系统PLFM_RADAR的设计与实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/11/2 1:00:37

PLFM雷达技术原理与应用解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/11/2 2:46:35

Flameshot 使用教程:截图、标注一步到位

给同事讲操作、给客服反馈问题、写教程配图&#xff0c;最费时间的往往不是截图本身&#xff0c;而是截完之后&#xff1a;打开修图软件、画箭头、打字、再导出。Flameshot 把这几步合在了一次框选里&#xff0c;截完图当场标好&#xff0c;直接发出去。 先装好&#xff1a;按…

作者头像 李华