消费者模式小例子

生产消费者模式是日常开发中常用的设计模式之一,最为关键的作用呢,其实就是将一个长过程拆分为两部分或者多部分异步完成。设计模式更符合面向对象的思想。
举个反面例子:
下面这张图真**丑🤮,不过也是日常开发中的常态,这种编程风格近乎于面向过程开发。
长业务.png

客户端在发起一次请求之后,后台需要记录本次用户的请求记录,对于客户端来说,记录日志的操作客户端是不需要感知到的,客户端只在乎返回结果。记录log完全是服务端自己的事情。

主要的缺点
1.对于用户不需要感知的操作却完全运行在客户端请求-响应的线程上。
2.每一次访问产生的一条sql都需要去独立io数据库,挺浪费的。
3.不符合面向对象的编程思想,修改log业务代码时其实就是在修改主流程业务的代码。

怎么优化呢?
我们可以把记录log的操作剥离出来,不占用用户请求-响应的这条线程,并且将记录log的任务放在一个缓冲区里,批量持久化到数据库中。这样一来似乎合理了很多。

是的,下面这张图更丑。。。
消费者模式.png

代码设计如下:

LogService 接口设计

public interface LogService{

    /**
     * 对外提供一个添加任务的方法
     * @param e
     */
    void add(LogEntity e);
}

LogServiceImpl 具体实现

@Slf4j
@Service
public class LogServiceImpl extends ServiceImpl<LogMapper, LogEntity> implements LogService {

    /**
     * 批量保存的大小
     */
    private static final int BATCH_SAVE_SIZE = 200;
    /**
     * 日志任务队列
     */
    private static final BlockingQueue<LogEntity> QUEUE = new LinkedBlockingQueue<>();
    /**
     * 单一线程池,具体的消费者
     */
    private static final ExecutorService EXECUTOR_SERVICE = Executors.newSingleThreadExecutor();

    @PostConstruct
    public void init(){
        //启动后台任务
        EXECUTOR_SERVICE.submit(this::backEndTask);
    }

    @Override
    public void add(LogEntity e) {
        if (Objects.isNull(e)) {
            return;
        }
        if (QUEUE.offer(e)) {
            log.error("添加任务失败,{}", e);
        }
    }

    private void backEndTask() {
        final List<LogEntity> logTasks = new ArrayList<>(BATCH_SAVE_SIZE);
        while (true) {
            //检索并删除此队列的头,如果此队列为空,则返回null
            LogEntity logTask = QUEUE.poll();
            //如果不是null,将任务添加进集合
            if (!Objects.isNull(logTask)) {
                logTasks.add(logTask);
            }
            //如果是空表示队列中没有任务了,或者集合的容量已经满了,就批量保存
            if (Objects.isNull(logTask) || logTasks.size() == BATCH_SAVE_SIZE) {
                saveAll(logTasks);
                logTasks.clear();
                try {
                    //检索并删除此队列的头,必要时等待,直到某个元素变为可用。
                    logTasks.add(QUEUE.take());
                } catch (InterruptedException e) {
                    logTasks.clear();
                    log.error("写入异常", e);
                }
            }
        }

    }

    private void saveAll(List<LogEntity> logs) {

        if (logs == null || logs.size() == 0) {
            return;
        }
        super.saveBatch(logs);
    }

}

关于持久化部分功能结合了mybatis的增强插件mybatis-plus,具体的逻辑运用了阻塞队列的特性。不足的地方还请多多指教。

posted @   0更新  阅读(63)  评论(0编辑  收藏  举报
编辑推荐:
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· PostgreSQL 和 SQL Server 在统计信息维护中的关键差异
阅读排行:
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
· 上周热点回顾(2.17-2.23)
点击右上角即可分享
微信分享提示