高并发设计模式-Master-Worker 模式

1,685 阅读5分钟

这是我参与8月更文挑战的第15天,活动详情查看:8月更文挑战

Master-Worker 模式是一种常见的高并发模式,它的核心思想,任务的调度和执行分离,调度任务的角色为 Master,执行任务的角色为 Worker,Master 负责接收和、分配任务和合并(Merge) 任务结果,Worker 进程负责执行任务。Master-Worker 模式是一种归并类型的模式。

Master-Worker架构

  1. master中有两个属性,一个是存放需要执行的任务,另一个是任务调度线程,专门从任务队列中获取任务并派发给worker线程。

  2. worker中也有两个属性,一个是存放需要执行的任务,另一个是执行任务的线程,执行任务的线程不断的从阻塞队列中获取任务并执行。

image.png

代码实现

因为涉及代码到封装性,所有的代码最终都会放到同一个包中,利用包特有的访问权限对一些方法实现访问权限限制,只允许包内的类进行访问。

Task封装

  1. task中核心的属性有taskId,workerId,任务执行后的结果以及对结果的异步回调。

  2. 通过抽象方法exec交给用户实现具体的任务

  3. 每个任务都会通过WORKER_ID_GENERATOR生成一个唯一的taskId

public abstract class Task<R> {

    private static final AtomicInteger WORKER_ID_GENERATOR = new AtomicInteger(0);

    /**
     * 任务id
     */
    private final int taskId;

    /**
     * 执行任务的work线程Id
     */
    @Setter
    private int workerId;

    /**
     * 任务执行结果
     */
    private R result;

    /**
     * 任务执行完后,对结果的回调
     */
    private Consumer<R> resultAction;

    public Task() {
        this.taskId = WORKER_ID_GENERATOR.getAndIncrement();
    }

    /**
     * 执行任务,执行完任务后并进行回调
     */
    void execute() {
        this.result = this.exec();
        if (resultAction != null) {
            resultAction.accept(this.result);
        }
        System.out.println(toString());
    }

    /**
     * 执行任务
     *
     * @return R
     */
    protected abstract R exec();

    @Override
    public String toString() {
        return "Task{" +
                "taskId=" + taskId +
                ", workerId=" + workerId +
                ", result=" + result +
                '}';
    }

    void setResultAction(Consumer<R> resultAction) {
        this.resultAction = resultAction;
    }
}

Worker

Worker 接收 Master 分配的任务,同样也通过阻塞队列对局部任务进行缓存。Worker 所拥有 的线程作为局部任务的阻塞队列的消费者,不断从阻塞队列获取任务并且执行,执行完成后回调 Master 传递过来的回调函数。

public class Worker<R> {

    private final ArrayBlockingQueue<Task<R>> taskQueue = new ArrayBlockingQueue<>(32);

    private static final AtomicInteger WORKER_ID_GENERATOR = new AtomicInteger();

    private final int workerId;

    private final Thread workerThread;

    public Worker() {
        this.workerId = WORKER_ID_GENERATOR.getAndIncrement();
        workerThread = new Thread(this::execute);
        workerThread.start();
    }

    private void execute() {
        while (true) {
            try {
                Task<R> task = taskQueue.take();
                task.setWorkerId(workerId);
                task.execute();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public void submit(Task<R> task) {
        taskQueue.offer(task);
    }

}

Master

Master 负责接收客户端提交的任务,然后通过阻塞队列对任务进行缓存。Master 所拥有的线 程作为阻塞队列的消费者,不断从阻塞队列获取任务并轮流分给 Worker。

public class Master<T extends Task<R>, R> {

    private final Map<String, Worker<R>> workerMap = new HashMap<>();

    private final ArrayBlockingQueue<Task<R>> taskQueue = new ArrayBlockingQueue<>(1024);

    private Thread masterThread = null;

    public Master(int workCount) {
        for (int i = 0; i < workCount; i++) {
            Worker<R> worker = new Worker<>();
            workerMap.put(String.format("%s-%s", "worker", i), worker);
        }
        masterThread = new Thread(this::execute);
        masterThread.start();
    }

    private void execute() {
        while (true) {
            workerMap.forEach((workerName, worker) -> {
                try {
                    Task<R> task = this.taskQueue.take();
                    worker.submit(task);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
    }

    public void submit(T task, Consumer<R> resultAction) {
        task.setResultAction(resultAction);
        taskQueue.add(task);
    }
}

案例实战

累加1~1000000

分析,可以对累加进行拆分成很多个子任务,每个任务负责累加一部分,将每个子任务得到的结果进行归并。

  1. 任务拆分
public class NumAddTask extends Task<Long> {

    private final long init;

    public NumAddTask(long init) {
        this.init = init;
    }

    @Override
    protected Long exec() {
        long res = 0;
        // 每个线程累加10万
        for (int i = 0; i < 100000; i++) {
            res += i + init;
        }
        return res;
    }
}
  1. 需求实现
public class MasterWorkerTest {
    @Test
    public void test() throws InterruptedException {
        AtomicLong totalCount = new AtomicLong(0);
        Master<NumAddTask, Long> master = new Master<>(4);
        master.submit(new NumAddTask(1), totalCount::addAndGet);
        master.submit(new NumAddTask(100001), totalCount::addAndGet);
        master.submit(new NumAddTask(200001), totalCount::addAndGet);
        master.submit(new NumAddTask(300001), totalCount::addAndGet);
        master.submit(new NumAddTask(400001), totalCount::addAndGet);
        master.submit(new NumAddTask(500001), totalCount::addAndGet);
        master.submit(new NumAddTask(600001), totalCount::addAndGet);
        master.submit(new NumAddTask(700001), totalCount::addAndGet);
        master.submit(new NumAddTask(800001), totalCount::addAndGet);
        master.submit(new NumAddTask(900001), totalCount::addAndGet);
        // 防止主线程退出
        Thread.sleep(3000);
        System.out.println("total Count = " + totalCount);
    }
}
  1. 每个任务执行结果及其总结果
Task{taskId=0, workerId=2, result=5000050000}
Task{taskId=1, workerId=1, result=15000050000}
Task{taskId=2, workerId=0, result=25000050000}
Task{taskId=3, workerId=3, result=35000050000}
Task{taskId=7, workerId=3, result=75000050000}
Task{taskId=4, workerId=2, result=45000050000}
Task{taskId=5, workerId=1, result=55000050000}
Task{taskId=6, workerId=0, result=65000050000}
Task{taskId=8, workerId=2, result=85000050000}
Task{taskId=9, workerId=1, result=95000050000}
total Count = 500000500000

Netty中的Master-Worker模式

  1. 高性能传输模式——Reactor 模式,就是 Master-Worker 模式在传输领域的一种应用。

  2. 在 Netty 中,EventLoop 反应器内部有一个线程负责 Java NIO 选择器的事件的轮询,然后进行对应的事件 分发。事件分发(Dispatch)的目标就是 Netty 的 Handler 处理器(含用户定义的业务处理器)。

image.png

  1. Netty 服务器程序中需要设置两个 EventLoopGroup 轮询组,一个组负责新连接的监听和接收,另外一个组负责 IO 传输事件的轮询与分发,两个轮询组的职责具体如下:

    1. 负责新连接的监听和接收的 EventLoopGroup 轮询组中的反应器(Reactor),完成查询 通道的新连接 IO 事件查询。
    2. 另一个轮询组中的反应器(Reactor),完成查询所有子通道的 IO 事件,并且执行对应 的 Handler 处理器完成 IO 处理——例如数据的输入和输出。
  2. Netty 既是基于 Reactor 模式实现,也体现了 Master-Worker 模式的思想。Netty 的 EventLoop (Reactor 角色)可以对应到 Master-Worker 模式的 Worker 角色;而 Netty 的 EventLoopGroup 轮 询组,则可以对应到 Master-Worker 模式的 Master 角色