解决某些有到期时间的场景,不适合定时扫描表来完成处理的情况
有几个场景业务的处理:
一:有一个很大的商品订单表,每天新增数十万条数据。每条数据有个到期时间,需要在到期时间后做一些处理,譬如关闭订单,改变状态之类的。
二:有个付款功能,有到期时间,时间到了需要关闭,或者通知用户等等。
三:抢购时,时间到了,用户不处理不付款的,要把商品回到库存里之类的。
大概类似的一些有到期时间功能的业务场景,但是要么是有较强的实时性,譬如希望到期后立马就改变状态或者做出一些通知之类的,要么是数据量巨大的。
那么可能首先想到的思路就是开个定时任务,隔一段时间去扫一下表,看看到期时间,然后做处理。
很明显,扫表是个很大的工作量,耗时耗资源,甚至会产生死锁什么的。而且大部分时候的扫描是无用的,产生了很多无用的查询。
那么这种问题是解决思路:
在添加数据时,将ID和过期时间放到redis里,用那个能排序的结构sortSet,或者类似的能记录时间的中间件,做好排序。然后起个后台任务或者新起个项目,专门是扫描这个redis的第一条数据,也就是最快要过期的,这样只需要查询一条就行了,只要第一条不过期,那后面的就不用看了,也就不需要去操作数据库。倘若第一条过期了,就做相应的处理,然后移除掉,再去扫第二条,依次类推。这样查询就很少,也不需要查表。所以可以把扫描间隔设的很短,来达到强实时性。
**************************************************
补充1:
阿里云消息队列ONS服务,里面有一个发送延时消息(定时消息)的功能,这个也可以应用于该场景,逻辑执行完毕后,譬如下单成功,30分钟后不付款就取消订单,那么就可以使用阿里的发送延时消息的功能。30分钟后消息发出,然后处理端去获取付款状态,如果未付款则取消订单。
**************************************************
补充2,今天看了一篇定时任务高效触发的文章,摘录部分,原文:http://blog.csdn.net/ligang2585116/article/details/72859359
开发中我们经常会遇到一些需要定时来解决的业务场景。比如,有这样一个需求:“如果连续30s没有请求包(例如登录,消息,keepalive包),服务端就要将这个用户的状态置为离线”。
环形队列处理
数据结构:
- 环形队列ListLoop,例如可以创建一个包含0-30的slot**环形队列**(本质是个数组);
- 每个环上的任务集合Slot,环上每一个slot是一个Set;
- 记录每个Task对应落到Slot的Map集合;
执行过程:
第一步:启动一个timer,每隔1s,在上述环形队列中移动一格,0->1->2->3…->29->30->0…
有一个CurrentSlotIndex指针来标识刚检测过的slot ;
第二步:当有某用户uid有请求包到达时,从Map结构中,查找出这个uid存储在哪一个slot里;
第三步:如果存在,从这个slot的Set结构中,删除这个uid,否则跳过该步骤;
第四步:将uid重新加入到新的slot中(CurrentSlotIndex指针所指向的上一个slot)因为这个slot,会被timer在30s之后扫描到
第五步:更新Map,重新设置该uid对应slot的index值
// new Array(31).fill(new Set())
// No,数组中所有Set集合为同一个
let listLoop = new Array(31),
map = new Map(), // 记录每个uid的slotIndex
currentSlotIndex = 1; // 当前要检测的slot
function doAction(uid) {
// 如果循环队列中已存在该uid,需要先干掉,重新计时
let slotIndex = map.get(uid);
slotIndex && listLoop[slotIndex].delete(uid);
// 将该uid重现添加到循环队列中
// 周期31,新插入的置入当前的后一个(即,30s后可以扫描到它)
// 更新map中这个uid的最新slotIndex
slotIndex = currentSlotIndex - 1;
listLoop[slotIndex] = listLoop[slotIndex] ?
listLoop[slotIndex].add(uid) : new Set().add(uid);
map.set(uid, slotIndex);
}
// 每秒钟移动一个slot,这个slot对应的set集合中所有uid都为超时
// 如果所有slot对应的set集合都为空,则表示没有uid超时
setInterval(function() {
var slotSet = listLoop[currentSlotIndex];
if(slotSet && slotSet.size > 0) {
for(let uid of slotSet.values()) {
// 执行完的uid从map集合中剔除
map.delete(uid);
console.log(`<${uid}>超过30s未做任何操作,设置为离线!`);
}
// 置空该集合
slotSet.clear();
}
// 指标继续+1
currentSlotIndex = (++currentSlotIndex) % 31;
}, 1000);
// 思路、注意Map集合的内心移除情况。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
方案的优点:
- 无需再轮询全部订单,效率高
- 无重复执行,一个订单,任务只执行一次
- 效性好,精确到秒(控制timer移动频率可以控制精度)
参照文章:10w定时任务,如何高效触发超时、1分钟实现“延迟消息”功能
举一反三
上述展示描述了一种业务场景,通过环形队列的方式我们还可以处理很多类似场景。
- 某打车软件订单完成后,如果用户一直不评价,48小时后会将自动评价为5星;
- 某数据产品用户修改设置,1小时后生效;
- …
- HBase 写优化之 BulkLoad 实现数据快速入库
- 玩转 Linux 之:由 Nginx log rotation 聊聊 mv 的妙用
- 玩转 SHELL 脚本之:linux date 知多少?
- Hive 中的 LEFT SEMI JOIN 与 JOIN ON 的前世今生
- Yaffs_guts
- 4大分析工具的代码表白术,520花式秀恩爱!
- yaffsfs.c
- yaffs_bitmap
- 原创 | 实战:R环境下Echart的8种可视化
- Yarn(MapReduce 2.0)下分布式缓存(DistributedCache)的注意事项
- Yaffs_guts(三)
- 浅谈 python multiprocessing(多进程)下如何共享变量
- 文件地址映射之yaffs_GetTnode
- bash/shell 解析命令行参数工具:getopts/getopt
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- TCP/IP详解 -奠基篇
- 段错误?打的就是段错误!!
- (Graph)图,挑着看看
- Mybatis学习笔记(四)调用存储过程
- 跟我一起 自己种一颗 AVL树(平衡二叉搜索树)吧!
- mybatis文件映射之利用collection定义关联集合(五)
- 【奇技淫巧】 -- 原地旋转数组
- mybatis文件映射之利用延迟加载解决collection分布查询(六)
- 【C++】攻克哈希表(unordered_map)
- 位图原理及实现 - 海量数据处理标配
- mybatis文件映射之利分布查询时传递多列值 (七)
- 位运算 - 初见
- Mybatis学习笔记(一)
- mybatis文件映射之鉴别器discriminator标签
- LeetCode刷题总结 -- 链表篇