前言在开发项目的时候,我们经常会遇到需要定时任务的应用场景,比如:登录Session管理,定时任务,计费订单用于基于时间的服务。通常我们使用(MinHeap)最小堆来实现。什么是MinHeapMinHeap是满足任意一个非叶子节点不大于其左子节点和右节点完全二叉树的值的数据。在超时任务的情况下,MinHeap可以保证最小的节点永远是根节点。PHP的SPL标准库也实现了一套MinHeap。通过使用它,我们可以轻松实现一组定时任务。但仅此而已。当你想进一步改进你的工作时,splMinHeap的局限性也就显露出来了。下面是一个使用splMinHeap的例子://超时任务管理类SessionTaskMgrextends\SplMinHeap{/***内部比较函数*/protectedfunctioncompare(mixed$a,mixed$b):int{if($a->expire>$b->过期)返回-1;如果($a->expire<$b->expire)返回1;返回0;}}classSessionTask{protectedint$expire;#会话保存时间protectedint$tickId;#计时器IDpublicfunction__construct(int$expire){$this->expire=$expire;静态::$mgr->插入($this);}/**启动超时任务*/publicstaticfunctionstart(){//创建超时任务管理器static::$mgr=$mgr=newFileLifeTaskMgr();#定时检查最小对中是否有任务已经到达执行时间。static::$tickeId=Timer::tick(1000,function()use($mgr){$time=time();while(!$mgr->isEmpty()){$node=$mgr->extract();如果($node->expire>$time){$mgr->insert($node);休息;}$节点->执行();}});}/**关闭超时任务*/publicstaticfunctionshutdown(bool$force=true){Timer::clear(static::$tickeId);$任务=静态::$任务;foreach($tasksas$task){$task->cancel($basedir);}}publicfunctionexecute(){echo"任务执行超时...\n";}publicfunctioncancel(){echo"超时任务取消...\n";}}看起来很简单对不对,但是由于splMinHeap没有实现delete接口,当任务的时间发生变化时,节点的执行时间无法调整,灵活性受到很大限制。需求1:我们做了一个登录会话管理,sesison超时设置为2小时,每次用户有网络请求,我们都会将超时时间设置为比当前时间晚2小时。即使不能调整位置,当前的splMinHeap也可以做到,只需要在到达时间后弹出节点判断是否真的超时。如果没有超时,重新加入splMinHeap即可。需求2:如果我们想在用户点击注销后立即将节点从splMinHeap中移除怎么办?这是不可能的。只能老老实实等到实际超时发生2小时后才处理。或许同学们可能想说,我们可以在session节点中添加一个成员,判断该节点是否有效。不管怎样,到时候它会从splMinHeap中弹出。是的,在不考虑不必要的内存开销的情况下,这个方案确实可行,也不是不能用^_^。换个场景,张三开了一家洗脚城,顾客可以给会员卡充值。顾客到店后,选择套餐。刚开始服务。结账系统根据消费时间自动扣费。有大量客户充值。每次顾客进店,我们都会根据用户的体验创建一个定时任务。扣完钱任务自动结束。或者用户自愿离开商店。早点结束任务。假设有大量用户充值了一大笔手续费,给定时器加了一个订单。可能需要1年的时间才能被触发。这时由于splMinHeap没有delete功能,所以这个顺序会一直保存在内存中,从而导致内存占用不断增加。我很好奇为什么删除功能不可用。看了spl库的源码,才知道是通过数组实现的。由于从数组中删除一个成员会导致大量的副本,所以没有提供删除函数。尝试解决libevent中的引用minheap实现。我们用C语言为php开发了一个扩展,实现了可以调整节点和删除节点的MinHeap类。同时,在使用上也需要更加灵活。实现原型如下:namespacemexti;classMinHeap{/***获取内部成员个数*/publicfunctioncount():int;/***是否为空等价于count()==0*/publicfunctionisEmpty():bool;/***向最小堆中插入一个成员*/publicfunctioninsert(MinHeapNode$n):bool|int;/***从最小堆中移除一个成员*/publicfunctionerase(MinHeapNode$n):bool|int;/***成员键值更新后,调整在最小堆中的位置*/publicfunctionadjust(MinHeapNode$n):bool|int;/***从最小堆中弹出A成员*/publicfunctionextract():MinHeapNode;/***获取最小堆中下一个将要弹出(也不会弹出)的成员,*/publicfunctiontop():MinHeapNode;}classMinHeapNode{/***需要的比较方法由继承类实现*/publicabstractcompare(\mexti\MinHeapNode$b):int;/***是否在MinHeap池中。*/公共函数inHeap():bool;/***索引更新后调整位置*/publicfunctionadjust():bool;/***从MinHeap中移除自身*/publicfunctionerase():bool;}复制代码用法:直接上传代码:classSampleNodeextendsMinHeapNode{//比较键值protectedint$key;/*实现比较方法*/publicfunctioncompare(\MinHeapNode$b):int{if($this->key>$b->key)return1;elseif($this->key<$b->key)返回-1;返回0;}}$heap=new\mexti\MinHeap();functionaddnodes($heap,int$count){echo"添加{$count}个节点...\n";for($i=0;$i<$count;$i++){$heap->insert(newMyNode(rand(0,999)));}}addnodes($heap,1000);while(!$heap->isEmpty()){#弹出一个节点$node=$heap->extract();}当前仓库开源:MinHeapPHP扩展代码
