作为一名资深的安企CMS运营人员,我深知在管理多个网站时,系统的稳定性和高效性至关重要。安企CMS凭借其Go语言的高并发特性和多站点管理功能,为我们提供了强大的支持。然而,在同一台服务器上部署多个安企CMS实例时,如何妥善处理进程ID(PID)冲突,确保每个实例独立、稳定运行,是运维中需要关注的核心问题。
理解多站点部署中的 PID 冲突
安企CMS的“多站点管理”功能允许我们在同一套系统架构下管理多个独立网站。这通常意味着每个网站实例需要一个独立的运行进程。Go语言开发的安企CMS通常通过一个二进制文件(默认为anqicms)启动。当我们在服务器上启动多个安企CMS实例时,每个实例都会尝试运行这个二进制文件。
安企CMS提供的 start.sh 脚本,其核心作用是检查名为 anqicms 的进程是否正在运行,如果未运行,则启动它。脚本中的关键部分使用了 ps -ef | grep '\<anqicms\>' 命令来查找进程。在单实例部署中,这没有任何问题。但当多个实例运行在同一台服务器上时,如果所有实例都使用相同的二进制文件名 anqicms,并且都通过各自独立的 start.sh 脚本来管理,那么所有 start.sh 脚本都会同时“看到”所有名为 anqicms 的进程。
这将导致以下问题:
- 重复启动: 如果
start.sh检测到anqicms进程存在,即使那是其他实例的进程,它也可能认为自己的实例已经运行,从而停止启动。反之,如果检测机制不够精确,可能导致多个脚本尝试启动同一个实例,或者将其他实例误认为是自己。 - 错误停止:
stop.sh脚本通常通过kill命令结合grep查找进程ID。如果它查找的是通用的anqicms进程名,那么执行kill -9 $exists时,可能会错误地终止所有正在运行的安企CMS实例,而不仅仅是目标实例。这种“误杀”对于多站点环境而言是灾难性的。
为了避免这种潜在的风险,我们需要为每个安企CMS实例提供唯一的身份标识,以便它们的管理脚本能够精准地控制各自的进程。
核心策略:为每个实例赋予唯一身份
解决PID冲突的关键在于,让每个安企CMS实例在系统层面拥有一个可区分的唯一标识。安企CMS的官方文档和社区实践中,主要推荐两种策略来达到这个目的。
一、通过唯一的二进制文件名称来区分实例
这是安企CMS官方文档中推荐且最为直观的方法。为每个实例的二进制可执行文件赋予一个独一无二的名称。例如,如果你的网站域名是 site1.com 和 site2.com,你可以将它们的 anqicms 二进制文件分别重命名为 anqicms_site1 和 anqicms_site2。
修改步骤如下:
复制安企CMS代码并重命名二进制文件: 为每个新站点复制一份完整的安企CMS安装包到一个独立目录(例如
/www/wwwroot/site1和/www/wwwroot/site2)。 进入每个站点的根目录,找到anqicms可执行文件,并将其重命名。例如:mv /www/wwwroot/site1/anqicms /www/wwwroot/site1/anqicms_site1 mv /www/wwwroot/site2/anqicms /www/wwwroot/site2/anqicms_site2修改
config.json配置: 确保每个实例的config.json文件中配置了唯一的端口号。例如,site1使用8001,site2使用8002。// /www/wwwroot/site1/config.json { "port": 8001, // ...其他配置 } // /www/wwwroot/site2/config.json { "port": 8002, // ...其他配置 }为每个实例创建独立的
start.sh和stop.sh脚本: 每个实例都应该有自己的start.sh和stop.sh脚本,位于其各自的根目录。这些脚本需要更新BINNAME和BINPATH变量,以匹配新的二进制文件名和路径。start.sh示例(针对anqicms_site1实例):#!/bin/bash ### check and start AnqiCMS for site1 # author fesion # the bin name is anqicms_site1 BINNAME=anqicms_site1 BINPATH=/www/wwwroot/site1 # 更改为当前实例的实际路径 # check the pid if exists using the unique binary name exists=`ps -ef | grep '\<${BINNAME}\>' |grep -v grep |wc -l` echo "$(date +'%Y%m%d %H:%M:%S') ${BINNAME} PID check: $exists" >> ${BINPATH}/check.log echo "PID ${BINNAME} check: $exists" if [ $exists -eq 0 ]; then echo "${BINNAME} NOT running" cd ${BINPATH} && nohup ${BINPATH}/${BINNAME} >> ${BINPATH}/running.log 2>&1 & fistop.sh示例(针对anqicms_site1实例):#!/bin/bash ### stop anqicms for site1 # author fesion # the bin name is anqicms_site1 BINNAME=anqicms_site1 BINPATH=/www/wwwroot/site1 # 更改为当前实例的实际路径 # check the pid if exists using the unique binary name exists=`ps -ef | grep '\<${BINNAME}\>' |grep -v grep |awk '{printf $2}'` echo "$(date +'%Y%m%d %H:%M:%S') ${BINNAME} PID check: $exists" >> ${BINPATH}/check.log echo "PID ${BINNAME} check: $exists" if [ $exists -eq 0 ]; then echo "${BINNAME} NOT running" else echo "${BINNAME} is running" kill -9 $exists echo "${BINNAME} is stop" fi重要提示: 请务必将
BINPATH变量修改为对应实例的实际部署路径,并确保BINNAME与你重命名的二进制文件名称一致。
二、通过 PID 文件进行进程管理(更健壮的运维实践)
虽然上述重命名二进制文件的方法可以有效解决大部分PID冲突问题,但在复杂的运维环境中,依靠PID文件来管理进程是更为健壮和标准化的做法。PID文件存储了特定进程的唯一PID,允许脚本精确地启动、停止和监控单个进程,避免了grep命令可能带来的误判(例如,如果某个不相关的程序恰好包含了anqicms作为其命令行参数的一部分)。
修改步骤如下:
修改
start.sh脚本以创建和使用 PID 文件: 在启动AnQiCMS实例时,其start.sh脚本应该将当前进程的PID写入一个特定的PID文件(例如anqicms.pid)。#!/bin/bash ### check and start AnqiCMS with PID file BINNAME=anqicms # 保持二进制文件名不变,或者使用唯一的名称 BINPATH=/www/wwwroot/site1 # 更改为当前实例的实际路径 PIDFILE=${BINPATH}/anqicms.pid # 定义PID文件路径 # 检查PID文件是否存在并且PID是否活跃 if [ -f "$PIDFILE" ]; then PID=$(cat "$PIDFILE") if ps -p "$PID" > /dev/null; then echo "$(date +'%Y%m%d %H:%M:%S') ${BINNAME} already running with PID ${PID}" >> ${BINPATH}/check.log exit 0 else echo "$(date +'%Y%m%d %H:%M:%S') PID file exists but process not running. Cleaning up stale PID file." >> ${BINPATH}/check.log rm -f "$PIDFILE" fi fi echo "$(date +'%Y%m%d %H:%M:%S') Starting ${BINNAME}..." >> ${BINPATH}/check.log cd ${BINPATH} nohup ${BINPATH}/${BINNAME} >> ${BINPATH}/running.log 2>&1 & echo $! > "$PIDFILE" # 将新启动的进程PID写入PID文件 echo "$(date +'%Y%m%d %H:%M:%S') ${BINNAME} started with PID $(cat "$PIDFILE")" >> ${BINPATH}/check.log修改
stop.sh脚本以读取 PID 文件并终止进程:stop.sh脚本将从对应的PID文件中读取PID,并只终止该PID的进程。 “`bash #!/bin/bashstop AnqiCMS with PID file
BINNAME=anqicms # 保持二进制文件名不变,或者使用唯一的名称 BINPATH=/www/wwwroot/site1 # 更改为当前实例的实际路径 PIDFILE=${BINPATH}/anqicms.pid # 定义PID文件路径
if [ -f “$PIDFILE” ]; then
PID=$(cat "$PIDFILE") if ps -p "$PID" > /dev/null; then echo "$(date +'%Y%m%d %H:%M:%S') Stopping ${BINNAME} with PID ${PID}..." >> ${BINPATH}/check.log kill "$PID" # 发送TERM信号 sleep 5 # 等待进程优雅关闭 if ps -p "$PID" > /dev/null; then echo "$(date +'%Y%m%d %H:%M:%S') ${BINNAME} (PID ${PID}) did not terminate gracefully. Forcing kill." >> ${BINPATH}/check.log kill -9 "$PID" # 强制终止 fi rm -f "$PIDFILE" # 移除PID文件 echo "$(date +'%Y%m%d %H:%M:%S') ${BINNAME} stopped." >> ${BINPATH}/check.log else echo "$(date +'%Y%m%d %H:%M:%S')