背景
公司帖子审核,需要把内容发送给第三方平台进行,之前只做了一次提交,成功就成功失败就失败了;但有时候因为网络问题失败的情况还是挺多的,于是要做一个重试的操作
Redis 有序集合(sorted set)
Redis 有序集合和集合一样也是 string 类型元素的集合,且不允许重复的成员。
不同的是每个元素都会关联一个 double 类型的分数。redis 正是通过分数来为集合中的成员进行从小到大的排序。
有序集合的成员是唯一的,但分数(score)却可以重复。
集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。 集合中最大的成员数为 232 - 1 (4294967295, 每个集合可存储40多亿个成员)。
我们把有序集合的分数,记录为下次要执行某段逻辑的时间戳,当到这个时间之后,获取分数为当前时间之前的数据,再进行开始没有提交成功的提交操作,这样就达到了延时的效果。
代码展示
/** 假装前边有一万行代码 ,然后走到提交后,发现失败了**/
$redis = redisConnect(); // 假装redis连上了
$RETRY_TIME = 5; // 假如5分钟重试1次
$SUBMIT_FAIL = "submit_fail"; // 假装key名称就叫这个
$time = 60*($RETRY_TIME);
$timeStamp = time();
// 存请求失败的数据
$zData = [
'dataId' => $dataId, // 来自前边1w行代码
'appId' => $appId, // 来自前边1w行代码
'reviewContext' => $reviewContext, // 来自前边1w行代码
'title' => $title, // 来自前边1w行代码
];
$redis->ZADD($SUBMIT_FAIL,$timeStamp+$time,json_encode($zData)); // 5分钟后,脚本拿到这条数据进行重试
$redis->DEL($SUBMIT_FAIL."_".$dataId); // 删除可能存在的脏数据
$redis->INCR($SUBMIT_FAIL."_".$dataId); // 记录帖子提交重试的次数
上面的操作完成了失败后记录下次执行的时间和数据,接下来就是要写一个脚本,来消费这个数据
public function mediaFailRetry(){
ini_set('default_socket_timeout', -1);
// 启动一个进程,随机执行5-10分钟,防止长时间进程锁死。
$timestamp=time();
$quit_time=rand(120,360)*50;
$redis = redisConnect(); // 还是假装连上redis
while(true){
if(time()-$timestamp > $quit_time){
return true;
}
// 获取分数在0~当前时间戳之间的数据
$tasks = $redis->ZRANGEBYSCORE(self::YIDUN_MEDIA_SUBMIT_FAIL,0,time());
if ($tasks) {
// 拿到符合条件的数据
foreach ($tasks as $task){
$taskArr = json_decode($task,true);
$dataId = $taskArr['dataId'];
$reviewContext = $taskArr['reviewContext'];
$title = $taskArr['title'];
$appId = $taskArr['appId'];
// 再次执行提交操作(省略1w行处理逻辑)
$flag = false; // 再次失败了
if(!$flag){
// 如果重试没达到10次,延迟5分钟再重试
if($redis->GET($SUBMIT_FAIL."_".$dataId) < $RETRY_COUNT){
echo date('Y-m-d H:i:s') . ",dataId:({$dataId})重试提交不成功" . PHP_EOL;
$time = 60*($RETRY_TIME);
$timeStamp = time();
// 存请求失败的数据
$zData = [
'appId' => $appId,
'reviewContext' => $reviewContext,
'dataId' => $dataId,
'title' => $title,
];
$redis->ZADD($SUBMIT_FAIL,$timeStamp+$time,json_encode($zData));
$redis->INCR($SUBMIT_FAIL."_".$dataId);
}else{
echo date('Y-m-d H:i:s') . ",dataId:({$dataId})已重试".self::RETRY_COUNT."次提交不成功,结束重试" . PHP_EOL;
$redis->DEL($SUBMIT_FAIL."_".$dataId);
}
continue;
}
// 能走到这里,说明成功了,其他逻辑,结束后续重试就可以了
$redis->DEL($SUBMIT_FAIL."_".$dataId);
echo date('Y-m-d H:i:s') . ",dataId:({$dataId})已重试提交成功,结束重试" . PHP_EOL;
}
}
sleep(60);
}
}
redis实现延时队列比较适合轻量级的延时操作,重一点可以采用mq进行实现。
下一篇文章我会用rabbitmq来实现一下同样的操作