并发编程之Master-Worker模式

时间:2022-07-16 16:22:23

Master-Worker模式的核心思想是在于Master进程和Worker进程各自分担各自的任务,协同完成信息处理的模式
使用一个图例来简单描述一下:
并发编程之Master-Worker模式
如图所示Master-Worker的工作机制就是Master接收到了一个任务,对任务进行拆分,并且分配给各个Worker,让各个Worker之间各自运行各自的子任务,最后Worker们的返回结果交给Master进行汇总并且最后返回给任务的发起方.

Master-Worker是一种并行模式,Master是主要进程,Master中有维护着一个Worker进程队列.Master把一个大的而且复杂的业务拆分成若干小的业务,只要是互不影响的都可以分而治之相互独立.可以通过多线程或多进程甚至多机联合计算,把拆分后的小业务交给更多的CPU或机器处理,通过并发/并行的方式提高整体业务的运算速度,压榨系统性能来提高效率.

这样做的好处就是,在某些业务场景下,尤其是业务比较复杂而且数据量较大的情况下,例如财务账单结算和生成.一个账单需要有很多关联计算而且条件和参数众多的时候,如果把所有的业务都放在一个任务中,效率是比较低的,数据量大的情况下往往耗时巨大,少则几十分钟,夸张的有整整一宿.这个在圆通速递的罗汉财务系统中体现尤其严重..不信可以问问许双芳.

具体的实现细节是Master任务切分 –> 交给任务队列 –> Worker处理任务
Master可以创建Worker线程池,分发任务给Worker.也可以只负责任务的接收和拆分,单不负责Worker的管理,通过其他第三方工具来负责Worker的监控和调度,这样对于解耦方面比较有利.

OK 有了思想之后 剩下的就是通过代码来简单实现一下

先创建Master和Worker
Master:

package com.unsc.Master_Worker;

import java.util.HashMap;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;

/** * Created by DELL on 2017/12/25. * Master-Worker中的 Master 类 * @author 犯罪嫌疑人卢某 */
public class cMaster {
    /** * 创建一个ConcurrentLinkedQueue 用来盛放任务 * ConcurrentLinkedQueue是一个线程安全的*线程安全队列 * 对元素的排序遵循先进先出的原则 获取元素时返回头部元素 添加元素则为尾部 */
    private Queue<Object> workersQueue = new ConcurrentLinkedQueue<>();
    /** * 创建HashMap<K,V> 来存放Worker对象 */
    private Map<String, Thread> workers = new HashMap<>();
    /** * 创建ConcurrentHashMap来存放Worker计算后的结果集 */
    private Map<String, Object> resultMap = new ConcurrentHashMap<>();
    /** * Master类的有参构造 * @param worker Worker对象 * @param workersCount Worker的数量 用来创建对应数量的线程 */
    public cMaster(cWorker worker , int workersCount ) {
        worker.setIntoWorkersQueue(this.workersQueue);
        worker.setResultMap(this.resultMap);
        /* * 创建对应数量的线程来模拟Worker * 至于显式new Thread() 阿里的代码规约什么的对我这个无业游民毫无约束 随它去 */
        for (int i = 0 ; i < workersCount ; i ++) {
            workers.put(Integer.toString(i) , new Thread(worker , Integer.toString(i)));
        }
    }
    /** * 判断是否所有的子任务都已完成 * @return 是否完成 */
    public boolean isComplete() {
        for(Map.Entry<String, Thread> entry : workers.entrySet()) {
            if (entry.getValue().getState() != Thread.State.TERMINATED) {
                return false;
            }
        }
        return true;
    }
    /** * 向任务队列中提交子任务 */
    public void missionSubmit(Object mission) {
        workersQueue.add(mission);
    }
    /** * 返回子任务的结果集 * @return 子任务运行完毕的结果集 */
    public Map<String, Object> getResultMap() {
        return resultMap;
    }
    /** * 启动所有的Workers线程 开始并行计算 */
    public void startAllWorkes() {
        for (Map.Entry<String,Thread> threadEntry : workers.entrySet()) {
            threadEntry.getValue().start();
        }
    }
}

再是Worker:

package com.unsc.Master_Worker;

import java.util.Map;
import java.util.Queue;


/** * Created by DELL on 2017/12/25. * Master-Worker 中的 Worker类 * @author 犯罪嫌疑人卢某 */
public class cWorker implements Runnable{
    /** * Worker中的任务队列 */
    private Queue<Object> workQueue;
    /** * Worker中任务的结果集Map */
    private Map<String, Object> resultMap;
    /** * 实现Runnable接口重写的Run方法 */
    @Override
    public void run() {
        /* * 设置轮询 获取子任务并且处理 */
        while (true) {
            Object mission = workQueue.poll();
            if (mission == null) {
                break;
            }
            /* * 模拟子任务的处理 并且把处理结果加入结果集 */
            Object result = executeMission(mission);
            resultMap.put(Integer.toString(mission.hashCode()) , result);
        }
    }
    /** * 具体执行任务的业务逻辑 这里是基本的Worker 具体的逻辑交给子类实现 * @param mission 任务 * @return 执行结果 */
    public Object executeMission(Object mission) {
        return mission;
    }
    /** * 设置Worker的任务队列 * @param workersQueue 任务队列 */
    void setIntoWorkersQueue(Queue<Object> workersQueue) {
        this.workQueue = workersQueue;
    }
    /** * 设置Worker的结果集 * @param resultMap 结果集 */
    void setResultMap(Map<String, Object> resultMap) {
        this.resultMap = resultMap;
    }
}

再创建一个Worker的具体的实现类 这里通过一个TrueWorker类重写Worker中的executeMission方法执行具体的操作.
TrueWorker:

package com.unsc.Master_Worker;

import java.util.Map;
import java.util.Set;

/** * Created by DELL on 2017/12/25. * Master-Worker模式的测试 * @author 犯罪嫌疑人卢某 */
public class MasterWorkerTest {
    public static void main(String[] args) {
        /* * 设置6个worker和100个子任务 for循环里面的魔法值无视吧 */
        cMaster master = new cMaster(new TrueWorker(), 4);
        for (int i = 1 ; i < 101 ; i++) {
            master.missionSubmit(i + 0.1);
        }
        /* * Master让所有的Worker开始工作 并且在运作完毕后获得结果集 */
        //打一个时间戳 对比测试消耗的时间
        long startTime = System.currentTimeMillis();
        master.startAllWorkes();
        Map<String, Object> resultMap = master.getResultMap();
        long endTime = System.currentTimeMillis();
        double finalScore = 0;
        while (true) {
            Set<String> keySet = resultMap.keySet();
            String key = null;
            for (String s : keySet) {
                key = s;
                break;
            }
            //计算结果变量
            Double score = null;
            if (key != null) {
                //计算结果并且从结果集中删除
                score = (Double) resultMap.get(key);
                resultMap.remove(key);
            }
            if (score != null) {
                finalScore += score;
            }
            if (master.isComplete() && resultMap.size() == 0) {
                System.out.println("任务执行完毕..");
                break;
            }
        }
        System.out.println("结果的值是 --->>> " + finalScore);
        System.out.println("总计耗时 : " + (endTime - startTime) + " ms");
    }
}

因为int类型计算太快了 我才用双精度浮点来运算 排除掉创建线程和其他的操作单纯就关注任务提交和获得结果集的耗时 得到的运行结果如图:
并发编程之Master-Worker模式

同样的再用传统的方法 再来检测一下效率:
代码如下 :

package com.unsc.Master_Worker;

/** * Created by DELL on 2017/12/25. * 直接单线程在main中计算 1 到 100的平方和 * @author 犯罪嫌疑人卢某 */
public class OldMethod {
    public static void main(String[] args) {
        /* * 开始的时间戳 */
        long startTime = System.currentTimeMillis();
        double finalScore = 0;
        for(int i = 0 ; i < 101 ; i++) {
            double num = i + 0.1;
            finalScore += num * num;
        }
        System.out.println("结果是 : ---->>>> " + finalScore);
        long endTime = System.currentTimeMillis();
        System.out.println("耗时总计 : " + (endTime - startTime) + " ms");
    }
}

结果如图:
并发编程之Master-Worker模式

这里存在一些性能差别 虽然很小 只是1ms的差距 但是在高并发大数据量的情况下 会有比较明显的性能差距 只是单机上进行测试不是很明显罢了..