技术探索与实践:在复杂业务中走出一条路来

勇敢艺术家
2025-06-28 10:08
阅读 552

开篇:技术,总要“有点意思”

开篇:技术,总要“有点意思”

我入行做开发已经有七八年了,从最初懵懵懂懂地写 CRUD 到现在独立负责一个完整的后端系统架构设计,中间经历了各种踩坑、试错和重构。一路走来,最深的感受就是:技术不是拿来炫耀的,而是用来解决实际问题的

这篇文章想分享一次我在参与公司核心项目时的真实经历——那个让我连续熬了好几个大夜的分布式任务调度系统搭建。这段经历不仅让我重新审视了自己的技术能力,也让我更深入理解了什么叫做“技术为业务服务”。


背景:业务增长带来的压力升级

背景:业务增长带来的压力升级

技术应用场景-2

我们公司是一个在线教育平台,主要产品是面向 K12 阶段学生的线上直播课程系统。随着用户量逐渐突破百万级别,我们后台的服务压力也开始显现。特别是在每天晚上 7 点到 9 点的上课高峰期,会有大量定时任务(比如课前提醒推送、课程数据汇总、作业分发等)集中触发,导致任务执行延迟严重、失败率飙升。

当时我们的任务调度还是采用老式的 Quartz 单机部署方案,虽然在小规模下表现还不错,但随着业务量上升,明显已经力不从心:

  • 并发能力有限,无法支持高吞吐场景;
  • 任务状态难以追踪,出错排查困难;
  • 资源利用率低,经常出现部分节点空闲而其他节点过载;
  • 部署环境单一,扩展性差,维护成本高。

这已经不是简单优化可以解决的问题,我们需要一套新的、能够支撑未来几年发展的任务调度系统。


挑战:如何选型?怎么落地?

挑战:如何选型?怎么落地?

我们要做的第一件事就是评估技术选型。团队内部做了几轮讨论,最终把范围缩小到以下几个主流方案:

  1. Quartz 分布式改造
  2. XXL-JOB
  3. Elastic-Job-Lite
  4. Airflow(Python生态)
  5. 自研调度框架

技术选型背后的考量

当时我们有几个硬性要求:

  • 支持分布式部署
  • 能够动态扩缩容
  • 提供可视化管理界面
  • 可监控任务状态和日志
  • 不影响现有业务逻辑迁移成本
  • 社区活跃,文档齐全,维护成本低

最终,我们在调研对比之后选择了 XXL-JOB。原因如下:

  • 成熟度高,社区活跃,中文文档丰富
  • 基于 Spring Boot 构建,对 Java 生态友好
  • 调度中心 + 执行器的设计模式便于横向扩展
  • 提供 Web 页面,易于运维同学查看日志和调整配置

尽管 ElasticJob 功能也很强大,但在可视化操作和易用性上略逊一筹,且对 ZK 的依赖让运维有些顾虑。至于 Airflow,虽然它在大数据领域非常流行,但我们是以微服务为核心架构,不太适合混用过多语言栈。


实施过程:从单体到分布式

实施过程:从单体到分布式

确定使用 XXL-JOB 后,我们开始着手搭建整个系统。这里我介绍一下整体架构和关键步骤:

架构概览

我们将原来的 Quartz 单体任务迁移到基于 XXL-JOB 的分布式调度体系中。整体架构如下:

[调度中心] <--> [多个执行器节点] <--> [业务服务]
  • 调度中心:部署为独立服务,用于统一管理和调度任务;
  • 执行器:每个微服务部署一个执行器节点,负责接收调度并调用本地接口;
  • 业务服务:原有的业务逻辑无需改动,只是通过 XXL-JOB 提供的注解方式接入任务入口。

核心代码实现

以下是接入 XXL-JOB 后的任务示例代码,结构清晰,易于维护:

@Component
public class CourseTaskHandler {
    
    @XxlJob("courseReminderJob")
    public void courseReminderJob() {
        XxlJobHelper.log("【课前提醒任务】开始执行...");

        try {
            // 调用具体业务方法
            List<Lesson> lessons = lessonService.getUpcomingLessons();
            for (Lesson lesson : lessons) {
                messageService.sendLessonReminder(lesson.getUserId(), lesson.getTitle());
            }

            XxlJobHelper.handleSuccess(); // 成功返回
        } catch (Exception e) {
            XxlJobHelper.handleFail(); // 异常标记失败
            log.error("课前提醒任务执行失败", e);
        }
    }
}

每个任务函数通过 @XxlJob 注解声明,并由 XXL-JOB 自动注册到调度中心。调度中心会根据配置策略(如故障转移、一致性哈希等)将任务派发给合适的执行器节点。


踩坑记录:别以为搭起来就万事大吉

虽然 XXL-JOB 本身很成熟,但在实际落地过程中,我们也踩了不少坑:

1. 调度中心频繁假死

现象: 在高峰期调度中心响应变慢,甚至出现页面卡顿或无法访问。

原因分析: XXL-JOB 默认使用数据库作为任务信息存储和状态同步媒介。在并发任务激增的情况下,频繁读写 MySQL 导致性能瓶颈。

解决思路:

  • 将调度中心的数据存储改为 Redis 缓存 + 定期落盘(需要自己实现中间适配层)
  • 优化 SQL 查询,添加索引
  • 增加调度中心的实例,做负载均衡(前后端分离部署)

2. 执行器节点重复拉取任务

现象: 多个节点同时执行相同任务,造成数据重复处理。

根本原因: 默认调度策略使用的是广播模式,未设置任务幂等机制。

修复方式:

  • 在任务触发前加入分布式锁,如 Redis SETNX 或 Zookeeper 临时节点
  • 使用唯一业务标识(如任务 ID + 时间戳)去重
String lockKey = "lock:job:" + jobId;
if (redisTemplate.opsForValue().setIfAbsent(lockKey, "locked", 60, TimeUnit.SECONDS)) {
    try {
        // 执行业务逻辑
    } finally {
        redisTemplate.delete(lockKey);
    }
} else {
    XxlJobHelper.handleSuccess(); // 视为已执行,跳过本次
}

3. 任务日志查询慢、页面加载卡顿

问题: Web 页面打开某个任务的日志时非常卡顿,尤其当任务运行次数多时。

分析: 日志文件是直接写入数据库的表中,查询没有分页也没有缓存。

改进措施:

  • 引入 ELK(ElasticSearch + Logstash + Kibana)进行日志采集和检索
  • 前端对接日志服务 API,按时间段/关键字筛选
  • 旧日志定期归档压缩,减少主表数据量

效果总结:效率提升看得见

技术概念图解-1

这套系统上线半年后,我们统计了一波数据,结果让我们非常满意:

指标 上线前 上线后
任务平均执行耗时 4s 1.2s
并发任务承载数 200 1500+
任务失败率 3.8% 0.1%
排查耗时(次均) 2h 15min
支持任务类型 固定 支持脚本、Java、HTTP等多种

最直观的变化是:过去每天都要收到运营反馈“有学生没收到通知”,现在几乎听不到这类问题了。

而且运维同学也反馈说,“现在的任务状态一看就明白,出了问题也能快速定位。”这对整个系统的稳定性贡献很大。


经验与建议:技术落地要有“人味儿”

在整个项目推进的过程中,我总结了一些经验,希望对读者有所帮助:

1. 不要为了新技术而用新技术

很多时候我们容易被新名词吸引,比如“xx 微服务架构”、“xx 中台方案”等等。但这些是否真的适合你当前的业务场景?是否有足够的团队支撑和学习成本?这些都是必须权衡的点。

我们之所以选择 XXL-JOB,而不是盲目追求更加“先进”的 Airflow 或者 Apache DolphinScheduler,是因为它贴合我们现有的 Java 技术栈,且能快速上手,风险可控。

2. 技术方案要考虑可扩展性和演进路径

一个技术组件好不好用,不仅是看它今天能不能跑通,更要考虑将来业务发展之后,这个组件能否支撑得住。

比如说,我们现在在计划引入调度工作流,这样就可以实现任务之间的依赖控制和异常重试策略。这就对调度框架提出了更高要求,XXL-JOB 目前还不能完全满足,所以我们会逐步向 DolphinScheduler 过渡。

3. 工程化思维比代码能力更重要

在团队协作中,我发现很多程序员只关心“功能能不能做出来”。其实真正拉开差距的,是你有没有考虑测试覆盖率、部署自动化、日志规范、错误报警机制等等这些看似“非核心”的部分。

这些看似繁琐的工作,才是构建稳定可靠系统的基石。

4. 保持开放心态,敢于试错

这次项目里我们也尝试了很多不同的方案,比如一开始考虑过自研调度框架,后来发现时间成本太高,果断放弃;又比如尝试过引入 Nacos 作为注册中心,但由于历史包袱太大最后作罢。

试错不可怕,可怕的是不愿意尝试,固步自封。


写在最后:技术的本质,是对世界的好奇

这篇文章写了这么久,可能大家会觉得有点长。但我想说,这是真实的成长轨迹。

我始终相信,技术探索不是空中楼阁,而是扎根在业务土壤里的种子。每一次难题的攻克,不只是解决问题的过程,更是认知边界的拓展和能力的锤炼。

如果你问我:“你为什么喜欢技术?”我会说:“因为我觉得搞清楚一件事为什么会发生,是一件特别爽的事情。”

这个世界总是在变,唯有不断学习才能不被淘汰。希望这篇文章能给你一些启发,哪怕一点点也好。

如果你也在做类似的任务调度、分布式系统相关的工作,欢迎留言交流,我们可以一起踩更多有意思的坑。😊

评论 0

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