边界判断缺失

作者简介:大家好,我是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.mzph.cn/news/588202.shtml

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

相关文章

2023年03月20日_对李开复3月20日线下媒体会的解读

最近这个AI大模型 因为GPT4.0 ChatGPT 文心一言等等这些事情呢 一下子就被推到了风口浪尖 我们也做了来介绍相关的进展 国内呢也不断有一些大佬开始下场 包括王慧文、张朝阳、李彦宏什么的 都开始说自己要搞AI大模型 就在昨天呢 创新工厂的董事长兼CEO李开复 也发朋友…

2024年:三大壁炉趋势

2024年的壁炉趋势是环保的、现代的和100%安全的&#xff0c;作为装饰性壁炉已经比取暖的壁炉更加受欢迎。现今&#xff0c;作为装饰性观赏的壁炉与为了加热取暖而开发的燃木壁炉、燃气壁炉之间存在明显区别。尽管加热取暖的壁炉在农村很有意义&#xff0c;但是装饰性壁炉在城市…

手拉手Springboot获取yml配置文件信息

环境介绍 技术栈 springboot3 软件 版本 mysql 8 IDEA IntelliJ IDEA 2022.2.1 JDK 17 Spring Boot 3.1.7 配置文件说明&#xff1a;启动配置文件优先级&#xff1a;properties高于yml 配置文件application.yml yml是 JSON 的超集&#xff0c;简洁而强大&#xf…

HTML标签基础入门

HTML 基本语法概述标签关系HTML基础结构HTML常用标签标题标签示例 段落和换行标签示例 文本格式化标签示例 div和span标签示例 图像标签和路径示例 超链接标签示例 注释 ctrl/特殊字符示例 表格标签 表头单元格标签表格属性示例 合并单元格示例 列表标签无序列表有序列表自定义…

介绍几种mfc140u.dll丢失的解决方法,找不到msvcp140.dll要怎么处理

如果你在使用电脑时遇到mfc140u.dll丢失错误时&#xff0c;这可能会导致程序无法正常运行&#xff0c;但是大家不必过于担心。今天的这篇文章本将为你介绍几种mfc140u.dll丢失的解决方法&#xff0c;找不到msvcp140.dll要怎么处理的一些解决方法。 一.mfc140u.dll文件缺失会有什…

数据结构【线性表篇】(一)

数据结构【线性表篇】(一&#xff09; 文章目录 数据结构【线性表篇】(一&#xff09;前言为什么突然想学算法了&#xff1f;为什么选择码蹄集作为刷题软件&#xff1f; 目录一、顺序表(一)、顺序表的定义(二)、顺序表的插入删除(三)、顺序表的查找 二、完整代码(一)、顺序表的…

Django Rest Framework(DRF)框架搭建步骤,包含部分错误解决

一、初步搭建项目 1.使用PyCharm 2021创建Djiango项目&#xff0c;配置如下&#xff08;假设应用名叫djiango_python) Python &#xff08;3.6&#xff0c; 3.7&#xff0c; 3.8&#xff0c; 3.9&#xff0c; 3.10&#xff0c; 3.11&#xff09;> 当前版本 3.8.6Django &a…

【unity中使用高度图创建地图】

unity中使用高度图创建地图 插件 讲解案例为unity2022版本 这个是插件地址 也可以在资源商店中搜索 terrain-tools 介绍 Terrain Tools入门Terrain Tools是一个软件包&#xff0c;你可以选择将其添加到Unity 2019.1或更高版本中的任何项目中。要将该软件包添加到你的项目…

准备好通过 “Breakin’ B.I.G.” 在嘻哈音乐界大放异彩吧!

在 The Sandbox 推出人物化身系列后&#xff0c;是时候通过 “Breakin’ B.I.G.” 重返嘻哈音乐的黄金时代了。该体验于 12 月 20 日推出&#xff0c;一直持续到 1 月 3 日&#xff0c;让玩家回到 20 世纪 90 年代&#xff0c;体验以 Notorious B.I.G 为主角的 2D 街舞游戏。 获…

初始Web服务器

一、web服务器 1、什么是web服务器&#xff1f; web服务器就是web项目的容器&#xff0c;我们将开发好的web项目部署到web容器中&#xff0c;才能使用网络中的用户通过浏览器进行访问。 一张图带你了解web服务器有啥作用&#xff1a; 在我的电脑上有一个已经做好的项目&#…

网络通信-入门1

网口框架 100M 2. 物理层解读 2.1 同步的方法&#xff1a;编码 为了让接收方在没有外部时钟参考的情况也能确定每一位的起始、结束和中间位置&#xff0c;在传输信号时不直接采用二进制编码。在 10BASE-T 的传输方式中采用曼彻斯特编码&#xff0c;在 100BASE-T 中则采用 4B/…

分布式技术之流量控制技术

文章目录 什么是流量控制&#xff1f;分布式系统流量控制策略漏桶策略令牌桶策略两种策略对比Sentinel 流量控制工作原理 什么是流量控制&#xff1f; 流量控制&#xff0c;如果学过计算机网络的话&#xff0c;第一反应肯定是网络传输中的流量控制。网络传输中的流量控制&…

家政行业的小程序都需要具备哪些功能?

家政服务小程序&#xff0c;覆盖多城&#xff0c;在线派单 适合行业&#xff1a;家电维修、家政保洁、养生护理、美容美发、预约服务上门等 系统功能&#xff1a;服务管理、商品管理、拼团/秒杀、订单管理、会员管理、派单管理、师傅管理、商家/服务点、财务管理、城市代理、次…

arkts中@Watch监听的使用

概述 Watch用于监听状态变量的变化&#xff0c;当状态变量变化时&#xff0c;Watch的回调方法将被调用。Watch在ArkUI框架内部判断数值有无更新使用的是严格相等&#xff08;&#xff09;&#xff0c;遵循严格相等规范。当在严格相等为false的情况下&#xff0c;就会触发Watch的…

Apollo自动驾驶系统:实现城市可持续交通的迈向

前言 「作者主页」&#xff1a;雪碧有白泡泡 「个人网站」&#xff1a;雪碧的个人网站 ChatGPT体验地址 文章目录 前言引言&#xff1a;1. 什么是微服务架构&#xff1f;2. 微服务架构的组成要素3. 微服务架构的挑战和解决方案4. 微服务架构的可扩展性和弹性 第二部分&#x…

ACW741.斐波那契额数列

输入整数 N&#xff0c;求出斐波那契数列中的第 N项是多少。 斐波那契数列的第 0项是 0&#xff0c;第 1项是 1&#xff0c;从第 2 项开始的每一项都等于前两项之和。输入格式 第一行包含整数 T&#xff0c;表示共有T个测试数据。接下来 T行&#xff0c;每行包含一个整数 N。输…

LeetCode刷题--- 黄金矿工

个人主页&#xff1a;元清加油_【C】,【C语言】,【数据结构与算法】-CSDN博客 个人专栏 力扣递归算法题 http://t.csdnimg.cn/yUl2I 【C】 ​​​​​​http://t.csdnimg.cn/6AbpV 数据结构与算法 ​​​​http://t.csdnimg.cn/hKh2l 前言&#xff1a;这个专栏主要讲述…

【Proteus仿真】【STM32单片机】自动除湿器系统

文章目录 一、功能简介二、软件设计三、实验现象联系作者 一、功能简介 本项目使用Proteus8仿真STM32单片机控制器&#xff0c;使用按键、LCD1602液晶、DHT11温湿度、继电器除湿模块等。 主要功能&#xff1a; 系统运行后&#xff0c;LCD1602显示DHT11传感器检测的湿度值阈值…

[JS设计模式]Prototype Pattern

Prototype pattern Prototype pattern可便于同类型的多个对象共享属性。原型&#xff08;prototype&#xff09;是JS原生的对象&#xff0c;其他对象可以通过原型链&#xff08;prototype chain&#xff09;来访问原型。单独看这句描述可能还是有点儿抽象&#xff0c;下面通过…

深度解析ShardingJDBC:Java开发者的分库分表利器

一、ShardingSphere ShardingSphere 是一款起源于当当网内部的应用框架。2015年在当当网内部诞 生&#xff0c;最初就叫ShardingJDBC 。2016年的时候&#xff0c;由其中一个主要的开发人员张亮&#xff0c; 带入到京东数科&#xff0c;组件团队继续开发。在国内历经了当当网、电…