设计原则

如果能给三年前的自己写一封信

三年了。

我翻出了第一天写的代码——一个 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 未来

没有完美的架构,只有合适的架构。

希望我的故事能帮你少走一些弯路。