Netty中实现设备消息串行处理:Semaphore + 线程池

目录

一、问题背景

[二、解决方案:Semaphore + 线程池](#二、解决方案:Semaphore + 线程池)

核心思想

完整代码实现

业务层调用

三、方案优势

四、关键技术点解析

[1. 为什么用Semaphore而不是synchronized?](#1. 为什么用Semaphore而不是synchronized?)

[2. 为什么用CachedThreadPool?](#2. 为什么用CachedThreadPool?)

[3. 资源清理为什么重要?](#3. 资源清理为什么重要?)


一、问题背景

在物联网终端设备接入服务中,我们经常遇到这样的场景:

  • 同一个设备(IMEI)会频繁发送不同类型的消息(登录、位置上报、心跳等)

  • 同类型消息必须串行处理(如连续两个登录请求,必须等第一个处理完再处理第二个)

  • 不同类型消息可以并行处理(如登录和位置上报可以同时进行)

如果用传统的单线程池方案,会导致所有消息排队,严重影响吞吐量;如果用普通线程池,又会出现并发冲突,导致数据错乱。

二、解决方案:Semaphore + 线程池

经过多次尝试,最终采用**Semaphore(信号量)**强制串行,彻底解决问题。

核心思想

  • 为每个 (IMEI, Worker类型) 分配一个Semaphore(许可证数量=1)

  • 任务执行前必须acquire() 获取许可证,执行后**release()**释放

  • 同一个Key的任务自然会排队,不同Key的任务互不影响

完整代码实现

java 复制代码
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.*;

@Slf4j
public class ExecutorCache {

    // IMEI+Worker类型 -> 信号量(保证串行)
    private final static ConcurrentHashMap<String, Semaphore> workerLocks = new ConcurrentHashMap<>();
    
    // IMEI+Worker类型 -> 线程池(负责执行任务)
    private final static ConcurrentHashMap<String, ExecutorService> deviceExecutors = new ConcurrentHashMap<>();

    /**
     * 执行任务,保证同一个 IMEI+Worker 串行执行
     * 
     * @param imei       设备ID
     * @param workerType Worker类型(如 LoginWorker)
     * @param task       需要执行的任务
     */
    public static void executeSerial(String imei, String workerType, Runnable task) {
        String lockKey = workerType + "-" + imei;

        // 获取信号量(最多1个许可 = 互斥锁)
        Semaphore semaphore = workerLocks.computeIfAbsent(lockKey, k -> new Semaphore(1));

        // 获取线程池(缓存线程池,因为锁已经控制了并发)
        ExecutorService executor = deviceExecutors.computeIfAbsent(lockKey, 
            k -> Executors.newCachedThreadPool(r -> new Thread(r, lockKey)));

        // 提交任务到线程池
        executor.submit(() -> {
            try {
                semaphore.acquire();  // 获取锁,如果没有许可则等待
                task.run();           // 执行业务逻辑
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                log.error("任务被中断:{}", lockKey, e);
            } finally {
                semaphore.release();  // 释放锁
            }
        });
    }

    /**
     * 清理设备的所有资源(设备断开时调用)
     */
    public static void removeDevice(String imei) {
        // 清理信号量
        workerLocks.keySet().removeIf(key -> key.endsWith("-" + imei));
        // 清理线程池
        deviceExecutors.keySet().removeIf(key -> key.endsWith("-" + imei));
    }
}

业务层调用

java 复制代码
private void protocolTerminal(String imei, Channel channel, String cmd, String data) {
    switch (cmd) {
        case ProtocolConstant.TLOGIN:
            TerminalChannelCache.putTempTerminalChannelMap(imei, channel);
            ExecutorCache.executeSerial(imei, "LoginWorker", 
                () -> loginWorker.handle(data));
            break;
        case ProtocolConstant.TPOS:
            ExecutorCache.executeSerial(imei, "locationWorker", 
                () -> locationWorker.handle(data));
            break;
        case ProtocolConstant.THEART:
            ExecutorCache.executeSerial(imei, "terHeartWorker", 
                () -> terminalHeartWorker.handle(data));
            break;
        // ... 其他命令类型
    }
}

三、方案优势

特性 传统方案 Semaphore方案
同设备同类型串行 ❌ 依赖线程池复用 ✅ 强制串行
同设备不同类型并行 ✅ 可以 ✅ 可以
资源清理 ⚠️ 容易内存泄漏 ✅ 显式清理
调试难度
代码复杂度

四、关键技术点解析

1. 为什么用Semaphore而不是synchronized?

  • synchronized 会阻塞当前线程(这里是Netty的IO线程),影响其他设备

  • Semaphore配合独立线程池,将等待放到业务线程池中,IO线程快速返回

2. 为什么用CachedThreadPool?

  • 每个 (IMEI, Worker类型) 的任务量不大

  • CachedThreadPool 空闲线程60秒自动回收,不会造成资源浪费

  • 锁机制已经保证了串行,线程池本身不需要限制并发数

3. 资源清理为什么重要?

如果不调用 removeDevice(),随着设备不断上下线:

  • workerLocksdeviceExecutors 会无限增长

  • 导致内存泄漏,最终OOM

相关推荐
一个儒雅随和的男子1 小时前
MQTT常见的问题?
java
2601_961194021 小时前
考研资料电子版|下载|pdf
java·python·考研·eclipse·django·pdf·pygame
骄马之死1 小时前
JVM 核心知识
java·jvm
好家伙VCC2 小时前
Rust+Bioinfo:80ms极速SNP注释引擎
java·开发语言·算法·rust
ANnianStriver2 小时前
PetLumina-AI 驱动的宠物生活管理平台
java·生活·vue3·springboot·ai编程·宠物·全栈开发
好家伙VCC2 小时前
Delta Lake + Flink 实现近实时数据湖 Schema 演化
java·大数据·flink
hoho_122 小时前
如何替换jar包中依赖的其他jar
java·pycharm·jar
码语智行2 小时前
接口请求处理流程
java
布朗克1682 小时前
23 泛型——类型安全的参数化编程
java·泛型