不少团队把 Confluence 当知识库来用,页面用着用着就越来越慢,打开要转圈圈,保存也卡,整个人都麻了。这种慢问题其实有规律可循,我们不需要一上来就拍脑袋,可以照着下面这套思路慢慢捋。

一、先搞清楚“慢”到底慢在哪

1.1 慢是“感觉”还是“数据”

遇到慢问题,先别急着动服务器。问自己几个问题:是所有页面都慢,还是只有某个页面慢?是上午高峰慢,还是半夜也慢?是第一次打开慢,还是刷新也没用?这些问题能把排查范围缩小很多。

比如所有页面都慢,优先怀疑数据库或应用本身;只有某个带报表的页面慢,那可能那个页面查询太重;每天早上慢,可能定时任务刚好在那个时候跑。我有个朋友的公司,上午十点全员反馈 Confluence 几乎打不开,后来发现是定时备份任务和早晨高峰期撞在一起,数据库被长时间锁住。你看,不先看场景直接调内存,问题大概率还会回来。

1.2 三个最可疑的地方

Confluence 是 Java 写的,跑在 Tomcat 上,数据存数据库,附件存文件目录。页面要渲染出来,需要经过“应用线程干活 + 数据库查数据 + 内存分配对象”这几步。所以慢的原因基本集中在三处:

  • 数据库响应慢:比如某条 SQL 没走索引,数据量大时全表扫描,一等就是几秒。
  • Java 堆内存不够:对象太多,垃圾回收频繁,CPU 忙个不停,页面就卡。
  • 应用线程被卡住:比如在等数据库连接、等锁,很多请求堆在一起,看起来就像死机。

这三处分别对应三样东西:慢查询日志、GC 日志、线程堆栈。下面我们一个个看怎么从日志里找出问题。

二、从日志里找线索

2.1 访问日志告诉你哪个页面慢

Confluence 的访问日志记录每个请求的处理时间。打开日志,找到那些耗时超过两秒的请求,看看路径是什么,就能知道用户平时抱怨的“慢”是不是集中在某个功能上。

日志路径一般在 Confluence 的 home 目录下的 logs 文件夹里,文件名类似 access.log,每一行最后的数值就是处理耗时。我们可以用 Python 把耗时排名前十的请求找出来。这里需要注意:同一个 URL 可能被请求很多次,光看单次耗时还不够,要按平均耗时排序。下面是一个简单脚本。

技术栈:Python 3.x

# 技术栈:Python 3.x
import re
from collections import defaultdict

# access.log 模板示例:
# 192.168.1.10 - - [10/May/2024:10:00:01 +0800] "GET /display/MyPage HTTP/1.1" 200 12345 0.456

pattern = re.compile(
    r'"(GET|POST) (\S+) HTTP/[\d.]+" \d+ \d+ (\d+\.\d+)'
)

def rank_slow_requests(access_log, top_n=10):
    # 用字典统计每个 URL 的总耗时和请求次数
    records = defaultdict(list)
    with open(access_log, 'r', encoding='utf-8') as f:
        for line in f:
            match = pattern.search(line)
            if match:
                url = match.group(2)
                cost = float(match.group(3))
                records[url].append(cost)

    avg_costs = []
    for url, costs in records.items():
        avg_cost = sum(costs) / len(costs)   # 平均耗时
        avg_costs.append((url, avg_cost, len(costs)))

    # 按平均耗时从高到低排序
    avg_costs.sort(key=lambda x: x[1], reverse=True)

    for url, avg_cost, count in avg_costs[:top_n]:
        print(f'URL: {url}, 平均耗时: {avg_cost:.3f} 秒, 次数: {count}')

rank_slow_requests('access.log')

这个脚本输出的结果,能帮我们快速定位:是首页慢、搜索慢,还是某个历史版本页面慢。如果慢的请求都打在同一个 URL 上,就可以顺着这个 URL 去查后台对应的查询逻辑。

2.2 GC 日志告诉你内存是不是不够

Java 应用的内存,如果频繁“腾地方”,就会表现成页面时不时卡一下,因为腾地方的时候所有线程都要暂停一下。这个“腾地方”就是垃圾回收,也就是 GC。

GC 日志在 Confluence 的 home 目录 logs 文件夹里,文件名以 atlassian-confluence-gc 开头,里面记录每次垃圾回收的停顿时间。我们用 Python 统计一下停顿的频率和平均耗时,看看内存压力到底大不大。

技术栈:Python 3.x

# 技术栈:Python 3.x
import glob
import re

# 日志里常见的一行:
# 2024-05-10T10:00:01.123+0800: 123.456: [GC (Allocation Failure) 1024M->512M(2048M), 0.2345678 secs]
# 我们只提取最后面的 "xxx secs" 中的秒数
pattern = re.compile(r',\s+(\d+\.\d+)\s+secs\]')

def parse_gc_logs():
    total_pauses = []
    # 匹配 home 目录下所有 GC 日志文件
    for log_path in glob.glob('atlassian-confluence-gc-*.log'):
        with open(log_path, 'r', encoding='utf-8') as f:
            for line in f:
                if 'GC' not in line:
                    continue
                match = pattern.search(line)
                if match:
                    total_pauses.append(float(match.group(1)))

    if not total_pauses:
        print('没有找到 GC 停顿记录,请确认日志路径是否正确')
        return

    avg_pause = sum(total_pauses) / len(total_pauses)
    max_pause = max(total_pauses)
    print(f'共统计到 {len(total_pauses)} 次 GC 停顿')
    print(f'平均停顿 {avg_pause:.3f} 秒')
    print(f'最长停顿 {max_pause:.3f} 秒')

    # 如果平均停顿超过 0.1 秒,说明内存分配压力已经比较高了
    if avg_pause > 0.1:
        print('平均停顿超过 0.1 秒,建议重点排查堆内存设置')

parse_gc_logs()

这里要注意,GC 日志的格式在不同 Java 版本上会有些差别,但思路都一样:看停顿次数和时长。如果 GC 频繁且停顿时间长,可以尝试调大堆内存参数,或者检查是不是有内存泄漏,比如很大的页面一次加载太多数据,长期不释放。

2.3 线程堆栈告诉你线程为什么等

线程卡住的时候,页面就像在“等一个永远不会回来的人”。比如数据库连接池被占满,新请求拿不到连接,线程全部变成等待状态。

我们可以用 Java 自带的 jstack 命令把线程堆栈导出到文件,再用 Python 统计一下线程状态。导出文件的名字可以自己定,比如 thread_stack.txt。

技术栈:Python 3.x

# 技术栈:Python 3.x
from collections import Counter

def count_thread_states(stack_file):
    states = Counter()
    with open(stack_file, 'r', encoding='utf-8') as f:
        for line in f:
            # 线程状态这一行类似:
            # java.lang.Thread.State: WAITING (parking)
            if 'java.lang.Thread.State:' in line:
                # 取冒号后面的部分,再去掉可能存在的括号说明
                state = line.split(':', 1)[1].strip()
                if '(' in state:
                    state = state.split('(')[0].strip()
                states[state] += 1
    return states

states = count_thread_states('thread_stack.txt')
for state, count in states.most_common():
    print(f'{state}: {count}')

如果 WAITING 或 BLOCKED 状态特别多,说明线程挤在一起等资源。这时候再结合堆栈里的类名和行号,能看到具体是等数据库连接,还是等某个锁。如果没有线索,就继续看是哪些类出现的频率高。

三、把三个信息合并成一个简易“体检单”

上面三个脚本是分开看的,其实可以把访问日志、GC 日志、线程堆栈的统计结果汇总起来,形成一个“体检单”。比如访问日志发现某个 URL 慢,GC 日志发现内存压力大,线程堆栈发现一堆线程在等连接池,三个信息拼在一起,基本就能断定是数据库连接池不够用,还是 SQL 本身慢。

下面用一个完整的 Python 示例,把“访问日志分析”和“GC 日志分析”整合成一个函数,方便我们一次跑完。

技术栈:Python 3.x

# 技术栈:Python 3.x
# 这个示例把“访问日志排名”和“GC停顿统计”整合成一个函数

import re
from collections import defaultdict

def analyze_confluence_slow(access_log_path, gc_log_path):
    # ---------- 1. 访问日志分析 ----------
    access_pattern = re.compile(
        r'"(GET|POST) (\S+) HTTP/[\d.]+" \d+ \d+ (\d+\.\d+)'
    )
    url_times = defaultdict(list)
    with open(access_log_path, 'r', encoding='utf-8') as f:
        for line in f:
            m = access_pattern.search(line)
            if m:
                url_times[m.group(2)].append(float(m.group(3)))

    print('=== 访问日志 TOP 3 慢请求 ===')
    sorted_urls = sorted(
        url_times.items(),
        key=lambda item: sum(item[1]) / len(item[1]),
        reverse=True
    )
    for url, times in sorted_urls[:3]:
        avg = sum(times) / len(times)
        print(f'{url} 平均耗时 {avg:.2f} 秒')

    # ---------- 2. GC 日志分析 ----------
    gc_pattern = re.compile(r',\s+(\d+\.\d+)\s+secs\]')
    pauses = []
    with open(gc_log_path, 'r', encoding='utf-8') as f:
        for line in f:
            m = gc_pattern.search(line)
            if m:
                pauses.append(float(m.group(1)))

    if pauses:
        avg_pause = sum(pauses) / len(pauses)
        print('=== GC 停顿统计 ===')
        print(f'共 {len(pauses)} 次,平均 {avg_pause:.3f} 秒')
    else:
        avg_pause = 0

    # 这里还可以继续加入线程堆栈分析,思路类似
    print('=== 提示 ===')
    if sorted_urls and avg_pause > 0.1:
        print('既存在慢请求,又存在频繁 GC,建议先调内存再看数据库')
    elif sorted_urls:
        print('主要问题可能集中在应用逻辑或 SQL 上,可以继续看线程堆栈')

# 调用示例
# analyze_confluence_slow('access.log', 'atlassian-confluence-gc-2024-05-10.log')

这个示例虽然简化了很多,但把排查的框架搭出来了。实际使用中,我们会根据日志格式调整正则,但排查路径是一样的。

四、常见优化手段和注意事项

4.1 从慢查询日志下手

访问日志能看出哪个页面慢,但数据库层也可能藏着大问题。MySQL 的慢查询日志能记录执行时间超过阈值的 SQL。我们用 Python 统计一下哪些 SQL 出现次数最多,这样就能知道到底是谁在拖后腿。

技术栈:Python 3.x

# 技术栈:Python 3.x
# 统计 MySQL 慢查询日志中,出现次数最多的 SQL

from collections import Counter

def top_slow_sql(slow_log_path, top_n=5):
    counter = Counter()
    current_sql = []

    with open(slow_log_path, 'r', encoding='utf-8') as f:
        for line in f:
            # 跳过注释行
            if line.startswith('#'):
                continue
            # 去掉首尾空白,并累积到当前 SQL
            current_sql.append(line.strip())
            # 一条 SQL 以分号结束
            if line.rstrip().endswith(';'):
                sql = ' '.join(current_sql).replace('\n', ' ')
                # 截取前 200 个字符,防止统计时被长 SQL 刷屏
                counter[sql[:200]] += 1
                current_sql = []

    for sql, count in counter.most_common(top_n):
        print(f'{count} 次 -> {sql}')

top_slow_sql('mysql-slow.log')

拿到慢 SQL 之后,可以打开执行计划看看有没有走索引。常见做法是给 WHERE 条件涉及的字段加索引,但要注意不要加太多,否则写入会变慢。索引不是越多越好,适合就好。

4.2 JVM 内存怎么调

前面 GC 停顿多,最直接的调整是修改 Confluence 的 JVM 堆大小。配置文件在 Confluence 安装目录的 bin 文件夹下,名字叫 setenv.sh,里面有一段 CATALINA_OPTS。想从 2G 调到 4G,可以用文本替换工具。但提醒一句,修改前先备份。

下面用 Python 演示自动替换配置的过程,重点在说明思路,实际操作时可以直接编辑文件。

技术栈:Python 3.x

# 技术栈:Python 3.x
# 演示如何修改 setenv.sh 中的 -Xmx 内存参数

import shutil

config_path = 'setenv.sh'

# 先备份,避免改错了回不去
shutil.copyfile(config_path, config_path + '.bak')

with open(config_path, 'r', encoding='utf-8') as f:
    content = f.read()

old_param = '-Xmx2048m'
new_param = '-Xmx4096m'

if old_param in content:
    content = content.replace(old_param, new_param)
    with open(config_path, 'w', encoding='utf-8') as f:
        f.write(content)
    print(f'已将 {old_param} 替换为 {new_param}')
    print('请重启 Confluence 后观察 GC 日志')
else:
    print('未找到目标参数,请检查实际配置')

需要注意的是,-Xmx 不是越大越好。如果服务器总共 16G 内存,Confluence 占 8G,数据库和操作系统就可能不够用了,反而把整个机器拖垮。一般来说,堆内存不要超过系统物理内存的一半,具体多少还要看其他服务的占用情况。

4.3 首页和页面内容别太“胖”

很多慢问题不是基础设施不行,而是页面内容太重。首页塞了几十个宏、十几张大图,每次有人打开都要生成一遍,服务器再快也扛不住。建议把首页改成静态内容,把动态列表改成定时生成的快照。

另外,历史页面里的附件版本过多也会拖慢浏览,可以定期清理无用的旧附件。这些优化效果往往比调内存更立竿见影,而且操作起来没有风险。

4.4 注意事项

  • 配置改动前一定备份,尤其是数据库和安装目录。
  • 观察日志时,要选高峰时段的数据,别拿深夜的数据代表整体情况。
  • 如果使用了自签名证书或代理服务器,还要检查 SSL 握手和反向代理的超时设置。
  • 不要同时改多个参数。一次只改一个,否则出了新问题都不知道是哪一步造成的。

五、总结

Confluence 页面慢,通常不是单个原因,而是几个原因叠加。先通过访问日志找到慢页面,再用 GC 日志判断内存压力,最后用线程堆栈看有没有线程在等待。三个信息放在一起,慢在哪一层就清楚了。优化时优先做成本低、见效快的动作:加索引、调堆内存、精简首页。改完一个变量,再回到日志里去看效果,反复几次,页面自然就快了。

排查慢问题最怕乱试,记住“量化优先、小步调整、持续观察”这十二个字,慢问题总能被一点一点磨掉。