[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,.... 而不是只写 数据库, 取决于使用目的而不会影响主要应用.
祝你成功



最新评论