请选择 进入手机版 | 继续访问电脑版

不看后悔的项目中线程池实际应用

[复制链接]
丁翼 发表于 2020-12-31 18:13:43 | 显示全部楼层 |阅读模式 打印 上一主题 下一主题

前言:

  最近在看线程池方面的内容,团结源码学习完其内部原理后,心想自己在项目中有实际使用过线程池吗?想了想,确实在项目中许多地方使用到了线程池;下面来简朴聊下最近在日志方面中多线程的应用:
  

  • 服务接口日志异步线程池化入库处置处罚
  • 定时任务中使用多线程举行日志清理

本文主线:

①、线程池根本原明确读;
②、线程池实际应用例子:


  • 线程池应用 Demo 项目结构形貌
  • 服务接口日志异步线程池化入库处置处罚
  • 定时任务中使用多线程举行日志清理
线程池根本原明确读:

  啥也不说,先贴一张脑图,通过脑图对线程池快速的举行相识;
  除了看图外,也可以通过此文章《ava线程池实现原理及其在美团业务中的实践》对线程池举行详细的相识;

线程池实际应用例子:

  下面就来聊聊最近在项目日志中线程池的应用;
线程池应用Demo项目形貌:

Demo 地点:https://github.com/leishen6/springboot_log
上面说的两种日志方面线程池应用已经写好了Demo,是一个 SpringBoot 项目,项目结构如下图:

服务接口日志异步线程池化入库处置处罚:

  背景服务接口项目中,经常需要对接口的请求报文和响应报文日志做入库生存;
  下面将通过对比 平凡方式入库操纵和线程池方式入库操纵 ,来说说为什么线程池式入库更加优雅;
平凡方式入库操纵:

平凡入库就是直接举行完业务逻辑处置处罚并构建好响应后同时将日志举行入数据库,入库乐成后再将响应返回;
流程图如下:

但是这样存在一个很大的弊端就是由于多了一次数据库操纵(日志入库),进而大概会导致响应速度比力慢;
下面就聊聊怎么通过线程池对日志入库举行优化,提升接口的响应速度;
线程池方式入库操纵:

线程池方式入库,可以将日志直接放入到队列中,然后就直接返反响应,最后使用线程池中的线程取出队列中的日志数据异步做入库操纵;
流程图如下:

使用线程池方式,处置处罚请求的主线程可以将日志放入到队列后,直接将响应返回,然后再使用线程池中的线程取出队列中的日志数据异步的将其举行入库;由于淘汰了一次数据库操纵,会极大的提升接口响应速度。
下面来看看代码实现:

1、上面说到的存放请求报文和响应报文日志的队列: LinkedBlockingDeque
  1. // 基于链表的双向阻塞队列,在队列的两端都可以插入和移除元素,是线程安全的,多线程并发下效率更高BlockingQueue queue = new LinkedBlockingDeque(MAX_QUEUE_SIZE);
复制代码
除了 LinkedBlockingDeque 阻塞队列外,尚有一些其它经常会用到的阻塞队列,如下图:

2、项目中举行日志入库操纵的线程池: 单线程的线程池 + 固定命线程的线程池


  • 单线程的线程池:用来循环的监听队列中的日志数量以及决议什么时候将队列中的日志取出交由固定命线程的线程池做入库操纵;
  • 固定命线程的线程池:主要用来举行日志的入库操纵;
部分代码实现如下:
  1. /** *  初始化 */public void init(){    // 基于链表的双向阻塞队列,在队列的两端都可以插入和移除元素,是线程安全的,多线程并发下效率更高    queue = new LinkedBlockingDeque(MAX_QUEUE_SIZE);    lastExecuteTime = System.currentTimeMillis();    logger.info("LogPoolManager init successfully......");    logManagerThreadPool.execute(new Runnable() {        @Override        public void run() {            while (run.get()){                try {                    // 线程休眠,详细时间根据项目的实际情况设置                    Thread.sleep(SLEEP_TIME);                } catch (InterruptedException e) {                    logger.error("log Manager Thread sleep fail ", e);                }                // 满足存放了10个日志  或  满足时间隔断已经大于设置的最大时间隔断时  执行日志插入                if (logCount.get() >= BATCH_SIZE || (System.currentTimeMillis() - lastExecuteTime) > MAX_EXE_TiME) {                    if (logCount.get() > 0) {                        logger.info("begin drain log queue to database...");                        List list = new ArrayList();                        /**                             *  drainTo (): 一次性从BlockingQueue获取所有可用的数据对象(还可以指定获取数据的个数),                             *  通过该方法,可以提升获取数据效率;不需要多次分批加锁或释放锁。                             *  将取出的数据放入指定的list聚集中                             */                        queue.drainTo(list);                        // 任务队列 中任务数量置为0                        logCount.set(0);                        // 从线程池中取出线程执行日志插入                        logWorkerThreadPool.execute(new InsertThread(testLogService, list));                        logger.info("end drain log queue to database...");                    }                    // 获取当前执行的时间                    lastExecuteTime = System.currentTimeMillis();                }            }            logger.info("LogPoolManager shutdown successfully");        }    });}
复制代码
  本项目中测试服务接口日志异步线程池化入库处置处罚,项目启动后,在欣赏器输入下面URL,并刷新页面即可:
  http://127.0.0.1:8081/v1/api/log/test
定时任务中使用多线程举行日志清理:

  当日志表中的数据量过多,占用了太多的磁盘空间,导致磁盘不停的告警,此时需要对日志表举行瘦身;
  此时可以使用多线程将日志表中部分数据清理掉,释放磁盘空间;
日志表中哪些数据需要清理掉呢?下面这个场景就大概会在需求中出现:
向导说需要生存日志表中最新一年的数据,就是从当前日期向前推365天;比方:本日2020-12-30,向前推365天的日期是2019-12-30,所以2019-12-30之前生成的日志都需要清理掉;
下面来看代码,由于需要尽大概包管步伐的机动性,所以需要将删除的表名,根据删除的字段等举行机动的设置;设置参数如下图: application-cleanLog.properties 设置文件
  1. ## 线程池巨细threads.pool.num=8## 需要举行清理的表log.clean.table=t_test_log## 清理时根据的字段log.clean.filed=createts## 每次清理的数据量巨细log.clean.batchCount=1000## 每次定时清理时 循环清理的次数log.clean.batchNum=6## 需生存据当前多少天的最新数据,别的的数据才可以被清理掉log.clean.dateNum=1
复制代码
注意:


  • 线程池巨细需要团结服务器硬件设置和实际的业务日志量巨细举行公道设置;如果设置的过大,大概会占用过多的内存和频繁的举行上下文切换,从而大概导致效率变低;
  • 本项目中是根据日期字段举行数据清理的,如果表已经根据日期举行了分区,可以直接根据分区举行清理,分区清理速度更加速些,但是按分区举行删除本项目中暂未实现;
  • 每次清理的数据量巨细:指的是举行一次 delete 时需要删除的数据量;发起不要设置的太大,因为删除的数据量太大的话,大概会导致锁表,从而影响表的正常查询、新增等操纵;
  • 定时任务执行时循环清理的次数:指的是定时任务执行时举行反复 delete 操纵;上面有说道 每次delete的数据量别设置太大,那么在总清理的数据量稳定的情况下,就需要将清理的次数设置大些;
    总清理的数据量 = 每次清理的数据量巨细 * 清理的次数
多线程举行日志清理的流程图如下所示:

下面来看看代码实现:

  部分代码实现如下所示:
  1. /** *  多线程清理日志启动 */public void cleanLogStart(){    // 循环举行日志清理的次数    int whileNum = props.getInt("log.clean.batchNum");    LogCleanBean logClean = null;    while (whileNum > 0){        // 查询符合每次删除数据量的时间段        List list = logCleanService.selectTime(logCleanBean);        if (list != null && list.size() > 0){            logClean = new LogCleanBean();            logClean.setTableName(logCleanBean.getTableName());            logClean.setFieldName(logCleanBean.getFieldName());            // 获取可以删除日志的最小生成时间            logClean.setMinTime(list.get(list.size()-1));            // 获取可以删除日志的最大生成时间            logClean.setMaxTime(list.get(0));            logCleanBean.setMinTime(logClean.getMinTime());            // 此次查询已经不满足设置的每次清理的数据量巨细了,说明已经清理干净了            if (list.size() < logCleanBean.getBatchCleanCount()){                whileNum = 0;            }else {                // 清理次数举行递减                --whileNum;            }        }else {            break;        }        // 举行多线程处置处罚        cleanManagerThreadPool.execute(new CleanThread(this.logCleanService, logClean));    }}
复制代码
扩展:

本项目中使用 delete 而且根据时间字段举行的数据删除,如果是 MySql 数据库 的话,则在删除数据后去检察磁盘空间的话,发现可用磁盘空间并没有增多,而且可用磁盘空间尚有大概淘汰了呢,why 为什么?


  • 因为在 InnoDB 存储引擎中,delete 其实并不会真的把数据删除,mysql 实际上只是给删除的数据打了个标志为已删除,因此 delete 删除表中的数据时,表文件在磁盘上所占空间不会变小,存储空间不会被释放,只是把删除的数据行设置为不可见。然未释放磁盘空间,但是下次插入数据的时候,仍然可以重用这部分空间(重用 → 覆盖)。
  • 而且由于在 delete 删除数据时,会记录 binlog日志 等,如果删除的数据中存在 Text和BLOB 等大字段,大概日志文件会变得额外的大,占用部分磁盘空间 ,这就会导致 free 磁盘空间的进一步淘汰;
管理方案:


  • 如果删除的数据中不存在Text和BLOB 等大字段,可以直接不消管,直接覆盖使用,而且期待MySql 的自动清理操纵即可,但是需要一定时间;
  • 可以在delete操纵以后使用 optimize table table_name 会立即释放磁盘空间。但是,由于optimize执行时会将表锁住,所以不要在高峰期使用,也不要经常使用;因为其会阻塞正常的查询、更新等操纵的。
也可以使用 truncate 、drop 举行数据的删除,快速释放磁盘空间;详细团结当前项目的实际情况举行选择 。
  – END –
  本文中先容了在日志处置处罚场景中线程池的使用场景,除此之外,尚有许多场景使用到了线程池,而且在许多框架中也使用到了线程池,各人也可以通过阅读框架源码学习线程池的使用方法。
点赞 + 批评 + 转发 哟

如果本文对您有资助的话,请摆荡下您爱发财的小手点下赞呀,您的支持就是我不停创作的动力,谢谢啦!
您可以微信搜索【木子雷】公众号,大量Java学习干货文章,您可以来瞧一瞧哟!


来源:https://blog.csdn.net/feichitianxia/article/details/111952031
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则


专注素材教程免费分享
全国免费热线电话

18768367769

周一至周日9:00-23:00

反馈建议

27428564@qq.com 在线QQ咨询

扫描二维码关注我们

Powered by Discuz! X3.4© 2001-2013 Comsenz Inc.( 蜀ICP备2021001884号-1 )