技术探索与实践的一些思考

Tech工程师
2025-06-22 23:24
阅读 364

引言:从“能跑就行”到“可持续发展”

引言:从“能跑就行”到“可持续发展”

作为一名从业多年的全栈开发工程师,我经历过很多项目。有的项目是赶进度、堆功能,上线能跑就行;有的则是要兼顾性能、可维护性、团队协作和长期演进。随着经验的积累,我越来越意识到一个问题:技术上的每一个决策,最终都会反映在项目的整体质量和长期运营成本上。

今天这篇文章,我想结合我参与的一个实际项目,分享我在技术探索与实践中的一些思考。这个项目虽然没有轰动业界的技术突破,但它是真实的、接地气的,也踩过不少坑,最终也取得了不错的效果。

希望你能从中找到一些共鸣,或者至少,在你下次做技术选型时,多一个参考角度。


项目背景:一次电商系统的重构之路

项目背景:一次电商系统的重构之路

事情发生在2021年,我当时在一家中型电商平台负责后端架构的优化工作。公司原本的系统已经运行了三年多,最初用的是 Spring Boot + MyBatis 做后端服务,前端是一个老旧的 Vue.js 单页应用,数据库是 MySQL 集群,部署在 AWS 上。整个系统单体结构,代码臃肿,模块耦合严重。

我们当时的业务需求主要有两个:

  1. 快速响应市场变化,增加新的促销玩法;
  2. 提高系统稳定性,减少因发布导致的服务不可用时间。

而现实情况是——每次发版都像一场赌博,谁也不知道新代码会不会触发某个隐藏的 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

最热最新
暂无评论
匿名用户Lv.1
0
影响力
0
文章
0
粉丝