派单调度啊

派单调度流程

在抢单业务中,用户下单成功由服务人员或机构进行抢单,抢单成功服务人员上门服务,除了抢单业务系统还设计了派单业务,由系统根据用户订单的特点自动派给合适的服务人员。

系统派单的目的是什么?

根据订单的特点及服务人员的特点进行撮合,提高交易成功率。

调度系统的应用场景

调度系统的应用场景广泛(如下):

即时配送服务: 将用户提交的订单派发给最近且可用的配送员,以确保订单在最短时间内送达。

服务行业派单: 将用户的服务请求分配给合适的服务人员,考虑到距离、技能、时间等因素。

出租车调度: 将用户的打车请求派发给附近空闲的出租车,以最快速度响应用户需求。

餐饮外卖配送: 将用户的订单分派给附近的餐馆,并将已经制作好的食物分派给配送员,以保证食品的新鲜度和送达时间。

快递中心派单: 将到达的包裹分派给不同的快递员,根据各自的配送区域和计划。

工程项目任务分配: 在建筑、工程项目中,需要将不同的任务分配给相应的工程师、技术人员或施工队,以确保项目按计划进行。

通过项目中派单调度的学习有能力迁移到类似场景,比如:工单调度。

另外尝试去理解其它领域的调度业务,从而对调度系统的开发有深刻的理解,有能力迁移到其它的场景。

系统设计

总体设计

根据需求分析,从以下几个问题出发进行设计。

  1. 涉及到距离搜索,参考抢单业务需要借助Elasticsearch搜索服务人员,所以需要将服务人员同步到Elasticsearch。根据搜索匹配的条件:服务技能,服务时间、接单范围,需要将服务人员和机构的相关信息同步到Elasticsearch。

同步的方式使用Canal+MQ实现。

  1. 如果为订单派单失败每隔3分钟再次对订单进行派单,考虑性能问题在redis创建派单池,调度程序扫描派单池获取订单,所以需要将MySQL中派单池的数据同步到Redis。

  2. 根据需求,派单策略有三种,后期还可能扩展,这里使用策略模式实现,提高系统扩展性。

  3. 根据需求,每个派单策略有多个规则,按规则逐个去匹配,只要匹配成功或规则使用完成,这里使用责任链儿模式,提高系统扩展性。

  4. 派单程序将订单和服务人员匹配成功,接下来调用抢单接口进行机器抢单,这样也体现公平性,因为派单的同步有服务人员也在抢单,这里是机器(平台)和人工(服务人员)在共同抢单。

整体交互流程如下:

说明:

  1. 使用Canal+MQ将服务提供者(服务人员和机构)信息(经纬度坐标、接单状态、当前接单数等)同步Elasticsearch中。

  2. 将派单池同步到Redis,派单池中是待派单的订单信息。

  3. 通过定时任务定时派单,从派单池查询多个订单,使用线程池对多个订单进行派单,每个线程负责一个订单。

  4. 派单过程首先根据订单信息(服务地址、服务项目)从Elasticsearch中的服务提供者索引中搜索。

  5. 找到多个服务提供者,根据派单策略去匹配服务提供者,通过责任链模式分别匹配每个规则,最终找到一个服务提供者。

  6. 系统调用抢单接口为服务提供者派单。

责任链模式

什么是责任链模式

责任链模式是一种行为型设计模式,它允许你将请求沿着处理者链进行传递,直到有一个处理者处理请求为止。每个处理者都可以决定是否将请求传递给下一个处理者。

根据派单的需求,根据订单信息从服务提供池中获取师傅及机构的信息,通过距离优先规则、评分优先规则等最终获取一个要派单的服务或机构。

下图描述了按距离优先派单的过程:

下边用责任链模式实现。

首先,阅读下边的数据处理规则的接口:

package com.jzo2o.orders.dispatch.strategys;

import com.jzo2o.orders.dispatch.model.dto.ServeProviderDTO;
import com.jzo2o.orders.dispatch.rules.IDispatchRule;
import lombok.Builder;
import lombok.ToString;

import java.util.List;

/**
 * @author Mr.M
 * @version 1.0
 * @description TODO
 * @date 2023/11/24 5:56
 */
public interface IProcessRule {

    /**
     * 根据派单规则过滤服务人员
     * @param serveProviderDTOS
     * @return
     */
    List<ServeProviderDTO> filter(List<ServeProviderDTO> serveProviderDTOS);

    /**
     * 获取下一级规则
     *
     * @return
     */
    IProcessRule next();
}

根据需求定义距离优先规则。

package com.jzo2o.orders.dispatch.strategys;

import com.jzo2o.common.utils.CollUtils;
import com.jzo2o.orders.dispatch.model.dto.ServeProviderDTO;
import com.jzo2o.orders.dispatch.rules.IDispatchRule;

import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author Mr.M
 * @version 1.0
 * @description 按距离排序
 * @date 2023/11/24 5:58
 */
public class DistanceRule implements IProcessRule {
    private IProcessRule next;

    public DistanceRule(IProcessRule next) {
        this.next = next;
    }

    public List<ServeProviderDTO> doFilter(List<ServeProviderDTO> serveProviderDTOS) {
        System.out.println("按距离排序,拿到距离最近的服务提供者");
        return null;
    }

    @Override
    public List<ServeProviderDTO> filter(List<ServeProviderDTO> serveProviderDTOS) {
        List<ServeProviderDTO> result = this.doFilter(serveProviderDTOS);
        if(CollUtils.size(result) > 1 && next != null) {
            return next.filter(result);
        }else {
            return result;
        }
    }

    @Override
    public IProcessRule next() {
        return next;
    }



}

再定义最少接单数规则:

package com.jzo2o.orders.dispatch.strategys;

import com.jzo2o.common.utils.CollUtils;
import com.jzo2o.orders.dispatch.model.dto.ServeProviderDTO;

import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author Mr.M
 * @version 1.0
 * @description 按接单数
 * @date 2023/11/24 5:58
 */
public class AcceptNumRule extends AbstractProcessRule implements IProcessRule {
    private IProcessRule next;

    public AcceptNumRule(IProcessRule next) {
        this.next = next;
    }

    public List<ServeProviderDTO> doFilter(List<ServeProviderDTO> serveProviderDTOS) {
        System.out.println("按接单数排序,拿到最少接单少的服务提供者");
        return null;
    }

    @Override
    public List<ServeProviderDTO> filter(List<ServeProviderDTO> serveProviderDTOS) {
        List<ServeProviderDTO> result = this.doFilter(serveProviderDTOS);
        if(CollUtils.size(result) > 1 && next != null) {
            return next.filter(result);
        }else {
            return result;
        }
    }

    @Override
    public IProcessRule next() {
        return next;
    }
}

下边将规则组成一个链儿,调用链儿中第一个规则的filter方法,最终获取处理后的结果,如果处理结果的数量大于1则随机选择一个,否则取出唯一的结果。

package com.jzo2o.orders.dispatch.strategys;

import com.jzo2o.common.utils.CollUtils;
import com.jzo2o.orders.dispatch.model.dto.ServeProviderDTO;

import java.util.Arrays;
import java.util.List;

/**
 * @author Mr.M
 * @version 1.0
 * @description TODO
 * @date 2023/11/24 6:05
 */
public class RuleHandlerTest {

    public static void main(String[] args) {

        // 策略1:构建责任链,先距离优先,距离相同再判断接单数
        IProcessRule rule = new AcceptNumRule(null);
        IProcessRule ruleChain = new DistanceRule(rule);
        // 策略2:构建责任链,先评分优先,评分相同再判断接单数
//        IProcessRule rule = new AcceptNumRule(null);
//        IProcessRule ruleChain = new ScoreRule(rule);
        // 策略3:构建责任链,先接单数优先,接单数相同再判断评分
//        IProcessRule rule = new ScoreRule(null);
//        IProcessRule ruleChain = new AcceptNumRule(rule);

        // 创建数据
        List<ServeProviderDTO> serveProviderDTOS = Arrays.asList(
                //1号 接单数最少
                ServeProviderDTO.builder().id(1L).acceptanceNum(0).acceptanceDistance(30).evaluationScore(50).build(),
                //2号 得分最高
                ServeProviderDTO.builder().id(2L).acceptanceNum(1).acceptanceDistance(10).evaluationScore(100).build(),
                //3号 得分最高
                ServeProviderDTO.builder().id(3L).acceptanceNum(2).acceptanceDistance(10).evaluationScore(100).build(),
                //4号 距离最近
                ServeProviderDTO.builder().id(4L).acceptanceNum(2).acceptanceDistance(5).evaluationScore(50).build(),
                //4号 距离最近
                ServeProviderDTO.builder().id(5L).acceptanceNum(1).acceptanceDistance(5).evaluationScore(50).build()
        );

        // 发起处理请求
        List<ServeProviderDTO> list = ruleChain.filter(serveProviderDTOS);
        //处理结果
        ServeProviderDTO result = null;
        // 3.1.唯一高优先级直接返回
        int size = 1;
        if((size = CollUtils.size(list)) == 1) {
            result = list.get(0);
        }
        // 3.2.多个高优先级随机返回,生成0到size之间的随机整数
        int randomIndex = (int) (Math.random() * size);
        result = list.get(randomIndex);
        System.out.println(result);
    }
}

代码完善

距离排序规则:

package com.jzo2o.orders.dispatch.strategys;

import com.jzo2o.common.utils.CollUtils;
import com.jzo2o.orders.dispatch.model.dto.ServeProviderDTO;
import com.jzo2o.orders.dispatch.rules.IDispatchRule;

import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author Mr.M
 * @version 1.0
 * @description 按距离排序
 * @date 2023/11/24 5:58
 */
public class DistanceRule implements IProcessRule {
    private IProcessRule next;

    public DistanceRule(IProcessRule next) {
        this.next = next;
    }

    public List<ServeProviderDTO> doFilter(List<ServeProviderDTO> serveProviderDTOS) {
        System.out.println("按距离排序");
        if (CollUtils.size(serveProviderDTOS) < 2) {
            return serveProviderDTOS;
        }
        //  2.按照比较器进行排序,排在最前方优先级最高
        serveProviderDTOS = serveProviderDTOS.stream().sorted(Comparator.comparing(ServeProviderDTO::getAcceptanceDistance)).collect(Collectors.toList());
        // 3.遍历优先级最高一批数据
        ServeProviderDTO first = CollUtils.getFirst(serveProviderDTOS);

        //获取相同级别的
        return serveProviderDTOS.stream()
                .filter(origin -> origin.getAcceptanceDistance().compareTo(first.getAcceptanceDistance()) == 0)
                .collect(Collectors.toList());
    }

    @Override
    public List<ServeProviderDTO> filter(List<ServeProviderDTO> serveProviderDTOS) {
        List<ServeProviderDTO> result = this.doFilter(serveProviderDTOS);
        if(CollUtils.size(result) > 1 && next != null) {
            return next.filter(result);
        }else {
            return result;
        }
    }

    @Override
    public IProcessRule next() {
        return next;
    }



}

按接单数排序规则:

package com.jzo2o.orders.dispatch.strategys;

import com.jzo2o.common.utils.CollUtils;
import com.jzo2o.orders.dispatch.model.dto.ServeProviderDTO;

import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author Mr.M
 * @version 1.0
 * @description 按接单数
 * @date 2023/11/24 5:58
 */
public class AcceptNumRule extends AbstractProcessRule implements IProcessRule {
    private IProcessRule next;

    public AcceptNumRule(IProcessRule next) {
        this.next = next;
    }

    public List<ServeProviderDTO> doFilter(List<ServeProviderDTO> serveProviderDTOS) {
        System.out.println("按接单数排序");
        if (CollUtils.size(serveProviderDTOS) < 2) {
            return serveProviderDTOS;
        }
        //  2.按照比较器进行排序,排在最前方优先级最高
        serveProviderDTOS = serveProviderDTOS.stream().sorted(Comparator.comparing(ServeProviderDTO::getAcceptanceNum)).collect(Collectors.toList());
        // 3.遍历优先级最高一批数据
        ServeProviderDTO first = CollUtils.getFirst(serveProviderDTOS);

        //获取相同级别的
        return serveProviderDTOS.stream()
                .filter(origin -> origin.getAcceptanceNum().compareTo(first.getAcceptanceNum()) == 0)
                .collect(Collectors.toList());
    }

    @Override
    public List<ServeProviderDTO> filter(List<ServeProviderDTO> serveProviderDTOS) {
        List<ServeProviderDTO> result = this.doFilter(serveProviderDTOS);
        if(CollUtils.size(result) > 1 && next != null) {
            return next.filter(result);
        }else {
            return result;
        }
    }

    @Override
    public IProcessRule next() {
        return next;
    }
}

下边进行测试

package com.jzo2o.orders.dispatch.strategys;

import com.jzo2o.common.utils.CollUtils;
import com.jzo2o.orders.dispatch.model.dto.ServeProviderDTO;

import java.util.Arrays;
import java.util.List;

/**
 * @author Mr.M
 * @version 1.0
 * @description TODO
 * @date 2023/11/24 6:05
 */
public class RuleHandlerTest {

    public static void main(String[] args) {

        // 策略1:构建责任链,先距离优先,距离相同再判断评分
        IProcessRule rule = new AcceptNumRule(null);
        IProcessRule ruleChain = new DistanceRule(rule);
        // 策略2:构建责任链,先评分优先,评分相同再判断接单数
//        IProcessRule rule = new AcceptNumRule(null);
//        IProcessRule ruleChain = new ScoreRule(rule);
        // 策略3:构建责任链,先接单数优先,接单数相同再判断评分
//        IProcessRule rule = new ScoreRule(null);
//        IProcessRule ruleChain = new AcceptNumRule(rule);

        // 创建数据
        List<ServeProviderDTO> serveProviderDTOS = Arrays.asList(
                //1号 接单数最少
                ServeProviderDTO.builder().id(1L).acceptanceNum(0).acceptanceDistance(30).evaluationScore(50).build(),
                //2号 得分最高
                ServeProviderDTO.builder().id(2L).acceptanceNum(1).acceptanceDistance(10).evaluationScore(100).build(),
                //3号 得分最高
                ServeProviderDTO.builder().id(3L).acceptanceNum(2).acceptanceDistance(10).evaluationScore(100).build(),
                //4号 距离最近
                ServeProviderDTO.builder().id(4L).acceptanceNum(2).acceptanceDistance(5).evaluationScore(50).build(),
                //4号 距离最近
                ServeProviderDTO.builder().id(5L).acceptanceNum(1).acceptanceDistance(5).evaluationScore(50).build()
        );

        // 发起处理请求
        List<ServeProviderDTO> list = ruleChain.filter(serveProviderDTOS);
        //处理结果
        ServeProviderDTO result = null;
        // 3.1.唯一高优先级直接返回
        int size = 1;
        if((size = CollUtils.size(list)) == 1) {
            result = list.get(0);
        }
        // 3.2.多个高优先级随机返回
        int randomIndex = (int) (Math.random() * size);
        result = list.get(randomIndex);
        System.out.println(result);
    }
}

责任链模式优化

上边的代码每个规则中filter方法和next方法都是重复一样的,我们创建抽象类提取

package com.jzo2o.orders.dispatch.strategys;

import com.jzo2o.common.utils.CollUtils;
import com.jzo2o.orders.dispatch.model.dto.ServeProviderDTO;

import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author Mr.M
 * @version 1.0
 * @description 规则抽象类
 * @date 2023/11/24 11:00
 */
public abstract class AbstractProcessRule implements IProcessRule{

    private IProcessRule next;

    public AbstractProcessRule(IProcessRule next) {
        this.next = next;
    }

    public abstract List<ServeProviderDTO> doFilter(List<ServeProviderDTO> serveProviderDTOS);

    @Override
    public List<ServeProviderDTO> filter(List<ServeProviderDTO> serveProviderDTOS) {
        List<ServeProviderDTO> result = this.doFilter(serveProviderDTOS);
        if(CollUtils.size(result) > 1 && next != null) {
            return next.filter(result);
        }else {
            return result;
        }
    }

    @Override
    public IProcessRule next() {
        return next;
    }
}

修改每个规则类:

下边以DistanceRule举例,其它的规则类自行修改。

package com.jzo2o.orders.dispatch.strategys;

import com.jzo2o.common.utils.CollUtils;
import com.jzo2o.orders.dispatch.model.dto.ServeProviderDTO;

import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author Mr.M
 * @version 1.0
 * @description 按距离排序
 * @date 2023/11/24 5:58
 */
public class DistanceRule extends AbstractProcessRule  {
//    private IProcessRule next;

    public DistanceRule(IProcessRule next) {
        super(next);
//        this.next = next;
    }

    public List<ServeProviderDTO> doFilter(List<ServeProviderDTO> serveProviderDTOS) {
        System.out.println("按距离排序");
        if (CollUtils.size(serveProviderDTOS) < 2) {
            return serveProviderDTOS;
        }
        //  2.按照比较器进行排序,排在最前方优先级最高
        serveProviderDTOS = serveProviderDTOS.stream().sorted(Comparator.comparing(ServeProviderDTO::getAcceptanceDistance)).collect(Collectors.toList());
        // 3.遍历优先级最高一批数据
        ServeProviderDTO first = CollUtils.getFirst(serveProviderDTOS);

        //获取相同级别的
        return serveProviderDTOS.stream()
                .filter(origin -> origin.getAcceptanceDistance().compareTo(first.getAcceptanceDistance()) == 0)
                .collect(Collectors.toList());
    }

//    @Override
//    public List<ServeProviderDTO> filter(List<ServeProviderDTO> serveProviderDTOS) {
//        List<ServeProviderDTO> result = this.doFilter(serveProviderDTOS);
//        if(CollUtils.size(result) > 1 && next != null) {
//            return next.filter(result);
//        }else {
//            return result;
//        }
//    }
//
//    @Override
//    public IProcessRule next() {
//        return next;
//    }

}

派单策略

1)技术方案

在前边我们测试责任链模式,如下代码怎么优化:

        // 策略1:构建责任链,先距离优先,距离相同再判断评分
       IProcessRule rule = new AcceptNumRule(null);
        IProcessRule ruleChain = new DistanceRule(rule);
        // 策略2:构建责任链,先评分优先,评分相同再判断接单数
//        IProcessRule rule = new AcceptNumRule(null);
//        IProcessRule ruleChain = new ScoreRule(rule);
        // 策略3:构建责任链,先接单数优先,接单数相同再判断评分
//        IProcessRule rule = new ScoreRule(null);
//        IProcessRule ruleChain = new AcceptNumRule(rule);

根据需求我们平台支持距离优先策略、评分优先策略、最少接单优先策略,针对上边的代码我们可以基于策略模式定义不同的策略去优化。

首先阅读下边的策略接口:

package com.jzo2o.orders.dispatch.strategys;

import com.jzo2o.orders.dispatch.model.dto.ServeProviderDTO;

import java.util.List;

/**
 * @author Mr.M
 * @version 1.0
 * @description 策略接口
 * @date 2023/11/24 10:56
 */
public interface IProcessStrategy {
    /**
     * 从服务人员/机构列表中获取高优先级别的一个,如果出现多个相同优先级随机获取一个
     *
     * @param serveProviderDTOS 服务人员/机构列表
     * @return
     */
    ServeProviderDTO getPrecedenceServeProvider(List<ServeProviderDTO> serveProviderDTOS);
}

每个策略类都需要实现getPrecedenceServeProvider(List<ServeProviderDTO> serveProviderDTOS)方法,其逻辑是一样的,每个策略类组装的责任链是不同的,我们定义抽象类将共同的方法写在抽象类中。

阅读抽象策略类:

package com.jzo2o.orders.dispatch.strategys;

import com.jzo2o.common.utils.CollUtils;
import com.jzo2o.orders.dispatch.model.dto.ServeProviderDTO;
import com.jzo2o.orders.dispatch.rules.IDispatchRule;

import java.util.List;
import java.util.Objects;

/**
 * @author Mr.M
 * @version 1.0
 * @description 抽象策略类
 * @date 2023/11/24 11:53
 */
public abstract class AbstractStrategyImpl implements IProcessStrategy {

    private final IProcessRule processRule;

    public AbstractStrategyImpl() {
        this.processRule = getRules();
    }

    /**
     * 设置派单规则
     *
     * @return
     */
    protected abstract IProcessRule getRules();

    @Override
    public ServeProviderDTO getPrecedenceServeProvider(List<ServeProviderDTO> serveProviderDTOS) {
        // 1.判空
        if (CollUtils.isEmpty(serveProviderDTOS)) {
            return null;
        }

        // 2.根据优先级获取高优先级别的
        serveProviderDTOS = processRule.filter(serveProviderDTOS);

        // 3.数据返回
        // 3.1.唯一高优先级直接返回
        int size = 1;
        if ((size = CollUtils.size(serveProviderDTOS)) == 1) {
            return serveProviderDTOS.get(0);
        }
        // 3.2.多个高优先级随即将返回
        int randomIndex = (int) (Math.random() * size);
        return serveProviderDTOS.get(randomIndex);
    }
}

 

定义各个策略类:

定义距离优先策略类

package com.jzo2o.orders.dispatch.strategys;

/**
 * @author Mr.M
 * @version 1.0
 * @description 先距离优先,距离相同再判断评分
 * @date 2023/11/24 11:56
 */
public class DistanceStrategyImpl extends AbstractStrategyImpl  {
    @Override
    protected IProcessRule getRules() {
        //构建责任链,先距离优先,距离相同再判断接单数
        IProcessRule acceptNumRule = new AcceptNumRule(null);
        IProcessRule ruleChain = new DistanceRule(rule);
        return ruleChain;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值