商城系统回顾:从0到1的技术选型总结

商城系统从立项到上线,技术选型和架构演进是核心环节。以下是从工程实践角度总结的技术选型经验和常见陷阱。

技术栈选择

中等规模商城系统的典型需求:日活 5k~2w、SKU 量级在万级别、需要全文搜索和实时库存。基于这些约束,推荐的技术栈:

  • 后端语言:Go。Go 在高并发场景的资源占用比 Spring Boot 低很多,单机 QPS 轻松上万。对于有 Java 经验的开发者,Go 的上手成本很低。不过 Go 的生态(特别是 ORM)跟 Java 比差距明显,GORM 在复杂查询场景下比较难用,建议考虑 sqlx 或 sqlc。
  • 主数据库:PostgreSQL。选 PG 而不是 MySQL 主要是看中了 JSONB 类型——商品的扩展属性直接存 JSONB,避免了 EAV 模型的复杂性。SKU 属性查询的开发效率提升很多。
  • 缓存:Redis。主要用于商品详情缓存、库存预扣、分布式锁、购物车。
  • 搜索:Elasticsearch。商品搜索、筛选、聚合都走 ES。中文分词是常见痛点——默认的 ik_smart 在电商场景下效果可能不理想,需要调分词词典和同义词配置。
  • 消息队列:Kafka。订单状态变更、库存扣减、日志收集都走 Kafka。选 Kafka 而不是 RabbitMQ 是因为吞吐量需求和日志场景。

架构演进:单体到微服务

项目一开始用单体架构是合理的。随着业务发展,可能出现以下问题:

  1. 部署耦合:改个小接口,整个系统都要重新部署
  2. 数据库连接数:所有模块共享连接池,高峰期连接数容易打满
  3. 并发冲突:特定场景下锁竞争严重

拆分微服务时,按业务域拆分是常见策略:用户服务、商品服务、库存服务、订单服务、支付服务、搜索服务。服务间通信用 gRPC,网关用 Kong。

拆分过程中最困难的是数据拆分。单体时代大量 JOIN 查询,拆成独立数据库后全部要改成服务调用。延迟通常会明显增加,需要靠本地缓存和数据冗余来优化。

建议:不要过早拆分微服务。等到单体出现明确的性能瓶颈后再拆,而不是「预防性」地拆分。对于中小规模项目,单体完全够用。

常见陷阱

1. Redis 缓存与数据库的一致性

「先删缓存再更新数据库」在并发场景下会出现脏读。推荐「先更新数据库,再删缓存」+ 延迟双删。极端场景下可以加 Canal 监听 binlog 来兜底。

2. 分布式事务

订单创建涉及库存扣减、优惠券核销、积分抵扣,跨了多个服务。Go 生态的 Seata 客户端不够成熟,Saga 模式是更可靠的选择——每个服务提供正向操作和补偿操作,由订单服务编排。开发量大但可靠性有保障。

3. ES 索引设计

把所有商品字段都塞进 ES 索引会导致写入性能差,且频繁变更 mapping 需要不停地 reindex。推荐只索引搜索和筛选需要的字段,详情数据回查 PG。

4. Kafka 消费者的幂等性

Kafka 消费者重启后可能重复消费消息,导致业务数据异常。所有消费者都应加幂等校验——基于消息 ID 做去重,业务操作前先查状态。

工程建议

  1. ORM 的选择。GORM 在复杂查询场景下较难用,sqlx 或 sqlc 直接写 SQL 反而更可控。
  2. 尽早引入 APM。排查性能问题靠日志 grep 非常低效。OpenTelemetry + Jaeger 可以显著提升排查效率,建议从项目初期就接入。
  3. 微服务不要拆太细。服务过多会增加服务间调用开销和运维负担。
  4. 前端技术栈。Vue 3 的 Composition API 和 TypeScript 支持比 Vue 2 好很多,新项目建议直接用 Vue 3。

总结

Go + PG + Redis + ES + Kafka 这套栈在中等规模的电商场景下表现不错。Go 的开发效率不如 Java/Spring Boot 那么高(生态差距),但运行时的资源效率优势明显。对于有 Java 或 Python 背景的开发者,Go 是一个务实的选择。