聊聊技术探索与实践:一个传统企业后端开发的碎碎念

动态规划狗
2025-12-19 11:39
阅读 2024

大家好,我是老张(不是那个卖课的老张),在一家传统制造业干了三年多Java后端。每天早上八点打卡进办公室,咖啡还没喝完,产品经理已经在群里@我:“这个需求今天能上线吗?”——别问,问就是双11压测又崩了,运维兄弟凌晨三点发朋友圈:“谁改的缓存策略?Redis内存打满啦!”

最近其实有点躁动。待了三年多,系统还是那套Spring Boot + MyBatis + Oracle的“祖传三件套”,业务逻辑写得跟意大利面条似的,连DTO和VO都混着用。眼看同届的哥们跳槽去互联网公司拿30K+,我连LeetCode都没刷满50题……于是今年年初,咬咬牙决定:不能只做CRUD Boy了,得搞点技术深度出来

这篇文章,就是我在“被迫成长”过程中的一些实践总结,也顺便整理些面试时被问到的技术点——毕竟,想跳槽,总得有点能吹的资本不是?


从一次线上事故说起:缓存穿透 vs 缓存雪崩

去年双11前一周,我们的商品详情页突然QPS飙到10万+(平时才3k)。结果呢?数据库CPU直接拉满,页面一片502。查日志发现,大量请求打到了根本不存在的商品ID上——典型的缓存穿透

我当时第一反应是加布隆过滤器(Bloom Filter),但运维说:“你这玩意儿占内存,而且重启就丢数据,不行。”
行吧,那就先用空值缓存兜底:

public Product getProduct(Long id) {
    String key = "product:" + id;
    Product product = redisTemplate.opsForValue().get(key);
    if (product != null) {
        return product; // 命中缓存
    }
    
    // 查数据库
    product = productMapper.selectById(id);
    if (product == null) {
        // 空值也缓存,防止穿透,TTL设短一点比如60s
        redisTemplate.opsForValue().set(key, EMPTY_PLACEHOLDER, Duration.ofSeconds(60));
        return null;
    }
    
    redisTemplate.opsForValue().set(key, product, Duration.ofHours(2));
    return product;
}

但问题没完。第二天测试同学压测时,故意把Redis干挂了。结果所有流量瞬间涌向DB,连接池爆了——这就是缓存雪崩

后来我们做了两件事:

  1. 本地缓存兜底:用Caffeine做二级缓存,即使Redis挂了,还能撑几秒。
  2. 熔断降级:集成Sentinel,当DB响应时间超过500ms,直接返回兜底数据或友好提示。

面试题来了:缓存穿透、击穿、雪崩的区别是什么?怎么解决?
别背八股文!结合你真实项目说:比如“我们在XX系统用了空值缓存 + 布隆过滤(后来换成Caffeine本地缓存)+ Sentinel熔断,上线后事故率降了90%”。


架构演进:从单体到模块化,再到微服务?

我们公司系统是典型单体架构,一个WAR包部署在WebLogic上(没错,还在用WebLogic!)。新需求一来,改个订单状态,结果把支付模块搞挂了——因为代码全在一个Git仓库里,依赖乱成一锅粥。

领导说:“要不拆微服务?”
我内心OS:“您知道Consul、Nacos、Seata这些组件维护成本有多高吗?咱们团队就5个后端,俩还在学Spring Cloud!”

于是我们折中:先做模块化拆分,不拆部署。用Maven多模块 + 领域驱动设计(DDD)的思想,把代码按业务边界切开:

order-service/
├── order-api     // 对外暴露的接口(Feign Client)
├── order-domain  // 核心领域模型、业务规则
├── order-infrastructure // DB、MQ、Redis等基础设施
└── order-application // 应用层,协调领域对象

好处立竿见影:

  • 新人接手订单模块,不用看整个项目
  • 单元测试覆盖率从30%提到70%
  • 重构时敢动代码了(以前改一行,提心吊胆)

开发心得:微服务不是银弹。在资源有限的传统企业,先做好代码内聚和解耦,比盲目上微服务更实际。等哪天你们团队有专职SRE、有完善的CI/CD流水线,再考虑拆也不迟。


面试官最爱问的:你怎么保证接口幂等性?

上个月面试某大厂,二面直接甩我一道题:“用户重复点击支付按钮,怎么防止重复扣款?”

我差点脱口而出:“加个唯一请求ID啊!”
但转念一想,这太浅了。于是我说了我们在项目中的做法:

  1. 前端:按钮点击后置灰 + 请求带requestId(UUID)
  2. 后端
    • 先查Redis是否存在pay:dedup:{requestId}
    • 存在 → 直接返回上次结果
    • 不存在 → 执行业务逻辑,完成后写入Redis(带过期时间)

关键代码:

public PayResult pay(PayRequest request) {
    String dedupKey = "pay:dedup:" + request.getRequestId();
    
    // 尝试获取锁(原子操作)
    Boolean isAbsent = redisTemplate.opsForValue().setIfAbsent(
        dedupKey, "processing", Duration.ofMinutes(5)
    );
    
    if (!Boolean.TRUE.equals(isAbsent)) {
        // 说明正在处理 or 已处理完成
        PayResult cached = getFromCache(dedupKey); // 实际项目中会存完整结果
        if (cached != null) return cached;
        throw new BizException("请勿重复提交");
    }
    
    try {
        PayResult result = doPay(request); // 执行真实支付
        // 成功后更新缓存为最终结果
        redisTemplate.opsForValue().set(dedupKey, result, Duration.ofHours(24));
        return result;
    } catch (Exception e) {
        // 失败也要清理?不!保留"processing"状态防止重试风暴
        throw e;
    }
}

注意:这里故意没在异常时删除key,是为了防止短时间内大量重试把系统打垮——这是我们在一次线上事故中学到的教训。


关于代码质量:那些年我踩过的坑

在传统企业,老板最关心的是“功能能不能跑”,而不是“代码好不好”。但作为有点追求的开发者,我还是坚持了几条底线:

1. 拒绝魔法值

// bad
if (status == 1) { ... }

// good
if (orderStatus == OrderStatus.PAID) { ... }

2. 异常不要吞!

见过太多同事这么写:

try {
    sendEmail(...);
} catch (Exception e) {
    // 啥也不干
}

结果用户收不到通知邮件,问题排查三天。现在我们团队强制要求:所有catch必须记录日志 or 抛出业务异常

3. 单元测试不是摆设

用JUnit 5 + Mockito + AssertJ,覆盖核心业务路径。比如订单状态机流转:

@Test
void shouldFailWhenCancelPaidOrder() {
    Order order = new Order();
    order.setStatus(OrderStatus.PAID);
    
    assertThatThrownBy(() -> order.cancel())
        .isInstanceOf(IllegalStateException.class)
        .hasMessage("已支付订单不能取消");
}

性能优化:别再用SELECT * 了!

上周五晚上,测试环境慢得像蜗牛。查慢SQL发现:

SELECT * FROM t_large_table WHERE user_id = ? ORDER BY create_time DESC LIMIT 10;

这张表有2亿条数据,而且*包含了几个TEXT字段。光是网络传输就花了800ms。

优化后:

SELECT id, status, amount FROM t_large_table 
WHERE user_id = ? AND create_time > '2023-01-01'
ORDER BY create_time DESC LIMIT 10;

并加上联合索引 (user_id, create_time)

效果对比

优化项 响应时间 CPU使用率
优化前 820ms 75%
优化后 45ms 12%

开发心得:在传统企业,数据库往往是性能瓶颈。少查字段、加合适索引、避免大事务,这三招能解决80%的性能问题。


最后聊聊跳槽:技术探索的本质是解决问题

写这篇文章时,我已经投了十几份简历。面试官问得最多的是:“你在传统企业,技术栈是不是很老旧?”

我的回答是:技术不分新旧,关键看你怎么用

  • 我们用Oracle,但通过读写分离 + 分库分表(ShardingSphere)扛住了高并发
  • 我们没上K8s,但用Docker + Jenkins实现了自动化部署
  • 我们代码老旧,但我推动团队引入SonarQube做静态扫描,bug率下降40%

技术探索不是为了追新,而是为了解决手头的问题。当你能把一个看似普通的场景讲清楚“为什么这么做”、“踩了什么坑”、“效果如何”,你就已经超越了90%的候选人。


结语

三年多下来,我最大的感悟是:在传统企业做开发,既要低头写代码,也要抬头看路
别抱怨技术栈老,老系统里藏着最真实的业务复杂度;
别嫌弃需求杂,正是这些“脏活累活”逼你思考架构和抽象;
更别停止学习——哪怕每天只看半小时源码,一年后也会感谢自己。

下个月我就要去新公司报道了(终于逃离WebLogic!)。但这段经历我会一直带着:好的工程师,不是在完美的环境里写出优雅代码,而是在泥泞中依然坚持把事情做对

共勉。

评论 0

最热最新
暂无评论
动态规划狗Lv.1
0
影响力
0
文章
0
粉丝