技术探索与实践的一些思考
引言:从“能跑就行”到“可持续发展”

作为一名从业多年的全栈开发工程师,我经历过很多项目。有的项目是赶进度、堆功能,上线能跑就行;有的则是要兼顾性能、可维护性、团队协作和长期演进。随着经验的积累,我越来越意识到一个问题:技术上的每一个决策,最终都会反映在项目的整体质量和长期运营成本上。
今天这篇文章,我想结合我参与的一个实际项目,分享我在技术探索与实践中的一些思考。这个项目虽然没有轰动业界的技术突破,但它是真实的、接地气的,也踩过不少坑,最终也取得了不错的效果。
希望你能从中找到一些共鸣,或者至少,在你下次做技术选型时,多一个参考角度。
项目背景:一次电商系统的重构之路

事情发生在2021年,我当时在一家中型电商平台负责后端架构的优化工作。公司原本的系统已经运行了三年多,最初用的是 Spring Boot + MyBatis 做后端服务,前端是一个老旧的 Vue.js 单页应用,数据库是 MySQL 集群,部署在 AWS 上。整个系统单体结构,代码臃肿,模块耦合严重。
我们当时的业务需求主要有两个:
- 快速响应市场变化,增加新的促销玩法;
- 提高系统稳定性,减少因发布导致的服务不可用时间。
而现实情况是——每次发版都像一场赌博,谁也不知道新代码会不会触发某个隐藏的 bug,甚至有时候只是一个简单的配置改动,就能让整个服务崩溃。
于是我们决定进行一次彻底的重构。
技术挑战:如何平衡稳定性和创新?

挑战一:旧系统包袱太重
原系统用了大量的单例类和静态方法,很多业务逻辑混杂在 Controller 层,DAO 层直接暴露给多个模块调用,模块之间互相引用,形成网状依赖。
重构第一步就是拆分服务,实现模块解耦。但我们面临两难选择:是直接推翻重写?还是渐进式改造?
推翻重写风险太高,尤其是核心订单和支付流程不能出错。最后我们选择了渐进式重构策略:将关键功能封装为独立微服务,逐步替换原有逻辑。
挑战二:引入新技术带来的不确定因素
我们计划引入 Spring Cloud Gateway 替代原有的 Nginx 路由层,并尝试使用 Kafka 来异步处理下单事件,降低主流程压力。同时,考虑将部分读操作迁移到 Elasticsearch 以提升性能。
这些技术方案理论上都很合理,但在实际实施过程中,每个环节都出现了意想不到的问题。
比如:
- Spring Cloud Gateway 的熔断机制配置不当,导致流量激增时下游服务依然被压垮;
- Kafka 的消费积压问题,由于消费者线程数量不合理,大量消息堆积;
- Elasticsearch 的同步更新时机错误,出现数据不一致的情况。
这些问题让我深刻体会到一句话:“没有万能的架构,只有不断试错的过程。”
解决方案:架构演进与关键技术落地
经过几轮讨论和验证,我们制定了如下改进路线图:
1. 微服务拆分(按业务域划分)
我们将原来的 Monolith 拆分为以下几个服务:
- 用户中心(User Service)
- 商品中心(Product Service)
- 订单中心(Order Service)
- 支付中心(Payment Service)
- 库存中心(Inventory Service)
每个服务都有独立的 Git 仓库、部署流水线、数据库和缓存实例,真正实现了松耦合、强边界的设计原则。
我们采用 Spring Cloud Alibaba 的 Nacos 作为注册中心,Dubbo 实现内部通信,Feign + Open Feign 用于对外接口调用。虽然 Dubbo 是 RPC 框架更偏向于 Java 生态,但由于团队基本都是 Java 开发者,沟通成本较低,因此没有选择 gRPC 或 Thrift。
2. API 网关统一入口
我们弃用了之前手动配置的 Nginx+Lua 方案,改用 Spring Cloud Gateway,配合 Sentinel 做限流和熔断。这一块我们也经历了不少波折,后面会详细讲。
3. 异步解耦:Kafka 的引入
我们在下单流程中引入了 Kafka,将原来同步的库存扣减和优惠券核销操作改为异步消费。这一步大大缓解了主流程的压力,但也带来了新的问题——消息重复消费和幂等控制。
为此我们设计了一个基于 Redis 的幂等令牌机制。每次请求前生成一个唯一的 token 存入 Redis,并设置较短的有效期。消费者在处理消息前先检查是否已处理过该 token,如果存在则跳过。虽然增加了复杂度,但也确保了业务的正确性。
4. 搜索优化:Elasticsearch 接入
为了提升商品搜索体验,我们引入了 Elasticsearch 作为搜索引擎。通过 Canal 监听 MySQL binlog 数据库变更,实时更新 ES 索引。
初期我们没有合理规划索引结构,导致查询效率低下。后来我们做了以下优化:
- 使用 keyword 类型代替 text 进行精确匹配;
- 对搜索条件组合较多的字段使用 nested 类型;
- 设置合理的 refresh_interval 以避免频繁刷盘;
- 合理分配 shard 数量,防止资源浪费或性能瓶颈。
代码实践:关键组件示例
示例一:Kafka 消费者幂等处理
@Component
public class OrderConsumer {
private final RedisTemplate<String, String> redisTemplate;
private final OrderService orderService;
public OrderConsumer(RedisTemplate<String, String> redisTemplate, OrderService orderService) {
this.redisTemplate = redisTemplate;
this.orderService = orderService;
}
@KafkaListener(topics = "order-topic")
public void consume(ConsumerRecord<String, String> record) {
String orderId = record.value();
String key = "processed_order:" + orderId;
Boolean isProcessed = redisTemplate.opsForValue().setIfAbsent(key, "1", 5, TimeUnit.MINUTES);
if (isProcessed == null || !isProcessed) {
// 已经处理过了,跳过
return;
}
try {
orderService.processOrder(orderId);
} catch (Exception e) {
// 记录日志,重新放回 Kafka 或其他补偿措施
log.error("Failed to process order: {}", orderId, e);
redisTemplate.delete(key); // 删除标记以便重试
}
}
}
这段代码的核心在于利用 Redis 的 setIfAbsent 方法实现幂等判断。注意有效期设置和异常处理逻辑,否则会导致永久性的跳过或无限重试问题。
示例二:Spring Cloud Gateway 配置限流规则(Sentinel)
我们采用了 Sentinel 配合 Spring Cloud Gateway 实现限流,核心配置如下:
spring:
cloud:
gateway:
routes:
- id: order-service
uri: lb://order-service
predicates:
- Path=/api/order/**
filters:
- StripPrefix=1
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 100
redis-rate-limiter.burstCapacity: 200
rate-limiter: ${spring.application.name}_rate_limiter
此外需要添加 Sentinel 的自动配置类,以及初始化 Dashboard 控制台,这里就不贴完整代码了。有兴趣可以看官方文档或我的 GitHub 示例项目。
踩坑经验:那些看似简单却容易掉进的陷阱
1. Spring Cloud Gateway 与熔断器的误解
一开始我们认为只要加个 Hystrix 就能解决问题,但真实环境中发现当并发很高时,Hystrix 可能会在短时间内触发大量 fallback,反而加重系统负担。后来我们换成了 Sentinel,它提供了更灵活的限流规则和热词探测能力。
教训是:熔断不是万能的,一定要根据业务场景设定合适的阈值。
2. Kafka 消息堆积怎么查?
有一次生产环境发现 Kafka topic 有十几万的消息积压。我们排查发现是因为消费者的并发数设置太小,而且消费速度受限于外部接口的响应时间。
解决方式:提升了消费者并发数,并引入线程池异步调用外部接口,最终解决了堆积问题。
建议:监控 Consumer Lag 是必须做的运维动作之一。
3. Elasticsearch 索引结构设计失误
初期没有对业务查询模式进行充分分析,导致某些复杂的 filter 查询响应很慢。后来调整了 mapping 设计,将部分字段设为 keyword 类型,并合理使用 should/should_not 进行查询优化。
效果总结:重构后的收益
经过大约半年的时间,重构工作基本完成。我们得到了以下成果:
| 指标 | 改造前 | 改造后 |
|---|---|---|
| 发布频率 | 每周最多 1 次 | 每天多次 |
| 平均响应时间 | ~800ms | ~300ms |
| 错误率 | ~1% | <0.1% |
| 部署耗时 | >30min | <5min |
另外,我们还建立了完善的自动化测试体系和灰度发布机制,显著降低了线上事故的概率。
经验分享:给正在路上的你们
1. 技术决策必须服务于业务目标
不要为了用新框架而用新框架。例如,我们最终没有使用 gRPC,因为团队熟悉程度和调试工具支持都不够成熟。选择合适的技术比追求流行更重要。
2. 架构不是一开始就完美的,而是迭代出来的
我们最初的微服务粒度过细,后来又合并了一些职责相近的服务。这是完全正常的。保持架构的灵活性和演化能力远比一开始设计得多么完美更重要。
3. 关注可观测性
在重构期间,我们投入了很多精力搭建监控体系(Prometheus + Grafana)、链路追踪(SkyWalking)和日志聚合(ELK)。这些基础建设在后期排障、性能优化中起到了至关重要的作用。
4. 不要把所有鸡蛋放进一个篮子里
例如缓存方面,我们同时用了 Redis 和本地 Caffeine 缓存。Redis 处理跨服务共享数据,Caffeine 用于高频读取的本地快速访问,两者互为补充。
5. 团队共识与持续学习同样重要
技术改革不仅关乎代码,更是组织文化的变革。我们每周开一次“技术午餐会”,分享新学的技术,轮流讲解最佳实践。这种氛围极大地促进了团队成长。
写在最后:技术人的自我修行
回头看这一段重构之旅,其实过程远比结果更有意义。每一次失败的背后,是团队的成长;每一段深夜debug的经历,是对自己意志的磨砺。
在这个技术日新月异的时代,我们永远在追逐下一个新技术的路上。但别忘了,真正的技术能力,是在有限条件下做出最优决策的能力。
希望这篇文章能给你带来一点启发,也许某一天你在某个技术路口上犹豫不决时,还记得有个同行者曾在这条路上踩过坑、流过汗,最终走了出来。
共勉!

评论 0