用 Go 从零实现的短链接服务 — 专为前端工程师设计的后端学习项目。
作为一个前端工程师,我需要快速补齐分布式系统、数据库、缓存、容器化、CI/CD 方面的知识。这个项目就是我的学习路径:从零开始,用一个真实的短链接服务串联所有后端核心技术。每一行代码都有详细注释,所有后端概念都有前端类比(Express ↔ Gin、localStorage ↔ Redis、Vercel ↔ K8s)。如果你也是前端想学后端,这个项目适合你。
你已经会 HTTP、JSON、异步编程、Vercel 部署。但全栈岗位要求的那些后端知识,你没碰过:
| 后端要求 | 你已经会的 | 这个项目教你的 |
|---|---|---|
| "设计一个短链接系统" | 没接触过 | 从零完整实现 |
| "缓存怎么用?" | 浏览器缓存、localStorage | Redis Cache-Aside、TTL、穿透防护 |
| "分布式 ID 怎么生成?" | crypto.randomUUID() |
Snowflake:时间戳 + 机器ID + 序列号 |
| "限流算法?" | debounce / throttle |
令牌桶(保护数据库,不是保护用户体验) |
| "Docker 和 K8s?" | Vercel 自动部署 | Dockerfile、Compose 编排、K8s 配置 |
| "数据库设计?" | localStorage.setItem() |
MySQL 索引、B+ 树、连接池 |
| "CI/CD 流水线?" | git push → Vercel 构建 |
GitHub Actions:lint → test → build → Docker |
| 前端 | 后端(本项目) | 核心区别 |
|---|---|---|
| Express / Koa | Gin | 同样的中间件模式,但 Go 编译型 + 真并发 |
fetch('/api') |
db.Where().First() |
前端调 API,后端调数据库 |
| localStorage | Redis | 都是键值存储;Redis 多台服务器共享 |
npm install → node_modules |
go mod tidy → 全局缓存 |
Go 没有 node_modules,依赖全局共享 |
npm run build → dist/ |
go build → 单个二进制文件 |
Go 编译成一个文件,不需要运行时 |
| Vercel / Netlify | Docker + K8s | 前端:平台全包。后端:你自己管 |
.env |
ConfigMap / Secret | 一样的思路,但 K8s 在多台机器间统一管理 |
| Sentry | Prometheus + Grafana | Sentry 捕获错误;Prometheus 追踪指标趋势 |
console.log |
结构化 JSON 日志 | 后端日志要机器可解析,用于搜索和告警 |
Promise.all() |
goroutine + WaitGroup | JS 假并行;Go 真并行 |
| ESLint + Prettier | golangci-lint | 同样的理念,不同的生态 |
- 短链接生成 — Base62 编码的 Snowflake ID
- 302 跳转 — 临时重定向,支持点击统计
- 点击统计 — 异步事件收集,并发消费者
- Redis 缓存 — Cache-Aside 模式,30 分钟 TTL
- 令牌桶限流 — 100 req/s,突发 200
- Prometheus 监控 — 请求延迟直方图 + 计数器
- Docker 就绪 — 多阶段构建,镜像约 15MB
- CI 流水线 — GitHub Actions:lint → test → build → Docker
- K8s 配置 — Deployment、Service、ConfigMap
┌──────────────────────────────────────────┐
│ Gin HTTP 路由 │
│ /api/v1/shorten /:code /api/v1/stats │
└────────────┬─────────────────────────────┘
│
┌────────────▼─────────────────────────────┐
│ Handler(类似 Express Controller) │
└────────────┬─────────────────────────────┘
│
┌────────────▼─────────────────────────────┐
│ Service(业务逻辑层) │
│ Snowflake ID → Base62 → 短码 │
└──┬─────────┬──────────────┬──────────────┘
│ │ │
┌────────▼──┐ ┌───▼────┐ ┌──────▼───────┐
│ Repository │ │ Cache │ │ Analytics │
│ (MySQL) │ │ (Redis)│ │ (事件收集器) │
└────────────┘ └────────┘ └──────────────┘
| 层 | 技术 | 用途 | 前端等价物 |
|---|---|---|---|
| 语言 | Go 1.26 | 编译成单文件,内置并发 | Node.js |
| HTTP | Gin | 高性能路由 + 中间件 | Express |
| ORM | GORM | 数据库操作 | Prisma / TypeORM |
| 数据库 | MySQL 8.0 | 持久化存储 | — |
| 缓存 | Redis 7 | Cache-Aside 加速读取 | localStorage |
| ID 生成 | Snowflake | 分布式唯一 ID | crypto.randomUUID() |
| 编码 | Base62 | 短码生成 | — |
| 监控 | Prometheus | 指标采集 | Sentry / DataDog |
| 容器化 | Docker + Compose | 构建 + 编排 | — |
| 编排 | Kubernetes | 部署 + 服务发现 | Vercel |
| CI/CD | GitHub Actions | 自动化流水线 | Vercel CI |
- Go 1.26+(
brew install go) - MySQL 8.0+(
brew install mysql && brew services start mysql) - Redis 7+(
brew install redis && brew services start redis) - Make
git clone https://github.com/venaissance/shorturl.git
cd shorturl
make depsmysql -u root -e "CREATE DATABASE IF NOT EXISTS shorturl DEFAULT CHARSET utf8mb4;"
mysql -u root -e "ALTER USER 'root'@'localhost' IDENTIFIED BY 'root123';"
mysql -u root -proot123 shorturl < scripts/init.sqlmake run
# 服务运行在 http://localhost:8080curl -s -X POST http://localhost:8080/api/v1/shorten \
-H "Content-Type: application/json" \
-d '{"url": "https://github.com"}' | python3 -m json.tool返回 201 Created:
{
"short_url": "http://localhost:8080/mfXXX",
"short_code": "mfXXX",
"original_url": "https://github.com"
}浏览器直接打开 http://localhost:8080/mfXXX,自动跳转到原始 URL。
curl -s http://localhost:8080/api/v1/stats/mfXXX | python3 -m json.tool返回 200 OK:
{
"short_code": "mfXXX",
"total_clicks": 42
}启动服务后(make run),试试这些:
redis-cli keys "short:*" # 看哪些短码被缓存了
redis-cli get "short:你的短码" # 看缓存的值
redis-cli ttl "short:你的短码" # 看剩余过期时间(秒)mysql -u root -proot123 -e \
"SELECT id, short_code, LEFT(original_url,40) as url FROM shorturl.short_urls;" 2>/dev/nullcurl -s http://localhost:8080/metrics | grep http_requests_total# 正常流量(不超限)— 全部 302
hey -n 100 -c 10 -disable-redirects http://localhost:8080/你的短码 2>&1 | grep "Status"
# 突发流量(超限)— 部分 429
hey -n 300 -c 50 -disable-redirects http://localhost:8080/你的短码 2>&1 | grep -E "\[302\]|\[429\]"限流器允许 100 req/s,突发 200。超出的请求返回 429 Too Many Requests。
go test -bench=. -benchmem ./internal/service/.
├── cmd/api/main.go # 入口:依赖注入 + 启动服务
├── configs/config.go # 配置管理
├── internal/
│ ├── handler/ # HTTP 处理器(类似 Express Controller)
│ ├── service/ # 业务逻辑(缩短、解析、统计)
│ ├── repository/ # 数据库 CRUD(GORM)
│ ├── model/ # 数据模型(对应 MySQL 表)
│ ├── cache/ # Redis Cache-Aside 实现
│ ├── analytics/ # 异步点击事件收集器
│ ├── middleware/ # 限流器、Prometheus 指标
│ ├── database/ # MySQL 连接管理
│ └── util/ # Snowflake ID、Base62 编码
├── proto/shorturl.proto # gRPC 服务定义
├── k8s/ # Kubernetes 配置
├── scripts/init.sql # 数据库建表脚本
├── docs/ # 学习文档(每个 Phase 一份)
├── Dockerfile # 多阶段构建
├── docker-compose.yml # 完整服务编排
├── Makefile # 常用命令
└── .github/workflows/ci.yml # CI 流水线
24 个 Go 源文件,13 个包。
分 5 个阶段,每个阶段包含:
- 学习笔记 — 详细注释 + 前端类比
- 交互式测验 — 克隆后用浏览器打开 HTML 即可做题(选择题 + 连线 + 填空 + 成绩单)
- 所有代码都有详细注释,每个 Go 概念都有 TypeScript/JS 等价物
| 阶段 | 你会学到什么 | 前端类比 | 学习笔记 | 交互式测验 |
|---|---|---|---|---|
| 1 | Go 基础、HTTP 服务、MySQL、分层架构 | "像用 Express + Prisma 搭 API" | 笔记 | 测验 |
| 2 | Redis 缓存、Snowflake ID、限流、压测 | "像加上 localStorage 缓存 + UUID + throttle" | 笔记 | 测验 |
| 3 | 微服务模式、异步事件、gRPC/Protobuf | "像把 monorepo 拆成多个 package + EventEmitter" | 笔记 | 测验 |
| 4 | Docker、Docker Compose、K8s 配置 | "像从 Vercel 到自己管理基础设施" | 笔记 | 测验 |
| 5 | CI/CD 流水线、Prometheus 监控、可观测性 | "像 GitHub Actions + Sentry 的后端版" | 笔记 | 测验 |
交互式测验是独立的 HTML 文件,克隆项目后用浏览器打开即可,如
open docs/phase1-quiz.html
- 从开发到上线全链路拆解 — 以大厂标准对比前端和后端从代码到上线的完整工程链路(85KB,覆盖 CI/CD、灰度发布、监控告警、容量规划)
make test52 个测试用例,覆盖全部 9 个包:
| 包 | 测试数 | 覆盖内容 |
|---|---|---|
| handler | 9 | HTTP 端点、错误响应、E2E 流程 |
| service | 7 | 缩短/解析、幂等性、缓存命中、过期 |
| repository | 7 | CRUD、重复检测、更新 |
| cache | 6 | Redis 读写、TTL 过期、穿透防护 |
| analytics | 6 | 事件收集、并发安全、优雅关闭 |
| middleware | 5 | 限流、Prometheus 指标 |
| model | 4 | 过期判断、表名 |
| util | 8 | Base62 编解码、Snowflake 唯一性/并发 |
Resolve 性能对比(Apple M4 Pro):
| 场景 | ops/sec | 延迟 | 内存/次 |
|---|---|---|---|
| 有 Redis 缓存 | ~25,700 | 45μs | 264B |
| 无缓存(纯 MySQL) | ~9,200 | 123μs | 4,667B |
Redis 缓存带来 2.7 倍吞吐提升,94% 内存节省。
压力测试(关闭限流,单机):
| 并发 | 请求数 | QPS | 平均延迟 | 失败 |
|---|---|---|---|---|
| 100 | 5,000 | 20,586 | 4.8ms | 0 |
| 200 | 10,000 | 23,058 | 8.5ms | 0 |
| 500 | 50,000 | 25,877 | 19.1ms | 0 |
米大王 — 目前专注于 AI Agent 全栈开发
如果这个项目对你有帮助,欢迎 Star 支持。有问题可以在知乎或 Issue 里交流。