登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  linux

systemd 服务怎样用 Type=notify 表达就绪状态

来源:17golang原创

时间:2026-10-14 14:20:31 436浏览 收藏

有些服务的进程很快就创建出来了,但真正可用还要等待配置加载、缓存预热或监听端口初始化。如果 unit 使用默认的启动语义,systemd 可能把“进程已经运行”误当成“服务已经就绪”。这时可以让服务明确发送 READY=1,再由 Type=notify 接收这个信号。

官方地址:https://www.freedesktop.org/software/systemd/man/latest/systemd.service.html

先记住三个边界
  • Type=notify 等待服务发出就绪通知,不等同于 ExecStart 已经启动。
  • 服务通常通过环境变量 NOTIFY_SOCKET 找到 systemd 提供的通知套接字,再发送 READY=1。
  • 通知应该放在真正完成初始化之后;漏发、发得太早或被 NotifyAccess 拒绝,都会让启动状态判断失真。

前置条件:先定义“就绪”是什么

本文用一个最小 Python 服务演示:进程启动后先完成一段初始化,再向通知套接字发送就绪消息。这里的初始化只用短暂等待模拟,实际项目应替换成读取配置、建立连接或加载本地资源等真实动作。

要特别区分两个时刻:ExecStart 表示 systemd 启动了进程,READY=1 才表示应用愿意对外提供服务。若应用没有明确的初始化阶段,使用 Type=simple 反而更直观;不要为了“看起来更完整”机械改成 notify。

ExecStart、NOTIFY_SOCKET、READY=1 与 systemd 服务状态的静态关系说明图
图1:Type=notify 与 READY=1 的服务就绪边界说明图,不是终端截图或运行证据。

编写一个会发送 READY=1 的服务

下面的程序不依赖额外 Python 包,只使用 systemd 注入的 NOTIFY_SOCKET。抽象 Unix 套接字地址以 @ 开头时,需要转换为 Python 可识别的空字节前缀;没有该环境变量时,程序仍能在非 systemd 环境中启动,但不会发送通知。

import os
import socket
import time


def notify_systemd(message: str) -> None:
    # 没有被 systemd 监督时,保持示例可直接运行,不把通知当成硬依赖
    notify_socket = os.environ.get("NOTIFY_SOCKET")
    if not notify_socket:
        return

    # 把 systemd 的抽象套接字地址转换成 Python 的抽象地址写法
    address = notify_socket
    if address.startswith("@"): 
        address = "\0" + address[1:]

    # 数据报发送后立即关闭套接字,避免为一次状态通知保留资源
    with socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) as sock:
        sock.connect(address)
        sock.sendall(message.encode("utf-8"))


def main() -> None:
    # 这里模拟配置加载、连接检查等真正决定可用性的初始化工作
    time.sleep(2)

    # 只有初始化成功后才发送就绪信号
    notify_systemd("READY=1\nSTATUS=服务已完成初始化")

    # 就绪后进入服务循环;真实项目替换为自己的请求处理逻辑
    while True:
        time.sleep(10)


if __name__ == "__main__":
    # 保留标准入口,便于 ExecStart 直接启动本文件
    main()

这个例子里最关键的不是等待两秒,而是通知位置。若把 READY=1 放在初始化前面,systemd 会过早把服务视为已就绪;若初始化失败就直接退出,则不应发送成功信号。

配置 Type=notify 和 NotifyAccess=main

接着创建一个 unit 文件。NotifyAccess=main 明确表示只接受主进程发出的通知;本例由 ExecStart 启动的 Python 进程自己发送,所以边界清楚,也方便后续排查。

[Unit]
Description=Type notify demonstration service
After=network.target

[Service]
# 只有收到 READY=1 后,启动状态才表示应用已就绪
Type=notify
# 本例由 ExecStart 主进程发送通知
NotifyAccess=main
ExecStart=/usr/bin/python3 /opt/notify-demo/service.py
Restart=on-failure
# 防止初始化永远不发送 READY=1 而无限等待
TimeoutStartSec=30

[Install]
WantedBy=multi-user.target

把文件保存为 /etc/systemd/system/notify-demo.service,把脚本保存为 /opt/notify-demo/service.py 后,再让 systemd 重新读取 unit。示例路径只是部署位置的约定,生产环境应替换为实际路径,并确认执行用户对脚本和依赖资源有权限。

# 让 systemd 重新读取新增或修改后的 unit 文件
sudo systemctl daemon-reload

# 启动服务并设置为开机启动;这一步会改变机器状态
sudo systemctl enable --now notify-demo.service

# 查看服务的高层状态和子状态,确认是否已经离开 activating
systemctl show notify-demo.service -p ActiveState -p SubState -p Type

运行后怎样检查是否真的就绪

检查时不要只看进程是否存在。先看 systemd 当前记录的状态,再看日志中是否有应用初始化失败或启动超时。通知成功时,服务通常会从 activating 进入 active;如果一直停留在前者,优先检查是否到达了发送 READY=1 的代码路径。

# 只读取当前状态,不改变服务配置
systemctl is-active notify-demo.service
systemctl show notify-demo.service -p ActiveState -p SubState -p ExecMainStatus

# 查看本次启动日志,重点关注初始化异常和超时提示
journalctl -u notify-demo.service -b --no-pager -n 80

如果示例服务启动后保持运行并进入 active/running,只能说明它的通知路径和 unit 配置相互匹配;这不是业务健康检查的替代品。应用仍然需要自己的端口探活、依赖检查或请求级监控。

unit 文件、NotifyAccess=main、READY=1 与 systemctl journalctl 检查对象的静态关系说明图
图2:Type=notify 启动检查对象的静态关系说明图,不是实际运行结果。

常见故障:为什么服务一直没有 ready

现象优先检查处理思路
一直处于 activating初始化分支是否真正调用了通知函数把 READY=1 放在成功初始化之后,并查看启动日志
启动超时后失败TimeoutStartSec 与实际初始化耗时先修复卡住或失败的初始化,再按业务需要调整超时
通知由子进程发送却不生效NotifyAccess 是否允许发送者让主进程统一发送,或按 systemd 文档明确配置允许范围
改完 unit 仍像旧配置是否执行过 daemon-reload重新加载后再重启服务,并区分 unit 读取问题与应用通知问题

排查顺序建议固定为“通知环境变量、发送时机、发送进程、unit 重载、日志结果”。不要先把超时调得很大来掩盖漏发通知;那只会让依赖方更晚发现服务并未就绪。

清理与回滚

如果只是实验,先停止并禁用服务,再删除自建 unit 和脚本;删除前确认路径确实是本次实验创建的文件。若要回滚到普通启动语义,则应同时移除应用里的通知代码,并把 unit 的 Type=notify 改回与你的进程行为匹配的类型,不能只改其中一侧。

# 停止并取消开机启动,避免实验服务继续运行
sudo systemctl disable --now notify-demo.service

# 删除本实验创建的 unit 文件后重新加载配置
sudo rm /etc/systemd/system/notify-demo.service
sudo systemctl daemon-reload

常见问题

Type=notify 和 Type=simple 有什么区别?

Type=simple 通常把进程启动视作服务启动完成;Type=notify 则要求服务主动发送就绪通知。选择哪一个取决于应用是否有可识别且值得暴露给服务管理器的初始化完成点。

READY=1 应该发送几次?

通常在一次启动的初始化成功点发送一次即可。重点是发送时机和发送者权限,不是反复发送来“保活”;持续存活和看门狗属于另一套机制。

没有 systemd 时这个 Python 示例能运行吗?

可以运行主循环,因为示例在没有 NOTIFY_SOCKET 时直接跳过通知;但此时没有 systemd 服务状态可以接收 READY=1。这正是把通知设计成可选环境能力的好处。

总结一下:Type=notify 的价值是把“进程已启动”和“应用已就绪”拆开。让主进程在真实初始化成功后通过 NOTIFY_SOCKET 发送 READY=1,再用 systemctl show 与 journalctl 检查状态,才能把启动依赖关系建立在明确的信号上。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>