
1. 为什么还要折腾 TongHttpServer V6如果你手上有一些偏传统的企业级项目尤其是那种跑在内网、要求国产化适配、又不想引入太重中间件的场景那大概率绕不开东方通这套东西。TongHttpServer后面我统一叫它 THS就是东方通体系里负责静态资源托管和反向代理的那一层V6 是现在比较常见的一个版本。很多人第一次接触它是因为项目里已经装好了只是让你去改个配置、重启一下服务结果一上手发现——这玩意儿的配置逻辑跟 Nginx 完全不是一回事目录结构、启动方式、配置文件命名都有自己的脾气。我写这篇东西的出发点很简单网上关于 THS V6 的资料要么是官方手册那种干巴巴的参数罗列要么是别人随手记的几行命令真正把“配置怎么改、服务怎么起、坑在哪”讲清楚的很少。而实际项目里你需要的恰恰是这些。比如httpserver.conf到底放在哪、rc.local里那行启动脚本为什么开机不生效、THS 和 Redis 到底是不是一回事热搜里居然有人问这个说明确实有人被绕晕了这些问题不解决服务就是起不来。这篇指南适合三类人一是刚接手国产化项目、第一次碰 THS 的运维或开发二是需要把 THS 集成到现有部署流程里的实施人员三是想搞清楚 THS 项目目录结构和启动机制、方便后续排查问题的技术负责人。我会从整体设计思路讲到具体配置项再到启动脚本的写法、常见故障的排查尽量做到你照着做就能跑起来。需要说明的是THS 不同小版本之间细节会有差异我下面讲的是基于 V6 常见发行版的通用实践具体以你手上的安装包为准。2. 先把 THS 的定位和目录结构搞清楚2.1 THS 到底是个什么东西别和 Redis 混为一谈热搜里有个词叫“东方通 和 redis 区别”我猜是有人在搜 THS 的时候被带偏了。这里必须先把概念掰清楚THS 是一个 HTTP 服务器干的是接收 HTTP 请求、返回静态文件、转发动态请求到后端应用服务器这类活本质上和 Nginx、Apache 是同一类东西。而 Redis 是内存键值数据库做缓存、做会话存储、做消息队列用的。两者根本不在一个层面上不存在替代关系。那为什么会被放一起搜我分析有两个原因。一是东方通的产品线里确实有和缓存、消息相关的组件名字里可能带“Redis”字样或者支持 Redis 协议导致搜索时混淆二是很多项目里 THS 和 Redis 是搭配部署的THS 做前端接入Redis 做后端缓存部署文档写在一起新人看的时候就容易懵。所以你只要记住THS 管的是“请求怎么进来、静态文件怎么出去”Redis 管的是“数据怎么快速存取”各司其职。THS 在架构里的典型位置是这样的客户端请求先到 THSTHS 判断是静态资源就直接从本地磁盘返回是动态接口就反向代理到后面的应用服务器比如东方通自己的 TongWeb或者 Tomcat、Spring Boot 内嵌容器。它还能做负载均衡、SSL 卸载、访问日志记录。理解了这个定位后面配置里的Location、ProxyPass这些概念就顺了。2.2 项目目录结构每个文件夹都有它的用处THS V6 安装完之后根目录下一般会有这么几个关键文件夹我按重要程度排一下bin/存放启动、停止脚本比如startserver.sh、stopserver.sh这类。有些版本还会有thsctl之类的控制脚本。这个目录是你操作最频繁的地方。conf/所有配置文件的所在地核心就是httpserver.conf可能还有mime.types、logging.conf等辅助配置。logs/运行日志、访问日志、错误日志都在这。排查问题第一站。webapps/默认的静态资源根目录你部署的前端页面、图片、JS 文件通常放这里或者通过配置指向别的路径。lib/依赖的 jar 包或 so 库一般不用动但排查类加载问题时需要关注。temp/临时文件目录上传大文件或者做缓存时可能会用到。我见过有人把配置文件改完随手放在bin/下面然后重启死活不生效找了半天才发现放错地方了。所以记住一句话配置只认conf/目录启动脚本只认bin/目录这是铁律。另外不同项目可能会自定义目录比如把webapps换成/data/webroot这种一般会在httpserver.conf里通过DocumentRoot之类的指令指定你拿到一个陌生环境时先看配置文件里的路径指向再去对应目录确认。2.3 配置文件的核心结构httpserver.conf 长什么样httpserver.conf是整个 THS 的大脑它的语法风格介于 Nginx 和 Apache 之间用的是“指令 块”的形式。一个典型的配置文件会包含这几块内容第一块是全局指令比如监听端口、运行用户、工作进程数、超时时间。第二块是Server块定义虚拟主机一个 THS 可以监听多个端口、服务多个域名。第三块是Location块定义 URL 路径的匹配规则和处理方式是返回静态文件还是转发给后端。第四块是日志和错误处理相关配置。我拿一个最常见的场景举例前端静态页面放在/data/webroot后端接口/api转发到127.0.0.1:8080。配置大概是这样Server { Listen 80 ServerName localhost DocumentRoot /data/webroot Location / { DirectoryIndex index.html AllowOverride None } Location /api/ { ProxyPass http://127.0.0.1:8080/ ProxySet connectiontimeout5 timeout30 } }这里有几个点要注意。Listen后面跟端口如果要用 80 端口启动用户得有权限否则就得用 root 或者做端口转发。DocumentRoot是静态资源根目录Location /里的DirectoryIndex指定默认首页。Location /api/里的ProxyPass末尾那个斜杠很关键它决定了路径怎么拼接——/api/user会被转成http://127.0.0.1:8080/user如果ProxyPass末尾不加斜杠就会变成http://127.0.0.1:8080/api/user这个差异在实际项目里经常导致 404后面排查章节我会细说。3. 配置文件的细节拆解与实操要点3.1 监听端口与虚拟主机配置端口配置看起来简单但有几个坑。首先是端口占用问题THS 启动时会去 bind 你配置的端口如果这个端口已经被别的进程占了启动日志里会报Address already in use但有些版本报错信息不明显你可能只看到服务没起来。所以启动前养成习惯用netstat -tlnp | grep 端口号或者ss -tlnp确认一下。其次是多端口监听。THS 支持在一个配置文件里写多个Server块每个块监听不同端口。比如你既要跑 HTTP 的 80又要跑 HTTPS 的 443就写两个Server块。但要注意如果两个Server块监听了同一个端口THS 会根据ServerName来做虚拟主机匹配匹配不上的走默认的第一个。这个逻辑和 Nginx 类似但 THS 的匹配优先级规则略有不同它更偏向于精确匹配ServerName通配符匹配的优先级较低。还有一个实际项目中常遇到的场景只允许内网访问。这时候你可以在Listen指令后面加上 IP 地址比如Listen 192.168.1.100:80这样 THS 只绑定这个网卡外部请求进不来。但要注意如果服务器有多张网卡你绑定的 IP 必须真实存在否则启动会失败。3.2 静态资源路径与目录权限DocumentRoot指向的目录THS 进程必须有读权限否则返回 403。我踩过的一个坑是目录权限明明给了755文件也是644但就是 403。后来发现是父目录的权限问题——THS 运行用户对DocumentRoot的每一级父目录都得有执行权限也就是x权限否则进不去。比如/data/webroot如果/data目录对 THS 用户没有x权限照样访问不了。另外THS 对符号链接的处理默认是关闭的也就是说DocumentRoot下面如果有软链接指向外部目录默认不跟随。如果你确实需要得在配置里显式开启FollowSymLinks之类的选项。这个设计是出于安全考虑防止通过软链接访问到系统敏感文件。还有一个细节是中文文件名。THS 默认的字符集编码可能不是 UTF-8导致中文文件名的静态资源访问 404。解决办法是在httpserver.conf的全局块里加上Charset UTF-8或者在Location块里单独指定。这个在国产化环境里特别常见因为很多项目的前端资源文件名带中文。3.3 反向代理与负载均衡配置反向代理是 THS 最常用的功能之一。ProxyPass指令把请求转发到后端ProxySet用来设置连接超时、读取超时这些参数。我重点讲几个容易出问题的地方。第一个是超时时间。默认的连接超时可能只有几秒如果后端接口处理慢就会报 504。这时候要调大connectiontimeout和timeout。但也不能无限大否则后端挂了前端请求会一直挂着用户体验更差。我的经验值是连接超时 5 秒读取超时根据业务定一般 30 到 60 秒特别慢的报表类接口可以放到 120 秒。第二个是负载均衡。THS 支持配置多个后端节点语法大概是Location /api/ { ProxyPass http://backend/ ProxySet lbmethodroundrobin ProxyBackend backend 127.0.0.1:8080 ProxyBackend backend 127.0.0.1:8081 }这里lbmethod指定负载均衡算法常见的有轮询roundrobin、最少连接leastconn、源地址哈希sourcehash。源地址哈希适合有会话保持需求的场景同一个客户端 IP 始终打到同一台后端。但要注意如果前端经过了多层代理THS 拿到的客户端 IP 可能是上一级代理的 IP这时候源地址哈希就失效了需要配合X-Forwarded-For头来取真实 IP。第三个是健康检查。THS 可以配置定期探测后端节点发现挂了就自动摘除。配置项一般叫ProxyHealthCheck之类的可以设置探测间隔、超时、失败次数阈值。这个功能在生产环境很有用但探测频率别设太高否则会给后端带来额外压力我一般设 10 秒一次。3.4 日志配置与日志切割THS 的日志分访问日志和错误日志。访问日志记录每个请求的来源、路径、状态码、耗时错误日志记录启动异常、运行时错误。日志配置一般在httpserver.conf的全局块或者Server块里用AccessLog和ErrorLog指令指定路径和格式。日志格式可以自定义常见的字段包括%h客户端 IP、%t时间、%r请求行、%s状态码、%b响应字节数、%D处理时间微秒。我建议至少保留这几个字段排查性能问题时特别有用。日志切割是个容易被忽略的问题。THS 本身不一定带自动切割功能日志文件会一直增长最后把磁盘撑满。常见的做法是用系统的logrotate来做配置一个每天切割、保留 7 天的策略。但要注意切割后需要通知 THS 重新打开日志文件否则它还在往旧文件句柄里写。有些版本的 THS 支持reopen信号有些不支持不支持的话就只能重启服务所以切割时间最好选在业务低峰期。4. 启动、停止与开机自启的完整实操4.1 手动启动与停止的标准流程THS 的启动脚本在bin/目录下常见的是startserver.sh。启动前我建议先做三件事一是确认配置文件语法没问题有些版本提供-t参数做配置检查类似startserver.sh -t二是确认端口没被占用三是确认日志目录可写。启动命令一般是cd /opt/THS/bin ./startserver.sh启动后别急着走用tail -f ../logs/error.log看一下有没有报错。如果看到Server started之类的字样再用ps -ef | grep ths确认进程在。停止服务用stopserver.sh它会发信号让进程优雅退出。如果停不掉可以用kill -9强杀但强杀可能导致正在处理的请求中断尽量少用。这里有个细节有些版本的启动脚本依赖环境变量比如THS_HOME、JAVA_HOME。如果你直接./startserver.sh报找不到路径就在脚本里或者当前 shell 里先 export 一下。我习惯在脚本开头加一行export THS_HOME/opt/THS这样不管从哪个目录执行都不会出错。4.2 rc.local 开机自启的正确写法rc.local是很多运维同学做开机自启的首选因为它简单直接。但 THS 放在rc.local里经常不生效原因通常有三个。第一个是执行顺序。rc.local在系统启动流程里执行得比较晚但网络可能还没完全就绪。如果 THS 启动时需要绑定某个 IP而那个 IP 还没配置好就会失败。解决办法是在启动命令前加个sleep 10等网络稳定了再起。或者更优雅一点写个循环检测网络状态的脚本。第二个是环境变量。rc.local执行时的环境变量和登录 shell 不一样PATH可能不包含 THS 需要的路径。所以启动命令最好用绝对路径并且在脚本里显式设置THS_HOME和JAVA_HOME。第三个是权限。rc.local默认以 root 执行如果你的 THS 配置了以普通用户运行启动脚本里可能需要su - thsuser -c ...来切换用户。但切换用户后环境变量又变了得在su命令里带上-或者手动 source 环境文件。一个我实测可用的rc.local写法是这样的#!/bin/bash # 开机自启 THS export THS_HOME/opt/THS export JAVA_HOME/usr/lib/jvm/java-1.8.0 sleep 15 su - thsuser -c cd $THS_HOME/bin ./startserver.sh exit 0注意最后那个exit 0rc.local脚本必须以 0 退出否则系统可能认为启动失败。另外su - thsuser里的-表示加载该用户的环境这样THS_HOME这些变量在切换后依然有效前提是写在用户的.bash_profile里。4.3 用 systemd 管理 THS 服务推荐方案rc.local虽然能用但不够优雅也不方便做进程监控。如果系统支持 systemd我更推荐写一个 service 文件。好处是可以配置自动重启、可以看systemctl status、可以统一管理日志。一个典型的 service 文件放在/etc/systemd/system/ths.service[Unit] DescriptionTongHttpServer V6 Afternetwork.target [Service] Typeforking Userthsuser Groupthsuser EnvironmentTHS_HOME/opt/THS EnvironmentJAVA_HOME/usr/lib/jvm/java-1.8.0 ExecStart/opt/THS/bin/startserver.sh ExecStop/opt/THS/bin/stopserver.sh Restarton-failure RestartSec10 [Install] WantedBymulti-user.target这里Typeforking是因为 THS 启动脚本会 fork 出后台进程。Restarton-failure让服务异常退出时自动拉起RestartSec10是重启间隔。配置好后执行systemctl daemon-reload、systemctl enable ths、systemctl start ths就行。要注意的是ExecStart指定的脚本必须是可执行的而且脚本里不能有交互式输入否则 systemd 会卡住。另外如果 THS 启动脚本自己会 daemon 化Typeforking是合适的如果它前台运行就得改成Typesimple。这个得看你手上的脚本具体行为拿不准就先手动跑一遍观察。4.4 启动参数调优与 JVM 相关设置THS V6 底层可能依赖 Java 运行时具体看发行版所以 JVM 参数会影响它的性能。常见的调优参数包括堆内存大小-Xmx、-Xms垃圾回收器选择以及一些网络相关的系统属性。堆内存不是越大越好。THS 主要做 IO 转发堆内存需求不大一般 512M 到 1G 就够了。设太大反而导致 GC 停顿时间长。我一般设-Xms512m -Xmx1g让初始堆和最大堆接近减少动态扩展带来的开销。垃圾回收器方面如果 JDK 版本是 8用-XX:UseG1GC比较稳如果是更高版本默认的 G1 或 ZGC 都可以。但要注意THS 本身可能对某些 GC 参数有要求改之前先看官方文档或者问一下厂商支持。还有一个容易忽略的是文件描述符限制。THS 作为 HTTP 服务器并发连接数高的时候会打开大量文件句柄。系统默认的ulimit -n可能只有 1024不够用。需要在启动脚本里加ulimit -n 65535或者在 systemd 的 service 文件里加LimitNOFILE65535。这个不设置的话高并发时会出现Too many open files错误服务直接不可用。5. 常见问题排查与避坑经验实录5.1 启动失败类问题速查启动失败是最让人头疼的因为往往报错信息不明确。我整理了一个速查表按现象、可能原因、排查方法三个维度来组织现象可能原因排查方法执行启动脚本无任何输出进程也没起来脚本没有执行权限或 THS_HOME 未设置ls -l startserver.sh看权限echo $THS_HOME看变量日志报 Address already in use端口被占用netstat -tlnp | grep 端口找到占用进程日志报 Permission denied端口小于 1024 但非 root 启动或目录无权限换端口或用 root检查目录权限日志报 Cannot allocate memory内存不足或 JVM 堆设置过大free -m看内存调小-Xmx启动后进程很快消失配置文件语法错误或依赖库缺失看 error.log 最后几行检查 lib 目录日志报 Too many open files文件描述符限制太低ulimit -n查看调大限制这个表里的每一条我都实际遇到过。特别是最后一条有一次压测的时候 THS 突然不响应了看日志就是Too many open files当时排查了半天才想到是 ulimit 的问题。后来在启动脚本里加了ulimit -n 65535就再没出现过。5.2 配置不生效的排查思路配置改完重启了但行为没变化这种情况一般是这几个原因第一改错了文件。THS 可能有多份配置文件比如conf/httpserver.conf和conf/httpserver.conf.bak你改的是 bak 那份。或者项目里通过-f参数指定了别的配置文件路径你改的是默认路径的。排查方法启动脚本里看有没有-f参数有的话以那个为准。第二配置块的作用域不对。比如你把ProxyPass写在了全局块里但它只能在Location块里生效。THS 对配置项的作用域有严格限制写错位置可能被忽略而不报错。排查方法对照官方文档确认每个指令的合法作用域。第三缓存问题。有些版本的 THS 会缓存配置文件重启时如果没有完全停掉旧进程新进程可能读到旧配置。排查方法重启后确认进程 PID 变了或者用stopserver.sh停干净再启动。第四虚拟主机匹配问题。你改的Server块没有被匹配到请求走了默认的Server块。排查方法在Server块里加一个独特的响应头或者错误页面看请求返回的是不是这个块的配置。5.3 反向代理 404 与 502 的经典坑反向代理的 404 和 502 是最常见的两个错误我分别说一下。404 通常是路径拼接问题。前面提过ProxyPass末尾斜杠的影响这里再强调一次如果Location是/api/ProxyPass是http://backend/那么/api/user转发后是http://backend/user如果ProxyPass是http://backend末尾无斜杠转发后是http://backend/api/user。后端接口如果没定义/api前缀就会 404。解决办法就是根据后端实际路径调整斜杠。502 通常是后端不可达。可能原因有后端服务没启动、后端端口不对、防火墙拦截、THS 和后端之间的网络不通。排查步骤先在 THS 服务器上用curl http://后端IP:端口/健康检查路径确认后端可达再看 THS 错误日志里的具体报错如果是Connection refused就是后端没起如果是Timeout就是网络或后端处理慢。还有一个隐蔽的 502 原因是后端返回的响应头过大超过了 THS 的缓冲区限制。这种情况日志里可能报upstream sent too big header。解决办法是调大ProxySet里的缓冲区相关参数具体参数名看版本一般是proxybuffersize之类的。5.4 性能问题的定位与调优THS 性能问题一般表现为响应慢、吞吐量上不去、CPU 或内存飙高。定位思路是从外到内逐层排查。先看系统层面top看 CPU 和内存iostat看磁盘 IOnetstat看连接数。如果 CPU 高但吞吐低可能是配置问题导致大量请求排队如果内存持续增长不释放可能是内存泄漏需要看 GC 日志。再看 THS 层面访问日志里的%D字段记录了每个请求的处理时间统计一下 P99 耗时看是普遍慢还是个别慢。错误日志里看有没有频繁的警告或异常。最后看后端层面如果 THS 本身处理很快但整体响应慢那瓶颈在后端。这时候要去看后端应用的日志和监控。调优方面除了前面说的 JVM 和文件描述符还有几个参数值得关注工作进程数一般设为 CPU 核数或核数的 1.5 倍、每个进程的最大连接数、TCP 相关参数如tcp_nodelay、keepalive_timeout。这些参数的具体名称和默认值因版本而异调整前最好做基准测试不要凭感觉改。5.5 我踩过的三个真实坑第一个坑rc.local里启动 THS开机后进程在但端口没监听。查了半天发现是rc.local执行时网络还没就绪THS 绑定 IP 失败但进程没退出。后来加了sleep 15解决。这个坑的教训是开机自启一定要考虑依赖服务的启动顺序。第二个坑改了httpserver.conf里的DocumentRoot重启后静态资源还是 404。原因是新目录的父目录权限不对THS 用户没有执行权限。用namei -l /新目录/路径逐级检查权限才找到问题。这个命令很好用推荐给大家。第三个坑负载均衡配置了两个后端但所有请求都打到同一台。排查发现是用了源地址哈希算法而测试时所有请求都来自同一个客户端 IP。换成轮询算法后正常。这个坑提醒我负载均衡算法要根据实际场景选测试时要注意模拟多客户端。6. 一些让部署更顺手的经验补充6.1 配置文件的版本管理与回滚THS 的配置文件改错了轻则服务异常重则起不来。所以每次改之前我都会先备份一份命名带上日期比如httpserver.conf.20240115。这样出问题可以快速回滚。如果项目用 Git 管理配置那就更好了每次变更都有记录还能 diff 看改了什么。回滚的时候注意不能只回滚配置文件还要考虑配置和当前运行环境是否匹配。比如你回滚了一个月前的配置但那个配置里指向的后端 IP 已经变了回滚后反而出问题。所以回滚前先确认配置内容。6.2 日志监控与告警的最小化方案生产环境不能等用户报障了才去看日志。一个最小化的监控方案是用tail -F配合grep实时过滤错误日志里的关键字比如ERROR、Exception、refused一旦出现就发告警。可以用简单的 shell 脚本实现也可以用现成的日志采集工具。访问日志方面关注两个指标一是状态码分布5xx 突然增多说明后端有问题二是 P99 响应时间持续升高说明有性能瓶颈。这两个指标用awk就能从日志里算出来配合定时任务定期输出到监控系统。6.3 国产化环境下的特殊注意事项国产化环境里操作系统可能是麒麟、统信 UOS 这类CPU 架构可能是 ARM 或龙芯。THS 在这些环境下的表现和 x86 上可能有差异。我遇到过的几个问题一是启动脚本里的某些命令在国产系统上不存在或参数不同比如netstat可能被ss替代二是 JVM 版本可能不是标准的 OpenJDK而是厂商定制的某些参数不支持三是文件路径大小写敏感性与预期不符。应对方法是拿到国产化环境后先手动跑一遍启动流程把每个命令都验证一遍不要假设和 x86 环境一样。遇到报错先查系统文档再查 THS 文档最后找厂商支持。国产化适配急不得一步步来。6.4 从 THS 迁移到其他服务器的思路有些项目后期可能要把 THS 换成 Nginx 或其他服务器。迁移的核心工作是配置翻译。THS 的Server块对应 Nginx 的server块Location对应locationProxyPass对应proxy_passDocumentRoot对应root。大部分概念是相通的但细节有差异比如路径拼接规则、正则匹配语法、变量命名。迁移时建议先搭一个测试环境把配置翻译过去用相同的请求做对比测试确认行为一致后再切生产。切的时候用灰度方式先切一小部分流量观察没问题再全量。这个过程中访问日志的对比分析很有用可以快速发现行为差异。6.5 关于 THS 后续扩展的一些想法THS 本身功能不算特别丰富但胜在稳定、国产化适配好。如果项目有更复杂的需求比如动态限流、灰度发布、WAF 功能THS 原生可能不支持需要在前端再加一层或者在后端应用里实现。我的建议是不要试图让 THS 做它不擅长的事保持它作为 HTTP 接入层的简单和稳定复杂逻辑放到应用层或者专门的网关组件里。如果确实需要在 THS 层面做扩展可以研究一下它是否支持自定义模块或插件。有些版本支持加载自定义的 handler用 Java 或 C 写。但这个属于高级用法一般项目用不到而且会增加维护成本。除非有明确的性能和功能需求否则不建议走这条路。最后分享一个小技巧THS 的配置里注释用#号但有些版本对行内注释支持不好#后面的内容可能被当成配置的一部分。所以注释最好单独占一行不要写在配置项后面。这个细节官方文档不一定写但实际用的时候很容易踩到。