需求: 用户下单,推荐合师傅给用户(类似滴滴派单)
场景: 在线服务平台有各类技术师傅入驻,顾客在下单后需要根据在线师傅及顾客位置计算推荐总分排行后返回推荐的师傅给用户;

问题:
1:php-fpm 框架,无法多线程工作;
2:平台师傅有多个评分属性,比如:位置,能力水平,信用分,服务时间等,推荐总分计算算法比较复杂,耗时;
3:平台师傅可能比较多,逐个计算后排行需要相当长时间;

解决方案:

1: 根据师傅位置 生成服务范围比如方圆20公里内(这里不能直接用区,很多在边上的用户);根据服务范围筛选出对应的工程师;

2:在总接口处切分计算任务,分发到异步接口

3:PHP异步接口计算推荐总分;

4:总接口轮询获取异步结果排序返回数据;

主要实现
1:数据库添加字段; 正常情况我们保存一个位置是用经纬度2个字段,这里会同时计算出最小跟最大的经纬度,一共4个字段组合成一个服务范围,然后添加对应的联合索引,最后根据用户位置查找服务范围符合用户的数据;

2:接口切分任务,伪代码如下:

	//分发任务
	public function handTask(){
      $taskNum=20;//每个进程计算的人数
      $ids="select id from table where xxx";//一次查询出所有需要计算的id,只找id,因为带范围数量不会很大
      $count=count($ids);
      $url="http:// you url";//异步处理计算任务接口,最好用内网ip或者自定义协议减少网络时间,伪代码看 第三步 doTask();
        //下面分发任务
        $taskIds=[];//保存task_id
       for ($i=0;$i<=$count;$i+=$taskNum){
           $task_id=session_id().time().mt_rand(1000,9999);//生成唯一任务id
           $pms=[];//切分任务需要参数 task_id,对应表的ids
           $pms['task_id']=$task_id;
           $pms['ids']=array_slice($ids,$i*$taskNum,$taskNum);//截取对应的ids
           httpsPost($url,$pms);//这里接口会直接返回1回来,不会等待到计算完成
           $taskIds[]=$task_id;
       }
       //获取结果
       $datas= $this->getResult($taskIds);
       if (empty($datas)){
           return ['code'=>0,'msg'=>'fail','data'=>[]];
       }
        return ['code'=>1,'msg'=>'succ','data'=>$datas];

    }

3:异步处理任务并且保存到Redis 等,伪代码如下

  //模拟异步接口 处理任务,这个接口可以独立部署到算力较好的服务器,最好只允许内部调用
    public function doTask(){
	    $ids=input("ids");
	    $task_id=input("task_id");
        echo 1;
        fastcgi_finish_request();//这里一定要及时返回,才能异步处理

        $users="select files from table where id in($ids)"; //根据id 找到所有需要计算的师傅数据,这里字段只取需要的,因为返回后还要排序
        $saveDatas=[];
        foreach ($users as $user) {
            //这里计算 积分
            $points="根据算法计算总分";
            $item['info']=$user;
            $item['points']=$points;
            $saveDatas[$user['id']]=$item;//以user id 为key 如果数据过大可以只排序id再根据id快速获得计算分数
        }

        //处理完,将数据保存到redis 设置过期时间 10s,比接口的超时时间稍大几秒即可
        getRedis()->set($task_id,$saveDatas,10);
        return true;
    }

4:接口轮询获取所有异步任务的计算结果,并合并排序后返回数据,伪代码如下

//获取结果
    public function getResult($task){
        $time=time();//时间搓
        $overTime=5;//超时时间
        //轮询去redis 获取结果
        $reData=[];
        while (1){
            //超时直接返回
            if (time()-$time>$overTime){
                return false;
            }
            foreach ($task as $k=> $t_id) {
            	//去redis 获取异步计算好的结果 数据结构大概是 [ {'info'=>[],'points'=100}, {'info'=>[],'point'=>99} ] points 是计算后的总分,排序用
                $data= getRedis()->get($t_id);
                if (!empty($data)){
                    unset($task[$k]);//删除已有数据
                    $reData=array_merge($reData,$data);
                }
            }
            //处理完跳出
            if (empty($task)){
                break;
            }
            usleep(1000*10);//睡眠10ms
        }

        if (!empty($reData)){
            //根据总分排序数据,如果数组很大 可以先只排id 然后根据id 取数据
            $reData=array_multisort(array_column($reData,'points'),SORT_DESC,$reData);
        }

        return $reData;

    }

总结:PHP-FPM 只有单线程就是难搞,要是用java 或者php swoole 通过多线程 起码单机多任务处理就不用这么麻烦,当然这样做能把计算的任务独立部署在算力比较好的服务器做到类似微服务那样的效果

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐