边界判断缺失

news2025/2/27 2:50:58

作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO

联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬

学习必须往深处挖,挖的越深,基础越扎实!

阶段1、深入多线程

阶段2、深入多线程设计模式

阶段3、深入juc源码解析

阶段4、深入jdk其余源码解析

阶段5、深入jvm源码解析

事故场景

我们在做需求开发时,经常会遇到一些边界条件的判断:

  • 查询身高大于180cm、年龄大于18岁的学生
  • 筛选出公司P7以上的程序员

大多数时候,我们只需要把产品语言转化为程序语言即可:

SELECT * FROM t_student WHERE height>180 and age>18;
SELECT * FROM t_employee WHERE level>=7;

但产品语言与程序语言并不完全等同,直接翻译有时会引发意想不到的BUG。

假设现在有一个需求:

  • 下单24小时以后,为用户发放奖励

一般来说,我们可以采用定时任务完成这个需求,具体做法是:

  • 用户下单后,在order_task表插入一条记录(为了方便记忆,type和status都用字符串代替):
# 一张任务表不止一种类型的任务,所以用type区分,和当前需求关系不大
INSERT INTO order_task (order_id, task_type, task_status, gmt_create, gmt_modify) 
VALUES(10000001, 'complete_order_prize', 'wait', 'xxxx-xx-xx', 'xxxx-xx-xx');
  • 定时任务扫描需要发放下单奖励的任务、为用户发放奖励、更新任务状态:
# 伪代码 86400=24*60*60
SELECT * FROM order_task WHERE order_type='complete_order_prize' and task_status='wait' and gmt_create < now-86400;

注意时间条件,由于产品需求是下单24小时后才发放奖励,所以要满足条件:gmt_create < now-86400。

正常来说,上面的做法是没有问题的。但是,昨天产品找我说,用户反馈自己一个月以前的3笔订单奖励至今未发放。

这位产品很优秀,比较懂开发,甚至自己会写SQL,他的第一反应是定时任务挂了。

好了,现在问题出现了,如果是你,打算怎么开始排查呢?

解决方案

先介绍一下背景,我们公司由于业务调整,上面业务所在的平台已经很久没迭代,我抓包看了下,发现底层逻辑是PHP写的。于是找到对应的工程及代码,确认近期确实没有人更改过代码逻辑。

紧接着,我打开公司的定时任务平台,查看了该任务过去一个月的执行日志,发现全都执行失败了。

定时任务是否执行成功,其实有两个指标:

  • 调度成功
  • 执行成功

调度成功只能说明定时任务没问题,机器也没挂,但具体本次操作是否执行完毕,需要看执行结果。

从上图可以看到,定时任务每五分钟执行一次,可以调度成功(尽管也有调度失败的),但执行结果无一例外都是失败的。

这说明定时任务本身是好的,只是执行过程出了问题。查看定时任务的执行策略,我发现采用的是丢弃后续调度:

这是一种什么策略呢?举个例子,假设定时任务每隔5分钟执行一次,而上一个任务A本次执行超时了(超过5分钟还在执行),当下一个任务B启动时结果发现上一个任务还在进行中,那么任务B就会被丢弃,等待下次任务C执行。

那么,现在就有个矛盾摆在我们面前:

  • 定时任务是好的,但执行过程出问题了
  • 然而我们排查后,确定代码近期没有变更

代码没动过,定时任务也没挂,然而却没有达到预期。一个线上的功能好端端的突然崩了,无非几点原因:

  • 用户行为改变,恰好命中之前没测到的bug
  • 其他人发布新代码,对当前功能产生影响
  • 数据出问题了

订单奖励属于定时任务,不存在用户行为,这个原因基本可以排除。只有第二、第三个原因,其实归根到底都是数据问题,A功能要想影响B功能,一定是两者有共同的数据,也就是有共同的表。但不论哪种原因,我们可以得出结论:一个运行好几年的定时任务跑着跑着突然崩了,大概率是数据出问题了。

但我得出这个结论,却是在2小时后。一方面,恰好这个功能的PHP代码以及远程调用的Java服务都没有打印日志,对问题排查造成了很大的困难。由于系统已经基本停止维护,为了找到当前工程的日志,花了将近1小时。另一方面,电商系统太大了,只能上预发环境测试。等给工程打上日志、重新发布,已经过去10分钟。希望大家吸取教训,实际开发时在必要的地方打上日志,方便日后排查问题。

那么,最终是什么原因导致定时任务不执行了呢?

下面是一段PHP代码,做了简化处理,具体细节不用理解:

/**
 * 定时任务脚本 处理签到下单任务奖励金的实质发放
 * @return bool
 */
public function finishSignOrderTask() {
    load_module_model('xxx');
    $start_time = time() - 86400; // 24小时以后的订单

    // 查出符合条件的最老的一条的记录
    $oldest_record = $this->CI->xxx_sign_task->getWaitFinishTask(self::TASK_ID_ORDER_TASK, $start_time, \Xxx_sign_task::TASK_STATUS_COMPLETE, 'asc');
    if (empty($oldest_record)) {
        return TRUE;
    }
    $start = $oldest_record->id;

    // 查出符合条件最新的一条记录
    $end_id = $this->CI->xxx_sign_task->getWaitFinishTask(self::TASK_ID_ORDER_TASK, $start_time, \Xxx_sign_task::TASK_STATUS_COMPLETE, 'desc')->id;

    // 最新~最老记录之间都是需要执行的订单奖励,具体做法是 每次从中捞出100条执行(start_id + 100),直到退出(start_id+100>end_id)
    $page_size = 100;
    $xxxSignAwardService = XxxSignAwardService::getInstance();
    while (TRUE) {
        $where = [
            'id >= ' . $start,
            'id < ' . ($start + $page_size),
            'task_id = ' . self::TASK_ID_ORDER_TASK,
            'task_status < ' . \Xxx_sign_task::TASK_STATUS_FINISH,
            'gmt_begin < ' . $start_time,
        ];
        $records = $this->CI->xxx_sign_task->get_by_where($where, '*', 1, $page_size);
        if (!empty($records)) {
            foreach ($records as $record) {
                // 再次校验订单状态
                $order = $this->CI->xxx_order->get_by_oid($record->biz_value);
                if (empty($order)) {
                    $this->logger->error("oid_deal_not_found", [$record]);
                    // 订单不存在 关闭任务
                    $this->updateTaskStatus(FALSE, $record);
                    continue;
                }

                // 发放奖励...
            }
        }

        if ($start > $end_id) {
            break;
        }
        $start += $page_size;
    }

    return TRUE;
}

我在观察表数据时,发现了一个现象:最早一条处理失败的奖励任务竟然是2020年12月的,它的状态仍旧是task_status='wait'!

这会产生一个什么现象呢?(划线表示已执行)

  • 1000000 2020-12-27 任务1(start)
  • 1000001 2020-12-27 任务2
  • 1000002 2020-xx-xx 任务...
  • ...
  • 2000001 2021-04-27 任务N
  • ...(后续新的任务)
  • ...
  • 2100001 2021-06-04 任务Z(end)

由于上面PHP代码的做法是:查出start~end之间所有数据,并从start开始每次查询100条数据。随着时间越来越长,到了2021-04-27时,任务1和任务N之间id号已经差了100w,即使除以pageSize=100,也需要循环查1w次,而这1w次SQL查询是查不到数据的(因为之前被处理过了)!

换句话说,每次定时任务开始,虽然目标是执行2021-04-27 ~ 2021-06-04之间的奖励任务,但却不得从2020-12-27的数据开始,以id+100的形式查询1w次后,才能到达任务N。

注意,while并不是空转,而是真的去查数据库了!空转1w次根本不算什么,但循环查询数据库1w次是不可接受的,即使分页查询一次200ms,整个过程也要耗时2000s,要30多分钟,而定时任务频率为5分钟。然而耗时长并不是任务失败的主要原因,最关键的是单任务循环次数过多导致日志输出及内存占用增加,最终每次还没执行到需要的数据,PHP自己就先挂了。

解决方法是,查询奖励任务时,加一个边界判断:下单24小时后,且最近3个月内的订单。

至于为什么2020年的那个任务一直没被执行掉,是因为它的任务流水不知为何被删除了(或者当初插入失败了),而Java远程服务会做流水校验,如果流水为空则抛异常跳过本次执行,所以2020年的那个数据躲过了一轮又一轮的定时任务,最终出现在2021年的某一天,突然抡起一锤子砸向了俺,尽管这个需求不是俺做的...

坑啊!!

之后隔天观察一下,发现任务已经正常了:

个人建议

  • 注意边界判断,不要做产品语言的翻译机,要从开发的角度考虑设计是否合理
  • 养成打日志的习惯有利于问题排查

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1347268.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

android开发调用百度地图api实现加载地图和定位

目录 一.踩的一些坑以及解决方法 1.权限声明不要少 2.地图初始化 3.定位问题 &#xff08;1&#xff09;监听器注册 &#xff08;2&#xff09;定位监听器类MyLocationListener的实现 &#xff08;3&#xff09;定位功能的调用 4.android studio连接真机调试问题 二.…

C语言函数篇——strcat()函数

strcat()函数介绍&#xff1a; strcat()函数是C语言中用于连接两个字符串的函数。它将第二个字符串连接到第一个字符串的末尾&#xff0c;并返回第一个字符串的地址。 strcat()函数的语法&#xff1a; char *strcat(char *dest, const char *src); 其中&#xff0c;dest是目标…

pytorch03:transforms常见数据增强操作

目录 一、数据增强二、transforms--Crop裁剪2.1 transforms.CenterCrop2.2 transforms.RandomCrop2.3 RandomResizedCrop2.4 FiveCrop和TenCrop 三、transforms—Flip翻转、旋转3.1RandomHorizontalFlip和RandomVerticalFlip3.2 RandomRotation 四、transforms —图像变换4.1 t…

【JavaScript】浮点数精度问题

✨ 专栏介绍 在现代Web开发中&#xff0c;JavaScript已经成为了不可或缺的一部分。它不仅可以为网页增加交互性和动态性&#xff0c;还可以在后端开发中使用Node.js构建高效的服务器端应用程序。作为一种灵活且易学的脚本语言&#xff0c;JavaScript具有广泛的应用场景&#x…

【MySQL表的增删查改】

文章目录 前言1 Create1.1 单行数据 全列插入1.2 多行数据 指定列插入1.3 插入否则更新1.4 替换 2 Retrieve2.1 SELECT 列2.1.1 全列查询2.1.2 指定列查询2.1.3 查询字段为表达式2.1.4 为查询结果指定别名2.1.5 结果去重 2.2 WHERE 条件2.2.1 英语不及格的同学及英语成绩 ( &…

vue 基础学习 一

1. vue 使用快速入门三步走 (1) 新建 HTML 页面&#xff0c;引入 Vue.js文件 1 2 3 4 5 6 7 <!DOCTYPE html> <html> <head> <meta charset"UTF-8"> <title>Vue.js 入门示例</title> <script src"https://cdn.j…

设计模式:抽象工厂模式(讲故事易懂)

抽象工厂模式 定义&#xff1a;将有关联关系的系列产品放到一个工厂里&#xff0c;通过该工厂生产一系列产品。 设计模式有三大分类&#xff1a;创建型模式、结构型模式、行为型模式 抽象工厂模式属于创建型模式 上篇 工厂方法模式 提到工厂方法模式中每个工厂只生产一种特定…

12.29_黑马数据结构与算法笔记Java

目录 305 旅行商问题 动态规划 实现2 306 旅行商问题 动态规划 实现3 307 分治 概述 308 快速选择算法 分治 309 快速选择算法 数组第k大数 Leetcode215 310 快速选择算法 数组中位数 311 快速幂 分治 312 快速幂 Leetcode50 313 平方根整数部分 Leetcode69-1 314 平方…

从实际工作情况,介绍嵌入式(MCU)软件开发常用(通用)工具

目录 前言 1、代码阅读及编辑工具&#xff08;VSCode、Understand&#xff09; 2、代码对比工具&#xff08;Beyond Compare&#xff09; 3、代码仓库相关工具&#xff08;Git、SVN、Tortoise&#xff09; 4、文本编辑器&#xff08;Notepad&#xff09; 5、电脑文件搜索工…

arkts状态管理使用(@State、@Prop、@Link、@Provide、@Consume、@objectLink和@observed)

一、状态管理 1.在声明式UI中&#xff0c;是以状态驱动视图更新&#xff1a; ①状态&#xff08;State&#xff09;:指驱动视图更新的数据&#xff08;被装饰器标记的变量&#xff09; ②视图&#xff08;View&#xff09;:基于UI描述渲染得到用户界面 注意&#xff1a; ①…

Radar System Pro - Plug Play Solution

Radar System Pro是一款功能多样且可定制的资源,旨在通过功能齐全且易于使用的雷达系统增强您的Unity项目。无论您是在开发第一人称射击游戏、策略游戏还是太空探索模拟器,我们的雷达系统都将为您提供所需的工具,以创建引人入胜且身临其境的体验。 雷达系统是一个模块化资产…

mysql间隙锁demo分析

概述 通常用的mysql都是innodb引擎&#xff1b; 一般在update的时候用id都会认为是给行记录加锁&#xff1b; 在使用非唯一索引更新时&#xff0c;会遇到临键锁&#xff08;范围锁&#xff09;&#xff1b; 临键锁和表中的数据有关&#xff1b; mysq版本:8 隔离级别&#xf…

雨课堂作业整理

第一次作业 1.下列序列是图序列的是&#xff08; &#xff09; A.1&#xff0c;2&#xff0c;2&#xff0c;3&#xff0c;4&#xff0c;4&#xff0c;5 B.1&#xff0c;1&#xff0c;2&#xff0c;2&#xff0c;4&#xff0c;6&#xff0c;6 C.0&#xff0c;0&#xff0c;2&am…

解决基于VectorGrid的矢量瓦片Y轴偏移的问题

目录 前言 一、GeoServer的瓦片 1、GeoWebcache缓存配置 2、矢量瓦片本地缓存 3、瓦片访问 二、VectorGrid加载本地瓦片 1、加载关键代码 2、默认模式的问题 3、问题分析 4、tms参数修改 总结 前言 在前面的博文介绍中&#xff0c;在线连接如下&#xff1a;浅谈前端自定义…

AI与数字化映像:颜值开端,功能至上_光点科技

在人工智能的浪潮中&#xff0c;AI数字人的兴起正成为一个不可忽视的现象。随着ChatGPT等生成式AI算法的进步&#xff0c;AIGC&#xff08;人工智能生成内容&#xff09;的应用呈现出爆发性增长&#xff0c;不仅在技术圈引起广泛关注&#xff0c;也为元宇宙及其相关产业链带来了…

C++每日一练(8):图像相似度

题目描述 给出两幅相同大小的黑白图像&#xff08;用0-1矩阵&#xff09;表示&#xff0c;求它们的相似度。 说明&#xff1a;若两幅图像在相同位置上的像素点颜色相同&#xff0c;则称它们在该位置具有相同的像素点。两幅图像的相似度定义为相同像素点数占总像素点数的百分比。…

非科班,培训出身,怎么进大厂?

今天分享一下我是怎么进大厂的经历&#xff0c;希望能给大家带来一点点启发&#xff01; 阿七毕业于上海一所大学的管理学院&#xff0c;在读期间没写过一行 Java 代码。毕业之后二战考研失利。 回过头来看&#xff0c;也很庆幸这次考研失利&#xff0c;因为这个时候对社会一…

精品Nodejs实现的在线菜谱食谱美食学习系统的设计与实现

《[含文档PPT源码等]精品Nodejs实现的在线菜谱学习系统的设计与实现[包运行成功]》该项目含有源码、文档、PPT、配套开发软件、软件安装教程、项目发布教程、包运行成功&#xff01; 软件开发环境及开发工具&#xff1a; 操作系统&#xff1a;Windows 10、Windows 7、Windows…

2023第三届中国高校大数据挑战赛B题代码

任务已完成&#xff0c;聚类效果很好&#xff08;主要在于数据的处理以及特征工程&#xff09;, 需代码si&#xff0c;yuer有限先到先得。

模型 KANO卡诺模型

本系列文章 主要是 分享 思维模型&#xff0c;涉及各个领域&#xff0c;重在提升认知。需求分析。 1 卡诺模型的应用 1.1 餐厅需求分析故事 假设你经营一家餐厅&#xff0c;你想了解客户对你的服务质量的满意度。你可以使用卡诺模型来收集客户的反馈&#xff0c;并分析客户的…