设计原则
如果能给三年前的自己写一封信
三年了。
我翻出了第一天写的代码——一个 50 行的 Python 脚本,SQLite 数据库,一台 1 核 2G 的云服务器。
再看看现在的系统:负载均衡、Redis 集群、CDN 加速、分布式 ID、风控系统……
“如果能给三年前的自己写一封信,我会告诉他这 10 条原则。“
原则一:先跑起来,再优化
故事
“我的第一个版本只用了一个下午。”
没有缓存,没有读写分离,没有高可用。只有最基本的:接收长 URL,返回短 URL。
“但我犯过反面错误。在自定义别名功能上,我一开始就设计了申请审核、竞价机制、商标保护……花了三周开发,结果上线后用户只想简单注册一个别名。三周的复杂设计,90% 用不上。“
教训
# ❌ 过度设计
class OverEngineered:
def __init__(self):
self.load_balancer = LoadBalancer()
self.cache_cluster = CacheCluster()
self.sharding = DatabaseSharding(shards=16)
# 三个月开发,大部分用不上
# ✅ 从简单开始
class Simple:
def __init__(self):
self.db = Database()
# 一下午搞定,遇到问题再优化原则
不要过早优化。先让系统跑起来,遇到真正的瓶颈再解决。
原则二:数据比直觉重要
故事
“我一度以为数据库是性能瓶颈,花了一周研究分库分表方案。后来加了监控才发现——95% 的请求时间花在调用外部 API 上,数据库只占 5%。”
“如果一开始就有监控,我能省一周时间。“
教训
我的直觉:数据库慢 → 需要分库分表
真实数据:外部 API 慢 → 需要缓存
差距:一周的无效工作原则
先加监控,再用数据驱动决策。没有数据的优化是盲目的。
原则三:简单方案先上
故事
“分布式 ID 方案,我一开始选了 Snowflake。实现了一周,还有时钟回拨的隐患。”
“后来我发现 Redis INCR 就能解决问题,代码只有 Snowflake 的十分之一。”
“再后来,连 Redis 调用都嫌多,用了号段缓存,一次取一批 ID,性能又提升 10 倍。“
演进过程
Snowflake(复杂,有时钟依赖)
↓
Redis INCR(简单,但有网络开销)
↓
号段缓存(最简,99% 内存操作)原则
能用简单方案解决的,就不要用复杂的。复杂是最后的选择,不是第一选择。
原则四:缓存是万能药?不,是药三分毒
故事
“缓存确实解决了大部分性能问题。但我也被它坑过。”
“有一次用户修改了原始 URL,但缓存还是旧的。用户投诉说’我明明改了,为什么还是跳到旧页面?’”
“还有一次,有人恶意查询大量不存在的短码,每次都穿透到数据库——这就是缓存穿透。“
教训
| 缓存问题 | 我的经历 | 解决方案 |
|---|---|---|
| 数据不一致 | 用户修改 URL 后缓存未更新 | 主动失效 + 过期时间 |
| 缓存穿透 | 恶意查询不存在的 key | 布隆过滤器 |
| 缓存击穿 | 热点 key 过期瞬间压垮数据库 | 互斥锁 |
| 缓存雪崩 | 大面积同时过期 | 随机过期时间 |
原则
缓存是强大的工具,但必须考虑一致性、穿透、击穿、雪崩四个问题。没有银弹。
原则五:数据库不是垃圾桶
故事
“日志表从 100 万条涨到 5000 万条那天,查询一次周报要 30 秒。”
“我突然意识到:我一直在往数据库里塞数据,从来没清理过。”
“后来做了冷热分离:7 天内的热数据放 MySQL,超过 7 天的冷数据归档到 S3。成本从 ¥500/月降到了 ¥110/月。“
原则
数据有生命周期。热数据放数据库,冷数据归档,过期数据清理。不要让数据库变成垃圾桶。
原则六:安全不是可选的
故事
“收到那封钓鱼诈骗投诉邮件时,我的心凉了半截。”
“我的短链接被用于仿冒银行网站。如果客户真的被骗了钱,平台要承担连带责任。”
“那天晚上我通宵实现了 URL 安全检测系统。三层防线:黑名单、特征检测、Google Safe Browsing。”
“事后回想,如果一开始就把安全检测作为创建流程的必经环节,就不会出这种事。“
原则
安全是底线,不是可选功能。出了事再补救,损失已经造成。
原则七:成本意识
故事
“前半年,每个月都在亏钱。服务器 ¥200、Redis ¥80、CDN ¥200……月成本 ¥480,收入 ¥0。”
“我开始精打细算:每个决策都考虑成本。MySQL 分区代替分库分表(省钱)、S3 归档冷数据(省存储)、号段缓存减少 Redis 调用(省流量)。”
“直到第 8 个月推出付费套餐,才第一次实现盈亏平衡。当月收入 ¥15,000,成本 ¥8,000。我终于不用自己掏钱了。“
成本对比
| 阶段 | 月成本 | 月收入 | 状态 |
|---|---|---|---|
| 第 1-6 月 | ¥480 | ¥0 | 亏损 |
| 第 7 月 | ¥2,000 | ¥0 | 亏损(扩容) |
| 第 8 月 | ¥8,000 | ¥15,000 | 盈利 |
原则
每一分钱都要花在刀刃上。作为独立开发者,成本意识是生存本能。
原则八:监控决定一切
故事
“流量洪峰那天晚上 8 点,手机开始疯狂震动。如果没有监控告警,我可能第二天早上才发现系统挂了。”
“我总结了监控的四层指标:“
monitoring_layers = {
'业务指标': ['QPS', '日活', '创建量', '错误率'],
'性能指标': ['P50/P95/P99 延迟', '缓存命中率'],
'系统指标': ['CPU', '内存', '磁盘', '网络'],
'依赖指标': ['Redis 延迟', 'MySQL 延迟', 'Kafka 消息堆积']
}原则
没有监控 = 盲人摸象。监控先行,数据驱动一切决策。
原则九:为未来设计,但不过度设计
故事
“我犯过两个极端的错误。”
“第一个极端:完全不考虑未来。SQLite 不支持并发,切换 MySQL 时数据迁移搞了一整夜,中间还丢了 2 小时数据。”
“第二个极端:过度设计。自定义别名功能一开始就做了竞价系统,结果根本用不上。”
“后来我找到了平衡点:核心路径做好扩展准备,非核心功能保持简单。“
我的架构演进节奏
第 1 天:Flask + SQLite(能跑就行)
第 2 周:加 Redis 缓存(性能不够了)
第 2 月:切 MySQL(并发不够了)
第 6 月:加负载均衡(单机不够了)
第 1 年:加 CDN + 分布式 ID(规模大了)
第 3 年:微服务拆分(团队大了)“每一步都是被逼出来的,没有提前过度设计。但核心路径的扩展性一直有准备。“
原则
为未来留好接口,但不要提前实现。在问题和方案之间保持一步的距离。
原则十:用户需求驱动技术
故事
“前三个月,我做了一堆自嗨功能:实时统计仪表盘、别名质量分析、URL 相似度检测……”
“结果用户最常问的是:‘能看看我的链接被点击了多少次吗?’”
“我花了一个周末做了最简版统计——总点击数、今日点击、来源分布。用户满意度立刻飙升。”
“我意识到:技术自嗨没有价值,解决用户问题才有价值。“
我的技术自嗨 vs 用户需求
| 我做的 | 用户要的 |
|---|---|
| URL 相似度分析 | 点击统计 |
| 别名质量评分 | 自定义别名 |
| 实时数据流处理 | CSV 批量导入 |
| 微服务拆分 | 服务别挂就行 |
原则
技术服务于业务,不是反过来。先理解用户需求,再选择技术方案。
结语
这三年的旅程教会我的最重要一课:
系统设计不是纸上谈兵,而是在真实问题的压力下做出最优选择。
每个决策都是在约束条件下权衡的结果:
- 成本 vs 性能
- 简单 vs 可扩展
- 安全 vs 便捷
- 现在 vs 未来
没有完美的架构,只有合适的架构。
希望我的故事能帮你少走一些弯路。