perf: optimize system concurrency, I/O stability and fix WebSocket disconnects

This commit is contained in:
2026-04-07 13:58:49 +08:00
parent 62c7341cf6
commit 11849208ed
21 changed files with 357 additions and 215 deletions

164
deploy/install-production.sh Normal file → Executable file
View File

@@ -166,7 +166,7 @@ KillMode=mixed
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full
ProtectHome=true
ProtectHome=false
LimitNOFILE=65535
TasksMax=4096
MemoryMax=${memory_max}
@@ -477,7 +477,17 @@ main() {
SERVICE_GROUP="${SERVICE_GROUP:-$(ask_required 'systemd 运行用户组' "$(id -gn)")}"
DOMAIN="${DOMAIN:-$(ask_required '部署域名(可填写 IP 或 localhost' 'localhost')}"
# 自动尝试获取公网 IP 作为默认域名值
local detected_ip=""
if [[ -z "${DOMAIN:-}" ]]; then
log "正在尝试自动获取公网 IP..."
detected_ip=$(curl -s --connect-timeout 5 https://ifconfig.me || curl -s --connect-timeout 5 https://api.ipify.org || echo "")
if [[ -n "${detected_ip}" ]]; then
log "自动检测到公网 IP: ${detected_ip}"
fi
fi
DOMAIN="${DOMAIN:-$(ask_required '部署域名(可填写 IP 或 localhost' "${detected_ip:-localhost}")}"
validate_domain_like "${DOMAIN}" || warn "域名/IP 形态看起来不标准,请再次确认: ${DOMAIN}"
ENV_FILE="${ENV_FILE:-$(ask_required '环境变量文件路径' '/etc/bigtime/bigtime.env')}"
@@ -486,53 +496,65 @@ main() {
PYTHON_BIN="${PYTHON_BIN:-$(ask 'Python 可执行文件路径' "${APP_DIR}/.venv/bin/python")}"
[[ -n "${PYTHON_BIN}" ]] || fail "Python 路径不能为空"
echo ""
echo -e "${CYAN}运行参数${NC}"
TICKERS="${TICKERS:-$(ask '默认股票池(逗号分隔)' 'AAPL,MSFT,GOOGL,AMZN,NVDA,META,TSLA,AMD,NFLX,AVGO,PLTR,COIN')}"
FIN_DATA_SOURCE="${FIN_DATA_SOURCE:-$(ask '行情数据源finnhub/yfinance/financial_datasets' 'finnhub')}"
MODEL_NAME="${MODEL_NAME:-$(ask '默认模型名' 'qwen3-max')}"
MAX_COMM_CYCLES="${MAX_COMM_CYCLES:-$(ask_required '最大讨论轮数' '2')}"
validate_numeric "${MAX_COMM_CYCLES}" || fail "最大讨论轮数必须是数字: ${MAX_COMM_CYCLES}"
MARGIN_REQUIREMENT="${MARGIN_REQUIREMENT:-$(ask_required '保证金比例' '0.5')}"
validate_numeric "${MARGIN_REQUIREMENT}" || fail "保证金比例必须是数字: ${MARGIN_REQUIREMENT}"
echo ""
echo -e "${CYAN}密钥配置${NC}"
FINANCIAL_DATASETS_API_KEY="${FINANCIAL_DATASETS_API_KEY:-$(ask 'FINANCIAL_DATASETS_API_KEY可留空' '')}"
FINNHUB_API_KEY="${FINNHUB_API_KEY:-$(ask 'FINNHUB_API_KEYlive 模式建议填写)' '')}"
POLYGON_API_KEY="${POLYGON_API_KEY:-$(ask 'POLYGON_API_KEY可留空' '')}"
OPENAI_API_KEY="${OPENAI_API_KEY:-$(ask 'OPENAI_API_KEY可留空' '')}"
OPENAI_BASE_URL="${OPENAI_BASE_URL:-$(ask 'OPENAI_BASE_URL可留空' '')}"
DASHSCOPE_API_KEY="${DASHSCOPE_API_KEY:-$(ask 'DASHSCOPE_API_KEY可留空' '')}"
MEMORY_API_KEY="${MEMORY_API_KEY:-$(ask 'MEMORY_API_KEY可留空' '')}"
if [[ "${FIN_DATA_SOURCE}" == "finnhub" && -z "${FINNHUB_API_KEY}" ]]; then
warn "你选择了 finnhub 作为数据源,但 FINNHUB_API_KEY 为空。live 模式下通常会失败。"
fi
if [[ -z "${OPENAI_API_KEY}" && -z "${DASHSCOPE_API_KEY}" ]]; then
warn "OPENAI_API_KEY 和 DASHSCOPE_API_KEY 都为空,模型调用可能无法工作。"
local SKIP_ENV_CONFIG=false
if [[ -f "${ENV_FILE}" ]]; then
echo ""
if confirm "检测到环境变量文件 ${ENV_FILE} 已存在,是否跳过详细参数配置并保留现有文件?" "Y"; then
SKIP_ENV_CONFIG=true
fi
fi
if confirm "使用 Docker 沙盒执行技能?" "N" "${AUTO_USE_DOCKER}"; then
SKILL_SANDBOX_MODE="docker"
if ! ${SKIP_ENV_CONFIG}; then
echo ""
echo -e "${CYAN}运行参数${NC}"
TICKERS="${TICKERS:-$(ask '默认股票池(逗号分隔)' 'AAPL,MSFT,GOOGL,AMZN,NVDA,META,TSLA,AMD,NFLX,AVGO,PLTR,COIN')}"
FIN_DATA_SOURCE="${FIN_DATA_SOURCE:-$(ask '行情数据源finnhub/yfinance/financial_datasets' 'finnhub')}"
MODEL_NAME="${MODEL_NAME:-$(ask '默认模型名' 'qwen3-max')}"
MAX_COMM_CYCLES="${MAX_COMM_CYCLES:-$(ask_required '最大讨论轮数' '2')}"
validate_numeric "${MAX_COMM_CYCLES}" || fail "最大讨论轮数必须是数字: ${MAX_COMM_CYCLES}"
MARGIN_REQUIREMENT="${MARGIN_REQUIREMENT:-$(ask_required '保证金比例' '0.5')}"
validate_numeric "${MARGIN_REQUIREMENT}" || fail "保证金比例必须是数字: ${MARGIN_REQUIREMENT}"
echo ""
echo -e "${CYAN}密钥配置${NC}"
FINANCIAL_DATASETS_API_KEY="${FINANCIAL_DATASETS_API_KEY:-$(ask 'FINANCIAL_DATASETS_API_KEY可留空' '')}"
FINNHUB_API_KEY="${FINNHUB_API_KEY:-$(ask 'FINNHUB_API_KEYlive 模式建议填写)' '')}"
POLYGON_API_KEY="${POLYGON_API_KEY:-$(ask 'POLYGON_API_KEY可留空' '')}"
OPENAI_API_KEY="${OPENAI_API_KEY:-$(ask 'OPENAI_API_KEY可留空' '')}"
OPENAI_BASE_URL="${OPENAI_BASE_URL:-$(ask 'OPENAI_BASE_URL可留空' '')}"
DASHSCOPE_API_KEY="${DASHSCOPE_API_KEY:-$(ask 'DASHSCOPE_API_KEY可留空' '')}"
MEMORY_API_KEY="${MEMORY_API_KEY:-$(ask 'MEMORY_API_KEY可留空' '')}"
if [[ "${FIN_DATA_SOURCE}" == "finnhub" && -z "${FINNHUB_API_KEY}" ]]; then
warn "你选择了 finnhub 作为数据源,但 FINNHUB_API_KEY 为空。live 模式下通常会失败。"
fi
if [[ -z "${OPENAI_API_KEY}" && -z "${DASHSCOPE_API_KEY}" ]]; then
warn "OPENAI_API_KEY 和 DASHSCOPE_API_KEY 都为空,模型调用可能无法工作。"
fi
if confirm "使用 Docker 沙盒执行技能?" "N" "${AUTO_USE_DOCKER}"; then
SKILL_SANDBOX_MODE="docker"
else
SKILL_SANDBOX_MODE="none"
fi
echo ""
echo -e "${CYAN}当前部署摘要${NC}"
echo " 应用目录: ${APP_DIR}"
echo " 运行用户: ${SERVICE_USER}:${SERVICE_GROUP}"
echo " 域名: ${DOMAIN}"
echo " 环境文件: ${ENV_FILE}"
echo " Python: ${PYTHON_BIN}"
echo " 数据源: ${FIN_DATA_SOURCE:-}"
echo " 模型: ${MODEL_NAME:-}"
echo " 沙盒模式: ${SKILL_SANDBOX_MODE:-none}"
echo ""
if ! confirm "确认以上配置并继续写入系统文件?" "Y"; then
fail "用户取消部署。"
fi
else
SKILL_SANDBOX_MODE="none"
fi
echo ""
echo -e "${CYAN}当前部署摘要${NC}"
echo " 应用目录: ${APP_DIR}"
echo " 运行用户: ${SERVICE_USER}:${SERVICE_GROUP}"
echo " 域名: ${DOMAIN}"
echo " 环境文件: ${ENV_FILE}"
echo " Python: ${PYTHON_BIN}"
echo " 数据源: ${FIN_DATA_SOURCE}"
echo " 模型: ${MODEL_NAME}"
echo " 沙盒模式: ${SKILL_SANDBOX_MODE}"
echo ""
if ! confirm "确认以上配置并继续写入系统文件?" "Y"; then
fail "用户取消部署。"
echo -e "${GREEN}将使用现有的环境文件,跳过详细参数配置。${NC}"
fi
if [[ ! -x "${PYTHON_BIN}" ]]; then
@@ -546,10 +568,12 @@ main() {
"${PYTHON_BIN}" -m pip install -e "${APP_DIR}"
log "构建前端"
(cd "${APP_DIR}/frontend" && npm ci && npm run build)
(cd "${APP_DIR}/frontend" && npm install && npm run build)
log "写入环境变量文件 ${ENV_FILE}"
write_env_file
if ! ${SKIP_ENV_CONFIG}; then
log "写入环境变量文件 ${ENV_FILE}"
write_env_file
fi
if confirm "生成并安装 systemd unit" "Y" "${AUTO_INSTALL_SYSTEMD}"; then
render_systemd_unit "Agent Service" "backend.apps.agent_service:app" "8000" "1" "1024M" "/etc/systemd/system/bigtime-agent.service"
@@ -568,20 +592,50 @@ main() {
if confirm "生成并安装 nginx 配置?" "Y" "${AUTO_INSTALL_NGINX}"; then
local use_tls="no"
if confirm "使用 HTTPS/Let's Encrypt 证书路径?" "N" "${AUTO_USE_TLS}"; then
use_tls="yes"
SSL_CERT_PATH="${SSL_CERT_PATH:-$(ask_required 'SSL 证书 fullchain.pem 路径' "/etc/letsencrypt/live/${DOMAIN}/fullchain.pem")}"
SSL_KEY_PATH="${SSL_KEY_PATH:-$(ask_required 'SSL 私钥 privkey.pem 路径' "/etc/letsencrypt/live/${DOMAIN}/privkey.pem")}"
[[ -f "${SSL_CERT_PATH}" ]] || warn "证书文件当前不存在: ${SSL_CERT_PATH}"
[[ -f "${SSL_KEY_PATH}" ]] || warn "私钥文件当前不存在: ${SSL_KEY_PATH}"
local ssl_err=0
[[ -f "${SSL_CERT_PATH}" ]] || { warn "SSL 证书文件不存在: ${SSL_CERT_PATH}"; ssl_err=1; }
[[ -f "${SSL_KEY_PATH}" ]] || { warn "SSL 私钥文件不存在: ${SSL_KEY_PATH}"; ssl_err=1; }
[[ -f "/etc/letsencrypt/options-ssl-nginx.conf" ]] || { warn "缺失 /etc/letsencrypt/options-ssl-nginx.conf请检查 certbot 配置"; ssl_err=1; }
[[ -f "/etc/letsencrypt/ssl-dhparams.pem" ]] || { warn "缺失 /etc/letsencrypt/ssl-dhparams.pem请检查 certbot 配置"; ssl_err=1; }
if [[ ${ssl_err} -eq 0 ]]; then
use_tls="yes"
else
warn "由于 SSL 关键文件缺失,将回退至 HTTP 模式,以确保 Nginx 能通过配置检查。"
use_tls="no"
fi
else
SSL_CERT_PATH=""
SSL_KEY_PATH=""
fi
NGINX_TARGET="/etc/nginx/conf.d/bigtime.conf"
render_nginx_conf "${NGINX_TARGET}" "${use_tls}"
if confirm "立即执行 nginx -t 并 reload" "Y" "${AUTO_RELOAD_NGINX}"; then
sudo nginx -t
sudo systemctl reload nginx
if confirm "立即执行 nginx -t 并生效配置" "Y" "${AUTO_RELOAD_NGINX}"; then
log "正在验证 Nginx 配置..."
if ! sudo nginx -t; then
fail "Nginx 配置检查失败请根据上方报错信息调整。常见的错误包括80/443 端口被占用,或 server_name 冲突。"
fi
if systemctl is-active --quiet nginx; then
log "Nginx 正在运行,执行 reload..."
sudo systemctl reload nginx
else
log "Nginx 未运行,尝试启动..."
sudo systemctl enable --now nginx
fi
# 关键修复:确保 nginx 用户对 /root 路径有 x 权限
if [[ "${APP_DIR}" == /root/* ]]; then
log "检测到应用部署在 /root 下,正在修复父目录访问权限..."
sudo chmod o+x /root 2>/dev/null || true
sudo chmod o+x "$(dirname "${APP_DIR}")" 2>/dev/null || true
sudo chmod -R o+rX "${APP_DIR}"
fi
log "Nginx 配置已生效。"
fi
fi