网站访问耗时优化 - 从数十秒到几百毫秒的“零成本”优化过程

网站访问耗时优化 - 从数十秒到几百毫秒的"零成本"优化过程 - 阿里云子域GeoDNS国内外分流

  • Website Latency Optimization: A Zero-Cost Journey from Tens of Seconds to Hundreds of Milliseconds
  • From 30 Seconds to 300 Milliseconds: Zero-Cost Website Performance Optimization

背景

之前为了保护我那台孱弱的小服务器的真实ip免受攻击,我将服务器的所有HTTP(s)服务限制为仅能通过域名访问,且把域名托管在了Cloudflare上开启了代理小云朵。

就这样几年过去了,越来越觉得网站访问是真的慢(不管有没有特殊网络都很慢)。并且有👇,所以决定优化一手。

CloudFlare: 网站性能如何影响转化率

沃尔玛发现,页面加载时间每减少 1 秒,转化数就会增加 2%...

最终,从每次访问"快则好几秒平均十来秒慢则好几十秒"到"粗略统计全球访问平均几百毫秒"的性能优化,并且几乎没有修改代码。(理想状态平均几十毫秒)

耗时原因分析

众所周知,正常情况下锅内外的网络传输都要不可避免地经过TheGreatWall,来保障国民的网络信息安全。

为什么CDN服务器遍布全球的赛博菩萨Cloudflare在锅内会变成减速CDN呢?大概率是经过了上面的它↑。

为了实锤一下,在服务器上执行命令

bash 复制代码
for i in {1..20}; do curl -o /dev/null -s -w "%{time_connect} %{time_starttransfer} %{time_total}\n" https://nextcloud.letmefly.xyz; done

得到结果20次请求的连接耗时、首字节返回耗时、总耗时:

复制代码
0.726453 4.388203 4.388237
1.346249 7.263477 7.263509
2.228933 10.111719 10.111752
2.660601 9.483325 9.483393
1.028620 3.424524 3.424555
0.795678 3.197058 3.197130
1.114911 3.688075 3.688237
0.697664 5.477339 5.477411
0.821841 26.322082 26.322112
0.916166 3.698248 3.698305
1.404712 4.999771 4.999802
0.772932 3.323453 3.323484
2.204168 5.768386 5.768476
1.264734 3.941814 3.941846
0.788361 6.344063 6.344097
3.425116 0.000000 72.185861
2.303902 9.694897 9.695196
0.919144 6.506560 6.506636
0.778423 4.272682 4.272722
0.527677 8.258768 8.258819

第二列和第三列几乎相同,说明服务端数据处理非常迅速,耗时几乎为0,而整个网络开销几乎均耗时在网络传输上(服务器--cloudflare--服务器),然后就有了第三列这灾难级别的耗时。

Geo-DNS分流

既然知道了网络开销的巨大头主要来自HTTP的跨境行为,所以让国内用户访问我的国内服务器,国外用户访问国外Github Pages(纯静态页面)不就好了么,具体访问的哪个IP对用户是无感的,且一个域名就能实现。

由于主域名letmefly.xyz托管在cloudflare上且仍有其用,而cloudflare的GeoDNS成本大约每个月几十美元(未详细统计确认),比小服务器贵很多,所以决定将blog.letmefly.xyz子域托管到免费的阿里云DNS服务上。(腾讯云DNS也有类似免费服务,但由于该域名购买自阿里云,所以选择了托管在阿里云上)

blog.letmefly.xyz子域托管

在cloudflare控制面板修改blog.letmefly.xyz的DNS记录为NS,值为ns1.alidns.com,再新建一个NS值为ns2.alidns.com。

在阿里云DNS解析控制台添加域名blog.letmefly.xyz,进行TXT校验(在cloudflare添加alidnscheck.letmefly.xyz的TXT类型的记录)。

添加记录:

主机记录 记录类型 解析请求来源 记录值
@ AAAAA 境外 2606:50c:0:8003::153
@ AAAAA 境外 2606:50c:0:8002::153
@ AAAAA 境外 2606:50c:0:8001::153
@ AAAAA 境外 2606:50c:0:8000::153
@ A 境外 185.199.111.153
@ A 境外 185.199.110.153
@ A 境外 185.199.109.153
@ A 境外 185.199.108.153
@ A 中国地区 39.105.42.186

前面8个是Github(官方文档),最后一个是我的服务器。

是的,就这么简单,完事了!

服务器拉取Github脚本

服务器上设计为:启动一个webhook listener,当github仓库website分支更新时向服务器发送通知,服务器拉取最新源码,并原子化链接整个目录到nginx配置的目录。

go 复制代码
/*
 * @LastEditTime: 2026-01-08 18:54:37
 */
package main

import (
    "crypto/hmac"
    "crypto/rand"
    "crypto/sha256"
    "encoding/hex"
    "encoding/json"
    "io"
    "log"
    "math/big"
    "net/http"
    "os"
    "os/exec"
    "path/filepath"
    "regexp"
    "strings"
    "time"
)

const (
    listenAddr = "127.0.0.1:1209"
    secretFile = ".secret"
)

type RepoConfig struct {
    BaseDir   string
    RepoURL   string
    TargetRef string
    Branch    string
}

var repos = map[string]RepoConfig{
    "/hook/github/leetcode": {
        BaseDir:   "/www/blog",
        RepoURL:   "git@github.com:LetMeFly666/LeetCode.git",
        TargetRef: "refs/heads/website",
        Branch:    "website",
    },
    "/hook/github/various": {
        BaseDir:   "/www/various",
        RepoURL:   "git@github.com:LetMeFly666/various.git",
        TargetRef: "refs/heads/master",
        Branch:    "master",
    },
}

type PushEvent struct {
    Ref string `json:"ref"`
}

var secret []byte

func loadSecret() {
    data, err := os.ReadFile(secretFile)
    if err != nil {
        log.Fatal("read secret failed:", err)
    }
    secret = []byte(strings.TrimSpace(string(data)))
}

func verifySignature(body []byte, signature string) bool {
    mac := hmac.New(sha256.New, secret)
    mac.Write(body)
    expected := "sha256=" + hex.EncodeToString(mac.Sum(nil))
    return hmac.Equal([]byte(signature), []byte(expected))
}

func handler(cfg RepoConfig) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        body, _ := io.ReadAll(r.Body)

        sig := r.Header.Get("X-Hub-Signature-256")
        if !verifySignature(body, sig) {
            http.Error(w, "invalid signature", http.StatusForbidden)
            return
        }

        var event PushEvent
        if err := json.Unmarshal(body, &event); err != nil {
            http.Error(w, "bad payload", http.StatusBadRequest)
            return
        }

        if event.Ref != cfg.TargetRef {
            w.Write([]byte("ignored\n"))
            return
        }

        w.Write([]byte("accepted\n"))

        go deployWebsite(cfg)
    }
}

func randomSuffix() string {
    n, _ := rand.Int(rand.Reader, big.NewInt(1_000_000))
    return n.String()
}

func deployWebsite(cfg RepoConfig) {
    ts := time.Now().Format("20060102_150405")
    newDir := filepath.Join(cfg.BaseDir, "website_"+ts+"_"+randomSuffix())
    currentLink := filepath.Join(cfg.BaseDir, "website")

    if err := os.MkdirAll(newDir, 0755); err != nil {
        log.Println("mkdir failed:", err)
        return
    }

    cmd := exec.Command(
        "git", "clone",
        "-b", cfg.Branch,
        "--depth=1",
        cfg.RepoURL,
        newDir,
    )
    if out, err := cmd.CombinedOutput(); err != nil {
        log.Println("git clone failed:", string(out))
        return
    }

    cmd = exec.Command("ln", "-sfn", newDir, currentLink)
    if out, err := cmd.CombinedOutput(); err != nil {
        log.Println("ln -sfn failed:", string(out))
        return
    }

    cleanupOldReleases(cfg.BaseDir, newDir)

    log.Println("deploy success:", newDir)
}

var releaseRegexp = regexp.MustCompile(`^website_\d{8}_\d{6}_\d+$`)

func cleanupOldReleases(baseDir, keepDir string) {
    entries, err := os.ReadDir(baseDir)
    if err != nil {
        return
    }

    for _, e := range entries {
        if !e.IsDir() {
            continue
        }
        name := e.Name()
        if !releaseRegexp.MatchString(name) {
            continue
        }
        full := filepath.Join(baseDir, name)
        if full == keepDir {
            continue
        }
        _ = os.RemoveAll(full)
    }
}

func main() {
    loadSecret()

    for path, cfg := range repos {
        http.HandleFunc(path, handler(cfg))
    }

    log.Println("listening on", listenAddr)
    log.Fatal(http.ListenAndServe(listenAddr, nil))
}

在github对应仓库--settings--webhook--新增--输入密钥(随机字符串,可由openssl rand -hex 32命令生成)--类型选择json--不需要额外数据。

本地在程序执行目录创建一个.secret文件并写入生成的密钥,后台运行该监听脚本即可。(上面方法俺设计的,嘻嘻)

结果

国内

在服务器上执行for i in {1..20}; do curl -o /dev/null -s -w "%{time_connect} %{time_starttransfer} %{time_total}\n" https://nextcloud.letmefly.xyz; done,结果:

复制代码
0.007227 0.079349 0.079377
0.005464 0.081657 0.081687
0.005054 0.074545 0.074576
0.456098 0.524839 0.524884
0.469940 0.541030 0.541060
0.006224 0.081239 0.081265
0.005814 0.080448 0.080479
0.006077 0.077889 0.077928
0.005238 0.079339 0.079365
0.004949 0.076759 0.076791
0.005104 0.079881 0.079907
0.005950 0.082108 0.082139
0.006417 0.082243 0.082295
0.005584 0.081760 0.081788
0.455143 0.551400 0.551427
0.005367 0.084855 0.084886
0.455233 0.535178 0.535216
0.005935 0.084052 0.084079
0.004623 0.101036 0.101117
0.005021 0.082109 0.082138

这个应该是直接走Localhost了?。

强制走公网:

bash 复制代码
for i in {1..20}; do curl -o /dev/null -s --resolve nextcloud.letmefly.xyz:443:39.105.42.186 -w "%{time_connect} %{time_starttransfer} %{time_total}\n" https://nextcloud.letmefly.xyz; done
tcpdump -i eth0 host 39.105.42.186 and port 443

并另外启动一个窗口抓包:

bash 复制代码
tcpdump -i eth0 host 39.105.42.186 and port 443

可以看到:

bash 复制代码
tisfy.https → 39.105.42.186.38608
39.105.42.186.38608 → tisfy.https

说明走了公网。

诶,还是很快。

直接使用一台手中的设备测试,不在服务器上测试了

复制代码
0.030351 0.122197 0.122252
0.012166 0.094488 0.094556
0.013372 0.118933 0.119127
0.015152 0.121100 0.121195
0.012782 0.093575 0.093694
0.011981 0.090665 0.090767
0.012699 0.092128 0.092318
0.014691 0.167777 0.167850
0.013061 0.207214 0.207498
0.013980 0.273402 0.273721
0.014736 0.186312 0.186655
0.015405 0.170255 0.170587
0.075688 0.263535 0.263765
0.014979 0.154608 0.154884
0.014043 0.276435 0.276624
0.014873 0.180383 0.180700
0.014767 0.260764 0.260981
0.015220 0.166855 0.167178
0.013348 0.176619 0.176928
0.014471 0.171316 0.171636

这下没办法了,走的wifi连的服务器,还是很快。

国外

由于没有在国外的设备,所以临时写了个github action测下

yml 复制代码
name: Test blog.letmefly.xyz latency - 境外访问延迟测试

on:
   workflow_dispatch:

jobs:
  latency-test:
    runs-on: ubuntu-latest

    steps:
      - name: Print debug info
        run: |
          echo "==== Debug Info ===="
          echo "Runner hostname: $(hostname)"
          echo "GitHub runner public IP:"
          curl -s https://ifconfig.me || echo "Failed to get public IP"
          echo "==================="

      - name: Resolve target IP
        id: resolve
        run: |
          TARGET=blog.letmefly.xyz
          IP=$(dig +short $TARGET | head -n1)
          echo "Target: $TARGET"
          echo "Resolved IP: $IP"
          echo "IP=$IP" >> $GITHUB_OUTPUT

      - name: Test latency 20 times
        run: |
          TARGET=blog.letmefly.xyz
          IP=${{ steps.resolve.outputs.IP }}
          echo "time_connect time_starttransfer time_total"
          
          # 初始化累加器
          sum_connect=0
          sum_starttransfer=0
          sum_total=0
          
          for i in {1..20}; do
            # 取三个时间
            read connect starttransfer total <<< $(curl -o /dev/null -s \
                 --resolve $TARGET:443:$IP \
                 -w "%{time_connect} %{time_starttransfer} %{time_total}" \
                 https://$TARGET)
                 
            # 累加
            sum_connect=$(echo "$sum_connect + $connect" | bc)
            sum_starttransfer=$(echo "$sum_starttransfer + $starttransfer" | bc)
            sum_total=$(echo "$sum_total + $total" | bc)
            
            # 输出
            echo "$connect $starttransfer $total"
          done
          
          # 计算平均
          avg_connect=$(echo "scale=4; $sum_connect/20" | bc)
          avg_starttransfer=$(echo "scale=4; $sum_starttransfer/20" | bc)
          avg_total=$(echo "scale=4; $sum_total/20" | bc)
          
          echo "AVERAGE $avg_connect $avg_starttransfer $avg_total"

Github上测Github Pages似乎有点小作弊(bushi),但没办法了。

复制代码
Runner hostname: runnervmi13qx
GitHub runner public IP:
172.212.165.69

Target: blog.letmefly.xyz
Resolved IP: 185.199.110.153

time_connect time_starttransfer time_total
0.013474 0.096664 0.101290
0.011528 0.091032 0.095257
0.012259 0.053301 0.058006
0.011481 0.049095 0.053492
0.009328 0.046950 0.050534
0.008894 0.042994 0.047134
0.013758 0.056136 0.061113
0.011655 0.059527 0.063557
0.014356 0.054987 0.060360
0.011576 0.049023 0.053552
0.011710 0.049211 0.053547
0.011660 0.048587 0.052985
0.011670 0.057137 0.061753
0.008896 0.045748 0.049883
0.014311 0.053899 0.059477
0.013615 0.053529 0.059438
0.011551 0.049421 0.053476
0.013896 0.060151 0.065085
0.014129 0.090222 0.095611
0.013318 0.052731 0.057438
AVERAGE .0121 .0580 .0626

国内外

也可以在itdog上测试国内外访问,网络质量及其不确定因素会增多,耗时结果稍高。

但HTTP请求只要不经过墙墙,平均还是比较快的。

国内外访问良民网站的DNS服务,也很快(并且还有缓存);国外访问Github(Anycast)本来就很快。

End

The Real End, Thanks! 同步发文于CSDN和我的个人博客,原创不易,转载经作者同意后请附上原文链接哦~

千篇源码题解已开源

相关推荐
不是万能的豆学长14 小时前
filetidy 文件整理助手:一键归类 + 规范重命名(Python 零依赖单文件工具)
大数据·开发语言·python·技术美术
小白跃升坊16 小时前
PHP2Shell:AI 两天挖出 9.2 分漏洞,让全球 70% 网站连夜打补丁!1Panel 已经把修复版本封装好,推到了你的应用商店里。
php·开源安全·1panel·ai安全·漏洞披露
懂点Tech19 小时前
ctfshow MISC入门图片篇(信息附加):misc41解题思路
开发语言·python
Json____19 小时前
PHP 突发严重安全漏洞,AI 开始改变网络安全的攻防方式
人工智能·web安全·php·it学习·wwwoop.com
catchadmin19 小时前
PHP 8.7 协程新提案 让跨协程代码共享请求身份
开发语言·php
福建佰胜张工20 小时前
SMC 薄型气爪 MHF2-16D1 深度解析:平行开闭、低剖面结构在自动化装配中的选型与落地
java·服务器·安全·自动化
念何架构之路20 小时前
go-grpc设计思想
开发语言·后端·golang
Web极客码20 小时前
我用 Claude 分析 Redis 哨兵日志,实现跨机房数据不一致自动对齐
数据库·redis·php
酷炫码神21 小时前
Day 10|Pinia 状态管理:把散落在各组件的数据,收进一个全局仓库
android·开发语言·javascript
xuefeiniao21 小时前
PHP2Shell漏洞修复
运维·服务器·php