短链接服务看似只是一次重定向,真正上线后却会同时面对链接查询、身份认证、访问记录、缓存一致性和数据隐私等问题。shrl.io 的架构把这些职责拆成四个 Go 微服务,并用 PostgreSQL、Redis、分析 worker 与 SvelteKit 前端组合成一套可以通过容器启动的自托管系统。
这类设计的价值不只是“自己部署一个短网址”,而是让链接数据和访问分析留在自己的基础设施中,减少对第三方短链平台的依赖。
四个服务,各自处理一类问题
shrl.io 将系统拆分为四个微服务:内部 API、公开认证服务、Redis 驱动的重定向器,以及分析 worker。
内部 API 负责管理短链接、修改目标地址以及提供后台需要的数据。它通常是业务规则最集中的位置,也适合放置权限检查、链接有效期和别名冲突处理。
公开认证服务面向浏览器或外部客户端,承担登录、会话或令牌相关流程。将认证入口独立出来,可以让内部 API 不必直接暴露所有认证细节,也便于后续替换认证策略。
重定向器位于访问链路的最前端。当用户访问短链接时,服务优先查询 Redis 中的映射,找到目标地址后直接返回 HTTP 重定向。高频读取不必每次访问 PostgreSQL,这对短链接这种读多写少的场景尤其重要。
分析 worker 则负责异步处理访问记录。重定向请求不需要等待完整的分析写入流程,访问事件可以先进入 Redis,再由 worker 流式消费并持久化或聚合。这样能够缩短用户感知到的重定向延迟,也能把分析处理与在线流量隔离开。
PostgreSQL 与 Redis 的分工
PostgreSQL 是系统的数据源,适合保存短链接的规范记录,例如短码、目标 URL、创建者、状态和创建时间。需要修改链接或执行管理查询时,应以 PostgreSQL 中的数据为准。
Redis 更适合作为两个角色使用:
- 缓存短码到目标 URL 的映射,加速重定向。
- 暂存或流式传递访问事件,供分析 worker 消费。
缓存带来的关键问题是失效策略。创建或修改短链接后,应用需要同步更新或删除对应 Redis key;删除短链接时也必须清理缓存,否则用户可能在短时间内继续被重定向到旧地址。一个可以实践的 key 命名方式如下:
shortlink:{code} -> https://example.com/target
访问事件则可以包含短码、时间戳和必要的请求上下文。隐私优先并不意味着完全不做分析,而是应避免默认收集不必要的个人信息,例如完整 IP 地址、长期可识别的用户标识或未经说明的跨站追踪字段。具体保留哪些字段,应根据业务目的和当地法规确定。
可以这样用 Podman Compose 启动本地栈
摘要说明整个栈通过 Docker 部署,也可以使用 Podman Compose 以一条命令启动。下面是一个最小化的实践示例,服务名、镜像名和环境变量需要根据实际项目调整:
services:
postgres:
image: postgres:16
environment:
POSTGRES_DB: shrl
POSTGRES_USER: shrl
POSTGRES_PASSWORD: change-me
volumes:
- pgdata:/var/lib/postgresql/data
redis:
image: redis:7-alpine
api:
image: your-registry/shrl-api:latest
depends_on:
- postgres
- redis
environment:
DATABASE_URL: postgres://shrl:change-me@postgres:5432/shrl
REDIS_URL: redis://redis:6379/0
redirector:
image: your-registry/shrl-redirector:latest
depends_on:
- redis
environment:
REDIS_URL: redis://redis:6379/0
analytics-worker:
image: your-registry/shrl-analytics-worker:latest
depends_on:
- postgres
- redis
environment:
DATABASE_URL: postgres://shrl:change-me@postgres:5432/shrl
REDIS_URL: redis://redis:6379/0
volumes:
pgdata:
将内容保存为 compose.yml 后,可以这样启动:
podman compose up -d
podman compose ps
podman compose logs -f redirector
生产环境不应继续使用示例密码。还需要补充数据库迁移、备份策略、TLS 终止、日志脱敏、容器资源限制以及服务健康检查。depends_on 只能表达启动顺序,不能保证 PostgreSQL 或 Redis 已经可以接受请求,服务本身仍应实现重试和健康探测。
一次访问请求会经过什么路径
用户访问 /{code} 时,典型流程可以是:
- 重定向器从请求中解析短码。
- 查询 Redis 缓存。
- 命中后返回
301或302,并异步记录访问事件。 - 未命中时查询 PostgreSQL,确认链接状态后回填 Redis。
- 分析 worker 消费事件,按需要写入统计数据。
缓存未命中时,必须处理并发回填问题。热点短码可能在同一时间触发大量数据库查询,可以通过 Redis 锁、请求合并或短暂的负缓存降低压力。对于不存在的短码,也可以缓存一个较短 TTL 的未找到结果,避免恶意扫描持续打到 PostgreSQL。
重定向状态码需要结合业务选择。短链接目标如果经常修改,302 或 307 更容易保留调整空间;目标永久稳定时才考虑 301 或 308。这不是单纯的性能参数,因为浏览器和代理可能缓存永久重定向。
SvelteKit 前端与自托管边界
SvelteKit 前端可以提供短链接创建、链接列表、认证和访问分析页面。前端展示的数据应来自内部 API 或公开认证服务,而不是直接访问 PostgreSQL 或 Redis。这样可以把凭据、权限和数据格式控制集中在服务端。
自托管系统的隐私优势依赖实际配置,而不是仅依赖技术栈名称。部署时应明确:
- 访问日志保留多久,是否包含完整 IP。
- 分析数据是否需要聚合或匿名化。
- 管理 API 是否只允许内网或经过反向代理访问。
- Redis 是否启用密码、网络隔离和持久化策略。
- PostgreSQL 备份是否加密,恢复流程是否经过演练。
采用前的检查清单
这套架构适合希望掌控链接数据、需要可定制分析,或不想把重定向流量交给第三方平台的团队。它也会带来运维成本:数据库升级、Redis 故障、认证安全、监控告警和公网防护都需要自行负责。
落地时可以按以下顺序推进:
- 先用 PostgreSQL 保存规范链接数据,再引入 Redis 缓存。
- 让重定向路径保持短小,分析写入异步化。
- 为新增、修改、删除链接定义明确的缓存失效规则。
- 对访问事件实施字段最小化和保留期限控制。
- 用 Compose 完成本地验证,再补齐生产环境的密钥、备份、TLS 和监控。
短链接服务的核心不是生成一段更短的字符串,而是围绕一次重定向建立清晰的数据边界。Go 微服务负责职责拆分,Redis 负责低延迟访问和事件流转,PostgreSQL 保持数据可靠性,SvelteKit 提供管理界面;组合起来,才能形成一套真正可维护的隐私优先自托管服务。