后端开发除了增删改查还有什么?从日常问题到工程思路
CRUD 是后端的入口,但真正决定一个系统能不能扛住的,是它周围那一圈机制。下面用"注册"这个最普通不过的接口当线索,把周边的问题和对应的工程方案过一遍——有些是行业标配,有些不常提、但遇到复杂场景时很顶用。这里不打算写成操作手册,只是把真实系统里常见的选择摆出来。
一、一个"注册"会牵出哪些问题
最朴素的注册只有三行:
1 | public void register(String phone, String password) { |
上线之后,真实环境会一个接一个地抛出问题:
- 重复提交:用户连点两下,或脚本一秒刷一千次,数据库扛得住吗?
- 慢依赖:注册完要发短信,短信接口两三秒才回,注册流程要干等吗?
- 跨服务:邀请注册送积分,积分服务是别的团队的,他们宕机你的注册也要挂吗?
- 数据量:单表涨到一千万行,注册都变慢了怎么办?
- 安全:密码要加密、密钥要轮换,加密服务自己挂了用户登不了录怎么办?
- 多机房:国内海外两个机房,数据双向同步有延迟,用户刚注册完切到海外登录查不到怎么办?
这些问题不是故意刁难,而是每个有一定规模的系统都会撞上的。下面按主题看解法,每个主题我都分两档:常用做法 和 不常用但有效。
二、限流:挡住重复和刷接口
常用做法
在业务逻辑最前面加一道闸门,同一 IP(或同一用户)对注册接口在窗口内只允许 N 次。工程上一般用令牌桶 / 漏桶 / 滑动窗口,状态放 Redis,并用 Lua 脚本保证"判断+扣减"原子,否则并发下限流会失效。优先级上,限流检查必须排在业务校验之前——命中就直接返回"请求频繁",根本不进后面逻辑。
1 | -- 滑动窗口限流(Redis + Lua,原子执行) |
不常用但有效
- 自适应限流:固定阈值("5 秒 1 次")是拍脑袋定的。Sentinel 的系统保护、Netflix 的 Concurrency Limits 会根据系统实时负载(CPU、线程、延迟)动态调阈值,比写死更稳。
- 按用户/设备配额而非仅按 IP:NAT、移动网络下大量用户共用一个出口 IP,纯 IP 限流会误伤;结合设备指纹、登录态做配额更准。
- 行为指纹识别脚本:仅靠限流挡不住低速率的恶意流量。用设备 ID、UA、操作轨迹做指纹,比 IP 维度识别机器人更靠谱。
- PoW 挑战:对可疑流量发一个轻量工作量证明(类似 Cloudflare 的 challenge),机器人要付出算力成本,真人无感——成本极低地挡掉批量刷接口。
取舍:中小团队用 Redis + 固定窗口足够;自适应限流和 PoW 是"被刷得痛过"之后才值得上的重武器。
三、异步通知:别让慢调用堵住主流程
常用做法
短信这种慢接口,决不能同步等。注册时落一条短信记录、状态标"发送中",真正发送交给 MQ 异步处理,拿到结果再回写状态。注册接口本身立刻返回。
1 | enum SmsStatus { SENDING, SUCCESS, FAILED } |
不常用但有效
- Transactional Outbox(事务性发件箱):比"本地消息表 + 定时任务"更标准。把"业务写入"和"消息"放在同一个本地事务写进
outbox表,再由一个 relay 捞出来发 MQ。好处是不依赖定时任务轮询,也不丢消息——本地事务成功就保证消息最终一定发出去。
取舍:MQ + 状态机是标配;Outbox 是当你被"消息丢了但库写了"这种坑过之后才体会到香的方案。
四、跨服务调用与补偿:加积分失败怎么办
常用做法
跨服务调用失败是常态。把失败的调用先记下来,定时任务周期性重试,成功就删、失败等下一轮;配合指数退避 + 死信队列(DLQ),避免无限重试拖垮下游。
不常用但有效
- Saga 模式:把"注册送积分"这种跨服务流程拆成一串本地事务,每步配一个补偿操作(正向创建、逆向撤销)。某步失败就按相反顺序回滚,比"要么全成要么全废"的两阶段提交更适合长流程。
- TCC(Try-Confirm-Cancel):比 Saga 更强一致,但侵入业务(要写 try/confirm/cancel 三套),一般只在金融级场景用。
- 幂等键(Idempotency-Key):客户端或网关生成,保证任何重试都不会重复执行——这是比"定时重试"更根本的防重复手段。
- 防羊毛:别只靠"唯一手机号":一个号绑一个手机号只是最弱一环。真实风控是设备指纹 + 图关联(同设备多账号)+ 风险评分模型 + 规则引擎(同 IP 段、同设备聚集)。手机号绑定拦得住小白,拦不住有接码平台的职业羊毛党。
- T+1 对账:两个系统跑批核对,差异自动修复,是最终一致性的最后一道保险。
五、数据规模:单表一千万之后
常用做法
顺序一般是:先加索引(多数慢查询是缺索引或索引失效),再读写分离(读走从库卸压力),真到极限才分库分表(按 user_id / 手机号 hash)。
不常用但有效
- CQRS:把写模型和读模型分开,读用专门优化的存储(物化视图、ES),写仍走主库。列表页、统计页不再和写入抢同一张表。
- 冷热分层:老数据归档到廉价存储(对象存储 / 列存),热数据留主库。很多"大表慢"其实只是冷数据占着地方。
- 专用存储替代 MySQL:全文检索走 ES,分析走 ClickHouse,时序走 TSDB——别什么都往关系库塞。
- 单表设计(如 DynamoDB):反范式、把关联拍平,靠 GSI 查询,彻底避免 join 和分片痛点,适合特定高并发场景。
- 分片键选错比不分更糟:按注册时间分片会造成"最新分片被狂写"的热点;按手机号 hash 才能打散。分片键是分库分表里最该想清楚的一件事。
六、密钥与加密:轮换、降级与格式
常用做法
字段加密 + 密钥版本号拼密文(v1:<密文>),解密时按前缀取对应版本密钥;密钥托管给 KMS,主密钥不出 KMS(信封加密:KEK 加密 DEK,DEK 加密数据)。
不常用但有效
- 应用层信封加密 + 本地 KEK 缓存:加密服务挂了怎么办?常用说法是"只能靠运维"。更具体的兜底是把 KEK 在应用本地做短缓存 + 熔断器,服务抖动时本地仍能解密,撑过那几秒——这不是认输,是明确的降级路径。
- HSM(硬件安全模块):密钥永不离开硬件,合规要求高的金融场景标配。
- 格式保留加密(FPE):加密后保持原格式(手机号还是 11 位),现有校验、模糊查询不用改就能跑,又不泄露明文。
- 字段级代理加密:在 DB 代理层(如 CipherTrust、ProxySQL 插件)透明加解密,业务代码零侵入——比"每个字段手搓加解密"干净得多。
- Shamir 秘密分享:主密钥分片给多人/多机,重建需达到阈值,避免单点泄露或被单人挟持。
取舍:KMS + 版本号是及格线;FPE 和代理加密在你"既要加密又要能按原字段查"时才值得上。
七、多机房:同步、冲突与单元化
常用做法
主从复制 + 最终一致,登录态和用户主数据交给**统一身份认证(SSO / IdP)**托管,各机房向它要,而不是机房之间互相迁就对账。
不常用但有效
- CDC(Change Data Capture,如 Debezium):捕获数据库 binlog 跨机房同步,比应用层"双写"可靠得多——双写任一步失败就会不一致,CDC 只认 binlog 这一个事实源。
- Active-Active + CRDT / 冲突解决:两地同时可写,用无冲突复制数据类型(CRDT)或 LWW + 向量时钟解决冲突,而不是"主写从读"。代价是高复杂度和可能的冲突窗口。
- 单元化(Unit Architecture):按用户维度切"单元",每个单元自包含(自己的 DB、缓存、服务),故障爆炸半径被限制在一个单元内。蚂蚁、字节的大规模系统大多走这条路。
- 用户路由 + 数据局部性:用户在哪个单元注册,后续请求都路由到那,天然避免跨单元读写——很多"跨机房同步难"的问题,从架构上就绕开了。
八、一张表:常用 vs 不常用但有效
| 主题 | 常用做法 | 不常用但有效 |
|---|---|---|
| 限流 | 令牌桶/滑动窗口 + Redis + Lua,IP 维度 | 自适应限流、设备指纹配额、PoW 挑战 |
| 异步通知 | MQ + 状态机 + 重试 | Transactional Outbox、幂等消费、webhook 回调、独立通知服务 |
| 跨服务 | 失败记录 + 定时重试 + DLQ | Saga 补偿、TCC、幂等键、风控图谱、T+1 对账 |
| 数据规模 | 索引 → 读写分离 → 分库分表 | CQRS、冷热分层、专用存储、单表设计、分片键优化 |
| 密钥加密 | KMS + 信封加密 + 版本号 | 本地 KEK 缓存降级、HSM、FPE、代理加密、Shamir 分片 |
| 多机房 | 主从 + 最终一致 + SSO | CDC、Active-Active+CRDT、单元化、数据局部性路由 |
九、收尾
CRUD 不是后端的全部,它只是入口。真正拉开系统之间差距的,是上面这些围绕 CRUD 的机制——限流、异步、补偿、分片、加密、多活。一个主题里,"常用做法"让你及格,"不常用但有效"的那一档,往往是系统撞到真实规模或真实攻击之后,才会被逼出来、也才真正显出价值的东西。
参考资料
- 知乎原问答(本文问题线索的灵感来源):后端开发除了增删改查还有什么? - 回答作者「花宝宝」

