消费者模式小例子

生产消费者模式是日常开发中常用的设计模式之一,最为关键的作用呢,其实就是将一个长过程拆分为两部分或者多部分异步完成。设计模式更符合面向对象的思想。
举个反面例子:
下面这张图真**丑🤮,不过也是日常开发中的常态,这种编程风格近乎于面向过程开发。
长业务.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 @ 2020-08-27 10:17  0更新  阅读(70)  评论(0)    收藏  举报