商城系统从立项到上线,技术选型和架构演进是核心环节。以下是从工程实践角度总结的技术选型经验和常见陷阱。
技术栈选择
中等规模商城系统的典型需求:日活 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 是因为吞吐量需求和日志场景。
架构演进:单体到微服务
项目一开始用单体架构是合理的。随着业务发展,可能出现以下问题:
- 部署耦合:改个小接口,整个系统都要重新部署
- 数据库连接数:所有模块共享连接池,高峰期连接数容易打满
- 并发冲突:特定场景下锁竞争严重
拆分微服务时,按业务域拆分是常见策略:用户服务、商品服务、库存服务、订单服务、支付服务、搜索服务。服务间通信用 gRPC,网关用 Kong。
拆分过程中最困难的是数据拆分。单体时代大量 JOIN 查询,拆成独立数据库后全部要改成服务调用。延迟通常会明显增加,需要靠本地缓存和数据冗余来优化。
建议:不要过早拆分微服务。等到单体出现明确的性能瓶颈后再拆,而不是「预防性」地拆分。对于中小规模项目,单体完全够用。
常见陷阱
1. Redis 缓存与数据库的一致性
「先删缓存再更新数据库」在并发场景下会出现脏读。推荐「先更新数据库,再删缓存」+ 延迟双删。极端场景下可以加 Canal 监听 binlog 来兜底。
2. 分布式事务
订单创建涉及库存扣减、优惠券核销、积分抵扣,跨了多个服务。Go 生态的 Seata 客户端不够成熟,Saga 模式是更可靠的选择——每个服务提供正向操作和补偿操作,由订单服务编排。开发量大但可靠性有保障。
3. ES 索引设计
把所有商品字段都塞进 ES 索引会导致写入性能差,且频繁变更 mapping 需要不停地 reindex。推荐只索引搜索和筛选需要的字段,详情数据回查 PG。
4. Kafka 消费者的幂等性
Kafka 消费者重启后可能重复消费消息,导致业务数据异常。所有消费者都应加幂等校验——基于消息 ID 做去重,业务操作前先查状态。
工程建议
- ORM 的选择。GORM 在复杂查询场景下较难用,sqlx 或 sqlc 直接写 SQL 反而更可控。
- 尽早引入 APM。排查性能问题靠日志 grep 非常低效。OpenTelemetry + Jaeger 可以显著提升排查效率,建议从项目初期就接入。
- 微服务不要拆太细。服务过多会增加服务间调用开销和运维负担。
- 前端技术栈。Vue 3 的 Composition API 和 TypeScript 支持比 Vue 2 好很多,新项目建议直接用 Vue 3。
总结
Go + PG + Redis + ES + Kafka 这套栈在中等规模的电商场景下表现不错。Go 的开发效率不如 Java/Spring Boot 那么高(生态差距),但运行时的资源效率优势明显。对于有 Java 或 Python 背景的开发者,Go 是一个务实的选择。