1. 项目概述:当PLC遇上自动洗车系统
在工业自动化领域,PLC(可编程逻辑控制器)就像人类的中枢神经系统,而S7-1200系列则是西门子家族中的"全能选手"。三年前我在一家汽车服务中心首次接触到这个组合——他们需要将传统人工洗车升级为全自动流水线。这个看似简单的需求背后,其实隐藏着对可靠性、安全性和效率的极致追求。
自动洗车系统最核心的矛盾在于:既要保证高压水柱、旋转刷毛等设备的强力清洁效果,又要确保车辆和人员绝对安全。传统继电器控制方案布线复杂、故障率高,而PLC恰恰能完美解决这些问题。S7-1200的模块化设计特别适合这种中等规模控制系统,其自带的高速计数器能精准控制传送带位置,PROFINET接口则方便连接各类传感器和执行器。
关键提示:选择S7-1200而非更高级的S7-1500,主要考虑性价比因素。洗车系统对运算速度要求不高,但需要稳定可靠的I/O处理能力,这正是S7-1200的优势所在。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 系统架构设计解析
2.1 硬件配置方案
经过多次现场实测,最终确定的硬件配置如下表所示:
| 模块类型 | 型号 | 数量 | 用途 |
|---|---|---|---|
| CPU | 1214C DC/DC/DC | 1 | 主控制器 |
| 数字量输入 | SM 1221 16x24VDC | 2 | 车辆检测/急停信号 |
| 数字量输出 | SM 1222 16x继电器 | 3 | 水泵/风机/刷毛控制 |
| 模拟量输入# 1. 概述 |
本文分享 Dubbo 的线程池策略。在 《精尽 Dubbo 源码分析 —— 线程池》 一文中,我们已经看到了多种线程池和线程队列,Dubbo 是如何使用的呢?Even if you know the SPI mechanism , you may not understand how to use it . So , let's go .
本文涉及类图如下:

2. Dispatcher
com.alibaba.dubbo.common.extension.Dispatcher ,SPI 拓展接口,线程派发器。代码如下:
code复制@SPI(AllDispatcher.NAME)
public interface Dispatcher {
/**
* dispatch the message to threadpool.
*
* 调度消息到线程池
*
* @param handler 处理器
* @param url url
* @return ChannelHandler
*/
@Adaptive({Constants.DISPATCHER_KEY, "dispather", "channel.handler"})
// 最后一个参数,实际是引用{@link ExtensionLoader}的{@link #getName}的注释说明
ChannelHandler dispatch(ChannelHandler handler, URL url);
}
@SPI(AllDispatcher.NAME)注解,Dubbo SPI 拓展点,默认为"all"。@Adaptive({Constants.DISPATCHER_KEY, "dispather", "channel.handler"})注解,基于 Dubbo SPI Adaptive 机制,加载对应的线程派发器实现,使用 URL 的dispatcher参数。- ChannelHandler 参数,可以理解为需要被派发器调度的 ChannelHandler 实现类。
2.1 AllDispatcher
com.alibaba.dubbo.remoting.dispatcher.all.AllDispatcher ,实现 Dispatcher 接口,所有消息都派发到线程池,包括请求,响应,连接事件,断开事件等。代码如下:
code复制public class AllDispatcher implements Dispatcher {
public static final String NAME = "all";
@Override
public ChannelHandler dispatch(ChannelHandler handler, URL url) {
return new AllChannelHandler(handler, url);
}
}
2.2 ConnectionOrderedDispatcher
com.alibaba.dubbo.remoting.dispatcher.connection.ConnectionOrderedDispatcher ,实现 Dispatcher 接口,只有连接事件和断开事件放入线程池,其它消息直接派发。代码如下:
code复制public class ConnectionOrderedDispatcher implements Dispatcher {
public static final String NAME = "connection";
@Override
public ChannelHandler dispatch(ChannelHandler handler, URL url) {
return new ConnectionOrderedChannelHandler(handler, url);
}
}
2.3 DirectDispatcher
com.alibaba.dubbo.remoting.dispatcher.direct.DirectDispatcher ,实现 Dispatcher 接口,所有消息都不派发到线程池,全部在 IO 线程上直接执行。代码如下:
code复制public class DirectDispatcher implements Dispatcher {
public static final String NAME = "direct";
@Override
public ChannelHandler dispatch(ChannelHandler handler, URL url) {
return handler;
}
}
2.4 ExecutionDispatcher
com.alibaba.dubbo.remoting.dispatcher.execution.ExecutionDispatcher ,实现 Dispatcher 接口,只有请求消息和响应消息派发到线程池,其它消息均在 IO 线程上执行。代码如下:
code复制public class ExecutionDispatcher implements Dispatcher {
public static final String NAME = "execution";
@Override
public ChannelHandler dispatch(ChannelHandler handler, URL url) {
return new ExecutionChannelHandler(handler, url);
}
}
2.5 MessageOnlyDispatcher
com.alibaba.dubbo.remoting.dispatcher.message.MessageOnlyDispatcher ,实现 Dispatcher 接口,只有请求消息和响应消息派发到线程池,其它消息均在 IO 线程上执行。代码如下:
code复制public class MessageOnlyDispatcher implements Dispatcher {
public static final String NAME = "message";
@Override
public ChannelHandler dispatch(ChannelHandler handler, URL url) {
return new MessageOnlyChannelHandler(handler, url);
}
}
3. ChannelHandler
在 《精尽 Dubbo 源码分析 —— NIO 服务器》 一文中,我们已经详细分享了 Dubbo 对 ChannelHandler 的封装处理。在本小节中,我们只分享 派发器 对 ChannelHandler 的处理。
3.1 WrappedChannelHandler
com.alibaba.dubbo.remoting.transport.dispatcher.WrappedChannelHandler ,实现 ChannelHandler 接口,ChannelHandler 装饰器,将不同的消息派发到线程池中。目前被 AllChannelHandler、ConnectionOrderedChannelHandler、ExecutionChannelHandler、MessageOnlyChannelHandler 继承。
3.1.1 构造方法
code复制/**
* 线程池
*/
protected final ExecutorService executor;
/**
* 被装饰的 ChannelHandler
*/
protected final ChannelHandler handler;
/**
* URL
*/
protected final URL url;
public WrappedChannelHandler(ChannelHandler handler, URL url) {
this.handler = handler;
this.url = url;
// 创建线程池
executor = (ExecutorService) ExtensionLoader.getExtensionLoader(ThreadPool.class).getAdaptiveExtension().getExecutor(url);
// 添加线程池到 DataStore 中
String componentKey = Constants.EXECUTOR_SERVICE_COMPONENT_KEY;
if (Constants.CONSUMER_SIDE.equals(url.getParameter(Constants.SIDE_KEY))) {
componentKey = Constants.CONSUMER_SIDE;
}
DataStore dataStore = ExtensionLoader.getExtensionLoader(DataStore.class).getDefaultExtension();
dataStore.put(componentKey, Integer.toString(url.getPort()), executor);
}
executor属性,线程池。在构造方法中,调用ExtensionLoader#getExtensionLoader(Class<T> type)方法,获得线程池的自适应拓展实现类,基于 Dubbo SPI 机制,创建对应的线程池。目前有FixedThreadPool、CachedThreadPool、LimitedThreadPool、EagerThreadPool四种线程池策略。在 《精尽 Dubbo 源码分析 —— 线程池》 一文中,有详细解析。handler属性,被装饰的 ChannelHandler 。url属性,URL 。- 将创建好的线程池,添加到
com.alibaba.dubbo.common.store.DataStore中。在 《精尽 Dubbo 源码分析 —— 数据存储》 一文中,有详细解析。
3.1.2 shutdown
#shutdown() 方法,关闭线程池。代码如下:
code复制public void shutdown() {
try {
if (executor != null) {
executor.shutdown();
}
} catch (Throwable t) {
logger.warn("fail to destroy thread pool of server: " + t.getMessage(), t);
}
}
3.1.3 实现方法
WrappedChannelHandler 对 ChannelHandler 的实现方法,直接调用 handler 对应的方法。代码如下:
code复制@Override
public void connected(Channel channel) throws RemotingException {
handler.connected(channel);
}
@Override
public void disconnected(Channel channel) throws RemotingException {
handler.disconnected(channel);
}
@Override
public void sent(Channel channel, Object message) throws RemotingException {
handler.sent(channel, message);
}
@Override
public void received(Channel channel, Object message) throws RemotingException {
handler.received(channel, message);
}
@Override
public void caught(Channel channel, Throwable exception) throws RemotingException {
handler.caught(channel, exception);
}
3.2 AllChannelHandler
com.alibaba.dubbo.remoting.transport.dispatcher.all.AllChannelHandler ,实现 WrappedChannelHandler 抽象类,所有消息都派发到线程池,包括请求,响应,连接事件,断开事件等。代码如下:
code复制public class AllChannelHandler extends WrappedChannelHandler {
public AllChannelHandler(ChannelHandler handler, URL url) {
super(handler, url);
}
/** 处理连接事件 */
@Override
public void connected(Channel channel) throws RemotingException {
// 获得线程池
ExecutorService cexecutor = getExecutorService();
try {
// 提交线程池,执行任务
cexecutor.execute(new ChannelEventRunnable(channel, handler, ChannelState.CONNECTED));
} catch (Throwable t) {
throw new ExecutionException("connect event", channel, getClass() + " error when process connected event .", t);
}
}
/** 处理断开事件 */
@Override
public void disconnected(Channel channel) throws RemotingException {
ExecutorService cexecutor = getExecutorService();
try {
cexecutor.execute(new ChannelEventRunnable(channel, handler, ChannelState.DISCONNECTED));
} catch (Throwable t) {
throw new ExecutionException("disconnect event", channel, getClass() + " error when process disconnected event .", t);
}
}
/** 处理请求和响应消息 */
@Override
public void received(Channel channel, Object message) throws RemotingException {
ExecutorService cexecutor = getExecutorService();
try {
cexecutor.execute(new ChannelEventRunnable(channel, handler, ChannelState.RECEIVED, message));
} catch (Throwable t) {
//TODO 临时解决线程池满后异常信息无法发送到对端的问题。待重构
//fix 线程池满了拒绝调用不返回,导致消费者一直等待超时
if(message instanceof Request && t instanceof RejectedExecutionException){
Request request = (Request)message;
if(request.isTwoWay()){
String msg = "Server side(" + url.getIp() + "," + url.getPort() + ") threadpool is exhausted ,detail msg:" + t.getMessage();
Response response = new Response(request.getId(), request.getVersion());
response.setStatus(Response.SERVER_THREADPOOL_EXHAUSTED_ERROR);
response.setErrorMessage(msg);
channel.send(response);
return;
}
}
throw new ExecutionException(message, channel, getClass() + " error when process received event .", t);
}
}
/** 处理异常消息 */
@Override
public void caught(Channel channel, Throwable exception) throws RemotingException {
ExecutorService cexecutor = getExecutorService();
try {
cexecutor.execute(new ChannelEventRunnable(channel, handler, ChannelState.CAUGHT, exception));
} catch (Throwable t) {
throw new ExecutionException("caught event", channel, getClass() + " error when process caught event .", t);
}
}
}
- 对于每个方法,创建 ChannelEventRunnable 任务,提交到线程池中。在 「3.3 ChannelEventRunnable」 详细解析。
- 在
#received(channel, message)方法中,提交任务发生异常时,如果是 Request 请求,并且线程池已满,会返回 SERVER_THREADPOOL_EXHAUSTED_ERROR 响应,从而避免 Consumer 侧,一直等待超时。🙂 良心开发者。
3.2.1 getExecutorService
#getExecutorService() 方法,获得线程池。代码如下:
code复制@Override
public ExecutorService getExecutorService() {
ExecutorService cexecutor = executor;
if (cexecutor == null || cexecutor.isShutdown()) { // 无可用线程池,使用共享线程池
cexecutor = SHARED_EXECUTOR;
}
return cexecutor;
}
- 当
executor未初始化或者关闭时,使用共享线程池SHARED_EXECUTOR。该线程池在io.netty.channel.MultithreadEventLoopGroup中。
3.3 ChannelEventRunnable
com.alibaba.dubbo.remoting.transport.ChannelEventRunnable ,实现 Runnable 接口,Channel 事件任务。代码如下:
code复制public class ChannelEventRunnable implements Runnable {
/**
* Channel 对象
*/
private final Channel channel;
/**
* ChannelHandler 对象
*/
private final ChannelHandler handler;
/**
* Channel 状态
*/
private final ChannelState state;
/**
* 异常
*/
private final Throwable exception;
/**
* 消息
*/
private final Object message;
public ChannelEventRunnable(Channel channel, ChannelHandler handler, ChannelState state) {
this(channel, handler, state, null);
}
public ChannelEventRunnable(Channel channel, ChannelHandler handler, ChannelState state, Object message) {
this(channel, handler, state, message, null);
}
public ChannelEventRunnable(Channel channel, ChannelHandler handler, ChannelState state, Throwable exception) {
this(channel, handler, state, null, exception);
}
public ChannelEventRunnable(Channel channel, ChannelHandler handler, ChannelState state, Object message, Throwable exception) {
this.channel = channel;
this.handler = handler;
this.state = state;
this.message = message;
this.exception = exception;
}
@Override
public void run() {
// 执行
switch (state) {
case CONNECTED:
try {
handler.connected(channel);
} catch (Exception e) {
logger.warn("ChannelEventRunnable handle " + state + " operation error, channel is " + channel, e);
}
break;
case DISCONNECTED:
try {
handler.disconnected(channel);
} catch (Exception e) {
logger.warn("ChannelEventRunnable handle " + state + " operation error, channel is " + channel, e);
}
break;
case SENT:
try {
handler.sent(channel, message);
} catch (Exception e) {
logger.warn("ChannelEventRunnable handle " + state + " operation error, channel is " + channel, e);
}
break;
case RECEIVED:
try {
handler.received(channel, message);
} catch (Exception e) {
logger.warn("ChannelEventRunnable handle " + state + " operation error, channel is " + channel, e);
}
break;
case CAUGHT:
try {
handler.caught(channel, exception);
} catch (Exception e) {
logger.warn("ChannelEventRunnable handle " + state + " operation error, channel is " + channel, e);
}
break;
default:
logger.warn("unknown state: " + state + ", message is " + message);
}
}
}
- 根据传入的
state状态,调用handler对应的方法。
3.4 ConnectionOrderedChannelHandler
com.alibaba.dubbo.remoting.transport.dispatcher.connection.ConnectionOrderedChannelHandler ,实现 WrappedChannelHandler 抽象类,只有连接事件和断开事件放入线程池,其它消息直接派发。代码如下:
code复制public class ConnectionOrderedChannelHandler extends WrappedChannelHandler {
/**
* 连接事件线程池
*/
protected final ThreadPoolExecutor connectionExecutor;
/**
* 队列大小
*/
private final int queuewarninglimit;
public ConnectionOrderedChannelHandler(ChannelHandler handler, URL url) {
super(handler, url);
// 创建连接事件线程池
String threadName = url.getParameter(Constants.THREAD_NAME_KEY, Constants.DEFAULT_THREAD_NAME);
connectionExecutor = new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(url.getPositiveParameter(Constants.CONNECT_QUEUE_CAPACITY, Integer.MAX_VALUE)), // 队列大小
new NamedThreadFactory(threadName, true), // 线程工厂
new AbortPolicyWithReport(threadName, url) // 拒绝策略
);
queuewarninglimit = url.getParameter(Constants.CONNECT_QUEUE_WARNING_SIZE, Constants.DEFAULT_CONNECT_QUEUE_WARNING_SIZE);
}
@Override
public void connected(Channel channel) throws RemotingException {
try {
// 检查队列数量
checkQueueLength();
// 提交线程池,执行任务
connectionExecutor.execute(new ChannelEventRunnable(channel, handler, ChannelState.CONNECTED));
} catch (Throwable t) {
throw new ExecutionException("connect event", channel, getClass() + " error when process connected event .", t);
}
}
@Override
public void disconnected(Channel channel) throws RemotingException {
try {
// 检查队列数量
checkQueueLength();
// 提交线程池,执行任务
connectionExecutor.execute(new ChannelEventRunnable(channel, handler, ChannelState.DISCONNECTED));
} catch (Throwable t) {
throw new ExecutionException("disconnected event", channel, getClass() + " error when process disconnected event .", t);
}
}
@Override
public void received(Channel channel, Object message) throws RemotingException {
handler.received(channel, message);
}
@Override
public void caught(Channel channel, Throwable exception) throws RemotingException {
handler.caught(channel, exception);
}
/**
* 检查队列数量
*/
private void checkQueueLength() {
if (connectionExecutor.getQueue().size() > queuewarninglimit) {
logger.warn(new IllegalThreadStateException("connectionordered channel handler `queue size: " + connectionExecutor.getQueue().size() + " exceed the warning limit number :" + queuewarninglimit));
}
}
}
- 和 AllChannelHandler 不同,ConnectionOrderedChannelHandler 仅对
#connected(channel)和#disconnected(channel)方法,提交到线程池。并且,线程池是固定的,即connectionExecutor属性。 #checkQueueLength()方法,检查队列数量。若队列大小超过queuewarninglimit大小,打印告警日志。该功能用于预警,当连接事件太多,说明服务器有问题或者连接在频繁重连。
3.5 ExecutionChannelHandler
com.alibaba.dubbo.remoting.transport.dispatcher.execution.ExecutionChannelHandler ,实现 WrappedChannelHandler 抽象类,只有请求消息和响应消息派发到线程池,其它消息均在 IO 线程上执行。代码如下:
code复制public class ExecutionChannelHandler extends WrappedChannelHandler {
public ExecutionChannelHandler(ChannelHandler handler, URL url) {
super(handler, url);
}
@Override
public void connected(Channel channel) throws RemotingException {
handler.connected(channel);
}
@Override
public void disconnected(Channel channel) throws RemotingException {
handler.disconnected(channel);
}
@Override
public void received(Channel channel, Object message) throws RemotingException {
// 获得线程池
ExecutorService cexecutor = getExecutorService();
// 提交线程池,执行任务
if (cexecutor != null) {
try {
cexecutor.execute(new ChannelEventRunnable(channel, handler, ChannelState.RECEIVED, message));
} catch (Throwable t) {
throw new ExecutionException(message, channel, getClass() + " error when process received event .", t);
}
// 无线程池,直接在 IO 线程执行
} else {
handler.received(channel, message);
}
}
@Override
public void caught(Channel channel, Throwable exception) throws RemotingException {
handler.caught(channel, exception);
}
}
3.6 MessageOnlyChannelHandler
com.alibaba.dubbo.remoting.transport.dispatcher.message.MessageOnlyChannelHandler ,实现 WrappedChannelHandler 抽象类,只有请求消息和响应消息派发到线程池,其它消息均在 IO 线程上执行。代码如下:
code复制public class MessageOnlyChannelHandler extends WrappedChannelHandler {
public MessageOnlyChannelHandler(ChannelHandler handler, URL url) {
super(handler, url);
}
@Override
public void connected(Channel channel) throws RemotingException {
handler.connected(channel);
}
@Override
public void disconnected(Channel channel) throws RemotingException {
handler.disconnected(channel);
}
@Override
public void received(Channel channel, Object message) throws RemotingException {
// 获得线程池
ExecutorService cexecutor = getExecutorService();
// 提交线程池,执行任务
try {
cexecutor.execute(new ChannelEventRunnable(channel, handler, ChannelState.RECEIVED, message));
} catch (Throwable t) {
throw new ExecutionException(message, channel, getClass() + " error when process received event .", t);
}
}
@Override
public void caught(Channel channel, Throwable exception) throws RemotingException {
handler.caught(channel, exception);
}
}
- 和 ExecutionChannelHandler 不同,MessageOnlyChannelHandler 必须有线程池,否则会抛出 ExecutionException 异常。
4. 不同派发策略的比较
在 《Dubbo 用户指南 —— 线程模型》 一文中,对不同的派发策略,进行了比较。如下:
不同的派发策略,在设置线程池后,对性能的影响,如下表:
| 配置 | 性能比较 |
|---|---|
dispatcher=all |
高并发时,能充分利用线程池资源,对性能有利。 |
dispatcher=direct |
省去线程池上下文切换的开销,对性能有利。 |
dispatcher=message |
只有请求响应消息派发线程池,其它消息IO线程直接执行。 |
dispatcher=execution |
只有请求消息派发线程池,不含响应。其它消息IO线程直接执行。 |
dispatcher=connection |
在IO线程上,将连接断开事件放入队列,有序逐个执行。 |
可以通过不同的派发策略和线程池的配合使用,达到最佳性能。
666. 彩蛋

😈 又是一篇,相对轻松的文章。
