14 KiB
转岗计划 · 第 5 周学习计划(项目一 · 下:Redis 缓存与限流 + 消息队列入门 + Docker 部署)
所属阶段:第一段 Go 全栈基础的最后一周。总纲见同目录
README.md,上周计划week04-auth-project1.md,下周计划week06-python-basics.md。
目标:给“团队任务看板”补上招聘里常见的三样东西:Redis(缓存、限流)、消息队列(用 Redis Streams 入门,了解 RabbitMQ 的基本概念)、Docker 部署(Docker Compose 一键启动,部署到自己的 Ubuntu 服务器,加一条 CI 流水线)。
原则:这三样都浅学够用。Go 全栈部分到本周结束,之后的主线是 Python 和 AI 应用。本周不追求高并发、集群这些深入话题。
前置条件:第 4 周的项目一功能完成,测试通过。
参考资料:
- Redis 命令参考(中文):https://redis.com.cn/commands.html
- go-redis:https://redis.uptrace.dev/zh/
- Redis Streams 介绍:https://redis.io/docs/latest/develop/data-types/streams/
- Docker 文档:https://docs.docker.com/get-started/
- RabbitMQ 入门教程:https://www.rabbitmq.com/tutorials
最终项目结构(只列新增和修改)
task-board/
├── docker-compose.yml # 全套:postgres、redis、backend、frontend(nginx)
├── docker-compose.dev.yml # 开发用:只起 postgres 和 redis
├── .env.example # 部署用的环境变量示例
├── deploy/
│ ├── nginx.conf # 前端静态文件 + /api 反向代理到后端
│ └── deploy.md # 部署到 Ubuntu 服务器的步骤
├── .ci/ # 按你的代码托管平台放到对应目录(见第 7 天)
│ └── ci.yml
├── backend/
│ ├── Dockerfile # 多阶段构建
│ ├── .dockerignore
│ ├── cmd/
│ │ ├── server/main.go # 原来的 main.go 移到这里
│ │ └── worker/main.go # 消息消费者(通知处理)
│ └── internal/
│ ├── cache/
│ │ ├── redis.go # 连接和通用 Get/Set
│ │ ├── board_cache.go # 看板任务列表缓存
│ │ └── board_cache_test.go
│ ├── ratelimit/
│ │ ├── limiter.go # 固定窗口限流
│ │ └── limiter_test.go
│ ├── middleware/
│ │ └── ratelimit.go # 登录接口限流中间件
│ ├── queue/
│ │ ├── stream.go # Redis Streams 生产和消费
│ │ └── stream_test.go
│ └── notify/
│ ├── events.go # 事件结构:任务被指派
│ ├── store.go # 通知表读写
│ └── handler.go # 消费者处理逻辑(幂等)
│ └── db/migrations/
│ ├── 000002_notifications.up.sql
│ └── 000002_notifications.down.sql
└── frontend/
├── Dockerfile # 构建静态文件,用 nginx 提供
└── src/components/NotificationBell.tsx
第 1 天:Redis 基础
学习内容
- Redis 是什么:内存里的键值数据库,常用来做缓存、计数、限流、会话、排行榜、简单队列。
- 五种常用类型:String、Hash、List、Set、Sorted Set;过期时间
EXPIRE/TTL。 - 用
redis-cli动手,Go 里用 go-redis:go get github.com/redis/go-redis/v9。
要建的文件
docker-compose.dev.yml:postgres:16+redis:7,Redis 端口6379。原来backend/docker-compose.yml的数据库服务挪到这里。backend/internal/cache/redis.go:func NewRedis(ctx context.Context, url string) (*redis.Client, error):连接并Ping。func GetJSON[T any](ctx, rdb, key string) (T, bool, error)、func SetJSON(ctx, rdb, key string, v any, ttl time.Duration) error(顺便练一下 Go 泛型)。
internal/config/config.go:新增RedisURL。backend/sql/practice/redis_cli.md:记录你在redis-cli里敲过的命令和结果(每种类型至少 2 条)。
运行方式:docker compose -f docker-compose.dev.yml up -d,docker compose -f docker-compose.dev.yml exec redis redis-cli
完成标准:能说出 Redis 为什么快、数据放内存的风险(重启丢数据,了解 RDB 和 AOF 两种持久化即可)。
第 2 天:缓存
学习内容
- 旁路缓存(Cache Aside):读时先查缓存,没有再查数据库并写回缓存;写时先更新数据库,再删除缓存。
- 三个常见问题,面试常问:缓存穿透(查不存在的数据)、缓存击穿(热点 key 过期瞬间大量请求)、缓存雪崩(大量 key 同时过期)。各自的常用办法:缓存空值、互斥锁 / 单飞(singleflight)、过期时间加随机值。
要建的文件
internal/cache/board_cache.go:type BoardCache struct { rdb *redis.Client; ttl time.Duration }func (c *BoardCache) GetTasks(ctx, boardID int64) (map[string][]model.Task, bool, error)func (c *BoardCache) SetTasks(ctx, boardID int64, tasks map[string][]model.Task) error:过期时间 = 基础时间 + 随机几十秒。func (c *BoardCache) Invalidate(ctx, boardID int64) error- key 统一格式:
board:{id}:tasks。
- 修改
internal/service/task_service.go:List先查缓存;Create、Update、Move、Delete成功后删除缓存。用golang.org/x/sync/singleflight合并同一看板的并发查询。 - Redis 不可用时降级:缓存出错只记日志,继续查数据库,不让整个接口失败。
internal/cache/board_cache_test.go:没设TEST_REDIS_URL时跳过;测试读写、过期、删除。
完成标准:能画出旁路缓存的读写流程,能解释为什么是“删缓存”而不是“更新缓存”;停掉 Redis 后接口照常可用。
第 3 天:限流
学习内容
- 为什么要限流:防暴力破解登录、防刷接口、保护下游(以后调用大模型接口时同样要限流,因为有额度和费用)。
- 固定窗口计数:
INCR key+ 首次设置EXPIRE;用 Lua 脚本或MULTI保证两步一起执行。了解滑动窗口和令牌桶的思路即可。
要建的文件
internal/ratelimit/limiter.go:type Limiter struct { rdb *redis.Client }func (l *Limiter) Allow(ctx, key string, limit int, window time.Duration) (allowed bool, remaining int, err error)
internal/ratelimit/limiter_test.go:同一个 key 第limit+1次被拒绝;窗口过去后恢复。internal/middleware/ratelimit.go:func RateLimit(l *ratelimit.Limiter, keyFn func(*gin.Context) string, limit int, window time.Duration) gin.HandlerFunc,超限返回 429,响应头带Retry-After。router.go:登录接口按“IP + 邮箱”限流(比如每分钟 5 次),其他接口按用户 ID 宽松限流。
完成标准:用 api.http 连续错误登录 6 次,第 6 次返回 429;能说出固定窗口在窗口边界的问题。
第 4 天:消息队列入门(Redis Streams)
学习内容
- 消息队列解决什么问题:解耦(指派任务的接口不用关心通知怎么发)、异步(接口快速返回)、削峰。
- 基本概念:生产者、消费者、消费者组、确认(ACK)、重试、死信、“至少一次”投递导致的重复消费——所以消费者必须幂等。
- Redis Streams 命令:
XADD、XREADGROUP、XACK、XPENDING、XAUTOCLAIM。 - RabbitMQ 只了解概念(交换机、队列、路由键、确认),对比一下:Redis Streams 轻量,适合小项目;RabbitMQ / Kafka 是专门的消息中间件,可靠性和功能更强。本周不装 RabbitMQ,读完官方入门教程的前两篇即可。
要建的文件
db/migrations/000002_notifications.up.sql/.down.sql:notifications(id, user_id, event_id TEXT UNIQUE, type, payload JSONB, read_at, created_at)。event_id唯一约束用来做幂等。internal/notify/events.go:type TaskAssigned struct { EventID string; BoardID, TaskID, AssigneeID, ByUserID int64; At time.Time }。internal/queue/stream.go:func Publish(ctx, rdb, stream string, v any) (string, error):XADD,消息体 JSON。func Consume(ctx, rdb, stream, group, consumer string, handle func(ctx, id string, body []byte) error) error:创建消费者组(已存在就忽略)→ 循环XREADGROUP→ 处理成功才XACK;启动时先用XAUTOCLAIM认领别的消费者挂掉后遗留的消息。
internal/notify/store.go:InsertNotification,用ON CONFLICT (event_id) DO NOTHING。internal/notify/handler.go:处理TaskAssigned→ 写通知表。- 修改
task_service.go:创建或修改任务时负责人有变化,就Publish一个TaskAssigned事件(发布失败只记日志,不影响主流程,并在 README 里写明这个取舍)。 cmd/worker/main.go:独立的消费者进程,支持优雅关机。- 新接口:
GET /api/notifications、POST /api/notifications/:id/read。 internal/queue/stream_test.go:同一条消息处理两次,通知表只有一行。
运行方式:一个终端 go run ./cmd/server,另一个 go run ./cmd/worker;指派任务后查通知接口。
完成标准:能说清“至少一次投递 + 幂等消费”;把 worker 停掉再指派任务,重新启动 worker 后通知能补上。
第 5 天:Docker 镜像
学习内容
- 镜像、容器、卷、网络;
Dockerfile常用指令。 - 多阶段构建:第一阶段编译,第二阶段只放编译好的二进制,镜像小、没有源码。
- 容器里的配置都走环境变量;服务之间用 Compose 的服务名互相访问(比如
postgres:5432)。
要建的文件
backend/Dockerfile:- 阶段一
golang:1.x镜像:先复制go.mod、go.sum下载依赖(利用缓存),再复制源码,CGO_ENABLED=0 go build出server和worker两个二进制。 - 阶段二用体积小的基础镜像(
alpine或distroless),非 root 用户运行。
- 阶段一
backend/.dockerignorefrontend/Dockerfile:阶段一node镜像npm ci && npm run build;阶段二nginx镜像放静态文件和deploy/nginx.conf。deploy/nginx.conf:/指向静态文件(单页应用的路由回退到index.html),/api/反向代理到backend:8080。- 前端接口地址改成相对路径
/api,开发时用 Vite 代理。
运行方式:docker build -t task-board-backend ./backend,docker images 看镜像大小。
完成标准:能说出多阶段构建的好处;后端镜像不包含源码和 Go 工具链。
第 6 天:Docker Compose 一键启动 + 前端通知
要建的文件
task-board/docker-compose.yml:postgres、redis(都带具名卷和健康检查)。migrate:用 golang-migrate 的官方镜像执行迁移,执行完就退出。backend:depends_on等数据库健康和迁移完成;环境变量从.env读。worker:同一个镜像,启动命令换成worker。frontend:对外暴露80端口。
task-board/.env.example:POSTGRES_PASSWORD、JWT_SECRET、REDIS_URL等,写清楚哪些必须改。frontend/src/components/NotificationBell.tsx:每 30 秒拉一次通知列表,显示未读数,点开可标记已读。
运行方式:cp .env.example .env(改密码和密钥)→ docker compose up -d --build → 打开 http://localhost。
完成标准:一台只装了 Docker 的机器,一条命令就能跑起完整系统。
第 7 天:部署到服务器 + CI + 收尾
学习内容
- 部署的最小流程:服务器装 Docker → 拉代码 → 配
.env→docker compose up -d --build;更新时git pull再执行一次。 - CI:每次推送自动跑检查和测试。不同代码托管平台的配置文件位置不同(GitHub Actions 是
.github/workflows/,GitLab 是.gitlab-ci.yml,Gitea 的 Actions 和 GitHub 写法接近),先确认你的git.gozeros.com是哪种平台、有没有开启 CI,没有的话就先写好文件、在本地用脚本跑同样的步骤。 - 了解即可:Kubernetes 是更大规模的容器编排,招聘里没有点名要求,本计划不学。
要建的文件和修改
deploy/deploy.md:在 Ubuntu 服务器上部署的完整步骤,包括防火墙只开 80(和 22)、.env权限设为 600、查看日志docker compose logs -f backend、备份数据库pg_dump。.ci/ci.yml(放到你平台对应的目录):后端go vet、go test -race(起 postgres 和 redis 服务容器跑集成测试);前端npm ci && npm run build;最后docker build检查镜像能构建。- 在你的 Ubuntu 服务器上按
deploy.md部署一次(服务器是内网或没有域名也没关系,能在内网访问即可;HTTPS 放到第 15 周和 AI 项目一起处理)。 - 更新
task-board/README.md:架构图加上 Redis、worker、Nginx;新增“缓存策略”“限流”“消息队列与幂等”“部署”四节;本地开发和一键部署两种运行方式。 - 提交并推送到
zhuangang仓库。确认.env没有提交。
完成标准
go vet ./...、go test ./... -race和集成测试都通过。- 服务器上能访问项目一,重启服务器后
docker compose能自动拉起(服务加restart: unless-stopped)。 - 能用 3 分钟讲清楚项目一的完整架构:分层、鉴权、缓存、限流、消息队列、部署。
- 在总纲
README.md的进度记录里勾掉第 5 周和“第一段 Go 全栈基础”。
本周自查清单
- 会用 Redis 五种常用类型,知道持久化的两种方式
- 会写旁路缓存,能讲清穿透、击穿、雪崩和对应办法
- Redis 挂了接口不跟着挂(降级)
- 会用 Redis 写固定窗口限流
- 理解消息队列的作用,能讲清“至少一次投递 + 幂等消费”
- 会写 Redis Streams 的生产者和消费者组,知道 RabbitMQ 的基本概念
- 会写多阶段构建的 Dockerfile 和多服务的 Docker Compose
- 项目一已部署到服务器,有 CI 配置
下周预告:第 6 周开始 Python。从这里起 Python 是主语言,Go 是加分项。