[Java的日志] P3: 异步日志记录使用的ExecutorService数据库
大家好, 在 2 以前在伐木
[Java的日志] P1: 测井软件开发中的重要性
[Java的日志] P2: Log4j的集成到软件
本人提到的日志记录的重要性, 登录时的一些原则, 并就如何整合指示 Log4j的 在Java程序中.
本文中,我将引导您创建一个功能 异步日志 在 数据库.
用于登录到数据库功能 Log4j
完全能够满足这可. 但是,在这一部分,我将指导如何创建一个模块记录到数据库中,这样就可以更好地理解 日志机制异步.
第一, 登录 数据库 应该有 表 保存日志, 并创建 顺序 来获取值日志表的ID
CREATE TABLE LOGS ( LOGS_ID NUMBER NOT NULL PRIMARY KEY, LEVEL_LOG VARCHAR2(10), CREATE_TIME DATE, CONTENT CLOB ); / CREATE SEQUENCE LOGS_SEQ;
在应用程序, 我将创建一个 实体 对应 表 是为了方便 插入 数据表 日志.
package net.tunghuynh.logging.core; public class Logs { private Integer logsId; private String levelLog; private String content; public Integer getLogsId(){ return logsId; } public void setLogsId(Integer logsId){ this.logsId = logsId; } public String getLevelLog() { return levelLog; } public void setLevelLog(String levelLog) { this.levelLog = levelLog; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } }
如果正常记录的目的只许成功,你绝对可以做一个直接的声明 insert into
日志数据到表中以下列方式
public static void main(String[] args) { Logs logs = new Logs(); logs.setLevelLog("INFO"); logs.setContent("Log by insert into database"); new Main().save(logs); } public void save(Logs item) { PreparedStatement stmt = null; Connection con = null; try { con = DatabaseUtils.getConnection(); stmt = con.prepareStatement("INSERT INTO LOGS (LOGS_ID, LEVEL_LOG, CONTENT, CREATE_TIME)" + " VALUES(LOGS_SEQ.NEXTVAL, ?, ?, SYSDATE)"); stmt.setString(1, item.getLevelLog()); stmt.setString(2, item.getContent()); stmt.execute(); } catch (Exception ex) { logger.error(ex.getMessage(), ex); } finally { DatabaseUtils.closeObject(stmt); DatabaseUtils.closeObject(con); } }
是这么认为的, 我在手柄上拆分代码作为本文的范围之外 数据库连接, 关闭的连接,... 在 1 命名其他类 DatabaseUtils
以避免稀释文章. 你可以看到的那部分细节 源全 底!.
但是,像他以前的文章中提到的原则,当日志记录 不影响时间 治疗原发性服务. 因此犯有代码这一原则, 因为它可以工作 连接数据库 和 执行 命令 插入 其他将占据 1 某些兼职.
为了解决这个问题,我们需要处理的数据库,并在执行连接插入 1 线 私人. 最简单的是 new Thread
新执行 Runnable
.
Thread saveLog = new Thread(new Runnable() { @Override public void run() { new Main().save(item); } }); saveLog.start();
但是,这样一来会相当遇到一个问题 危险 即当业务功能被称为连续, 导致需要创造过 多线程 在应用程序 不解放 得到, 这将影响到很多 性能 应用程序并在服务器的影响. 因此,你应该使用窗体 池 管理和 限制 分娩 Thread
虚张声势以上. 在这里,我通常使用 ExecutorService
来承担这项工作, 当或任何需要,只要登录 提交日志在和货架男孩 ExecutorService
手柄日志逐渐, 所以不会影响主要业务的处理时间.
首先,我创建了一个 abstract class Task
管理 对象日志列表 必要 保存 在 数据库, abstract class Task
得到 implements Callable
到 执行 业务.
package net.tunghuynh.logging.core; import java.util.List; import java.util.concurrent.Callable; public abstract class Task<E> implements Callable<Integer> { private List<E> items; public void setItems(List<E> items){ this.items = items; } public List<E> getItems(){ return items; } }
然后,我创建了一个 abstract class ThreadManager
声明和定义的框架用于记录的过程.
package net.tunghuynh.logging.core; import org.apache.logging.log4j.Logger; import java.util.ArrayList; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicBoolean; public abstract class ThreadManager { public final int BATCH_SIZE = 10;//Chương trình sẽ thực hiện doProcess khi hàng đợi vượt quá 10 phần tử public final long WAIT_TIME_OUT = 1000; //ms public final long TIME_OUT = 2 * 1000; //ms. Chương trình sẽ thực hiện doProcess với chu kỳ 2 giây private final Logger logger = org.apache.logging.log4j.LogManager.getLogger(ThreadManager.class); private final BlockingQueue sourceQueue = new LinkedBlockingQueue(); protected ArrayList items = new ArrayList(BATCH_SIZE); protected AtomicBoolean shouldWork = new AtomicBoolean(true); protected AtomicBoolean isRunning = new AtomicBoolean(true); private boolean listening = false; private String name = "DB LOGGER"; protected ExecutorService executorService = Executors.newFixedThreadPool(5); private Thread mainThread; public ThreadManager() { logger.debug("Start task manager named: " + name); mainThread = new Thread(new Runnable() { @Override public void run() { logger.info("Queued job manager " + name + " is running and watching for queue... "); isRunning.set(true); int recNum = 0; long lgnStart = System.currentTimeMillis(); while (shouldWork.get()) { try { Object item = sourceQueue.poll(WAIT_TIME_OUT, TimeUnit.MILLISECONDS); if (item != null) { items.add(item); recNum++; } if (recNum >= BATCH_SIZE || timedOut(lgnStart)) { if (items.size() > 0) { logger.info(String.format("Thread %s: %s submits %d item(s)", Thread.currentThread().getName(), name, items.size())); doProcess(items); items = new ArrayList(BATCH_SIZE); lgnStart = System.currentTimeMillis(); recNum = 0; } } } catch (Exception e) { logger.error(e.getMessage()); } isRunning.set(false); } logger.info("Taskmanager " + name + " is stopped!!"); } private boolean timedOut(Long startTime) { return System.currentTimeMillis() - startTime > TIME_OUT; } }); } /** * abstract method xử lý nghiệp vụ * @param items */ public abstract void doProcess(ArrayList items); /** * Bắt đầu lắng nghe dữ liệu cần xử lý từ hàng đợi */ public synchronized void listen() { if (!listening) { mainThread.start(); listening = true; } } public BlockingQueue getSourceQueue() { return sourceQueue; } public void stop() { logger.info(String.format("%s received a termination signal, stopping ... ", name)); this.shouldWork.set(false); int tryTime = 0; while (isRunning.get() && tryTime < 50) { try { Thread.currentThread().sleep(50L); } catch (Exception ex) { } tryTime++; } } /** * Submit một đối tượng cần xử lý vào hàng đợi * @param item */ public void submit(Object item) { sourceQueue.offer(item); } }
本节将相对复杂的你是谁没有多少接触到手柄多线程 在Java核心. 我会形容过去如下:
- BlockingQueue sourceQueue
: 排队对象日志 必要插入, 外部将一个元素添加到队列经由方法submit()
- ExecutorService executorService
: 服务实现, 已固定配置只允许创建5 线同时, 尽管被称为几次,只有最大 5 创建线程, 避免产生太大的影响线程高性能服务器
- ArrayList items
: 列表,以保存对象被从队列中删除sourceQueue 指挥doProcess
到插入 在数据库
- 流处理器: 在这里你有1线 创建和开始 1 在所述方法中的唯一时间listen()
, 这个线程的应用程序的运行过程中存在的. 内侧把手的连续螺纹读数据 从队列sourceQueue 要添加到列表项目. 这里最重要的是满足 1 在 2 条件: 名单项目 超过BATCH_SIZE
或元素存储在项目列表太TIME_OUT
将推动项目 去插入. 条件忌 插入太多记录 1 时间, 和忌在全存储数据太长 没有插入. 发送后doProcess
他们必须明确名单项目 和重启 可变计时器暂停. 你可以看到下面的图片直观更容易.
创建后, 2 抽象 一般以上, 我将创建类处理 记录.
第一个是 class LogThread
得到 extends Task
, 任务 插入 1 名单 对象 日志 在 数据库. 这部分是相似于上述的例子,但另一种是插入 1 只有列表.
package net.tunghuynh.logging.core; import net.tunghuynh.preparestatement.DatabaseUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.sql.Connection; import java.sql.PreparedStatement; import java.util.List; public class LogThread extends Task { private final Logger logger = LogManager.getLogger(LogThread.class); @Override public Integer call() throws Exception { List lstLog = getItems(); try { if (lstLog != null && !lstLog.isEmpty()) { save(lstLog); } } catch (Exception e) { logger.error(e.toString(), e); return 0; } return 1; } public void save(List<Logs> lst) { PreparedStatement stmt = null; Connection con = null; try { con = DatabaseUtils.getConnection(); stmt = con.prepareStatement("INSERT INTO AD_SCHEDULE_LOG " + " (LOGS_ID, LEVEL_LOG, CONTENT, CREATE_TIME)" + " VALUES(LOGS_SEQ.NEXTVAL, ?, ?, SYSDATE)"); for(Logs item : lst){ stmt.setString(1, item.getLevelLog()); stmt.setString(2, item.getContent()); stmt.execute(); } } catch (Exception ex) { logger.error(ex.getMessage(), ex); } finally { DatabaseUtils.closeObject(stmt); DatabaseUtils.closeObject(con); } } }
接下来,我将创建 class LogManager
得到 extends ThreadManager
, 任务是让对象列表,以保持原木 队列 在 ThreadManager
到 提交 在 ExecutorService
.
这段话可以有 1 一些人怀疑你为什么不使用 插入西奥Lô. 因为在 ThreadManager
从队列中读取出来, 和最多只 10 元素 被推入名单 doProcess
, 手段本质上是 批量 并且不需要治疗在这里了.
package net.tunghuynh.logging.core; import java.util.ArrayList; public class LogManager extends ThreadManager { @Override public void doProcess(ArrayList items) { LogThread logThread = new LogThread(); logThread.setItems(items); executorService.submit(logThread); } }
来到这里让你想像, 当发生的其他类型的日志保持对 其他表, 或 发送QUA FTP,...或必要的处理任何其他任务 异步 它可以被重用 abstract class ThreadManager
骨架, 刚刚重新定义其他同级 LogThread
处理主要的商业和 LogManager
从队列中读取的数据. 所以,你有一个基本的流程框架.
最后,使用异步日志成以上创建的数据库,我写 1 段 测试在 Main.java
package net.tunghuynh.logging; import net.tunghuynh.logging.core.Logs; import net.tunghuynh.preparestatement.DatabaseUtils; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.sql.Connection; import java.sql.PreparedStatement; import java.util.ArrayList; import java.util.Date; import java.util.List; public class Main { static Logger logger = LogManager.getLogger(Main.class); public static void main(String[] args) { //Tạo đối tượng test để ghi log Logs logs = new Logs(); logs.setLevelLog("INFO"); logs.setContent("Content"); long start = new Date().getTime(); //Khởi tạo LogManager để ghi log bất đồng bộ net.tunghuynh.logging.core.LogManager logManager = new net.tunghuynh.logging.core.LogManager(); logManager.listen(); //Submit vào hàng đợi logManager.submit(logs); logger.info("Async: " + (new Date().getTime()-start) + "ms"); start = new Date().getTime(); //Ghi log không dùng thread new Main().save(logs); logger.info("No-async: " + (new Date().getTime()-start) + "ms"); } public void save(Logs item) { PreparedStatement stmt = null; Connection con = null; try { con = DatabaseUtils.getConnection(); stmt = con.prepareStatement("INSERT INTO LOGS (LOGS_ID, LEVEL_LOG, CONTENT, CREATE_TIME)" + " VALUES(LOGS_SEQ.NEXTVAL, ?, ?, SYSDATE)"); stmt.setString(1, item.getLevelLog()); stmt.setString(2, item.getContent()); stmt.execute(); } catch (Exception ex) { logger.error(ex.getMessage(), ex); } finally { DatabaseUtils.closeObject(stmt); DatabaseUtils.closeObject(con); } } }
在这个测试期间单独联合编写 不使用线程 初始和写作 异步 新建筑测量处理时间
尝试运行和查看结果
2019-07-12 16:34:58 PM INFO [Thread-1] net.tunghuynh.logging.core.ThreadManager.run : Queued job manager ACTION/API LOGGER is running and watching for queue... 2019-07-12 16:34:58 PM INFO [main] net.tunghuynh.logging.Main.main : Async: 4ms 2019-07-12 16:35:00 PM INFO [main] net.tunghuynh.logging.Main.main : No-async: 1898ms 2019-07-12 16:35:00 PM INFO [Thread-1] net.tunghuynh.logging.core.ThreadManager.run : Thread Thread-1: ACTION/API LOGGER submits 1 item(s) 2019-07-12 16:35:29 PM INFO [Thread-1] net.tunghuynh.logging.core.ThreadManager.run : Queued job manager ACTION/API LOGGER is running and watching for queue... 2019-07-12 16:35:29 PM INFO [main] net.tunghuynh.logging.Main.main : Async: 7ms 2019-07-12 16:35:31 PM INFO [main] net.tunghuynh.logging.Main.main : No-async: 1519ms 2019-07-12 16:35:31 PM INFO [Thread-1] net.tunghuynh.logging.core.ThreadManager.run : Thread Thread-1: ACTION/API LOGGER submits 1 item(s)
你看,我们看到的结果 区别 巨大. 随着日志 异步 只有处理时间 下面 10 毫秒, 因为事情已经运行,就可以为其他人处理它. 还 插入常见 他们必须采取 1.5 到 2 秒 (长于 500 时间), 非常显著使用.
在对自己的测试只是放置到主要功能测试,方便, 你可以 在里面 该 ThreadManager
在 1 单位 static
使用始终函数 提交 而无需重置多次, 或者如果你使用 弹簧 应该创建 1 豆角,扁豆 给 LogManager
同 构造函数 是 listen()
和 破坏 是 stop()
使用
@Bean(name = "logManager", initMethod = "listen", destroyMethod = "stop") public LogManager getLogManager() { return new LogManager(); }
需要的时候 Bean logManager 这就是 Autowired
像其他任何人一样 豆角,扁豆 另一个可用.
@Autowired LogManager logManager;
在本演示中,您已经创建了一个模块 异步日志 在 数据库 足够使用和安全. 您可以通过调用以下命令来完全添加日志记录功能 该API保存日志 其他, 发送日志 唱 服务器 不同 的FTP,.... 而不是只写 数据库, 取决于使用目的而不会影响主要应用.
祝你成功
最新评论