上一篇设计模式——命令模式实战之WCS概要设计(三)基于WCS的逻辑进行了简单抽象,并基于命令模式进行了概要设计。接下来,基于实际仓内情况,进行比较详细的设计。
基础信息
从上图仓库内的情况很复杂,在进行概要设计的时候,需要进行高度的抽象,在详细设计的时候,需要对很多细节进行明确的设计和实现。
这里只基于仓库中的两种设备进行抽象,对场景进行一定的抽象。具体场景如下:
- 五条巷道,一条巷道里面有一台堆垛机(Stacker)
- 每条巷道口有接驳口,要放入当前巷道的货物由输送线(ConveyorLine)送至接驳口
这里涉及到两种设备Stacker
和ConveyorLine
,两种设备的关系是任务执行过程中的两个阶段。文章重点对Stacker
进行设计。
Stacker 控制详细设计
更直观的看一下堆垛机。
图中就是堆垛机,底部是在轨道上的,带有可上下移动并且可以内外伸缩的叉子。
功能点
- 堆垛机的健康状态监控
监控堆垛机的健康状态,确保堆垛机一直在线,对上游的分货位进行通知等逻辑 - 堆垛机的工作线程的健康状态
堆垛机获取到执行命令以后,会有一个执行命令的执行中的一个时间段,需要对工作线程保持监控,如果线程发生异常,需要即时重建工作线程,保证流水线的正常运转。 - 即时获取命令去执行
工作线程要及时获取,待执行的任务或命令,去执行。保证任务被读取到以后,可以准确及时执行。 - 即时反馈命令执行结果
堆垛机执行完以后,需要及时的反馈执行结果,给上游业务逻辑,更新进数据库;以便后续对异常任务人工修复。
分析上述功能点以后,第一思路产生的是实现所有功能,需要多线程、定时任务、阻塞队列、锁 等Java技术实现。
基于队列模型实现
代码流程 如图:
从流程中可以看到核心控制命令顺序和并发性是通过阻塞队列完成的。阻塞队列可以实现任务的有序性和工作线程的阻塞监听。
接下来详细介绍关键类的实现:
堆垛机的实例
// 存放堆垛机实例,
private Map<String/*stacker id*/, Stacker> stackerMap=new HashMap<>();
使用map保存堆垛机的实例,key为堆垛机的id,值为堆垛机实例。
任务队列的声明
// 任务队列
private Map<String/*stacker id*/, LinkedBlockingQueue<StackerTask>> taskQueueMap =new HashMap<>();
使用map声明任务队列,key为堆垛机的id,值为此堆垛机的任务队列,这里使用阻塞队列,工作线程可以利用阻塞队列的阻塞等待特性,实现监听。
堆垛机工作线程监听阻塞队列的核心实现如下 :
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
LinkedBlockingQueue<StackerTask> q = taskQueueMap.get(stacker.getId());
// 阻塞等待任务
while (q.peek() != null) {
StackerTask task = q.poll();
// 执行任务
stacker.action(task, taskCallbackMap.get(task.getTaskId()));
}
}
}, "stacker-"+stacker.getId());
堆垛机的监控实现
if(healthCheckLock.isLocked()){
return;
}
healthCheckLock.lock();
// 检查堆垛机状态
heartbeatPool.execute(new Runnable() {
@Override
public void run() {
try {
for (Stacker stacker: stackerMap.values()) {
stacker.checkHealth();
}
}finally {
healthCheckLock.unlock();
}
}
});
工作线程的监控
// 检查工作线程状态
for (Map.Entry<String,Thread> t : workerMap.entrySet()) {
if(!t.getValue().isAlive()){
rebuildWorker(stackerMap.get(t.getKey()));
}
}
核心代码的完整示例如下:
public class StackerManager {
// 存放堆垛机实例,
private Map<String/*stacker id*/, Stacker> stackerMap=new HashMap<>();
// 任务队列
private Map<String/*stacker id*/, LinkedBlockingQueue<StackerTask>> taskQueueMap =new HashMap<>();
// 存放任务对应的回调函数
private Map<Long/*task id*/, Function<StackerTaskResult,Boolean>> taskCallbackMap=new HashMap<>();
// 调度线程池,负责周期性调度需要执行的任务
private ScheduledExecutorService scheduledExecutorService= Executors.newSingleThreadScheduledExecutor();
// 健康检查锁,避免多个线程同时检查一台设备或者避免一个线程还没检查完,进行第二次检查
private ReentrantLock healthCheckLock= new ReentrantLock();
/**
* 健康检查线程池
*/
private ExecutorService heartbeatPool=Executors.newSingleThreadExecutor();
// 工作线程池
private ExecutorService taskWorkerPool;
// 独立持有工作线程
private Map<String/*stacker id*/,Thread> workerMap=new HashMap<>();
private StackerNotify stackerNotify;
public StackerManager(int stackerCount,StackerNotify stackerNotify){
// 匹配堆垛机的数量
taskWorkerPool =Executors.newFixedThreadPool(stackerCount);
this.stackerNotify=stackerNotify;
}
public void start(){
scheduledExecutorService.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
if(healthCheckLock.isLocked()){
return;
}
healthCheckLock.lock();
// 检查堆垛机状态
heartbeatPool.execute(new Runnable() {
@Override
public void run() {
try {
for (Stacker stacker: stackerMap.values()) {
stacker.checkHealth();
}
}finally {
healthCheckLock.unlock();
}
}
});
// 检查工作线程状态
for (Map.Entry<String,Thread> t : workerMap.entrySet()) {
if(!t.getValue().isAlive()){
rebuildWorker(stackerMap.get(t.getKey()));
}
}
}
}, 0, 500, TimeUnit.MILLISECONDS);
// 启动堆垛机,执行任务
for (Stacker stacker: stackerMap.values()) {
buildWorker(stacker);
}
}
private void buildWorker(Stacker stacker){
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
LinkedBlockingQueue<StackerTask> q = taskQueueMap.get(stacker.getId());
// 阻塞等待任务
while (q.peek() != null) {
StackerTask task = q.poll();
// 执行任务
stacker.action(task, taskCallbackMap.get(task.getTaskId()));
}
}
}, "stacker-"+stacker.getId());
// 注册工作线程
workerMap.put(stacker.getId(),thread);
// 执行工作线程
taskWorkerPool.execute(thread);
}
/**
* 重建工作线程
* @param stacker
*/
private void rebuildWorker(Stacker stacker){
// 中止线程
workerMap.get(stacker).interrupt();
if(stacker.getStackerStatus().isHealth()){
buildWorker(stacker);
}else {
log.warn("堆垛机无法连接,销毁工作线程:{}",stacker.getId());
stackerNotify.notify(stacker.getId(), "无法连接堆垛机");
}
}
public void registerStacker(String id,String ip,int port){
stackerMap.put(id,new Stacker(id,ip,port));
}
/**
* 任务注册状态
* @param task
* @param callback
* @return
*/
public boolean asyncExecuteTask(StackerTask task, Function<StackerTaskResult,Boolean> callback){
Stacker stacker = stackerMap.get(task.getTunnel());
if(!stacker.getStackerStatus().isHealth()){
log.error("堆垛机{}异常,无法注册任务:{}",stacker.getId(),task.getTaskId());
return false;
}
taskQueueMap.get(task.getTunnel()).add(task);
taskCallbackMap.put(task.getTaskId(),callback);
// 异步放入成功
return true;
}
}
上面代码是和整个堆垛机控制的核心类。
归纳复盘
通过上面的核心实现,发现并没有按照预期的命令模式实现,相对于频繁读取数据库,顺序调用代码来说,加入了队列,任务,命令,健康之间的互动等。并且硬件健康状态也没有反馈给上游任务等,实现不完整,代码不够优雅。
下一篇真正的引入命令模式,进行代码重构实现类图如下:
本次代码示例下载