skywarth/chaotic-schedule

随机化预定命令执行的时间和日期间隔

v1.1.0 2024-05-25 22:22 UTC

This package is auto-updated.

Last update: 2024-09-08 20:42:24 UTC


README

Laravel包,通过pRNGs随机化命令计划间隔。

Reliability Rating Maintainability Rating Duplicated Lines (%)

codecov Quality Gate Status

DeepSource DeepSource

Packagist: https://packagist.org.cn/packages/skywarth/chaotic-schedule

目录

安装

  1. 考虑需求

    • PHP >=7.4是必需的
  2. 通过composer安装包

composer require skywarth/chaotic-schedule
  1. (可选)发布配置以便自定义
 php artisan vendor:publish --provider "Skywarth\ChaoticSchedule\Providers\ChaoticScheduleServiceProvider" --tag="config"
  1. 完成。你现在可以在计划中使用随机时间和日期宏

问题定义

是否曾想在自己的计划中运行在一天的随机时间或者一周的特定日子执行的命令?或者你可能需要发送一些不是在固定的时间而是随机间隔的通知,这样看起来更人性化。那么这个包就是你要找的。

这个Laravel包使你能够在尊重你设定的界限的情况下,随机运行命令。

使用场景

  • 我有一个发送通知给客户的命令。但我希望它在14:0017:00之间的随机时间发送。
  • 如果用户在我特殊活动的期间(每周五和周六的00:00至04:20)活跃,我想给他们发送一些礼物。
  • 我的老板要求我每月生成并发送有关数据库活动的统计报告,但只在每周一、三和五。这个报告要在早上08:0009:30之间发送,我想让它看起来像是我个人生成和发送的。所以随机时间和日期对于这场表演至关重要。
  • 我想给客户发送提醒,并希望它们看起来和感觉人性化。因此,每周随机运行的时间和日期会对我有很大帮助。否则,如果我每周二11:00发送,他们就会知道这是自动化的,并忽略这些。
  • 由于存在财务赤字,为了检测赤字的原因,我将运行审计计算。但这些都必须是随机的,否则它们将相应地更改记录。我需要在随机时间每天运行审计计算/断言3次。
  • 我正在尝试检测数据中的某些异常,因此运行一个完全随机的命令将非常有帮助,但至少每年至少100次。

文档

随机时间宏

1. ->atRandom(string $minTime, string $maxTime,?string $uniqueIdentifier=null,?callable $closure=null)

用于安排命令在一天的随机时间运行。

  • 只指定随机运行时间
  • 不指定计划中的任何日期。因此你可能需要提供一些日期计划,如daily()weekly()mondays()randomDays()等。
  • 示例用法#1

在08:15和11:42之间随机时间每天运行一个命令

$schedule->command('your-command-signature:here')->daily()->atRandom('08:15','11:42');
  • 示例用法#2

每周二、周六和周日,在04:20至06:09之间的随机时间运行一个命令

$schedule->command('your-command-signature:here')->days([Schedule::TUESDAY, Schedule::SATURDAY, Schedule::SUNDAY])->atRandom('04:20','06:09');
  • 示例用法 #3

每周日16:00 - 17:00之间和周一09:00 - 12:00之间运行一个命令

注意唯一的标识符参数

//Observe that the both schedules share the same command, but one has custom unique identifier
$schedule->command('your-command-signature:here')->sundays()->atRandom('16:00','17:00');
$schedule->command('your-command-signature:here')->mondays()->atRandom('09:00','12:00','this-is-special');
//Since the latter has a unique identifier, it has a distinguished seed which completely differentiates the generated randoms.
  • 示例用法 #4

在工作日随机时间12:00至20:00之间运行一个命令,但小时不能是15:00。

$schedule->command('your-command-signature:here')->weekdays()->atRandom('16:00', '17:00', null, function(int $motd){
  if($motd>=900 && $motd<=960){//$motd represents minute-of-the-day. 900th minute is 15:00. 
    return $motd+60;
  }else{
    return $motd;     
  }
});

2. ->dailyAtRandom(string $minTime, string $maxTime,?string $uniqueIdentifier=null,?callable $closure=null)

atRandom宏相同。只是不同的名称。

3. ->hourlyAtRandom(int $minMinutes=0, int $maxMinutes=59,?string $uniqueIdentifier=null,?callable $closure=null)

用于安排您的命令每小时在随机分钟运行。

  • 每小时运行一次,但每个小时的分钟数是随机的
  • 只指定随机运行时间
  • 未指定计划中的任何日期。因此,您可能需要提供一些日期安排,例如daily()weekly()mondays()等。
  • 示例用法#1

每小时随机在15至25分钟之间运行一个命令。

$schedule->command('your-command-signature:here')->hourlyAtRandom(15,25);
  • 示例用法#2

每小时运行两次,一次在0-12分钟标记之间,另一次在48-59分钟标记之间。

$schedule->command('your-command-signature:here')->hourlyAtRandom(0,12);
$schedule->command('your-command-signature:here')->hourlyAtRandom(48,59,'custom-identifier-to-customize-seed');
  • 示例用法 #3

每小时运行一次,在30-45分钟之间,但只在工作日的5的倍数上。

$schedule->command('your-command-signature:here')->hourlyAtRandom(30,45,null,function(int $minute, Event $schedule){
return min(($minute%5),0);
});

4. ->hourlyMultipleAtRandom(int $minMinutes=0, int $maxMinutes=59, int $timesMin=1, int $timesMax=1, ?string $uniqueIdentifier=null,?callable $closure=null)

类似于->hourlyAtRandom,它用于安排您的命令每小时在随机分钟运行。与->hourlyAtRandom的区别在于:->hourlyMultipleAtRandom允许您每小时运行多次命令。

示例用例:我想每小时随机运行1-5次命令,在随机分钟。例如,运行分钟:[5,11,32,44]

  • 每小时运行一次
  • 仅指定随机运行时间
  • 每小时运行多次,根据$timesMin$timesMax参数
  • [!] 与->everySixHours()或类似(->everyXHours())方法一起使用**不推荐**,因为这些也是时间安排方法,它们会相互覆盖。
  • 未指定计划中的任何日期。因此,您可能需要提供一些日期安排,例如daily()weekly()mondays()等。
  • 如果timesMin=1timesMax=1,则与->hourlyAtRandom的行为完全相同。(我的意思是当然)
  • 示例用法#1

每小时随机4-5次,只在工作日(固定,每天)08:00至18:00(固定,每小时)之间运行,每个小时的分钟数是随机的。

https://www.reddit.com/r/laravel/comments/18v714l/comment/ktkyc72/?utm_source=share&utm_medium=web2x&context=3

$schedule->command('your-command-signature:here')->hourlyMultipleAtRandom(0,59,4,5)->weekdays()->between('08:00','18:00');
  • 示例用法#2

每小时正好运行8次,只在20-40分钟标记之间运行,只在周三运行。

$schedule->command('your-command-signature:here')->hourlyMultipleAtRandom(20,40,8,8)->wednesdays();
  • 示例用法 #3

每小时运行2-6次,只在10-48分钟标记之间运行,只在周二、周四、周六运行,且只在偶数分钟上运行。

$schedule->command('your-command-signature:here')->hourlyMultipleAtRandom(10,48,2,6,null,function(Collection $designatedMinutes,Event $event){
    return $designatedMinutes->map(function(int $minute){
        return $minute-(($minute%2));//rounding numbers to closest even number, if the number is odd
    });
})->days([Schedule::TUESDAY, Schedule::THURSDAY,Schedule::SATURDAY])();

随机日期宏

1. ->randomDays(int $periodType, ?array $daysOfTheWeek, int $timesMin, int $timesMax, ?string $uniqueIdentifier=null,?callable $closure=null)

用于安排您的命令在给定的约束和周期内随机日期运行。

  • 仅指定随机运行日期
  • 未指定计划中的任何运行时间。因此,您必须提供一些时间安排,例如hourly()everySixHours()everyTenMinutes()atRandom()等。
  • 示例用法#1

每月随机运行命令5到10次。

$schedule->command('your-command-signature:here')->randomDays(RandomDateScheduleBasis::MONTH,[],5,10);
  • 示例用法#2

每周恰好运行2次(作为日期),但只在周三或周六。

$schedule->command('your-command-signature:here')->randomDays(RandomDateScheduleBasis::WEEK,[Carbon::WEDNESDAY,Carbon::SATURDAY],2,2);
  • 示例用法 #3

每年运行15-30次(作为日期),但只在周五。

$schedule->command('your-command-signature:here')->randomDays(RandomDateScheduleBasis::YEAR,[Carbon::FRIDAY],15,30);
  • 示例用法 #4

每月1到3次(作为日期)运行命令,但只在周末,且只在奇数日。

$schedule->command('your-command-signature:here')->randomDays(
    RandomDateScheduleBasis::MONTH,
    [Carbon::SATURDAY,Carbon::SUNDAY],
    1,3,
    null,
    function (Collection $dates){
        return $dates->filter(function (Carbon $date){
            return $date->day%2!==0;//odd numbered days only
        });
    }
);

联合示例

关于同时使用随机时间和随机日期宏的示例。

  • 示例用法#1

在周五、周二、周日之间运行命令1至2次(如同日期),时间仅限于14:48 - 16:54。

$schedule->command('your-command-signature:here')->weekly()->randomDays(RandomDateScheduleBasis::WEEK,[Carbon::FRIDAY,Carbon::Tuesday,Carbon::Sunday],1,2)->atRandom('14:48','16:54');

技术信息

一致性、种子和pRNG

生成给定区间内一致且相同的随机值是一个问题。这是因为Laravel调度器每分钟通过CRON任务触发。因此,我们需要一种为每次调度触发生成一致随机值的方法。否则,它每次运行时都会简单地指定随机的日期/时间运行,这会导致:命令根本不会运行(因为运行指定不断变化),或者命令运行次数超过预期/计划。

在密码学和统计学领域,此类挑战通过pRNGs(伪随机数生成器)来解决。pRNGs正如其名所示:是一种伪随机数生成器,它使用种子值并生成由该种子决定的随机数,因此得名。因此,只要种子保持相同,pRNGs就可以生成一致且完全相同的随机值。现在的问题是,我们应该将什么种子与pRNG配对?经过一番思考,我推断,如果我给出区间的相应日期/时间,它将有效地在整个区间内保持一致。因此,所有随机化方法和宏都通过利用SeedGenerationService来工作,该服务负责根据某些区间(日、月、周等)生成种子。这确保了您的随机运行日期/时间在整个区间内保持一致,从而实现了一致性。

对于那些有敏锐眼光的人来说,这可能会提出一个可能的问题。如果种子仅与区间配对,这不是会导致不同命令具有相同的随机日期/时间运行吗?正是如此!正因为如此,SeedGenerationService还通过将其种子指定方法中的uniqueIdentifier(要么是命令签名,要么是自定义标识符)纳入考虑,来考虑命令签名。这样,即使不同的命令具有相同的随机调度,它们也会因为uniqueIdentifier而具有不同的随机化。

确保混乱

在处理pRNGs时,实际上没有什么真正混乱和随机的。它完全是数学和统计学,是确定的。为了确保这些随机值不会造成伤害,我已经为库的不同方面准备了数十个单元和功能测试。从种子生成到生成的随机一致性,从分布均匀性到验证,从设计模式实现到依赖注入,所有这些都经过了良好的测试和断言。请参阅有关这些功能测试的代码覆盖率报告和CI/CD运行。

性能

正如您可能已经知道的,Laravel调度器通过CRON任务条目每分钟运行(参见:https://laravel.net.cn/docs/10.x/scheduling#running-the-scheduler)。由于kernel.php(您在其中定义您的计划)每分钟运行,因此Laravel必须确定每个命令是否指定在此分钟运行。这是通过运行并检查您的命令调度上的日期/时间调度分配和->when()语句来完成的。

此库严重依赖于pRNG,基于日期/时间和运行指定生成种子。因此,这些计算、随机化(伪)和指定在每次kernel.php运行时都会执行,这是每分钟,如前一段所述。因此,是的,如果您是低规格的,它可能会影响您的管道。因为这些种子确定、运行日期/时间指定和伪随机值是在每次调度迭代中确定的。所以,如果您配置低,它可能会影响您的服务器性能,尤其是在内存使用方面。大量的随机化命令计划(250+)可能会在内存使用方面阻塞您的服务器性能。

但除此之外,正如《低俗小说》中的朱尔斯所说

"据我所知,MF是顶尖的"。

路线图 & TODOs

  • 问题:这些该死的PRNGs与大量种子值配合得不好。
    • 用于强制执行种子格式(大小、类型等)的RNG适配器的抽象类。
    • 针对种子生成服务的稳定种子流的新哈希解决方案。
      • 服务中的每个方法都应该经过哈希处理,仅使用 intval 实际上很糟糕。
  • 时区适应,我们应该使用时区宏。(已取消。不需要。Laravel 可以处理它)
  • 基于闭包的调度,那些没有命令的。
  • 基于时间的宏
    • 对于 ->at('15:30') 的随机。精确的单个时间。
    • 对于 ->hourlyAt(17) 的随机
    • 对于 ->dailyAt('13:00') 的随机
    • (跳过。并非真正必要)对于 自定义everyRandomMinutes() 进行随机
    • (不可行。我们打算将种子绑定/锚定在哪里?)对于 自定义everyRandomMinutes() 进行随机
    • [!] 种子应该被扩展并区分。例如:->days(Schedule::MONDAY,Schedule::FRIDAY,Schedule::SATURDAY)->atRandom('09:00','22:44')。否则,它不会正确显示下一个到期日期。这并非真正的错误,但在告知用户方面是不正确的。此配置可能更好。->nextRunDate()->daily() 有问题。
    • 用于调整和灵活性的闭包参数
    • 确定和指示边界包含性
  • 基于日期的宏
    • (略有变化)创建一个包含要选择的指定星期的数组的数组。使用 RNG 打乱这个数组。根据要求(如每周两次或每月六次),切片数组以获取所需的天数。返回选定的日子。
    • 它应该启用以下场景(以下的时间仅定义基于日期/天的时间!不考虑时间/分钟。)
      • 每周一次,任何一天
      • 每周一次,在周三和周五之间
        • 示例:这周将运行周三。(基本上你每周掷骰子)
      • 每周两次,在周四、周六和周一之间。
        • 示例:这周将运行周四和周一。
        • 示例:这周将运行周六和周一。
      • 这周1-3次,在周二、周三和周五上 (需要验证,以确保时间不超过最大可能天数)
        • 示例:这周将运行2次,在周二和周三。
        • 示例:这周将运行1次,在周五。
      • 这周2-7次,在周一的任何一天
        • 示例:这周将在 [...] 上运行5次
      • 每月一次,在周一的任何一天
      • 每月4次,在只有奇数的天上(3,7,17,23)
      • 每月10-20次,在周一、周三、周四和周六
      • 每年30次,在周一和周三。
      • 这个有点难:每年10次,在周六和周日,运行应至少有4周的缓冲跨度。因此,它至少每4周运行一次。
    • 所以到目前为止的聚会,分析
      • period 上下文。周、月、年...
      • 约束和限制:days of the week(单独的参数),buffer(单独的参数?。指定运行之间的最小差异是什么)以及其他(例如仅在奇数天运行)可以通过闭包来处理。
      • times 定义了在给定 period 中应运行多少次。它与随机时间表没有任何关系。
      • times 应该被验证,以确保不会超过给定 period 和约束(星期几等)的最大可能运行次数。
  • 通过覆盖 schedule:list 或定义一个特定的命令来指示下一次运行的日期,该命令专门用于使用我们的宏的命令。
    • 标记使用我们宏的命令。
  • CI/CD 管道(构建、运行测试、可能自动发布?)
  • randomMultipleMinutesSchedule 添加到文档中
  • 根据方法/宏创建单独的测试类
  • 为方法和类添加 PHPDoc 注释
  • 将基于 dateTime Carbon 实例注入到闭包中
  • 单元/功能测试
    • 基于时间的方法和宏
      • 宏注册断言
      • 基于种子的随机数生成一致性
      • 唯一标识符(参数和自动生成)
      • 无效参数(超出范围、最小-最大顺序、格式)
      • 边界得到尊重(最小-最大值、生成的日期是否超出这些限制?)
        • 在RNGAdapter上
        • 在宏定义上
      • 闭包
    • 基于日期的方法和宏
      • 宏注册断言
      • 基于种子的随机数生成一致性
      • 唯一标识符(参数和自动生成)
      • 无效参数(超出范围、最小-最大顺序、格式)
      • 闭包
    • 统一宏使用功能测试
    • 时区测试
      • 应用时区与时间宏协同工作
      • 应用时区与日期宏协同工作
    • [CRUCIBLE!] 将测试中的所有分布式日期时间迭代方法合并为一种
  • 来自reddit的用例,N1
  • 可能的bug:`->dateOfWeek` 和 `->dateOfWeekIso` 由于起始的周一到周日的差异而不同。检查现有断言。

鸣谢 & 参考文献

RNGs

该项目使用JetBrains产品开发。感谢他们为开源开发的支持